From e0ef887c8553cee8c98f561e03e2b01983614a2a Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 31 Oct 2025 18:05:08 +0100 Subject: [PATCH 1/4] Added all changes from nicegui-testing --- src/struphy/bsplines/bsplines.py | 62 +- src/struphy/bsplines/bsplines_kernels.py | 16 +- src/struphy/bsplines/evaluation_kernels_1d.py | 7 +- src/struphy/bsplines/evaluation_kernels_3d.py | 289 +- .../bsplines/tests/test_bsplines_kernels.py | 54 +- .../bsplines/tests/test_eval_spline_mpi.py | 179 +- src/struphy/console/compile.py | 20 +- src/struphy/console/format.py | 1253 +++-- src/struphy/console/main.py | 6 +- src/struphy/console/params.py | 6 +- src/struphy/console/profile.py | 14 +- src/struphy/console/run.py | 2 +- src/struphy/console/test.py | 111 +- src/struphy/console/tests/test_console.py | 29 +- src/struphy/diagnostics/console_diagn.py | 6 +- src/struphy/diagnostics/continuous_spectra.py | 41 +- src/struphy/diagnostics/diagn_tools.py | 138 +- src/struphy/diagnostics/diagnostics_pic.ipynb | 86 +- .../diagnostics/paraview/mesh_creator.py | 74 +- src/struphy/dispersion_relations/analytic.py | 100 +- src/struphy/dispersion_relations/base.py | 10 +- src/struphy/dispersion_relations/utilities.py | 4 +- src/struphy/eigenvalue_solvers/derivatives.py | 8 +- .../kernels_projectors_global.py | 62 +- .../legacy/MHD_eigenvalues_cylinder_1D.py | 417 +- .../control_variates/control_variate.py | 42 +- .../fB_massless_control_variate.py | 4 +- .../fB_massless_kernels_control_variate.py | 42 +- .../fnB_massless_control_variate.py | 6 +- .../fnB_massless_kernels_control_variate.py | 56 +- .../massless_control_variate.py | 6 +- .../massless_kernels_control_variate.py | 129 +- .../legacy/emw_operators.py | 14 +- .../legacy/inner_products_1d.py | 10 +- .../legacy/inner_products_2d.py | 38 +- .../legacy/inner_products_3d.py | 38 +- .../eigenvalue_solvers/legacy/l2_error_1d.py | 10 +- .../eigenvalue_solvers/legacy/l2_error_2d.py | 50 +- .../eigenvalue_solvers/legacy/l2_error_3d.py | 50 +- .../legacy/mass_matrices_3d_pre.py | 92 +- .../legacy/massless_operators/fB_arrays.py | 212 +- .../legacy/massless_operators/fB_bv_kernel.py | 24 +- .../fB_massless_linear_operators.py | 119 +- .../legacy/massless_operators/fB_vv_kernel.py | 8 +- .../legacy/mhd_operators_MF.py | 226 +- .../pro_local/mhd_operators_3d_local.py | 618 ++- .../pro_local/projectors_local.py | 686 ++- .../shape_L2_projector_kernel.py | 54 +- .../shape_function_projectors_L2.py | 167 +- .../shape_function_projectors_local.py | 273 +- .../shape_local_projector_kernel.py | 141 +- .../eigenvalue_solvers/mass_matrices_1d.py | 26 +- .../eigenvalue_solvers/mass_matrices_2d.py | 92 +- .../eigenvalue_solvers/mass_matrices_3d.py | 101 +- .../mhd_axisymmetric_main.py | 34 +- .../mhd_axisymmetric_pproc.py | 15 +- .../eigenvalue_solvers/mhd_operators.py | 85 +- .../eigenvalue_solvers/mhd_operators_core.py | 658 ++- .../eigenvalue_solvers/projectors_global.py | 369 +- .../eigenvalue_solvers/spline_space.py | 451 +- src/struphy/examples/_draw_parallel.py | 14 +- .../examples/restelli2018/callables.py | 66 +- src/struphy/feec/basis_projection_ops.py | 67 +- src/struphy/feec/linear_operators.py | 112 +- src/struphy/feec/local_projectors_kernels.py | 66 +- src/struphy/feec/mass.py | 243 +- src/struphy/feec/mass_kernels.py | 19 +- src/struphy/feec/preconditioner.py | 52 +- src/struphy/feec/projectors.py | 175 +- src/struphy/feec/psydac_derham.py | 355 +- src/struphy/feec/tests/test_basis_ops.py | 78 +- src/struphy/feec/tests/test_derham.py | 54 +- src/struphy/feec/tests/test_eval_field.py | 213 +- src/struphy/feec/tests/test_field_init.py | 253 +- src/struphy/feec/tests/test_l2_projectors.py | 66 +- .../feec/tests/test_local_projectors.py | 421 +- .../feec/tests/test_lowdim_nel_is_1.py | 74 +- src/struphy/feec/tests/test_mass_matrices.py | 166 +- .../feec/tests/test_toarray_struphy.py | 36 +- .../feec/tests/test_tosparse_struphy.py | 74 +- src/struphy/feec/tests/xx_test_preconds.py | 16 +- src/struphy/feec/utilities.py | 40 +- .../feec/utilities_local_projectors.py | 127 +- src/struphy/feec/variational_utilities.py | 143 +- src/struphy/fields_background/base.py | 129 +- src/struphy/fields_background/equils.py | 260 +- .../mhd_equil/eqdsk/readeqdsk.py | 6 +- .../tests/test_desc_equil.py | 80 +- .../tests/test_generic_equils.py | 28 +- .../tests/test_mhd_equils.py | 83 +- .../tests/test_numerical_mhd_equil.py | 72 +- src/struphy/geometry/base.py | 628 ++- src/struphy/geometry/domains.py | 41 +- src/struphy/geometry/evaluation_kernels.py | 24 +- src/struphy/geometry/mappings_kernels.py | 192 +- src/struphy/geometry/tests/test_domain.py | 129 +- src/struphy/geometry/transform_kernels.py | 24 +- src/struphy/geometry/utilities.py | 43 +- src/struphy/geometry/utilities_kernels.py | 8 +- src/struphy/initial/eigenfunctions.py | 49 +- src/struphy/initial/perturbations.py | 192 +- .../initial/tests/test_init_perturbations.py | 43 +- src/struphy/initial/utilities.py | 4 +- src/struphy/io/inp/parameters.yml | 2 +- src/struphy/io/inp/params_Maxwell.py | 8 +- src/struphy/io/options.py | 19 +- src/struphy/io/output_handling.py | 16 +- src/struphy/io/setup.py | 55 +- src/struphy/kinetic_background/base.py | 72 +- src/struphy/kinetic_background/maxwellians.py | 101 +- .../kinetic_background/tests/test_base.py | 38 +- .../tests/test_maxwellians.py | 538 +-- src/struphy/linear_algebra/linalg_kron.py | 23 +- src/struphy/linear_algebra/saddle_point.py | 133 +- src/struphy/linear_algebra/solver.py | 25 - .../tests/test_saddle_point_propagator.py | 6 +- .../tests/test_saddlepoint_massmatrices.py | 70 +- .../tests/test_stencil_dot_kernels.py | 44 +- .../tests/test_stencil_transpose_kernels.py | 38 +- src/struphy/main.py | 155 +- src/struphy/models/__init__.py | 146 +- src/struphy/models/base.py | 413 +- src/struphy/models/fluid.py | 2702 +++++------ src/struphy/models/hybrid.py | 1466 +++--- src/struphy/models/kinetic.py | 1015 ++-- src/struphy/models/species.py | 24 +- src/struphy/models/tests/test_models.py | 62 +- .../models/tests/test_verif_LinearMHD.py | 26 +- .../models/tests/test_verif_Maxwell.py | 70 +- src/struphy/models/tests/verification.py | 118 +- src/struphy/models/toy.py | 1053 +++-- src/struphy/models/variables.py | 22 +- src/struphy/ode/solvers.py | 2 +- src/struphy/ode/tests/test_ode_feec.py | 39 +- src/struphy/ode/utils.py | 8 +- src/struphy/pic/accumulation/accum_kernels.py | 79 +- .../pic/accumulation/accum_kernels_gc.py | 848 +--- .../pic/accumulation/filter_kernels.py | 22 +- .../accumulation/particle_to_mat_kernels.py | 42 +- .../pic/accumulation/particles_to_grid.py | 168 +- src/struphy/pic/base.py | 610 ++- src/struphy/pic/particles.py | 41 +- src/struphy/pic/pushing/pusher.py | 51 +- src/struphy/pic/pushing/pusher_kernels.py | 529 ++- src/struphy/pic/pushing/pusher_kernels_gc.py | 493 +- .../pic/pushing/pusher_utilities_kernels.py | 463 ++ src/struphy/pic/sampling_kernels.py | 6 +- src/struphy/pic/sobol_seq.py | 68 +- src/struphy/pic/sph_eval_kernels.py | 14 +- src/struphy/pic/tests/test_accum_vec_H1.py | 86 +- src/struphy/pic/tests/test_accumulation.py | 71 +- src/struphy/pic/tests/test_binning.py | 234 +- src/struphy/pic/tests/test_draw_parallel.py | 16 +- src/struphy/pic/tests/test_mat_vec_filler.py | 120 +- .../test_pic_legacy_files/accumulation.py | 44 +- .../accumulation_kernels_3d.py | 192 +- .../test_pic_legacy_files/mappings_3d.py | 212 +- .../test_pic_legacy_files/mappings_3d_fast.py | 305 +- .../pic/tests/test_pic_legacy_files/pusher.py | 2 +- .../tests/test_pic_legacy_files/pusher_pos.py | 1257 +---- .../test_pic_legacy_files/pusher_vel_2d.py | 200 +- .../test_pic_legacy_files/pusher_vel_3d.py | 294 +- .../spline_evaluation_2d.py | 2 +- .../spline_evaluation_3d.py | 268 +- src/struphy/pic/tests/test_pushers.py | 132 +- src/struphy/pic/tests/test_sorting.py | 46 +- src/struphy/pic/tests/test_sph.py | 368 +- src/struphy/pic/tests/test_tesselation.py | 50 +- src/struphy/pic/utilities.py | 38 +- src/struphy/pic/utilities_kernels.py | 285 +- src/struphy/polar/basic.py | 20 +- src/struphy/polar/extraction_operators.py | 245 +- src/struphy/polar/linear_operators.py | 21 +- .../polar/tests/test_legacy_polar_splines.py | 20 +- src/struphy/polar/tests/test_polar.py | 49 +- .../likwid/plot_likwidproject.py | 12 +- .../likwid/plot_time_traces.py | 68 +- .../likwid/roofline_plotter.py | 12 +- .../post_processing/orbits/orbits_tools.py | 48 +- .../post_processing/post_processing_tools.py | 70 +- src/struphy/post_processing/pproc_struphy.py | 30 +- .../post_processing/profile_struphy.py | 10 +- src/struphy/profiling/profiling.py | 24 +- src/struphy/propagators/__init__.py | 193 +- src/struphy/propagators/base.py | 21 +- .../propagators/propagators_coupling.py | 2081 ++++----- src/struphy/propagators/propagators_fields.py | 4159 ++++++++--------- .../propagators/propagators_markers.py | 590 +-- .../tests/test_gyrokinetic_poisson.py | 188 +- src/struphy/propagators/tests/test_poisson.py | 386 +- .../psydac-2.4.5.dev0-py3-none-any.whl | Bin 0 -> 240986 bytes src/struphy/topology/grids.py | 2 +- src/struphy/utils/clone_config.py | 49 +- src/struphy/utils/set_release_dependencies.py | 76 + src/struphy/utils/test_clone_config.py | 17 +- src/struphy/utils/utils.py | 10 +- 196 files changed, 16863 insertions(+), 20608 deletions(-) create mode 100644 src/struphy/psydac-2.4.5.dev0-py3-none-any.whl create mode 100644 src/struphy/utils/set_release_dependencies.py diff --git a/src/struphy/bsplines/bsplines.py b/src/struphy/bsplines/bsplines.py index 9974a9ff2..31738fe74 100644 --- a/src/struphy/bsplines/bsplines.py +++ b/src/struphy/bsplines/bsplines.py @@ -16,7 +16,7 @@ """ -import cunumpy as xp +import numpy as np __all__ = [ "find_span", @@ -105,7 +105,7 @@ def scaling_vector(knots, degree, span): Scaling vector with elements (p + 1)/(t[i + p + 1] - t[i]) """ - x = xp.zeros(degree + 1, dtype=float) + x = np.zeros(degree + 1, dtype=float) for il in range(degree + 1): i = span - il @@ -148,9 +148,9 @@ def basis_funs(knots, degree, x, span, normalize=False): by using 'left' and 'right' temporary arrays that are one element shorter. """ - left = xp.empty(degree, dtype=float) - right = xp.empty(degree, dtype=float) - values = xp.empty(degree + 1, dtype=float) + left = np.empty(degree, dtype=float) + right = np.empty(degree, dtype=float) + values = np.empty(degree + 1, dtype=float) values[0] = 1.0 @@ -164,7 +164,7 @@ def basis_funs(knots, degree, x, span, normalize=False): saved = left[j - r] * temp values[j + 1] = saved - if normalize: + if normalize == True: values = values * scaling_vector(knots, degree, span) return values @@ -205,7 +205,7 @@ def basis_funs_1st_der(knots, degree, x, span): # Compute derivatives at x using formula based on difference of splines of degree deg - 1 # ------- # j = 0 - ders = xp.empty(degree + 1, dtype=float) + ders = np.empty(degree + 1, dtype=float) saved = degree * values[0] / (knots[span + 1] - knots[span + 1 - degree]) ders[0] = -saved @@ -261,11 +261,11 @@ def basis_funs_all_ders(knots, degree, x, span, n): - innermost loops are replaced with vector operations on slices. """ - left = xp.empty(degree) - right = xp.empty(degree) - ndu = xp.empty((degree + 1, degree + 1)) - a = xp.empty((2, degree + 1)) - ders = xp.zeros((n + 1, degree + 1)) # output array + left = np.empty(degree) + right = np.empty(degree) + ndu = np.empty((degree + 1, degree + 1)) + a = np.empty((2, degree + 1)) + ders = np.zeros((n + 1, degree + 1)) # output array # Number of derivatives that need to be effectively computed # Derivatives higher than degree are = 0. @@ -304,7 +304,7 @@ def basis_funs_all_ders(knots, degree, x, span, n): j1 = 1 if (rk > -1) else -rk j2 = k - 1 if (r - 1 <= pk) else degree - r a[s2, j1 : j2 + 1] = (a[s1, j1 : j2 + 1] - a[s1, j1 - 1 : j2]) * ndu[pk + 1, rk + j1 : rk + j2 + 1] - d += xp.dot(a[s2, j1 : j2 + 1], ndu[rk + j1 : rk + j2 + 1, pk]) + d += np.dot(a[s2, j1 : j2 + 1], ndu[rk + j1 : rk + j2 + 1, pk]) if r <= pk: a[s2, k] = -a[s1, k - 1] * ndu[pk + 1, r] d += a[s2, k] * ndu[r, pk] @@ -362,7 +362,7 @@ def collocation_matrix(knots, degree, xgrid, periodic, normalize=False): nx = len(xgrid) # Collocation matrix as 2D Numpy array (dense storage) - mat = xp.zeros((nx, nb), dtype=float) + mat = np.zeros((nx, nb), dtype=float) # Indexing of basis functions (periodic or not) for a given span if periodic: @@ -418,12 +418,12 @@ def histopolation_matrix(knots, degree, xgrid, periodic): # Number of integrals if periodic: el_b = breakpoints(knots, degree) - xgrid = xp.array([el_b[0]] + list(xgrid) + [el_b[-1]]) + xgrid = np.array([el_b[0]] + list(xgrid) + [el_b[-1]]) ni = len(xgrid) - 1 # Histopolation matrix of M-splines as 2D Numpy array (dense storage) - his = xp.zeros((ni, nbD), dtype=float) + his = np.zeros((ni, nbD), dtype=float) # Collocation matrix of B-splines col = collocation_matrix(knots, degree, xgrid, False, normalize=False) @@ -434,7 +434,7 @@ def histopolation_matrix(knots, degree, xgrid, periodic): for k in range(j + 1): his[i, j % nbD] += col[i, k] - col[i + 1, k] - if xp.abs(his[i, j % nbD]) < 1e-14: + if np.abs(his[i, j % nbD]) < 1e-14: his[i, j % nbD] = 0.0 # add first to last integration interval in case of periodic splines @@ -470,7 +470,7 @@ def breakpoints(knots, degree): else: endsl = -degree - return xp.unique(knots[slice(degree, endsl)]) + return np.unique(knots[slice(degree, endsl)]) # ============================================================================== @@ -501,13 +501,13 @@ def greville(knots, degree, periodic): n = len(T) - 2 * p - 1 if periodic else len(T) - p - 1 # Compute greville abscissas as average of p consecutive knot values - xg = xp.around([sum(T[i : i + p]) / p for i in range(s, s + n)], decimals=15) + xg = np.around([sum(T[i : i + p]) / p for i in range(s, s + n)], decimals=15) # If needed apply periodic boundary conditions if periodic: a = T[p] b = T[-p] - xg = xp.around((xg - a) % (b - a) + a, decimals=15) + xg = np.around((xg - a) % (b - a) + a, decimals=15) return xg @@ -537,7 +537,7 @@ def elements_spans(knots, degree): >>> from psydac.core.bsplines import make_knots, elements_spans >>> p = 3 ; n = 8 - >>> grid = xp.arange( n-p+1 ) + >>> grid = np.arange( n-p+1 ) >>> knots = make_knots( breaks=grid, degree=p, periodic=False ) >>> spans = elements_spans( knots=knots, degree=p ) >>> spans @@ -549,13 +549,13 @@ def elements_spans(knots, degree): 2) This function could be written in two lines: breaks = breakpoints( knots, degree ) - spans = xp.searchsorted( knots, breaks[:-1], side='right' ) - 1 + spans = np.searchsorted( knots, breaks[:-1], side='right' ) - 1 """ breaks = breakpoints(knots, degree) nk = len(knots) ne = len(breaks) - 1 - spans = xp.zeros(ne, dtype=int) + spans = np.zeros(ne, dtype=int) ie = 0 for ik in range(degree, nk - degree): @@ -600,13 +600,13 @@ def make_knots(breaks, degree, periodic): # Consistency checks assert len(breaks) > 1 - assert all(xp.diff(breaks) > 0) + assert all(np.diff(breaks) > 0) assert degree > 0 if periodic: assert len(breaks) > degree p = degree - T = xp.zeros(len(breaks) + 2 * p, dtype=float) + T = np.zeros(len(breaks) + 2 * p, dtype=float) T[p:-p] = breaks if periodic: @@ -671,13 +671,13 @@ def quadrature_grid(breaks, quad_rule_x, quad_rule_w): assert min(quad_rule_x) >= -1 assert max(quad_rule_x) <= +1 - quad_rule_x = xp.asarray(quad_rule_x) - quad_rule_w = xp.asarray(quad_rule_w) + quad_rule_x = np.asarray(quad_rule_x) + quad_rule_w = np.asarray(quad_rule_w) ne = len(breaks) - 1 nq = len(quad_rule_x) - quad_x = xp.zeros((ne, nq), dtype=float) - quad_w = xp.zeros((ne, nq), dtype=float) + quad_x = np.zeros((ne, nq), dtype=float) + quad_w = np.zeros((ne, nq), dtype=float) # Compute location and weight of quadrature points from basic rule for ie, (a, b) in enumerate(zip(breaks[:-1], breaks[1:])): @@ -724,7 +724,7 @@ def basis_ders_on_quad_grid(knots, degree, quad_grid, nders, normalize=False): # TODO: check if it is safe to compute span only once for each element ne, nq = quad_grid.shape - basis = xp.zeros((ne, degree + 1, nders + 1, nq), dtype=float) + basis = np.zeros((ne, degree + 1, nders + 1, nq), dtype=float) # Loop over elements for ie in range(ne): @@ -735,7 +735,7 @@ def basis_ders_on_quad_grid(knots, degree, quad_grid, nders, normalize=False): span = find_span(knots, degree, xq) ders = basis_funs_all_ders(knots, degree, xq, span, nders) - if normalize: + if normalize == True: ders = ders * scaling_vector(knots, degree, span) basis[ie, :, :, iq] = ders.transpose() diff --git a/src/struphy/bsplines/bsplines_kernels.py b/src/struphy/bsplines/bsplines_kernels.py index 9fa1a9521..17374f178 100644 --- a/src/struphy/bsplines/bsplines_kernels.py +++ b/src/struphy/bsplines/bsplines_kernels.py @@ -83,13 +83,7 @@ def find_span(t: "Final[float[:]]", p: "int", eta: "float") -> "int": @pure def basis_funs( - t: "Final[float[:]]", - p: "int", - eta: "float", - span: "int", - left: "float[:]", - right: "float[:]", - values: "float[:]", + t: "Final[float[:]]", p: "int", eta: "float", span: "int", left: "float[:]", right: "float[:]", values: "float[:]" ): """ Parameters @@ -601,13 +595,7 @@ def basis_funs_and_der( @pure @stack_array("values_b") def basis_funs_1st_der( - t: "Final[float[:]]", - p: "int", - eta: "float", - span: "int", - left: "float[:]", - right: "float[:]", - values: "float[:]", + t: "Final[float[:]]", p: "int", eta: "float", span: "int", left: "float[:]", right: "float[:]", values: "float[:]" ): """ Parameters diff --git a/src/struphy/bsplines/evaluation_kernels_1d.py b/src/struphy/bsplines/evaluation_kernels_1d.py index 6510eafff..a6ec8b7a5 100644 --- a/src/struphy/bsplines/evaluation_kernels_1d.py +++ b/src/struphy/bsplines/evaluation_kernels_1d.py @@ -61,12 +61,7 @@ def evaluation_kernel_1d(p1: int, basis1: "Final[float[:]]", ind1: "Final[int[:] @pure @stack_array("tmp1", "tmp2") def evaluate( - kind1: int, - t1: "Final[float[:]]", - p1: int, - ind1: "Final[int[:,:]]", - coeff: "Final[float[:]]", - eta1: float, + kind1: int, t1: "Final[float[:]]", p1: int, ind1: "Final[int[:,:]]", coeff: "Final[float[:]]", eta1: float ) -> float: """ Point-wise evaluation of a spline. diff --git a/src/struphy/bsplines/evaluation_kernels_3d.py b/src/struphy/bsplines/evaluation_kernels_3d.py index a6c900616..8ccaa252b 100644 --- a/src/struphy/bsplines/evaluation_kernels_3d.py +++ b/src/struphy/bsplines/evaluation_kernels_3d.py @@ -246,212 +246,47 @@ def evaluate_tensor_product( for i3 in range(len(eta3)): if kind == 0: spline_values[i1, i2, i3] = evaluate_3d( - 1, - 1, - 1, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 1, 1, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 11: spline_values[i1, i2, i3] = evaluate_3d( - 2, - 1, - 1, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 2, 1, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 12: spline_values[i1, i2, i3] = evaluate_3d( - 1, - 2, - 1, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 1, 2, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 13: spline_values[i1, i2, i3] = evaluate_3d( - 1, - 1, - 2, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 1, 1, 2, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 21: spline_values[i1, i2, i3] = evaluate_3d( - 1, - 2, - 2, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 1, 2, 2, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 22: spline_values[i1, i2, i3] = evaluate_3d( - 2, - 1, - 2, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 2, 1, 2, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 23: spline_values[i1, i2, i3] = evaluate_3d( - 2, - 2, - 1, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 2, 2, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 3: spline_values[i1, i2, i3] = evaluate_3d( - 2, - 2, - 2, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 2, 2, 2, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 41: spline_values[i1, i2, i3] = evaluate_3d( - 3, - 1, - 1, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 3, 1, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 42: spline_values[i1, i2, i3] = evaluate_3d( - 1, - 3, - 1, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 1, 3, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 43: spline_values[i1, i2, i3] = evaluate_3d( - 1, - 1, - 3, - t1, - t2, - t3, - p1, - p2, - p3, - ind1, - ind2, - ind3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + 1, 1, 3, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] ) @@ -1216,17 +1051,7 @@ def eval_spline_mpi( b3 = bd3 value = eval_spline_mpi_kernel( - pn[0] - kind[0], - pn[1] - kind[1], - pn[2] - kind[2], - b1, - b2, - b3, - span1, - span2, - span3, - _data, - starts, + pn[0] - kind[0], pn[1] - kind[1], pn[2] - kind[2], b1, b2, b3, span1, span2, span3, _data, starts ) return value @@ -1371,17 +1196,7 @@ def eval_spline_mpi_tensor_product_fast( b3 = bd3 values[i, j, k] = eval_spline_mpi_kernel( - pn[0] - kind[0], - pn[1] - kind[1], - pn[2] - kind[2], - b1, - b2, - b3, - span1, - span2, - span3, - _data, - starts, + pn[0] - kind[0], pn[1] - kind[1], pn[2] - kind[2], b1, b2, b3, span1, span2, span3, _data, starts ) @@ -1447,17 +1262,7 @@ def eval_spline_mpi_tensor_product_fixed( b3[:] = b3s[k, :] values[i, j, k] = eval_spline_mpi_kernel( - pn[0] - kind[0], - pn[1] - kind[1], - pn[2] - kind[2], - b1, - b2, - b3, - span1, - span2, - span3, - _data, - starts, + pn[0] - kind[0], pn[1] - kind[1], pn[2] - kind[2], b1, b2, b3, span1, span2, span3, _data, starts ) @@ -1515,16 +1320,7 @@ def eval_spline_mpi_matrix( continue # point not in process domain values[i, j, k] = eval_spline_mpi( - eta1[i, j, k], - eta2[i, j, k], - eta3[i, j, k], - _data, - kind, - pn, - tn1, - tn2, - tn3, - starts, + eta1[i, j, k], eta2[i, j, k], eta3[i, j, k], _data, kind, pn, tn1, tn2, tn3, starts ) @@ -1586,16 +1382,7 @@ def eval_spline_mpi_sparse_meshgrid( continue # point not in process domain values[i, j, k] = eval_spline_mpi( - eta1[i, 0, 0], - eta2[0, j, 0], - eta3[0, 0, k], - _data, - kind, - pn, - tn1, - tn2, - tn3, - starts, + eta1[i, 0, 0], eta2[0, j, 0], eta3[0, 0, k], _data, kind, pn, tn1, tn2, tn3, starts ) @@ -1645,16 +1432,7 @@ def eval_spline_mpi_markers( continue # point not in process domain values[ip] = eval_spline_mpi( - markers[ip, 0], - markers[ip, 1], - markers[ip, 2], - _data, - kind, - pn, - tn1, - tn2, - tn3, - starts, + markers[ip, 0], markers[ip, 1], markers[ip, 2], _data, kind, pn, tn1, tn2, tn3, starts ) @@ -1675,39 +1453,20 @@ def get_spans(eta1: float, eta2: float, eta3: float, args_derham: "DerhamArgumen # get spline values at eta bsplines_kernels.b_d_splines_slim( - args_derham.tn1, - args_derham.pn[0], - eta1, - int(span1), - args_derham.bn1, - args_derham.bd1, + args_derham.tn1, args_derham.pn[0], eta1, int(span1), args_derham.bn1, args_derham.bd1 ) bsplines_kernels.b_d_splines_slim( - args_derham.tn2, - args_derham.pn[1], - eta2, - int(span2), - args_derham.bn2, - args_derham.bd2, + args_derham.tn2, args_derham.pn[1], eta2, int(span2), args_derham.bn2, args_derham.bd2 ) bsplines_kernels.b_d_splines_slim( - args_derham.tn3, - args_derham.pn[2], - eta3, - int(span3), - args_derham.bn3, - args_derham.bd3, + args_derham.tn3, args_derham.pn[2], eta3, int(span3), args_derham.bn3, args_derham.bd3 ) return span1, span2, span3 def eval_0form_spline_mpi( - span1: int, - span2: int, - span3: int, - args_derham: "DerhamArguments", - form_coeffs: "float[:,:,:]", + span1: int, span2: int, span3: int, args_derham: "DerhamArguments", form_coeffs: "float[:,:,:]" ) -> float: """Single-point evaluation of Derham 0-form spline defined by form_coeffs, given N-spline values (in bn) and knot span indices span.""" @@ -1843,11 +1602,7 @@ def eval_2form_spline_mpi( def eval_3form_spline_mpi( - span1: int, - span2: int, - span3: int, - args_derham: "DerhamArguments", - form_coeffs: "float[:,:,:]", + span1: int, span2: int, span3: int, args_derham: "DerhamArguments", form_coeffs: "float[:,:,:]" ) -> float: """Single-point evaluation of Derham 0-form spline defined by form_coeffs, given D-spline values (in bd) and knot span indices span.""" diff --git a/src/struphy/bsplines/tests/test_bsplines_kernels.py b/src/struphy/bsplines/tests/test_bsplines_kernels.py index c1010dd08..96e3a0a21 100644 --- a/src/struphy/bsplines/tests/test_bsplines_kernels.py +++ b/src/struphy/bsplines/tests/test_bsplines_kernels.py @@ -1,10 +1,11 @@ import time -import cunumpy as xp +import numpy as np import pytest -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 1], [2, 1, 2], [3, 4, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -21,6 +22,7 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -33,9 +35,9 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): # Random points in domain of process n_pts = 100 dom = derham.domain_array[rank] - eta1s = xp.random.rand(n_pts) * (dom[1] - dom[0]) + dom[0] - eta2s = xp.random.rand(n_pts) * (dom[4] - dom[3]) + dom[3] - eta3s = xp.random.rand(n_pts) * (dom[7] - dom[6]) + dom[6] + eta1s = np.random.rand(n_pts) * (dom[1] - dom[0]) + dom[0] + eta2s = np.random.rand(n_pts) * (dom[4] - dom[3]) + dom[3] + eta3s = np.random.rand(n_pts) * (dom[7] - dom[6]) + dom[6] # struphy find_span t0 = time.time() @@ -59,18 +61,18 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): if rank == 0: print(f"psydac find_span_p : {t1 - t0}") - assert xp.allclose(span1s, span1s_psy) - assert xp.allclose(span2s, span2s_psy) - assert xp.allclose(span3s, span3s_psy) + assert np.allclose(span1s, span1s_psy) + assert np.allclose(span2s, span2s_psy) + assert np.allclose(span3s, span3s_psy) # allocate tmps - bn1 = xp.empty(derham.p[0] + 1, dtype=float) - bn2 = xp.empty(derham.p[1] + 1, dtype=float) - bn3 = xp.empty(derham.p[2] + 1, dtype=float) + bn1 = np.empty(derham.p[0] + 1, dtype=float) + bn2 = np.empty(derham.p[1] + 1, dtype=float) + bn3 = np.empty(derham.p[2] + 1, dtype=float) - bd1 = xp.empty(derham.p[0], dtype=float) - bd2 = xp.empty(derham.p[1], dtype=float) - bd3 = xp.empty(derham.p[2], dtype=float) + bd1 = np.empty(derham.p[0], dtype=float) + bd2 = np.empty(derham.p[1], dtype=float) + bd3 = np.empty(derham.p[2], dtype=float) # struphy b_splines_slim val1s, val2s, val3s = [], [], [] @@ -102,13 +104,13 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): # compare for val1, val1_psy in zip(val1s, val1s_psy): - assert xp.allclose(val1, val1_psy) + assert np.allclose(val1, val1_psy) for val2, val2_psy in zip(val2s, val2s_psy): - assert xp.allclose(val2, val2_psy) + assert np.allclose(val2, val2_psy) for val3, val3_psy in zip(val3s, val3s_psy): - assert xp.allclose(val3, val3_psy) + assert np.allclose(val3, val3_psy) # struphy b_d_splines_slim val1s_n, val2s_n, val3s_n = [], [], [] @@ -130,13 +132,13 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): # compare for val1, val1_psy in zip(val1s_n, val1s_psy): - assert xp.allclose(val1, val1_psy) + assert np.allclose(val1, val1_psy) for val2, val2_psy in zip(val2s_n, val2s_psy): - assert xp.allclose(val2, val2_psy) + assert np.allclose(val2, val2_psy) for val3, val3_psy in zip(val3s_n, val3s_psy): - assert xp.allclose(val3, val3_psy) + assert np.allclose(val3, val3_psy) # struphy d_splines_slim span1s, span2s, span3s = [], [], [] @@ -174,22 +176,22 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): # compare for val1, val1_psy in zip(val1s, val1s_psy): - assert xp.allclose(val1, val1_psy) + assert np.allclose(val1, val1_psy) for val2, val2_psy in zip(val2s, val2s_psy): - assert xp.allclose(val2, val2_psy) + assert np.allclose(val2, val2_psy) for val3, val3_psy in zip(val3s, val3s_psy): - assert xp.allclose(val3, val3_psy) + assert np.allclose(val3, val3_psy) for val1, val1_psy in zip(val1s_d, val1s_psy): - assert xp.allclose(val1, val1_psy) + assert np.allclose(val1, val1_psy) for val2, val2_psy in zip(val2s_d, val2s_psy): - assert xp.allclose(val2, val2_psy) + assert np.allclose(val2, val2_psy) for val3, val3_psy in zip(val3s_d, val3s_psy): - assert xp.allclose(val3, val3_psy) + assert np.allclose(val3, val3_psy) if __name__ == "__main__": diff --git a/src/struphy/bsplines/tests/test_eval_spline_mpi.py b/src/struphy/bsplines/tests/test_eval_spline_mpi.py index 923fc8ea6..2c00eb459 100644 --- a/src/struphy/bsplines/tests/test_eval_spline_mpi.py +++ b/src/struphy/bsplines/tests/test_eval_spline_mpi.py @@ -1,11 +1,12 @@ from sys import int_info from time import sleep -import cunumpy as xp +import numpy as np import pytest -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3], [3, 1, 2]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -19,6 +20,7 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -37,9 +39,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): # Random points in domain of process dom = derham.domain_array[rank] - eta1s = xp.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] - eta2s = xp.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] - eta3s = xp.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] + eta1s = np.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] + eta2s = np.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] + eta3s = np.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] for eta1, eta2, eta3 in zip(eta1s, eta2s, eta3s): comm.Barrier() @@ -55,13 +57,13 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span3 = bsp.find_span(tn3, derham.p[2], eta3) # non-zero spline values at eta - bn1 = xp.empty(derham.p[0] + 1, dtype=float) - bn2 = xp.empty(derham.p[1] + 1, dtype=float) - bn3 = xp.empty(derham.p[2] + 1, dtype=float) + bn1 = np.empty(derham.p[0] + 1, dtype=float) + bn2 = np.empty(derham.p[1] + 1, dtype=float) + bn3 = np.empty(derham.p[2] + 1, dtype=float) - bd1 = xp.empty(derham.p[0], dtype=float) - bd2 = xp.empty(derham.p[1], dtype=float) - bd3 = xp.empty(derham.p[2], dtype=float) + bd1 = np.empty(derham.p[0], dtype=float) + bd2 = np.empty(derham.p[1], dtype=float) + bd3 = np.empty(derham.p[2], dtype=float) bsp.b_d_splines_slim(tn1, derham.p[0], eta1, span1, bn1, bd1) bsp.b_d_splines_slim(tn2, derham.p[1], eta2, span2, bn2, bd2) @@ -82,8 +84,8 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): # compare spline evaluation routines in V0 val = eval3d(*derham.p, bn1, bn2, bn3, ind_n1, ind_n2, ind_n3, x0[0]) - val_mpi = eval3d_mpi(*derham.p, bn1, bn2, bn3, span1, span2, span3, x0_psy._data, xp.array(x0_psy.starts)) - assert xp.allclose(val, val_mpi) + val_mpi = eval3d_mpi(*derham.p, bn1, bn2, bn3, span1, span2, span3, x0_psy._data, np.array(x0_psy.starts)) + assert np.allclose(val, val_mpi) # compare spline evaluation routines in V1 val = eval3d(derham.p[0] - 1, derham.p[1], derham.p[2], bd1, bn2, bn3, ind_d1, ind_n2, ind_n3, x1[0]) @@ -98,9 +100,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x1_psy[0]._data, - xp.array(x1_psy[0].starts), + np.array(x1_psy[0].starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) val = eval3d(derham.p[0], derham.p[1] - 1, derham.p[2], bn1, bd2, bn3, ind_n1, ind_d2, ind_n3, x1[1]) val_mpi = eval3d_mpi( @@ -114,9 +116,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x1_psy[1]._data, - xp.array(x1_psy[1].starts), + np.array(x1_psy[1].starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) val = eval3d(derham.p[0], derham.p[1], derham.p[2] - 1, bn1, bn2, bd3, ind_n1, ind_n2, ind_d3, x1[2]) val_mpi = eval3d_mpi( @@ -130,9 +132,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x1_psy[2]._data, - xp.array(x1_psy[2].starts), + np.array(x1_psy[2].starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # compare spline evaluation routines in V2 val = eval3d(derham.p[0], derham.p[1] - 1, derham.p[2] - 1, bn1, bd2, bd3, ind_n1, ind_d2, ind_d3, x2[0]) @@ -147,9 +149,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x2_psy[0]._data, - xp.array(x2_psy[0].starts), + np.array(x2_psy[0].starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) val = eval3d(derham.p[0] - 1, derham.p[1], derham.p[2] - 1, bd1, bn2, bd3, ind_d1, ind_n2, ind_d3, x2[1]) val_mpi = eval3d_mpi( @@ -163,9 +165,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x2_psy[1]._data, - xp.array(x2_psy[1].starts), + np.array(x2_psy[1].starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) val = eval3d(derham.p[0] - 1, derham.p[1] - 1, derham.p[2], bd1, bd2, bn3, ind_d1, ind_d2, ind_n3, x2[2]) val_mpi = eval3d_mpi( @@ -179,9 +181,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x2_psy[2]._data, - xp.array(x2_psy[2].starts), + np.array(x2_psy[2].starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # compare spline evaluation routines in V3 val = eval3d(derham.p[0] - 1, derham.p[1] - 1, derham.p[2] - 1, bd1, bd2, bd3, ind_d1, ind_d2, ind_d3, x3[0]) @@ -196,11 +198,12 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x3_psy._data, - xp.array(x3_psy.starts), + np.array(x3_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3], [3, 1, 2]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -213,6 +216,7 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -229,9 +233,9 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): # Random points in domain of process dom = derham.domain_array[rank] - eta1s = xp.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] - eta2s = xp.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] - eta3s = xp.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] + eta1s = np.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] + eta2s = np.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] + eta3s = np.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] for eta1, eta2, eta3 in zip(eta1s, eta2s, eta3s): comm.Barrier() @@ -250,14 +254,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x0_psy._data, derham.spline_types_pyccel["0"], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # compare spline evaluation routines in V1 # 1st component @@ -286,14 +290,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x1_psy[0]._data, derham.spline_types_pyccel["1"][0], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # 2nd component val = evaluate_3d( @@ -321,14 +325,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x1_psy[1]._data, derham.spline_types_pyccel["1"][1], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # 3rd component val = evaluate_3d( @@ -356,14 +360,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x1_psy[2]._data, derham.spline_types_pyccel["1"][2], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # compare spline evaluation routines in V2 # 1st component @@ -392,14 +396,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x2_psy[0]._data, derham.spline_types_pyccel["2"][0], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # 2nd component val = evaluate_3d( @@ -427,14 +431,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x2_psy[1]._data, derham.spline_types_pyccel["2"][1], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # 3rd component val = evaluate_3d( @@ -462,14 +466,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x2_psy[2]._data, derham.spline_types_pyccel["2"][2], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) # compare spline evaluation routines in V3 val = evaluate_3d( @@ -495,16 +499,17 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x3_psy._data, derham.spline_types_pyccel["3"], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), ) - assert xp.allclose(val, val_mpi) + assert np.allclose(val, val_mpi) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3], [3, 1, 2]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -529,6 +534,7 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -543,13 +549,13 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): # Random points in domain of process dom = derham.domain_array[rank] - eta1s = xp.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] - eta2s = xp.random.rand(n_markers + 1) * (dom[4] - dom[3]) + dom[3] - eta3s = xp.random.rand(n_markers + 2) * (dom[7] - dom[6]) + dom[6] + eta1s = np.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] + eta2s = np.random.rand(n_markers + 1) * (dom[4] - dom[3]) + dom[3] + eta3s = np.random.rand(n_markers + 2) * (dom[7] - dom[6]) + dom[6] - vals = xp.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) - vals_mpi = xp.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) - vals_mpi_fast = xp.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) + vals = np.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) + vals_mpi = np.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) + vals_mpi_fast = np.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) comm.Barrier() sleep(0.02 * (rank + 1)) @@ -572,11 +578,11 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): eta3s, x0_psy._data, derham.spline_types_pyccel["0"], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), vals_mpi, ) t1 = time.time() @@ -590,19 +596,19 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): eta3s, x0_psy._data, derham.spline_types_pyccel["0"], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), vals_mpi_fast, ) t1 = time.time() if rank == 0: print("v0 eval_spline_mpi_tensor_product_fast:".ljust(40), t1 - t0) - assert xp.allclose(vals, vals_mpi) - assert xp.allclose(vals, vals_mpi_fast) + assert np.allclose(vals, vals_mpi) + assert np.allclose(vals, vals_mpi_fast) # compare spline evaluation routines in V3 t0 = time.time() @@ -632,11 +638,11 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): eta3s, x3_psy._data, derham.spline_types_pyccel["3"], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), vals_mpi, ) t1 = time.time() @@ -650,21 +656,22 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): eta3s, x3_psy._data, derham.spline_types_pyccel["3"], - xp.array(derham.p), + np.array(derham.p), tn1, tn2, tn3, - xp.array(x0_psy.starts), + np.array(x0_psy.starts), vals_mpi_fast, ) t1 = time.time() if rank == 0: print("v3 eval_spline_mpi_tensor_product_fast:".ljust(40), t1 - t0) - assert xp.allclose(vals, vals_mpi) - assert xp.allclose(vals, vals_mpi_fast) + assert np.allclose(vals, vals_mpi) + assert np.allclose(vals, vals_mpi_fast) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 1], [2, 1, 2], [3, 4, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -685,6 +692,7 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -700,10 +708,7 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): # Histopolation grids spaces = derham.Vh_fem["3"].spaces ptsG, wtsG, spans, bases, subs = prepare_projection_of_basis( - spaces, - spaces, - derham.Vh["3"].starts, - derham.Vh["3"].ends, + spaces, spaces, derham.Vh["3"].starts, derham.Vh["3"].ends ) eta1s = ptsG[0].flatten() eta2s = ptsG[1].flatten() @@ -712,15 +717,15 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): spans_f, bns_f, bds_f = derham.prepare_eval_tp_fixed([eta1s, eta2s, eta3s]) # output arrays - vals = xp.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) - vals_mpi_fixed = xp.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) - vals_mpi_grid = xp.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) + vals = np.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) + vals_mpi_fixed = np.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) + vals_mpi_grid = np.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) comm.Barrier() sleep(0.02 * (rank + 1)) - print(f"rank {rank} | {eta1s =}") - print(f"rank {rank} | {eta2s =}") - print(f"rank {rank} | {eta3s =}\n") + print(f"rank {rank} | {eta1s = }") + print(f"rank {rank} | {eta2s = }") + print(f"rank {rank} | {eta3s = }\n") comm.Barrier() # compare spline evaluation routines @@ -750,20 +755,20 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): *bds_f, x3_psy._data, derham.spline_types_pyccel["3"], - xp.array(derham.p), - xp.array(x0_psy.starts), + np.array(derham.p), + np.array(x0_psy.starts), vals_mpi_fixed, ) t1 = time.time() if rank == 0: print("v3 eval_spline_mpi_tensor_product_fixed:".ljust(40), t1 - t0) - assert xp.allclose(vals, vals_mpi_fixed) + assert np.allclose(vals, vals_mpi_fixed) field = derham.create_spline_function("test", "L2") field.vector = x3_psy - assert xp.allclose(field.vector._data, x3_psy._data) + assert np.allclose(field.vector._data, x3_psy._data) t0 = time.time() field.eval_tp_fixed_loc(spans_f, bds_f, out=vals_mpi_fixed) @@ -771,7 +776,7 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): if rank == 0: print("v3 field.eval_tp_fixed:".ljust(40), t1 - t0) - assert xp.allclose(vals, vals_mpi_fixed) + assert np.allclose(vals, vals_mpi_fixed) if __name__ == "__main__": diff --git a/src/struphy/console/compile.py b/src/struphy/console/compile.py index d92f31285..0cb628b18 100644 --- a/src/struphy/console/compile.py +++ b/src/struphy/console/compile.py @@ -4,17 +4,7 @@ def struphy_compile( - language, - compiler, - compiler_config, - omp_pic, - omp_feec, - delete, - status, - verbose, - dependencies, - time_execution, - yes, + language, compiler, compiler_config, omp_pic, omp_feec, delete, status, verbose, dependencies, time_execution, yes ): """Compile Struphy kernels. All files that contain "kernels" are detected automatically and saved to state.yml. @@ -197,9 +187,9 @@ def struphy_compile( deps = depmod.get_dependencies(ker.replace(".py", so_suffix)) deps_li = deps.split(" ") print("-" * 28) - print(f"{ker =}") + print(f"{ker = }") for dep in deps_li: - print(f"{dep =}") + print(f"{dep = }") else: # struphy and psydac (change dir not to be in source path) @@ -268,11 +258,11 @@ def struphy_compile( # only install (from .whl) if psydac not up-to-date if psydac_ver < struphy_ver: print( - f"You have psydac version {psydac_ver}, but version {struphy_ver} is available. Please re-install struphy (e.g. pip install .)\n", + f"You have psydac version {psydac_ver}, but version {struphy_ver} is available. Please re-install struphy (e.g. pip install .)\n" ) sys.exit(1) else: - print("Psydac is not installed. To install it, please re-install struphy (e.g. pip install .)\n") + print(f"Psydac is not installed. To install it, please re-install struphy (e.g. pip install .)\n") sys.exit(1) else: diff --git a/src/struphy/console/format.py b/src/struphy/console/format.py index 7ba6795c4..7db28daed 100644 --- a/src/struphy/console/format.py +++ b/src/struphy/console/format.py @@ -125,34 +125,6 @@ def check_omp_flags(file_path, verbose=False): raise ValueError(f"Error reading file: {e}") -def check_ssort(file_path, verbose=False): - """Check if a file is sorted according to ssort. - - Parameters - ---------- - file_path : str - Path to the Python file. - - verbose : bool, optional - If True, enables detailed output (default=False). - - Returns - ------- - bool - True if ssort check passes, False otherwise. - """ - result = subprocess.run( - ["ssort", "--check", file_path], - check=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - if verbose: - print("stdout:", result.stdout.decode("utf-8")) - print("stderr:", result.stderr.decode("utf-8")) - return result.returncode == 0 - - def check_ruff(file_path, verbose=False): """Check if a file passes Ruff linting. @@ -407,12 +379,9 @@ def parse_path(directory): python_files = [] for root, _, files in os.walk(directory): for filename in files: - if re.search(r"__\w+__", root): - continue - if (filename.endswith(".py") or filename.endswith(".ipynb")) and not re.search(r"__\w+__", filename): + if filename.endswith(".py") and not re.search(r"__\w+__", filename): file_path = os.path.join(root, filename) python_files.append(file_path) - # exit() return python_files @@ -484,9 +453,7 @@ def get_python_files(input_type, path=None): # python_files = [f for f in files if f.endswith(".py") and os.path.isfile(f)] python_files = [ - os.path.join(repopath, f) - for f in files - if (f.endswith(".py") or f.endswith(".ipynb")) and os.path.isfile(os.path.join(repopath, f)) + os.path.join(repopath, f) for f in files if f.endswith(".py") and os.path.isfile(os.path.join(repopath, f)) ] if not python_files: @@ -509,415 +476,292 @@ def get_python_files(input_type, path=None): return python_files -def replace_backticks_with_code_tags(text): - """Recursively replaces inline backticks with tags. - Handles multiple or nested occurrences. +def struphy_lint(config, verbose): + """Lint Python files based on the given configuration and specified linters. - Args: - text (str): Input string with backticks to be replaced. + Parameters + ---------- + config : dict + Configuration dictionary containing the following keys: + - input_type : str, optional + The type of files to lint ('all', 'path', 'staged', or 'branch'). Defaults to 'all'. + - output_format: str, optional + The format of the lint output ('table', or 'plain'). Defaults to 'table' + - path : str, optional + Directory or file path to lint. + - linters : list + List of linter names to apply. - Returns: - str: Formatted string with tags. + verbose : bool + If True, enables detailed output. """ - # Regular expression to match text inside single backtick pairs - pattern = r"`([^`]*)`" - # Replace one level of backticks with tags - new_text = re.sub(pattern, r"\1", text) + # Extract individual settings from config + input_type = config.get("input_type", "all") + path = config.get("path") + output_format = config.get("output_format", "table") + linters = config.get("linters", []) - # If additional backticks are found, process recursively - if "`" in new_text: - return replace_backticks_with_code_tags(new_text) + if input_type is None and path is not None: + input_type = "path" + # Define standard linters which will be checked in the CI + ci_linters = ["ruff", "omp_flags"] + python_files = get_python_files(input_type, path) + if len(python_files) == 0: + sys.exit(0) - return new_text + print( + tabulate( + [[file] for file in python_files], + headers=[f"The following files will be linted with {linters}"], + ), + ) + print("\n") + if output_format == "report": + generate_report(python_files, linters=linters, verbose=verbose) + sys.exit(0) -def generate_html_table_from_combined_data(combined_data, sort_descending=True): - html = "" - html += "" - sorted_items = sorted(combined_data.items(), reverse=sort_descending) - for count, info in sorted_items: - codes_links = ", ".join(info["Codes"]) - html += f"" - html += "
CountCodes
{count}{codes_links}
" - return html + max_pathlen = max(len(os.path.relpath(file_path)) for file_path in python_files) + stats_list = [] + # Check if all ci_linters are included in linters + if all(ci_linter in linters for ci_linter in ci_linters): + print(f"Passes CI if {ci_linters} passes") + print("-" * 40) + check_ci_pass = True + else: + skipped_ci_linters = [ci_linter for ci_linter in ci_linters if ci_linter not in linters] + print( + f'The "Pass CI" check is skipped since not --linters {" ".join(skipped_ci_linters)} is used.', + ) + check_ci_pass = False + # Collect statistics for each file + for ifile, file_path in enumerate(python_files): + stats = analyze_file(file_path, linters=linters, verbose=verbose) + stats_list.append(stats) -def parse_json_file_to_html(json_file_path, html_output_path): - """Parses a JSON file containing code issues and writes an HTML report. - Parses a JSON file containing code issues, groups them by filename, - reads the source code to extract context, and writes an HTML report. - Each file's section is foldable using
and tags. + # Print the statistics in a table + if output_format == "table": + print_stats_table( + [stats], + linters, + print_header=(ifile == 0), + pathlen=max_pathlen, + ) + elif output_format == "plain": + print_stats_plain(stats, linters) - Args: - json_file_path (str): The path to the JSON file containing code issues. - html_output_path (str): The path where the HTML report will be saved. - """ + if check_ci_pass: + passes_ci = True + for stats in stats_list: + if not all(stats[f"passes_{ci_linter}"] for ci_linter in ci_linters): + passes_ci = False + if passes_ci: + print("All files will pass CI") + sys.exit(0) + else: + print("Not all files will pass CI") + sys.exit(1) + print("Not all CI linters were checked, unknown if all files will pass CI") + sys.exit(1) - try: - with open(json_file_path, "r") as file: - data = json.load(file) - if not isinstance(data, list): - print("Invalid JSON format: Expected a list of objects.") - return +def generate_report(python_files, linters=["ruff"], verbose=False): + for linter in linters: + if linter == "ruff": + for python_file in python_files: + report_json_filename = "code_analysis_report.json" + report_html_filename = "code_analysis_report.html" + command = [ + "ruff", + "check", + "--preview", + "--select", + "ALL", + "--ignore", + "D211,D213", + "--output-format", + "json", + "-o", + report_json_filename, + ] + python_files + subprocess.run(command, check=False) + parse_json_file_to_html(report_json_filename, report_html_filename) + if os.path.exists(report_json_filename): + os.remove(report_json_filename) + sys.exit(0) - # Group issues by filename - issues_by_file = defaultdict(list) - for issue in data: - filename = issue.get("filename", "Unknown file") - issues_by_file[filename].append(issue) - # Start building the HTML content - html_content = [] - html_content.extend( - [ - "", - "", - "", - "", - "", - "Code Analysis Report", - ], - ) +def confirm_formatting(python_files, linters, yes): + """Confirm with the user whether to format the listed Python files.""" + print( + tabulate( + [[file] for file in python_files], + headers=[f"The following files will be formatted with {linters}"], + ), + ) + print("\n") + if not yes: + ans = input("Format files (Y/n)?\n") + if ans.lower() not in ("y", "yes", ""): + print("Exiting...") + sys.exit(1) - # Include external CSS and JS libraries - html_content.extend( - [ - "", - "", - "", - "", - ], - ) - # Custom CSS for light mode and code prettification - html_content.append("") +def files_require_formatting(python_files, linters): + """Check if any of the specified files still require formatting based on the specified linters. - # JavaScript to initialize Highlight.js with custom options - html_content.append( - """ - -""", - ) + Parameters + ---------- + python_files : list + List of Python file paths to check. - html_content.extend(["", "", "

Code Issues Report

"]) + linters : list + List of linter names to check against (e.g., ['autopep8', 'isort']). - # Add summary statistics - total_issues = sum(len(issues) for issues in issues_by_file.values()) - total_files = len(issues_by_file) - html_content.append( - f""" -
-

Total Issues: {total_issues}

-

Number of files: {total_files}

-
-""", - ) + Returns + ------- + bool + True if any files still require formatting, False otherwise. + """ + linter_check_functions = { + "autopep8": check_autopep8, + "isort": check_isort, + "add-trailing-comma": check_trailing_commas, + } - # Navigation menu - # html_content.append("") + for file_path in python_files: + for linter in linters: + check_function = linter_check_functions.get(linter) + if check_function and not check_function(file_path): + return True + return False - for filename, issues in issues_by_file.items(): - print(f"Parsing {filename}") - # Start foldable section for the file - anchor = filename.replace(LIBPATH, "src/struphy").replace("/", "_").replace("\\", "_") - display_name = filename.replace(LIBPATH, "src/struphy") - html_content.append( - f""" -
- File: {display_name} -""", - ) - issue_data = {} - for issue in issues: - code = issue.get("code", "Unknown code") - message = replace_backticks_with_code_tags(issue.get("message", "No message")) - url = issue.get("url", "No URL provided") - if code in issue_data: - issue_data[code]["Count"] += 1 - else: - issue_data[code] = { - "Count": 1, - "Message": message, - "url": url, - } +def run_linters_on_files(linters, python_files, flags, verbose): + """Run each linter on the specified files with appropriate flags.""" + for linter in linters: + for python_file in python_files: + print(f"Formatting {python_file}") + linter_flags = flags.get(linter, []) + if isinstance(linter_flags[0], list): + # If linter_flags is a list, run each separately + for flag in linter_flags: + command = [linter] + flag + [python_file] + if verbose: + print(f"Running command: {' '.join(command)}") - combined_data = {} - for code, info in issue_data.items(): - count = info["Count"] - url = info["url"] - link = f"{code}" - if count in combined_data: - combined_data[count]["Codes"].append(link) + subprocess.run(command, check=False) + else: + # If linter_flags is not a list, treat it as a single value + command = [linter] + linter_flags + [python_file] + if verbose: + print(f"Running command: {' '.join(command)}") + subprocess.run(command, check=False) + + # Loop over each line and replace '# $' with '#$' in place + for line in fileinput.input(python_file, inplace=True): + if line.lstrip().startswith("# $"): + print(line.replace("# $", "#$"), end="") else: - combined_data[count] = { - "Codes": [link], - } - # Generate the HTML table - html_content.append(generate_html_table_from_combined_data(combined_data, sort_descending=True)) + print(line, end="") - for issue in issues: - code = issue.get("code", "Unknown code") - message = replace_backticks_with_code_tags(issue.get("message", "No message")) - location = issue.get("location", {}) - row = location.get("row", None) - column = location.get("column", None) - end_location = issue.get("end_location", {}) - # end_row = end_location.get("row", row) - end_column = end_location.get("column", column) - fix = issue.get("fix", None) - url = issue.get("url", "No URL provided") - html_content.append("
") - html_content.append("

") - html_content.append( - f"Issue: " - f"{code} - " - f"{message}
" - f"Location: " - f"{display_name}:{row}:{column}
", - ) - html_content.append("

") +def struphy_format(config, verbose, yes=False): + """Format Python files with specified linters, optionally iterating multiple times. - # Read the file and extract the code snippet - if os.path.exists(filename) and row is not None: - with open(filename, "r") as source_file: - lines = source_file.readlines() - total_lines = len(lines) - # Adjust indices for zero-based indexing - context_radius = 2 # Number of lines before and after the issue line - start_line = max(row - context_radius - 1, 0) - end_line = min(row + context_radius, total_lines) - snippet_lines = lines[start_line:end_line] + Parameters + ---------- + config : dict + Configuration dictionary containing the following keys: + - input_type : str, optional + The type of files to format ('all', 'path', 'staged', 'branch', or '__init__.py'). Defaults to 'all'. + - path : str, optional + Directory or file path where files will be formatted. + - linters : list + List of formatter names to apply. + - iterations : int, optional + Maximum number of times to apply formatting (default=5). - # Build the code snippet - code_lines = [] - for idx, line_content in enumerate(snippet_lines, start=start_line + 1): - line_content = line_content.rstrip("\n") - # Fix HTML special characters - line_content = line_content.replace("&", "&").replace("<", "<").replace(">", ">") - # Highlight the error - if idx == row and column is not None and end_column is not None: - start_col = column - 1 # Adjust for zero-based indexing - end_col = end_column - 1 + verbose : bool + If True, enables detailed output, showing each command and iteration. - start_col = max(start_col, 0) - end_col = min(end_col, len(line_content)) + yes : bool, optional + If True, skips the confirmation prompt before formatting. + """ - before = line_content[:start_col] - problem = line_content[start_col:end_col] - after = line_content[end_col:] - # Wrap the problematic part with - highlighted_line = f"{before}{problem}{after}" - code_lines.append((idx, highlighted_line)) - else: - code_lines.append((idx, line_content)) - # Make code block with line numbers - html_content.append("
")
-                        for line_number, line_content in code_lines:
-                            html_content.append(
-                                # f"
" - # f"{line_number}{line_content}
" - f"{line_number}: {line_content}", - ) - html_content.append("
") - # Include fix details if available - if fix: - html_content.append("
") - html_content.append( - f"

Fix Available ({fix.get('applicability', 'Unknown')}): " - f"ruff check --select ALL --fix {display_name}

", - ) - html_content.append("
") - else: - html_content.append( - f"

Cannot read file {filename} or invalid row {row}.

", - ) + # Extract individual settings from config + input_type = config.get("input_type", "all") + path = config.get("path") + linters = config.get("linters", []) + iterations = config.get("iterations", 5) - html_content.append("
") - html_content.append("
") + if input_type is None and path is not None: + input_type = "path" - html_content.append("
") + if input_type == "__init__.py": + print(f"Rewriting {PROPAGATORS_INIT_PATH}") + propagators_init = construct_propagators_init_file() + with open(PROPAGATORS_INIT_PATH, "w") as f: + f.write(propagators_init) - # Footer - html_content.append( - f""" -
-

Generated by on {time.strftime("%Y-%m-%d %H:%M:%S")}

-
-""", - ) + print(f"Rewriting {MODELS_INIT_PATH}") + models_init = construct_models_init_file() + with open(MODELS_INIT_PATH, "w") as f: + f.write(models_init) - html_content.extend(["", ""]) + python_files = [PROPAGATORS_INIT_PATH, MODELS_INIT_PATH] + input_type = "path" + else: + python_files = get_python_files(input_type, path) - # Write the HTML content to the output file - with open(html_output_path, "w") as html_file: - html_file.write("\n".join(html_content)) + if len(python_files) == 0: + print("No Python files to format.") + sys.exit(0) - print(f"HTML report generated at {html_output_path}") + confirm_formatting(python_files, linters, yes) - except FileNotFoundError as e: - print(f"Error: {e}") - except json.JSONDecodeError as e: - print(f"Error: Failed to parse JSON file. {e}") - except Exception as e: - print(f"An unexpected error occurred: {e}") + flags = { + "autopep8": ["--in-place"], + "isort": [], + "add-trailing-comma": ["--exit-zero-even-if-changed"], + "ruff": [["check", "--fix", "--select", "I"], ["format"]], + } + # Skip linting with add-trailing-comma since it disagrees with autopep8 + skip_linters = ["add-trailing-comma"] -def generate_report(python_files, linters=["ruff"], verbose=False): - for linter in linters: - if linter == "ruff": - for python_file in python_files: - report_json_filename = "code_analysis_report.json" - report_html_filename = "code_analysis_report.html" - command = [ - "ruff", - "check", - "--preview", - "--select", - "ALL", - "--ignore", - "D211,D213", - "--output-format", - "json", - "-o", - report_json_filename, - ] + python_files - subprocess.run(command, check=False) - parse_json_file_to_html(report_json_filename, report_html_filename) - if os.path.exists(report_json_filename): - os.remove(report_json_filename) - sys.exit(0) + if python_files: + for iteration in range(iterations): + if verbose: + print(f"Iteration {iteration + 1}: Running formatters...") + + run_linters_on_files( + linters, + python_files, + flags, + verbose, + ) + + # Check if any files still require changes + if not files_require_formatting( + python_files, + [lint for lint in linters if lint not in skip_linters], + ): + print("All files are properly formatted.") + break + else: + if verbose: + print( + "Max iterations reached. The following files may still require manual checks:", + ) + for file_path in python_files: + if files_require_formatting([file_path], linters): + print(f" - {file_path}") + print("Contact Max about this") + else: + print("No Python files to format.") def print_stats_plain(stats, linters, ci_linters=["ruff"]): @@ -1053,7 +897,6 @@ def analyze_file(file_path, linters=None, verbose=False): "passes_add-trailing-comma": False, "passes_ruff": False, "passes_omp_flags": False, - "passes_ssort": False, } # Read the file content @@ -1095,178 +938,393 @@ def analyze_file(file_path, linters=None, verbose=False): file_path, verbose=verbose, ) - if "ssort" in linters: - stats["passes_ssort"] = check_ssort( - file_path, - verbose=verbose, - ) + return stats -def struphy_lint(config, verbose): - """Lint Python files based on the given configuration and specified linters. +def replace_backticks_with_code_tags(text): + """Recursively replaces inline backticks with tags. + Handles multiple or nested occurrences. - Parameters - ---------- - config : dict - Configuration dictionary containing the following keys: - - input_type : str, optional - The type of files to lint ('all', 'path', 'staged', or 'branch'). Defaults to 'all'. - - output_format: str, optional - The format of the lint output ('table', or 'plain'). Defaults to 'table' - - path : str, optional - Directory or file path to lint. - - linters : list - List of linter names to apply. + Args: + text (str): Input string with backticks to be replaced. - verbose : bool - If True, enables detailed output. + Returns: + str: Formatted string with tags. """ + # Regular expression to match text inside single backtick pairs + pattern = r"`([^`]*)`" - # Extract individual settings from config - input_type = config.get("input_type", "all") - path = config.get("path") - output_format = config.get("output_format", "table") - linters = config.get("linters", []) + # Replace one level of backticks with tags + new_text = re.sub(pattern, r"\1", text) - if input_type is None and path is not None: - input_type = "path" - # Define standard linters which will be checked in the CI - ci_linters = ["ruff", "omp_flags"] - python_files = get_python_files(input_type, path) - if len(python_files) == 0: - sys.exit(0) + # If additional backticks are found, process recursively + if "`" in new_text: + return replace_backticks_with_code_tags(new_text) - print( - tabulate( - [[file] for file in python_files], - headers=[f"The following files will be linted with {linters}"], - ), - ) - print("\n") + return new_text - if output_format == "report": - generate_report(python_files, linters=linters, verbose=verbose) - sys.exit(0) - max_pathlen = max(len(os.path.relpath(file_path)) for file_path in python_files) - stats_list = [] +def generate_html_table_from_combined_data(combined_data, sort_descending=True): + html = "" + html += "" + sorted_items = sorted(combined_data.items(), reverse=sort_descending) + for count, info in sorted_items: + codes_links = ", ".join(info["Codes"]) + html += f"" + html += "
CountCodes
{count}{codes_links}
" + return html - # Check if all ci_linters are included in linters - if all(ci_linter in linters for ci_linter in ci_linters): - print(f"Passes CI if {ci_linters} passes") - print("-" * 40) - check_ci_pass = True - else: - skipped_ci_linters = [ci_linter for ci_linter in ci_linters if ci_linter not in linters] - print( - f'The "Pass CI" check is skipped since not --linters {" ".join(skipped_ci_linters)} is used.', + +def parse_json_file_to_html(json_file_path, html_output_path): + """Parses a JSON file containing code issues and writes an HTML report. + Parses a JSON file containing code issues, groups them by filename, + reads the source code to extract context, and writes an HTML report. + Each file's section is foldable using
and tags. + + Args: + json_file_path (str): The path to the JSON file containing code issues. + html_output_path (str): The path where the HTML report will be saved. + """ + + try: + with open(json_file_path, "r") as file: + data = json.load(file) + + if not isinstance(data, list): + print("Invalid JSON format: Expected a list of objects.") + return + + # Group issues by filename + issues_by_file = defaultdict(list) + for issue in data: + filename = issue.get("filename", "Unknown file") + issues_by_file[filename].append(issue) + + # Start building the HTML content + html_content = [] + html_content.extend( + [ + "", + "", + "", + "", + "", + "Code Analysis Report", + ] ) - check_ci_pass = False - # Collect statistics for each file - for ifile, file_path in enumerate(python_files): - stats = analyze_file(file_path, linters=linters, verbose=verbose) - stats_list.append(stats) - # Print the statistics in a table - if output_format == "table": - print_stats_table( - [stats], - linters, - print_header=(ifile == 0), - pathlen=max_pathlen, + # Include external CSS and JS libraries + html_content.extend( + [ + "", + "", + "", + "", + ] + ) + + # Custom CSS for light mode and code prettification + html_content.append("") + + # JavaScript to initialize Highlight.js with custom options + html_content.append( + """ + +""" + ) + + html_content.extend(["", "", "

Code Issues Report

"]) + + # Add summary statistics + total_issues = sum(len(issues) for issues in issues_by_file.values()) + total_files = len(issues_by_file) + html_content.append( + f""" +
+

Total Issues: {total_issues}

+

Number of files: {total_files}

+
+""" + ) + + # Navigation menu + # html_content.append("") + + for filename, issues in issues_by_file.items(): + print(f"Parsing {filename}") + # Start foldable section for the file + anchor = filename.replace(LIBPATH, "src/struphy").replace("/", "_").replace("\\", "_") + display_name = filename.replace(LIBPATH, "src/struphy") + html_content.append( + f""" +
+ File: {display_name} +""" ) - elif output_format == "plain": - print_stats_plain(stats, linters) - if check_ci_pass: - passes_ci = True - for stats in stats_list: - if not all(stats[f"passes_{ci_linter}"] for ci_linter in ci_linters): - passes_ci = False - if passes_ci: - print("All files will pass CI") - sys.exit(0) - else: - print("Not all files will pass CI") - sys.exit(1) - print("Not all CI linters were checked, unknown if all files will pass CI") - sys.exit(1) + issue_data = {} + for issue in issues: + code = issue.get("code", "Unknown code") + message = replace_backticks_with_code_tags(issue.get("message", "No message")) + url = issue.get("url", "No URL provided") + if code in issue_data: + issue_data[code]["Count"] += 1 + else: + issue_data[code] = { + "Count": 1, + "Message": message, + "url": url, + } + + combined_data = {} + for code, info in issue_data.items(): + count = info["Count"] + url = info["url"] + link = f"{code}" + if count in combined_data: + combined_data[count]["Codes"].append(link) + else: + combined_data[count] = { + "Codes": [link], + } + # Generate the HTML table + html_content.append(generate_html_table_from_combined_data(combined_data, sort_descending=True)) + for issue in issues: + code = issue.get("code", "Unknown code") + message = replace_backticks_with_code_tags(issue.get("message", "No message")) + location = issue.get("location", {}) + row = location.get("row", None) + column = location.get("column", None) + end_location = issue.get("end_location", {}) + # end_row = end_location.get("row", row) + end_column = end_location.get("column", column) + fix = issue.get("fix", None) + url = issue.get("url", "No URL provided") -def confirm_formatting(python_files, linters, yes): - """Confirm with the user whether to format the listed Python files.""" - print( - tabulate( - [[file] for file in python_files], - headers=[f"The following files will be formatted with {linters}"], - ), - ) - print("\n") - if not yes: - ans = input("Format files (Y/n)?\n") - if ans.lower() not in ("y", "yes", ""): - print("Exiting...") - sys.exit(1) + html_content.append("
") + html_content.append("

") + html_content.append( + f"Issue: " + f"{code} - " + f"{message}
" + f"Location: " + f"{display_name}:{row}:{column}
" + ) + html_content.append("

") + # Read the file and extract the code snippet + if os.path.exists(filename) and row is not None: + with open(filename, "r") as source_file: + lines = source_file.readlines() + total_lines = len(lines) + # Adjust indices for zero-based indexing + context_radius = 2 # Number of lines before and after the issue line + start_line = max(row - context_radius - 1, 0) + end_line = min(row + context_radius, total_lines) + snippet_lines = lines[start_line:end_line] -def files_require_formatting(python_files, linters): - """Check if any of the specified files still require formatting based on the specified linters. + # Build the code snippet + code_lines = [] + for idx, line_content in enumerate(snippet_lines, start=start_line + 1): + line_content = line_content.rstrip("\n") + # Fix HTML special characters + line_content = line_content.replace("&", "&").replace("<", "<").replace(">", ">") + # Highlight the error + if idx == row and column is not None and end_column is not None: + start_col = column - 1 # Adjust for zero-based indexing + end_col = end_column - 1 - Parameters - ---------- - python_files : list - List of Python file paths to check. + start_col = max(start_col, 0) + end_col = min(end_col, len(line_content)) - linters : list - List of linter names to check against (e.g., ['autopep8', 'isort']). + before = line_content[:start_col] + problem = line_content[start_col:end_col] + after = line_content[end_col:] + # Wrap the problematic part with + highlighted_line = f"{before}{problem}{after}" + code_lines.append((idx, highlighted_line)) + else: + code_lines.append((idx, line_content)) + # Make code block with line numbers + html_content.append("
")
+                        for line_number, line_content in code_lines:
+                            html_content.append(
+                                # f"
" + # f"{line_number}{line_content}
" + f"{line_number}: {line_content}" + ) + html_content.append("
") + # Include fix details if available + if fix: + html_content.append("
") + html_content.append( + f"

Fix Available ({fix.get('applicability', 'Unknown')}): " + f"ruff check --select ALL --fix {display_name}

" + ) + html_content.append("
") + else: + html_content.append( + f"

Cannot read file {filename} or invalid row {row}.

" + ) - Returns - ------- - bool - True if any files still require formatting, False otherwise. - """ - linter_check_functions = { - "autopep8": check_autopep8, - "isort": check_isort, - "add-trailing-comma": check_trailing_commas, - } + html_content.append("
") + html_content.append("
") - for file_path in python_files: - for linter in linters: - check_function = linter_check_functions.get(linter) - if check_function and not check_function(file_path): - return True - return False + html_content.append("
") + # Footer + html_content.append( + f""" +
+

Generated by on {time.strftime("%Y-%m-%d %H:%M:%S")}

+
+""" + ) -def run_linters_on_files(linters, python_files, flags, verbose): - """Run each linter on the specified files with appropriate flags.""" - for linter in linters: - for python_file in python_files: - print(f"Formatting {python_file}") - linter_flags = flags.get(linter, []) - if len(linter_flags) > 0 and isinstance(linter_flags[0], list): - # If linter_flags is a list, run each separately - for flag in linter_flags: - command = [linter] + flag + [python_file] - if verbose: - print(f"Running command: {' '.join(command)}") + html_content.extend(["", ""]) - subprocess.run(command, check=False) - else: - # If linter_flags is not a list, treat it as a single value - command = [linter] + linter_flags + [python_file] - if verbose: - print(f"Running command: {' '.join(command)}") - subprocess.run(command, check=False) + # Write the HTML content to the output file + with open(html_output_path, "w") as html_file: + html_file.write("\n".join(html_content)) - # Loop over each line and replace '# $' with '#$' in place - for line in fileinput.input(python_file, inplace=True): - if line.lstrip().startswith("# $"): - print(line.replace("# $", "#$"), end="") - else: - print(line, end="") + print(f"HTML report generated at {html_output_path}") + + except FileNotFoundError as e: + print(f"Error: {e}") + except json.JSONDecodeError as e: + print(f"Error: Failed to parse JSON file. {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") def construct_models_init_file() -> str: @@ -1317,100 +1375,3 @@ def construct_propagators_init_file() -> str: propagators_init += "\n\n" propagators_init += f"__all__ = {propagators_names}\n" return propagators_init - - -def struphy_format(config, verbose, yes=False): - """Format Python files with specified linters, optionally iterating multiple times. - - Parameters - ---------- - config : dict - Configuration dictionary containing the following keys: - - input_type : str, optional - The type of files to format ('all', 'path', 'staged', 'branch', or '__init__.py'). Defaults to 'all'. - - path : str, optional - Directory or file path where files will be formatted. - - linters : list - List of formatter names to apply. - - iterations : int, optional - Maximum number of times to apply formatting (default=5). - - verbose : bool - If True, enables detailed output, showing each command and iteration. - - yes : bool, optional - If True, skips the confirmation prompt before formatting. - """ - - # Extract individual settings from config - input_type = config.get("input_type", "all") - path = config.get("path") - linters = config.get("linters", []) - iterations = config.get("iterations", 5) - - if input_type is None and path is not None: - input_type = "path" - - if input_type == "__init__.py": - print(f"Rewriting {PROPAGATORS_INIT_PATH}") - propagators_init = construct_propagators_init_file() - with open(PROPAGATORS_INIT_PATH, "w") as f: - f.write(propagators_init) - - print(f"Rewriting {MODELS_INIT_PATH}") - models_init = construct_models_init_file() - with open(MODELS_INIT_PATH, "w") as f: - f.write(models_init) - - python_files = [PROPAGATORS_INIT_PATH, MODELS_INIT_PATH] - input_type = "path" - else: - python_files = get_python_files(input_type, path) - - if len(python_files) == 0: - print("No Python files to format.") - sys.exit(0) - - confirm_formatting(python_files, linters, yes) - - flags = { - "autopep8": ["--in-place"], - "isort": [], - "add-trailing-comma": ["--exit-zero-even-if-changed"], - "ruff": [["check", "--fix", "--select", "I"], ["format"]], - "ssort": [], - } - - # Skip linting with add-trailing-comma since it disagrees with autopep8 - skip_linters = ["add-trailing-comma"] - - if python_files: - for iteration in range(iterations): - if verbose: - print(f"Iteration {iteration + 1}: Running formatters...") - - run_linters_on_files( - linters, - python_files, - flags, - verbose, - ) - - # Check if any files still require changes - if not files_require_formatting( - python_files, - [lint for lint in linters if lint not in skip_linters], - ): - print("All files are properly formatted.") - break - else: - if verbose: - print( - "Max iterations reached. The following files may still require manual checks:", - ) - for file_path in python_files: - if files_require_formatting([file_path], linters): - print(f" - {file_path}") - print("Contact Max about this") - else: - print("No Python files to format.") diff --git a/src/struphy/console/main.py b/src/struphy/console/main.py index 545e4a24c..19832a830 100644 --- a/src/struphy/console/main.py +++ b/src/struphy/console/main.py @@ -452,7 +452,7 @@ def add_parser_run(subparsers, list_models, model_message, params_files, batch_f default=None, # fallback if nothing is passed choices=list_models, metavar="MODEL", - help=model_message + " (default: None)", + help=model_message + f" (default: None)", ) parser_run.add_argument( @@ -953,8 +953,8 @@ def add_parser_test(subparsers, list_models): "--mpi", type=int, metavar="N", - help="set number of MPI processes used in tests (default=1))", - default=1, + help="set number of MPI processes used in tests (default=2))", + default=2, ) parser_test.add_argument( diff --git a/src/struphy/console/params.py b/src/struphy/console/params.py index 4916cd1c0..bbe33ace3 100644 --- a/src/struphy/console/params.py +++ b/src/struphy/console/params.py @@ -1,7 +1,7 @@ import sys import yaml -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy.models import fluid, hybrid, kinetic, toy from struphy.models.base import StruphyModel @@ -29,8 +29,6 @@ def struphy_params(model_name: str, params_path: str, yes: bool = False, check_f except AttributeError: pass - print(f"{model_name =}") - # print units if check_file: print(f"Checking {check_file} with model {model_class}") @@ -48,5 +46,3 @@ def struphy_params(model_name: str, params_path: str, yes: bool = False, check_f else: prompt = not yes model.generate_default_parameter_file(path=params_path, prompt=prompt) - # print(f"Generating default parameter file for {model_class}.") - # model_class().generate_default_parameter_file(path=params_path, prompt=prompt) diff --git a/src/struphy/console/profile.py b/src/struphy/console/profile.py index a2f10ce66..76f7d1322 100644 --- a/src/struphy/console/profile.py +++ b/src/struphy/console/profile.py @@ -6,7 +6,7 @@ def struphy_profile(dirs, replace, all, n_lines, print_callers, savefig): import os import pickle - import cunumpy as xp + import numpy as np import yaml from matplotlib import pyplot as plt @@ -106,7 +106,7 @@ def struphy_profile(dirs, replace, all, n_lines, print_callers, savefig): + "ncalls".ljust(15) + "tottime".ljust(15) + "percall".ljust(15) - + "cumtime".ljust(15), + + "cumtime".ljust(15) ) print("-" * 154) for position, key in enumerate(dicts[0].keys()): @@ -167,10 +167,10 @@ def struphy_profile(dirs, replace, all, n_lines, print_callers, savefig): ratio.append(str(int(float(t) / runtime * 100)) + "%") # strong scaling plot - if xp.all([Nel == val["Nel"][0] for Nel in val["Nel"]]): + if np.all([Nel == val["Nel"][0] for Nel in val["Nel"]]): # ideal scaling if n == 0: - ax.loglog(val["mpi_size"], 1 / 2 ** xp.arange(len(val["time"])), "k--", alpha=0.3, label="ideal") + ax.loglog(val["mpi_size"], 1 / 2 ** np.arange(len(val["time"])), "k--", alpha=0.3, label="ideal") # print average time per one time step if "integrate" in key: @@ -206,11 +206,11 @@ def struphy_profile(dirs, replace, all, n_lines, print_callers, savefig): ax.set_ylabel("time [s]") ax.set( title="Weak scaling for cells/mpi_size=" - + str(xp.prod(val["Nel"][0]) / val["mpi_size"][0]) - + "=const.", + + str(np.prod(val["Nel"][0]) / val["mpi_size"][0]) + + "=const." ) ax.legend(loc="upper left") - # ax.loglog(val['mpi_size'], val['time'][0]*xp.ones_like(val['time']), 'k--', alpha=0.3) + # ax.loglog(val['mpi_size'], val['time'][0]*np.ones_like(val['time']), 'k--', alpha=0.3) ax.set_xscale("log") if savefig is None: diff --git a/src/struphy/console/run.py b/src/struphy/console/run.py index 3fa8ee56d..1845230e9 100644 --- a/src/struphy/console/run.py +++ b/src/struphy/console/run.py @@ -210,7 +210,7 @@ def struphy_run( if likwid: command = likwid_command + command + ["--likwid"] - print(f"Running with likwid with {likwid_repetitions =}") + print(f"Running with likwid with {likwid_repetitions = }") f.write(f"# Launching likwid {likwid_repetitions} times with likwid-mpirun\n") for i in range(likwid_repetitions): f.write(f"\n\n# Run number {i + 1:03}\n") diff --git a/src/struphy/console/test.py b/src/struphy/console/test.py index ab64707e3..09a0872ac 100644 --- a/src/struphy/console/test.py +++ b/src/struphy/console/test.py @@ -4,7 +4,7 @@ def struphy_test( group: str, *, - mpi: int = 1, + mpi: int = 2, with_desc: bool = False, vrbose: bool = False, show_plots: bool = False, @@ -19,7 +19,7 @@ def struphy_test( Test identifier: "unit", "models", "fluid", "kinetic", "hybrid", "toy", "verification" or a model name. mpi : int - Number of MPI processes used in tests (default=1). + Number of MPI processes used in tests (must be >1, default=2). with_desc : bool Whether to include DESC equilibrium in unit tests (mem consuming). @@ -35,57 +35,51 @@ def struphy_test( """ if "unit" in group: - if mpi > 1: - cmd = [ - "mpirun", - "-n", - str(mpi), - "pytest", - "-k", - "not _models and not _tutorial and not pproc and not _verif_", - "--with-mpi", - ] - else: - cmd = [ - "pytest", - "-k", - "not _models and not _tutorial and not pproc and not _verif_", - ] - + # first run only tests that require single process + cmd = [ + "pytest", + "-k", + "not _models and not _tutorial and not pproc", + ] if with_desc: cmd += ["--with-desc"] if vrbose: cmd += ["--vrbose"] if show_plots: cmd += ["--show-plots"] + subp_run(cmd) + # now run parallel unit tests + cmd = [ + "mpirun", + "-n", + str(mpi), + "pytest", + "-k", + "not _models and not _tutorial and not pproc", + "--with-mpi", + ] + if with_desc: + cmd += ["--with-desc"] + if vrbose: + cmd += ["--vrbose"] + if show_plots: + cmd += ["--show-plots"] subp_run(cmd) elif group in {"models", "fluid", "kinetic", "hybrid", "toy"}: - if mpi > 1: - cmd = [ - "mpirun", - "--oversubscribe", - "-n", - str(mpi), - "pytest", - "-k", - "_models", - "-m", - group, - "-s", - "--with-mpi", - ] - else: - cmd = [ - "pytest", - "-k", - "_models", - "-m", - group, - "-s", - ] - + cmd = [ + "mpirun", + "-n", + str(mpi), + "pytest", + "-k", + "_models", + "-m", + group, + "-s", + "--with-mpi", + ] if vrbose: cmd += ["--vrbose"] if nclones > 1: @@ -95,26 +89,16 @@ def struphy_test( subp_run(cmd) elif "verification" in group: - if mpi > 1: - cmd = [ - "mpirun", - "--oversubscribe", - "-n", - str(mpi), - "pytest", - "-k", - "_verif_", - "-s", - "--with-mpi", - ] - else: - cmd = [ - "pytest", - "-k", - "_verif_", - "-s", - ] - + cmd = [ + "mpirun", + "-n", + str(mpi), + "pytest", + "-k", + "_verif_", + "-s", + "--with-mpi", + ] if vrbose: cmd += ["--vrbose"] if nclones > 1: @@ -126,7 +110,6 @@ def struphy_test( else: cmd = [ "mpirun", - "--oversubscribe", "-n", str(mpi), "pytest", diff --git a/src/struphy/console/tests/test_console.py b/src/struphy/console/tests/test_console.py index c94e41eb2..781f25426 100644 --- a/src/struphy/console/tests/test_console.py +++ b/src/struphy/console/tests/test_console.py @@ -6,7 +6,8 @@ import pytest -# from psydac.ddm.mpi import mpi as MPI +# from mpi4py import MPI +import struphy import struphy as struphy_lib from struphy.console.compile import struphy_compile from struphy.console.main import struphy @@ -289,7 +290,7 @@ def mock_remove(path): # Otherwise, we will not remove all the *_tmp.py files # We can not use the real os.remove becuase then # the state and all compiled files will be removed - print(f"{path =}") + print(f"{path = }") if "_tmp.py" in path: print("Not mock remove") os_remove(path) @@ -317,19 +318,19 @@ def mock_remove(path): time_execution=time_execution, yes=yes, ) - print(f"{language =}") - print(f"{compiler =}") - print(f"{omp_pic =}") - print(f"{omp_feec =}") - print(f"{delete =}") + print(f"{language = }") + print(f"{compiler = }") + print(f"{omp_pic = }") + print(f"{omp_feec = }") + print(f"{delete = }") print(f"{status} = ") - print(f"{verbose =}") - print(f"{dependencies =}") - print(f"{time_execution =}") - print(f"{yes =}") - print(f"{mock_save_state.call_count =}") - print(f"{mock_subprocess_run.call_count =}") - print(f"{mock_os_remove.call_count =}") + print(f"{verbose = }") + print(f"{dependencies = }") + print(f"{time_execution = }") + print(f"{yes = }") + print(f"{mock_save_state.call_count = }") + print(f"{mock_subprocess_run.call_count = }") + print(f"{mock_os_remove.call_count = }") if delete: print("if delete") diff --git a/src/struphy/diagnostics/console_diagn.py b/src/struphy/diagnostics/console_diagn.py index e110d1497..81be7bb38 100644 --- a/src/struphy/diagnostics/console_diagn.py +++ b/src/struphy/diagnostics/console_diagn.py @@ -5,8 +5,8 @@ import os import subprocess -import cunumpy as xp import h5py +import numpy as np import yaml import struphy @@ -301,7 +301,7 @@ def main(): bckgr_fun = getattr(maxwellians, default_bckgr_type)() # Get values of background shifts in velocity space - positions = [xp.array([grid_slices["e" + str(k)]]) for k in range(1, 4)] + positions = [np.array([grid_slices["e" + str(k)]]) for k in range(1, 4)] u = bckgr_fun.u(*positions) eval_params = {"u" + str(k + 1): u[k][0] for k in range(3)} @@ -315,7 +315,7 @@ def main(): # Plot the distribution function if "plot_distr" in actions: # Get index of where to plot in time - time_idx = xp.argmin(xp.abs(time - saved_time)) + time_idx = np.argmin(np.abs(time - saved_time)) plot_distr_fun( path=os.path.join( diff --git a/src/struphy/diagnostics/continuous_spectra.py b/src/struphy/diagnostics/continuous_spectra.py index c31d789c7..ec52b4744 100644 --- a/src/struphy/diagnostics/continuous_spectra.py +++ b/src/struphy/diagnostics/continuous_spectra.py @@ -37,7 +37,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, the radial location s_spec[m][0], squared eigenfrequencis s_spec[m][1] and global mode index s_spec[m][2] corresponding to slow sound modes for each poloidal mode number m in m_range. """ - import cunumpy as xp + import numpy as np import struphy.bsplines.bsplines as bsp @@ -50,7 +50,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, gD_2 = bsp.greville(space.t[1], space.p[1] - 1, space.spl_kind[1]) # poloidal mode numbers - ms = xp.arange(m_range[1] - m_range[0] + 1) + m_range[0] + ms = np.arange(m_range[1] - m_range[0] + 1) + m_range[0] # grid for normalized Jacobian determinant det_df = domain.jacobian_det(gD_1, gD_2, 0.0) @@ -66,7 +66,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, s_spec = [[[], [], []] for m in ms] # only consider eigenmodes in range omega^2/omega_A^2 = [0, 1] - modes_ind = xp.where((xp.real(omega2) / omega_A**2 < 1.0) & (xp.real(omega2) / omega_A**2 > 0.0))[0] + modes_ind = np.where((np.real(omega2) / omega_A**2 < 1.0) & (np.real(omega2) / omega_A**2 > 0.0))[0] for i in range(modes_ind.size): # determine whether it's an Alfvén branch or sound branch by checking DIV(U) @@ -86,14 +86,14 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, U2_1_coeff = (U2_1_coeff[:, :, 0] - 1j * U2_1_coeff[:, :, 1]) / 2 # determine radial location of singularity by looking for a peak in eigenfunction U2_1 - s_ind = xp.unravel_index(xp.argmax(abs(U2_1_coeff)), U2_1_coeff.shape)[0] + s_ind = np.unravel_index(np.argmax(abs(U2_1_coeff)), U2_1_coeff.shape)[0] s = gN_1[s_ind] # perform fft to determine m - U2_1_fft = xp.fft.fft(U2_1_coeff) + U2_1_fft = np.fft.fft(U2_1_coeff) # determine m by looking for peak in Fourier spectrum at singularity - m = int((xp.fft.fftfreq(U2_1_fft[s_ind].size) * U2_1_fft[s_ind].size)[xp.argmax(abs(U2_1_fft[s_ind]))]) + m = int((np.fft.fftfreq(U2_1_fft[s_ind].size) * U2_1_fft[s_ind].size)[np.argmax(abs(U2_1_fft[s_ind]))]) ## perform shift for negative m # if m >= (space.Nel[1] + 1)//2: @@ -103,7 +103,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, for j in range(ms.size): if ms[j] == m: a_spec[j][0].append(s) - a_spec[j][1].append(xp.real(omega2[modes_ind[i]])) + a_spec[j][1].append(np.real(omega2[modes_ind[i]])) a_spec[j][2].append(modes_ind[i]) # Sound branch @@ -117,14 +117,14 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, U2_coeff = (U2_coeff[:, :, 0] - 1j * U2_coeff[:, :, 1]) / 2 # determine radial location of singularity by looking for a peak in eigenfunction (U2_2 or U2_3) - s_ind = xp.unravel_index(xp.argmax(abs(U2_coeff)), U2_coeff.shape)[0] + s_ind = np.unravel_index(np.argmax(abs(U2_coeff)), U2_coeff.shape)[0] s = gD_1[s_ind] # perform fft to determine m - U2_fft = xp.fft.fft(U2_coeff) + U2_fft = np.fft.fft(U2_coeff) # determine m by looking for peak in Fourier spectrum at singularity - m = int((xp.fft.fftfreq(U2_fft[s_ind].size) * U2_fft[s_ind].size)[xp.argmax(abs(U2_fft[s_ind]))]) + m = int((np.fft.fftfreq(U2_fft[s_ind].size) * U2_fft[s_ind].size)[np.argmax(abs(U2_fft[s_ind]))]) ## perform shift for negative m # if m >= (space.Nel[1] + 1)//2: @@ -134,13 +134,13 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, for j in range(ms.size): if ms[j] == m: s_spec[j][0].append(s) - s_spec[j][1].append(xp.real(omega2[modes_ind[i]])) + s_spec[j][1].append(np.real(omega2[modes_ind[i]])) s_spec[j][2].append(modes_ind[i]) # convert to array for j in range(ms.size): - a_spec[j] = xp.array(a_spec[j]) - s_spec[j] = xp.array(s_spec[j]) + a_spec[j] = np.array(a_spec[j]) + s_spec[j] = np.array(s_spec[j]) return a_spec, s_spec @@ -152,12 +152,12 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, import os import shutil - import cunumpy as xp + import numpy as np import yaml # parse arguments parser = argparse.ArgumentParser( - description="Looks for eigenmodes in a given MHD eigenspectrum in a certain poloidal mode number range and plots the continuous shear Alfvén and slow sound spectra (frequency versus radial-like coordinate).", + description="Looks for eigenmodes in a given MHD eigenspectrum in a certain poloidal mode number range and plots the continuous shear Alfvén and slow sound spectra (frequency versus radial-like coordinate)." ) parser.add_argument("m_l_alfvén", type=int, help="lower bound of poloidal mode number range for Alfvénic modes") @@ -252,16 +252,11 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, fem_1d_2 = Spline_space_1d(Nel[1], p[1], spl_kind[1], nq_el[1], dirichlet_bc[1]) fem_2d = Tensor_spline_space( - [fem_1d_1, fem_1d_2], - polar_ck, - domain.cx[:, :, 0], - domain.cy[:, :, 0], - n_tor=n_tor, - basis_tor="i", + [fem_1d_1, fem_1d_2], polar_ck, domain.cx[:, :, 0], domain.cy[:, :, 0], n_tor=n_tor, basis_tor="i" ) # load and analyze spectrum - omega2, U2_eig = xp.split(xp.load(spec_path), [1], axis=0) + omega2, U2_eig = np.split(np.load(spec_path), [1], axis=0) omega2 = omega2.flatten() m_range_alfven = [args.m_l_alfvén, args.m_u_alfvén] @@ -287,7 +282,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, fig.set_figheight(12) fig.set_figwidth(14) - etaplot = [xp.linspace(0.0, 1.0, 201), xp.linspace(0.0, 1.0, 101)] + etaplot = [np.linspace(0.0, 1.0, 201), np.linspace(0.0, 1.0, 101)] etaplot[0][0] += 1e-5 diff --git a/src/struphy/diagnostics/diagn_tools.py b/src/struphy/diagnostics/diagn_tools.py index e7a9d8ee3..dc50161fb 100644 --- a/src/struphy/diagnostics/diagn_tools.py +++ b/src/struphy/diagnostics/diagn_tools.py @@ -3,9 +3,9 @@ import shutil import subprocess -import cunumpy as xp import matplotlib.colors as colors import matplotlib.pyplot as plt +import numpy as np from scipy.fft import fftfreq, fftn from scipy.signal import argrelextrema from tqdm import tqdm @@ -37,7 +37,7 @@ def power_spectrum_2d( Parameters ---------- values : dict - Dictionary holding values of a B-spline FemField on the grid as 3d xp.arrays: + Dictionary holding values of a B-spline FemField on the grid as 3d np.arrays: values[n] contains the values at time step n, where n = 0:Nt-1:step with 0 0: @@ -162,9 +162,9 @@ def power_spectrum_2d( for n in range(fit_branches): omega_fit[n] = [] for k, f_of_omega in zip(kvec[k_start:k_end], dispersion[:, k_start:k_end].T): - threshold = xp.max(f_of_omega) * noise_level - extrms = argrelextrema(f_of_omega, xp.greater, order=extr_order)[0] - above_noise = xp.nonzero(f_of_omega > threshold)[0] + threshold = np.max(f_of_omega) * noise_level + extrms = argrelextrema(f_of_omega, np.greater, order=extr_order)[0] + above_noise = np.nonzero(f_of_omega > threshold)[0] intersec = list(set(extrms) & set(above_noise)) # intersec = list(set(extrms)) if not intersec: @@ -173,7 +173,7 @@ def power_spectrum_2d( # print(f"{intersec = }") # print(f"{[omega[intersec[n]] for n in range(fit_branches)]}") assert len(intersec) == fit_branches, ( - f"Number of found branches {len(intersec)} is not {fit_branches =}! \ + f"Number of found branches {len(intersec)} is not {fit_branches = }! \ Try to lower 'noise_level' or increase 'extr_order'." ) k_fit += [k] @@ -183,14 +183,14 @@ def power_spectrum_2d( # fit coeffs = [] for m, om in omega_fit.items(): - coeffs += [xp.polyfit(k_fit, om, deg=fit_degree[n])] - print(f"\nFitted {coeffs =}") + coeffs += [np.polyfit(k_fit, om, deg=fit_degree[n])] + print(f"\nFitted {coeffs = }") if do_plot: _, ax = plt.subplots(1, 1, figsize=(10, 10)) colormap = "plasma" - K, W = xp.meshgrid(kvec, omega) - lvls = xp.logspace(-15, -1, 27) + K, W = np.meshgrid(kvec, omega) + lvls = np.logspace(-15, -1, 27) disp_plot = ax.contourf( K, W, @@ -214,7 +214,7 @@ def power_spectrum_2d( def fun(k): out = k * 0.0 - for i, c in enumerate(xp.flip(cs)): + for i, c in enumerate(np.flip(cs)): out += c * k**i return out @@ -230,12 +230,12 @@ def fun(k): set_min = 0.0 set_max = 0.0 for key, branch in branches.items(): - vals = xp.real(branch) + vals = np.real(branch) ax.plot(kvec, vals, "--", label=key) - tmp = xp.min(vals) + tmp = np.min(vals) if tmp < set_min: set_min = tmp - tmp = xp.max(vals) + tmp = np.max(vals) if tmp > set_max: set_max = tmp @@ -331,8 +331,8 @@ def plot_scalars( plt.figure("en_tot_rel_err") plt.plot( time[1:], - xp.divide( - xp.abs(en_tot[1:] - en_tot[0]), + np.divide( + np.abs(en_tot[1:] - en_tot[0]), en_tot[0], ), ) @@ -363,9 +363,9 @@ def plot_scalars( for key, plot_quantity in plot_quantities.items(): # Get the indices of the extrema if do_fit: - inds_exs = argrelextrema(plot_quantity, xp.greater, order=order) + inds_exs = argrelextrema(plot_quantity, np.greater, order=order) elif fit_minima: - inds_exs = argrelextrema(plot_quantity, xp.less, order=order) + inds_exs = argrelextrema(plot_quantity, np.less, order=order) else: inds_exs = None @@ -376,10 +376,10 @@ def plot_scalars( # for plotting take a bit more time at start and end if len(inds_exs[0]) >= 2: - time_start_idx = xp.max( + time_start_idx = np.max( [0, 2 * inds_exs[0][start_extremum] - inds_exs[0][start_extremum + 1]], ) - time_end_idx = xp.min( + time_end_idx = np.min( [ len(time) - 1, 2 * inds_exs[0][start_extremum + no_extrema - 1] - inds_exs[0][start_extremum + no_extrema - 2], @@ -395,9 +395,9 @@ def plot_scalars( if inds_exs is not None: # do the fitting - coeffs = xp.polyfit( + coeffs = np.polyfit( times_extrema, - xp.log( + np.log( quantity_extrema, ), deg=degree, @@ -410,15 +410,15 @@ def plot_scalars( ) plt.plot( time_cut, - xp.exp(coeffs[0] * time_cut + coeffs[1]), - label=r"$a * \exp(m x)$ with" + f"\na={xp.round(xp.exp(coeffs[1]), 3)} m={xp.round(coeffs[0], 3)}", + np.exp(coeffs[0] * time_cut + coeffs[1]), + label=r"$a * \exp(m x)$ with" + f"\na={np.round(np.exp(coeffs[1]), 3)} m={np.round(coeffs[0], 3)}", ) else: plt.plot(time, plot_quantity[:], ".", label=key, markersize=2) if inds_exs is not None: # do the fitting - coeffs = xp.polyfit( + coeffs = np.polyfit( times_extrema, quantity_extrema, deg=degree, @@ -433,8 +433,8 @@ def plot_scalars( ) plt.plot( time_cut, - xp.exp(coeffs[0] * time_cut + coeffs[1]), - label=r"$a x + b$ with" + f"\na={xp.round(coeffs[1], 3)} b={xp.round(coeffs[0], 3)}", + np.exp(coeffs[0] * time_cut + coeffs[1]), + label=r"$a x + b$ with" + f"\na={np.round(coeffs[1], 3)} b={np.round(coeffs[0], 3)}", ) plt.legend() @@ -496,11 +496,11 @@ def plot_distr_fun( # load full distribution functions if filename == "f_binned.npy": - f = xp.load(filepath) + f = np.load(filepath) # load delta f elif filename == "delta_f_binned.npy": - delta_f = xp.load(filepath) + delta_f = np.load(filepath) assert f is not None, "No distribution function file found!" @@ -508,7 +508,7 @@ def plot_distr_fun( directions = folder.split("_") for direction in directions: grids += [ - xp.load( + np.load( os.path.join( subpath, "grid_" + direction + ".npy", @@ -519,8 +519,8 @@ def plot_distr_fun( # Get indices of where to plot in other directions grid_idxs = {} for k in range(f.ndim - 1): - grid_idxs[directions[k]] = xp.argmin( - xp.abs(grids[k] - grid_slices[directions[k]]), + grid_idxs[directions[k]] = np.argmin( + np.abs(grids[k] - grid_slices[directions[k]]), ) for k in range(f.ndim - 1): @@ -655,17 +655,17 @@ def plots_videos_2d( grid_idxs = {} for k in range(df_data.ndim - 1): direc = directions[k] - grid_idxs[direc] = xp.argmin( - xp.abs(grids[direc] - grid_slices[direc]), + grid_idxs[direc] = np.argmin( + np.abs(grids[direc] - grid_slices[direc]), ) - grid_1 = xp.load( + grid_1 = np.load( os.path.join( data_path, "grid_" + label_1 + ".npy", ), ) - grid_2 = xp.load( + grid_2 = np.load( os.path.join( data_path, "grid_" + label_2 + ".npy", @@ -683,7 +683,7 @@ def plots_videos_2d( df_binned = df_data[tuple(f_slicing)].squeeze() - assert t_grid.ndim == grid_1.ndim == grid_2.ndim == 1, "Input arrays must be 1D!" + assert t_grid.ndim == grid_1.ndim == grid_2.ndim == 1, f"Input arrays must be 1D!" assert df_binned.shape[0] == t_grid.size, f"{df_binned.shape =}, {t_grid.shape =}" assert df_binned.shape[1] == grid_1.size, f"{df_binned.shape =}, {grid_1.shape =}" assert df_binned.shape[2] == grid_2.size, f"{df_binned.shape =}, {grid_2.shape =}" @@ -696,9 +696,9 @@ def plots_videos_2d( var *= polar_params["r_max"] - polar_params["r_min"] var += polar_params["r_min"] elif polar_params["angular_coord"] == sl: - var *= 2 * xp.pi + var *= 2 * np.pi - grid_1_mesh, grid_2_mesh = xp.meshgrid(grid_1, grid_2, indexing="ij") + grid_1_mesh, grid_2_mesh = np.meshgrid(grid_1, grid_2, indexing="ij") if output == "video": plots_2d_video( @@ -745,7 +745,7 @@ def video_2d(slc, diagn_path, images_path): Parameters ---------- - t_grid : xp.ndarray + t_grid : np.ndarray 1D-array containing all the times grid_slices : dict @@ -833,15 +833,15 @@ def plots_2d_video( # Get parameters for time and labelling for it nt = len(t_grid) - log_nt = int(xp.log10(nt)) + 1 + log_nt = int(np.log10(nt)) + 1 len_dt = len(str(t_grid[1]).split(".")[1]) # Get the correct scale for the plots - vmin += [xp.min(df_binned[:]) / 3] - vmax += [xp.max(df_binned[:]) / 3] - vmin = xp.min(vmin) - vmax = xp.max(vmax) - vscale = xp.max(xp.abs([vmin, vmax])) + vmin += [np.min(df_binned[:]) / 3] + vmax += [np.max(df_binned[:]) / 3] + vmin = np.min(vmin) + vmax = np.max(vmax) + vscale = np.max(np.abs([vmin, vmax])) # Set up the figure and axis once if do_polar: @@ -939,18 +939,18 @@ def plots_2d_overview( fig_height = 8.5 else: n_cols = 3 - n_rows = int(xp.ceil(n_times / n_cols)) + n_rows = int(np.ceil(n_times / n_cols)) fig_height = 4 * n_rows fig_size = (4 * n_cols, fig_height) # Get the correct scale for the plots for time in times: - vmin += [xp.min(df_binned[time]) / 3] - vmax += [xp.max(df_binned[time]) / 3] - vmin = xp.min(vmin) - vmax = xp.max(vmax) - vscale = xp.max(xp.abs([vmin, vmax])) + vmin += [np.min(df_binned[time]) / 3] + vmax += [np.max(df_binned[time]) / 3] + vmin = np.min(vmin) + vmax = np.max(vmax) + vscale = np.max(np.abs([vmin, vmax])) # Plot options for polar plots subplot_kw = dict(projection="polar") if do_polar else None @@ -959,8 +959,8 @@ def plots_2d_overview( fig, axes = plt.subplots(n_rows, n_cols, figsize=fig_size, subplot_kw=subplot_kw) # So we an use .flatten() even for just 1 plot - if not isinstance(axes, xp.ndarray): - axes = xp.array([axes]) + if not isinstance(axes, np.ndarray): + axes = np.array([axes]) # fig.tight_layout(h_pad=5.0, w_pad=5.0) # fig.tight_layout(pad=5.0) @@ -976,7 +976,7 @@ def plots_2d_overview( # Set the suptitle fig.suptitle(f"Struphy model '{model_name}'") - for k in xp.arange(n_times): + for k in np.arange(n_times): obj = axes.flatten()[k] n = times[k] t = f"%.{len_dt}f" % t_grid[n] @@ -1048,13 +1048,13 @@ def get_slices_grids_directions_and_df_data(plot_full_f, grid_slices, data_path, slices_2d : list[string] A list of all the slicings - grids : list[xp.ndarray] + grids : list[np.ndarray] A list of all grids according to the slices directions : list[string] A list of the directions that appear in all slices - df_data : xp.ndarray + df_data : np.ndarray The data of delta-f (in case of full-f: distribution function minus background) """ @@ -1063,7 +1063,7 @@ def get_slices_grids_directions_and_df_data(plot_full_f, grid_slices, data_path, # Load all the grids grids = {} for direction in directions: - grids[direction] = xp.load( + grids[direction] = np.load( os.path.join(data_path, "grid_" + direction + ".npy"), ) @@ -1072,7 +1072,7 @@ def get_slices_grids_directions_and_df_data(plot_full_f, grid_slices, data_path, _name = "f_binned.npy" else: _name = "delta_f_binned.npy" - _data = xp.load(os.path.join(data_path, _name)) + _data = np.load(os.path.join(data_path, _name)) # Check how many slicings have been given and make slices_2d for all # combinations of spatial and velocity dimensions diff --git a/src/struphy/diagnostics/diagnostics_pic.ipynb b/src/struphy/diagnostics/diagnostics_pic.ipynb index f41425141..d4b2f2e0f 100644 --- a/src/struphy/diagnostics/diagnostics_pic.ipynb +++ b/src/struphy/diagnostics/diagnostics_pic.ipynb @@ -7,13 +7,11 @@ "outputs": [], "source": [ "import os\n", - "\n", + "import struphy\n", "import numpy as np\n", "from matplotlib import pyplot as plt\n", "\n", - "import struphy\n", - "\n", - "path_out = os.path.join(struphy.__path__[0], \"io/out\", \"sim_1\")\n", + "path_out = os.path.join(struphy.__path__[0], 'io/out', 'sim_1')\n", "\n", "print(path_out)\n", "os.listdir(path_out)" @@ -30,7 +28,7 @@ "metadata": {}, "outputs": [], "source": [ - "data_path = os.path.join(path_out, \"post_processing\")\n", + "data_path = os.path.join(path_out, 'post_processing')\n", "\n", "os.listdir(data_path)" ] @@ -41,7 +39,7 @@ "metadata": {}, "outputs": [], "source": [ - "t_grid = np.load(os.path.join(data_path, \"t_grid.npy\"))\n", + "t_grid = np.load(os.path.join(data_path, 't_grid.npy'))\n", "t_grid" ] }, @@ -51,7 +49,7 @@ "metadata": {}, "outputs": [], "source": [ - "f_path = os.path.join(data_path, \"kinetic_data\", \"ions\", \"distribution_function\")\n", + "f_path = os.path.join(data_path, 'kinetic_data', 'ions', 'distribution_function')\n", "\n", "print(os.listdir(f_path))" ] @@ -62,7 +60,7 @@ "metadata": {}, "outputs": [], "source": [ - "path = os.path.join(f_path, \"e1\")\n", + "path = os.path.join(f_path, 'e1')\n", "print(os.listdir(path))" ] }, @@ -72,9 +70,9 @@ "metadata": {}, "outputs": [], "source": [ - "grid = np.load(os.path.join(f_path, \"e1/\", \"grid_e1.npy\"))\n", - "f_binned = np.load(os.path.join(f_path, \"e1/\", \"f_binned.npy\"))\n", - "delta_f_e1_binned = np.load(os.path.join(f_path, \"e1/\", \"delta_f_binned.npy\"))\n", + "grid = np.load(os.path.join(f_path, 'e1/', 'grid_e1.npy'))\n", + "f_binned = np.load(os.path.join(f_path, 'e1/', 'f_binned.npy'))\n", + "delta_f_e1_binned = np.load(os.path.join(f_path, 'e1/', 'delta_f_binned.npy'))\n", "\n", "print(grid.shape)\n", "print(f_binned.shape)\n", @@ -89,18 +87,18 @@ "source": [ "steps = list(np.arange(10))\n", "\n", - "plt.figure(figsize=(12, 5 * len(steps)))\n", + "plt.figure(figsize=(12, 5*len(steps)))\n", "for n, step in enumerate(steps):\n", - " plt.subplot(len(steps), 2, 2 * n + 1)\n", - " plt.plot(grid, f_binned[step], label=f\"time = {t_grid[step]}\")\n", - " plt.xlabel(\"e1\")\n", - " # plt.ylim([.5, 1.5])\n", - " plt.title(\"full-f\")\n", - " plt.subplot(len(steps), 2, 2 * n + 2)\n", - " plt.plot(grid, delta_f_e1_binned[step], label=f\"time = {t_grid[step]}\")\n", - " plt.xlabel(\"e1\")\n", - " # plt.ylim([-3e-3, 3e-3])\n", - " plt.title(r\"$\\delta f$\")\n", + " plt.subplot(len(steps), 2, 2*n + 1)\n", + " plt.plot(grid, f_binned[step], label=f'time = {t_grid[step]}')\n", + " plt.xlabel('e1')\n", + " #plt.ylim([.5, 1.5])\n", + " plt.title('full-f')\n", + " plt.subplot(len(steps), 2, 2*n + 2)\n", + " plt.plot(grid, delta_f_e1_binned[step], label=f'time = {t_grid[step]}')\n", + " plt.xlabel('e1')\n", + " #plt.ylim([-3e-3, 3e-3])\n", + " plt.title(r'$\\delta f$')\n", " plt.legend()" ] }, @@ -110,7 +108,7 @@ "metadata": {}, "outputs": [], "source": [ - "path = os.path.join(f_path, \"e1_v1\")\n", + "path = os.path.join(f_path, 'e1_v1')\n", "print(os.listdir(path))" ] }, @@ -120,10 +118,10 @@ "metadata": {}, "outputs": [], "source": [ - "grid_e1 = np.load(os.path.join(f_path, \"e1_v1/\", \"grid_e1.npy\"))\n", - "grid_v1 = np.load(os.path.join(f_path, \"e1_v1/\", \"grid_v1.npy\"))\n", - "f_binned = np.load(os.path.join(f_path, \"e1_v1/\", \"f_binned.npy\"))\n", - "delta_f_binned = np.load(os.path.join(f_path, \"e1_v1/\", \"delta_f_binned.npy\"))\n", + "grid_e1 = np.load(os.path.join(f_path, 'e1_v1/', 'grid_e1.npy'))\n", + "grid_v1 = np.load(os.path.join(f_path, 'e1_v1/', 'grid_v1.npy'))\n", + "f_binned = np.load(os.path.join(f_path, 'e1_v1/', 'f_binned.npy'))\n", + "delta_f_binned = np.load(os.path.join(f_path, 'e1_v1/', 'delta_f_binned.npy'))\n", "\n", "print(grid_e1.shape)\n", "print(grid_v1.shape)\n", @@ -139,20 +137,20 @@ "source": [ "steps = list(np.arange(10))\n", "\n", - "plt.figure(figsize=(12, 5 * len(steps)))\n", + "plt.figure(figsize=(12, 5*len(steps)))\n", "for n, step in enumerate(steps):\n", - " plt.subplot(len(steps), 2, 2 * n + 1)\n", - " plt.pcolor(grid_e1, grid_v1, f_binned[step].T, label=f\"time = {t_grid[step]}\")\n", - " plt.xlabel(\"$e1$\")\n", - " plt.ylabel(r\"$v_\\parallel$\")\n", - " plt.title(\"full-f\")\n", + " plt.subplot(len(steps), 2, 2*n + 1)\n", + " plt.pcolor(grid_e1, grid_v1, f_binned[step].T, label=f'time = {t_grid[step]}')\n", + " plt.xlabel('$e1$')\n", + " plt.ylabel(r'$v_\\parallel$')\n", + " plt.title('full-f')\n", " plt.legend()\n", " plt.colorbar()\n", - " plt.subplot(len(steps), 2, 2 * n + 2)\n", - " plt.pcolor(grid_e1, grid_v1, delta_f_binned[step].T, label=f\"time = {t_grid[step]}\")\n", - " plt.xlabel(\"$e1$\")\n", - " plt.ylabel(r\"$v_\\parallel$\")\n", - " plt.title(r\"$\\delta f$\")\n", + " plt.subplot(len(steps), 2, 2*n + 2)\n", + " plt.pcolor(grid_e1, grid_v1, delta_f_binned[step].T, label=f'time = {t_grid[step]}')\n", + " plt.xlabel('$e1$')\n", + " plt.ylabel(r'$v_\\parallel$')\n", + " plt.title(r'$\\delta f$')\n", " plt.legend()\n", " plt.colorbar()" ] @@ -163,7 +161,7 @@ "metadata": {}, "outputs": [], "source": [ - "fields_path = os.path.join(data_path, \"fields_data\")\n", + "fields_path = os.path.join(data_path, 'fields_data')\n", "\n", "print(os.listdir(fields_path))" ] @@ -176,7 +174,7 @@ "source": [ "import pickle\n", "\n", - "with open(os.path.join(fields_path, \"grids_phy.bin\"), \"rb\") as file:\n", + "with open(os.path.join(fields_path, 'grids_phy.bin'), 'rb') as file:\n", " x_grid, y_grid, z_grid = pickle.load(file)\n", "\n", "print(type(x_grid))\n", @@ -189,7 +187,7 @@ "metadata": {}, "outputs": [], "source": [ - "with open(os.path.join(fields_path, \"em_fields\", \"phi_phy.bin\"), \"rb\") as file:\n", + "with open(os.path.join(fields_path, 'em_fields', 'phi_phy.bin'), 'rb') as file:\n", " phi = pickle.load(file)\n", "\n", "plt.figure(figsize=(12, 12))\n", @@ -199,9 +197,9 @@ " t = t_grid[step]\n", " print(phi[t][0].shape)\n", " plt.subplot(2, 2, n + 1)\n", - " plt.plot(x_grid[:, 0, 0], phi[t][0][:, 0, 0], label=f\"time = {t}\")\n", - " plt.xlabel(\"x\")\n", - " plt.ylabel(r\"$\\phi$(x)\")\n", + " plt.plot(x_grid[:, 0, 0], phi[t][0][:, 0, 0], label=f'time = {t}')\n", + " plt.xlabel('x')\n", + " plt.ylabel(r'$\\phi$(x)')\n", " plt.legend()" ] }, diff --git a/src/struphy/diagnostics/paraview/mesh_creator.py b/src/struphy/diagnostics/paraview/mesh_creator.py index 4bf83211c..aad81c3ba 100644 --- a/src/struphy/diagnostics/paraview/mesh_creator.py +++ b/src/struphy/diagnostics/paraview/mesh_creator.py @@ -1,5 +1,6 @@ +import numpy as np + # from tqdm import tqdm -import cunumpy as xp import vtkmodules.all as vtk from vtkmodules.util.numpy_support import numpy_to_vtk as np2vtk from vtkmodules.util.numpy_support import vtk_to_numpy as vtk2np @@ -37,12 +38,7 @@ def make_ugrid_and_write_vtu(filename: str, writer, vtk_dir, gvec, s_range, u_ra point_data = {} cell_data = {} vtk_points, suv_points, xyz_points, point_indices = gen_vtk_points( - gvec, - s_range, - u_range, - v_range, - point_data, - cell_data, + gvec, s_range, u_range, v_range, point_data, cell_data ) print("vtk_points.GetNumberOfPoints()", vtk_points.GetNumberOfPoints(), flush=True) @@ -85,43 +81,43 @@ def gen_vtk_points(gvec, s_range, u_range, v_range, point_data, cell_data): pt_idx = 0 vtk_points = vtk.vtkPoints() - suv_points = xp.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0], 3)) - xyz_points = xp.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0], 3)) - point_indices = xp.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0]), dtype=xp.int_) + suv_points = np.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0], 3)) + xyz_points = np.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0], 3)) + point_indices = np.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0]), dtype=np.int_) # Add metadata to grid. num_pts = s_range.shape[0] * u_range.shape[0] * v_range.shape[0] - point_data["s"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["u"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["v"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["x"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["y"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["z"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["theta"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["zeta"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["Point ID"] = xp.zeros(num_pts, dtype=xp.int_) - point_data["pressure"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["phi"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["chi"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["iota"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["q"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["det"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["det/(2pi)^2"] = xp.zeros(num_pts, dtype=xp.float_) - point_data["A"] = xp.zeros((num_pts, 3), dtype=xp.float_) - point_data["A_vec"] = xp.zeros((num_pts, 3), dtype=xp.float_) - point_data["A_1"] = xp.zeros((num_pts, 3), dtype=xp.float_) - point_data["A_2"] = xp.zeros((num_pts, 3), dtype=xp.float_) - point_data["B"] = xp.zeros((num_pts, 3), dtype=xp.float_) - point_data["B_vec"] = xp.zeros((num_pts, 3), dtype=xp.float_) - point_data["B_1"] = xp.zeros((num_pts, 3), dtype=xp.float_) - point_data["B_2"] = xp.zeros((num_pts, 3), dtype=xp.float_) + point_data["s"] = np.zeros(num_pts, dtype=np.float_) + point_data["u"] = np.zeros(num_pts, dtype=np.float_) + point_data["v"] = np.zeros(num_pts, dtype=np.float_) + point_data["x"] = np.zeros(num_pts, dtype=np.float_) + point_data["y"] = np.zeros(num_pts, dtype=np.float_) + point_data["z"] = np.zeros(num_pts, dtype=np.float_) + point_data["theta"] = np.zeros(num_pts, dtype=np.float_) + point_data["zeta"] = np.zeros(num_pts, dtype=np.float_) + point_data["Point ID"] = np.zeros(num_pts, dtype=np.int_) + point_data["pressure"] = np.zeros(num_pts, dtype=np.float_) + point_data["phi"] = np.zeros(num_pts, dtype=np.float_) + point_data["chi"] = np.zeros(num_pts, dtype=np.float_) + point_data["iota"] = np.zeros(num_pts, dtype=np.float_) + point_data["q"] = np.zeros(num_pts, dtype=np.float_) + point_data["det"] = np.zeros(num_pts, dtype=np.float_) + point_data["det/(2pi)^2"] = np.zeros(num_pts, dtype=np.float_) + point_data["A"] = np.zeros((num_pts, 3), dtype=np.float_) + point_data["A_vec"] = np.zeros((num_pts, 3), dtype=np.float_) + point_data["A_1"] = np.zeros((num_pts, 3), dtype=np.float_) + point_data["A_2"] = np.zeros((num_pts, 3), dtype=np.float_) + point_data["B"] = np.zeros((num_pts, 3), dtype=np.float_) + point_data["B_vec"] = np.zeros((num_pts, 3), dtype=np.float_) + point_data["B_1"] = np.zeros((num_pts, 3), dtype=np.float_) + point_data["B_2"] = np.zeros((num_pts, 3), dtype=np.float_) # pbar = tqdm(total=num_pts) for s_idx, s in enumerate(s_range): for u_idx, u in enumerate(u_range): for v_idx, v in enumerate(v_range): point = gvec.f(s, u, v) - suv_points[s_idx, u_idx, v_idx, :] = xp.array([s, u, v]) + suv_points[s_idx, u_idx, v_idx, :] = np.array([s, u, v]) xyz_points[s_idx, u_idx, v_idx, :] = point point_indices[s_idx, u_idx, v_idx] = pt_idx vtk_points.InsertPoint(pt_idx, point) @@ -153,10 +149,10 @@ def gen_vtk_points(gvec, s_range, u_range, v_range, point_data, cell_data): pt_idx += 1 # pbar.close() - point_data["theta"] = 2 * xp.pi * point_data["u"] - point_data["zeta"] = 2 * xp.pi * point_data["v"] + point_data["theta"] = 2 * np.pi * point_data["u"] + point_data["zeta"] = 2 * np.pi * point_data["v"] point_data["q"] = 1 / point_data["iota"] - point_data["det/(2pi)^2"] = point_data["det"] / (2 * xp.pi) ** 2 + point_data["det/(2pi)^2"] = point_data["det"] / (2 * np.pi) ** 2 return vtk_points, suv_points, xyz_points, point_indices @@ -316,4 +312,4 @@ def connect_cell(s_range, u_range, v_range, point_indices, ugrid, point_data, ce cell_data["Cell ID"].append(cell_idx) cell_idx += 1 - cell_data["Cell ID"] = xp.array(cell_data["Cell ID"], dtype=xp.int_) + cell_data["Cell ID"] = np.array(cell_data["Cell ID"], dtype=np.int_) diff --git a/src/struphy/dispersion_relations/analytic.py b/src/struphy/dispersion_relations/analytic.py index a54355428..f68ca5774 100644 --- a/src/struphy/dispersion_relations/analytic.py +++ b/src/struphy/dispersion_relations/analytic.py @@ -1,6 +1,6 @@ "Analytic dispersion relations." -import cunumpy as xp +import numpy as np from numpy.polynomial import Polynomial from scipy.optimize import fsolve @@ -108,18 +108,18 @@ def __call__(self, k): Bsquare = self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2 # Alfvén velocity and speed of sound - vA = xp.sqrt(Bsquare / self.params["n0"]) + vA = np.sqrt(Bsquare / self.params["n0"]) - cS = xp.sqrt(self.params["gamma"] * self.params["p0"] / self.params["n0"]) + cS = np.sqrt(self.params["gamma"] * self.params["p0"] / self.params["n0"]) # shear Alfvén branch - self._branches["shear Alfvén"] = vA * k * self.params["B0z"] / xp.sqrt(Bsquare) + self._branches["shear Alfvén"] = vA * k * self.params["B0z"] / np.sqrt(Bsquare) # slow/fast magnetosonic branch delta = (4 * self.params["B0z"] ** 2 * cS**2 * vA**2) / ((cS**2 + vA**2) ** 2 * Bsquare) - self._branches["slow magnetosonic"] = xp.sqrt(1 / 2 * k**2 * (cS**2 + vA**2) * (1 - xp.sqrt(1 - delta))) - self._branches["fast magnetosonic"] = xp.sqrt(1 / 2 * k**2 * (cS**2 + vA**2) * (1 + xp.sqrt(1 - delta))) + self._branches["slow magnetosonic"] = np.sqrt(1 / 2 * k**2 * (cS**2 + vA**2) * (1 - np.sqrt(1 - delta))) + self._branches["fast magnetosonic"] = np.sqrt(1 / 2 * k**2 * (cS**2 + vA**2) * (1 + np.sqrt(1 - delta))) return self.branches @@ -186,14 +186,14 @@ def __call__(self, k): Bsquare = self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2 - cos_theta = self.params["B0z"] / xp.sqrt(Bsquare) + cos_theta = self.params["B0z"] / np.sqrt(Bsquare) # Alfvén velocity, speed of sound and cyclotron frequency - vA = xp.sqrt(Bsquare / self.params["n0"]) + vA = np.sqrt(Bsquare / self.params["n0"]) - cS = xp.sqrt(self.params["gamma"] * self.params["p0"] / self.params["n0"]) + cS = np.sqrt(self.params["gamma"] * self.params["p0"] / self.params["n0"]) - Omega_i = xp.sqrt(Bsquare) / self.params["eps"] + Omega_i = np.sqrt(Bsquare) / self.params["eps"] # auxiliary functions def omega_0(k): @@ -218,7 +218,7 @@ def discriminant(k): ) # solve - out = xp.zeros((k.size, 4), dtype=complex) + out = np.zeros((k.size, 4), dtype=complex) for i, ki in enumerate(k): p0 = Polynomial([-(omega_0(ki) ** 2), 1.0]) p1 = Polynomial([d(ki), c(ki), b(ki), 1.0]) @@ -261,15 +261,7 @@ class FluidSlabITG(DispersionRelations1D): def __init__(self, vstar=10.0, vi=1.0, Z=1.0, kz=1.0, gamma=5 / 3): super().__init__( - "wave 1", - "wave 2", - "wave 3", - velocity_scale="thermal", - vstar=vstar, - vi=vi, - Z=Z, - kz=kz, - gamma=gamma, + "wave 1", "wave 2", "wave 3", velocity_scale="thermal", vstar=vstar, vi=vi, Z=Z, kz=kz, gamma=gamma ) def __call__(self, k): @@ -310,7 +302,7 @@ def discriminant(k): return -4.0 * p**3 - 27.0 * q(k) ** 2 # solve - out = xp.zeros((k.size, 3), dtype=complex) + out = np.zeros((k.size, 3), dtype=complex) for i, ki in enumerate(k): poly = Polynomial([q(ki), p, 0.0, 1.0]) out[i] = poly.roots() @@ -350,17 +342,17 @@ def __call__(self, kvec): # One complex array for each branch tmps = [] for n in range(self.nbranches): - tmps += [xp.zeros_like(kvec, dtype=complex)] + tmps += [np.zeros_like(kvec, dtype=complex)] ########### Model specific part ############################## # angle between k and magnetic field if self.params["B0z"] == 0: - theta = xp.pi / 2 + theta = np.pi / 2 else: - theta = xp.arctan(xp.sqrt(self.params["B0x"] ** 2 + self.params["B0y"] ** 2) / self.params["B0z"]) + theta = np.arctan(np.sqrt(self.params["B0x"] ** 2 + self.params["B0y"] ** 2) / self.params["B0z"]) print(theta) - cos2 = xp.cos(theta) ** 2 + cos2 = np.cos(theta) ** 2 neq = self.params["n0"] @@ -401,10 +393,10 @@ def __call__(self, kvec): e = eps6 # determinant in polynomial form - det = xp.polynomial.Polynomial([a, b, c, d, e]) + det = np.polynomial.Polynomial([a, b, c, d, e]) # solutions - sol = xp.sqrt(xp.abs(det.roots())) + sol = np.sqrt(np.abs(det.roots())) # Ion-cyclotron branch tmps[0][n] = sol[0] # Electron-cyclotron branch @@ -497,7 +489,7 @@ def __init__(self, **params): ee = 1.602176634e-19 # calculate coupling parameter alpha_c from bulk number density and mass number - self._kappa = ee * xp.sqrt(mu * self.params["Ab"] * self.params["nb"] * 1e20 / mp) + self._kappa = ee * np.sqrt(mu * self.params["Ab"] * self.params["nb"] * 1e20 / mp) def __call__(self, k, method="newton", tol=1e-10, max_it=100): """ @@ -526,7 +518,7 @@ def __call__(self, k, method="newton", tol=1e-10, max_it=100): # One complex array for each branch tmps = [] for _ in range(self.nbranches): - tmps += [xp.zeros_like(k, dtype=complex)] + tmps += [np.zeros_like(k, dtype=complex)] ########### Model specific part ############################## @@ -540,8 +532,8 @@ def __call__(self, k, method="newton", tol=1e-10, max_it=100): wR = [self.params["B0"] * ki, 0.0] wL = [self.params["B0"] * ki, 0.0] else: - wR = [xp.real(tmps[0][i - 1]), xp.imag(tmps[0][i - 1])] - wL = [xp.real(tmps[1][i - 1]), xp.imag(tmps[1][i - 1])] + wR = [np.real(tmps[0][i - 1]), np.imag(tmps[0][i - 1])] + wL = [np.real(tmps[1][i - 1]), np.imag(tmps[1][i - 1])] # apply solver if method == "newton": @@ -550,13 +542,13 @@ def __call__(self, k, method="newton", tol=1e-10, max_it=100): Dr, Di = self.D_RL(wR, ki, +1) - while xp.abs(Dr + Di * 1j) > tol or counter == max_it: + while np.abs(Dr + Di * 1j) > tol or counter == max_it: # derivative Drp, Dip = self.D_RL(wR, ki, +1, 1) # update - wR[0] = wR[0] - xp.real((Dr + Di * 1j) / (Drp + Dip * 1j)) - wR[1] = wR[1] - xp.imag((Dr + Di * 1j) / (Drp + Dip * 1j)) + wR[0] = wR[0] - np.real((Dr + Di * 1j) / (Drp + Dip * 1j)) + wR[1] = wR[1] - np.imag((Dr + Di * 1j) / (Drp + Dip * 1j)) Dr, Di = self.D_RL(wR, ki, +1) counter += 1 @@ -566,13 +558,13 @@ def __call__(self, k, method="newton", tol=1e-10, max_it=100): Dr, Di = self.D_RL(wL, ki, -1) - while xp.abs(Dr + Di * 1j) > tol or counter == max_it: + while np.abs(Dr + Di * 1j) > tol or counter == max_it: # derivative Drp, Dip = self.D_RL(wL, ki, -1, 1) # update - wL[0] = wL[0] - xp.real((Dr + Di * 1j) / (Drp + Dip * 1j)) - wL[1] = wL[1] - xp.imag((Dr + Di * 1j) / (Drp + Dip * 1j)) + wL[0] = wL[0] - np.real((Dr + Di * 1j) / (Drp + Dip * 1j)) + wL[1] = wL[1] - np.imag((Dr + Di * 1j) / (Drp + Dip * 1j)) Dr, Di = self.D_RL(wL, ki, -1) counter += 1 @@ -659,7 +651,7 @@ def D_RL(self, w, k, pol, der=0): * (Zplasma(xi, 0) + (w - k * v0) * Zplasma(xi, 1) * xip) ) - return xp.real(out), xp.imag(out) + return np.real(out), np.imag(out) class PressureCouplingFull6DParallel(DispersionRelations1D): @@ -731,7 +723,7 @@ def __call__(self, k, tol=1e-10): # One complex array for each branch tmps = [] for n in range(self.nbranches): - tmps += [xp.zeros_like(k, dtype=complex)] + tmps += [np.zeros_like(k, dtype=complex)] ########### Model specific part ############################## @@ -743,9 +735,9 @@ def __call__(self, k, tol=1e-10): wL = [1 * ki, 0.0] # TODO: use vA wS = [1 * ki, 0.0] # TODO: use cS else: - wR = [xp.real(tmps[0][i - 1]), xp.imag(tmps[0][i - 1])] - wL = [xp.real(tmps[1][i - 1]), xp.imag(tmps[1][i - 1])] - wS = [xp.real(tmps[2][i - 1]), xp.imag(tmps[2][i - 1])] + wR = [np.real(tmps[0][i - 1]), np.imag(tmps[0][i - 1])] + wL = [np.real(tmps[1][i - 1]), np.imag(tmps[1][i - 1])] + wS = [np.real(tmps[2][i - 1]), np.imag(tmps[2][i - 1])] # R/L shear Alfvén wave sol_R = fsolve(self.D_RL, x0=wR, args=(ki, +1), xtol=tol) @@ -804,8 +796,8 @@ def D_RL(self, w, k, pol): vperp = 1.0 # TODO vth = 1.0 - vA = xp.sqrt((self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2) / self.params["n0"]) - # cS = xp.sqrt(self.params['beta']*vA) + vA = np.sqrt((self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2) / self.params["n0"]) + # cS = np.sqrt(self.params['beta']*vA) cS = 1.0 a0 = u0 / vpara # TODO @@ -848,7 +840,7 @@ def D_RL(self, w, k, pol): ) ) - return xp.real(c1), xp.imag(c1) + return np.real(c1), np.imag(c1) def D_sonic(self, w, k): r""" @@ -881,8 +873,8 @@ def D_sonic(self, w, k): vperp = 1.0 # TODO vth = 1.0 - vA = xp.sqrt((self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2) / self.params["n0"]) - # cS = xp.sqrt(self.params['beta']*vA) + vA = np.sqrt((self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2) / self.params["n0"]) + # cS = np.sqrt(self.params['beta']*vA) cS = 1.0 a0 = u0 / vpara # TODO @@ -893,7 +885,7 @@ def D_sonic(self, w, k): c1 = w**2 - k**2 * cS**2 + 2 * w * k * nu * vpara * x4 - return xp.real(c1), xp.imag(c1) + return np.real(c1), np.imag(c1) # private methods: # ---------------- @@ -1022,11 +1014,11 @@ def __call__(self, x, m, n): specs = {} # shear Alfvén continuum - specs["shear_Alfvén"] = xp.sqrt(F(x, m, n) ** 2 / rho(x)) + specs["shear_Alfvén"] = np.sqrt(F(x, m, n) ** 2 / rho(x)) # slow sound continuum - specs["slow_sound"] = xp.sqrt( - gamma * p(x) * F(x, m, n) ** 2 / (rho(x) * (gamma * p(x) + By(x) ** 2 + Bz(x) ** 2)), + specs["slow_sound"] = np.sqrt( + gamma * p(x) * F(x, m, n) ** 2 / (rho(x) * (gamma * p(x) + By(x) ** 2 + Bz(x) ** 2)) ) return specs @@ -1129,11 +1121,11 @@ def __call__(self, r, m, n): specs = {} # shear Alfvén continuum - specs["shear_Alfvén"] = xp.sqrt(F(r, m, n) ** 2 / rho(r)) + specs["shear_Alfvén"] = np.sqrt(F(r, m, n) ** 2 / rho(r)) # slow sound continuum - specs["slow_sound"] = xp.sqrt( - gamma * p(r) * F(r, m, n) ** 2 / (rho(r) * (gamma * p(r) + Bt(r) ** 2 + Bz(r) ** 2)), + specs["slow_sound"] = np.sqrt( + gamma * p(r) * F(r, m, n) ** 2 / (rho(r) * (gamma * p(r) + Bt(r) ** 2 + Bz(r) ** 2)) ) return specs diff --git a/src/struphy/dispersion_relations/base.py b/src/struphy/dispersion_relations/base.py index 6994ae2fb..1700955c2 100644 --- a/src/struphy/dispersion_relations/base.py +++ b/src/struphy/dispersion_relations/base.py @@ -2,7 +2,7 @@ from abc import ABCMeta, abstractmethod -import cunumpy as xp +import numpy as np from matplotlib import pyplot as plt @@ -99,18 +99,18 @@ def plot(self, k): plt.ylabel(rf"Im($\omega$) [{unit_om}]") for name, omega in self.branches.items(): plt.subplot(2, 1, 1) - plt.plot(k, xp.real(omega), label=name) + plt.plot(k, np.real(omega), label=name) plt.subplot(2, 1, 2) - plt.plot(k, xp.imag(omega), label=name) + plt.plot(k, np.imag(omega), label=name) plt.subplot(2, 1, 1) for lab, kc in self.k_crit.items(): - if kc > xp.min(k) and kc < xp.max(k): + if kc > np.min(k) and kc < np.max(k): plt.axvline(kc, color="k", linestyle="--", linewidth=0.5, label=lab) plt.legend() plt.subplot(2, 1, 2) for lab, kc in self.k_crit.items(): - if kc > xp.min(k) and kc < xp.max(k): + if kc > np.min(k) and kc < np.max(k): plt.axvline(kc, color="k", linestyle="--", linewidth=0.5, label=lab) diff --git a/src/struphy/dispersion_relations/utilities.py b/src/struphy/dispersion_relations/utilities.py index b796eb321..40c2b57f0 100644 --- a/src/struphy/dispersion_relations/utilities.py +++ b/src/struphy/dispersion_relations/utilities.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np from scipy.special import erfi @@ -23,7 +23,7 @@ def Zplasma(xi, der=0): assert der == 0 or der == 1, 'Parameter "der" must be either 0 or 1' if der == 0: - z = xp.sqrt(xp.pi) * xp.exp(-(xi**2)) * (1j - erfi(xi)) + z = np.sqrt(np.pi) * np.exp(-(xi**2)) * (1j - erfi(xi)) else: z = -2 * (1 + xi * Zplasma(xi, 0)) diff --git a/src/struphy/eigenvalue_solvers/derivatives.py b/src/struphy/eigenvalue_solvers/derivatives.py index 0e34cceb1..122dade81 100644 --- a/src/struphy/eigenvalue_solvers/derivatives.py +++ b/src/struphy/eigenvalue_solvers/derivatives.py @@ -6,7 +6,7 @@ Modules to assemble discrete derivatives. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa @@ -31,7 +31,7 @@ def grad_1d_matrix(spl_kind, NbaseN): NbaseD = NbaseN - 1 + spl_kind - grad = xp.zeros((NbaseD, NbaseN), dtype=float) + grad = np.zeros((NbaseD, NbaseN), dtype=float) for i in range(NbaseD): grad[i, i] = -1.0 @@ -79,9 +79,9 @@ def discrete_derivatives_3d(space): grad_1d_3 = 0 * spa.identity(1, format="csr") else: if space.basis_tor == "r": - grad_1d_3 = 2 * xp.pi * space.n_tor * spa.csr_matrix(xp.array([[0.0, 1.0], [-1.0, 0.0]])) + grad_1d_3 = 2 * np.pi * space.n_tor * spa.csr_matrix(np.array([[0.0, 1.0], [-1.0, 0.0]])) else: - grad_1d_3 = 1j * 2 * xp.pi * space.n_tor * spa.identity(1, format="csr") + grad_1d_3 = 1j * 2 * np.pi * space.n_tor * spa.identity(1, format="csr") # standard tensor-product derivatives if space.ck == -1: diff --git a/src/struphy/eigenvalue_solvers/kernels_projectors_global.py b/src/struphy/eigenvalue_solvers/kernels_projectors_global.py index f01cefc1c..4f6c01392 100644 --- a/src/struphy/eigenvalue_solvers/kernels_projectors_global.py +++ b/src/struphy/eigenvalue_solvers/kernels_projectors_global.py @@ -24,13 +24,7 @@ def kernel_int_2d(nq1: "int", nq2: "int", w1: "float[:]", w2: "float[:]", mat_f: # ========= kernel for integration in 3d ================== def kernel_int_3d( - nq1: "int", - nq2: "int", - nq3: "int", - w1: "float[:]", - w2: "float[:]", - w3: "float[:]", - mat_f: "float[:,:,:]", + nq1: "int", nq2: "int", nq3: "int", w1: "float[:]", w2: "float[:]", w3: "float[:]", mat_f: "float[:,:,:]" ) -> "float": f_loc = 0.0 @@ -53,11 +47,7 @@ def kernel_int_3d( # ========= kernel for integration along eta1 direction, reducing to a 2d array ============================ def kernel_int_2d_eta1( - subs1: "int[:]", - subs_cum1: "int[:]", - w1: "float[:,:]", - mat_f: "float[:,:,:]", - f_int: "float[:,:]", + subs1: "int[:]", subs_cum1: "int[:]", w1: "float[:,:]", mat_f: "float[:,:,:]", f_int: "float[:,:]" ): n1, n2 = shape(f_int) @@ -76,11 +66,7 @@ def kernel_int_2d_eta1( # ========= kernel for integration along eta2 direction, reducing to a 2d array ============================ def kernel_int_2d_eta2( - subs2: "int[:]", - subs_cum2: "int[:]", - w2: "float[:,:]", - mat_f: "float[:,:,:]", - f_int: "float[:,:]", + subs2: "int[:]", subs_cum2: "int[:]", w2: "float[:,:]", mat_f: "float[:,:,:]", f_int: "float[:,:]" ): n1, n2 = shape(f_int) @@ -181,11 +167,7 @@ def kernel_int_2d_eta1_eta2_old(w1: "float[:,:]", w2: "float[:,:]", mat_f: "floa # ========= kernel for integration along eta1 direction, reducing to a 3d array ============================ def kernel_int_3d_eta1( - subs1: "int[:]", - subs_cum1: "int[:]", - w1: "float[:,:]", - mat_f: "float[:,:,:,:]", - f_int: "float[:,:,:]", + subs1: "int[:]", subs_cum1: "int[:]", w1: "float[:,:]", mat_f: "float[:,:,:,:]", f_int: "float[:,:,:]" ): n1, n2, n3 = shape(f_int) @@ -204,11 +186,7 @@ def kernel_int_3d_eta1( def kernel_int_3d_eta1_transpose( - subs1: "int[:]", - subs_cum1: "int[:]", - w1: "float[:,:]", - f_int: "float[:,:,:]", - mat_f: "float[:,:,:,:]", + subs1: "int[:]", subs_cum1: "int[:]", w1: "float[:,:]", f_int: "float[:,:,:]", mat_f: "float[:,:,:,:]" ): n1, n2, n3 = shape(f_int) @@ -227,11 +205,7 @@ def kernel_int_3d_eta1_transpose( # ========= kernel for integration along eta2 direction, reducing to a 3d array ============================ def kernel_int_3d_eta2( - subs2: "int[:]", - subs_cum2: "int[:]", - w2: "float[:,:]", - mat_f: "float[:,:,:,:]", - f_int: "float[:,:,:]", + subs2: "int[:]", subs_cum2: "int[:]", w2: "float[:,:]", mat_f: "float[:,:,:,:]", f_int: "float[:,:,:]" ): n1, n2, n3 = shape(f_int) @@ -250,11 +224,7 @@ def kernel_int_3d_eta2( def kernel_int_3d_eta2_transpose( - subs2: "int[:]", - subs_cum2: "int[:]", - w2: "float[:,:]", - f_int: "float[:,:,:]", - mat_f: "float[:,:,:,:]", + subs2: "int[:]", subs_cum2: "int[:]", w2: "float[:,:]", f_int: "float[:,:,:]", mat_f: "float[:,:,:,:]" ): n1, n2, n3 = shape(f_int) @@ -273,11 +243,7 @@ def kernel_int_3d_eta2_transpose( # ========= kernel for integration along eta3 direction, reducing to a 3d array ============================ def kernel_int_3d_eta3( - subs3: "int[:]", - subs_cum3: "int[:]", - w3: "float[:,:]", - mat_f: "float[:,:,:,:]", - f_int: "float[:,:,:]", + subs3: "int[:]", subs_cum3: "int[:]", w3: "float[:,:]", mat_f: "float[:,:,:,:]", f_int: "float[:,:,:]" ): n1, n2, n3 = shape(f_int) @@ -296,11 +262,7 @@ def kernel_int_3d_eta3( def kernel_int_3d_eta3_transpose( - subs3: "int[:]", - subs_cum3: "int[:]", - w3: "float[:,:]", - f_int: "float[:,:,:]", - mat_f: "float[:,:,:,:]", + subs3: "int[:]", subs_cum3: "int[:]", w3: "float[:,:]", f_int: "float[:,:,:]", mat_f: "float[:,:,:,:]" ): n1, n2, n3 = shape(f_int) @@ -693,11 +655,7 @@ def kernel_int_3d_eta1_eta2_eta3_transpose( # ========= kernel for integration in eta1-eta2-eta3 cell, reducing to a 3d array ======================= def kernel_int_3d_eta1_eta2_eta3_old( - w1: "float[:,:]", - w2: "float[:,:]", - w3: "float[:,:]", - mat_f: "float[:,:,:,:,:,:]", - f_int: "float[:,:,:]", + w1: "float[:,:]", w2: "float[:,:]", w3: "float[:,:]", mat_f: "float[:,:,:,:,:,:]", f_int: "float[:,:,:]" ): ne1, nq1, ne2, nq2, ne3, nq3 = shape(mat_f) diff --git a/src/struphy/eigenvalue_solvers/legacy/MHD_eigenvalues_cylinder_1D.py b/src/struphy/eigenvalue_solvers/legacy/MHD_eigenvalues_cylinder_1D.py index a16b44e3d..4d2bd5fa2 100644 --- a/src/struphy/eigenvalue_solvers/legacy/MHD_eigenvalues_cylinder_1D.py +++ b/src/struphy/eigenvalue_solvers/legacy/MHD_eigenvalues_cylinder_1D.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np import scipy as sc import scipy.sparse as spa import scipy.special as sp @@ -21,7 +21,7 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ r = lambda eta: a * eta # jacobian for integration - jac = lambda eta1: a * xp.ones(eta1.shape, dtype=float) + jac = lambda eta1: a * np.ones(eta1.shape, dtype=float) # ========================== kinetic energy functional ============================== # integrands (multiplied by -2/omega**2) @@ -46,11 +46,11 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ # Bspline_A = Bsp.Bspline(splines.T, splines.p ) # Bspline_B = Bsp.Bspline(splines.t, splines.p - 1) # - # K_11_scipy = xp.zeros((splines.NbaseN, splines.NbaseN), dtype=float) - # K_22_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # K_33_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # K_23_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # K_32_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # K_11_scipy = np.zeros((splines.NbaseN, splines.NbaseN), dtype=float) + # K_22_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # K_33_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # K_23_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # K_32_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) # # for i in range(1, Bspline_A.N - 1): # for j in range(1, Bspline_A.N - 1): @@ -76,11 +76,11 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ # integrand = lambda eta : a*K_ZV(eta)*Bspline_B(eta, i)*Bspline_B(eta, j) # K_32_scipy[i, j] = integrate.quad(integrand, 0., 1.)[0] - # assert xp.allclose(K_11.toarray(), K_11_scipy[1:-1, 1:-1]) - # assert xp.allclose(K_22.toarray(), K_22_scipy ) - # assert xp.allclose(K_33.toarray(), K_33_scipy[bcZ:, bcZ:]) - # assert xp.allclose(K_23.toarray(), K_23_scipy[ : , bcZ:]) - # assert xp.allclose(K_32.toarray(), K_32_scipy[bcZ:, :]) + # assert np.allclose(K_11.toarray(), K_11_scipy[1:-1, 1:-1]) + # assert np.allclose(K_22.toarray(), K_22_scipy ) + # assert np.allclose(K_33.toarray(), K_33_scipy[bcZ:, bcZ:]) + # assert np.allclose(K_23.toarray(), K_23_scipy[ : , bcZ:]) + # assert np.allclose(K_32.toarray(), K_32_scipy[bcZ:, :]) # ========================== potential energy functional =========================== # integrands (multiplied by 2) @@ -120,17 +120,17 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ ) W_dXZ = lambda eta: B_phi(r(eta)) * gamma * m * p(r(eta)) / r(eta) ** 2 + B_z(r(eta)) * gamma * k * p(r(eta)) / r( - eta, + eta ) W_ZdX = lambda eta: B_phi(r(eta)) * gamma * m * p(r(eta)) / r(eta) ** 2 + B_z(r(eta)) * gamma * k * p(r(eta)) / r( - eta, + eta ) W_VZ = lambda eta: B_phi(r(eta)) * gamma * m**2 * p(r(eta)) / r(eta) ** 2 + B_z(r(eta)) * gamma * k * m * p( - r(eta), + r(eta) ) / r(eta) W_ZV = lambda eta: B_phi(r(eta)) * gamma * m**2 * p(r(eta)) / r(eta) ** 2 + B_z(r(eta)) * gamma * k * m * p( - r(eta), + r(eta) ) / r(eta) # compute matrices @@ -163,15 +163,15 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ # return W_22 ## test correct computation - # W_11_scipy = xp.zeros((splines.NbaseN, splines.NbaseN), dtype=float) - # W_22_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # W_33_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # W_12_scipy = xp.zeros((splines.NbaseN, splines.NbaseD), dtype=float) - # W_21_scipy = xp.zeros((splines.NbaseD, splines.NbaseN), dtype=float) - # W_13_scipy = xp.zeros((splines.NbaseN, splines.NbaseD), dtype=float) - # W_31_scipy = xp.zeros((splines.NbaseD, splines.NbaseN), dtype=float) - # W_23_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # W_32_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # W_11_scipy = np.zeros((splines.NbaseN, splines.NbaseN), dtype=float) + # W_22_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # W_33_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # W_12_scipy = np.zeros((splines.NbaseN, splines.NbaseD), dtype=float) + # W_21_scipy = np.zeros((splines.NbaseD, splines.NbaseN), dtype=float) + # W_13_scipy = np.zeros((splines.NbaseN, splines.NbaseD), dtype=float) + # W_31_scipy = np.zeros((splines.NbaseD, splines.NbaseN), dtype=float) + # W_23_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # W_32_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) # # for i in range(1, Bspline_A.N - 1): # for j in range(1, Bspline_A.N - 1): @@ -187,15 +187,15 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ # integrand = lambda eta : W_XdX(eta) * Bspline_A(eta, i, 0) * Bspline_A(eta, j, 1) # W_11_scipy[i, j] += integrate.quad(integrand, 0., 1.)[0] # - # assert xp.allclose(W_11.toarray(), W_11_scipy[1:-1, 1:-1]) + # assert np.allclose(W_11.toarray(), W_11_scipy[1:-1, 1:-1]) - # print(xp.allclose(K, K.T)) - # print(xp.allclose(W, W.T)) + # print(np.allclose(K, K.T)) + # print(np.allclose(W, W.T)) # solve eigenvalue problem omega**2*K*xi = W*xi - A = xp.linalg.inv(K).dot(W) + A = np.linalg.inv(K).dot(W) - omega2, XVZ_eig = xp.linalg.eig(A) + omega2, XVZ_eig = np.linalg.eig(A) # extract components X_eig = XVZ_eig[: (splines.NbaseN - 2), :] @@ -203,11 +203,11 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ Z_eig = XVZ_eig[(splines.NbaseN - 2 + splines.NbaseD) :, :] # add boundary conditions X(0) = X(1) = 0 - X_eig = xp.vstack((xp.zeros(X_eig.shape[1], dtype=float), X_eig, xp.zeros(X_eig.shape[1], dtype=float))) + X_eig = np.vstack((np.zeros(X_eig.shape[1], dtype=float), X_eig, np.zeros(X_eig.shape[1], dtype=float))) # add boundary condition Z(0) = 0 if bcZ == 1: - Z_eig = xp.vstack((xp.zeros(Z_eig.shape[1], dtype=float), Z_eig)) + Z_eig = np.vstack((np.zeros(Z_eig.shape[1], dtype=float), Z_eig)) return omega2, X_eig, V_eig, Z_eig @@ -225,43 +225,43 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, # components of metric tensor and Jacobian determinant G_r = a**2 - G_phi = lambda eta: 4 * xp.pi**2 * r(eta) ** 2 - G_z = 4 * xp.pi**2 * R0**2 - J = lambda eta: 4 * xp.pi**2 * R0 * a * r(eta) + G_phi = lambda eta: 4 * np.pi**2 * r(eta) ** 2 + G_z = 4 * np.pi**2 * R0**2 + J = lambda eta: 4 * np.pi**2 * R0 * a * r(eta) # 2-from components of equilibrium magnetic field and its projection - B2_phi = lambda eta: 2 * xp.pi * R0 * a * B_phi(r(eta)) - B2_z = lambda eta: 2 * xp.pi * a * r(eta) * B_z(r(eta)) + B2_phi = lambda eta: 2 * np.pi * R0 * a * B_phi(r(eta)) + B2_z = lambda eta: 2 * np.pi * a * r(eta) * B_z(r(eta)) - b2_eq_phi = xp.linalg.solve(proj.D.toarray(), proj.rhs_1(B2_phi)) - b2_eq_z = xp.append(xp.array([0.0]), xp.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(B2_z)[1:])) + b2_eq_phi = np.linalg.solve(proj.D.toarray(), proj.rhs_1(B2_phi)) + b2_eq_z = np.append(np.array([0.0]), np.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(B2_z)[1:])) # 3-form components of equilibrium density and pessure and its projection Rho3 = lambda eta: J(eta) * Rho(r(eta)) P3 = lambda eta: J(eta) * P(r(eta)) - rho3_eq = xp.append(xp.array([0.0]), xp.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(Rho3)[1:])) - p3_eq = xp.append(xp.array([0.0]), xp.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(P3)[1:])) + rho3_eq = np.append(np.array([0.0]), np.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(Rho3)[1:])) + p3_eq = np.append(np.array([0.0]), np.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(P3)[1:])) # 2-form components of initial velocity and its projection U2_r = lambda eta: J(eta) * eta * (1 - eta) u2_r = proj.pi_0(U2_r) - u2_phi = -1 / (2 * xp.pi * m) * GRAD_all.dot(u2_r) - u2_z = xp.zeros(len(u2_phi), dtype=float) + u2_phi = -1 / (2 * np.pi * m) * GRAD_all.dot(u2_r) + u2_z = np.zeros(len(u2_phi), dtype=float) - b2_r = xp.zeros(len(u2_r), dtype=float) - b2_phi = xp.zeros(len(u2_phi), dtype=float) - b2_z = xp.zeros(len(u2_z), dtype=float) + b2_r = np.zeros(len(u2_r), dtype=float) + b2_phi = np.zeros(len(u2_phi), dtype=float) + b2_z = np.zeros(len(u2_z), dtype=float) - p3 = xp.zeros(len(u2_z), dtype=float) + p3 = np.zeros(len(u2_z), dtype=float) # projection matrices pi0_N_i, pi0_D_i, pi1_N_i, pi1_D_i = proj.projection_matrices_1d_reduced() pi0_NN_i, pi0_DN_i, pi0_ND_i, pi0_DD_i, pi1_NN_i, pi1_DN_i, pi1_ND_i, pi1_DD_i = proj.projection_matrices_1d() # 1D collocation matrices for interpolation in format (point, global basis function) - x_int = xp.copy(proj.x_int) + x_int = np.copy(proj.x_int) kind_splines = [False, True] @@ -270,35 +270,29 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, # 1D integration sub-intervals, quadrature points and weights if splines.p % 2 == 0: - x_his = xp.union1d(x_int, splines.el_b) + x_his = np.union1d(x_int, splines.el_b) else: - x_his = xp.copy(x_int) + x_his = np.copy(x_int) pts, wts = bsp.quadrature_grid(x_his, proj.pts_loc, proj.wts_loc) # compute number of sub-intervals for integrations (even degree) if splines.p % 2 == 0: - subs = 2 * xp.ones(proj.pts.shape[0], dtype=int) + subs = 2 * np.ones(proj.pts.shape[0], dtype=int) subs[: splines.p // 2] = 1 subs[-splines.p // 2 :] = 1 # compute number of sub-intervals for integrations (odd degree) else: - subs = xp.ones(proj.pts.shape[0], dtype=int) + subs = np.ones(proj.pts.shape[0], dtype=int) # evaluate basis functions on quadrature points in format (interval, local quad. point, global basis function) basis_his_N = bsp.collocation_matrix(splines.T, splines.p, pts.flatten(), False, normalize=kind_splines[0]).reshape( - pts.shape[0], - pts.shape[1], - splines.NbaseN, + pts.shape[0], pts.shape[1], splines.NbaseN ) basis_his_D = bsp.collocation_matrix( - splines.t, - splines.p - 1, - pts.flatten(), - False, - normalize=kind_splines[1], + splines.t, splines.p - 1, pts.flatten(), False, normalize=kind_splines[1] ).reshape(pts.shape[0], pts.shape[1], splines.NbaseD) # shift first interpolation point away from pole @@ -314,26 +308,26 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, M2_z = mass.get_M1(splines, mapping=lambda eta: J(eta) / G_z).toarray() # === matrices for curl of equilibrium field (with integration by parts) ========== - MB_12_eq = xp.empty((splines.NbaseN, splines.NbaseD), dtype=float) - MB_13_eq = xp.empty((splines.NbaseN, splines.NbaseD), dtype=float) + MB_12_eq = np.empty((splines.NbaseN, splines.NbaseD), dtype=float) + MB_13_eq = np.empty((splines.NbaseN, splines.NbaseD), dtype=float) - MB_21_eq = xp.empty((splines.NbaseD, splines.NbaseN), dtype=float) - MB_31_eq = xp.empty((splines.NbaseD, splines.NbaseN), dtype=float) + MB_21_eq = np.empty((splines.NbaseD, splines.NbaseN), dtype=float) + MB_31_eq = np.empty((splines.NbaseD, splines.NbaseN), dtype=float) - f_phi = xp.linalg.inv(proj.N.toarray()).T.dot(GRAD_all.T.dot(M2_phi.dot(b2_eq_phi))) - f_z = xp.linalg.inv(proj.N.toarray()).T.dot(GRAD_all.T.dot(M2_z.dot(b2_eq_z))) + f_phi = np.linalg.inv(proj.N.toarray()).T.dot(GRAD_all.T.dot(M2_phi.dot(b2_eq_phi))) + f_z = np.linalg.inv(proj.N.toarray()).T.dot(GRAD_all.T.dot(M2_z.dot(b2_eq_z))) - pi0_ND_phi = xp.empty(pi0_ND_i[3].max() + 1, dtype=float) - pi0_ND_z = xp.empty(pi0_ND_i[3].max() + 1, dtype=float) + pi0_ND_phi = np.empty(pi0_ND_i[3].max() + 1, dtype=float) + pi0_ND_z = np.empty(pi0_ND_i[3].max() + 1, dtype=float) - row_ND = xp.empty(pi0_ND_i[3].max() + 1, dtype=int) - col_ND = xp.empty(pi0_ND_i[3].max() + 1, dtype=int) + row_ND = np.empty(pi0_ND_i[3].max() + 1, dtype=int) + col_ND = np.empty(pi0_ND_i[3].max() + 1, dtype=int) - pi0_DN_phi = xp.empty(pi0_DN_i[3].max() + 1, dtype=float) - pi0_DN_z = xp.empty(pi0_DN_i[3].max() + 1, dtype=float) + pi0_DN_phi = np.empty(pi0_DN_i[3].max() + 1, dtype=float) + pi0_DN_z = np.empty(pi0_DN_i[3].max() + 1, dtype=float) - row_DN = xp.empty(pi0_DN_i[3].max() + 1, dtype=int) - col_DN = xp.empty(pi0_DN_i[3].max() + 1, dtype=int) + row_DN = np.empty(pi0_DN_i[3].max() + 1, dtype=int) + col_DN = np.empty(pi0_DN_i[3].max() + 1, dtype=int) ker.rhs0_f_1d(pi0_ND_i, basis_int_N, basis_int_D, 1 / J(x_int), f_phi, pi0_ND_phi, row_ND, col_ND) ker.rhs0_f_1d(pi0_ND_i, basis_int_N, basis_int_D, 1 / J(x_int), f_z, pi0_ND_z, row_ND, col_ND) @@ -354,23 +348,23 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, MB_31_eq[:, :] = -pi0_DN_z # === matrices for curl of equilibrium field (without integration by parts) ====== - MB_12_eq = xp.empty((splines.NbaseN, splines.NbaseD), dtype=float) - MB_13_eq = xp.empty((splines.NbaseN, splines.NbaseD), dtype=float) + MB_12_eq = np.empty((splines.NbaseN, splines.NbaseD), dtype=float) + MB_13_eq = np.empty((splines.NbaseN, splines.NbaseD), dtype=float) - MB_21_eq = xp.empty((splines.NbaseD, splines.NbaseN), dtype=float) - MB_31_eq = xp.empty((splines.NbaseD, splines.NbaseN), dtype=float) + MB_21_eq = np.empty((splines.NbaseD, splines.NbaseN), dtype=float) + MB_31_eq = np.empty((splines.NbaseD, splines.NbaseN), dtype=float) - cN = xp.empty(splines.NbaseN, dtype=float) - cD = xp.empty(splines.NbaseD, dtype=float) + cN = np.empty(splines.NbaseN, dtype=float) + cD = np.empty(splines.NbaseD, dtype=float) for j in range(splines.NbaseD): cD[:] = 0.0 cD[j] = 1.0 integrand2 = ( - lambda eta: splines.evaluate_D(eta, cD) / J(eta) * 2 * xp.pi * a * (B_phi(r(eta)) + r(eta) * dB_phi(r(eta))) + lambda eta: splines.evaluate_D(eta, cD) / J(eta) * 2 * np.pi * a * (B_phi(r(eta)) + r(eta) * dB_phi(r(eta))) ) - integrand3 = lambda eta: splines.evaluate_D(eta, cD) / J(eta) * 2 * xp.pi * a * R0 * dB_z(r(eta)) + integrand3 = lambda eta: splines.evaluate_D(eta, cD) / J(eta) * 2 * np.pi * a * R0 * dB_z(r(eta)) MB_12_eq[:, j] = inner.inner_prod_V0(splines, integrand2) MB_13_eq[:, j] = inner.inner_prod_V0(splines, integrand3) @@ -380,39 +374,39 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, cN[j] = 1.0 integrand2 = ( - lambda eta: splines.evaluate_N(eta, cN) / J(eta) * 2 * xp.pi * a * (B_phi(r(eta)) + r(eta) * dB_phi(r(eta))) + lambda eta: splines.evaluate_N(eta, cN) / J(eta) * 2 * np.pi * a * (B_phi(r(eta)) + r(eta) * dB_phi(r(eta))) ) - integrand3 = lambda eta: splines.evaluate_N(eta, cN) / J(eta) * 2 * xp.pi * a * R0 * dB_z(r(eta)) + integrand3 = lambda eta: splines.evaluate_N(eta, cN) / J(eta) * 2 * np.pi * a * R0 * dB_z(r(eta)) MB_21_eq[:, j] = inner.inner_prod_V1(splines, integrand2) MB_31_eq[:, j] = inner.inner_prod_V1(splines, integrand3) # ===== right-hand sides of projection matrices =============== - rhs0_N_phi = xp.empty(pi0_N_i[0].size, dtype=float) - rhs0_N_z = xp.empty(pi0_N_i[0].size, dtype=float) + rhs0_N_phi = np.empty(pi0_N_i[0].size, dtype=float) + rhs0_N_z = np.empty(pi0_N_i[0].size, dtype=float) - rhs1_D_phi = xp.empty(pi1_D_i[0].size, dtype=float) - rhs1_D_z = xp.empty(pi1_D_i[0].size, dtype=float) + rhs1_D_phi = np.empty(pi1_D_i[0].size, dtype=float) + rhs1_D_z = np.empty(pi1_D_i[0].size, dtype=float) - rhs0_N_pr = xp.empty(pi0_N_i[0].size, dtype=float) - rhs1_D_pr = xp.empty(pi1_D_i[0].size, dtype=float) + rhs0_N_pr = np.empty(pi0_N_i[0].size, dtype=float) + rhs1_D_pr = np.empty(pi1_D_i[0].size, dtype=float) - rhs0_N_rho = xp.empty(pi0_N_i[0].size, dtype=float) - rhs1_D_rho = xp.empty(pi1_D_i[0].size, dtype=float) + rhs0_N_rho = np.empty(pi0_N_i[0].size, dtype=float) + rhs1_D_rho = np.empty(pi1_D_i[0].size, dtype=float) # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, b2_eq_phi)/J(x_int), rhs0_N_phi) # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, b2_eq_z )/J(x_int), rhs0_N_z ) # - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), b2_eq_z )/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_z) - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), b2_eq_phi)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_phi) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), b2_eq_z )/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_z) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), b2_eq_phi)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_phi) # # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, p3_eq)/J(x_int), rhs0_N_pr) - # temp = xp.empty(pi0_N_i[0].size, dtype=float) + # temp = np.empty(pi0_N_i[0].size, dtype=float) # temp[:] = rhs0_N_pr - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), p3_eq)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_pr) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), p3_eq)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_pr) # # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, rho3)/J(x_int), rhs0_N_rho) - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), rho3)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_rho) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), rho3)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_rho) ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, B2_phi(x_int) / J(x_int), rhs0_N_phi) ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, B2_z(x_int) / J(x_int), rhs0_N_z) @@ -421,7 +415,7 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, pi1_D_i[0], pi1_D_i[1], subs, - xp.append(0, xp.cumsum(subs - 1)[:-1]), + np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (B2_phi(pts.flatten()) / J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), @@ -432,20 +426,20 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, pi1_D_i[0], pi1_D_i[1], subs, - xp.append(0, xp.cumsum(subs - 1)[:-1]), + np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (B2_z(pts.flatten()) / J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_z, ) - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, xp.ones(pts.shape, dtype=float), rhs1_D_z) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, np.ones(pts.shape, dtype=float), rhs1_D_z) ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, P3(x_int) / J(x_int), rhs0_N_pr) ker.rhs1_1d( pi1_D_i[0], pi1_D_i[1], subs, - xp.append(0, xp.cumsum(subs - 1)[:-1]), + np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (P3(pts.flatten()) / J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), @@ -457,7 +451,7 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, pi1_D_i[0], pi1_D_i[1], subs, - xp.append(0, xp.cumsum(subs - 1)[:-1]), + np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (Rho3(pts.flatten()) / J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), @@ -465,14 +459,12 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, ) rhs0_N_phi = spa.csr_matrix( - (rhs0_N_phi, (pi0_N_i[0], pi0_N_i[1])), - shape=(splines.NbaseN, splines.NbaseN), + (rhs0_N_phi, (pi0_N_i[0], pi0_N_i[1])), shape=(splines.NbaseN, splines.NbaseN) ).toarray() rhs0_N_z = spa.csr_matrix((rhs0_N_z, (pi0_N_i[0], pi0_N_i[1])), shape=(splines.NbaseN, splines.NbaseN)).toarray() rhs1_D_phi = spa.csr_matrix( - (rhs1_D_phi, (pi1_D_i[0], pi1_D_i[1])), - shape=(splines.NbaseD, splines.NbaseD), + (rhs1_D_phi, (pi1_D_i[0], pi1_D_i[1])), shape=(splines.NbaseD, splines.NbaseD) ).toarray() rhs1_D_z = spa.csr_matrix((rhs1_D_z, (pi1_D_i[0], pi1_D_i[1])), shape=(splines.NbaseD, splines.NbaseD)).toarray() @@ -482,144 +474,142 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, rhs1_D_pr = spa.csr_matrix((rhs1_D_pr, (pi1_D_i[0], pi1_D_i[1])), shape=(splines.NbaseD, splines.NbaseD)).toarray() rhs0_N_rho = spa.csr_matrix( - (rhs0_N_rho, (pi0_N_i[0], pi0_N_i[1])), - shape=(splines.NbaseN, splines.NbaseN), + (rhs0_N_rho, (pi0_N_i[0], pi0_N_i[1])), shape=(splines.NbaseN, splines.NbaseN) ).toarray() rhs1_D_rho = spa.csr_matrix( - (rhs1_D_rho, (pi1_D_i[0], pi1_D_i[1])), - shape=(splines.NbaseD, splines.NbaseD), + (rhs1_D_rho, (pi1_D_i[0], pi1_D_i[1])), shape=(splines.NbaseD, splines.NbaseD) ).toarray() - pi0_N_phi = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_phi[1:-1, 1:-1]) - pi0_N_z = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_z[1:-1, 1:-1]) + pi0_N_phi = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_phi[1:-1, 1:-1]) + pi0_N_z = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_z[1:-1, 1:-1]) - pi1_D_phi = xp.linalg.inv(proj.D.toarray()).dot(rhs1_D_phi) - pi1_D_z = xp.linalg.inv(proj.D.toarray()).dot(rhs1_D_z) + pi1_D_phi = np.linalg.inv(proj.D.toarray()).dot(rhs1_D_phi) + pi1_D_z = np.linalg.inv(proj.D.toarray()).dot(rhs1_D_z) - pi0_N_pr = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_pr[1:-1, 1:-1]) - pi1_D_pr = xp.linalg.inv(proj.D.toarray()).dot(rhs1_D_pr) + pi0_N_pr = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_pr[1:-1, 1:-1]) + pi1_D_pr = np.linalg.inv(proj.D.toarray()).dot(rhs1_D_pr) - pi0_N_rho = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_rho[1:-1, 1:-1]) - pi1_D_rho = xp.linalg.inv(proj.D.toarray()).dot(rhs1_D_rho) + pi0_N_rho = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_rho[1:-1, 1:-1]) + pi1_D_rho = np.linalg.inv(proj.D.toarray()).dot(rhs1_D_rho) # ======= matrices in strong induction equation ================ # 11 block - I_11 = -2 * xp.pi * m * pi0_N_phi - 2 * xp.pi * n * pi0_N_z + I_11 = -2 * np.pi * m * pi0_N_phi - 2 * np.pi * n * pi0_N_z # 21 block and 31 block I_21 = -GRAD.dot(pi0_N_phi) I_31 = -GRAD.dot(pi0_N_z) # 22 block and 32 block - I_22 = 2 * xp.pi * n * pi1_D_z - I_32 = -2 * xp.pi * m * pi1_D_z + I_22 = 2 * np.pi * n * pi1_D_z + I_32 = -2 * np.pi * m * pi1_D_z # 23 block and 33 block - I_23 = -2 * xp.pi * n * pi1_D_phi - I_33 = 2 * xp.pi * m * pi1_D_phi + I_23 = -2 * np.pi * n * pi1_D_phi + I_33 = 2 * np.pi * m * pi1_D_phi # total - I_all = xp.block( + I_all = np.block( [ - [I_11, xp.zeros((len(u2_r) - 2, len(u2_phi))), xp.zeros((len(u2_r) - 2, len(u2_z) - 1))], + [I_11, np.zeros((len(u2_r) - 2, len(u2_phi))), np.zeros((len(u2_r) - 2, len(u2_z) - 1))], [I_21, I_22, I_23[:, 1:]], [I_31[1:, :], I_32[1:, :], I_33[1:, 1:]], - ], + ] ) # ======= matrices in strong pressure equation ================ P_1 = -GRAD.dot(pi0_N_pr) - (gamma - 1) * pi1_D_pr.dot(GRAD) - P_2 = -2 * xp.pi * m * gamma * pi1_D_pr - P_3 = -2 * xp.pi * n * gamma * pi1_D_pr + P_2 = -2 * np.pi * m * gamma * pi1_D_pr + P_3 = -2 * np.pi * n * gamma * pi1_D_pr - P_all = xp.block([[P_1[1:, :], P_2[1:, :], P_3[1:, 1:]]]) + P_all = np.block([[P_1[1:, :], P_2[1:, :], P_3[1:, 1:]]]) # ========== matrices in weak momentum balance equation ====== A_1 = 1 / 2 * (pi0_N_rho.T.dot(M2_r) + M2_r.dot(pi0_N_rho)) A_2 = 1 / 2 * (pi1_D_rho.T.dot(M2_phi) + M2_phi.dot(pi1_D_rho)) A_3 = 1 / 2 * (pi1_D_rho.T.dot(M2_z) + M2_z.dot(pi1_D_rho))[:, :] - A_all = xp.block( + A_all = np.block( [ - [A_1, xp.zeros((A_1.shape[0], A_2.shape[1])), xp.zeros((A_1.shape[0], A_3.shape[1]))], - [xp.zeros((A_2.shape[0], A_1.shape[1])), A_2, xp.zeros((A_2.shape[0], A_3.shape[1]))], - [xp.zeros((A_3.shape[0], A_1.shape[1])), xp.zeros((A_3.shape[0], A_2.shape[1])), A_3], - ], + [A_1, np.zeros((A_1.shape[0], A_2.shape[1])), np.zeros((A_1.shape[0], A_3.shape[1]))], + [np.zeros((A_2.shape[0], A_1.shape[1])), A_2, np.zeros((A_2.shape[0], A_3.shape[1]))], + [np.zeros((A_3.shape[0], A_1.shape[1])), np.zeros((A_3.shape[0], A_2.shape[1])), A_3], + ] ) - MB_11 = 2 * xp.pi * n * pi0_N_z.T.dot(M2_r) + 2 * xp.pi * m * pi0_N_phi.T.dot(M2_r) + MB_11 = 2 * np.pi * n * pi0_N_z.T.dot(M2_r) + 2 * np.pi * m * pi0_N_phi.T.dot(M2_r) MB_12 = pi0_N_phi.T.dot(GRAD.T.dot(M2_phi)) - MB_12_eq[1:-1, :] MB_13 = pi0_N_z.T.dot(GRAD.T.dot(M2_z)) - MB_13_eq[1:-1, :] MB_14 = GRAD.T.dot(M3) MB_21 = MB_21_eq[:, 1:-1] - MB_22 = -2 * xp.pi * n * pi1_D_z.T.dot(M2_phi) - MB_23 = 2 * xp.pi * m * pi1_D_z.T.dot(M2_z) - MB_24 = 2 * xp.pi * m * M3 + MB_22 = -2 * np.pi * n * pi1_D_z.T.dot(M2_phi) + MB_23 = 2 * np.pi * m * pi1_D_z.T.dot(M2_z) + MB_24 = 2 * np.pi * m * M3 MB_31 = MB_31_eq[:, 1:-1] - MB_32 = 2 * xp.pi * n * pi1_D_phi.T.dot(M2_phi) - MB_33 = -2 * xp.pi * m * pi1_D_phi.T.dot(M2_z) - MB_34 = 2 * xp.pi * n * M3 + MB_32 = 2 * np.pi * n * pi1_D_phi.T.dot(M2_phi) + MB_33 = -2 * np.pi * m * pi1_D_phi.T.dot(M2_z) + MB_34 = 2 * np.pi * n * M3 - MB_b_all = xp.block( - [[MB_11, MB_12, MB_13[:, 1:]], [MB_21, MB_22, MB_23[:, 1:]], [MB_31[1:, :], MB_32[1:, :], MB_33[1:, 1:]]], + MB_b_all = np.block( + [[MB_11, MB_12, MB_13[:, 1:]], [MB_21, MB_22, MB_23[:, 1:]], [MB_31[1:, :], MB_32[1:, :], MB_33[1:, 1:]]] ) - MB_p_all = xp.block([[MB_14[:, 1:]], [MB_24[:, 1:]], [MB_34[1:, 1:]]]) + MB_p_all = np.block([[MB_14[:, 1:]], [MB_24[:, 1:]], [MB_34[1:, 1:]]]) ## ======= matrices in strong induction equation ================ ## 11 block - # I_11 = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(-2*xp.pi*m*rhs0_N_phi[1:-1, 1:-1] - 2*xp.pi*n*rhs0_N_z[1:-1, 1:-1]) + # I_11 = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(-2*np.pi*m*rhs0_N_phi[1:-1, 1:-1] - 2*np.pi*n*rhs0_N_z[1:-1, 1:-1]) # ## 21 block and 31 block - # I_21 = -GRAD[: , 1:-1].dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_phi[1:-1, 1:-1])) - # I_31 = -GRAD[1:, 1:-1].dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_z[1:-1, 1:-1])) + # I_21 = -GRAD[: , 1:-1].dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_phi[1:-1, 1:-1])) + # I_31 = -GRAD[1:, 1:-1].dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_z[1:-1, 1:-1])) # ## 22 block and 32 block - # I_22 = 2*xp.pi*n*xp.linalg.inv(proj.D.toarray()[ :, :]).dot(rhs1_D_z[ :, :]) - # I_32 = -2*xp.pi*m*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_z[1:, :]) + # I_22 = 2*np.pi*n*np.linalg.inv(proj.D.toarray()[ :, :]).dot(rhs1_D_z[ :, :]) + # I_32 = -2*np.pi*m*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_z[1:, :]) # ## 23 block and 33 block - # I_23 = -2*xp.pi*n*xp.linalg.inv(proj.D.toarray()[ :, :]).dot(rhs1_D_phi[ :, 1:]) - # I_33 = 2*xp.pi*m*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_phi[1:, 1:]) + # I_23 = -2*np.pi*n*np.linalg.inv(proj.D.toarray()[ :, :]).dot(rhs1_D_phi[ :, 1:]) + # I_33 = 2*np.pi*m*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_phi[1:, 1:]) # # ## ======= matrices in strong pressure equation ================ - # P_1 = -GRAD[1:, 1:-1].dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_pr[1:-1, 1:-1])) - (gamma - 1)*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, :].dot(GRAD[:, 1:-1])) - # P_2 = -2*xp.pi*m*gamma*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, :]) - # P_3 = -2*xp.pi*n*gamma*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, 1:]) + # P_1 = -GRAD[1:, 1:-1].dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_pr[1:-1, 1:-1])) - (gamma - 1)*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, :].dot(GRAD[:, 1:-1])) + # P_2 = -2*np.pi*m*gamma*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, :]) + # P_3 = -2*np.pi*n*gamma*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, 1:]) # # ## ========== matrices in weak momentum balance equation ====== - # rhs0_N_rho = xp.empty(pi0_N_i[0].size, dtype=float) + # rhs0_N_rho = np.empty(pi0_N_i[0].size, dtype=float) # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, rho3)/J(x_int), rhs0_N_rho) # # - # rhs1_D_rho = xp.empty(pi1_D_i[0].size, dtype=float) - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), rho3)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_rho) + # rhs1_D_rho = np.empty(pi1_D_i[0].size, dtype=float) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), rho3)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_rho) # # # - # A_1 = 1/2*(rhs0_N_rho[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + M2_r[1:-1, 1:-1].dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_rho[1:-1, 1:-1]))) - # A_2 = 1/2*(rhs1_D_rho.T.dot(xp.linalg.inv(proj.D.toarray()[:, :]).T.dot(M2_phi)) + M2_phi.dot(xp.linalg.inv(proj.D.toarray()[:, :]).dot(rhs1_D_rho))) - # A_3 = 1/2*(rhs1_D_rho[1:, 1:].T.dot(xp.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) + M2_z[1:, 1:].dot(xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_rho[1:, 1:]))) + # A_1 = 1/2*(rhs0_N_rho[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + M2_r[1:-1, 1:-1].dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_rho[1:-1, 1:-1]))) + # A_2 = 1/2*(rhs1_D_rho.T.dot(np.linalg.inv(proj.D.toarray()[:, :]).T.dot(M2_phi)) + M2_phi.dot(np.linalg.inv(proj.D.toarray()[:, :]).dot(rhs1_D_rho))) + # A_3 = 1/2*(rhs1_D_rho[1:, 1:].T.dot(np.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) + M2_z[1:, 1:].dot(np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_rho[1:, 1:]))) # # - # MB_11 = 2*xp.pi*n*rhs0_N_z[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + 2*xp.pi*m*rhs0_N_phi[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + # MB_11 = 2*np.pi*n*rhs0_N_z[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + 2*np.pi*m*rhs0_N_phi[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) # - # MB_12 = rhs0_N_phi[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(GRAD[:, 1:-1].T.dot(M2_phi))) - # MB_13 = rhs0_N_z[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(GRAD[1:, 1:-1].T.dot(M2_z[1:, 1:]))) + # MB_12 = rhs0_N_phi[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(GRAD[:, 1:-1].T.dot(M2_phi))) + # MB_13 = rhs0_N_z[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(GRAD[1:, 1:-1].T.dot(M2_z[1:, 1:]))) # # MB_14 = GRAD[1:, 1:-1].T.dot(M3[1:, 1:]) # # - # MB_22 = -2*xp.pi*n*rhs1_D_z.T.dot(xp.linalg.inv(proj.D.toarray()).T.dot(M2_phi)) - # MB_23 = 2*xp.pi*m*rhs1_D_z[1:, :].T.dot(xp.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) - # MB_24 = 2*xp.pi*m*M3[ :, 1:] + # MB_22 = -2*np.pi*n*rhs1_D_z.T.dot(np.linalg.inv(proj.D.toarray()).T.dot(M2_phi)) + # MB_23 = 2*np.pi*m*rhs1_D_z[1:, :].T.dot(np.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) + # MB_24 = 2*np.pi*m*M3[ :, 1:] # - # MB_32 = 2*xp.pi*n*rhs1_D_phi[:, 1:].T.dot(xp.linalg.inv(proj.D.toarray()).T.dot(M2_phi)) - # MB_33 = -2*xp.pi*m*rhs1_D_phi[1:, 1:].T.dot(xp.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) - # MB_34 = 2*xp.pi*n*M3[1:, 1:] + # MB_32 = 2*np.pi*n*rhs1_D_phi[:, 1:].T.dot(np.linalg.inv(proj.D.toarray()).T.dot(M2_phi)) + # MB_33 = -2*np.pi*m*rhs1_D_phi[1:, 1:].T.dot(np.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) + # MB_34 = 2*np.pi*n*M3[1:, 1:] # # # ==== matrices in eigenvalue problem ======== @@ -635,17 +625,17 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, W_32 = MB_32.dot(I_22) + MB_33.dot(I_32) + MB_34.dot(P_2) W_33 = MB_32.dot(I_23) + MB_33.dot(I_33) + MB_34.dot(P_3) - # W = xp.block([[W_11, W_12, W_13[:, 1:]], [W_21, W_22, W_23[:, 1:]], [W_31[1:, :], W_32[1:, :], W_33[1:, 1:]]]) - W = xp.block([[W_11, W_12, W_13[:, :]], [W_21, W_22, W_23[:, :]], [W_31[:, :], W_32[:, :], W_33[:, :]]]) + # W = np.block([[W_11, W_12, W_13[:, 1:]], [W_21, W_22, W_23[:, 1:]], [W_31[1:, :], W_32[1:, :], W_33[1:, 1:]]]) + W = np.block([[W_11, W_12, W_13[:, :]], [W_21, W_22, W_23[:, :]], [W_31[:, :], W_32[:, :], W_33[:, :]]]) - # print(xp.allclose(K, K.T)) - # print(xp.allclose(W, W.T)) + # print(np.allclose(K, K.T)) + # print(np.allclose(W, W.T)) # solve eigenvalue problem omega**2*K*xi = W*xi - MAT = xp.linalg.inv(-A_all).dot(W) + MAT = np.linalg.inv(-A_all).dot(W) - omega2, XYZ_eig = xp.linalg.eig(MAT) - # omega2, XYZ_eig = xp.linalg.eig(xp.linalg.inv(-A_all).dot(MB_b_all.dot(I_all) + MB_p_all.dot(P_all))) + omega2, XYZ_eig = np.linalg.eig(MAT) + # omega2, XYZ_eig = np.linalg.eig(np.linalg.inv(-A_all).dot(MB_b_all.dot(I_all) + MB_p_all.dot(P_all))) # extract components X_eig = XYZ_eig[: (splines.NbaseN - 2), :] @@ -653,71 +643,71 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, Z_eig = XYZ_eig[(splines.NbaseN - 2 + splines.NbaseD) :, :] # add boundary conditions X(0) = X(1) = 0 - X_eig = xp.vstack((xp.zeros(X_eig.shape[1], dtype=float), X_eig, xp.zeros(X_eig.shape[1], dtype=float))) + X_eig = np.vstack((np.zeros(X_eig.shape[1], dtype=float), X_eig, np.zeros(X_eig.shape[1], dtype=float))) # add boundary condition Z(0) = 0 - Z_eig = xp.vstack((xp.zeros(Z_eig.shape[1], dtype=float), Z_eig)) + Z_eig = np.vstack((np.zeros(Z_eig.shape[1], dtype=float), Z_eig)) return omega2, X_eig, Y_eig, Z_eig ## ========== matrices in initial value problem === - LHS = xp.block( + LHS = np.block( [ - [A_all, xp.zeros((A_all.shape[0], A_all.shape[1])), xp.zeros((A_all.shape[0], len(p3) - 1))], + [A_all, np.zeros((A_all.shape[0], A_all.shape[1])), np.zeros((A_all.shape[0], len(p3) - 1))], [ - xp.zeros((A_all.shape[0], A_all.shape[1])), - xp.identity(A_all.shape[0]), - xp.zeros((A_all.shape[0], len(p3) - 1)), + np.zeros((A_all.shape[0], A_all.shape[1])), + np.identity(A_all.shape[0]), + np.zeros((A_all.shape[0], len(p3) - 1)), ], [ - xp.zeros((len(p3) - 1, A_all.shape[1])), - xp.zeros((len(p3) - 1, A_all.shape[1])), - xp.identity(len(p3) - 1), + np.zeros((len(p3) - 1, A_all.shape[1])), + np.zeros((len(p3) - 1, A_all.shape[1])), + np.identity(len(p3) - 1), ], - ], + ] ) - RHS = xp.block( + RHS = np.block( [ - [xp.zeros((MB_b_all.shape[0], I_all.shape[1])), MB_b_all, MB_p_all], - [I_all, xp.zeros((I_all.shape[0], MB_b_all.shape[1])), xp.zeros((I_all.shape[0], MB_p_all.shape[1]))], - [P_all, xp.zeros((P_all.shape[0], MB_b_all.shape[1])), xp.zeros((P_all.shape[0], MB_p_all.shape[1]))], - ], + [np.zeros((MB_b_all.shape[0], I_all.shape[1])), MB_b_all, MB_p_all], + [I_all, np.zeros((I_all.shape[0], MB_b_all.shape[1])), np.zeros((I_all.shape[0], MB_p_all.shape[1]))], + [P_all, np.zeros((P_all.shape[0], MB_b_all.shape[1])), np.zeros((P_all.shape[0], MB_p_all.shape[1]))], + ] ) dt = 0.05 T = 200.0 Nt = int(T / dt) - UPDATE = xp.linalg.inv(LHS - dt / 2 * RHS).dot(LHS + dt / 2 * RHS) - ##UPDATE = xp.linalg.inv(LHS).dot(LHS + dt*RHS) + UPDATE = np.linalg.inv(LHS - dt / 2 * RHS).dot(LHS + dt / 2 * RHS) + ##UPDATE = np.linalg.inv(LHS).dot(LHS + dt*RHS) # - # lambdas, eig_vecs = xp.linalg.eig(UPDATE) + # lambdas, eig_vecs = np.linalg.eig(UPDATE) # return lambdas # # return lambdas # - u2_r_all = xp.zeros((Nt + 1, len(u2_r)), dtype=float) - u2_phi_all = xp.zeros((Nt + 1, len(u2_phi)), dtype=float) - u2_z_all = xp.zeros((Nt + 1, len(u2_z)), dtype=float) + u2_r_all = np.zeros((Nt + 1, len(u2_r)), dtype=float) + u2_phi_all = np.zeros((Nt + 1, len(u2_phi)), dtype=float) + u2_z_all = np.zeros((Nt + 1, len(u2_z)), dtype=float) - b2_r_all = xp.zeros((Nt + 1, len(b2_r)), dtype=float) - b2_phi_all = xp.zeros((Nt + 1, len(b2_phi)), dtype=float) - b2_z_all = xp.zeros((Nt + 1, len(b2_z)), dtype=float) + b2_r_all = np.zeros((Nt + 1, len(b2_r)), dtype=float) + b2_phi_all = np.zeros((Nt + 1, len(b2_phi)), dtype=float) + b2_z_all = np.zeros((Nt + 1, len(b2_z)), dtype=float) - p3_all = xp.zeros((Nt + 1, len(p3)), dtype=float) + p3_all = np.zeros((Nt + 1, len(p3)), dtype=float) # initialization # u2_r_all[0, :] = u2_r # u2_phi_all[0, :] = u2_phi - u2_r_all[0, 1:-1] = xp.random.rand(len(u2_r) - 2) - p3_all[0, 1:] = xp.random.rand(len(p3) - 1) + u2_r_all[0, 1:-1] = np.random.rand(len(u2_r) - 2) + p3_all[0, 1:] = np.random.rand(len(p3) - 1) # time integration for n in range(Nt): - old = xp.concatenate( + old = np.concatenate( ( u2_r_all[n, 1:-1], u2_phi_all[n, :], @@ -726,24 +716,23 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, b2_phi_all[n, :], b2_z_all[n, 1:], p3_all[n, 1:], - ), + ) ) new = UPDATE.dot(old) # extract components - unew, bnew, pnew = xp.split( - new, - [len(u2_r) - 2 + len(u2_phi) + len(u2_z) - 1, 2 * (len(u2_r) - 2 + len(u2_phi) + len(u2_z) - 1)], + unew, bnew, pnew = np.split( + new, [len(u2_r) - 2 + len(u2_phi) + len(u2_z) - 1, 2 * (len(u2_r) - 2 + len(u2_phi) + len(u2_z) - 1)] ) - u2_r_all[n + 1, :] = xp.array([0.0] + list(unew[: (splines.NbaseN - 2)]) + [0.0]) + u2_r_all[n + 1, :] = np.array([0.0] + list(unew[: (splines.NbaseN - 2)]) + [0.0]) u2_phi_all[n + 1, :] = unew[(splines.NbaseN - 2) : (splines.NbaseN - 2 + splines.NbaseD)] - u2_z_all[n + 1, :] = xp.array([0.0] + list(unew[(splines.NbaseN - 2 + splines.NbaseD) :])) + u2_z_all[n + 1, :] = np.array([0.0] + list(unew[(splines.NbaseN - 2 + splines.NbaseD) :])) - b2_r_all[n + 1, :] = xp.array([0.0] + list(bnew[: (splines.NbaseN - 2)]) + [0.0]) + b2_r_all[n + 1, :] = np.array([0.0] + list(bnew[: (splines.NbaseN - 2)]) + [0.0]) b2_phi_all[n + 1, :] = bnew[(splines.NbaseN - 2) : (splines.NbaseN - 2 + splines.NbaseD)] - b2_z_all[n + 1, :] = xp.array([0.0] + list(bnew[(splines.NbaseN - 2 + splines.NbaseD) :])) + b2_z_all[n + 1, :] = np.array([0.0] + list(bnew[(splines.NbaseN - 2 + splines.NbaseD) :])) - p3_all[n + 1, :] = xp.array([0.0] + list(pnew)) + p3_all[n + 1, :] = np.array([0.0] + list(pnew)) return u2_r_all, u2_phi_all, u2_z_all, b2_r_all, b2_phi_all, b2_z_all, p3_all, omega2 diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/control_variate.py index a4b95eb06..ca5134edc 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/control_variate.py @@ -6,7 +6,7 @@ Class for control variates in delta-f method for current coupling scheme. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.feec.basics.kernels_3d as ker @@ -40,7 +40,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): kind_fun_eq = [11, 12, 13, 14] # ========= evaluation of DF^(-1) * jh_eq_phys * |det(DF)| at quadrature points ========= - self.mat_jh1 = xp.empty( + self.mat_jh1 = np.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -51,7 +51,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.mat_jh2 = xp.empty( + self.mat_jh2 = np.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -62,7 +62,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.mat_jh3 = xp.empty( + self.mat_jh3 = np.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -133,7 +133,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ) # ========= evaluation of nh_eq_phys * |det(DF)| at quadrature points =================== - self.mat_nh = xp.empty( + self.mat_nh = np.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -166,7 +166,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ) # =========== 2-form magnetic field at quadrature points ================================= - self.B2_1 = xp.empty( + self.B2_1 = np.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -177,7 +177,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.B2_2 = xp.empty( + self.B2_2 = np.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -188,7 +188,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.B2_3 = xp.empty( + self.B2_3 = np.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -202,7 +202,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): # ================== correction matrices in step 1 ======================== if self.basis_u == 0: - self.M12 = xp.empty( + self.M12 = np.empty( ( self.space.NbaseN[0], self.space.NbaseN[1], @@ -213,7 +213,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.M13 = xp.empty( + self.M13 = np.empty( ( self.space.NbaseN[0], self.space.NbaseN[1], @@ -224,7 +224,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.M23 = xp.empty( + self.M23 = np.empty( ( self.space.NbaseN[0], self.space.NbaseN[1], @@ -237,7 +237,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ) elif self.basis_u == 2: - self.M12 = xp.empty( + self.M12 = np.empty( ( self.space.NbaseN[0], self.space.NbaseD[1], @@ -248,7 +248,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.M13 = xp.empty( + self.M13 = np.empty( ( self.space.NbaseN[0], self.space.NbaseD[1], @@ -259,7 +259,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.M23 = xp.empty( + self.M23 = np.empty( ( self.space.NbaseD[0], self.space.NbaseN[1], @@ -273,14 +273,14 @@ def __init__(self, tensor_space_FEM, domain, basis_u): # ==================== correction vectors in step 3 ======================= if self.basis_u == 0: - self.F1 = xp.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) - self.F2 = xp.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) - self.F3 = xp.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) + self.F1 = np.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) + self.F2 = np.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) + self.F3 = np.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) elif self.basis_u == 2: - self.F1 = xp.empty((self.space.NbaseN[0], self.space.NbaseD[1], self.space.NbaseD[2]), dtype=float) - self.F2 = xp.empty((self.space.NbaseD[0], self.space.NbaseN[1], self.space.NbaseD[2]), dtype=float) - self.F3 = xp.empty((self.space.NbaseD[0], self.space.NbaseD[1], self.space.NbaseN[2]), dtype=float) + self.F1 = np.empty((self.space.NbaseN[0], self.space.NbaseD[1], self.space.NbaseD[2]), dtype=float) + self.F2 = np.empty((self.space.NbaseD[0], self.space.NbaseN[1], self.space.NbaseD[2]), dtype=float) + self.F3 = np.empty((self.space.NbaseD[0], self.space.NbaseD[1], self.space.NbaseN[2]), dtype=float) # ===== inner product in V0^3 resp. V2 of (B x jh_eq) - term ========== def inner_prod_jh_eq(self, b1, b2, b3): @@ -511,7 +511,7 @@ def inner_prod_jh_eq(self, b1, b2, b3): self.B2_1 * self.mat_jh2 - self.B2_2 * self.mat_jh1, ) - return xp.concatenate((self.F1.flatten(), self.F2.flatten(), self.F3.flatten())) + return np.concatenate((self.F1.flatten(), self.F2.flatten(), self.F3.flatten())) # ===== mass matrix in V0^3 resp. V2 of -(rhoh_eq * (B x U)) - term ======= def mass_nh_eq(self, b1, b2, b3): diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_control_variate.py index 39156f985..562df0691 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_control_variate.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.feec.basics.kernels_3d as ker @@ -204,7 +204,7 @@ def bv_right( ) # ========================= C.T =========================== return tensor_space_FEM.C.T.dot( - xp.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())), + np.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())) ) diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_kernels_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_kernels_control_variate.py index 7f15931ec..9220cdc69 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_kernels_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_kernels_control_variate.py @@ -584,49 +584,13 @@ def vv( bd3[:] = b3[pd3, :pn3] * d3[:] vel[0] = eva.evaluation_kernel( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - NbaseD[0], - NbaseN[1], - NbaseN[2], - bb1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, NbaseD[0], NbaseN[1], NbaseN[2], bb1 ) vel[1] = eva.evaluation_kernel( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - NbaseN[0], - NbaseD[1], - NbaseN[2], - bb2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, NbaseN[0], NbaseD[1], NbaseN[2], bb2 ) vel[2] = eva.evaluation_kernel( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - NbaseN[0], - NbaseN[1], - NbaseD[2], - bb3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, NbaseN[0], NbaseN[1], NbaseD[2], bb3 ) # ======= here we use the linear hat function =========== ie1 = int(eta1 * Nel[0]) diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_control_variate.py index 5e9c04eb0..f0279fbcf 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_control_variate.py @@ -1,6 +1,6 @@ -import cunumpy as xp import hylife.utilitis_FEEC.basics.kernels_3d as ker import hylife.utilitis_FEEC.control_variates.fnB_massless_kernels_control_variate as ker_cv +import numpy as np import scipy.sparse as spa @@ -248,7 +248,7 @@ def bv_right( ) # ========================= C.T =========================== return tensor_space_FEM.C.T.dot( - xp.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())), + np.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())) ) @@ -429,7 +429,7 @@ def uv_right( ) # ========================= C.T =========================== temp_final = temp_final_0.flatten() + tensor_space_FEM.G.T.dot( - xp.concatenate((temp_final_1.flatten(), temp_final_2.flatten(), temp_final_3.flatten())), + np.concatenate((temp_final_1.flatten(), temp_final_2.flatten(), temp_final_3.flatten())) ) return temp_final diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_kernels_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_kernels_control_variate.py index 965a7af33..f3b6fca0e 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_kernels_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_kernels_control_variate.py @@ -212,65 +212,17 @@ def vv( bd3[:] = b3[pd3, :pn3] * d3[:] vel[0] = eva.evaluation_kernel( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - NbaseD[0], - NbaseN[1], - NbaseN[2], - bb1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, NbaseD[0], NbaseN[1], NbaseN[2], bb1 ) vel[1] = eva.evaluation_kernel( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - NbaseN[0], - NbaseD[1], - NbaseN[2], - bb2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, NbaseN[0], NbaseD[1], NbaseN[2], bb2 ) vel[2] = eva.evaluation_kernel( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - NbaseN[0], - NbaseN[1], - NbaseD[2], - bb3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, NbaseN[0], NbaseN[1], NbaseD[2], bb3 ) tt = eva.evaluation_kernel( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span1, - span2, - span3, - NbaseN[0], - NbaseN[1], - NbaseN[2], - n, + pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, NbaseN[0], NbaseN[1], NbaseN[2], n ) if abs(tt) > tol: U_value = 1.0 / tt diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_control_variate.py index 3459ff7b2..09deb07f1 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_control_variate.py @@ -1,6 +1,6 @@ -import cunumpy as xp import hylife.utilitis_FEEC.basics.kernels_3d as ker import hylife.utilitis_FEEC.control_variates.massless_kernels_control_variate as ker_cv +import numpy as np import scipy.sparse as spa @@ -247,7 +247,7 @@ def bv_right( ) # ========================= C.T =========================== return tensor_space_FEM.C.T.dot( - xp.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())), + np.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())) ) @@ -430,7 +430,7 @@ def uv_right( ) # ========================= C.T =========================== temp_final = temp_final_0.flatten() + tensor_space_FEM.G.T.dot( - xp.concatenate((temp_final_1.flatten(), temp_final_2.flatten(), temp_final_3.flatten())), + np.concatenate((temp_final_1.flatten(), temp_final_2.flatten(), temp_final_3.flatten())) ) return temp_final diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_kernels_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_kernels_control_variate.py index bfff64b2a..c56b67711 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_kernels_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_kernels_control_variate.py @@ -27,7 +27,6 @@ def uvpre( bn3: "float[:,:,:,:]", ): from numpy import empty, exp, zeros - # -- removed omp: #$ omp parallel # -- removed omp: #$ omp do private (ie1, ie2, ie3, q1, q2, q3, il1, il2, il3, value) @@ -196,84 +195,34 @@ def uvright( for q2 in range(nq2): for q3 in range(nq3): dft[0, 0] = DFI_11[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[0, 0]) dft[0, 1] = DFI_21[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[0, 1]) dft[0, 2] = DFI_31[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[0, 2]) dft[1, 0] = DFI_12[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[1, 0]) dft[1, 1] = DFI_22[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[1, 1]) dft[1, 2] = DFI_32[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[1, 2]) dft[2, 0] = DFI_13[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[2, 0]) dft[2, 1] = DFI_23[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[2, 1]) dft[2, 2] = DFI_33[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[2, 2]) detdet = df_det[ - ie1, - ie2, - ie3, - q1, - q2, - q3, + ie1, ie2, ie3, q1, q2, q3 ] # mappings_analytical.det_df(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map) Jeq[0] = Jeqx[ie1, ie2, ie3, q1, q2, q3] Jeq[1] = Jeqy[ie1, ie2, ie3, q1, q2, q3] @@ -756,67 +705,19 @@ def vv( bd3[:] = b3[pd3, :pn3] * d3[:] vel[0] = eva.evaluation_kernel( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - NbaseD[0], - NbaseN[1], - NbaseN[2], - bb1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, NbaseD[0], NbaseN[1], NbaseN[2], bb1 ) vel[1] = eva.evaluation_kernel( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - NbaseN[0], - NbaseD[1], - NbaseN[2], - bb2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, NbaseN[0], NbaseD[1], NbaseN[2], bb2 ) vel[2] = eva.evaluation_kernel( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - NbaseN[0], - NbaseN[1], - NbaseD[2], - bb3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, NbaseN[0], NbaseN[1], NbaseD[2], bb3 ) U_value = exp( -eva.evaluation_kernel( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span1, - span2, - span3, - NbaseN[0], - NbaseN[1], - NbaseN[2], - u, - ), + pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, NbaseN[0], NbaseN[1], NbaseN[2], u + ) ) # ========= mapping evaluation ============= diff --git a/src/struphy/eigenvalue_solvers/legacy/emw_operators.py b/src/struphy/eigenvalue_solvers/legacy/emw_operators.py index 9e8c95b3f..171ad21f8 100755 --- a/src/struphy/eigenvalue_solvers/legacy/emw_operators.py +++ b/src/struphy/eigenvalue_solvers/legacy/emw_operators.py @@ -6,7 +6,7 @@ Class for 2D/3D linear MHD projection operators. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_3d as ker @@ -134,7 +134,7 @@ def __assemble_M1_cross(self, weight): Ni = self.SPACES.Nbase_1form[a] Nj = self.SPACES.Nbase_1form[b] - M[a][b] = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M[a][b] = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) # evaluate metric tensor at quadrature points if a == 1 and b == 2: @@ -185,9 +185,9 @@ def __assemble_M1_cross(self, weight): mat_w, ) # convert to sparse matrix - indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -198,14 +198,12 @@ def __assemble_M1_cross(self, weight): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M[a][b] = spa.csr_matrix( - (M[a][b].flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (M[a][b].flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M[a][b].eliminate_zeros() M = spa.bmat( - [[M[0][0], M[0][1], M[0][2]], [M[1][0], M[1][1], M[1][2]], [M[2][0], M[2][1], M[2][2]]], - format="csr", + [[M[0][0], M[0][1], M[0][2]], [M[1][0], M[1][1], M[1][2]], [M[2][0], M[2][1], M[2][2]]], format="csr" ) self.R1_mat = -self.SPACES.E1_0.dot(M.dot(self.SPACES.E1_0.T)).tocsr() diff --git a/src/struphy/eigenvalue_solvers/legacy/inner_products_1d.py b/src/struphy/eigenvalue_solvers/legacy/inner_products_1d.py index b4f019995..c894496b5 100644 --- a/src/struphy/eigenvalue_solvers/legacy/inner_products_1d.py +++ b/src/struphy/eigenvalue_solvers/legacy/inner_products_1d.py @@ -6,7 +6,7 @@ Modules to compute inner products in 1d. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa @@ -39,7 +39,7 @@ def inner_prod_V0(spline_space, fun, mapping=None): # evaluation of mapping at quadrature points if mapping == None: - mat_map = xp.ones(pts.shape, dtype=float) + mat_map = np.ones(pts.shape, dtype=float) else: mat_map = mapping(pts.flatten()).reshape(pts.shape) @@ -47,7 +47,7 @@ def inner_prod_V0(spline_space, fun, mapping=None): mat_f = fun(pts.flatten()).reshape(pts.shape) # assembly - F = xp.zeros(NbaseN, dtype=float) + F = np.zeros(NbaseN, dtype=float) for ie in range(Nel): for il in range(p + 1): @@ -90,7 +90,7 @@ def inner_prod_V1(spline_space, fun, mapping=None): # evaluation of mapping at quadrature points if mapping == None: - mat_map = xp.ones(pts.shape, dtype=float) + mat_map = np.ones(pts.shape, dtype=float) else: mat_map = 1 / mapping(pts.flatten()).reshape(pts.shape) @@ -98,7 +98,7 @@ def inner_prod_V1(spline_space, fun, mapping=None): mat_f = fun(pts.flatten()).reshape(pts.shape) # assembly - F = xp.zeros(NbaseD, dtype=float) + F = np.zeros(NbaseD, dtype=float) for ie in range(Nel): for il in range(p): diff --git a/src/struphy/eigenvalue_solvers/legacy/inner_products_2d.py b/src/struphy/eigenvalue_solvers/legacy/inner_products_2d.py index 05df4725f..ab8ce9f04 100644 --- a/src/struphy/eigenvalue_solvers/legacy/inner_products_2d.py +++ b/src/struphy/eigenvalue_solvers/legacy/inner_products_2d.py @@ -6,7 +6,7 @@ Modules to compute inner products with given functions in 2D. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_2d as ker @@ -25,7 +25,7 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : callable or xp.ndarray + fun : callable or np.ndarray the 0-form with which the inner products shall be computed (either callable or 2D array with values at quadrature points) """ @@ -46,10 +46,10 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): det_df = det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # evaluation of given 0-form at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size), dtype=float) if callable(fun): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") mat_f[:, :] = fun(quad_mesh[0], quad_mesh[1], 0.0) else: mat_f[:, :] = fun @@ -57,7 +57,7 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): # assembly Ni = tensor_space_FEM.Nbase_0form - F = xp.zeros((Ni[0], Ni[1]), dtype=float) + F = np.zeros((Ni[0], Ni[1]), dtype=float) mat_f = mat_f.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) @@ -94,7 +94,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : list of callables or xp.ndarrays + fun : list of callables or np.ndarrays the 1-form components with which the inner products shall be computed (either list of 3 callables or 2D arrays with values at quadrature points) """ @@ -127,10 +127,10 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): g_inv = domain.metric_inv(pts[0].flatten(), pts[1].flatten(), 0.0) # 1-form components at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size), dtype=float) if callable(fun[0]): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") # components of global inner product F = [0, 0, 0] @@ -138,7 +138,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): # assembly for a in range(3): Ni = tensor_space_FEM.Nbase_1form[a] - F[a] = xp.zeros((Ni[0], Ni[1]), dtype=float) + F[a] = np.zeros((Ni[0], Ni[1]), dtype=float) mat_f[:, :] = 0.0 @@ -170,7 +170,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): mat_f * det_df, ) - F1 = tensor_space_FEM.E1_pol_0.dot(xp.concatenate((F[0].flatten(), F[1].flatten()))) + F1 = tensor_space_FEM.E1_pol_0.dot(np.concatenate((F[0].flatten(), F[1].flatten()))) F2 = tensor_space_FEM.E0_pol_0.dot(F[2].flatten()) return F1, F2 @@ -187,7 +187,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : list of callables or xp.ndarrays + fun : list of callables or np.ndarrays the 2-form components with which the inner products shall be computed (either list of 3 callables or 2D arrays with values at quadrature points) """ @@ -220,10 +220,10 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): g = domain.metric(pts[0].flatten(), pts[1].flatten(), 0.0) # 2-form components at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size), dtype=float) if callable(fun[0]): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") # components of global inner product F = [0, 0, 0] @@ -231,7 +231,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): # assembly for a in range(3): Ni = tensor_space_FEM.Nbase_2form[a] - F[a] = xp.zeros((Ni[0], Ni[1]), dtype=float) + F[a] = np.zeros((Ni[0], Ni[1]), dtype=float) mat_f[:, :] = 0.0 @@ -263,7 +263,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): mat_f / det_df, ) - F1 = tensor_space_FEM.E2_pol_0.dot(xp.concatenate((F[0].flatten(), F[1].flatten()))) + F1 = tensor_space_FEM.E2_pol_0.dot(np.concatenate((F[0].flatten(), F[1].flatten()))) F2 = tensor_space_FEM.E3_pol_0.dot(F[2].flatten()) return F1, F2 @@ -280,7 +280,7 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : callable or xp.ndarray + fun : callable or np.ndarray the 3-form component with which the inner products shall be computed (either callable or 2D array with values at quadrature points) """ @@ -301,10 +301,10 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): det_df = det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # evaluation of given 3-form at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size), dtype=float) if callable(fun): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") mat_f[:, :] = fun(quad_mesh[0], quad_mesh[1], 0.0) else: mat_f[:, :] = fun @@ -312,7 +312,7 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): # assembly Ni = tensor_space_FEM.Nbase_3form - F = xp.zeros((Ni[0], Ni[1]), dtype=float) + F = np.zeros((Ni[0], Ni[1]), dtype=float) mat_f = mat_f.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) diff --git a/src/struphy/eigenvalue_solvers/legacy/inner_products_3d.py b/src/struphy/eigenvalue_solvers/legacy/inner_products_3d.py index 20d95c05c..29394e7e8 100644 --- a/src/struphy/eigenvalue_solvers/legacy/inner_products_3d.py +++ b/src/struphy/eigenvalue_solvers/legacy/inner_products_3d.py @@ -6,7 +6,7 @@ Modules to compute inner products with given functions in 3D. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_3d as ker @@ -25,7 +25,7 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : callable or xp.ndarray + fun : callable or np.ndarray the 0-form with which the inner products shall be computed (either callable or 3D array with values at quadrature points) """ @@ -46,10 +46,10 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): det_df = det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # evaluation of given 0-form at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun(quad_mesh[0], quad_mesh[1], quad_mesh[2]) else: mat_f[:, :, :] = fun @@ -57,7 +57,7 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): # assembly Ni = tensor_space.Nbase_0form - F = xp.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) + F = np.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) mat_f = mat_f.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) @@ -101,7 +101,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : list of callables or xp.ndarrays + fun : list of callables or np.ndarrays the 1-form components with which the inner products shall be computed (either list of 3 callables or 3D arrays with values at quadrature points) """ @@ -134,10 +134,10 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): g_inv = domain.metric_inv(pts[0].flatten(), pts[1].flatten(), pts[2].flatten()) # 1-form components at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun[0]): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") # components of global inner product F = [0, 0, 0] @@ -146,7 +146,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): for a in range(3): Ni = tensor_space_FEM.Nbase_1form[a] - F[a] = xp.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) + F[a] = np.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) mat_f[:, :, :] = 0.0 @@ -185,7 +185,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): mat_f * det_df, ) - return tensor_space_FEM.E1_0.dot(xp.concatenate((F[0].flatten(), F[1].flatten(), F[2].flatten()))) + return tensor_space_FEM.E1_0.dot(np.concatenate((F[0].flatten(), F[1].flatten(), F[2].flatten()))) # ================ inner product in V2 =========================== @@ -199,7 +199,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : list of callables or xp.ndarrays + fun : list of callables or np.ndarrays the 2-form components with which the inner products shall be computed (either list of 3 callables or 3D arrays with values at quadrature points) """ @@ -232,10 +232,10 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): g = domain.metric(pts[0].flatten(), pts[1].flatten(), pts[2].flatten()) # 2-form components at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun[0]): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") # components of global inner product F = [0, 0, 0] @@ -244,7 +244,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): for a in range(3): Ni = tensor_space_FEM.Nbase_2form[a] - F[a] = xp.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) + F[a] = np.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) mat_f[:, :, :] = 0.0 @@ -283,7 +283,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): mat_f / det_df, ) - return tensor_space_FEM.E2_0.dot(xp.concatenate((F[0].flatten(), F[1].flatten(), F[2].flatten()))) + return tensor_space_FEM.E2_0.dot(np.concatenate((F[0].flatten(), F[1].flatten(), F[2].flatten()))) # ================ inner product in V3 =========================== @@ -297,7 +297,7 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : callable or xp.ndarray + fun : callable or np.ndarray the 3-form component with which the inner products shall be computed (either callable or 3D array with values at quadrature points) """ @@ -318,10 +318,10 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): det_df = det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # evaluation of given 3-form at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun(quad_mesh[0], quad_mesh[1], quad_mesh[2]) else: mat_f[:, :, :] = fun @@ -329,7 +329,7 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): # assembly Ni = tensor_space.Nbase_3form - F = xp.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) + F = np.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) ker.kernel_inner( Nel[0], diff --git a/src/struphy/eigenvalue_solvers/legacy/l2_error_1d.py b/src/struphy/eigenvalue_solvers/legacy/l2_error_1d.py index d568d3207..3fcc3d55f 100644 --- a/src/struphy/eigenvalue_solvers/legacy/l2_error_1d.py +++ b/src/struphy/eigenvalue_solvers/legacy/l2_error_1d.py @@ -6,7 +6,7 @@ Modules to compute L2-errors in 1d. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa @@ -47,7 +47,7 @@ def l2_error_V0(spline_space, mapping, coeff, fun): mat_f = fun(pts) # assembly - error = xp.zeros(Nel, dtype=float) + error = np.zeros(Nel, dtype=float) for ie in range(Nel): for q in range(n_quad): @@ -58,7 +58,7 @@ def l2_error_V0(spline_space, mapping, coeff, fun): error[ie] += wts[ie, q] * (bi - mat_f[ie, q]) ** 2 - return xp.sqrt(error.sum()) + return np.sqrt(error.sum()) # ======= error in V1 ==================== @@ -98,7 +98,7 @@ def l2_error_V1(spline_space, mapping, coeff, fun): mat_f = fun(pts) # assembly - error = xp.zeros(Nel, dtype=float) + error = np.zeros(Nel, dtype=float) for ie in range(Nel): for q in range(n_quad): @@ -109,4 +109,4 @@ def l2_error_V1(spline_space, mapping, coeff, fun): error[ie] += wts[ie, q] * (bi - mat_f[ie, q]) ** 2 - return xp.sqrt(error.sum()) + return np.sqrt(error.sum()) diff --git a/src/struphy/eigenvalue_solvers/legacy/l2_error_2d.py b/src/struphy/eigenvalue_solvers/legacy/l2_error_2d.py index 452dd570b..f7224061b 100644 --- a/src/struphy/eigenvalue_solvers/legacy/l2_error_2d.py +++ b/src/struphy/eigenvalue_solvers/legacy/l2_error_2d.py @@ -6,7 +6,7 @@ Modules to compute L2-errors of discrete p-forms with analytical forms in 2D. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_2d as ker @@ -25,7 +25,7 @@ def l2_error_V0(tensor_space_FEM, domain, f0, c0, method="standard"): domain : domain domain object defining the geometry - f0 : callable or xp.ndarray + f0 : callable or np.ndarray the 0-form with which the error shall be computed c0 : array_like @@ -63,12 +63,12 @@ def l2_error_V0(tensor_space_FEM, domain, f0, c0, method="standard"): # evaluation of exact 0-form at quadrature points if callable(f0): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") f0 = f0(quad_mesh[0], quad_mesh[1], 0.0) if method == "standard": # evaluation of discrete 0-form at quadrature points - f0_h = tensor_space_FEM.evaluate_NN(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c0, "V0")[:, :, 0] + f0_h = tensor_space_FEM.evaluate_NN(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c0, "V0")[:, :, 0] # compute error error = 0.0 @@ -78,7 +78,7 @@ def l2_error_V0(tensor_space_FEM, domain, f0, c0, method="standard"): else: # compute error in each element - error = xp.zeros(Nel[:2], dtype=float) + error = np.zeros(Nel[:2], dtype=float) ker.kernel_l2error( Nel, @@ -106,7 +106,7 @@ def l2_error_V0(tensor_space_FEM, domain, f0, c0, method="standard"): error = error.sum() - return xp.sqrt(error) + return np.sqrt(error) # ======= error in V1 ==================== @@ -122,7 +122,7 @@ def l2_error_V1(tensor_space_FEM, domain, f1, c1, method="standard"): domain : domain domain object defining the geometry - f1 : list of callables or xp.ndarrays + f1 : list of callables or np.ndarrays the three 1-form components with which the error shall be computed c1 : list of array_like @@ -162,16 +162,16 @@ def l2_error_V1(tensor_space_FEM, domain, f1, c1, method="standard"): # evaluation of exact 1-form components at quadrature points if callable(f1[0]): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") f1_1 = f1[0](quad_mesh[0], quad_mesh[1], 0.0) f1_2 = f1[1](quad_mesh[0], quad_mesh[1], 0.0) f1_3 = f1[2](quad_mesh[0], quad_mesh[1], 0.0) if method == "standard": # evaluation of discrete 1-form components at quadrature points - f1_h_1 = tensor_space_FEM.evaluate_DN(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c1_1, "V1")[:, :, 0] - f1_h_2 = tensor_space_FEM.evaluate_ND(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c1_2, "V1")[:, :, 0] - f1_h_3 = tensor_space_FEM.evaluate_NN(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c1_3, "V1")[:, :, 0] + f1_h_1 = tensor_space_FEM.evaluate_DN(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c1_1, "V1")[:, :, 0] + f1_h_2 = tensor_space_FEM.evaluate_ND(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c1_2, "V1")[:, :, 0] + f1_h_3 = tensor_space_FEM.evaluate_NN(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c1_3, "V1")[:, :, 0] # compute error error = 0.0 @@ -194,7 +194,7 @@ def l2_error_V1(tensor_space_FEM, domain, f1, c1, method="standard"): else: # compute error in each element - error = xp.zeros(Nel[:2], dtype=float) + error = np.zeros(Nel[:2], dtype=float) # 1 * d_f1 * G^11 * |det(DF)| * d_f1 ker.kernel_l2error( @@ -298,7 +298,7 @@ def l2_error_V1(tensor_space_FEM, domain, f1, c1, method="standard"): error = error.sum() - return xp.sqrt(error) + return np.sqrt(error) # ======= error in V2 ==================== @@ -314,7 +314,7 @@ def l2_error_V2(tensor_space_FEM, domain, f2, c2, method="standard"): domain : domain domain object defining the geometry - f2 : list of callables or xp.ndarrays + f2 : list of callables or np.ndarrays the three 2-form components with which the error shall be computed c2 : list of array_like @@ -354,16 +354,16 @@ def l2_error_V2(tensor_space_FEM, domain, f2, c2, method="standard"): # evaluation of exact 2-form components at quadrature points if callable(f2[0]): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") f2_1 = f2[0](quad_mesh[0], quad_mesh[1], 0.0) f2_2 = f2[1](quad_mesh[0], quad_mesh[1], 0.0) f2_3 = f2[2](quad_mesh[0], quad_mesh[1], 0.0) if method == "standard": # evaluation of discrete 2-form components at quadrature points - f2_h_1 = tensor_space_FEM.evaluate_ND(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c2_1, "V2")[:, :, 0] - f2_h_2 = tensor_space_FEM.evaluate_DN(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c2_2, "V2")[:, :, 0] - f2_h_3 = tensor_space_FEM.evaluate_DD(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c2_3, "V2")[:, :, 0] + f2_h_1 = tensor_space_FEM.evaluate_ND(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c2_1, "V2")[:, :, 0] + f2_h_2 = tensor_space_FEM.evaluate_DN(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c2_2, "V2")[:, :, 0] + f2_h_3 = tensor_space_FEM.evaluate_DD(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c2_3, "V2")[:, :, 0] # compute error error = 0.0 @@ -386,7 +386,7 @@ def l2_error_V2(tensor_space_FEM, domain, f2, c2, method="standard"): else: # compute error in each element - error = xp.zeros(Nel[:2], dtype=float) + error = np.zeros(Nel[:2], dtype=float) # 1 * d_f1 * G_11 / |det(DF)| * d_f1 ker.kernel_l2error( @@ -490,7 +490,7 @@ def l2_error_V2(tensor_space_FEM, domain, f2, c2, method="standard"): error = error.sum() - return xp.sqrt(error) + return np.sqrt(error) # ======= error in V3 ==================== @@ -506,7 +506,7 @@ def l2_error_V3(tensor_space_FEM, domain, f3, c3, method="standard"): domain : domain domain object defining the geometry - f3 : callable or xp.ndarray + f3 : callable or np.ndarray the 3-form component with which the error shall be computed c3 : array_like @@ -544,12 +544,12 @@ def l2_error_V3(tensor_space_FEM, domain, f3, c3, method="standard"): # evaluation of exact 3-form at quadrature points if callable(f3): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") f3 = f3(quad_mesh[0], quad_mesh[1], 0.0) if method == "standard": # evaluation of discrete 3-form at quadrature points - f3_h = tensor_space_FEM.evaluate_DD(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c3, "V3")[:, :, 0] + f3_h = tensor_space_FEM.evaluate_DD(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c3, "V3")[:, :, 0] # compute error error = 0.0 @@ -559,7 +559,7 @@ def l2_error_V3(tensor_space_FEM, domain, f3, c3, method="standard"): else: # compute error in each element - error = xp.zeros(Nel[:2], dtype=float) + error = np.zeros(Nel[:2], dtype=float) ker.kernel_l2error( Nel, @@ -587,4 +587,4 @@ def l2_error_V3(tensor_space_FEM, domain, f3, c3, method="standard"): error = error.sum() - return xp.sqrt(error) + return np.sqrt(error) diff --git a/src/struphy/eigenvalue_solvers/legacy/l2_error_3d.py b/src/struphy/eigenvalue_solvers/legacy/l2_error_3d.py index 7553e3a83..bc246310a 100644 --- a/src/struphy/eigenvalue_solvers/legacy/l2_error_3d.py +++ b/src/struphy/eigenvalue_solvers/legacy/l2_error_3d.py @@ -6,7 +6,7 @@ Modules to compute L2-errors of discrete p-forms with analytical forms in 3D. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_3d as ker @@ -25,7 +25,7 @@ def l2_error_V0(tensor_space_FEM, domain, fun, coeff): domain : domain domain object defining the geometry - fun : callable or xp.ndarray + fun : callable or np.ndarray the 0-form with which the error shall be computed coeff : array_like @@ -54,16 +54,16 @@ def l2_error_V0(tensor_space_FEM, domain, fun, coeff): det_df = abs(domain.jacobian_det(pts[0].flatten(), pts[1].flatten(), pts[2].flatten())) # evaluation of given 0-form at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun(quad_mesh[0], quad_mesh[1], quad_mesh[2]) else: mat_f[:, :, :] = fun # compute error - error = xp.zeros(Nel, dtype=float) + error = np.zeros(Nel, dtype=float) ker.kernel_l2error( Nel, @@ -94,7 +94,7 @@ def l2_error_V0(tensor_space_FEM, domain, fun, coeff): det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]), ) - return xp.sqrt(error.sum()) + return np.sqrt(error.sum()) # ======= error in V1 ==================== @@ -110,7 +110,7 @@ def l2_error_V1(tensor_space_FEM, domain, fun, coeff): domain : domain domain object defining the geometry - fun : list of callables or xp.ndarrays + fun : list of callables or np.ndarrays the three 1-form components with which the error shall be computed coeff : list of array_like @@ -141,12 +141,12 @@ def l2_error_V1(tensor_space_FEM, domain, fun, coeff): metric_coeffs *= abs(domain.jacobian_det(pts[0].flatten(), pts[1].flatten(), pts[2].flatten())) # evaluation of given 1-form components at quadrature points - mat_f1 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) - mat_f2 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) - mat_f3 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f1 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f2 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f3 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun[0]): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f1[:, :, :] = fun[0](quad_mesh[0], quad_mesh[1], quad_mesh[2]) mat_f2[:, :, :] = fun[1](quad_mesh[0], quad_mesh[1], quad_mesh[2]) mat_f3[:, :, :] = fun[2](quad_mesh[0], quad_mesh[1], quad_mesh[2]) @@ -156,7 +156,7 @@ def l2_error_V1(tensor_space_FEM, domain, fun, coeff): mat_f3[:, :, :] = fun[2] # compute error - error = xp.zeros(Nel, dtype=float) + error = np.zeros(Nel, dtype=float) # 1 * f1 * G^11 * |det(DF)| * f1 ker.kernel_l2error( @@ -314,7 +314,7 @@ def l2_error_V1(tensor_space_FEM, domain, fun, coeff): 1 * metric_coeffs[2, 2].reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]), ) - return xp.sqrt(error.sum()) + return np.sqrt(error.sum()) # ======= error in V2 ==================== @@ -330,7 +330,7 @@ def l2_error_V2(tensor_space_FEM, domain, fun, coeff): domain : domain domain object defining the geometry - fun : list of callables or xp.ndarrays + fun : list of callables or np.ndarrays the three 2-form components with which the error shall be computed coeff : list of array_like @@ -361,12 +361,12 @@ def l2_error_V2(tensor_space_FEM, domain, fun, coeff): metric_coeffs /= abs(domain.jacobian_det(pts[0].flatten(), pts[1].flatten(), pts[2].flatten())) # evaluation of given 2-form components at quadrature points - mat_f1 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) - mat_f2 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) - mat_f3 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f1 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f2 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f3 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun[0]): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f1[:, :, :] = fun[0](quad_mesh[0], quad_mesh[1], quad_mesh[2]) mat_f2[:, :, :] = fun[1](quad_mesh[0], quad_mesh[1], quad_mesh[2]) mat_f3[:, :, :] = fun[2](quad_mesh[0], quad_mesh[1], quad_mesh[2]) @@ -376,7 +376,7 @@ def l2_error_V2(tensor_space_FEM, domain, fun, coeff): mat_f3[:, :, :] = fun[2] # compute error - error = xp.zeros(Nel, dtype=float) + error = np.zeros(Nel, dtype=float) # 1 * f1 * G_11 / |det(DF)| * f1 ker.kernel_l2error( @@ -534,7 +534,7 @@ def l2_error_V2(tensor_space_FEM, domain, fun, coeff): 1 * metric_coeffs[2, 2].reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]), ) - return xp.sqrt(error.sum()) + return np.sqrt(error.sum()) # ======= error in V3 ==================== @@ -550,7 +550,7 @@ def l2_error_V3(tensor_space_FEM, domain, fun, coeff): domain : domain domain object defining the geometry - fun : callable or xp.ndarray + fun : callable or np.ndarray the 3-form component with which the error shall be computed coeff : array_like @@ -579,16 +579,16 @@ def l2_error_V3(tensor_space_FEM, domain, fun, coeff): det_df = abs(domain.jacobian_det(pts[0].flatten(), pts[1].flatten(), pts[2].flatten())) # evaluation of given 3-form component at quadrature points - mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun): - quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun(quad_mesh[0], quad_mesh[1], quad_mesh[2]) else: mat_f[:, :, :] = fun # compute error - error = xp.zeros(Nel, dtype=float) + error = np.zeros(Nel, dtype=float) ker.kernel_l2error( Nel, @@ -619,4 +619,4 @@ def l2_error_V3(tensor_space_FEM, domain, fun, coeff): 1 / det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]), ) - return xp.sqrt(error.sum()) + return np.sqrt(error.sum()) diff --git a/src/struphy/eigenvalue_solvers/legacy/mass_matrices_3d_pre.py b/src/struphy/eigenvalue_solvers/legacy/mass_matrices_3d_pre.py index a46097a8f..3b6d2a31b 100644 --- a/src/struphy/eigenvalue_solvers/legacy/mass_matrices_3d_pre.py +++ b/src/struphy/eigenvalue_solvers/legacy/mass_matrices_3d_pre.py @@ -6,7 +6,7 @@ Modules to obtain preconditioners for mass matrices in 3D. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.spline_space as spl @@ -32,9 +32,9 @@ def get_M0_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) c_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] @@ -63,20 +63,20 @@ def get_M1_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) - spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) c11_pre = [spaces_pre[0].M1.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] c22_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M1.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] c33_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M1.toarray()[:, 0]] def solve(x): - x1, x2, x3 = xp.split(x, 3) + x1, x2, x3 = np.split(x, 3) x1 = x1.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) x2 = x2.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) @@ -86,7 +86,7 @@ def solve(x): r2 = linkron.kron_fftsolve_3d(c22_pre, x2).flatten() r3 = linkron.kron_fftsolve_3d(c33_pre, x3).flatten() - return xp.concatenate((r1, r2, r3)) + return np.concatenate((r1, r2, r3)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.M1.shape, matvec=solve) @@ -110,20 +110,20 @@ def get_M2_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) - spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) c11_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M1.toarray()[:, 0], spaces_pre[2].M1.toarray()[:, 0]] c22_pre = [spaces_pre[0].M1.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M1.toarray()[:, 0]] c33_pre = [spaces_pre[0].M1.toarray()[:, 0], spaces_pre[1].M1.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] def solve(x): - x1, x2, x3 = xp.split(x, 3) + x1, x2, x3 = np.split(x, 3) x1 = x1.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) x2 = x2.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) @@ -133,7 +133,7 @@ def solve(x): r2 = linkron.kron_fftsolve_3d(c22_pre, x2).flatten() r3 = linkron.kron_fftsolve_3d(c33_pre, x3).flatten() - return xp.concatenate((r1, r2, r3)) + return np.concatenate((r1, r2, r3)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.M2.shape, matvec=solve) @@ -157,9 +157,9 @@ def get_M3_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) c_pre = [spaces_pre[0].M1.toarray()[:, 0], spaces_pre[1].M1.toarray()[:, 0], spaces_pre[2].M1.toarray()[:, 0]] @@ -188,26 +188,26 @@ def get_Mv_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M0(lambda eta: domain.params[0] ** 3 * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: domain.params[1] * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: domain.params[2] * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: domain.params[0] ** 3 * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: domain.params[1] * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: domain.params[2] * np.ones(eta.shape, dtype=float)) c11_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] - spaces_pre[0].assemble_M0(lambda eta: domain.params[0] * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: domain.params[1] ** 3 * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: domain.params[2] * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: domain.params[0] * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: domain.params[1] ** 3 * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: domain.params[2] * np.ones(eta.shape, dtype=float)) c22_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] - spaces_pre[0].assemble_M0(lambda eta: domain.params[0] * xp.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: domain.params[1] * xp.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: domain.params[2] ** 3 * xp.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: domain.params[0] * np.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: domain.params[1] * np.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: domain.params[2] ** 3 * np.ones(eta.shape, dtype=float)) c33_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] def solve(x): - x1, x2, x3 = xp.split(x, 3) + x1, x2, x3 = np.split(x, 3) x1 = x1.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) x2 = x2.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) @@ -217,7 +217,7 @@ def solve(x): r2 = linkron.kron_fftsolve_3d(c22_pre, x2).flatten() r3 = linkron.kron_fftsolve_3d(c33_pre, x3).flatten() - return xp.concatenate((r1, r2, r3)) + return np.concatenate((r1, r2, r3)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.Mv.shape, matvec=solve) @@ -273,18 +273,16 @@ def get_M1_PRE_3(tensor_space_FEM, mats_pol=None): def solve(x): x1 = x[: tensor_space_FEM.E1_pol_0.shape[0] * tensor_space_FEM.NbaseN[2]].reshape( - tensor_space_FEM.E1_pol_0.shape[0], - tensor_space_FEM.NbaseN[2], + tensor_space_FEM.E1_pol_0.shape[0], tensor_space_FEM.NbaseN[2] ) x2 = x[tensor_space_FEM.E1_pol_0.shape[0] * tensor_space_FEM.NbaseN[2] :].reshape( - tensor_space_FEM.E0_pol_0.shape[0], - tensor_space_FEM.NbaseD[2], + tensor_space_FEM.E0_pol_0.shape[0], tensor_space_FEM.NbaseD[2] ) r1 = linkron.kron_fftsolve_2d(M1_pol_0_11_LU, tor_vec0, x1).flatten() r2 = linkron.kron_fftsolve_2d(M1_pol_0_22_LU, tor_vec1, x2).flatten() - return xp.concatenate((r1, r2)) + return np.concatenate((r1, r2)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.M1_0.shape, matvec=solve) @@ -313,18 +311,16 @@ def get_M2_PRE_3(tensor_space_FEM, mats_pol=None): def solve(x): x1 = x[: tensor_space_FEM.E2_pol_0.shape[0] * tensor_space_FEM.NbaseD[2]].reshape( - tensor_space_FEM.E2_pol_0.shape[0], - tensor_space_FEM.NbaseD[2], + tensor_space_FEM.E2_pol_0.shape[0], tensor_space_FEM.NbaseD[2] ) x2 = x[tensor_space_FEM.E2_pol_0.shape[0] * tensor_space_FEM.NbaseD[2] :].reshape( - tensor_space_FEM.E3_pol_0.shape[0], - tensor_space_FEM.NbaseN[2], + tensor_space_FEM.E3_pol_0.shape[0], tensor_space_FEM.NbaseN[2] ) r1 = linkron.kron_fftsolve_2d(M2_pol_0_11_LU, tor_vec1, x1).flatten() r2 = linkron.kron_fftsolve_2d(M2_pol_0_22_LU, tor_vec0, x2).flatten() - return xp.concatenate((r1, r2)) + return np.concatenate((r1, r2)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.M2_0.shape, matvec=solve) @@ -377,17 +373,15 @@ def get_Mv_PRE_3(tensor_space_FEM, mats_pol=None): def solve(x): x1 = x[: tensor_space_FEM.Ev_pol_0.shape[0] * tensor_space_FEM.NbaseN[2]].reshape( - tensor_space_FEM.Ev_pol_0.shape[0], - tensor_space_FEM.NbaseN[2], + tensor_space_FEM.Ev_pol_0.shape[0], tensor_space_FEM.NbaseN[2] ) x2 = x[tensor_space_FEM.Ev_pol_0.shape[0] * tensor_space_FEM.NbaseN[2] :].reshape( - tensor_space_FEM.E0_pol.shape[0], - tensor_space_FEM.NbaseN[2], + tensor_space_FEM.E0_pol.shape[0], tensor_space_FEM.NbaseN[2] ) r1 = linkron.kron_fftsolve_2d(Mv_pol_0_11_LU, tor_vec0, x1).flatten() r2 = linkron.kron_fftsolve_2d(Mv_pol_0_22_LU, tor_vec0, x2).flatten() - return xp.concatenate((r1, r2)) + return np.concatenate((r1, r2)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.Mv_0.shape, matvec=solve) diff --git a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_arrays.py b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_arrays.py index 65faf9209..7705da576 100644 --- a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_arrays.py +++ b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_arrays.py @@ -1,9 +1,9 @@ import time import timeit -import cunumpy as xp +import numpy as np import scipy.sparse as spa -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI import struphy.geometry.mappings_3d as mapping3d import struphy.geometry.mappings_3d_fast as mapping_fast @@ -39,67 +39,67 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): self.Ntot_1form = TENSOR_SPACE_FEM.Ntot_1form self.Ntot_2form = TENSOR_SPACE_FEM.Ntot_2form - self.b1_old = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[0], dtype=float) - self.b2_old = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[1], dtype=float) - self.b3_old = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[2], dtype=float) + self.b1_old = np.empty(TENSOR_SPACE_FEM.Nbase_1form[0], dtype=float) + self.b2_old = np.empty(TENSOR_SPACE_FEM.Nbase_1form[1], dtype=float) + self.b3_old = np.empty(TENSOR_SPACE_FEM.Nbase_1form[2], dtype=float) - self.b1_iter = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[0], dtype=float) - self.b2_iter = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[1], dtype=float) - self.b3_iter = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[2], dtype=float) + self.b1_iter = np.empty(TENSOR_SPACE_FEM.Nbase_1form[0], dtype=float) + self.b2_iter = np.empty(TENSOR_SPACE_FEM.Nbase_1form[1], dtype=float) + self.b3_iter = np.empty(TENSOR_SPACE_FEM.Nbase_1form[2], dtype=float) - self.temp_dft = xp.empty((3, 3), dtype=float) - self.temp_generate_weight1 = xp.empty(3, dtype=float) - self.temp_generate_weight2 = xp.empty(3, dtype=float) - self.temp_generate_weight3 = xp.empty(3, dtype=float) + self.temp_dft = np.empty((3, 3), dtype=float) + self.temp_generate_weight1 = np.empty(3, dtype=float) + self.temp_generate_weight2 = np.empty(3, dtype=float) + self.temp_generate_weight3 = np.empty(3, dtype=float) - self.zerosform_temp_long = xp.empty(TENSOR_SPACE_FEM.Ntot_0form, dtype=float) - self.oneform_temp1_long = xp.empty(TENSOR_SPACE_FEM.Ntot_1form[0], dtype=float) - self.oneform_temp2_long = xp.empty(TENSOR_SPACE_FEM.Ntot_1form[1], dtype=float) - self.oneform_temp3_long = xp.empty(TENSOR_SPACE_FEM.Ntot_1form[2], dtype=float) + self.zerosform_temp_long = np.empty(TENSOR_SPACE_FEM.Ntot_0form, dtype=float) + self.oneform_temp1_long = np.empty(TENSOR_SPACE_FEM.Ntot_1form[0], dtype=float) + self.oneform_temp2_long = np.empty(TENSOR_SPACE_FEM.Ntot_1form[1], dtype=float) + self.oneform_temp3_long = np.empty(TENSOR_SPACE_FEM.Ntot_1form[2], dtype=float) - self.oneform_temp_long = xp.empty( + self.oneform_temp_long = np.empty( TENSOR_SPACE_FEM.Ntot_1form[0] + TENSOR_SPACE_FEM.Ntot_1form[1] + TENSOR_SPACE_FEM.Ntot_1form[2], dtype=float, ) - self.twoform_temp1_long = xp.empty(TENSOR_SPACE_FEM.Ntot_2form[0], dtype=float) - self.twoform_temp2_long = xp.empty(TENSOR_SPACE_FEM.Ntot_2form[1], dtype=float) - self.twoform_temp3_long = xp.empty(TENSOR_SPACE_FEM.Ntot_2form[2], dtype=float) + self.twoform_temp1_long = np.empty(TENSOR_SPACE_FEM.Ntot_2form[0], dtype=float) + self.twoform_temp2_long = np.empty(TENSOR_SPACE_FEM.Ntot_2form[1], dtype=float) + self.twoform_temp3_long = np.empty(TENSOR_SPACE_FEM.Ntot_2form[2], dtype=float) - self.twoform_temp_long = xp.empty( + self.twoform_temp_long = np.empty( TENSOR_SPACE_FEM.Ntot_2form[0] + TENSOR_SPACE_FEM.Ntot_2form[1] + TENSOR_SPACE_FEM.Ntot_2form[2], dtype=float, ) - self.temp_twoform1 = xp.empty(TENSOR_SPACE_FEM.Nbase_2form[0], dtype=float) - self.temp_twoform2 = xp.empty(TENSOR_SPACE_FEM.Nbase_2form[1], dtype=float) - self.temp_twoform3 = xp.empty(TENSOR_SPACE_FEM.Nbase_2form[2], dtype=float) + self.temp_twoform1 = np.empty(TENSOR_SPACE_FEM.Nbase_2form[0], dtype=float) + self.temp_twoform2 = np.empty(TENSOR_SPACE_FEM.Nbase_2form[1], dtype=float) + self.temp_twoform3 = np.empty(TENSOR_SPACE_FEM.Nbase_2form[2], dtype=float) # arrays used to store intermidaite values - self.form_0_flatten = xp.empty(self.Ntot_0form, dtype=float) + self.form_0_flatten = np.empty(self.Ntot_0form, dtype=float) - self.form_1_1_flatten = xp.empty(self.Ntot_1form[0], dtype=float) - self.form_1_2_flatten = xp.empty(self.Ntot_1form[1], dtype=float) - self.form_1_3_flatten = xp.empty(self.Ntot_1form[2], dtype=float) + self.form_1_1_flatten = np.empty(self.Ntot_1form[0], dtype=float) + self.form_1_2_flatten = np.empty(self.Ntot_1form[1], dtype=float) + self.form_1_3_flatten = np.empty(self.Ntot_1form[2], dtype=float) - self.form_1_tot_flatten = xp.empty(self.Ntot_1form[0] + self.Ntot_1form[1] + self.Ntot_1form[2], dtype=float) + self.form_1_tot_flatten = np.empty(self.Ntot_1form[0] + self.Ntot_1form[1] + self.Ntot_1form[2], dtype=float) - self.form_2_1_flatten = xp.empty(self.Ntot_2form[0], dtype=float) - self.form_2_2_flatten = xp.empty(self.Ntot_2form[1], dtype=float) - self.form_2_3_flatten = xp.empty(self.Ntot_2form[2], dtype=float) + self.form_2_1_flatten = np.empty(self.Ntot_2form[0], dtype=float) + self.form_2_2_flatten = np.empty(self.Ntot_2form[1], dtype=float) + self.form_2_3_flatten = np.empty(self.Ntot_2form[2], dtype=float) - self.form_2_tot_flatten = xp.empty(self.Ntot_2form[0] + self.Ntot_2form[1] + self.Ntot_2form[2], dtype=float) + self.form_2_tot_flatten = np.empty(self.Ntot_2form[0] + self.Ntot_2form[1] + self.Ntot_2form[2], dtype=float) - self.bulkspeed_loc = xp.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) - self.temperature_loc = xp.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) - self.bulkspeed = xp.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) + self.bulkspeed_loc = np.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) + self.temperature_loc = np.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) + self.bulkspeed = np.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) if self.mpi_rank == 0: - temperature = xp.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) + temperature = np.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) else: temperature = None # values of magnetic fields at all quadrature points - self.LO_inv = xp.empty( + self.LO_inv = np.empty( ( self.Nel[0], self.Nel[1], @@ -111,7 +111,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.LO_b1 = xp.empty( + self.LO_b1 = np.empty( ( self.Nel[0], self.Nel[1], @@ -122,7 +122,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_b2 = xp.empty( + self.LO_b2 = np.empty( ( self.Nel[0], self.Nel[1], @@ -133,7 +133,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_b3 = xp.empty( + self.LO_b3 = np.empty( ( self.Nel[0], self.Nel[1], @@ -145,7 +145,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # values of weights (used in the linear operators) - self.LO_w1 = xp.empty( + self.LO_w1 = np.empty( ( self.Nel[0], self.Nel[1], @@ -156,7 +156,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_w2 = xp.empty( + self.LO_w2 = np.empty( ( self.Nel[0], self.Nel[1], @@ -167,7 +167,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_w3 = xp.empty( + self.LO_w3 = np.empty( ( self.Nel[0], self.Nel[1], @@ -179,7 +179,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # values of a function (given its finite element coefficients) at all quadrature points - self.LO_r1 = xp.empty( + self.LO_r1 = np.empty( ( self.Nel[0], self.Nel[1], @@ -190,7 +190,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_r2 = xp.empty( + self.LO_r2 = np.empty( ( self.Nel[0], self.Nel[1], @@ -201,7 +201,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_r3 = xp.empty( + self.LO_r3 = np.empty( ( self.Nel[0], self.Nel[1], @@ -213,7 +213,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # values of determinant of Jacobi matrix of the map at all quadrature points - self.df_det = xp.empty( + self.df_det = np.empty( ( self.Nel[0], self.Nel[1], @@ -225,8 +225,8 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # when using delta f method, the values of current equilibrium at all quadrature points - if control: - self.Jeqx = xp.empty( + if control == True: + self.Jeqx = np.empty( ( self.Nel[0], self.Nel[1], @@ -237,7 +237,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.Jeqy = xp.empty( + self.Jeqy = np.empty( ( self.Nel[0], self.Nel[1], @@ -248,7 +248,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.Jeqz = xp.empty( + self.Jeqz = np.empty( ( self.Nel[0], self.Nel[1], @@ -260,7 +260,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # values of DF and inverse of DF at all quadrature points - self.DF_11 = xp.empty( + self.DF_11 = np.empty( ( self.Nel[0], self.Nel[1], @@ -271,7 +271,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_12 = xp.empty( + self.DF_12 = np.empty( ( self.Nel[0], self.Nel[1], @@ -282,7 +282,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_13 = xp.empty( + self.DF_13 = np.empty( ( self.Nel[0], self.Nel[1], @@ -293,7 +293,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_21 = xp.empty( + self.DF_21 = np.empty( ( self.Nel[0], self.Nel[1], @@ -304,7 +304,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_22 = xp.empty( + self.DF_22 = np.empty( ( self.Nel[0], self.Nel[1], @@ -315,7 +315,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_23 = xp.empty( + self.DF_23 = np.empty( ( self.Nel[0], self.Nel[1], @@ -326,7 +326,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_31 = xp.empty( + self.DF_31 = np.empty( ( self.Nel[0], self.Nel[1], @@ -337,7 +337,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_32 = xp.empty( + self.DF_32 = np.empty( ( self.Nel[0], self.Nel[1], @@ -348,7 +348,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_33 = xp.empty( + self.DF_33 = np.empty( ( self.Nel[0], self.Nel[1], @@ -360,7 +360,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.DFI_11 = xp.empty( + self.DFI_11 = np.empty( ( self.Nel[0], self.Nel[1], @@ -371,7 +371,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_12 = xp.empty( + self.DFI_12 = np.empty( ( self.Nel[0], self.Nel[1], @@ -382,7 +382,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_13 = xp.empty( + self.DFI_13 = np.empty( ( self.Nel[0], self.Nel[1], @@ -393,7 +393,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_21 = xp.empty( + self.DFI_21 = np.empty( ( self.Nel[0], self.Nel[1], @@ -404,7 +404,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_22 = xp.empty( + self.DFI_22 = np.empty( ( self.Nel[0], self.Nel[1], @@ -415,7 +415,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_23 = xp.empty( + self.DFI_23 = np.empty( ( self.Nel[0], self.Nel[1], @@ -426,7 +426,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_31 = xp.empty( + self.DFI_31 = np.empty( ( self.Nel[0], self.Nel[1], @@ -437,7 +437,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_32 = xp.empty( + self.DFI_32 = np.empty( ( self.Nel[0], self.Nel[1], @@ -448,7 +448,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_33 = xp.empty( + self.DFI_33 = np.empty( ( self.Nel[0], self.Nel[1], @@ -460,7 +460,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.DFIT_11 = xp.empty( + self.DFIT_11 = np.empty( ( self.Nel[0], self.Nel[1], @@ -471,7 +471,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_12 = xp.empty( + self.DFIT_12 = np.empty( ( self.Nel[0], self.Nel[1], @@ -482,7 +482,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_13 = xp.empty( + self.DFIT_13 = np.empty( ( self.Nel[0], self.Nel[1], @@ -493,7 +493,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_21 = xp.empty( + self.DFIT_21 = np.empty( ( self.Nel[0], self.Nel[1], @@ -504,7 +504,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_22 = xp.empty( + self.DFIT_22 = np.empty( ( self.Nel[0], self.Nel[1], @@ -515,7 +515,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_23 = xp.empty( + self.DFIT_23 = np.empty( ( self.Nel[0], self.Nel[1], @@ -526,7 +526,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_31 = xp.empty( + self.DFIT_31 = np.empty( ( self.Nel[0], self.Nel[1], @@ -537,7 +537,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_32 = xp.empty( + self.DFIT_32 = np.empty( ( self.Nel[0], self.Nel[1], @@ -548,7 +548,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_33 = xp.empty( + self.DFIT_33 = np.empty( ( self.Nel[0], self.Nel[1], @@ -560,7 +560,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.G_inv_11 = xp.empty( + self.G_inv_11 = np.empty( ( self.Nel[0], self.Nel[1], @@ -571,7 +571,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.G_inv_12 = xp.empty( + self.G_inv_12 = np.empty( ( self.Nel[0], self.Nel[1], @@ -582,7 +582,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.G_inv_13 = xp.empty( + self.G_inv_13 = np.empty( ( self.Nel[0], self.Nel[1], @@ -594,7 +594,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.G_inv_22 = xp.empty( + self.G_inv_22 = np.empty( ( self.Nel[0], self.Nel[1], @@ -605,7 +605,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.G_inv_23 = xp.empty( + self.G_inv_23 = np.empty( ( self.Nel[0], self.Nel[1], @@ -617,7 +617,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.G_inv_33 = xp.empty( + self.G_inv_33 = np.empty( ( self.Nel[0], self.Nel[1], @@ -629,7 +629,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.temp_particle = xp.empty(3, dtype=float) + self.temp_particle = np.empty(3, dtype=float) # initialization of DF and its inverse # ================ for mapping evaluation ================== # spline degrees @@ -638,34 +638,34 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): pf3 = DOMAIN.p[2] # pf + 1 non-vanishing basis functions up tp degree pf - b1f = xp.empty((pf1 + 1, pf1 + 1), dtype=float) - b2f = xp.empty((pf2 + 1, pf2 + 1), dtype=float) - b3f = xp.empty((pf3 + 1, pf3 + 1), dtype=float) + b1f = np.empty((pf1 + 1, pf1 + 1), dtype=float) + b2f = np.empty((pf2 + 1, pf2 + 1), dtype=float) + b3f = np.empty((pf3 + 1, pf3 + 1), dtype=float) # left and right values for spline evaluation - l1f = xp.empty(pf1, dtype=float) - l2f = xp.empty(pf2, dtype=float) - l3f = xp.empty(pf3, dtype=float) + l1f = np.empty(pf1, dtype=float) + l2f = np.empty(pf2, dtype=float) + l3f = np.empty(pf3, dtype=float) - r1f = xp.empty(pf1, dtype=float) - r2f = xp.empty(pf2, dtype=float) - r3f = xp.empty(pf3, dtype=float) + r1f = np.empty(pf1, dtype=float) + r2f = np.empty(pf2, dtype=float) + r3f = np.empty(pf3, dtype=float) # scaling arrays for M-splines - d1f = xp.empty(pf1, dtype=float) - d2f = xp.empty(pf2, dtype=float) - d3f = xp.empty(pf3, dtype=float) + d1f = np.empty(pf1, dtype=float) + d2f = np.empty(pf2, dtype=float) + d3f = np.empty(pf3, dtype=float) # pf + 1 derivatives - der1f = xp.empty(pf1 + 1, dtype=float) - der2f = xp.empty(pf2 + 1, dtype=float) - der3f = xp.empty(pf3 + 1, dtype=float) + der1f = np.empty(pf1 + 1, dtype=float) + der2f = np.empty(pf2 + 1, dtype=float) + der3f = np.empty(pf3 + 1, dtype=float) # needed mapping quantities - df = xp.empty((3, 3), dtype=float) - fx = xp.empty(3, dtype=float) - ginv = xp.empty((3, 3), dtype=float) - dfinv = xp.empty((3, 3), dtype=float) + df = np.empty((3, 3), dtype=float) + fx = np.empty(3, dtype=float) + ginv = np.empty((3, 3), dtype=float) + dfinv = np.empty((3, 3), dtype=float) for ie1 in range(self.Nel[0]): for ie2 in range(self.Nel[1]): @@ -761,7 +761,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): self.df_det[ie1, ie2, ie3, q1, q2, q3] = det_number - if control: + if control == True: x1 = mapping3d.f( TENSOR_SPACE_FEM.pts[0][ie1, q1], TENSOR_SPACE_FEM.pts[1][ie2, q2], diff --git a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_bv_kernel.py b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_bv_kernel.py index e477940e6..5cf3830a4 100644 --- a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_bv_kernel.py +++ b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_bv_kernel.py @@ -226,9 +226,7 @@ def right_hand2( * bd2[ie2, il2, 0, q2] * bd3[ie3, il3, 0, q3] * temp_vector_1[ - N_index_x[ie1, il1], - D_index_y[ie2, il2], - D_index_z[ie3, il3], + N_index_x[ie1, il1], D_index_y[ie2, il2], D_index_z[ie3, il3] ] ) @@ -254,9 +252,7 @@ def right_hand2( * bn2[ie2, il2, 0, q2] * bd3[ie3, il3, 0, q3] * temp_vector_2[ - D_index_x[ie1, il1], - N_index_y[ie2, il2], - D_index_z[ie3, il3], + D_index_x[ie1, il1], N_index_y[ie2, il2], D_index_z[ie3, il3] ] ) @@ -282,9 +278,7 @@ def right_hand2( * bd2[ie2, il2, 0, q2] * bn3[ie3, il3, 0, q3] * temp_vector_3[ - D_index_x[ie1, il1], - D_index_y[ie2, il2], - N_index_z[ie3, il3], + D_index_x[ie1, il1], D_index_y[ie2, il2], N_index_z[ie3, il3] ] ) @@ -344,9 +338,7 @@ def right_hand1( * bn2[ie2, il2, 0, q2] * bn3[ie3, il3, 0, q3] * temp_vector_1[ - D_index_x[ie1, il1], - N_index_y[ie2, il2], - N_index_z[ie3, il3], + D_index_x[ie1, il1], N_index_y[ie2, il2], N_index_z[ie3, il3] ] ) @@ -372,9 +364,7 @@ def right_hand1( * bd2[ie2, il2, 0, q2] * bn3[ie3, il3, 0, q3] * temp_vector_2[ - N_index_x[ie1, il1], - D_index_y[ie2, il2], - N_index_z[ie3, il3], + N_index_x[ie1, il1], D_index_y[ie2, il2], N_index_z[ie3, il3] ] ) @@ -400,9 +390,7 @@ def right_hand1( * bn2[ie2, il2, 0, q2] * bd3[ie3, il3, 0, q3] * temp_vector_3[ - N_index_x[ie1, il1], - N_index_y[ie2, il2], - D_index_z[ie3, il3], + N_index_x[ie1, il1], N_index_y[ie2, il2], D_index_z[ie3, il3] ] ) diff --git a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_massless_linear_operators.py b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_massless_linear_operators.py index bdf01bf8b..a26a22679 100644 --- a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_massless_linear_operators.py +++ b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_massless_linear_operators.py @@ -1,6 +1,6 @@ import time -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.feec.massless_operators.fB_bb_kernel as bb_kernel @@ -49,15 +49,15 @@ def linearoperator_step_vv(self, M2_PRE, M2, M1_PRE, M1, TEMP, ACC_VV): This function is used in substep vv with L2 projector. """ - dft = xp.empty((3, 3), dtype=float) - generate_weight1 = xp.zeros(3, dtype=float) - generate_weight2 = xp.zeros(3, dtype=float) - generate_weight3 = xp.zeros(3, dtype=float) + dft = np.empty((3, 3), dtype=float) + generate_weight1 = np.zeros(3, dtype=float) + generate_weight2 = np.zeros(3, dtype=float) + generate_weight3 = np.zeros(3, dtype=float) # =========================inverse of M1 =========================== - ACC_VV.temp1[:], ACC_VV.temp2[:], ACC_VV.temp3[:] = xp.split( + ACC_VV.temp1[:], ACC_VV.temp2[:], ACC_VV.temp3[:] = np.split( spa.linalg.cg( M1, - 1.0 / self.Np * xp.concatenate((ACC_VV.vec1.flatten(), ACC_VV.vec2.flatten(), ACC_VV.vec3.flatten())), + 1.0 / self.Np * np.concatenate((ACC_VV.vec1.flatten(), ACC_VV.vec2.flatten(), ACC_VV.vec3.flatten())), tol=10 ** (-14), M=M1_PRE, )[0], @@ -153,10 +153,10 @@ def linearoperator_step_vv(self, M2_PRE, M2, M1_PRE, M1, TEMP, ACC_VV): ) # =========================inverse of M1 =========================== - ACC_VV.temp1[:], ACC_VV.temp2[:], ACC_VV.temp3[:] = xp.split( + ACC_VV.temp1[:], ACC_VV.temp2[:], ACC_VV.temp3[:] = np.split( spa.linalg.cg( M1, - xp.concatenate((ACC_VV.one_form1.flatten(), ACC_VV.one_form2.flatten(), ACC_VV.one_form3.flatten())), + np.concatenate((ACC_VV.one_form1.flatten(), ACC_VV.one_form2.flatten(), ACC_VV.one_form3.flatten())), tol=10 ** (-14), M=M1_PRE, )[0], @@ -310,10 +310,10 @@ def linearoperator_pre_step_vv( indN = tensor_space_FEM.indN indD = tensor_space_FEM.indD - dft = xp.empty((3, 3), dtype=float) - generate_weight1 = xp.zeros(3, dtype=float) - generate_weight2 = xp.zeros(3, dtype=float) - generate_weight3 = xp.zeros(3, dtype=float) + dft = np.empty((3, 3), dtype=float) + generate_weight1 = np.zeros(3, dtype=float) + generate_weight2 = np.zeros(3, dtype=float) + generate_weight3 = np.zeros(3, dtype=float) vv_kernel.prepre( indN[0], @@ -716,17 +716,16 @@ def linearoperator_step3( Ntot_2form = tensor_space_FEM.Ntot_2form Nbase_2form = tensor_space_FEM.Nbase_2form - dft = xp.empty((3, 3), dtype=float) - generate_weight1 = xp.empty(3, dtype=float) - generate_weight2 = xp.empty(3, dtype=float) - generate_weight3 = xp.empty(3, dtype=float) + dft = np.empty((3, 3), dtype=float) + generate_weight1 = np.empty(3, dtype=float) + generate_weight2 = np.empty(3, dtype=float) + generate_weight3 = np.empty(3, dtype=float) # ================================================================== # ========================= C =========================== # time1 = time.time() - twoform_temp1_long[:], twoform_temp2_long[:], twoform_temp3_long[:] = xp.split( - tensor_space_FEM.C.dot(input_vector), - [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], + twoform_temp1_long[:], twoform_temp2_long[:], twoform_temp3_long[:] = np.split( + tensor_space_FEM.C.dot(input_vector), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]] ) temp_vector_1[:, :, :] = twoform_temp1_long.reshape(Nbase_2form[0]) temp_vector_2[:, :, :] = twoform_temp2_long.reshape(Nbase_2form[1]) @@ -827,7 +826,7 @@ def linearoperator_step3( # ========================= C.T =========================== # time1 = time.time() temp_final = tensor_space_FEM.M1.dot(input_vector) - dt / 2.0 * tensor_space_FEM.C.T.dot( - xp.concatenate((temp_vector_1.flatten(), temp_vector_2.flatten(), temp_vector_3.flatten())), + np.concatenate((temp_vector_1.flatten(), temp_vector_2.flatten(), temp_vector_3.flatten())) ) # time2 = time.time() # print('second_curl_time', time2 - time1) @@ -922,16 +921,15 @@ def linearoperator_right_step3( Ntot_2form = tensor_space_FEM.Ntot_2form Nbase_2form = tensor_space_FEM.Nbase_2form - dft = xp.empty((3, 3), dtype=float) - generate_weight1 = xp.empty(3, dtype=float) - generate_weight2 = xp.empty(3, dtype=float) - generate_weight3 = xp.empty(3, dtype=float) + dft = np.empty((3, 3), dtype=float) + generate_weight1 = np.empty(3, dtype=float) + generate_weight2 = np.empty(3, dtype=float) + generate_weight3 = np.empty(3, dtype=float) # ================================================================== # ========================= C =========================== - twoform_temp1_long[:], twoform_temp2_long[:], twoform_temp3_long[:] = xp.split( - tensor_space_FEM.C.dot(input_vector), - [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], + twoform_temp1_long[:], twoform_temp2_long[:], twoform_temp3_long[:] = np.split( + tensor_space_FEM.C.dot(input_vector), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]] ) temp_vector_1[:, :, :] = twoform_temp1_long.reshape(Nbase_2form[0]) temp_vector_2[:, :, :] = twoform_temp2_long.reshape(Nbase_2form[1]) @@ -1084,7 +1082,7 @@ def linearoperator_right_step3( # print('final_bb', time2 - time1) # ========================= C.T =========================== temp_final = tensor_space_FEM.M1.dot(input_vector) + dt / 2.0 * tensor_space_FEM.C.T.dot( - xp.concatenate((temp_vector_1.flatten(), temp_vector_2.flatten(), temp_vector_3.flatten())), + np.concatenate((temp_vector_1.flatten(), temp_vector_2.flatten(), temp_vector_3.flatten())) ) return temp_final @@ -1147,9 +1145,8 @@ def substep4_linear_operator( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( - tensor_space_FEM.C.dot(input), - [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( + tensor_space_FEM.C.dot(input), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]] ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) acc.twoform_temp2[:, :, :] = acc.twoform_temp2_long.reshape(Nbase_2form[1]) @@ -1252,12 +1249,12 @@ def substep4_linear_operator( ) acc.oneform_temp_long[:] = spa.linalg.gmres( M1, - xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), + np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), tol=10 ** (-10), M=M1_PRE, )[0] - acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = xp.split( + acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = np.split( spa.linalg.gmres(M1, mat.dot(acc.oneform_temp_long), tol=10 ** (-10), M=M1_PRE)[0], [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]], ) @@ -1362,7 +1359,7 @@ def substep4_linear_operator( ) return M1.dot(input) + dt**2 / 4.0 * tensor_space_FEM.C.T.dot( - xp.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())), + np.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())) ) # ========================================================================================================== @@ -1425,8 +1422,8 @@ def substep4_linear_operator_right( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( - CURL.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( + CURL.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) @@ -1532,13 +1529,13 @@ def substep4_linear_operator_right( acc.oneform_temp_long[:] = mat.dot( spa.linalg.gmres( M1, - xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), + np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), tol=10 ** (-10), M=M1_PRE, - )[0], + )[0] ) - acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = xp.split( + acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = np.split( spa.linalg.gmres(M1, dt**2.0 / 4.0 * acc.oneform_temp_long + dt * vec, tol=10 ** (-10), M=M1_PRE)[0], [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]], ) @@ -1642,8 +1639,8 @@ def substep4_linear_operator_right( tensor_space_FEM.basisD[2], ) - return M1.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))) - CURL.T.dot( - xp.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())), + return M1.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))) - CURL.T.dot( + np.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())) ) # ========================================================================================================== @@ -1795,8 +1792,8 @@ def substep4_pusher_field( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( - CURL.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( + CURL.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) @@ -1901,7 +1898,7 @@ def substep4_pusher_field( return spa.linalg.cg( M1, - xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), + np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), tol=10 ** (-13), M=M1_PRE, )[0] @@ -1963,9 +1960,8 @@ def substep4_localproj_linear_operator( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( - tensor_space_FEM.C.dot(input), - [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( + tensor_space_FEM.C.dot(input), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]] ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) acc.twoform_temp2[:, :, :] = acc.twoform_temp2_long.reshape(Nbase_2form[1]) @@ -2067,9 +2063,9 @@ def substep4_localproj_linear_operator( tensor_space_FEM.basisD[2], ) - acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = xp.split( + acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = np.split( mat.dot( - xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), + np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())) ), [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]], ) @@ -2175,7 +2171,7 @@ def substep4_localproj_linear_operator( ) return M1.dot(input) + dt**2 / 4.0 * tensor_space_FEM.C.T.dot( - xp.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())), + np.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())) ) # ========================================================================================================== @@ -2238,8 +2234,8 @@ def substep4_localproj_linear_operator_right( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( - CURL.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( + CURL.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) @@ -2343,12 +2339,11 @@ def substep4_localproj_linear_operator_right( tensor_space_FEM.basisD[2], ) acc.oneform_temp_long[:] = mat.dot( - xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), + np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())) ) - acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = xp.split( - (dt**2.0 / 4.0 * acc.oneform_temp_long + dt * vec), - [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]], + acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = np.split( + (dt**2.0 / 4.0 * acc.oneform_temp_long + dt * vec), [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]] ) acc.oneform_temp1[:, :, :] = acc.oneform_temp1_long.reshape(Nbase_1form[0]) @@ -2451,8 +2446,8 @@ def substep4_localproj_linear_operator_right( tensor_space_FEM.basisD[2], ) - return M1.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))) - CURL.T.dot( - xp.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())), + return M1.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))) - CURL.T.dot( + np.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())) ) # ========================================================================================================== @@ -2514,8 +2509,8 @@ def substep4_localproj_pusher_field( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( - CURL.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( + CURL.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) @@ -2618,4 +2613,4 @@ def substep4_localproj_pusher_field( tensor_space_FEM.basisD[2], ) - return xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())) + return np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())) diff --git a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_vv_kernel.py b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_vv_kernel.py index 1ef3376dc..7adfaf080 100644 --- a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_vv_kernel.py +++ b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_vv_kernel.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np from numpy import empty, exp, floor, zeros import struphy.bsplines.bsplines_kernels as bsp @@ -542,8 +542,7 @@ def piecewise_gather( for q2 in range(n_quad[1]): for q3 in range(n_quad[2]): temp1[0] = (cell_left[0] + il1) / Nel[0] + pts1[ - 0, - q1, + 0, q1 ] # quadrature points in the cell x direction temp4[0] = abs(temp1[0] - eta1) - compact[0] / 2.0 # if > 0, result is 0 @@ -742,8 +741,7 @@ def piecewise_scatter( for q2 in range(n_quad[1]): for q3 in range(n_quad[2]): temp1[0] = (cell_left[0] + il1) / Nel[0] + pts1[ - 0, - q1, + 0, q1 ] # quadrature points in the cell x direction temp4[0] = abs(temp1[0] - eta1) - compact[0] / 2 # if > 0, result is 0 diff --git a/src/struphy/eigenvalue_solvers/legacy/mhd_operators_MF.py b/src/struphy/eigenvalue_solvers/legacy/mhd_operators_MF.py index 560314458..6cdfc04b1 100644 --- a/src/struphy/eigenvalue_solvers/legacy/mhd_operators_MF.py +++ b/src/struphy/eigenvalue_solvers/legacy/mhd_operators_MF.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np import scipy.sparse as spa from struphy.eigenvalue_solvers.projectors_global import Projectors_tensor_3d @@ -107,9 +107,9 @@ def __init__(self, space, eq_MHD): self.pts1_D_2 = self.space.spaces[1].projectors.D_pts self.pts1_D_3 = self.space.spaces[2].projectors.D_pts - # assert xp.allclose(self.N_1.toarray(), self.pts0_N_1.toarray(), atol=1e-14) - # assert xp.allclose(self.N_2.toarray(), self.pts0_N_2.toarray(), atol=1e-14) - # assert xp.allclose(self.N_3.toarray(), self.pts0_N_3.toarray(), atol=1e-14) + # assert np.allclose(self.N_1.toarray(), self.pts0_N_1.toarray(), atol=1e-14) + # assert np.allclose(self.N_2.toarray(), self.pts0_N_2.toarray(), atol=1e-14) + # assert np.allclose(self.N_3.toarray(), self.pts0_N_3.toarray(), atol=1e-14) # ===== call equilibrium_mhd values at the projection points ===== # projection points @@ -210,11 +210,11 @@ def __init__(self, space, eq_MHD): # # Operator A # if self.basis_u == 1: # self.A = spa.linalg.LinearOperator((self.dim_1, self.dim_1), matvec = lambda x : (self.M1.dot(self.W1_dot(x)) + self.transpose_W1_dot(self.M1.dot(x))) / 2 ) - # self.A_mat = spa.csc_matrix(self.A.dot(xp.identity(self.dim_1))) + # self.A_mat = spa.csc_matrix(self.A.dot(np.identity(self.dim_1))) # elif self.basis_u == 2: # self.A = spa.linalg.LinearOperator((self.dim_2, self.dim_2), matvec = lambda x : (self.M2.dot(self.Q2_dot(x)) + self.transpose_Q2_dot(self.M2.dot(x))) / 2 ) - # self.A_mat = spa.csc_matrix(self.A.dot(xp.identity(self.dim_2))) + # self.A_mat = spa.csc_matrix(self.A.dot(np.identity(self.dim_2))) # self.A_inv = spa.linalg.inv(self.A_mat) @@ -228,12 +228,12 @@ def Q1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^1} Returns ---------- - res : xp.array + res : np.array dim R^{N^2} Notes @@ -320,7 +320,7 @@ def Q1_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ================================================================== def transpose_Q1_dot(self, x): @@ -329,12 +329,12 @@ def transpose_Q1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- - res : xp.array + res : np.array dim R^{N^1} Notes @@ -403,7 +403,7 @@ def transpose_Q1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # =================================================================== def W1_dot(self, x): @@ -412,12 +412,12 @@ def W1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^1} Returns ---------- - res : xp.array + res : np.array dim R^{N^1} Notes @@ -482,7 +482,7 @@ def W1_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # =================================================================== def transpose_W1_dot(self, x): @@ -491,12 +491,12 @@ def transpose_W1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^1} Returns ---------- - res : xp.array + res : np.array dim R{N^1} Notes @@ -545,7 +545,7 @@ def transpose_W1_dot(self, x): res_2 = kron_matvec_3d([self.pts0_N_1.T, self.pts1_D_2.T, self.pts0_N_3.T], mat_f_2_c) res_3 = kron_matvec_3d([self.pts0_N_1.T, self.pts0_N_2.T, self.pts1_D_3.T], mat_f_3_c) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def U1_dot(self, x): @@ -554,12 +554,12 @@ def U1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^1} Returns ---------- - res : xp.array + res : np.array dim R^{N^2} Notes @@ -645,7 +645,7 @@ def U1_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_U1_dot(self, x): @@ -654,12 +654,12 @@ def transpose_U1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^2} Returns ---------- - res : xp.array + res : np.array dim R{N^1} Notes @@ -728,7 +728,7 @@ def transpose_U1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def P1_dot(self, x): @@ -737,12 +737,12 @@ def P1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- - res : xp.array + res : np.array dim R^{N^1} Notes @@ -831,7 +831,7 @@ def P1_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_P1_dot(self, x): @@ -840,12 +840,12 @@ def transpose_P1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^1} Returns ---------- - res : xp.array + res : np.array dim R{N^2} Notes @@ -914,7 +914,7 @@ def transpose_P1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def S1_dot(self, x): @@ -923,12 +923,12 @@ def S1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^1} Returns ---------- - res : xp.array + res : np.array dim R^{N^2} Notes @@ -1015,7 +1015,7 @@ def S1_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_S1_dot(self, x): @@ -1024,12 +1024,12 @@ def transpose_S1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^2} Returns ---------- - res : xp.array + res : np.array dim R{N^1} Notes @@ -1098,7 +1098,7 @@ def transpose_S1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # =================================================================== def S10_dot(self, x): @@ -1107,12 +1107,12 @@ def S10_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^1} Returns ---------- - res : xp.array + res : np.array dim R^{N^1} Notes @@ -1178,7 +1178,7 @@ def S10_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # =================================================================== def transpose_S10_dot(self, x): @@ -1187,12 +1187,12 @@ def transpose_S10_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^1} Returns ---------- - res : xp.array + res : np.array dim R{N^1} Notes @@ -1241,7 +1241,7 @@ def transpose_S10_dot(self, x): res_2 = kron_matvec_3d([self.pts0_N_1.T, self.pts1_D_2.T, self.pts0_N_3.T], mat_f_2_c) res_3 = kron_matvec_3d([self.pts0_N_1.T, self.pts0_N_2.T, self.pts1_D_3.T], mat_f_3_c) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ================================================================= def K1_dot(self, x): @@ -1250,12 +1250,12 @@ def K1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^3} Returns ---------- - res : xp.array + res : np.array dim R^{N^3} Notes @@ -1307,7 +1307,7 @@ def transpose_K1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^3} Returns @@ -1350,12 +1350,12 @@ def K10_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^0} Returns ---------- - res : xp.array + res : np.array dim R^{N^0} Notes @@ -1406,7 +1406,7 @@ def transpose_K10_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^0} Returns @@ -1449,12 +1449,12 @@ def T1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^1} Returns ---------- - res : xp.array + res : np.array dim R^{N^1} Notes @@ -1543,7 +1543,7 @@ def T1_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ================================================================= def transpose_T1_dot(self, x): @@ -1552,12 +1552,12 @@ def transpose_T1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^1} Returns ---------- - res : xp.array + res : np.array dim R{N^1} Notes @@ -1626,7 +1626,7 @@ def transpose_T1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ================================================================= def X1_dot(self, x): @@ -1635,13 +1635,13 @@ def X1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^1} Returns ---------- res : list - 3 xp.arrays of dim R^{N^0} + 3 np.arrays of dim R^{N^0} Notes ----- @@ -1718,12 +1718,12 @@ def transpose_X1_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^0 x 3} Returns ---------- - res : xp.array + res : np.array dim R{N^1} Notes @@ -1738,9 +1738,9 @@ def transpose_X1_dot(self, x): # x dim check # x should be R{N^0 * 3} # assert len(x) == self.space.Ntot_0form * 3 - # x_loc_1 = self.space.extract_0(xp.split(x,3)[0]) - # x_loc_2 = self.space.extract_0(xp.split(x,3)[1]) - # x_loc_3 = self.space.extract_0(xp.split(x,3)[2]) + # x_loc_1 = self.space.extract_0(np.split(x,3)[0]) + # x_loc_2 = self.space.extract_0(np.split(x,3)[1]) + # x_loc_3 = self.space.extract_0(np.split(x,3)[2]) # x_loc = list((x_loc_1, x_loc_2, x_loc_3)) x_loc_1 = self.space.extract_0(x[0]) @@ -1794,7 +1794,7 @@ def transpose_X1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) ######################################## ########## 2-form formulation ########## @@ -1806,12 +1806,12 @@ def Q2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- - res : xp.array + res : np.array dim R^{N^2} Notes @@ -1882,7 +1882,7 @@ def Q2_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_Q2_dot(self, x): @@ -1891,12 +1891,12 @@ def transpose_Q2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^2} Returns ---------- - res : xp.array + res : np.array dim R{N^2} Notes @@ -1946,7 +1946,7 @@ def transpose_Q2_dot(self, x): res_2 = kron_matvec_3d([self.pts1_D_1.T, self.pts0_N_2.T, self.pts1_D_3.T], mat_f_2_c) res_3 = kron_matvec_3d([self.pts1_D_1.T, self.pts1_D_2.T, self.pts0_N_3.T], mat_f_3_c) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def T2_dot(self, x): @@ -1955,12 +1955,12 @@ def T2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- - res : xp.array + res : np.array dim R^{N^1} Notes @@ -2049,7 +2049,7 @@ def T2_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_T2_dot(self, x): @@ -2058,12 +2058,12 @@ def transpose_T2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^1} Returns ---------- - res : xp.array + res : np.array dim R{N^2} Notes @@ -2132,7 +2132,7 @@ def transpose_T2_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def P2_dot(self, x): @@ -2141,12 +2141,12 @@ def P2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- - res : xp.array + res : np.array dim R^{N^2} Notes @@ -2235,7 +2235,7 @@ def P2_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_P2_dot(self, x): @@ -2244,12 +2244,12 @@ def transpose_P2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^2} Returns ---------- - res : xp.array + res : np.array dim R{N^2} Notes @@ -2317,7 +2317,7 @@ def transpose_P2_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def S2_dot(self, x): @@ -2326,12 +2326,12 @@ def S2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- - res : xp.array + res : np.array dim R^{N^2} Notes @@ -2402,7 +2402,7 @@ def S2_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_S2_dot(self, x): @@ -2411,12 +2411,12 @@ def transpose_S2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^2} Returns ---------- - res : xp.array + res : np.array dim R{N^2} Notes @@ -2466,7 +2466,7 @@ def transpose_S2_dot(self, x): res_2 = kron_matvec_3d([self.pts1_D_1.T, self.pts0_N_2.T, self.pts1_D_3.T], mat_f_2_c) res_3 = kron_matvec_3d([self.pts1_D_1.T, self.pts1_D_2.T, self.pts0_N_3.T], mat_f_3_c) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def K2_dot(self, x): @@ -2475,12 +2475,12 @@ def K2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^3} Returns ---------- - res : xp.array + res : np.array dim R^{N^3} Notes @@ -2532,7 +2532,7 @@ def transpose_K2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^3} Returns @@ -2575,13 +2575,13 @@ def X2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- res : list - 3 xp.arrays of dim R^{N^0} + 3 np.arrays of dim R^{N^0} Notes ----- @@ -2658,12 +2658,12 @@ def transpose_X2_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^0 x 3} Returns ---------- - res : xp.array + res : np.array dim R{N^2} Notes @@ -2678,9 +2678,9 @@ def transpose_X2_dot(self, x): # x dim check # x should be R{N^0 * 3} # assert len(x) == self.space.Ntot_0form * 3 - # x_loc_1 = self.space.extract_0(xp.split(x,3)[0]) - # x_loc_2 = self.space.extract_0(xp.split(x,3)[1]) - # x_loc_3 = self.space.extract_0(xp.split(x,3)[2]) + # x_loc_1 = self.space.extract_0(np.split(x,3)[0]) + # x_loc_2 = self.space.extract_0(np.split(x,3)[1]) + # x_loc_3 = self.space.extract_0(np.split(x,3)[2]) # x_loc = list((x_loc_1, x_loc_2, x_loc_3)) x_loc_1 = self.space.extract_0(x[0]) @@ -2734,7 +2734,7 @@ def transpose_X2_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def Z20_dot(self, x): @@ -2743,12 +2743,12 @@ def Z20_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- - res : xp.array + res : np.array dim R^{N^1} Notes @@ -2835,7 +2835,7 @@ def Z20_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_Z20_dot(self, x): @@ -2844,12 +2844,12 @@ def transpose_Z20_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^2} Returns ---------- - res : xp.array + res : np.array dim R{N^1} Notes @@ -2918,7 +2918,7 @@ def transpose_Z20_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def Y20_dot(self, x): @@ -2927,12 +2927,12 @@ def Y20_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^0} Returns ---------- - res : xp.array + res : np.array dim R^{N^3} Notes @@ -2984,12 +2984,12 @@ def transpose_Y20_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^3} Returns ---------- - res : xp.array + res : np.array dim R{N^0} Notes @@ -3027,12 +3027,12 @@ def S20_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R^{N^2} Returns ---------- - res : xp.array + res : np.array dim R^{N^1} Notes @@ -3119,7 +3119,7 @@ def S20_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_S20_dot(self, x): @@ -3128,12 +3128,12 @@ def transpose_S20_dot(self, x): Parameters ---------- - x : xp.array + x : np.array dim R{N^1} Returns ---------- - res : xp.array + res : np.array dim R{N^2} Notes @@ -3202,4 +3202,4 @@ def transpose_S20_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/mhd_operators_3d_local.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/mhd_operators_3d_local.py index 6734a11b0..aef91b87d 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/mhd_operators_3d_local.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/mhd_operators_3d_local.py @@ -8,7 +8,7 @@ import sys -import cunumpy as xp +import numpy as np import scipy.sparse as spa import source_run.kernels_projectors_evaluation as ker_eva @@ -44,87 +44,87 @@ def __init__(self, tensor_space, n_quad): self.n_quad = n_quad # number of quadrature point per integration interval # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = [xp.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] - self.wts_loc = [xp.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] + self.pts_loc = [np.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] + self.wts_loc = [np.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] # set interpolation and histopolation coefficients self.coeff_i = [0, 0, 0] self.coeff_h = [0, 0, 0] for a in range(3): - if self.bc[a]: - self.coeff_i[a] = xp.zeros((1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = xp.zeros((1, 2 * self.p[a]), dtype=float) + if self.bc[a] == True: + self.coeff_i[a] = np.zeros((1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = np.zeros((1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = xp.array([1.0]) - self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) + self.coeff_i[a][0, :] = np.array([1.0]) + self.coeff_h[a][0, :] = np.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) - self.coeff_h[a][0, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_i[a][0, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) + self.coeff_h[a][0, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 6 * xp.array([1.0, -8.0, 20.0, -8.0, 1.0]) - self.coeff_h[a][0, :] = 1 / 6 * xp.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) + self.coeff_i[a][0, :] = 1 / 6 * np.array([1.0, -8.0, 20.0, -8.0, 1.0]) + self.coeff_h[a][0, :] = 1 / 6 * np.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 2 / 45 * xp.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) + self.coeff_i[a][0, :] = 2 / 45 * np.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) self.coeff_h[a][0, :] = ( - 2 / 45 * xp.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) + 2 / 45 * np.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) ) else: print("degree > 4 not implemented!") else: - self.coeff_i[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) + self.coeff_i[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = xp.array([1.0]) - self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) + self.coeff_i[a][0, :] = np.array([1.0]) + self.coeff_h[a][0, :] = np.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * xp.array([2.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, 2.0]) + self.coeff_i[a][0, :] = 1 / 2 * np.array([2.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 2 * np.array([0.0, 0.0, 2.0]) - self.coeff_h[a][0, :] = 1 / 2 * xp.array([3.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) - self.coeff_h[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, -1.0, 3.0]) + self.coeff_h[a][0, :] = 1 / 2 * np.array([3.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_h[a][2, :] = 1 / 2 * np.array([0.0, 0.0, -1.0, 3.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 18 * xp.array([18.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 18 * xp.array([-5.0, 40.0, -24.0, 8.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 18 * xp.array([3.0, -24.0, 60.0, -24.0, 3.0]) - self.coeff_i[a][3, :] = 1 / 18 * xp.array([-1.0, 8.0, -24.0, 40.0, -5.0]) - self.coeff_i[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, 0.0, 0.0, 18.0]) - - self.coeff_h[a][0, :] = 1 / 18 * xp.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 18 * xp.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 18 * xp.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) - self.coeff_h[a][3, :] = 1 / 18 * xp.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) - self.coeff_h[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) + self.coeff_i[a][0, :] = 1 / 18 * np.array([18.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 18 * np.array([-5.0, 40.0, -24.0, 8.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 18 * np.array([3.0, -24.0, 60.0, -24.0, 3.0]) + self.coeff_i[a][3, :] = 1 / 18 * np.array([-1.0, 8.0, -24.0, 40.0, -5.0]) + self.coeff_i[a][4, :] = 1 / 18 * np.array([0.0, 0.0, 0.0, 0.0, 18.0]) + + self.coeff_h[a][0, :] = 1 / 18 * np.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 18 * np.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 18 * np.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) + self.coeff_h[a][3, :] = 1 / 18 * np.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) + self.coeff_h[a][4, :] = 1 / 18 * np.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 1 / 360 * xp.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 360 * xp.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) - self.coeff_i[a][2, :] = 1 / 360 * xp.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) - self.coeff_i[a][3, :] = 1 / 360 * xp.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) - self.coeff_i[a][4, :] = 1 / 360 * xp.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) - self.coeff_i[a][5, :] = 1 / 360 * xp.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) - self.coeff_i[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) - - self.coeff_h[a][0, :] = 1 / 360 * xp.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 360 * xp.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 360 * xp.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) + self.coeff_i[a][0, :] = 1 / 360 * np.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 360 * np.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) + self.coeff_i[a][2, :] = 1 / 360 * np.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) + self.coeff_i[a][3, :] = 1 / 360 * np.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) + self.coeff_i[a][4, :] = 1 / 360 * np.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) + self.coeff_i[a][5, :] = 1 / 360 * np.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) + self.coeff_i[a][6, :] = 1 / 360 * np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) + + self.coeff_h[a][0, :] = 1 / 360 * np.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 360 * np.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 360 * np.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) self.coeff_h[a][3, :] = ( - 1 / 360 * xp.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) + 1 / 360 * np.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) ) - self.coeff_h[a][4, :] = 1 / 360 * xp.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) - self.coeff_h[a][5, :] = 1 / 360 * xp.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) - self.coeff_h[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) + self.coeff_h[a][4, :] = 1 / 360 * np.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) + self.coeff_h[a][5, :] = 1 / 360 * np.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) + self.coeff_h[a][6, :] = 1 / 360 * np.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) else: print("degree > 4 not implemented!") @@ -150,31 +150,31 @@ def __init__(self, tensor_space, n_quad): ) # number of non-vanishing D bf in interpolation interval (1, 2, 4, 6) self.x_int = [ - xp.zeros((n_lambda_int, n_int), dtype=float) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) + np.zeros((n_lambda_int, n_int), dtype=float) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) ] self.int_global_N = [ - xp.zeros((n_lambda_int, n_int_locbf_N), dtype=int) + np.zeros((n_lambda_int, n_int_locbf_N), dtype=int) for n_lambda_int, n_int_locbf_N in zip(n_lambda_int, self.n_int_locbf_N) ] self.int_global_D = [ - xp.zeros((n_lambda_int, n_int_locbf_D), dtype=int) + np.zeros((n_lambda_int, n_int_locbf_D), dtype=int) for n_lambda_int, n_int_locbf_D in zip(n_lambda_int, self.n_int_locbf_D) ] self.int_loccof_N = [ - xp.zeros((n_lambda_int, n_int_locbf_N), dtype=int) + np.zeros((n_lambda_int, n_int_locbf_N), dtype=int) for n_lambda_int, n_int_locbf_N in zip(n_lambda_int, self.n_int_locbf_N) ] self.int_loccof_D = [ - xp.zeros((n_lambda_int, n_int_locbf_D), dtype=int) + np.zeros((n_lambda_int, n_int_locbf_D), dtype=int) for n_lambda_int, n_int_locbf_D in zip(n_lambda_int, self.n_int_locbf_D) ] self.x_int_indices = [ - xp.zeros((n_lambda_int, n_int), dtype=int) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) + np.zeros((n_lambda_int, n_int), dtype=int) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) ] - self.coeffi_indices = [xp.zeros(n_lambda_int, dtype=int) for n_lambda_int in n_lambda_int] + self.coeffi_indices = [np.zeros(n_lambda_int, dtype=int) for n_lambda_int in n_lambda_int] self.n_int_nvcof_D = [None, None, None] self.n_int_nvcof_N = [None, None, None] @@ -186,7 +186,7 @@ def __init__(self, tensor_space, n_quad): self.int_shift_N = [0, 0, 0] for a in range(3): - if not self.bc[a]: + if self.bc[a] == False: # maximum number of non-vanishing coefficients if self.p[a] == 1: self.n_int_nvcof_D[a] = 2 @@ -197,39 +197,39 @@ def __init__(self, tensor_space, n_quad): self.n_int_nvcof_N[a] = 3 * self.p[a] - 2 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.int_add_D[a] = xp.arange(self.n_int[a] - 2) + 1 - self.int_add_N[a] = xp.arange(self.n_int[a] - 1) + 1 + self.int_add_D[a] = np.arange(self.n_int[a] - 2) + 1 + self.int_add_N[a] = np.arange(self.n_int[a] - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) if self.p[a] == 1: - self.int_shift_D[a] = xp.arange(self.NbaseD[a]) + self.int_shift_D[a] = np.arange(self.NbaseD[a]) else: - self.int_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 2) + self.int_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 2) self.int_shift_D[a][: 2 * self.p[a] - 2] = 0 self.int_shift_D[a][-(2 * self.p[a] - 2) :] = self.int_shift_D[a][-(2 * self.p[a] - 2)] # shift local coefficients --> global coefficients (N) if self.p[a] == 1: - self.int_shift_N[a] = xp.arange(self.NbaseN[a]) + self.int_shift_N[a] = np.arange(self.NbaseN[a]) self.int_shift_N[a][-1] = self.int_shift_N[a][-2] else: - self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) self.int_shift_N[a][: 2 * self.p[a] - 1] = 0 self.int_shift_N[a][-(2 * self.p[a] - 1) :] = self.int_shift_N[a][-(2 * self.p[a] - 1)] - counter_coeffi = xp.copy(self.p[a]) + counter_coeffi = np.copy(self.p[a]) for i in range(n_lambda_int[a]): # left boundary region if i < self.p[a] - 1: - self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) - self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) - self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + self.x_int_indices[a][i] = np.arange(self.n_int[a]) self.coeffi_indices[a][i] = i for j in range(2 * (self.p[a] - 1) + 1): xi = self.p[a] - 1 @@ -240,13 +240,13 @@ def __init__(self, tensor_space, n_quad): # right boundary region elif i > n_lambda_int[a] - self.p[a]: self.int_global_N[a][i] = ( - xp.arange(self.n_int_locbf_N[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) + np.arange(self.n_int_locbf_N[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) self.int_global_D[a][i] = ( - xp.arange(self.n_int_locbf_D[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) + np.arange(self.n_int_locbf_D[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) - self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + 2 * ( + self.x_int_indices[a][i] = np.arange(self.n_int[a]) + 2 * ( n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) self.coeffi_indices[a][i] = counter_coeffi @@ -260,20 +260,20 @@ def __init__(self, tensor_space, n_quad): # interior else: if self.p[a] == 1: - self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + i - self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + i + self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + i + self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + i self.int_global_N[a][-1] = self.int_global_N[a][-2] self.int_global_D[a][-1] = self.int_global_D[a][-2] else: - self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1) - self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1) + self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1) + self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1) if self.p[a] == 1: self.x_int_indices[a][i] = i else: - self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1)) + self.x_int_indices[a][i] = np.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1)) self.coeffi_indices[a][i] = self.p[a] - 1 @@ -284,8 +284,8 @@ def __init__(self, tensor_space, n_quad): # local coefficient index if self.p[a] == 1: - self.int_loccof_N[a][i] = xp.array([0, 1]) - self.int_loccof_D[a][-1] = xp.array([1]) + self.int_loccof_N[a][i] = np.array([0, 1]) + self.int_loccof_D[a][-1] = np.array([1]) else: if i > 0: @@ -293,8 +293,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.int_global_D[a][i, il] bol = k_glob_new == self.int_global_D[a][i - 1] - if xp.any(bol): - self.int_loccof_D[a][i, il] = self.int_loccof_D[a][i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.int_loccof_D[a][i, il] = self.int_loccof_D[a][i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int[a] - self.p[a] - (self.p[a] - 2)) and ( self.int_loccof_D[a][i, il] == 0 @@ -306,8 +306,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.int_global_N[a][i, il] bol = k_glob_new == self.int_global_N[a][i - 1] - if xp.any(bol): - self.int_loccof_N[a][i, il] = self.int_loccof_N[a][i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.int_loccof_N[a][i, il] = self.int_loccof_N[a][i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int[a] - self.p[a] - (self.p[a] - 2)) and ( self.int_loccof_N[a][i, il] == 0 @@ -327,24 +327,24 @@ def __init__(self, tensor_space, n_quad): # shift local coefficients --> global coefficients if self.p[a] == 1: - self.int_shift_D[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) - self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a]) + self.int_shift_D[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a]) else: - self.int_shift_D[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 2) - self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_D[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 2) + self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) for i in range(n_lambda_int[a]): # global indices of non-vanishing basis functions and position of coefficients in final matrix - self.int_global_N[a][i] = (xp.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] - self.int_global_D[a][i] = (xp.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] + self.int_global_N[a][i] = (np.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] + self.int_global_D[a][i] = (np.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] - self.int_loccof_N[a][i] = xp.arange(self.n_int_locbf_N[a] - 1, -1, -1) - self.int_loccof_D[a][i] = xp.arange(self.n_int_locbf_D[a] - 1, -1, -1) + self.int_loccof_N[a][i] = np.arange(self.n_int_locbf_N[a] - 1, -1, -1) + self.int_loccof_D[a][i] = np.arange(self.n_int_locbf_D[a] - 1, -1, -1) if self.p[a] == 1: self.x_int_indices[a][i] = i else: - self.x_int_indices[a][i] = (xp.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1))) % ( + self.x_int_indices[a][i] = (np.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1))) % ( 2 * self.Nel[a] ) @@ -356,41 +356,41 @@ def __init__(self, tensor_space, n_quad): ) % 1.0 # identify unique interpolation points to save memory - self.x_int[a] = xp.unique(self.x_int[a].flatten()) + self.x_int[a] = np.unique(self.x_int[a].flatten()) # set histopolation points, quadrature points and weights - n_lambda_his = [xp.copy(NbaseD) for NbaseD in self.NbaseD] # number of coefficients in space V1 + n_lambda_his = [np.copy(NbaseD) for NbaseD in self.NbaseD] # number of coefficients in space V1 self.n_his = [2 * p for p in self.p] # number of histopolation intervals self.n_his_locbf_N = [2 * p for p in self.p] # number of non-vanishing N bf in histopolation interval self.n_his_locbf_D = [2 * p - 1 for p in self.p] # number of non-vanishing D bf in histopolation interval self.x_his = [ - xp.zeros((n_lambda_his, n_his + 1), dtype=float) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) + np.zeros((n_lambda_his, n_his + 1), dtype=float) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) ] self.his_global_N = [ - xp.zeros((n_lambda_his, n_his_locbf_N), dtype=int) + np.zeros((n_lambda_his, n_his_locbf_N), dtype=int) for n_lambda_his, n_his_locbf_N in zip(n_lambda_his, self.n_his_locbf_N) ] self.his_global_D = [ - xp.zeros((n_lambda_his, n_his_locbf_D), dtype=int) + np.zeros((n_lambda_his, n_his_locbf_D), dtype=int) for n_lambda_his, n_his_locbf_D in zip(n_lambda_his, self.n_his_locbf_D) ] self.his_loccof_N = [ - xp.zeros((n_lambda_his, n_his_locbf_N), dtype=int) + np.zeros((n_lambda_his, n_his_locbf_N), dtype=int) for n_lambda_his, n_his_locbf_N in zip(n_lambda_his, self.n_his_locbf_N) ] self.his_loccof_D = [ - xp.zeros((n_lambda_his, n_his_locbf_D), dtype=int) + np.zeros((n_lambda_his, n_his_locbf_D), dtype=int) for n_lambda_his, n_his_locbf_D in zip(n_lambda_his, self.n_his_locbf_D) ] self.x_his_indices = [ - xp.zeros((n_lambda_his, n_his), dtype=int) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) + np.zeros((n_lambda_his, n_his), dtype=int) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) ] - self.coeffh_indices = [xp.zeros(n_lambda_his, dtype=int) for n_lambda_his in n_lambda_his] + self.coeffh_indices = [np.zeros(n_lambda_his, dtype=int) for n_lambda_his in n_lambda_his] self.pts = [0, 0, 0] self.wts = [0, 0, 0] @@ -405,37 +405,37 @@ def __init__(self, tensor_space, n_quad): self.his_shift_N = [0, 0, 0] for a in range(3): - if not self.bc[a]: + if self.bc[a] == False: # maximum number of non-vanishing coefficients self.n_his_nvcof_D[a] = 3 * self.p[a] - 2 self.n_his_nvcof_N[a] = 3 * self.p[a] - 1 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.his_add_D[a] = xp.arange(self.n_his[a] - 2) + 1 - self.his_add_N[a] = xp.arange(self.n_his[a] - 1) + 1 + self.his_add_D[a] = np.arange(self.n_his[a] - 2) + 1 + self.his_add_N[a] = np.arange(self.n_his[a] - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) - self.his_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 1) + self.his_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 1) self.his_shift_D[a][: 2 * self.p[a] - 1] = 0 self.his_shift_D[a][-(2 * self.p[a] - 1) :] = self.his_shift_D[a][-(2 * self.p[a] - 1)] # shift local coefficients --> global coefficients (N) - self.his_shift_N[a] = xp.arange(self.NbaseN[a]) - self.p[a] + self.his_shift_N[a] = np.arange(self.NbaseN[a]) - self.p[a] self.his_shift_N[a][: 2 * self.p[a]] = 0 self.his_shift_N[a][-2 * self.p[a] :] = self.his_shift_N[a][-2 * self.p[a]] - counter_coeffh = xp.copy(self.p[a]) + counter_coeffh = np.copy(self.p[a]) for i in range(n_lambda_his[a]): # left boundary region if i < self.p[a] - 1: - self.his_global_N[a][i] = xp.arange(self.n_his_locbf_N[a]) - self.his_global_D[a][i] = xp.arange(self.n_his_locbf_D[a]) + self.his_global_N[a][i] = np.arange(self.n_his_locbf_N[a]) + self.his_global_D[a][i] = np.arange(self.n_his_locbf_D[a]) - self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + self.x_his_indices[a][i] = np.arange(self.n_his[a]) self.coeffh_indices[a][i] = i for j in range(2 * self.p[a] + 1): xi = self.p[a] - 1 @@ -446,13 +446,13 @@ def __init__(self, tensor_space, n_quad): # right boundary region elif i > n_lambda_his[a] - self.p[a]: self.his_global_N[a][i] = ( - xp.arange(self.n_his_locbf_N[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) + np.arange(self.n_his_locbf_N[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) self.his_global_D[a][i] = ( - xp.arange(self.n_his_locbf_D[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) + np.arange(self.n_his_locbf_D[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) - self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + 2 * ( + self.x_his_indices[a][i] = np.arange(self.n_his[a]) + 2 * ( n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) self.coeffh_indices[a][i] = counter_coeffh @@ -465,10 +465,10 @@ def __init__(self, tensor_space, n_quad): # interior else: - self.his_global_N[a][i] = xp.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1) - self.his_global_D[a][i] = xp.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1) + self.his_global_N[a][i] = np.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1) + self.his_global_D[a][i] = np.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1) - self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1)) + self.x_his_indices[a][i] = np.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1)) self.coeffh_indices[a][i] = self.p[a] - 1 for j in range(2 * self.p[a] + 1): self.x_his[a][i, j] = ( @@ -481,8 +481,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.his_global_D[a][i, il] bol = k_glob_new == self.his_global_D[a][i - 1] - if xp.any(bol): - self.his_loccof_D[a][i, il] = self.his_loccof_D[a][i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.his_loccof_D[a][i, il] = self.his_loccof_D[a][i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his[a] - self.p[a] - (self.p[a] - 2)) and ( self.his_loccof_D[a][i, il] == 0 @@ -494,8 +494,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.his_global_N[a][i, il] bol = k_glob_new == self.his_global_N[a][i - 1] - if xp.any(bol): - self.his_loccof_N[a][i, il] = self.his_loccof_N[a][i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.his_loccof_N[a][i, il] = self.his_loccof_N[a][i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his[a] - self.p[a] - (self.p[a] - 2)) and ( self.his_loccof_N[a][i, il] == 0 @@ -505,9 +505,7 @@ def __init__(self, tensor_space, n_quad): # quadrature points and weights self.pts[a], self.wts[a] = bsp.quadrature_grid( - xp.unique(self.x_his[a].flatten()), - self.pts_loc[a], - self.wts_loc[a], + np.unique(self.x_his[a].flatten()), self.pts_loc[a], self.wts_loc[a] ) else: @@ -516,18 +514,18 @@ def __init__(self, tensor_space, n_quad): self.n_his_nvcof_N[a] = 2 * self.p[a] # shift local coefficients --> global coefficients (D) - self.his_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 1) + self.his_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 1) # shift local coefficients --> global coefficients (N) - self.his_shift_N[a] = xp.arange(self.NbaseD[a]) - self.p[a] + self.his_shift_N[a] = np.arange(self.NbaseD[a]) - self.p[a] for i in range(n_lambda_his[a]): - self.his_global_N[a][i] = (xp.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] - self.his_global_D[a][i] = (xp.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] - self.his_loccof_N[a][i] = xp.arange(self.n_his_locbf_N[a] - 1, -1, -1) - self.his_loccof_D[a][i] = xp.arange(self.n_his_locbf_D[a] - 1, -1, -1) + self.his_global_N[a][i] = (np.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] + self.his_global_D[a][i] = (np.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] + self.his_loccof_N[a][i] = np.arange(self.n_his_locbf_N[a] - 1, -1, -1) + self.his_loccof_D[a][i] = np.arange(self.n_his_locbf_D[a] - 1, -1, -1) - self.x_his_indices[a][i] = (xp.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1))) % ( + self.x_his_indices[a][i] = (np.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1))) % ( 2 * self.Nel[a] ) self.coeffh_indices[a][i] = 0 @@ -537,9 +535,7 @@ def __init__(self, tensor_space, n_quad): # quadrature points and weights self.pts[a], self.wts[a] = bsp.quadrature_grid( - xp.append(xp.unique(self.x_his[a].flatten() % 1.0), 1.0), - self.pts_loc[a], - self.wts_loc[a], + np.append(np.unique(self.x_his[a].flatten() % 1.0), 1.0), self.pts_loc[a], self.wts_loc[a] ) # evaluate N basis functions at interpolation and quadrature points @@ -560,9 +556,7 @@ def __init__(self, tensor_space, n_quad): self.basisD_his = [ bsp.collocation_matrix(T[1:-1], p - 1, pts.flatten(), bc, normalize=True).reshape( - pts[:, 0].size, - pts[0, :].size, - NbaseD, + pts[:, 0].size, pts[0, :].size, NbaseD ) for T, p, pts, bc, NbaseD in zip(self.T, self.p, self.pts, self.bc, self.NbaseD) ] @@ -592,7 +586,7 @@ def projection_Q_0form(self, domain): """ # non-vanishing coefficients - Q11 = xp.empty( + Q11 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -603,7 +597,7 @@ def projection_Q_0form(self, domain): ), dtype=float, ) - Q22 = xp.empty( + Q22 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -614,7 +608,7 @@ def projection_Q_0form(self, domain): ), dtype=float, ) - Q33 = xp.empty( + Q33 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -632,7 +626,7 @@ def projection_Q_0form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -688,7 +682,7 @@ def projection_Q_0form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -744,7 +738,7 @@ def projection_Q_0form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -800,7 +794,7 @@ def projection_Q_0form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -808,7 +802,7 @@ def projection_Q_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -825,7 +819,7 @@ def projection_Q_0form(self, domain): Q11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -833,7 +827,7 @@ def projection_Q_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -850,7 +844,7 @@ def projection_Q_0form(self, domain): Q22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -858,7 +852,7 @@ def projection_Q_0form(self, domain): self.n_his_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -901,7 +895,7 @@ def projection_Q_2form(self, domain): """ # non-vanishing coefficients - Q11 = xp.empty( + Q11 = np.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -912,7 +906,7 @@ def projection_Q_2form(self, domain): ), dtype=float, ) - Q22 = xp.empty( + Q22 = np.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -923,7 +917,7 @@ def projection_Q_2form(self, domain): ), dtype=float, ) - Q33 = xp.empty( + Q33 = np.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -941,7 +935,7 @@ def projection_Q_2form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -997,7 +991,7 @@ def projection_Q_2form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -1053,7 +1047,7 @@ def projection_Q_2form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -1109,7 +1103,7 @@ def projection_Q_2form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -1117,7 +1111,7 @@ def projection_Q_2form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_D[1], self.n_his_nvcof_D[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -1134,7 +1128,7 @@ def projection_Q_2form(self, domain): Q11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -1142,7 +1136,7 @@ def projection_Q_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_D[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -1159,7 +1153,7 @@ def projection_Q_2form(self, domain): Q22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -1167,7 +1161,7 @@ def projection_Q_2form(self, domain): self.n_his_nvcof_D[0], self.n_his_nvcof_D[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1210,7 +1204,7 @@ def projection_W_0form(self, domain): """ # non-vanishing coefficients - W1 = xp.empty( + W1 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1221,14 +1215,14 @@ def projection_W_0form(self, domain): ), dtype=float, ) - # W2 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2]), dtype=float) - # W3 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2]), dtype=float) + # W2 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2]), dtype=float) + # W3 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2]), dtype=float) # size of interpolation/quadrature points of the 3 components n_unique = [self.x_int[0].size, self.x_int[1].size, self.x_int[2].size] # assembly - mat_eq = xp.empty((n_unique[0], n_unique[1], n_unique[2]), dtype=float) + mat_eq = np.empty((n_unique[0], n_unique[1], n_unique[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -1290,7 +1284,7 @@ def projection_W_0form(self, domain): """ # conversion to sparse matrix - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1298,7 +1292,7 @@ def projection_W_0form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) # row indices @@ -1351,7 +1345,7 @@ def projection_T_0form(self, domain): """ # non-vanishing coefficients - T12 = xp.empty( + T12 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1362,7 +1356,7 @@ def projection_T_0form(self, domain): ), dtype=float, ) - T13 = xp.empty( + T13 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1374,7 +1368,7 @@ def projection_T_0form(self, domain): dtype=float, ) - T21 = xp.empty( + T21 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1385,7 +1379,7 @@ def projection_T_0form(self, domain): ), dtype=float, ) - T23 = xp.empty( + T23 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1397,7 +1391,7 @@ def projection_T_0form(self, domain): dtype=float, ) - T31 = xp.empty( + T31 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1408,7 +1402,7 @@ def projection_T_0form(self, domain): ), dtype=float, ) - T32 = xp.empty( + T32 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1426,7 +1420,7 @@ def projection_T_0form(self, domain): n_unique3 = [self.x_int[0].size, self.x_int[1].size, self.pts[2].flatten().size] # ================= assembly of 1 - component (pi1_1 : his, int, int) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -1521,7 +1515,7 @@ def projection_T_0form(self, domain): ) # ================= assembly of 2 - component (PI_1_2 : int, his, int) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -1627,7 +1621,7 @@ def projection_T_0form(self, domain): ) # ================= assembly of 3 - component (PI_1_3 : int, int, his) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -1733,7 +1727,7 @@ def projection_T_0form(self, domain): ) # conversion to sparse matrices (1 - component) - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1741,7 +1735,7 @@ def projection_T_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1757,7 +1751,7 @@ def projection_T_0form(self, domain): ) T12.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1765,7 +1759,7 @@ def projection_T_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1782,7 +1776,7 @@ def projection_T_0form(self, domain): T13.eliminate_zeros() # conversion to sparse matrices (2 - component) - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1790,7 +1784,7 @@ def projection_T_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1806,7 +1800,7 @@ def projection_T_0form(self, domain): ) T21.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1814,7 +1808,7 @@ def projection_T_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1831,7 +1825,7 @@ def projection_T_0form(self, domain): T23.eliminate_zeros() # conversion to sparse matrices (3 - component) - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1839,7 +1833,7 @@ def projection_T_0form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1855,7 +1849,7 @@ def projection_T_0form(self, domain): ) T31.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1863,7 +1857,7 @@ def projection_T_0form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1906,7 +1900,7 @@ def projection_T_1form(self, domain): """ # non-vanishing coefficients - T12 = xp.empty( + T12 = np.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -1917,7 +1911,7 @@ def projection_T_1form(self, domain): ), dtype=float, ) - T13 = xp.empty( + T13 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1929,7 +1923,7 @@ def projection_T_1form(self, domain): dtype=float, ) - T21 = xp.empty( + T21 = np.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -1940,7 +1934,7 @@ def projection_T_1form(self, domain): ), dtype=float, ) - T23 = xp.empty( + T23 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1952,7 +1946,7 @@ def projection_T_1form(self, domain): dtype=float, ) - T31 = xp.empty( + T31 = np.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -1963,7 +1957,7 @@ def projection_T_1form(self, domain): ), dtype=float, ) - T32 = xp.empty( + T32 = np.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -1981,7 +1975,7 @@ def projection_T_1form(self, domain): n_unique3 = [self.x_int[0].size, self.x_int[1].size, self.pts[2].flatten().size] # ================= assembly of 1 - component (pi1_1 : his, int, int) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -2076,7 +2070,7 @@ def projection_T_1form(self, domain): ) # ================= assembly of 2 - component (PI_1_2 : int, his, int) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -2171,7 +2165,7 @@ def projection_T_1form(self, domain): ) # ================= assembly of 3 - component (PI_1_3 : int, int, his) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -2266,7 +2260,7 @@ def projection_T_1form(self, domain): ) # conversion to sparse matrices (1 - component) - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -2274,7 +2268,7 @@ def projection_T_1form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_D[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2290,7 +2284,7 @@ def projection_T_1form(self, domain): ) T12.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -2298,7 +2292,7 @@ def projection_T_1form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_D[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2315,7 +2309,7 @@ def projection_T_1form(self, domain): T13.eliminate_zeros() # conversion to sparse matrices (2 - component) - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -2323,7 +2317,7 @@ def projection_T_1form(self, domain): self.n_int_nvcof_D[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2339,7 +2333,7 @@ def projection_T_1form(self, domain): ) T21.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -2347,7 +2341,7 @@ def projection_T_1form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_D[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2364,7 +2358,7 @@ def projection_T_1form(self, domain): T23.eliminate_zeros() # conversion to sparse matrices (3 - component) - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -2372,7 +2366,7 @@ def projection_T_1form(self, domain): self.n_int_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2388,7 +2382,7 @@ def projection_T_1form(self, domain): ) T31.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -2396,7 +2390,7 @@ def projection_T_1form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_D[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2439,7 +2433,7 @@ def projection_T_2form(self, domain): """ # non-vanishing coefficients - T12 = xp.empty( + T12 = np.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -2450,7 +2444,7 @@ def projection_T_2form(self, domain): ), dtype=float, ) - T13 = xp.empty( + T13 = np.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -2462,7 +2456,7 @@ def projection_T_2form(self, domain): dtype=float, ) - T21 = xp.empty( + T21 = np.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -2473,7 +2467,7 @@ def projection_T_2form(self, domain): ), dtype=float, ) - T23 = xp.empty( + T23 = np.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -2485,7 +2479,7 @@ def projection_T_2form(self, domain): dtype=float, ) - T31 = xp.empty( + T31 = np.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -2496,7 +2490,7 @@ def projection_T_2form(self, domain): ), dtype=float, ) - T32 = xp.empty( + T32 = np.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -2514,7 +2508,7 @@ def projection_T_2form(self, domain): n_unique3 = [self.x_int[0].size, self.x_int[1].size, self.pts[2].flatten().size] # ================= assembly of 1 - component (pi1_1 : his, int, int) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -2609,7 +2603,7 @@ def projection_T_2form(self, domain): ) # ================= assembly of 2 - component (PI_1_2 : int, his, int) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -2704,7 +2698,7 @@ def projection_T_2form(self, domain): ) # ================= assembly of 3 - component (PI_1_3 : int, int, his) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -2799,7 +2793,7 @@ def projection_T_2form(self, domain): ) # ============== conversion to sparse matrices (1 - component) ============== - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -2807,7 +2801,7 @@ def projection_T_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_N[1], self.n_int_nvcof_D[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2823,7 +2817,7 @@ def projection_T_2form(self, domain): ) T12.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -2831,7 +2825,7 @@ def projection_T_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_D[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2848,7 +2842,7 @@ def projection_T_2form(self, domain): T13.eliminate_zeros() # ============== conversion to sparse matrices (2 - component) ============== - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -2856,7 +2850,7 @@ def projection_T_2form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_D[1], self.n_int_nvcof_D[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2872,7 +2866,7 @@ def projection_T_2form(self, domain): ) T21.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -2880,7 +2874,7 @@ def projection_T_2form(self, domain): self.n_int_nvcof_D[0], self.n_his_nvcof_D[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2897,7 +2891,7 @@ def projection_T_2form(self, domain): T23.eliminate_zeros() # ============== conversion to sparse matrices (3 - component) ============== - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -2905,7 +2899,7 @@ def projection_T_2form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_D[1], self.n_his_nvcof_D[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2921,7 +2915,7 @@ def projection_T_2form(self, domain): ) T31.eliminate_zeros() - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -2929,7 +2923,7 @@ def projection_T_2form(self, domain): self.n_int_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_D[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2972,7 +2966,7 @@ def projection_S_0form(self, domain): """ # non-vanishing coefficients - S11 = xp.empty( + S11 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -2983,7 +2977,7 @@ def projection_S_0form(self, domain): ), dtype=float, ) - S22 = xp.empty( + S22 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -2994,7 +2988,7 @@ def projection_S_0form(self, domain): ), dtype=float, ) - S33 = xp.empty( + S33 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -3012,7 +3006,7 @@ def projection_S_0form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -3068,7 +3062,7 @@ def projection_S_0form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3124,7 +3118,7 @@ def projection_S_0form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3180,7 +3174,7 @@ def projection_S_0form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3188,7 +3182,7 @@ def projection_S_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3205,7 +3199,7 @@ def projection_S_0form(self, domain): S11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3213,7 +3207,7 @@ def projection_S_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3230,7 +3224,7 @@ def projection_S_0form(self, domain): S22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3238,7 +3232,7 @@ def projection_S_0form(self, domain): self.n_his_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3281,7 +3275,7 @@ def projection_S_2form(self, domain): """ # non-vanishing coefficients - S11 = xp.empty( + S11 = np.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -3292,7 +3286,7 @@ def projection_S_2form(self, domain): ), dtype=float, ) - S22 = xp.empty( + S22 = np.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -3303,7 +3297,7 @@ def projection_S_2form(self, domain): ), dtype=float, ) - S33 = xp.empty( + S33 = np.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -3321,7 +3315,7 @@ def projection_S_2form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -3377,7 +3371,7 @@ def projection_S_2form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3433,7 +3427,7 @@ def projection_S_2form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3489,7 +3483,7 @@ def projection_S_2form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -3497,7 +3491,7 @@ def projection_S_2form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_D[1], self.n_his_nvcof_D[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -3514,7 +3508,7 @@ def projection_S_2form(self, domain): S11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -3522,7 +3516,7 @@ def projection_S_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_D[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -3539,7 +3533,7 @@ def projection_S_2form(self, domain): S22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -3547,7 +3541,7 @@ def projection_S_2form(self, domain): self.n_his_nvcof_D[0], self.n_his_nvcof_D[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3588,7 +3582,7 @@ def projection_K_3form(self, domain): """ # non-vanishing coefficients - K = xp.zeros( + K = np.zeros( ( self.NbaseD[0], self.NbaseD[1], @@ -3603,7 +3597,7 @@ def projection_K_3form(self, domain): # evaluation of equilibrium pressure at interpolation points n_unique = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.pts[2].flatten().size] - mat_eq = xp.zeros((n_unique[0], n_unique[1], n_unique[2]), dtype=float) + mat_eq = np.zeros((n_unique[0], n_unique[1], n_unique[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3662,7 +3656,7 @@ def projection_K_3form(self, domain): ) # conversion to sparse matrix - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -3670,7 +3664,7 @@ def projection_K_3form(self, domain): self.n_his_nvcof_D[0], self.n_his_nvcof_D[1], self.n_his_nvcof_D[2], - ), + ) ) # row indices @@ -3717,7 +3711,7 @@ def projection_N_0form(self, domain): """ # non-vanishing coefficients - N11 = xp.empty( + N11 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -3728,7 +3722,7 @@ def projection_N_0form(self, domain): ), dtype=float, ) - N22 = xp.empty( + N22 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -3739,7 +3733,7 @@ def projection_N_0form(self, domain): ), dtype=float, ) - N33 = xp.empty( + N33 = np.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -3757,7 +3751,7 @@ def projection_N_0form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -3813,7 +3807,7 @@ def projection_N_0form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3869,7 +3863,7 @@ def projection_N_0form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3925,7 +3919,7 @@ def projection_N_0form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3933,7 +3927,7 @@ def projection_N_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3950,7 +3944,7 @@ def projection_N_0form(self, domain): N11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3958,7 +3952,7 @@ def projection_N_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3975,7 +3969,7 @@ def projection_N_0form(self, domain): N22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3983,7 +3977,7 @@ def projection_N_0form(self, domain): self.n_his_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -4026,7 +4020,7 @@ def projection_N_2form(self, domain): """ # non-vanishing coefficients - N11 = xp.empty( + N11 = np.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -4037,7 +4031,7 @@ def projection_N_2form(self, domain): ), dtype=float, ) - N22 = xp.empty( + N22 = np.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -4048,7 +4042,7 @@ def projection_N_2form(self, domain): ), dtype=float, ) - N33 = xp.empty( + N33 = np.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -4066,7 +4060,7 @@ def projection_N_2form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -4114,7 +4108,7 @@ def projection_N_2form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -4162,7 +4156,7 @@ def projection_N_2form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -4210,7 +4204,7 @@ def projection_N_2form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -4218,7 +4212,7 @@ def projection_N_2form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_D[1], self.n_his_nvcof_D[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -4235,7 +4229,7 @@ def projection_N_2form(self, domain): N11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -4243,7 +4237,7 @@ def projection_N_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_D[2], - ), + ) ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -4260,7 +4254,7 @@ def projection_N_2form(self, domain): N22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = xp.indices( + indices = np.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -4268,7 +4262,7 @@ def projection_N_2form(self, domain): self.n_his_nvcof_D[0], self.n_his_nvcof_D[1], self.n_int_nvcof_N[2], - ), + ) ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -4329,15 +4323,7 @@ class term_curl_beq: """ def __init__( - self, - tensor_space, - mapping, - kind_map=None, - params_map=None, - tensor_space_F=None, - cx=None, - cy=None, - cz=None, + self, tensor_space, mapping, kind_map=None, params_map=None, tensor_space_F=None, cx=None, cy=None, cz=None ): self.p = tensor_space.p # spline degrees self.Nel = tensor_space.Nel # number of elements @@ -4370,17 +4356,14 @@ def __init__( self.cz = cz # ============= evaluation of background magnetic field at quadrature points ========= - self.mat_curl_beq_1 = xp.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), - dtype=float, + self.mat_curl_beq_1 = np.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float ) - self.mat_curl_beq_2 = xp.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), - dtype=float, + self.mat_curl_beq_2 = np.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float ) - self.mat_curl_beq_3 = xp.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), - dtype=float, + self.mat_curl_beq_3 = np.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float ) if mapping == 0: @@ -4471,23 +4454,20 @@ def __init__( ) # ====================== perturbed magnetic field at quadrature points ========== - self.B1 = xp.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), - dtype=float, + self.B1 = np.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float ) - self.B2 = xp.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), - dtype=float, + self.B2 = np.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float ) - self.B3 = xp.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), - dtype=float, + self.B3 = np.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float ) # ========================== inner products ===================================== - self.F1 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) - self.F2 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) - self.F3 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + self.F1 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + self.F2 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + self.F3 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) # ============================================================ def inner_curl_beq(self, b1, b2, b3): @@ -4618,7 +4598,7 @@ def inner_curl_beq(self, b1, b2, b3): # ker_loc_3d.kernel_inner_2(self.Nel[0], self.Nel[1], self.Nel[2], self.p[0], self.p[1], self.p[2], self.n_quad[0], self.n_quad[1], self.n_quad[2], 0, 0, 0, self.wts[0], self.wts[1], self.wts[2], self.basisN[0], self.basisN[1], self.basisN[2], self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.F3, self.mat_curl_beq_3) # convert to 1d array and return - return xp.concatenate((self.F1.flatten(), self.F2.flatten(), self.F3.flatten())) + return np.concatenate((self.F1.flatten(), self.F2.flatten(), self.F3.flatten())) # ================ mass matrix in V1 =========================== @@ -4661,9 +4641,9 @@ def mass_curl(tensor_space, kind_map, params_map): Nbj3 = [NbaseD[2], NbaseN[2], NbaseD[2], NbaseN[2], NbaseD[2], NbaseD[2]] # ============= evaluation of background magnetic field at quadrature points ========= - mat_curl_beq_1 = xp.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) - mat_curl_beq_2 = xp.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) - mat_curl_beq_3 = xp.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) + mat_curl_beq_1 = np.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) + mat_curl_beq_2 = np.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) + mat_curl_beq_3 = np.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) ker_eva.kernel_eva_quad(Nel, n_quad, pts[0], pts[1], pts[2], mat_curl_beq_1, 61, kind_map, params_map) ker_eva.kernel_eva_quad(Nel, n_quad, pts[0], pts[1], pts[2], mat_curl_beq_2, 62, kind_map, params_map) @@ -4672,7 +4652,7 @@ def mass_curl(tensor_space, kind_map, params_map): # blocks of global mass matrix M = [ - xp.zeros((Nbi1, Nbi2, Nbi3, 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + np.zeros((Nbi1, Nbi2, Nbi3, 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) for Nbi1, Nbi2, Nbi3 in zip(Nbi1, Nbi2, Nbi3) ] @@ -4878,11 +4858,11 @@ def mass_curl(tensor_space, kind_map, params_map): counter = 0 for i in range(6): - indices = xp.indices((Nbi1[counter], Nbi2[counter], Nbi3[counter], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = np.indices((Nbi1[counter], Nbi2[counter], Nbi3[counter], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift1 = xp.arange(Nbi1[counter]) - p[0] - shift2 = xp.arange(Nbi2[counter]) - p[1] - shift3 = xp.arange(Nbi3[counter]) - p[2] + shift1 = np.arange(Nbi1[counter]) - p[0] + shift2 = np.arange(Nbi2[counter]) - p[1] + shift3 = np.arange(Nbi3[counter]) - p[2] row = (Nbi2[counter] * Nbi3[counter] * indices[0] + Nbi3[counter] * indices[1] + indices[2]).flatten() diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/projectors_local.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/projectors_local.py index 9ede3f608..f7f880f2b 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/projectors_local.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/projectors_local.py @@ -6,7 +6,7 @@ Classes for local projectors in 1D and 3D based on quasi-spline interpolation and histopolation. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.feec.bsplines as bsp @@ -41,85 +41,85 @@ def __init__(self, spline_space, n_quad): self.n_quad = n_quad # number of quadrature point per integration interval # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[0] - self.wts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[1] + self.pts_loc = np.polynomial.legendre.leggauss(self.n_quad)[0] + self.wts_loc = np.polynomial.legendre.leggauss(self.n_quad)[1] # set interpolation and histopolation coefficients - if self.bc: - self.coeff_i = xp.zeros((1, 2 * self.p - 1), dtype=float) - self.coeff_h = xp.zeros((1, 2 * self.p), dtype=float) + if self.bc == True: + self.coeff_i = np.zeros((1, 2 * self.p - 1), dtype=float) + self.coeff_h = np.zeros((1, 2 * self.p), dtype=float) if self.p == 1: - self.coeff_i[0, :] = xp.array([1.0]) - self.coeff_h[0, :] = xp.array([1.0, 1.0]) + self.coeff_i[0, :] = np.array([1.0]) + self.coeff_h[0, :] = np.array([1.0, 1.0]) elif self.p == 2: - self.coeff_i[0, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) - self.coeff_h[0, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_i[0, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) + self.coeff_h[0, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) elif self.p == 3: - self.coeff_i[0, :] = 1 / 6 * xp.array([1.0, -8.0, 20.0, -8.0, 1.0]) - self.coeff_h[0, :] = 1 / 6 * xp.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) + self.coeff_i[0, :] = 1 / 6 * np.array([1.0, -8.0, 20.0, -8.0, 1.0]) + self.coeff_h[0, :] = 1 / 6 * np.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) elif self.p == 4: - self.coeff_i[0, :] = 2 / 45 * xp.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) - self.coeff_h[0, :] = 2 / 45 * xp.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) + self.coeff_i[0, :] = 2 / 45 * np.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) + self.coeff_h[0, :] = 2 / 45 * np.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) else: print("degree > 4 not implemented!") else: - self.coeff_i = xp.zeros((2 * self.p - 1, 2 * self.p - 1), dtype=float) - self.coeff_h = xp.zeros((2 * self.p - 1, 2 * self.p), dtype=float) + self.coeff_i = np.zeros((2 * self.p - 1, 2 * self.p - 1), dtype=float) + self.coeff_h = np.zeros((2 * self.p - 1, 2 * self.p), dtype=float) if self.p == 1: - self.coeff_i[0, :] = xp.array([1.0]) - self.coeff_h[0, :] = xp.array([1.0, 1.0]) + self.coeff_i[0, :] = np.array([1.0]) + self.coeff_h[0, :] = np.array([1.0, 1.0]) elif self.p == 2: - self.coeff_i[0, :] = 1 / 2 * xp.array([2.0, 0.0, 0.0]) - self.coeff_i[1, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) - self.coeff_i[2, :] = 1 / 2 * xp.array([0.0, 0.0, 2.0]) + self.coeff_i[0, :] = 1 / 2 * np.array([2.0, 0.0, 0.0]) + self.coeff_i[1, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) + self.coeff_i[2, :] = 1 / 2 * np.array([0.0, 0.0, 2.0]) - self.coeff_h[0, :] = 1 / 2 * xp.array([3.0, -1.0, 0.0, 0.0]) - self.coeff_h[1, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) - self.coeff_h[2, :] = 1 / 2 * xp.array([0.0, 0.0, -1.0, 3.0]) + self.coeff_h[0, :] = 1 / 2 * np.array([3.0, -1.0, 0.0, 0.0]) + self.coeff_h[1, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_h[2, :] = 1 / 2 * np.array([0.0, 0.0, -1.0, 3.0]) elif self.p == 3: - self.coeff_i[0, :] = 1 / 18 * xp.array([18.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[1, :] = 1 / 18 * xp.array([-5.0, 40.0, -24.0, 8.0, -1.0]) - self.coeff_i[2, :] = 1 / 18 * xp.array([3.0, -24.0, 60.0, -24.0, 3.0]) - self.coeff_i[3, :] = 1 / 18 * xp.array([-1.0, 8.0, -24.0, 40.0, -5.0]) - self.coeff_i[4, :] = 1 / 18 * xp.array([0.0, 0.0, 0.0, 0.0, 18.0]) - - self.coeff_h[0, :] = 1 / 18 * xp.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) - self.coeff_h[1, :] = 1 / 18 * xp.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) - self.coeff_h[2, :] = 1 / 18 * xp.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) - self.coeff_h[3, :] = 1 / 18 * xp.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) - self.coeff_h[4, :] = 1 / 18 * xp.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) + self.coeff_i[0, :] = 1 / 18 * np.array([18.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[1, :] = 1 / 18 * np.array([-5.0, 40.0, -24.0, 8.0, -1.0]) + self.coeff_i[2, :] = 1 / 18 * np.array([3.0, -24.0, 60.0, -24.0, 3.0]) + self.coeff_i[3, :] = 1 / 18 * np.array([-1.0, 8.0, -24.0, 40.0, -5.0]) + self.coeff_i[4, :] = 1 / 18 * np.array([0.0, 0.0, 0.0, 0.0, 18.0]) + + self.coeff_h[0, :] = 1 / 18 * np.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) + self.coeff_h[1, :] = 1 / 18 * np.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) + self.coeff_h[2, :] = 1 / 18 * np.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) + self.coeff_h[3, :] = 1 / 18 * np.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) + self.coeff_h[4, :] = 1 / 18 * np.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) elif self.p == 4: - self.coeff_i[0, :] = 1 / 360 * xp.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[1, :] = 1 / 360 * xp.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) - self.coeff_i[2, :] = 1 / 360 * xp.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) - self.coeff_i[3, :] = 1 / 360 * xp.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) - self.coeff_i[4, :] = 1 / 360 * xp.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) - self.coeff_i[5, :] = 1 / 360 * xp.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) - self.coeff_i[6, :] = 1 / 360 * xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) - - self.coeff_h[0, :] = 1 / 360 * xp.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) - self.coeff_h[1, :] = 1 / 360 * xp.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) - self.coeff_h[2, :] = 1 / 360 * xp.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) - self.coeff_h[3, :] = 1 / 360 * xp.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) - self.coeff_h[4, :] = 1 / 360 * xp.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) - self.coeff_h[5, :] = 1 / 360 * xp.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) - self.coeff_h[6, :] = 1 / 360 * xp.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) + self.coeff_i[0, :] = 1 / 360 * np.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[1, :] = 1 / 360 * np.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) + self.coeff_i[2, :] = 1 / 360 * np.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) + self.coeff_i[3, :] = 1 / 360 * np.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) + self.coeff_i[4, :] = 1 / 360 * np.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) + self.coeff_i[5, :] = 1 / 360 * np.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) + self.coeff_i[6, :] = 1 / 360 * np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) + + self.coeff_h[0, :] = 1 / 360 * np.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) + self.coeff_h[1, :] = 1 / 360 * np.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) + self.coeff_h[2, :] = 1 / 360 * np.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) + self.coeff_h[3, :] = 1 / 360 * np.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) + self.coeff_h[4, :] = 1 / 360 * np.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) + self.coeff_h[5, :] = 1 / 360 * np.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) + self.coeff_h[6, :] = 1 / 360 * np.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) else: print("degree > 4 not implemented!") # set interpolation points - n_lambda_int = xp.copy(self.NbaseN) # number of coefficients in space V0 + n_lambda_int = np.copy(self.NbaseN) # number of coefficients in space V0 self.n_int = 2 * self.p - 1 # number of local interpolation points (1, 3, 5, 7, ...) if self.p == 1: @@ -134,25 +134,23 @@ def __init__(self, spline_space, n_quad): 2 * self.p - 2 ) # number of non-vanishing D bf in interpolation interval (1, 2, 4, 6, ...) - self.x_int = xp.zeros((n_lambda_int, self.n_int), dtype=float) # interpolation points for each coeff. + self.x_int = np.zeros((n_lambda_int, self.n_int), dtype=float) # interpolation points for each coeff. - self.int_global_N = xp.zeros( - (n_lambda_int, self.n_int_locbf_N), - dtype=int, + self.int_global_N = np.zeros( + (n_lambda_int, self.n_int_locbf_N), dtype=int ) # global indices of non-vanishing N bf - self.int_global_D = xp.zeros( - (n_lambda_int, self.n_int_locbf_D), - dtype=int, + self.int_global_D = np.zeros( + (n_lambda_int, self.n_int_locbf_D), dtype=int ) # global indices of non-vanishing D bf - self.int_loccof_N = xp.zeros((n_lambda_int, self.n_int_locbf_N), dtype=int) # index of non-vanishing coeff. (N) - self.int_loccof_D = xp.zeros((n_lambda_int, self.n_int_locbf_D), dtype=int) # index of non-vanishing coeff. (D) + self.int_loccof_N = np.zeros((n_lambda_int, self.n_int_locbf_N), dtype=int) # index of non-vanishing coeff. (N) + self.int_loccof_D = np.zeros((n_lambda_int, self.n_int_locbf_D), dtype=int) # index of non-vanishing coeff. (D) - self.x_int_indices = xp.zeros((n_lambda_int, self.n_int), dtype=int) + self.x_int_indices = np.zeros((n_lambda_int, self.n_int), dtype=int) - self.coeffi_indices = xp.zeros(n_lambda_int, dtype=int) + self.coeffi_indices = np.zeros(n_lambda_int, dtype=int) - if not self.bc: + if self.bc == False: # maximum number of non-vanishing coefficients if self.p == 1: self.n_int_nvcof_D = 2 @@ -162,39 +160,39 @@ def __init__(self, spline_space, n_quad): self.n_int_nvcof_N = 3 * self.p - 2 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.int_add_D = xp.arange(self.n_int - 2) + 1 - self.int_add_N = xp.arange(self.n_int - 1) + 1 + self.int_add_D = np.arange(self.n_int - 2) + 1 + self.int_add_N = np.arange(self.n_int - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) if self.p == 1: - self.int_shift_D = xp.arange(self.NbaseD) + self.int_shift_D = np.arange(self.NbaseD) else: - self.int_shift_D = xp.arange(self.NbaseD) - (self.p - 2) + self.int_shift_D = np.arange(self.NbaseD) - (self.p - 2) self.int_shift_D[: 2 * self.p - 2] = 0 self.int_shift_D[-(2 * self.p - 2) :] = self.int_shift_D[-(2 * self.p - 2)] # shift local coefficients --> global coefficients (N) if self.p == 1: - self.int_shift_N = xp.arange(self.NbaseN) + self.int_shift_N = np.arange(self.NbaseN) self.int_shift_N[-1] = self.int_shift_N[-2] else: - self.int_shift_N = xp.arange(self.NbaseN) - (self.p - 1) + self.int_shift_N = np.arange(self.NbaseN) - (self.p - 1) self.int_shift_N[: 2 * self.p - 1] = 0 self.int_shift_N[-(2 * self.p - 1) :] = self.int_shift_N[-(2 * self.p - 1)] - counter_coeffi = xp.copy(self.p) + counter_coeffi = np.copy(self.p) for i in range(n_lambda_int): # left boundary region if i < self.p - 1: - self.int_global_N[i] = xp.arange(self.n_int_locbf_N) - self.int_global_D[i] = xp.arange(self.n_int_locbf_D) + self.int_global_N[i] = np.arange(self.n_int_locbf_N) + self.int_global_D[i] = np.arange(self.n_int_locbf_D) - self.x_int_indices[i] = xp.arange(self.n_int) + self.x_int_indices[i] = np.arange(self.n_int) self.coeffi_indices[i] = i for j in range(2 * (self.p - 1) + 1): xi = self.p - 1 @@ -202,10 +200,10 @@ def __init__(self, spline_space, n_quad): # right boundary region elif i > n_lambda_int - self.p: - self.int_global_N[i] = xp.arange(self.n_int_locbf_N) + n_lambda_int - self.p - (self.p - 1) - self.int_global_D[i] = xp.arange(self.n_int_locbf_D) + n_lambda_int - self.p - (self.p - 1) + self.int_global_N[i] = np.arange(self.n_int_locbf_N) + n_lambda_int - self.p - (self.p - 1) + self.int_global_D[i] = np.arange(self.n_int_locbf_D) + n_lambda_int - self.p - (self.p - 1) - self.x_int_indices[i] = xp.arange(self.n_int) + 2 * (n_lambda_int - self.p - (self.p - 1)) + self.x_int_indices[i] = np.arange(self.n_int) + 2 * (n_lambda_int - self.p - (self.p - 1)) self.coeffi_indices[i] = counter_coeffi counter_coeffi += 1 for j in range(2 * (self.p - 1) + 1): @@ -215,20 +213,20 @@ def __init__(self, spline_space, n_quad): # interior else: if self.p == 1: - self.int_global_N[i] = xp.arange(self.n_int_locbf_N) + i - self.int_global_D[i] = xp.arange(self.n_int_locbf_D) + i + self.int_global_N[i] = np.arange(self.n_int_locbf_N) + i + self.int_global_D[i] = np.arange(self.n_int_locbf_D) + i self.int_global_N[-1] = self.int_global_N[-2] self.int_global_D[-1] = self.int_global_D[-2] else: - self.int_global_N[i] = xp.arange(self.n_int_locbf_N) + i - (self.p - 1) - self.int_global_D[i] = xp.arange(self.n_int_locbf_D) + i - (self.p - 1) + self.int_global_N[i] = np.arange(self.n_int_locbf_N) + i - (self.p - 1) + self.int_global_D[i] = np.arange(self.n_int_locbf_D) + i - (self.p - 1) if self.p == 1: self.x_int_indices[i] = i else: - self.x_int_indices[i] = xp.arange(self.n_int) + 2 * (i - (self.p - 1)) + self.x_int_indices[i] = np.arange(self.n_int) + 2 * (i - (self.p - 1)) self.coeffi_indices[i] = self.p - 1 for j in range(2 * (self.p - 1) + 1): @@ -236,8 +234,8 @@ def __init__(self, spline_space, n_quad): # local coefficient index if self.p == 1: - self.int_loccof_N[i] = xp.array([0, 1]) - self.int_loccof_D[-1] = xp.array([1]) + self.int_loccof_N[i] = np.array([0, 1]) + self.int_loccof_D[-1] = np.array([1]) else: if i > 0: @@ -245,8 +243,8 @@ def __init__(self, spline_space, n_quad): k_glob_new = self.int_global_D[i, il] bol = k_glob_new == self.int_global_D[i - 1] - if xp.any(bol): - self.int_loccof_D[i, il] = self.int_loccof_D[i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.int_loccof_D[i, il] = self.int_loccof_D[i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int - self.p - (self.p - 2)) and (self.int_loccof_D[i, il] == 0): self.int_loccof_D[i, il] = self.int_add_D[counter_D] @@ -256,8 +254,8 @@ def __init__(self, spline_space, n_quad): k_glob_new = self.int_global_N[i, il] bol = k_glob_new == self.int_global_N[i - 1] - if xp.any(bol): - self.int_loccof_N[i, il] = self.int_loccof_N[i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.int_loccof_N[i, il] = self.int_loccof_N[i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int - self.p - (self.p - 2)) and (self.int_loccof_N[i, il] == 0): self.int_loccof_N[i, il] = self.int_add_N[counter_N] @@ -275,24 +273,24 @@ def __init__(self, spline_space, n_quad): # shift local coefficients --> global coefficients if self.p == 1: - self.int_shift_D = xp.arange(self.NbaseN) - (self.p - 1) - self.int_shift_N = xp.arange(self.NbaseN) - (self.p) + self.int_shift_D = np.arange(self.NbaseN) - (self.p - 1) + self.int_shift_N = np.arange(self.NbaseN) - (self.p) else: - self.int_shift_D = xp.arange(self.NbaseN) - (self.p - 2) - self.int_shift_N = xp.arange(self.NbaseN) - (self.p - 1) + self.int_shift_D = np.arange(self.NbaseN) - (self.p - 2) + self.int_shift_N = np.arange(self.NbaseN) - (self.p - 1) for i in range(n_lambda_int): # global indices of non-vanishing basis functions and position of coefficients in final matrix - self.int_global_D[i] = (xp.arange(self.n_int_locbf_D) + i - (self.p - 1)) % self.NbaseD - self.int_loccof_D[i] = xp.arange(self.n_int_locbf_D - 1, -1, -1) + self.int_global_D[i] = (np.arange(self.n_int_locbf_D) + i - (self.p - 1)) % self.NbaseD + self.int_loccof_D[i] = np.arange(self.n_int_locbf_D - 1, -1, -1) - self.int_global_N[i] = (xp.arange(self.n_int_locbf_N) + i - (self.p - 1)) % self.NbaseN - self.int_loccof_N[i] = xp.arange(self.n_int_locbf_N - 1, -1, -1) + self.int_global_N[i] = (np.arange(self.n_int_locbf_N) + i - (self.p - 1)) % self.NbaseN + self.int_loccof_N[i] = np.arange(self.n_int_locbf_N - 1, -1, -1) if self.p == 1: self.x_int_indices[i] = i else: - self.x_int_indices[i] = xp.arange(self.n_int) + 2 * (i - (self.p - 1)) + self.x_int_indices[i] = np.arange(self.n_int) + 2 * (i - (self.p - 1)) self.coeffi_indices[i] = 0 @@ -300,55 +298,55 @@ def __init__(self, spline_space, n_quad): self.x_int[i, j] = ((self.T[i + 1 + int(j / 2)] + self.T[i + 1 + int((j + 1) / 2)]) / 2) % 1.0 # set histopolation points, quadrature points and weights - n_lambda_his = xp.copy(self.NbaseD) # number of coefficients in space V1 + n_lambda_his = np.copy(self.NbaseD) # number of coefficients in space V1 self.n_his = 2 * self.p # number of histopolation intervals (2, 4, 6, 8, ...) self.n_his_locbf_N = 2 * self.p # number of non-vanishing N bf in histopolation interval (2, 4, 6, 8, ...) self.n_his_locbf_D = 2 * self.p - 1 # number of non-vanishing D bf in histopolation interval (2, 4, 6, 8, ...) - self.x_his = xp.zeros((n_lambda_his, self.n_his + 1), dtype=float) # histopolation boundaries + self.x_his = np.zeros((n_lambda_his, self.n_his + 1), dtype=float) # histopolation boundaries - self.his_global_N = xp.zeros((n_lambda_his, self.n_his_locbf_N), dtype=int) - self.his_global_D = xp.zeros((n_lambda_his, self.n_his_locbf_D), dtype=int) + self.his_global_N = np.zeros((n_lambda_his, self.n_his_locbf_N), dtype=int) + self.his_global_D = np.zeros((n_lambda_his, self.n_his_locbf_D), dtype=int) - self.his_loccof_N = xp.zeros((n_lambda_his, self.n_his_locbf_N), dtype=int) - self.his_loccof_D = xp.zeros((n_lambda_his, self.n_his_locbf_D), dtype=int) + self.his_loccof_N = np.zeros((n_lambda_his, self.n_his_locbf_N), dtype=int) + self.his_loccof_D = np.zeros((n_lambda_his, self.n_his_locbf_D), dtype=int) - self.x_his_indices = xp.zeros((n_lambda_his, self.n_his), dtype=int) + self.x_his_indices = np.zeros((n_lambda_his, self.n_his), dtype=int) - self.coeffh_indices = xp.zeros(n_lambda_his, dtype=int) + self.coeffh_indices = np.zeros(n_lambda_his, dtype=int) - if not self.bc: + if self.bc == False: # maximum number of non-vanishing coefficients self.n_his_nvcof_D = 3 * self.p - 2 self.n_his_nvcof_N = 3 * self.p - 1 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.his_add_D = xp.arange(self.n_his - 2) + 1 - self.his_add_N = xp.arange(self.n_his - 1) + 1 + self.his_add_D = np.arange(self.n_his - 2) + 1 + self.his_add_N = np.arange(self.n_his - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) - self.his_shift_D = xp.arange(self.NbaseD) - (self.p - 1) + self.his_shift_D = np.arange(self.NbaseD) - (self.p - 1) self.his_shift_D[: 2 * self.p - 1] = 0 self.his_shift_D[-(2 * self.p - 1) :] = self.his_shift_D[-(2 * self.p - 1)] # shift local coefficients --> global coefficients (N) - self.his_shift_N = xp.arange(self.NbaseN) - self.p + self.his_shift_N = np.arange(self.NbaseN) - self.p self.his_shift_N[: 2 * self.p] = 0 self.his_shift_N[-2 * self.p :] = self.his_shift_N[-2 * self.p] - counter_coeffh = xp.copy(self.p) + counter_coeffh = np.copy(self.p) for i in range(n_lambda_his): # left boundary region if i < self.p - 1: - self.his_global_N[i] = xp.arange(self.n_his_locbf_N) - self.his_global_D[i] = xp.arange(self.n_his_locbf_D) + self.his_global_N[i] = np.arange(self.n_his_locbf_N) + self.his_global_D[i] = np.arange(self.n_his_locbf_D) - self.x_his_indices[i] = xp.arange(self.n_his) + self.x_his_indices[i] = np.arange(self.n_his) self.coeffh_indices[i] = i for j in range(2 * self.p + 1): xi = self.p - 1 @@ -356,10 +354,10 @@ def __init__(self, spline_space, n_quad): # right boundary region elif i > n_lambda_his - self.p: - self.his_global_N[i] = xp.arange(self.n_his_locbf_N) + n_lambda_his - self.p - (self.p - 1) - self.his_global_D[i] = xp.arange(self.n_his_locbf_D) + n_lambda_his - self.p - (self.p - 1) + self.his_global_N[i] = np.arange(self.n_his_locbf_N) + n_lambda_his - self.p - (self.p - 1) + self.his_global_D[i] = np.arange(self.n_his_locbf_D) + n_lambda_his - self.p - (self.p - 1) - self.x_his_indices[i] = xp.arange(self.n_his) + 2 * (n_lambda_his - self.p - (self.p - 1)) + self.x_his_indices[i] = np.arange(self.n_his) + 2 * (n_lambda_his - self.p - (self.p - 1)) self.coeffh_indices[i] = counter_coeffh counter_coeffh += 1 for j in range(2 * self.p + 1): @@ -368,10 +366,10 @@ def __init__(self, spline_space, n_quad): # interior else: - self.his_global_N[i] = xp.arange(self.n_his_locbf_N) + i - (self.p - 1) - self.his_global_D[i] = xp.arange(self.n_his_locbf_D) + i - (self.p - 1) + self.his_global_N[i] = np.arange(self.n_his_locbf_N) + i - (self.p - 1) + self.his_global_D[i] = np.arange(self.n_his_locbf_D) + i - (self.p - 1) - self.x_his_indices[i] = xp.arange(self.n_his) + 2 * (i - (self.p - 1)) + self.x_his_indices[i] = np.arange(self.n_his) + 2 * (i - (self.p - 1)) self.coeffh_indices[i] = self.p - 1 for j in range(2 * self.p + 1): self.x_his[i, j] = (self.T[i + 1 + int(j / 2)] + self.T[i + 1 + int((j + 1) / 2)]) / 2 @@ -382,8 +380,8 @@ def __init__(self, spline_space, n_quad): k_glob_new = self.his_global_D[i, il] bol = k_glob_new == self.his_global_D[i - 1] - if xp.any(bol): - self.his_loccof_D[i, il] = self.his_loccof_D[i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.his_loccof_D[i, il] = self.his_loccof_D[i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his - self.p - (self.p - 2)) and (self.his_loccof_D[i, il] == 0): self.his_loccof_D[i, il] = self.his_add_D[counter_D] @@ -393,15 +391,15 @@ def __init__(self, spline_space, n_quad): k_glob_new = self.his_global_N[i, il] bol = k_glob_new == self.his_global_N[i - 1] - if xp.any(bol): - self.his_loccof_N[i, il] = self.his_loccof_N[i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.his_loccof_N[i, il] = self.his_loccof_N[i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his - self.p - (self.p - 2)) and (self.his_loccof_N[i, il] == 0): self.his_loccof_N[i, il] = self.his_add_N[counter_N] counter_N += 1 # quadrature points and weights - self.pts, self.wts = bsp.quadrature_grid(xp.unique(self.x_his.flatten()), self.pts_loc, self.wts_loc) + self.pts, self.wts = bsp.quadrature_grid(np.unique(self.x_his.flatten()), self.pts_loc, self.wts_loc) else: # maximum number of non-vanishing coefficients @@ -409,33 +407,31 @@ def __init__(self, spline_space, n_quad): self.n_his_nvcof_N = 2 * self.p # shift local coefficients --> global coefficients - self.his_shift_D = xp.arange(self.NbaseD) - (self.p - 1) - self.his_shift_N = xp.arange(self.NbaseD) - self.p + self.his_shift_D = np.arange(self.NbaseD) - (self.p - 1) + self.his_shift_N = np.arange(self.NbaseD) - self.p for i in range(n_lambda_his): - self.his_global_N[i] = (xp.arange(self.n_his_locbf_N) + i - (self.p - 1)) % self.NbaseN - self.his_global_D[i] = (xp.arange(self.n_his_locbf_D) + i - (self.p - 1)) % self.NbaseD - self.his_loccof_N[i] = xp.arange(self.n_his_locbf_N - 1, -1, -1) - self.his_loccof_D[i] = xp.arange(self.n_his_locbf_D - 1, -1, -1) + self.his_global_N[i] = (np.arange(self.n_his_locbf_N) + i - (self.p - 1)) % self.NbaseN + self.his_global_D[i] = (np.arange(self.n_his_locbf_D) + i - (self.p - 1)) % self.NbaseD + self.his_loccof_N[i] = np.arange(self.n_his_locbf_N - 1, -1, -1) + self.his_loccof_D[i] = np.arange(self.n_his_locbf_D - 1, -1, -1) - self.x_his_indices[i] = xp.arange(self.n_his) + 2 * (i - (self.p - 1)) + self.x_his_indices[i] = np.arange(self.n_his) + 2 * (i - (self.p - 1)) self.coeffh_indices[i] = 0 for j in range(2 * self.p + 1): self.x_his[i, j] = (self.T[i + 1 + int(j / 2)] + self.T[i + 1 + int((j + 1) / 2)]) / 2 # quadrature points and weights self.pts, self.wts = bsp.quadrature_grid( - xp.append(xp.unique(self.x_his.flatten() % 1.0), 1.0), - self.pts_loc, - self.wts_loc, + np.append(np.unique(self.x_his.flatten() % 1.0), 1.0), self.pts_loc, self.wts_loc ) # quasi interpolation def pi_0(self, fun): - lambdas = xp.zeros(self.NbaseN, dtype=float) + lambdas = np.zeros(self.NbaseN, dtype=float) # evaluate function at interpolation points - mat_f = fun(xp.unique(self.x_int.flatten())) + mat_f = fun(np.unique(self.x_int.flatten())) for i in range(self.NbaseN): for j in range(self.n_int): @@ -445,7 +441,7 @@ def pi_0(self, fun): # quasi histopolation def pi_1(self, fun): - lambdas = xp.zeros(self.NbaseD, dtype=float) + lambdas = np.zeros(self.NbaseD, dtype=float) # evaluate function at quadrature points mat_f = fun(self.pts) @@ -463,17 +459,17 @@ def pi_1(self, fun): # projection matrices of products of basis functions: pi0_i(A_j*B_k) and pi1_i(A_j*B_k) def projection_matrices_1d(self, bc_kind=["free", "free"]): - PI0_NN = xp.empty((self.NbaseN, self.NbaseN, self.NbaseN), dtype=float) - PI0_DN = xp.empty((self.NbaseN, self.NbaseD, self.NbaseN), dtype=float) - PI0_DD = xp.empty((self.NbaseN, self.NbaseD, self.NbaseD), dtype=float) + PI0_NN = np.empty((self.NbaseN, self.NbaseN, self.NbaseN), dtype=float) + PI0_DN = np.empty((self.NbaseN, self.NbaseD, self.NbaseN), dtype=float) + PI0_DD = np.empty((self.NbaseN, self.NbaseD, self.NbaseD), dtype=float) - PI1_NN = xp.empty((self.NbaseD, self.NbaseN, self.NbaseN), dtype=float) - PI1_DN = xp.empty((self.NbaseD, self.NbaseD, self.NbaseN), dtype=float) - PI1_DD = xp.empty((self.NbaseD, self.NbaseD, self.NbaseD), dtype=float) + PI1_NN = np.empty((self.NbaseD, self.NbaseN, self.NbaseN), dtype=float) + PI1_DN = np.empty((self.NbaseD, self.NbaseD, self.NbaseN), dtype=float) + PI1_DD = np.empty((self.NbaseD, self.NbaseD, self.NbaseD), dtype=float) # ========= PI0__NN and PI1_NN ============= - ci = xp.zeros(self.NbaseN, dtype=float) - cj = xp.zeros(self.NbaseN, dtype=float) + ci = np.zeros(self.NbaseN, dtype=float) + cj = np.zeros(self.NbaseN, dtype=float) for i in range(self.NbaseN): for j in range(self.NbaseN): @@ -489,8 +485,8 @@ def projection_matrices_1d(self, bc_kind=["free", "free"]): PI1_NN[:, i, j] = self.pi_1(fun) # ========= PI0__DN and PI1_DN ============= - ci = xp.zeros(self.NbaseD, dtype=float) - cj = xp.zeros(self.NbaseN, dtype=float) + ci = np.zeros(self.NbaseD, dtype=float) + cj = np.zeros(self.NbaseN, dtype=float) for i in range(self.NbaseD): for j in range(self.NbaseN): @@ -506,8 +502,8 @@ def projection_matrices_1d(self, bc_kind=["free", "free"]): PI1_DN[:, i, j] = self.pi_1(fun) # ========= PI0__DD and PI1_DD ============= - ci = xp.zeros(self.NbaseD, dtype=float) - cj = xp.zeros(self.NbaseD, dtype=float) + ci = np.zeros(self.NbaseD, dtype=float) + cj = np.zeros(self.NbaseD, dtype=float) for i in range(self.NbaseD): for j in range(self.NbaseD): @@ -522,8 +518,8 @@ def projection_matrices_1d(self, bc_kind=["free", "free"]): PI0_DD[:, i, j] = self.pi_0(fun) PI1_DD[:, i, j] = self.pi_1(fun) - PI0_ND = xp.transpose(PI0_DN, (0, 2, 1)) - PI1_ND = xp.transpose(PI1_DN, (0, 2, 1)) + PI0_ND = np.transpose(PI0_DN, (0, 2, 1)) + PI1_ND = np.transpose(PI1_DN, (0, 2, 1)) # remove contributions from first and last N-splines if bc_kind[0] == "dirichlet": @@ -548,25 +544,25 @@ def projection_matrices_1d(self, bc_kind=["free", "free"]): PI1_DN[:, :, -1] = 0.0 PI1_ND[:, -1, :] = 0.0 - PI0_NN_indices = xp.nonzero(PI0_NN) - PI0_DN_indices = xp.nonzero(PI0_DN) - PI0_ND_indices = xp.nonzero(PI0_ND) - PI0_DD_indices = xp.nonzero(PI0_DD) + PI0_NN_indices = np.nonzero(PI0_NN) + PI0_DN_indices = np.nonzero(PI0_DN) + PI0_ND_indices = np.nonzero(PI0_ND) + PI0_DD_indices = np.nonzero(PI0_DD) - PI1_NN_indices = xp.nonzero(PI1_NN) - PI1_DN_indices = xp.nonzero(PI1_DN) - PI1_ND_indices = xp.nonzero(PI1_ND) - PI1_DD_indices = xp.nonzero(PI1_DD) + PI1_NN_indices = np.nonzero(PI1_NN) + PI1_DN_indices = np.nonzero(PI1_DN) + PI1_ND_indices = np.nonzero(PI1_ND) + PI1_DD_indices = np.nonzero(PI1_DD) - PI0_NN_indices = xp.vstack((PI0_NN_indices[0], PI0_NN_indices[1], PI0_NN_indices[2])) - PI0_DN_indices = xp.vstack((PI0_DN_indices[0], PI0_DN_indices[1], PI0_DN_indices[2])) - PI0_ND_indices = xp.vstack((PI0_ND_indices[0], PI0_ND_indices[1], PI0_ND_indices[2])) - PI0_DD_indices = xp.vstack((PI0_DD_indices[0], PI0_DD_indices[1], PI0_DD_indices[2])) + PI0_NN_indices = np.vstack((PI0_NN_indices[0], PI0_NN_indices[1], PI0_NN_indices[2])) + PI0_DN_indices = np.vstack((PI0_DN_indices[0], PI0_DN_indices[1], PI0_DN_indices[2])) + PI0_ND_indices = np.vstack((PI0_ND_indices[0], PI0_ND_indices[1], PI0_ND_indices[2])) + PI0_DD_indices = np.vstack((PI0_DD_indices[0], PI0_DD_indices[1], PI0_DD_indices[2])) - PI1_NN_indices = xp.vstack((PI1_NN_indices[0], PI1_NN_indices[1], PI1_NN_indices[2])) - PI1_DN_indices = xp.vstack((PI1_DN_indices[0], PI1_DN_indices[1], PI1_DN_indices[2])) - PI1_ND_indices = xp.vstack((PI1_ND_indices[0], PI1_ND_indices[1], PI1_ND_indices[2])) - PI1_DD_indices = xp.vstack((PI1_DD_indices[0], PI1_DD_indices[1], PI1_DD_indices[2])) + PI1_NN_indices = np.vstack((PI1_NN_indices[0], PI1_NN_indices[1], PI1_NN_indices[2])) + PI1_DN_indices = np.vstack((PI1_DN_indices[0], PI1_DN_indices[1], PI1_DN_indices[2])) + PI1_ND_indices = np.vstack((PI1_ND_indices[0], PI1_ND_indices[1], PI1_ND_indices[2])) + PI1_DD_indices = np.vstack((PI1_DD_indices[0], PI1_DD_indices[1], PI1_DD_indices[2])) return ( PI0_NN, @@ -621,87 +617,87 @@ def __init__(self, tensor_space, n_quad): self.polar = False # local projectors for polar splines are not implemented yet # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = [xp.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] - self.wts_loc = [xp.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] + self.pts_loc = [np.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] + self.wts_loc = [np.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] # set interpolation and histopolation coefficients self.coeff_i = [0, 0, 0] self.coeff_h = [0, 0, 0] for a in range(3): - if self.bc[a]: - self.coeff_i[a] = xp.zeros((1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = xp.zeros((1, 2 * self.p[a]), dtype=float) + if self.bc[a] == True: + self.coeff_i[a] = np.zeros((1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = np.zeros((1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = xp.array([1.0]) - self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) + self.coeff_i[a][0, :] = np.array([1.0]) + self.coeff_h[a][0, :] = np.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) - self.coeff_h[a][0, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_i[a][0, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) + self.coeff_h[a][0, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 6 * xp.array([1.0, -8.0, 20.0, -8.0, 1.0]) - self.coeff_h[a][0, :] = 1 / 6 * xp.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) + self.coeff_i[a][0, :] = 1 / 6 * np.array([1.0, -8.0, 20.0, -8.0, 1.0]) + self.coeff_h[a][0, :] = 1 / 6 * np.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 2 / 45 * xp.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) + self.coeff_i[a][0, :] = 2 / 45 * np.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) self.coeff_h[a][0, :] = ( - 2 / 45 * xp.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) + 2 / 45 * np.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) ) else: print("degree > 4 not implemented!") else: - self.coeff_i[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) + self.coeff_i[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = xp.array([1.0]) - self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) + self.coeff_i[a][0, :] = np.array([1.0]) + self.coeff_h[a][0, :] = np.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * xp.array([2.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, 2.0]) + self.coeff_i[a][0, :] = 1 / 2 * np.array([2.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 2 * np.array([0.0, 0.0, 2.0]) - self.coeff_h[a][0, :] = 1 / 2 * xp.array([3.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) - self.coeff_h[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, -1.0, 3.0]) + self.coeff_h[a][0, :] = 1 / 2 * np.array([3.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_h[a][2, :] = 1 / 2 * np.array([0.0, 0.0, -1.0, 3.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 18 * xp.array([18.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 18 * xp.array([-5.0, 40.0, -24.0, 8.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 18 * xp.array([3.0, -24.0, 60.0, -24.0, 3.0]) - self.coeff_i[a][3, :] = 1 / 18 * xp.array([-1.0, 8.0, -24.0, 40.0, -5.0]) - self.coeff_i[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, 0.0, 0.0, 18.0]) - - self.coeff_h[a][0, :] = 1 / 18 * xp.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 18 * xp.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 18 * xp.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) - self.coeff_h[a][3, :] = 1 / 18 * xp.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) - self.coeff_h[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) + self.coeff_i[a][0, :] = 1 / 18 * np.array([18.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 18 * np.array([-5.0, 40.0, -24.0, 8.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 18 * np.array([3.0, -24.0, 60.0, -24.0, 3.0]) + self.coeff_i[a][3, :] = 1 / 18 * np.array([-1.0, 8.0, -24.0, 40.0, -5.0]) + self.coeff_i[a][4, :] = 1 / 18 * np.array([0.0, 0.0, 0.0, 0.0, 18.0]) + + self.coeff_h[a][0, :] = 1 / 18 * np.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 18 * np.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 18 * np.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) + self.coeff_h[a][3, :] = 1 / 18 * np.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) + self.coeff_h[a][4, :] = 1 / 18 * np.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 1 / 360 * xp.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 360 * xp.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) - self.coeff_i[a][2, :] = 1 / 360 * xp.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) - self.coeff_i[a][3, :] = 1 / 360 * xp.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) - self.coeff_i[a][4, :] = 1 / 360 * xp.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) - self.coeff_i[a][5, :] = 1 / 360 * xp.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) - self.coeff_i[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) - - self.coeff_h[a][0, :] = 1 / 360 * xp.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 360 * xp.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 360 * xp.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) + self.coeff_i[a][0, :] = 1 / 360 * np.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 360 * np.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) + self.coeff_i[a][2, :] = 1 / 360 * np.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) + self.coeff_i[a][3, :] = 1 / 360 * np.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) + self.coeff_i[a][4, :] = 1 / 360 * np.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) + self.coeff_i[a][5, :] = 1 / 360 * np.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) + self.coeff_i[a][6, :] = 1 / 360 * np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) + + self.coeff_h[a][0, :] = 1 / 360 * np.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 360 * np.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 360 * np.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) self.coeff_h[a][3, :] = ( - 1 / 360 * xp.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) + 1 / 360 * np.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) ) - self.coeff_h[a][4, :] = 1 / 360 * xp.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) - self.coeff_h[a][5, :] = 1 / 360 * xp.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) - self.coeff_h[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) + self.coeff_h[a][4, :] = 1 / 360 * np.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) + self.coeff_h[a][5, :] = 1 / 360 * np.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) + self.coeff_h[a][6, :] = 1 / 360 * np.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) else: print("degree > 4 not implemented!") @@ -727,31 +723,31 @@ def __init__(self, tensor_space, n_quad): ) # number of non-vanishing D bf in interpolation interval (1, 2, 4, 6) self.x_int = [ - xp.zeros((n_lambda_int, n_int), dtype=float) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) + np.zeros((n_lambda_int, n_int), dtype=float) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) ] self.int_global_N = [ - xp.zeros((n_lambda_int, n_int_locbf_N), dtype=int) + np.zeros((n_lambda_int, n_int_locbf_N), dtype=int) for n_lambda_int, n_int_locbf_N in zip(n_lambda_int, self.n_int_locbf_N) ] self.int_global_D = [ - xp.zeros((n_lambda_int, n_int_locbf_D), dtype=int) + np.zeros((n_lambda_int, n_int_locbf_D), dtype=int) for n_lambda_int, n_int_locbf_D in zip(n_lambda_int, self.n_int_locbf_D) ] self.int_loccof_N = [ - xp.zeros((n_lambda_int, n_int_locbf_N), dtype=int) + np.zeros((n_lambda_int, n_int_locbf_N), dtype=int) for n_lambda_int, n_int_locbf_N in zip(n_lambda_int, self.n_int_locbf_N) ] self.int_loccof_D = [ - xp.zeros((n_lambda_int, n_int_locbf_D), dtype=int) + np.zeros((n_lambda_int, n_int_locbf_D), dtype=int) for n_lambda_int, n_int_locbf_D in zip(n_lambda_int, self.n_int_locbf_D) ] self.x_int_indices = [ - xp.zeros((n_lambda_int, n_int), dtype=int) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) + np.zeros((n_lambda_int, n_int), dtype=int) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) ] - self.coeffi_indices = [xp.zeros(n_lambda_int, dtype=int) for n_lambda_int in n_lambda_int] + self.coeffi_indices = [np.zeros(n_lambda_int, dtype=int) for n_lambda_int in n_lambda_int] self.n_int_nvcof_D = [None, None, None] self.n_int_nvcof_N = [None, None, None] @@ -763,7 +759,7 @@ def __init__(self, tensor_space, n_quad): self.int_shift_N = [0, 0, 0] for a in range(3): - if not self.bc[a]: + if self.bc[a] == False: # maximum number of non-vanishing coefficients if self.p[a] == 1: self.n_int_nvcof_D[a] = 2 @@ -774,39 +770,39 @@ def __init__(self, tensor_space, n_quad): self.n_int_nvcof_N[a] = 3 * self.p[a] - 2 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.int_add_D[a] = xp.arange(self.n_int[a] - 2) + 1 - self.int_add_N[a] = xp.arange(self.n_int[a] - 1) + 1 + self.int_add_D[a] = np.arange(self.n_int[a] - 2) + 1 + self.int_add_N[a] = np.arange(self.n_int[a] - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) if self.p[a] == 1: - self.int_shift_D[a] = xp.arange(self.NbaseD[a]) + self.int_shift_D[a] = np.arange(self.NbaseD[a]) else: - self.int_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 2) + self.int_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 2) self.int_shift_D[a][: 2 * self.p[a] - 2] = 0 self.int_shift_D[a][-(2 * self.p[a] - 2) :] = self.int_shift_D[a][-(2 * self.p[a] - 2)] # shift local coefficients --> global coefficients (N) if self.p[a] == 1: - self.int_shift_N[a] = xp.arange(self.NbaseN[a]) + self.int_shift_N[a] = np.arange(self.NbaseN[a]) self.int_shift_N[a][-1] = self.int_shift_N[a][-2] else: - self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) self.int_shift_N[a][: 2 * self.p[a] - 1] = 0 self.int_shift_N[a][-(2 * self.p[a] - 1) :] = self.int_shift_N[a][-(2 * self.p[a] - 1)] - counter_coeffi = xp.copy(self.p[a]) + counter_coeffi = np.copy(self.p[a]) for i in range(n_lambda_int[a]): # left boundary region if i < self.p[a] - 1: - self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) - self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) - self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + self.x_int_indices[a][i] = np.arange(self.n_int[a]) self.coeffi_indices[a][i] = i for j in range(2 * (self.p[a] - 1) + 1): xi = self.p[a] - 1 @@ -817,13 +813,13 @@ def __init__(self, tensor_space, n_quad): # right boundary region elif i > n_lambda_int[a] - self.p[a]: self.int_global_N[a][i] = ( - xp.arange(self.n_int_locbf_N[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) + np.arange(self.n_int_locbf_N[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) self.int_global_D[a][i] = ( - xp.arange(self.n_int_locbf_D[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) + np.arange(self.n_int_locbf_D[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) - self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + 2 * ( + self.x_int_indices[a][i] = np.arange(self.n_int[a]) + 2 * ( n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) self.coeffi_indices[a][i] = counter_coeffi @@ -837,20 +833,20 @@ def __init__(self, tensor_space, n_quad): # interior else: if self.p[a] == 1: - self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + i - self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + i + self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + i + self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + i self.int_global_N[a][-1] = self.int_global_N[a][-2] self.int_global_D[a][-1] = self.int_global_D[a][-2] else: - self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1) - self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1) + self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1) + self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1) if self.p[a] == 1: self.x_int_indices[a][i] = i else: - self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1)) + self.x_int_indices[a][i] = np.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1)) self.coeffi_indices[a][i] = self.p[a] - 1 @@ -861,8 +857,8 @@ def __init__(self, tensor_space, n_quad): # local coefficient index if self.p[a] == 1: - self.int_loccof_N[a][i] = xp.array([0, 1]) - self.int_loccof_D[a][-1] = xp.array([1]) + self.int_loccof_N[a][i] = np.array([0, 1]) + self.int_loccof_D[a][-1] = np.array([1]) else: if i > 0: @@ -870,8 +866,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.int_global_D[a][i, il] bol = k_glob_new == self.int_global_D[a][i - 1] - if xp.any(bol): - self.int_loccof_D[a][i, il] = self.int_loccof_D[a][i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.int_loccof_D[a][i, il] = self.int_loccof_D[a][i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int[a] - self.p[a] - (self.p[a] - 2)) and ( self.int_loccof_D[a][i, il] == 0 @@ -883,8 +879,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.int_global_N[a][i, il] bol = k_glob_new == self.int_global_N[a][i - 1] - if xp.any(bol): - self.int_loccof_N[a][i, il] = self.int_loccof_N[a][i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.int_loccof_N[a][i, il] = self.int_loccof_N[a][i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int[a] - self.p[a] - (self.p[a] - 2)) and ( self.int_loccof_N[a][i, il] == 0 @@ -904,24 +900,24 @@ def __init__(self, tensor_space, n_quad): # shift local coefficients --> global coefficients if self.p[a] == 1: - self.int_shift_D[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) - self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a]) + self.int_shift_D[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a]) else: - self.int_shift_D[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 2) - self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_D[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 2) + self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) for i in range(n_lambda_int[a]): # global indices of non-vanishing basis functions and position of coefficients in final matrix - self.int_global_N[a][i] = (xp.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] - self.int_global_D[a][i] = (xp.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] + self.int_global_N[a][i] = (np.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] + self.int_global_D[a][i] = (np.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] - self.int_loccof_N[a][i] = xp.arange(self.n_int_locbf_N[a] - 1, -1, -1) - self.int_loccof_D[a][i] = xp.arange(self.n_int_locbf_D[a] - 1, -1, -1) + self.int_loccof_N[a][i] = np.arange(self.n_int_locbf_N[a] - 1, -1, -1) + self.int_loccof_D[a][i] = np.arange(self.n_int_locbf_D[a] - 1, -1, -1) if self.p[a] == 1: self.x_int_indices[a][i] = i else: - self.x_int_indices[a][i] = (xp.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1))) % ( + self.x_int_indices[a][i] = (np.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1))) % ( 2 * self.Nel[a] ) @@ -933,38 +929,38 @@ def __init__(self, tensor_space, n_quad): ) % 1.0 # set histopolation points, quadrature points and weights - n_lambda_his = [xp.copy(NbaseD) for NbaseD in self.NbaseD] # number of coefficients in space V1 + n_lambda_his = [np.copy(NbaseD) for NbaseD in self.NbaseD] # number of coefficients in space V1 self.n_his = [2 * p for p in self.p] # number of histopolation intervals self.n_his_locbf_N = [2 * p for p in self.p] # number of non-vanishing N bf in histopolation interval self.n_his_locbf_D = [2 * p - 1 for p in self.p] # number of non-vanishing D bf in histopolation interval self.x_his = [ - xp.zeros((n_lambda_his, n_his + 1), dtype=float) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) + np.zeros((n_lambda_his, n_his + 1), dtype=float) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) ] self.his_global_N = [ - xp.zeros((n_lambda_his, n_his_locbf_N), dtype=int) + np.zeros((n_lambda_his, n_his_locbf_N), dtype=int) for n_lambda_his, n_his_locbf_N in zip(n_lambda_his, self.n_his_locbf_N) ] self.his_global_D = [ - xp.zeros((n_lambda_his, n_his_locbf_D), dtype=int) + np.zeros((n_lambda_his, n_his_locbf_D), dtype=int) for n_lambda_his, n_his_locbf_D in zip(n_lambda_his, self.n_his_locbf_D) ] self.his_loccof_N = [ - xp.zeros((n_lambda_his, n_his_locbf_N), dtype=int) + np.zeros((n_lambda_his, n_his_locbf_N), dtype=int) for n_lambda_his, n_his_locbf_N in zip(n_lambda_his, self.n_his_locbf_N) ] self.his_loccof_D = [ - xp.zeros((n_lambda_his, n_his_locbf_D), dtype=int) + np.zeros((n_lambda_his, n_his_locbf_D), dtype=int) for n_lambda_his, n_his_locbf_D in zip(n_lambda_his, self.n_his_locbf_D) ] self.x_his_indices = [ - xp.zeros((n_lambda_his, n_his), dtype=int) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) + np.zeros((n_lambda_his, n_his), dtype=int) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) ] - self.coeffh_indices = [xp.zeros(n_lambda_his, dtype=int) for n_lambda_his in n_lambda_his] + self.coeffh_indices = [np.zeros(n_lambda_his, dtype=int) for n_lambda_his in n_lambda_his] self.pts = [0, 0, 0] self.wts = [0, 0, 0] @@ -979,37 +975,37 @@ def __init__(self, tensor_space, n_quad): self.his_shift_N = [0, 0, 0] for a in range(3): - if not self.bc[a]: + if self.bc[a] == False: # maximum number of non-vanishing coefficients self.n_his_nvcof_D[a] = 3 * self.p[a] - 2 self.n_his_nvcof_N[a] = 3 * self.p[a] - 1 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.his_add_D[a] = xp.arange(self.n_his[a] - 2) + 1 - self.his_add_N[a] = xp.arange(self.n_his[a] - 1) + 1 + self.his_add_D[a] = np.arange(self.n_his[a] - 2) + 1 + self.his_add_N[a] = np.arange(self.n_his[a] - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) - self.his_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 1) + self.his_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 1) self.his_shift_D[a][: 2 * self.p[a] - 1] = 0 self.his_shift_D[a][-(2 * self.p[a] - 1) :] = self.his_shift_D[a][-(2 * self.p[a] - 1)] # shift local coefficients --> global coefficients (N) - self.his_shift_N[a] = xp.arange(self.NbaseN[a]) - self.p[a] + self.his_shift_N[a] = np.arange(self.NbaseN[a]) - self.p[a] self.his_shift_N[a][: 2 * self.p[a]] = 0 self.his_shift_N[a][-2 * self.p[a] :] = self.his_shift_N[a][-2 * self.p[a]] - counter_coeffh = xp.copy(self.p[a]) + counter_coeffh = np.copy(self.p[a]) for i in range(n_lambda_his[a]): # left boundary region if i < self.p[a] - 1: - self.his_global_N[a][i] = xp.arange(self.n_his_locbf_N[a]) - self.his_global_D[a][i] = xp.arange(self.n_his_locbf_D[a]) + self.his_global_N[a][i] = np.arange(self.n_his_locbf_N[a]) + self.his_global_D[a][i] = np.arange(self.n_his_locbf_D[a]) - self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + self.x_his_indices[a][i] = np.arange(self.n_his[a]) self.coeffh_indices[a][i] = i for j in range(2 * self.p[a] + 1): xi = self.p[a] - 1 @@ -1020,13 +1016,13 @@ def __init__(self, tensor_space, n_quad): # right boundary region elif i > n_lambda_his[a] - self.p[a]: self.his_global_N[a][i] = ( - xp.arange(self.n_his_locbf_N[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) + np.arange(self.n_his_locbf_N[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) self.his_global_D[a][i] = ( - xp.arange(self.n_his_locbf_D[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) + np.arange(self.n_his_locbf_D[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) - self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + 2 * ( + self.x_his_indices[a][i] = np.arange(self.n_his[a]) + 2 * ( n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) self.coeffh_indices[a][i] = counter_coeffh @@ -1039,10 +1035,10 @@ def __init__(self, tensor_space, n_quad): # interior else: - self.his_global_N[a][i] = xp.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1) - self.his_global_D[a][i] = xp.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1) + self.his_global_N[a][i] = np.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1) + self.his_global_D[a][i] = np.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1) - self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1)) + self.x_his_indices[a][i] = np.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1)) self.coeffh_indices[a][i] = self.p[a] - 1 for j in range(2 * self.p[a] + 1): self.x_his[a][i, j] = ( @@ -1055,8 +1051,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.his_global_D[a][i, il] bol = k_glob_new == self.his_global_D[a][i - 1] - if xp.any(bol): - self.his_loccof_D[a][i, il] = self.his_loccof_D[a][i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.his_loccof_D[a][i, il] = self.his_loccof_D[a][i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his[a] - self.p[a] - (self.p[a] - 2)) and ( self.his_loccof_D[a][i, il] == 0 @@ -1068,8 +1064,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.his_global_N[a][i, il] bol = k_glob_new == self.his_global_N[a][i - 1] - if xp.any(bol): - self.his_loccof_N[a][i, il] = self.his_loccof_N[a][i - 1, xp.where(bol)[0][0]] + 1 + if np.any(bol): + self.his_loccof_N[a][i, il] = self.his_loccof_N[a][i - 1, np.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his[a] - self.p[a] - (self.p[a] - 2)) and ( self.his_loccof_N[a][i, il] == 0 @@ -1079,9 +1075,7 @@ def __init__(self, tensor_space, n_quad): # quadrature points and weights self.pts[a], self.wts[a] = bsp.quadrature_grid( - xp.unique(self.x_his[a].flatten()), - self.pts_loc[a], - self.wts_loc[a], + np.unique(self.x_his[a].flatten()), self.pts_loc[a], self.wts_loc[a] ) else: @@ -1090,18 +1084,18 @@ def __init__(self, tensor_space, n_quad): self.n_his_nvcof_N[a] = 2 * self.p[a] # shift local coefficients --> global coefficients (D) - self.his_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 1) + self.his_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 1) # shift local coefficients --> global coefficients (N) - self.his_shift_N[a] = xp.arange(self.NbaseD[a]) - self.p[a] + self.his_shift_N[a] = np.arange(self.NbaseD[a]) - self.p[a] for i in range(n_lambda_his[a]): - self.his_global_N[a][i] = (xp.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] - self.his_global_D[a][i] = (xp.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] - self.his_loccof_N[a][i] = xp.arange(self.n_his_locbf_N[a] - 1, -1, -1) - self.his_loccof_D[a][i] = xp.arange(self.n_his_locbf_D[a] - 1, -1, -1) + self.his_global_N[a][i] = (np.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] + self.his_global_D[a][i] = (np.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] + self.his_loccof_N[a][i] = np.arange(self.n_his_locbf_N[a] - 1, -1, -1) + self.his_loccof_D[a][i] = np.arange(self.n_his_locbf_D[a] - 1, -1, -1) - self.x_his_indices[a][i] = (xp.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1))) % ( + self.x_his_indices[a][i] = (np.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1))) % ( 2 * self.Nel[a] ) self.coeffh_indices[a][i] = 0 @@ -1111,9 +1105,7 @@ def __init__(self, tensor_space, n_quad): # quadrature points and weights self.pts[a], self.wts[a] = bsp.quadrature_grid( - xp.append(xp.unique(self.x_his[a].flatten() % 1.0), 1.0), - self.pts_loc[a], - self.wts_loc[a], + np.append(np.unique(self.x_his[a].flatten() % 1.0), 1.0), self.pts_loc[a], self.wts_loc[a] ) # projector on space V0 (interpolation) @@ -1139,18 +1131,18 @@ def pi_0(self, fun, include_bc=True, eval_kind="meshgrid"): """ # interpolation points - x_int1 = xp.unique(self.x_int[0].flatten()) - x_int2 = xp.unique(self.x_int[1].flatten()) - x_int3 = xp.unique(self.x_int[2].flatten()) + x_int1 = np.unique(self.x_int[0].flatten()) + x_int2 = np.unique(self.x_int[1].flatten()) + x_int3 = np.unique(self.x_int[2].flatten()) # evaluation of function at interpolation points - mat_f = xp.empty((x_int1.size, x_int2.size, x_int3.size), dtype=float) + mat_f = np.empty((x_int1.size, x_int2.size, x_int3.size), dtype=float) # external function call if a callable is passed if callable(fun): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid(x_int1, x_int2, x_int3, indexing="ij") + pts1, pts2, pts3 = np.meshgrid(x_int1, x_int2, x_int3, indexing="ij") mat_f[:, :, :] = fun(pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1169,7 +1161,7 @@ def pi_0(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # coefficients - lambdas = xp.zeros((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + lambdas = np.zeros((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) ker_loc.kernel_pi0_3d( self.NbaseN, @@ -1212,20 +1204,20 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): """ # interpolation points - x_int1 = xp.unique(self.x_int[0].flatten()) - x_int2 = xp.unique(self.x_int[1].flatten()) - x_int3 = xp.unique(self.x_int[2].flatten()) + x_int1 = np.unique(self.x_int[0].flatten()) + x_int2 = np.unique(self.x_int[1].flatten()) + x_int3 = np.unique(self.x_int[2].flatten()) # ======== 1-component ======== # evaluation of function at interpolation/quadrature points - mat_f = xp.empty((self.pts[0].flatten().size, x_int2.size, x_int3.size), dtype=float) + mat_f = np.empty((self.pts[0].flatten().size, x_int2.size, x_int3.size), dtype=float) # external function call if a callable is passed if callable(fun[0]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid(self.pts[0].flatten(), x_int2, x_int3, indexing="ij") + pts1, pts2, pts3 = np.meshgrid(self.pts[0].flatten(), x_int2, x_int3, indexing="ij") mat_f[:, :, :] = fun[0](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1244,7 +1236,7 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas1 = xp.zeros((self.NbaseD[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + lambdas1 = np.zeros((self.NbaseD[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) ker_loc.kernel_pi11_3d( [self.NbaseD[0], self.NbaseN[1], self.NbaseN[2]], @@ -1267,13 +1259,13 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): # ======== 2-component ======== # evaluation of function at interpolation/quadrature points - mat_f = xp.empty((x_int1.size, self.pts[1].flatten().size, x_int3.size), dtype=float) + mat_f = np.empty((x_int1.size, self.pts[1].flatten().size, x_int3.size), dtype=float) # external function call if a callable is passed if callable(fun[1]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid(x_int1, self.pts[1].flatten(), x_int3, indexing="ij") + pts1, pts2, pts3 = np.meshgrid(x_int1, self.pts[1].flatten(), x_int3, indexing="ij") mat_f[:, :, :] = fun[1](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1292,7 +1284,7 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas2 = xp.zeros((self.NbaseN[0], self.NbaseD[1], self.NbaseN[2]), dtype=float) + lambdas2 = np.zeros((self.NbaseN[0], self.NbaseD[1], self.NbaseN[2]), dtype=float) ker_loc.kernel_pi12_3d( [self.NbaseN[0], self.NbaseD[1], self.NbaseN[2]], @@ -1315,13 +1307,13 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): # ======== 3-component ======== # evaluation of function at interpolation/quadrature points - mat_f = xp.empty((x_int1.size, x_int1.size, self.pts[2].flatten().size), dtype=float) + mat_f = np.empty((x_int1.size, x_int1.size, self.pts[2].flatten().size), dtype=float) # external function call if a callable is passed if callable(fun[2]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid(x_int1, x_int2, self.pts[2].flatten(), indexing="ij") + pts1, pts2, pts3 = np.meshgrid(x_int1, x_int2, self.pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun[2](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1340,7 +1332,7 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas3 = xp.zeros((self.NbaseN[0], self.NbaseN[1], self.NbaseD[2]), dtype=float) + lambdas3 = np.zeros((self.NbaseN[0], self.NbaseN[1], self.NbaseD[2]), dtype=float) ker_loc.kernel_pi13_3d( [self.NbaseN[0], self.NbaseN[1], self.NbaseD[2]], @@ -1360,7 +1352,7 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): lambdas3, ) - return xp.concatenate((lambdas1.flatten(), lambdas2.flatten(), lambdas3.flatten())) + return np.concatenate((lambdas1.flatten(), lambdas2.flatten(), lambdas3.flatten())) # projector on space V1 ([inter, histo, histo], [histo, inter, histo], [histo, histo, inter]) def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): @@ -1385,20 +1377,20 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): """ # interpolation points - x_int1 = xp.unique(self.x_int[0].flatten()) - x_int2 = xp.unique(self.x_int[1].flatten()) - x_int3 = xp.unique(self.x_int[2].flatten()) + x_int1 = np.unique(self.x_int[0].flatten()) + x_int2 = np.unique(self.x_int[1].flatten()) + x_int3 = np.unique(self.x_int[2].flatten()) # ======== 1-component ======== # evaluation of function at interpolation/quadrature points - mat_f = xp.empty((x_int1.size, self.pts[1].flatten().size, self.pts[2].flatten().size), dtype=float) + mat_f = np.empty((x_int1.size, self.pts[1].flatten().size, self.pts[2].flatten().size), dtype=float) # external function call if a callable is passed if callable(fun[0]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid(x_int1, self.pts[1].flatten(), self.pts[2].flatten(), indexing="ij") + pts1, pts2, pts3 = np.meshgrid(x_int1, self.pts[1].flatten(), self.pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun[0](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1417,7 +1409,7 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas1 = xp.zeros((self.NbaseN[0], self.NbaseD[1], self.NbaseD[2]), dtype=float) + lambdas1 = np.zeros((self.NbaseN[0], self.NbaseD[1], self.NbaseD[2]), dtype=float) ker_loc.kernel_pi21_3d( [self.NbaseN[0], self.NbaseD[1], self.NbaseD[2]], @@ -1435,11 +1427,7 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): self.wts[1], self.wts[2], mat_f.reshape( - x_int1.size, - self.pts[1].shape[0], - self.pts[1].shape[1], - self.pts[2].shape[0], - self.pts[2].shape[1], + x_int1.size, self.pts[1].shape[0], self.pts[1].shape[1], self.pts[2].shape[0], self.pts[2].shape[1] ), lambdas1, ) @@ -1447,13 +1435,13 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): # ======== 2-component ======== # evaluation of function at interpolation/quadrature points - mat_f = xp.empty((self.pts[0].flatten().size, x_int2.size, self.pts[2].flatten().size), dtype=float) + mat_f = np.empty((self.pts[0].flatten().size, x_int2.size, self.pts[2].flatten().size), dtype=float) # external function call if a callable is passed if callable(fun[1]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid(self.pts[0].flatten(), x_int2, self.pts[2].flatten(), indexing="ij") + pts1, pts2, pts3 = np.meshgrid(self.pts[0].flatten(), x_int2, self.pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun[1](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1472,7 +1460,7 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas2 = xp.zeros((self.NbaseD[0], self.NbaseN[1], self.NbaseD[2]), dtype=float) + lambdas2 = np.zeros((self.NbaseD[0], self.NbaseN[1], self.NbaseD[2]), dtype=float) ker_loc.kernel_pi22_3d( [self.NbaseD[0], self.NbaseN[1], self.NbaseD[2]], @@ -1490,11 +1478,7 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): self.wts[0], self.wts[2], mat_f.reshape( - self.pts[0].shape[0], - self.pts[0].shape[1], - x_int2.size, - self.pts[2].shape[0], - self.pts[2].shape[1], + self.pts[0].shape[0], self.pts[0].shape[1], x_int2.size, self.pts[2].shape[0], self.pts[2].shape[1] ), lambdas2, ) @@ -1502,13 +1486,13 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): # ======== 3-component ======== # evaluation of function at interpolation/quadrature points - mat_f = xp.empty((self.pts[0].flatten().size, self.pts[1].flatten().size, x_int3.size), dtype=float) + mat_f = np.empty((self.pts[0].flatten().size, self.pts[1].flatten().size, x_int3.size), dtype=float) # external function call if a callable is passed if callable(fun[2]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid(self.pts[0].flatten(), self.pts[1].flatten(), x_int3, indexing="ij") + pts1, pts2, pts3 = np.meshgrid(self.pts[0].flatten(), self.pts[1].flatten(), x_int3, indexing="ij") mat_f[:, :, :] = fun[2](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1527,7 +1511,7 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas3 = xp.zeros((self.NbaseD[0], self.NbaseD[1], self.NbaseN[2]), dtype=float) + lambdas3 = np.zeros((self.NbaseD[0], self.NbaseD[1], self.NbaseN[2]), dtype=float) ker_loc.kernel_pi23_3d( [self.NbaseD[0], self.NbaseD[1], self.NbaseN[2]], @@ -1545,16 +1529,12 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): self.wts[0], self.wts[1], mat_f.reshape( - self.pts[0].shape[0], - self.pts[0].shape[1], - self.pts[1].shape[0], - self.pts[1].shape[1], - x_int3.size, + self.pts[0].shape[0], self.pts[0].shape[1], self.pts[1].shape[0], self.pts[1].shape[1], x_int3.size ), lambdas3, ) - return xp.concatenate((lambdas1.flatten(), lambdas2.flatten(), lambdas3.flatten())) + return np.concatenate((lambdas1.flatten(), lambdas2.flatten(), lambdas3.flatten())) # projector on space V3 (histopolation) def pi_3(self, fun, include_bc=True, eval_kind="meshgrid"): @@ -1579,20 +1559,16 @@ def pi_3(self, fun, include_bc=True, eval_kind="meshgrid"): """ # evaluation of function at quadrature points - mat_f = xp.empty( - (self.pts[0].flatten().size, self.pts[1].flatten().size, self.pts[2].flatten().size), - dtype=float, + mat_f = np.empty( + (self.pts[0].flatten().size, self.pts[1].flatten().size, self.pts[2].flatten().size), dtype=float ) # external function call if a callable is passed if callable(fun): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid( - self.pts[0].flatten(), - self.pts[1].flatten(), - self.pts[2].flatten(), - indexing="ij", + pts1, pts2, pts3 = np.meshgrid( + self.pts[0].flatten(), self.pts[1].flatten(), self.pts[2].flatten(), indexing="ij" ) mat_f[:, :, :] = fun(pts1, pts2, pts3) @@ -1606,9 +1582,7 @@ def pi_3(self, fun, include_bc=True, eval_kind="meshgrid"): for i2 in range(self.pts[1].size): for i3 in range(self.pts[2].size): mat_f[i1, i2, i3] = fun( - self.pts[0].flatten()[i1], - self.pts[1].flatten()[i2], - self.pts[2].flatten()[i3], + self.pts[0].flatten()[i1], self.pts[1].flatten()[i2], self.pts[2].flatten()[i3] ) # internal function call @@ -1616,7 +1590,7 @@ def pi_3(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas = xp.zeros((self.NbaseD[0], self.NbaseD[1], self.NbaseD[2]), dtype=float) + lambdas = np.zeros((self.NbaseD[0], self.NbaseD[1], self.NbaseD[2]), dtype=float) ker_loc.kernel_pi3_3d( self.NbaseD, diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_L2_projector_kernel.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_L2_projector_kernel.py index 0e711dbcf..9aa26b243 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_L2_projector_kernel.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_L2_projector_kernel.py @@ -1390,42 +1390,33 @@ def vv_1_form( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) for i1 in range(cell_number[0]): @@ -1931,42 +1922,33 @@ def vv_push( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) for i1 in range(cell_number[0]): diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_L2.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_L2.py index 137df7f09..c1ae9e9f6 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_L2.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_L2.py @@ -5,9 +5,9 @@ Classes for local projectors in 1D and 3D based on quasi-spline interpolation and histopolation. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI import struphy.feec.bsplines as bsp import struphy.feec.projectors.shape_pro_local.shape_L2_projector_kernel as ker_loc @@ -50,48 +50,48 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): self.indD = tensor_space.indD self.polar = False # local projectors for polar splines are not implemented yet - self.lambdas_0 = xp.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) - self.potential_lambdas_0 = xp.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_0 = np.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) + self.potential_lambdas_0 = np.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_11 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_12 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_13 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_11 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_12 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_13 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_1_21 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_22 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_23 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_21 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_22 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_23 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_1_31 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_32 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_33 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_31 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_32 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_33 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_11 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_12 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_13 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_11 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_12 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_13 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_2_21 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_22 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_23 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_21 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_22 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_23 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_2_31 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_32 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_33 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_31 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_32 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_33 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_3 = xp.zeros((NbaseD[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_3 = np.zeros((NbaseD[0], NbaseD[1], NbaseD[2]), dtype=float) self.p_size = p_size self.p_shape = p_shape - self.related = xp.zeros(3, dtype=int) + self.related = np.zeros(3, dtype=int) for a in range(3): - # self.related[a] = int(xp.floor(NbaseN[a]/2.0)) + # self.related[a] = int(np.floor(NbaseN[a]/2.0)) self.related[a] = int( - xp.floor((3 * int((self.p_size[a] * (self.p_shape[a] + 1)) * self.Nel[a] + 1) + 3 * self.p[a]) / 2.0), + np.floor((3 * int((self.p_size[a] * (self.p_shape[a] + 1)) * self.Nel[a] + 1) + 3 * self.p[a]) / 2.0) ) if (2 * self.related[a] + 1) > NbaseN[a]: - self.related[a] = int(xp.floor(NbaseN[a] / 2.0)) + self.related[a] = int(np.floor(NbaseN[a] / 2.0)) - self.kernel_0_loc = xp.zeros( + self.kernel_0_loc = np.zeros( ( NbaseN[0], NbaseN[1], @@ -103,7 +103,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_11_loc = xp.zeros( + self.kernel_1_11_loc = np.zeros( ( NbaseD[0], NbaseN[1], @@ -114,7 +114,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_12_loc = xp.zeros( + self.kernel_1_12_loc = np.zeros( ( NbaseD[0], NbaseN[1], @@ -125,7 +125,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_13_loc = xp.zeros( + self.kernel_1_13_loc = np.zeros( ( NbaseD[0], NbaseN[1], @@ -137,7 +137,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_22_loc = xp.zeros( + self.kernel_1_22_loc = np.zeros( ( NbaseN[0], NbaseD[1], @@ -148,7 +148,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_23_loc = xp.zeros( + self.kernel_1_23_loc = np.zeros( ( NbaseN[0], NbaseD[1], @@ -160,7 +160,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_33_loc = xp.zeros( + self.kernel_1_33_loc = np.zeros( ( NbaseN[0], NbaseN[1], @@ -172,12 +172,12 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.right_loc_1 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.right_loc_2 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.right_loc_3 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.right_loc_1 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.right_loc_2 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.right_loc_3 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) if self.mpi_rank == 0: - self.kernel_0 = xp.zeros( + self.kernel_0 = np.zeros( ( NbaseN[0], NbaseN[1], @@ -189,7 +189,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_11 = xp.zeros( + self.kernel_1_11 = np.zeros( ( NbaseD[0], NbaseN[1], @@ -200,7 +200,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_12 = xp.zeros( + self.kernel_1_12 = np.zeros( ( NbaseN[0], NbaseD[1], @@ -211,7 +211,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_13 = xp.zeros( + self.kernel_1_13 = np.zeros( ( NbaseN[0], NbaseN[1], @@ -223,7 +223,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_22 = xp.zeros( + self.kernel_1_22 = np.zeros( ( NbaseN[0], NbaseD[1], @@ -234,7 +234,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_23 = xp.zeros( + self.kernel_1_23 = np.zeros( ( NbaseN[0], NbaseN[1], @@ -246,7 +246,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_33 = xp.zeros( + self.kernel_1_33 = np.zeros( ( NbaseN[0], NbaseN[1], @@ -258,9 +258,9 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.right_1 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.right_2 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.right_3 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.right_1 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.right_2 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.right_3 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) else: self.kernel_0 = None @@ -301,11 +301,11 @@ def assemble_0_form(self, tensor_space_FEM, mpi_comm): Nj = tensor_space_FEM.Nbase_0form # conversion to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -316,8 +316,7 @@ def assemble_0_form(self, tensor_space_FEM, mpi_comm): col = Nj[1] * Ni[2] * col1 + Ni[2] * col2 + col3 M = spa.csr_matrix( - (self.kernel_0.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_0.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M.eliminate_zeros() @@ -359,11 +358,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -374,8 +373,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M11 = spa.csr_matrix( - (self.kernel_1_11.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_11.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M11.eliminate_zeros() @@ -386,11 +384,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -401,8 +399,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M12 = spa.csr_matrix( - (self.kernel_1_12.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_12.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M12.eliminate_zeros() @@ -413,11 +410,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -428,8 +425,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M13 = spa.csr_matrix( - (self.kernel_1_13.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_13.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M13.eliminate_zeros() @@ -440,11 +436,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -455,8 +451,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M22 = spa.csr_matrix( - (self.kernel_1_22.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_22.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M22.eliminate_zeros() @@ -467,11 +462,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -482,8 +477,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M23 = spa.csr_matrix( - (self.kernel_1_23.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_23.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M23.eliminate_zeros() @@ -494,11 +488,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -509,15 +503,14 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M33 = spa.csr_matrix( - (self.kernel_1_33.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_33.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M33.eliminate_zeros() # final block matrix M = spa.bmat([[M11, M12, M13], [M12.T, M22, M23], [M13.T, M23.T, M33]], format="csr") # print('insider_check', self.kernel_1_33) - return (M, xp.concatenate((self.right_1.flatten(), self.right_2.flatten(), self.right_3.flatten()))) + return (M, np.concatenate((self.right_1.flatten(), self.right_2.flatten(), self.right_3.flatten()))) def heavy_test(self, test1, test2, test3, acc, particles_loc, Np, domain): ker_loc.kernel_1_heavy( @@ -590,7 +583,7 @@ def potential_pi_0(self, particles_loc, Np, domain, mpi_comm): ------- kernel_0 matrix """ - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: ker_loc.potential_kernel_0_form( Np, self.p, @@ -637,7 +630,7 @@ def S_pi_0(self, particles_loc, Np, domain): kernel_0 matrix """ self.kernel_0[:, :, :, :, :, :] = 0.0 - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: ker_loc.kernel_0_form( Np, self.p, @@ -699,7 +692,7 @@ def S_pi_1(self, particles_loc, Np, domain): self.right_loc_2[:, :, :] = 0.0 self.right_loc_3[:, :, :] = 0.0 - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: ker_loc.kernel_1_form( self.indN[0], self.indN[1], @@ -764,7 +757,7 @@ def S_pi_1(self, particles_loc, Np, domain): print("non-periodic case not implemented!!!") def vv_S1(self, particles_loc, Np, domain, index_label, accvv, dt, mpi_comm): - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: if index_label == 1: ker_loc.vv_1_form( self.wts[0][0], diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_local.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_local.py index 2ebb497a3..d361075e3 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_local.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_local.py @@ -5,9 +5,9 @@ Classes for local projectors in 1D and 3D based on quasi-spline interpolation and histopolation. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI import struphy.feec.bsplines as bsp import struphy.feec.projectors.shape_pro_local.shape_local_projector_kernel as ker_loc @@ -51,48 +51,48 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co self.polar = False # local projectors for polar splines are not implemented yet - self.lambdas_0 = xp.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) - self.potential_lambdas_0 = xp.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_0 = np.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) + self.potential_lambdas_0 = np.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_11 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_12 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_13 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_11 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_12 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_13 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_1_21 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_22 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_23 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_21 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_22 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_23 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_1_31 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_32 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_33 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_31 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_32 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_33 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_11 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_12 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_13 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_11 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_12 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_13 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_2_21 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_22 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_23 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_21 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_22 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_23 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_2_31 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_32 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_33 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_31 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_32 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_33 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_3 = xp.zeros((NbaseD[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_3 = np.zeros((NbaseD[0], NbaseD[1], NbaseD[2]), dtype=float) self.p_size = p_size self.p_shape = p_shape - self.related = xp.zeros(3, dtype=int) + self.related = np.zeros(3, dtype=int) for a in range(3): - # self.related[a] = int(xp.floor(NbaseN[a]/2.0)) + # self.related[a] = int(np.floor(NbaseN[a]/2.0)) self.related[a] = int( - xp.floor((3 * int((self.p_size[a] * (self.p_shape[a] + 1)) * self.Nel[a] + 1) + 3 * self.p[a]) / 2.0), + np.floor((3 * int((self.p_size[a] * (self.p_shape[a] + 1)) * self.Nel[a] + 1) + 3 * self.p[a]) / 2.0) ) if (2 * self.related[a] + 1) > NbaseN[a]: - self.related[a] = int(xp.floor(NbaseN[a] / 2.0)) + self.related[a] = int(np.floor(NbaseN[a] / 2.0)) - self.kernel_0_loc = xp.zeros( + self.kernel_0_loc = np.zeros( ( NbaseN[0], NbaseN[1], @@ -104,7 +104,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_11_loc = xp.zeros( + self.kernel_1_11_loc = np.zeros( ( NbaseD[0], NbaseN[1], @@ -115,7 +115,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_12_loc = xp.zeros( + self.kernel_1_12_loc = np.zeros( ( NbaseD[0], NbaseN[1], @@ -126,7 +126,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_13_loc = xp.zeros( + self.kernel_1_13_loc = np.zeros( ( NbaseD[0], NbaseN[1], @@ -138,7 +138,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_22_loc = xp.zeros( + self.kernel_1_22_loc = np.zeros( ( NbaseN[0], NbaseD[1], @@ -149,7 +149,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_23_loc = xp.zeros( + self.kernel_1_23_loc = np.zeros( ( NbaseN[0], NbaseD[1], @@ -161,7 +161,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_33_loc = xp.zeros( + self.kernel_1_33_loc = np.zeros( ( NbaseN[0], NbaseN[1], @@ -173,12 +173,12 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.right_loc_1 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.right_loc_2 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.right_loc_3 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.right_loc_1 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.right_loc_2 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.right_loc_3 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) if self.mpi_rank == 0: - self.kernel_0 = xp.zeros( + self.kernel_0 = np.zeros( ( NbaseN[0], NbaseN[1], @@ -190,7 +190,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_11 = xp.zeros( + self.kernel_1_11 = np.zeros( ( NbaseD[0], NbaseN[1], @@ -201,7 +201,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_12 = xp.zeros( + self.kernel_1_12 = np.zeros( ( NbaseN[0], NbaseD[1], @@ -212,7 +212,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_13 = xp.zeros( + self.kernel_1_13 = np.zeros( ( NbaseN[0], NbaseN[1], @@ -224,7 +224,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_22 = xp.zeros( + self.kernel_1_22 = np.zeros( ( NbaseN[0], NbaseD[1], @@ -235,7 +235,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_23 = xp.zeros( + self.kernel_1_23 = np.zeros( ( NbaseN[0], NbaseN[1], @@ -247,7 +247,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_33 = xp.zeros( + self.kernel_1_33 = np.zeros( ( NbaseN[0], NbaseN[1], @@ -259,9 +259,9 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.right_1 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.right_2 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.right_3 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.right_1 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.right_2 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.right_3 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) else: self.kernel_0 = None @@ -279,7 +279,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co self.right_2 = None self.right_3 = None - self.num_cell = xp.empty(3, dtype=int) + self.num_cell = np.empty(3, dtype=int) for i in range(3): if self.p[i] == 1: self.num_cell[i] = 1 @@ -287,16 +287,14 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co self.num_cell[i] = 2 # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = [xp.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] - self.wts_loc = [xp.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] + self.pts_loc = [np.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] + self.wts_loc = [np.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] self.pts = [0, 0, 0] self.wts = [0, 0, 0] for a in range(3): self.pts[a], self.wts[a] = bsp.quadrature_grid( - [0, 1.0 / 2.0 / self.Nel[a]], - self.pts_loc[a], - self.wts_loc[a], + [0, 1.0 / 2.0 / self.Nel[a]], self.pts_loc[a], self.wts_loc[a] ) # print('check_pts', self.pts[0].shape, self.pts[1].shape, self.pts[2].shape) # print('check_pts', self.wts) @@ -304,79 +302,79 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co self.coeff_i = [0, 0, 0] self.coeff_h = [0, 0, 0] for a in range(3): - if self.bc[a]: - self.coeff_i[a] = xp.zeros(2 * self.p[a], dtype=float) - self.coeff_h[a] = xp.zeros(2 * self.p[a], dtype=float) + if self.bc[a] == True: + self.coeff_i[a] = np.zeros(2 * self.p[a], dtype=float) + self.coeff_h[a] = np.zeros(2 * self.p[a], dtype=float) if self.p[a] == 1: - self.coeff_i[a][:] = xp.array([1.0, 0.0]) - self.coeff_h[a][:] = xp.array([1.0, 1.0]) + self.coeff_i[a][:] = np.array([1.0, 0.0]) + self.coeff_h[a][:] = np.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][:] = 1 / 2 * xp.array([-1.0, 4.0, -1.0, 0.0]) - self.coeff_h[a][:] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_i[a][:] = 1 / 2 * np.array([-1.0, 4.0, -1.0, 0.0]) + self.coeff_h[a][:] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) elif self.p[a] == 3: - self.coeff_i[a][:] = 1 / 6 * xp.array([1.0, -8.0, 20.0, -8.0, 1.0, 0.0]) - self.coeff_h[a][:] = 1 / 6 * xp.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) + self.coeff_i[a][:] = 1 / 6 * np.array([1.0, -8.0, 20.0, -8.0, 1.0, 0.0]) + self.coeff_h[a][:] = 1 / 6 * np.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) elif self.p[a] == 4: - self.coeff_i[a][:] = 2 / 45 * xp.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0, 0.0]) + self.coeff_i[a][:] = 2 / 45 * np.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0, 0.0]) self.coeff_h[a][:] = ( - 2 / 45 * xp.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) + 2 / 45 * np.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) ) else: print("degree > 4 not implemented!") else: - self.coeff_i[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) + self.coeff_i[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = xp.array([1.0]) - self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) + self.coeff_i[a][0, :] = np.array([1.0]) + self.coeff_h[a][0, :] = np.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * xp.array([2.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, 2.0]) + self.coeff_i[a][0, :] = 1 / 2 * np.array([2.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 2 * np.array([0.0, 0.0, 2.0]) - self.coeff_h[a][0, :] = 1 / 2 * xp.array([3.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) - self.coeff_h[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, -1.0, 3.0]) + self.coeff_h[a][0, :] = 1 / 2 * np.array([3.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_h[a][2, :] = 1 / 2 * np.array([0.0, 0.0, -1.0, 3.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 18 * xp.array([18.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 18 * xp.array([-5.0, 40.0, -24.0, 8.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 18 * xp.array([3.0, -24.0, 60.0, -24.0, 3.0]) - self.coeff_i[a][3, :] = 1 / 18 * xp.array([-1.0, 8.0, -24.0, 40.0, -5.0]) - self.coeff_i[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, 0.0, 0.0, 18.0]) - - self.coeff_h[a][0, :] = 1 / 18 * xp.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 18 * xp.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 18 * xp.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) - self.coeff_h[a][3, :] = 1 / 18 * xp.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) - self.coeff_h[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) + self.coeff_i[a][0, :] = 1 / 18 * np.array([18.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 18 * np.array([-5.0, 40.0, -24.0, 8.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 18 * np.array([3.0, -24.0, 60.0, -24.0, 3.0]) + self.coeff_i[a][3, :] = 1 / 18 * np.array([-1.0, 8.0, -24.0, 40.0, -5.0]) + self.coeff_i[a][4, :] = 1 / 18 * np.array([0.0, 0.0, 0.0, 0.0, 18.0]) + + self.coeff_h[a][0, :] = 1 / 18 * np.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 18 * np.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 18 * np.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) + self.coeff_h[a][3, :] = 1 / 18 * np.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) + self.coeff_h[a][4, :] = 1 / 18 * np.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 1 / 360 * xp.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 360 * xp.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) - self.coeff_i[a][2, :] = 1 / 360 * xp.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) - self.coeff_i[a][3, :] = 1 / 360 * xp.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) - self.coeff_i[a][4, :] = 1 / 360 * xp.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) - self.coeff_i[a][5, :] = 1 / 360 * xp.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) - self.coeff_i[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) - - self.coeff_h[a][0, :] = 1 / 360 * xp.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 360 * xp.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 360 * xp.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) + self.coeff_i[a][0, :] = 1 / 360 * np.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 360 * np.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) + self.coeff_i[a][2, :] = 1 / 360 * np.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) + self.coeff_i[a][3, :] = 1 / 360 * np.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) + self.coeff_i[a][4, :] = 1 / 360 * np.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) + self.coeff_i[a][5, :] = 1 / 360 * np.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) + self.coeff_i[a][6, :] = 1 / 360 * np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) + + self.coeff_h[a][0, :] = 1 / 360 * np.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 360 * np.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 360 * np.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) self.coeff_h[a][3, :] = ( - 1 / 360 * xp.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) + 1 / 360 * np.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) ) - self.coeff_h[a][4, :] = 1 / 360 * xp.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) - self.coeff_h[a][5, :] = 1 / 360 * xp.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) - self.coeff_h[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) + self.coeff_h[a][4, :] = 1 / 360 * np.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) + self.coeff_h[a][5, :] = 1 / 360 * np.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) + self.coeff_h[a][6, :] = 1 / 360 * np.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) else: print("degree > 4 not implemented!") @@ -404,11 +402,11 @@ def assemble_0_form(self, tensor_space_FEM, mpi_comm): Nj = tensor_space_FEM.Nbase_0form # conversion to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -419,8 +417,7 @@ def assemble_0_form(self, tensor_space_FEM, mpi_comm): col = Nj[1] * Ni[2] * col1 + Ni[2] * col2 + col3 M = spa.csr_matrix( - (self.kernel_0.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_0.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M.eliminate_zeros() @@ -462,11 +459,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -477,8 +474,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M11 = spa.csr_matrix( - (self.kernel_1_11.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_11.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M11.eliminate_zeros() @@ -489,11 +485,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -504,8 +500,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M12 = spa.csr_matrix( - (self.kernel_1_12.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_12.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M12.eliminate_zeros() @@ -516,11 +511,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -531,8 +526,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M13 = spa.csr_matrix( - (self.kernel_1_13.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_13.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M13.eliminate_zeros() @@ -543,11 +537,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -558,8 +552,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M22 = spa.csr_matrix( - (self.kernel_1_22.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_22.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M22.eliminate_zeros() @@ -570,11 +563,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -585,8 +578,7 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M23 = spa.csr_matrix( - (self.kernel_1_23.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_23.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M23.eliminate_zeros() @@ -597,11 +589,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = xp.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), + indices = np.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) ) - shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -612,15 +604,14 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M33 = spa.csr_matrix( - (self.kernel_1_33.flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (self.kernel_1_33.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M33.eliminate_zeros() # final block matrix M = spa.bmat([[M11, M12, M13], [M12.T, M22, M23], [M13.T, M23.T, M33]], format="csr") # print('insider_check', self.kernel_1_33) - return (M, xp.concatenate((self.right_1.flatten(), self.right_2.flatten(), self.right_3.flatten()))) + return (M, np.concatenate((self.right_1.flatten(), self.right_2.flatten(), self.right_3.flatten()))) def heavy_test(self, test1, test2, test3, acc, particles_loc, Np, domain): ker_loc.kernel_1_heavy( @@ -686,7 +677,7 @@ def potential_pi_0(self, particles_loc, Np, domain, mpi_comm): ------- kernel_0 matrix """ - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: ker_loc.potential_kernel_0_form( Np, self.p, @@ -733,7 +724,7 @@ def S_pi_0(self, particles_loc, Np, domain): kernel_0 matrix """ self.kernel_0[:, :, :, :, :, :] = 0.0 - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: ker_loc.kernel_0_form( Np, self.p, @@ -795,7 +786,7 @@ def S_pi_1(self, particles_loc, Np, domain): self.right_loc_2[:, :, :] = 0.0 self.right_loc_3[:, :, :] = 0.0 - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: ker_loc.kernel_1_form( self.right_loc_1, self.right_loc_2, @@ -882,7 +873,7 @@ def S_pi_01(self, particles_loc, Np, domain): self.right_loc_2[:, :, :] = 0.0 self.right_loc_3[:, :, :] = 0.0 - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: ker_loc.kernel_01_form( self.right_loc_1, self.right_loc_2, @@ -933,7 +924,7 @@ def S_pi_01(self, particles_loc, Np, domain): print("non-periodic case not implemented!!!") def vv_S1(self, particles_loc, Np, domain, index_label, accvv, dt, mpi_comm): - if self.bc[0] and self.bc[1] and self.bc[2]: + if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: if index_label == 1: ker_loc.vv_1_form( self.wts[0][0], diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_local_projector_kernel.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_local_projector_kernel.py index 6db315daa..c0ebc624d 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_local_projector_kernel.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_local_projector_kernel.py @@ -108,8 +108,7 @@ def kernel_0_form( width[il1] = p[il1] + cell_number[il1] - 1 mat_f = empty( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], num_cell[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], num_cell[2]), dtype=float ) mat_f[:, :, :, :, :, :] = 0.0 @@ -336,8 +335,7 @@ def potential_kernel_0_form( width[il1] = p[il1] + cell_number[il1] - 1 mat_f = empty( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], num_cell[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], num_cell[2]), dtype=float ) mat_f[:, :, :, :, :, :] = 0.0 @@ -541,42 +539,33 @@ def kernel_1_form( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) for i1 in range(cell_number[0]): @@ -1270,42 +1259,33 @@ def bv_localproj_push( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) for i1 in range(cell_number[0]): @@ -1825,42 +1805,33 @@ def kernel_1_heavy( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) for i1 in range(cell_number[0]): @@ -2402,42 +2373,33 @@ def vv_1_form( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) for i1 in range(cell_number[0]): @@ -2943,42 +2905,33 @@ def vv_push( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), - dtype=float, + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float ) for i1 in range(cell_number[0]): diff --git a/src/struphy/eigenvalue_solvers/mass_matrices_1d.py b/src/struphy/eigenvalue_solvers/mass_matrices_1d.py index b5013c088..4af163bc4 100644 --- a/src/struphy/eigenvalue_solvers/mass_matrices_1d.py +++ b/src/struphy/eigenvalue_solvers/mass_matrices_1d.py @@ -2,7 +2,7 @@ # # Copyright 2020 Florian Holderied -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.bsplines.bsplines as bsp @@ -47,7 +47,7 @@ def get_M(spline_space, phi_i=0, phi_j=0, fun=None): # evaluation of weight function at quadrature points (optional) if fun == None: - mat_fun = xp.ones(pts.shape, dtype=float) + mat_fun = np.ones(pts.shape, dtype=float) else: mat_fun = fun(pts.flatten()).reshape(Nel, n_quad) @@ -74,7 +74,7 @@ def get_M(spline_space, phi_i=0, phi_j=0, fun=None): bj = basisD[:, :, 0, :] # matrix assembly - M = xp.zeros((Ni, 2 * p + 1), dtype=float) + M = np.zeros((Ni, 2 * p + 1), dtype=float) for ie in range(Nel): for il in range(p + 1 - ni): @@ -86,8 +86,8 @@ def get_M(spline_space, phi_i=0, phi_j=0, fun=None): M[(ie + il) % Ni, p + jl - il] += value - indices = xp.indices((Ni, 2 * p + 1)) - shift = xp.arange(Ni) - p + indices = np.indices((Ni, 2 * p + 1)) + shift = np.arange(Ni) - p row = indices[0].flatten() col = (indices[1] + shift[:, None]) % Nj @@ -137,13 +137,13 @@ def get_M_gen(spline_space, phi_i=0, phi_j=0, fun=None, jac=None): # evaluation of weight function at quadrature points (optional) if fun == None: - mat_fun = xp.ones(pts.shape, dtype=float) + mat_fun = np.ones(pts.shape, dtype=float) else: mat_fun = fun(pts.flatten()).reshape(Nel, n_quad) # evaluation of jacobian at quadrature points if jac == None: - mat_jac = xp.ones(pts.shape, dtype=float) + mat_jac = np.ones(pts.shape, dtype=float) else: mat_jac = jac(pts.flatten()).reshape(Nel, n_quad) @@ -180,7 +180,7 @@ def get_M_gen(spline_space, phi_i=0, phi_j=0, fun=None, jac=None): bj = basis_t[:, :, 0, :] # matrix assembly - M = xp.zeros((Ni, 2 * p + 1), dtype=float) + M = np.zeros((Ni, 2 * p + 1), dtype=float) for ie in range(Nel): for il in range(p + 1 - ni): @@ -192,8 +192,8 @@ def get_M_gen(spline_space, phi_i=0, phi_j=0, fun=None, jac=None): M[(ie + il) % Ni, p + jl - il] += value - indices = xp.indices((Ni, 2 * p + 1)) - shift = xp.arange(Ni) - p + indices = np.indices((Ni, 2 * p + 1)) + shift = np.arange(Ni) - p row = indices[0].flatten() col = (indices[1] + shift[:, None]) % Nj @@ -235,11 +235,11 @@ def test_M(spline_space, phi_i=0, phi_j=0, fun=lambda eta: 1.0, jac=lambda eta: bj = lambda eta: spline_space.evaluate_D(eta, cj) / spline_space.Nel # coefficients - ci = xp.zeros(Ni, dtype=float) - cj = xp.zeros(Nj, dtype=float) + ci = np.zeros(Ni, dtype=float) + cj = np.zeros(Nj, dtype=float) # integration - M = xp.zeros((Ni, Nj), dtype=float) + M = np.zeros((Ni, Nj), dtype=float) for i in range(Ni): for j in range(Nj): diff --git a/src/struphy/eigenvalue_solvers/mass_matrices_2d.py b/src/struphy/eigenvalue_solvers/mass_matrices_2d.py index c2c6c3ae9..4e62c6c72 100644 --- a/src/struphy/eigenvalue_solvers/mass_matrices_2d.py +++ b/src/struphy/eigenvalue_solvers/mass_matrices_2d.py @@ -2,7 +2,7 @@ # # Copyright 2020 Florian Holderied -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_2d as ker @@ -46,7 +46,7 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): # evaluation of weight function at quadrature points if weight == None: - mat_w = xp.ones(det_df.shape, dtype=float) + mat_w = np.ones(det_df.shape, dtype=float) else: mat_w = weight(pts[0].flatten(), pts[1].flatten(), 0.0) mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) @@ -55,14 +55,14 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_0form Nj = tensor_space_FEM.Nbase_0form - M = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array([0, 0]), - xp.array([0, 0]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array([0, 0]), + np.array([0, 0]), wts[0], wts[1], basisN[0], @@ -76,9 +76,9 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # conversion to sparse matrix - indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() @@ -156,7 +156,7 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_1form[a] Nj = tensor_space_FEM.Nbase_1form[b] - M[a][b] = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M[a][b] = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) # evaluate inverse metric tensor at quadrature points if weight == None: @@ -167,13 +167,13 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # assemble block if weight is not zero - if xp.any(mat_w): + if np.any(mat_w): ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array(ns[a]), - xp.array(ns[b]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array(ns[a]), + np.array(ns[b]), wts[0], wts[1], basis[a][0], @@ -187,9 +187,9 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() @@ -272,7 +272,7 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_2form[a] Nj = tensor_space_FEM.Nbase_2form[b] - M[a][b] = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M[a][b] = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -283,13 +283,13 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # assemble block if weight is not zero - if xp.any(mat_w): + if np.any(mat_w): ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array(ns[a]), - xp.array(ns[b]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array(ns[a]), + np.array(ns[b]), wts[0], wts[1], basis[a][0], @@ -303,9 +303,9 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() @@ -369,7 +369,7 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): # evaluation of weight function at quadrature points if weight == None: - mat_w = xp.ones(det_df.shape, dtype=float) + mat_w = np.ones(det_df.shape, dtype=float) else: mat_w = weight(pts[0].flatten(), pts[1].flatten(), 0.0) mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) @@ -378,14 +378,14 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_3form Nj = tensor_space_FEM.Nbase_3form - M = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array([1, 1]), - xp.array([1, 1]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array([1, 1]), + np.array([1, 1]), wts[0], wts[1], basisD[0], @@ -399,9 +399,9 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # conversion to sparse matrix - indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() @@ -475,7 +475,7 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_0form Nj = tensor_space_FEM.Nbase_0form - M[a][b] = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M[a][b] = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -486,13 +486,13 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # assemble block if weight is not zero - if xp.any(mat_w): + if np.any(mat_w): ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array(ns[a]), - xp.array(ns[b]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array(ns[a]), + np.array(ns[b]), wts[0], wts[1], basis[a][0], @@ -506,9 +506,9 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() diff --git a/src/struphy/eigenvalue_solvers/mass_matrices_3d.py b/src/struphy/eigenvalue_solvers/mass_matrices_3d.py index d3dc4cad2..d77052727 100644 --- a/src/struphy/eigenvalue_solvers/mass_matrices_3d.py +++ b/src/struphy/eigenvalue_solvers/mass_matrices_3d.py @@ -2,7 +2,7 @@ # # Copyright 2020 Florian Holderied (florian.holderied@ipp.mpg.de) -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_3d as ker @@ -46,7 +46,7 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): # evaluation of weight function at quadrature points if weight == None: - mat_w = xp.ones(det_df.shape, dtype=float) + mat_w = np.ones(det_df.shape, dtype=float) else: mat_w = weight(pts[0].flatten(), pts[1].flatten(), pts[2].flatten()) mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) @@ -55,14 +55,14 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_0form Nj = tensor_space_FEM.Nbase_0form - M = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array([0, 0, 0]), - xp.array([0, 0, 0]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array([0, 0, 0]), + np.array([0, 0, 0]), wts[0], wts[1], wts[2], @@ -80,9 +80,9 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # conversion to sparse matrix - indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -161,7 +161,7 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_1form[a] Nj = tensor_space_FEM.Nbase_1form[b] - M[a][b] = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M[a][b] = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -172,13 +172,13 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # assemble block if weight is not zero - if xp.any(mat_w): + if np.any(mat_w): ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array(ns[a]), - xp.array(ns[b]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array(ns[a]), + np.array(ns[b]), wts[0], wts[1], wts[2], @@ -196,9 +196,9 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -209,8 +209,7 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M[a][b] = spa.csr_matrix( - (M[a][b].flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (M[a][b].flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M[a][b].eliminate_zeros() @@ -281,7 +280,7 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_2form[a] Nj = tensor_space_FEM.Nbase_2form[b] - M[a][b] = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M[a][b] = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -292,13 +291,13 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # assemble block if weight is not zero - if xp.any(mat_w): + if np.any(mat_w): ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array(ns[a]), - xp.array(ns[b]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array(ns[a]), + np.array(ns[b]), wts[0], wts[1], wts[2], @@ -316,9 +315,9 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -329,8 +328,7 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M[a][b] = spa.csr_matrix( - (M[a][b].flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (M[a][b].flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M[a][b].eliminate_zeros() @@ -383,7 +381,7 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): # evaluation of weight function at quadrature points if weight == None: - mat_w = xp.ones(det_df.shape, dtype=float) + mat_w = np.ones(det_df.shape, dtype=float) else: mat_w = weight(pts[0].flatten(), pts[1].flatten(), pts[2].flatten()) mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) @@ -392,14 +390,14 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_3form Nj = tensor_space_FEM.Nbase_3form - M = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array([1, 1, 1]), - xp.array([1, 1, 1]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array([1, 1, 1]), + np.array([1, 1, 1]), wts[0], wts[1], wts[2], @@ -417,9 +415,9 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # conversion to sparse matrix - indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -517,7 +515,7 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_2form[a] Nj = tensor_space_FEM.Nbase_2form[b] - M[a][b] = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M[a][b] = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -528,13 +526,13 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # assemble block if weight is not zero - if xp.any(mat_w): + if np.any(mat_w): ker.kernel_mass( - xp.array(Nel), - xp.array(p), - xp.array(n_quad), - xp.array(ns[a]), - xp.array(ns[b]), + np.array(Nel), + np.array(p), + np.array(n_quad), + np.array(ns[a]), + np.array(ns[b]), wts[0], wts[1], wts[2], @@ -552,9 +550,9 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -565,8 +563,7 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M[a][b] = spa.csr_matrix( - (M[a][b].flatten(), (row, col.flatten())), - shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), + (M[a][b].flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) ) M[a][b].eliminate_zeros() diff --git a/src/struphy/eigenvalue_solvers/mhd_axisymmetric_main.py b/src/struphy/eigenvalue_solvers/mhd_axisymmetric_main.py index 04a194c7f..3810f1d60 100644 --- a/src/struphy/eigenvalue_solvers/mhd_axisymmetric_main.py +++ b/src/struphy/eigenvalue_solvers/mhd_axisymmetric_main.py @@ -32,7 +32,7 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N import os import time - import cunumpy as xp + import numpy as np import scipy.sparse as spa from struphy.eigenvalue_solvers.mhd_operators import MHDOperators @@ -45,13 +45,13 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N # print grid info print("\nGrid parameters:") - print("number of elements :", num_params["Nel"]) - print("spline degrees :", num_params["p"]) - print("periodic bcs :", num_params["spl_kind"]) - print("hom. Dirichlet bc :", num_params["bc"]) - print("GL quad pts (L2) :", num_params["nq_el"]) - print("GL quad pts (hist) :", num_params["nq_pr"]) - print("polar Ck :", num_params["polar_ck"]) + print(f"number of elements :", num_params["Nel"]) + print(f"spline degrees :", num_params["p"]) + print(f"periodic bcs :", num_params["spl_kind"]) + print(f"hom. Dirichlet bc :", num_params["bc"]) + print(f"GL quad pts (L2) :", num_params["nq_el"]) + print(f"GL quad pts (hist) :", num_params["nq_pr"]) + print(f"polar Ck :", num_params["polar_ck"]) print("") # extract numerical parameters @@ -72,12 +72,7 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N # set up 2d tensor-product space space_2d = Tensor_spline_space( - [space_1d_1, space_1d_2], - polar_ck, - eq_mhd.domain.cx[:, :, 0], - eq_mhd.domain.cy[:, :, 0], - n_tor, - basis_tor, + [space_1d_1, space_1d_2], polar_ck, eq_mhd.domain.cx[:, :, 0], eq_mhd.domain.cy[:, :, 0], n_tor, basis_tor ) # set up 2d projectors @@ -146,14 +141,14 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N .dot( EF.T.dot(space_2d.C0.conjugate().T.dot(M2_0.dot(space_2d.C0.dot(EF)))) + mhd_ops.MJ_mat.dot(space_2d.C0.dot(EF)) - - space_2d.D0.conjugate().T.dot(M3_0.dot(L)), + - space_2d.D0.conjugate().T.dot(M3_0.dot(L)) ) .toarray() ) print("Assembly of final system matrix done --> start of eigenvalue calculation") - omega2, U2_eig = xp.linalg.eig(MAT) + omega2, U2_eig = np.linalg.eig(MAT) print("Eigenstates calculated") @@ -166,9 +161,8 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N else: n_tor_str = "+" + str(n_tor) - xp.save( - os.path.join(path_out, "spec_n_" + n_tor_str + ".npy"), - xp.vstack((omega2.reshape(1, omega2.size), U2_eig)), + np.save( + os.path.join(path_out, "spec_n_" + n_tor_str + ".npy"), np.vstack((omega2.reshape(1, omega2.size), U2_eig)) ) # or return eigenfrequencies, eigenvectors and system matrix @@ -186,7 +180,7 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N # parse arguments parser = argparse.ArgumentParser( - description="Computes the complete eigenspectrum for a given axisymmetric MHD equilibrium.", + description="Computes the complete eigenspectrum for a given axisymmetric MHD equilibrium." ) parser.add_argument("n_tor", type=int, help="the toroidal mode number") diff --git a/src/struphy/eigenvalue_solvers/mhd_axisymmetric_pproc.py b/src/struphy/eigenvalue_solvers/mhd_axisymmetric_pproc.py index 1685147e1..75a116c09 100644 --- a/src/struphy/eigenvalue_solvers/mhd_axisymmetric_pproc.py +++ b/src/struphy/eigenvalue_solvers/mhd_axisymmetric_pproc.py @@ -3,7 +3,7 @@ def main(): import argparse import os - import cunumpy as xp + import numpy as np import yaml # parse arguments @@ -21,10 +21,7 @@ def main(): ) parser.add_argument( - "--input-abs", - type=str, - metavar="DIR", - help="directory with eigenspectrum (.npy) file, absolute path", + "--input-abs", type=str, metavar="DIR", help="directory with eigenspectrum (.npy) file, absolute path" ) parser.add_argument("lower", type=float, help="lower range of squared eigenfrequency") @@ -54,18 +51,18 @@ def main(): spec_path = os.path.join(input_path, "spec_n_" + n_tor_str + ".npy") - omega2, U2_eig = xp.split(xp.load(spec_path), [1], axis=0) + omega2, U2_eig = np.split(np.load(spec_path), [1], axis=0) omega2 = omega2.flatten() - modes_ind = xp.where((xp.real(omega2) < args.upper) & (xp.real(omega2) > args.lower))[0] + modes_ind = np.where((np.real(omega2) < args.upper) & (np.real(omega2) > args.lower))[0] omega2 = omega2[modes_ind] U2_eig = U2_eig[:, modes_ind] # save restricted spectrum - xp.save( + np.save( os.path.join(input_path, "spec_" + str(args.lower) + "_" + str(args.upper) + "_n_" + n_tor_str + ".npy"), - xp.vstack((omega2.reshape(1, omega2.size), U2_eig)), + np.vstack((omega2.reshape(1, omega2.size), U2_eig)), ) diff --git a/src/struphy/eigenvalue_solvers/mhd_operators.py b/src/struphy/eigenvalue_solvers/mhd_operators.py index 6f7325c6b..22355c1c9 100644 --- a/src/struphy/eigenvalue_solvers/mhd_operators.py +++ b/src/struphy/eigenvalue_solvers/mhd_operators.py @@ -3,7 +3,7 @@ # Copyright 2021 Florian Holderied (florian.holderied@ipp.mpg.de) -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.legacy.mass_matrices_3d_pre as mass_3d_pre @@ -402,7 +402,7 @@ def __EF(self, u): out1 = self.int_N3.dot(self.dofs_EF[0].dot(u1).T).T + self.int_N3.dot(self.dofs_EF[1].dot(u3).T).T out3 = self.his_N3.dot(self.dofs_EF[2].dot(u1).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: u1, u3 = self.core.space.reshape_pol_2(u) @@ -410,7 +410,7 @@ def __EF(self, u): out1 = self.int_D3.dot(self.dofs_EF[0].dot(u1).T).T + self.int_N3.dot(self.dofs_EF[1].dot(u3).T).T out3 = self.his_D3.dot(self.dofs_EF[2].dot(u1).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_EF.dot(u) @@ -434,7 +434,7 @@ def __EF_transposed(self, e): ) out3 = self.int_N3.T.dot(self.dofs_EF[1].T.dot(e1).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: out1 = ( @@ -442,7 +442,7 @@ def __EF_transposed(self, e): ) out3 = self.int_N3.T.dot(self.dofs_EF[1].T.dot(e1).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_EF.T.dot(e) @@ -462,7 +462,7 @@ def __MF(self, u): out1 = self.his_N3.dot(self.dofs_MF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_MF[1].dot(u3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: u1, u3 = self.core.space.reshape_pol_2(u) @@ -470,7 +470,7 @@ def __MF(self, u): out1 = self.his_D3.dot(self.dofs_MF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_MF[1].dot(u3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_MF.dot(u) @@ -492,13 +492,13 @@ def __MF_transposed(self, f): out1 = self.his_N3.T.dot(self.dofs_MF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_MF[1].T.dot(f3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: out1 = self.his_D3.T.dot(self.dofs_MF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_MF[1].T.dot(f3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_MF.T.dot(f) @@ -518,7 +518,7 @@ def __PF(self, u): out1 = self.his_N3.dot(self.dofs_PF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_PF[1].dot(u3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: u1, u3 = self.core.space.reshape_pol_2(u) @@ -526,7 +526,7 @@ def __PF(self, u): out1 = self.his_D3.dot(self.dofs_PF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_PF[1].dot(u3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_PF.dot(u) @@ -548,13 +548,13 @@ def __PF_transposed(self, f): out1 = self.his_N3.T.dot(self.dofs_PF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_PF[1].T.dot(f3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: out1 = self.his_D3.T.dot(self.dofs_PF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_PF[1].T.dot(f3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_PF.T.dot(f) @@ -574,7 +574,7 @@ def __JF(self, u): out1 = self.his_N3.dot(self.dofs_JF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_JF[1].dot(u3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: u1, u3 = self.core.space.reshape_pol_2(u) @@ -582,7 +582,7 @@ def __JF(self, u): out1 = self.his_D3.dot(self.dofs_JF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_JF[1].dot(u3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_JF.dot(u) @@ -604,13 +604,13 @@ def __JF_transposed(self, f): out1 = self.his_N3.T.dot(self.dofs_JF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_JF[1].T.dot(f3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: out1 = self.his_D3.T.dot(self.dofs_JF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_JF[1].T.dot(f3).T).T - out = xp.concatenate((out1.flatten(), out3.flatten())) + out = np.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_JF.T.dot(f) @@ -658,13 +658,11 @@ def __Mn(self, u): if self.Mn_as_tensor: if self.core.basis_u == 0: out = self.core.space.apply_Mv_ten( - u, - [[self.Mn_mat[0], self.core.space.M0_tor], [self.Mn_mat[1], self.core.space.M0_tor]], + u, [[self.Mn_mat[0], self.core.space.M0_tor], [self.Mn_mat[1], self.core.space.M0_tor]] ) elif self.core.basis_u == 2: out = self.core.space.apply_M2_ten( - u, - [[self.Mn_mat[0], self.core.space.M1_tor], [self.Mn_mat[1], self.core.space.M0_tor]], + u, [[self.Mn_mat[0], self.core.space.M1_tor], [self.Mn_mat[1], self.core.space.M0_tor]] ) else: @@ -680,16 +678,15 @@ def __MJ(self, b): if self.MJ_as_tensor: if self.core.basis_u == 0: - out = xp.zeros(self.core.space.Ev_0.shape[0], dtype=float) + out = np.zeros(self.core.space.Ev_0.shape[0], dtype=float) elif self.core.basis_u == 2: out = self.core.space.apply_M2_ten( - b, - [[self.MJ_mat[0], self.core.space.M1_tor], [self.MJ_mat[1], self.core.space.M0_tor]], + b, [[self.MJ_mat[0], self.core.space.M1_tor], [self.MJ_mat[1], self.core.space.M0_tor]] ) else: if self.core.basis_u == 0: - out = xp.zeros(self.core.space.Ev_0.shape[0], dtype=float) + out = np.zeros(self.core.space.Ev_0.shape[0], dtype=float) elif self.core.basis_u == 2: out = self.MJ_mat.dot(b) @@ -703,7 +700,7 @@ def __L(self, u): if self.core.basis_u == 0: out = -self.core.space.D0.dot(self.__PF(u)) - (self.gamma - 1) * self.__PR( - self.core.space.D0.dot(self.__JF(u)), + self.core.space.D0.dot(self.__JF(u)) ) elif self.core.basis_u == 2: out = -self.core.space.D0.dot(self.__PF(u)) - (self.gamma - 1) * self.__PR(self.core.space.D0.dot(u)) @@ -785,32 +782,27 @@ def set_operators(self, dt_2=1.0, dt_6=1.0): if hasattr(self, "dofs_Mn"): self.Mn = spa.linalg.LinearOperator( - (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), - matvec=self.__Mn, + (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), matvec=self.__Mn ) if hasattr(self, "dofs_MJ"): self.MJ = spa.linalg.LinearOperator( - (self.core.space.Ev_0.shape[0], self.core.space.E2_0.shape[0]), - matvec=self.__MJ, + (self.core.space.Ev_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__MJ ) if hasattr(self, "dofs_PF") and hasattr(self, "dofs_PR") and hasattr(self, "dofs_JF"): self.L = spa.linalg.LinearOperator( - (self.core.space.E3_0.shape[0], self.core.space.Ev_0.shape[0]), - matvec=self.__L, + (self.core.space.E3_0.shape[0], self.core.space.Ev_0.shape[0]), matvec=self.__L ) if hasattr(self, "Mn_mat") and hasattr(self, "dofs_EF"): self.S2 = spa.linalg.LinearOperator( - (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), - matvec=self.__S2, + (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), matvec=self.__S2 ) if hasattr(self, "Mn_mat") and hasattr(self, "L"): self.S6 = spa.linalg.LinearOperator( - (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), - matvec=self.__S6, + (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), matvec=self.__S6 ) elif self.core.basis_u == 2: @@ -844,32 +836,27 @@ def set_operators(self, dt_2=1.0, dt_6=1.0): if hasattr(self, "Mn_mat"): self.Mn = spa.linalg.LinearOperator( - (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), - matvec=self.__Mn, + (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__Mn ) if hasattr(self, "MJ_mat"): self.MJ = spa.linalg.LinearOperator( - (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), - matvec=self.__MJ, + (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__MJ ) if hasattr(self, "dofs_PF") and hasattr(self, "dofs_PR"): self.L = spa.linalg.LinearOperator( - (self.core.space.E3_0.shape[0], self.core.space.E2_0.shape[0]), - matvec=self.__L, + (self.core.space.E3_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__L ) if hasattr(self, "Mn_mat") and hasattr(self, "dofs_EF"): self.S2 = spa.linalg.LinearOperator( - (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), - matvec=self.__S2, + (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__S2 ) if hasattr(self, "Mn_mat") and hasattr(self, "L"): self.S6 = spa.linalg.LinearOperator( - (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), - matvec=self.__S6, + (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__S6 ) # ====================================== @@ -942,7 +929,7 @@ def guess_S2(self, u, b, kind): u_guess = u + self.dt_2 / 6 * (k1_u + 2 * k2_u + 2 * k3_u + k4_u) else: - u_guess = xp.copy(u) + u_guess = np.copy(u) return u_guess @@ -1037,7 +1024,7 @@ def set_preconditioner_S2(self, which, tol_inv=1e-15, drop_tol=1e-4, fill_fac=10 # assemble approximate S2 matrix S2_approx = Mn + self.dt_2**2 / 4 * EF_approx.T.dot( - self.core.space.C0.T.dot(M2_0.dot(self.core.space.C0.dot(EF_approx))), + self.core.space.C0.T.dot(M2_0.dot(self.core.space.C0.dot(EF_approx))) ) del Mn, EF_approx, M2_0 @@ -1136,7 +1123,7 @@ def set_preconditioner_S6(self, which, tol_inv=1e-15, drop_tol=1e-4, fill_fac=10 # assemble approximate L matrix if self.core.basis_u == 0: L_approx = -self.core.space.D0.dot(PF_approx) - (self.gamma - 1) * PR_approx.dot( - self.core.space.D0.dot(JF_approx), + self.core.space.D0.dot(JF_approx) ) del PF_approx, PR_approx diff --git a/src/struphy/eigenvalue_solvers/mhd_operators_core.py b/src/struphy/eigenvalue_solvers/mhd_operators_core.py index 61d534148..a8c23e3c7 100644 --- a/src/struphy/eigenvalue_solvers/mhd_operators_core.py +++ b/src/struphy/eigenvalue_solvers/mhd_operators_core.py @@ -3,7 +3,7 @@ # Copyright 2021 Florian Holderied (florian.holderied@ipp.mpg.de) -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_projectors_global_mhd as ker @@ -58,11 +58,11 @@ def __init__(self, space, equilibrium, basis_u): self.subs_cum = [space.projectors.subs_cum for space in self.space.spaces] # get 1D indices of non-vanishing values of expressions dofs_0(N), dofs_0(D), dofs_1(N) and dofs_1(D) - self.dofs_0_N_i = [list(xp.nonzero(space.projectors.I.toarray())) for space in self.space.spaces] - self.dofs_1_D_i = [list(xp.nonzero(space.projectors.H.toarray())) for space in self.space.spaces] + self.dofs_0_N_i = [list(np.nonzero(space.projectors.I.toarray())) for space in self.space.spaces] + self.dofs_1_D_i = [list(np.nonzero(space.projectors.H.toarray())) for space in self.space.spaces] - self.dofs_0_D_i = [list(xp.nonzero(space.projectors.ID.toarray())) for space in self.space.spaces] - self.dofs_1_N_i = [list(xp.nonzero(space.projectors.HN.toarray())) for space in self.space.spaces] + self.dofs_0_D_i = [list(np.nonzero(space.projectors.ID.toarray())) for space in self.space.spaces] + self.dofs_1_N_i = [list(np.nonzero(space.projectors.HN.toarray())) for space in self.space.spaces] for i in range(self.space.dim): for j in range(2): @@ -116,9 +116,9 @@ def get_blocks_EF(self, pol=True): B2_3_pts = B2_3_pts.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_N_i[0][0], @@ -130,8 +130,8 @@ def get_blocks_EF(self, pol=True): self.wts[0], self.basis_his_N[0], self.basis_int_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_3_pts, val, row, @@ -139,8 +139,7 @@ def get_blocks_EF(self, pol=True): ) EF_12 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_0form // self.N3) ) EF_12.eliminate_zeros() # ---------------------------------------------------- @@ -151,9 +150,9 @@ def get_blocks_EF(self, pol=True): B2_2_pts = B2_2_pts.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_N_i[0][0], @@ -165,8 +164,8 @@ def get_blocks_EF(self, pol=True): self.wts[0], self.basis_his_N[0], self.basis_int_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_2_pts, val, row, @@ -174,8 +173,7 @@ def get_blocks_EF(self, pol=True): ) EF_13 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_0form // self.N3) ) EF_13.eliminate_zeros() # ---------------------------------------------------- @@ -186,9 +184,9 @@ def get_blocks_EF(self, pol=True): B2_3_pts = B2_3_pts.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -200,8 +198,8 @@ def get_blocks_EF(self, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_3_pts, val, row, @@ -209,8 +207,7 @@ def get_blocks_EF(self, pol=True): ) EF_21 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_0form // self.N3) ) EF_21.eliminate_zeros() # ---------------------------------------------------- @@ -221,9 +218,9 @@ def get_blocks_EF(self, pol=True): B2_1_pts = B2_1_pts.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -235,8 +232,8 @@ def get_blocks_EF(self, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_1_pts, val, row, @@ -244,8 +241,7 @@ def get_blocks_EF(self, pol=True): ) EF_23 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_0form // self.N3) ) EF_23.eliminate_zeros() # ---------------------------------------------------- @@ -255,9 +251,9 @@ def get_blocks_EF(self, pol=True): B2_2_pts = self.equilibrium.b2_2(self.eta_int[0], self.eta_int[1], 0.0) # assemble sparse matrix - val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs0_2d( self.dofs_0_N_i[0][0], @@ -273,8 +269,7 @@ def get_blocks_EF(self, pol=True): ) EF_31 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_0form // self.N3) ) EF_31.eliminate_zeros() # ---------------------------------------------------- @@ -284,9 +279,9 @@ def get_blocks_EF(self, pol=True): B2_1_pts = self.equilibrium.b2_1(self.eta_int[0], self.eta_int[1], 0.0) # assemble sparse matrix - val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs0_2d( self.dofs_0_N_i[0][0], @@ -302,8 +297,7 @@ def get_blocks_EF(self, pol=True): ) EF_32 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_0form // self.N3) ) EF_32.eliminate_zeros() # ---------------------------------------------------- @@ -315,17 +309,14 @@ def get_blocks_EF(self, pol=True): B2_3_pts = B2_3_pts.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) ker.rhs11( @@ -341,8 +332,8 @@ def get_blocks_EF(self, pol=True): self.basis_his_N[0], self.basis_int_N[1], self.basis_int_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_3_pts, val, row, @@ -359,17 +350,14 @@ def get_blocks_EF(self, pol=True): B2_2_pts = B2_2_pts.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) ker.rhs11( @@ -385,8 +373,8 @@ def get_blocks_EF(self, pol=True): self.basis_his_N[0], self.basis_int_N[1], self.basis_int_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_2_pts, val, row, @@ -403,17 +391,14 @@ def get_blocks_EF(self, pol=True): B2_3_pts = B2_3_pts.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) ker.rhs12( @@ -429,8 +414,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_his_N[1], self.basis_int_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_3_pts, val, row, @@ -447,17 +432,14 @@ def get_blocks_EF(self, pol=True): B2_1_pts = B2_1_pts.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) ker.rhs12( @@ -473,8 +455,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_his_N[1], self.basis_int_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_1_pts, val, row, @@ -491,17 +473,14 @@ def get_blocks_EF(self, pol=True): B2_2_pts = B2_2_pts.reshape(self.nint[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int ) ker.rhs13( @@ -517,8 +496,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_int_N[1], self.basis_his_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_2_pts, val, row, @@ -535,17 +514,14 @@ def get_blocks_EF(self, pol=True): B2_1_pts = B2_1_pts.reshape(self.nint[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int ) ker.rhs13( @@ -561,8 +537,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_int_N[1], self.basis_his_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_1_pts, val, row, @@ -585,9 +561,9 @@ def get_blocks_EF(self, pol=True): det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_D_i[0][0], @@ -599,8 +575,8 @@ def get_blocks_EF(self, pol=True): self.wts[0], self.basis_his_D[0], self.basis_int_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_3_pts / det_dF, val, row, @@ -608,8 +584,7 @@ def get_blocks_EF(self, pol=True): ) EF_12 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_2form[1] // self.D3), + (val, (row, col)), shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_2form[1] // self.D3) ) EF_12.eliminate_zeros() # ---------------------------------------------------- @@ -624,9 +599,9 @@ def get_blocks_EF(self, pol=True): det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_D_i[0][0], @@ -638,8 +613,8 @@ def get_blocks_EF(self, pol=True): self.wts[0], self.basis_his_D[0], self.basis_int_D[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_2_pts / det_dF, val, row, @@ -647,8 +622,7 @@ def get_blocks_EF(self, pol=True): ) EF_13 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_2form[2] // self.N3), + (val, (row, col)), shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_2form[2] // self.N3) ) EF_13.eliminate_zeros() # ---------------------------------------------------- @@ -663,9 +637,9 @@ def get_blocks_EF(self, pol=True): det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -677,8 +651,8 @@ def get_blocks_EF(self, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_D[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_3_pts / det_dF, val, row, @@ -686,8 +660,7 @@ def get_blocks_EF(self, pol=True): ) EF_21 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_2form[0] // self.D3), + (val, (row, col)), shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_2form[0] // self.D3) ) EF_21.eliminate_zeros() # ---------------------------------------------------- @@ -702,9 +675,9 @@ def get_blocks_EF(self, pol=True): det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_D_i[0][0], @@ -716,8 +689,8 @@ def get_blocks_EF(self, pol=True): self.wts[1], self.basis_int_D[0], self.basis_his_D[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_1_pts / det_dF, val, row, @@ -725,8 +698,7 @@ def get_blocks_EF(self, pol=True): ) EF_23 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_2form[2] // self.N3), + (val, (row, col)), shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_2form[2] // self.N3) ) EF_23.eliminate_zeros() # ---------------------------------------------------- @@ -739,9 +711,9 @@ def get_blocks_EF(self, pol=True): det_dF = abs(self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], 0.0)) # assemble sparse matrix - val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) ker.rhs0_2d( self.dofs_0_N_i[0][0], @@ -757,8 +729,7 @@ def get_blocks_EF(self, pol=True): ) EF_31 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_2form[0] // self.D3), + (val, (row, col)), shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_2form[0] // self.D3) ) EF_31.eliminate_zeros() # ---------------------------------------------------- @@ -771,9 +742,9 @@ def get_blocks_EF(self, pol=True): det_dF = abs(self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], 0.0)) # assemble sparse matrix - val = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs0_2d( self.dofs_0_D_i[0][0], @@ -789,8 +760,7 @@ def get_blocks_EF(self, pol=True): ) EF_32 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_2form[1] // self.D3), + (val, (row, col)), shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_2form[1] // self.D3) ) EF_32.eliminate_zeros() # ---------------------------------------------------- @@ -803,22 +773,19 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_int[1], self.eta_int[2]), + self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_int[1], self.eta_int[2]) ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=int ) ker.rhs11( @@ -834,8 +801,8 @@ def get_blocks_EF(self, pol=True): self.basis_his_D[0], self.basis_int_N[1], self.basis_int_D[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_3_pts / det_dF, val, row, @@ -853,22 +820,19 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_int[1], self.eta_int[2]), + self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_int[1], self.eta_int[2]) ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) ker.rhs11( @@ -884,8 +848,8 @@ def get_blocks_EF(self, pol=True): self.basis_his_D[0], self.basis_int_D[1], self.basis_int_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_2_pts / det_dF, val, row, @@ -903,22 +867,19 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_his[1].flatten(), self.eta_int[2]), + self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_his[1].flatten(), self.eta_int[2]) ) det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=int ) ker.rhs12( @@ -934,8 +895,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_his_D[1], self.basis_int_D[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_3_pts / det_dF, val, row, @@ -953,22 +914,19 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_his[1].flatten(), self.eta_int[2]), + self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_his[1].flatten(), self.eta_int[2]) ) det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) ker.rhs12( @@ -984,8 +942,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_D[0], self.basis_his_D[1], self.basis_int_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_1_pts / det_dF, val, row, @@ -1003,22 +961,19 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], self.eta_his[2].flatten()), + self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], self.eta_his[2].flatten()) ) det_dF = det_dF.reshape(self.nint[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) ker.rhs13( @@ -1034,8 +989,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_int_D[1], self.basis_his_D[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), -B2_2_pts / det_dF, val, row, @@ -1053,22 +1008,19 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], self.eta_his[2].flatten()), + self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], self.eta_his[2].flatten()) ) det_dF = det_dF.reshape(self.nint[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) ker.rhs13( @@ -1084,8 +1036,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_D[0], self.basis_int_N[1], self.basis_his_D[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), B2_1_pts / det_dF, val, row, @@ -1141,9 +1093,9 @@ def get_blocks_FL(self, which, pol=True): EQ = EQ.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -1155,8 +1107,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ, val, row, @@ -1164,8 +1116,7 @@ def get_blocks_FL(self, which, pol=True): ) F_11 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_2form[0] // self.D3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_2form[0] // self.D3, self.space.Ntot_0form // self.N3) ) F_11.eliminate_zeros() # ------------------------------------------------------------ @@ -1182,9 +1133,9 @@ def get_blocks_FL(self, which, pol=True): EQ = EQ.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_N_i[0][0], @@ -1196,8 +1147,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[0], self.basis_his_N[0], self.basis_int_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ, val, row, @@ -1205,8 +1156,7 @@ def get_blocks_FL(self, which, pol=True): ) F_22 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_2form[1] // self.D3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_2form[1] // self.D3, self.space.Ntot_0form // self.N3) ) F_22.eliminate_zeros() # ------------------------------------------------------------ @@ -1223,9 +1173,9 @@ def get_blocks_FL(self, which, pol=True): EQ = EQ.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) ker.rhs2_2d( self.dofs_1_N_i[0][0], @@ -1240,8 +1190,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[1], self.basis_his_N[0], self.basis_his_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ, val, row, @@ -1249,8 +1199,7 @@ def get_blocks_FL(self, which, pol=True): ) F_33 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_2form[2] // self.N3, self.space.Ntot_0form // self.N3), + (val, (row, col)), shape=(self.space.Ntot_2form[2] // self.N3, self.space.Ntot_0form // self.N3) ) F_33.eliminate_zeros() # ------------------------------------------------------------ @@ -1264,25 +1213,20 @@ def get_blocks_FL(self, which, pol=True): EQ = self.equilibrium.p3(self.eta_int[0], self.eta_his[1].flatten(), self.eta_his[2].flatten()) else: EQ = self.equilibrium.domain.jacobian_det( - self.eta_int[0], - self.eta_his[1].flatten(), - self.eta_his[2].flatten(), + self.eta_int[0], self.eta_his[1].flatten(), self.eta_his[2].flatten() ) EQ = EQ.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int ) ker.rhs21( @@ -1301,8 +1245,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_int_N[0], self.basis_his_N[1], self.basis_his_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ, val, row, @@ -1321,25 +1265,20 @@ def get_blocks_FL(self, which, pol=True): EQ = self.equilibrium.p3(self.eta_his[0].flatten(), self.eta_int[1], self.eta_his[2].flatten()) else: EQ = self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), - self.eta_int[1], - self.eta_his[2].flatten(), + self.eta_his[0].flatten(), self.eta_int[1], self.eta_his[2].flatten() ) EQ = EQ.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int ) ker.rhs22( @@ -1358,8 +1297,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_his_N[0], self.basis_int_N[1], self.basis_his_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ, val, row, @@ -1378,25 +1317,20 @@ def get_blocks_FL(self, which, pol=True): EQ = self.equilibrium.p3(self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_int[2]) else: EQ = self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), - self.eta_his[1].flatten(), - self.eta_int[2], + self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_int[2] ) EQ = EQ.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) ker.rhs23( @@ -1415,8 +1349,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_his_N[0], self.basis_his_N[1], self.basis_int_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ, val, row, @@ -1443,9 +1377,9 @@ def get_blocks_FL(self, which, pol=True): det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -1457,8 +1391,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_D[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1466,8 +1400,7 @@ def get_blocks_FL(self, which, pol=True): ) F_11 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_2form[0] // self.D3, self.space.Ntot_2form[0] // self.D3), + (val, (row, col)), shape=(self.space.Ntot_2form[0] // self.D3, self.space.Ntot_2form[0] // self.D3) ) F_11.eliminate_zeros() # ------------------------------------------------------------ @@ -1486,9 +1419,9 @@ def get_blocks_FL(self, which, pol=True): det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_D_i[0][0], @@ -1500,8 +1433,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[0], self.basis_his_D[0], self.basis_int_N[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1509,8 +1442,7 @@ def get_blocks_FL(self, which, pol=True): ) F_22 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_2form[1] // self.D3, self.space.Ntot_2form[1] // self.D3), + (val, (row, col)), shape=(self.space.Ntot_2form[1] // self.D3, self.space.Ntot_2form[1] // self.D3) ) F_22.eliminate_zeros() # ------------------------------------------------------------ @@ -1526,14 +1458,14 @@ def get_blocks_FL(self, which, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_his[1].flatten(), 0.0), + self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_his[1].flatten(), 0.0) ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs2_2d( self.dofs_1_D_i[0][0], @@ -1548,8 +1480,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[1], self.basis_his_D[0], self.basis_his_D[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1557,8 +1489,7 @@ def get_blocks_FL(self, which, pol=True): ) F_33 = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_2form[2] // self.N3, self.space.Ntot_2form[2] // self.N3), + (val, (row, col)), shape=(self.space.Ntot_2form[2] // self.N3, self.space.Ntot_2form[2] // self.N3) ) F_33.eliminate_zeros() # ------------------------------------------------------------ @@ -1576,25 +1507,20 @@ def get_blocks_FL(self, which, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( self.equilibrium.domain.jacobian_det( - self.eta_int[0], - self.eta_his[1].flatten(), - self.eta_his[2].flatten(), - ), + self.eta_int[0], self.eta_his[1].flatten(), self.eta_his[2].flatten() + ) ) det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) ker.rhs21( @@ -1613,8 +1539,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_int_N[0], self.basis_his_D[1], self.basis_his_D[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1637,25 +1563,20 @@ def get_blocks_FL(self, which, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), - self.eta_int[1], - self.eta_his[2].flatten(), - ), + self.eta_his[0].flatten(), self.eta_int[1], self.eta_his[2].flatten() + ) ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) ker.rhs22( @@ -1674,8 +1595,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_his_D[0], self.basis_int_N[1], self.basis_his_D[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1698,25 +1619,20 @@ def get_blocks_FL(self, which, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), - self.eta_his[1].flatten(), - self.eta_int[2], - ), + self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_int[2] + ) ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int ) ker.rhs23( @@ -1735,8 +1651,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_his_D[0], self.basis_his_D[1], self.basis_int_N[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1775,14 +1691,14 @@ def get_blocks_PR(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_his[1].flatten(), 0.0), + self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_his[1].flatten(), 0.0) ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs2_2d( self.dofs_1_D_i[0][0], @@ -1797,8 +1713,8 @@ def get_blocks_PR(self, pol=True): self.wts[1], self.basis_his_D[0], self.basis_his_D[1], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), P3_pts / det_dF, val, row, @@ -1806,8 +1722,7 @@ def get_blocks_PR(self, pol=True): ) PR = spa.csr_matrix( - (val, (row, col)), - shape=(self.space.Ntot_3form // self.D3, self.space.Ntot_3form // self.D3), + (val, (row, col)), shape=(self.space.Ntot_3form // self.D3, self.space.Ntot_3form // self.D3) ) PR.eliminate_zeros() # ----------------------------------------------------- @@ -1816,9 +1731,7 @@ def get_blocks_PR(self, pol=True): # --------------- ([his, his, his] of DDD) ------------ # evaluate equilibrium pressure at quadrature points P3_pts = self.equilibrium.p3( - self.eta_his[0].flatten(), - self.eta_his[1].flatten(), - self.eta_his[2].flatten(), + self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_his[2].flatten() ) P3_pts = P3_pts.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1], self.nhis[2], self.nq[2]) @@ -1826,25 +1739,20 @@ def get_blocks_PR(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), - self.eta_his[1].flatten(), - self.eta_his[2].flatten(), - ), + self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_his[2].flatten() + ) ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=float, + val = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float ) - row = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + row = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) - col = xp.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, - dtype=int, + col = np.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int ) ker.rhs3( @@ -1866,8 +1774,8 @@ def get_blocks_PR(self, pol=True): self.basis_his_D[0], self.basis_his_D[1], self.basis_his_D[2], - xp.array(self.space.NbaseN), - xp.array(self.space.NbaseD), + np.array(self.space.NbaseN), + np.array(self.space.NbaseD), P3_pts / det_dF, val, row, diff --git a/src/struphy/eigenvalue_solvers/projectors_global.py b/src/struphy/eigenvalue_solvers/projectors_global.py index 9d246cdac..df9080f01 100644 --- a/src/struphy/eigenvalue_solvers/projectors_global.py +++ b/src/struphy/eigenvalue_solvers/projectors_global.py @@ -6,7 +6,7 @@ Classes for commuting projectors in 1D, 2D and 3D based on global spline interpolation and histopolation. """ -import cunumpy as xp +import numpy as np import scipy.sparse as spa import struphy.bsplines.bsplines as bsp @@ -156,20 +156,20 @@ def __init__(self, spline_space, n_quad=6): self.n_quad = n_quad # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[0] - self.wts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[1] + self.pts_loc = np.polynomial.legendre.leggauss(self.n_quad)[0] + self.wts_loc = np.polynomial.legendre.leggauss(self.n_quad)[1] # set interpolation points (Greville points) self.x_int = spline_space.greville.copy() # set number of sub-intervals per integration interval between Greville points and integration boundaries - self.subs = xp.ones(spline_space.NbaseD, dtype=int) - self.x_his = xp.array([self.x_int[0]]) + self.subs = np.ones(spline_space.NbaseD, dtype=int) + self.x_his = np.array([self.x_int[0]]) for i in range(spline_space.NbaseD): for br in spline_space.el_b: # left and right integration boundaries - if not spline_space.spl_kind: + if spline_space.spl_kind == False: xl = self.x_int[i] xr = self.x_int[i + 1] else: @@ -181,16 +181,16 @@ def __init__(self, spline_space, n_quad=6): # compute subs and x_his if (br > xl + 1e-10) and (br < xr - 1e-10): self.subs[i] += 1 - self.x_his = xp.append(self.x_his, br) + self.x_his = np.append(self.x_his, br) elif br >= xr - 1e-10: - self.x_his = xp.append(self.x_his, xr) + self.x_his = np.append(self.x_his, xr) break - if spline_space.spl_kind and spline_space.p % 2 == 0: - self.x_his = xp.append(self.x_his, spline_space.el_b[-1] + self.x_his[0]) + if spline_space.spl_kind == True and spline_space.p % 2 == 0: + self.x_his = np.append(self.x_his, spline_space.el_b[-1] + self.x_his[0]) # cumulative number of sub-intervals for conversion local interval --> global interval - self.subs_cum = xp.append(0, xp.cumsum(self.subs - 1)[:-1]) + self.subs_cum = np.append(0, np.cumsum(self.subs - 1)[:-1]) # quadrature points and weights self.pts, self.wts = bsp.quadrature_grid(self.x_his, self.pts_loc, self.wts_loc) @@ -198,33 +198,33 @@ def __init__(self, spline_space, n_quad=6): # quadrature points and weights, ignoring subs (less accurate integration for even degree) self.x_hisG = self.x_int - if spline_space.spl_kind: + if spline_space.spl_kind == True: if spline_space.p % 2 == 0: - self.x_hisG = xp.append(self.x_hisG, spline_space.el_b[-1] + self.x_hisG[0]) + self.x_hisG = np.append(self.x_hisG, spline_space.el_b[-1] + self.x_hisG[0]) else: - self.x_hisG = xp.append(self.x_hisG, spline_space.el_b[-1]) + self.x_hisG = np.append(self.x_hisG, spline_space.el_b[-1]) self.ptsG, self.wtsG = bsp.quadrature_grid(self.x_hisG, self.pts_loc, self.wts_loc) self.ptsG = self.ptsG % spline_space.el_b[-1] # Knot span indices at interpolation points in format (greville, 0) - self.span_x_int_N = xp.zeros(self.x_int[:, None].shape, dtype=int) - self.span_x_int_D = xp.zeros(self.x_int[:, None].shape, dtype=int) + self.span_x_int_N = np.zeros(self.x_int[:, None].shape, dtype=int) + self.span_x_int_D = np.zeros(self.x_int[:, None].shape, dtype=int) for i in range(self.x_int.shape[0]): self.span_x_int_N[i, 0] = bsp.find_span(self.space.T, self.space.p, self.x_int[i]) self.span_x_int_D[i, 0] = bsp.find_span(self.space.t, self.space.p - 1, self.x_int[i]) # Knot span indices at quadrature points between x_int in format (i, iq) - self.span_ptsG_N = xp.zeros(self.ptsG.shape, dtype=int) - self.span_ptsG_D = xp.zeros(self.ptsG.shape, dtype=int) + self.span_ptsG_N = np.zeros(self.ptsG.shape, dtype=int) + self.span_ptsG_D = np.zeros(self.ptsG.shape, dtype=int) for i in range(self.ptsG.shape[0]): for iq in range(self.ptsG.shape[1]): self.span_ptsG_N[i, iq] = bsp.find_span(self.space.T, self.space.p, self.ptsG[i, iq]) self.span_ptsG_D[i, iq] = bsp.find_span(self.space.t, self.space.p - 1, self.ptsG[i, iq]) # Values of p + 1 non-zero basis functions at Greville points in format (greville, 0, basis function) - self.basis_x_int_N = xp.zeros((*self.x_int[:, None].shape, self.space.p + 1), dtype=float) - self.basis_x_int_D = xp.zeros((*self.x_int[:, None].shape, self.space.p), dtype=float) + self.basis_x_int_N = np.zeros((*self.x_int[:, None].shape, self.space.p + 1), dtype=float) + self.basis_x_int_D = np.zeros((*self.x_int[:, None].shape, self.space.p), dtype=float) N_temp = bsp.basis_ders_on_quad_grid(self.space.T, self.space.p, self.x_int[:, None], 0, normalize=False) D_temp = bsp.basis_ders_on_quad_grid(self.space.t, self.space.p - 1, self.x_int[:, None], 0, normalize=True) @@ -236,8 +236,8 @@ def __init__(self, spline_space, n_quad=6): self.basis_x_int_D[i, 0, b] = D_temp[i, b, 0, 0] # Values of p + 1 non-zero basis functions at quadrature points points between x_int in format (i, iq, basis function) - self.basis_ptsG_N = xp.zeros((*self.ptsG.shape, self.space.p + 1), dtype=float) - self.basis_ptsG_D = xp.zeros((*self.ptsG.shape, self.space.p), dtype=float) + self.basis_ptsG_N = np.zeros((*self.ptsG.shape, self.space.p + 1), dtype=float) + self.basis_ptsG_D = np.zeros((*self.ptsG.shape, self.space.p), dtype=float) N_temp = bsp.basis_ders_on_quad_grid(self.space.T, self.space.p, self.ptsG, 0, normalize=False) D_temp = bsp.basis_ders_on_quad_grid(self.space.t, self.space.p - 1, self.ptsG, 0, normalize=True) @@ -250,7 +250,7 @@ def __init__(self, spline_space, n_quad=6): self.basis_ptsG_D[i, iq, b] = D_temp[i, b, 0, iq] # quadrature matrix for performing integrations as matrix-vector products - self.Q = xp.zeros((spline_space.NbaseD, self.wts.shape[0] * self.n_quad), dtype=float) + self.Q = np.zeros((spline_space.NbaseD, self.wts.shape[0] * self.n_quad), dtype=float) for i in range(spline_space.NbaseD): for j in range(self.subs[i]): @@ -260,7 +260,7 @@ def __init__(self, spline_space, n_quad=6): self.Q = spa.csr_matrix(self.Q) # quadrature matrix for performing integrations as matrix-vector products, ignoring subs (less accurate integration for even degree) - self.QG = xp.zeros((spline_space.NbaseD, self.wtsG.shape[0] * self.n_quad), dtype=float) + self.QG = np.zeros((spline_space.NbaseD, self.wtsG.shape[0] * self.n_quad), dtype=float) for i in range(spline_space.NbaseD): self.QG[i, self.n_quad * i : self.n_quad * (i + 1)] = self.wtsG[i] @@ -271,18 +271,10 @@ def __init__(self, spline_space, n_quad=6): BM_splines = [False, True] self.N_int = bsp.collocation_matrix( - spline_space.T, - spline_space.p - 0, - self.x_int, - spline_space.spl_kind, - BM_splines[0], + spline_space.T, spline_space.p - 0, self.x_int, spline_space.spl_kind, BM_splines[0] ) self.D_int = bsp.collocation_matrix( - spline_space.t, - spline_space.p - 1, - self.x_int, - spline_space.spl_kind, - BM_splines[1], + spline_space.t, spline_space.p - 1, self.x_int, spline_space.spl_kind, BM_splines[1] ) self.N_int[self.N_int < 1e-12] = 0.0 @@ -292,18 +284,10 @@ def __init__(self, spline_space, n_quad=6): self.D_int = spa.csr_matrix(self.D_int) self.N_pts = bsp.collocation_matrix( - spline_space.T, - spline_space.p - 0, - self.pts.flatten(), - spline_space.spl_kind, - BM_splines[0], + spline_space.T, spline_space.p - 0, self.pts.flatten(), spline_space.spl_kind, BM_splines[0] ) self.D_pts = bsp.collocation_matrix( - spline_space.t, - spline_space.p - 1, - self.pts.flatten(), - spline_space.spl_kind, - BM_splines[1], + spline_space.t, spline_space.p - 1, self.pts.flatten(), spline_space.spl_kind, BM_splines[1] ) self.N_pts = spa.csr_matrix(self.N_pts) @@ -415,17 +399,17 @@ def dofs_1d_bases_products(self, space): dofs_1_i(D_j*D_k). """ - dofs_0_NN = xp.empty((space.NbaseN, space.NbaseN, space.NbaseN), dtype=float) - dofs_0_DN = xp.empty((space.NbaseN, space.NbaseD, space.NbaseN), dtype=float) - dofs_0_DD = xp.empty((space.NbaseN, space.NbaseD, space.NbaseD), dtype=float) + dofs_0_NN = np.empty((space.NbaseN, space.NbaseN, space.NbaseN), dtype=float) + dofs_0_DN = np.empty((space.NbaseN, space.NbaseD, space.NbaseN), dtype=float) + dofs_0_DD = np.empty((space.NbaseN, space.NbaseD, space.NbaseD), dtype=float) - dofs_1_NN = xp.empty((space.NbaseD, space.NbaseN, space.NbaseN), dtype=float) - dofs_1_DN = xp.empty((space.NbaseD, space.NbaseD, space.NbaseN), dtype=float) - dofs_1_DD = xp.empty((space.NbaseD, space.NbaseD, space.NbaseD), dtype=float) + dofs_1_NN = np.empty((space.NbaseD, space.NbaseN, space.NbaseN), dtype=float) + dofs_1_DN = np.empty((space.NbaseD, space.NbaseD, space.NbaseN), dtype=float) + dofs_1_DD = np.empty((space.NbaseD, space.NbaseD, space.NbaseD), dtype=float) # ========= dofs_0_NN and dofs_1_NN ============== - cj = xp.zeros(space.NbaseN, dtype=float) - ck = xp.zeros(space.NbaseN, dtype=float) + cj = np.zeros(space.NbaseN, dtype=float) + ck = np.zeros(space.NbaseN, dtype=float) for j in range(space.NbaseN): for k in range(space.NbaseN): @@ -442,8 +426,8 @@ def N_jN_k(eta): dofs_1_NN[:, j, k] = self.dofs_1(N_jN_k) # ========= dofs_0_DN and dofs_1_DN ============== - cj = xp.zeros(space.NbaseD, dtype=float) - ck = xp.zeros(space.NbaseN, dtype=float) + cj = np.zeros(space.NbaseD, dtype=float) + ck = np.zeros(space.NbaseN, dtype=float) for j in range(space.NbaseD): for k in range(space.NbaseN): @@ -460,8 +444,8 @@ def D_jN_k(eta): dofs_1_DN[:, j, k] = self.dofs_1(D_jN_k) # ========= dofs_0_DD and dofs_1_DD ============= - cj = xp.zeros(space.NbaseD, dtype=float) - ck = xp.zeros(space.NbaseD, dtype=float) + cj = np.zeros(space.NbaseD, dtype=float) + ck = np.zeros(space.NbaseD, dtype=float) for j in range(space.NbaseD): for k in range(space.NbaseD): @@ -477,110 +461,110 @@ def D_jD_k(eta): dofs_0_DD[:, j, k] = self.dofs_0(D_jD_k) dofs_1_DD[:, j, k] = self.dofs_1(D_jD_k) - dofs_0_ND = xp.transpose(dofs_0_DN, (0, 2, 1)) - dofs_1_ND = xp.transpose(dofs_1_DN, (0, 2, 1)) + dofs_0_ND = np.transpose(dofs_0_DN, (0, 2, 1)) + dofs_1_ND = np.transpose(dofs_1_DN, (0, 2, 1)) # find non-zero entries - dofs_0_NN_indices = xp.nonzero(dofs_0_NN) - dofs_0_DN_indices = xp.nonzero(dofs_0_DN) - dofs_0_ND_indices = xp.nonzero(dofs_0_ND) - dofs_0_DD_indices = xp.nonzero(dofs_0_DD) - - dofs_1_NN_indices = xp.nonzero(dofs_1_NN) - dofs_1_DN_indices = xp.nonzero(dofs_1_DN) - dofs_1_ND_indices = xp.nonzero(dofs_1_ND) - dofs_1_DD_indices = xp.nonzero(dofs_1_DD) - - dofs_0_NN_i_red = xp.empty(dofs_0_NN_indices[0].size, dtype=int) - dofs_0_DN_i_red = xp.empty(dofs_0_DN_indices[0].size, dtype=int) - dofs_0_ND_i_red = xp.empty(dofs_0_ND_indices[0].size, dtype=int) - dofs_0_DD_i_red = xp.empty(dofs_0_DD_indices[0].size, dtype=int) - - dofs_1_NN_i_red = xp.empty(dofs_1_NN_indices[0].size, dtype=int) - dofs_1_DN_i_red = xp.empty(dofs_1_DN_indices[0].size, dtype=int) - dofs_1_ND_i_red = xp.empty(dofs_1_ND_indices[0].size, dtype=int) - dofs_1_DD_i_red = xp.empty(dofs_1_DD_indices[0].size, dtype=int) + dofs_0_NN_indices = np.nonzero(dofs_0_NN) + dofs_0_DN_indices = np.nonzero(dofs_0_DN) + dofs_0_ND_indices = np.nonzero(dofs_0_ND) + dofs_0_DD_indices = np.nonzero(dofs_0_DD) + + dofs_1_NN_indices = np.nonzero(dofs_1_NN) + dofs_1_DN_indices = np.nonzero(dofs_1_DN) + dofs_1_ND_indices = np.nonzero(dofs_1_ND) + dofs_1_DD_indices = np.nonzero(dofs_1_DD) + + dofs_0_NN_i_red = np.empty(dofs_0_NN_indices[0].size, dtype=int) + dofs_0_DN_i_red = np.empty(dofs_0_DN_indices[0].size, dtype=int) + dofs_0_ND_i_red = np.empty(dofs_0_ND_indices[0].size, dtype=int) + dofs_0_DD_i_red = np.empty(dofs_0_DD_indices[0].size, dtype=int) + + dofs_1_NN_i_red = np.empty(dofs_1_NN_indices[0].size, dtype=int) + dofs_1_DN_i_red = np.empty(dofs_1_DN_indices[0].size, dtype=int) + dofs_1_ND_i_red = np.empty(dofs_1_ND_indices[0].size, dtype=int) + dofs_1_DD_i_red = np.empty(dofs_1_DD_indices[0].size, dtype=int) # ================================ nv = space.NbaseN * dofs_0_NN_indices[1] + dofs_0_NN_indices[2] - un = xp.unique(nv) + un = np.unique(nv) for i in range(dofs_0_NN_indices[0].size): - dofs_0_NN_i_red[i] = xp.nonzero(un == nv[i])[0] + dofs_0_NN_i_red[i] = np.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseN * dofs_0_DN_indices[1] + dofs_0_DN_indices[2] - un = xp.unique(nv) + un = np.unique(nv) for i in range(dofs_0_DN_indices[0].size): - dofs_0_DN_i_red[i] = xp.nonzero(un == nv[i])[0] + dofs_0_DN_i_red[i] = np.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseD * dofs_0_ND_indices[1] + dofs_0_ND_indices[2] - un = xp.unique(nv) + un = np.unique(nv) for i in range(dofs_0_ND_indices[0].size): - dofs_0_ND_i_red[i] = xp.nonzero(un == nv[i])[0] + dofs_0_ND_i_red[i] = np.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseD * dofs_0_DD_indices[1] + dofs_0_DD_indices[2] - un = xp.unique(nv) + un = np.unique(nv) for i in range(dofs_0_DD_indices[0].size): - dofs_0_DD_i_red[i] = xp.nonzero(un == nv[i])[0] + dofs_0_DD_i_red[i] = np.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseN * dofs_1_NN_indices[1] + dofs_1_NN_indices[2] - un = xp.unique(nv) + un = np.unique(nv) for i in range(dofs_1_NN_indices[0].size): - dofs_1_NN_i_red[i] = xp.nonzero(un == nv[i])[0] + dofs_1_NN_i_red[i] = np.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseN * dofs_1_DN_indices[1] + dofs_1_DN_indices[2] - un = xp.unique(nv) + un = np.unique(nv) for i in range(dofs_1_DN_indices[0].size): - dofs_1_DN_i_red[i] = xp.nonzero(un == nv[i])[0] + dofs_1_DN_i_red[i] = np.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseD * dofs_1_ND_indices[1] + dofs_1_ND_indices[2] - un = xp.unique(nv) + un = np.unique(nv) for i in range(dofs_1_ND_indices[0].size): - dofs_1_ND_i_red[i] = xp.nonzero(un == nv[i])[0] + dofs_1_ND_i_red[i] = np.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseD * dofs_1_DD_indices[1] + dofs_1_DD_indices[2] - un = xp.unique(nv) + un = np.unique(nv) for i in range(dofs_1_DD_indices[0].size): - dofs_1_DD_i_red[i] = xp.nonzero(un == nv[i])[0] + dofs_1_DD_i_red[i] = np.nonzero(un == nv[i])[0] - dofs_0_NN_indices = xp.vstack( - (dofs_0_NN_indices[0], dofs_0_NN_indices[1], dofs_0_NN_indices[2], dofs_0_NN_i_red), + dofs_0_NN_indices = np.vstack( + (dofs_0_NN_indices[0], dofs_0_NN_indices[1], dofs_0_NN_indices[2], dofs_0_NN_i_red) ) - dofs_0_DN_indices = xp.vstack( - (dofs_0_DN_indices[0], dofs_0_DN_indices[1], dofs_0_DN_indices[2], dofs_0_DN_i_red), + dofs_0_DN_indices = np.vstack( + (dofs_0_DN_indices[0], dofs_0_DN_indices[1], dofs_0_DN_indices[2], dofs_0_DN_i_red) ) - dofs_0_ND_indices = xp.vstack( - (dofs_0_ND_indices[0], dofs_0_ND_indices[1], dofs_0_ND_indices[2], dofs_0_ND_i_red), + dofs_0_ND_indices = np.vstack( + (dofs_0_ND_indices[0], dofs_0_ND_indices[1], dofs_0_ND_indices[2], dofs_0_ND_i_red) ) - dofs_0_DD_indices = xp.vstack( - (dofs_0_DD_indices[0], dofs_0_DD_indices[1], dofs_0_DD_indices[2], dofs_0_DD_i_red), + dofs_0_DD_indices = np.vstack( + (dofs_0_DD_indices[0], dofs_0_DD_indices[1], dofs_0_DD_indices[2], dofs_0_DD_i_red) ) - dofs_1_NN_indices = xp.vstack( - (dofs_1_NN_indices[0], dofs_1_NN_indices[1], dofs_1_NN_indices[2], dofs_1_NN_i_red), + dofs_1_NN_indices = np.vstack( + (dofs_1_NN_indices[0], dofs_1_NN_indices[1], dofs_1_NN_indices[2], dofs_1_NN_i_red) ) - dofs_1_DN_indices = xp.vstack( - (dofs_1_DN_indices[0], dofs_1_DN_indices[1], dofs_1_DN_indices[2], dofs_1_DN_i_red), + dofs_1_DN_indices = np.vstack( + (dofs_1_DN_indices[0], dofs_1_DN_indices[1], dofs_1_DN_indices[2], dofs_1_DN_i_red) ) - dofs_1_ND_indices = xp.vstack( - (dofs_1_ND_indices[0], dofs_1_ND_indices[1], dofs_1_ND_indices[2], dofs_1_ND_i_red), + dofs_1_ND_indices = np.vstack( + (dofs_1_ND_indices[0], dofs_1_ND_indices[1], dofs_1_ND_indices[2], dofs_1_ND_i_red) ) - dofs_1_DD_indices = xp.vstack( - (dofs_1_DD_indices[0], dofs_1_DD_indices[1], dofs_1_DD_indices[2], dofs_1_DD_i_red), + dofs_1_DD_indices = np.vstack( + (dofs_1_DD_indices[0], dofs_1_DD_indices[1], dofs_1_DD_indices[2], dofs_1_DD_i_red) ) return ( @@ -658,8 +642,8 @@ def eval_for_PI(self, comp, fun): pts_PI = self.pts_PI[comp] - pts1, pts2 = xp.meshgrid(pts_PI[0], pts_PI[1], indexing="ij") - # pts1, pts2 = xp.meshgrid(pts_PI[0], pts_PI[1], indexing='ij', sparse=True) # numpy >1.7 + pts1, pts2 = np.meshgrid(pts_PI[0], pts_PI[1], indexing="ij") + # pts1, pts2 = np.meshgrid(pts_PI[0], pts_PI[1], indexing='ij', sparse=True) # numpy >1.7 return fun(pts1, pts2) @@ -922,8 +906,8 @@ def eval_for_PI(self, comp, fun): pts_PI = self.pts_PI[comp] - pts1, pts2, pts3 = xp.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing="ij") - # pts1, pts2, pts3 = xp.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing='ij', sparse=True) # numpy >1.7 + pts1, pts2, pts3 = np.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing="ij") + # pts1, pts2, pts3 = np.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing='ij', sparse=True) # numpy >1.7 return fun(pts1, pts2, pts3) @@ -955,25 +939,25 @@ def eval_for_PI(self, comp, fun): # rhs = mat_f # # elif comp=='11': - # rhs = xp.empty( (self.d1, self.n2, self.n3) ) + # rhs = np.empty( (self.d1, self.n2, self.n3) ) # # ker_glob.kernel_int_3d_eta1(self.subs1, self.subs_cum1, self.wts1, # mat_f.reshape(self.ne1, self.nq1, self.n2, self.n3), rhs # ) # elif comp=='12': - # rhs = xp.empty( (self.n1, self.d2, self.n3) ) + # rhs = np.empty( (self.n1, self.d2, self.n3) ) # # ker_glob.kernel_int_3d_eta2(self.subs2, self.subs_cum2, self.wts2, # mat_f.reshape(self.n1, self.ne2, self.nq2, self.n3), rhs # ) # elif comp=='13': - # rhs = xp.empty( (self.n1, self.n2, self.d3) ) + # rhs = np.empty( (self.n1, self.n2, self.d3) ) # # ker_glob.kernel_int_3d_eta3(self.subs3, self.subs_cum3, self.wts3, # mat_f.reshape(self.n1, self.n2, self.ne3, self.nq3), rhs # ) # elif comp=='21': - # rhs = xp.empty( (self.n1, self.d2, self.d3) ) + # rhs = np.empty( (self.n1, self.d2, self.d3) ) # # ker_glob.kernel_int_3d_eta2_eta3(self.subs2, self.subs3, # self.subs_cum2, self.subs_cum3, @@ -981,7 +965,7 @@ def eval_for_PI(self, comp, fun): # mat_f.reshape(self.n1, self.ne2, self.nq2, self.ne3, self.nq3), rhs # ) # elif comp=='22': - # rhs = xp.empty( (self.d1, self.n2, self.d3) ) + # rhs = np.empty( (self.d1, self.n2, self.d3) ) # # ker_glob.kernel_int_3d_eta1_eta3(self.subs1, self.subs3, # self.subs_cum1, self.subs_cum3, @@ -989,7 +973,7 @@ def eval_for_PI(self, comp, fun): # mat_f.reshape(self.ne1, self.nq1, self.n2, self.ne3, self.nq3), rhs # ) # elif comp=='23': - # rhs = xp.empty( (self.d1, self.d2, self.n3) ) + # rhs = np.empty( (self.d1, self.d2, self.n3) ) # # ker_glob.kernel_int_3d_eta1_eta2(self.subs1, self.subs2, # self.subs_cum1, self.subs_cum2, @@ -997,7 +981,7 @@ def eval_for_PI(self, comp, fun): # mat_f.reshape(self.ne1, self.nq1, self.ne2, self.nq2, self.n3), rhs # ) # elif comp=='3': - # rhs = xp.empty( (self.d1, self.d2, self.d3) ) + # rhs = np.empty( (self.d1, self.d2, self.d3) ) # # ker_glob.kernel_int_3d_eta1_eta2_eta3(self.subs1, self.subs2, self.subs3, # self.subs_cum1, self.subs_cum2, self.subs_cum3, @@ -1041,7 +1025,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='11': # assert mat_dofs.shape == (self.d1, self.n2, self.n3) - # rhs = xp.empty( (self.ne1, self.nq1, self.n2, self.n3) ) + # rhs = np.empty( (self.ne1, self.nq1, self.n2, self.n3) ) # # ker_glob.kernel_int_3d_eta1_transpose(self.subs1, self.subs_cum1, self.wts1, # mat_dofs, rhs) @@ -1050,7 +1034,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='12': # assert mat_dofs.shape == (self.n1, self.d2, self.n3) - # rhs = xp.empty( (self.n1, self.ne2, self.nq2, self.n3) ) + # rhs = np.empty( (self.n1, self.ne2, self.nq2, self.n3) ) # # ker_glob.kernel_int_3d_eta2_transpose(self.subs2, self.subs_cum2, self.wts2, # mat_dofs, rhs) @@ -1059,7 +1043,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='13': # assert mat_dofs.shape == (self.n1, self.n2, self.d3) - # rhs = xp.empty( (self.n1, self.n2, self.ne3, self.nq3) ) + # rhs = np.empty( (self.n1, self.n2, self.ne3, self.nq3) ) # # ker_glob.kernel_int_3d_eta3_transpose(self.subs3, self.subs_cum3, self.wts3, # mat_dofs, rhs) @@ -1068,7 +1052,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='21': # assert mat_dofs.shape == (self.n1, self.d2, self.d3) - # rhs = xp.empty( (self.n1, self.ne2, self.nq2, self.ne3, self.nq3) ) + # rhs = np.empty( (self.n1, self.ne2, self.nq2, self.ne3, self.nq3) ) # # ker_glob.kernel_int_3d_eta2_eta3_transpose(self.subs2, self.subs3, # self.subs_cum2, self.subs_cum3, @@ -1078,7 +1062,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='22': # assert mat_dofs.shape == (self.d1, self.n2, self.d3) - # rhs = xp.empty( (self.ne1, self.nq1, self.n2, self.ne3, self.nq3) ) + # rhs = np.empty( (self.ne1, self.nq1, self.n2, self.ne3, self.nq3) ) # # ker_glob.kernel_int_3d_eta1_eta3_transpose(self.subs1, self.subs3, # self.subs_cum1, self.subs_cum3, @@ -1088,7 +1072,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='23': # assert mat_dofs.shape == (self.d1, self.d2, self.n3) - # rhs = xp.empty( (self.ne1, self.nq1, self.ne2, self.nq2, self.n3) ) + # rhs = np.empty( (self.ne1, self.nq1, self.ne2, self.nq2, self.n3) ) # # ker_glob.kernel_int_3d_eta1_eta2_transpose(self.subs1, self.subs2, # self.subs_cum1, self.subs_cum2, @@ -1098,7 +1082,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='3': # assert mat_dofs.shape == (self.d1, self.d2, self.d3) - # rhs = xp.empty( (self.ne1, self.nq1, self.ne2, self.nq2, self.ne3, self.nq3) ) + # rhs = np.empty( (self.ne1, self.nq1, self.ne2, self.nq2, self.ne3, self.nq3) ) # # ker_glob.kernel_int_3d_eta1_eta2_eta3_transpose(self.subs1, self.subs2, self.subs3, # self.subs_cum1, self.subs_cum2, self.subs_cum3, @@ -1135,8 +1119,7 @@ def dofs(self, comp, mat_f): if comp == "0": dofs = kron_matvec_3d( - [spa.identity(mat_f.shape[0]), spa.identity(mat_f.shape[1]), spa.identity(mat_f.shape[2])], - mat_f, + [spa.identity(mat_f.shape[0]), spa.identity(mat_f.shape[1]), spa.identity(mat_f.shape[2])], mat_f ) elif comp == "11": @@ -1189,18 +1172,15 @@ def dofs_T(self, comp, mat_dofs): elif comp == "11": rhs = kron_matvec_3d( - [self.Q1.T, spa.identity(mat_dofs.shape[1]), spa.identity(mat_dofs.shape[2])], - mat_dofs, + [self.Q1.T, spa.identity(mat_dofs.shape[1]), spa.identity(mat_dofs.shape[2])], mat_dofs ) elif comp == "12": rhs = kron_matvec_3d( - [spa.identity(mat_dofs.shape[0]), self.Q2.T, spa.identity(mat_dofs.shape[2])], - mat_dofs, + [spa.identity(mat_dofs.shape[0]), self.Q2.T, spa.identity(mat_dofs.shape[2])], mat_dofs ) elif comp == "13": rhs = kron_matvec_3d( - [spa.identity(mat_dofs.shape[0]), spa.identity(mat_dofs.shape[1]), self.Q3.T], - mat_dofs, + [spa.identity(mat_dofs.shape[0]), spa.identity(mat_dofs.shape[1]), self.Q3.T], mat_dofs ) elif comp == "21": @@ -1615,26 +1595,26 @@ def __init__(self, tensor_space): else: if tensor_space.n_tor == 0: - x_i3 = xp.array([0.0]) - x_q3 = xp.array([0.0]) - x_q3G = xp.array([0.0]) + x_i3 = np.array([0.0]) + x_q3 = np.array([0.0]) + x_q3G = np.array([0.0]) else: if tensor_space.basis_tor == "r": if tensor_space.n_tor > 0: - x_i3 = xp.array([1.0, 0.25 / tensor_space.n_tor]) - x_q3 = xp.array([1.0, 0.25 / tensor_space.n_tor]) - x_q3G = xp.array([1.0, 0.25 / tensor_space.n_tor]) + x_i3 = np.array([1.0, 0.25 / tensor_space.n_tor]) + x_q3 = np.array([1.0, 0.25 / tensor_space.n_tor]) + x_q3G = np.array([1.0, 0.25 / tensor_space.n_tor]) else: - x_i3 = xp.array([1.0, 0.75 / (-tensor_space.n_tor)]) - x_q3 = xp.array([1.0, 0.75 / (-tensor_space.n_tor)]) - x_q3G = xp.array([1.0, 0.75 / (-tensor_space.n_tor)]) + x_i3 = np.array([1.0, 0.75 / (-tensor_space.n_tor)]) + x_q3 = np.array([1.0, 0.75 / (-tensor_space.n_tor)]) + x_q3G = np.array([1.0, 0.75 / (-tensor_space.n_tor)]) else: - x_i3 = xp.array([0.0]) - x_q3 = xp.array([0.0]) - x_q3G = xp.array([0.0]) + x_i3 = np.array([0.0]) + x_q3 = np.array([0.0]) + x_q3G = np.array([0.0]) self.Q3 = spa.identity(tensor_space.NbaseN[2], format="csr") self.Q3G = spa.identity(tensor_space.NbaseN[2], format="csr") @@ -1776,11 +1756,11 @@ def eval_for_PI(self, comp, fun, eval_kind, with_subs=True): pts_PI = self.getpts_for_PI(comp, with_subs) # array of evaluated function - mat_f = xp.empty((pts_PI[0].size, pts_PI[1].size, pts_PI[2].size), dtype=float) + mat_f = np.empty((pts_PI[0].size, pts_PI[1].size, pts_PI[2].size), dtype=float) # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = xp.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing="ij") + pts1, pts2, pts3 = np.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing="ij") mat_f[:, :, :] = fun(pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1803,13 +1783,13 @@ def eval_for_PI(self, comp, fun, eval_kind, with_subs=True): # n2 = self.pts_PI_0[1].size # # # apply (I0_22) to each column - # self.S0 = xp.zeros(((n1 - 2)*n2, 3), dtype=float) + # self.S0 = np.zeros(((n1 - 2)*n2, 3), dtype=float) # # for i in range(3): # self.S0[:, i] = kron_lusolve_2d(self.I0_22_LUs, self.I0_21[:, i].toarray().reshape(n1 - 2, n2)).flatten() # # # 3 x 3 matrix - # self.S0 = xp.linalg.inv(self.I0_11.toarray() - self.I0_12.toarray().dot(self.S0)) + # self.S0 = np.linalg.inv(self.I0_11.toarray() - self.I0_12.toarray().dot(self.S0)) # # # ====================================== @@ -1834,7 +1814,7 @@ def eval_for_PI(self, comp, fun, eval_kind, with_subs=True): # # solve for tensor-product coefficients # out2 = out2 - kron_lusolve_2d(self.I0_22_LUs, self.I0_21.dot(self.S0.dot(rhs1)).reshape(n1 - 2, n2)) + kron_lusolve_2d(self.I0_22_LUs, self.I0_21.dot(self.S0.dot(self.I0_12.dot(out2.flatten()))).reshape(n1 - 2, n2)) # - # return xp.concatenate((out1, out2.flatten())) + # return np.concatenate((out1, out2.flatten())) # ====================================== @@ -1856,12 +1836,10 @@ def solve_V1(self, dofs_1, include_bc): # with boundary splines if include_bc: dofs_11 = dofs_1[: self.P1_pol.shape[0] * self.I_tor.shape[0]].reshape( - self.P1_pol.shape[0], - self.I_tor.shape[0], + self.P1_pol.shape[0], self.I_tor.shape[0] ) dofs_12 = dofs_1[self.P1_pol.shape[0] * self.I_tor.shape[0] :].reshape( - self.P0_pol.shape[0], - self.H_tor.shape[0], + self.P0_pol.shape[0], self.H_tor.shape[0] ) coeffs1 = self.I_tor_LU.solve(self.I1_pol_LU.solve(dofs_11).T).T @@ -1870,30 +1848,26 @@ def solve_V1(self, dofs_1, include_bc): # without boundary splines else: dofs_11 = dofs_1[: self.P1_pol_0.shape[0] * self.I0_tor.shape[0]].reshape( - self.P1_pol_0.shape[0], - self.I0_tor.shape[0], + self.P1_pol_0.shape[0], self.I0_tor.shape[0] ) dofs_12 = dofs_1[self.P1_pol_0.shape[0] * self.I0_tor.shape[0] :].reshape( - self.P0_pol_0.shape[0], - self.H0_tor.shape[0], + self.P0_pol_0.shape[0], self.H0_tor.shape[0] ) coeffs1 = self.I0_tor_LU.solve(self.I1_pol_0_LU.solve(dofs_11).T).T coeffs2 = self.H0_tor_LU.solve(self.I0_pol_0_LU.solve(dofs_12).T).T - return xp.concatenate((coeffs1.flatten(), coeffs2.flatten())) + return np.concatenate((coeffs1.flatten(), coeffs2.flatten())) # ====================================== def solve_V2(self, dofs_2, include_bc): # with boundary splines if include_bc: dofs_21 = dofs_2[: self.P2_pol.shape[0] * self.H_tor.shape[0]].reshape( - self.P2_pol.shape[0], - self.H_tor.shape[0], + self.P2_pol.shape[0], self.H_tor.shape[0] ) dofs_22 = dofs_2[self.P2_pol.shape[0] * self.H_tor.shape[0] :].reshape( - self.P3_pol.shape[0], - self.I_tor.shape[0], + self.P3_pol.shape[0], self.I_tor.shape[0] ) coeffs1 = self.H_tor_LU.solve(self.I2_pol_LU.solve(dofs_21).T).T @@ -1902,18 +1876,16 @@ def solve_V2(self, dofs_2, include_bc): # without boundary splines else: dofs_21 = dofs_2[: self.P2_pol_0.shape[0] * self.H0_tor.shape[0]].reshape( - self.P2_pol_0.shape[0], - self.H0_tor.shape[0], + self.P2_pol_0.shape[0], self.H0_tor.shape[0] ) dofs_22 = dofs_2[self.P2_pol_0.shape[0] * self.H0_tor.shape[0] :].reshape( - self.P3_pol_0.shape[0], - self.I0_tor.shape[0], + self.P3_pol_0.shape[0], self.I0_tor.shape[0] ) coeffs1 = self.H0_tor_LU.solve(self.I2_pol_0_LU.solve(dofs_21).T).T coeffs2 = self.I0_tor_LU.solve(self.I3_pol_0_LU.solve(dofs_22).T).T - return xp.concatenate((coeffs1.flatten(), coeffs2.flatten())) + return np.concatenate((coeffs1.flatten(), coeffs2.flatten())) # ====================================== def solve_V3(self, dofs_3, include_bc): @@ -1966,18 +1938,16 @@ def apply_IinvT_V1(self, rhs, include_bc=False): # without boundary splines else: rhs1 = rhs[: self.P1_pol_0.shape[0] * self.I0_tor.shape[0]].reshape( - self.P1_pol_0.shape[0], - self.I0_tor.shape[0], + self.P1_pol_0.shape[0], self.I0_tor.shape[0] ) rhs2 = rhs[self.P1_pol_0.shape[0] * self.I0_tor.shape[0] :].reshape( - self.P0_pol_0.shape[0], - self.H0_tor.shape[0], + self.P0_pol_0.shape[0], self.H0_tor.shape[0] ) rhs1 = self.I1_pol_0_T_LU.solve(self.I0_tor_T_LU.solve(rhs1.T).T) rhs2 = self.I0_pol_0_T_LU.solve(self.H0_tor_T_LU.solve(rhs2.T).T) - return xp.concatenate((rhs1.flatten(), rhs2.flatten())) + return np.concatenate((rhs1.flatten(), rhs2.flatten())) # ====================================== def apply_IinvT_V2(self, rhs, include_bc=False): @@ -1998,18 +1968,16 @@ def apply_IinvT_V2(self, rhs, include_bc=False): # without boundary splines else: rhs1 = rhs[: self.P2_pol_0.shape[0] * self.H0_tor.shape[0]].reshape( - self.P2_pol_0.shape[0], - self.H0_tor.shape[0], + self.P2_pol_0.shape[0], self.H0_tor.shape[0] ) rhs2 = rhs[self.P2_pol_0.shape[0] * self.H0_tor.shape[0] :].reshape( - self.P3_pol_0.shape[0], - self.I0_tor.shape[0], + self.P3_pol_0.shape[0], self.I0_tor.shape[0] ) rhs1 = self.I2_pol_0_T_LU.solve(self.H0_tor_T_LU.solve(rhs1.T).T) rhs2 = self.I3_pol_0_T_LU.solve(self.I0_tor_T_LU.solve(rhs2.T).T) - return xp.concatenate((rhs1.flatten(), rhs2.flatten())) + return np.concatenate((rhs1.flatten(), rhs2.flatten())) # ====================================== def apply_IinvT_V3(self, rhs, include_bc=False): @@ -2036,8 +2004,7 @@ def dofs_0(self, fun, include_bc=True, eval_kind="meshgrid"): # get dofs on tensor-product grid dofs = kron_matvec_3d( - [spa.identity(dofs.shape[0]), spa.identity(dofs.shape[1]), spa.identity(dofs.shape[2])], - dofs, + [spa.identity(dofs.shape[0]), spa.identity(dofs.shape[1]), spa.identity(dofs.shape[2])], dofs ) # apply extraction operator for dofs @@ -2075,9 +2042,9 @@ def dofs_1(self, fun, include_bc=True, eval_kind="meshgrid", with_subs=True): # apply extraction operator for dofs if include_bc: - dofs = self.P1.dot(xp.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) + dofs = self.P1.dot(np.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) else: - dofs = self.P1_0.dot(xp.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) + dofs = self.P1_0.dot(np.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) return dofs @@ -2108,9 +2075,9 @@ def dofs_2(self, fun, include_bc=True, eval_kind="meshgrid", with_subs=True): # apply extraction operator for dofs if include_bc: - dofs = self.P2.dot(xp.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) + dofs = self.P2.dot(np.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) else: - dofs = self.P2_0.dot(xp.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) + dofs = self.P2_0.dot(np.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) return dofs @@ -2153,20 +2120,20 @@ def pi_3(self, fun, include_bc=True, eval_kind="meshgrid", with_subs=True): # ======================================== def assemble_approx_inv(self, tol): - if not self.approx_Ik_0_inv or (self.approx_Ik_0_inv and self.approx_Ik_0_tol != tol): + if self.approx_Ik_0_inv == False or (self.approx_Ik_0_inv == True and self.approx_Ik_0_tol != tol): # poloidal plane - I0_pol_0_inv_approx = xp.linalg.inv(self.I0_pol_0.toarray()) - I1_pol_0_inv_approx = xp.linalg.inv(self.I1_pol_0.toarray()) - I2_pol_0_inv_approx = xp.linalg.inv(self.I2_pol_0.toarray()) - I3_pol_0_inv_approx = xp.linalg.inv(self.I3_pol_0.toarray()) - I0_pol_inv_approx = xp.linalg.inv(self.I0_pol.toarray()) + I0_pol_0_inv_approx = np.linalg.inv(self.I0_pol_0.toarray()) + I1_pol_0_inv_approx = np.linalg.inv(self.I1_pol_0.toarray()) + I2_pol_0_inv_approx = np.linalg.inv(self.I2_pol_0.toarray()) + I3_pol_0_inv_approx = np.linalg.inv(self.I3_pol_0.toarray()) + I0_pol_inv_approx = np.linalg.inv(self.I0_pol.toarray()) if tol > 1e-14: - I0_pol_0_inv_approx[xp.abs(I0_pol_0_inv_approx) < tol] = 0.0 - I1_pol_0_inv_approx[xp.abs(I1_pol_0_inv_approx) < tol] = 0.0 - I2_pol_0_inv_approx[xp.abs(I2_pol_0_inv_approx) < tol] = 0.0 - I3_pol_0_inv_approx[xp.abs(I3_pol_0_inv_approx) < tol] = 0.0 - I0_pol_inv_approx[xp.abs(I0_pol_inv_approx) < tol] = 0.0 + I0_pol_0_inv_approx[np.abs(I0_pol_0_inv_approx) < tol] = 0.0 + I1_pol_0_inv_approx[np.abs(I1_pol_0_inv_approx) < tol] = 0.0 + I2_pol_0_inv_approx[np.abs(I2_pol_0_inv_approx) < tol] = 0.0 + I3_pol_0_inv_approx[np.abs(I3_pol_0_inv_approx) < tol] = 0.0 + I0_pol_inv_approx[np.abs(I0_pol_inv_approx) < tol] = 0.0 I0_pol_0_inv_approx = spa.csr_matrix(I0_pol_0_inv_approx) I1_pol_0_inv_approx = spa.csr_matrix(I1_pol_0_inv_approx) @@ -2175,12 +2142,12 @@ def assemble_approx_inv(self, tol): I0_pol_inv_approx = spa.csr_matrix(I0_pol_inv_approx) # toroidal direction - I_inv_tor_approx = xp.linalg.inv(self.I_tor.toarray()) - H_inv_tor_approx = xp.linalg.inv(self.H_tor.toarray()) + I_inv_tor_approx = np.linalg.inv(self.I_tor.toarray()) + H_inv_tor_approx = np.linalg.inv(self.H_tor.toarray()) if tol > 1e-14: - I_inv_tor_approx[xp.abs(I_inv_tor_approx) < tol] = 0.0 - H_inv_tor_approx[xp.abs(H_inv_tor_approx) < tol] = 0.0 + I_inv_tor_approx[np.abs(I_inv_tor_approx) < tol] = 0.0 + H_inv_tor_approx[np.abs(H_inv_tor_approx) < tol] = 0.0 I_inv_tor_approx = spa.csr_matrix(I_inv_tor_approx) H_inv_tor_approx = spa.csr_matrix(H_inv_tor_approx) diff --git a/src/struphy/eigenvalue_solvers/spline_space.py b/src/struphy/eigenvalue_solvers/spline_space.py index c10124e57..a4239af46 100644 --- a/src/struphy/eigenvalue_solvers/spline_space.py +++ b/src/struphy/eigenvalue_solvers/spline_space.py @@ -6,8 +6,8 @@ Basic modules to create tensor-product finite element spaces of univariate B-splines. """ -import cunumpy as xp import matplotlib +import numpy as np import scipy.sparse as spa matplotlib.rcParams.update({"font.size": 16}) @@ -49,19 +49,19 @@ class Spline_space_1d: Attributes ---------- - el_b : xp.array + el_b : np.array Element boundaries, equally spaced. delta : float Uniform grid spacing - T : xp.array + T : np.array Knot vector of 0-space. - t : xp.arrray + t : np.arrray Knot vector of 1-space. - greville : xp.array + greville : np.array Greville points. NbaseN : int @@ -70,22 +70,22 @@ class Spline_space_1d: NbaseD : int Dimension of 1-space. - indN : xp.array + indN : np.array Global indices of non-vanishing B-splines in each element in format (element, local basis function) - indD : xp.array + indD : np.array Global indices of non-vanishing M-splines in each element in format (element, local basis function) - pts : xp.array + pts : np.array Global GL quadrature points in format (element, local point). - wts : xp.array + wts : np.array Global GL quadrature weights in format (element, local point). - basisN : xp.array + basisN : np.array N-basis functions evaluated at quadrature points in format (element, local basis function, derivative, local point) - basisD : xp.array + basisD : np.array D-basis functions evaluated at quadrature points in format (element, local basis function, derivative, local point) E0 : csr_matrix @@ -139,7 +139,7 @@ def __init__(self, Nel, p, spl_kind, n_quad=6, bc=["f", "f"]): else: self.bc = bc - self.el_b = xp.linspace(0.0, 1.0, Nel + 1) # element boundaries + self.el_b = np.linspace(0.0, 1.0, Nel + 1) # element boundaries self.delta = 1 / self.Nel # element length self.T = bsp.make_knots(self.el_b, self.p, self.spl_kind) # spline knot vector for B-splines (N) @@ -151,13 +151,13 @@ def __init__(self, Nel, p, spl_kind, n_quad=6, bc=["f", "f"]): self.NbaseD = self.NbaseN - 1 + self.spl_kind # total number of M-splines (D) # global indices of non-vanishing splines in each element in format (Nel, p + 1) - self.indN = (xp.indices((self.Nel, self.p + 1 - 0))[1] + xp.arange(self.Nel)[:, None]) % self.NbaseN - self.indD = (xp.indices((self.Nel, self.p + 1 - 1))[1] + xp.arange(self.Nel)[:, None]) % self.NbaseD + self.indN = (np.indices((self.Nel, self.p + 1 - 0))[1] + np.arange(self.Nel)[:, None]) % self.NbaseN + self.indD = (np.indices((self.Nel, self.p + 1 - 1))[1] + np.arange(self.Nel)[:, None]) % self.NbaseD self.n_quad = n_quad # number of Gauss-Legendre points per grid cell (defined by break points) - self.pts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[0] # Gauss-Legendre points (GLQP) in (-1, 1) - self.wts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[1] # Gauss-Legendre weights (GLQW) in (-1, 1) + self.pts_loc = np.polynomial.legendre.leggauss(self.n_quad)[0] # Gauss-Legendre points (GLQP) in (-1, 1) + self.wts_loc = np.polynomial.legendre.leggauss(self.n_quad)[1] # Gauss-Legendre weights (GLQW) in (-1, 1) # global GLQP in format (element, local point) and total number of GLQP self.pts = bsp.quadrature_grid(self.el_b, self.pts_loc, self.wts_loc)[0] @@ -177,8 +177,8 @@ def __init__(self, Nel, p, spl_kind, n_quad=6, bc=["f", "f"]): d1 = self.NbaseD # boundary operators - self.B0 = xp.identity(n1, dtype=float) - self.B1 = xp.identity(d1, dtype=float) + self.B0 = np.identity(n1, dtype=float) + self.B1 = np.identity(d1, dtype=float) # extraction operators without boundary conditions self.E0 = spa.csr_matrix(self.B0.copy()) @@ -267,16 +267,16 @@ def evaluate_N(self, eta, coeff, kind=0): coeff = self.E0_0.T.dot(coeff) if isinstance(eta, float): - pts = xp.array([eta]) - elif isinstance(eta, xp.ndarray): + pts = np.array([eta]) + elif isinstance(eta, np.ndarray): pts = eta.flatten() - values = xp.empty(pts.size, dtype=float) + values = np.empty(pts.size, dtype=float) eva_1d.evaluate_vector(self.T, self.p, self.indN, coeff, pts, values, kind) if isinstance(eta, float): values = values[0] - elif isinstance(eta, xp.ndarray): + elif isinstance(eta, np.ndarray): values = values.reshape(eta.shape) return values @@ -303,16 +303,16 @@ def evaluate_D(self, eta, coeff): assert coeff.size == self.E1.shape[0] if isinstance(eta, float): - pts = xp.array([eta]) - elif isinstance(eta, xp.ndarray): + pts = np.array([eta]) + elif isinstance(eta, np.ndarray): pts = eta.flatten() - values = xp.empty(pts.size, dtype=float) + values = np.empty(pts.size, dtype=float) eva_1d.evaluate_vector(self.t, self.p - 1, self.indD, coeff, pts, values, 1) if isinstance(eta, float): values = values[0] - elif isinstance(eta, xp.ndarray): + elif isinstance(eta, np.ndarray): values = values.reshape(eta.shape) return values @@ -331,12 +331,12 @@ def plot_splines(self, n_pts=500, which="N"): which basis to plot. 'N', 'D' or 'dN' (optional, default='N') """ - etaplot = xp.linspace(0.0, 1.0, n_pts) + etaplot = np.linspace(0.0, 1.0, n_pts) degree = self.p if which == "N": - coeff = xp.zeros(self.NbaseN, dtype=float) + coeff = np.zeros(self.NbaseN, dtype=float) for i in range(self.NbaseN): coeff[:] = 0.0 @@ -344,7 +344,7 @@ def plot_splines(self, n_pts=500, which="N"): plt.plot(etaplot, self.evaluate_N(etaplot, coeff), label=str(i)) elif which == "D": - coeff = xp.zeros(self.NbaseD, dtype=float) + coeff = np.zeros(self.NbaseD, dtype=float) for i in range(self.NbaseD): coeff[:] = 0.0 @@ -354,7 +354,7 @@ def plot_splines(self, n_pts=500, which="N"): degree = self.p - 1 elif which == "dN": - coeff = xp.zeros(self.NbaseN, dtype=float) + coeff = np.zeros(self.NbaseN, dtype=float) for i in range(self.NbaseN): coeff[:] = 0.0 @@ -369,8 +369,8 @@ def plot_splines(self, n_pts=500, which="N"): else: bcs = "clamped" - (greville,) = plt.plot(self.greville, xp.zeros(self.greville.shape), "ro", label="greville") - (breaks,) = plt.plot(self.el_b, xp.zeros(self.el_b.shape), "k+", label="breaks") + (greville,) = plt.plot(self.greville, np.zeros(self.greville.shape), "ro", label="greville") + (breaks,) = plt.plot(self.el_b, np.zeros(self.el_b.shape), "k+", label="breaks") plt.title(which + f"$^{degree}$-splines, " + bcs + f", Nel={self.Nel}") plt.legend(handles=[greville, breaks]) @@ -554,8 +554,8 @@ def __init__(self, spline_spaces, ck=-1, cx=None, cy=None, n_tor=0, basis_tor="r self.M1_tor = spa.identity(1, format="csr") else: - self.M0_tor = spa.csr_matrix(xp.identity(2) / 2) - self.M1_tor = spa.csr_matrix(xp.identity(2) / 2) + self.M0_tor = spa.csr_matrix(np.identity(2) / 2) + self.M1_tor = spa.csr_matrix(np.identity(2) / 2) else: self.M0_tor = mass_1d.get_M(self.spaces[2], 0, 0) @@ -712,33 +712,27 @@ def __init__(self, spline_spaces, ck=-1, cx=None, cy=None, n_tor=0, basis_tor="r # extraction operators for 3D diagram: without boundary conditions self.E0 = spa.kron(self.E0_pol, self.E0_tor, format="csr") self.E1 = spa.bmat( - [[spa.kron(self.E1_pol, self.E0_tor), None], [None, spa.kron(self.E0_pol, self.E1_tor)]], - format="csr", + [[spa.kron(self.E1_pol, self.E0_tor), None], [None, spa.kron(self.E0_pol, self.E1_tor)]], format="csr" ) self.E2 = spa.bmat( - [[spa.kron(self.E2_pol, self.E1_tor), None], [None, spa.kron(self.E3_pol, self.E0_tor)]], - format="csr", + [[spa.kron(self.E2_pol, self.E1_tor), None], [None, spa.kron(self.E3_pol, self.E0_tor)]], format="csr" ) self.E3 = spa.kron(self.E3_pol, self.E1_tor, format="csr") self.Ev = spa.bmat( - [[spa.kron(self.Ev_pol, self.E0_tor), None], [None, spa.kron(self.E0_pol, self.E0_tor)]], - format="csr", + [[spa.kron(self.Ev_pol, self.E0_tor), None], [None, spa.kron(self.E0_pol, self.E0_tor)]], format="csr" ) # boundary operators for 3D diagram self.B0 = spa.kron(self.B0_pol, self.B0_tor, format="csr") self.B1 = spa.bmat( - [[spa.kron(self.B1_pol, self.B0_tor), None], [None, spa.kron(self.B0_pol, self.B1_tor)]], - format="csr", + [[spa.kron(self.B1_pol, self.B0_tor), None], [None, spa.kron(self.B0_pol, self.B1_tor)]], format="csr" ) self.B2 = spa.bmat( - [[spa.kron(self.B2_pol, self.B1_tor), None], [None, spa.kron(self.B3_pol, self.B0_tor)]], - format="csr", + [[spa.kron(self.B2_pol, self.B1_tor), None], [None, spa.kron(self.B3_pol, self.B0_tor)]], format="csr" ) self.B3 = spa.kron(self.B3_pol, self.B1_tor, format="csr") self.Bv = spa.bmat( - [[spa.kron(self.Bv_pol, self.E0_tor), None], [None, spa.kron(Bv3, self.B0_tor)]], - format="csr", + [[spa.kron(self.Bv_pol, self.E0_tor), None], [None, spa.kron(Bv3, self.B0_tor)]], format="csr" ) # extraction operators for 3D diagram: with boundary conditions @@ -791,7 +785,7 @@ def apply_M1_ten(self, x, mats): out1 = mats[0][1].dot(mats[0][0].dot(x1).T).T out2 = mats[1][1].dot(mats[1][0].dot(x2).T).T - return xp.concatenate((out1.flatten(), out2.flatten())) + return np.concatenate((out1.flatten(), out2.flatten())) def apply_M2_ten(self, x, mats): """ @@ -803,7 +797,7 @@ def apply_M2_ten(self, x, mats): out1 = mats[0][1].dot(mats[0][0].dot(x1).T).T out2 = mats[1][1].dot(mats[1][0].dot(x2).T).T - return xp.concatenate((out1.flatten(), out2.flatten())) + return np.concatenate((out1.flatten(), out2.flatten())) def apply_M3_ten(self, x, mats): """ @@ -826,7 +820,7 @@ def apply_Mv_ten(self, x, mats): out1 = mats[0][1].dot(mats[0][0].dot(x1).T).T out2 = mats[1][1].dot(mats[1][0].dot(x2).T).T - return xp.concatenate((out1.flatten(), out2.flatten())) + return np.concatenate((out1.flatten(), out2.flatten())) def apply_M0_0_ten(self, x, mats): """ @@ -847,13 +841,13 @@ def apply_M1_0_ten(self, x, mats): x1, x2 = self.reshape_pol_1(x) out1 = self.B0_tor.dot( - mats[0][1].dot(self.B0_tor.T.dot(self.B1_pol.dot(mats[0][0].dot(self.B1_pol.T.dot(x1))).T)), + mats[0][1].dot(self.B0_tor.T.dot(self.B1_pol.dot(mats[0][0].dot(self.B1_pol.T.dot(x1))).T)) ).T out2 = self.B1_tor.dot( - mats[1][1].dot(self.B1_tor.T.dot(self.B0_pol.dot(mats[1][0].dot(self.B0_pol.T.dot(x2))).T)), + mats[1][1].dot(self.B1_tor.T.dot(self.B0_pol.dot(mats[1][0].dot(self.B0_pol.T.dot(x2))).T)) ).T - return xp.concatenate((out1.flatten(), out2.flatten())) + return np.concatenate((out1.flatten(), out2.flatten())) def apply_M2_0_ten(self, x, mats): """ @@ -863,13 +857,13 @@ def apply_M2_0_ten(self, x, mats): x1, x2 = self.reshape_pol_2(x) out1 = self.B1_tor.dot( - mats[0][1].dot(self.B1_tor.T.dot(self.B2_pol.dot(mats[0][0].dot(self.B2_pol.T.dot(x1))).T)), + mats[0][1].dot(self.B1_tor.T.dot(self.B2_pol.dot(mats[0][0].dot(self.B2_pol.T.dot(x1))).T)) ).T out2 = self.B0_tor.dot( - mats[1][1].dot(self.B0_tor.T.dot(self.B3_pol.dot(mats[1][0].dot(self.B3_pol.T.dot(x2))).T)), + mats[1][1].dot(self.B0_tor.T.dot(self.B3_pol.dot(mats[1][0].dot(self.B3_pol.T.dot(x2))).T)) ).T - return xp.concatenate((out1.flatten(), out2.flatten())) + return np.concatenate((out1.flatten(), out2.flatten())) def apply_M3_0_ten(self, x, mats): """ @@ -892,7 +886,7 @@ def apply_Mv_0_ten(self, x, mats): out1 = mats[0][1].dot(self.Bv_pol.dot(mats[0][0].dot(self.Bv_pol.T.dot(x1))).T).T out2 = self.B0_tor.dot(mats[1][1].dot(self.B0_tor.T.dot(mats[1][0].dot(x2).T))).T - return xp.concatenate((out1.flatten(), out2.flatten())) + return np.concatenate((out1.flatten(), out2.flatten())) def __assemble_M0(self, domain, as_tensor=False): """ @@ -934,12 +928,10 @@ def __assemble_M1(self, domain, as_tensor=False): self.M1_pol_mat = mass_2d.get_M1(self, domain) matvec = lambda x: self.apply_M1_ten( - x, - [[self.M1_pol_mat[0], self.M0_tor], [self.M1_pol_mat[1], self.M1_tor]], + x, [[self.M1_pol_mat[0], self.M0_tor], [self.M1_pol_mat[1], self.M1_tor]] ) matvec_0 = lambda x: self.apply_M1_0_ten( - x, - [[self.M1_pol_mat[0], self.M0_tor], [self.M1_pol_mat[1], self.M1_tor]], + x, [[self.M1_pol_mat[0], self.M0_tor], [self.M1_pol_mat[1], self.M1_tor]] ) # 3D @@ -947,8 +939,7 @@ def __assemble_M1(self, domain, as_tensor=False): if self.dim == 2: M11, M22 = mass_2d.get_M1(self, domain) self.M1_mat = spa.bmat( - [[spa.kron(M11, self.M0_tor), None], [None, spa.kron(M22, self.M1_tor)]], - format="csr", + [[spa.kron(M11, self.M0_tor), None], [None, spa.kron(M22, self.M1_tor)]], format="csr" ) else: self.M1_mat = mass_3d.get_M1(self, domain) @@ -972,12 +963,10 @@ def __assemble_M2(self, domain, as_tensor=False): self.M2_pol_mat = mass_2d.get_M2(self, domain) matvec = lambda x: self.apply_M2_ten( - x, - [[self.M2_pol_mat[0], self.M1_tor], [self.M2_pol_mat[1], self.M0_tor]], + x, [[self.M2_pol_mat[0], self.M1_tor], [self.M2_pol_mat[1], self.M0_tor]] ) matvec_0 = lambda x: self.apply_M2_0_ten( - x, - [[self.M2_pol_mat[0], self.M1_tor], [self.M2_pol_mat[1], self.M0_tor]], + x, [[self.M2_pol_mat[0], self.M1_tor], [self.M2_pol_mat[1], self.M0_tor]] ) # 3D @@ -985,8 +974,7 @@ def __assemble_M2(self, domain, as_tensor=False): if self.dim == 2: M11, M22 = mass_2d.get_M2(self, domain) self.M2_mat = spa.bmat( - [[spa.kron(M11, self.M1_tor), None], [None, spa.kron(M22, self.M0_tor)]], - format="csr", + [[spa.kron(M11, self.M1_tor), None], [None, spa.kron(M22, self.M0_tor)]], format="csr" ) else: self.M2_mat = mass_3d.get_M2(self, domain) @@ -1038,12 +1026,10 @@ def __assemble_Mv(self, domain, as_tensor=False): self.Mv_pol_mat = mass_2d.get_Mv(self, domain) matvec = lambda x: self.apply_Mv_ten( - x, - [[self.Mv_pol_mat[0], self.M0_tor], [self.Mv_pol_mat[1], self.M0_tor]], + x, [[self.Mv_pol_mat[0], self.M0_tor], [self.Mv_pol_mat[1], self.M0_tor]] ) matvec_0 = lambda x: self.apply_Mv_0_ten( - x, - [[self.Mv_pol_mat[0], self.M0_tor], [self.Mv_pol_mat[1], self.M0_tor]], + x, [[self.Mv_pol_mat[0], self.M0_tor], [self.Mv_pol_mat[1], self.M0_tor]] ) # 3D @@ -1051,8 +1037,7 @@ def __assemble_Mv(self, domain, as_tensor=False): if self.dim == 2: M11, M22 = mass_2d.get_Mv(self, domain) self.Mv_mat = spa.bmat( - [[spa.kron(M11, self.M0_tor), None], [None, spa.kron(M22, self.M0_tor)]], - format="csr", + [[spa.kron(M11, self.M0_tor), None], [None, spa.kron(M22, self.M0_tor)]], format="csr" ) else: self.Mv_mat = mass_3d.get_Mv(self, domain) @@ -1108,21 +1093,17 @@ def reshape_pol_1(self, coeff): if c_size == self.E1.shape[0]: coeff1_pol_1 = coeff[: self.E1_pol.shape[0] * self.E0_tor.shape[0]].reshape( - self.E1_pol.shape[0], - self.E0_tor.shape[0], + self.E1_pol.shape[0], self.E0_tor.shape[0] ) coeff1_pol_3 = coeff[self.E1_pol.shape[0] * self.E0_tor.shape[0] :].reshape( - self.E0_pol.shape[0], - self.E1_tor.shape[0], + self.E0_pol.shape[0], self.E1_tor.shape[0] ) else: coeff1_pol_1 = coeff[: self.E1_pol_0.shape[0] * self.E0_tor_0.shape[0]].reshape( - self.E1_pol_0.shape[0], - self.E0_tor_0.shape[0], + self.E1_pol_0.shape[0], self.E0_tor_0.shape[0] ) coeff1_pol_3 = coeff[self.E1_pol_0.shape[0] * self.E0_tor_0.shape[0] :].reshape( - self.E0_pol_0.shape[0], - self.E1_tor_0.shape[0], + self.E0_pol_0.shape[0], self.E1_tor_0.shape[0] ) return coeff1_pol_1, coeff1_pol_3 @@ -1138,21 +1119,17 @@ def reshape_pol_2(self, coeff): if c_size == self.E2.shape[0]: coeff2_pol_1 = coeff[: self.E2_pol.shape[0] * self.E1_tor.shape[0]].reshape( - self.E2_pol.shape[0], - self.E1_tor.shape[0], + self.E2_pol.shape[0], self.E1_tor.shape[0] ) coeff2_pol_3 = coeff[self.E2_pol.shape[0] * self.E1_tor.shape[0] :].reshape( - self.E3_pol.shape[0], - self.E0_tor.shape[0], + self.E3_pol.shape[0], self.E0_tor.shape[0] ) else: coeff2_pol_1 = coeff[: self.E2_pol_0.shape[0] * self.E1_tor_0.shape[0]].reshape( - self.E2_pol_0.shape[0], - self.E1_tor_0.shape[0], + self.E2_pol_0.shape[0], self.E1_tor_0.shape[0] ) coeff2_pol_3 = coeff[self.E2_pol_0.shape[0] * self.E1_tor_0.shape[0] :].reshape( - self.E3_pol_0.shape[0], - self.E0_tor_0.shape[0], + self.E3_pol_0.shape[0], self.E0_tor_0.shape[0] ) return coeff2_pol_1, coeff2_pol_3 @@ -1184,22 +1161,18 @@ def reshape_pol_v(self, coeff): if c_size == self.Ev.shape[0]: coeffv_pol_1 = coeff[: self.Ev_pol.shape[0] * self.E0_tor.shape[0]].reshape( - self.Ev_pol.shape[0], - self.E0_tor.shape[0], + self.Ev_pol.shape[0], self.E0_tor.shape[0] ) coeffv_pol_3 = coeff[self.Ev_pol.shape[0] * self.E0_tor.shape[0] :].reshape( - self.E0_pol.shape[0], - self.E0_tor.shape[0], + self.E0_pol.shape[0], self.E0_tor.shape[0] ) else: coeffv_pol_1 = coeff[: self.Ev_pol_0.shape[0] * self.E0_tor.shape[0]].reshape( - self.Ev_pol_0.shape[0], - self.E0_tor.shape[0], + self.Ev_pol_0.shape[0], self.E0_tor.shape[0] ) coeffv_pol_3 = coeff[self.Ev_pol_0.shape[0] * self.E0_tor.shape[0] :].reshape( - self.E0_pol.shape[0], - self.E0_tor_0.shape[0], + self.E0_pol.shape[0], self.E0_tor_0.shape[0] ) return coeffv_pol_1, coeffv_pol_3 @@ -1255,7 +1228,7 @@ def extract_1(self, coeff): else: coeff1 = self.E1_0.T.dot(coeff) - coeff1_1, coeff1_2, coeff1_3 = xp.split(coeff1, [self.Ntot_1form_cum[0], self.Ntot_1form_cum[1]]) + coeff1_1, coeff1_2, coeff1_3 = np.split(coeff1, [self.Ntot_1form_cum[0], self.Ntot_1form_cum[1]]) coeff1_1 = coeff1_1.reshape(self.Nbase_1form[0]) coeff1_2 = coeff1_2.reshape(self.Nbase_1form[1]) @@ -1286,7 +1259,7 @@ def extract_2(self, coeff): else: coeff2 = self.E2_0.T.dot(coeff) - coeff2_1, coeff2_2, coeff2_3 = xp.split(coeff2, [self.Ntot_2form_cum[0], self.Ntot_2form_cum[1]]) + coeff2_1, coeff2_2, coeff2_3 = np.split(coeff2, [self.Ntot_2form_cum[0], self.Ntot_2form_cum[1]]) coeff2_1 = coeff2_1.reshape(self.Nbase_2form[0]) coeff2_2 = coeff2_2.reshape(self.Nbase_2form[1]) @@ -1331,7 +1304,7 @@ def extract_v(self, coeff): else: coeffv = self.Ev_0.T.dot(coeff) - coeffv_1, coeffv_2, coeffv_3 = xp.split(coeffv, [self.Ntot_0form, 2 * self.Ntot_0form]) + coeffv_1, coeffv_2, coeffv_3 = np.split(coeffv, [self.Ntot_0form, 2 * self.Ntot_0form]) coeffv_1 = coeffv_1.reshape(self.Nbase_0form) coeffv_2 = coeffv_2.reshape(self.Nbase_0form) @@ -1384,15 +1357,15 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): assert coeff.shape[:2] == (self.NbaseN[0], self.NbaseN[1]) # get real and imaginary part - coeff_r = xp.real(coeff) - coeff_i = xp.imag(coeff) + coeff_r = np.real(coeff) + coeff_i = np.imag(coeff) # ------ evaluate FEM field at given points -------- - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values_r_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.T[0], @@ -1422,8 +1395,8 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.T[0], @@ -1454,8 +1427,8 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): # matrix evaluation else: - values_r_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.T[0], @@ -1485,8 +1458,8 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.T[0], @@ -1517,15 +1490,15 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): # multiply with Fourier basis in third direction if self.n_tor == 0: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.ones(eta3.shape, dtype=float) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.ones(eta3.shape, dtype=float) else: if self.basis_tor == "r": - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.cos(2 * xp.pi * self.n_tor * eta3) - out += (values_r_2 + 1j * values_i_2)[:, :, None] * xp.sin(2 * xp.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.cos(2 * np.pi * self.n_tor * eta3) + out += (values_r_2 + 1j * values_i_2)[:, :, None] * np.sin(2 * np.pi * self.n_tor * eta3) else: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.exp(1j * 2 * np.pi * self.n_tor * eta3) # --------- evaluate FEM field at given point ------- else: @@ -1554,26 +1527,10 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): if self.n_tor != 0 and self.basis_tor == "r": real_2 = eva_2d.evaluate_n_n( - self.T[0], - self.T[1], - self.p[0], - self.p[1], - self.indN[0], - self.indN[1], - coeff_r[:, :, 1], - eta1, - eta2, + self.T[0], self.T[1], self.p[0], self.p[1], self.indN[0], self.indN[1], coeff_r[:, :, 1], eta1, eta2 ) imag_2 = eva_2d.evaluate_n_n( - self.T[0], - self.T[1], - self.p[0], - self.p[1], - self.indN[0], - self.indN[1], - coeff_i[:, :, 1], - eta1, - eta2, + self.T[0], self.T[1], self.p[0], self.p[1], self.indN[0], self.indN[1], coeff_i[:, :, 1], eta1, eta2 ) # multiply with Fourier basis in third direction if |n_tor| > 0 @@ -1582,17 +1539,17 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): else: if self.basis_tor == "r": - out = (real_1 + 1j * imag_1) * xp.cos(2 * xp.pi * self.n_tor * eta3) - out += (real_2 + 1j * imag_2) * xp.sin(2 * xp.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * np.cos(2 * np.pi * self.n_tor * eta3) + out += (real_2 + 1j * imag_2) * np.sin(2 * np.pi * self.n_tor * eta3) else: - out = (real_1 + 1j * imag_1) * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * np.exp(1j * 2 * np.pi * self.n_tor * eta3) # return real or imaginary part if part == "r": - out = xp.real(out) + out = np.real(out) else: - out = xp.imag(out) + out = np.imag(out) return out @@ -1641,15 +1598,15 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): assert coeff.shape[:2] == (self.NbaseD[0], self.NbaseN[1]) # get real and imaginary part - coeff_r = xp.real(coeff) - coeff_i = xp.imag(coeff) + coeff_r = np.real(coeff) + coeff_i = np.imag(coeff) # ------ evaluate FEM field at given points -------- - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values_r_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.t[0], @@ -1679,8 +1636,8 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.t[0], @@ -1711,8 +1668,8 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): # matrix evaluation else: - values_r_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.t[0], @@ -1742,8 +1699,8 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.t[0], @@ -1774,15 +1731,15 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): # multiply with Fourier basis in third direction if self.n_tor == 0: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.ones(eta3.shape, dtype=float) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.ones(eta3.shape, dtype=float) else: if self.basis_tor == "r": - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.cos(2 * xp.pi * self.n_tor * eta3) - out += (values_r_2 + 1j * values_i_2)[:, :, None] * xp.sin(2 * xp.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.cos(2 * np.pi * self.n_tor * eta3) + out += (values_r_2 + 1j * values_i_2)[:, :, None] * np.sin(2 * np.pi * self.n_tor * eta3) else: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.exp(1j * 2 * np.pi * self.n_tor * eta3) # --------- evaluate FEM field at given point ------- else: @@ -1839,17 +1796,17 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): else: if self.basis_tor == "r": - out = (real_1 + 1j * imag_1) * xp.cos(2 * xp.pi * self.n_tor * eta3) - out += (real_2 + 1j * imag_2) * xp.sin(2 * xp.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * np.cos(2 * np.pi * self.n_tor * eta3) + out += (real_2 + 1j * imag_2) * np.sin(2 * np.pi * self.n_tor * eta3) else: - out = (real_1 + 1j * imag_1) * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * np.exp(1j * 2 * np.pi * self.n_tor * eta3) # return real or imaginary part if part == "r": - out = xp.real(out) + out = np.real(out) else: - out = xp.imag(out) + out = np.imag(out) return out @@ -1898,15 +1855,15 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): assert coeff.shape[:2] == (self.NbaseN[0], self.NbaseD[1]) # get real and imaginary part - coeff_r = xp.real(coeff) - coeff_i = xp.imag(coeff) + coeff_r = np.real(coeff) + coeff_i = np.imag(coeff) # ------ evaluate FEM field at given points -------- - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values_r_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.T[0], @@ -1936,8 +1893,8 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.T[0], @@ -1968,8 +1925,8 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): # matrix evaluation else: - values_r_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.T[0], @@ -1999,8 +1956,8 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.T[0], @@ -2031,15 +1988,15 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): # multiply with Fourier basis in third direction if self.n_tor == 0: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.ones(eta3.shape, dtype=float) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.ones(eta3.shape, dtype=float) else: if self.basis_tor == "r": - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.cos(2 * xp.pi * self.n_tor * eta3) - out += (values_r_2 + 1j * values_i_2)[:, :, None] * xp.sin(2 * xp.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.cos(2 * np.pi * self.n_tor * eta3) + out += (values_r_2 + 1j * values_i_2)[:, :, None] * np.sin(2 * np.pi * self.n_tor * eta3) else: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.exp(1j * 2 * np.pi * self.n_tor * eta3) # --------- evaluate FEM field at given point ------- else: @@ -2096,17 +2053,17 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): else: if self.basis_tor == "r": - out = (real_1 + 1j * imag_1) * xp.cos(2 * xp.pi * self.n_tor * eta3) - out += (real_2 + 1j * imag_2) * xp.sin(2 * xp.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * np.cos(2 * np.pi * self.n_tor * eta3) + out += (real_2 + 1j * imag_2) * np.sin(2 * np.pi * self.n_tor * eta3) else: - out = (real_1 + 1j * imag_1) * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * np.exp(1j * 2 * np.pi * self.n_tor * eta3) # return real or imaginary part if part == "r": - out = xp.real(out) + out = np.real(out) else: - out = xp.imag(out) + out = np.imag(out) return out @@ -2158,15 +2115,15 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): assert coeff.shape[:2] == (self.NbaseD[0], self.NbaseD[1]) # get real and imaginary part - coeff_r = xp.real(coeff) - coeff_i = xp.imag(coeff) + coeff_r = np.real(coeff) + coeff_i = np.imag(coeff) # ------ evaluate FEM field at given points -------- - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values_r_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.t[0], @@ -2196,8 +2153,8 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.t[0], @@ -2228,8 +2185,8 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): # matrix evaluation else: - values_r_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.t[0], @@ -2259,8 +2216,8 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.t[0], @@ -2291,15 +2248,15 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): # multiply with Fourier basis in third direction if self.n_tor == 0: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.ones(eta3.shape, dtype=float) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.ones(eta3.shape, dtype=float) else: if self.basis_tor == "r": - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.cos(2 * xp.pi * self.n_tor * eta3) - out += (values_r_2 + 1j * values_i_2)[:, :, None] * xp.sin(2 * xp.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.cos(2 * np.pi * self.n_tor * eta3) + out += (values_r_2 + 1j * values_i_2)[:, :, None] * np.sin(2 * np.pi * self.n_tor * eta3) else: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.exp(1j * 2 * np.pi * self.n_tor * eta3) # --------- evaluate FEM field at given point ------- else: @@ -2356,17 +2313,17 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): else: if self.basis_tor == "r": - out = (real_1 + 1j * imag_1) * xp.cos(2 * xp.pi * self.n_tor * eta3) - out += (real_2 + 1j * imag_2) * xp.sin(2 * xp.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * np.cos(2 * np.pi * self.n_tor * eta3) + out += (real_2 + 1j * imag_2) * np.sin(2 * np.pi * self.n_tor * eta3) else: - out = (real_1 + 1j * imag_1) * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * np.exp(1j * 2 * np.pi * self.n_tor * eta3) # return real or imaginary part if part == "r": - out = xp.real(out) + out = np.real(out) else: - out = xp.imag(out) + out = np.imag(out) return out @@ -2377,13 +2334,13 @@ def evaluate_NNN(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or xp.ndarray + eta1 : double or np.ndarray 1st component of logical evaluation point - eta2 : double or xp.ndarray + eta2 : double or np.ndarray 2nd component of logical evaluation point - eta3 : double or xp.ndarray + eta3 : double or np.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2398,10 +2355,10 @@ def evaluate_NNN(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_0(coeff) - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.T[0], self.T[1], @@ -2422,7 +2379,7 @@ def evaluate_NNN(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2492,13 +2449,13 @@ def evaluate_DNN(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or xp.ndarray + eta1 : double or np.ndarray 1st component of logical evaluation point - eta2 : double or xp.ndarray + eta2 : double or np.ndarray 2nd component of logical evaluation point - eta3 : double or xp.ndarray + eta3 : double or np.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2513,10 +2470,10 @@ def evaluate_DNN(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_1(coeff)[0] - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.t[0], self.T[1], @@ -2537,7 +2494,7 @@ def evaluate_DNN(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2606,13 +2563,13 @@ def evaluate_NDN(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or xp.ndarray + eta1 : double or np.ndarray 1st component of logical evaluation point - eta2 : double or xp.ndarray + eta2 : double or np.ndarray 2nd component of logical evaluation point - eta3 : double or xp.ndarray + eta3 : double or np.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2627,10 +2584,10 @@ def evaluate_NDN(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_1(coeff)[1] - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.T[0], self.t[1], @@ -2651,7 +2608,7 @@ def evaluate_NDN(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2720,13 +2677,13 @@ def evaluate_NND(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or xp.ndarray + eta1 : double or np.ndarray 1st component of logical evaluation point - eta2 : double or xp.ndarray + eta2 : double or np.ndarray 2nd component of logical evaluation point - eta3 : double or xp.ndarray + eta3 : double or np.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2741,10 +2698,10 @@ def evaluate_NND(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_1(coeff)[2] - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.T[0], self.T[1], @@ -2765,7 +2722,7 @@ def evaluate_NND(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2834,13 +2791,13 @@ def evaluate_NDD(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or xp.ndarray + eta1 : double or np.ndarray 1st component of logical evaluation point - eta2 : double or xp.ndarray + eta2 : double or np.ndarray 2nd component of logical evaluation point - eta3 : double or xp.ndarray + eta3 : double or np.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2855,10 +2812,10 @@ def evaluate_NDD(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_2(coeff)[0] - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.T[0], self.t[1], @@ -2879,7 +2836,7 @@ def evaluate_NDD(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2948,13 +2905,13 @@ def evaluate_DND(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or xp.ndarray + eta1 : double or np.ndarray 1st component of logical evaluation point - eta2 : double or xp.ndarray + eta2 : double or np.ndarray 2nd component of logical evaluation point - eta3 : double or xp.ndarray + eta3 : double or np.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2969,10 +2926,10 @@ def evaluate_DND(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_2(coeff)[1] - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.t[0], self.T[1], @@ -2993,7 +2950,7 @@ def evaluate_DND(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -3062,13 +3019,13 @@ def evaluate_DDN(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or xp.ndarray + eta1 : double or np.ndarray 1st component of logical evaluation point - eta2 : double or xp.ndarray + eta2 : double or np.ndarray 2nd component of logical evaluation point - eta3 : double or xp.ndarray + eta3 : double or np.ndarray 3rd component of logical evaluation point coeff : array_like @@ -3083,10 +3040,10 @@ def evaluate_DDN(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_2(coeff)[2] - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.t[0], self.t[1], @@ -3107,7 +3064,7 @@ def evaluate_DDN(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -3176,13 +3133,13 @@ def evaluate_DDD(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or xp.ndarray + eta1 : double or np.ndarray 1st component of logical evaluation point - eta2 : double or xp.ndarray + eta2 : double or np.ndarray 2nd component of logical evaluation point - eta3 : double or xp.ndarray + eta3 : double or np.ndarray 3rd component of logical evaluation point coeff : array_like @@ -3197,10 +3154,10 @@ def evaluate_DDD(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_3(coeff) - if isinstance(eta1, xp.ndarray): + if isinstance(eta1, np.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.t[0], self.t[1], @@ -3221,7 +3178,7 @@ def evaluate_DDD(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( diff --git a/src/struphy/examples/_draw_parallel.py b/src/struphy/examples/_draw_parallel.py index b31ba19c4..040e55c65 100644 --- a/src/struphy/examples/_draw_parallel.py +++ b/src/struphy/examples/_draw_parallel.py @@ -1,5 +1,5 @@ -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.geometry import domains @@ -69,19 +69,19 @@ def main(): ) # are all markers in the correct domain? - conds = xp.logical_and( + conds = np.logical_and( particles.markers[:, :3] > derham.domain_array[rank, 0::3], particles.markers[:, :3] < derham.domain_array[rank, 1::3], ) holes = particles.markers[:, 0] == -1.0 - stay = xp.all(conds, axis=1) + stay = np.all(conds, axis=1) - error_mks = particles.markers[xp.logical_and(~stay, ~holes)] + error_mks = particles.markers[np.logical_and(~stay, ~holes)] print( - f"rank {rank} | markers not on correct process: {xp.nonzero(xp.logical_and(~stay, ~holes))} \ - \n corresponding positions:\n {error_mks[:, :3]}", + f"rank {rank} | markers not on correct process: {np.nonzero(np.logical_and(~stay, ~holes))} \ + \n corresponding positions:\n {error_mks[:, :3]}" ) assert error_mks.size == 0 diff --git a/src/struphy/examples/restelli2018/callables.py b/src/struphy/examples/restelli2018/callables.py index 505a60f90..89ce4bfda 100644 --- a/src/struphy/examples/restelli2018/callables.py +++ b/src/struphy/examples/restelli2018/callables.py @@ -1,6 +1,6 @@ "Analytical callables needed for the simulation of the Two-Fluid Quasi-Neutral Model by Restelli." -import cunumpy as xp +import numpy as np class RestelliForcingTerm: @@ -74,9 +74,9 @@ def __init__(self, nu=1.0, R0=2.0, a=1.0, B0=10.0, Bp=12.5, alpha=0.1, beta=1.0, self._eps_norm = eps def __call__(self, x, y, z): - R = xp.sqrt(x**2 + y**2) - R = xp.where(R == 0.0, 1e-9, R) - phi = xp.arctan2(-y, x) + R = np.sqrt(x**2 + y**2) + R = np.where(R == 0.0, 1e-9, R) + phi = np.arctan2(-y, x) force_Z = self._nu * ( self._alpha * (self._R0 - 4 * R) / (self._a * self._R0 * R) - self._beta * self._Bp * self._R0**2 / (self._B0 * self._a * R**3) @@ -197,31 +197,31 @@ def __init__( def __call__(self, x, y, z): A = self._alpha / (self._a * self._R0) C = self._beta * self._Bp * self._R0 / (self._B0 * self._a) - R = xp.sqrt(x**2 + y**2) - R = xp.where(R == 0.0, 1e-9, R) - phi = xp.arctan2(-y, x) + R = np.sqrt(x**2 + y**2) + R = np.where(R == 0.0, 1e-9, R) + phi = np.arctan2(-y, x) if self._species == "Ions": """Forceterm for ions on the right hand side.""" if self._dimension == "2D": fx = ( - -2.0 * xp.pi * xp.sin(2 * xp.pi * x) - + xp.cos(2 * xp.pi * x) * xp.cos(2 * xp.pi * y) * self._B0 / self._eps_norm - - self._nu * 8.0 * xp.pi**2 * xp.sin(2 * xp.pi * x) * xp.sin(2 * xp.pi * y) + -2.0 * np.pi * np.sin(2 * np.pi * x) + + np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y) * self._B0 / self._eps_norm + - self._nu * 8.0 * np.pi**2 * np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y) ) fy = ( - 2.0 * xp.pi * xp.cos(2 * xp.pi * y) - - xp.sin(2 * xp.pi * x) * xp.sin(2 * xp.pi * y) * self._B0 / self._eps_norm - - self._nu * 8.0 * xp.pi**2 * xp.cos(2 * xp.pi * x) * xp.cos(2 * xp.pi * y) + 2.0 * np.pi * np.cos(2 * np.pi * y) + - np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y) * self._B0 / self._eps_norm + - self._nu * 8.0 * np.pi**2 * np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y) ) fz = 0.0 * x elif self._dimension == "1D": fx = ( - 2.0 * xp.pi * xp.cos(2 * xp.pi * x) - + self._nu * 4.0 * xp.pi**2 * xp.sin(2 * xp.pi * x) - + (xp.sin(2 * xp.pi * x) + 1.0) / self._dt + 2.0 * np.pi * np.cos(2 * np.pi * x) + + self._nu * 4.0 * np.pi**2 * np.sin(2 * np.pi * x) + + (np.sin(2 * np.pi * x) + 1.0) / self._dt ) - fy = (xp.sin(2 * xp.pi * x) + 1.0) * self._B0 / self._eps_norm + fy = (np.sin(2 * np.pi * x) + 1.0) * self._B0 / self._eps_norm fz = 0.0 * x elif self._dimension == "Tokamak": @@ -234,8 +234,8 @@ def __call__(self, x, y, z): fZ = self._alpha * self._B0 * z / self._a + A * self._R0 / R * ((R - self._R0) * self._B0) fphi = A * self._R0 * self._Bp / (self._a * R**2) * ((R - self._R0) ** 2 + z**2) - fx = xp.cos(phi) * fR - R * xp.sin(phi) * fphi - fy = -xp.sin(phi) * fR - R * xp.cos(phi) * fphi + fx = np.cos(phi) * fR - R * np.sin(phi) * fphi + fy = -np.sin(phi) * fR - R * np.cos(phi) * fphi fz = fZ if self._comp == "0": @@ -251,26 +251,26 @@ def __call__(self, x, y, z): """Forceterm for electrons on the right hand side.""" if self._dimension == "2D": fx = ( - 2.0 * xp.pi * xp.sin(2 * xp.pi * x) - - xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y) * self._B0 / self._eps_norm - - self._nu_e * 32.0 * xp.pi**2 * xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y) - - self._stab_sigma * (-xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y)) + 2.0 * np.pi * np.sin(2 * np.pi * x) + - np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y) * self._B0 / self._eps_norm + - self._nu_e * 32.0 * np.pi**2 * np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y) + - self._stab_sigma * (-np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y)) ) fy = ( - -2.0 * xp.pi * xp.cos(2 * xp.pi * y) - + xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y) * self._B0 / self._eps_norm - - self._nu_e * 32.0 * xp.pi**2 * xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y) - - self._stab_sigma * (-xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y)) + -2.0 * np.pi * np.cos(2 * np.pi * y) + + np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y) * self._B0 / self._eps_norm + - self._nu_e * 32.0 * np.pi**2 * np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y) + - self._stab_sigma * (-np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y)) ) fz = 0.0 * x elif self._dimension == "1D": fx = ( - -2.0 * xp.pi * xp.cos(2 * xp.pi * x) - + self._nu_e * 4.0 * xp.pi**2 * xp.sin(2 * xp.pi * x) - - self._stab_sigma * xp.sin(2 * xp.pi * x) + -2.0 * np.pi * np.cos(2 * np.pi * x) + + self._nu_e * 4.0 * np.pi**2 * np.sin(2 * np.pi * x) + - self._stab_sigma * np.sin(2 * np.pi * x) ) - fy = -xp.sin(2 * xp.pi * x) * self._B0 / self._eps_norm + fy = -np.sin(2 * np.pi * x) * self._B0 / self._eps_norm fz = 0.0 * x elif self._dimension == "Tokamak": @@ -283,8 +283,8 @@ def __call__(self, x, y, z): fZ = -self._alpha * self._B0 * z / self._a - A * self._R0 / R * ((R - self._R0) * self._B0) fphi = -A * self._R0 * self._Bp / (self._a * R**2) * ((R - self._R0) ** 2 + z**2) - fx = xp.cos(phi) * fR - R * xp.sin(phi) * fphi - fy = -xp.sin(phi) * fR - R * xp.cos(phi) * fphi + fx = np.cos(phi) * fR - R * np.sin(phi) * fphi + fy = -np.sin(phi) * fR - R * np.cos(phi) * fphi fz = fZ if self._comp == "0": diff --git a/src/struphy/feec/basis_projection_ops.py b/src/struphy/feec/basis_projection_ops.py index ab0925828..b335edea5 100644 --- a/src/struphy/feec/basis_projection_ops.py +++ b/src/struphy/feec/basis_projection_ops.py @@ -1,6 +1,6 @@ -import cunumpy as xp +import numpy as np +from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL -from psydac.ddm.mpi import mpi as MPI from psydac.fem.basic import FemSpace from psydac.fem.tensor import TensorFemSpace from psydac.linalg.basic import IdentityOperator, LinearOperator, Vector @@ -15,7 +15,6 @@ from struphy.feec.utilities import RotationMatrix from struphy.polar.basic import PolarDerhamSpace, PolarVector from struphy.polar.linear_operators import PolarExtractionOperator -from struphy.utils.pyccel import Pyccelkernel class BasisProjectionOperators: @@ -51,7 +50,7 @@ def __init__(self, derham, domain, verbose=True, **weights): self._rank = derham.comm.Get_rank() if derham.comm is not None else 0 - if xp.any([p == 1 and Nel > 1 for p, Nel in zip(derham.p, derham.Nel)]): + if np.any([p == 1 and Nel > 1 for p, Nel in zip(derham.p, derham.Nel)]): if MPI.COMM_WORLD.Get_rank() == 0: print( f'\nWARNING: Class "BasisProjectionOperators" called with p={derham.p} (interpolation of piece-wise constants should be avoided).', @@ -147,7 +146,7 @@ def K3(self): e3, ) / self.sqrt_g(e1, e2, e3), - ], + ] ] self._K3 = self.create_basis_op( fun, @@ -263,7 +262,7 @@ def Q3(self): e3, ) / self.sqrt_g(e1, e2, e3), - ], + ] ] self._Q3 = self.create_basis_op( fun, @@ -1051,11 +1050,11 @@ def __init__( if isinstance(V, TensorFemSpace): self._Vspaces = [V.coeff_space] self._V1ds = [V.spaces] - self._VNbasis = xp.array([self._V1ds[0][0].nbasis, self._V1ds[0][1].nbasis, self._V1ds[0][2].nbasis]) + self._VNbasis = np.array([self._V1ds[0][0].nbasis, self._V1ds[0][1].nbasis, self._V1ds[0][2].nbasis]) else: self._Vspaces = V.coeff_space self._V1ds = [comp.spaces for comp in V.spaces] - self._VNbasis = xp.array( + self._VNbasis = np.array( [ [self._V1ds[0][0].nbasis, self._V1ds[0][1].nbasis, self._V1ds[0][2].nbasis], [ @@ -1064,7 +1063,7 @@ def __init__( self._V1ds[1][2].nbasis, ], [self._V1ds[2][0].nbasis, self._V1ds[2][1].nbasis, self._V1ds[2][2].nbasis], - ], + ] ) # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component @@ -1283,7 +1282,7 @@ def assemble(self, verbose=False): self._pds, self._periodic, self._p, - xp.array([col0, col1, col2]), + np.array([col0, col1, col2]), self._VNbasis, self._mat._data, coeff, @@ -1358,12 +1357,12 @@ def assemble(self, verbose=False): self._pds, self._periodic, self._p, - xp.array( + np.array( [ col0, col1, col2, - ], + ] ), self._VNbasis[hh], Aux._data, @@ -1438,12 +1437,12 @@ def assemble(self, verbose=False): self._pds[h], self._periodic, self._p, - xp.array( + np.array( [ col0, col1, col2, - ], + ] ), self._VNbasis, Aux[h]._data, @@ -1539,12 +1538,12 @@ def assemble(self, verbose=False): self._pds[h], self._periodic, self._p, - xp.array( + np.array( [ col0, col1, col2, - ], + ] ), self._VNbasis[hh], Aux[h]._data, @@ -1613,7 +1612,7 @@ class BasisProjectionOperator(LinOpWithTransp): Finite element spline space (domain, input space). weights : list - Weight function(s) (callables or xp.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. + Weight function(s) (callables or np.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. V_extraction_op : PolarExtractionOperator | IdentityOperator Extraction operator to polar sub-space of V. @@ -1889,7 +1888,7 @@ def update_weights(self, weights): Parameters ---------- weights : list - Weight function(s) (callables or xp.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. + Weight function(s) (callables or np.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. """ self._weights = weights @@ -1945,13 +1944,13 @@ def assemble(self, weights=None, verbose=False): # input vector space (domain), column of block for j, (Vspace, V1d, loc_weight) in enumerate(zip(_Vspaces, _V1ds, weight_line)): - _starts_in = xp.array(Vspace.starts) - _ends_in = xp.array(Vspace.ends) - _pads_in = xp.array(Vspace.pads) + _starts_in = np.array(Vspace.starts) + _ends_in = np.array(Vspace.ends) + _pads_in = np.array(Vspace.pads) - _starts_out = xp.array(Wspace.starts) - _ends_out = xp.array(Wspace.ends) - _pads_out = xp.array(Wspace.pads) + _starts_out = np.array(Wspace.starts) + _ends_out = np.array(Wspace.ends) + _pads_out = np.array(Wspace.pads) # use cached information if asked if self._use_cache: @@ -1998,21 +1997,21 @@ def assemble(self, weights=None, verbose=False): # Evaluate weight function at quadrature points # evaluate weight at quadrature points if callable(loc_weight): - PTS = xp.meshgrid(*_ptsG, indexing="ij") + PTS = np.meshgrid(*_ptsG, indexing="ij") mat_w = loc_weight(*PTS).copy() - elif isinstance(loc_weight, xp.ndarray): + elif isinstance(loc_weight, np.ndarray): assert loc_weight.shape == (len(_ptsG[0]), len(_ptsG[1]), len(_ptsG[2])) mat_w = loc_weight elif loc_weight is not None: raise TypeError( - "weights must be xp.ndarray, callable or None", + "weights must be np.ndarray, callable or None", ) # Call the kernel if weight function is not zero or in the scalar case # to avoid calling _block of a StencilMatrix in the else - not_weight_zero = xp.array( - int(loc_weight is not None and xp.any(xp.abs(mat_w) > 1e-14)), + not_weight_zero = np.array( + int(loc_weight is not None and np.any(np.abs(mat_w) > 1e-14)), ) if self._mpi_comm is not None: @@ -2039,11 +2038,9 @@ def assemble(self, weights=None, verbose=False): ) dofs_mat = self._dof_mat[i, j] - kernel = Pyccelkernel( - getattr( - basis_projection_kernels, - "assemble_dofs_for_weighted_basisfuns_" + str(V.ldim) + "d", - ), + kernel = getattr( + basis_projection_kernels, + "assemble_dofs_for_weighted_basisfuns_" + str(V.ldim) + "d", ) if rank == 0 and verbose: @@ -2385,7 +2382,7 @@ def find_relative_col(col, row, Nbasis, periodic): The relative column position of col with respect to the the current row of the StencilMatrix. """ - if not periodic: + if periodic == False: relativecol = col - row # In the periodic case we must account for the possible looping of the basis functions when computing the relative row postion else: diff --git a/src/struphy/feec/linear_operators.py b/src/struphy/feec/linear_operators.py index 28b4a0805..0fbcee763 100644 --- a/src/struphy/feec/linear_operators.py +++ b/src/struphy/feec/linear_operators.py @@ -1,9 +1,8 @@ import itertools from abc import abstractmethod -import cunumpy as xp -from psydac.ddm.mpi import MockComm -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI from psydac.linalg.basic import LinearOperator, Vector, VectorSpace from psydac.linalg.block import BlockVectorSpace from psydac.linalg.stencil import StencilVectorSpace @@ -53,27 +52,26 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): if isinstance(self.domain, BlockVectorSpace): comm = self.domain.spaces[0].cart.comm + if comm is None: + comm = MPI.COMM_SELF elif isinstance(self.domain, StencilVectorSpace): comm = self.domain.cart.comm + if comm is None: + comm = MPI.COMM_SELF + rank = comm.Get_rank() + size = comm.Get_size() - if comm is None: - rank = 0 - size = 1 - else: - rank = comm.Get_rank() - size = comm.Get_size() - - if not is_sparse: + if is_sparse == False: if out is None: # We declare the matrix form of our linear operator - out = xp.zeros([self.codomain.dimension, self.domain.dimension], dtype=self.dtype) + out = np.zeros([self.codomain.dimension, self.domain.dimension], dtype=self.dtype) else: - assert isinstance(out, xp.ndarray) + assert isinstance(out, np.ndarray) assert out.shape[0] == self.codomain.dimension assert out.shape[1] == self.domain.dimension # We use this matrix to store the partial results that we shall combine into the final matrix with a reduction at the end - result = xp.zeros((self.codomain.dimension, self.domain.dimension), dtype=self.dtype) + result = np.zeros((self.codomain.dimension, self.domain.dimension), dtype=self.dtype) else: if out is not None: raise Exception("If is_sparse is True then out must be set to None.") @@ -97,29 +95,23 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): ndim = [sp.ndim for sp in self.domain.spaces] # First each rank is going to need to know the starts and ends of all other ranks - startsarr = xp.array([starts[i][j] for i in range(nsp) for j in range(ndim[i])], dtype=int) + startsarr = np.array([starts[i][j] for i in range(nsp) for j in range(ndim[i])], dtype=int) # Create an array to store gathered data from all ranks - allstarts = xp.empty(size * len(startsarr), dtype=int) + allstarts = np.empty(size * len(startsarr), dtype=int) # Use Allgather to gather 'starts' from all ranks into 'allstarts' - if comm is None or isinstance(comm, MockComm): - allstarts = startsarr - else: - comm.Allgather(startsarr, allstarts) + comm.Allgather(startsarr, allstarts) # Reshape 'allstarts' to have 9 columns and 'size' rows allstarts = allstarts.reshape((size, len(startsarr))) - endsarr = xp.array([ends[i][j] for i in range(nsp) for j in range(ndim[i])], dtype=int) + endsarr = np.array([ends[i][j] for i in range(nsp) for j in range(ndim[i])], dtype=int) # Create an array to store gathered data from all ranks - allends = xp.empty(size * len(endsarr), dtype=int) + allends = np.empty(size * len(endsarr), dtype=int) # Use Allgather to gather 'ends' from all ranks into 'allends' - if comm is None or isinstance(comm, MockComm): - allends = endsarr - else: - comm.Allgather(endsarr, allends) + comm.Allgather(endsarr, allends) # Reshape 'allends' to have 9 columns and 'size' rows allends = allends.reshape((size, len(endsarr))) @@ -136,7 +128,7 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): itterables = [] for i in range(ndim[h]): itterables.append( - range(allstarts[currentrank][i + npredim], allends[currentrank][i + npredim] + 1), + range(allstarts[currentrank][i + npredim], allends[currentrank][i + npredim] + 1) ) # We iterate over all the entries that belong to rank number currentrank for i in itertools.product(*itterables): @@ -148,13 +140,13 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): self.dot(v, out=tmp2) # Compute to which column this iteration belongs col = spoint - col += xp.ravel_multi_index(i, npts[h]) - if not is_sparse: + col += np.ravel_multi_index(i, npts[h]) + if is_sparse == False: result[:, col] = tmp2.toarray() else: aux = tmp2.toarray() # We now need to now which entries on tmp2 are non-zero and store then in our data list - for l in xp.where(aux != 0)[0]: + for l in np.where(aux != 0)[0]: data.append(aux[l]) colarr.append(col) row.append(l) @@ -179,28 +171,22 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): ndim = self.domain.ndim # First each rank is going to need to know the starts and ends of all other ranks - startsarr = xp.array([starts[j] for j in range(ndim)], dtype=int) + startsarr = np.array([starts[j] for j in range(ndim)], dtype=int) # Create an array to store gathered data from all ranks - allstarts = xp.empty(size * len(startsarr), dtype=int) + allstarts = np.empty(size * len(startsarr), dtype=int) # Use Allgather to gather 'starts' from all ranks into 'allstarts' - if comm is None or isinstance(comm, MockComm): - allstarts = startsarr - else: - comm.Allgather(startsarr, allstarts) + comm.Allgather(startsarr, allstarts) # Reshape 'allstarts' to have 3 columns and 'size' rows allstarts = allstarts.reshape((size, len(startsarr))) - endsarr = xp.array([ends[j] for j in range(ndim)], dtype=int) + endsarr = np.array([ends[j] for j in range(ndim)], dtype=int) # Create an array to store gathered data from all ranks - allends = xp.empty(size * len(endsarr), dtype=int) + allends = np.empty(size * len(endsarr), dtype=int) # Use Allgather to gather 'ends' from all ranks into 'allends' - if comm is None or isinstance(comm, MockComm): - allends = endsarr - else: - comm.Allgather(endsarr, allends) + comm.Allgather(endsarr, allends) # Reshape 'allends' to have 3 columns and 'size' rows allends = allends.reshape((size, len(endsarr))) @@ -219,13 +205,13 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): # Compute dot product with the linear operator. self.dot(v, out=tmp2) # Compute to which column this iteration belongs - col = xp.ravel_multi_index(i, npts) - if not is_sparse: + col = np.ravel_multi_index(i, npts) + if is_sparse == False: result[:, col] = tmp2.toarray() else: aux = tmp2.toarray() # We now need to now which entries on tmp2 are non-zero and store then in our data list - for l in xp.where(aux != 0)[0]: + for l in np.where(aux != 0)[0]: data.append(aux[l]) colarr.append(col) row.append(l) @@ -237,22 +223,17 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): # I cannot conceive any situation where this error should be thrown, but I put it here just in case something unexpected happens. raise Exception("Function toarray_struphy() only supports Stencil Vectors or Block Vectors.") - if not is_sparse: + if is_sparse == False: # Use Allreduce to perform addition reduction and give one copy of the result to all ranks. - if comm is None or isinstance(comm, MockComm): - out[:] = result - else: - comm.Allreduce(result, out, op=MPI.SUM) + comm.Allreduce(result, out, op=MPI.SUM) return out else: - if comm is None or isinstance(comm, MockComm): - gathered_rows = [row] - gathered_cols = [colarr] - gathered_data = [data] - else: - gathered_rows = comm.gather(row, root=0) - gathered_cols = comm.gather(colarr, root=0) - gathered_data = comm.gather(data, root=0) + # Gather all rows on rank 0 + gathered_rows = comm.gather(row, root=0) + # Gather all colarr on rank 0 + gathered_cols = comm.gather(colarr, root=0) + # Gather all data on rank 0 + gathered_data = comm.gather(data, root=0) if rank == 0: # Rank 0 collects all rows from other ranks @@ -262,13 +243,12 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): # Rank 0 collects all data from other ranks all_data = [item for sublist in gathered_data for item in sublist] - if comm is not None: - # Broadcast 'all_rows' to all other ranks - comm.bcast(all_rows, root=0) - # Broadcast 'all_cols' to all other ranks - comm.bcast(all_cols, root=0) - # Broadcast 'all_data' to all other ranks - comm.bcast(all_data, root=0) + # Broadcast 'all_rows' to all other ranks + comm.bcast(all_rows, root=0) + # Broadcast 'all_cols' to all other ranks + comm.bcast(all_cols, root=0) + # Broadcast 'all_data' to all other ranks + comm.bcast(all_data, root=0) else: # Other ranks receive the 'all_rows' list through broadcast all_rows = comm.bcast(None, root=0) @@ -293,7 +273,7 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): return sparse.csr_matrix((all_data, (all_rows, all_cols)), shape=(numrows, numcols)).todia() else: raise Exception( - "The selected sparse matrix format must be one of the following : csr, csc, bsr, lil, dok, coo or dia.", + "The selected sparse matrix format must be one of the following : csr, csc, bsr, lil, dok, coo or dia." ) diff --git a/src/struphy/feec/local_projectors_kernels.py b/src/struphy/feec/local_projectors_kernels.py index 706b3f78e..09b4c182f 100644 --- a/src/struphy/feec/local_projectors_kernels.py +++ b/src/struphy/feec/local_projectors_kernels.py @@ -63,7 +63,7 @@ def get_local_problem_size(periodic: "bool[:]", p: "int[:]", IoH: "bool[:]"): for h in range(3): # Interpolation - if not IoH[h]: + if IoH[h] == False: lenj[h] = 2 * p[h] - 1 # Histopolation else: @@ -168,10 +168,7 @@ def get_dofs_local_1_form_ec_component_weighted( @stack_array("shp") def get_dofs_local_1_form_ec_component( - args_solve: LocalProjectorsArguments, - f3: "float[:,:,:]", - f_eval_aux: "float[:,:,:]", - c: int, + args_solve: LocalProjectorsArguments, f3: "float[:,:,:]", f_eval_aux: "float[:,:,:]", c: int ): """Kernel for evaluating the degrees of freedom for the c-th component of 1-forms. This function is for local commuting projetors. @@ -223,10 +220,7 @@ def get_dofs_local_1_form_ec_component( @stack_array("shp") def get_dofs_local_2_form_ec_component( - args_solve: LocalProjectorsArguments, - fc: "float[:,:,:]", - f_eval_aux: "float[:,:,:]", - c: int, + args_solve: LocalProjectorsArguments, fc: "float[:,:,:]", f_eval_aux: "float[:,:,:]", c: int ): """Kernel for evaluating the degrees of freedom for the c-th component of 2-forms. This function is for local commuting projetors. @@ -734,7 +728,7 @@ def solve_local_main_loop_weighted( if counteri0 >= rows0[i00] and counteri0 <= rowe0[i00]: compute0 = True break - if compute0: + if compute0 == True: counteri1 = 0 for i1 in range(args_solve.starts[1], args_solve.ends[1] + 1): # This bool variable tell us if this row has a non-zero FE coefficient, based on the current basis function we are using on our projection @@ -744,7 +738,7 @@ def solve_local_main_loop_weighted( if counteri1 >= rows1[i11] and counteri1 <= rowe1[i11]: compute1 = True break - if compute1: + if compute1 == True: counteri2 = 0 for i2 in range(args_solve.starts[2], args_solve.ends[2] + 1): # This bool variable tell us if this row has a non-zero FE coefficient, based on the current basis function we are using on our projection @@ -754,7 +748,7 @@ def solve_local_main_loop_weighted( if counteri2 >= rows2[i22] and counteri2 <= rowe2[i22]: compute2 = True break - if compute2: + if compute2 == True: L123 = 0.0 startj1, endj1 = select_quasi_points( i0, @@ -850,7 +844,7 @@ def find_relative_col(col: int, row: int, Nbasis: int, periodic: bool): The relative column position of col with respect to the the current row of the StencilMatrix. """ - if not periodic: + if periodic == False: relativecol = col - row # In the periodic case we must account for the possible looping of the basis functions when computing the relative row postion else: @@ -944,7 +938,7 @@ def assemble_basis_projection_operator_local( compute0 = True break relativecol0 = find_relative_col(col[0], row0, VNbasis[0], periodic[0]) - if relativecol0 >= -p[0] and relativecol0 <= p[0] and compute0: + if relativecol0 >= -p[0] and relativecol0 <= p[0] and compute0 == True: count1 = 0 for row1 in range(starts[1], ends[1] + 1): # This bool variable tell us if this row has a non-zero FE coefficient, based on the current basis function we are using on our projection @@ -955,7 +949,7 @@ def assemble_basis_projection_operator_local( compute1 = True break relativecol1 = find_relative_col(col[1], row1, VNbasis[1], periodic[1]) - if relativecol1 >= -p[1] and relativecol1 <= p[1] and compute1: + if relativecol1 >= -p[1] and relativecol1 <= p[1] and compute1 == True: count2 = 0 for row2 in range(starts[2], ends[2] + 1): # This bool variable tell us if this row has a non-zero FE coefficient, based on the current basis function we are using on our projection @@ -966,7 +960,7 @@ def assemble_basis_projection_operator_local( compute2 = True break relativecol2 = find_relative_col(col[2], row2, VNbasis[2], periodic[2]) - if relativecol2 >= -p[2] and relativecol2 <= p[2] and compute2: + if relativecol2 >= -p[2] and relativecol2 <= p[2] and compute2 == True: mat[ count0 + pds[0], count1 + pds[1], @@ -1002,7 +996,7 @@ def are_quadrature_points_zero(aux: "int[:]", p: int, basis: "float[:]"): if basis[in_start + ii] != 0.0: all_zero = False break - if all_zero: + if all_zero == True: aux[i] = 0 @@ -1043,15 +1037,7 @@ def get_rows_periodic(starts: int, ends: int, modl: int, modr: int, Nbasis: int, def get_rows( - col: int, - starts: int, - ends: int, - p: int, - Nbasis: int, - periodic: bool, - IoH: bool, - BoD: bool, - aux: "int[:]", + col: int, starts: int, ends: int, p: int, Nbasis: int, periodic: bool, IoH: bool, BoD: bool, aux: "int[:]" ): """Kernel for getting the list of rows that are non-zero for the current BasisProjectionLocal column, within the start and end indices of the current MPI rank. @@ -1085,33 +1071,33 @@ def get_rows( Array where we put a one if the current row could have a non-zero FE coefficient for the column given by col. """ # Periodic boundary conditions - if periodic: + if periodic == True: # Histopolation - if IoH: + if IoH == True: # D-splines - if BoD: + if BoD == True: get_rows_periodic(starts, ends, -p + 1, p, Nbasis, col, aux) # B-splines - if not BoD: + if BoD == False: get_rows_periodic(starts, ends, -p + 1, p + 1, Nbasis, col, aux) # Interpolation - if not IoH: + if IoH == False: # D-splines - if BoD: + if BoD == True: # Special case p = 1 if p == 1: get_rows_periodic(starts, ends, -1, 1, Nbasis, col, aux) if p != 1: get_rows_periodic(starts, ends, -p + 1, p - 1, Nbasis, col, aux) # B-splines - if not BoD: + if BoD == False: get_rows_periodic(starts, ends, -p + 1, p, Nbasis, col, aux) # Clamped boundary conditions - if not periodic: + if periodic == False: # Histopolation - if IoH: + if IoH == True: # D-splines - if BoD: + if BoD == True: count = 0 for row in range(starts, ends + 1): if row >= 0 and row <= (p - 2) and col >= 0 and col <= row + p - 1: @@ -1124,7 +1110,7 @@ def get_rows( aux[count] = 1 count += 1 # B-splines - if not BoD: + if BoD == False: count = 0 for row in range(starts, ends + 1): if row >= 0 and row <= (p - 2) and col >= 0 and col <= (row + p): @@ -1135,9 +1121,9 @@ def get_rows( aux[count] = 1 count += 1 # Interpolation - if not IoH: + if IoH == False: # D-splines - if BoD: + if BoD == True: count = 0 for row in range(starts, ends + 1): if row == 0 and col <= (p - 1): @@ -1152,7 +1138,7 @@ def get_rows( aux[count] = 1 count += 1 # B-splines - if not BoD: + if BoD == False: count = 0 for row in range(starts, ends + 1): if row == 0 and col <= p: diff --git a/src/struphy/feec/mass.py b/src/struphy/feec/mass.py index 5964f5f7c..9d5531e98 100644 --- a/src/struphy/feec/mass.py +++ b/src/struphy/feec/mass.py @@ -1,14 +1,12 @@ import inspect -from copy import deepcopy -import cunumpy as xp +import numpy as np +from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL -from psydac.ddm.mpi import mpi as MPI from psydac.fem.tensor import TensorFemSpace from psydac.fem.vector import VectorFemSpace from psydac.linalg.basic import IdentityOperator, LinearOperator, Vector from psydac.linalg.block import BlockLinearOperator, BlockVector -from psydac.linalg.solvers import inverse from psydac.linalg.stencil import StencilDiagonalMatrix, StencilMatrix, StencilVector from struphy.feec import mass_kernels @@ -17,7 +15,6 @@ from struphy.feec.utilities import RotationMatrix from struphy.geometry.base import Domain from struphy.polar.linear_operators import PolarExtractionOperator -from struphy.utils.pyccel import Pyccelkernel class WeightedMassOperators: @@ -449,7 +446,7 @@ def M2B_div0(self): self.weights[self.selected_weight].a1_1, self.weights[self.selected_weight].a1_2, self.weights[self.selected_weight].a1_3, - ], + ] ) tmp_b2 = self.derham.curl.dot(a_eq) @@ -555,7 +552,7 @@ def M2Bn(self): self.weights[self.selected_weight].a1_1, self.weights[self.selected_weight].a1_2, self.weights[self.selected_weight].a1_3, - ], + ] ) tmp_b2 = self.derham.curl.dot(a_eq) @@ -731,12 +728,6 @@ def M1gyro(self): return self._M1gyro - @property - def WMM(self): - if not hasattr(self, "_WMM"): - self._WMM = self.H1vecMassMatrix_density(self.derham, self, self.domain) - return self._WMM - ####################################### # Wrapper around WeightedMassOperator # ####################################### @@ -777,7 +768,7 @@ def create_weighted_mass( 1. ``str`` : for square block matrices (V=W), a symmetry can be set in order to accelerate the assembly process. Possible strings are ``symm`` (symmetric), ``asym`` (anti-symmetric) and ``diag`` (diagonal). 2. ``None`` : all blocks are allocated, disregarding zero-blocks or any symmetry. 3. ``1D list`` : 1d list consisting of either a) strings or b) matrices (3x3 callables or 3x3 list) and can be mixed. Predefined names are ``G``, ``Ginv``, ``DFinv``, ``sqrt_g``. Access them using strings in the 1d list: ``weights=['']``. Possible choices for key-value pairs in **weights** are, at the moment: ``eq_mhd``: :class:`~struphy.fields_background.base.MHDequilibrium`. To access them, use for ```` the string ``eq_``, where ```` can be found in the just mentioned base classes for MHD equilibria. By default, all scalars are multiplied. For division of scalars use ``1/``. - 4. ``2D list`` : 2d list with the same number of rows/columns as the number of components of the domain/codomain spaces. The entries can be either a) callables or b) xp.ndarrays representing the weights at the quadrature points. If an entry is zero or ``None``, the corresponding block is set to ``None`` to accelerate the dot product. + 4. ``2D list`` : 2d list with the same number of rows/columns as the number of components of the domain/codomain spaces. The entries can be either a) callables or b) np.ndarrays representing the weights at the quadrature points. If an entry is zero or ``None``, the corresponding block is set to ``None`` to accelerate the dot product. assemble: bool Whether to assemble the weighted mass matrix, i.e. computes the integrals with @@ -905,7 +896,7 @@ def DFinvT(e1, e2, e3): if weights_rank2: # if matrix exits fun = [] - if listinput and len(weights_rank2) == 1: + if listinput == True and len(weights_rank2) == 1: for m in range(3): fun += [[]] for n in range(3): @@ -918,11 +909,7 @@ def DFinvT(e1, e2, e3): for n in range(3): fun[-1] += [ lambda e1, e2, e3, m=m, n=n: self._matrix_operate(e1, e2, e3, *weights_rank2)[ - :, - :, - :, - m, - n, + :, :, :, m, n ], ] # Scalar operations second @@ -949,14 +936,14 @@ def DFinvT(e1, e2, e3): fun = [ [ lambda e1, e2, e3: 1.0 / weights_rank0[0](e1, e2, e3), - ], + ] ] for f2, op in zip(weights_rank0[1:], operations[1:]): fun = [ [ lambda e1, e2, e3, f=fun[0][0], op=op, f2=f2: self._operate(f, f2, op, e1, e2, e3), - ], + ] ] V_id = self.derham.space_to_form[V_id] @@ -990,11 +977,11 @@ def _get_range_rank(self, func): else: dummy_eta = (0.0, 0.0, 0.0) val = func(*dummy_eta) - assert isinstance(val, xp.ndarray) + assert isinstance(val, np.ndarray) out = len(val.shape) - 3 else: if isinstance(func, list): - if isinstance(func[0], xp.ndarray): + if isinstance(func[0], np.ndarray): out = 2 else: out = len(func) - 1 @@ -1037,92 +1024,6 @@ def _operate(self, f1, f2, op, e1, e2, e3): return out - ####################################### - # Aux classes (to be removed in TODO) # - ####################################### - class H1vecMassMatrix_density: - """Wrapper around a Weighted mass operator from H1vec to H1vec whose weights are given by a 3 form""" - - def __init__(self, derham, mass_ops, domain): - self._massop = mass_ops.create_weighted_mass("H1vec", "H1vec") - self.field = derham.create_spline_function("field", "L2") - - integration_grid = [grid_1d.flatten() for grid_1d in derham.quad_grid_pts["0"]] - - self.integration_grid_spans, self.integration_grid_bn, self.integration_grid_bd = ( - derham.prepare_eval_tp_fixed( - integration_grid, - ) - ) - - grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._f_values = xp.zeros(grid_shape, dtype=float) - - metric = domain.metric(*integration_grid) - self._mass_metric_term = deepcopy(metric) - self._full_term_mass = deepcopy(metric) - - @property - def massop( - self, - ): - """The WeightedMassOperator""" - return self._massop - - @property - def inv( - self, - ): - """The inverse WeightedMassOperator""" - if not hasattr(self, "_inv"): - self._create_inv() - return self._inv - - def update_weight(self, coeffs): - """Update the weighted mass matrix operator""" - - self.field.vector = coeffs - f_values = self.field.eval_tp_fixed_loc( - self.integration_grid_spans, - self.integration_grid_bd, - out=self._f_values, - ) - for i in range(3): - for j in range(3): - self._full_term_mass[i, j] = f_values * self._mass_metric_term[i, j] - - self._massop.assemble( - [ - [self._full_term_mass[0, 0], self._full_term_mass[0, 1], self._full_term_mass[0, 2]], - [ - self._full_term_mass[1, 0], - self._full_term_mass[ - 1, - 1, - ], - self._full_term_mass[1, 2], - ], - [self._full_term_mass[2, 0], self._full_term_mass[2, 1], self._full_term_mass[2, 2]], - ], - verbose=False, - ) - - if hasattr(self, "_inv") and self.inv._options["pc"] is not None: - self.inv._options["pc"].update_mass_operator(self.massop) - - def _create_inv(self, type="pcg", tol=1e-16, maxiter=500, verbose=False): - """Inverse the weighted mass matrix, preconditioner must be set outside - via self._inv._options['pc'] = ...""" - self._inv = inverse( - self.massop, - type, - pc=None, - tol=tol, - maxiter=maxiter, - verbose=verbose, - recycle=True, - ) - class WeightedMassOperatorsOldForTesting: r""" @@ -1630,7 +1531,7 @@ def M2B_div0(self): self.weights[self.selected_weight].a1_1, self.weights[self.selected_weight].a1_2, self.weights[self.selected_weight].a1_3, - ], + ] ) tmp_b2 = self.derham.curl.dot(a_eq) @@ -1744,7 +1645,7 @@ def M2Bn(self): self.weights[self.selected_weight].a1_1, self.weights[self.selected_weight].a1_2, self.weights[self.selected_weight].a1_3, - ], + ] ) tmp_b2 = self.derham.curl.dot(a_eq) @@ -1865,11 +1766,7 @@ def M1perp(self): for n in range(3): fun[-1] += [ lambda e1, e2, e3, m=m, n=n: (self.DFinv(e1, e2, e3) @ self.D @ self.DFinv(e1, e2, e3))[ - :, - :, - :, - m, - n, + :, :, :, m, n ] * self.sqrt_g( e1, @@ -1906,7 +1803,7 @@ def M0ad(self): e3, ) * self.sqrt_g(e1, e2, e3), - ], + ] ] self._M0ad = self._assemble_weighted_mass( @@ -2091,7 +1988,7 @@ class WeightedMassOperator(LinOpWithTransp): 1. ``None`` : all blocks are allocated, disregarding zero-blocks or any symmetry. 2. ``str`` : for square block matrices (V=W), a symmetry can be set in order to accelerate the assembly process. Possible strings are ``symm`` (symmetric), ``asym`` (anti-symmetric) and ``diag`` (diagonal). - 3. ``list`` : 2d list with the same number of rows/columns as the number of components of the domain/codomain spaces. The entries can be either a) callables or b) xp.ndarrays representing the weights at the quadrature points. If an entry is zero or ``None``, the corresponding block is set to ``None`` to accelerate the dot product. + 3. ``list`` : 2d list with the same number of rows/columns as the number of components of the domain/codomain spaces. The entries can be either a) callables or b) np.ndarrays representing the weights at the quadrature points. If an entry is zero or ``None``, the corresponding block is set to ``None`` to accelerate the dot product. transposed : bool Whether to assemble the transposed operator. @@ -2367,22 +2264,21 @@ def __init__( pts = [ quad_grid[nquad].points.flatten() for quad_grid, nquad in zip( - self.derham.get_quad_grids(wspace, nquads=self.nquads), - self.nquads, + self.derham.get_quad_grids(wspace, nquads=self.nquads), self.nquads ) ] if callable(weights_info[a][b]): - PTS = xp.meshgrid(*pts, indexing="ij") + PTS = np.meshgrid(*pts, indexing="ij") mat_w = weights_info[a][b](*PTS).copy() - elif isinstance(weights_info[a][b], xp.ndarray): + elif isinstance(weights_info[a][b], np.ndarray): mat_w = weights_info[a][b] assert mat_w.shape == tuple( [pt.size for pt in pts], ) - if xp.any(xp.abs(mat_w) > 1e-14): + if np.any(np.abs(mat_w) > 1e-14): if self._matrix_free: blocks[-1] += [ StencilMatrixFreeMassOperator( @@ -2478,11 +2374,9 @@ def __init__( # load assembly kernel if not self._matrix_free: - self._assembly_kernel = Pyccelkernel( - getattr( - mass_kernels, - "kernel_" + str(self._V.ldim) + "d_mat", - ), + self._assembly_kernel = getattr( + mass_kernels, + "kernel_" + str(self._V.ldim) + "d_mat", ) @property @@ -2518,10 +2412,10 @@ def tosparse(self): if all(op is None for op in (self._W_extraction_op, self._V_extraction_op)): for bl in self._V_boundary_op.bc: for bc in bl: - assert not bc, print(".tosparse() only works without boundary conditions at the moment") + assert bc == False, print(".tosparse() only works without boundary conditions at the moment") for bl in self._W_boundary_op.bc: for bc in bl: - assert not bc, print(".tosparse() only works without boundary conditions at the moment") + assert bc == False, print(".tosparse() only works without boundary conditions at the moment") return self._mat.tosparse() elif all(isinstance(op, IdentityOperator) for op in (self._W_extraction_op, self._V_extraction_op)): @@ -2534,10 +2428,10 @@ def toarray(self): if all(op is None for op in (self._W_extraction_op, self._V_extraction_op)): for bl in self._V_boundary_op.bc: for bc in bl: - assert not bc, print(".toarray() only works without boundary conditions at the moment") + assert bc == False, print(".toarray() only works without boundary conditions at the moment") for bl in self._W_boundary_op.bc: for bc in bl: - assert not bc, print(".toarray() only works without boundary conditions at the moment") + assert bc == False, print(".toarray() only works without boundary conditions at the moment") return self._mat.toarray() elif all(isinstance(op, IdentityOperator) for op in (self._W_extraction_op, self._V_extraction_op)): @@ -2698,7 +2592,7 @@ def assemble(self, weights=None, clear=True, verbose=True): Parameters ---------- weights : list | NoneType - Weight function(s) (callables or xp.ndarrays) in a 2d list of shape corresponding to + Weight function(s) (callables or np.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. If ``weights=None``, the weight is taken from the given weights in the instanziation of the object, else it will be overriden. @@ -2721,7 +2615,7 @@ def assemble(self, weights=None, clear=True, verbose=True): if weight is not None: assert callable(weight) or isinstance( weight, - xp.ndarray, + np.ndarray, ) self._mat[a, b].weights = weight @@ -2788,8 +2682,7 @@ def assemble(self, weights=None, clear=True, verbose=True): codomain_spans = [ quad_grid[nquad].spans for quad_grid, nquad in zip( - self.derham.get_quad_grids(codomain_space, nquads=self.nquads), - self.nquads, + self.derham.get_quad_grids(codomain_space, nquads=self.nquads), self.nquads ) ] @@ -2802,8 +2695,7 @@ def assemble(self, weights=None, clear=True, verbose=True): pts = [ quad_grid[nquad].points.flatten() for quad_grid, nquad in zip( - self.derham.get_quad_grids(codomain_space, nquads=self.nquads), - self.nquads, + self.derham.get_quad_grids(codomain_space, nquads=self.nquads), self.nquads ) ] wts = [ @@ -2818,8 +2710,7 @@ def assemble(self, weights=None, clear=True, verbose=True): codomain_basis = [ quad_grid[nquad].basis for quad_grid, nquad in zip( - self.derham.get_quad_grids(codomain_space, nquads=self.nquads), - self.nquads, + self.derham.get_quad_grids(codomain_space, nquads=self.nquads), self.nquads ) ] @@ -2834,13 +2725,13 @@ def assemble(self, weights=None, clear=True, verbose=True): # evaluate weight at quadrature points if callable(loc_weight): - PTS = xp.meshgrid(*pts, indexing="ij") + PTS = np.meshgrid(*pts, indexing="ij") mat_w = loc_weight(*PTS).copy() - elif isinstance(loc_weight, xp.ndarray): + elif isinstance(loc_weight, np.ndarray): mat_w = loc_weight elif loc_weight is not None: raise TypeError( - "weights must be callable or xp.ndarray or None but is {}".format( + "weights must be callable or np.ndarray or None but is {}".format( type(self._weights[a][b]), ), ) @@ -2848,8 +2739,8 @@ def assemble(self, weights=None, clear=True, verbose=True): if loc_weight is not None: assert mat_w.shape == tuple([pt.size for pt in pts]) - not_weight_zero = xp.array( - int(loc_weight is not None and xp.any(xp.abs(mat_w) > 1e-14)), + not_weight_zero = np.array( + int(loc_weight is not None and np.any(np.abs(mat_w) > 1e-14)), ) if self._mpi_comm is not None: self._mpi_comm.Allreduce( @@ -2862,8 +2753,7 @@ def assemble(self, weights=None, clear=True, verbose=True): domain_basis = [ quad_grid[nquad].basis for quad_grid, nquad in zip( - self.derham.get_quad_grids(domain_space, nquads=self.nquads), - self.nquads, + self.derham.get_quad_grids(domain_space, nquads=self.nquads), self.nquads ) ] @@ -2874,7 +2764,7 @@ def assemble(self, weights=None, clear=True, verbose=True): mat = self._mat if loc_weight is None: # in case it's none we still need to have zeros weights to call the kernel - mat_w = xp.zeros( + mat_w = np.zeros( tuple([pt.size for pt in pts]), ) else: @@ -3010,12 +2900,12 @@ def eval_quad(W, coeffs, out=None): coeffs : StencilVector | BlockVector The coefficient vector corresponding to the FEM field. Ghost regions must be up-to-date! - out : xp.ndarray | list/tuple of xp.ndarrays, optional + out : np.ndarray | list/tuple of np.ndarrays, optional If given, the result will be written into these arrays in-place. Number of outs must be compatible with number of components of FEM field. Returns ------- - out : xp.ndarray | list/tuple of xp.ndarrays + out : np.ndarray | list/tuple of np.ndarrays The values of the FEM field at the quadrature points. """ @@ -3034,7 +2924,7 @@ def eval_quad(W, coeffs, out=None): out = () if isinstance(W, TensorFemSpace): out += ( - xp.zeros( + np.zeros( [ q_grid[nquad].points.size for q_grid, nquad in zip(self.derham.get_quad_grids(W, nquads=self.nquads), self.nquads) @@ -3045,12 +2935,11 @@ def eval_quad(W, coeffs, out=None): else: for space in W.spaces: out += ( - xp.zeros( + np.zeros( [ q_grid[nquad].points.size for q_grid, nquad in zip( - self.derham.get_quad_grids(space, nquads=self.nquads), - self.nquads, + self.derham.get_quad_grids(space, nquads=self.nquads), self.nquads ) ], dtype=float, @@ -3059,13 +2948,13 @@ def eval_quad(W, coeffs, out=None): else: if isinstance(W, TensorFemSpace): - assert isinstance(out, xp.ndarray) + assert isinstance(out, np.ndarray) out = (out,) else: assert isinstance(out, (list, tuple)) # load assembly kernel - kernel = Pyccelkernel(getattr(mass_kernels, "kernel_" + str(W.ldim) + "d_eval")) + kernel = getattr(mass_kernels, "kernel_" + str(W.ldim) + "d_eval") # loop over components for a, wspace in enumerate(Wspaces): @@ -3160,22 +3049,18 @@ def __init__(self, derham, V, W, weights=None, nquads=None): self._nquads = nquads self._dtype = V.coeff_space.dtype - self._dot_kernel = Pyccelkernel( - getattr( - mass_kernels, - "kernel_" + str(self._V.ldim) + "d_matrixfree", - ), + self._dot_kernel = getattr( + mass_kernels, + "kernel_" + str(self._V.ldim) + "d_matrixfree", ) - self._diag_kernel = Pyccelkernel( - getattr( - mass_kernels, - "kernel_" + str(self._V.ldim) + "d_diag", - ), + self._diag_kernel = getattr( + mass_kernels, + "kernel_" + str(self._V.ldim) + "d_diag", ) shape = tuple(e - s + 1 for s, e in zip(V.coeff_space.starts, V.coeff_space.ends)) - self._diag_tmp = xp.zeros((shape)) + self._diag_tmp = np.zeros((shape)) # knot span indices of elements of local domain self._codomain_spans = [ @@ -3259,11 +3144,7 @@ def toarray(self): def transpose(self, conjugate=False): return StencilMatrixFreeMassOperator( - self._derham, - self._codomain, - self._domain, - self._weights, - nquads=self._nquads, + self._derham, self._codomain, self._domain, self._weights, nquads=self._nquads ) @property @@ -3306,16 +3187,16 @@ def dot(self, v, out=None): # evaluate weight at quadrature points if callable(self._weights): - PTS = xp.meshgrid(*self._pts, indexing="ij") + PTS = np.meshgrid(*self._pts, indexing="ij") mat_w = self._weights(*PTS).copy() - elif isinstance(self._weights, xp.ndarray): + elif isinstance(self._weights, np.ndarray): mat_w = self._weights if self._weights is not None: assert mat_w.shape == tuple([pt.size for pt in self._pts]) # call kernel (if mat_w is not zero) by calling the appropriate kernel (1d, 2d or 3d) - if xp.any(xp.abs(mat_w) > 1e-14): + if np.any(np.abs(mat_w) > 1e-14): self._dot_kernel( *self._codomain_spans, *self._domain_spans, @@ -3375,9 +3256,9 @@ def diagonal(self, inverse=False, sqrt=False, out=None): # evaluate weight at quadrature points if callable(self._weights): - PTS = xp.meshgrid(*self._pts, indexing="ij") + PTS = np.meshgrid(*self._pts, indexing="ij") mat_w = self._weights(*PTS).copy() - elif isinstance(self._weights, xp.ndarray): + elif isinstance(self._weights, np.ndarray): mat_w = self._weights diag = self._diag_tmp @@ -3397,12 +3278,12 @@ def diagonal(self, inverse=False, sqrt=False, out=None): # Calculate entries of StencilDiagonalMatrix if sqrt: - diag = xp.sqrt(diag) + diag = np.sqrt(diag) if inverse: - data = xp.divide(1, diag, out=data) + data = np.divide(1, diag, out=data) elif out: - xp.copyto(data, diag) + np.copyto(data, diag) else: data = diag.copy() diff --git a/src/struphy/feec/mass_kernels.py b/src/struphy/feec/mass_kernels.py index 7b4f09720..f5fc6926b 100644 --- a/src/struphy/feec/mass_kernels.py +++ b/src/struphy/feec/mass_kernels.py @@ -2,7 +2,6 @@ Integral kernels for mass matrices and L2-projections. """ -import numpy as np from numpy import shape # ================= 1d ================================= @@ -315,6 +314,8 @@ def kernel_3d_mat( The results are written into data (attention: data is NOT set to zero first, but the results are added to data). """ + import numpy as np + ne1 = spans1.size ne2 = spans2.size ne3 = spans3.size @@ -341,9 +342,7 @@ def kernel_3d_mat( for iel2 in range(ne2): for iel3 in range(ne3): tmp_mat_fun[:, :, :] = mat_fun[ - iel1 * nq1 : (iel1 + 1) * nq1, - iel2 * nq2 : (iel2 + 1) * nq2, - iel3 * nq3 : (iel3 + 1) * nq3, + iel1 * nq1 : (iel1 + 1) * nq1, iel2 * nq2 : (iel2 + 1) * nq2, iel3 * nq3 : (iel3 + 1) * nq3 ] tmp_w1[:] = w1[iel1, :] @@ -576,6 +575,8 @@ def kernel_3d_matrixfree( The results are written into data (attention: data is NOT set to zero first, but the results are added to data). """ + import numpy as np + ne1 = spansi1.size ne2 = spansi2.size ne3 = spansi3.size @@ -602,9 +603,7 @@ def kernel_3d_matrixfree( for iel2 in range(ne2): for iel3 in range(ne3): tmp_mat_fun[:, :, :] = mat_fun[ - iel1 * nq1 : (iel1 + 1) * nq1, - iel2 * nq2 : (iel2 + 1) * nq2, - iel3 * nq3 : (iel3 + 1) * nq3, + iel1 * nq1 : (iel1 + 1) * nq1, iel2 * nq2 : (iel2 + 1) * nq2, iel3 * nq3 : (iel3 + 1) * nq3 ] tmp_w1[:] = w1[iel1, :] @@ -693,6 +692,8 @@ def kernel_3d_diag( The results are written into data (attention: data is NOT set to zero first, but the results are added to data). """ + import numpy as np + ne1 = spans1.size ne2 = spans2.size ne3 = spans3.size @@ -717,9 +718,7 @@ def kernel_3d_diag( for iel2 in range(ne2): for iel3 in range(ne3): tmp_mat_fun[:, :, :] = mat_fun[ - iel1 * nq1 : (iel1 + 1) * nq1, - iel2 * nq2 : (iel2 + 1) * nq2, - iel3 * nq3 : (iel3 + 1) * nq3, + iel1 * nq1 : (iel1 + 1) * nq1, iel2 * nq2 : (iel2 + 1) * nq2, iel3 * nq3 : (iel3 + 1) * nq3 ] tmp_w1[:] = w1[iel1, :] diff --git a/src/struphy/feec/preconditioner.py b/src/struphy/feec/preconditioner.py index dfa00df4c..eab61c930 100644 --- a/src/struphy/feec/preconditioner.py +++ b/src/struphy/feec/preconditioner.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np from psydac.api.essential_bc import apply_essential_bc_stencil from psydac.ddm.cart import CartDecomposition, DomainDecomposition from psydac.fem.tensor import TensorFemSpace @@ -94,12 +94,12 @@ def fun(e): s = e.shape[0] newshape = tuple([1 if i != d else s for i in range(n_dims)]) f = e.reshape(newshape) - return xp.atleast_1d( + return np.atleast_1d( loc_weights( - *[xp.array(xp.full_like(f, 0.5)) if i != d else xp.array(f) for i in range(n_dims)], + *[np.array(np.full_like(f, 0.5)) if i != d else np.array(f) for i in range(n_dims)], ).squeeze(), ) - elif isinstance(loc_weights, xp.ndarray): + elif isinstance(loc_weights, np.ndarray): s = loc_weights.shape if d == 0: fun = loc_weights[:, s[1] // 2, s[2] // 2] @@ -108,14 +108,14 @@ def fun(e): elif d == 2: fun = loc_weights[s[0] // 2, s[1] // 2, :] elif loc_weights is None: - fun = lambda e: xp.ones(e.size, dtype=float) + fun = lambda e: np.ones(e.size, dtype=float) else: raise TypeError( - "weights needs to be callable, xp.ndarray or None but is{}".format(type(loc_weights)), + "weights needs to be callable, np.ndarray or None but is{}".format(type(loc_weights)), ) fun = [[fun]] else: - fun = [[lambda e: xp.ones(e.size, dtype=float)]] + fun = [[lambda e: np.ones(e.size, dtype=float)]] # get 1D FEM space (serial, not distributed) and quadrature order femspace_1d = femspaces[c].spaces[d] @@ -207,7 +207,7 @@ def fun(e): M_local = StencilMatrix(V_local, V_local) - row_indices, col_indices = xp.nonzero(M_arr) + row_indices, col_indices = np.nonzero(M_arr) for row_i, col_i in zip(row_indices, col_indices): # only consider row indices on process @@ -220,7 +220,7 @@ def fun(e): ] = M_arr[row_i, col_i] # check if stencil matrix was built correctly - assert xp.allclose(M_local.toarray()[s : e + 1], M_arr[s : e + 1]) + assert np.allclose(M_local.toarray()[s : e + 1], M_arr[s : e + 1]) matrixcells += [M_local.copy()] # ======================================================================================================= @@ -318,6 +318,11 @@ def solver(self): """KroneckerLinearSolver or BlockDiagonalSolver for exactly inverting the approximate mass matrix self.matrix.""" return self._solver + @property + def domain(self): + """The domain of the linear operator - an element of Vectorspace""" + return self._space + @property def codomain(self): """The codomain of the linear operator - an element of Vectorspace""" @@ -482,7 +487,7 @@ def __init__(self, mass_operator, apply_bc=True): # loop over spatial directions for d in range(n_dims): - fun = [[lambda e: xp.ones(e.size, dtype=float)]] + fun = [[lambda e: np.ones(e.size, dtype=float)]] # get 1D FEM space (serial, not distributed) and quadrature order femspace_1d = femspaces[c].spaces[d] @@ -574,7 +579,7 @@ def __init__(self, mass_operator, apply_bc=True): M_local = StencilMatrix(V_local, V_local) - row_indices, col_indices = xp.nonzero(M_arr) + row_indices, col_indices = np.nonzero(M_arr) for row_i, col_i in zip(row_indices, col_indices): # only consider row indices on process @@ -587,7 +592,7 @@ def __init__(self, mass_operator, apply_bc=True): ] = M_arr[row_i, col_i] # check if stencil matrix was built correctly - assert xp.allclose(M_local.toarray()[s : e + 1], M_arr[s : e + 1]) + assert np.allclose(M_local.toarray()[s : e + 1], M_arr[s : e + 1]) matrixcells += [M_local.copy()] # ======================================================================================================= @@ -671,7 +676,7 @@ def __init__(self, mass_operator, apply_bc=True): # Need to assemble the logical mass matrix to extract the coefficients fun = [ - [lambda e1, e2, e3: xp.ones_like(e1, dtype=float) if i == j else None for j in range(3)] for i in range(3) + [lambda e1, e2, e3: np.ones_like(e1, dtype=float) if i == j else None for j in range(3)] for i in range(3) ] log_M = WeightedMassOperator( self._mass_operator.derham, @@ -699,6 +704,9 @@ def matrix(self): def solver(self): """KroneckerLinearSolver or BlockDiagonalSolver for exactly inverting the approximate mass matrix self.matrix.""" return self._solver + + @property + def domain(self): """The domain of the linear operator - an element of Vectorspace""" return self._space @@ -856,15 +864,15 @@ class FFTSolver(BandedSolver): Parameters ---------- - circmat : xp.ndarray + circmat : np.ndarray Generic circulant matrix. """ def __init__(self, circmat): - assert isinstance(circmat, xp.ndarray) + assert isinstance(circmat, np.ndarray) assert is_circulant(circmat) - self._space = xp.ndarray + self._space = np.ndarray self._column = circmat[:, 0] # -------------------------------------- @@ -881,13 +889,13 @@ def solve(self, rhs, out=None, transposed=False): Parameters ---------- - rhs : xp.ndarray + rhs : np.ndarray The right-hand sides to solve for. The vectors are assumed to be given in C-contiguous order, i.e. if multiple right-hand sides are given, then rhs is a two-dimensional array with the 0-th index denoting the number of the right-hand side, and the 1-st index denoting the element inside a right-hand side. - out : xp.ndarray, optional + out : np.ndarray, optional Output vector. If given, it has to have the same shape and datatype as rhs. transposed : bool @@ -905,9 +913,9 @@ def solve(self, rhs, out=None, transposed=False): try: out[:] = solve_circulant(self._column, rhs.T).T - except xp.linalg.LinAlgError: + except np.linalg.LinAlgError: eps = 1e-4 - print(f"Stabilizing singular preconditioning FFTSolver with {eps =}:") + print(f"Stabilizing singular preconditioning FFTSolver with {eps = }:") self._column[0] *= 1.0 + eps out[:] = solve_circulant(self._column, rhs.T).T @@ -929,13 +937,13 @@ def is_circulant(mat): Whether the matrix is circulant (=True) or not (=False). """ - assert isinstance(mat, xp.ndarray) + assert isinstance(mat, np.ndarray) assert len(mat.shape) == 2 assert mat.shape[0] == mat.shape[1] if mat.shape[0] > 1: for i in range(mat.shape[0] - 1): - circulant = xp.allclose(mat[i, :], xp.roll(mat[i + 1, :], -1)) + circulant = np.allclose(mat[i, :], np.roll(mat[i + 1, :], -1)) if not circulant: return circulant else: diff --git a/src/struphy/feec/projectors.py b/src/struphy/feec/projectors.py index be56cc722..c8c93f668 100644 --- a/src/struphy/feec/projectors.py +++ b/src/struphy/feec/projectors.py @@ -1,6 +1,6 @@ -import cunumpy as xp +import numpy as np +from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL -from psydac.ddm.mpi import mpi as MPI from psydac.feec.global_projectors import GlobalProjector from psydac.fem.basic import FemSpace from psydac.fem.tensor import TensorFemSpace @@ -80,11 +80,7 @@ class CommutingProjector: """ def __init__( - self, - projector_tensor: GlobalProjector, - dofs_extraction_op=None, - base_extraction_op=None, - boundary_op=None, + self, projector_tensor: GlobalProjector, dofs_extraction_op=None, base_extraction_op=None, boundary_op=None ): self._projector_tensor = projector_tensor @@ -580,24 +576,24 @@ class CommutingProjectorLocal: fem_space : FemSpace FEEC space into which the functions shall be projected. - pts : list of xp.array + pts : list of np.array 3-list (or nested 3-list[3-list] for BlockVectors) of 2D arrays with the quasi-interpolation points (or Gauss-Legendre quadrature points for histopolation). In format [spatial direction](B-spline index, point) for StencilVector spaces or [vector component][spatial direction](B-spline index, point) for BlockVector spaces. - wts : list of xp.array + wts : list of np.array 3D (4D for BlockVectors) list of 2D array with the Gauss-Legendre quadrature weights (full of ones for interpolation). In format [spatial direction](B-spline index, point) for StencilVector spaces or [vector component][spatial direction](B-spline index, point) for BlockVector spaces. - wij : list of xp.array + wij : list of np.array List of 2D arrays for the coefficients :math:`\omega_j^i` obtained by inverting the local collocation matrix. Use for obtaining the FE coefficients of a function via interpolation. In format [spatial direction](B-spline index, point). - whij : list of xp.array + whij : list of np.array List of 2D arrays for the coefficients :math:`\hat{\omega}_j^i` obtained from the :math:`\omega_j^i`. Use for obtaining the FE coefficients of a function via histopolation. In format [spatial direction](D-spline index, point). @@ -643,72 +639,64 @@ def __init__( # FE space of zero forms. That means that we have B-splines in all three spatial directions. Bspaces_1d = [fem_space_B.spaces] - self._B_nbasis = xp.array([space.nbasis for space in Bspaces_1d[0]]) + self._B_nbasis = np.array([space.nbasis for space in Bspaces_1d[0]]) # Degree of the B-spline space, not to be confused with the degrees given by fem_space.spaces.degree since depending on the situation it will give the D-spline degree instead - self._p = xp.zeros(3, dtype=int) + self._p = np.zeros(3, dtype=int) for i, space in enumerate(fem_space_B.spaces): self._p[i] = space.degree # FE space of three forms. That means that we have D-splines in all three spatial directions. Dspaces_1d = [fem_space_D.spaces] - D_nbasis = xp.array([space.nbasis for space in Dspaces_1d[0]]) + D_nbasis = np.array([space.nbasis for space in Dspaces_1d[0]]) self._periodic = [] for space in fem_space.spaces: self._periodic.append(space.periodic) - self._periodic = xp.array(self._periodic) + self._periodic = np.array(self._periodic) if isinstance(fem_space, TensorFemSpace): # The comm, rank and size are only necessary for debugging. In particular, for printing stuff self._comm = self._coeff_space.cart.comm - if self._comm is None: - self._rank = 0 - self._size = 1 - else: - self._rank = self._comm.Get_rank() - self._size = self._comm.Get_size() + self._rank = self._comm.Get_rank() + self._size = self._comm.Get_size() # We get the start and endpoint for each sublist in out - self._starts = xp.array(self.coeff_space.starts) - self._ends = xp.array(self.coeff_space.ends) + self._starts = np.array(self.coeff_space.starts) + self._ends = np.array(self.coeff_space.ends) # We compute the number of FE coefficients the current MPI rank is responsible for - self._loc_num_coeff = xp.array([self._ends[i] + 1 - self._starts[i] for i in range(3)], dtype=int) + self._loc_num_coeff = np.array([self._ends[i] + 1 - self._starts[i] for i in range(3)], dtype=int) # We get the pads - self._pds = xp.array(self.coeff_space.pads) + self._pds = np.array(self.coeff_space.pads) # We get the number of spaces we have self._nsp = 1 self._localpts = [] self._index_translation = [] self._inv_index_translation = [] - self._original_pts_size = xp.zeros((3), dtype=int) + self._original_pts_size = np.zeros((3), dtype=int) elif isinstance(fem_space, VectorFemSpace): # The comm, rank and size are only necessary for debugging. In particular, for printing stuff self._comm = self._coeff_space.spaces[0].cart.comm - if self._comm is None: - self._rank = 0 - self._size = 1 - else: - self._rank = self._comm.Get_rank() - self._size = self._comm.Get_size() + self._rank = self._comm.Get_rank() + self._size = self._comm.Get_size() # we collect all starts and ends in two big lists - self._starts = xp.array([vi.starts for vi in self.coeff_space.spaces]) - self._ends = xp.array([vi.ends for vi in self.coeff_space.spaces]) + self._starts = np.array([vi.starts for vi in self.coeff_space.spaces]) + self._ends = np.array([vi.ends for vi in self.coeff_space.spaces]) # We compute the number of FE coefficients the current MPI rank is responsible for - self._loc_num_coeff = xp.array( + self._loc_num_coeff = np.array( [[self._ends[h][i] + 1 - self._starts[h][i] for i in range(3)] for h in range(3)], dtype=int, ) # We collect the pads - self._pds = xp.array([vi.pads for vi in self.coeff_space.spaces]) + self._pds = np.array([vi.pads for vi in self.coeff_space.spaces]) # We get the number of space we have self._nsp = len(self.coeff_space.spaces) @@ -724,7 +712,7 @@ def __init__( self._localpts = [[], [], []] # Here we will store the global number of points for each block entry and for each spatial direction. - self._original_pts_size = [xp.zeros((3), dtype=int), xp.zeros((3), dtype=int), xp.zeros((3), dtype=int)] + self._original_pts_size = [np.zeros((3), dtype=int), np.zeros((3), dtype=int), np.zeros((3), dtype=int)] # This will be a list of three elements (the first one for the first block element, the second one for the second block element, ...), each one being a list with three arrays, # each array will contain the B-spline indices of the corresponding spatial direction for which this MPI rank has to store at least one non-zero FE coefficient for the storage of the @@ -744,33 +732,33 @@ def __init__( self._are_zero_block_B_or_D_splines = [[], [], []] # self._Basis_function_indices_agreggated_B[i][j] = -1 if the jth B-spline is not necessary for any of the three block entries in the ith spatial direction, otherwise it is 0 - self._Basis_function_indices_agreggated_B = [-1 * xp.ones(nbasis, dtype=int) for nbasis in self._B_nbasis] - self._Basis_function_indices_agreggated_D = [-1 * xp.ones(nbasis, dtype=int) for nbasis in D_nbasis] + self._Basis_function_indices_agreggated_B = [-1 * np.ones(nbasis, dtype=int) for nbasis in self._B_nbasis] + self._Basis_function_indices_agreggated_D = [-1 * np.ones(nbasis, dtype=int) for nbasis in D_nbasis] # List that will contain the LocalProjectorsArguments for each value of h = 0,1,2. self._solve_args = [] else: - raise TypeError(f"{fem_space =} is not of type FemSpace.") + raise TypeError(f"{fem_space = } is not of type FemSpace.") if isinstance(fem_space, TensorFemSpace): if space_id == "H1": # List of list that tell us for each spatial direction whether we have Interpolation or Histopolation. IoH_for_indices = ["I", "I", "I"] # Same list as before but with bools instead of chars - self._IoH = xp.array([False, False, False], dtype=bool) + self._IoH = np.array([False, False, False], dtype=bool) # We make a list with the interpolation/histopolation weights we need for each block and each direction. self._geo_weights = [self._wij[0], self._wij[1], self._wij[2]] elif space_id == "L2": IoH_for_indices = ["H", "H", "H"] - self._IoH = xp.array([True, True, True], dtype=bool) + self._IoH = np.array([True, True, True], dtype=bool) self._geo_weights = [self._whij[0], self._whij[1], self._whij[2]] lenj1, lenj2, lenj3 = get_local_problem_size(self._periodic, self._p, self._IoH) lenj = [lenj1, lenj2, lenj3] - self._shift = xp.array([0, 0, 0], dtype=int) + self._shift = np.array([0, 0, 0], dtype=int) compute_shifts(self._IoH, self._p, self._B_nbasis, self._shift) split_points( @@ -792,7 +780,7 @@ def __init__( ) # We want to build the meshgrid for the evaluation of the degrees of freedom so it only contains the evaluation points that each specific MPI rank is actually going to use. - self._meshgrid = xp.meshgrid( + self._meshgrid = np.meshgrid( *[pt for pt in self._localpts], indexing="ij", ) @@ -931,18 +919,18 @@ def __init__( ) elif isinstance(fem_space, VectorFemSpace): - self._shift = [xp.array([0, 0, 0], dtype=int) for _ in range(3)] + self._shift = [np.array([0, 0, 0], dtype=int) for _ in range(3)] if space_id == "H1vec": # List of list that tell us for each block entry and for each spatial direction whether we have Interpolation or Histopolation. IoH_for_indices = [["I", "I", "I"], ["I", "I", "I"], ["I", "I", "I"]] # Same list as before but with bools instead of chars self._IoH = [ - xp.array([False, False, False], dtype=bool), - xp.array( + np.array([False, False, False], dtype=bool), + np.array( [False, False, False], dtype=bool, ), - xp.array([False, False, False], dtype=bool), + np.array([False, False, False], dtype=bool), ] # We make a list with the interpolation/histopolation weights we need for each block and each direction. self._geo_weights = [[self._wij[0], self._wij[1], self._wij[2]] for _ in range(3)] @@ -950,12 +938,12 @@ def __init__( elif space_id == "Hcurl": IoH_for_indices = [["H", "I", "I"], ["I", "H", "I"], ["I", "I", "H"]] self._IoH = [ - xp.array([True, False, False], dtype=bool), - xp.array( + np.array([True, False, False], dtype=bool), + np.array( [False, True, False], dtype=bool, ), - xp.array([False, False, True], dtype=bool), + np.array([False, False, True], dtype=bool), ] self._geo_weights = [ [self._whij[0], self._wij[1], self._wij[2]], @@ -970,12 +958,12 @@ def __init__( elif space_id == "Hdiv": IoH_for_indices = [["I", "H", "H"], ["H", "I", "H"], ["H", "H", "I"]] self._IoH = [ - xp.array([False, True, True], dtype=bool), - xp.array( + np.array([False, True, True], dtype=bool), + np.array( [True, False, True], dtype=bool, ), - xp.array([True, True, False], dtype=bool), + np.array([True, True, False], dtype=bool), ] self._geo_weights = [ [self._wij[0], self._whij[1], self._whij[2]], @@ -1014,7 +1002,7 @@ def __init__( # meshgrid for h component self._meshgrid.append( - xp.meshgrid( + np.meshgrid( *[pt for pt in self._localpts[h]], indexing="ij", ), @@ -1332,9 +1320,9 @@ def solve_weighted(self, rhs, out=None): if isinstance(self._fem_space, TensorFemSpace): if out is None: - out = xp.zeros((self._loc_num_coeff[0], self._loc_num_coeff[1], self._loc_num_coeff[2]), dtype=float) + out = np.zeros((self._loc_num_coeff[0], self._loc_num_coeff[1], self._loc_num_coeff[2]), dtype=float) else: - assert xp.shape(out) == (self._loc_num_coeff[0], self._loc_num_coeff[1], self._loc_num_coeff[2]) + assert np.shape(out) == (self._loc_num_coeff[0], self._loc_num_coeff[1], self._loc_num_coeff[2]) solve_local_main_loop_weighted( self._solve_args, @@ -1356,7 +1344,7 @@ def solve_weighted(self, rhs, out=None): out = [] for h in range(3): out.append( - xp.zeros( + np.zeros( ( self._loc_num_coeff[h][0], self._loc_num_coeff[h][1], @@ -1369,7 +1357,7 @@ def solve_weighted(self, rhs, out=None): else: assert len(out) == 3 for h in range(3): - assert xp.shape(out[h]) == ( + assert np.shape(out[h]) == ( self._loc_num_coeff[h][0], self._loc_num_coeff[h][1], self._loc_num_coeff[h][2], @@ -1379,7 +1367,7 @@ def solve_weighted(self, rhs, out=None): # the out block for which do_nothing tell us before hand they shall be zero. for h in range(3): if self._do_nothing[h] == 1: - out[h] = xp.zeros( + out[h] = np.zeros( ( self._loc_num_coeff[h][0], self._loc_num_coeff[h][1], @@ -1429,12 +1417,12 @@ def get_dofs(self, fun, dofs=None): fh = fun(*self._meshgrid[h])[h] # Case in which fun is a list of three functions, each one with one output. else: - assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) =}." + assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) = }." # Evaluation of the function to compute the h component fh = fun[h](*self._meshgrid[h]) # Array into which we will write the Dofs. - f_eval_aux = xp.zeros(tuple(xp.shape(dim)[0] for dim in self._localpts[h])) + f_eval_aux = np.zeros(tuple(np.shape(dim)[0] for dim in self._localpts[h])) # For 1-forms if self._space_key == "1": @@ -1446,7 +1434,7 @@ def get_dofs(self, fun, dofs=None): f_eval.append(f_eval_aux) elif self._space_key == "3": - f_eval = xp.zeros(tuple(xp.shape(dim)[0] for dim in self._localpts)) + f_eval = np.zeros(tuple(np.shape(dim)[0] for dim in self._localpts)) # Evaluation of the function at all Gauss-Legendre quadrature points faux = fun(*self._meshgrid) get_dofs_local_3_form(self._solve_args, faux, f_eval) @@ -1465,7 +1453,7 @@ def get_dofs(self, fun, dofs=None): fun, ) == 3 - ), f"List input only for vector-valued spaces of size 3, but {len(fun) =}." + ), f"List input only for vector-valued spaces of size 3, but {len(fun) = }." for h in range(3): f_eval.append(fun[h](*self._meshgrid[h])) @@ -1481,26 +1469,26 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non Builds 3D numpy array with the evaluation of the right-hand-side. """ if self._space_key == "0": - if first_go: + if first_go == True: pre_computed_dofs = [fun(*self._meshgrid)] elif self._space_key == "1" or self._space_key == "2": - assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) =}." + assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) = }." - self._do_nothing = xp.zeros(3, dtype=int) + self._do_nothing = np.zeros(3, dtype=int) f_eval = [] # If this is the first time this rank has to evaluate the weights degrees of freedom we declare the list where to store them. - if first_go: + if first_go == True: pre_computed_dofs = [] for h in range(3): # Evaluation of the function to compute the h component - if first_go: + if first_go == True: pre_computed_dofs.append(fun[h](*self._meshgrid[h])) # Array into which we will write the Dofs. - f_eval_aux = xp.zeros(tuple(xp.shape(dim)[0] for dim in self._localpts[h])) + f_eval_aux = np.zeros(tuple(np.shape(dim)[0] for dim in self._localpts[h])) # We check if the current set of basis functions is not one of those we have to compute in the current MPI rank. if ( @@ -1545,9 +1533,9 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non f_eval.append(f_eval_aux) elif self._space_key == "3": - f_eval = xp.zeros(tuple(xp.shape(dim)[0] for dim in self._localpts)) + f_eval = np.zeros(tuple(np.shape(dim)[0] for dim in self._localpts)) # Evaluation of the function at all Gauss-Legendre quadrature points - if first_go: + if first_go == True: pre_computed_dofs = [fun(*self._meshgrid)] get_dofs_local_3_form_weighted( @@ -1565,9 +1553,9 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non ) elif self._space_key == "v": - assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) =}." + assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) = }." - self._do_nothing = xp.zeros(3, dtype=int) + self._do_nothing = np.zeros(3, dtype=int) for h in range(3): # We check if the current set of basis functions is not one of those we have to compute in the current MPI rank. if ( @@ -1578,7 +1566,7 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non # We should do nothing here self._do_nothing[h] = 1 - if first_go: + if first_go == True: f_eval = [] for h in range(3): f_eval.append(fun[h](*self._meshgrid[h])) @@ -1588,7 +1576,7 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non "Uknown space. It must be either H1, Hcurl, Hdiv, L2 or H1vec.", ) - if first_go: + if first_go == True: if self._space_key == "0": return pre_computed_dofs[0], pre_computed_dofs elif self._space_key == "v": @@ -1645,23 +1633,23 @@ def __call__( set to false it means we computed it once already and we can reuse the dofs evaluation of the weights instead of recomputing them. - pre_computed_dofs : list of xp.arrays + pre_computed_dofs : list of np.arrays If we have already computed the evaluation of the weights at the dofs we can pass the arrays with their values here, so we do not have to compute them again. Returns ------- - coeffs : psydac.linalg.basic.vector | xp.array 3D + coeffs : psydac.linalg.basic.vector | np.array 3D The FEM spline coefficients after projection. """ - if not weighted: + if weighted == False: return self.solve(self.get_dofs(fun, dofs=dofs), out=out) else: # We set B_or_D and basis_indices as attributes of the projectors so we can easily access them in the get_rowstarts, get_rowends and get_values functions, where they are needed. self._B_or_D = B_or_D self._basis_indices = basis_indices - if first_go: + if first_go == True: # rhs contains the evaluation over the degrees of freedom of the weights multiplied by the basis function # rhs_weights contains the evaluation over the degrees of freedom of only the weights rhs, rhs_weights = self.get_dofs_weighted( @@ -1672,8 +1660,7 @@ def __call__( return self.solve_weighted(rhs, out=out), rhs_weights else: return self.solve_weighted( - self.get_dofs_weighted(fun, dofs=dofs, first_go=False, pre_computed_dofs=pre_computed_dofs), - out=out, + self.get_dofs_weighted(fun, dofs=dofs, first_go=False, pre_computed_dofs=pre_computed_dofs), out=out ) def get_translation_b(self, i, h): @@ -1863,7 +1850,7 @@ def __init__(self, space_id, mass_ops, **params): self._quad_grid_pts = self.mass_ops.derham.quad_grid_pts[self.space_key] if space_id in ("H1", "L2"): - self._quad_grid_mesh = xp.meshgrid( + self._quad_grid_mesh = np.meshgrid( *[pt.flatten() for pt in self.quad_grid_pts], indexing="ij", ) @@ -1873,12 +1860,12 @@ def __init__(self, space_id, mass_ops, **params): self._tmp = [] # tmp for matrix-vector product of geom_weights with fun for pts in self.quad_grid_pts: self._quad_grid_mesh += [ - xp.meshgrid( + np.meshgrid( *[pt.flatten() for pt in pts], indexing="ij", ), ] - self._tmp += [xp.zeros_like(self.quad_grid_mesh[-1][0])] + self._tmp += [np.zeros_like(self.quad_grid_mesh[-1][0])] # geometric weights evaluated at quadrature grid self._geom_weights = [] # loop over rows (different meshes) @@ -1889,7 +1876,7 @@ def __init__(self, space_id, mass_ops, **params): if weight is not None: self._geom_weights[-1] += [weight(*mesh)] else: - self._geom_weights[-1] += [xp.zeros_like(mesh[0])] + self._geom_weights[-1] += [np.zeros_like(mesh[0])] # other quad grid info if isinstance(self.space, TensorFemSpace): @@ -2015,7 +2002,7 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): Parameters ---------- fun : callable | list - Weight function(s) (callables or xp.ndarrays) in a 1d list of shape corresponding to number of components. + Weight function(s) (callables or np.ndarrays) in a 1d list of shape corresponding to number of components. dofs : StencilVector | BlockVector, optional The vector for the output. @@ -2030,9 +2017,9 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): # evaluate fun at quad_grid or check array size if callable(fun): fun_weights = fun(*self._quad_grid_mesh) - elif isinstance(fun, xp.ndarray): + elif isinstance(fun, np.ndarray): assert fun.shape == self._quad_grid_mesh[0].shape, ( - f"Expected shape {self._quad_grid_mesh[0].shape}, got {fun.shape =} instead." + f"Expected shape {self._quad_grid_mesh[0].shape}, got {fun.shape = } instead." ) fun_weights = fun else: @@ -2041,7 +2028,7 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): fun, ) == 3 - ), f"List input only for vector-valued spaces of size 3, but {len(fun) =}." + ), f"List input only for vector-valued spaces of size 3, but {len(fun) = }." fun_weights = [] # loop over rows (different meshes) for mesh in self._quad_grid_mesh: @@ -2050,12 +2037,12 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): for f in fun: if callable(f): fun_weights[-1] += [f(*mesh)] - elif isinstance(f, xp.ndarray): - assert f.shape == mesh[0].shape, f"Expected shape {mesh[0].shape}, got {f.shape =} instead." + elif isinstance(f, np.ndarray): + assert f.shape == mesh[0].shape, f"Expected shape {mesh[0].shape}, got {f.shape = } instead." fun_weights[-1] += [f] else: raise ValueError( - f"Expected callable or numpy array, got {type(f) =} instead.", + f"Expected callable or numpy array, got {type(f) = } instead.", ) # check output vector @@ -2067,7 +2054,7 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): # compute matrix data for kernel, i.e. fun * geom_weight tot_weights = [] - if isinstance(fun_weights, xp.ndarray): + if isinstance(fun_weights, np.ndarray): tot_weights += [fun_weights * self.geom_weights] else: # loop over rows (differnt meshes) diff --git a/src/struphy/feec/psydac_derham.py b/src/struphy/feec/psydac_derham.py index e5a8cde32..4fb115a0c 100644 --- a/src/struphy/feec/psydac_derham.py +++ b/src/struphy/feec/psydac_derham.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 import importlib.metadata -import cunumpy as xp +import numpy as np import psydac.core.bsplines as bsp +from mpi4py import MPI +from mpi4py.MPI import Intracomm from psydac.ddm.cart import DomainDecomposition -from psydac.ddm.mpi import MockComm, MockMPI -from psydac.ddm.mpi import mpi as MPI from psydac.feec.derivatives import Curl_3D, Divergence_3D, Gradient_3D from psydac.feec.global_projectors import Projector_H1, Projector_H1vec, Projector_Hcurl, Projector_Hdiv, Projector_L2 from psydac.fem.grid import FemAssemblyGrid @@ -96,7 +96,7 @@ def __init__( dirichlet_bc: list | tuple = None, nquads: list | tuple = None, nq_pr: list | tuple = None, - comm=None, + comm: Intracomm = None, mpi_dims_mask: list = None, with_projectors: bool = True, polar_ck: int = -1, @@ -117,7 +117,7 @@ def __init__( if dirichlet_bc is not None: assert len(dirichlet_bc) == 3 # make sure that boundary conditions are compatible with spline space - assert xp.all([bc == (False, False) for i, bc in enumerate(dirichlet_bc) if spl_kind[i]]) + assert np.all([bc == (False, False) for i, bc in enumerate(dirichlet_bc) if spl_kind[i]]) self._dirichlet_bc = dirichlet_bc @@ -300,7 +300,7 @@ def __init__( fag.basis, ] - self._spline_types_pyccel[sp_form][-1] = xp.array( + self._spline_types_pyccel[sp_form][-1] = np.array( self._spline_types_pyccel[sp_form][-1], ) # In this case we are working with a scalar valued space @@ -352,11 +352,11 @@ def __init__( self._quad_grid_spans[sp_form] += [fag.spans] self._quad_grid_bases[sp_form] += [fag.basis] - self._spline_types_pyccel[sp_form] = xp.array( + self._spline_types_pyccel[sp_form] = np.array( self._spline_types_pyccel[sp_form], ) else: - raise TypeError(f"{fem_space =} is not a valid type.") + raise TypeError(f"{fem_space = } is not a valid type.") # break points self._breaks = [space.breaks for space in _derham.spaces[0].spaces] @@ -364,8 +364,8 @@ def __init__( # index arrays self._indN = [ ( - xp.indices((space.ncells, space.degree + 1))[1] - + xp.arange( + np.indices((space.ncells, space.degree + 1))[1] + + np.arange( space.ncells, )[:, None] ) @@ -374,8 +374,8 @@ def __init__( ] self._indD = [ ( - xp.indices((space.ncells, space.degree + 1))[1] - + xp.arange( + np.indices((space.ncells, space.degree + 1))[1] + + np.arange( space.ncells, )[:, None] ) @@ -384,6 +384,8 @@ def __init__( ] # distribute info on domain decomposition + self._domain_decomposition = self._Vh["0"].cart.domain_decomposition + self._domain_array = self._get_domain_array() self._breaks_loc = [ self.breaks[k][self.domain_decomposition.starts[k] : self.domain_decomposition.ends[k] + 2] @@ -391,7 +393,7 @@ def __init__( ] self._index_array = self._get_index_array( - self.domain_decomposition, + self._domain_decomposition, ) self._index_array_N = self._get_index_array(self._Vh["0"].cart) self._index_array_D = self._get_index_array(self._Vh["3"].cart) @@ -525,11 +527,11 @@ def __init__( # collect arguments for kernels self._args_derham = DerhamArguments( - xp.array(self.p), + np.array(self.p), self.Vh_fem["0"].knots[0], self.Vh_fem["0"].knots[1], self.Vh_fem["0"].knots[2], - xp.array(self.Vh["0"].starts), + np.array(self.Vh["0"].starts), ) @property @@ -798,7 +800,7 @@ def init_derham( Nel: tuple | list, p: tuple | list, spl_kind: tuple | list, - comm=None, + comm: Intracomm = None, mpi_dims_mask: tuple | list = None, ): """Discretize the Derahm complex. Allows for the use of tiny-psydac. @@ -826,13 +828,12 @@ def init_derham( if "dev" in psydac_ver: # use tiny-psydac version - self._domain_decomposition = DomainDecomposition(Nel, spl_kind, comm=comm, mpi_dims_mask=mpi_dims_mask) - + ddm = DomainDecomposition(Nel, spl_kind, comm=comm, mpi_dims_mask=mpi_dims_mask) _derham = self._discretize_derham( Nel=Nel, p=p, spl_kind=spl_kind, - ddm=self.domain_decomposition, + ddm=ddm, ) else: from psydac.api.discretization import discretize @@ -1062,7 +1063,7 @@ def _discretize_space( ) # Create uniform grid - grids = [xp.linspace(xmin, xmax, num=ne + 1) for xmin, xmax, ne in zip(min_coords, max_coords, ncells)] + grids = [np.linspace(xmin, xmax, num=ne + 1) for xmin, xmax, ne in zip(min_coords, max_coords, ncells)] # Create 1D finite element spaces and precompute quadrature data spaces_1d = [ @@ -1093,7 +1094,7 @@ def _discretize_space( elif V == "L2": Wh = Vh.reduce_degree(axes=[0, 1, 2], multiplicity=Vh.multiplicity, basis=basis) else: - raise ValueError(f"V must be one of H1, Hcurl, Hdiv or L2, but is {V =}.") + raise ValueError(f"V must be one of H1, Hcurl, Hdiv or L2, but is {V = }.") Wh.symbolic_space = V for key in Wh._refined_space: @@ -1107,7 +1108,7 @@ def _get_domain_array(self): Returns ------- - dom_arr : xp.ndarray + dom_arr : np.ndarray A 2d array of shape (#MPI processes, 9). The row index denotes the process rank. The columns are for n=0,1,2: - arr[i, 3*n + 0] holds the LEFT domain boundary of process i in direction eta_(n+1). - arr[i, 3*n + 1] holds the RIGHT domain boundary of process i in direction eta_(n+1). @@ -1121,10 +1122,10 @@ def _get_domain_array(self): nproc = 1 # send buffer - dom_arr_loc = xp.zeros(9, dtype=float) + dom_arr_loc = np.zeros(9, dtype=float) # main array (receive buffers) - dom_arr = xp.zeros(nproc * 9, dtype=float) + dom_arr = np.zeros(nproc * 9, dtype=float) # Get global starts and ends of domain decomposition gl_s = self.domain_decomposition.starts @@ -1137,7 +1138,7 @@ def _get_domain_array(self): dom_arr_loc[3 * n + 2] = el_end - el_sta + 1 # distribute - if not isinstance(self.comm, (MockComm, type(None))): + if self.comm is not None: self.comm.Allgather(dom_arr_loc, dom_arr) else: dom_arr[:] = dom_arr_loc @@ -1155,23 +1156,23 @@ def _get_index_array(self, decomposition): Returns ------- - ind_arr : xp.ndarray + ind_arr : np.ndarray A 2d array of shape (#MPI processes, 6). The row index denotes the process rank. The columns are for n=0,1,2: - arr[i, 2*n + 0] holds the global start index process i in direction eta_(n+1). - arr[i, 2*n + 1] holds the global end index of process i in direction eta_(n+1). """ # MPI info - if not isinstance(self.comm, (MockComm, type(None))): + if self.comm is not None: nproc = self.comm.Get_size() else: nproc = 1 # send buffer - ind_arr_loc = xp.zeros(6, dtype=int) + ind_arr_loc = np.zeros(6, dtype=int) # main array (receive buffers) - ind_arr = xp.zeros(nproc * 6, dtype=int) + ind_arr = np.zeros(nproc * 6, dtype=int) # Get global starts and ends of cart OR domain decomposition gl_s = decomposition.starts @@ -1183,7 +1184,7 @@ def _get_index_array(self, decomposition): ind_arr_loc[2 * n + 1] = end # distribute - if not isinstance(self.comm, (MockComm, type(None))): + if self.comm is not None: self.comm.Allgather(ind_arr_loc, ind_arr) else: ind_arr[:] = ind_arr_loc @@ -1214,13 +1215,13 @@ def _get_neighbours(self): Returns ------- - neighbours : xp.ndarray + neighbours : np.ndarray A 3d array of shape (3,3,3). The i-th axis is the direction eta_(i+1). Neighbours along the faces have index with two 1s, neighbours along the edges only have one 1, neighbours along the edges have no 1 in the index. """ - neighs = xp.empty((3, 3, 3), dtype=int) + neighs = np.empty((3, 3, 3), dtype=int) for i in range(3): for j in range(3): @@ -1265,12 +1266,12 @@ def _get_neighbour_one_component(self, comp): if comp == [1, 1, 1]: return neigh_id - comp = xp.array(comp) - kinds = xp.array(kinds) + comp = np.array(comp) + kinds = np.array(kinds) # if only one process: check if comp is neighbour in non-peridic directions, if this is not the case then return the rank as neighbour id if size == 1: - if (comp[~kinds] == 1).all(): + if (comp[kinds == False] == 1).all(): return rank # multiple processes @@ -1301,15 +1302,15 @@ def _get_neighbour_one_component(self, comp): "Wrong value for component; must be 0 or 1 or 2 !", ) - neigh_inds = xp.array(neigh_inds) + neigh_inds = np.array(neigh_inds) # only use indices where information is present to find the neighbours rank - inds = xp.where(neigh_inds != None) + inds = np.where(neigh_inds != None) # find ranks (row index of domain_array) which agree in start/end indices - index_temp = xp.squeeze(self.index_array[:, inds]) - unique_ranks = xp.where( - xp.equal(index_temp, neigh_inds[inds]).all(1), + index_temp = np.squeeze(self.index_array[:, inds]) + unique_ranks = np.where( + np.equal(index_temp, neigh_inds[inds]).all(1), )[0] # if any row satisfies condition, return its index (=rank of neighbour) @@ -1329,7 +1330,7 @@ def _get_span_and_basis_for_eval_mpi(self, etas, Nspace, end): Parameters ---------- - etas : xp.array + etas : np.array 1d array of evaluation points (ascending). Nspace : SplineSpace @@ -1340,13 +1341,13 @@ def _get_span_and_basis_for_eval_mpi(self, etas, Nspace, end): Returns ------- - spans : xp.array + spans : np.array 1d array of knot span indices. - bn : xp.array + bn : np.array 2d array of pn + 1 values of N-splines indexed by (eta, spline value). - bd : xp.array + bd : np.array 2d array of pn values of D-splines indexed by (eta, spline value). """ @@ -1356,11 +1357,11 @@ def _get_span_and_basis_for_eval_mpi(self, etas, Nspace, end): Tn = Nspace.knots pn = Nspace.degree - spans = xp.zeros(etas.size, dtype=int) - bns = xp.zeros((etas.size, pn + 1), dtype=float) - bds = xp.zeros((etas.size, pn), dtype=float) - bn = xp.zeros(pn + 1, dtype=float) - bd = xp.zeros(pn, dtype=float) + spans = np.zeros(etas.size, dtype=int) + bns = np.zeros((etas.size, pn + 1), dtype=float) + bds = np.zeros((etas.size, pn), dtype=float) + bn = np.zeros(pn + 1, dtype=float) + bd = np.zeros(pn, dtype=float) for n in range(etas.size): # avoid 1. --> 0. for clamped interpolation @@ -1538,7 +1539,7 @@ def vector(self, value): """In-place setter for Stencil-/Block-/PolarVector.""" if isinstance(self._vector, StencilVector): - assert isinstance(value, (StencilVector, xp.ndarray)) + assert isinstance(value, (StencilVector, np.ndarray)) s1, s2, s3 = self.starts e1, e2, e3 = self.ends @@ -1561,10 +1562,10 @@ def vector(self, value): self._vector.set_vector(value) else: if isinstance(self._vector.tp, StencilVector): - assert isinstance(value[0], xp.ndarray) + assert isinstance(value[0], np.ndarray) assert isinstance( value[1], - (StencilVector, xp.ndarray), + (StencilVector, np.ndarray), ) self._vector.pol[0][:] = value[0][:] @@ -1573,16 +1574,14 @@ def vector(self, value): e1, e2, e3 = self.ends self._vector.tp[s1 : e1 + 1, s2 : e2 + 1, s3 : e3 + 1] = value[1][ - s1 : e1 + 1, - s2 : e2 + 1, - s3 : e3 + 1, + s1 : e1 + 1, s2 : e2 + 1, s3 : e3 + 1 ] else: for n in range(3): - assert isinstance(value[n][0], xp.ndarray) + assert isinstance(value[n][0], np.ndarray) assert isinstance( value[n][1], - (StencilVector, xp.ndarray), + (StencilVector, np.ndarray), ) self._vector.pol[n][:] = value[n][0][:] @@ -1591,9 +1590,7 @@ def vector(self, value): e1, e2, e3 = self.ends[n] self._vector.tp[n][s1 : e1 + 1, s2 : e2 + 1, s3 : e3 + 1] = value[n][1][ - s1 : e1 + 1, - s2 : e2 + 1, - s3 : e3 + 1, + s1 : e1 + 1, s2 : e2 + 1, s3 : e3 + 1 ] self._vector.update_ghost_regions() @@ -1736,13 +1733,13 @@ def f_tmp(e1, e2, e3): else: assert equil is not None var = fb.variable - assert var in dir(MHDequilibrium), f"{var =} is not an attribute of any fields background." + assert var in dir(MHDequilibrium), f"{var = } is not an attribute of any fields background." if self.space_id in {"H1", "L2"}: fun = getattr(equil, var) else: assert (var + "_1") in dir(MHDequilibrium), ( - f"{(var + '_1') =} is not an attribute of any fields background." + f"{(var + '_1') = } is not an attribute of any fields background." ) fun = [ getattr(equil, var + "_1"), @@ -1888,7 +1885,7 @@ def eval_tp_fixed_loc(self, spans, bases, out=None): assert [span.size for span in spans] == [base.shape[0] for base in bases] if out is None: - out = xp.empty([span.size for span in spans], dtype=float) + out = np.empty([span.size for span in spans], dtype=float) else: assert out.shape == tuple([span.size for span in spans]) @@ -1897,8 +1894,8 @@ def eval_tp_fixed_loc(self, spans, bases, out=None): *bases, vec._data, self.derham.spline_types_pyccel[self.space_key], - xp.array(self.derham.p), - xp.array(self.starts), + np.array(self.derham.p), + np.array(self.starts), out, ) @@ -1912,7 +1909,7 @@ def eval_tp_fixed_loc(self, spans, bases, out=None): assert [span.size for span in spans] == [base.shape[0] for base in bases[i]] if out_is_none: - out += xp.empty( + out += np.empty( [span.size for span in spans], dtype=float, ) @@ -1926,10 +1923,10 @@ def eval_tp_fixed_loc(self, spans, bases, out=None): *bases[i], vec[i]._data, self.derham.spline_types_pyccel[self.space_key][i], - xp.array( + np.array( self.derham.p, ), - xp.array( + np.array( self.starts[i], ), out[i], @@ -1996,14 +1993,14 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): # prepare arrays for AllReduce if tmp is None: - tmp = xp.zeros( + tmp = np.zeros( tmp_shape, dtype=float, ) else: - assert isinstance(tmp, xp.ndarray) + assert isinstance(tmp, np.ndarray) assert tmp.shape == tmp_shape - assert tmp.dtype.type is xp.float64 + assert tmp.dtype.type is np.float64 tmp[:] = 0.0 # scalar-valued field @@ -2018,11 +2015,11 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): E3, self._vector_stencil._data, kind, - xp.array(self.derham.p), + np.array(self.derham.p), T1, T2, T3, - xp.array(self.starts), + np.array(self.starts), tmp, ) elif marker_evaluation: @@ -2031,11 +2028,11 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): markers, self._vector_stencil._data, kind, - xp.array(self.derham.p), + np.array(self.derham.p), T1, T2, T3, - xp.array(self.starts), + np.array(self.starts), tmp, ) else: @@ -2046,16 +2043,16 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): E3, self._vector_stencil._data, kind, - xp.array(self.derham.p), + np.array(self.derham.p), T1, T2, T3, - xp.array(self.starts), + np.array(self.starts), tmp, ) if self.derham.comm is not None: - if not local: + if local == False: self.derham.comm.Allreduce( MPI.IN_PLACE, tmp, @@ -2070,7 +2067,7 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): out += tmp if squeeze_out: - out = xp.squeeze(out) + out = np.squeeze(out) if out.ndim == 0: out = out.item() @@ -2089,11 +2086,11 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): E3, self._vector_stencil[n]._data, kind, - xp.array(self.derham.p), + np.array(self.derham.p), T1, T2, T3, - xp.array(self.starts[n]), + np.array(self.starts[n]), tmp, ) elif marker_evaluation: @@ -2102,11 +2099,11 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): markers, self._vector_stencil[n]._data, kind, - xp.array(self.derham.p), + np.array(self.derham.p), T1, T2, T3, - xp.array(self.starts[n]), + np.array(self.starts[n]), tmp, ) else: @@ -2117,16 +2114,16 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): E3, self._vector_stencil[n]._data, kind, - xp.array(self.derham.p), + np.array(self.derham.p), T1, T2, T3, - xp.array(self.starts[n]), + np.array(self.starts[n]), tmp, ) if self.derham.comm is not None: - if not local: + if local == False: self.derham.comm.Allreduce( MPI.IN_PLACE, tmp, @@ -2143,7 +2140,7 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): tmp[:] = 0.0 if squeeze_out: - out[-1] = xp.squeeze(out[-1]) + out[-1] = np.squeeze(out[-1]) if out[-1].ndim == 0: out[-1] = out[-1].item() @@ -2176,11 +2173,11 @@ def _flag_pts_not_on_proc(self, *etas): markers = etas[0] # check which particles are on the current process domain - is_on_proc_domain = xp.logical_and( + is_on_proc_domain = np.logical_and( markers[:, :3] >= dom_arr[rank, 0::3], markers[:, :3] <= dom_arr[rank, 1::3], ) - on_proc = xp.all(is_on_proc_domain, axis=1) + on_proc = np.all(is_on_proc_domain, axis=1) markers[~on_proc, :] = -1.0 @@ -2206,15 +2203,15 @@ def _flag_pts_not_on_proc(self, *etas): E3[E3 == dom_arr[rank, 7]] += 1e-8 # True for eval points on current process - E1_on_proc = xp.logical_and( + E1_on_proc = np.logical_and( E1 >= dom_arr[rank, 0], E1 <= dom_arr[rank, 1], ) - E2_on_proc = xp.logical_and( + E2_on_proc = np.logical_and( E2 >= dom_arr[rank, 3], E2 <= dom_arr[rank, 4], ) - E3_on_proc = xp.logical_and( + E3_on_proc = np.logical_and( E3 >= dom_arr[rank, 6], E3 <= dom_arr[rank, 7], ) @@ -2375,7 +2372,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): Returns ------- - _amps : xp.array + _amps : np.array The noisy FE coefficients in the desired direction (1d, 2d or 3d array).""" if self.derham.comm is not None: @@ -2390,40 +2387,40 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): domain_array = self.derham.domain_array if seed is not None: - xp.random.seed(seed) + np.random.seed(seed) # temporary - _amps = xp.zeros(shapes) + _amps = np.zeros(shapes) # no process has been drawn for yet - already_drawn = xp.zeros(nprocs) == 1.0 + already_drawn = np.zeros(nprocs) == 1.0 # 1d mid point arrays in each direction mid_points = [] for npr in nprocs: delta = 1.0 / npr - mid_points_i = xp.zeros(npr) + mid_points_i = np.zeros(npr) for n in range(npr): mid_points_i[n] = delta * (n + 1 / 2) mid_points += [mid_points_i] if direction == "e1": - tmp_arrays = xp.zeros(nprocs[0]).tolist() + tmp_arrays = np.zeros(nprocs[0]).tolist() elif direction == "e2": - tmp_arrays = xp.zeros(nprocs[1]).tolist() + tmp_arrays = np.zeros(nprocs[1]).tolist() elif direction == "e3": - tmp_arrays = xp.zeros(nprocs[2]).tolist() + tmp_arrays = np.zeros(nprocs[2]).tolist() elif direction == "e1e2": - tmp_arrays = xp.zeros((nprocs[0], nprocs[1])).tolist() + tmp_arrays = np.zeros((nprocs[0], nprocs[1])).tolist() Warning, f"2d noise in the directions {direction} is not correctly initilaized for MPI !!" elif direction == "e1e3": - tmp_arrays = xp.zeros((nprocs[0], nprocs[2])).tolist() + tmp_arrays = np.zeros((nprocs[0], nprocs[2])).tolist() Warning, f"2d noise in the directions {direction} is not correctly initilaized for MPI !!" elif direction == "e2e3": - tmp_arrays = xp.zeros((nprocs[1], nprocs[2])).tolist() + tmp_arrays = np.zeros((nprocs[1], nprocs[2])).tolist() Warning, f"2d noise in the directions {direction} is not correctly initilaized for MPI !!" elif direction == "e1e2e3": - tmp_arrays = xp.zeros((nprocs[0], nprocs[1], nprocs[2])).tolist() + tmp_arrays = np.zeros((nprocs[0], nprocs[1], nprocs[2])).tolist() Warning, f"3d noise in the directions {direction} is not correctly initilaized for MPI !!" else: raise ValueError("Invalid direction for tmp_arrays.") @@ -2432,7 +2429,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): inds_current = [] for n in range(3): mid_pt_current = (domain_array[rank, 3 * n] + domain_array[rank, 3 * n + 1]) / 2.0 - inds_current += [xp.argmin(xp.abs(mid_points[n] - mid_pt_current))] + inds_current += [np.argmin(np.abs(mid_points[n] - mid_pt_current))] # loop over processes for i in range(comm_size): @@ -2440,7 +2437,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): inds = [] for n in range(3): mid_pt = (domain_array[i, 3 * n] + domain_array[i, 3 * n + 1]) / 2.0 - inds += [xp.argmin(xp.abs(mid_points[n] - mid_pt))] + inds += [np.argmin(np.abs(mid_points[n] - mid_pt))] if already_drawn[inds[0], inds[1], inds[2]]: if direction == "e1": @@ -2462,7 +2459,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): if direction == "e1": tmp_arrays[inds[0]] = ( ( - xp.random.rand( + np.random.rand( *shapes, ) - 0.5 @@ -2475,7 +2472,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): elif direction == "e2": tmp_arrays[inds[1]] = ( ( - xp.random.rand( + np.random.rand( *shapes, ) - 0.5 @@ -2488,7 +2485,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): elif direction == "e3": tmp_arrays[inds[2]] = ( ( - xp.random.rand( + np.random.rand( *shapes, ) - 0.5 @@ -2499,23 +2496,23 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): already_drawn[:, :, inds[2]] = True _amps[:] = tmp_arrays[inds[2]] elif direction == "e1e2": - tmp_arrays[inds[0]][inds[1]] = (xp.random.rand(*shapes) - 0.5) * 2.0 * amp + tmp_arrays[inds[0]][inds[1]] = (np.random.rand(*shapes) - 0.5) * 2.0 * amp already_drawn[inds[0], inds[1], :] = True _amps[:] = tmp_arrays[inds[0]][inds[1]] elif direction == "e1e3": - tmp_arrays[inds[0]][inds[2]] = (xp.random.rand(*shapes) - 0.5) * 2.0 * amp + tmp_arrays[inds[0]][inds[2]] = (np.random.rand(*shapes) - 0.5) * 2.0 * amp already_drawn[inds[0], :, inds[2]] = True _amps[:] = tmp_arrays[inds[0]][inds[2]] elif direction == "e2e3": - tmp_arrays[inds[1]][inds[2]] = (xp.random.rand(*shapes) - 0.5) * 2.0 * amp + tmp_arrays[inds[1]][inds[2]] = (np.random.rand(*shapes) - 0.5) * 2.0 * amp already_drawn[:, inds[1], inds[2]] = True _amps[:] = tmp_arrays[inds[1]][inds[2]] elif direction == "e1e2e3": - tmp_arrays[inds[0]][inds[1]][inds[2]] = (xp.random.rand(*shapes) - 0.5) * 2.0 * amp + tmp_arrays[inds[0]][inds[1]][inds[2]] = (np.random.rand(*shapes) - 0.5) * 2.0 * amp already_drawn[inds[0], inds[1], inds[2]] = True _amps[:] = tmp_arrays[inds[0]][inds[1]][inds[2]] - if xp.all(xp.array([ind_c == ind for ind_c, ind in zip(inds_current, inds)])): + if np.all(np.array([ind_c == ind for ind_c, ind in zip(inds_current, inds)])): return _amps @@ -2766,16 +2763,16 @@ def get_pts_and_wts(space_1d, start, end, n_quad=None, polar_shift=False): histopol_loc = space_1d.histopolation_grid[start : end + 2].copy() # make sure that greville points used for interpolation are in [0, 1] - assert xp.all(xp.logical_and(greville_loc >= 0.0, greville_loc <= 1.0)) + assert np.all(np.logical_and(greville_loc >= 0.0, greville_loc <= 1.0)) # interpolation if space_1d.basis == "B": x_grid = greville_loc pts = greville_loc[:, None] - wts = xp.ones(pts.shape, dtype=float) + wts = np.ones(pts.shape, dtype=float) # sub-interval index is always 0 for interpolation. - subs = xp.zeros(pts.shape[0], dtype=int) + subs = np.zeros(pts.shape[0], dtype=int) # !! shift away first interpolation point in eta_1 direction for polar domains !! if pts[0] == 0.0 and polar_shift: @@ -2789,27 +2786,27 @@ def get_pts_and_wts(space_1d, start, end, n_quad=None, polar_shift=False): union_breaks = space_1d.breaks[:-1] # Make union of Greville and break points - tmp = set(xp.round(space_1d.histopolation_grid, decimals=14)).union( - xp.round(union_breaks, decimals=14), + tmp = set(np.round(space_1d.histopolation_grid, decimals=14)).union( + np.round(union_breaks, decimals=14), ) tmp = list(tmp) tmp.sort() - tmp_a = xp.array(tmp) + tmp_a = np.array(tmp) x_grid = tmp_a[ - xp.logical_and( + np.logical_and( tmp_a - >= xp.min( + >= np.min( histopol_loc, ) - 1e-14, - tmp_a <= xp.max(histopol_loc) + 1e-14, + tmp_a <= np.max(histopol_loc) + 1e-14, ) ] # determine subinterval index (= 0 or 1): - subs = xp.zeros(x_grid[:-1].size, dtype=int) + subs = np.zeros(x_grid[:-1].size, dtype=int) for n, x_h in enumerate(x_grid[:-1]): add = 1 for x_g in histopol_loc: @@ -2822,7 +2819,7 @@ def get_pts_and_wts(space_1d, start, end, n_quad=None, polar_shift=False): # products of basis functions are integrated exactly n_quad = space_1d.degree + 1 - pts_loc, wts_loc = xp.polynomial.legendre.leggauss(n_quad) + pts_loc, wts_loc = np.polynomial.legendre.leggauss(n_quad) x, wts = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) @@ -2885,12 +2882,12 @@ def get_pts_and_wts_quasi( # interpolation if space_1d.basis == "B": if p == 1 and h != 1.0: - x_grid = xp.linspace(-(p - 1) * h, 1.0 - h + (h / 2.0), (N + p - 1) * 2) + x_grid = np.linspace(-(p - 1) * h, 1.0 - h + (h / 2.0), (N + p - 1) * 2) else: - x_grid = xp.linspace(-(p - 1) * h, 1.0 - h, (N + p - 1) * 2 - 1) + x_grid = np.linspace(-(p - 1) * h, 1.0 - h, (N + p - 1) * 2 - 1) pts = x_grid[:, None] % 1.0 - wts = xp.ones(pts.shape, dtype=float) + wts = np.ones(pts.shape, dtype=float) # !! shift away first interpolation point in eta_1 direction for polar domains !! if pts[0] == 0.0 and polar_shift: @@ -2901,16 +2898,16 @@ def get_pts_and_wts_quasi( # The computation of histopolation points breaks in case we have Nel=1 and periodic boundary conditions since we end up with only one x_grid point. # We need to build the histopolation points by hand in this scenario. if p == 0 and h == 1.0: - x_grid = xp.array([0.0, 0.5, 1.0]) + x_grid = np.array([0.0, 0.5, 1.0]) elif p == 0 and h != 1.0: - x_grid = xp.linspace(-p * h, 1.0 - h + (h / 2.0), (N + p) * 2) + x_grid = np.linspace(-p * h, 1.0 - h + (h / 2.0), (N + p) * 2) else: - x_grid = xp.linspace(-p * h, 1.0 - h, (N + p) * 2 - 1) + x_grid = np.linspace(-p * h, 1.0 - h, (N + p) * 2 - 1) n_quad = p + 1 # Gauss - Legendre quadrature points and weights # products of basis functions are integrated exactly - pts_loc, wts_loc = xp.polynomial.legendre.leggauss(n_quad) + pts_loc, wts_loc = np.polynomial.legendre.leggauss(n_quad) x, wts = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) pts = x % 1.0 @@ -2924,26 +2921,26 @@ def get_pts_and_wts_quasi( N_b = N + p # Filling the quasi-interpolation points for i=0 and i=1 (since they are equal) - x_grid = xp.linspace(0.0, knots[p + 1], p + 1) - x_aux = xp.linspace(0.0, knots[p + 1], p + 1) - x_grid = xp.append(x_grid, x_aux) + x_grid = np.linspace(0.0, knots[p + 1], p + 1) + x_aux = np.linspace(0.0, knots[p + 1], p + 1) + x_grid = np.append(x_grid, x_aux) # Now we append those for 1 V2):") res_PSY = OPS_PSY.Q1.dot(x1_st) - res_STR = OPS_STR.Q1_dot(xp.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) + res_STR = OPS_STR.Q1_dot(np.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_2(res_STR) MPI_COMM.Barrier() @@ -284,7 +284,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): Q1T = OPS_PSY.Q1.transpose() res_PSY = Q1T.dot(x2_st) - res_STR = OPS_STR.transpose_Q1_dot(xp.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) + res_STR = OPS_STR.transpose_Q1_dot(np.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_1(res_STR) MPI_COMM.Barrier() @@ -310,7 +310,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): print("\nW1 (V1 --> V1, Identity operator in this case):") res_PSY = OPS_PSY.W1.dot(x1_st) - res_STR = OPS_STR.W1_dot(xp.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) + res_STR = OPS_STR.W1_dot(np.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_1(res_STR) MPI_COMM.barrier() @@ -333,7 +333,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): W1T = OPS_PSY.W1.transpose() res_PSY = W1T.dot(x1_st) - res_STR = OPS_STR.transpose_W1_dot(xp.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) + res_STR = OPS_STR.transpose_W1_dot(np.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_1(res_STR) MPI_COMM.barrier() @@ -359,7 +359,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): print("\nQ2 (V2 --> V2, Identity operator in this case):") res_PSY = OPS_PSY.Q2.dot(x2_st) - res_STR = OPS_STR.Q2_dot(xp.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) + res_STR = OPS_STR.Q2_dot(np.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_2(res_STR) MPI_COMM.Barrier() @@ -382,7 +382,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): Q2T = OPS_PSY.Q2.transpose() res_PSY = Q2T.dot(x2_st) - res_STR = OPS_STR.transpose_Q2_dot(xp.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) + res_STR = OPS_STR.transpose_Q2_dot(np.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_2(res_STR) MPI_COMM.Barrier() @@ -408,7 +408,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): print("\nX1 (V1 --> V0 x V0 x V0):") res_PSY = OPS_PSY.X1.dot(x1_st) - res_STR = OPS_STR.X1_dot(xp.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) + res_STR = OPS_STR.X1_dot(np.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) res_STR_0 = SPACES.extract_0(res_STR[0]) res_STR_1 = SPACES.extract_0(res_STR[1]) res_STR_2 = SPACES.extract_0(res_STR[2]) @@ -455,6 +455,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): print(f"Rank {mpi_rank} | Assertion passed.") +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[6, 9, 7]]) @pytest.mark.parametrize("p", [[2, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -464,8 +465,8 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): ) @pytest.mark.parametrize("mapping", [["IGAPolarCylinder", {"a": 1.0, "Lz": 3.0}]]) def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.mhd_operators import MHDOperators from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space @@ -503,7 +504,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) if show_plots: @@ -584,11 +585,11 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal x2_pol_psy.tp = x2_psy x3_pol_psy.tp = x3_psy - xp.random.seed(1607) - x0_pol_psy.pol = [xp.random.rand(x0_pol_psy.pol[0].shape[0], x0_pol_psy.pol[0].shape[1])] - x1_pol_psy.pol = [xp.random.rand(x1_pol_psy.pol[n].shape[0], x1_pol_psy.pol[n].shape[1]) for n in range(3)] - x2_pol_psy.pol = [xp.random.rand(x2_pol_psy.pol[n].shape[0], x2_pol_psy.pol[n].shape[1]) for n in range(3)] - x3_pol_psy.pol = [xp.random.rand(x3_pol_psy.pol[0].shape[0], x3_pol_psy.pol[0].shape[1])] + np.random.seed(1607) + x0_pol_psy.pol = [np.random.rand(x0_pol_psy.pol[0].shape[0], x0_pol_psy.pol[0].shape[1])] + x1_pol_psy.pol = [np.random.rand(x1_pol_psy.pol[n].shape[0], x1_pol_psy.pol[n].shape[1]) for n in range(3)] + x2_pol_psy.pol = [np.random.rand(x2_pol_psy.pol[n].shape[0], x2_pol_psy.pol[n].shape[1]) for n in range(3)] + x3_pol_psy.pol = [np.random.rand(x3_pol_psy.pol[0].shape[0], x3_pol_psy.pol[0].shape[1])] # apply boundary conditions to legacy vectors for right shape x0_pol_str = space.B0.dot(x0_pol_psy.toarray(True)) @@ -614,7 +615,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.PR(x3_pol_str) print(f"Rank {mpi_rank} | Asserting MHD operator K3.") - xp.allclose(space.B3.T.dot(r_str), r_psy.toarray(True)) + np.allclose(space.B3.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") mpi_comm.Barrier() @@ -627,7 +628,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.PR.T(x3_pol_str) print(f"Rank {mpi_rank} | Asserting transpose MHD operator K3.T.") - xp.allclose(space.B3.T.dot(r_str), r_psy.toarray(True)) + np.allclose(space.B3.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") # ===== operator Q2 (V2 --> V2) ============ @@ -644,7 +645,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.MF(x2_pol_str) print(f"Rank {mpi_rank} | Asserting MHD operator Q2.") - xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") mpi_comm.Barrier() @@ -657,7 +658,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.MF.T(x2_pol_str) print(f"Rank {mpi_rank} | Asserting transposed MHD operator Q2.T.") - xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") # ===== operator T2 (V2 --> V1) ============ @@ -674,7 +675,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.EF(x2_pol_str) print(f"Rank {mpi_rank} | Asserting MHD operator T2.") - xp.allclose(space.B1.T.dot(r_str), r_psy.toarray(True)) + np.allclose(space.B1.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") mpi_comm.Barrier() @@ -687,7 +688,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.EF.T(x1_pol_str) print(f"Rank {mpi_rank} | Asserting transposed MHD operator T2.T.") - xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") # ===== operator S2 (V2 --> V2) ============ @@ -704,7 +705,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.PF(x2_pol_str) print(f"Rank {mpi_rank} | Asserting MHD operator S2.") - xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") mpi_comm.Barrier() @@ -717,7 +718,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.PF.T(x2_pol_str) print(f"Rank {mpi_rank} | Asserting transposed MHD operator S2.T.") - xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") @@ -726,7 +727,7 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): TODO """ - import cunumpy as xp + import numpy as np if verbose: if MPI_COMM is not None: @@ -789,8 +790,8 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): print( f"Rank {mpi_rank} | Maximum absolute diference (result):\n", - xp.max( - xp.abs( + np.max( + np.abs( res_PSY[ res_PSY.starts[0] : res_PSY.ends[0] + 1, res_PSY.starts[1] : res_PSY.ends[1] + 1, @@ -800,8 +801,8 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): res_PSY.starts[0] : res_PSY.ends[0] + 1, res_PSY.starts[1] : res_PSY.ends[1] + 1, res_PSY.starts[2] : res_PSY.ends[2] + 1, - ], - ), + ] + ) ), ) @@ -809,7 +810,7 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): MPI_COMM.Barrier() # Compare results. (Works only for Nel=[N, N, N] so far! TODO: Find this bug!) - assert xp.allclose( + assert np.allclose( res_PSY[ res_PSY.starts[0] : res_PSY.ends[0] + 1, res_PSY.starts[1] : res_PSY.ends[1] + 1, @@ -834,10 +835,5 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): # mapping=["Cuboid", {"l1": 0.0, "r1": 1.0, "l2": 0.0, "r2": 1.0, "l3": 0.0, "r3": 1.0}], # ) test_basis_ops_polar( - [6, 9, 7], - [2, 2, 3], - [False, True, True], - None, - ["IGAPolarCylinder", {"a": 1.0, "Lz": 3.0}], - False, + [6, 9, 7], [2, 2, 3], [False, True, True], None, ["IGAPolarCylinder", {"a": 1.0, "Lz": 3.0}], False ) diff --git a/src/struphy/feec/tests/test_derham.py b/src/struphy/feec/tests/test_derham.py index 1e857b5a2..e89d1a1a6 100644 --- a/src/struphy/feec/tests/test_derham.py +++ b/src/struphy/feec/tests/test_derham.py @@ -1,14 +1,15 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 8, 12]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True]]) def test_psydac_derham(Nel, p, spl_kind): """Remark: p=even projectors yield slightly different results, pass with atol=1e-3.""" - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -17,6 +18,7 @@ def test_psydac_derham(Nel, p, spl_kind): from struphy.feec.utilities import compare_arrays comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() print("Nel=", Nel) @@ -47,11 +49,11 @@ def test_psydac_derham(Nel, p, spl_kind): N3_tot = DR_STR.Ntot_3form # Random vectors for testing - xp.random.seed(1981) - x0 = xp.random.rand(N0_tot) - x1 = xp.random.rand(xp.sum(N1_tot)) - x2 = xp.random.rand(xp.sum(N2_tot)) - x3 = xp.random.rand(N3_tot) + np.random.seed(1981) + x0 = np.random.rand(N0_tot) + x1 = np.random.rand(np.sum(N1_tot)) + x2 = np.random.rand(np.sum(N2_tot)) + x3 = np.random.rand(N3_tot) ############################ ### TEST STENCIL VECTORS ### @@ -70,9 +72,7 @@ def test_psydac_derham(Nel, p, spl_kind): # Assign from start to end index + 1 x0_PSY[s0[0] : e0[0] + 1, s0[1] : e0[1] + 1, s0[2] : e0[2] + 1] = DR_STR.extract_0(x0)[ - s0[0] : e0[0] + 1, - s0[1] : e0[1] + 1, - s0[2] : e0[2] + 1, + s0[0] : e0[0] + 1, s0[1] : e0[1] + 1, s0[2] : e0[2] + 1 ] # Block of StencilVecttors @@ -89,19 +89,13 @@ def test_psydac_derham(Nel, p, spl_kind): x11, x12, x13 = DR_STR.extract_1(x1) x1_PSY[0][s11[0] : e11[0] + 1, s11[1] : e11[1] + 1, s11[2] : e11[2] + 1] = x11[ - s11[0] : e11[0] + 1, - s11[1] : e11[1] + 1, - s11[2] : e11[2] + 1, + s11[0] : e11[0] + 1, s11[1] : e11[1] + 1, s11[2] : e11[2] + 1 ] x1_PSY[1][s12[0] : e12[0] + 1, s12[1] : e12[1] + 1, s12[2] : e12[2] + 1] = x12[ - s12[0] : e12[0] + 1, - s12[1] : e12[1] + 1, - s12[2] : e12[2] + 1, + s12[0] : e12[0] + 1, s12[1] : e12[1] + 1, s12[2] : e12[2] + 1 ] x1_PSY[2][s13[0] : e13[0] + 1, s13[1] : e13[1] + 1, s13[2] : e13[2] + 1] = x13[ - s13[0] : e13[0] + 1, - s13[1] : e13[1] + 1, - s13[2] : e13[2] + 1, + s13[0] : e13[0] + 1, s13[1] : e13[1] + 1, s13[2] : e13[2] + 1 ] x2_PSY = BlockVector(derham.Vh["2"]) @@ -117,19 +111,13 @@ def test_psydac_derham(Nel, p, spl_kind): x21, x22, x23 = DR_STR.extract_2(x2) x2_PSY[0][s21[0] : e21[0] + 1, s21[1] : e21[1] + 1, s21[2] : e21[2] + 1] = x21[ - s21[0] : e21[0] + 1, - s21[1] : e21[1] + 1, - s21[2] : e21[2] + 1, + s21[0] : e21[0] + 1, s21[1] : e21[1] + 1, s21[2] : e21[2] + 1 ] x2_PSY[1][s22[0] : e22[0] + 1, s22[1] : e22[1] + 1, s22[2] : e22[2] + 1] = x22[ - s22[0] : e22[0] + 1, - s22[1] : e22[1] + 1, - s22[2] : e22[2] + 1, + s22[0] : e22[0] + 1, s22[1] : e22[1] + 1, s22[2] : e22[2] + 1 ] x2_PSY[2][s23[0] : e23[0] + 1, s23[1] : e23[1] + 1, s23[2] : e23[2] + 1] = x23[ - s23[0] : e23[0] + 1, - s23[1] : e23[1] + 1, - s23[2] : e23[2] + 1, + s23[0] : e23[0] + 1, s23[1] : e23[1] + 1, s23[2] : e23[2] + 1 ] x3_PSY = StencilVector(derham.Vh["3"]) @@ -144,9 +132,7 @@ def test_psydac_derham(Nel, p, spl_kind): e3 = x3_PSY.ends x3_PSY[s3[0] : e3[0] + 1, s3[1] : e3[1] + 1, s3[2] : e3[2] + 1] = DR_STR.extract_3(x3)[ - s3[0] : e3[0] + 1, - s3[1] : e3[1] + 1, - s3[2] : e3[2] + 1, + s3[0] : e3[0] + 1, s3[1] : e3[1] + 1, s3[2] : e3[2] + 1 ] ######################## @@ -190,7 +176,7 @@ def test_psydac_derham(Nel, p, spl_kind): zero2_STR = curl_STR.dot(d1_STR) zero2_PSY = derham.curl.dot(d1_PSY) - assert xp.allclose(zero2_STR, xp.zeros_like(zero2_STR)) + assert np.allclose(zero2_STR, np.zeros_like(zero2_STR)) if rank == 0: print("\nCompare curl of grad:") compare_arrays(zero2_PSY, DR_STR.extract_2(zero2_STR), rank) @@ -199,7 +185,7 @@ def test_psydac_derham(Nel, p, spl_kind): zero3_STR = div_STR.dot(d2_STR) zero3_PSY = derham.div.dot(d2_PSY) - assert xp.allclose(zero3_STR, xp.zeros_like(zero3_STR)) + assert np.allclose(zero3_STR, np.zeros_like(zero3_STR)) if rank == 0: print("\nCompare div of curl:") compare_arrays(zero3_PSY, DR_STR.extract_3(zero3_STR), rank) @@ -217,7 +203,7 @@ def test_psydac_derham(Nel, p, spl_kind): # compare projectors def f(eta1, eta2, eta3): - return xp.sin(4 * xp.pi * eta1) * xp.cos(2 * xp.pi * eta2) + xp.exp(xp.cos(2 * xp.pi * eta3)) + return np.sin(4 * np.pi * eta1) * np.cos(2 * np.pi * eta2) + np.exp(np.cos(2 * np.pi * eta3)) fh0_STR = PI("0", f) fh0_PSY = derham.P["0"](f) diff --git a/src/struphy/feec/tests/test_eval_field.py b/src/struphy/feec/tests/test_eval_field.py index f9a00c18d..9fb829942 100644 --- a/src/struphy/feec/tests/test_eval_field.py +++ b/src/struphy/feec/tests/test_eval_field.py @@ -1,9 +1,9 @@ -import cunumpy as xp +import numpy as np import pytest -from psydac.ddm.mpi import MockComm -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[3, 2, 4]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -53,34 +53,31 @@ def test_eval_field(Nel, p, spl_kind): uv.initialize_coeffs(perturbations=[pert_uv_1, pert_uv_2, pert_uv_3]) # evaluation points for meshgrid - eta1 = xp.linspace(0, 1, 11) - eta2 = xp.linspace(0, 1, 14) - eta3 = xp.linspace(0, 1, 18) + eta1 = np.linspace(0, 1, 11) + eta2 = np.linspace(0, 1, 14) + eta3 = np.linspace(0, 1, 18) # evaluation points for markers Np = 33 - markers = xp.random.rand(Np, 3) - markers_1 = xp.zeros((eta1.size, 3)) + markers = np.random.rand(Np, 3) + markers_1 = np.zeros((eta1.size, 3)) markers_1[:, 0] = eta1 - markers_2 = xp.zeros((eta2.size, 3)) + markers_2 = np.zeros((eta2.size, 3)) markers_2[:, 1] = eta2 - markers_3 = xp.zeros((eta3.size, 3)) + markers_3 = np.zeros((eta3.size, 3)) markers_3[:, 2] = eta3 # arrays for legacy evaluation arr1, arr2, arr3, is_sparse_meshgrid = Domain.prepare_eval_pts(eta1, eta2, eta3) - tmp = xp.zeros_like(arr1) + tmp = np.zeros_like(arr1) ###### # V0 # ###### # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(p0.vector.toarray(), p0.nbasis) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(p0.vector.toarray(), p0.nbasis) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(p0.vector, coeffs, rank) # legacy evaluation @@ -101,12 +98,12 @@ def test_eval_field(Nel, p, spl_kind): tmp, 0, ) - val_legacy = xp.squeeze(tmp.copy()) + val_legacy = np.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val = p0(eta1, eta2, eta3, squeeze_out=True) - assert xp.allclose(val, val_legacy) + assert np.allclose(val, val_legacy) # marker evaluation m_vals = p0(markers) @@ -119,20 +116,17 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = p0(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = p0(0.0, 0.0, eta3, squeeze_out=True) - assert xp.allclose(m_vals_1, m_vals_ref_1) - assert xp.allclose(m_vals_2, m_vals_ref_2) - assert xp.allclose(m_vals_3, m_vals_ref_3) + assert np.allclose(m_vals_1, m_vals_ref_1) + assert np.allclose(m_vals_2, m_vals_ref_2) + assert np.allclose(m_vals_3, m_vals_ref_3) ###### # V1 # ###### # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(E1.vector[0].toarray(), E1.nbasis[0]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(E1.vector[0].toarray(), E1.nbasis[0]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(E1.vector[0], coeffs, rank) # legacy evaluation @@ -153,16 +147,13 @@ def test_eval_field(Nel, p, spl_kind): tmp, 11, ) - val_legacy_1 = xp.squeeze(tmp.copy()) + val_legacy_1 = np.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(E1.vector[1].toarray(), E1.nbasis[1]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(E1.vector[1].toarray(), E1.nbasis[1]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(E1.vector[1], coeffs, rank) # legacy evaluation @@ -183,16 +174,13 @@ def test_eval_field(Nel, p, spl_kind): tmp, 12, ) - val_legacy_2 = xp.squeeze(tmp.copy()) + val_legacy_2 = np.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(E1.vector[2].toarray(), E1.nbasis[2]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(E1.vector[2].toarray(), E1.nbasis[2]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(E1.vector[2], coeffs, rank) # legacy evaluation @@ -213,14 +201,14 @@ def test_eval_field(Nel, p, spl_kind): tmp, 13, ) - val_legacy_3 = xp.squeeze(tmp.copy()) + val_legacy_3 = np.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val1, val2, val3 = E1(eta1, eta2, eta3, squeeze_out=True) - assert xp.allclose(val1, val_legacy_1) - assert xp.allclose(val2, val_legacy_2) - assert xp.allclose(val3, val_legacy_3) + assert np.allclose(val1, val_legacy_1) + assert np.allclose(val2, val_legacy_2) + assert np.allclose(val3, val_legacy_3) # marker evaluation m_vals = E1(markers) @@ -233,26 +221,23 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = E1(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = E1(0.0, 0.0, eta3, squeeze_out=True) - assert xp.all( - [xp.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)], + assert np.all( + [np.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)] ) - assert xp.all( - [xp.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)], + assert np.all( + [np.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)] ) - assert xp.all( - [xp.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)], + assert np.all( + [np.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)] ) ###### # V2 # ###### # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(B2.vector[0].toarray(), B2.nbasis[0]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(B2.vector[0].toarray(), B2.nbasis[0]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(B2.vector[0], coeffs, rank) # legacy evaluation @@ -273,16 +258,13 @@ def test_eval_field(Nel, p, spl_kind): tmp, 21, ) - val_legacy_1 = xp.squeeze(tmp.copy()) + val_legacy_1 = np.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(B2.vector[1].toarray(), B2.nbasis[1]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(B2.vector[1].toarray(), B2.nbasis[1]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(B2.vector[1], coeffs, rank) # legacy evaluation @@ -303,16 +285,13 @@ def test_eval_field(Nel, p, spl_kind): tmp, 22, ) - val_legacy_2 = xp.squeeze(tmp.copy()) + val_legacy_2 = np.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(B2.vector[2].toarray(), B2.nbasis[2]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(B2.vector[2].toarray(), B2.nbasis[2]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(B2.vector[2], coeffs, rank) # legacy evaluation @@ -333,14 +312,14 @@ def test_eval_field(Nel, p, spl_kind): tmp, 23, ) - val_legacy_3 = xp.squeeze(tmp.copy()) + val_legacy_3 = np.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val1, val2, val3 = B2(eta1, eta2, eta3, squeeze_out=True) - assert xp.allclose(val1, val_legacy_1) - assert xp.allclose(val2, val_legacy_2) - assert xp.allclose(val3, val_legacy_3) + assert np.allclose(val1, val_legacy_1) + assert np.allclose(val2, val_legacy_2) + assert np.allclose(val3, val_legacy_3) # marker evaluation m_vals = B2(markers) @@ -353,26 +332,23 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = B2(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = B2(0.0, 0.0, eta3, squeeze_out=True) - assert xp.all( - [xp.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)], + assert np.all( + [np.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)] ) - assert xp.all( - [xp.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)], + assert np.all( + [np.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)] ) - assert xp.all( - [xp.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)], + assert np.all( + [np.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)] ) ###### # V3 # ###### # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(n3.vector.toarray(), n3.nbasis) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(n3.vector.toarray(), n3.nbasis) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(n3.vector, coeffs, rank) # legacy evaluation @@ -393,12 +369,12 @@ def test_eval_field(Nel, p, spl_kind): tmp, 3, ) - val_legacy = xp.squeeze(tmp.copy()) + val_legacy = np.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val = n3(eta1, eta2, eta3, squeeze_out=True) - assert xp.allclose(val, val_legacy) + assert np.allclose(val, val_legacy) # marker evaluation m_vals = n3(markers) @@ -411,20 +387,17 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = n3(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = n3(0.0, 0.0, eta3, squeeze_out=True) - assert xp.allclose(m_vals_1, m_vals_ref_1) - assert xp.allclose(m_vals_2, m_vals_ref_2) - assert xp.allclose(m_vals_3, m_vals_ref_3) + assert np.allclose(m_vals_1, m_vals_ref_1) + assert np.allclose(m_vals_2, m_vals_ref_2) + assert np.allclose(m_vals_3, m_vals_ref_3) ######### # V0vec # ######### # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(uv.vector[0].toarray(), uv.nbasis[0]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(uv.vector[0].toarray(), uv.nbasis[0]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(uv.vector[0], coeffs, rank) # legacy evaluation @@ -445,16 +418,13 @@ def test_eval_field(Nel, p, spl_kind): tmp, 0, ) - val_legacy_1 = xp.squeeze(tmp.copy()) + val_legacy_1 = np.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(uv.vector[1].toarray(), uv.nbasis[1]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(uv.vector[1].toarray(), uv.nbasis[1]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(uv.vector[1], coeffs, rank) # legacy evaluation @@ -475,16 +445,13 @@ def test_eval_field(Nel, p, spl_kind): tmp, 0, ) - val_legacy_2 = xp.squeeze(tmp.copy()) + val_legacy_2 = np.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = xp.reshape(uv.vector[2].toarray(), uv.nbasis[2]) - if isinstance(comm, MockComm): - coeffs = coeffs_loc - else: - coeffs = xp.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = np.reshape(uv.vector[2].toarray(), uv.nbasis[2]) + coeffs = np.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(uv.vector[2], coeffs, rank) # legacy evaluation @@ -505,14 +472,14 @@ def test_eval_field(Nel, p, spl_kind): tmp, 0, ) - val_legacy_3 = xp.squeeze(tmp.copy()) + val_legacy_3 = np.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val1, val2, val3 = uv(eta1, eta2, eta3, squeeze_out=True) - assert xp.allclose(val1, val_legacy_1) - assert xp.allclose(val2, val_legacy_2) - assert xp.allclose(val3, val_legacy_3) + assert np.allclose(val1, val_legacy_1) + assert np.allclose(val2, val_legacy_2) + assert np.allclose(val3, val_legacy_3) # marker evaluation m_vals = uv(markers) @@ -525,14 +492,14 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = uv(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = uv(0.0, 0.0, eta3, squeeze_out=True) - assert xp.all( - [xp.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)], + assert np.all( + [np.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)] ) - assert xp.all( - [xp.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)], + assert np.all( + [np.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)] ) - assert xp.all( - [xp.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)], + assert np.all( + [np.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)] ) print("\nAll assertions passed.") diff --git a/src/struphy/feec/tests/test_field_init.py b/src/struphy/feec/tests/test_field_init.py index 2f0da1611..a6d5ca815 100644 --- a/src/struphy/feec/tests/test_field_init.py +++ b/src/struphy/feec/tests/test_field_init.py @@ -1,6 +1,7 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 10, 12]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [True, True, False]]) @@ -9,8 +10,8 @@ def test_bckgr_init_const(Nel, p, spl_kind, spaces, vec_comps): """Test field background initialization of "LogicalConst" with multiple fields in params.""" - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.io.options import FieldsBackground @@ -22,14 +23,14 @@ def test_bckgr_init_const(Nel, p, spl_kind, spaces, vec_comps): derham = Derham(Nel, p, spl_kind, comm=comm) # evaluation grids for comparisons - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) - meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) + meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") # test values - xp.random.seed(1234) - val = xp.random.rand() + np.random.seed(1234) + val = np.random.rand() if val > 0.5: val = int(val * 10) @@ -40,22 +41,23 @@ def test_bckgr_init_const(Nel, p, spl_kind, spaces, vec_comps): background = FieldsBackground(type="LogicalConst", values=(val,)) field.initialize_coeffs(backgrounds=background) print( - f"\n{rank =}, {space =}, after init:\n {xp.max(xp.abs(field(*meshgrids) - val)) =}", + f"\n{rank = }, {space = }, after init:\n {np.max(np.abs(field(*meshgrids) - val)) = }", ) # print(f'{field(*meshgrids) = }') - assert xp.allclose(field(*meshgrids), val) + assert np.allclose(field(*meshgrids), val) else: background = FieldsBackground(type="LogicalConst", values=(val, None, val)) field.initialize_coeffs(backgrounds=background) for j, val in enumerate(background.values): if val is not None: print( - f"\n{rank =}, {space =}, after init:\n {j =}, {xp.max(xp.abs(field(*meshgrids)[j] - val)) =}", + f"\n{rank = }, {space = }, after init:\n {j = }, {np.max(np.abs(field(*meshgrids)[j] - val)) = }", ) # print(f'{field(*meshgrids)[i] = }') - assert xp.allclose(field(*meshgrids)[j], val) + assert np.allclose(field(*meshgrids)[j], val) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[18, 24, 12]]) @pytest.mark.parametrize("p", [[1, 2, 1]]) @pytest.mark.parametrize("spl_kind", [[False, True, True]]) @@ -64,9 +66,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show import inspect - import cunumpy as xp + import numpy as np from matplotlib import pyplot as plt - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.fields_background import equils @@ -88,28 +90,28 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show bckgr_4 = FieldsBackground(type="FluidEquilibrium", variable="uv") # evaluation grids for comparisons - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) - meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) + meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") # test for key, val in inspect.getmembers(equils): if inspect.isclass(val) and val.__module__ == equils.__name__: - print(f"{key =}") + print(f"{key = }") if "DESC" in key and not with_desc: - print(f"Attention: {with_desc =}, DESC not tested here !!") + print(f"Attention: {with_desc = }, DESC not tested here !!") continue if "GVEC" in key and not with_gvec: - print(f"Attention: {with_gvec =}, GVEC not tested here !!") + print(f"Attention: {with_gvec = }, GVEC not tested here !!") continue mhd_equil = val() if not isinstance(mhd_equil, FluidEquilibriumWithB): continue - print(f"{mhd_equil.params =}") + print(f"{mhd_equil.params = }") if "AdhocTorus" in key: mhd_equil.domain = domains.HollowTorus( @@ -132,8 +134,8 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show elif "ShearedSlab" in key: mhd_equil.domain = domains.Cuboid( r1=mhd_equil.params["a"], - r2=mhd_equil.params["a"] * 2 * xp.pi, - r3=mhd_equil.params["R0"] * 2 * xp.pi, + r2=mhd_equil.params["a"] * 2 * np.pi, + r3=mhd_equil.params["R0"] * 2 * np.pi, ) elif "ShearFluid" in key: mhd_equil.domain = domains.Cuboid( @@ -145,7 +147,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show mhd_equil.domain = domains.HollowCylinder( a1=1e-3, a2=mhd_equil.params["a"], - Lz=mhd_equil.params["R0"] * 2 * xp.pi, + Lz=mhd_equil.params["R0"] * 2 * np.pi, ) else: try: @@ -186,57 +188,57 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show # scalar spaces print( - f"{xp.max(xp.abs(field_3(*meshgrids) - mhd_equil.p3(*meshgrids))) / xp.max(xp.abs(mhd_equil.p3(*meshgrids)))}", + f"{np.max(np.abs(field_3(*meshgrids) - mhd_equil.p3(*meshgrids))) / np.max(np.abs(mhd_equil.p3(*meshgrids)))}" ) assert ( - xp.max( - xp.abs(field_3(*meshgrids) - mhd_equil.p3(*meshgrids)), + np.max( + np.abs(field_3(*meshgrids) - mhd_equil.p3(*meshgrids)), ) - / xp.max(xp.abs(mhd_equil.p3(*meshgrids))) + / np.max(np.abs(mhd_equil.p3(*meshgrids))) < 0.54 ) if isinstance(mhd_equil, FluidEquilibriumWithB): print( - f"{xp.max(xp.abs(field_0(*meshgrids) - mhd_equil.absB0(*meshgrids))) / xp.max(xp.abs(mhd_equil.absB0(*meshgrids)))}", + f"{np.max(np.abs(field_0(*meshgrids) - mhd_equil.absB0(*meshgrids))) / np.max(np.abs(mhd_equil.absB0(*meshgrids)))}" ) assert ( - xp.max( - xp.abs(field_0(*meshgrids) - mhd_equil.absB0(*meshgrids)), + np.max( + np.abs(field_0(*meshgrids) - mhd_equil.absB0(*meshgrids)), ) - / xp.max(xp.abs(mhd_equil.absB0(*meshgrids))) + / np.max(np.abs(mhd_equil.absB0(*meshgrids))) < 0.057 ) print("Scalar asserts passed.") # vector-valued spaces ref = mhd_equil.u1(*meshgrids) - if xp.max(xp.abs(ref[0])) < 1e-11: + if np.max(np.abs(ref[0])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[0])) + denom = np.max(np.abs(ref[0])) print( - f"{xp.max(xp.abs(field_1(*meshgrids)[0] - ref[0])) / denom =}", + f"{np.max(np.abs(field_1(*meshgrids)[0] - ref[0])) / denom = }", ) - assert xp.max(xp.abs(field_1(*meshgrids)[0] - ref[0])) / denom < 0.28 - if xp.max(xp.abs(ref[1])) < 1e-11: + assert np.max(np.abs(field_1(*meshgrids)[0] - ref[0])) / denom < 0.28 + if np.max(np.abs(ref[1])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[1])) + denom = np.max(np.abs(ref[1])) print( - f"{xp.max(xp.abs(field_1(*meshgrids)[1] - ref[1])) / denom =}", + f"{np.max(np.abs(field_1(*meshgrids)[1] - ref[1])) / denom = }", ) - assert xp.max(xp.abs(field_1(*meshgrids)[1] - ref[1])) / denom < 0.33 - if xp.max(xp.abs(ref[2])) < 1e-11: + assert np.max(np.abs(field_1(*meshgrids)[1] - ref[1])) / denom < 0.33 + if np.max(np.abs(ref[2])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[2])) + denom = np.max(np.abs(ref[2])) print( - f"{xp.max(xp.abs(field_1(*meshgrids)[2] - ref[2])) / denom =}", + f"{np.max(np.abs(field_1(*meshgrids)[2] - ref[2])) / denom = }", ) assert ( - xp.max( - xp.abs( + np.max( + np.abs( field_1(*meshgrids)[2] - ref[2], ), ) @@ -246,75 +248,75 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show print("u1 asserts passed.") ref = mhd_equil.u2(*meshgrids) - if xp.max(xp.abs(ref[0])) < 1e-11: + if np.max(np.abs(ref[0])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[0])) + denom = np.max(np.abs(ref[0])) print( - f"{xp.max(xp.abs(field_2(*meshgrids)[0] - ref[0])) / denom =}", + f"{np.max(np.abs(field_2(*meshgrids)[0] - ref[0])) / denom = }", ) - assert xp.max(xp.abs(field_2(*meshgrids)[0] - ref[0])) / denom < 0.86 - if xp.max(xp.abs(ref[1])) < 1e-11: + assert np.max(np.abs(field_2(*meshgrids)[0] - ref[0])) / denom < 0.86 + if np.max(np.abs(ref[1])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[1])) + denom = np.max(np.abs(ref[1])) print( - f"{xp.max(xp.abs(field_2(*meshgrids)[1] - ref[1])) / denom =}", + f"{np.max(np.abs(field_2(*meshgrids)[1] - ref[1])) / denom = }", ) assert ( - xp.max( - xp.abs( + np.max( + np.abs( field_2(*meshgrids)[1] - ref[1], ), ) / denom < 0.4 ) - if xp.max(xp.abs(ref[2])) < 1e-11: + if np.max(np.abs(ref[2])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[2])) + denom = np.max(np.abs(ref[2])) print( - f"{xp.max(xp.abs(field_2(*meshgrids)[2] - ref[2])) / denom =}", + f"{np.max(np.abs(field_2(*meshgrids)[2] - ref[2])) / denom = }", ) - assert xp.max(xp.abs(field_2(*meshgrids)[2] - ref[2])) / denom < 0.21 + assert np.max(np.abs(field_2(*meshgrids)[2] - ref[2])) / denom < 0.21 print("u2 asserts passed.") ref = mhd_equil.uv(*meshgrids) - if xp.max(xp.abs(ref[0])) < 1e-11: + if np.max(np.abs(ref[0])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[0])) + denom = np.max(np.abs(ref[0])) print( - f"{xp.max(xp.abs(field_4(*meshgrids)[0] - ref[0])) / denom =}", + f"{np.max(np.abs(field_4(*meshgrids)[0] - ref[0])) / denom = }", ) - assert xp.max(xp.abs(field_4(*meshgrids)[0] - ref[0])) / denom < 0.6 - if xp.max(xp.abs(ref[1])) < 1e-11: + assert np.max(np.abs(field_4(*meshgrids)[0] - ref[0])) / denom < 0.6 + if np.max(np.abs(ref[1])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[1])) + denom = np.max(np.abs(ref[1])) print( - f"{xp.max(xp.abs(field_4(*meshgrids)[1] - ref[1])) / denom =}", + f"{np.max(np.abs(field_4(*meshgrids)[1] - ref[1])) / denom = }", ) assert ( - xp.max( - xp.abs( + np.max( + np.abs( field_4(*meshgrids)[1] - ref[1], ), ) / denom < 0.2 ) - if xp.max(xp.abs(ref[2])) < 1e-11: + if np.max(np.abs(ref[2])) < 1e-11: denom = 1.0 else: - denom = xp.max(xp.abs(ref[2])) + denom = np.max(np.abs(ref[2])) print( - f"{xp.max(xp.abs(field_4(*meshgrids)[2] - ref[2])) / denom =}", + f"{np.max(np.abs(field_4(*meshgrids)[2] - ref[2])) / denom = }", ) assert ( - xp.max( - xp.abs( + np.max( + np.abs( field_4(*meshgrids)[2] - ref[2], ), ) @@ -325,27 +327,27 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show # plotting fields with equilibrium if show_plot and rank == 0: - plt.figure(f"0/3-forms top, {mhd_equil =}", figsize=(24, 16)) + plt.figure(f"0/3-forms top, {mhd_equil = }", figsize=(24, 16)) plt.figure( - f"0/3-forms poloidal, {mhd_equil =}", + f"0/3-forms poloidal, {mhd_equil = }", figsize=(24, 16), ) - plt.figure(f"1-forms top, {mhd_equil =}", figsize=(24, 16)) + plt.figure(f"1-forms top, {mhd_equil = }", figsize=(24, 16)) plt.figure( - f"1-forms poloidal, {mhd_equil =}", + f"1-forms poloidal, {mhd_equil = }", figsize=(24, 16), ) - plt.figure(f"2-forms top, {mhd_equil =}", figsize=(24, 16)) + plt.figure(f"2-forms top, {mhd_equil = }", figsize=(24, 16)) plt.figure( - f"2-forms poloidal, {mhd_equil =}", + f"2-forms poloidal, {mhd_equil = }", figsize=(24, 16), ) plt.figure( - f"vector-fields top, {mhd_equil =}", + f"vector-fields top, {mhd_equil = }", figsize=(24, 16), ) plt.figure( - f"vector-fields poloidal, {mhd_equil =}", + f"vector-fields poloidal, {mhd_equil = }", figsize=(24, 16), ) x, y, z = mhd_equil.domain(*meshgrids) @@ -355,9 +357,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show absB0_h = mhd_equil.domain.push(field_0, *meshgrids) absB0 = mhd_equil.domain.push(mhd_equil.absB0, *meshgrids) - levels = xp.linspace(xp.min(absB0) - 1e-10, xp.max(absB0), 20) + levels = np.linspace(np.min(absB0) - 1e-10, np.max(absB0), 20) - plt.figure(f"0/3-forms top, {mhd_equil =}") + plt.figure(f"0/3-forms top, {mhd_equil = }") plt.subplot(2, 3, 1) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -443,7 +445,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"0/3-forms poloidal, {mhd_equil =}") + plt.figure(f"0/3-forms poloidal, {mhd_equil = }") plt.subplot(2, 3, 1) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -493,9 +495,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show p3_h = mhd_equil.domain.push(field_3, *meshgrids) p3 = mhd_equil.domain.push(mhd_equil.p3, *meshgrids) - levels = xp.linspace(xp.min(p3) - 1e-10, xp.max(p3), 20) + levels = np.linspace(np.min(p3) - 1e-10, np.max(p3), 20) - plt.figure(f"0/3-forms top, {mhd_equil =}") + plt.figure(f"0/3-forms top, {mhd_equil = }") plt.subplot(2, 3, 2) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -581,7 +583,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"0/3-forms poloidal, {mhd_equil =}") + plt.figure(f"0/3-forms poloidal, {mhd_equil = }") plt.subplot(2, 3, 2) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -640,9 +642,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show ) for i, (bh, b) in enumerate(zip(b1h, b1)): - levels = xp.linspace(xp.min(b) - 1e-10, xp.max(b), 20) + levels = np.linspace(np.min(b) - 1e-10, np.max(b), 20) - plt.figure(f"1-forms top, {mhd_equil =}") + plt.figure(f"1-forms top, {mhd_equil = }") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -728,7 +730,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"1-forms poloidal, {mhd_equil =}") + plt.figure(f"1-forms poloidal, {mhd_equil = }") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -789,9 +791,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show ) for i, (bh, b) in enumerate(zip(b2h, b2)): - levels = xp.linspace(xp.min(b) - 1e-10, xp.max(b), 20) + levels = np.linspace(np.min(b) - 1e-10, np.max(b), 20) - plt.figure(f"2-forms top, {mhd_equil =}") + plt.figure(f"2-forms top, {mhd_equil = }") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -877,7 +879,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"2-forms poloidal, {mhd_equil =}") + plt.figure(f"2-forms poloidal, {mhd_equil = }") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -938,9 +940,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show ) for i, (bh, b) in enumerate(zip(bvh, bv)): - levels = xp.linspace(xp.min(b) - 1e-10, xp.max(b), 20) + levels = np.linspace(np.min(b) - 1e-10, np.max(b), 20) - plt.figure(f"vector-fields top, {mhd_equil =}") + plt.figure(f"vector-fields top, {mhd_equil = }") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -1026,7 +1028,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"vector-fields poloidal, {mhd_equil =}") + plt.figure(f"vector-fields poloidal, {mhd_equil = }") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -1077,15 +1079,16 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.show() +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[1, 32, 32]]) @pytest.mark.parametrize("p", [[1, 3, 3]]) @pytest.mark.parametrize("spl_kind", [[True, True, True]]) def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): """Test field perturbation with ModesSin + ModesCos on top of of "LogicalConst" with multiple fields in params.""" - import cunumpy as xp + import numpy as np from matplotlib import pyplot as plt - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.initial.perturbations import ModesCos, ModesSin @@ -1162,18 +1165,15 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): field_0 = derham.create_spline_function("name_0", "H1", backgrounds=bckgr_0, perturbations=[f_sin_0, f_cos_0]) field_1 = derham.create_spline_function( - "name_1", - "Hcurl", - backgrounds=bckgr_1, - perturbations=[f_sin_11, f_sin_13, f_cos_11, f_cos_12], + "name_1", "Hcurl", backgrounds=bckgr_1, perturbations=[f_sin_11, f_sin_13, f_cos_11, f_cos_12] ) field_2 = derham.create_spline_function("name_2", "Hdiv", backgrounds=bckgr_2, perturbations=[f_cos_22]) # evaluation grids for comparisons - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) - meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) + meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") fun_0 = avg_0 + f_sin_0(*meshgrids) + f_cos_0(*meshgrids) @@ -1192,24 +1192,24 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): f1_h = field_1(*meshgrids) f2_h = field_2(*meshgrids) - print(f"{xp.max(xp.abs(fun_0 - f0_h)) =}") - print(f"{xp.max(xp.abs(fun_1[0] - f1_h[0])) =}") - print(f"{xp.max(xp.abs(fun_1[1] - f1_h[1])) =}") - print(f"{xp.max(xp.abs(fun_1[2] - f1_h[2])) =}") - print(f"{xp.max(xp.abs(fun_2[0] - f2_h[0])) =}") - print(f"{xp.max(xp.abs(fun_2[1] - f2_h[1])) =}") - print(f"{xp.max(xp.abs(fun_2[2] - f2_h[2])) =}") - - assert xp.max(xp.abs(fun_0 - f0_h)) < 3e-5 - assert xp.max(xp.abs(fun_1[0] - f1_h[0])) < 3e-5 - assert xp.max(xp.abs(fun_1[1] - f1_h[1])) < 3e-5 - assert xp.max(xp.abs(fun_1[2] - f1_h[2])) < 3e-5 - assert xp.max(xp.abs(fun_2[0] - f2_h[0])) < 3e-5 - assert xp.max(xp.abs(fun_2[1] - f2_h[1])) < 3e-5 - assert xp.max(xp.abs(fun_2[2] - f2_h[2])) < 3e-5 + print(f"{np.max(np.abs(fun_0 - f0_h)) = }") + print(f"{np.max(np.abs(fun_1[0] - f1_h[0])) = }") + print(f"{np.max(np.abs(fun_1[1] - f1_h[1])) = }") + print(f"{np.max(np.abs(fun_1[2] - f1_h[2])) = }") + print(f"{np.max(np.abs(fun_2[0] - f2_h[0])) = }") + print(f"{np.max(np.abs(fun_2[1] - f2_h[1])) = }") + print(f"{np.max(np.abs(fun_2[2] - f2_h[2])) = }") + + assert np.max(np.abs(fun_0 - f0_h)) < 3e-5 + assert np.max(np.abs(fun_1[0] - f1_h[0])) < 3e-5 + assert np.max(np.abs(fun_1[1] - f1_h[1])) < 3e-5 + assert np.max(np.abs(fun_1[2] - f1_h[2])) < 3e-5 + assert np.max(np.abs(fun_2[0] - f2_h[0])) < 3e-5 + assert np.max(np.abs(fun_2[1] - f2_h[1])) < 3e-5 + assert np.max(np.abs(fun_2[2] - f2_h[2])) < 3e-5 if show_plot and rank == 0: - levels = xp.linspace(xp.min(fun_0) - 1e-10, xp.max(fun_0), 40) + levels = np.linspace(np.min(fun_0) - 1e-10, np.max(fun_0), 40) plt.figure("0-form", figsize=(10, 16)) plt.subplot(2, 1, 1) @@ -1242,7 +1242,7 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): plt.figure("1-form", figsize=(30, 16)) for i, (f_h, fun) in enumerate(zip(f1_h, fun_1)): - levels = xp.linspace(xp.min(fun) - 1e-10, xp.max(fun), 40) + levels = np.linspace(np.min(fun) - 1e-10, np.max(fun), 40) plt.subplot(2, 3, 1 + i) plt.contourf( @@ -1274,7 +1274,7 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): plt.figure("2-form", figsize=(30, 16)) for i, (f_h, fun) in enumerate(zip(f2_h, fun_2)): - levels = xp.linspace(xp.min(fun) - 1e-10, xp.max(fun), 40) + levels = np.linspace(np.min(fun) - 1e-10, np.max(fun), 40) plt.subplot(2, 3, 1 + i) plt.contourf( @@ -1307,6 +1307,7 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): plt.show() +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 10, 12]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [True, False, True]]) @@ -1315,8 +1316,8 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): def test_noise_init(Nel, p, spl_kind, space, direction): """Only tests 1d noise ('e1', 'e2', 'e3') !!""" - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.feec.utilities import compare_arrays diff --git a/src/struphy/feec/tests/test_l2_projectors.py b/src/struphy/feec/tests/test_l2_projectors.py index 2e9f611eb..e376d2d84 100644 --- a/src/struphy/feec/tests/test_l2_projectors.py +++ b/src/struphy/feec/tests/test_l2_projectors.py @@ -1,9 +1,9 @@ import inspect -import cunumpy as xp import matplotlib.pyplot as plt +import numpy as np import pytest -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.projectors import L2Projector @@ -11,11 +11,12 @@ from struphy.geometry import domains +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[16, 32, 1]]) @pytest.mark.parametrize("p", [[2, 1, 1], [3, 2, 1]]) @pytest.mark.parametrize("spl_kind", [[False, True, True]]) @pytest.mark.parametrize("array_input", [False, True]) -def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_gvec=False, with_desc=False, do_plot=False): +def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_desc, do_plot=False): """Tests the L2-projectors for all available mappings. Both callable and array inputs to the projectors are tested. @@ -28,7 +29,7 @@ def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_gvec=False, derham = Derham(Nel, p, spl_kind, comm=comm) # constant function - f = lambda e1, e2, e3: xp.sin(xp.pi * e1) * xp.cos(2 * xp.pi * e2) + f = lambda e1, e2, e3: np.sin(np.pi * e1) * np.cos(2 * np.pi * e2) # create domain object dom_types = [] @@ -39,23 +40,19 @@ def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_gvec=False, dom_classes += [val] # evaluation points - e1 = xp.linspace(0.0, 1.0, 30) - e2 = xp.linspace(0.0, 1.0, 40) + e1 = np.linspace(0.0, 1.0, 30) + e2 = np.linspace(0.0, 1.0, 40) e3 = 0.0 - ee1, ee2, ee3 = xp.meshgrid(e1, e2, e3, indexing="ij") + ee1, ee2, ee3 = np.meshgrid(e1, e2, e3, indexing="ij") for dom_type, dom_class in zip(dom_types, dom_classes): print("#" * 80) - print(f"Testing {dom_class =}") + print(f"Testing {dom_class = }") print("#" * 80) - if "GVEC" in dom_type and not with_gvec: - print(f"Attention: {with_gvec =}, GVEC not tested here !!") - continue - if "DESC" in dom_type and not with_desc: - print(f"Attention: {with_desc =}, DESC not tested here !!") + print(f"Attention: {with_desc = }, DESC not tested here !!") continue domain = dom_class() @@ -80,12 +77,12 @@ def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_gvec=False, if array_input: pts_q = derham.quad_grid_pts[sp_key] if sp_id in ("H1", "L2"): - ee = xp.meshgrid(*[pt.flatten() for pt in pts_q], indexing="ij") + ee = np.meshgrid(*[pt.flatten() for pt in pts_q], indexing="ij") f_array = f(*ee) else: f_array = [] for pts in pts_q: - ee = xp.meshgrid(*[pt.flatten() for pt in pts], indexing="ij") + ee = np.meshgrid(*[pt.flatten() for pt in pts], indexing="ij") f_array += [f(*ee)] f_args = f_array else: @@ -95,30 +92,31 @@ def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_gvec=False, veco = P_L2(f_args, out=out) assert veco is out - assert xp.all(vec.toarray() == veco.toarray()) + assert np.all(vec.toarray() == veco.toarray()) field.vector = vec field_vals = field(e1, e2, e3) if sp_id in ("H1", "L2"): - err = xp.max(xp.abs(f_analytic(ee1, ee2, ee3) - field_vals)) + err = np.max(np.abs(f_analytic(ee1, ee2, ee3) - field_vals)) f_plot = field_vals else: - err = [xp.max(xp.abs(exact(ee1, ee2, ee3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] + err = [np.max(np.abs(exact(ee1, ee2, ee3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] f_plot = field_vals[0] - print(f"{sp_id =}, {xp.max(err) =}") + print(f"{sp_id = }, {np.max(err) = }") if sp_id in ("H1", "H1vec"): - assert xp.max(err) < 0.004 + assert np.max(err) < 0.004 else: - assert xp.max(err) < 0.12 + assert np.max(err) < 0.12 if do_plot and rank == 0: plt.figure(f"{dom_type}, {sp_id}") - plt.contourf(e1, e2, xp.squeeze(f_plot[:, :, 0].T)) + plt.contourf(e1, e2, np.squeeze(f_plot[:, :, 0].T)) plt.show() +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("direction", [0, 1, 2]) @pytest.mark.parametrize("pi", [1, 2]) @pytest.mark.parametrize("spl_kindi", [True, False]) @@ -138,7 +136,7 @@ def test_l2_projectors_convergence(direction, pi, spl_kindi, do_plot=False): for n, Neli in enumerate(Nels): # test function def fun(eta): - return xp.cos(4 * xp.pi * eta) + return np.cos(4 * np.pi * eta) # create derham object, test functions and evaluation points e1 = 0.0 @@ -148,7 +146,7 @@ def fun(eta): Nel = [Neli, 1, 1] p = [pi, 1, 1] spl_kind = [spl_kindi, True, True] - e1 = xp.linspace(0.0, 1.0, 100) + e1 = np.linspace(0.0, 1.0, 100) e = e1 c = 0 @@ -158,7 +156,7 @@ def f(x, y, z): Nel = [1, Neli, 1] p = [1, pi, 1] spl_kind = [True, spl_kindi, True] - e2 = xp.linspace(0.0, 1.0, 100) + e2 = np.linspace(0.0, 1.0, 100) e = e2 c = 1 @@ -168,7 +166,7 @@ def f(x, y, z): Nel = [1, 1, Neli] p = [1, 1, pi] spl_kind = [True, True, spl_kindi] - e3 = xp.linspace(0.0, 1.0, 100) + e3 = np.linspace(0.0, 1.0, 100) e = e3 c = 2 @@ -203,19 +201,19 @@ def f(x, y, z): vec = P_L2(f_analytic) veco = P_L2(f_analytic, out=out) assert veco is out - assert xp.all(vec.toarray() == veco.toarray()) + assert np.all(vec.toarray() == veco.toarray()) field.vector = vec field_vals = field(e1, e2, e3, squeeze_out=True) if sp_id in ("H1", "L2"): - err = xp.max(xp.abs(f_analytic(e1, e2, e3) - field_vals)) + err = np.max(np.abs(f_analytic(e1, e2, e3) - field_vals)) f_plot = field_vals else: - err = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] + err = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] f_plot = field_vals[0] - errors[sp_id] += [xp.max(err)] + errors[sp_id] += [np.max(err)] if do_plot: plt.figure(sp_id + ", L2-proj. convergence") @@ -236,8 +234,8 @@ def f(x, y, z): line_for_rate_p1 = [Ne ** (-rate_p1) * errors[sp_id][0] / Nels[0] ** (-rate_p1) for Ne in Nels] line_for_rate_p0 = [Ne ** (-rate_p0) * errors[sp_id][0] / Nels[0] ** (-rate_p0) for Ne in Nels] - m, _ = xp.polyfit(xp.log(Nels), xp.log(errors[sp_id]), deg=1) - print(f"{sp_id =}, fitted convergence rate = {-m}, degree = {pi}") + m, _ = np.polyfit(np.log(Nels), np.log(errors[sp_id]), deg=1) + print(f"{sp_id = }, fitted convergence rate = {-m}, degree = {pi}") if sp_id in ("H1", "H1vec"): assert -m > (pi + 1 - 0.05) else: @@ -251,7 +249,7 @@ def f(x, y, z): plt.loglog(Nels, line_for_rate_p0, "k--") plt.text(Nels[-2], line_for_rate_p1[-2], f"1/Nel^{rate_p1}") plt.text(Nels[-2], line_for_rate_p0[-2], f"1/Nel^{rate_p0}") - plt.title(f"{sp_id =}, degree = {pi}") + plt.title(f"{sp_id = }, degree = {pi}") plt.xlabel("Nel") if do_plot and rank == 0: @@ -264,5 +262,5 @@ def f(x, y, z): spl_kind = [False, True, True] array_input = True test_l2_projectors_mappings(Nel, p, spl_kind, array_input, do_plot=False, with_desc=False) - test_l2_projectors_convergence(0, 1, True, do_plot=False) + # test_l2_projectors_convergence(0, 1, True, do_plot=True) # test_l2_projectors_convergence(1, 1, False, do_plot=True) diff --git a/src/struphy/feec/tests/test_local_projectors.py b/src/struphy/feec/tests/test_local_projectors.py index f51177a6a..3a6216c9f 100644 --- a/src/struphy/feec/tests/test_local_projectors.py +++ b/src/struphy/feec/tests/test_local_projectors.py @@ -1,11 +1,10 @@ import inspect import time -import cunumpy as xp import matplotlib.pyplot as plt +import numpy as np import pytest -from psydac.ddm.mpi import MockComm -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy.bsplines.bsplines import basis_funs, find_span from struphy.bsplines.evaluation_kernels_1d import evaluation_kernel_1d @@ -15,6 +14,53 @@ from struphy.feec.utilities_local_projectors import get_one_spline, get_span_and_basis, get_values_and_indices_splines +def get_span_and_basis(pts, space): + """Compute the knot span index and the values of p + 1 basis function at each point in pts. + + Parameters + ---------- + pts : np.array + 2d array of points (ii, iq) = (interval, quadrature point). + + space : SplineSpace + Psydac object, the 1d spline space to be projected. + + Returns + ------- + span : np.array + 2d array indexed by (n, nq), where n is the interval and nq is the quadrature point in the interval. + + basis : np.array + 3d array of values of basis functions indexed by (n, nq, basis function). + """ + + import psydac.core.bsplines as bsp + + # Extract knot vectors, degree and kind of basis + T = space.knots + p = space.degree + + span = np.zeros(pts.shape, dtype=int) + basis = np.zeros((*pts.shape, p + 1), dtype=float) + + for n in range(pts.shape[0]): + for nq in range(pts.shape[1]): + # avoid 1. --> 0. for clamped interpolation + x = pts[n, nq] % (1.0 + 1e-14) + span_tmp = bsp.find_span(T, p, x) + basis[n, nq, :] = bsp.basis_funs_all_ders( + T, + p, + x, + span_tmp, + 0, + normalization=space.basis, + ) + span[n, nq] = span_tmp # % space.nbasis + + return span, basis + + @pytest.mark.parametrize("Nel", [[14, 16, 18]]) @pytest.mark.parametrize("p", [[5, 4, 3]]) @pytest.mark.parametrize("spl_kind", [[True, False, False], [False, True, False], [False, False, True]]) @@ -32,15 +78,15 @@ def test_local_projectors_compare_global(Nel, p, spl_kind): # constant function def f(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) * xp.cos(4.0 * xp.pi * e2) * xp.sin(6.0 * xp.pi * e3) + return np.sin(2.0 * np.pi * e1) * np.cos(4.0 * np.pi * e2) * np.sin(6.0 * np.pi * e3) - # f = lambda e1, e2, e3: xp.sin(2.0*xp.pi*e1) * xp.cos(4.0*xp.pi*e2) + # f = lambda e1, e2, e3: np.sin(2.0*np.pi*e1) * np.cos(4.0*np.pi*e2) # evaluation points - e1 = xp.linspace(0.0, 1.0, 10) - e2 = xp.linspace(0.0, 1.0, 9) - e3 = xp.linspace(0.0, 1.0, 8) + e1 = np.linspace(0.0, 1.0, 10) + e2 = np.linspace(0.0, 1.0, 9) + e3 = np.linspace(0.0, 1.0, 8) - ee1, ee2, ee3 = xp.meshgrid(e1, e2, e3, indexing="ij") + ee1, ee2, ee3 = np.meshgrid(e1, e2, e3, indexing="ij") # loop over spaces for sp_id, sp_key in derham.space_to_form.items(): @@ -79,31 +125,32 @@ def f(e1, e2, e3): fieldg_vals = fieldg(e1, e2, e3) if sp_id in ("H1", "L2"): - err = xp.max(xp.abs(f_analytic(ee1, ee2, ee3) - field_vals)) + err = np.max(np.abs(f_analytic(ee1, ee2, ee3) - field_vals)) # Error comparing the global and local projectors - errg = xp.max(xp.abs(fieldg_vals - field_vals)) + errg = np.max(np.abs(fieldg_vals - field_vals)) else: - err = xp.zeros(3) - err[0] = xp.max(xp.abs(f(ee1, ee2, ee3) - field_vals[0])) - err[1] = xp.max(xp.abs(f(ee1, ee2, ee3) - field_vals[1])) - err[2] = xp.max(xp.abs(f(ee1, ee2, ee3) - field_vals[2])) + err = np.zeros(3) + err[0] = np.max(np.abs(f(ee1, ee2, ee3) - field_vals[0])) + err[1] = np.max(np.abs(f(ee1, ee2, ee3) - field_vals[1])) + err[2] = np.max(np.abs(f(ee1, ee2, ee3) - field_vals[2])) # Error comparing the global and local projectors - errg = xp.zeros(3) - errg[0] = xp.max(xp.abs(fieldg_vals[0] - field_vals[0])) - errg[1] = xp.max(xp.abs(fieldg_vals[1] - field_vals[1])) - errg[2] = xp.max(xp.abs(fieldg_vals[2] - field_vals[2])) + errg = np.zeros(3) + errg[0] = np.max(np.abs(fieldg_vals[0] - field_vals[0])) + errg[1] = np.max(np.abs(fieldg_vals[1] - field_vals[1])) + errg[2] = np.max(np.abs(fieldg_vals[2] - field_vals[2])) - print(f"{sp_id =}, {xp.max(err) =}, {xp.max(errg) =},{exectime =}") + print(f"{sp_id = }, {np.max(err) = }, {np.max(errg) = },{exectime = }") if sp_id in ("H1", "H1vec"): - assert xp.max(err) < 0.011 - assert xp.max(errg) < 0.011 + assert np.max(err) < 0.011 + assert np.max(errg) < 0.011 else: - assert xp.max(err) < 0.1 - assert xp.max(errg) < 0.1 + assert np.max(err) < 0.1 + assert np.max(errg) < 0.1 +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("direction", [0, 1, 2]) @pytest.mark.parametrize("pi", [3, 4]) @pytest.mark.parametrize("spl_kindi", [True, False]) @@ -126,7 +173,7 @@ def test_local_projectors_convergence(direction, pi, spl_kindi, do_plot=False): for n, Neli in enumerate(Nels): # test function def fun(eta): - return xp.cos(4 * xp.pi * eta) + return np.cos(4 * np.pi * eta) # create derham object, test functions and evaluation points e1 = 0.0 @@ -136,7 +183,7 @@ def fun(eta): Nel = [Neli, 1, 1] p = [pi, 1, 1] spl_kind = [spl_kindi, True, True] - e1 = xp.linspace(0.0, 1.0, 100) + e1 = np.linspace(0.0, 1.0, 100) e = e1 c = 0 @@ -146,7 +193,7 @@ def f(x, y, z): Nel = [1, Neli, 1] p = [1, pi, 1] spl_kind = [True, spl_kindi, True] - e2 = xp.linspace(0.0, 1.0, 100) + e2 = np.linspace(0.0, 1.0, 100) e = e2 c = 1 @@ -156,7 +203,7 @@ def f(x, y, z): Nel = [1, 1, Neli] p = [1, 1, pi] spl_kind = [True, True, spl_kindi] - e3 = xp.linspace(0.0, 1.0, 100) + e3 = np.linspace(0.0, 1.0, 100) e = e3 c = 2 @@ -185,13 +232,13 @@ def f(x, y, z): field_vals = field(e1, e2, e3, squeeze_out=True) if sp_id in ("H1", "L2"): - err = xp.max(xp.abs(f_analytic(e1, e2, e3) - field_vals)) + err = np.max(np.abs(f_analytic(e1, e2, e3) - field_vals)) f_plot = field_vals else: - err = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] + err = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] f_plot = field_vals[0] - errors[sp_id] += [xp.max(err)] + errors[sp_id] += [np.max(err)] if do_plot: plt.figure(sp_id + ", Local-proj. convergence") @@ -210,21 +257,21 @@ def f(x, y, z): line_for_rate_p1 = [Ne ** (-rate_p1) * errors[sp_id][0] / Nels[0] ** (-rate_p1) for Ne in Nels] line_for_rate_p0 = [Ne ** (-rate_p0) * errors[sp_id][0] / Nels[0] ** (-rate_p0) for Ne in Nels] - m, _ = xp.polyfit(xp.log(Nels), xp.log(errors[sp_id]), deg=1) + m, _ = np.polyfit(np.log(Nels), np.log(errors[sp_id]), deg=1) if sp_id in ("H1", "H1vec"): # Sometimes for very large number of elements the convergance rate falls of a bit since the error is already so small floating point impressions become relevant # for those cases is better to compute the convergance rate using only the information of Nel with smaller number if -m <= (pi + 1 - 0.1): - m = -xp.log2(errors[sp_id][1] / errors[sp_id][2]) - print(f"{sp_id =}, fitted convergence rate = {-m}, degree = {pi}") + m = -np.log2(errors[sp_id][1] / errors[sp_id][2]) + print(f"{sp_id = }, fitted convergence rate = {-m}, degree = {pi}") assert -m > (pi + 1 - 0.1) else: # Sometimes for very large number of elements the convergance rate falls of a bit since the error is already so small floating point impressions become relevant # for those cases is better to compute the convergance rate using only the information of Nel with smaller number if -m <= (pi - 0.1): - m = -xp.log2(errors[sp_id][1] / errors[sp_id][2]) - print(f"{sp_id =}, fitted convergence rate = {-m}, degree = {pi}") + m = -np.log2(errors[sp_id][1] / errors[sp_id][2]) + print(f"{sp_id = }, fitted convergence rate = {-m}, degree = {pi}") assert -m > (pi - 0.1) if do_plot: @@ -235,7 +282,7 @@ def f(x, y, z): plt.loglog(Nels, line_for_rate_p0, "k--") plt.text(Nels[-2], line_for_rate_p1[-2], f"1/Nel^{rate_p1}") plt.text(Nels[-2], line_for_rate_p0[-2], f"1/Nel^{rate_p0}") - plt.title(f"{sp_id =}, degree = {pi}") + plt.title(f"{sp_id = }, degree = {pi}") plt.xlabel("Nel") if do_plot and rank == 0: @@ -268,12 +315,12 @@ def aux_test_replication_of_basis(Nel, plist, spl_kind): def make_basis_fun(i): def fun(etas, eta2, eta3): if isinstance(etas, float) or isinstance(etas, int): - etas = xp.array([etas]) - out = xp.zeros_like(etas) + etas = np.array([etas]) + out = np.zeros_like(etas) for j, eta in enumerate(etas): span = find_span(T, p, eta) - inds = xp.arange(span - p, span + 1) % N - pos = xp.argwhere(inds == i) + inds = np.arange(span - p, span + 1) % N + pos = np.argwhere(inds == i) # print(f'{pos = }') if pos.size > 0: pos = pos[0, 0] @@ -288,18 +335,18 @@ def fun(etas, eta2, eta3): fun = make_basis_fun(j) lambdas = P_Loc(fun).toarray() - etas = xp.linspace(0.0, 1.0, 100) - fun_h = xp.zeros(100) + etas = np.linspace(0.0, 1.0, 100) + fun_h = np.zeros(100) for k, eta in enumerate(etas): span = find_span(T, p, eta) - ind1 = xp.arange(span - p, span + 1) % N + ind1 = np.arange(span - p, span + 1) % N basis = basis_funs(T, p, eta, span, normalize=normalize) fun_h[k] = evaluation_kernel_1d(p, basis, ind1, lambdas) - if xp.max(xp.abs(fun(etas, 0.0, 0.0) - fun_h)) >= 10.0**-10: - print(xp.max(xp.abs(fun(etas, 0.0, 0.0) - fun_h))) - assert xp.max(xp.abs(fun(etas, 0.0, 0.0) - fun_h)) < 10.0**-10 - # print(f'{j = }, max error: {xp.max(xp.abs(fun(etas,0.0,0.0) - fun_h))}') + if np.max(np.abs(fun(etas, 0.0, 0.0) - fun_h)) >= 10.0**-10: + print(np.max(np.abs(fun(etas, 0.0, 0.0) - fun_h))) + assert np.max(np.abs(fun(etas, 0.0, 0.0) - fun_h)) < 10.0**-10 + # print(f'{j = }, max error: {np.max(np.abs(fun(etas,0.0,0.0) - fun_h))}') # For D-splines @@ -374,7 +421,7 @@ def test_basis_projection_operator_local(Nel, plist, spl_kind, out_sp_key, in_sp # Helper function to handle reshaping and getting spans and basis def process_eta(eta, w1d): if isinstance(eta, (float, int)): - eta = xp.array([eta]) + eta = np.array([eta]) if len(eta.shape) == 1: eta = eta.reshape((eta.shape[0], 1)) spans, values = get_span_and_basis(eta, w1d) @@ -387,7 +434,7 @@ def fun(eta1, eta2, eta3): eta = eta_map[dim_idx] w1d = W1ds[0][dim_idx] if is_B else V1ds[0][dim_idx] - out = xp.zeros_like(eta) + out = np.zeros_like(eta) for j1 in range(eta.shape[0]): for j2 in range(eta.shape[1]): for j3 in range(eta.shape[2]): @@ -430,21 +477,21 @@ def fun(eta1, eta2, eta3): if out_sp_key == "0" or out_sp_key == "3": npts_out = derham.Vh[out_sp_key].npts - starts = xp.array(out.starts, dtype=int) - ends = xp.array(out.ends, dtype=int) - pds = xp.array(out.pads, dtype=int) + starts = np.array(out.starts, dtype=int) + ends = np.array(out.ends, dtype=int) + pds = np.array(out.pads, dtype=int) VFEM1ds = [VFEM.spaces] - nbasis_out = xp.array([VFEM1ds[0][0].nbasis, VFEM1ds[0][1].nbasis, VFEM1ds[0][2].nbasis]) + nbasis_out = np.array([VFEM1ds[0][0].nbasis, VFEM1ds[0][1].nbasis, VFEM1ds[0][2].nbasis]) else: - npts_out = xp.array([sp.npts for sp in P_Loc.coeff_space.spaces]) - pds = xp.array([vi.pads for vi in P_Loc.coeff_space.spaces]) - starts = xp.array([vi.starts for vi in P_Loc.coeff_space.spaces]) - ends = xp.array([vi.ends for vi in P_Loc.coeff_space.spaces]) - starts = xp.array(starts, dtype=int) - ends = xp.array(ends, dtype=int) - pds = xp.array(pds, dtype=int) + npts_out = np.array([sp.npts for sp in P_Loc.coeff_space.spaces]) + pds = np.array([vi.pads for vi in P_Loc.coeff_space.spaces]) + starts = np.array([vi.starts for vi in P_Loc.coeff_space.spaces]) + ends = np.array([vi.ends for vi in P_Loc.coeff_space.spaces]) + starts = np.array(starts, dtype=int) + ends = np.array(ends, dtype=int) + pds = np.array(pds, dtype=int) VFEM1ds = [comp.spaces for comp in VFEM.spaces] - nbasis_out = xp.array( + nbasis_out = np.array( [ [VFEM1ds[0][0].nbasis, VFEM1ds[0][1].nbasis, VFEM1ds[0][2].nbasis], [ @@ -453,13 +500,13 @@ def fun(eta1, eta2, eta3): VFEM1ds[1][2].nbasis, ], [VFEM1ds[2][0].nbasis, VFEM1ds[2][1].nbasis, VFEM1ds[2][2].nbasis], - ], + ] ) if in_sp_key == "0" or in_sp_key == "3": npts_in = derham.Vh[in_sp_key].npts else: - npts_in = xp.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + npts_in = np.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) def define_basis(in_sp_key): def wrapper(dim, index, h=None): @@ -509,13 +556,13 @@ def basis3(i3, h=None): input[random_i0, random_i1, random_i2] = 1.0 input.update_ghost_regions() else: - npts_in = xp.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + npts_in = np.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) random_h = random.randrange(0, 3) random_i0 = random.randrange(0, npts_in[random_h][0]) random_i1 = random.randrange(0, npts_in[random_h][1]) random_i2 = random.randrange(0, npts_in[random_h][2]) - starts_in = xp.array([sp.starts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) - ends_in = xp.array([sp.ends for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + starts_in = np.array([sp.starts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + ends_in = np.array([sp.ends for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) if starts_in[random_h][0] <= random_i0 and random_i0 <= ends_in[random_h][0]: input[random_h][random_i0, random_i1, random_i2] = 1.0 input.update_ghost_regions() @@ -523,84 +570,84 @@ def basis3(i3, h=None): # We define the matrix if out_sp_key == "0" or out_sp_key == "3": if in_sp_key == "0" or in_sp_key == "3": - matrix = xp.zeros((npts_out[0] * npts_out[1] * npts_out[2], npts_in[0] * npts_in[1] * npts_in[2])) + matrix = np.zeros((npts_out[0] * npts_out[1] * npts_out[2], npts_in[0] * npts_in[1] * npts_in[2])) else: - matrix = xp.zeros( + matrix = np.zeros( ( npts_out[0] * npts_out[1] * npts_out[2], npts_in[0][0] * npts_in[0][1] * npts_in[0][2] + npts_in[1][0] * npts_in[1][1] * npts_in[1][2] + npts_in[2][0] * npts_in[2][1] * npts_in[2][2], - ), + ) ) else: if in_sp_key == "0" or in_sp_key == "3": - matrix0 = xp.zeros((npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[0] * npts_in[1] * npts_in[2])) - matrix1 = xp.zeros((npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[0] * npts_in[1] * npts_in[2])) - matrix2 = xp.zeros((npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[0] * npts_in[1] * npts_in[2])) + matrix0 = np.zeros((npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[0] * npts_in[1] * npts_in[2])) + matrix1 = np.zeros((npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[0] * npts_in[1] * npts_in[2])) + matrix2 = np.zeros((npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[0] * npts_in[1] * npts_in[2])) else: - matrix00 = xp.zeros( + matrix00 = np.zeros( ( npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[0][0] * npts_in[0][1] * npts_in[0][2], - ), + ) ) - matrix10 = xp.zeros( + matrix10 = np.zeros( ( npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[0][0] * npts_in[0][1] * npts_in[0][2], - ), + ) ) - matrix20 = xp.zeros( + matrix20 = np.zeros( ( npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[0][0] * npts_in[0][1] * npts_in[0][2], - ), + ) ) - matrix01 = xp.zeros( + matrix01 = np.zeros( ( npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[1][0] * npts_in[1][1] * npts_in[1][2], - ), + ) ) - matrix11 = xp.zeros( + matrix11 = np.zeros( ( npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[1][0] * npts_in[1][1] * npts_in[1][2], - ), + ) ) - matrix21 = xp.zeros( + matrix21 = np.zeros( ( npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[1][0] * npts_in[1][1] * npts_in[1][2], - ), + ) ) - matrix02 = xp.zeros( + matrix02 = np.zeros( ( npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[2][0] * npts_in[2][1] * npts_in[2][2], - ), + ) ) - matrix12 = xp.zeros( + matrix12 = np.zeros( ( npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[2][0] * npts_in[2][1] * npts_in[2][2], - ), + ) ) - matrix22 = xp.zeros( + matrix22 = np.zeros( ( npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[2][0] * npts_in[2][1] * npts_in[2][2], - ), + ) ) # We build the BasisProjectionOperator by hand if out_sp_key == "0" or out_sp_key == "3": if in_sp_key == "0" or in_sp_key == "3": - # def f_analytic(e1,e2,e3): return (xp.sin(2.0*xp.pi*e1)+xp.cos(4.0*xp.pi*e2))*basis1(random_i0)(e1,e2,e3)*basis2(random_i1)(e1,e2,e3)*basis3(random_i2)(e1,e2,e3) + # def f_analytic(e1,e2,e3): return (np.sin(2.0*np.pi*e1)+np.cos(4.0*np.pi*e2))*basis1(random_i0)(e1,e2,e3)*basis2(random_i1)(e1,e2,e3)*basis3(random_i2)(e1,e2,e3) # out = P_Loc(f_analytic) counter = 0 @@ -610,7 +657,7 @@ def basis3(i3, h=None): def f_analytic(e1, e2, e3): return ( - (xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2)) + (np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2)) * basis1(col0)(e1, e2, e3) * basis2(col1)(e1, e2, e3) * basis3(col2)(e1, e2, e3) @@ -630,7 +677,7 @@ def f_analytic(e1, e2, e3): def f_analytic(e1, e2, e3): return ( - (xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2)) + (np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -649,7 +696,7 @@ def f_analytic(e1, e2, e3): def f_analytic1(e1, e2, e3): return ( - (xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2)) + (np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2)) * basis1(col0)(e1, e2, e3) * basis2(col1)(e1, e2, e3) * basis3(col2)(e1, e2, e3) @@ -657,7 +704,7 @@ def f_analytic1(e1, e2, e3): def f_analytic2(e1, e2, e3): return ( - (xp.cos(2.0 * xp.pi * e2) + xp.cos(6.0 * xp.pi * e3)) + (np.cos(2.0 * np.pi * e2) + np.cos(6.0 * np.pi * e3)) * basis1(col0)(e1, e2, e3) * basis2(col1)(e1, e2, e3) * basis3(col2)(e1, e2, e3) @@ -665,7 +712,7 @@ def f_analytic2(e1, e2, e3): def f_analytic3(e1, e2, e3): return ( - (xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e3)) + (np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e3)) * basis1(col0)(e1, e2, e3) * basis2(col1)(e1, e2, e3) * basis3(col2)(e1, e2, e3) @@ -677,7 +724,7 @@ def f_analytic3(e1, e2, e3): fill_matrix_column(starts[2], ends[2], pds[2], counter, nbasis_out[2], matrix2, out[2]._data) counter += 1 - matrix = xp.vstack((matrix0, matrix1, matrix2)) + matrix = np.vstack((matrix0, matrix1, matrix2)) else: for h in range(3): @@ -689,7 +736,7 @@ def f_analytic3(e1, e2, e3): def f_analytic0(e1, e2, e3): return ( - (xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2)) + (np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -697,7 +744,7 @@ def f_analytic0(e1, e2, e3): def f_analytic1(e1, e2, e3): return ( - (xp.sin(10.0 * xp.pi * e1) + xp.cos(41.0 * xp.pi * e2)) + (np.sin(10.0 * np.pi * e1) + np.cos(41.0 * np.pi * e2)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -705,7 +752,7 @@ def f_analytic1(e1, e2, e3): def f_analytic2(e1, e2, e3): return ( - (xp.sin(25.0 * xp.pi * e1) + xp.cos(49.0 * xp.pi * e2)) + (np.sin(25.0 * np.pi * e1) + np.cos(49.0 * np.pi * e2)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -715,7 +762,7 @@ def f_analytic2(e1, e2, e3): def f_analytic0(e1, e2, e3): return ( - (xp.cos(2.0 * xp.pi * e2) + xp.cos(6.0 * xp.pi * e3)) + (np.cos(2.0 * np.pi * e2) + np.cos(6.0 * np.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -723,7 +770,7 @@ def f_analytic0(e1, e2, e3): def f_analytic1(e1, e2, e3): return ( - (xp.cos(12.0 * xp.pi * e2) + xp.cos(62.0 * xp.pi * e3)) + (np.cos(12.0 * np.pi * e2) + np.cos(62.0 * np.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -731,7 +778,7 @@ def f_analytic1(e1, e2, e3): def f_analytic2(e1, e2, e3): return ( - (xp.cos(25.0 * xp.pi * e2) + xp.cos(68.0 * xp.pi * e3)) + (np.cos(25.0 * np.pi * e2) + np.cos(68.0 * np.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -740,7 +787,7 @@ def f_analytic2(e1, e2, e3): def f_analytic0(e1, e2, e3): return ( - (xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e3)) + (np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -748,7 +795,7 @@ def f_analytic0(e1, e2, e3): def f_analytic1(e1, e2, e3): return ( - (xp.sin(16.0 * xp.pi * e1) + xp.sin(43.0 * xp.pi * e3)) + (np.sin(16.0 * np.pi * e1) + np.sin(43.0 * np.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -756,7 +803,7 @@ def f_analytic1(e1, e2, e3): def f_analytic2(e1, e2, e3): return ( - (xp.sin(65.0 * xp.pi * e1) + xp.sin(47.0 * xp.pi * e3)) + (np.sin(65.0 * np.pi * e1) + np.sin(47.0 * np.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -851,23 +898,23 @@ def f_analytic2(e1, e2, e3): ) counter += 1 - matrix0 = xp.hstack((matrix00, matrix01, matrix02)) - matrix1 = xp.hstack((matrix10, matrix11, matrix12)) - matrix2 = xp.hstack((matrix20, matrix21, matrix22)) - matrix = xp.vstack((matrix0, matrix1, matrix2)) + matrix0 = np.hstack((matrix00, matrix01, matrix02)) + matrix1 = np.hstack((matrix10, matrix11, matrix12)) + matrix2 = np.hstack((matrix20, matrix21, matrix22)) + matrix = np.vstack((matrix0, matrix1, matrix2)) # Now we build the same matrix using the BasisProjectionOperatorLocal if out_sp_key == "0" or out_sp_key == "3": if in_sp_key == "0" or in_sp_key == "3": def f_analytic(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2) + return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2) matrix_new = BasisProjectionOperatorLocal(P_Loc, derham.Vh_fem[in_sp_key], [[f_analytic]], transposed=False) else: def f_analytic(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2) + return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -882,13 +929,13 @@ def f_analytic(e1, e2, e3): if in_sp_key == "0" or in_sp_key == "3": def f_analytic1(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2) + return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2) def f_analytic2(e1, e2, e3): - return xp.cos(2.0 * xp.pi * e2) + xp.cos(6.0 * xp.pi * e3) + return np.cos(2.0 * np.pi * e2) + np.cos(6.0 * np.pi * e3) def f_analytic3(e1, e2, e3): - return xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e3) + return np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e3) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -905,31 +952,31 @@ def f_analytic3(e1, e2, e3): else: def f_analytic00(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2) + return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2) def f_analytic01(e1, e2, e3): - return xp.cos(2.0 * xp.pi * e2) + xp.cos(6.0 * xp.pi * e3) + return np.cos(2.0 * np.pi * e2) + np.cos(6.0 * np.pi * e3) def f_analytic02(e1, e2, e3): - return xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e3) + return np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e3) def f_analytic10(e1, e2, e3): - return xp.sin(10.0 * xp.pi * e1) + xp.cos(41.0 * xp.pi * e2) + return np.sin(10.0 * np.pi * e1) + np.cos(41.0 * np.pi * e2) def f_analytic11(e1, e2, e3): - return xp.cos(12.0 * xp.pi * e2) + xp.cos(62.0 * xp.pi * e3) + return np.cos(12.0 * np.pi * e2) + np.cos(62.0 * np.pi * e3) def f_analytic12(e1, e2, e3): - return xp.sin(16.0 * xp.pi * e1) + xp.sin(43.0 * xp.pi * e3) + return np.sin(16.0 * np.pi * e1) + np.sin(43.0 * np.pi * e3) def f_analytic20(e1, e2, e3): - return xp.sin(25.0 * xp.pi * e1) + xp.cos(49.0 * xp.pi * e2) + return np.sin(25.0 * np.pi * e1) + np.cos(49.0 * np.pi * e2) def f_analytic21(e1, e2, e3): - return xp.cos(25.0 * xp.pi * e2) + xp.cos(68.0 * xp.pi * e3) + return np.cos(25.0 * np.pi * e2) + np.cos(68.0 * np.pi * e3) def f_analytic22(e1, e2, e3): - return xp.sin(65.0 * xp.pi * e1) + xp.sin(47.0 * xp.pi * e3) + return np.sin(65.0 * np.pi * e1) + np.sin(47.0 * np.pi * e3) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -946,7 +993,7 @@ def f_analytic22(e1, e2, e3): transposed=False, ) - compare_arrays(matrix_new.dot(v), xp.matmul(matrix, varr), rank) + compare_arrays(matrix_new.dot(v), np.matmul(matrix, varr), rank) print("BasisProjectionOperatorLocal test passed.") @@ -982,7 +1029,7 @@ def test_basis_projection_operator_local_new(Nel, plist, spl_kind, out_sp_key, i # Helper function to handle reshaping and getting spans and basis def process_eta(eta, w1d): if isinstance(eta, (float, int)): - eta = xp.array([eta]) + eta = np.array([eta]) if len(eta.shape) == 1: eta = eta.reshape((eta.shape[0], 1)) spans, values = get_span_and_basis(eta, w1d) @@ -995,7 +1042,7 @@ def fun(eta1, eta2, eta3): eta = eta_map[dim_idx] w1d = W1ds[0][dim_idx] if is_B else V1ds[0][dim_idx] - out = xp.zeros_like(eta) + out = np.zeros_like(eta) for j1 in range(eta.shape[0]): for j2 in range(eta.shape[1]): for j3 in range(eta.shape[2]): @@ -1072,22 +1119,22 @@ def basis3(i3, h=None): input[random_i0, random_i1, random_i2] = 1.0 input.update_ghost_regions() else: - npts_in = xp.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + npts_in = np.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) random_h = random.randrange(0, 3) random_i0 = random.randrange(0, npts_in[random_h][0]) random_i1 = random.randrange(0, npts_in[random_h][1]) random_i2 = random.randrange(0, npts_in[random_h][2]) - starts = xp.array([sp.starts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) - ends = xp.array([sp.ends for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + starts = np.array([sp.starts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + ends = np.array([sp.ends for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) if starts[random_h][0] <= random_i0 and random_i0 <= ends[random_h][0]: input[random_h][random_i0, random_i1, random_i2] = 1.0 input.update_ghost_regions() - etas1 = xp.linspace(0.0, 1.0, 1000) - etas2 = xp.array([0.5]) + etas1 = np.linspace(0.0, 1.0, 1000) + etas2 = np.array([0.5]) - etas3 = xp.array([0.5]) - meshgrid = xp.meshgrid(*[etas1, etas2, etas3], indexing="ij") + etas3 = np.array([0.5]) + meshgrid = np.meshgrid(*[etas1, etas2, etas3], indexing="ij") # Now we build the same matrix using the BasisProjectionOperatorLocal and BasisProjectionOperator @@ -1095,7 +1142,7 @@ def basis3(i3, h=None): if in_sp_key == "0" or in_sp_key == "3": def f_analytic(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e1) + return np.sin(2.0 * np.pi * e1) + np.sin(4.0 * np.pi * e1) matrix_new = BasisProjectionOperatorLocal(P_Loc, derham.Vh_fem[in_sp_key], [[f_analytic]], transposed=False) matrix_global = BasisProjectionOperator(P, derham.Vh_fem[in_sp_key], [[f_analytic]], transposed=False) @@ -1109,7 +1156,7 @@ def f_analytic(e1, e2, e3): else: def f_analytic(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) + return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -1139,13 +1186,13 @@ def f_analytic(e1, e2, e3): if in_sp_key == "0" or in_sp_key == "3": def f_analytic1(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) + return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) def f_analytic2(e1, e2, e3): - return xp.cos(2.0 * xp.pi * e1) + xp.cos(6.0 * xp.pi * e1) + return np.cos(2.0 * np.pi * e1) + np.cos(6.0 * np.pi * e1) def f_analytic3(e1, e2, e3): - return xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e1) + return np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e1) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -1172,7 +1219,7 @@ def f_analytic3(e1, e2, e3): transposed=False, ) - analytic_vals = xp.array( + analytic_vals = np.array( [ f_analytic1(*meshgrid) * basis1(random_i0)(*meshgrid) @@ -1186,36 +1233,36 @@ def f_analytic3(e1, e2, e3): * basis1(random_i0)(*meshgrid) * basis2(random_i1)(*meshgrid) * basis3(random_i2)(*meshgrid), - ], + ] ) else: def f_analytic00(e1, e2, e3): - return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) + return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) def f_analytic01(e1, e2, e3): - return xp.cos(2.0 * xp.pi * e1) + xp.cos(6.0 * xp.pi * e1) + return np.cos(2.0 * np.pi * e1) + np.cos(6.0 * np.pi * e1) def f_analytic02(e1, e2, e3): - return xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e1) + return np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e1) def f_analytic10(e1, e2, e3): - return xp.sin(3.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) + return np.sin(3.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) def f_analytic11(e1, e2, e3): - return xp.cos(2.0 * xp.pi * e1) + xp.cos(3.0 * xp.pi * e1) + return np.cos(2.0 * np.pi * e1) + np.cos(3.0 * np.pi * e1) def f_analytic12(e1, e2, e3): - return xp.sin(5.0 * xp.pi * e1) + xp.sin(3.0 * xp.pi * e1) + return np.sin(5.0 * np.pi * e1) + np.sin(3.0 * np.pi * e1) def f_analytic20(e1, e2, e3): - return xp.sin(5.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) + return np.sin(5.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) def f_analytic21(e1, e2, e3): - return xp.cos(5.0 * xp.pi * e1) + xp.cos(6.0 * xp.pi * e1) + return np.cos(5.0 * np.pi * e1) + np.cos(6.0 * np.pi * e1) def f_analytic22(e1, e2, e3): - return xp.sin(5.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e1) + return np.sin(5.0 * np.pi * e1) + np.sin(4.0 * np.pi * e1) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -1253,14 +1300,14 @@ def f_analytic22(e1, e2, e3): } # Use the map to get analytic values - analytic_vals = xp.array( + analytic_vals = np.array( [ f_analytic_map[dim][random_h](*meshgrid) * basis1(random_i0, random_h)(*meshgrid) * basis2(random_i1, random_h)(*meshgrid) * basis3(random_i2, random_h)(*meshgrid) for dim in range(3) - ], + ] ) FE_loc = matrix_new.dot(input) @@ -1283,47 +1330,31 @@ def f_analytic22(e1, e2, e3): fieldglo = derham.create_spline_function("fh", out_sp_id) fieldglo.vector = FE_glo - errorloc = xp.abs(fieldloc(*meshgrid) - analytic_vals) - errorglo = xp.abs(fieldglo(*meshgrid) - analytic_vals) + errorloc = np.abs(fieldloc(*meshgrid) - analytic_vals) + errorglo = np.abs(fieldglo(*meshgrid) - analytic_vals) - meanlocal = xp.mean(errorloc) - maxlocal = xp.max(errorloc) + meanlocal = np.mean(errorloc) + maxlocal = np.max(errorloc) - meanglobal = xp.mean(errorglo) - maxglobal = xp.max(errorglo) - - if isinstance(comm, MockComm): - reducemeanlocal = meanlocal - else: - reducemeanlocal = comm.reduce(meanlocal, op=MPI.SUM, root=0) + meanglobal = np.mean(errorglo) + maxglobal = np.max(errorglo) + reducemeanlocal = comm.reduce(meanlocal, op=MPI.SUM, root=0) if rank == 0: reducemeanlocal = reducemeanlocal / world_size + reducemaxlocal = comm.reduce(maxlocal, op=MPI.MAX, root=0) - if isinstance(comm, MockComm): - reducemaxlocal = maxlocal - else: - reducemaxlocal = comm.reduce(maxlocal, op=MPI.MAX, root=0) - - if isinstance(comm, MockComm): - reducemeanglobal = meanglobal - else: - reducemeanglobal = comm.reduce(meanglobal, op=MPI.SUM, root=0) - + reducemeanglobal = comm.reduce(meanglobal, op=MPI.SUM, root=0) if rank == 0: reducemeanglobal = reducemeanglobal / world_size - - if isinstance(comm, MockComm): - reducemaxglobal = maxglobal - else: - reducemaxglobal = comm.reduce(maxglobal, op=MPI.MAX, root=0) + reducemaxglobal = comm.reduce(maxglobal, op=MPI.MAX, root=0) if rank == 0: assert reducemeanlocal < 10.0 * reducemeanglobal or reducemeanlocal < 10.0**-5 - print(f"{reducemeanlocal =}") - print(f"{reducemaxlocal =}") - print(f"{reducemeanglobal =}") - print(f"{reducemaxglobal =}") + print(f"{reducemeanlocal = }") + print(f"{reducemaxlocal = }") + print(f"{reducemeanglobal = }") + print(f"{reducemaxglobal = }") if do_plot: if out_sp_key == "0" or out_sp_key == "3": @@ -1377,7 +1408,7 @@ def aux_test_spline_evaluation(Nel, plist, spl_kind): # Helper function to handle reshaping and getting spans and basis def process_eta(eta, w1d): if isinstance(eta, (float, int)): - eta = xp.array([eta]) + eta = np.array([eta]) if len(eta.shape) == 1: eta = eta.reshape((eta.shape[0], 1)) spans, values = get_span_and_basis(eta, w1d) @@ -1390,7 +1421,7 @@ def fun(eta1, eta2, eta3): eta = eta_map[dim_idx] w1d = W1ds[0][dim_idx] if is_B else V1ds[0][dim_idx] - out = xp.zeros_like(eta) + out = np.zeros_like(eta) for j1 in range(eta.shape[0]): for j2 in range(eta.shape[1]): for j3 in range(eta.shape[2]): @@ -1424,10 +1455,10 @@ def fun(eta1, eta2, eta3): fieldD = derham.create_spline_function("fh", "L2") npts_in_D = derham.Vh["3"].npts - etas1 = xp.linspace(0.0, 1.0, 20) - etas2 = xp.linspace(0.0, 1.0, 20) - etas3 = xp.linspace(0.0, 1.0, 20) - meshgrid = xp.meshgrid(*[etas1, etas2, etas3], indexing="ij") + etas1 = np.linspace(0.0, 1.0, 20) + etas2 = np.linspace(0.0, 1.0, 20) + etas3 = np.linspace(0.0, 1.0, 20) + meshgrid = np.meshgrid(*[etas1, etas2, etas3], indexing="ij") maxerrorB = 0.0 @@ -1440,7 +1471,7 @@ def fun(eta1, eta2, eta3): fieldB.vector = inputB def error(e1, e2, e3): - return xp.abs( + return np.abs( fieldB(e1, e2, e3) - ( make_basis_fun(True, 0, col0)(e1, e2, e3) @@ -1449,13 +1480,13 @@ def error(e1, e2, e3): ), ) - auxerror = xp.max(error(*meshgrid)) + auxerror = np.max(error(*meshgrid)) if auxerror > maxerrorB: maxerrorB = auxerror inputB[col0, col1, col2] = 0.0 - print(f"{maxerrorB =}") + print(f"{maxerrorB = }") assert maxerrorB < 10.0**-13 maxerrorD = 0.0 @@ -1468,7 +1499,7 @@ def error(e1, e2, e3): fieldD.vector = inputD def error(e1, e2, e3): - return xp.abs( + return np.abs( fieldD(e1, e2, e3) - ( make_basis_fun(False, 0, col0)(e1, e2, e3) @@ -1477,13 +1508,13 @@ def error(e1, e2, e3): ), ) - auxerror = xp.max(error(*meshgrid)) + auxerror = np.max(error(*meshgrid)) if auxerror > maxerrorD: maxerrorD = auxerror inputD[col0, col1, col2] = 0.0 - print(f"{maxerrorD =}") + print(f"{maxerrorD = }") assert maxerrorD < 10.0**-13 print("Test spline evaluation passed.") diff --git a/src/struphy/feec/tests/test_lowdim_nel_is_1.py b/src/struphy/feec/tests/test_lowdim_nel_is_1.py index 325da31ea..fe8c11727 100644 --- a/src/struphy/feec/tests/test_lowdim_nel_is_1.py +++ b/src/struphy/feec/tests/test_lowdim_nel_is_1.py @@ -1,21 +1,23 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[32, 1, 1], [1, 32, 1], [1, 1, 32], [31, 32, 1], [32, 1, 31], [1, 31, 32]]) @pytest.mark.parametrize("p", [[1, 1, 1]]) @pytest.mark.parametrize("spl_kind", [[True, True, True]]) def test_lowdim_derham(Nel, p, spl_kind, do_plot=False): """Test Nel=1 in various directions.""" - import cunumpy as xp + import numpy as np from matplotlib import pyplot as plt - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector from struphy.feec.psydac_derham import Derham comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() print("Nel=", Nel) @@ -74,17 +76,17 @@ def test_lowdim_derham(Nel, p, spl_kind, do_plot=False): ### TEST COMMUTING PROJECTORS ### ################################# def fun(eta): - return xp.cos(2 * xp.pi * eta) + return np.cos(2 * np.pi * eta) def dfun(eta): - return -2 * xp.pi * xp.sin(2 * xp.pi * eta) + return -2 * np.pi * np.sin(2 * np.pi * eta) # evaluation points and gradient e1 = 0.0 e2 = 0.0 e3 = 0.0 if Nel[0] > 1: - e1 = xp.linspace(0.0, 1.0, 100) + e1 = np.linspace(0.0, 1.0, 100) e = e1 c = 0 @@ -95,12 +97,12 @@ def dfx(x, y, z): return dfun(x) def dfy(x, y, z): - return xp.zeros_like(x) + return np.zeros_like(x) def dfz(x, y, z): - return xp.zeros_like(x) + return np.zeros_like(x) elif Nel[1] > 1: - e2 = xp.linspace(0.0, 1.0, 100) + e2 = np.linspace(0.0, 1.0, 100) e = e2 c = 1 @@ -108,15 +110,15 @@ def f(x, y, z): return fun(y) def dfx(x, y, z): - return xp.zeros_like(y) + return np.zeros_like(y) def dfy(x, y, z): return dfun(y) def dfz(x, y, z): - return xp.zeros_like(y) + return np.zeros_like(y) elif Nel[2] > 1: - e3 = xp.linspace(0.0, 1.0, 100) + e3 = np.linspace(0.0, 1.0, 100) e = e3 c = 2 @@ -124,10 +126,10 @@ def f(x, y, z): return fun(z) def dfx(x, y, z): - return xp.zeros_like(z) + return np.zeros_like(z) def dfy(x, y, z): - return xp.zeros_like(z) + return np.zeros_like(z) def dfz(x, y, z): return dfun(z) @@ -160,22 +162,22 @@ def div_f(x, y, z): field_f0_vals = field_f0(e1, e2, e3, squeeze_out=True) # a) projection error - err_f0 = xp.max(xp.abs(f(e1, e2, e3) - field_f0_vals)) - print(f"\n{err_f0 =}") + err_f0 = np.max(np.abs(f(e1, e2, e3) - field_f0_vals)) + print(f"\n{err_f0 = }") assert err_f0 < 1e-2 # b) commuting property df0_h = derham.grad.dot(f0_h) - assert xp.allclose(df0_h.toarray(), proj_of_grad_f.toarray()) + assert np.allclose(df0_h.toarray(), proj_of_grad_f.toarray()) # c) derivative error field_df0 = derham.create_spline_function("df0", "Hcurl") field_df0.vector = df0_h field_df0_vals = field_df0(e1, e2, e3, squeeze_out=True) - err_df0 = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(grad_f, field_df0_vals)] - print(f"{err_df0 =}") - assert xp.max(err_df0) < 0.64 + err_df0 = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(grad_f, field_df0_vals)] + print(f"{err_df0 = }") + assert np.max(err_df0) < 0.64 # d) plotting plt.figure(figsize=(8, 12)) @@ -202,22 +204,22 @@ def div_f(x, y, z): field_f1_vals = field_f1(e1, e2, e3, squeeze_out=True) # a) projection error - err_f1 = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip([f, f, f], field_f1_vals)] - print(f"{err_f1 =}") - assert xp.max(err_f1) < 0.09 + err_f1 = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip([f, f, f], field_f1_vals)] + print(f"{err_f1 = }") + assert np.max(err_f1) < 0.09 # b) commuting property df1_h = derham.curl.dot(f1_h) - assert xp.allclose(df1_h.toarray(), proj_of_curl_fff.toarray()) + assert np.allclose(df1_h.toarray(), proj_of_curl_fff.toarray()) # c) derivative error field_df1 = derham.create_spline_function("df1", "Hdiv") field_df1.vector = df1_h field_df1_vals = field_df1(e1, e2, e3, squeeze_out=True) - err_df1 = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(curl_f, field_df1_vals)] - print(f"{err_df1 =}") - assert xp.max(err_df1) < 0.64 + err_df1 = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(curl_f, field_df1_vals)] + print(f"{err_df1 = }") + assert np.max(err_df1) < 0.64 # d) plotting plt.figure(figsize=(8, 12)) @@ -249,22 +251,22 @@ def div_f(x, y, z): field_f2_vals = field_f2(e1, e2, e3, squeeze_out=True) # a) projection error - err_f2 = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip([f, f, f], field_f2_vals)] - print(f"{err_f2 =}") - assert xp.max(err_f2) < 0.09 + err_f2 = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip([f, f, f], field_f2_vals)] + print(f"{err_f2 = }") + assert np.max(err_f2) < 0.09 # b) commuting property df2_h = derham.div.dot(f2_h) - assert xp.allclose(df2_h.toarray(), proj_of_div_fff.toarray()) + assert np.allclose(df2_h.toarray(), proj_of_div_fff.toarray()) # c) derivative error field_df2 = derham.create_spline_function("df2", "L2") field_df2.vector = df2_h field_df2_vals = field_df2(e1, e2, e3, squeeze_out=True) - err_df2 = xp.max(xp.abs(div_f(e1, e2, e3) - field_df2_vals)) - print(f"{err_df2 =}") - assert xp.max(err_df2) < 0.64 + err_df2 = np.max(np.abs(div_f(e1, e2, e3) - field_df2_vals)) + print(f"{err_df2 = }") + assert np.max(err_df2) < 0.64 # d) plotting plt.figure(figsize=(8, 12)) @@ -277,7 +279,7 @@ def div_f(x, y, z): plt.subplot(2, 1, 2) plt.plot(e, div_f(e1, e2, e3), "o") plt.plot(e, field_df2_vals) - plt.title("div") + plt.title(f"div") plt.subplots_adjust(wspace=1.0, hspace=0.4) @@ -291,8 +293,8 @@ def div_f(x, y, z): field_f3_vals = field_f3(e1, e2, e3, squeeze_out=True) # a) projection error - err_f3 = xp.max(xp.abs(f(e1, e2, e3) - field_f3_vals)) - print(f"{err_f3 =}") + err_f3 = np.max(np.abs(f(e1, e2, e3) - field_f3_vals)) + print(f"{err_f3 = }") assert err_f3 < 0.09 # d) plotting diff --git a/src/struphy/feec/tests/test_mass_matrices.py b/src/struphy/feec/tests/test_mass_matrices.py index e1d629c2e..5f05c3228 100644 --- a/src/struphy/feec/tests/test_mass_matrices.py +++ b/src/struphy/feec/tests/test_mass_matrices.py @@ -1,6 +1,7 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[5, 6, 7]]) @pytest.mark.parametrize("p", [[2, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [True, False, True]]) @@ -12,8 +13,8 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): """Compare Struphy mass matrices to Struphy-legacy mass matrices.""" - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.mhd_operators import MHDOperators from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space @@ -48,7 +49,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): eq_mhd = ShearedSlab( **{ "a": (mapping[1]["r1"] - mapping[1]["l1"]), - "R0": (mapping[1]["r3"] - mapping[1]["l3"]) / (2 * xp.pi), + "R0": (mapping[1]["r3"] - mapping[1]["l3"]) / (2 * np.pi), "B0": 1.0, "q0": 1.05, "q1": 1.8, @@ -56,14 +57,14 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) elif mapping[0] == "Colella": eq_mhd = ShearedSlab( **{ "a": mapping[1]["Lx"], - "R0": mapping[1]["Lz"] / (2 * xp.pi), + "R0": mapping[1]["Lz"] / (2 * np.pi), "B0": 1.0, "q0": 1.05, "q1": 1.8, @@ -71,7 +72,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) if show_plots: @@ -89,7 +90,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) if show_plots: @@ -106,7 +107,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): dirichlet_bc = [(False, False)] * 3 dirichlet_bc = tuple(dirichlet_bc) - print(f"{dirichlet_bc =}") + print(f"{dirichlet_bc = }") # derham object derham = Derham(Nel, p, spl_kind, comm=mpi_comm, dirichlet_bc=dirichlet_bc) @@ -124,7 +125,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): # test calling the diagonal method aaa = mass_mats.M0.matrix.diagonal() bbb = mass_mats.M1.matrix.diagonal() - print(f"{aaa =}, {bbb[0, 0] =}, {bbb[0, 1] =}") + print(f"{aaa = }, {bbb[0, 0] = }, {bbb[0, 1] = }") # compare to old STRUPHY bc_old = [[None, None], [None, None], [None, None]] @@ -220,11 +221,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): # Change order of input in callable rM1ninvswitch_psy = mass_mats.create_weighted_mass( - "Hcurl", - "Hcurl", - weights=["sqrt_g", "1/eq_n0", "Ginv"], - name="M1ninv", - assemble=True, + "Hcurl", "Hcurl", weights=["sqrt_g", "1/eq_n0", "Ginv"], name="M1ninv", assemble=True ).dot(x1_psy, apply_bc=True) rot_B = RotationMatrix( @@ -233,11 +230,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): mass_mats.weights[mass_mats.selected_weight].b2_3, ) rM1Bninvswitch_psy = mass_mats.create_weighted_mass( - "Hcurl", - "Hcurl", - weights=["1/eq_n0", "sqrt_g", "Ginv", rot_B, "Ginv"], - name="M1Bninv", - assemble=True, + "Hcurl", "Hcurl", weights=["1/eq_n0", "sqrt_g", "Ginv", rot_B, "Ginv"], name="M1Bninv", assemble=True ).dot(x1_psy, apply_bc=True) # Test matrix free operators @@ -263,11 +256,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): # Change order of input in callable rM1ninvswitch_fre = mass_mats_free.create_weighted_mass( - "Hcurl", - "Hcurl", - weights=["sqrt_g", "1/eq_n0", "Ginv"], - name="M1ninvswitch", - assemble=True, + "Hcurl", "Hcurl", weights=["sqrt_g", "1/eq_n0", "Ginv"], name="M1ninvswitch", assemble=True ).dot(x1_psy, apply_bc=True) rot_B = RotationMatrix( mass_mats_free.weights[mass_mats_free.selected_weight].b2_1, @@ -276,11 +265,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): ) rM1Bninvswitch_fre = mass_mats_free.create_weighted_mass( - "Hcurl", - "Hcurl", - weights=["1/eq_n0", "sqrt_g", "Ginv", rot_B, "Ginv"], - name="M1Bninvswitch", - assemble=True, + "Hcurl", "Hcurl", weights=["1/eq_n0", "sqrt_g", "Ginv", rot_B, "Ginv"], name="M1Bninvswitch", assemble=True ).dot(x1_psy, apply_bc=True) # compare output arrays @@ -381,6 +366,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): print(f"Rank {mpi_rank} | All tests passed!") +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 12, 6]]) @pytest.mark.parametrize("p", [[2, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -392,8 +378,8 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): """Compare Struphy polar mass matrices to Struphy-legacy polar mass matrices.""" - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.mhd_operators import MHDOperators from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space @@ -436,7 +422,7 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) if show_plots: @@ -456,14 +442,7 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): # derham object derham = Derham( - Nel, - p, - spl_kind, - comm=mpi_comm, - dirichlet_bc=dirichlet_bc, - with_projectors=False, - polar_ck=1, - domain=domain, + Nel, p, spl_kind, comm=mpi_comm, dirichlet_bc=dirichlet_bc, with_projectors=False, polar_ck=1, domain=domain ) print(f"Rank {mpi_rank} | Local domain : " + str(derham.domain_array[mpi_rank])) @@ -522,11 +501,11 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): x2_pol_psy.tp = x2_psy x3_pol_psy.tp = x3_psy - xp.random.seed(1607) - x0_pol_psy.pol = [xp.random.rand(x0_pol_psy.pol[0].shape[0], x0_pol_psy.pol[0].shape[1])] - x1_pol_psy.pol = [xp.random.rand(x1_pol_psy.pol[n].shape[0], x1_pol_psy.pol[n].shape[1]) for n in range(3)] - x2_pol_psy.pol = [xp.random.rand(x2_pol_psy.pol[n].shape[0], x2_pol_psy.pol[n].shape[1]) for n in range(3)] - x3_pol_psy.pol = [xp.random.rand(x3_pol_psy.pol[0].shape[0], x3_pol_psy.pol[0].shape[1])] + np.random.seed(1607) + x0_pol_psy.pol = [np.random.rand(x0_pol_psy.pol[0].shape[0], x0_pol_psy.pol[0].shape[1])] + x1_pol_psy.pol = [np.random.rand(x1_pol_psy.pol[n].shape[0], x1_pol_psy.pol[n].shape[1]) for n in range(3)] + x2_pol_psy.pol = [np.random.rand(x2_pol_psy.pol[n].shape[0], x2_pol_psy.pol[n].shape[1]) for n in range(3)] + x3_pol_psy.pol = [np.random.rand(x3_pol_psy.pol[0].shape[0], x3_pol_psy.pol[0].shape[1])] # apply boundary conditions to old STRUPHY x0_pol_str = x0_pol_psy.toarray(True) @@ -556,12 +535,12 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): rn_pol_psy = mass_mats.M2n.dot(x2_pol_psy, apply_bc=True) rJ_pol_psy = mass_mats.M2J.dot(x2_pol_psy, apply_bc=True) - assert xp.allclose(r0_pol_str, r0_pol_psy.toarray(True)) - assert xp.allclose(r1_pol_str, r1_pol_psy.toarray(True)) - assert xp.allclose(r2_pol_str, r2_pol_psy.toarray(True)) - assert xp.allclose(r3_pol_str, r3_pol_psy.toarray(True)) - assert xp.allclose(rn_pol_str, rn_pol_psy.toarray(True)) - assert xp.allclose(rJ_pol_str, rJ_pol_psy.toarray(True)) + assert np.allclose(r0_pol_str, r0_pol_psy.toarray(True)) + assert np.allclose(r1_pol_str, r1_pol_psy.toarray(True)) + assert np.allclose(r2_pol_str, r2_pol_psy.toarray(True)) + assert np.allclose(r3_pol_str, r3_pol_psy.toarray(True)) + assert np.allclose(rn_pol_str, rn_pol_psy.toarray(True)) + assert np.allclose(rJ_pol_str, rJ_pol_psy.toarray(True)) # perfrom matrix-vector products (without boundary conditions) r0_pol_str = space.M0(x0_pol_str) @@ -574,16 +553,17 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): r2_pol_psy = mass_mats.M2.dot(x2_pol_psy, apply_bc=False) r3_pol_psy = mass_mats.M3.dot(x3_pol_psy, apply_bc=False) - assert xp.allclose(r0_pol_str, r0_pol_psy.toarray(True)) - assert xp.allclose(r1_pol_str, r1_pol_psy.toarray(True)) - assert xp.allclose(r2_pol_str, r2_pol_psy.toarray(True)) - assert xp.allclose(r3_pol_str, r3_pol_psy.toarray(True)) - assert xp.allclose(rn_pol_str, rn_pol_psy.toarray(True)) - assert xp.allclose(rJ_pol_str, rJ_pol_psy.toarray(True)) + assert np.allclose(r0_pol_str, r0_pol_psy.toarray(True)) + assert np.allclose(r1_pol_str, r1_pol_psy.toarray(True)) + assert np.allclose(r2_pol_str, r2_pol_psy.toarray(True)) + assert np.allclose(r3_pol_str, r3_pol_psy.toarray(True)) + assert np.allclose(rn_pol_str, rn_pol_psy.toarray(True)) + assert np.allclose(rJ_pol_str, rJ_pol_psy.toarray(True)) print(f"Rank {mpi_rank} | All tests passed!") +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 12, 6]]) @pytest.mark.parametrize("p", [[2, 3, 2]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -598,8 +578,8 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots import time - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from psydac.linalg.solvers import inverse from struphy.feec.mass import WeightedMassOperators, WeightedMassOperatorsOldForTesting @@ -634,7 +614,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots eq_mhd = ShearedSlab( **{ "a": (mapping[1]["r1"] - mapping[1]["l1"]), - "R0": (mapping[1]["r3"] - mapping[1]["l3"]) / (2 * xp.pi), + "R0": (mapping[1]["r3"] - mapping[1]["l3"]) / (2 * np.pi), "B0": 1.0, "q0": 1.05, "q1": 1.8, @@ -642,14 +622,14 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) elif mapping[0] == "Colella": eq_mhd = ShearedSlab( **{ "a": mapping[1]["Lx"], - "R0": mapping[1]["Lz"] / (2 * xp.pi), + "R0": mapping[1]["Lz"] / (2 * np.pi), "B0": 1.0, "q0": 1.05, "q1": 1.8, @@ -657,7 +637,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) if show_plots: @@ -675,7 +655,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) if show_plots: @@ -774,27 +754,27 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots print("Done") # compare output arrays - assert xp.allclose(r0.toarray(), r0_pre.toarray()) - assert xp.allclose(r1.toarray(), r1_pre.toarray()) - assert xp.allclose(r2.toarray(), r2_pre.toarray()) - assert xp.allclose(r3.toarray(), r3_pre.toarray()) - assert xp.allclose(rv.toarray(), rv_pre.toarray()) + assert np.allclose(r0.toarray(), r0_pre.toarray()) + assert np.allclose(r1.toarray(), r1_pre.toarray()) + assert np.allclose(r2.toarray(), r2_pre.toarray()) + assert np.allclose(r3.toarray(), r3_pre.toarray()) + assert np.allclose(rv.toarray(), rv_pre.toarray()) - assert xp.allclose(r1n.toarray(), r1n_pre.toarray()) - assert xp.allclose(r2n.toarray(), r2n_pre.toarray()) - assert xp.allclose(rvn.toarray(), rvn_pre.toarray()) + assert np.allclose(r1n.toarray(), r1n_pre.toarray()) + assert np.allclose(r2n.toarray(), r2n_pre.toarray()) + assert np.allclose(rvn.toarray(), rvn_pre.toarray()) - assert xp.allclose(r1Bninv.toarray(), r1Bninv_pre.toarray()) - assert xp.allclose(r1Bninv.toarray(), r1Bninvold_pre.toarray()) - assert xp.allclose(r1Bninvold.toarray(), r1Bninv_pre.toarray()) + assert np.allclose(r1Bninv.toarray(), r1Bninv_pre.toarray()) + assert np.allclose(r1Bninv.toarray(), r1Bninvold_pre.toarray()) + assert np.allclose(r1Bninvold.toarray(), r1Bninv_pre.toarray()) # test if preconditioner satisfies PC * M = Identity if mapping[0] == "Cuboid" or mapping[0] == "HollowCylinder": - assert xp.allclose(mass_mats.M0.dot(M0pre.solve(x0)).toarray(), derham.boundary_ops["0"].dot(x0).toarray()) - assert xp.allclose(mass_mats.M1.dot(M1pre.solve(x1)).toarray(), derham.boundary_ops["1"].dot(x1).toarray()) - assert xp.allclose(mass_mats.M2.dot(M2pre.solve(x2)).toarray(), derham.boundary_ops["2"].dot(x2).toarray()) - assert xp.allclose(mass_mats.M3.dot(M3pre.solve(x3)).toarray(), derham.boundary_ops["3"].dot(x3).toarray()) - assert xp.allclose(mass_mats.Mv.dot(Mvpre.solve(xv)).toarray(), derham.boundary_ops["v"].dot(xv).toarray()) + assert np.allclose(mass_mats.M0.dot(M0pre.solve(x0)).toarray(), derham.boundary_ops["0"].dot(x0).toarray()) + assert np.allclose(mass_mats.M1.dot(M1pre.solve(x1)).toarray(), derham.boundary_ops["1"].dot(x1).toarray()) + assert np.allclose(mass_mats.M2.dot(M2pre.solve(x2)).toarray(), derham.boundary_ops["2"].dot(x2).toarray()) + assert np.allclose(mass_mats.M3.dot(M3pre.solve(x3)).toarray(), derham.boundary_ops["3"].dot(x3).toarray()) + assert np.allclose(mass_mats.Mv.dot(Mvpre.solve(xv)).toarray(), derham.boundary_ops["v"].dot(xv).toarray()) # test preconditioner in iterative solver M0inv = inverse(mass_mats.M0, "pcg", pc=M0pre, tol=1e-8, maxiter=1000) @@ -891,6 +871,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots print(f"Rank {mpi_rank} | All tests passed!") +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 6]]) @pytest.mark.parametrize("p", [[2, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -905,8 +886,8 @@ def test_mass_preconditioner_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show import time - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from psydac.linalg.solvers import inverse from struphy.feec.mass import WeightedMassOperators @@ -949,7 +930,7 @@ def test_mass_preconditioner_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show "n2": 4.0, "na": 0.0, "beta": 0.1, - }, + } ) if show_plots: @@ -969,14 +950,7 @@ def test_mass_preconditioner_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show # derham object derham = Derham( - Nel, - p, - spl_kind, - comm=mpi_comm, - dirichlet_bc=dirichlet_bc, - with_projectors=False, - polar_ck=1, - domain=domain, + Nel, p, spl_kind, comm=mpi_comm, dirichlet_bc=dirichlet_bc, with_projectors=False, polar_ck=1, domain=domain ) print(f"Rank {mpi_rank} | Local domain : " + str(derham.domain_array[mpi_rank])) @@ -1016,11 +990,11 @@ def test_mass_preconditioner_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show x2_pol.tp = x2 x3_pol.tp = x3 - xp.random.seed(1607) - x0_pol.pol = [xp.random.rand(x0_pol.pol[0].shape[0], x0_pol.pol[0].shape[1])] - x1_pol.pol = [xp.random.rand(x1_pol.pol[n].shape[0], x1_pol.pol[n].shape[1]) for n in range(3)] - x2_pol.pol = [xp.random.rand(x2_pol.pol[n].shape[0], x2_pol.pol[n].shape[1]) for n in range(3)] - x3_pol.pol = [xp.random.rand(x3_pol.pol[0].shape[0], x3_pol.pol[0].shape[1])] + np.random.seed(1607) + x0_pol.pol = [np.random.rand(x0_pol.pol[0].shape[0], x0_pol.pol[0].shape[1])] + x1_pol.pol = [np.random.rand(x1_pol.pol[n].shape[0], x1_pol.pol[n].shape[1]) for n in range(3)] + x2_pol.pol = [np.random.rand(x2_pol.pol[n].shape[0], x2_pol.pol[n].shape[1]) for n in range(3)] + x3_pol.pol = [np.random.rand(x3_pol.pol[0].shape[0], x3_pol.pol[0].shape[1])] # test preconditioner in iterative solver and compare to case without preconditioner M0inv = inverse(mass_mats.M0, "pcg", pc=M0pre, tol=1e-8, maxiter=500) diff --git a/src/struphy/feec/tests/test_toarray_struphy.py b/src/struphy/feec/tests/test_toarray_struphy.py index 90427d8e4..3b7386fca 100644 --- a/src/struphy/feec/tests/test_toarray_struphy.py +++ b/src/struphy/feec/tests/test_toarray_struphy.py @@ -1,20 +1,20 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[12, 5, 2], [8, 12, 4], [5, 4, 12]]) @pytest.mark.parametrize("p", [[3, 2, 1]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [True, False, False]]) @pytest.mark.parametrize( - "mapping", - [["Cuboid", {"l1": 1.0, "r1": 2.0, "l2": 10.0, "r2": 20.0, "l3": 100.0, "r3": 200.0}]], + "mapping", [["Cuboid", {"l1": 1.0, "r1": 2.0, "l2": 10.0, "r2": 20.0, "l3": 100.0, "r3": 200.0}]] ) def test_toarray_struphy(Nel, p, spl_kind, mapping): """ TODO """ - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.psydac_derham import Derham @@ -33,7 +33,7 @@ def test_toarray_struphy(Nel, p, spl_kind, mapping): domain = domain_class(**dom_params) # create derham object - derham = Derham(Nel, p, spl_kind, comm=comm) + derham = Derham(Nel, p, spl_kind, comm=MPI.COMM_WORLD) # assemble mass matrices in V0 and V1 mass = WeightedMassOperators(derham, domain) @@ -71,30 +71,30 @@ def test_toarray_struphy(Nel, p, spl_kind, mapping): v3arr = v3arr[0].flatten() # not in-place - compare_arrays(M0.dot(v0), xp.matmul(M0arr, v0arr), rank) - compare_arrays(M1.dot(v1), xp.matmul(M1arr, v1arr), rank) - compare_arrays(M2.dot(v2), xp.matmul(M2arr, v2arr), rank) - compare_arrays(M3.dot(v3), xp.matmul(M3arr, v3arr), rank) + compare_arrays(M0.dot(v0), np.matmul(M0arr, v0arr), rank) + compare_arrays(M1.dot(v1), np.matmul(M1arr, v1arr), rank) + compare_arrays(M2.dot(v2), np.matmul(M2arr, v2arr), rank) + compare_arrays(M3.dot(v3), np.matmul(M3arr, v3arr), rank) # Now we test the in-place version - IM0 = xp.zeros([M0.codomain.dimension, M0.domain.dimension], dtype=M0.dtype) - IM1 = xp.zeros([M1.codomain.dimension, M1.domain.dimension], dtype=M1.dtype) - IM2 = xp.zeros([M2.codomain.dimension, M2.domain.dimension], dtype=M2.dtype) - IM3 = xp.zeros([M3.codomain.dimension, M3.domain.dimension], dtype=M3.dtype) + IM0 = np.zeros([M0.codomain.dimension, M0.domain.dimension], dtype=M0.dtype) + IM1 = np.zeros([M1.codomain.dimension, M1.domain.dimension], dtype=M1.dtype) + IM2 = np.zeros([M2.codomain.dimension, M2.domain.dimension], dtype=M2.dtype) + IM3 = np.zeros([M3.codomain.dimension, M3.domain.dimension], dtype=M3.dtype) M0.toarray_struphy(out=IM0) M1.toarray_struphy(out=IM1) M2.toarray_struphy(out=IM2) M3.toarray_struphy(out=IM3) - compare_arrays(M0.dot(v0), xp.matmul(IM0, v0arr), rank) - compare_arrays(M1.dot(v1), xp.matmul(IM1, v1arr), rank) - compare_arrays(M2.dot(v2), xp.matmul(IM2, v2arr), rank) - compare_arrays(M3.dot(v3), xp.matmul(IM3, v3arr), rank) + compare_arrays(M0.dot(v0), np.matmul(IM0, v0arr), rank) + compare_arrays(M1.dot(v1), np.matmul(IM1, v1arr), rank) + compare_arrays(M2.dot(v2), np.matmul(IM2, v2arr), rank) + compare_arrays(M3.dot(v3), np.matmul(IM3, v3arr), rank) print("test_toarray_struphy passed!") - # assert xp.allclose(out1.toarray(), v1.toarray(), atol=1e-5) + # assert np.allclose(out1.toarray(), v1.toarray(), atol=1e-5) if __name__ == "__main__": diff --git a/src/struphy/feec/tests/test_tosparse_struphy.py b/src/struphy/feec/tests/test_tosparse_struphy.py index 48cbfd7a2..2e3850890 100644 --- a/src/struphy/feec/tests/test_tosparse_struphy.py +++ b/src/struphy/feec/tests/test_tosparse_struphy.py @@ -3,21 +3,20 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[12, 5, 2], [8, 12, 4], [5, 4, 12]]) @pytest.mark.parametrize("p", [[3, 2, 1]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [True, False, False]]) @pytest.mark.parametrize( - "mapping", - [["Cuboid", {"l1": 1.0, "r1": 2.0, "l2": 10.0, "r2": 20.0, "l3": 100.0, "r3": 200.0}]], + "mapping", [["Cuboid", {"l1": 1.0, "r1": 2.0, "l2": 10.0, "r2": 20.0, "l3": 100.0, "r3": 200.0}]] ) def test_tosparse_struphy(Nel, p, spl_kind, mapping): """ TODO """ - import cunumpy as xp - from psydac.ddm.mpi import MockComm - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.psydac_derham import Derham @@ -75,67 +74,40 @@ def test_tosparse_struphy(Nel, p, spl_kind, mapping): M2arrad = M2.toarray_struphy(is_sparse=True, format="dia") v0_local = M0.dot(v0).toarray() - if isinstance(comm, MockComm): - v0_global = v0_local - else: - v0_global = M0.domain.zeros().toarray() - comm.Allreduce(v0_local, v0_global, op=MPI.SUM) - + v0_global = M0.domain.zeros().toarray() + comm.Allreduce(v0_local, v0_global, op=MPI.SUM) v1_local = M1.dot(v1).toarray() - if isinstance(comm, MockComm): - v1_global = v1_local - else: - v1_global = M1.domain.zeros().toarray() - comm.Allreduce(v1_local, v1_global, op=MPI.SUM) - + v1_global = M1.domain.zeros().toarray() + comm.Allreduce(v1_local, v1_global, op=MPI.SUM) v2_local = M2.dot(v2).toarray() - if isinstance(comm, MockComm): - v2_global = v2_local - else: - v2_global = M2.domain.zeros().toarray() - comm.Allreduce(v2_local, v2_global, op=MPI.SUM) - + v2_global = M2.domain.zeros().toarray() + comm.Allreduce(v2_local, v2_global, op=MPI.SUM) v3_local = M3.dot(v3).toarray() - if isinstance(comm, MockComm): - v3_global = v3_local - else: - v3_global = M3.domain.zeros().toarray() - comm.Allreduce(v3_local, v3_global, op=MPI.SUM) + v3_global = M3.domain.zeros().toarray() + comm.Allreduce(v3_local, v3_global, op=MPI.SUM) # not in-place - assert xp.allclose(v0_global, M0arr.dot(v0arr)) - assert xp.allclose(v1_global, M1arr.dot(v1arr)) - assert xp.allclose(v2_global, M2arr.dot(v2arr)) - assert xp.allclose(v3_global, M3arr.dot(v3arr)) - assert xp.allclose(v0_global, M0arrad.dot(v0arr)) - assert xp.allclose(v1_global, M1arrad.dot(v1arr)) - assert xp.allclose(v2_global, M2arrad.dot(v2arr)) + assert np.allclose(v0_global, M0arr.dot(v0arr)) + assert np.allclose(v1_global, M1arr.dot(v1arr)) + assert np.allclose(v2_global, M2arr.dot(v2arr)) + assert np.allclose(v3_global, M3arr.dot(v3arr)) + assert np.allclose(v0_global, M0arrad.dot(v0arr)) + assert np.allclose(v1_global, M1arrad.dot(v1arr)) + assert np.allclose(v2_global, M2arrad.dot(v2arr)) print("test_tosparse_struphy passed!") if __name__ == "__main__": test_tosparse_struphy( - [32, 2, 2], - [2, 1, 1], - [True, True, True], - ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}], + [32, 2, 2], [2, 1, 1], [True, True, True], ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}] ) test_tosparse_struphy( - [2, 32, 2], - [1, 2, 1], - [True, True, True], - ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}], + [2, 32, 2], [1, 2, 1], [True, True, True], ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}] ) test_tosparse_struphy( - [2, 2, 32], - [1, 1, 2], - [True, True, True], - ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}], + [2, 2, 32], [1, 1, 2], [True, True, True], ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}] ) test_tosparse_struphy( - [2, 2, 32], - [1, 1, 2], - [False, False, False], - ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}], + [2, 2, 32], [1, 1, 2], [False, False, False], ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}] ) diff --git a/src/struphy/feec/tests/xx_test_preconds.py b/src/struphy/feec/tests/xx_test_preconds.py index 267e0279a..c00a8fdaf 100644 --- a/src/struphy/feec/tests/xx_test_preconds.py +++ b/src/struphy/feec/tests/xx_test_preconds.py @@ -12,8 +12,8 @@ ], ) def test_mass_preconditioner(Nel, p, spl_kind, mapping): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -40,22 +40,22 @@ def test_mass_preconditioner(Nel, p, spl_kind, mapping): v = [] v += [StencilVector(derham.V0.coeff_space)] - v[-1]._data = xp.random.rand(*v[-1]._data.shape) + v[-1]._data = np.random.rand(*v[-1]._data.shape) v += [BlockVector(derham.V1.coeff_space)] for v1i in v[-1]: - v1i._data = xp.random.rand(*v1i._data.shape) + v1i._data = np.random.rand(*v1i._data.shape) v += [BlockVector(derham.V2.coeff_space)] for v1i in v[-1]: - v1i._data = xp.random.rand(*v1i._data.shape) + v1i._data = np.random.rand(*v1i._data.shape) v += [StencilVector(derham.V3.coeff_space)] - v[-1]._data = xp.random.rand(*v[-1]._data.shape) + v[-1]._data = np.random.rand(*v[-1]._data.shape) v += [BlockVector(derham.V0vec.coeff_space)] for v1i in v[-1]: - v1i._data = xp.random.rand(*v1i._data.shape) + v1i._data = np.random.rand(*v1i._data.shape) # assemble preconditioners M_pre = [] @@ -68,7 +68,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, mapping): n = "v" if domain.kind_map == 10 or domain.kind_map == 11: - assert xp.allclose(M._mat.toarray(), M_p.matrix.toarray()) + assert np.allclose(M._mat.toarray(), M_p.matrix.toarray()) print(f'Matrix assertion for space {n} case "Cuboid/HollowCylinder" passed.') inv_A = InverseLinearOperator(M, pc=M_p, tol=1e-8, maxiter=5000) diff --git a/src/struphy/feec/utilities.py b/src/struphy/feec/utilities.py index 2fffe38e3..2541f9a63 100644 --- a/src/struphy/feec/utilities.py +++ b/src/struphy/feec/utilities.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np from psydac.api.essential_bc import apply_essential_bc_stencil from psydac.fem.tensor import TensorFemSpace from psydac.fem.vector import VectorFemSpace @@ -41,15 +41,15 @@ def __init__(self, *vec_fun): def __call__(self, e1, e2, e3): # array from 2d list gives 3x3 array is in the first two indices - tmp = xp.array( + tmp = np.array( [ [self._cross_mask[m][n] * fun(e1, e2, e3) for n, fun in enumerate(row)] for m, row in enumerate(self._funs) - ], + ] ) # numpy operates on the last two indices with @ - return xp.transpose(tmp, axes=(2, 3, 4, 0, 1)) + return np.transpose(tmp, axes=(2, 3, 4, 0, 1)) def create_equal_random_arrays(V, seed=123, flattened=False): @@ -77,7 +77,7 @@ def create_equal_random_arrays(V, seed=123, flattened=False): assert isinstance(V, (TensorFemSpace, VectorFemSpace)) - xp.random.seed(seed) + np.random.seed(seed) arr = [] @@ -93,15 +93,13 @@ def create_equal_random_arrays(V, seed=123, flattened=False): dims = V.coeff_space.npts - arr += [xp.random.rand(*dims)] + arr += [np.random.rand(*dims)] s = arr_psy.starts e = arr_psy.ends arr_psy[s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1] = arr[-1][ - s[0] : e[0] + 1, - s[1] : e[1] + 1, - s[2] : e[2] + 1, + s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1 ] if flattened: @@ -113,24 +111,22 @@ def create_equal_random_arrays(V, seed=123, flattened=False): for d, block in enumerate(arr_psy.blocks): dims = V.spaces[d].coeff_space.npts - arr += [xp.random.rand(*dims)] + arr += [np.random.rand(*dims)] s = block.starts e = block.ends arr_psy[d][s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1] = arr[-1][ - s[0] : e[0] + 1, - s[1] : e[1] + 1, - s[2] : e[2] + 1, + s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1 ] if flattened: - arr = xp.concatenate( + arr = np.concatenate( ( arr[0].flatten(), arr[1].flatten(), arr[2].flatten(), - ), + ) ) arr_psy.update_ghost_regions() @@ -172,11 +168,11 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): arr_psy.space.npts[2], )[s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1] - assert xp.allclose(tmp1, tmp2, atol=atol) + assert np.allclose(tmp1, tmp2, atol=atol) elif isinstance(arr_psy, BlockVector): if not (isinstance(arr, tuple) or isinstance(arr, list)): - arrs = xp.split( + arrs = np.split( arr, [ arr_psy.blocks[0].shape[0], @@ -201,7 +197,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): s[2] : e[2] + 1, ] - assert xp.allclose(tmp1, tmp2, atol=atol) + assert np.allclose(tmp1, tmp2, atol=atol) elif isinstance(arr_psy, StencilMatrix): s = arr_psy.codomain.starts @@ -220,7 +216,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): if tmp_arr.shape == tmp1.shape: tmp2 = tmp_arr else: - tmp2 = xp.zeros( + tmp2 = np.zeros( ( e[0] + 1 - s[0], e[1] + 1 - s[1], @@ -233,7 +229,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): ) bts.band_to_stencil_3d(tmp_arr, tmp2) - assert xp.allclose(tmp1, tmp2, atol=atol) + assert np.allclose(tmp1, tmp2, atol=atol) elif isinstance(arr_psy, BlockLinearOperator): for row_psy, row in zip(arr_psy.blocks, arr): @@ -264,7 +260,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): if tmp_mat.shape == tmp1.shape: tmp2 = tmp_mat else: - tmp2 = xp.zeros( + tmp2 = np.zeros( ( e[0] + 1 - s[0], e[1] + 1 - s[1], @@ -277,7 +273,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): ) bts.band_to_stencil_3d(tmp_mat, tmp2) - assert xp.allclose(tmp1, tmp2, atol=atol) + assert np.allclose(tmp1, tmp2, atol=atol) else: raise AssertionError("Wrong input type.") diff --git a/src/struphy/feec/utilities_local_projectors.py b/src/struphy/feec/utilities_local_projectors.py index 3ce6590f2..1831cb31e 100644 --- a/src/struphy/feec/utilities_local_projectors.py +++ b/src/struphy/feec/utilities_local_projectors.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np from struphy.feec.local_projectors_kernels import are_quadrature_points_zero, get_rows, select_quasi_points @@ -33,7 +33,7 @@ def split_points( shifts : 1d int array For each one of the three spatial directions it determines by which amount to shift the position index (pos) in case we have to loop over the evaluation points. - pts : list of xp.array + pts : list of np.array 3D list of 2D array with the quasi-interpolation points (or Gauss-Legendre quadrature points for histopolation). In format (ns, nb, np) = (spatial direction, B-spline index, point) for StencilVector spaces . @@ -50,7 +50,7 @@ def split_points( npts : list of ints Contains the number of B-splines for each one of the three spatial directions. - periodic : 1D bool xp.array + periodic : 1D bool np.array For each one of the three spatial directions contains the information of whether the B-splines are periodic or not. wij: 3d float array @@ -75,18 +75,18 @@ def split_points( """ # We iterate over the three spatial directions for n, pt in enumerate(pts): - original_pts_size[n] = xp.shape(pt)[0] + original_pts_size[n] = np.shape(pt)[0] # We initialize localpts with as many entries as the global pt, but with all entries being -1 # This function will change the values of the needed entries from -1 to the value of the point. if IoH[n] == "I": - localpts = xp.full((xp.shape(pt)[0]), fill_value=-1, dtype=float) + localpts = np.full((np.shape(pt)[0]), fill_value=-1, dtype=float) elif IoH[n] == "H": - localpts = xp.full((xp.shape(pt)), fill_value=-1, dtype=float) + localpts = np.full((np.shape(pt)), fill_value=-1, dtype=float) for i in range(starts[n], ends[n] + 1): startj1, endj1 = select_quasi_points(int(i), int(p[n]), int(npts[n]), bool(periodic[n])) for j1 in range(lenj[n]): - if startj1 + j1 < xp.shape(pt)[0]: + if startj1 + j1 < np.shape(pt)[0]: pos = startj1 + j1 else: pos = int(startj1 + j1 + shift[n]) @@ -98,42 +98,42 @@ def split_points( localpts[pos] = pt[pos] # We get the local points by grabing only the values different from -1. if IoH[n] == "I": - localpos = xp.where(localpts != -1)[0] + localpos = np.where(localpts != -1)[0] elif IoH[n] == "H": - localpos = xp.where(localpts[:, 0] != -1)[0] + localpos = np.where(localpts[:, 0] != -1)[0] localpts = localpts[localpos] - localptsout.append(xp.array(localpts)) + localptsout.append(np.array(localpts)) ## # We build the index_translation array that shall turn global indices into local indices ## - mini_indextranslation = xp.full( - (xp.shape(pt)[0]), + mini_indextranslation = np.full( + (np.shape(pt)[0]), fill_value=-1, dtype=int, ) for i, j in enumerate(localpos): mini_indextranslation[j] = i - index_translation.append(xp.array(mini_indextranslation)) + index_translation.append(np.array(mini_indextranslation)) ## # We build the inv_index_translation that shall turn local indices into global indices ## - inv_mini_indextranslation = xp.full( - (xp.shape(localptsout[-1])[0]), + inv_mini_indextranslation = np.full( + (np.shape(localptsout[-1])[0]), fill_value=-1, dtype=int, ) for i, j in enumerate(localpos): inv_mini_indextranslation[i] = j - inv_index_translation.append(xp.array(inv_mini_indextranslation)) + inv_index_translation.append(np.array(inv_mini_indextranslation)) def get_values_and_indices_splines(Nbasis, degree, periodic, spans, values): - """Given an array with the values of the splines evaluated at certain points this function returns a xp.array that tell us the index of each spline. So we can know to which spline each + """Given an array with the values of the splines evaluated at certain points this function returns a np.array that tell us the index of each spline. So we can know to which spline each value corresponds. It also modifies the evaluation values in the case we have one spline of degree one with periodic boundary conditions, so it is artificially equal to the identity. Parameters @@ -147,31 +147,31 @@ def get_values_and_indices_splines(Nbasis, degree, periodic, spans, values): periodic : bool Whether we have periodic boundary conditions or nor. - span : xp.array + span : np.array 2d array indexed by (n, nq), where n is the interval and nq is the quadrature point in the interval. - values : xp.array + values : np.array 3d array of values of basis functions indexed by (n, nq, basis function). Returns ------- - eval_indeces : xp.array + eval_indeces : np.array 3d array of basis functions indices, indexed by (n, nq, basis function). - values : xp.array + values : np.array 3d array of values of basis functions indexed by (n, nq, basis function). """ # In this case we want this spatial direction to be "neglected", that means we artificially set the values of the B-spline to 1 at all points. So it becomes the multiplicative identity. if Nbasis == 1 and degree == 1 and periodic: # Set all values to 1 for the identity case - values = xp.ones((values.shape[0], values.shape[1], 1)) - eval_indeces = xp.zeros_like(values, dtype=int) + values = np.ones((values.shape[0], values.shape[1], 1)) + eval_indeces = np.zeros_like(values, dtype=int) else: - eval_indeces = xp.zeros_like(values, dtype=int) - for i in range(xp.shape(spans)[0]): - for k in range(xp.shape(spans)[1]): + eval_indeces = np.zeros_like(values, dtype=int) + for i in range(np.shape(spans)[0]): + for k in range(np.shape(spans)[1]): for j in range(degree + 1): eval_indeces[i, k, j] = (spans[i][k] - degree + j) % Nbasis @@ -180,31 +180,31 @@ def get_values_and_indices_splines(Nbasis, degree, periodic, spans, values): def get_one_spline(a, values, eval_indeces): """Given the spline index, an array with the splines evaluated at the evaluation points and another array with the indices indicating to which spline each value corresponds, this function returns - a 1d xp.array with the desired spline evaluated at all evaluation points. + a 1d np.array with the desired spline evaluated at all evaluation points. Parameters ---------- a : int Spline index - values : xp.array + values : np.array 3d array of values of basis functions indexed by (n, nq, basis function). - eval_indeces : xp.array + eval_indeces : np.array 3d array of basis functions indices, indexed by (n, nq, basis function). Returns ------- - my_values : xp.array + my_values : np.array 1d array of values for the spline evaluated at all evaluation points. """ - my_values = xp.zeros(xp.shape(values)[0] * xp.shape(values)[1]) - for i in range(xp.shape(values)[0]): - for j in range(xp.shape(values)[1]): - for k in range(xp.shape(values)[2]): + my_values = np.zeros(np.shape(values)[0] * np.shape(values)[1]) + for i in range(np.shape(values)[0]): + for j in range(np.shape(values)[1]): + for k in range(np.shape(values)[2]): if eval_indeces[i, j, k] == a: - my_values[i * xp.shape(values)[1] + j] = values[i, j, k] + my_values[i * np.shape(values)[1] + j] = values[i, j, k] break return my_values @@ -214,7 +214,7 @@ def get_span_and_basis(pts, space): Parameters ---------- - pts : xp.array + pts : np.array 2d array of points (ii, iq) = (interval, quadrature point). space : SplineSpace @@ -222,10 +222,10 @@ def get_span_and_basis(pts, space): Returns ------- - span : xp.array + span : np.array 2d array indexed by (n, nq), where n is the interval and nq is the quadrature point in the interval. - basis : xp.array + basis : np.array 3d array of values of basis functions indexed by (n, nq, basis function). """ @@ -235,8 +235,8 @@ def get_span_and_basis(pts, space): T = space.knots p = space.degree - span = xp.zeros(pts.shape, dtype=int) - basis = xp.zeros((*pts.shape, p + 1), dtype=float) + span = np.zeros(pts.shape, dtype=int) + basis = np.zeros((*pts.shape, p + 1), dtype=float) for n in range(pts.shape[0]): for nq in range(pts.shape[1]): @@ -464,7 +464,7 @@ def get_non_zero_B_spline_indices(periodic, IoH, p, B_nbasis, starts, ends, Basi stuck, ) - Basis_functions_indices_B.append(xp.array(aux_indices)) + Basis_functions_indices_B.append(np.array(aux_indices)) def get_non_zero_D_spline_indices(periodic, IoH, p, D_nbasis, starts, ends, Basis_functions_indices_D): @@ -522,7 +522,7 @@ def get_non_zero_D_spline_indices(periodic, IoH, p, D_nbasis, starts, ends, Basi stuck, ) - Basis_functions_indices_D.append(xp.array(aux_indices)) + Basis_functions_indices_D.append(np.array(aux_indices)) def build_translation_list_for_non_zero_spline_indices( @@ -584,18 +584,18 @@ def build_translation_list_for_non_zero_spline_indices( """ translation_indices_B_or_D_splines = [ { - "B": xp.full((B_nbasis[h]), fill_value=-1, dtype=int), - "D": xp.full((D_nbasis[h]), fill_value=-1, dtype=int), + "B": np.full((B_nbasis[h]), fill_value=-1, dtype=int), + "D": np.full((D_nbasis[h]), fill_value=-1, dtype=int), } for h in range(3) ] for h in range(3): - translation_indices_B_or_D_splines[h]["B"][Basis_functions_indices_B[h]] = xp.arange( - len(Basis_functions_indices_B[h]), + translation_indices_B_or_D_splines[h]["B"][Basis_functions_indices_B[h]] = np.arange( + len(Basis_functions_indices_B[h]) ) - translation_indices_B_or_D_splines[h]["D"][Basis_functions_indices_D[h]] = xp.arange( - len(Basis_functions_indices_D[h]), + translation_indices_B_or_D_splines[h]["D"][Basis_functions_indices_D[h]] = np.arange( + len(Basis_functions_indices_D[h]) ) if sp_id in {"Hcurl", "Hdiv", "H1vec"}: @@ -610,11 +610,7 @@ def build_translation_list_for_non_zero_spline_indices( def evaluate_relevant_splines_at_relevant_points( - localpts, - Bspaces_1d, - Dspaces_1d, - Basis_functions_indices_B, - Basis_functions_indices_D, + localpts, Bspaces_1d, Dspaces_1d, Basis_functions_indices_B, Basis_functions_indices_D ): """This function evaluates all the B and D-splines that produce non-zeros in the BasisProjectionOperatorLocal's rows that belong to the current MPI rank over all the local evaluation points. They are store as float arrays in a dictionary of lists. @@ -658,7 +654,7 @@ def evaluate_relevant_splines_at_relevant_points( for h in range(3): # Reshape localpts[h] if necessary localpts_reshaped = ( - localpts[h].reshape((xp.shape(localpts[h])[0], 1)) if len(xp.shape(localpts[h])) == 1 else localpts[h] + localpts[h].reshape((np.shape(localpts[h])[0], 1)) if len(np.shape(localpts[h])) == 1 else localpts[h] ) # Get spans and evaluation values for B-splines and D-splines @@ -697,15 +693,7 @@ def evaluate_relevant_splines_at_relevant_points( def determine_non_zero_rows_for_each_spline( - Basis_functions_indices_B, - Basis_functions_indices_D, - starts, - ends, - p, - B_nbasis, - D_nbasis, - periodic, - IoH, + Basis_functions_indices_B, Basis_functions_indices_D, starts, ends, p, B_nbasis, D_nbasis, periodic, IoH ): """This function determines for which rows (amongst those belonging to the current MPI rank) of the BasisProjectionOperatorLocal each B and D spline, of relevance for the current MPI rank, produces non-zero entries and annotates this regions of non-zeros by saving the rows at which each region starts and ends. @@ -772,7 +760,7 @@ def determine_non_zero_rows_for_each_spline( def process_splines(indices, nbasis, is_D, h): for i in indices[h]: - aux = xp.zeros((ends[h] + 1 - starts[h]), dtype=int) + aux = np.zeros((ends[h] + 1 - starts[h]), dtype=int) get_rows( int(i), int(starts[h]), @@ -786,8 +774,8 @@ def process_splines(indices, nbasis, is_D, h): ) rangestart, rangeend = transform_into_ranges(aux) key = "D" if is_D else "B" - rows_B_or_D_splines[h][key].append(xp.array(rangestart, dtype=int)) - rowe_B_or_D_splines[h][key].append(xp.array(rangeend, dtype=int)) + rows_B_or_D_splines[h][key].append(np.array(rangestart, dtype=int)) + rowe_B_or_D_splines[h][key].append(np.array(rangeend, dtype=int)) for h in range(3): process_splines(Basis_functions_indices_B, B_nbasis, False, h) @@ -804,8 +792,7 @@ def process_splines(indices, nbasis, is_D, h): def get_splines_that_are_relevant_for_at_least_one_block( - Basis_function_indices_agreggated_B, - Basis_function_indices_agreggated_D, + Basis_function_indices_agreggated_B, Basis_function_indices_agreggated_D ): """This function builds one list with all the B-spline indices (and another one for the D-splines) that are required for at least one block of the FE coefficients the current MPI rank needs to build its share of the BasisProjectionOperatorLocal. @@ -904,7 +891,7 @@ def is_spline_zero_at_quadrature_points( for h in range(3): if necessary_direction[h]: for i in Basis_functions_indices_B[h]: - Auxiliar = xp.ones((xp.shape(localpts[h])[0]), dtype=int) + Auxiliar = np.ones((np.shape(localpts[h])[0]), dtype=int) are_quadrature_points_zero( Auxiliar, int( @@ -915,7 +902,7 @@ def is_spline_zero_at_quadrature_points( are_zero_B_or_D_splines[h]["B"].append(Auxiliar) for i in Basis_functions_indices_D[h]: - Auxiliar = xp.ones((xp.shape(localpts[h])[0]), dtype=int) + Auxiliar = np.ones((np.shape(localpts[h])[0]), dtype=int) are_quadrature_points_zero( Auxiliar, int( diff --git a/src/struphy/feec/variational_utilities.py b/src/struphy/feec/variational_utilities.py index 8174a1a5b..8a9dabe9c 100644 --- a/src/struphy/feec/variational_utilities.py +++ b/src/struphy/feec/variational_utilities.py @@ -1,6 +1,6 @@ from copy import deepcopy -import cunumpy as xp +import numpy as np from psydac.linalg.basic import IdentityOperator, Vector from psydac.linalg.block import BlockVector from psydac.linalg.solvers import inverse @@ -94,7 +94,7 @@ def __init__( self.Pcoord3 = CoordinateProjector(2, derham.Vh_pol["v"], derham.Vh_pol["0"]) @ derham.boundary_ops["v"] # Initialize the BasisProjectionOperators - if derham._with_local_projectors: + if derham._with_local_projectors == True: self.PiuT = BasisProjectionOperatorLocal( P0, V1h, @@ -192,10 +192,10 @@ def __init__( # Create tmps for later use in evaluating on the grid grid_shape = tuple([len(loc_grid) for loc_grid in interpolation_grid]) - self._vf_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._gvf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._gvf2_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._gvf3_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._vf_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._gvf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._gvf2_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._gvf3_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] # gradient of the component of the vector field grad = derham.grad_bcfree @@ -264,27 +264,19 @@ def dot(self, v, out=None): self.gv3f.vector = grad_3_v vf_values = self.vf.eval_tp_fixed_loc( - self.interpolation_grid_spans, - [self.interpolation_grid_bn] * 3, - out=self._vf_values, + self.interpolation_grid_spans, [self.interpolation_grid_bn] * 3, out=self._vf_values ) gvf1_values = self.gv1f.eval_tp_fixed_loc( - self.interpolation_grid_spans, - self.interpolation_grid_gradient, - out=self._gvf1_values, + self.interpolation_grid_spans, self.interpolation_grid_gradient, out=self._gvf1_values ) gvf2_values = self.gv2f.eval_tp_fixed_loc( - self.interpolation_grid_spans, - self.interpolation_grid_gradient, - out=self._gvf2_values, + self.interpolation_grid_spans, self.interpolation_grid_gradient, out=self._gvf2_values ) gvf3_values = self.gv3f.eval_tp_fixed_loc( - self.interpolation_grid_spans, - self.interpolation_grid_gradient, - out=self._gvf3_values, + self.interpolation_grid_spans, self.interpolation_grid_gradient, out=self._gvf3_values ) self.PiuT.update_weights([[vf_values[0], vf_values[1], vf_values[2]]]) @@ -386,13 +378,13 @@ def __init__(self, derham, transposed=False, weights=None): ) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_0]) - self._f_0_values = xp.zeros(grid_shape, dtype=float) + self._f_0_values = np.zeros(grid_shape, dtype=float) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_1]) - self._f_1_values = xp.zeros(grid_shape, dtype=float) + self._f_1_values = np.zeros(grid_shape, dtype=float) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_2]) - self._f_2_values = xp.zeros(grid_shape, dtype=float) + self._f_2_values = np.zeros(grid_shape, dtype=float) @property def domain(self): @@ -541,7 +533,7 @@ def __init__(self, derham, transposed=False, weights=None): ) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_0]) - self._bf0_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._bf0_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] self.hist_grid_0_b = [ [self.hist_grid_0_bn[0], self.hist_grid_0_bd[1], self.hist_grid_0_bd[2]], [ @@ -552,7 +544,7 @@ def __init__(self, derham, transposed=False, weights=None): [self.hist_grid_0_bd[0], self.hist_grid_0_bd[1], self.hist_grid_0_bn[2]], ] grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_1]) - self._bf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._bf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] self.hist_grid_1_b = [ [self.hist_grid_1_bn[0], self.hist_grid_1_bd[1], self.hist_grid_1_bd[2]], [ @@ -564,7 +556,7 @@ def __init__(self, derham, transposed=False, weights=None): ] grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_2]) - self._bf2_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._bf2_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] self.hist_grid_2_b = [ [self.hist_grid_2_bn[0], self.hist_grid_2_bd[1], self.hist_grid_2_bd[2]], [ @@ -735,8 +727,8 @@ def __init__(self, derham, phys_domain, Uv, gamma, transposed=False, weights1=No self._proj_p_metric = deepcopy(metric) grid_shape = tuple([len(loc_grid) for loc_grid in int_grid]) - self._pf_values = xp.zeros(grid_shape, dtype=float) - self._mapped_pf_values = xp.zeros(grid_shape, dtype=float) + self._pf_values = np.zeros(grid_shape, dtype=float) + self._mapped_pf_values = np.zeros(grid_shape, dtype=float) # gradient of the component of the vector field @@ -757,13 +749,13 @@ def __init__(self, derham, phys_domain, Uv, gamma, transposed=False, weights1=No ) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_20]) - self._pf_0_values = xp.zeros(grid_shape, dtype=float) + self._pf_0_values = np.zeros(grid_shape, dtype=float) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_21]) - self._pf_1_values = xp.zeros(grid_shape, dtype=float) + self._pf_1_values = np.zeros(grid_shape, dtype=float) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_22]) - self._pf_2_values = xp.zeros(grid_shape, dtype=float) + self._pf_2_values = np.zeros(grid_shape, dtype=float) @property def domain(self): @@ -885,21 +877,21 @@ def __init__(self, derham, gamma): self.rhof1 = self._derham.create_spline_function("rhof1", "L2") grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._rhof_values = xp.zeros(grid_shape, dtype=float) - self._rhof1_values = xp.zeros(grid_shape, dtype=float) - self._sf_values = xp.zeros(grid_shape, dtype=float) - self._sf1_values = xp.zeros(grid_shape, dtype=float) - self._delta_values = xp.zeros(grid_shape, dtype=float) - self._rhof_mid_values = xp.zeros(grid_shape, dtype=float) - self._sf_mid_values = xp.zeros(grid_shape, dtype=float) - self._eta_values = xp.zeros(grid_shape, dtype=float) - self._en_values = xp.zeros(grid_shape, dtype=float) - self._en1_values = xp.zeros(grid_shape, dtype=float) - self._de_values = xp.zeros(grid_shape, dtype=float) - self._d2e_values = xp.zeros(grid_shape, dtype=float) - self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) - self._tmp_int_grid2 = xp.zeros(grid_shape, dtype=float) - self._DG_values = xp.zeros(grid_shape, dtype=float) + self._rhof_values = np.zeros(grid_shape, dtype=float) + self._rhof1_values = np.zeros(grid_shape, dtype=float) + self._sf_values = np.zeros(grid_shape, dtype=float) + self._sf1_values = np.zeros(grid_shape, dtype=float) + self._delta_values = np.zeros(grid_shape, dtype=float) + self._rhof_mid_values = np.zeros(grid_shape, dtype=float) + self._sf_mid_values = np.zeros(grid_shape, dtype=float) + self._eta_values = np.zeros(grid_shape, dtype=float) + self._en_values = np.zeros(grid_shape, dtype=float) + self._en1_values = np.zeros(grid_shape, dtype=float) + self._de_values = np.zeros(grid_shape, dtype=float) + self._d2e_values = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid2 = np.zeros(grid_shape, dtype=float) + self._DG_values = np.zeros(grid_shape, dtype=float) def ener(self, rho, s, out=None): r"""Themodynamical energy as a function of rho and s, usign the perfect gaz hypothesis. @@ -909,13 +901,13 @@ def ener(self, rho, s, out=None): """ gam = self._gamma if out is None: - out = xp.power(rho, gam) * xp.exp(s / rho) + out = np.power(rho, gam) * np.exp(s / rho) else: out *= 0.0 out += s out /= rho - xp.exp(out, out=out) - xp.power(rho, gam, out=self._tmp_int_grid) + np.exp(out, out=out) + np.power(rho, gam, out=self._tmp_int_grid) out *= self._tmp_int_grid return out @@ -927,17 +919,17 @@ def dener_drho(self, rho, s, out=None): """ gam = self._gamma if out is None: - out = (gam * xp.power(rho, gam - 1) - s * xp.power(rho, gam - 2)) * xp.exp(s / rho) + out = (gam * np.power(rho, gam - 1) - s * np.power(rho, gam - 2)) * np.exp(s / rho) else: out *= 0.0 out += s out /= rho - xp.exp(out, out=out) + np.exp(out, out=out) - xp.power(rho, gam - 1, out=self._tmp_int_grid) + np.power(rho, gam - 1, out=self._tmp_int_grid) self._tmp_int_grid *= gam - xp.power(rho, gam - 2, out=self._tmp_int_grid2) + np.power(rho, gam - 2, out=self._tmp_int_grid2) self._tmp_int_grid2 *= s self._tmp_int_grid -= self._tmp_int_grid2 @@ -952,13 +944,13 @@ def dener_ds(self, rho, s, out=None): """ gam = self._gamma if out is None: - out = xp.power(rho, gam - 1) * xp.exp(s / rho) + out = np.power(rho, gam - 1) * np.exp(s / rho) else: out *= 0.0 out += s out /= rho - xp.exp(out, out=out) - xp.power(rho, gam - 1, out=self._tmp_int_grid) + np.exp(out, out=out) + np.power(rho, gam - 1, out=self._tmp_int_grid) out *= self._tmp_int_grid return out @@ -971,25 +963,25 @@ def d2ener_drho2(self, rho, s, out=None): gam = self._gamma if out is None: out = ( - gam * (gam - 1) * xp.power(rho, gam - 2) - - s * 2 * (gam - 1) * xp.power(rho, gam - 3) - + s**2 * xp.power(rho, gam - 4) - ) * xp.exp(s / rho) + gam * (gam - 1) * np.power(rho, gam - 2) + - s * 2 * (gam - 1) * np.power(rho, gam - 3) + + s**2 * np.power(rho, gam - 4) + ) * np.exp(s / rho) else: out *= 0.0 out += s out /= rho - xp.exp(out, out=out) + np.exp(out, out=out) - xp.power(rho, gam - 2, out=self._tmp_int_grid) + np.power(rho, gam - 2, out=self._tmp_int_grid) self._tmp_int_grid *= gam * (gam - 1) - xp.power(rho, gam - 3, out=self._tmp_int_grid2) + np.power(rho, gam - 3, out=self._tmp_int_grid2) self._tmp_int_grid2 *= s self._tmp_int_grid2 *= 2 * (gam - 1) self._tmp_int_grid -= self._tmp_int_grid2 - xp.power(rho, gam - 4, out=self._tmp_int_grid2) + np.power(rho, gam - 4, out=self._tmp_int_grid2) self._tmp_int_grid2 *= s self._tmp_int_grid2 *= s self._tmp_int_grid += self._tmp_int_grid2 @@ -1004,27 +996,27 @@ def d2ener_ds2(self, rho, s, out=None): """ gam = self._gamma if out is None: - out = xp.power(rho, gam - 2) * xp.exp(s / rho) + out = np.power(rho, gam - 2) * np.exp(s / rho) else: out *= 0.0 out += s out /= rho - xp.exp(out, out=out) - xp.power(rho, gam - 2, out=self._tmp_int_grid) + np.exp(out, out=out) + np.power(rho, gam - 2, out=self._tmp_int_grid) out *= self._tmp_int_grid return out def eta(self, delta_x, out=None): r"""Switch function :math:`\eta(\delta) = 1- \text{exp}((-\delta/10^{-5})^2)`.""" if out is None: - out = 1.0 - xp.exp(-((delta_x / 1e-5) ** 2)) + out = 1.0 - np.exp(-((delta_x / 1e-5) ** 2)) else: out *= 0.0 out += delta_x out /= 1e-5 out **= 2 out *= -1 - xp.exp(out, out=out) + np.exp(out, out=out) out *= -1 out += 1.0 return out @@ -1336,7 +1328,7 @@ def __init__(self, derham, mass_ops, domain): ) grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._f_values = xp.zeros(grid_shape, dtype=float) + self._f_values = np.zeros(grid_shape, dtype=float) metric = domain.metric(*integration_grid) self._mass_metric_term = deepcopy(metric) @@ -1391,12 +1383,7 @@ def update_weight(self, coeffs): self._pc.update_mass_operator(self._massop) def _create_inv( - self, - type="pcg", - pc_type="MassMatrixDiagonalPreconditioner", - tol=1e-16, - maxiter=500, - verbose=False, + self, type="pcg", pc_type="MassMatrixDiagonalPreconditioner", tol=1e-16, maxiter=500, verbose=False ): """Inverse the weighted mass matrix""" if pc_type is None: @@ -1450,10 +1437,10 @@ def __init__(self, derham, domain, mass_ops): self.uf = derham.create_spline_function("uf", "H1vec") self.uf1 = derham.create_spline_function("uf1", "H1vec") - self._uf_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._uf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._Guf_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) + self._uf_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._Guf_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._tmp_int_grid = np.zeros(grid_shape, dtype=float) metric = domain.metric( *integration_grid, diff --git a/src/struphy/fields_background/base.py b/src/struphy/fields_background/base.py index 7ad6e3887..ef6b3a769 100644 --- a/src/struphy/fields_background/base.py +++ b/src/struphy/fields_background/base.py @@ -2,7 +2,7 @@ from abc import ABCMeta, abstractmethod -import cunumpy as xp +import numpy as np from matplotlib import pyplot as plt from pyevtk.hl import gridToVTK @@ -140,7 +140,7 @@ def t3(self, *etas, squeeze_out=False): def vth0(self, *etas, squeeze_out=False): """0-form thermal velocity on logical cube [0, 1]^3.""" - return xp.sqrt(self.t0(*etas, squeeze_out=squeeze_out)) + return np.sqrt(self.t0(*etas, squeeze_out=squeeze_out)) def vth3(self, *etas, squeeze_out=False): """3-form thermal velocity on logical cube [0, 1]^3.""" @@ -156,7 +156,7 @@ def q0(self, *etas, squeeze_out=False): """0-form square root of the pressure on logical cube [0, 1]^3.""" # xyz = self.domain(*etas, squeeze_out=False) p = self.p0(*etas) - q = xp.sqrt(p) + q = np.sqrt(p) return self.domain.pull(q, *etas, kind="0", squeeze_out=squeeze_out) def q3(self, *etas, squeeze_out=False): @@ -176,7 +176,7 @@ def s0_monoatomic(self, *etas, squeeze_out=False): # xyz = self.domain(*etas, squeeze_out=False) p = self.p0(*etas) n = self.n0(*etas) - s = n * xp.log(p / (2 / 3 * xp.power(n, 5 / 3))) + s = n * np.log(p / (2 / 3 * np.power(n, 5 / 3))) return self.domain.pull(s, *etas, kind="0", squeeze_out=squeeze_out) def s3_monoatomic(self, *etas, squeeze_out=False): @@ -198,7 +198,7 @@ def s0_diatomic(self, *etas, squeeze_out=False): # xyz = self.domain(*etas, squeeze_out=False) p = self.p0(*etas) n = self.n0(*etas) - s = n * xp.log(p / (2 / 5 * xp.power(n, 7 / 5))) + s = n * np.log(p / (2 / 5 * np.power(n, 7 / 5))) return self.domain.pull(s, *etas, kind="0", squeeze_out=squeeze_out) def s3_diatomic(self, *etas, squeeze_out=False): @@ -395,7 +395,7 @@ def unit_b_cart(self, *etas, squeeze_out=False): """Unit vector Cartesian components of magnetic field evaluated on logical cube [0, 1]^3. Returns also (x,y,z).""" b, xyz = self.b_cart(*etas, squeeze_out=squeeze_out) absB = self.absB0(*etas, squeeze_out=squeeze_out) - out = xp.array([b[0] / absB, b[1] / absB, b[2] / absB], dtype=float) + out = np.array([b[0] / absB, b[1] / absB, b[2] / absB], dtype=float) return out, xyz def gradB1(self, *etas, squeeze_out=False): @@ -481,7 +481,7 @@ def av(self, *etas, squeeze_out=False): def absB0(self, *etas, squeeze_out=False): """0-form absolute value of magnetic field on logical cube [0, 1]^3.""" b, xyz = self.b_cart(*etas, squeeze_out=squeeze_out) - return xp.sqrt(b[0] ** 2 + b[1] ** 2 + b[2] ** 2) + return np.sqrt(b[0] ** 2 + b[1] ** 2 + b[2] ** 2) def absB3(self, *etas, squeeze_out=False): """3-form absolute value of magnetic field on logical cube [0, 1]^3.""" @@ -783,28 +783,19 @@ def u_cart(self, *etas, squeeze_out=False): def curl_unit_b1(self, *etas, squeeze_out=False): """1-form components of curl of unit magnetic field evaluated on logical cube [0, 1]^3. Returns also (x,y,z).""" return self.domain.pull( - self.curl_unit_b_cart(*etas, squeeze_out=False)[0], - *etas, - kind="1", - squeeze_out=squeeze_out, + self.curl_unit_b_cart(*etas, squeeze_out=False)[0], *etas, kind="1", squeeze_out=squeeze_out ) def curl_unit_b2(self, *etas, squeeze_out=False): """2-form components of curl of unit magnetic field evaluated on logical cube [0, 1]^3. Returns also (x,y,z).""" return self.domain.pull( - self.curl_unit_b_cart(*etas, squeeze_out=False)[0], - *etas, - kind="2", - squeeze_out=squeeze_out, + self.curl_unit_b_cart(*etas, squeeze_out=False)[0], *etas, kind="2", squeeze_out=squeeze_out ) def curl_unit_bv(self, *etas, squeeze_out=False): """Contra-variant components of curl of unit magnetic field evaluated on logical cube [0, 1]^3. Returns also (x,y,z).""" return self.domain.pull( - self.curl_unit_b_cart(*etas, squeeze_out=False)[0], - *etas, - kind="v", - squeeze_out=squeeze_out, + self.curl_unit_b_cart(*etas, squeeze_out=False)[0], *etas, kind="v", squeeze_out=squeeze_out ) def curl_unit_b_cart(self, *etas, squeeze_out=False): @@ -813,7 +804,7 @@ def curl_unit_b_cart(self, *etas, squeeze_out=False): j, xyz = self.j_cart(*etas, squeeze_out=squeeze_out) gradB, xyz = self.gradB_cart(*etas, squeeze_out=squeeze_out) absB = self.absB0(*etas, squeeze_out=squeeze_out) - out = xp.array( + out = np.array( [ j[0] / absB + (b[1] * gradB[2] - b[2] * gradB[1]) / absB**2, j[1] / absB + (b[2] * gradB[0] - b[0] * gradB[2]) / absB**2, @@ -917,9 +908,9 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): "HollowTorus", ) - e1 = xp.linspace(0.0001, 1, n1) - e2 = xp.linspace(0, 1, n2) - e3 = xp.linspace(0, 1, n3) + e1 = np.linspace(0.0001, 1, n1) + e2 = np.linspace(0, 1, n2) + e3 = np.linspace(0, 1, n3) if self.domain.__class__.__name__ in ("GVECunit", "DESCunit"): if n_planes > 1: @@ -944,7 +935,7 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): print("Computation of abs(B) done.") j_cart, xyz = self.j_cart(e1, e2, e3) print("Computation of current density done.") - absJ = xp.sqrt(j_cart[0] ** 2 + j_cart[1] ** 2 + j_cart[2] ** 2) + absJ = np.sqrt(j_cart[0] ** 2 + j_cart[1] ** 2 + j_cart[2] ** 2) _path = struphy.__path__[0] + "/fields_background/mhd_equil/gvec/output/" gridToVTK( @@ -967,24 +958,24 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): print(key, ": ", val) # poloidal plane grid - fig = plt.figure(figsize=(13, xp.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(13, np.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xpp = x[:, :, int(n * jump)].squeeze() + xp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = xp.sqrt(xpp**2 + yp**2) + pc1 = np.sqrt(xp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xpp + pc1 = xp pc2 = yp l1 = "x" l2 = "y" - ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) for i in range(pc1.shape[0]): for j in range(pc1.shape[1] - 1): if i < pc1.shape[0] - 1: @@ -1013,26 +1004,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): ) # top view - e1 = xp.linspace(0, 1, n1) # radial coordinate in [0, 1] - e2 = xp.linspace(0, 1, 3) # poloidal angle in [0, 1] - e3 = xp.linspace(0, 1, n3) # toroidal angle in [0, 1] + e1 = np.linspace(0, 1, n1) # radial coordinate in [0, 1] + e2 = np.linspace(0, 1, 3) # poloidal angle in [0, 1] + e3 = np.linspace(0, 1, n3) # toroidal angle in [0, 1] xt, yt, zt = self.domain(e1, e2, e3) fig = plt.figure(figsize=(13, 2 * 6.5)) ax = fig.add_subplot() for m in range(2): - xpp = xt[:, m, :].squeeze() + xp = xt[:, m, :].squeeze() yp = yt[:, m, :].squeeze() zp = zt[:, m, :].squeeze() if self.domain.__class__.__name__ in torus_mappings: - tc1 = xpp + tc1 = xp tc2 = yp l1 = "x" l2 = "y" else: - tc1 = xpp + tc1 = xp tc2 = zp l1 = "x" l2 = "z" @@ -1067,26 +1058,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): ax.set_title("Device top view") # Jacobian determinant - fig = plt.figure(figsize=(13, xp.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(13, np.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xpp = x[:, :, int(n * jump)].squeeze() + xp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = xp.sqrt(xpp**2 + yp**2) + pc1 = np.sqrt(xp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xpp + pc1 = xp pc2 = yp l1 = "x" l2 = "y" detp = det_df[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, detp, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1097,26 +1088,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): fig.colorbar(map, ax=ax, location="right") # pressure - fig = plt.figure(figsize=(15, xp.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(15, np.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xpp = x[:, :, int(n * jump)].squeeze() + xp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = xp.sqrt(xpp**2 + yp**2) + pc1 = np.sqrt(xp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xpp + pc1 = xp pc2 = yp l1 = "x" l2 = "y" pp = p[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, pp, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1127,26 +1118,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): fig.colorbar(map, ax=ax, location="right") # density - fig = plt.figure(figsize=(15, xp.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(15, np.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xpp = x[:, :, int(n * jump)].squeeze() + xp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = xp.sqrt(xpp**2 + yp**2) + pc1 = np.sqrt(xp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xpp + pc1 = xp pc2 = yp l1 = "x" l2 = "y" nn = n_dens[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, nn, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1157,26 +1148,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): fig.colorbar(map, ax=ax, location="right") # magnetic field strength - fig = plt.figure(figsize=(15, xp.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(15, np.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xpp = x[:, :, int(n * jump)].squeeze() + xp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = xp.sqrt(xpp**2 + yp**2) + pc1 = np.sqrt(xp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xpp + pc1 = xp pc2 = yp l1 = "x" l2 = "y" ab = absB[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, ab, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1187,26 +1178,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): fig.colorbar(map, ax=ax, location="right") # current density - fig = plt.figure(figsize=(15, xp.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(15, np.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xpp = x[:, :, int(n * jump)].squeeze() + xp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = xp.sqrt(xpp**2 + yp**2) + pc1 = np.sqrt(xp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xpp + pc1 = xp pc2 = yp l1 = "x" l2 = "y" ab = absJ[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, ab, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1316,8 +1307,8 @@ def b_xyz(self, x, y, z): BZ = self.psi(R, Z, dR=1) / R # push-forward to Cartesian components - Bx = BR * xp.cos(Phi) - BP * xp.sin(Phi) - By = BR * xp.sin(Phi) + BP * xp.cos(Phi) + Bx = BR * np.cos(Phi) - BP * np.sin(Phi) + By = BR * np.sin(Phi) + BP * np.cos(Phi) Bz = 1 * BZ return Bx, By, Bz @@ -1333,8 +1324,8 @@ def j_xyz(self, x, y, z): jZ = self.g_tor(R, Z, dR=1) / R # push-forward to Cartesian components - jx = jR * xp.cos(Phi) - jP * xp.sin(Phi) - jy = jR * xp.sin(Phi) + jP * xp.cos(Phi) + jx = jR * np.cos(Phi) - jP * np.sin(Phi) + jy = jR * np.sin(Phi) + jP * np.cos(Phi) jz = 1 * jZ return jx, jy, jz @@ -1344,7 +1335,7 @@ def gradB_xyz(self, x, y, z): R, Phi, Z = self.inverse_map(x, y, z) - RabsB = xp.sqrt( + RabsB = np.sqrt( self.psi(R, Z, dZ=1) ** 2 + self.g_tor(R, Z) ** 2 + self.psi(R, Z, dR=1) ** 2, ) @@ -1372,8 +1363,8 @@ def gradB_xyz(self, x, y, z): ) # push-forward to Cartesian components - gradBx = gradBR * xp.cos(Phi) - gradBP * xp.sin(Phi) - gradBy = gradBR * xp.sin(Phi) + gradBP * xp.cos(Phi) + gradBx = gradBR * np.cos(Phi) - gradBP * np.sin(Phi) + gradBy = gradBR * np.sin(Phi) + gradBP * np.cos(Phi) gradBz = 1 * gradBZ return gradBx, gradBy, gradBz @@ -1382,8 +1373,8 @@ def gradB_xyz(self, x, y, z): def inverse_map(x, y, z): """Inverse cylindrical mapping.""" - R = xp.sqrt(x**2 + y**2) - P = xp.arctan2(y, x) + R = np.sqrt(x**2 + y**2) + P = np.arctan2(y, x) Z = 1 * z return R, P, Z diff --git a/src/struphy/fields_background/equils.py b/src/struphy/fields_background/equils.py index f1afacd35..96febc74b 100644 --- a/src/struphy/fields_background/equils.py +++ b/src/struphy/fields_background/equils.py @@ -7,7 +7,7 @@ import warnings from time import time -import cunumpy as xp +import numpy as np from scipy.integrate import odeint, quad from scipy.interpolate import RectBivariateSpline, UnivariateSpline from scipy.optimize import fsolve, minimize @@ -178,8 +178,7 @@ class ShearedSlab(CartesianMHDequilibrium): Ion number density at x=a (default: 1.). beta : float Plasma beta (ratio of kinematic pressure to B^2/2, default: 0.1). - q_kind : int - Kind of safety factor profile, (0 or 1, default: 0). + Note ---- In the parameter .yml, use the following in the section ``fluid_background``:: @@ -194,7 +193,6 @@ class ShearedSlab(CartesianMHDequilibrium): n2 : 0. # 2nd shape factor for ion number density profile na : 1. # number density at r=a beta : .1 # plasma beta = p*2/B^2 - q_kind : 0. # kind of safety factor profile """ def __init__( @@ -208,7 +206,6 @@ def __init__( n2: float = 0.0, na: float = 1.0, beta: float = 0.1, - q_kind: int = 0, ): # use params setter self.params = copy.deepcopy(locals()) @@ -229,19 +226,10 @@ def q_x(self, x, der=0): qout = 0 * x else: - if self.params["q_kind"] == 0: - if der == 0: - qout = self.params["q0"] + (self.params["q1"] - self.params["q0"]) * (x / self.params["a"]) ** 2 - else: - qout = 2 * (self.params["q1"] - self.params["q0"]) * x / self.params["a"] ** 2 - + if der == 0: + qout = self.params["q0"] + (self.params["q1"] - self.params["q0"]) * (x / self.params["a"]) ** 2 else: - if der == 0: - qout = self.params["q0"] + self.params["q1"] * xp.sin(2.0 * xp.pi * x / self.params["a"]) - else: - qout = ( - 2.0 * xp.pi / self.params["a"] * self.params["q1"] * xp.cos(2.0 * xp.pi * x / self.params["a"]) - ) + qout = 2 * (self.params["q1"] - self.params["q0"]) * x / self.params["a"] ** 2 return qout @@ -251,7 +239,7 @@ def p_x(self, x): eps = self.params["a"] / self.params["R0"] - if xp.all(q >= 100.0): + if np.all(q >= 100.0): pout = self.params["B0"] ** 2 * self.params["beta"] / 2.0 - 0 * x else: pout = self.params["B0"] ** 2 * self.params["beta"] / 2.0 * (1 + eps**2 / q**2) + self.params[ @@ -273,7 +261,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - x = xp.linspace(0.0, self.params["a"], n_pts) + x = np.linspace(0.0, self.params["a"], n_pts) fig, ax = plt.subplots(1, 3) @@ -307,7 +295,7 @@ def b_xyz(self, x, y, z): q = self.q_x(x) eps = self.params["a"] / self.params["R0"] - if xp.all(q >= 100.0): + if np.all(q >= 100.0): by = 0 * x bz = self.params["B0"] - 0 * x else: @@ -324,7 +312,7 @@ def j_xyz(self, x, y, z): q = self.q_x(x) eps = self.params["a"] / self.params["R0"] - if xp.all(q >= 100.0): + if np.all(q >= 100.0): jz = 0 * x else: jz = -self.params["B0"] * eps * self.q_x(x, der=1) / q**2 @@ -353,13 +341,13 @@ def gradB_xyz(self, x, y, z): q = self.q_x(x) eps = self.params["a"] / self.params["R0"] - if xp.all(q >= 100.0): + if np.all(q >= 100.0): gradBx = 0 * x else: gradBx = ( -self.params["B0"] * eps**2 - / xp.sqrt(1 + eps**2 / self.q_x(x) ** 2) + / np.sqrt(1 + eps**2 / self.q_x(x) ** 2) * self.q_x(x, der=1) / self.q_x(x) ** 3 ) @@ -457,8 +445,8 @@ def __init__( def T_z(self, z): r"""Swap function T(z) = \tanh(z - z_1)/\delta) - \tanh(z - z_2)/\delta)""" Tout = ( - xp.tanh((z - self.params["z1"]) / self.params["delta"]) - - xp.tanh((z - self.params["z2"]) / self.params["delta"]) + np.tanh((z - self.params["z1"]) / self.params["delta"]) + - np.tanh((z - self.params["z2"]) / self.params["delta"]) ) / 2.0 return Tout @@ -480,7 +468,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - z = xp.linspace(0.0, self.params["c"], n_pts) + z = np.linspace(0.0, self.params["c"], n_pts) fig, ax = plt.subplots(1, 3) @@ -647,8 +635,8 @@ def __init__( self.params = copy.deepcopy(locals()) # inverse cylindrical coordinate transformation (x, y, z) --> (r, theta, phi) - self.r = lambda x, y, z: xp.sqrt(x**2 + y**2) - self.theta = lambda x, y, z: xp.arctan2(y, x) + self.r = lambda x, y, z: np.sqrt(x**2 + y**2) + self.theta = lambda x, y, z: np.arctan2(y, x) self.z = lambda x, y, z: 1 * z # =============================================================== @@ -707,7 +695,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - r = xp.linspace(0.0, self.params["a"], n_pts) + r = np.linspace(0.0, self.params["a"], n_pts) fig, ax = plt.subplots(1, 3) @@ -718,7 +706,7 @@ def plot_profiles(self, n_pts=501): ax[0].set_xlabel("r") ax[0].set_ylabel("q") - ax[0].plot(r, xp.ones(r.size), "k--") + ax[0].plot(r, np.ones(r.size), "k--") ax[1].plot(r, self.p_r(r)) ax[1].set_xlabel("r") @@ -743,13 +731,13 @@ def b_xyz(self, x, y, z): theta = self.theta(x, y, z) q = self.q_r(r) # azimuthal component - if xp.all(q >= 100.0): + if np.all(q >= 100.0): b_theta = 0 * r else: b_theta = self.params["B0"] * r / (self.params["R0"] * q) # cartesian x-component - bx = -b_theta * xp.sin(theta) - by = b_theta * xp.cos(theta) + bx = -b_theta * np.sin(theta) + by = b_theta * np.cos(theta) bz = self.params["B0"] - 0 * x return bx, by, bz @@ -763,7 +751,7 @@ def j_xyz(self, x, y, z): r = self.r(x, y, z) q = self.q_r(r) q_p = self.q_r(r, der=1) - if xp.all(q >= 100.0): + if np.all(q >= 100.0): jz = 0 * x else: jz = self.params["B0"] / (self.params["R0"] * q**2) * (2 * q - r * q_p) @@ -790,13 +778,13 @@ def gradB_xyz(self, x, y, z): r = self.r(x, y, z) theta = self.theta(x, y, z) q = self.q_r(r) - if xp.all(q >= 100.0): + if np.all(q >= 100.0): gradBr = 0 * x else: gradBr = ( self.params["B0"] / self.params["R0"] ** 2 - / xp.sqrt( + / np.sqrt( 1 + r**2 / self.q_r( @@ -807,8 +795,8 @@ def gradB_xyz(self, x, y, z): ) * (r / self.q_r(r) ** 2 - r**2 / self.q_r(r) ** 3 * self.q_r(r, der=1)) ) - gradBx = gradBr * xp.cos(theta) - gradBy = gradBr * xp.sin(theta) + gradBx = gradBr * np.cos(theta) + gradBy = gradBr * np.sin(theta) gradBz = 0 * x return gradBx, gradBy, gradBz @@ -947,10 +935,10 @@ def __init__( self.params = copy.deepcopy(locals()) # plasma boundary contour - ths = xp.linspace(0.0, 2 * xp.pi, 201) + ths = np.linspace(0.0, 2 * np.pi, 201) - self._rbs = self.params["R0"] * (1 + self.params["a"] / self.params["R0"] * xp.cos(ths)) - self._zbs = self.params["a"] * xp.sin(ths) + self._rbs = self.params["R0"] * (1 + self.params["a"] / self.params["R0"] * np.cos(ths)) + self._zbs = self.params["a"] * np.sin(ths) # set on-axis and boundary fluxes if self.params["q_kind"] == 0: @@ -961,12 +949,12 @@ def __init__( self._p_i = None else: - r_i = xp.linspace(0.0, self.params["a"], self.params["psi_nel"] + 1) + r_i = np.linspace(0.0, self.params["a"], self.params["psi_nel"] + 1) def dpsi_dr(r): - return self.params["B0"] * r / (self.q_r(r) * xp.sqrt(1 - r**2 / self.params["R0"] ** 2)) + return self.params["B0"] * r / (self.q_r(r) * np.sqrt(1 - r**2 / self.params["R0"] ** 2)) - psis = xp.zeros_like(r_i) + psis = np.zeros_like(r_i) for i, rr in enumerate(r_i): psis[i] = quad(dpsi_dr, 0.0, rr)[0] @@ -989,7 +977,7 @@ def dp_dr(r): * (2 * self.q_r(r) - r * self.q_r(r, der=1)) ) - ps = xp.zeros_like(r_i) + ps = np.zeros_like(r_i) for i, rr in enumerate(r_i): ps[i] = quad(dp_dr, 0.0, rr)[0] @@ -1044,7 +1032,7 @@ def psi_r(self, r, der=0): dq = q1 - q0 # geometric correction factor and its first derivative - gf_0 = xp.sqrt(1 - (r / self.params["R0"]) ** 2) + gf_0 = np.sqrt(1 - (r / self.params["R0"]) ** 2) gf_1 = -r / (self.params["R0"] ** 2 * gf_0) # safety factors @@ -1055,9 +1043,9 @@ def psi_r(self, r, der=0): q_bar_1 = q_1 * gf_0 + q_0 * gf_1 if der == 0: - out = -self.params["B0"] * self.params["a"] ** 2 / xp.sqrt(dq * q0 * eps**2 + dq**2) - out *= xp.arctanh( - xp.sqrt((dq - dq * (r / self.params["R0"]) ** 2) / (q0 * eps**2 + dq)), + out = -self.params["B0"] * self.params["a"] ** 2 / np.sqrt(dq * q0 * eps**2 + dq**2) + out *= np.arctanh( + np.sqrt((dq - dq * (r / self.params["R0"]) ** 2) / (q0 * eps**2 + dq)), ) elif der == 1: out = self.params["B0"] * r / q_bar_0 @@ -1127,10 +1115,10 @@ def q_r(self, r, der=0): r_flat = r.flatten() - r_zeros = xp.where(r_flat == 0.0)[0] - r_nzero = xp.where(r_flat != 0.0)[0] + r_zeros = np.where(r_flat == 0.0)[0] + r_nzero = np.where(r_flat != 0.0)[0] - qout = xp.zeros(r_flat.size, dtype=float) + qout = np.zeros(r_flat.size, dtype=float) if der == 0: if self.params["q0"] == self.params["q1"]: @@ -1223,7 +1211,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - r = xp.linspace(0.0, self.params["a"], n_pts) + r = np.linspace(0.0, self.params["a"], n_pts) fig, ax = plt.subplots(2, 2) @@ -1257,7 +1245,7 @@ def plot_profiles(self, n_pts=501): def psi(self, R, Z, dR=0, dZ=0): """Poloidal flux function psi = psi(R, Z).""" - r = xp.sqrt(Z**2 + (R - self.params["R0"]) ** 2) + r = np.sqrt(Z**2 + (R - self.params["R0"]) ** 2) if dR == 0 and dZ == 0: out = self.psi_r(r, der=0) @@ -1305,7 +1293,7 @@ def g_tor(self, R, Z, dR=0, dZ=0): def p_xyz(self, x, y, z): """Pressure p = p(x, y, z).""" - r = xp.sqrt((xp.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) + r = np.sqrt((np.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) pp = self.p_r(r) @@ -1313,7 +1301,7 @@ def p_xyz(self, x, y, z): def n_xyz(self, x, y, z): """Number density n = n(x, y, z).""" - r = xp.sqrt((xp.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) + r = np.sqrt((np.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) nn = self.n_r(r) @@ -1441,10 +1429,10 @@ def __init__( self.params = copy.deepcopy(locals()) # plasma boundary contour - ths = xp.linspace(0.0, 2 * xp.pi, 201) + ths = np.linspace(0.0, 2 * np.pi, 201) - self._rbs = self.params["R0"] * (1 + self.params["a"] / self.params["R0"] * xp.cos(ths)) - self._zbs = self.params["a"] * xp.sin(ths) + self._rbs = self.params["R0"] * (1 + self.params["a"] / self.params["R0"] * np.cos(ths)) + self._zbs = self.params["a"] * np.sin(ths) # on-axis flux (arbitrary value) self._psi0 = -10.0 @@ -1465,12 +1453,12 @@ def dpsi_dr(psi, r, psi1): q = q0 + psi_norm * (q1 - q0 + (q1p - q1 + q0) * (1 - psi_s) * (psi_norm - 1) / (psi_norm - psi_s)) - out = B0 * r / (q * xp.sqrt(1 - r**2 / R0**2)) + out = B0 * r / (q * np.sqrt(1 - r**2 / R0**2)) return out # solve differential equation and fix boundary flux - r_i = xp.linspace(0.0, self.params["a"], self.params["psi_nel"] + 1) + r_i = np.linspace(0.0, self.params["a"], self.params["psi_nel"] + 1) def fun(psi1): out = odeint(dpsi_dr, self._psi0, r_i, args=(psi1,)).flatten() @@ -1557,13 +1545,13 @@ def p_psi(self, psi, der=0): psi_norm = (psi - self._psi0) / (self._psi1 - self._psi0) if der == 0: - out = self.params["beta"] * self.params["B0"] ** 2 / 2.0 * xp.exp(-psi_norm / p1) + out = self.params["beta"] * self.params["B0"] ** 2 / 2.0 * np.exp(-psi_norm / p1) else: out = ( -self.params["beta"] * self.params["B0"] ** 2 / 2.0 - * xp.exp(-psi_norm / p1) + * np.exp(-psi_norm / p1) / (p1 * (self._psi1 - self._psi0)) ) @@ -1592,8 +1580,8 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - r = xp.linspace(0.0, self.params["a"], n_pts) - psi = xp.linspace(self._psi0, self._psi1, n_pts) + r = np.linspace(0.0, self.params["a"], n_pts) + psi = np.linspace(self._psi0, self._psi1, n_pts) fig, ax = plt.subplots(2, 2) @@ -1627,7 +1615,7 @@ def plot_profiles(self, n_pts=501): def psi(self, R, Z, dR=0, dZ=0): """Poloidal flux function psi = psi(R, Z).""" - r = xp.sqrt(Z**2 + (R - self.params["R0"]) ** 2) + r = np.sqrt(Z**2 + (R - self.params["R0"]) ** 2) if dR == 0 and dZ == 0: out = self.psi_r(r, der=0) @@ -1671,13 +1659,13 @@ def g_tor(self, R, Z, dR=0, dZ=0): def p_xyz(self, x, y, z): """Pressure p = p(x, y, z).""" - r = xp.sqrt((xp.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) + r = np.sqrt((np.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) return self.p_psi(self.psi_r(r)) def n_xyz(self, x, y, z): """Number density n = n(x, y, z).""" - r = xp.sqrt((xp.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) + r = np.sqrt((np.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) return self.n_psi(self.psi_r(r)) @@ -1759,7 +1747,7 @@ def __init__( units["p"] = 1.0 units["n"] = 1e20 warnings.warn( - f"{units =}, no rescaling performed in EQDSK output.", + f"{units = }, no rescaling performed in EQDSK output.", ) self._units = units @@ -1815,8 +1803,8 @@ def __init__( self._r_range = [rleft, rleft + rdim] self._z_range = [zmid - zdim / 2, zmid + zdim / 2] - R = xp.linspace(self._r_range[0], self._r_range[1], nR) - Z = xp.linspace(self._z_range[0], self._z_range[1], nZ) + R = np.linspace(self._r_range[0], self._r_range[1], nR) + Z = np.linspace(self._z_range[0], self._z_range[1], nZ) smooth_steps = [ int(1 / (self.params["psi_resolution"][0] * 0.01)), @@ -1846,7 +1834,7 @@ def __init__( self._psi1 = psi_edge # interpolate toroidal field function, pressure profile and q-profile on unifrom flux grid from axis to boundary - flux_grid = xp.linspace(self._psi0, self._psi1, g_profile.size) + flux_grid = np.linspace(self._psi0, self._psi1, g_profile.size) smooth_step = int(1 / (self.params["flux_resolution"] * 0.01)) @@ -2018,7 +2006,7 @@ def g_tor(self, R, Z, dR=0, dZ=0): def p_xyz(self, x, y, z): """Pressure p = p(x, y, z) in units 1 Tesla^2/mu_0.""" - R = xp.sqrt(x**2 + y**2) + R = np.sqrt(x**2 + y**2) Z = 1 * z out = self.p_psi(self.psi(R, Z)) @@ -2031,7 +2019,7 @@ def p_xyz(self, x, y, z): def n_xyz(self, x, y, z): """Number density in physical space. Units from parameter file.""" - R = xp.sqrt(x**2 + y**2) + R = np.sqrt(x**2 + y**2) Z = 1 * z out = self.n_psi(self.psi(R, Z)) @@ -2133,7 +2121,7 @@ def __init__( with pytest.raises(SystemExit) as exc: print("Simulation aborted, gvec must be installed (pip install gvec)!") sys.exit(1) - print(f"{exc.value.code =}") + print(f"{exc.value.code = }") import gvec @@ -2148,7 +2136,7 @@ def __init__( units["p"] = 1.0 units["n"] = 1e20 warnings.warn( - f"{units =}, no rescaling performed in GVEC output.", + f"{units = }, no rescaling performed in GVEC output.", ) self._units = units @@ -2211,9 +2199,9 @@ def bv(self, *etas, squeeze_out=False): bt += "_B" bz += "_B" self.state.compute(ev, bt, bz) - bv_2 = getattr(ev, bt).data / (2 * xp.pi) - bv_3 = getattr(ev, bz).data / (2 * xp.pi) * self._nfp - out = (xp.zeros_like(bv_2), bv_2, bv_3) + bv_2 = getattr(ev, bt).data / (2 * np.pi) + bv_3 = getattr(ev, bz).data / (2 * np.pi) * self._nfp + out = (np.zeros_like(bv_2), bv_2, bv_3) # apply struphy units for o in out: @@ -2231,8 +2219,8 @@ def jv(self, *etas, squeeze_out=False): self.state.compute(ev, jr, jt, jz) rmin = self._params["rmin"] jv_1 = ev.J_contra_r.data / (1.0 - rmin) - jv_2 = ev.J_contra_t.data / (2 * xp.pi) - jv_3 = ev.J_contra_z.data / (2 * xp.pi) * self._nfp + jv_2 = ev.J_contra_t.data / (2 * np.pi) + jv_3 = ev.J_contra_z.data / (2 * np.pi) * self._nfp if self.params["use_boozer"]: warnings.warn("GVEC current density in Boozer coords not yet implemented, set to zero.") # jr += "_B" @@ -2257,11 +2245,11 @@ def p0(self, *etas, squeeze_out=False): if not flat_eval: eta2 = etas[1] eta3 = etas[2] - if isinstance(eta2, xp.ndarray): + if isinstance(eta2, np.ndarray): if eta2.ndim == 3: eta2 = eta2[0, :, 0] eta3 = eta3[0, 0, :] - tmp, _1, _2 = xp.meshgrid(ev.p.data, eta2, eta3, indexing="ij") + tmp, _1, _2 = np.meshgrid(ev.p.data, eta2, eta3, indexing="ij") else: tmp = ev.p.data @@ -2321,7 +2309,7 @@ def _gvec_evaluations(self, *etas): etas = list(etas) for i, eta in enumerate(etas): if isinstance(eta, (float, int)): - etas[i] = xp.array((eta,)) + etas[i] = np.array((eta,)) assert etas[0].ndim == etas[1].ndim == etas[2].ndim if etas[0].ndim == 1: eta1 = etas[0] @@ -2338,8 +2326,8 @@ def _gvec_evaluations(self, *etas): # gvec coordinates rho = rmin + eta1 * (1.0 - rmin) - theta = 2 * xp.pi * eta2 - zeta = 2 * xp.pi * eta3 + theta = 2 * np.pi * eta2 + zeta = 2 * np.pi * eta3 # evaluate if self.params["use_boozer"]: @@ -2428,7 +2416,7 @@ def __init__( units["p"] = 1.0 units["n"] = 1e20 warnings.warn( - f"{units =}, no rescaling performed in DESC output.", + f"{units = }, no rescaling performed in DESC output.", ) self._units = units @@ -2509,7 +2497,7 @@ def bv(self, *etas, squeeze_out=False): li = [] for gi, ei in zip(grid, etas): if gi.shape == ei.shape: - li += [xp.allclose(gi, ei)] + li += [np.allclose(gi, ei)] else: li += [False] if all(li): @@ -2561,9 +2549,9 @@ def _eval_bv(self, *etas, squeeze_out=False): if var == "B^rho": tmp /= 1.0 - self.rmin elif var == "B^theta": - tmp /= 2.0 * xp.pi + tmp /= 2.0 * np.pi elif var == "B^zeta": - tmp /= 2.0 * xp.pi / nfp + tmp /= 2.0 * np.pi / nfp # adjust for Struphy units tmp /= self.units["B"] / self.units["x"] out += [tmp] @@ -2582,7 +2570,7 @@ def jv(self, *etas, squeeze_out=False): li = [] for gi, ei in zip(grid, etas): if gi.shape == ei.shape: - li += [xp.allclose(gi, ei)] + li += [np.allclose(gi, ei)] else: li += [False] if all(li): @@ -2634,9 +2622,9 @@ def _eval_jv(self, *etas, squeeze_out=False): if var == "J^rho": tmp /= 1.0 - self.rmin elif var == "J^theta": - tmp /= 2.0 * xp.pi + tmp /= 2.0 * np.pi elif var == "J^zeta": - tmp /= 2.0 * xp.pi / nfp + tmp /= 2.0 * np.pi / nfp # adjust for Struphy units tmp /= self.units["j"] / self.units["x"] out += [tmp] @@ -2706,7 +2694,7 @@ def gradB1(self, *etas, squeeze_out=False): li = [] for gi, ei in zip(grid, etas): if gi.shape == ei.shape: - li += [xp.allclose(gi, ei)] + li += [np.allclose(gi, ei)] else: li += [False] if all(li): @@ -2757,9 +2745,9 @@ def _eval_gradB1(self, *etas, squeeze_out=False): if var == "|B|_r": tmp *= 1.0 - self.rmin elif var == "|B|_t": - tmp *= 2.0 * xp.pi + tmp *= 2.0 * np.pi elif var == "|B|_z": - tmp *= 2.0 * xp.pi / nfp + tmp *= 2.0 * np.pi / nfp # adjust for Struphy units tmp /= self.units["B"] out += [tmp] @@ -2769,9 +2757,9 @@ def _eval_gradB1(self, *etas, squeeze_out=False): def desc_eval( self, var: str, - e1: xp.ndarray, - e2: xp.ndarray, - e3: xp.ndarray, + e1: np.ndarray, + e2: np.ndarray, + e3: np.ndarray, flat_eval: bool = False, nfp: int = 1, verbose: bool = False, @@ -2785,7 +2773,7 @@ def desc_eval( Desc equilibrium quantitiy to evaluate, from `https://desc-docs.readthedocs.io/en/latest/variables.html#list-of-variables`_. - e1, e2, e3 : xp.ndarray + e1, e2, e3 : np.ndarray Input grids, either 1d or 3d. flat_eval : bool @@ -2804,21 +2792,21 @@ def desc_eval( warnings.filterwarnings("ignore") ttime = time() # Fix issue 353 with float dummy etas - e1 = xp.array([e1]) if isinstance(e1, float) else e1 - e2 = xp.array([e2]) if isinstance(e2, float) else e2 - e3 = xp.array([e3]) if isinstance(e3, float) else e3 + e1 = np.array([e1]) if isinstance(e1, float) else e1 + e2 = np.array([e2]) if isinstance(e2, float) else e2 + e3 = np.array([e3]) if isinstance(e3, float) else e3 # transform input grids if e1.ndim == 3: assert e1.shape == e2.shape == e3.shape rho = self.rmin + e1[:, 0, 0] * (1.0 - self.rmin) - theta = 2 * xp.pi * e2[0, :, 0] - zeta = 2 * xp.pi * e3[0, 0, :] / nfp + theta = 2 * np.pi * e2[0, :, 0] + zeta = 2 * np.pi * e3[0, 0, :] / nfp else: assert e1.ndim == e2.ndim == e3.ndim == 1 rho = self.rmin + e1 * (1.0 - self.rmin) - theta = 2 * xp.pi * e2 - zeta = 2 * xp.pi * e3 / nfp + theta = 2 * np.pi * e2 + zeta = 2 * np.pi * e3 / nfp # eval type if flat_eval: @@ -2827,13 +2815,13 @@ def desc_eval( t = theta z = zeta else: - r, t, z = xp.meshgrid(rho, theta, zeta, indexing="ij") + r, t, z = np.meshgrid(rho, theta, zeta, indexing="ij") r = r.flatten() t = t.flatten() z = z.flatten() - nodes = xp.stack((r, t, z)).T - grid_3d = Grid(nodes, spacing=xp.ones_like(nodes), jitable=False) + nodes = np.stack((r, t, z)).T + grid_3d = Grid(nodes, spacing=np.ones_like(nodes), jitable=False) # compute output corresponding to the generated desc grid node_values = self.eq.compute( @@ -2874,32 +2862,32 @@ def desc_eval( )[0, 0, :] # make sure the desc grid is correct - assert xp.all(rho == rho1) - assert xp.all(theta == theta1) - assert xp.all(zeta == zeta1) + assert np.all(rho == rho1) + assert np.all(theta == theta1) + assert np.all(zeta == zeta1) if verbose: # import sys - print(f"\n{nfp =}") - print(f"{self.eq.axis =}") - print(f"{rho.size =}") - print(f"{theta.size =}") - print(f"{zeta.size =}") - print(f"{grid_3d.num_rho =}") - print(f"{grid_3d.num_theta =}") - print(f"{grid_3d.num_zeta =}") + print(f"\n{nfp = }") + print(f"{self.eq.axis = }") + print(f"{rho.size = }") + print(f"{theta.size = }") + print(f"{zeta.size = }") + print(f"{grid_3d.num_rho = }") + print(f"{grid_3d.num_theta = }") + print(f"{grid_3d.num_zeta = }") # print(f'\n{grid_3d.nodes[:, 0] = }') # print(f'\n{grid_3d.nodes[:, 1] = }') # print(f'\n{grid_3d.nodes[:, 2] = }') - print(f"\n{rho =}") - print(f"{rho1 =}") - print(f"\n{theta =}") - print(f"{theta1 =}") - print(f"\n{zeta =}") - print(f"{zeta1 =}") + print(f"\n{rho = }") + print(f"{rho1 = }") + print(f"\n{theta = }") + print(f"{theta1 = }") + print(f"\n{zeta = }") + print(f"{zeta1 = }") # make c-contiguous - out = xp.ascontiguousarray(out) + out = np.ascontiguousarray(out) print(f"desc_eval for {var}: {time() - ttime} seconds") return out @@ -2947,12 +2935,12 @@ def n_xyz(self, x, y, z): elif self.params["density_profile"] == "affine": return self.params["n"] + self.params["n1"] * x elif self.params["density_profile"] == "gaussian_xy": - return self.params["n"] * xp.exp(-(x**2 + y**2) / self.params["p0"]) + return self.params["n"] * np.exp(-(x**2 + y**2) / self.params["p0"]) elif self.params["density_profile"] == "step_function_x": out = 1e-8 + 0 * x - # mask_x = xp.logical_and(x < .6, x > .4) - # mask_y = xp.logical_and(y < .6, y > .4) - # mask = xp.logical_and(mask_x, mask_y) + # mask_x = np.logical_and(x < .6, x > .4) + # mask_y = np.logical_and(y < .6, y > .4) + # mask = np.logical_and(mask_x, mask_y) mask = x < -2.0 out[mask] = self.params["n"] return out @@ -3278,7 +3266,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - r = xp.linspace(0.0, self.params["a"], n_pts) + r = np.linspace(0.0, self.params["a"], n_pts) fig, ax = plt.subplots(1, 3) @@ -3289,7 +3277,7 @@ def plot_profiles(self, n_pts=501): ax[0].set_xlabel("r") ax[0].set_ylabel("q") - ax[0].plot(r, xp.ones(r.size), "k--") + ax[0].plot(r, np.ones(r.size), "k--") ax[1].plot(r, self.p_r(r)) ax[1].set_xlabel("r") @@ -3312,8 +3300,8 @@ def b_xyz(self, x, y, z): """Magnetic field.""" bz = 0 * x - by = xp.tanh(z / self._params["delta"]) - bx = xp.sqrt(1 - by**2) + by = np.tanh(z / self._params["delta"]) + bx = np.sqrt(1 - by**2) bxs = self._params["amp"] * bx bys = self._params["amp"] * by diff --git a/src/struphy/fields_background/mhd_equil/eqdsk/readeqdsk.py b/src/struphy/fields_background/mhd_equil/eqdsk/readeqdsk.py index 812381e87..67578ce17 100644 --- a/src/struphy/fields_background/mhd_equil/eqdsk/readeqdsk.py +++ b/src/struphy/fields_background/mhd_equil/eqdsk/readeqdsk.py @@ -173,11 +173,7 @@ def main(): action="store_true", ) parser.add_option( - "-v", - "--vars", - dest="vars", - help="comma separated list of variables (use '-v \"*\"' for all)", - default="*", + "-v", "--vars", dest="vars", help="comma separated list of variables (use '-v \"*\"' for all)", default="*" ) parser.add_option( "-p", diff --git a/src/struphy/fields_background/tests/test_desc_equil.py b/src/struphy/fields_background/tests/test_desc_equil.py index c7130f0a3..be5a3d2db 100644 --- a/src/struphy/fields_background/tests/test_desc_equil.py +++ b/src/struphy/fields_background/tests/test_desc_equil.py @@ -1,6 +1,6 @@ import importlib.util -import cunumpy as xp +import numpy as np import pytest from matplotlib import pyplot as plt @@ -32,9 +32,9 @@ def test_desc_equil(do_plot=False): n2 = 9 n3 = 11 - e1 = xp.linspace(0.0001, 1, n1) - e2 = xp.linspace(0, 1, n2) - e3 = xp.linspace(0, 1 - 1e-6, n3) + e1 = np.linspace(0.0001, 1, n1) + e2 = np.linspace(0, 1, n2) + e3 = np.linspace(0, 1 - 1e-6, n3) # desc grid and evaluation vars = [ @@ -69,43 +69,43 @@ def test_desc_equil(do_plot=False): outs[nfp] = {} rho = rmin + e1 * (1.0 - rmin) - theta = 2 * xp.pi * e2 - zeta = 2 * xp.pi * e3 / nfp + theta = 2 * np.pi * e2 + zeta = 2 * np.pi * e3 / nfp - r, t, ze = xp.meshgrid(rho, theta, zeta, indexing="ij") + r, t, ze = np.meshgrid(rho, theta, zeta, indexing="ij") r = r.flatten() t = t.flatten() ze = ze.flatten() - nodes = xp.stack((r, t, ze)).T - grid_3d = Grid(nodes, spacing=xp.ones_like(nodes), jitable=False) + nodes = np.stack((r, t, ze)).T + grid_3d = Grid(nodes, spacing=np.ones_like(nodes), jitable=False) for var in vars: node_values = desc_eq.compute(var, grid=grid_3d, override_grid=False) if node_values[var].ndim == 1: out = node_values[var].reshape((rho.size, theta.size, zeta.size), order="C") - outs[nfp][var] = xp.ascontiguousarray(out) + outs[nfp][var] = np.ascontiguousarray(out) else: B = [] for i in range(3): Bcomp = node_values[var][:, i].reshape((rho.size, theta.size, zeta.size), order="C") - Bcomp = xp.ascontiguousarray(Bcomp) + Bcomp = np.ascontiguousarray(Bcomp) B += [Bcomp] outs[nfp][var + str(i + 1)] = Bcomp - outs[nfp][var] = xp.sqrt(B[0] ** 2 + B[1] ** 2 + B[2] ** 2) + outs[nfp][var] = np.sqrt(B[0] ** 2 + B[1] ** 2 + B[2] ** 2) - assert xp.allclose(outs[nfp]["B1"], outs[nfp]["B_R"]) - assert xp.allclose(outs[nfp]["B2"], outs[nfp]["B_phi"]) - assert xp.allclose(outs[nfp]["B3"], outs[nfp]["B_Z"]) + assert np.allclose(outs[nfp]["B1"], outs[nfp]["B_R"]) + assert np.allclose(outs[nfp]["B2"], outs[nfp]["B_phi"]) + assert np.allclose(outs[nfp]["B3"], outs[nfp]["B_Z"]) - assert xp.allclose(outs[nfp]["J1"], outs[nfp]["J_R"]) - assert xp.allclose(outs[nfp]["J2"], outs[nfp]["J_phi"]) - assert xp.allclose(outs[nfp]["J3"], outs[nfp]["J_Z"]) + assert np.allclose(outs[nfp]["J1"], outs[nfp]["J_R"]) + assert np.allclose(outs[nfp]["J2"], outs[nfp]["J_phi"]) + assert np.allclose(outs[nfp]["J3"], outs[nfp]["J_Z"]) - outs[nfp]["Bx"] = xp.cos(outs[nfp]["phi"]) * outs[nfp]["B_R"] - xp.sin(outs[nfp]["phi"]) * outs[nfp]["B_phi"] + outs[nfp]["Bx"] = np.cos(outs[nfp]["phi"]) * outs[nfp]["B_R"] - np.sin(outs[nfp]["phi"]) * outs[nfp]["B_phi"] - outs[nfp]["By"] = xp.sin(outs[nfp]["phi"]) * outs[nfp]["B_R"] + xp.cos(outs[nfp]["phi"]) * outs[nfp]["B_phi"] + outs[nfp]["By"] = np.sin(outs[nfp]["phi"]) * outs[nfp]["B_R"] + np.cos(outs[nfp]["phi"]) * outs[nfp]["B_phi"] outs[nfp]["Bz"] = outs[nfp]["B_Z"] @@ -122,32 +122,32 @@ def test_desc_equil(do_plot=False): outs_struphy[nfp]["Y"] = y outs_struphy[nfp]["Z"] = z - outs_struphy[nfp]["R"] = xp.sqrt(x**2 + y**2) - tmp = xp.arctan2(y, x) - tmp[tmp < -1e-6] += 2 * xp.pi + outs_struphy[nfp]["R"] = np.sqrt(x**2 + y**2) + tmp = np.arctan2(y, x) + tmp[tmp < -1e-6] += 2 * np.pi outs_struphy[nfp]["phi"] = tmp - outs_struphy[nfp]["sqrt(g)"] = s_eq.domain.jacobian_det(e1, e2, e3) / (4 * xp.pi**2 / nfp) + outs_struphy[nfp]["sqrt(g)"] = s_eq.domain.jacobian_det(e1, e2, e3) / (4 * np.pi**2 / nfp) outs_struphy[nfp]["p"] = s_eq.p0(e1, e2, e3) # include push forward to DESC logical coordinates bv = s_eq.bv(e1, e2, e3) outs_struphy[nfp]["B^rho"] = bv[0] * (1 - rmin) - outs_struphy[nfp]["B^theta"] = bv[1] * 2 * xp.pi - outs_struphy[nfp]["B^zeta"] = bv[2] * 2 * xp.pi / nfp + outs_struphy[nfp]["B^theta"] = bv[1] * 2 * np.pi + outs_struphy[nfp]["B^zeta"] = bv[2] * 2 * np.pi / nfp outs_struphy[nfp]["B"] = s_eq.absB0(e1, e2, e3) # include push forward to DESC logical coordinates jv = s_eq.jv(e1, e2, e3) outs_struphy[nfp]["J^rho"] = jv[0] * (1 - rmin) - outs_struphy[nfp]["J^theta"] = jv[1] * 2 * xp.pi - outs_struphy[nfp]["J^zeta"] = jv[2] * 2 * xp.pi / nfp + outs_struphy[nfp]["J^theta"] = jv[1] * 2 * np.pi + outs_struphy[nfp]["J^zeta"] = jv[2] * 2 * np.pi / nfp j1 = s_eq.j1(e1, e2, e3) - outs_struphy[nfp]["J"] = xp.sqrt(jv[0] * j1[0] + jv[1] * j1[1] + jv[2] * j1[2]) + outs_struphy[nfp]["J"] = np.sqrt(jv[0] * j1[0] + jv[1] * j1[1] + jv[2] * j1[2]) b_cart, xyz = s_eq.b_cart(e1, e2, e3) outs_struphy[nfp]["Bx"] = b_cart[0] @@ -157,8 +157,8 @@ def test_desc_equil(do_plot=False): # include push forward to DESC logical coordinates gradB1 = s_eq.gradB1(e1, e2, e3) outs_struphy[nfp]["|B|_r"] = gradB1[0] / (1 - rmin) - outs_struphy[nfp]["|B|_t"] = gradB1[1] / (2 * xp.pi) - outs_struphy[nfp]["|B|_z"] = gradB1[2] / (2 * xp.pi / nfp) + outs_struphy[nfp]["|B|_t"] = gradB1[1] / (2 * np.pi) + outs_struphy[nfp]["|B|_z"] = gradB1[2] / (2 * np.pi / nfp) # comparisons vars += ["Bx", "By", "Bz"] @@ -167,25 +167,25 @@ def test_desc_equil(do_plot=False): err_lim = 0.09 for nfp in nfps: - print(f"\n{nfp =}") + print(f"\n{nfp = }") for var in vars: if var in ("B_R", "B_phi", "B_Z", "J_R", "J_phi", "J_Z"): continue else: - max_norm = xp.max(xp.abs(outs[nfp][var])) + max_norm = np.max(np.abs(outs[nfp][var])) if max_norm < 1e-16: max_norm = 1.0 - err = xp.max(xp.abs(outs[nfp][var] - outs_struphy[nfp][var])) / max_norm + err = np.max(np.abs(outs[nfp][var] - outs_struphy[nfp][var])) / max_norm assert err < err_lim print( - f"compare {var}: {err =}", + f"compare {var}: {err = }", ) if do_plot: fig = plt.figure(figsize=(12, 13)) - levels = xp.linspace(xp.min(outs[nfp][var]) - 1e-10, xp.max(outs[nfp][var]), 20) + levels = np.linspace(np.min(outs[nfp][var]) - 1e-10, np.max(outs[nfp][var]), 20) # poloidal plot R = outs[nfp]["R"][:, :, 0].squeeze() @@ -193,7 +193,7 @@ def test_desc_equil(do_plot=False): plt.subplot(2, 2, 1) map1 = plt.contourf(R, Z, outs[nfp][var][:, :, 0], levels=levels) - plt.title(f"DESC, {var =}, {nfp =}") + plt.title(f"DESC, {var = }, {nfp = }") plt.xlabel("$R$") plt.ylabel("$Z$") plt.axis("equal") @@ -201,7 +201,7 @@ def test_desc_equil(do_plot=False): plt.subplot(2, 2, 2) map2 = plt.contourf(R, Z, outs_struphy[nfp][var][:, :, 0], levels=levels) - plt.title(f"Struphy, {err =}") + plt.title(f"Struphy, {err = }") plt.xlabel("$R$") plt.ylabel("$Z$") plt.axis("equal") @@ -217,7 +217,7 @@ def test_desc_equil(do_plot=False): plt.subplot(2, 2, 3) map3 = plt.contourf(x1, y1, outs[nfp][var][:, 0, :], levels=levels) map3b = plt.contourf(x2, y2, outs[nfp][var][:, n2 // 2, :], levels=levels) - plt.title(f"DESC, {var =}, {nfp =}") + plt.title(f"DESC, {var = }, {nfp = }") plt.xlabel("$x$") plt.ylabel("$y$") plt.axis("equal") @@ -226,7 +226,7 @@ def test_desc_equil(do_plot=False): plt.subplot(2, 2, 4) map4 = plt.contourf(x1, y1, outs_struphy[nfp][var][:, 0, :], levels=levels) map4b = plt.contourf(x2, y2, outs_struphy[nfp][var][:, n2 // 2, :], levels=levels) - plt.title(f"Struphy, {err =}") + plt.title(f"Struphy, {err = }") plt.xlabel("$x$") plt.ylabel("$y$") plt.axis("equal") diff --git a/src/struphy/fields_background/tests/test_generic_equils.py b/src/struphy/fields_background/tests/test_generic_equils.py index 77ca8baaa..d4d7fc25b 100644 --- a/src/struphy/fields_background/tests/test_generic_equils.py +++ b/src/struphy/fields_background/tests/test_generic_equils.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np import pytest from matplotlib import pyplot as plt @@ -9,8 +9,8 @@ def test_generic_equils(show=False): - fun_vec = lambda x, y, z: (xp.cos(2 * xp.pi * x), xp.cos(2 * xp.pi * y), z) - fun_n = lambda x, y, z: xp.exp(-((x - 1) ** 2) - (y) ** 2) + fun_vec = lambda x, y, z: (np.cos(2 * np.pi * x), np.cos(2 * np.pi * y), z) + fun_n = lambda x, y, z: np.exp(-((x - 1) ** 2) - (y) ** 2) fun_p = lambda x, y, z: x**2 gen_eq = GenericCartesianFluidEquilibrium( u_xyz=fun_vec, @@ -25,22 +25,22 @@ def test_generic_equils(show=False): gradB_xyz=fun_vec, ) - x = xp.linspace(-3, 3, 32) - y = xp.linspace(-4, 4, 32) + x = np.linspace(-3, 3, 32) + y = np.linspace(-4, 4, 32) z = 1.0 - xx, yy, zz = xp.meshgrid(x, y, z) + xx, yy, zz = np.meshgrid(x, y, z) # gen_eq - assert all([xp.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq.u_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) - assert xp.all(gen_eq.p_xyz(xx, yy, zz) == fun_p(xx, yy, zz)) - assert xp.all(gen_eq.n_xyz(xx, yy, zz) == fun_n(xx, yy, zz)) + assert all([np.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq.u_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) + assert np.all(gen_eq.p_xyz(xx, yy, zz) == fun_p(xx, yy, zz)) + assert np.all(gen_eq.n_xyz(xx, yy, zz) == fun_n(xx, yy, zz)) # gen_eq_B - assert all([xp.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.u_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) - assert xp.all(gen_eq_B.p_xyz(xx, yy, zz) == fun_p(xx, yy, zz)) - assert xp.all(gen_eq_B.n_xyz(xx, yy, zz) == fun_n(xx, yy, zz)) - assert all([xp.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.b_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) - assert all([xp.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.gradB_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) + assert all([np.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.u_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) + assert np.all(gen_eq_B.p_xyz(xx, yy, zz) == fun_p(xx, yy, zz)) + assert np.all(gen_eq_B.n_xyz(xx, yy, zz) == fun_n(xx, yy, zz)) + assert all([np.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.b_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) + assert all([np.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.gradB_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) if show: plt.figure(figsize=(12, 12)) diff --git a/src/struphy/fields_background/tests/test_mhd_equils.py b/src/struphy/fields_background/tests/test_mhd_equils.py index f363ddbe3..4ab6f7e19 100644 --- a/src/struphy/fields_background/tests/test_mhd_equils.py +++ b/src/struphy/fields_background/tests/test_mhd_equils.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np import pytest from struphy.fields_background import equils @@ -9,44 +9,44 @@ [ ("HomogenSlab", {}, "Cuboid", {}), ("HomogenSlab", {}, "Colella", {"alpha": 0.06}), - ("ShearedSlab", {"a": 0.75, "R0": 3.5}, "Cuboid", {"r1": 0.75, "r2": 2 * xp.pi * 0.75, "r3": 2 * xp.pi * 3.5}), + ("ShearedSlab", {"a": 0.75, "R0": 3.5}, "Cuboid", {"r1": 0.75, "r2": 2 * np.pi * 0.75, "r3": 2 * np.pi * 3.5}), ( "ShearedSlab", {"a": 0.75, "R0": 3.5, "q0": "inf", "q1": "inf"}, "Cuboid", - {"r1": 0.75, "r2": 2 * xp.pi * 0.75, "r3": 2 * xp.pi * 3.5}, + {"r1": 0.75, "r2": 2 * np.pi * 0.75, "r3": 2 * np.pi * 3.5}, ), ( "ShearedSlab", {"a": 0.55, "R0": 4.5}, "Orthogonal", - {"Lx": 0.55, "Ly": 2 * xp.pi * 0.55, "Lz": 2 * xp.pi * 4.5}, + {"Lx": 0.55, "Ly": 2 * np.pi * 0.55, "Lz": 2 * np.pi * 4.5}, ), - ("ScrewPinch", {"a": 0.45, "R0": 2.5}, "HollowCylinder", {"a1": 0.05, "a2": 0.45, "Lz": 2 * xp.pi * 2.5}), - ("ScrewPinch", {"a": 1.45, "R0": 6.5}, "IGAPolarCylinder", {"a": 1.45, "Lz": 2 * xp.pi * 6.5}), + ("ScrewPinch", {"a": 0.45, "R0": 2.5}, "HollowCylinder", {"a1": 0.05, "a2": 0.45, "Lz": 2 * np.pi * 2.5}), + ("ScrewPinch", {"a": 1.45, "R0": 6.5}, "IGAPolarCylinder", {"a": 1.45, "Lz": 2 * np.pi * 6.5}), ( "ScrewPinch", {"a": 0.45, "R0": 2.5, "q0": 1.5, "q1": 1.5}, "HollowCylinder", - {"a1": 0.05, "a2": 0.45, "Lz": 2 * xp.pi * 2.5}, + {"a1": 0.05, "a2": 0.45, "Lz": 2 * np.pi * 2.5}, ), ( "ScrewPinch", {"a": 1.45, "R0": 6.5, "q0": 1.5, "q1": 1.5}, "IGAPolarCylinder", - {"a": 1.45, "Lz": 2 * xp.pi * 6.5}, + {"a": 1.45, "Lz": 2 * np.pi * 6.5}, ), ( "ScrewPinch", {"a": 0.45, "R0": 2.5, "q0": "inf", "q1": "inf"}, "HollowCylinder", - {"a1": 0.05, "a2": 0.45, "Lz": 2 * xp.pi * 2.5}, + {"a1": 0.05, "a2": 0.45, "Lz": 2 * np.pi * 2.5}, ), ( "ScrewPinch", {"a": 1.45, "R0": 6.5, "q0": "inf", "q1": "inf"}, "IGAPolarCylinder", - {"a": 1.45, "Lz": 2 * xp.pi * 6.5}, + {"a": 1.45, "Lz": 2 * np.pi * 6.5}, ), ( "AdhocTorus", @@ -136,28 +136,29 @@ def test_equils(equil_domain_pair): Test field evaluations of all implemented MHD equilbria with default parameters. """ + from struphy.fields_background import equils from struphy.fields_background.base import CartesianMHDequilibrium, NumericalMHDequilibrium from struphy.geometry import domains # logical evalution point - pt = (xp.random.rand(), xp.random.rand(), xp.random.rand()) + pt = (np.random.rand(), np.random.rand(), np.random.rand()) # logical arrays: - e1 = xp.random.rand(4) - e2 = xp.random.rand(5) - e3 = xp.random.rand(6) + e1 = np.random.rand(4) + e2 = np.random.rand(5) + e3 = np.random.rand(6) # 2d slices - mat_12_1, mat_12_2 = xp.meshgrid(e1, e2, indexing="ij") - mat_13_1, mat_13_3 = xp.meshgrid(e1, e3, indexing="ij") - mat_23_2, mat_23_3 = xp.meshgrid(e2, e3, indexing="ij") + mat_12_1, mat_12_2 = np.meshgrid(e1, e2, indexing="ij") + mat_13_1, mat_13_3 = np.meshgrid(e1, e3, indexing="ij") + mat_23_2, mat_23_3 = np.meshgrid(e2, e3, indexing="ij") # 3d - mat_123_1, mat_123_2, mat_123_3 = xp.meshgrid(e1, e2, e3, indexing="ij") - mat_123_1_sp, mat_123_2_sp, mat_123_3_sp = xp.meshgrid(e1, e2, e3, indexing="ij", sparse=True) + mat_123_1, mat_123_2, mat_123_3 = np.meshgrid(e1, e2, e3, indexing="ij") + mat_123_1_sp, mat_123_2_sp, mat_123_3_sp = np.meshgrid(e1, e2, e3, indexing="ij", sparse=True) # markers - markers = xp.random.rand(33, 10) + markers = np.random.rand(33, 10) # create MHD equilibrium eq_mhd = getattr(equils, equil_domain_pair[0])(**equil_domain_pair[1]) @@ -273,8 +274,8 @@ def test_equils(equil_domain_pair): # --------- eta1 evaluation --------- results = [] - e2_pt = xp.random.rand() - e3_pt = xp.random.rand() + e2_pt = np.random.rand() + e3_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(e1, e2_pt, e3_pt, squeeze_out=True)) @@ -320,8 +321,8 @@ def test_equils(equil_domain_pair): # --------- eta2 evaluation --------- results = [] - e1_pt = xp.random.rand() - e3_pt = xp.random.rand() + e1_pt = np.random.rand() + e3_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(e1_pt, e2, e3_pt, squeeze_out=True)) @@ -369,8 +370,8 @@ def test_equils(equil_domain_pair): # --------- eta3 evaluation --------- results = [] - e1_pt = xp.random.rand() - e2_pt = xp.random.rand() + e1_pt = np.random.rand() + e2_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(e1_pt, e2_pt, e3, squeeze_out=True)) @@ -418,7 +419,7 @@ def test_equils(equil_domain_pair): # --------- eta1-eta2 evaluation --------- results = [] - e3_pt = xp.random.rand() + e3_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(e1, e2, e3_pt, squeeze_out=True)) @@ -466,7 +467,7 @@ def test_equils(equil_domain_pair): # --------- eta1-eta3 evaluation --------- results = [] - e2_pt = xp.random.rand() + e2_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(e1, e2_pt, e3, squeeze_out=True)) @@ -514,7 +515,7 @@ def test_equils(equil_domain_pair): # --------- eta2-eta3 evaluation --------- results = [] - e1_pt = xp.random.rand() + e1_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(e1_pt, e2, e3, squeeze_out=True)) @@ -608,7 +609,7 @@ def test_equils(equil_domain_pair): # --------- 12 matrix evaluation --------- results = [] - e3_pt = xp.random.rand() + e3_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(mat_12_1, mat_12_2, e3_pt, squeeze_out=True)) @@ -656,7 +657,7 @@ def test_equils(equil_domain_pair): # --------- 13 matrix evaluation --------- results = [] - e2_pt = xp.random.rand() + e2_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(mat_13_1, e2_pt, mat_13_3, squeeze_out=True)) @@ -704,7 +705,7 @@ def test_equils(equil_domain_pair): # --------- 23 matrix evaluation --------- results = [] - e1_pt = xp.random.rand() + e1_pt = np.random.rand() # scalar functions results.append(eq_mhd.absB0(e1_pt, mat_23_2, mat_23_3, squeeze_out=True)) @@ -847,22 +848,22 @@ def assert_scalar(result, kind, *etas): markers = etas[0] n_p = markers.shape[0] - assert isinstance(result, xp.ndarray) + assert isinstance(result, np.ndarray) assert result.shape == (n_p,) for ip in range(n_p): assert isinstance(result[ip], float) - assert not xp.isnan(result[ip]) + assert not np.isnan(result[ip]) else: # point-wise if kind == "point": assert isinstance(result, float) - assert not xp.isnan(result) + assert not np.isnan(result) # slices else: - assert isinstance(result, xp.ndarray) + assert isinstance(result, np.ndarray) # eta1-array if kind == "e1": @@ -914,27 +915,27 @@ def assert_vector(result, kind, *etas): markers = etas[0] n_p = markers.shape[0] - assert isinstance(result, xp.ndarray) + assert isinstance(result, np.ndarray) assert result.shape == (3, n_p) for c in range(3): for ip in range(n_p): assert isinstance(result[c, ip], float) - assert not xp.isnan(result[c, ip]) + assert not np.isnan(result[c, ip]) else: # point-wise if kind == "point": - assert isinstance(result, xp.ndarray) + assert isinstance(result, np.ndarray) assert result.shape == (3,) for c in range(3): assert isinstance(result[c], float) - assert not xp.isnan(result[c]) + assert not np.isnan(result[c]) # slices else: - assert isinstance(result, xp.ndarray) + assert isinstance(result, np.ndarray) # eta1-array if kind == "e1": diff --git a/src/struphy/fields_background/tests/test_numerical_mhd_equil.py b/src/struphy/fields_background/tests/test_numerical_mhd_equil.py index aa1278d5d..619bad2d9 100644 --- a/src/struphy/fields_background/tests/test_numerical_mhd_equil.py +++ b/src/struphy/fields_background/tests/test_numerical_mhd_equil.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np import pytest from struphy.fields_background.base import FluidEquilibrium, LogicalMHDequilibrium @@ -50,53 +50,53 @@ def test_transformations(mapping, mhd_equil): num_equil = NumEqTest(domain, proxy) # compare values: - eta1 = xp.random.rand(4) - eta2 = xp.random.rand(5) - eta3 = xp.random.rand(6) + eta1 = np.random.rand(4) + eta2 = np.random.rand(5) + eta3 = np.random.rand(6) - assert xp.allclose(ana_equil.absB0(eta1, eta2, eta3), num_equil.absB0(eta1, eta2, eta3)) + assert np.allclose(ana_equil.absB0(eta1, eta2, eta3), num_equil.absB0(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.bv(eta1, eta2, eta3)[0], num_equil.bv(eta1, eta2, eta3)[0]) - assert xp.allclose(ana_equil.bv(eta1, eta2, eta3)[1], num_equil.bv(eta1, eta2, eta3)[1]) - assert xp.allclose(ana_equil.bv(eta1, eta2, eta3)[2], num_equil.bv(eta1, eta2, eta3)[2]) + assert np.allclose(ana_equil.bv(eta1, eta2, eta3)[0], num_equil.bv(eta1, eta2, eta3)[0]) + assert np.allclose(ana_equil.bv(eta1, eta2, eta3)[1], num_equil.bv(eta1, eta2, eta3)[1]) + assert np.allclose(ana_equil.bv(eta1, eta2, eta3)[2], num_equil.bv(eta1, eta2, eta3)[2]) - assert xp.allclose(ana_equil.b1_1(eta1, eta2, eta3), num_equil.b1_1(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.b1_2(eta1, eta2, eta3), num_equil.b1_2(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.b1_3(eta1, eta2, eta3), num_equil.b1_3(eta1, eta2, eta3)) + assert np.allclose(ana_equil.b1_1(eta1, eta2, eta3), num_equil.b1_1(eta1, eta2, eta3)) + assert np.allclose(ana_equil.b1_2(eta1, eta2, eta3), num_equil.b1_2(eta1, eta2, eta3)) + assert np.allclose(ana_equil.b1_3(eta1, eta2, eta3), num_equil.b1_3(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.b2_1(eta1, eta2, eta3), num_equil.b2_1(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.b2_2(eta1, eta2, eta3), num_equil.b2_2(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.b2_3(eta1, eta2, eta3), num_equil.b2_3(eta1, eta2, eta3)) + assert np.allclose(ana_equil.b2_1(eta1, eta2, eta3), num_equil.b2_1(eta1, eta2, eta3)) + assert np.allclose(ana_equil.b2_2(eta1, eta2, eta3), num_equil.b2_2(eta1, eta2, eta3)) + assert np.allclose(ana_equil.b2_3(eta1, eta2, eta3), num_equil.b2_3(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[0], num_equil.unit_bv(eta1, eta2, eta3)[0]) - assert xp.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[1], num_equil.unit_bv(eta1, eta2, eta3)[1]) - assert xp.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[2], num_equil.unit_bv(eta1, eta2, eta3)[2]) + assert np.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[0], num_equil.unit_bv(eta1, eta2, eta3)[0]) + assert np.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[1], num_equil.unit_bv(eta1, eta2, eta3)[1]) + assert np.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[2], num_equil.unit_bv(eta1, eta2, eta3)[2]) - assert xp.allclose(ana_equil.unit_b1_1(eta1, eta2, eta3), num_equil.unit_b1_1(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.unit_b1_2(eta1, eta2, eta3), num_equil.unit_b1_2(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.unit_b1_3(eta1, eta2, eta3), num_equil.unit_b1_3(eta1, eta2, eta3)) + assert np.allclose(ana_equil.unit_b1_1(eta1, eta2, eta3), num_equil.unit_b1_1(eta1, eta2, eta3)) + assert np.allclose(ana_equil.unit_b1_2(eta1, eta2, eta3), num_equil.unit_b1_2(eta1, eta2, eta3)) + assert np.allclose(ana_equil.unit_b1_3(eta1, eta2, eta3), num_equil.unit_b1_3(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.unit_b2_1(eta1, eta2, eta3), num_equil.unit_b2_1(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.unit_b2_2(eta1, eta2, eta3), num_equil.unit_b2_2(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.unit_b2_3(eta1, eta2, eta3), num_equil.unit_b2_3(eta1, eta2, eta3)) + assert np.allclose(ana_equil.unit_b2_1(eta1, eta2, eta3), num_equil.unit_b2_1(eta1, eta2, eta3)) + assert np.allclose(ana_equil.unit_b2_2(eta1, eta2, eta3), num_equil.unit_b2_2(eta1, eta2, eta3)) + assert np.allclose(ana_equil.unit_b2_3(eta1, eta2, eta3), num_equil.unit_b2_3(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.jv(eta1, eta2, eta3)[0], num_equil.jv(eta1, eta2, eta3)[0]) - assert xp.allclose(ana_equil.jv(eta1, eta2, eta3)[1], num_equil.jv(eta1, eta2, eta3)[1]) - assert xp.allclose(ana_equil.jv(eta1, eta2, eta3)[2], num_equil.jv(eta1, eta2, eta3)[2]) + assert np.allclose(ana_equil.jv(eta1, eta2, eta3)[0], num_equil.jv(eta1, eta2, eta3)[0]) + assert np.allclose(ana_equil.jv(eta1, eta2, eta3)[1], num_equil.jv(eta1, eta2, eta3)[1]) + assert np.allclose(ana_equil.jv(eta1, eta2, eta3)[2], num_equil.jv(eta1, eta2, eta3)[2]) - assert xp.allclose(ana_equil.j1_1(eta1, eta2, eta3), num_equil.j1_1(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.j1_2(eta1, eta2, eta3), num_equil.j1_2(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.j1_3(eta1, eta2, eta3), num_equil.j1_3(eta1, eta2, eta3)) + assert np.allclose(ana_equil.j1_1(eta1, eta2, eta3), num_equil.j1_1(eta1, eta2, eta3)) + assert np.allclose(ana_equil.j1_2(eta1, eta2, eta3), num_equil.j1_2(eta1, eta2, eta3)) + assert np.allclose(ana_equil.j1_3(eta1, eta2, eta3), num_equil.j1_3(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.j2_1(eta1, eta2, eta3), num_equil.j2_1(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.j2_2(eta1, eta2, eta3), num_equil.j2_2(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.j2_3(eta1, eta2, eta3), num_equil.j2_3(eta1, eta2, eta3)) + assert np.allclose(ana_equil.j2_1(eta1, eta2, eta3), num_equil.j2_1(eta1, eta2, eta3)) + assert np.allclose(ana_equil.j2_2(eta1, eta2, eta3), num_equil.j2_2(eta1, eta2, eta3)) + assert np.allclose(ana_equil.j2_3(eta1, eta2, eta3), num_equil.j2_3(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.p0(eta1, eta2, eta3), num_equil.p0(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.p3(eta1, eta2, eta3), num_equil.p3(eta1, eta2, eta3)) + assert np.allclose(ana_equil.p0(eta1, eta2, eta3), num_equil.p0(eta1, eta2, eta3)) + assert np.allclose(ana_equil.p3(eta1, eta2, eta3), num_equil.p3(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.n0(eta1, eta2, eta3), num_equil.n0(eta1, eta2, eta3)) - assert xp.allclose(ana_equil.n3(eta1, eta2, eta3), num_equil.n3(eta1, eta2, eta3)) + assert np.allclose(ana_equil.n0(eta1, eta2, eta3), num_equil.n0(eta1, eta2, eta3)) + assert np.allclose(ana_equil.n3(eta1, eta2, eta3), num_equil.n3(eta1, eta2, eta3)) class NumEqTest(LogicalMHDequilibrium): diff --git a/src/struphy/geometry/base.py b/src/struphy/geometry/base.py index d2b21688e..528a813ad 100644 --- a/src/struphy/geometry/base.py +++ b/src/struphy/geometry/base.py @@ -3,8 +3,10 @@ from abc import ABCMeta, abstractmethod -import cunumpy as xp import h5py +import matplotlib.pyplot as plt +import numpy as np +from mpl_toolkits.mplot3d import Axes3D from scipy.sparse import csc_matrix, kron from scipy.sparse.linalg import splu, spsolve @@ -56,12 +58,12 @@ def __init__( self._NbaseN = [Nel + p - kind * p for Nel, p, kind in zip(Nel, p, spl_kind)] - el_b = [xp.linspace(0.0, 1.0, Nel + 1) for Nel in Nel] + el_b = [np.linspace(0.0, 1.0, Nel + 1) for Nel in Nel] self._T = [bsp.make_knots(el_b, p, kind) for el_b, p, kind in zip(el_b, p, spl_kind)] self._indN = [ - (xp.indices((Nel, p + 1))[1] + xp.arange(Nel)[:, None]) % NbaseN + (np.indices((Nel, p + 1))[1] + np.arange(Nel)[:, None]) % NbaseN for Nel, p, NbaseN in zip(Nel, p, self._NbaseN) ] @@ -71,15 +73,15 @@ def __init__( self._p = (*self._p, 0) self._NbaseN = self._NbaseN + [0] - self._T = self._T + [xp.zeros((1,), dtype=float)] + self._T = self._T + [np.zeros((1,), dtype=float)] - self._indN = self._indN + [xp.zeros((1, 1), dtype=int)] + self._indN = self._indN + [np.zeros((1, 1), dtype=int)] # create dummy attributes for analytical mappings if self.kind_map >= 10: - self._cx = xp.zeros((1, 1, 1), dtype=float) - self._cy = xp.zeros((1, 1, 1), dtype=float) - self._cz = xp.zeros((1, 1, 1), dtype=float) + self._cx = np.zeros((1, 1, 1), dtype=float) + self._cy = np.zeros((1, 1, 1), dtype=float) + self._cz = np.zeros((1, 1, 1), dtype=float) self._transformation_ids = { "pull": 0, @@ -120,7 +122,7 @@ def __init__( self._args_domain = DomainArguments( self.kind_map, self.params_numpy, - xp.array(self.p), + np.array(self.p), self.T[0], self.T[1], self.T[2], @@ -165,15 +167,15 @@ def params(self, new): self._params = new @property - def params_numpy(self) -> xp.ndarray: + def params_numpy(self) -> np.ndarray: """Mapping parameters as numpy array (can be empty).""" if not hasattr(self, "_params_numpy"): - self._params_numpy = xp.array([0], dtype=float) + self._params_numpy = np.array([0], dtype=float) return self._params_numpy @params_numpy.setter def params_numpy(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) assert new.ndim == 1 self._params_numpy = new @@ -768,7 +770,7 @@ def _evaluate_metric_coefficient(self, *etas, which=0, **kwargs): markers = etas[0] # to keep C-ordering the (3, 3)-part is in the last indices - out = xp.empty((markers.shape[0], 3, 3), dtype=float) + out = np.empty((markers.shape[0], 3, 3), dtype=float) n_inside = evaluation_kernels.kernel_evaluate_pic( markers, @@ -780,24 +782,24 @@ def _evaluate_metric_coefficient(self, *etas, which=0, **kwargs): ) # move the (3, 3)-part to front - out = xp.transpose(out, axes=(1, 2, 0)) + out = np.transpose(out, axes=(1, 2, 0)) # remove holes out = out[:, :, :n_inside] if transposed: - out = xp.transpose(out, axes=(1, 0, 2)) + out = np.transpose(out, axes=(1, 0, 2)) # change size of "out" depending on which metric coeff has been evaluated if which == 0 or which == -1: out = out[:, 0, :] if change_out_order: - out = xp.transpose(out, axes=(1, 0)) + out = np.transpose(out, axes=(1, 0)) elif which == 2: out = out[0, 0, :] else: if change_out_order: - out = xp.transpose(out, axes=(2, 0, 1)) + out = np.transpose(out, axes=(2, 0, 1)) # tensor-product/slice evaluation else: @@ -809,7 +811,7 @@ def _evaluate_metric_coefficient(self, *etas, which=0, **kwargs): ) # to keep C-ordering the (3, 3)-part is in the last indices - out = xp.empty( + out = np.empty( (E1.shape[0], E2.shape[1], E3.shape[2], 3, 3), dtype=float, ) @@ -825,20 +827,20 @@ def _evaluate_metric_coefficient(self, *etas, which=0, **kwargs): ) # move the (3, 3)-part to front - out = xp.transpose(out, axes=(3, 4, 0, 1, 2)) + out = np.transpose(out, axes=(3, 4, 0, 1, 2)) if transposed: - out = xp.transpose(out, axes=(1, 0, 2, 3, 4)) + out = np.transpose(out, axes=(1, 0, 2, 3, 4)) if which == 0: out = out[:, 0, :, :, :] if change_out_order: - out = xp.transpose(out, axes=(1, 2, 3, 0)) + out = np.transpose(out, axes=(1, 2, 3, 0)) elif which == 2: out = out[0, 0, :, :, :] else: if change_out_order: - out = xp.transpose(out, axes=(2, 3, 4, 0, 1)) + out = np.transpose(out, axes=(2, 3, 4, 0, 1)) # remove singleton dimensions for slice evaluation if squeeze_out: @@ -903,7 +905,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa assert len(etas) == 3 assert etas[0].shape == etas[1].shape == etas[2].shape assert etas[0].ndim == 1 - markers = xp.stack(etas, axis=1) + markers = np.stack(etas, axis=1) else: markers = etas[0] @@ -955,7 +957,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa A_has_holes = False # call evaluation kernel - out = xp.empty((markers.shape[0], 3), dtype=float) + out = np.empty((markers.shape[0], 3), dtype=float) # make sure we don't have stride = 0 A = A.copy() @@ -971,7 +973,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa ) # move the (3, 3)-part to front - out = xp.transpose(out, axes=(1, 0)) + out = np.transpose(out, axes=(1, 0)) # remove holes out = out[:, :n_inside] @@ -985,7 +987,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa out = out[0, :] else: if change_out_order: - out = xp.transpose(out, axes=(1, 0)) + out = np.transpose(out, axes=(1, 0)) # tensor-product/slice evaluation else: @@ -1012,7 +1014,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa A = Domain.prepare_arg(a, X[0], X[1], X[2], a_kwargs=a_kwargs) # call evaluation kernel - out = xp.empty( + out = np.empty( (E1.shape[0], E2.shape[1], E3.shape[2], 3), dtype=float, ) @@ -1029,14 +1031,14 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa ) # move the (3, 3)-part to front - out = xp.transpose(out, axes=(3, 0, 1, 2)) + out = np.transpose(out, axes=(3, 0, 1, 2)) # change output order if kind_int < 10: out = out[0, :, :, :] else: if change_out_order: - out = xp.transpose(out, axes=(1, 2, 3, 0)) + out = np.transpose(out, axes=(1, 2, 3, 0)) # remove singleton dimensions for slice evaluation if squeeze_out: @@ -1083,22 +1085,22 @@ def prepare_eval_pts(x, y, z, flat_eval=False): if flat_eval: # convert list type data to numpy array: if isinstance(x, list): - arg_x = xp.array(x) - elif isinstance(x, xp.ndarray): + arg_x = np.array(x) + elif isinstance(x, np.ndarray): arg_x = x else: raise ValueError("Input x must be a 1d list or numpy array") if isinstance(y, list): - arg_y = xp.array(y) - elif isinstance(y, xp.ndarray): + arg_y = np.array(y) + elif isinstance(y, np.ndarray): arg_y = y else: raise ValueError("Input y must be a 1d list or numpy array") if isinstance(z, list): - arg_z = xp.array(z) - elif isinstance(z, xp.ndarray): + arg_z = np.array(z) + elif isinstance(z, np.ndarray): arg_z = z else: raise ValueError("Input z must be a 1d list or numpy array") @@ -1117,56 +1119,56 @@ def prepare_eval_pts(x, y, z, flat_eval=False): else: # convert list type data to numpy array: if isinstance(x, float): - arg_x = xp.array([x]) + arg_x = np.array([x]) elif isinstance(x, int): - arg_x = xp.array([float(x)]) + arg_x = np.array([float(x)]) elif isinstance(x, list): - arg_x = xp.array(x) - elif isinstance(x, xp.ndarray): + arg_x = np.array(x) + elif isinstance(x, np.ndarray): arg_x = x.copy() else: raise ValueError(f"data type {type(x)} not supported") if isinstance(y, float): - arg_y = xp.array([y]) + arg_y = np.array([y]) elif isinstance(y, int): - arg_y = xp.array([float(y)]) + arg_y = np.array([float(y)]) elif isinstance(y, list): - arg_y = xp.array(y) - elif isinstance(y, xp.ndarray): + arg_y = np.array(y) + elif isinstance(y, np.ndarray): arg_y = y.copy() else: raise ValueError(f"data type {type(y)} not supported") if isinstance(z, float): - arg_z = xp.array([z]) + arg_z = np.array([z]) elif isinstance(z, int): - arg_z = xp.array([float(z)]) + arg_z = np.array([float(z)]) elif isinstance(z, list): - arg_z = xp.array(z) - elif isinstance(z, xp.ndarray): + arg_z = np.array(z) + elif isinstance(z, np.ndarray): arg_z = z.copy() else: raise ValueError(f"data type {type(z)} not supported") # tensor-product for given three 1D arrays if arg_x.ndim == 1 and arg_y.ndim == 1 and arg_z.ndim == 1: - E1, E2, E3 = xp.meshgrid(arg_x, arg_y, arg_z, indexing="ij") + E1, E2, E3 = np.meshgrid(arg_x, arg_y, arg_z, indexing="ij") # given xy-plane at point z: elif arg_x.ndim == 2 and arg_y.ndim == 2 and arg_z.size == 1: E1 = arg_x[:, :, None] E2 = arg_y[:, :, None] - E3 = arg_z * xp.ones(E1.shape) + E3 = arg_z * np.ones(E1.shape) # given xz-plane at point y: elif arg_x.ndim == 2 and arg_y.size == 1 and arg_z.ndim == 2: E1 = arg_x[:, None, :] - E2 = arg_y * xp.ones(E1.shape) + E2 = arg_y * np.ones(E1.shape) E3 = arg_z[:, None, :] # given yz-plane at point x: elif arg_x.size == 1 and arg_y.ndim == 2 and arg_z.ndim == 2: E2 = arg_y[None, :, :] E3 = arg_z[None, :, :] - E1 = arg_x * xp.ones(E2.shape) + E1 = arg_x * np.ones(E2.shape) # given three 3D arrays elif arg_x.ndim == 3 and arg_y.ndim == 3 and arg_z.ndim == 3: # Distinguish if input coordinates are from sparse or dense meshgrid. @@ -1224,7 +1226,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): # float (point-wise, scalar function) if isinstance(a_in, float): - a_out = xp.array([[[[a_in]]]]) + a_out = np.array([[[[a_in]]]]) # single callable: # scalar function -> must return a 3d array for 3d evaluation points @@ -1237,7 +1239,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): else: if is_sparse_meshgrid: a_out = a_in( - *xp.meshgrid(Xs[0][:, 0, 0], Xs[1][0, :, 0], Xs[2][0, 0, :], indexing="ij"), + *np.meshgrid(Xs[0][:, 0, 0], Xs[1][0, :, 0], Xs[2][0, 0, :], indexing="ij"), **a_kwargs, ) else: @@ -1245,7 +1247,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): # case of Field.__call__ if isinstance(a_out, list): - a_out = xp.array(a_out) + a_out = np.array(a_out) if a_out.ndim == 3: a_out = a_out[None, :, :, :] @@ -1273,7 +1275,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): if is_sparse_meshgrid: a_out += [ component( - *xp.meshgrid( + *np.meshgrid( Xs[0][:, 0, 0], Xs[1][0, :, 0], Xs[2][0, 0, :], @@ -1285,25 +1287,25 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): else: a_out += [component(*Xs, **a_kwargs)] - elif isinstance(component, xp.ndarray): + elif isinstance(component, np.ndarray): if flat_eval: - assert component.ndim == 1, print(f"{component.ndim =}") + assert component.ndim == 1, print(f"{component.ndim = }") else: - assert component.ndim == 3, print(f"{component.ndim =}") + assert component.ndim == 3, print(f"{component.ndim = }") a_out += [component] elif isinstance(component, float): - a_out += [xp.array([component])[:, None, None]] + a_out += [np.array([component])[:, None, None]] - a_out = xp.array(a_out, dtype=float) + a_out = np.array(a_out, dtype=float) # numpy array: # 1d array (flat_eval=True and scalar input or flat_eval=False and length 1 (scalar) or length 3 (vector)) # 2d array (flat_eval=True and vector-valued input of shape (3,:)) # 3d array (flat_eval=False and scalar input) # 4d array (flat_eval=False and vector-valued input of shape (3,:,:,:)) - elif isinstance(a_in, xp.ndarray): + elif isinstance(a_in, np.ndarray): if flat_eval: if a_in.ndim == 1: a_out = a_in[None, :] @@ -1312,7 +1314,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): else: raise ValueError( "Input array a_in must be either 1d (scalar) or \ - 2d (vector-valued, shape (3,:)) for flat evaluation!", + 2d (vector-valued, shape (3,:)) for flat evaluation!" ) else: @@ -1331,39 +1333,415 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): else: raise ValueError( "Input array a_in must be either 3d (scalar) or \ - 4d (vector-valued, shape (3,:,:,:)) for non-flat evaluation!", + 4d (vector-valued, shape (3,:,:,:)) for non-flat evaluation!" ) else: raise TypeError( "Argument a must be either a float OR a list/tuple of 1 or 3 callable(s)/numpy array(s)/float(s) \ - OR a single numpy array OR a single callable!", + OR a single numpy array OR a single callable!" ) # make sure that output array is 2d and of shape (:, 1) or (:, 3) for flat evaluation if flat_eval: assert a_out.ndim == 2 assert a_out.shape[0] == 1 or a_out.shape[0] == 3 - a_out = xp.ascontiguousarray(xp.transpose(a_out, axes=(1, 0))).copy() # Make sure we don't have stride 0 + a_out = np.ascontiguousarray(np.transpose(a_out, axes=(1, 0))).copy() # Make sure we don't have stride 0 # make sure that output array is 4d and of shape (:,:,:, 1) or (:,:,:, 3) for tensor-product/slice evaluation else: assert a_out.ndim == 4 assert a_out.shape[0] == 1 or a_out.shape[0] == 3 - a_out = xp.ascontiguousarray( - xp.transpose(a_out, axes=(1, 2, 3, 0)), + a_out = np.ascontiguousarray( + np.transpose(a_out, axes=(1, 2, 3, 0)), ).copy() # Make sure we don't have stride 0 return a_out # ================================ - def get_params_numpy(self) -> xp.ndarray: + def get_params_numpy(self) -> np.ndarray: """Convert parameter dict into numpy array.""" params_numpy = [] for k, v in self.params.items(): params_numpy.append(v) - return xp.array(params_numpy) + return np.array(params_numpy) + + def show3D_interactive( + self, + fig=None, + logical=False, + grid_info=None, + markers=None, + marker_coords="logical", + show_control_pts=False, + save_dir=None, + ): + import numpy as np + import plotly.graph_objects as go + + torus_mappings = ( + "Tokamak", + "GVECunit", + "DESCunit", + "IGAPolarTorus", + "HollowTorus", + ) + if fig is None: + fig = go.Figure() + + # --- default grid + e1 = np.linspace(0.0, 1.0, 20) + e2 = np.linspace(0.0, 1.0, 20) + e3 = np.linspace(0.0, 1.0, 20) + + if logical: + E1, E2, E3 = np.meshgrid(e1, e2, e3, indexing="ij") + X, Y, Z = E1, E2, E3 + else: + XYZ = self(e1, e2, e3, squeeze_out=True) + X, Y, Z = XYZ[0], XYZ[1], XYZ[2] + + # add wireframes along e1, e2, e3 directions + axis_colors = ["black", "red", "blue"] # colors for each direction + + # e1 isolines + for j in range(len(e2)): + for k in range(len(e3)): + fig.add_trace( + go.Scatter3d( + x=X[:, j, k], + y=Y[:, j, k], + z=Z[:, j, k], + mode="lines", + line=dict(color=axis_colors[0], width=1), + opacity=0.3, + ) + ) + + # e2 isolines + for k in range(len(e3)): + for i in range(len(e1)): + fig.add_trace( + go.Scatter3d( + x=X[i, :, k], + y=Y[i, :, k], + z=Z[i, :, k], + mode="lines", + line=dict(color=axis_colors[1], width=1), + opacity=0.3, + ) + ) + + # e3 isolines + for i in range(len(e1)): + for j in range(len(e2)): + fig.add_trace( + go.Scatter3d( + x=X[i, j, :], + y=Y[i, j, :], + z=Z[i, j, :], + mode="lines", + line=dict(color=axis_colors[2], width=1), + opacity=0.3, + ) + ) + + # Layout + fig.update_layout( + scene=dict(xaxis_title="x", yaxis_title="y", zaxis_title="z", aspectmode="data"), + title=self.__class__.__name__ + " 3D Domain (Interactive)", + showlegend=False, + autosize=True, + margin=dict(l=0, r=0, t=30, b=0), # remove internal margins + width=None, + height=None, + ) + + # if save_dir: + # fig.write_html(save_dir) + # else: + # fig.show() + + return fig + + def show_plotly( + self, + logical=False, + grid_info=None, + markers=None, + marker_coords="logical", + show_control_pts=False, + save_dir=None, + fig=None, + ): + import numpy as np + import plotly.graph_objects as go + from plotly.subplots import make_subplots + + torus_mappings = ( + "Tokamak", + "GVECunit", + "DESCunit", + "IGAPolarTorus", + "HollowTorus", + ) + is_not_cube = self.kind_map < 10 or self.kind_map > 19 + + # --- figure with 2 subplots + if fig is None: + fig = make_subplots( + rows=1, + cols=2, + subplot_titles=[f"{self.__class__.__name__} at η₃=0", "Top view"], + ) + + # --- default grid + if grid_info is None: + e1 = np.linspace(0.0, 1.0, 16) + e2 = np.linspace(0.0, 1.0, 65) + e3 = np.linspace(0.0, 1.0, 65) + else: + e1 = np.linspace(0.0, 1.0, grid_info[0] + 1) + e2 = np.linspace(0.0, 1.0, grid_info[1] + 1) + e3 = np.linspace(0.0, 1.0, grid_info[2] + 1 if len(grid_info) > 2 else 2) + + # --- compute coordinates + if logical: + E1, E2 = np.meshgrid(e1, e2, indexing="ij") + X, Y = E1, E2 + else: + XYZ = self(e1, e2, 0.0, squeeze_out=True) + X = XYZ[0] + Y = XYZ[2] if self.__class__.__name__ in torus_mappings else XYZ[1] + + # --- subplot 1: side view + for i in range(len(e1)): + fig.add_trace( + go.Scatter( + x=X[i, :], + y=Y[i, :], + mode="lines", + line=dict(color="blue", width=1), + opacity=0.5, + ), + row=1, + col=1, + ) + for j in range(len(e2) - int(is_not_cube)): + fig.add_trace( + go.Scatter( + x=X[:, j], + y=Y[:, j], + mode="lines", + line=dict(color="blue", width=1), + opacity=0.5, + ), + row=1, + col=1, + ) + + # --- subplot 2: top view + if not logical: + theta_0 = self(e1, 0.0, e3, squeeze_out=True) + theta_pi = self(e1, 0.5, e3, squeeze_out=True) + X0, Z0 = theta_0[0], theta_0[2] if self.__class__.__name__ in torus_mappings else theta_0[2] + Xpi, Zpi = theta_pi[0], theta_pi[2] if self.__class__.__name__ in torus_mappings else theta_pi[2] + + for i in range(len(e1)): + fig.add_trace( + go.Scatter(x=X0[i, :], y=Z0[i, :], mode="lines", line=dict(color="blue", width=1), opacity=0.5), + row=1, + col=2, + ) + for j in range(len(e2)): + fig.add_trace( + go.Scatter(x=X0[:, j], y=Z0[:, j], mode="lines", line=dict(color="blue", width=1), opacity=0.5), + row=1, + col=2, + ) + + if is_not_cube: + for i in range(len(e1)): + fig.add_trace( + go.Scatter( + x=Xpi[i, :], y=Zpi[i, :], mode="lines", line=dict(color="blue", width=1), opacity=0.5 + ), + row=1, + col=2, + ) + for j in range(len(e2)): + fig.add_trace( + go.Scatter( + x=Xpi[:, j], y=Zpi[:, j], mode="lines", line=dict(color="blue", width=1), opacity=0.5 + ), + row=1, + col=2, + ) + + # --- layout + fig.update_layout( + title_text=f"{self.__class__.__name__} Domain", + showlegend=False, + autosize=True, + margin=dict(l=10, r=10, t=40, b=10), + ) + + return fig + + def show_combined_plotly( + self, + logical=False, + grid_info=None, + markers=None, + marker_coords="logical", + show_control_pts=False, + save_dir=None, + fig=None, + ): + """ + Combined interactive Plotly figure: + 1) 3D domain wireframe + 2) Side view (eta1/eta2) + 3) Top view (eta1/eta3) + """ + import numpy as np + import plotly.graph_objects as go + from plotly.subplots import make_subplots + + torus_mappings = ( + "Tokamak", + "GVECunit", + "DESCunit", + "IGAPolarTorus", + "HollowTorus", + ) + is_not_cube = self.kind_map < 10 or self.kind_map > 19 + + # --- default figure with 3 subplots + if fig is None: + fig = make_subplots( + rows=1, + cols=3, + specs=[[{"type": "scene"}, {}, {}]], + subplot_titles=["3D Domain", f"{self.__class__.__name__} at η₃=0", "Top view"], + ) + + # --- grid setup + if grid_info is None: + e1 = np.linspace(0.0, 1.0, 20) + e2 = np.linspace(0.0, 1.0, 20) + e3 = np.linspace(0.0, 1.0, 20) + else: + e1 = np.linspace(0.0, 1.0, grid_info[0] + 1) + e2 = np.linspace(0.0, 1.0, grid_info[1] + 1) + e3 = np.linspace(0.0, 1.0, grid_info[2] + 1 if len(grid_info) > 2 else 2) + + # --- compute coordinates + if logical: + E1, E2, E3 = np.meshgrid(e1, e2, e3, indexing="ij") + X3d, Y3d, Z3d = E1, E2, E3 + X_side, Y_side = E1[:, :, 0], E2[:, :, 0] + X_top, Z_top = E1[:, 0, :], E3[:, 0, :] + else: + XYZ = self(e1, e2, e3, squeeze_out=True) + X3d, Y3d, Z3d = XYZ[0], XYZ[1], XYZ[2] + + # side view at eta3=0 + XYZ_side = self(e1, e2, 0.0, squeeze_out=True) + X_side = XYZ_side[0] + Y_side = XYZ_side[2] if self.__class__.__name__ in torus_mappings else XYZ_side[1] + + # top view at eta2=0 + XYZ_top = self(e1, 0.0, e3, squeeze_out=True) + X_top = XYZ_top[0] + Z_top = XYZ_top[2] if self.__class__.__name__ in torus_mappings else XYZ_top[2] + + # --- 3D wireframe (subplot 1) + axis_colors = ["black", "red", "blue"] + # e1 lines + for j in range(Y3d.shape[1]): + for k in range(Z3d.shape[2]): + fig.add_trace( + go.Scatter3d( + x=X3d[:, j, k], + y=Y3d[:, j, k], + z=Z3d[:, j, k], + mode="lines", + line=dict(color=axis_colors[0], width=1), + opacity=0.3, + ), + row=1, + col=1, + ) + # e2 lines + for k in range(Z3d.shape[2]): + for i in range(X3d.shape[0]): + fig.add_trace( + go.Scatter3d( + x=X3d[i, :, k], + y=Y3d[i, :, k], + z=Z3d[i, :, k], + mode="lines", + line=dict(color=axis_colors[1], width=1), + opacity=0.3, + ), + row=1, + col=1, + ) + # e3 lines + for i in range(X3d.shape[0]): + for j in range(Y3d.shape[1]): + fig.add_trace( + go.Scatter3d( + x=X3d[i, j, :], + y=Y3d[i, j, :], + z=Z3d[i, j, :], + mode="lines", + line=dict(color=axis_colors[2], width=1), + opacity=0.3, + ), + row=1, + col=1, + ) + + # --- Side view (subplot 2) + for i in range(X_side.shape[0]): + fig.add_trace( + go.Scatter(x=X_side[i, :], y=Y_side[i, :], mode="lines", line=dict(color="blue", width=1), opacity=0.5), + row=1, + col=2, + ) + for j in range(X_side.shape[1]): + fig.add_trace( + go.Scatter(x=X_side[:, j], y=Y_side[:, j], mode="lines", line=dict(color="blue", width=1), opacity=0.5), + row=1, + col=2, + ) + + # --- Top view (subplot 3) + for i in range(X_top.shape[0]): + fig.add_trace( + go.Scatter(x=X_top[i, :], y=Z_top[i, :], mode="lines", line=dict(color="blue", width=1), opacity=0.5), + row=1, + col=3, + ) + for j in range(X_top.shape[1]): + fig.add_trace( + go.Scatter(x=X_top[:, j], y=Z_top[:, j], mode="lines", line=dict(color="blue", width=1), opacity=0.5), + row=1, + col=3, + ) + + fig.update_layout( + title_text=f"{self.__class__.__name__} Combined Domain", + showlegend=False, + autosize=True, + margin=dict(l=10, r=10, t=40, b=10), + height=500, + ) + + return fig def show( self, @@ -1374,6 +1752,7 @@ def show( show_control_pts=False, figsize=(12, 5), save_dir=None, + fig=None, ): """Plots isolines (and control point in case on spline mappings) of the 2D physical domain for eta3 = 0. Markers can be plotted as well (optional). @@ -1414,12 +1793,12 @@ def show( # plot domain without MPI decomposition and high resolution if grid_info is None: - e1 = xp.linspace(0.0, 1.0, 16) - e2 = xp.linspace(0.0, 1.0, 65) + e1 = np.linspace(0.0, 1.0, 16) + e2 = np.linspace(0.0, 1.0, 65) if logical: - E1, E2 = xp.meshgrid(e1, e2, indexing="ij") - X = xp.stack((E1, E2), axis=0) + E1, E2 = np.meshgrid(e1, e2, indexing="ij") + X = np.stack((E1, E2), axis=0) else: XYZ = self(e1, e2, 0.0, squeeze_out=True) @@ -1429,7 +1808,8 @@ def show( else: Y = XYZ[1] - fig = plt.figure(figsize=figsize) + if fig is None: + fig = plt.figure(figsize=figsize) ax = fig.add_subplot(1, 2, 1) # eta1-isolines @@ -1459,11 +1839,11 @@ def show( ) # top view - e3 = xp.linspace(0.0, 1.0, 65) + e3 = np.linspace(0.0, 1.0, 65) if logical: - E1, E2 = xp.meshgrid(e1, e2, indexing="ij") - X = xp.stack((E1, E2), axis=0) + E1, E2 = np.meshgrid(e1, e2, indexing="ij") + X = np.stack((E1, E2), axis=0) else: theta_0 = self(e1, 0.0, e3, squeeze_out=True) theta_pi = self(e1, 0.5, e3, squeeze_out=True) @@ -1524,7 +1904,7 @@ def show( # coordinates # e3 = [0., .25, .5, .75] # x, y, z = self(e1, e2, e3) - # R = xp.sqrt(x**2 + y**2) + # R = np.sqrt(x**2 + y**2) # fig = plt.figure(figsize=(13, 13)) # for n in range(4): @@ -1551,14 +1931,14 @@ def show( elif isinstance(grid_info, list): assert len(grid_info) > 1 - e1 = xp.linspace(0.0, 1.0, grid_info[0] + 1) - e2 = xp.linspace(0.0, 1.0, grid_info[1] + 1) + e1 = np.linspace(0.0, 1.0, grid_info[0] + 1) + e2 = np.linspace(0.0, 1.0, grid_info[1] + 1) fig = plt.figure(figsize=figsize) ax = fig.add_subplot(1, 1, 1) if logical: - E1, E2 = xp.meshgrid(e1, e2, indexing="ij") + E1, E2 = np.meshgrid(e1, e2, indexing="ij") # eta1-isolines for i in range(e1.size): @@ -1586,7 +1966,7 @@ def show( ax.plot(X[co1, :, j], X[co2, :, j], "tab:blue", alpha=0.5) # plot domain with MPI decomposition - elif isinstance(grid_info, xp.ndarray): + elif isinstance(grid_info, np.ndarray): assert grid_info.ndim == 2 assert grid_info.shape[1] > 5 @@ -1594,7 +1974,7 @@ def show( ax = fig.add_subplot(1, 1, 1) for i in range(grid_info.shape[0]): - e1 = xp.linspace( + e1 = np.linspace( grid_info[i, 0], grid_info[i, 1], int( @@ -1602,7 +1982,7 @@ def show( ) + 1, ) - e2 = xp.linspace( + e2 = np.linspace( grid_info[i, 3], grid_info[i, 4], int( @@ -1612,7 +1992,7 @@ def show( ) if logical: - E1, E2 = xp.meshgrid(e1, e2, indexing="ij") + E1, E2 = np.meshgrid(e1, e2, indexing="ij") # eta1-isolines first_line = ax.plot( @@ -1737,7 +2117,7 @@ def show( ax.axis("equal") - if isinstance(grid_info, xp.ndarray): + if isinstance(grid_info, np.ndarray): plt.legend() if self.__class__.__name__ in torus_mappings: @@ -1747,10 +2127,12 @@ def show( ax.set_xlabel("x") ax.set_ylabel(ylab) - if save_dir is not None: - plt.savefig(save_dir, bbox_inches="tight") - else: - plt.show() + # if save_dir is not None: + # plt.savefig(save_dir, bbox_inches="tight") + # else: + # plt.show() + + return fig, ax class Spline(Domain): @@ -1772,9 +2154,9 @@ def __init__( Nel: tuple[int] = (8, 24, 6), p: tuple[int] = (2, 3, 1), spl_kind: tuple[bool] = (False, True, True), - cx: xp.ndarray = None, - cy: xp.ndarray = None, - cz: xp.ndarray = None, + cx: np.ndarray = None, + cy: np.ndarray = None, + cz: np.ndarray = None, ): self.kind_map = 0 @@ -1805,7 +2187,7 @@ def __init__( assert self.cz.shape == expected_shape # identify polar singularity at eta1=0 - if xp.all(self.cx[0, :, 0] == self.cx[0, 0, 0]): + if np.all(self.cx[0, :, 0] == self.cx[0, 0, 0]): self.pole = True else: self.pole = False @@ -1836,17 +2218,17 @@ def __init__( Nel: tuple[int] = (8, 24), p: tuple[int] = (2, 3), spl_kind: tuple[bool] = (False, True), - cx: xp.ndarray = None, - cy: xp.ndarray = None, + cx: np.ndarray = None, + cy: np.ndarray = None, ): # get default control points if cx is None or cy is None: def X(eta1, eta2): - return eta1 * xp.cos(2 * xp.pi * eta2) + 3.0 + return eta1 * np.cos(2 * np.pi * eta2) + 3.0 def Y(eta1, eta2): - return eta1 * xp.sin(2 * xp.pi * eta2) + return eta1 * np.sin(2 * np.pi * eta2) cx, cy = interp_mapping(Nel, p, spl_kind, X, Y) @@ -1869,7 +2251,7 @@ def Y(eta1, eta2): assert self.cy.shape == expected_shape # identify polar singularity at eta1=0 - if xp.all(self.cx[0, :] == self.cx[0, 0]): + if np.all(self.cx[0, :] == self.cx[0, 0]): self.pole = True else: self.pole = False @@ -1877,7 +2259,7 @@ def Y(eta1, eta2): # reshape control points to 3D self._cx = self.cx[:, :, None] self._cy = self.cy[:, :, None] - self._cz = xp.zeros((1, 1, 1), dtype=float) + self._cz = np.zeros((1, 1, 1), dtype=float) # init base class super().__init__(Nel=Nel, p=p, spl_kind=spl_kind) @@ -1902,8 +2284,8 @@ def __init__( Nel: tuple[int] = (8, 24), p: tuple[int] = (2, 3), spl_kind: tuple[bool] = (False, True), - cx: xp.ndarray = None, - cy: xp.ndarray = None, + cx: np.ndarray = None, + cy: np.ndarray = None, Lz: float = 4.0, ): self.kind_map = 1 @@ -1912,10 +2294,10 @@ def __init__( if cx is None or cy is None: def X(eta1, eta2): - return eta1 * xp.cos(2 * xp.pi * eta2) + return eta1 * np.cos(2 * np.pi * eta2) def Y(eta1, eta2): - return eta1 * xp.sin(2 * xp.pi * eta2) + return eta1 * np.sin(2 * np.pi * eta2) cx, cy = interp_mapping(Nel, p, spl_kind, X, Y) @@ -1923,7 +2305,7 @@ def Y(eta1, eta2): cx[0] = 0.0 cy[0] = 0.0 - self.params_numpy = xp.array([Lz]) + self.params_numpy = np.array([Lz]) self.periodic_eta3 = False # init base class @@ -1954,7 +2336,7 @@ class PoloidalSplineTorus(PoloidalSpline): spl_kind : tuple[bool] Kind of spline in each poloidal direction (True=periodic, False=clamped). - cx, cy : xp.ndarray + cx, cy : np.ndarray Control points (spline coefficients) of the poloidal mapping. If None, a default square-to-disc mapping of radius 1 centered around (x, y) = (3, 0) is interpolated. @@ -1967,23 +2349,23 @@ def __init__( Nel: tuple[int] = (8, 24), p: tuple[int] = (2, 3), spl_kind: tuple[bool] = (False, True), - cx: xp.ndarray = None, - cy: xp.ndarray = None, + cx: np.ndarray = None, + cy: np.ndarray = None, tor_period: int = 3, ): # use setters for mapping attributes self.kind_map = 2 - self.params_numpy = xp.array([float(tor_period)]) + self.params_numpy = np.array([float(tor_period)]) self.periodic_eta3 = True # get default control points if cx is None or cy is None: def X(eta1, eta2): - return eta1 * xp.cos(2 * xp.pi * eta2) + 3.0 + return eta1 * np.cos(2 * np.pi * eta2) + 3.0 def Y(eta1, eta2): - return eta1 * xp.sin(2 * xp.pi * eta2) + return eta1 * np.sin(2 * np.pi * eta2) cx, cy = interp_mapping(Nel, p, spl_kind, X, Y) @@ -2025,7 +2407,7 @@ def interp_mapping(Nel, p, spl_kind, X, Y, Z=None): NbaseN = [Nel + p - kind * p for Nel, p, kind in zip(Nel, p, spl_kind)] # element boundaries - el_b = [xp.linspace(0.0, 1.0, Nel + 1) for Nel in Nel] + el_b = [np.linspace(0.0, 1.0, Nel + 1) for Nel in Nel] # spline knot vectors T = [bsp.make_knots(el_b, p, kind) for el_b, p, kind in zip(el_b, p, spl_kind)] @@ -2040,7 +2422,7 @@ def interp_mapping(Nel, p, spl_kind, X, Y, Z=None): if len(Nel) == 2: I = kron(I_mat[0], I_mat[1], format="csc") - I_pts = xp.meshgrid(I_pts[0], I_pts[1], indexing="ij") + I_pts = np.meshgrid(I_pts[0], I_pts[1], indexing="ij") cx = spsolve(I, X(I_pts[0], I_pts[1]).flatten()).reshape( NbaseN[0], @@ -2073,7 +2455,7 @@ def interp_mapping(Nel, p, spl_kind, X, Y, Z=None): return 0.0 -def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: xp.ndarray): +def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: np.ndarray): """n-dimensional tensor-product spline interpolation with discrete input. The interpolation points are passed as a list of 1d arrays, each array with increasing entries g[0]=0 < g[1] < ... @@ -2095,7 +2477,7 @@ def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: xp. Returns -------- - coeffs : xp.array + coeffs : np.array spline coefficients as nd array. T : list[array] @@ -2110,11 +2492,11 @@ def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: xp. I_mat = [] I_LU = [] for sh, x_grid, p_i, kind_i in zip(values.shape, grids_1d, p, spl_kind): - assert isinstance(x_grid, xp.ndarray) + assert isinstance(x_grid, np.ndarray) assert sh == x_grid.size assert ( - xp.all( - xp.roll(x_grid, 1)[1:] < x_grid[1:], + np.all( + np.roll(x_grid, 1)[1:] < x_grid[1:], ) and x_grid[-1] > x_grid[-2] ) @@ -2122,17 +2504,17 @@ def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: xp. if kind_i: assert x_grid[-1] < 1.0, "Interpolation points must be <1 for periodic interpolation." - breaks = xp.ones(x_grid.size + 1) + breaks = np.ones(x_grid.size + 1) if p_i % 2 == 0: - breaks[1:-1] = (x_grid[1:] + xp.roll(x_grid, 1)[1:]) / 2.0 + breaks[1:-1] = (x_grid[1:] + np.roll(x_grid, 1)[1:]) / 2.0 breaks[0] = 0.0 else: breaks[:-1] = x_grid else: assert ( - xp.abs( + np.abs( x_grid[-1] - 1.0, ) < 1e-14 @@ -2149,12 +2531,12 @@ def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: xp. breaks[0] = 0.0 breaks[-1] = 1.0 - # breaks = xp.linspace(0., 1., x_grid.size - (not kind_i)*p_i + 1) + # breaks = np.linspace(0., 1., x_grid.size - (not kind_i)*p_i + 1) T += [bsp.make_knots(breaks, p_i, periodic=kind_i)] indN += [ - (xp.indices((breaks.size - 1, p_i + 1))[1] + xp.arange(breaks.size - 1)[:, None]) % x_grid.size, + (np.indices((breaks.size - 1, p_i + 1))[1] + np.arange(breaks.size - 1)[:, None]) % x_grid.size, ] I_mat += [bsp.collocation_matrix(T[-1], p_i, x_grid, periodic=kind_i)] diff --git a/src/struphy/geometry/domains.py b/src/struphy/geometry/domains.py index 20f995779..e33b6a2e8 100644 --- a/src/struphy/geometry/domains.py +++ b/src/struphy/geometry/domains.py @@ -2,7 +2,7 @@ import copy -import cunumpy as xp +import numpy as np from struphy.fields_background.base import AxisymmMHDequilibrium from struphy.fields_background.equils import EQDSKequilibrium @@ -157,8 +157,8 @@ def __init__(self, gvec_equil=None): def XYZ(e1, e2, e3): rho = _rmin + e1 * (1.0 - _rmin) - theta = 2 * xp.pi * e2 - zeta = 2 * xp.pi * e3 / gvec_equil._nfp + theta = 2 * np.pi * e2 + zeta = 2 * np.pi * e3 / gvec_equil._nfp if gvec_equil.params["use_boozer"]: ev = gvec.EvaluationsBoozer(rho=rho, theta_B=theta, zeta_B=zeta, state=gvec_equil.state) else: @@ -210,6 +210,9 @@ def __init__(self, desc_equil=None): else: assert isinstance(desc_equil, DESCequilibrium) + # use params setter + self.params = copy.deepcopy(locals()) + Nel = desc_equil.params["Nel"] p = desc_equil.params["p"] @@ -287,10 +290,10 @@ def __init__( # get control points def X(eta1, eta2): - return a * eta1 * xp.cos(2 * xp.pi * eta2) + return a * eta1 * np.cos(2 * np.pi * eta2) def Y(eta1, eta2): - return a * eta1 * xp.sin(2 * xp.pi * eta2) + return a * eta1 * np.sin(2 * np.pi * eta2) spl_kind = (False, True) @@ -364,17 +367,17 @@ def __init__( if sfl: def theta(eta1, eta2): - return 2 * xp.arctan(xp.sqrt((1 + a * eta1 / R0) / (1 - a * eta1 / R0)) * xp.tan(xp.pi * eta2)) + return 2 * np.arctan(np.sqrt((1 + a * eta1 / R0) / (1 - a * eta1 / R0)) * np.tan(np.pi * eta2)) else: def theta(eta1, eta2): - return 2 * xp.pi * eta2 + return 2 * np.pi * eta2 def R(eta1, eta2): - return a * eta1 * xp.cos(theta(eta1, eta2)) + R0 + return a * eta1 * np.cos(theta(eta1, eta2)) + R0 def Z(eta1, eta2): - return a * eta1 * xp.sin(theta(eta1, eta2)) + return a * eta1 * np.sin(theta(eta1, eta2)) spl_kind = (False, True) @@ -743,11 +746,11 @@ def __init__( self.params = copy.deepcopy(locals()) self.params_numpy = self.get_params_numpy() - assert a2 <= R0, f"The minor radius must be smaller or equal than the major radius! {a2 =}, {R0 =}" + assert a2 <= R0, f"The minor radius must be smaller or equal than the major radius! {a2 = }, {R0 = }" if sfl: assert pol_period == 1, ( - "Piece-of-cake is only implemented for torus coordinates, not for straight field line coordinates!" + f"Piece-of-cake is only implemented for torus coordinates, not for straight field line coordinates!" ) # periodicity in eta3-direction and pole at eta1=0 @@ -763,24 +766,24 @@ def __init__( def inverse_map(self, x, y, z, bounded=True, change_out_order=False): """Analytical inverse map of HollowTorus""" - mr = xp.sqrt(x**2 + y**2) - self.params["R0"] + mr = np.sqrt(x**2 + y**2) - self.params["R0"] - eta3 = xp.arctan2(-y, x) % (2 * xp.pi / self.params["tor_period"]) / (2 * xp.pi) * self.params["tor_period"] - eta2 = xp.arctan2(z, mr) % (2 * xp.pi / self.params["pol_period"]) / (2 * xp.pi / self.params["pol_period"]) - eta1 = (z / xp.sin(2 * xp.pi * eta2 / self.params["pol_period"]) - self.params["a1"]) / ( + eta3 = np.arctan2(-y, x) % (2 * np.pi / self.params["tor_period"]) / (2 * np.pi) * self.params["tor_period"] + eta2 = np.arctan2(z, mr) % (2 * np.pi / self.params["pol_period"]) / (2 * np.pi / self.params["pol_period"]) + eta1 = (z / np.sin(2 * np.pi * eta2 / self.params["pol_period"]) - self.params["a1"]) / ( self.params["a2"] - self.params["a1"] ) if bounded: eta1[eta1 > 1] = 1.0 eta1[eta1 < 0] = 0.0 - assert xp.all(xp.logical_and(eta1 >= 0, eta1 <= 1)) + assert np.all(np.logical_and(eta1 >= 0, eta1 <= 1)) - assert xp.all(xp.logical_and(eta2 >= 0, eta2 <= 1)) - assert xp.all(xp.logical_and(eta3 >= 0, eta3 <= 1)) + assert np.all(np.logical_and(eta2 >= 0, eta2 <= 1)) + assert np.all(np.logical_and(eta3 >= 0, eta3 <= 1)) if change_out_order: - return xp.transpose((eta1, eta2, eta3)) + return np.transpose((eta1, eta2, eta3)) else: return eta1, eta2, eta3 diff --git a/src/struphy/geometry/evaluation_kernels.py b/src/struphy/geometry/evaluation_kernels.py index 4f97b9ce9..a357bbea1 100644 --- a/src/struphy/geometry/evaluation_kernels.py +++ b/src/struphy/geometry/evaluation_kernels.py @@ -31,7 +31,7 @@ def f( args: DomainArguments Arguments for the mapping. - f_out : xp.array + f_out : np.array Output array of shape (3,). """ @@ -196,7 +196,7 @@ def df( args: DomainArguments Arguments for the mapping. - df_out : xp.array + df_out : np.array Output array of shape (3, 3). """ @@ -354,7 +354,7 @@ def det_df( args: DomainArguments Arguments for the mapping. - tmp1 : xp.array + tmp1 : np.array Temporary array of shape (3, 3). """ @@ -388,13 +388,13 @@ def df_inv( args: DomainArguments Arguments for the mapping. - tmp1: xp.array + tmp1: np.array Temporary array of shape (3, 3). avoid_round_off: bool Whether to manually set exact zeros in arrays. - dfinv_out: xp.array + dfinv_out: np.array Output array of shape (3, 3). """ @@ -484,13 +484,13 @@ def g( args: DomainArguments Arguments for the mapping. - tmp1, tmp2: xp.array + tmp1, tmp2: np.array Temporary arrays of shape (3, 3). avoid_round_off: bool Whether to manually set exact zeros in arrays. - g_out: xp.array + g_out: np.array Output array of shape (3, 3). """ df( @@ -601,13 +601,13 @@ def g_inv( args: DomainArguments Arguments for the mapping. - tmp1, tmp2, tmp3: xp.array + tmp1, tmp2, tmp3: np.array Temporary arrays of shape (3, 3). avoid_round_off: bool Whether to manually set exact zeros in arrays. - ginv_out: xp.array + ginv_out: np.array Output array of shape (3, 3). """ g( @@ -732,16 +732,16 @@ def select_metric_coeff( args: DomainArguments Arguments for the mapping. - tmp0: xp.array + tmp0: np.array Temporary array of shape (3,). - tmp1, tmp2, tmp3: xp.array + tmp1, tmp2, tmp3: np.array Temporary arrays of shape (3, 3). avoid_round_off: bool Whether to manually set exact zeros in arrays. - out: xp.array + out: np.array Output array of shape (3, 3). """ # identity map diff --git a/src/struphy/geometry/mappings_kernels.py b/src/struphy/geometry/mappings_kernels.py index 83e6275fd..8b643855d 100644 --- a/src/struphy/geometry/mappings_kernels.py +++ b/src/struphy/geometry/mappings_kernels.py @@ -49,40 +49,13 @@ def spline_3d( tmp3 = ind3[span3 - int(p[2]), :] f_out[0] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - b2, - b3, - tmp1, - tmp2, - tmp3, - args.cx, + int(p[0]), int(p[1]), int(p[2]), b1, b2, b3, tmp1, tmp2, tmp3, args.cx ) f_out[1] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - b2, - b3, - tmp1, - tmp2, - tmp3, - args.cy, + int(p[0]), int(p[1]), int(p[2]), b1, b2, b3, tmp1, tmp2, tmp3, args.cy ) f_out[2] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - b2, - b3, - tmp1, - tmp2, - tmp3, - args.cz, + int(p[0]), int(p[1]), int(p[2]), b1, b2, b3, tmp1, tmp2, tmp3, args.cz ) @@ -124,112 +97,31 @@ def spline_3d_df( tmp3 = ind3[span3 - int(p[2]), :] df_out[0, 0] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - der1, - b2, - b3, - tmp1, - tmp2, - tmp3, - args.cx, + int(p[0]), int(p[1]), int(p[2]), der1, b2, b3, tmp1, tmp2, tmp3, args.cx ) df_out[0, 1] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - der2, - b3, - tmp1, - tmp2, - tmp3, - args.cx, + int(p[0]), int(p[1]), int(p[2]), b1, der2, b3, tmp1, tmp2, tmp3, args.cx ) df_out[0, 2] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - b2, - der3, - tmp1, - tmp2, - tmp3, - args.cx, + int(p[0]), int(p[1]), int(p[2]), b1, b2, der3, tmp1, tmp2, tmp3, args.cx ) df_out[1, 0] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - der1, - b2, - b3, - tmp1, - tmp2, - tmp3, - args.cy, + int(p[0]), int(p[1]), int(p[2]), der1, b2, b3, tmp1, tmp2, tmp3, args.cy ) df_out[1, 1] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - der2, - b3, - tmp1, - tmp2, - tmp3, - args.cy, + int(p[0]), int(p[1]), int(p[2]), b1, der2, b3, tmp1, tmp2, tmp3, args.cy ) df_out[1, 2] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - b2, - der3, - tmp1, - tmp2, - tmp3, - args.cy, + int(p[0]), int(p[1]), int(p[2]), b1, b2, der3, tmp1, tmp2, tmp3, args.cy ) df_out[2, 0] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - der1, - b2, - b3, - tmp1, - tmp2, - tmp3, - args.cz, + int(p[0]), int(p[1]), int(p[2]), der1, b2, b3, tmp1, tmp2, tmp3, args.cz ) df_out[2, 1] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - der2, - b3, - tmp1, - tmp2, - tmp3, - args.cz, + int(p[0]), int(p[1]), int(p[2]), b1, der2, b3, tmp1, tmp2, tmp3, args.cz ) df_out[2, 2] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), - int(p[1]), - int(p[2]), - b1, - b2, - der3, - tmp1, - tmp2, - tmp3, - args.cz, + int(p[0]), int(p[1]), int(p[2]), b1, b2, der3, tmp1, tmp2, tmp3, args.cz ) @@ -384,7 +276,7 @@ def spline_2d_torus( tmp2 = ind2[span2 - int(p[1]), :] f_out[0] = evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), b1, b2, tmp1, tmp2, cx) * cos( - 2 * pi * eta3 / tor_period, + 2 * pi * eta3 / tor_period ) f_out[1] = ( evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), b1, b2, tmp1, tmp2, cx) @@ -437,10 +329,10 @@ def spline_2d_torus_df( tmp2 = ind2[span2 - int(p[1]), :] df_out[0, 0] = evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), der1, b2, tmp1, tmp2, cx) * cos( - 2 * pi * eta3 / tor_period, + 2 * pi * eta3 / tor_period ) df_out[0, 1] = evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), b1, der2, tmp1, tmp2, cx) * cos( - 2 * pi * eta3 / tor_period, + 2 * pi * eta3 / tor_period ) df_out[0, 2] = ( evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), b1, b2, tmp1, tmp2, cx) @@ -729,14 +621,7 @@ def hollow_cyl_df(eta1: float, eta2: float, a1: float, a2: float, lz: float, poc @pure def powered_ellipse( - eta1: float, - eta2: float, - eta3: float, - rx: float, - ry: float, - lz: float, - s: float, - f_out: "float[:]", + eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, s: float, f_out: "float[:]" ): r""" Point-wise evaluation of @@ -779,14 +664,7 @@ def powered_ellipse( @pure def powered_ellipse_df( - eta1: float, - eta2: float, - eta3: float, - rx: float, - ry: float, - lz: float, - s: float, - df_out: "float[:,:]", + eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, s: float, df_out: "float[:,:]" ): """Jacobian matrix for :meth:`struphy.geometry.mappings_kernels.powered_ellipse`.""" @@ -965,14 +843,7 @@ def hollow_torus_df( @pure def shafranov_shift( - eta1: float, - eta2: float, - eta3: float, - rx: float, - ry: float, - lz: float, - de: float, - f_out: "float[:]", + eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, de: float, f_out: "float[:]" ): r""" Point-wise evaluation of @@ -1016,14 +887,7 @@ def shafranov_shift( @pure def shafranov_shift_df( - eta1: float, - eta2: float, - eta3: float, - rx: float, - ry: float, - lz: float, - de: float, - df_out: "float[:,:]", + eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, de: float, df_out: "float[:,:]" ): """Jacobian matrix for :meth:`struphy.geometry.mappings_kernels.shafranov_shift`.""" @@ -1040,14 +904,7 @@ def shafranov_shift_df( @pure def shafranov_sqrt( - eta1: float, - eta2: float, - eta3: float, - rx: float, - ry: float, - lz: float, - de: float, - f_out: "float[:]", + eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, de: float, f_out: "float[:]" ): r""" Point-wise evaluation of @@ -1089,14 +946,7 @@ def shafranov_sqrt( @pure def shafranov_sqrt_df( - eta1: float, - eta2: float, - eta3: float, - rx: float, - ry: float, - lz: float, - de: float, - df_out: "float[:,:]", + eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, de: float, df_out: "float[:,:]" ): """Jacobian matrix for :meth:`struphy.geometry.mappings_kernels.shafranov_sqrt`.""" diff --git a/src/struphy/geometry/tests/test_domain.py b/src/struphy/geometry/tests/test_domain.py index c9a489331..b63c5b4c6 100644 --- a/src/struphy/geometry/tests/test_domain.py +++ b/src/struphy/geometry/tests/test_domain.py @@ -4,7 +4,7 @@ def test_prepare_arg(): """Tests prepare_arg static method in domain base class.""" - import cunumpy as xp + import numpy as np from struphy.geometry.base import Domain @@ -22,12 +22,12 @@ def a_vec(e1, e2, e3): a_2 = e2 * e3 a_3 = e3 * e1 - return xp.stack((a_1, a_2, a_3), axis=0) + return np.stack((a_1, a_2, a_3), axis=0) # ========== tensor-product/slice evaluation =============== - e1 = xp.random.rand(4) - e2 = xp.random.rand(5) - e3 = xp.random.rand(6) + e1 = np.random.rand(4) + e2 = np.random.rand(5) + e3 = np.random.rand(6) E1, E2, E3, is_sparse_meshgrid = Domain.prepare_eval_pts(e1, e2, e3, flat_eval=False) @@ -85,7 +85,7 @@ def a_vec(e1, e2, e3): assert Domain.prepare_arg([A1, A2, A3], E1, E2, E3).shape == shape_vector # ============== markers evaluation ========================== - markers = xp.random.rand(10, 6) + markers = np.random.rand(10, 6) shape_scalar = (markers.shape[0], 1) shape_vector = (markers.shape[0], 3) @@ -150,7 +150,6 @@ def a_vec(e1, e2, e3): "ShafranovSqrtCylinder", "ShafranovDshapedCylinder", "GVECunit", - "DESCunit", "IGAPolarCylinder", "IGAPolarTorus", "Tokamak", @@ -159,16 +158,16 @@ def a_vec(e1, e2, e3): def test_evaluation_mappings(mapping): """Tests domain object creation with default parameters and evaluation of metric coefficients.""" - import cunumpy as xp + import numpy as np from struphy.geometry import domains from struphy.geometry.base import Domain # arrays: - arr1 = xp.linspace(0.0, 1.0, 4) - arr2 = xp.linspace(0.0, 1.0, 5) - arr3 = xp.linspace(0.0, 1.0, 6) - arrm = xp.random.rand(10, 8) + arr1 = np.linspace(0.0, 1.0, 4) + arr2 = np.linspace(0.0, 1.0, 5) + arr3 = np.linspace(0.0, 1.0, 6) + arrm = np.random.rand(10, 8) print() print('Testing "evaluate"...') print("array shapes:", arr1.shape, arr2.shape, arr3.shape, arrm.shape) @@ -264,9 +263,9 @@ def test_evaluation_mappings(mapping): assert domain.metric_inv(arr1, arr2, arr3).shape == (3, 3) + arr1.shape + arr2.shape + arr3.shape # matrix evaluations at one point in third direction - mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing="ij") - mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing="ij") - mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing="ij") + mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing="ij") + mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing="ij") + mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing="ij") # eta1-eta2 matrix evaluation: print("eta1-eta2 matrix evaluation, shape:", domain(mat12_x, mat12_y, 0.5, squeeze_out=True).shape) @@ -296,7 +295,7 @@ def test_evaluation_mappings(mapping): assert domain.metric_inv(0.5, mat23_y, mat23_z, squeeze_out=True).shape == (3, 3) + mat23_y.shape # matrix evaluations for sparse meshgrid - mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) + mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) print("sparse meshgrid matrix evaluation, shape:", domain(mat_x, mat_y, mat_z).shape) assert domain(mat_x, mat_y, mat_z).shape == (3,) + (mat_x.shape[0], mat_y.shape[1], mat_z.shape[2]) assert domain.jacobian(mat_x, mat_y, mat_z).shape == (3, 3) + (mat_x.shape[0], mat_y.shape[1], mat_z.shape[2]) @@ -306,7 +305,7 @@ def test_evaluation_mappings(mapping): assert domain.metric_inv(mat_x, mat_y, mat_z).shape == (3, 3) + (mat_x.shape[0], mat_y.shape[1], mat_z.shape[2]) # matrix evaluations - mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij") + mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij") print("matrix evaluation, shape:", domain(mat_x, mat_y, mat_z).shape) assert domain(mat_x, mat_y, mat_z).shape == (3,) + mat_x.shape assert domain.jacobian(mat_x, mat_y, mat_z).shape == (3, 3) + mat_x.shape @@ -319,24 +318,24 @@ def test_evaluation_mappings(mapping): def test_pullback(): """Tests pullbacks to p-forms.""" - import cunumpy as xp + import numpy as np from struphy.geometry import domains from struphy.geometry.base import Domain # arrays: - arr1 = xp.linspace(0.0, 1.0, 4) - arr2 = xp.linspace(0.0, 1.0, 5) - arr3 = xp.linspace(0.0, 1.0, 6) + arr1 = np.linspace(0.0, 1.0, 4) + arr2 = np.linspace(0.0, 1.0, 5) + arr3 = np.linspace(0.0, 1.0, 6) print() print('Testing "pull"...') print("array shapes:", arr1.shape, arr2.shape, arr3.shape) - markers = xp.random.rand(13, 6) + markers = np.random.rand(13, 6) # physical function to pull back (used as components of forms too): def fun(x, y, z): - return xp.exp(x) * xp.sin(y) * xp.cos(z) + return np.exp(x) * np.sin(y) * np.cos(z) domain_class = getattr(domains, "Colella") domain = domain_class() @@ -424,9 +423,9 @@ def fun(x, y, z): ) # matrix pullbacks at one point in third direction - mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing="ij") - mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing="ij") - mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing="ij") + mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing="ij") + mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing="ij") + mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing="ij") # eta1-eta2 matrix pullback: if p_str == "0" or p_str == "3": @@ -453,7 +452,7 @@ def fun(x, y, z): ) # matrix pullbacks for sparse meshgrid - mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) + mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) if p_str == "0" or p_str == "3": assert domain.pull(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == ( mat_x.shape[0], @@ -469,7 +468,7 @@ def fun(x, y, z): ) # matrix pullbacks - mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij") + mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij") if p_str == "0" or p_str == "3": assert domain.pull(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == mat_x.shape else: @@ -479,24 +478,24 @@ def fun(x, y, z): def test_pushforward(): """Tests pushforward of p-forms.""" - import cunumpy as xp + import numpy as np from struphy.geometry import domains from struphy.geometry.base import Domain # arrays: - arr1 = xp.linspace(0.0, 1.0, 4) - arr2 = xp.linspace(0.0, 1.0, 5) - arr3 = xp.linspace(0.0, 1.0, 6) + arr1 = np.linspace(0.0, 1.0, 4) + arr2 = np.linspace(0.0, 1.0, 5) + arr3 = np.linspace(0.0, 1.0, 6) print() print('Testing "push"...') print("array shapes:", arr1.shape, arr2.shape, arr3.shape) - markers = xp.random.rand(13, 6) + markers = np.random.rand(13, 6) # logical function to push (used as components of forms too): def fun(e1, e2, e3): - return xp.exp(e1) * xp.sin(e2) * xp.cos(e3) + return np.exp(e1) * np.sin(e2) * np.cos(e3) domain_class = getattr(domains, "Colella") domain = domain_class() @@ -584,9 +583,9 @@ def fun(e1, e2, e3): ) # matrix pushs at one point in third direction - mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing="ij") - mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing="ij") - mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing="ij") + mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing="ij") + mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing="ij") + mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing="ij") # eta1-eta2 matrix push: if p_str == "0" or p_str == "3": @@ -613,7 +612,7 @@ def fun(e1, e2, e3): ) # matrix pushs for sparse meshgrid - mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) + mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) if p_str == "0" or p_str == "3": assert domain.push(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == ( mat_x.shape[0], @@ -629,7 +628,7 @@ def fun(e1, e2, e3): ) # matrix pushs - mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij") + mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij") if p_str == "0" or p_str == "3": assert domain.push(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == mat_x.shape else: @@ -639,24 +638,24 @@ def fun(e1, e2, e3): def test_transform(): """Tests transformation of p-forms.""" - import cunumpy as xp + import numpy as np from struphy.geometry import domains from struphy.geometry.base import Domain # arrays: - arr1 = xp.linspace(0.0, 1.0, 4) - arr2 = xp.linspace(0.0, 1.0, 5) - arr3 = xp.linspace(0.0, 1.0, 6) + arr1 = np.linspace(0.0, 1.0, 4) + arr2 = np.linspace(0.0, 1.0, 5) + arr3 = np.linspace(0.0, 1.0, 6) print() print('Testing "transform"...') print("array shapes:", arr1.shape, arr2.shape, arr3.shape) - markers = xp.random.rand(13, 6) + markers = np.random.rand(13, 6) # logical function to push (used as components of forms too): def fun(e1, e2, e3): - return xp.exp(e1) * xp.sin(e2) * xp.cos(e3) + return np.exp(e1) * np.sin(e2) * np.cos(e3) domain_class = getattr(domains, "Colella") domain = domain_class() @@ -756,9 +755,9 @@ def fun(e1, e2, e3): ) # matrix transforms at one point in third direction - mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing="ij") - mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing="ij") - mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing="ij") + mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing="ij") + mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing="ij") + mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing="ij") # eta1-eta2 matrix transform: if p_str == "0_to_3" or p_str == "3_to_0": @@ -794,7 +793,7 @@ def fun(e1, e2, e3): ) # matrix transforms for sparse meshgrid - mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) + mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) if p_str == "0_to_3" or p_str == "3_to_0": assert domain.transform(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == ( mat_x.shape[0], @@ -810,7 +809,7 @@ def fun(e1, e2, e3): ) # matrix transforms - mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij") + mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij") if p_str == "0_to_3" or p_str == "3_to_0": assert domain.transform(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == mat_x.shape else: @@ -822,18 +821,18 @@ def fun(e1, e2, e3): # """ # # from struphy.geometry import domains -# import cunumpy as xp +# import numpy as np # # # arrays: -# arr1 = xp.linspace(0., 1., 4) -# arr2 = xp.linspace(0., 1., 5) -# arr3 = xp.linspace(0., 1., 6) +# arr1 = np.linspace(0., 1., 4) +# arr2 = np.linspace(0., 1., 5) +# arr3 = np.linspace(0., 1., 6) # print() # print('Testing "transform"...') # print('array shapes:', arr1.shape, arr2.shape, arr3.shape) # # # logical function to tranform (used as components of forms too): -# fun = lambda eta1, eta2, eta3: xp.exp(eta1)*xp.sin(eta2)*xp.cos(eta3) +# fun = lambda eta1, eta2, eta3: np.exp(eta1)*np.sin(eta2)*np.cos(eta3) # # domain_class = getattr(domains, 'Colella') # domain = domain_class() @@ -890,9 +889,9 @@ def fun(e1, e2, e3): # assert a.shape[0] == arr1.size and a.shape[1] == arr2.size and a.shape[2] == arr3.size # # # matrix transformation at one point in third direction -# mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing='ij') -# mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing='ij') -# mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing='ij') +# mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing='ij') +# mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing='ij') +# mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing='ij') # # # eta1-eta2 matrix transformation: # a = domain.transform(fun_form, mat12_x, mat12_y, .5, p_str) @@ -908,21 +907,21 @@ def fun(e1, e2, e3): # assert a.shape == mat23_y.shape # # # matrix transformation for sparse meshgrid -# mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing='ij', sparse=True) +# mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing='ij', sparse=True) # a = domain.transform(fun_form, mat_x, mat_y, mat_z, p_str) # #print('sparse meshgrid matrix transformation, shape:', a.shape) # assert a.shape[0] == mat_x.shape[0] and a.shape[1] == mat_y.shape[1] and a.shape[2] == mat_z.shape[2] # # # matrix transformation -# mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing='ij') +# mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing='ij') # a = domain.transform(fun_form, mat_x, mat_y, mat_z, p_str) # #print('matrix transformation, shape:', a.shape) # assert a.shape == mat_x.shape if __name__ == "__main__": - # test_prepare_arg() - test_evaluation_mappings("DESCunit") - # test_pullback() - # test_pushforward() - # test_transform() + test_prepare_arg() + test_evaluation_mappings("GVECunit") + test_pullback() + test_pushforward() + test_transform() diff --git a/src/struphy/geometry/transform_kernels.py b/src/struphy/geometry/transform_kernels.py index f9e6d8077..c95156b96 100644 --- a/src/struphy/geometry/transform_kernels.py +++ b/src/struphy/geometry/transform_kernels.py @@ -54,13 +54,7 @@ @stack_array("dfmat1", "dfmat2") def pull( - a: "float[:]", - eta1: float, - eta2: float, - eta3: float, - kind_fun: int, - args_domain: "DomainArguments", - out: "float[:]", + a: "float[:]", eta1: float, eta2: float, eta3: float, kind_fun: int, args_domain: "DomainArguments", out: "float[:]" ): """ Pull-back of a Cartesian scalar/vector field to a differential p-form. @@ -120,13 +114,7 @@ def pull( @stack_array("dfmat1", "dfmat2", "dfmat3") def push( - a: "float[:]", - eta1: float, - eta2: float, - eta3: float, - kind_fun: int, - args_domain: "DomainArguments", - out: "float[:]", + a: "float[:]", eta1: float, eta2: float, eta3: float, kind_fun: int, args_domain: "DomainArguments", out: "float[:]" ): """ Pushforward of a differential p-forms to a Cartesian scalar/vector field. @@ -184,13 +172,7 @@ def push( @stack_array("dfmat1", "dfmat2", "dfmat3", "vec1", "vec2") def tran( - a: "float[:]", - eta1: float, - eta2: float, - eta3: float, - kind_fun: int, - args_domain: "DomainArguments", - out: "float[:]", + a: "float[:]", eta1: float, eta2: float, eta3: float, kind_fun: int, args_domain: "DomainArguments", out: "float[:]" ): """ Transformations between differential p-forms and/or vector fields. diff --git a/src/struphy/geometry/utilities.py b/src/struphy/geometry/utilities.py index ccc159692..6449174df 100644 --- a/src/struphy/geometry/utilities.py +++ b/src/struphy/geometry/utilities.py @@ -3,7 +3,6 @@ from typing import Callable -import cunumpy as xp import numpy as np # from typing import TYPE_CHECKING @@ -129,10 +128,10 @@ def field_line_tracing( Returns ------- - cR : xp.ndarray + cR : np.ndarray Control points (2d) of flux aligned spline mapping (R-component). - cZ : xp.ndarray + cZ : np.ndarray Control points (2d) of flux aligned spline mapping (Z-component). """ @@ -145,8 +144,8 @@ def field_line_tracing( ps, px = p_pre # spline knots - Ts = bsp.make_knots(xp.linspace(0.0, 1.0, ns + 1), ps, False) - Tx = bsp.make_knots(xp.linspace(0.0, 1.0, nx + 1), px, True) + Ts = bsp.make_knots(np.linspace(0.0, 1.0, ns + 1), ps, False) + Tx = bsp.make_knots(np.linspace(0.0, 1.0, nx + 1), px, True) # interpolation (Greville) points s_gr = bsp.greville(Ts, ps, False) @@ -165,13 +164,13 @@ def field_line_tracing( ] # check if pole is included - if xp.abs(psi(psi_axis_R, psi_axis_Z) - psi0) < 1e-14: + if np.abs(psi(psi_axis_R, psi_axis_Z) - psi0) < 1e-14: pole = True else: pole = False - R = xp.zeros((s_gr.size, x_gr.size), dtype=float) - Z = xp.zeros((s_gr.size, x_gr.size), dtype=float) + R = np.zeros((s_gr.size, x_gr.size), dtype=float) + Z = np.zeros((s_gr.size, x_gr.size), dtype=float) # function whose root must be found for j, x in enumerate(x_gr): @@ -188,8 +187,8 @@ def field_line_tracing( # function whose root must be found def f(r): - _R = psi_axis_R + r * xp.cos(2 * xp.pi * x) - _Z = psi_axis_Z + r * xp.sin(2 * xp.pi * x) + _R = psi_axis_R + r * np.cos(2 * np.pi * x) + _Z = psi_axis_Z + r * np.sin(2 * np.pi * x) psi_norm = (psi(_R, _Z) - psi0) / (psi1 - psi0) @@ -200,8 +199,8 @@ def f(r): r_flux_surface = newton(f, x0=r_guess) - R[i, j] = psi_axis_R + r_flux_surface * xp.cos(2 * xp.pi * x) - Z[i, j] = psi_axis_Z + r_flux_surface * xp.sin(2 * xp.pi * x) + R[i, j] = psi_axis_R + r_flux_surface * np.cos(2 * np.pi * x) + Z[i, j] = psi_axis_Z + r_flux_surface * np.sin(2 * np.pi * x) # get control points cR_equal_angle = kron_lusolve_2d(ILUs, R) @@ -227,8 +226,8 @@ def f(r): ps, px = p # spline knots - Ts = bsp.make_knots(xp.linspace(0.0, 1.0, ns + 1), ps, False) - Tx = bsp.make_knots(xp.linspace(0.0, 1.0, nx + 1), px, True) + Ts = bsp.make_knots(np.linspace(0.0, 1.0, ns + 1), ps, False) + Tx = bsp.make_knots(np.linspace(0.0, 1.0, nx + 1), px, True) # interpolation (Greville) points s_gr = bsp.greville(Ts, ps, False) @@ -255,10 +254,10 @@ def f(r): # target function for xi parametrization def f_angles(xis, s_val): - assert xp.all(xp.logical_and(xis > 0.0, xis < 1.0)) + assert np.all(np.logical_and(xis > 0.0, xis < 1.0)) # add 0 and 1 to angles array - xis_extended = xp.array([0.0] + list(xis) + [1.0]) + xis_extended = np.array([0.0] + list(xis) + [1.0]) # compute (R, Z) coordinates for given xis on fixed flux surface corresponding to s_val _RZ = domain_eq_angle(s_val, xis_extended, 0.0) @@ -267,17 +266,17 @@ def f_angles(xis, s_val): _Z = _RZ[2] # |grad(psi)| at xis - gp = xp.sqrt(psi(_R, _Z, dR=1) ** 2 + psi(_R, _Z, dZ=1) ** 2) + gp = np.sqrt(psi(_R, _Z, dR=1) ** 2 + psi(_R, _Z, dZ=1) ** 2) # compute weighted arc_lengths between two successive points in xis_extended array - dl = xp.zeros(xis_extended.size - 1, dtype=float) + dl = np.zeros(xis_extended.size - 1, dtype=float) weighted_arc_lengths_flux_surface(_R, _Z, gp, dl, xi_param_dict[xi_param]) # total length of the flux surface - l = xp.sum(dl) + l = np.sum(dl) # cumulative sum of arc lengths, start with 0! - l_cum = xp.cumsum(dl) + l_cum = np.cumsum(dl) # odd spline degree if px % 2 == 1: @@ -289,8 +288,8 @@ def f_angles(xis, s_val): return xi_diff # loop over flux surfaces and find xi parametrization - R = xp.zeros((s_gr.size, x_gr.size), dtype=float) - Z = xp.zeros((s_gr.size, x_gr.size), dtype=float) + R = np.zeros((s_gr.size, x_gr.size), dtype=float) + Z = np.zeros((s_gr.size, x_gr.size), dtype=float) if px % 2 == 1: xis0 = x_gr[1:].copy() diff --git a/src/struphy/geometry/utilities_kernels.py b/src/struphy/geometry/utilities_kernels.py index 1c26b25c5..85ccdbcf9 100644 --- a/src/struphy/geometry/utilities_kernels.py +++ b/src/struphy/geometry/utilities_kernels.py @@ -18,16 +18,16 @@ def weighted_arc_lengths_flux_surface(r: "float[:]", z: "float[:]", grad_psi: "f Parameters ---------- - r : xp.ndarray + r : np.ndarray R coordinates of the flux surface. - z : xp.ndarray + z : np.ndarray Z coordinates of the flux surface. - grad_psi : xp.ndarray + grad_psi : np.ndarray Absolute values of the flux function gradient on the flux surface: |grad(psi)| = sqrt[ (d_R psi)**2 + (d_Z psi)**2 ]. - dwls : xp.ndarray + dwls : np.ndarray The weighted arc lengths will be written into this array. Length must be one smaller than lengths of r, z and grad_psi. kind : int diff --git a/src/struphy/initial/eigenfunctions.py b/src/struphy/initial/eigenfunctions.py index 239ae24a9..c13397a1f 100644 --- a/src/struphy/initial/eigenfunctions.py +++ b/src/struphy/initial/eigenfunctions.py @@ -1,6 +1,6 @@ import os -import cunumpy as xp +import numpy as np import yaml from psydac.api.discretization import discretize from sympde.topology import Derham, Line @@ -54,11 +54,11 @@ def __init__(self, derham, **params): spec_path = params["spec_abs"] # load eigenvector for velocity field - omega2, U2_eig = xp.split(xp.load(spec_path), [1], axis=0) + omega2, U2_eig = np.split(np.load(spec_path), [1], axis=0) omega2 = omega2.flatten() # find eigenvector corresponding to given squared eigenfrequency range - mode = xp.where((xp.real(omega2) < params["eig_freq_upper"]) & (xp.real(omega2) > params["eig_freq_lower"]))[0] + mode = np.where((np.real(omega2) < params["eig_freq_upper"]) & (np.real(omega2) > params["eig_freq_lower"]))[0] assert mode.size == 1 mode = mode[0] @@ -67,16 +67,13 @@ def __init__(self, derham, **params): nnz_tor = derham.boundary_ops["2"].dim_nz_tor eig_vec_1 = U2_eig[ - 0 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2], - mode, + 0 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2], mode ] eig_vec_2 = U2_eig[ - 1 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 1 * nnz_pol[1] + 0 * nnz_pol[2], - mode, + 1 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 1 * nnz_pol[1] + 0 * nnz_pol[2], mode ] eig_vec_3 = U2_eig[ - 1 * nnz_pol[0] + 1 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 1 * nnz_pol[1] + 1 * nnz_pol[2], - mode, + 1 * nnz_pol[0] + 1 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 1 * nnz_pol[1] + 1 * nnz_pol[2], mode ] del omega2, U2_eig @@ -92,28 +89,28 @@ def __init__(self, derham, **params): n_tor = int(os.path.split(spec_path)[-1][-6:-4]) - N_cos = p0(lambda phi: xp.cos(2 * xp.pi * n_tor * phi)).coeffs.toarray() - N_sin = p0(lambda phi: xp.sin(2 * xp.pi * n_tor * phi)).coeffs.toarray() + N_cos = p0(lambda phi: np.cos(2 * np.pi * n_tor * phi)).coeffs.toarray() + N_sin = p0(lambda phi: np.sin(2 * np.pi * n_tor * phi)).coeffs.toarray() - D_cos = p1(lambda phi: xp.cos(2 * xp.pi * n_tor * phi)).coeffs.toarray() - D_sin = p1(lambda phi: xp.sin(2 * xp.pi * n_tor * phi)).coeffs.toarray() + D_cos = p1(lambda phi: np.cos(2 * np.pi * n_tor * phi)).coeffs.toarray() + D_sin = p1(lambda phi: np.sin(2 * np.pi * n_tor * phi)).coeffs.toarray() # select real part or imaginary part assert params["kind"] == "r" or params["kind"] == "i" if params["kind"] == "r": - eig_vec_1 = (xp.outer(xp.real(eig_vec_1), D_cos) - xp.outer(xp.imag(eig_vec_1), D_sin)).flatten() - eig_vec_2 = (xp.outer(xp.real(eig_vec_2), D_cos) - xp.outer(xp.imag(eig_vec_2), D_sin)).flatten() - eig_vec_3 = (xp.outer(xp.real(eig_vec_3), N_cos) - xp.outer(xp.imag(eig_vec_3), N_sin)).flatten() + eig_vec_1 = (np.outer(np.real(eig_vec_1), D_cos) - np.outer(np.imag(eig_vec_1), D_sin)).flatten() + eig_vec_2 = (np.outer(np.real(eig_vec_2), D_cos) - np.outer(np.imag(eig_vec_2), D_sin)).flatten() + eig_vec_3 = (np.outer(np.real(eig_vec_3), N_cos) - np.outer(np.imag(eig_vec_3), N_sin)).flatten() else: - eig_vec_1 = (xp.outer(xp.imag(eig_vec_1), D_cos) + xp.outer(xp.real(eig_vec_1), D_sin)).flatten() - eig_vec_2 = (xp.outer(xp.imag(eig_vec_2), D_cos) + xp.outer(xp.real(eig_vec_2), D_sin)).flatten() - eig_vec_3 = (xp.outer(xp.imag(eig_vec_3), N_cos) + xp.outer(xp.real(eig_vec_3), N_sin)).flatten() + eig_vec_1 = (np.outer(np.imag(eig_vec_1), D_cos) + np.outer(np.real(eig_vec_1), D_sin)).flatten() + eig_vec_2 = (np.outer(np.imag(eig_vec_2), D_cos) + np.outer(np.real(eig_vec_2), D_sin)).flatten() + eig_vec_3 = (np.outer(np.imag(eig_vec_3), N_cos) + np.outer(np.real(eig_vec_3), N_sin)).flatten() # set coefficients in full space - eigvec_1_ten = xp.zeros(derham.nbasis["2"][0], dtype=float) - eigvec_2_ten = xp.zeros(derham.nbasis["2"][1], dtype=float) - eigvec_3_ten = xp.zeros(derham.nbasis["2"][2], dtype=float) + eigvec_1_ten = np.zeros(derham.nbasis["2"][0], dtype=float) + eigvec_2_ten = np.zeros(derham.nbasis["2"][1], dtype=float) + eigvec_3_ten = np.zeros(derham.nbasis["2"][2], dtype=float) bc1_1 = derham.dirichlet_bc[0][0] bc1_2 = derham.dirichlet_bc[0][1] @@ -141,19 +138,19 @@ def __init__(self, derham, **params): else: # split into polar/tensor product parts - eig_vec_1 = xp.split( + eig_vec_1 = np.split( eig_vec_1, [ derham.Vh_pol["2"].n_polar[0] * nnz_tor[0], ], ) - eig_vec_2 = xp.split( + eig_vec_2 = np.split( eig_vec_2, [ derham.Vh_pol["2"].n_polar[1] * nnz_tor[1], ], ) - eig_vec_3 = xp.split( + eig_vec_3 = np.split( eig_vec_3, [ derham.Vh_pol["2"].n_polar[2] * nnz_tor[2], @@ -185,7 +182,7 @@ def __init__(self, derham, **params): ] eigvec_1_ten[derham.Vh_pol["2"].n_rings[0] : derham.nbasis["2"][0][0] - bc1_2, :, :] = eig_vec_1[1].reshape( - n_v2_0[0], + n_v2_0[0] ) eigvec_2_ten[derham.Vh_pol["2"].n_rings[1] :, :, :] = eig_vec_2[1].reshape(n_v2_0[1]) eigvec_3_ten[derham.Vh_pol["2"].n_rings[2] :, :, :] = eig_vec_3[1].reshape(n_v2_0[2]) diff --git a/src/struphy/initial/perturbations.py b/src/struphy/initial/perturbations.py index 4c113d02d..f37e38584 100644 --- a/src/struphy/initial/perturbations.py +++ b/src/struphy/initial/perturbations.py @@ -3,7 +3,7 @@ from dataclasses import dataclass -import cunumpy as xp +import numpy as np import scipy import scipy.special @@ -168,7 +168,7 @@ def __init__( self._pfuns += [lambda eta3: 1.0] elif pfun == "localize": self._pfuns += [ - lambda eta3: xp.tanh((eta3 - 0.5) / params) / xp.cosh((eta3 - 0.5) / params), + lambda eta3: np.tanh((eta3 - 0.5) / params) / np.cosh((eta3 - 0.5) / params), ] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -193,10 +193,10 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * xp.sin( - l * 2.0 * xp.pi / self._Lx * x - + m * 2.0 * xp.pi / self._Ly * y - + n * 2.0 * xp.pi / self._Lz * z + * np.sin( + l * 2.0 * np.pi / self._Lx * x + + m * 2.0 * np.pi / self._Ly * y + + n * 2.0 * np.pi / self._Lz * z + t, ) ) @@ -296,8 +296,8 @@ def __call__(self, x, y, z): val = 0.0 for amp, l, m, n in zip(self._amps, self._ls, self._ms, self._ns): - val += amp * xp.cos( - l * 2.0 * xp.pi / self._Lx * x + m * 2.0 * xp.pi / self._Ly * y + n * 2.0 * xp.pi / self._Lz * z, + val += amp * np.cos( + l * 2.0 * np.pi / self._Lx * x + m * 2.0 * np.pi / self._Ly * y + n * 2.0 * np.pi / self._Lz * z, ) # print( "Cos max value", val.max()) return val @@ -333,12 +333,12 @@ def __init__(self, m=1, a1=1.0, a2=2.0, a=1, b=-0.28): def __call__(self, eta1, eta2, eta3): val = 0.0 r = eta1 * (self._r2 - self._r1) + self._r1 - theta = eta2 * 2.0 * xp.pi + theta = eta2 * 2.0 * np.pi val += ( -self._m / r - * xp.cos(self._m * theta) + * np.cos(self._m * theta) * (self._a * scipy.special.jv(self._m, r) + self._b * scipy.special.yn(self._m, r)) ) return val @@ -375,12 +375,12 @@ def __init__(self, m=1, a1=1.0, a2=2.0, a=1, b=-0.28): def __call__(self, eta1, eta2, eta3): val = 0.0 r = eta1 * (self._r2 - self._r1) + self._r1 - theta = eta2 * 2.0 * xp.pi + theta = eta2 * 2.0 * np.pi val += ( self._a * ((self._m / r) * scipy.special.jv(self._m, r) - scipy.special.jv(self._m + 1, r)) + (self._b * ((self._m / r) * scipy.special.yn(self._m, r) - scipy.special.yn(self._m + 1, r))) - ) * xp.sin(self._m * theta) + ) * np.sin(self._m * theta) return val @@ -414,11 +414,11 @@ def __init__(self, m=1, a1=1.0, a2=2.0, a=1, b=-0.28): def __call__(self, eta1, eta2, eta3): val = 0.0 r = eta1 * (self._r2 - self._r1) + self._r1 - theta = eta2 * 2.0 * xp.pi + theta = eta2 * 2.0 * np.pi z = eta3 - val += (self._a * scipy.special.jv(self._m, r) + self._b * scipy.special.yn(self._m, r)) * xp.cos( - self._m * theta, + val += (self._a * scipy.special.jv(self._m, r) + self._b * scipy.special.yn(self._m, r)) * np.cos( + self._m * theta ) return val @@ -509,7 +509,7 @@ def __init__( if pfun == "Id": self._pfuns += [lambda z: 1.0] elif pfun == "localize": - self._pfuns += [lambda z, p=params: xp.tanh((z - 0.5) / p) / xp.cosh((z - 0.5) / p)] + self._pfuns += [lambda z, p=params: np.tanh((z - 0.5) / p) / np.cosh((z - 0.5) / p)] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -523,8 +523,8 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * xp.cos(l * 2.0 * xp.pi / self._Lx * x + thx) - * xp.cos(m * 2.0 * xp.pi / self._Ly * y + thy) + * np.cos(l * 2.0 * np.pi / self._Lx * x + thx) + * np.cos(m * 2.0 * np.pi / self._Ly * y + thy) ) return val @@ -614,7 +614,7 @@ def __init__( if pfun == "Id": self._pfuns += [lambda z: 1.0] elif pfun == "localize": - self._pfuns += [lambda z, p=params: xp.tanh((z - 0.5) / p) / xp.cosh((z - 0.5) / p)] + self._pfuns += [lambda z, p=params: np.tanh((z - 0.5) / p) / np.cosh((z - 0.5) / p)] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -628,8 +628,8 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * xp.sin(l * 2.0 * xp.pi / self._Lx * x + thx) - * xp.sin(m * 2.0 * xp.pi / self._Ly * y + thy) + * np.sin(l * 2.0 * np.pi / self._Lx * x + thx) + * np.sin(m * 2.0 * np.pi / self._Ly * y + thy) ) return val @@ -721,7 +721,7 @@ def __init__( if pfun == "Id": self._pfuns += [lambda z: 1.0] elif pfun == "localize": - self._pfuns += [lambda z, p=params: xp.tanh((z - 0.5) / p) / xp.cosh((z - 0.5) / p)] + self._pfuns += [lambda z, p=params: np.tanh((z - 0.5) / p) / np.cosh((z - 0.5) / p)] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -735,8 +735,8 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * xp.sin(l * 2.0 * xp.pi / self._Lx * x + thx) - * xp.cos(m * 2.0 * xp.pi / self._Ly * y + thy) + * np.sin(l * 2.0 * np.pi / self._Lx * x + thx) + * np.cos(m * 2.0 * np.pi / self._Ly * y + thy) ) return val @@ -828,7 +828,7 @@ def __init__( if pfun == "Id": self._pfuns += [lambda z: 1.0] elif pfun == "localize": - self._pfuns += [lambda z, p=params: xp.tanh((z - 0.5) / p) / xp.cosh((z - 0.5) / p)] + self._pfuns += [lambda z, p=params: np.tanh((z - 0.5) / p) / np.cosh((z - 0.5) / p)] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -842,8 +842,8 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * xp.cos(l * 2.0 * xp.pi / self._Lx * x + thx) - * xp.sin(m * 2.0 * xp.pi / self._Ly * y + thy) + * np.cos(l * 2.0 * np.pi / self._Lx * x + thx) + * np.sin(m * 2.0 * np.pi / self._Ly * y + thy) ) return val @@ -946,18 +946,18 @@ def __init__( ls = 1 else: ls = params - self._pfuns += [lambda eta1: xp.sin(ls * xp.pi * eta1)] + self._pfuns += [lambda eta1: np.sin(ls * np.pi * eta1)] elif pfun == "exp": self._pfuns += [ - lambda eta1: xp.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) - / xp.sqrt(2 * xp.pi * params[1] ** 2), + lambda eta1: np.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) + / np.sqrt(2 * np.pi * params[1] ** 2), ] elif pfun == "d_exp": self._pfuns += [ lambda eta1: -(eta1 - params[0]) / params[1] ** 2 - * xp.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) - / xp.sqrt(2 * xp.pi * params[1] ** 2), + * np.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) + / np.sqrt(2 * np.pi * params[1] ** 2), ] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -972,8 +972,8 @@ def __call__(self, eta1, eta2, eta3): val += ( amp * pfun(eta1) - * xp.sin( - mi * 2.0 * xp.pi * eta2 + ni * 2.0 * xp.pi * eta3, + * np.sin( + mi * 2.0 * np.pi * eta2 + ni * 2.0 * np.pi * eta3, ) ) @@ -1078,20 +1078,20 @@ def __init__( ls = 1 else: ls = params - self._pfuns += [lambda eta1: xp.sin(ls * xp.pi * eta1)] + self._pfuns += [lambda eta1: np.sin(ls * np.pi * eta1)] elif pfun == "cos": - self._pfuns += [lambda eta1: xp.cos(xp.pi * eta1)] + self._pfuns += [lambda eta1: np.cos(np.pi * eta1)] elif pfun == "exp": self._pfuns += [ - lambda eta1: xp.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) - / xp.sqrt(2 * xp.pi * params[1] ** 2), + lambda eta1: np.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) + / np.sqrt(2 * np.pi * params[1] ** 2), ] elif pfun == "d_exp": self._pfuns += [ lambda eta1: -(eta1 - params[0]) / params[1] ** 2 - * xp.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) - / xp.sqrt(2 * xp.pi * params[1] ** 2), + * np.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) + / np.sqrt(2 * np.pi * params[1] ** 2), ] else: raise ValueError( @@ -1108,8 +1108,8 @@ def __call__(self, eta1, eta2, eta3): val += ( amp * pfun(eta1) - * xp.cos( - mi * 2.0 * xp.pi * eta2 + ni * 2.0 * xp.pi * eta3, + * np.cos( + mi * 2.0 * np.pi * eta2 + ni * 2.0 * np.pi * eta3, ) ) @@ -1157,7 +1157,7 @@ def __init__( self.comp = comp def __call__(self, e1, e2, e3): - val = self._amp * (-xp.tanh((e1 - 0.75) / self._delta) + xp.tanh((e1 - 0.25) / self._delta) - 1) + val = self._amp * (-np.tanh((e1 - 0.75) / self._delta) + np.tanh((e1 - 0.25) / self._delta) - 1) return val @@ -1203,7 +1203,7 @@ def __init__( self.comp = comp def __call__(self, e1, e2, e3): - val = self._amp * (-xp.tanh((e2 - 0.75) / self._delta) + xp.tanh((e2 - 0.25) / self._delta) - 1) + val = self._amp * (-np.tanh((e2 - 0.75) / self._delta) + np.tanh((e2 - 0.25) / self._delta) - 1) return val @@ -1249,7 +1249,7 @@ def __init__( self.comp = comp def __call__(self, e1, e2, e3): - val = self._amp * (-xp.tanh((e3 - 0.75) / self._delta) + xp.tanh((e3 - 0.25) / self._delta) - 1) + val = self._amp * (-np.tanh((e3 - 0.75) / self._delta) + np.tanh((e3 - 0.25) / self._delta) - 1) return val @@ -1376,9 +1376,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = xp.sqrt(x**2 + y**2) - R = xp.where(R == 0.0, 1e-9, R) - phi = xp.arctan2(-y, x) + R = np.sqrt(x**2 + y**2) + R = np.where(R == 0.0, 1e-9, R) + phi = np.arctan2(-y, x) ustarR = ( self._alpha * R / (self._a * self._R0) * (-z) + self._beta * self._Bp * self._R0 / (self._B0 * self._a * R) * z @@ -1396,10 +1396,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi + ux = np.cos(phi) * uR - R * np.sin(phi) * uphi return ux elif self.comp == 1: - uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi + uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -1482,9 +1482,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = xp.sqrt(x**2 + y**2) - R = xp.where(R == 0.0, 1e-9, R) - phi = xp.arctan2(-y, x) + R = np.sqrt(x**2 + y**2) + R = np.where(R == 0.0, 1e-9, R) + phi = np.arctan2(-y, x) ustarR = ( self._alpha * R / (self._a * self._R0) * (-z) + self._beta * self._Bp * self._R0 / (self._B0 * self._a * R) * z @@ -1502,10 +1502,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi + ux = np.cos(phi) * uR - R * np.sin(phi) * uphi return ux elif self.comp == 1: - uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi + uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -1588,9 +1588,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = xp.sqrt(x**2 + y**2) - R = xp.where(R == 0.0, 1e-9, R) - phi = xp.arctan2(-y, x) + R = np.sqrt(x**2 + y**2) + R = np.where(R == 0.0, 1e-9, R) + phi = np.arctan2(-y, x) ustarR = ( self._alpha * R / (self._a * self._R0) * (-z) + self._beta * self._Bp * self._R0 / (self._B0 * self._a * R) * z @@ -1608,10 +1608,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi + ux = np.cos(phi) * uR - R * np.sin(phi) * uphi return ux elif self.comp == 1: - uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi + uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -1682,7 +1682,7 @@ def __init__(self, a=1.0, R0=2.0, B0=10.0, Bp=12.5, alpha=0.1, beta=1.0): # equilibrium potential def __call__(self, x, y, z): """Equilibrium potential.""" - R = xp.sqrt(x**2 + y**2) + R = np.sqrt(x**2 + y**2) pp = 0.5 * self._a * self._B0 * self._alpha * (((R - self._R0) ** 2 + z**2) / self._a**2 - 2.0 / 3.0) return pp @@ -1745,15 +1745,15 @@ def __call__(self, x, y, z): """Velocity of ions.""" """x component""" if self._dimension == "2D": - ux = -xp.sin(2 * xp.pi * x) * xp.sin(2 * xp.pi * y) + ux = -np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y) elif self._dimension == "1D": - ux = xp.sin(2 * xp.pi * x) + 1.0 + ux = np.sin(2 * np.pi * x) + 1.0 """y component""" if self._dimension == "2D": - uy = -xp.cos(2 * xp.pi * x) * xp.cos(2 * xp.pi * y) + uy = -np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y) elif self._dimension == "1D": - uy = xp.cos(2 * xp.pi * x) + uy = np.cos(2 * np.pi * x) """z component""" uz = 0.0 * x @@ -1771,15 +1771,15 @@ def __call__(self, x, y, z): """Velocity of electrons.""" """x component""" if self._dimension == "2D": - ux = -xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y) + ux = -np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y) elif self._dimension == "1D": - ux = xp.sin(2.0 * xp.pi * x) + ux = np.sin(2.0 * np.pi * x) """y component""" if self._dimension == "2D": - uy = -xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y) + uy = -np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y) elif self._dimension == "1D": - uy = xp.cos(2 * xp.pi * x) + uy = np.cos(2 * np.pi * x) """z component""" uz = 0.0 * x @@ -1844,9 +1844,9 @@ def __init__(self, dimension="1D", b0=1.0): def __call__(self, x, y, z): """Potential.""" if self._dimension == "2D": - phi = xp.cos(2 * xp.pi * x) + xp.sin(2 * xp.pi * y) + phi = np.cos(2 * np.pi * x) + np.sin(2 * np.pi * y) elif self._dimension == "1D": - phi = xp.sin(2.0 * xp.pi * x) + phi = np.sin(2.0 * np.pi * x) return phi @@ -1906,15 +1906,15 @@ def __call__(self, x, y, z): """Velocity of ions.""" """x component""" if self._dimension == "2D": - ux = -xp.sin(2 * xp.pi * x) * xp.sin(2 * xp.pi * y) + ux = -np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y) elif self._dimension == "1D": - ux = xp.sin(2 * xp.pi * x) + 1.0 + ux = np.sin(2 * np.pi * x) + 1.0 """y component""" if self._dimension == "2D": - uy = -xp.cos(2 * xp.pi * x) * xp.cos(2 * xp.pi * y) + uy = -np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y) elif self._dimension == "1D": - uy = xp.cos(2 * xp.pi * x) + uy = np.cos(2 * np.pi * x) """z component""" uz = 0.0 * x @@ -1932,15 +1932,15 @@ def __call__(self, x, y, z): """Velocity of electrons.""" """x component""" if self._dimension == "2D": - ux = -xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y) + ux = -np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y) elif self._dimension == "1D": - ux = xp.sin(2.0 * xp.pi * x) + ux = np.sin(2.0 * np.pi * x) """y component""" if self._dimension == "2D": - uy = -xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y) + uy = -np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y) elif self._dimension == "1D": - uy = xp.cos(2 * xp.pi * x) + uy = np.cos(2 * np.pi * x) """z component""" uz = 0.0 * x @@ -2001,8 +2001,8 @@ def __call__(self, eta1, eta2=None, eta3=None): val = ( self._n0 * self._c[3] - * xp.exp( - -self._c[2] / self._c[1] * xp.tanh((eta1 - self._c[0]) / self._c[2]), + * np.exp( + -self._c[2] / self._c[1] * np.tanh((eta1 - self._c[0]) / self._c[2]), ) ) @@ -2088,9 +2088,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = xp.sqrt(x**2 + y**2) - R = xp.where(R == 0.0, 1e-9, R) - phi = xp.arctan2(-y, x) + R = np.sqrt(x**2 + y**2) + R = np.where(R == 0.0, 1e-9, R) + phi = np.arctan2(-y, x) A = self._alpha / (self._a * self._R0) C = self._beta * self._Bp * self._R0 / (self._B0 * self._a) @@ -2101,10 +2101,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi + ux = np.cos(phi) * uR - R * np.sin(phi) * uphi return ux elif self.comp == 1: - uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi + uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -2192,9 +2192,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = xp.sqrt(x**2 + y**2) - R = xp.where(R == 0.0, 1e-9, R) - phi = xp.arctan2(-y, x) + R = np.sqrt(x**2 + y**2) + R = np.where(R == 0.0, 1e-9, R) + phi = np.arctan2(-y, x) A = self._alpha / (self._a * self._R0) C = self._beta * self._Bp * self._R0 / (self._B0 * self._a) @@ -2205,10 +2205,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi + ux = np.cos(phi) * uR - R * np.sin(phi) * uphi return ux elif self.comp == 1: - uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi + uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -2296,9 +2296,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = xp.sqrt(x**2 + y**2) - R = xp.where(R == 0.0, 1e-9, R) - phi = xp.arctan2(-y, x) + R = np.sqrt(x**2 + y**2) + R = np.where(R == 0.0, 1e-9, R) + phi = np.arctan2(-y, x) A = self._alpha / (self._a * self._R0) C = self._beta * self._Bp * self._R0 / (self._B0 * self._a) @@ -2309,10 +2309,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi + ux = np.cos(phi) * uR - R * np.sin(phi) * uphi return ux elif self.comp == 1: - uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi + uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ diff --git a/src/struphy/initial/tests/test_init_perturbations.py b/src/struphy/initial/tests/test_init_perturbations.py index 5d52e9291..4a012f08b 100644 --- a/src/struphy/initial/tests/test_init_perturbations.py +++ b/src/struphy/initial/tests/test_init_perturbations.py @@ -4,6 +4,7 @@ import pytest +# @pytest.mark.mpi(min_size=2) # @pytest.mark.parametrize('combine_comps', [('f0', 'f1'), ('f0', 'f3'), ('f1', 'f2'), ('fvec', 'f3'), ('f1', 'fvec', 'f0')]) @pytest.mark.parametrize("Nel", [[16, 16, 16]]) @pytest.mark.parametrize("p", [[2, 3, 4]]) @@ -20,9 +21,9 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False): """Test the initialization Field.initialize_coeffs with all "Modes" classes in perturbations.py.""" - import cunumpy as xp + import numpy as np from matplotlib import pyplot as plt - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.geometry import domains @@ -50,10 +51,10 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False form_vector = ["1", "2", "v", "norm", "physical_at_eta"] # evaluation points - e1 = xp.linspace(0.0, 1.0, 30) - e2 = xp.linspace(0.0, 1.0, 40) - e3 = xp.linspace(0.0, 1.0, 50) - eee1, eee2, eee3 = xp.meshgrid(e1, e2, e3, indexing="ij") + e1 = np.linspace(0.0, 1.0, 30) + e2 = np.linspace(0.0, 1.0, 40) + e3 = np.linspace(0.0, 1.0, 50) + eee1, eee2, eee3 = np.meshgrid(e1, e2, e3, indexing="ij") # mode paramters kwargs = {} @@ -130,7 +131,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False field_vals_xyz = domain.push(field, e1, e2, e3, kind=form) x, y, z = domain(e1, e2, e3) - r = xp.sqrt(x**2 + y**2) + r = np.sqrt(x**2 + y**2) if fun_form == "physical": fun_vals_xyz = perturbation_xyz(x, y, z) @@ -139,7 +140,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False else: fun_vals_xyz = domain.push(perturbation, eee1, eee2, eee3, kind=fun_form) - error = xp.max(xp.abs(field_vals_xyz - fun_vals_xyz)) / xp.max(xp.abs(fun_vals_xyz)) + error = np.max(np.abs(field_vals_xyz - fun_vals_xyz)) / np.max(np.abs(fun_vals_xyz)) print(f"{rank=}, {key=}, {form=}, {fun_form=}, {error=}") assert error < 0.02 @@ -169,7 +170,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False plt.xlabel("x") plt.ylabel("y") plt.colorbar() - plt.title("exact function") + plt.title(f"exact function") ax = plt.gca() ax.set_aspect("equal", adjustable="box") @@ -198,7 +199,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False plt.xlabel("x") plt.ylabel("z") plt.colorbar() - plt.title("exact function") + plt.title(f"exact function") ax = plt.gca() ax.set_aspect("equal", adjustable="box") @@ -222,7 +223,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False params = { key: { "given_in_basis": [fun_form] * 3, - }, + } } if "Modes" in key: @@ -254,7 +255,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False f_xyz = [f1_xyz, f2_xyz, f3_xyz] x, y, z = domain(e1, e2, e3) - r = xp.sqrt(x**2 + y**2) + r = np.sqrt(x**2 + y**2) # exact values if fun_form == "physical": @@ -267,27 +268,19 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False fun3_xyz = perturbation(eee1, eee2, eee3) elif fun_form == "norm": tmp1, tmp2, tmp3 = domain.transform( - [perturbation, perturbation, perturbation], - eee1, - eee2, - eee3, - kind=fun_form + "_to_v", + [perturbation, perturbation, perturbation], eee1, eee2, eee3, kind=fun_form + "_to_v" ) fun1_xyz, fun2_xyz, fun3_xyz = domain.push([tmp1, tmp2, tmp3], eee1, eee2, eee3, kind="v") else: fun1_xyz, fun2_xyz, fun3_xyz = domain.push( - [perturbation, perturbation, perturbation], - eee1, - eee2, - eee3, - kind=fun_form, + [perturbation, perturbation, perturbation], eee1, eee2, eee3, kind=fun_form ) fun_xyz_vec = [fun1_xyz, fun2_xyz, fun3_xyz] error = 0.0 for fi, funi in zip(f_xyz, fun_xyz_vec): - error += xp.max(xp.abs(fi - funi)) / xp.max(xp.abs(funi)) + error += np.max(np.abs(fi - funi)) / np.max(np.abs(funi)) error /= 3.0 print(f"{rank=}, {key=}, {form=}, {fun_form=}, {error=}") assert error < 0.02 @@ -307,7 +300,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False plt.ylabel("y") plt.colorbar() plt.title( - f"component {c + 1}, init was {fun_form}, (m,n)=({kwargs['ms'][0]},{kwargs['ns'][0]})", + f"component {c + 1}, init was {fun_form}, (m,n)=({kwargs['ms'][0]},{kwargs['ns'][0]})" ) ax = plt.gca() ax.set_aspect("equal", adjustable="box") @@ -324,7 +317,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False plt.ylabel("z") plt.colorbar() plt.title( - f"component {c + 1}, init was {fun_form}, (m,n)=({kwargs['ms'][0]},{kwargs['ns'][0]})", + f"component {c + 1}, init was {fun_form}, (m,n)=({kwargs['ms'][0]},{kwargs['ns'][0]})" ) ax = plt.gca() ax.set_aspect("equal", adjustable="box") diff --git a/src/struphy/initial/utilities.py b/src/struphy/initial/utilities.py index 7af042249..3f9f2dfec 100644 --- a/src/struphy/initial/utilities.py +++ b/src/struphy/initial/utilities.py @@ -1,7 +1,7 @@ import os -import cunumpy as xp import h5py +import numpy as np from struphy.fields_background.equils import set_defaults from struphy.io.output_handling import DataContainer @@ -98,6 +98,6 @@ def __init__( self._amp = amp def __call__(self, x, y, z): - val = self._amp * xp.random.rand(*x.shape).squeeze() + val = self._amp * np.random.rand(*x.shape).squeeze() return val diff --git a/src/struphy/io/inp/parameters.yml b/src/struphy/io/inp/parameters.yml index 020aeffdf..73fbf48ae 100644 --- a/src/struphy/io/inp/parameters.yml +++ b/src/struphy/io/inp/parameters.yml @@ -109,7 +109,7 @@ kinetic : plot_pts : [[32, 1, 1], [1, 16, 1]] # number of plot points in each direction background : # background is mandatory for kinetic species Maxwellian3D : - n : 1.0 + n : 0.05 u2 : 2.5 perturbation : n: diff --git a/src/struphy/io/inp/params_Maxwell.py b/src/struphy/io/inp/params_Maxwell.py index 984b81620..a60b57588 100644 --- a/src/struphy/io/inp/params_Maxwell.py +++ b/src/struphy/io/inp/params_Maxwell.py @@ -21,7 +21,11 @@ time_opts = Time() # geometry -domain = domains.Cuboid() +domain = domains.GVECunit() # Tokamak(tor_period=3) + +domain.show3D_interactive(save_dir="3d.html") + +exit() # fluid equilibrium (can be used as part of initial conditions) equil = equils.HomogenSlab() @@ -36,7 +40,7 @@ model = Model() # propagator options -model.propagators.maxwell.set_options() +# model.propagators.maxwell.set_options() # initial conditions (background + perturbation) model.em_fields.b_field.add_background(FieldsBackground()) diff --git a/src/struphy/io/options.py b/src/struphy/io/options.py index cecd4b72c..13c1ae134 100644 --- a/src/struphy/io/options.py +++ b/src/struphy/io/options.py @@ -2,8 +2,7 @@ from dataclasses import dataclass from typing import Literal, get_args -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np from struphy.physics.physics import ConstantsOfNature @@ -26,11 +25,8 @@ # solvers OptsSymmSolver = Literal["pcg", "cg"] -OptsGenSolver = Literal["pbicgstab", "bicgstab", "GMRES"] -OptsMassPrecond = Literal["MassMatrixPreconditioner", "MassMatrixDiagonalPreconditioner", None] -OptsSaddlePointSolver = Literal["Uzawa", "GMRES"] -OptsDirectSolver = Literal["SparseSolver", "ScipySparse", "InexactNPInverse", "DirectNPInverse"] -OptsNonlinearSolver = Literal["Picard", "Newton"] +OptsGenSolver = Literal["pbicgstab", "bicgstab"] +OptsMassPrecond = Literal["MassMatrixPreconditioner", None] # markers OptsPICSpace = Literal["Particles6D", "DeltaFParticles6D", "Particles5D", "Particles3D"] @@ -47,9 +43,6 @@ OptsSpatialLoading = Literal["uniform", "disc"] OptsMPIsort = Literal["each", "last", None] -# filters -OptsFilter = Literal["fourier_in_tor", "hybrid", "three_point", None] - # sph OptsKernel = Literal[ "trigonometric_1d", @@ -178,6 +171,8 @@ def j(self): def derive_units(self, velocity_scale: str = "light", A_bulk: int = None, Z_bulk: int = None, verbose=False): """Derive the remaining units from the base units, velocity scale and bulk species' A and Z.""" + from mpi4py import MPI + con = ConstantsOfNature() # velocity (m/s) @@ -189,7 +184,7 @@ def derive_units(self, velocity_scale: str = "light", A_bulk: int = None, Z_bulk elif velocity_scale == "alfvén": assert A_bulk is not None, 'Need bulk species to choose velocity scale "alfvén".' - self._v = self.B / xp.sqrt(self.n * A_bulk * con.mH * con.mu0) + self._v = self.B / np.sqrt(self.n * A_bulk * con.mH * con.mu0) elif velocity_scale == "cyclotron": assert Z_bulk is not None, 'Need bulk species to choose velocity scale "cyclotron".' @@ -199,7 +194,7 @@ def derive_units(self, velocity_scale: str = "light", A_bulk: int = None, Z_bulk elif velocity_scale == "thermal": assert A_bulk is not None, 'Need bulk species to choose velocity scale "thermal".' assert self.kBT is not None - self._v = xp.sqrt(self.kBT * 1000 * con.e / (con.mH * A_bulk)) + self._v = np.sqrt(self.kBT * 1000 * con.e / (con.mH * A_bulk)) # time (s) self._t = self.x / self.v diff --git a/src/struphy/io/output_handling.py b/src/struphy/io/output_handling.py index 808c2bcf3..35c8fbfc0 100644 --- a/src/struphy/io/output_handling.py +++ b/src/struphy/io/output_handling.py @@ -1,8 +1,8 @@ import ctypes import os -import cunumpy as xp import h5py +import numpy as np class DataContainer: @@ -24,7 +24,7 @@ def __init__(self, path_out, file_name=None, comm=None): # set name of hdf5 file if comm is None: self._rank = None - _affix = "_proc0" + _affix = "" else: self._rank = comm.Get_rank() _affix = "_proc" + str(self._rank) @@ -54,7 +54,7 @@ def __init__(self, path_out, file_name=None, comm=None): dataset_keys = [] self._file.visit( - lambda key: dataset_keys.append(key) if isinstance(self._file[key], h5py.Dataset) else None, + lambda key: dataset_keys.append(key) if isinstance(self._file[key], h5py.Dataset) else None ) for key in dataset_keys: @@ -82,11 +82,11 @@ def add_data(self, data_dict): Parameters ---------- data_dict : dict - Name-object pairs to save during time stepping, e.g. {key : val}. key must be a string and val must be a xp.array of fixed shape. Scalar values (floats) must therefore be passed as 1d arrays of size 1. + Name-object pairs to save during time stepping, e.g. {key : val}. key must be a string and val must be a np.array of fixed shape. Scalar values (floats) must therefore be passed as 1d arrays of size 1. """ for key, val in data_dict.items(): - assert isinstance(val, xp.ndarray) + assert isinstance(val, np.ndarray) # if dataset already exists, check for compatibility with given array if key in self._dset_dict: @@ -110,11 +110,7 @@ def add_data(self, data_dict): self._file[key][0] = val[0] else: self._file.create_dataset( - key, - (1,) + val.shape, - maxshape=(None,) + val.shape, - dtype=val.dtype, - chunks=True, + key, (1,) + val.shape, maxshape=(None,) + val.shape, dtype=val.dtype, chunks=True ) self._file[key][0] = val diff --git a/src/struphy/io/setup.py b/src/struphy/io/setup.py index 4ecd96f47..ed23926df 100644 --- a/src/struphy/io/setup.py +++ b/src/struphy/io/setup.py @@ -5,11 +5,12 @@ import sys from types import ModuleType -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import yaml +from mpi4py import MPI from struphy.geometry.base import Domain -from struphy.io.options import DerhamOptions +from struphy.geometry.domains import Cuboid +from struphy.io.options import DerhamOptions, Time, Units from struphy.topology.grids import TensorProductGrid @@ -152,12 +153,12 @@ def setup_derham( if MPI.COMM_WORLD.Get_rank() == 0 and verbose: print("\nDERHAM:") - print("number of elements:".ljust(25), Nel) - print("spline degrees:".ljust(25), p) - print("periodic bcs:".ljust(25), spl_kind) - print("hom. Dirichlet bc:".ljust(25), dirichlet_bc) - print("GL quad pts (L2):".ljust(25), nquads) - print("GL quad pts (hist):".ljust(25), nq_pr) + print(f"number of elements:".ljust(25), Nel) + print(f"spline degrees:".ljust(25), p) + print(f"periodic bcs:".ljust(25), spl_kind) + print(f"hom. Dirichlet bc:".ljust(25), dirichlet_bc) + print(f"GL quad pts (L2):".ljust(25), nquads) + print(f"GL quad pts (hist):".ljust(25), nq_pr) print( "MPI proc. per dir.:".ljust(25), derham.domain_decomposition.nprocs, @@ -233,35 +234,35 @@ def descend_options_dict( out = copy.deepcopy(d) if verbose: - print(f"{d =}") - print(f"{out =}") - print(f"{d_default =}") - print(f"{d_opts =}") - print(f"{keys =}") - print(f"{depth =}") - print(f"{pop_again =}") + print(f"{d = }") + print(f"{out = }") + print(f"{d_default = }") + print(f"{d_opts = }") + print(f"{keys = }") + print(f"{depth = }") + print(f"{pop_again = }") if verbose: - print(f"{d =}") - print(f"{out =}") - print(f"{d_default =}") - print(f"{d_opts =}") - print(f"{keys =}") - print(f"{depth =}") - print(f"{pop_again =}") + print(f"{d = }") + print(f"{out = }") + print(f"{d_default = }") + print(f"{d_opts = }") + print(f"{keys = }") + print(f"{depth = }") + print(f"{pop_again = }") count = 0 for key, val in d.items(): count += 1 if verbose: - print(f"\n{keys =} | {key =}, {type(val) =}, {count =}\n") + print(f"\n{keys = } | {key = }, {type(val) = }, {count = }\n") if isinstance(val, list): # create default parameter dict "out" if verbose: - print(f"{val =}") + print(f"{val = }") if d_default is None: if len(keys) == 0: @@ -299,10 +300,10 @@ def descend_options_dict( out += [out_sublist] if verbose: - print(f"{out =}") + print(f"{out = }") if verbose: - print(f"{out =}") + print(f"{out = }") # recurse if necessary elif isinstance(val, dict): diff --git a/src/struphy/kinetic_background/base.py b/src/struphy/kinetic_background/base.py index 765ee1508..ca84a8ec5 100644 --- a/src/struphy/kinetic_background/base.py +++ b/src/struphy/kinetic_background/base.py @@ -3,10 +3,12 @@ from abc import ABCMeta, abstractmethod from typing import Callable -import cunumpy as xp +import numpy as np from struphy.fields_background.base import FluidEquilibriumWithB +from struphy.fields_background.equils import set_defaults from struphy.initial.base import Perturbation +from struphy.initial.utilities import Noise class KineticBackground(metaclass=ABCMeta): @@ -104,7 +106,7 @@ def __call__(self, *args): Returns ------- - f0 : xp.ndarray + f0 : np.ndarray The evaluated background. """ pass @@ -119,12 +121,12 @@ def __rmul__(self, a): return ScalarMultiplyKineticBackground(self, a) def __div__(self, a): - assert isinstance(a, float) or isinstance(a, int) or isinstance(a, xp.int64) + assert isinstance(a, float) or isinstance(a, int) or isinstance(a, np.int64) assert a != 0, "Cannot divide by zero!" return ScalarMultiplyKineticBackground(self, 1 / a) def __rdiv__(self, a): - assert isinstance(a, float) or isinstance(a, int) or isinstance(a, xp.int64) + assert isinstance(a, float) or isinstance(a, int) or isinstance(a, np.int64) assert a != 0, "Cannot divide by zero!" return ScalarMultiplyKineticBackground(self, 1 / a) @@ -232,7 +234,7 @@ def __call__(self, *args): Returns ------- - f0 : xp.ndarray + f0 : np.ndarray The evaluated background. """ return self._f1(*args) + self._f2(*args) @@ -241,7 +243,7 @@ def __call__(self, *args): class ScalarMultiplyKineticBackground(KineticBackground): def __init__(self, f0, a): assert isinstance(f0, KineticBackground) - assert isinstance(a, float) or isinstance(a, int) or isinstance(a, xp.int64) + assert isinstance(a, float) or isinstance(a, int) or isinstance(a, np.int64) self._f = f0 self._a = a @@ -318,7 +320,7 @@ def __call__(self, *args): Returns ------- - f0 : xp.ndarray + f0 : np.ndarray The evaluated background. """ return self._a * self._f(*args) @@ -394,14 +396,14 @@ def gaussian(self, v, u=0.0, vth=1.0, polar=False, volume_form=False): An array of size(v). """ - if isinstance(v, xp.ndarray) and isinstance(u, xp.ndarray): - assert v.shape == u.shape, f"{v.shape =} but {u.shape =}" + if isinstance(v, np.ndarray) and isinstance(u, np.ndarray): + assert v.shape == u.shape, f"{v.shape = } but {u.shape = }" if not polar: - out = 1.0 / vth * 1.0 / xp.sqrt(2.0 * xp.pi) * xp.exp(-((v - u) ** 2) / (2.0 * vth**2)) + out = 1.0 / vth * 1.0 / np.sqrt(2.0 * np.pi) * np.exp(-((v - u) ** 2) / (2.0 * vth**2)) else: - assert xp.all(v >= 0.0) - out = 1.0 / vth**2 * xp.exp(-((v - u) ** 2) / (2.0 * vth**2)) + assert np.all(v >= 0.0) + out = 1.0 / vth**2 * np.exp(-((v - u) ** 2) / (2.0 * vth**2)) if volume_form: out *= v @@ -427,16 +429,16 @@ def __call__(self, *args): Returns ------- - f : xp.ndarray + f : np.ndarray The evaluated Maxwellian. """ # Check that all args have the same shape - shape0 = xp.shape(args[0]) + shape0 = np.shape(args[0]) for i, arg in enumerate(args): - assert xp.shape(arg) == shape0, f"Argument {i} has {xp.shape(arg) =}, but must be {shape0 =}." - assert xp.ndim(arg) == 1 or xp.ndim(arg) == 3 + self.vdim, ( - f"{xp.ndim(arg) =} not allowed for Maxwellian evaluation." + assert np.shape(arg) == shape0, f"Argument {i} has {np.shape(arg) = }, but must be {shape0 = }." + assert np.ndim(arg) == 1 or np.ndim(arg) == 3 + self.vdim, ( + f"{np.ndim(arg) = } not allowed for Maxwellian evaluation." ) # flat or meshgrid evaluation # Get result evaluated at eta's @@ -445,33 +447,33 @@ def __call__(self, *args): vths = self.vth(*args[: -self.vdim]) # take care of correct broadcasting, assuming args come from phase space meshgrid - if xp.ndim(args[0]) > 3: + if np.ndim(args[0]) > 3: # move eta axes to the back - arg_t = xp.moveaxis(args[0], 0, -1) - arg_t = xp.moveaxis(arg_t, 0, -1) - arg_t = xp.moveaxis(arg_t, 0, -1) + arg_t = np.moveaxis(args[0], 0, -1) + arg_t = np.moveaxis(arg_t, 0, -1) + arg_t = np.moveaxis(arg_t, 0, -1) # broadcast res_broad = res + 0.0 * arg_t # move eta axes to the front - res = xp.moveaxis(res_broad, -1, 0) - res = xp.moveaxis(res, -1, 0) - res = xp.moveaxis(res, -1, 0) + res = np.moveaxis(res_broad, -1, 0) + res = np.moveaxis(res, -1, 0) + res = np.moveaxis(res, -1, 0) # Multiply result with gaussian in v's for i, v in enumerate(args[-self.vdim :]): # correct broadcasting - if xp.ndim(args[0]) > 3: + if np.ndim(args[0]) > 3: u_broad = us[i] + 0.0 * arg_t - u = xp.moveaxis(u_broad, -1, 0) - u = xp.moveaxis(u, -1, 0) - u = xp.moveaxis(u, -1, 0) + u = np.moveaxis(u_broad, -1, 0) + u = np.moveaxis(u, -1, 0) + u = np.moveaxis(u, -1, 0) vth_broad = vths[i] + 0.0 * arg_t - vth = xp.moveaxis(vth_broad, -1, 0) - vth = xp.moveaxis(vth, -1, 0) - vth = xp.moveaxis(vth, -1, 0) + vth = np.moveaxis(vth_broad, -1, 0) + vth = np.moveaxis(vth, -1, 0) + vth = np.moveaxis(vth, -1, 0) else: u = us[i] vth = vths[i] @@ -500,9 +502,9 @@ def _evaluate_moment(self, eta1, eta2, eta3, *, name: str = "n", add_perturbatio """ # collect arguments - assert isinstance(eta1, xp.ndarray) - assert isinstance(eta2, xp.ndarray) - assert isinstance(eta3, xp.ndarray) + assert isinstance(eta1, np.ndarray) + assert isinstance(eta2, np.ndarray) + assert isinstance(eta3, np.ndarray) assert eta1.shape == eta2.shape == eta3.shape params = self.maxw_params[name] @@ -512,7 +514,7 @@ def _evaluate_moment(self, eta1, eta2, eta3, *, name: str = "n", add_perturbatio # flat evaluation for markers if eta1.ndim == 1: etas = [ - xp.concatenate( + np.concatenate( (eta1[:, None], eta2[:, None], eta3[:, None]), axis=1, ), diff --git a/src/struphy/kinetic_background/maxwellians.py b/src/struphy/kinetic_background/maxwellians.py index d9e34dcab..5e240a1b7 100644 --- a/src/struphy/kinetic_background/maxwellians.py +++ b/src/struphy/kinetic_background/maxwellians.py @@ -2,7 +2,7 @@ from typing import Callable -import cunumpy as xp +import numpy as np from struphy.fields_background.base import FluidEquilibriumWithB from struphy.fields_background.equils import set_defaults @@ -251,7 +251,7 @@ def velocity_jacobian_det(self, eta1, eta2, eta3, *v): assert len(v) == 2 # call equilibrium - etas = (xp.vstack((eta1, eta2, eta3)).T).copy() + etas = (np.vstack((eta1, eta2, eta3)).T).copy() absB0 = self.equil.absB0(etas) # J = v_perp/B @@ -405,14 +405,14 @@ def velocity_jacobian_det(self, eta1, eta2, eta3, energy): assert eta3.ndim == 1 if self.maxw_params["type"] == "Particles6D": - return xp.sqrt(2.0 * energy) * 4.0 * xp.pi + return np.sqrt(2.0 * energy) * 4.0 * np.pi else: # call equilibrium - etas = (xp.vstack((eta1, eta2, eta3)).T).copy() + etas = (np.vstack((eta1, eta2, eta3)).T).copy() absB0 = self.equil.absB0(etas) - return xp.sqrt(energy) * 2.0 * xp.sqrt(2.0) / absB0 + return np.sqrt(energy) * 2.0 * np.sqrt(2.0) / absB0 def gaussian(self, e, vth=1.0): """3-dim. normal distribution, to which array-valued thermal velocities can be passed. @@ -430,10 +430,10 @@ def gaussian(self, e, vth=1.0): An array of size(e). """ - if isinstance(vth, xp.ndarray): - assert e.shape == vth.shape, f"{e.shape =} but {vth.shape =}" + if isinstance(vth, np.ndarray): + assert e.shape == vth.shape, f"{e.shape = } but {vth.shape = }" - return 2.0 * xp.sqrt(e / xp.pi) / vth**3 * xp.exp(-e / vth**2) + return 2.0 * np.sqrt(e / np.pi) / vth**3 * np.exp(-e / vth**2) def __call__(self, *args): """Evaluates the canonical Maxwellian distribution function. @@ -455,16 +455,16 @@ def __call__(self, *args): Returns ------- - f : xp.ndarray + f : np.ndarray The evaluated Maxwellian. """ # Check that all args have the same shape - shape0 = xp.shape(args[0]) + shape0 = np.shape(args[0]) for i, arg in enumerate(args): - assert xp.shape(arg) == shape0, f"Argument {i} has {xp.shape(arg) =}, but must be {shape0 =}." - assert xp.ndim(arg) == 1 or xp.ndim(arg) == 3, ( - f"{xp.ndim(arg) =} not allowed for canonical Maxwellian evaluation." + assert np.shape(arg) == shape0, f"Argument {i} has {np.shape(arg) = }, but must be {shape0 = }." + assert np.ndim(arg) == 1 or np.ndim(arg) == 3, ( + f"{np.ndim(arg) = } not allowed for canonical Maxwellian evaluation." ) # flat or meshgrid evaluation # Get result evaluated with each particles' psic @@ -472,26 +472,26 @@ def __call__(self, *args): vths = self.vth(args[2]) # take care of correct broadcasting, assuming args come from phase space meshgrid - if xp.ndim(args[0]) == 3: + if np.ndim(args[0]) == 3: # move eta axes to the back - arg_t = xp.moveaxis(args[0], 0, -1) - arg_t = xp.moveaxis(arg_t, 0, -1) - arg_t = xp.moveaxis(arg_t, 0, -1) + arg_t = np.moveaxis(args[0], 0, -1) + arg_t = np.moveaxis(arg_t, 0, -1) + arg_t = np.moveaxis(arg_t, 0, -1) # broadcast res_broad = res + 0.0 * arg_t # move eta axes to the front - res = xp.moveaxis(res_broad, -1, 0) - res = xp.moveaxis(res, -1, 0) - res = xp.moveaxis(res, -1, 0) + res = np.moveaxis(res_broad, -1, 0) + res = np.moveaxis(res, -1, 0) + res = np.moveaxis(res, -1, 0) # Multiply result with gaussian in energy - if xp.ndim(args[0]) == 3: + if np.ndim(args[0]) == 3: vth_broad = vths + 0.0 * arg_t - vth = xp.moveaxis(vth_broad, -1, 0) - vth = xp.moveaxis(vth, -1, 0) - vth = xp.moveaxis(vth, -1, 0) + vth = np.moveaxis(vth_broad, -1, 0) + vth = np.moveaxis(vth, -1, 0) + vth = np.moveaxis(vth, -1, 0) else: vth = vths @@ -544,13 +544,13 @@ def rc(self, psic): rc_squared = (psic - self.equil.psi_range[0]) / (self.equil.psi_range[1] - self.equil.psi_range[0]) # sorting out indices of negative rc² - neg_index = xp.logical_not(rc_squared >= 0) + neg_index = np.logical_not(rc_squared >= 0) # make them positive rc_squared[neg_index] *= -1 # calculate rc - rc = xp.sqrt(rc_squared) + rc = np.sqrt(rc_squared) rc[neg_index] *= -1 return rc @@ -568,7 +568,7 @@ def n(self, psic, add_perturbation: bool = None): A float (background value) or a numpy.array of the evaluated density. """ # collect arguments - assert isinstance(psic, xp.ndarray) + assert isinstance(psic, np.ndarray) # assuming that input comes from meshgrid. if psic.ndim == 3: @@ -612,7 +612,7 @@ def vth(self, psic): """ # collect arguments - assert isinstance(psic, xp.ndarray) + assert isinstance(psic, np.ndarray) # assuming that input comes from meshgrid. if psic.ndim == 3: @@ -655,28 +655,20 @@ def default_maxw_params(cls): def __init__( self, - n: tuple[float | Callable, Perturbation] = (1.0, None), - u1: tuple[float | Callable, Perturbation] = (0.0, None), - u2: tuple[float | Callable, Perturbation] = (0.0, None), - u3: tuple[float | Callable, Perturbation] = (0.0, None), + maxw_params: dict = None, + pert_params: dict = None, equil: FluidEquilibriumWithB = None, ): - self._maxw_params = {} - self._maxw_params["n"] = n - self._maxw_params["u1"] = u1 - self._maxw_params["u2"] = u2 - self._maxw_params["u3"] = u3 - self._maxw_params["vth1"] = (0.0, None) - self._maxw_params["vth2"] = (0.0, None) - self._maxw_params["vth3"] = (0.0, None) - - self.check_maxw_params() - - self._equil = equil + super().__init__( + maxw_params=maxw_params, + pert_params=pert_params, + equil=equil, + ) - @property - def maxw_params(self): - return self._maxw_params + # make sure temperatures are zero + self._maxw_params["vth1"] = 0.0 + self._maxw_params["vth2"] = 0.0 + self._maxw_params["vth3"] = 0.0 @property def coords(self): @@ -699,10 +691,6 @@ def volume_form(self): return False @property - def equil(self) -> FluidEquilibriumWithB: - """Fluid background with B-field.""" - return self._equil - def velocity_jacobian_det(self, eta1, eta2, eta3, *v): """Jacobian determinant of the velocity coordinate transformation.""" return 1.0 @@ -730,3 +718,14 @@ def vth(self, eta1, eta2, eta3): def __call__(self, eta1, eta2, eta3): return self.n(eta1, eta2, eta3) + + @property + def add_perturbation(self) -> bool: + if not hasattr(self, "_add_perturbation"): + self._add_perturbation = True + return self._add_perturbation + + @add_perturbation.setter + def add_perturbation(self, new): + assert isinstance(new, bool) + self._add_perturbation = new diff --git a/src/struphy/kinetic_background/tests/test_base.py b/src/struphy/kinetic_background/tests/test_base.py index 8a2e89d28..09172c866 100644 --- a/src/struphy/kinetic_background/tests/test_base.py +++ b/src/struphy/kinetic_background/tests/test_base.py @@ -1,16 +1,16 @@ def test_kinetic_background_magics(show_plot=False): """Test the magic commands __sum__, __mul__ and __sub__ of the Maxwellian base class.""" - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from struphy.kinetic_background.maxwellians import Maxwellian3D Nel = [32, 1, 1] - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) - v1 = xp.linspace(-7.0, 7.0, 128) + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) + v1 = np.linspace(-7.0, 7.0, 128) m1_params = {"n": 0.5, "u1": 3.0} m2_params = {"n": 0.5, "u1": -3.0} @@ -22,11 +22,11 @@ def test_kinetic_background_magics(show_plot=False): m_rmul_int = 2 * m1 m_mul_int = m1 * 2 m_mul_float = 2.0 * m1 - m_mul_npint = xp.ones(1, dtype=int)[0] * m1 + m_mul_npint = np.ones(1, dtype=int)[0] * m1 m_sub = m1 - m2 # compare distribution function - meshgrids = xp.meshgrid(e1, e2, e3, v1, [0.0], [0.0]) + meshgrids = np.meshgrid(e1, e2, e3, v1, [0.0], [0.0]) m1_vals = m1(*meshgrids) m2_vals = m2(*meshgrids) @@ -38,15 +38,15 @@ def test_kinetic_background_magics(show_plot=False): m_mul_npint_vals = m_mul_npint(*meshgrids) m_sub_vals = m_sub(*meshgrids) - assert xp.allclose(m1_vals + m2_vals, m_add_vals) - assert xp.allclose(2 * m1_vals, m_rmul_int_vals) - assert xp.allclose(2 * m1_vals, m_mul_int_vals) - assert xp.allclose(2.0 * m1_vals, m_mul_float_vals) - assert xp.allclose(xp.ones(1, dtype=int)[0] * m1_vals, m_mul_npint_vals) - assert xp.allclose(m1_vals - m2_vals, m_sub_vals) + assert np.allclose(m1_vals + m2_vals, m_add_vals) + assert np.allclose(2 * m1_vals, m_rmul_int_vals) + assert np.allclose(2 * m1_vals, m_mul_int_vals) + assert np.allclose(2.0 * m1_vals, m_mul_float_vals) + assert np.allclose(np.ones(1, dtype=int)[0] * m1_vals, m_mul_npint_vals) + assert np.allclose(m1_vals - m2_vals, m_sub_vals) # compare first two moments - meshgrids = xp.meshgrid(e1, e2, e3) + meshgrids = np.meshgrid(e1, e2, e3) n1_vals = m1.n(*meshgrids) n2_vals = m2.n(*meshgrids) @@ -57,11 +57,11 @@ def test_kinetic_background_magics(show_plot=False): u_add1, u_add2, u_add3 = m_add.u(*meshgrids) n_sub_vals = m_sub.n(*meshgrids) - assert xp.allclose(n1_vals + n2_vals, n_add_vals) - assert xp.allclose(u11 + u21, u_add1) - assert xp.allclose(u12 + u22, u_add2) - assert xp.allclose(u13 + u23, u_add3) - assert xp.allclose(n1_vals - n2_vals, n_sub_vals) + assert np.allclose(n1_vals + n2_vals, n_add_vals) + assert np.allclose(u11 + u21, u_add1) + assert np.allclose(u12 + u22, u_add2) + assert np.allclose(u13 + u23, u_add3) + assert np.allclose(n1_vals - n2_vals, n_sub_vals) if show_plot: plt.figure(figsize=(12, 8)) diff --git a/src/struphy/kinetic_background/tests/test_maxwellians.py b/src/struphy/kinetic_background/tests/test_maxwellians.py index 4aaa0624a..b64a5bbe3 100644 --- a/src/struphy/kinetic_background/tests/test_maxwellians.py +++ b/src/struphy/kinetic_background/tests/test_maxwellians.py @@ -8,31 +8,31 @@ def test_maxwellian_3d_uniform(Nel, show_plot=False): Asserts that the results over the domain and velocity space correspond to the analytical computation. """ - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from struphy.kinetic_background.maxwellians import Maxwellian3D - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) # ========================================================== # ==== Test uniform non-shifted, isothermal Maxwellian ===== # ========================================================== maxwellian = Maxwellian3D(n=(2.0, None)) - meshgrids = xp.meshgrid(e1, e2, e3, [0.0], [0.0], [0.0]) + meshgrids = np.meshgrid(e1, e2, e3, [0.0], [0.0], [0.0]) # Test constant value at v=0 res = maxwellian(*meshgrids).squeeze() - assert xp.allclose(res, 2.0 / (2 * xp.pi) ** (3 / 2) + 0 * e1, atol=10e-10), ( - f"{res=},\n {2.0 / (2 * xp.pi) ** (3 / 2)}" + assert np.allclose(res, 2.0 / (2 * np.pi) ** (3 / 2) + 0 * e1, atol=10e-10), ( + f"{res=},\n {2.0 / (2 * np.pi) ** (3 / 2)}" ) # test Maxwellian profile in v - v1 = xp.linspace(-5, 5, 128) - meshgrids = xp.meshgrid( + v1 = np.linspace(-5, 5, 128) + meshgrids = np.meshgrid( [0.0], [0.0], [0.0], @@ -41,8 +41,8 @@ def test_maxwellian_3d_uniform(Nel, show_plot=False): [0.0], ) res = maxwellian(*meshgrids).squeeze() - res_ana = 2.0 * xp.exp(-(v1**2) / 2.0) / (2 * xp.pi) ** (3 / 2) - assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + res_ana = 2.0 * np.exp(-(v1**2) / 2.0) / (2 * np.pi) ** (3 / 2) + assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # ======================================================= # ===== Test non-zero shifts and thermal velocities ===== @@ -68,14 +68,14 @@ def test_maxwellian_3d_uniform(Nel, show_plot=False): # test Maxwellian profile in v for i in range(3): vs = [0, 0, 0] - vs[i] = xp.linspace(-5, 5, 128) - meshgrids = xp.meshgrid([0.0], [0.0], [0.0], *vs) + vs[i] = np.linspace(-5, 5, 128) + meshgrids = np.meshgrid([0.0], [0.0], [0.0], *vs) res = maxwellian(*meshgrids).squeeze() - res_ana = xp.exp(-((vs[0] - u1) ** 2) / (2 * vth1**2)) - res_ana *= xp.exp(-((vs[1] - u2) ** 2) / (2 * vth2**2)) - res_ana *= xp.exp(-((vs[2] - u3) ** 2) / (2 * vth3**2)) - res_ana *= n / ((2 * xp.pi) ** (3 / 2) * vth1 * vth2 * vth3) + res_ana = np.exp(-((vs[0] - u1) ** 2) / (2 * vth1**2)) + res_ana *= np.exp(-((vs[1] - u2) ** 2) / (2 * vth2**2)) + res_ana *= np.exp(-((vs[2] - u3) ** 2) / (2 * vth3**2)) + res_ana *= n / ((2 * np.pi) ** (3 / 2) * vth1 * vth2 * vth3) if show_plot: plt.plot(vs[i], res_ana, label="analytical") @@ -86,21 +86,21 @@ def test_maxwellian_3d_uniform(Nel, show_plot=False): plt.xlabel("v_" + str(i + 1)) plt.show() - assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana =}" + assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana =}" @pytest.mark.parametrize("Nel", [[64, 1, 1]]) def test_maxwellian_3d_perturbed(Nel, show_plot=False): """Tests the Maxwellian3D class for perturbations.""" - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from struphy.initial import perturbations from struphy.kinetic_background.maxwellians import Maxwellian3D - e1 = xp.linspace(0.0, 1.0, Nel[0]) - v1 = xp.linspace(-5.0, 5.0, 128) + e1 = np.linspace(0.0, 1.0, Nel[0]) + v1 = np.linspace(-5.0, 5.0, 128) # =============================================== # ===== Test cosine perturbation in density ===== @@ -112,10 +112,10 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): maxwellian = Maxwellian3D(n=(2.0, pert)) - meshgrids = xp.meshgrid(e1, [0.0], [0.0], [0.0], [0.0], [0.0]) + meshgrids = np.meshgrid(e1, [0.0], [0.0], [0.0], [0.0], [0.0]) res = maxwellian(*meshgrids).squeeze() - ana_res = (2.0 + amp * xp.cos(2 * xp.pi * mode * e1)) / (2 * xp.pi) ** (3 / 2) + ana_res = (2.0 + amp * np.cos(2 * np.pi * mode * e1)) / (2 * np.pi) ** (3 / 2) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -126,7 +126,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ============================================= # ===== Test cosine perturbation in shift ===== @@ -140,7 +140,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): maxwellian = Maxwellian3D(n=(n, None), u1=(u1, pert)) - meshgrids = xp.meshgrid( + meshgrids = np.meshgrid( e1, [0.0], [0.0], @@ -150,9 +150,9 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - shift = u1 + amp * xp.cos(2 * xp.pi * mode * e1) - ana_res = xp.exp(-((v1 - shift[:, None]) ** 2) / 2) - ana_res *= n / (2 * xp.pi) ** (3 / 2) + shift = u1 + amp * np.cos(2 * np.pi * mode * e1) + ana_res = np.exp(-((v1 - shift[:, None]) ** 2) / 2) + ana_res *= n / (2 * np.pi) ** (3 / 2) if show_plot: plt.figure(1) @@ -173,7 +173,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # =========================================== # ===== Test cosine perturbation in vth ===== @@ -187,7 +187,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): maxwellian = Maxwellian3D(n=(n, None), vth1=(vth1, pert)) - meshgrids = xp.meshgrid( + meshgrids = np.meshgrid( e1, [0.0], [0.0], @@ -197,9 +197,9 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - thermal = vth1 + amp * xp.cos(2 * xp.pi * mode * e1) - ana_res = xp.exp(-(v1**2) / (2.0 * thermal[:, None] ** 2)) - ana_res *= n / ((2 * xp.pi) ** (3 / 2) * thermal[:, None]) + thermal = vth1 + amp * np.cos(2 * np.pi * mode * e1) + ana_res = np.exp(-(v1**2) / (2.0 * thermal[:, None] ** 2)) + ana_res *= n / ((2 * np.pi) ** (3 / 2) * thermal[:, None]) if show_plot: plt.figure(1) @@ -220,7 +220,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ============================================= # ===== Test ITPA perturbation in density ===== @@ -232,10 +232,10 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): maxwellian = Maxwellian3D(n=(0.0, pert)) - meshgrids = xp.meshgrid(e1, [0.0], [0.0], [0.0], [0.0], [0.0]) + meshgrids = np.meshgrid(e1, [0.0], [0.0], [0.0], [0.0], [0.0]) res = maxwellian(*meshgrids).squeeze() - ana_res = n0 * c[3] * xp.exp(-c[2] / c[1] * xp.tanh((e1 - c[0]) / c[2])) / (2 * xp.pi) ** (3 / 2) + ana_res = n0 * c[3] * np.exp(-c[2] / c[1] * np.tanh((e1 - c[0]) / c[2])) / (2 * np.pi) ** (3 / 2) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -246,7 +246,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" @pytest.mark.parametrize("Nel", [[8, 11, 12]]) @@ -255,8 +255,8 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): import inspect - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from struphy.fields_background import equils from struphy.fields_background.base import FluidEquilibrium @@ -265,75 +265,65 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): from struphy.initial.base import Perturbation from struphy.kinetic_background.maxwellians import Maxwellian3D - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) v1 = [0.0] v2 = [0.0, -1.0] v3 = [0.0, -1.0, -1.3] - meshgrids = xp.meshgrid(e1, e2, e3, v1, v2, v3, indexing="ij") - e_meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") + meshgrids = np.meshgrid(e1, e2, e3, v1, v2, v3, indexing="ij") + e_meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") n_mks = 17 - e1_fl = xp.random.rand(n_mks) - e2_fl = xp.random.rand(n_mks) - e3_fl = xp.random.rand(n_mks) - v1_fl = xp.random.randn(n_mks) - v2_fl = xp.random.randn(n_mks) - v3_fl = xp.random.randn(n_mks) + e1_fl = np.random.rand(n_mks) + e2_fl = np.random.rand(n_mks) + e3_fl = np.random.rand(n_mks) + v1_fl = np.random.randn(n_mks) + v2_fl = np.random.randn(n_mks) + v3_fl = np.random.randn(n_mks) args_fl = [e1_fl, e2_fl, e3_fl, v1_fl, v2_fl, v3_fl] - e_args_fl = xp.concatenate((e1_fl[:, None], e2_fl[:, None], e3_fl[:, None]), axis=1) + e_args_fl = np.concatenate((e1_fl[:, None], e2_fl[:, None], e3_fl[:, None]), axis=1) for key, val in inspect.getmembers(equils): if inspect.isclass(val) and val.__module__ == equils.__name__: - print(f"{key =}") + print(f"{key = }") if "DESCequilibrium" in key and not with_desc: - print(f"Attention: {with_desc =}, DESC not tested here !!") + print(f"Attention: {with_desc = }, DESC not tested here !!") continue if "GVECequilibrium" in key: - print("Attention: flat (marker) evaluation not tested for GVEC at the moment.") + print(f"Attention: flat (marker) evaluation not tested for GVEC at the moment.") mhd_equil = val() assert isinstance(mhd_equil, FluidEquilibrium) - print(f"{mhd_equil.params =}") + print(f"{mhd_equil.params = }") if "AdhocTorus" in key: mhd_equil.domain = domains.HollowTorus( - a1=1e-3, - a2=mhd_equil.params["a"], - R0=mhd_equil.params["R0"], - tor_period=1, + a1=1e-3, a2=mhd_equil.params["a"], R0=mhd_equil.params["R0"], tor_period=1 ) elif "EQDSKequilibrium" in key: mhd_equil.domain = domains.Tokamak(equilibrium=mhd_equil) elif "CircularTokamak" in key: mhd_equil.domain = domains.HollowTorus( - a1=1e-3, - a2=mhd_equil.params["a"], - R0=mhd_equil.params["R0"], - tor_period=1, + a1=1e-3, a2=mhd_equil.params["a"], R0=mhd_equil.params["R0"], tor_period=1 ) elif "HomogenSlab" in key: mhd_equil.domain = domains.Cuboid() elif "ShearedSlab" in key: mhd_equil.domain = domains.Cuboid( r1=mhd_equil.params["a"], - r2=mhd_equil.params["a"] * 2 * xp.pi, - r3=mhd_equil.params["R0"] * 2 * xp.pi, + r2=mhd_equil.params["a"] * 2 * np.pi, + r3=mhd_equil.params["R0"] * 2 * np.pi, ) elif "ShearFluid" in key: mhd_equil.domain = domains.Cuboid( - r1=mhd_equil.params["a"], - r2=mhd_equil.params["b"], - r3=mhd_equil.params["c"], + r1=mhd_equil.params["a"], r2=mhd_equil.params["b"], r3=mhd_equil.params["c"] ) elif "ScrewPinch" in key: mhd_equil.domain = domains.HollowCylinder( - a1=1e-3, - a2=mhd_equil.params["a"], - Lz=mhd_equil.params["R0"] * 2 * xp.pi, + a1=1e-3, a2=mhd_equil.params["a"], Lz=mhd_equil.params["R0"] * 2 * np.pi ) else: try: @@ -363,59 +353,51 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): # test meshgrid evaluation n0 = mhd_equil.n0(*e_meshgrids) - assert xp.allclose( - maxwellian(*meshgrids)[:, :, :, 0, 0, 0], - n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 0, 0], + assert np.allclose( + maxwellian(*meshgrids)[:, :, :, 0, 0, 0], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 0, 0] ) - assert xp.allclose( - maxwellian(*meshgrids)[:, :, :, 0, 1, 2], - n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 1, 2], + assert np.allclose( + maxwellian(*meshgrids)[:, :, :, 0, 1, 2], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 1, 2] ) # test flat evaluation if "GVECequilibrium" in key: pass else: - assert xp.allclose(maxwellian(*args_fl), mhd_equil.n0(e_args_fl) * maxwellian_1(*args_fl)) - assert xp.allclose(maxwellian.n(e1_fl, e2_fl, e3_fl), mhd_equil.n0(e_args_fl)) + assert np.allclose(maxwellian(*args_fl), mhd_equil.n0(e_args_fl) * maxwellian_1(*args_fl)) + assert np.allclose(maxwellian.n(e1_fl, e2_fl, e3_fl), mhd_equil.n0(e_args_fl)) u_maxw = maxwellian.u(e1_fl, e2_fl, e3_fl) u_eq = mhd_equil.u_cart(e_args_fl)[0] - assert all([xp.allclose(m, e) for m, e in zip(u_maxw, u_eq)]) + assert all([np.allclose(m, e) for m, e in zip(u_maxw, u_eq)]) vth_maxw = maxwellian.vth(e1_fl, e2_fl, e3_fl) - vth_eq = xp.sqrt(mhd_equil.p0(e_args_fl) / mhd_equil.n0(e_args_fl)) - assert all([xp.allclose(v, vth_eq) for v in vth_maxw]) + vth_eq = np.sqrt(mhd_equil.p0(e_args_fl) / mhd_equil.n0(e_args_fl)) + assert all([np.allclose(v, vth_eq) for v in vth_maxw]) # plotting moments if show_plot: - plt.figure(f"{mhd_equil =}", figsize=(24, 16)) + plt.figure(f"{mhd_equil = }", figsize=(24, 16)) x, y, z = mhd_equil.domain(*e_meshgrids) # density plots n_cart = mhd_equil.domain.push(maxwellian.n, *e_meshgrids) - levels = xp.linspace(xp.min(n_cart) - 1e-10, xp.max(n_cart), 20) + levels = np.linspace(np.min(n_cart) - 1e-10, np.max(n_cart), 20) plt.subplot(2, 5, 1) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - z[:, Nel[1] // 2 - 1, :], - n_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], z[:, Nel[1] // 2 - 1, :], n_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - y[:, Nel[1] // 2 - 1, :], - n_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], y[:, Nel[1] // 2 - 1, :], n_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("y") @@ -438,7 +420,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): # velocity plots us = maxwellian.u(*e_meshgrids) for i, u in enumerate(us): - levels = xp.linspace(xp.min(u) - 1e-10, xp.max(u), 20) + levels = np.linspace(np.min(u) - 1e-10, np.max(u), 20) plt.subplot(2, 5, 2 + i) if "Slab" in key or "Pinch" in key: @@ -471,32 +453,26 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): vth = maxwellian.vth(*e_meshgrids)[0] vth_cart = mhd_equil.domain.push(vth, *e_meshgrids) - levels = xp.linspace(xp.min(vth_cart) - 1e-10, xp.max(vth_cart), 20) + levels = np.linspace(np.min(vth_cart) - 1e-10, np.max(vth_cart), 20) plt.subplot(2, 5, 5) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], vth_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - z[:, Nel[1] // 2 - 1, :], - vth_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], z[:, Nel[1] // 2 - 1, :], vth_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], vth_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - y[:, Nel[1] // 2 - 1, :], - vth_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], y[:, Nel[1] // 2 - 1, :], vth_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("y") plt.axis("equal") plt.colorbar() - plt.title("Maxwellian thermal velocity $v_t$, top view (e1-e3)") + plt.title(f"Maxwellian thermal velocity $v_t$, top view (e1-e3)") plt.subplot(2, 5, 10) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, :, 0], y[:, :, 0], vth_cart[:, :, 0], levels=levels) @@ -508,7 +484,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("z") plt.axis("equal") plt.colorbar() - plt.title("Maxwellian thermal velocity $v_t$, poloidal view (e1-e2)") + plt.title(f"Maxwellian thermal velocity $v_t$, poloidal view (e1-e2)") plt.show() @@ -520,7 +496,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): if inspect.isclass(val_2) and val_2.__module__ == perturbations.__name__: pert = val_2() assert isinstance(pert, Perturbation) - print(f"{pert =}") + print(f"{pert = }") if isinstance(pert, perturbations.Noise): continue @@ -552,13 +528,13 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): vth3=(0.0, pert), ) - assert xp.allclose(maxwellian_zero_bckgr.n(*e_meshgrids), pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[0], pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[1], pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[2], pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[0], pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[1], pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[2], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.n(*e_meshgrids), pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[0], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[1], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[2], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[0], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[1], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[2], pert(*e_meshgrids)) # plotting perturbations if show_plot: # and 'Torus' in key_2: @@ -568,26 +544,20 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): # density plots n_cart = mhd_equil.domain.push(maxwellian_zero_bckgr.n, *e_meshgrids) - levels = xp.linspace(xp.min(n_cart) - 1e-10, xp.max(n_cart), 20) + levels = np.linspace(np.min(n_cart) - 1e-10, np.max(n_cart), 20) plt.subplot(2, 5, 1) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - z[:, Nel[1] // 2, :], - n_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], z[:, Nel[1] // 2, :], n_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - y[:, Nel[1] // 2, :], - n_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], y[:, Nel[1] // 2, :], n_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("y") @@ -610,26 +580,20 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): # velocity plots us = maxwellian_zero_bckgr.u(*e_meshgrids) for i, u in enumerate(us): - levels = xp.linspace(xp.min(u) - 1e-10, xp.max(u), 20) + levels = np.linspace(np.min(u) - 1e-10, np.max(u), 20) plt.subplot(2, 5, 2 + i) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], u[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - z[:, Nel[1] // 2, :], - u[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], z[:, Nel[1] // 2, :], u[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], u[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - y[:, Nel[1] // 2, :], - u[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], y[:, Nel[1] // 2, :], u[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("y") @@ -653,7 +617,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): vth = maxwellian_zero_bckgr.vth(*e_meshgrids)[0] vth_cart = mhd_equil.domain.push(vth, *e_meshgrids) - levels = xp.linspace(xp.min(vth_cart) - 1e-10, xp.max(vth_cart), 20) + levels = np.linspace(np.min(vth_cart) - 1e-10, np.max(vth_cart), 20) plt.subplot(2, 5, 5) if "Slab" in key or "Pinch" in key: @@ -678,7 +642,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("y") plt.axis("equal") plt.colorbar() - plt.title("Maxwellian perturbed thermal velocity $v_t$, top view (e1-e3)") + plt.title(f"Maxwellian perturbed thermal velocity $v_t$, top view (e1-e3)") plt.subplot(2, 5, 10) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, :, 0], y[:, :, 0], vth_cart[:, :, 0], levels=levels) @@ -690,7 +654,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("z") plt.axis("equal") plt.colorbar() - plt.title("Maxwellian perturbed thermal velocity $v_t$, poloidal view (e1-e2)") + plt.title(f"Maxwellian perturbed thermal velocity $v_t$, poloidal view (e1-e2)") plt.show() @@ -702,34 +666,34 @@ def test_maxwellian_2d_uniform(Nel, show_plot=False): Asserts that the results over the domain and velocity space correspond to the analytical computation. """ - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from struphy.kinetic_background.maxwellians import GyroMaxwellian2D - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) # =========================================================== # ===== Test uniform non-shifted, isothermal Maxwellian ===== # =========================================================== maxwellian = GyroMaxwellian2D(n=(2.0, None), volume_form=False) - meshgrids = xp.meshgrid(e1, e2, e3, [0.01], [0.01]) + meshgrids = np.meshgrid(e1, e2, e3, [0.01], [0.01]) # Test constant value at v_para = v_perp = 0.01 res = maxwellian(*meshgrids).squeeze() - assert xp.allclose(res, 2.0 / (2 * xp.pi) ** (1 / 2) * xp.exp(-(0.01**2)) + 0 * e1, atol=10e-10), ( - f"{res=},\n {2.0 / (2 * xp.pi) ** (3 / 2)}" + assert np.allclose(res, 2.0 / (2 * np.pi) ** (1 / 2) * np.exp(-(0.01**2)) + 0 * e1, atol=10e-10), ( + f"{res=},\n {2.0 / (2 * np.pi) ** (3 / 2)}" ) # test Maxwellian profile in v - v_para = xp.linspace(-5, 5, 64) - v_perp = xp.linspace(0, 2.5, 64) - vpara, vperp = xp.meshgrid(v_para, v_perp) + v_para = np.linspace(-5, 5, 64) + v_perp = np.linspace(0, 2.5, 64) + vpara, vperp = np.meshgrid(v_para, v_perp) - meshgrids = xp.meshgrid( + meshgrids = np.meshgrid( [0.0], [0.0], [0.0], @@ -738,8 +702,8 @@ def test_maxwellian_2d_uniform(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - res_ana = 2.0 / (2 * xp.pi) ** (1 / 2) * xp.exp(-(vpara.T**2) / 2.0 - vperp.T**2 / 2.0) - assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + res_ana = 2.0 / (2 * np.pi) ** (1 / 2) * np.exp(-(vpara.T**2) / 2.0 - vperp.T**2 / 2.0) + assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # ======================================================= # ===== Test non-zero shifts and thermal velocities ===== @@ -760,16 +724,16 @@ def test_maxwellian_2d_uniform(Nel, show_plot=False): ) # test Maxwellian profile in v - v_para = xp.linspace(-5, 5, 64) - v_perp = xp.linspace(0, 2.5, 64) - vpara, vperp = xp.meshgrid(v_para, v_perp) + v_para = np.linspace(-5, 5, 64) + v_perp = np.linspace(0, 2.5, 64) + vpara, vperp = np.meshgrid(v_para, v_perp) - meshgrids = xp.meshgrid([0.0], [0.0], [0.0], v_para, v_perp) + meshgrids = np.meshgrid([0.0], [0.0], [0.0], v_para, v_perp) res = maxwellian(*meshgrids).squeeze() - res_ana = xp.exp(-((vpara.T - u_para) ** 2) / (2 * vth_para**2)) - res_ana *= xp.exp(-((vperp.T - u_perp) ** 2) / (2 * vth_perp**2)) - res_ana *= n / ((2 * xp.pi) ** (1 / 2) * vth_para * vth_perp**2) + res_ana = np.exp(-((vpara.T - u_para) ** 2) / (2 * vth_para**2)) + res_ana *= np.exp(-((vperp.T - u_perp) ** 2) / (2 * vth_perp**2)) + res_ana *= n / ((2 * np.pi) ** (1 / 2) * vth_para * vth_perp**2) if show_plot: plt.plot(v_para, res_ana[:, 32], label="analytical") @@ -788,22 +752,22 @@ def test_maxwellian_2d_uniform(Nel, show_plot=False): plt.xlabel("v_" + "perp") plt.show() - assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana =}" + assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana =}" @pytest.mark.parametrize("Nel", [[6, 1, 1]]) def test_maxwellian_2d_perturbed(Nel, show_plot=False): """Tests the GyroMaxwellian2D class for perturbations.""" - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from struphy.initial import perturbations from struphy.kinetic_background.maxwellians import GyroMaxwellian2D - e1 = xp.linspace(0.0, 1.0, Nel[0]) - v1 = xp.linspace(-5.0, 5.0, 128) - v2 = xp.linspace(0, 2.5, 128) + e1 = np.linspace(0.0, 1.0, Nel[0]) + v1 = np.linspace(-5.0, 5.0, 128) + v2 = np.linspace(0, 2.5, 128) # =============================================== # ===== Test cosine perturbation in density ===== @@ -815,11 +779,11 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): maxwellian = GyroMaxwellian2D(n=(2.0, pert), volume_form=False) v_perp = 0.1 - meshgrids = xp.meshgrid(e1, [0.0], [0.0], [0.0], v_perp) + meshgrids = np.meshgrid(e1, [0.0], [0.0], [0.0], v_perp) res = maxwellian(*meshgrids).squeeze() - ana_res = (2.0 + amp * xp.cos(2 * xp.pi * mode * e1)) / (2 * xp.pi) ** (1 / 2) - ana_res *= xp.exp(-(v_perp**2) / 2) + ana_res = (2.0 + amp * np.cos(2 * np.pi * mode * e1)) / (2 * np.pi) ** (1 / 2) + ana_res *= np.exp(-(v_perp**2) / 2) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -830,7 +794,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ==================================================== # ===== Test cosine perturbation in shift (para) ===== @@ -848,12 +812,12 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): ) v_perp = 0.1 - meshgrids = xp.meshgrid(e1, [0.0], [0.0], v1, v_perp) + meshgrids = np.meshgrid(e1, [0.0], [0.0], v1, v_perp) res = maxwellian(*meshgrids).squeeze() - shift = u_para + amp * xp.cos(2 * xp.pi * mode * e1) - ana_res = xp.exp(-((v1 - shift[:, None]) ** 2) / 2.0) - ana_res *= n / (2 * xp.pi) ** (1 / 2) * xp.exp(-(v_perp**2) / 2.0) + shift = u_para + amp * np.cos(2 * np.pi * mode * e1) + ana_res = np.exp(-((v1 - shift[:, None]) ** 2) / 2.0) + ana_res *= n / (2 * np.pi) ** (1 / 2) * np.exp(-(v_perp**2) / 2.0) if show_plot: plt.figure(1) @@ -874,7 +838,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ==================================================== # ===== Test cosine perturbation in shift (perp) ===== @@ -891,12 +855,12 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): volume_form=False, ) - meshgrids = xp.meshgrid(e1, [0.0], [0.0], 0.0, v2) + meshgrids = np.meshgrid(e1, [0.0], [0.0], 0.0, v2) res = maxwellian(*meshgrids).squeeze() - shift = u_perp + amp * xp.cos(2 * xp.pi * mode * e1) - ana_res = xp.exp(-((v2 - shift[:, None]) ** 2) / 2.0) - ana_res *= n / (2 * xp.pi) ** (1 / 2) + shift = u_perp + amp * np.cos(2 * np.pi * mode * e1) + ana_res = np.exp(-((v2 - shift[:, None]) ** 2) / 2.0) + ana_res *= n / (2 * np.pi) ** (1 / 2) if show_plot: plt.figure(1) @@ -917,7 +881,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ================================================== # ===== Test cosine perturbation in vth (para) ===== @@ -935,7 +899,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): ) v_perp = 0.1 - meshgrids = xp.meshgrid( + meshgrids = np.meshgrid( e1, [0.0], [0.0], @@ -944,10 +908,10 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - thermal = vth_para + amp * xp.cos(2 * xp.pi * mode * e1) - ana_res = xp.exp(-(v1**2) / (2.0 * thermal[:, None] ** 2)) - ana_res *= n / ((2 * xp.pi) ** (1 / 2) * thermal[:, None]) - ana_res *= xp.exp(-(v_perp**2) / 2.0) + thermal = vth_para + amp * np.cos(2 * np.pi * mode * e1) + ana_res = np.exp(-(v1**2) / (2.0 * thermal[:, None] ** 2)) + ana_res *= n / ((2 * np.pi) ** (1 / 2) * thermal[:, None]) + ana_res *= np.exp(-(v_perp**2) / 2.0) if show_plot: plt.figure(1) @@ -968,7 +932,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ================================================== # ===== Test cosine perturbation in vth (perp) ===== @@ -985,7 +949,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): volume_form=False, ) - meshgrids = xp.meshgrid( + meshgrids = np.meshgrid( e1, [0.0], [0.0], @@ -994,9 +958,9 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - thermal = vth_perp + amp * xp.cos(2 * xp.pi * mode * e1) - ana_res = xp.exp(-(v2**2) / (2.0 * thermal[:, None] ** 2)) - ana_res *= n / ((2 * xp.pi) ** (1 / 2) * thermal[:, None] ** 2) + thermal = vth_perp + amp * np.cos(2 * np.pi * mode * e1) + ana_res = np.exp(-(v2**2) / (2.0 * thermal[:, None] ** 2)) + ana_res *= n / ((2 * np.pi) ** (1 / 2) * thermal[:, None] ** 2) if show_plot: plt.figure(1) @@ -1017,7 +981,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ============================================= # ===== Test ITPA perturbation in density ===== @@ -1029,11 +993,11 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): maxwellian = GyroMaxwellian2D(n=(0.0, pert), volume_form=False) v_perp = 0.1 - meshgrids = xp.meshgrid(e1, [0.0], [0.0], [0.0], v_perp) + meshgrids = np.meshgrid(e1, [0.0], [0.0], [0.0], v_perp) res = maxwellian(*meshgrids).squeeze() - ana_res = n0 * c[3] * xp.exp(-c[2] / c[1] * xp.tanh((e1 - c[0]) / c[2])) / (2 * xp.pi) ** (1 / 2) - ana_res *= xp.exp(-(v_perp**2) / 2.0) + ana_res = n0 * c[3] * np.exp(-c[2] / c[1] * np.tanh((e1 - c[0]) / c[2])) / (2 * np.pi) ** (1 / 2) + ana_res *= np.exp(-(v_perp**2) / 2.0) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -1044,7 +1008,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" @pytest.mark.parametrize("Nel", [[8, 12, 12]]) @@ -1053,8 +1017,8 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): import inspect - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from struphy.fields_background import equils from struphy.fields_background.base import FluidEquilibriumWithB @@ -1063,75 +1027,65 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): from struphy.initial.base import Perturbation from struphy.kinetic_background.maxwellians import GyroMaxwellian2D - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) v1 = [0.0] v2 = [0.0, 2.0] - meshgrids = xp.meshgrid(e1, e2, e3, v1, v2, indexing="ij") - e_meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") + meshgrids = np.meshgrid(e1, e2, e3, v1, v2, indexing="ij") + e_meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") n_mks = 17 - e1_fl = xp.random.rand(n_mks) - e2_fl = xp.random.rand(n_mks) - e3_fl = xp.random.rand(n_mks) - v1_fl = xp.random.randn(n_mks) - v2_fl = xp.random.rand(n_mks) + e1_fl = np.random.rand(n_mks) + e2_fl = np.random.rand(n_mks) + e3_fl = np.random.rand(n_mks) + v1_fl = np.random.randn(n_mks) + v2_fl = np.random.rand(n_mks) args_fl = [e1_fl, e2_fl, e3_fl, v1_fl, v2_fl] - e_args_fl = xp.concatenate((e1_fl[:, None], e2_fl[:, None], e3_fl[:, None]), axis=1) + e_args_fl = np.concatenate((e1_fl[:, None], e2_fl[:, None], e3_fl[:, None]), axis=1) for key, val in inspect.getmembers(equils): if inspect.isclass(val) and val.__module__ == equils.__name__: - print(f"{key =}") + print(f"{key = }") if "DESCequilibrium" in key and not with_desc: - print(f"Attention: {with_desc =}, DESC not tested here !!") + print(f"Attention: {with_desc = }, DESC not tested here !!") continue if "GVECequilibrium" in key: - print("Attention: flat (marker) evaluation not tested for GVEC at the moment.") + print(f"Attention: flat (marker) evaluation not tested for GVEC at the moment.") mhd_equil = val() if not isinstance(mhd_equil, FluidEquilibriumWithB): continue - print(f"{mhd_equil.params =}") + print(f"{mhd_equil.params = }") if "AdhocTorus" in key: mhd_equil.domain = domains.HollowTorus( - a1=1e-3, - a2=mhd_equil.params["a"], - R0=mhd_equil.params["R0"], - tor_period=1, + a1=1e-3, a2=mhd_equil.params["a"], R0=mhd_equil.params["R0"], tor_period=1 ) elif "EQDSKequilibrium" in key: mhd_equil.domain = domains.Tokamak(equilibrium=mhd_equil) elif "CircularTokamak" in key: mhd_equil.domain = domains.HollowTorus( - a1=1e-3, - a2=mhd_equil.params["a"], - R0=mhd_equil.params["R0"], - tor_period=1, + a1=1e-3, a2=mhd_equil.params["a"], R0=mhd_equil.params["R0"], tor_period=1 ) elif "HomogenSlab" in key: mhd_equil.domain = domains.Cuboid() elif "ShearedSlab" in key: mhd_equil.domain = domains.Cuboid( r1=mhd_equil.params["a"], - r2=mhd_equil.params["a"] * 2 * xp.pi, - r3=mhd_equil.params["R0"] * 2 * xp.pi, + r2=mhd_equil.params["a"] * 2 * np.pi, + r3=mhd_equil.params["R0"] * 2 * np.pi, ) elif "ShearFluid" in key: mhd_equil.domain = domains.Cuboid( - r1=mhd_equil.params["a"], - r2=mhd_equil.params["b"], - r3=mhd_equil.params["c"], + r1=mhd_equil.params["a"], r2=mhd_equil.params["b"], r3=mhd_equil.params["c"] ) elif "ScrewPinch" in key: mhd_equil.domain = domains.HollowCylinder( - a1=1e-3, - a2=mhd_equil.params["a"], - Lz=mhd_equil.params["R0"] * 2 * xp.pi, + a1=1e-3, a2=mhd_equil.params["a"], Lz=mhd_equil.params["R0"] * 2 * np.pi ) else: try: @@ -1157,56 +1111,50 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): # test meshgrid evaluation n0 = mhd_equil.n0(*e_meshgrids) - assert xp.allclose(maxwellian(*meshgrids)[:, :, :, 0, 0], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 0]) + assert np.allclose(maxwellian(*meshgrids)[:, :, :, 0, 0], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 0]) - assert xp.allclose(maxwellian(*meshgrids)[:, :, :, 0, 1], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 1]) + assert np.allclose(maxwellian(*meshgrids)[:, :, :, 0, 1], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 1]) # test flat evaluation if "GVECequilibrium" in key: pass else: - assert xp.allclose(maxwellian(*args_fl), mhd_equil.n0(e_args_fl) * maxwellian_1(*args_fl)) - assert xp.allclose(maxwellian.n(e1_fl, e2_fl, e3_fl), mhd_equil.n0(e_args_fl)) + assert np.allclose(maxwellian(*args_fl), mhd_equil.n0(e_args_fl) * maxwellian_1(*args_fl)) + assert np.allclose(maxwellian.n(e1_fl, e2_fl, e3_fl), mhd_equil.n0(e_args_fl)) u_maxw = maxwellian.u(e1_fl, e2_fl, e3_fl) tmp_jv = mhd_equil.jv(e_args_fl) / mhd_equil.n0(e_args_fl) tmp_unit_b1 = mhd_equil.unit_b1(e_args_fl) # j_parallel = jv.b1 j_para = sum([ji * bi for ji, bi in zip(tmp_jv, tmp_unit_b1)]) - assert xp.allclose(u_maxw[0], j_para) + assert np.allclose(u_maxw[0], j_para) vth_maxw = maxwellian.vth(e1_fl, e2_fl, e3_fl) - vth_eq = xp.sqrt(mhd_equil.p0(e_args_fl) / mhd_equil.n0(e_args_fl)) - assert all([xp.allclose(v, vth_eq) for v in vth_maxw]) + vth_eq = np.sqrt(mhd_equil.p0(e_args_fl) / mhd_equil.n0(e_args_fl)) + assert all([np.allclose(v, vth_eq) for v in vth_maxw]) # plotting moments if show_plot: - plt.figure(f"{mhd_equil =}", figsize=(24, 16)) + plt.figure(f"{mhd_equil = }", figsize=(24, 16)) x, y, z = mhd_equil.domain(*e_meshgrids) # density plots n_cart = mhd_equil.domain.push(maxwellian.n, *e_meshgrids) - levels = xp.linspace(xp.min(n_cart) - 1e-10, xp.max(n_cart), 20) + levels = np.linspace(np.min(n_cart) - 1e-10, np.max(n_cart), 20) plt.subplot(2, 4, 1) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - z[:, Nel[1] // 2 - 1, :], - n_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], z[:, Nel[1] // 2 - 1, :], n_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - y[:, Nel[1] // 2 - 1, :], - n_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], y[:, Nel[1] // 2 - 1, :], n_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("y") @@ -1229,7 +1177,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): # velocity plots us = maxwellian.u(*e_meshgrids) for i, u in enumerate(us[:1]): - levels = xp.linspace(xp.min(u) - 1e-10, xp.max(u), 20) + levels = np.linspace(np.min(u) - 1e-10, np.max(u), 20) plt.subplot(2, 4, 2 + i) if "Slab" in key or "Pinch" in key: @@ -1262,32 +1210,26 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): vth = maxwellian.vth(*e_meshgrids)[0] vth_cart = mhd_equil.domain.push(vth, *e_meshgrids) - levels = xp.linspace(xp.min(vth_cart) - 1e-10, xp.max(vth_cart), 20) + levels = np.linspace(np.min(vth_cart) - 1e-10, np.max(vth_cart), 20) plt.subplot(2, 4, 4) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], vth_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - z[:, Nel[1] // 2 - 1, :], - vth_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], z[:, Nel[1] // 2 - 1, :], vth_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], vth_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - y[:, Nel[1] // 2 - 1, :], - vth_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], y[:, Nel[1] // 2 - 1, :], vth_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("y") plt.axis("equal") plt.colorbar() - plt.title("Maxwellian thermal velocity $v_t$, top view (e1-e3)") + plt.title(f"Maxwellian thermal velocity $v_t$, top view (e1-e3)") plt.subplot(2, 4, 8) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, :, 0], y[:, :, 0], vth_cart[:, :, 0], levels=levels) @@ -1299,7 +1241,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("z") plt.axis("equal") plt.colorbar() - plt.title("Maxwellian density $v_t$, poloidal view (e1-e2)") + plt.title(f"Maxwellian density $v_t$, poloidal view (e1-e2)") plt.show() @@ -1308,7 +1250,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): for key_2, val_2 in inspect.getmembers(perturbations): if inspect.isclass(val_2) and val_2.__module__ == perturbations.__name__: pert = val_2() - print(f"{pert =}") + print(f"{pert = }") assert isinstance(pert, Perturbation) if isinstance(pert, perturbations.Noise): @@ -1339,11 +1281,11 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): volume_form=False, ) - assert xp.allclose(maxwellian_zero_bckgr.n(*e_meshgrids), pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[0], pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[1], pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[0], pert(*e_meshgrids)) - assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[1], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.n(*e_meshgrids), pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[0], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[1], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[0], pert(*e_meshgrids)) + assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[1], pert(*e_meshgrids)) # plotting perturbations if show_plot and "EQDSKequilibrium" in key: # and 'Torus' in key_2: @@ -1353,26 +1295,20 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): # density plots n_cart = mhd_equil.domain.push(maxwellian_zero_bckgr.n, *e_meshgrids) - levels = xp.linspace(xp.min(n_cart) - 1e-10, xp.max(n_cart), 20) + levels = np.linspace(np.min(n_cart) - 1e-10, np.max(n_cart), 20) plt.subplot(2, 4, 1) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - z[:, Nel[1] // 2, :], - n_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], z[:, Nel[1] // 2, :], n_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - y[:, Nel[1] // 2, :], - n_cart[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], y[:, Nel[1] // 2, :], n_cart[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("y") @@ -1395,26 +1331,20 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): # velocity plots us = maxwellian_zero_bckgr.u(*e_meshgrids) for i, u in enumerate(us): - levels = xp.linspace(xp.min(u) - 1e-10, xp.max(u), 20) + levels = np.linspace(np.min(u) - 1e-10, np.max(u), 20) plt.subplot(2, 4, 2 + i) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], u[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - z[:, Nel[1] // 2, :], - u[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], z[:, Nel[1] // 2, :], u[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], u[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], - y[:, Nel[1] // 2, :], - u[:, Nel[1] // 2, :], - levels=levels, + x[:, Nel[1] // 2, :], y[:, Nel[1] // 2, :], u[:, Nel[1] // 2, :], levels=levels ) plt.xlabel("x") plt.ylabel("y") @@ -1438,7 +1368,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): vth = maxwellian_zero_bckgr.vth(*e_meshgrids)[0] vth_cart = mhd_equil.domain.push(vth, *e_meshgrids) - levels = xp.linspace(xp.min(vth_cart) - 1e-10, xp.max(vth_cart), 20) + levels = np.linspace(np.min(vth_cart) - 1e-10, np.max(vth_cart), 20) plt.subplot(2, 4, 4) if "Slab" in key or "Pinch" in key: @@ -1463,7 +1393,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("y") plt.axis("equal") plt.colorbar() - plt.title("Maxwellian perturbed thermal velocity $v_t$, top view (e1-e3)") + plt.title(f"Maxwellian perturbed thermal velocity $v_t$, top view (e1-e3)") plt.subplot(2, 4, 8) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, :, 0], y[:, :, 0], vth_cart[:, :, 0], levels=levels) @@ -1475,7 +1405,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("z") plt.axis("equal") plt.colorbar() - plt.title("Maxwellian perturbed density $v_t$, poloidal view (e1-e2)") + plt.title(f"Maxwellian perturbed density $v_t$, poloidal view (e1-e2)") plt.show() @@ -1487,19 +1417,19 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): Asserts that the results over the domain and velocity space correspond to the analytical computation. """ - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from struphy.fields_background import equils from struphy.geometry import domains from struphy.initial import perturbations from struphy.kinetic_background.maxwellians import CanonicalMaxwellian - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) - eta_meshgrid = xp.meshgrid(e1, e2, e3) + eta_meshgrid = np.meshgrid(e1, e2, e3) v_para = 0.01 v_perp = 0.01 @@ -1546,7 +1476,7 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): psi = mhd_equil.psi_r(r) psic = psi - epsilon * B0 * R0 / absB * v_para - psic += epsilon * xp.sign(v_para) * xp.sqrt(2 * (energy - mu * B0)) * R0 * xp.heaviside(energy - mu * B0, 0) + psic += epsilon * np.sign(v_para) * np.sqrt(2 * (energy - mu * B0)) * R0 * np.heaviside(energy - mu * B0, 0) # =========================================================== # ===== Test uniform, isothermal canonical Maxwellian ===== @@ -1560,14 +1490,14 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): res_ana = ( maxw_params["n"] * 2 - * xp.sqrt(energy / xp.pi) + * np.sqrt(energy / np.pi) / maxw_params["vth"] ** 3 - * xp.exp(-energy / maxw_params["vth"] ** 2) + * np.exp(-energy / maxw_params["vth"] ** 2) ) - assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # test canonical Maxwellian profile in v_para - v_para = xp.linspace(-5, 5, 64) + v_para = np.linspace(-5, 5, 64) v_perp = 0.1 absB = mhd_equil.absB0(0.0, 0.0, 0.0)[0, 0, 0] @@ -1584,18 +1514,18 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): psi = mhd_equil.psi_r(r) psic = psi - epsilon * B0 * R0 / absB * v_para - psic += epsilon * xp.sign(v_para) * xp.sqrt(2 * (energy - mu * B0)) * R0 * xp.heaviside(energy - mu * B0, 0) + psic += epsilon * np.sign(v_para) * np.sqrt(2 * (energy - mu * B0)) * R0 * np.heaviside(energy - mu * B0, 0) - com_meshgrids = xp.meshgrid(energy, mu, psic) + com_meshgrids = np.meshgrid(energy, mu, psic) res = maxwellian(*com_meshgrids).squeeze() res_ana = ( maxw_params["n"] * 2 - * xp.sqrt(com_meshgrids[0] / xp.pi) + * np.sqrt(com_meshgrids[0] / np.pi) / maxw_params["vth"] ** 3 - * xp.exp(-com_meshgrids[0] / maxw_params["vth"] ** 2) + * np.exp(-com_meshgrids[0] / maxw_params["vth"] ** 2) ) if show_plot: @@ -1607,11 +1537,11 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): plt.xlabel("v_para") plt.show() - assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # test canonical Maxwellian profile in v_perp v_para = 0.1 - v_perp = xp.linspace(0, 2.5, 64) + v_perp = np.linspace(0, 2.5, 64) absB = mhd_equil.absB0(0.5, 0.5, 0.5)[0, 0, 0] @@ -1627,18 +1557,18 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): psi = mhd_equil.psi_r(r) psic = psi - epsilon * B0 * R0 / absB * v_para - psic += epsilon * xp.sign(v_para) * xp.sqrt(2 * (energy - mu * B0)) * R0 * xp.heaviside(energy - mu * B0, 0) + psic += epsilon * np.sign(v_para) * np.sqrt(2 * (energy - mu * B0)) * R0 * np.heaviside(energy - mu * B0, 0) - com_meshgrids = xp.meshgrid(energy, mu, psic) + com_meshgrids = np.meshgrid(energy, mu, psic) res = maxwellian(*com_meshgrids).squeeze() res_ana = ( maxw_params["n"] * 2 - * xp.sqrt(com_meshgrids[0] / xp.pi) + * np.sqrt(com_meshgrids[0] / np.pi) / maxw_params["vth"] ** 3 - * xp.exp(-com_meshgrids[0] / maxw_params["vth"] ** 2) + * np.exp(-com_meshgrids[0] / maxw_params["vth"] ** 2) ) if show_plot: @@ -1650,7 +1580,7 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): plt.xlabel("v_perp") plt.show() - assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # ============================================= # ===== Test ITPA perturbation in density ===== @@ -1665,11 +1595,11 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): maxwellian = CanonicalMaxwellian(n=(0.0, pert), equil=mhd_equil, volume_form=False) - e1 = xp.linspace(0.0, 1.0, Nel[0]) - e2 = xp.linspace(0.0, 1.0, Nel[1]) - e3 = xp.linspace(0.0, 1.0, Nel[2]) + e1 = np.linspace(0.0, 1.0, Nel[0]) + e2 = np.linspace(0.0, 1.0, Nel[1]) + e3 = np.linspace(0.0, 1.0, Nel[2]) - eta_meshgrid = xp.meshgrid(e1, e2, e3) + eta_meshgrid = np.meshgrid(e1, e2, e3) v_para = 0.01 v_perp = 0.01 @@ -1688,16 +1618,16 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): psi = mhd_equil.psi_r(r[0, :, 0]) psic = psi - epsilon * B0 * R0 / absB * v_para - psic += epsilon * xp.sign(v_para) * xp.sqrt(2 * (energy - mu * B0)) * R0 * xp.heaviside(energy - mu * B0, 0) + psic += epsilon * np.sign(v_para) * np.sqrt(2 * (energy - mu * B0)) * R0 * np.heaviside(energy - mu * B0, 0) - com_meshgrids = xp.meshgrid(energy, mu, psic) + com_meshgrids = np.meshgrid(energy, mu, psic) res = maxwellian(energy, mu, psic).squeeze() # calculate rc rc = maxwellian.rc(psic) - ana_res = n0 * c[3] * xp.exp(-c[2] / c[1] * xp.tanh((rc - c[0]) / c[2])) - ana_res *= 2 * xp.sqrt(energy / xp.pi) / maxw_params["vth"] ** 3 * xp.exp(-energy / maxw_params["vth"] ** 2) + ana_res = n0 * c[3] * np.exp(-c[2] / c[1] * np.tanh((rc - c[0]) / c[2])) + ana_res *= 2 * np.sqrt(energy / np.pi) / maxw_params["vth"] ** 3 * np.exp(-energy / maxw_params["vth"] ** 2) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -1708,7 +1638,7 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" if __name__ == "__main__": diff --git a/src/struphy/linear_algebra/linalg_kron.py b/src/struphy/linear_algebra/linalg_kron.py index fedd05979..712b47e43 100644 --- a/src/struphy/linear_algebra/linalg_kron.py +++ b/src/struphy/linear_algebra/linalg_kron.py @@ -13,7 +13,7 @@ [r_M11, rM12, ... , r_MNO]] """ -import cunumpy as xp +import numpy as np from scipy.linalg import solve_circulant from scipy.sparse.linalg import splu @@ -82,9 +82,8 @@ def kron_matvec_3d(kmat, vec3d): ( kmat[2].dot( ((kmat[1].dot(((kmat[0].dot(vec3d.reshape(v0, v1 * v2))).T).reshape(v1, v2 * k0))).T).reshape( - v2, - k0 * k1, - ), + v2, k0 * k1 + ) ) ).T ).reshape(k0, k1, k2) @@ -197,9 +196,9 @@ def kron_matmat_fft_3d(a_vec, b_vec): c_vec = [0, 0, 0] - c_vec[0] = xp.fft.ifft(xp.fft.fft(a_vec[0]) * xp.fft.fft(b_vec[0])) - c_vec[1] = xp.fft.ifft(xp.fft.fft(a_vec[1]) * xp.fft.fft(b_vec[1])) - c_vec[2] = xp.fft.ifft(xp.fft.fft(a_vec[2]) * xp.fft.fft(b_vec[2])) + c_vec[0] = np.fft.ifft(np.fft.fft(a_vec[0]) * np.fft.fft(b_vec[0])) + c_vec[1] = np.fft.ifft(np.fft.fft(a_vec[1]) * np.fft.fft(b_vec[1])) + c_vec[2] = np.fft.ifft(np.fft.fft(a_vec[2]) * np.fft.fft(b_vec[2])) return c_vec @@ -279,9 +278,8 @@ def kron_lusolve_3d(kmatlu, rhs): ( kmatlu[2].solve( ((kmatlu[1].solve(((kmatlu[0].solve(rhs.reshape(r0, r1 * r2))).T).reshape(r1, r2 * r0))).T).reshape( - r2, - r0 * r1, - ), + r2, r0 * r1 + ) ) ).T ).reshape(r0, r1, r2) @@ -322,7 +320,7 @@ def kron_solve_3d(kmat, rhs): splu(kmat[2]).solve( ( (splu(kmat[1]).solve(((splu(kmat[0]).solve(rhs.reshape(r0, r1 * r2))).T).reshape(r1, r2 * r0))).T - ).reshape(r2, r0 * r1), + ).reshape(r2, r0 * r1) ) ).T ).reshape(r0, r1, r2) @@ -363,8 +361,7 @@ def kron_fftsolve_3d(cvec, rhs): ( ( solve_circulant( - cvec[1], - ((solve_circulant(cvec[0], rhs.reshape(r0, r1 * r2))).T).reshape(r1, r2 * r0), + cvec[1], ((solve_circulant(cvec[0], rhs.reshape(r0, r1 * r2))).T).reshape(r1, r2 * r0) ) ).T ).reshape(r2, r0 * r1), diff --git a/src/struphy/linear_algebra/saddle_point.py b/src/struphy/linear_algebra/saddle_point.py index 337664754..15ca4aac4 100644 --- a/src/struphy/linear_algebra/saddle_point.py +++ b/src/struphy/linear_algebra/saddle_point.py @@ -1,6 +1,6 @@ from typing import Union -import cunumpy as xp +import numpy as np import scipy as sc from psydac.linalg.basic import LinearOperator, Vector from psydac.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace @@ -28,7 +28,7 @@ class SaddlePointSolver: } \right) using either the Uzawa iteration :math:`BA^{-1}B^{\top} y = BA^{-1} f` or using on of the solvers given in :mod:`psydac.linalg.solvers`. The prefered solver is GMRES. - The decission which variant to use is given by the type of A. If A is of type list of xp.ndarrays or sc.sparse.csr_matrices, then this class uses the Uzawa algorithm. + The decission which variant to use is given by the type of A. If A is of type list of np.ndarrays or sc.sparse.csr_matrices, then this class uses the Uzawa algorithm. If A is of type LinearOperator or BlockLinearOperator, a solver is used for the inverse. Using the Uzawa algorithm, solution is given by: @@ -41,7 +41,7 @@ class SaddlePointSolver: ---------- A : list, LinearOperator or BlockLinearOperator Upper left block. - Either the entries on the diagonals of block A are given as list of xp.ndarray or sc.sparse.csr_matrix. + Either the entries on the diagonals of block A are given as list of np.ndarray or sc.sparse.csr_matrix. Alternative: Give whole matrice A as LinearOperator or BlockLinearOperator. list: Uzawa algorithm is used. LinearOperator: A solver given in :mod:`psydac.linalg.solvers` is used. Specified by solver_name. @@ -49,16 +49,16 @@ class SaddlePointSolver: B : list, LinearOperator or BlockLinearOperator Lower left block. - Uzwaw Algorithm: All entries of block B are given either as list of xp.ndarray or sc.sparse.csr_matrix. + Uzwaw Algorithm: All entries of block B are given either as list of np.ndarray or sc.sparse.csr_matrix. Solver: Give whole B as LinearOperator or BlocklinearOperator F : list Right hand side of the upper block. - Uzawa: Given as list of xp.ndarray or sc.sparse.csr_matrix. + Uzawa: Given as list of np.ndarray or sc.sparse.csr_matrix. Solver: Given as LinearOperator or BlockLinearOperator Apre : list - The non-inverted preconditioner for entries on the diagonals of block A are given as list of xp.ndarray or sc.sparse.csr_matrix. Only required for the Uzawa algorithm. + The non-inverted preconditioner for entries on the diagonals of block A are given as list of np.ndarray or sc.sparse.csr_matrix. Only required for the Uzawa algorithm. method_to_solve : str Method for the inverses. Choose from 'DirectNPInverse', 'ScipySparse', 'InexactNPInverse' ,'SparseSolver'. Only required for the Uzawa algorithm. @@ -98,14 +98,14 @@ def __init__( if isinstance(A, list): self._variant = "Uzawa" for i in A: - assert isinstance(i, xp.ndarray) or isinstance(i, sc.sparse.csr_matrix) + assert isinstance(i, np.ndarray) or isinstance(i, sc.sparse.csr_matrix) for i in B: - assert isinstance(i, xp.ndarray) or isinstance(i, sc.sparse.csr_matrix) + assert isinstance(i, np.ndarray) or isinstance(i, sc.sparse.csr_matrix) for i in F: - assert isinstance(i, xp.ndarray) or isinstance(i, sc.sparse.csr_matrix) + assert isinstance(i, np.ndarray) or isinstance(i, sc.sparse.csr_matrix) for i in Apre: assert ( - isinstance(i, xp.ndarray) + isinstance(i, np.ndarray) or isinstance(i, sc.sparse.csr_matrix) or isinstance(i, sc.sparse.csr_array) ) @@ -169,9 +169,9 @@ def __init__( self._setup_inverses() # Solution vectors numpy - self._Pnp = xp.zeros(self._B1np.shape[0]) - self._Unp = xp.zeros(self._A[0].shape[1]) - self._Uenp = xp.zeros(self._A[1].shape[1]) + self._Pnp = np.zeros(self._B1np.shape[0]) + self._Unp = np.zeros(self._A[0].shape[1]) + self._Uenp = np.zeros(self._A[1].shape[1]) # Allocate memory for matrices used in solving the system self._rhs0np = self._F[0].copy() self._rhs1np = self._F[1].copy() @@ -195,8 +195,8 @@ def A(self, a): same_A0 = (A0_old != A0_new).nnz == 0 same_A1 = (A1_old != A1_new).nnz == 0 else: - same_A0 = xp.allclose(A0_old, A0_new, atol=1e-10) - same_A1 = xp.allclose(A1_old, A1_new, atol=1e-10) + same_A0 = np.allclose(A0_old, A0_new, atol=1e-10) + same_A1 = np.allclose(A1_old, A1_new, atol=1e-10) if same_A0 and same_A1: need_update = False self._A = a @@ -240,8 +240,8 @@ def Apre(self, a): same_A0 = (A0_old != A0_new).nnz == 0 same_A1 = (A1_old != A1_new).nnz == 0 else: - same_A0 = xp.allclose(A0_old, A0_new, atol=1e-10) - same_A1 = xp.allclose(A1_old, A1_new, atol=1e-10) + same_A0 = np.allclose(A0_old, A0_new, atol=1e-10) + same_A1 = np.allclose(A1_old, A1_new, atol=1e-10) if same_A0 and same_A1: need_update = False self._Apre = a @@ -256,11 +256,11 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): Parameters ---------- - U_init : Vector, xp.ndarray or sc.sparse.csr.csr_matrix, optional - Initial guess for the velocity of the ions. If None, initializes to zero. Types xp.ndarray and sc.sparse.csr.csr_matrix can only be given if system should be solved with Uzawa algorithm. + U_init : Vector, np.ndarray or sc.sparse.csr.csr_matrix, optional + Initial guess for the velocity of the ions. If None, initializes to zero. Types np.ndarray and sc.sparse.csr.csr_matrix can only be given if system should be solved with Uzawa algorithm. - Ue_init : Vector, xp.ndarray or sc.sparse.csr.csr_matrix, optional - Initial guess for the velocity of the electrons. If None, initializes to zero. Types xp.ndarray and sc.sparse.csr.csr_matrix can only be given if system should be solved with Uzawa algorithm. + Ue_init : Vector, np.ndarray or sc.sparse.csr.csr_matrix, optional + Initial guess for the velocity of the electrons. If None, initializes to zero. Types np.ndarray and sc.sparse.csr.csr_matrix can only be given if system should be solved with Uzawa algorithm. P_init : Vector, optional Initial guess for the potential. If None, initializes to zero. @@ -304,13 +304,13 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): elif self._variant == "Uzawa": info = {} - if self._spectralanalysis: + if self._spectralanalysis == True: self._spectralresult = self._spectral_analysis() else: self._spectralresult = [] # Initialize P to zero or given initial guess - if isinstance(U_init, xp.ndarray) or isinstance(U_init, sc.sparse.csr.csr_matrix): + if isinstance(U_init, np.ndarray) or isinstance(U_init, sc.sparse.csr.csr_matrix): self._Pnp = P_init if P_init is not None else self._P self._Unp = U_init if U_init is not None else self._U self._Uenp = Ue_init if U_init is not None else self._Ue @@ -333,9 +333,9 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): self._rhs0np -= self._B1np.transpose().dot(self._Pnp) self._rhs0np -= self._Anp.dot(self._Unp) self._rhs0np += self._F[0] - if not self._preconditioner: + if self._preconditioner == False: self._Unp += self._Anpinv.dot(self._rhs0np) - elif self._preconditioner: + elif self._preconditioner == True: self._Unp += self._Anpinv.dot(self._A11npinv @ self._rhs0np) R1 = self._B1np.dot(self._Unp) @@ -344,17 +344,17 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): self._rhs1np -= self._B2np.transpose().dot(self._Pnp) self._rhs1np -= self._Aenp.dot(self._Uenp) self._rhs1np += self._F[1] - if not self._preconditioner: + if self._preconditioner == False: self._Uenp += self._Aenpinv.dot(self._rhs1np) - elif self._preconditioner: + elif self._preconditioner == True: self._Uenp += self._Aenpinv.dot(self._A22npinv @ self._rhs1np) R2 = self._B2np.dot(self._Uenp) # Step 2: Compute residual R = BU (divergence of U) R = R1 + R2 # self._B1np.dot(self._Unp) + self._B2np.dot(self._Uenp) - residual_norm = xp.linalg.norm(R) - residual_normR1 = xp.linalg.norm(R) + residual_norm = np.linalg.norm(R) + residual_normR1 = np.linalg.norm(R) self._residual_norms.append(residual_normR1) # Store residual norm # Check for convergence based on residual norm if residual_norm < self._tol: @@ -382,7 +382,7 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): # Return with info if maximum iterations reached info["success"] = False info["niter"] = iteration + 1 - if self._verbose: + if self._verbose == True: _plot_residual_norms(self._residual_norms) return self._Unp, self._Uenp, self._Pnp, info, self._residual_norms, self._spectralresult @@ -413,10 +413,7 @@ def _setup_inverses(self): # === Inverse for A[1] if hasattr(self, "_Aenpinv") and self._is_inverse_still_valid( - self._Aenpinv, - A1, - "A[1]", - pre=self._A22npinv, + self._Aenpinv, A1, "A[1]", pre=self._A22npinv ): pass else: @@ -447,10 +444,10 @@ def _is_inverse_still_valid(self, inv, mat, name="", pre=None): I_approx = inv @ test_mat if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - I_exact = xp.eye(test_mat.shape[0]) - if not xp.allclose(I_approx, I_exact, atol=1e-6): + I_exact = np.eye(test_mat.shape[0]) + if not np.allclose(I_approx, I_exact, atol=1e-6): diff = I_approx - I_exact - max_abs = xp.abs(diff).max() + max_abs = np.abs(diff).max() print(f"{name} inverse is NOT valid anymore. Max diff: {max_abs:.2e}") return False print(f"{name} inverse is still valid.") @@ -458,7 +455,7 @@ def _is_inverse_still_valid(self, inv, mat, name="", pre=None): elif self._method_to_solve == "ScipySparse": I_exact = sc.sparse.identity(I_approx.shape[0], format=I_approx.format) diff = (I_approx - I_exact).tocoo() - max_abs = xp.abs(diff.data).max() if diff.nnz > 0 else 0.0 + max_abs = np.abs(diff.data).max() if diff.nnz > 0 else 0.0 if max_abs > 1e-6: print(f"{name} inverse is NOT valid anymore.") @@ -471,12 +468,12 @@ def _is_inverse_still_valid(self, inv, mat, name="", pre=None): def _compute_inverse(self, mat, which="matrix"): print(f"Computing inverse for {which} using method {self._method_to_solve}") if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - return xp.linalg.inv(mat) + return np.linalg.inv(mat) elif self._method_to_solve == "ScipySparse": return sc.sparse.linalg.inv(mat) elif self._method_to_solve == "SparseSolver": solver = SparseSolver(mat) - return solver.solve(xp.eye(mat.shape[0])) + return solver.solve(np.eye(mat.shape[0])) else: raise ValueError(f"Unknown solver method {self._method_to_solve}") @@ -484,14 +481,14 @@ def _spectral_analysis(self): # Spectral analysis # A11 before if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - eigvalsA11_before, eigvecs_before = xp.linalg.eig(self._A[0]) - condA11_before = xp.linalg.cond(self._A[0]) + eigvalsA11_before, eigvecs_before = np.linalg.eig(self._A[0]) + condA11_before = np.linalg.cond(self._A[0]) elif self._method_to_solve in ("SparseSolver", "ScipySparse"): - eigvalsA11_before, eigvecs_before = xp.linalg.eig(self._A[0].toarray()) - condA11_before = xp.linalg.cond(self._A[0].toarray()) + eigvalsA11_before, eigvecs_before = np.linalg.eig(self._A[0].toarray()) + condA11_before = np.linalg.cond(self._A[0].toarray()) maxbeforeA11 = max(eigvalsA11_before) - maxbeforeA11_abs = xp.max(xp.abs(eigvalsA11_before)) - minbeforeA11_abs = xp.min(xp.abs(eigvalsA11_before)) + maxbeforeA11_abs = np.max(np.abs(eigvalsA11_before)) + minbeforeA11_abs = np.min(np.abs(eigvalsA11_before)) minbeforeA11 = min(eigvalsA11_before) specA11_bef = maxbeforeA11 / minbeforeA11 specA11_bef_abs = maxbeforeA11_abs / minbeforeA11_abs @@ -500,18 +497,18 @@ def _spectral_analysis(self): # print(f'{minbeforeA11_abs = }') # print(f'{minbeforeA11 = }') # print(f'{specA11_bef = }') - print(f"{specA11_bef_abs =}") + print(f"{specA11_bef_abs = }") # A22 before if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - eigvalsA22_before, eigvecs_before = xp.linalg.eig(self._A[1]) - condA22_before = xp.linalg.cond(self._A[1]) + eigvalsA22_before, eigvecs_before = np.linalg.eig(self._A[1]) + condA22_before = np.linalg.cond(self._A[1]) elif self._method_to_solve in ("SparseSolver", "ScipySparse"): - eigvalsA22_before, eigvecs_before = xp.linalg.eig(self._A[1].toarray()) - condA22_before = xp.linalg.cond(self._A[1].toarray()) + eigvalsA22_before, eigvecs_before = np.linalg.eig(self._A[1].toarray()) + condA22_before = np.linalg.cond(self._A[1].toarray()) maxbeforeA22 = max(eigvalsA22_before) - maxbeforeA22_abs = xp.max(xp.abs(eigvalsA22_before)) - minbeforeA22_abs = xp.min(xp.abs(eigvalsA22_before)) + maxbeforeA22_abs = np.max(np.abs(eigvalsA22_before)) + minbeforeA22_abs = np.min(np.abs(eigvalsA22_before)) minbeforeA22 = min(eigvalsA22_before) specA22_bef = maxbeforeA22 / minbeforeA22 specA22_bef_abs = maxbeforeA22_abs / minbeforeA22_abs @@ -520,19 +517,19 @@ def _spectral_analysis(self): # print(f'{minbeforeA22_abs = }') # print(f'{minbeforeA22 = }') # print(f'{specA22_bef = }') - print(f"{specA22_bef_abs =}") - print(f"{condA22_before =}") + print(f"{specA22_bef_abs = }") + print(f"{condA22_before = }") - if self._preconditioner: + if self._preconditioner == True: # A11 after preconditioning with its inverse if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - eigvalsA11_after_prec, eigvecs_after = xp.linalg.eig(self._A11npinv @ self._A[0]) # Implement this + eigvalsA11_after_prec, eigvecs_after = np.linalg.eig(self._A11npinv @ self._A[0]) # Implement this elif self._method_to_solve in ("SparseSolver", "ScipySparse"): - eigvalsA11_after_prec, eigvecs_after = xp.linalg.eig((self._A11npinv @ self._A[0]).toarray()) + eigvalsA11_after_prec, eigvecs_after = np.linalg.eig((self._A11npinv @ self._A[0]).toarray()) maxafterA11_prec = max(eigvalsA11_after_prec) minafterA11_prec = min(eigvalsA11_after_prec) - maxafterA11_abs_prec = xp.max(xp.abs(eigvalsA11_after_prec)) - minafterA11_abs_prec = xp.min(xp.abs(eigvalsA11_after_prec)) + maxafterA11_abs_prec = np.max(np.abs(eigvalsA11_after_prec)) + minafterA11_abs_prec = np.min(np.abs(eigvalsA11_after_prec)) specA11_aft_prec = maxafterA11_prec / minafterA11_prec specA11_aft_abs_prec = maxafterA11_abs_prec / minafterA11_abs_prec # print(f'{maxafterA11_prec = }') @@ -540,19 +537,19 @@ def _spectral_analysis(self): # print(f'{minafterA11_abs_prec = }') # print(f'{minafterA11_prec = }') # print(f'{specA11_aft_prec = }') - print(f"{specA11_aft_abs_prec =}") + print(f"{specA11_aft_abs_prec = }") # A22 after preconditioning with its inverse if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - eigvalsA22_after_prec, eigvecs_after = xp.linalg.eig(self._A22npinv @ self._A[1]) # Implement this - condA22_after = xp.linalg.cond(self._A22npinv @ self._A[1]) + eigvalsA22_after_prec, eigvecs_after = np.linalg.eig(self._A22npinv @ self._A[1]) # Implement this + condA22_after = np.linalg.cond(self._A22npinv @ self._A[1]) elif self._method_to_solve in ("SparseSolver", "ScipySparse"): - eigvalsA22_after_prec, eigvecs_after = xp.linalg.eig((self._A22npinv @ self._A[1]).toarray()) - condA22_after = xp.linalg.cond((self._A22npinv @ self._A[1]).toarray()) + eigvalsA22_after_prec, eigvecs_after = np.linalg.eig((self._A22npinv @ self._A[1]).toarray()) + condA22_after = np.linalg.cond((self._A22npinv @ self._A[1]).toarray()) maxafterA22_prec = max(eigvalsA22_after_prec) minafterA22_prec = min(eigvalsA22_after_prec) - maxafterA22_abs_prec = xp.max(xp.abs(eigvalsA22_after_prec)) - minafterA22_abs_prec = xp.min(xp.abs(eigvalsA22_after_prec)) + maxafterA22_abs_prec = np.max(np.abs(eigvalsA22_after_prec)) + minafterA22_abs_prec = np.min(np.abs(eigvalsA22_after_prec)) specA22_aft_prec = maxafterA22_prec / minafterA22_prec specA22_aft_abs_prec = maxafterA22_abs_prec / minafterA22_abs_prec # print(f'{maxafterA22_prec = }') @@ -560,7 +557,7 @@ def _spectral_analysis(self): # print(f'{minafterA22_abs_prec = }') # print(f'{minafterA22_prec = }') # print(f'{specA22_aft_prec = }') - print(f"{specA22_aft_abs_prec =}") + print(f"{specA22_aft_abs_prec = }") return condA22_before, specA22_bef_abs, condA11_before, condA22_after, specA22_aft_abs_prec diff --git a/src/struphy/linear_algebra/solver.py b/src/struphy/linear_algebra/solver.py index 217326309..0e36cfcaf 100644 --- a/src/struphy/linear_algebra/solver.py +++ b/src/struphy/linear_algebra/solver.py @@ -1,7 +1,5 @@ from dataclasses import dataclass -from struphy.io.options import OptsNonlinearSolver - @dataclass class SolverParameters: @@ -12,26 +10,3 @@ class SolverParameters: info: bool = False verbose: bool = False recycle: bool = True - - -@dataclass -class DiscreteGradientSolverParameters: - """Parameters for discrete gradient solvers.""" - - relaxation_factor: float = 0.5 - tol: float = 1e-12 - maxiter: int = 20 - verbose: bool = False - info: bool = False - - -@dataclass -class NonlinearSolverParameters: - """Parameters for psydac solvers.""" - - tol: float = 1e-8 - maxiter: int = 100 - info: bool = False - verbose: bool = False - type: OptsNonlinearSolver = "Picard" - linearize: bool = False diff --git a/src/struphy/linear_algebra/tests/test_saddle_point_propagator.py b/src/struphy/linear_algebra/tests/test_saddle_point_propagator.py index 3aa3f4ab0..74ee6293b 100644 --- a/src/struphy/linear_algebra/tests/test_saddle_point_propagator.py +++ b/src/struphy/linear_algebra/tests/test_saddle_point_propagator.py @@ -13,7 +13,7 @@ def test_propagator1D(Nel, p, spl_kind, dirichlet_bc, mapping, epsilon, dt): """Test saddle-point-solver by propagator TwoFluidQuasiNeutralFull. Use manufactured solutions from perturbations to verify h- and p-convergence when model TwoFluidQuasiNeutralToy calculates solution with SaddlePointSolver.""" - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI from struphy.feec.basis_projection_ops import BasisProjectionOperators from struphy.feec.mass import WeightedMassOperators @@ -224,7 +224,7 @@ def test_propagator1D(Nel, p, spl_kind, dirichlet_bc, mapping, epsilon, dt): def test_propagator2D(Nel, p, spl_kind, dirichlet_bc, mapping, epsilon, dt): """Test saddle-point-solver by propagator TwoFluidQuasiNeutralFull. Use manufactured solutions from perturbations to verify h- and p-convergence when model TwoFluidQuasiNeutralToy calculates solution with SaddlePointSolver. Allow a certain error after one time step, save this solution and compare the follwing timesteps with this solution but with less tolerance. Shows that the solver can stay in a steady state solution.""" - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI from struphy.feec.basis_projection_ops import BasisProjectionOperators from struphy.feec.mass import WeightedMassOperators @@ -306,7 +306,7 @@ def test_propagator2D(Nel, p, spl_kind, dirichlet_bc, mapping, epsilon, dt): "ManufacturedSolutionPotential": { "given_in_basis": "physical", "dimension": "2D", - }, + } } uvec.initialize_coeffs(domain=domain, pert_params=pp_u) diff --git a/src/struphy/linear_algebra/tests/test_saddlepoint_massmatrices.py b/src/struphy/linear_algebra/tests/test_saddlepoint_massmatrices.py index 823584bc9..67de947e0 100644 --- a/src/struphy/linear_algebra/tests/test_saddlepoint_massmatrices.py +++ b/src/struphy/linear_algebra/tests/test_saddlepoint_massmatrices.py @@ -13,9 +13,9 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m import time - import cunumpy as xp + import numpy as np import scipy as sc - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI from psydac.linalg.basic import IdentityOperator from psydac.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace @@ -107,7 +107,7 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m Cnp = derhamnumpy.curl.toarray() # Dnp = D.toarray() # Cnp = C.toarray() - if derham.with_local_projectors: + if derham.with_local_projectors == True: S21np = S21.toarray else: S21np = S21.toarray_struphy() @@ -121,7 +121,7 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m Cnp = derhamnumpy.curl.tosparse() # Dnp = D.tosparse() # Cnp = C.tosparse() - if derham.with_local_projectors: + if derham.with_local_projectors == True: S21np = S21.tosparse else: S21np = S21.toarray_struphy(is_sparse=True) @@ -132,12 +132,12 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m A11np = M2np / dt + nu * (Dnp.T @ M3np @ Dnp + S21np.T @ Cnp.T @ M2np @ Cnp @ S21np) - M2Bnp if method_to_solve in ("DirectNPInverse", "InexactNPInverse"): A22np = ( - stab_sigma * xp.identity(A11np.shape[0]) + stab_sigma * np.identity(A11np.shape[0]) + nue * (Dnp.T @ M3np @ Dnp + S21np.T @ Cnp.T @ M2np @ Cnp @ S21np) + M2Bnp ) # Preconditioner - _A22np_pre = stab_sigma * xp.identity(A22np.shape[0]) # + nue*(Dnp.T @ M3np @ Dnp) + _A22np_pre = stab_sigma * np.identity(A22np.shape[0]) # + nue*(Dnp.T @ M3np @ Dnp) _A11np_pre = M2np / dt # + nu * (Dnp.T @ M3np @ Dnp) elif method_to_solve in ("SparseSolver", "ScipySparse"): A22np = ( @@ -201,9 +201,9 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m - (B[0, 1].T).dot(y1_rdm) ) TestDiv = -B1.dot(x1) + B2.dot(x2) - RestDiv = xp.linalg.norm(TestDiv.toarray()) - RestA = xp.linalg.norm(TestA.toarray()) - RestAe = xp.linalg.norm(TestAe.toarray()) + RestDiv = np.linalg.norm(TestDiv.toarray()) + RestA = np.linalg.norm(TestA.toarray()) + RestAe = np.linalg.norm(TestAe.toarray()) print(f"{RestA =}") print(f"{RestAe =}") print(f"{RestDiv =}") @@ -218,10 +218,10 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m - (nue * (Dnp.T @ M3np @ Dnp + S21np.T @ Cnp.T @ M2np @ Cnp @ S21np) + M2Bnp).dot(x2np) - B2np.T.dot(ynp) ) - RestAnp = xp.linalg.norm(TestAnp) - RestAenp = xp.linalg.norm(TestAenp) + RestAnp = np.linalg.norm(TestAnp) + RestAenp = np.linalg.norm(TestAenp) TestDivnp = -B1np.dot(x1np) + B2np.dot(x2np) - RestDivnp = xp.linalg.norm(TestDivnp) + RestDivnp = np.linalg.norm(TestDivnp) print(f"{RestAnp =}") print(f"{RestAenp =}") print(f"{RestDivnp =}") @@ -242,7 +242,7 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m TestA11dot = TestA11.dot(x1) compare_arrays(TestA11dot, TestA11composeddot, mpi_rank, atol=1e-5) # compare_arrays(TestA11dot, TestA11npdot, mpi_rank, atol=1e-5) - print("Comparison numpy to psydac succesfull.") + print(f"Comparison numpy to psydac succesfull.") M2pre = MassMatrixPreconditioner(mass_mats.M2) @@ -270,7 +270,7 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m x_uzawa = {} x_uzawa[0] = x_u x_uzawa[1] = x_ue - if show_plots: + if show_plots == True: _plot_residual_norms(residual_norms) elif method_for_solving == "SaddlePointSolverGMRES": # Wrong initialization to check if changed @@ -296,22 +296,22 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m elapsed_time = end_time - start_time print(f"Method execution time: {elapsed_time:.6f} seconds") - if isinstance(x_uzawa[0], xp.ndarray): - # Output as xp.ndarray + if isinstance(x_uzawa[0], np.ndarray): + # Output as np.ndarray Rx1 = x1np - x_uzawa[0] Rx2 = x2np - x_uzawa[1] Ry = ynp - y_uzawa - residualx_normx1 = xp.linalg.norm(Rx1) - residualx_normx2 = xp.linalg.norm(Rx2) - residualy_norm = xp.linalg.norm(Ry) + residualx_normx1 = np.linalg.norm(Rx1) + residualx_normx2 = np.linalg.norm(Rx2) + residualy_norm = np.linalg.norm(Ry) TestRest1 = F1np - A11np.dot(x_uzawa[0]) - B1np.T.dot(y_uzawa) - TestRest1val = xp.max(abs(TestRest1)) + TestRest1val = np.max(abs(TestRest1)) Testoldy1 = F1np - A11np.dot(x_uzawa[0]) - B1np.T.dot(ynp) - Testoldy1val = xp.max(abs(Testoldy1)) + Testoldy1val = np.max(abs(Testoldy1)) TestRest2 = F2np - A22np.dot(x_uzawa[1]) - B2np.T.dot(y_uzawa) - TestRest2val = xp.max(abs(TestRest2)) + TestRest2val = np.max(abs(TestRest2)) Testoldy2 = F2np - A22np.dot(x_uzawa[1]) - B2np.T.dot(ynp) - Testoldy2val = xp.max(abs(Testoldy2)) + Testoldy2val = np.max(abs(Testoldy2)) print(f"{TestRest1val =}") print(f"{TestRest2val =}") print(f"{Testoldy1val =}") @@ -323,24 +323,24 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m compare_arrays(y1_rdm, y_uzawa, mpi_rank, atol=1e-5) compare_arrays(x1, x_uzawa[0], mpi_rank, atol=1e-5) compare_arrays(x2, x_uzawa[1], mpi_rank, atol=1e-5) - print(f"{info =}") + print(f"{info = }") elif isinstance(x_uzawa[0], BlockVector): # Output as Blockvector Rx1 = x1 - x_uzawa[0] Rx2 = x2 - x_uzawa[1] Ry = y1_rdm - y_uzawa - residualx_normx1 = xp.linalg.norm(Rx1.toarray()) - residualx_normx2 = xp.linalg.norm(Rx2.toarray()) - residualy_norm = xp.linalg.norm(Ry.toarray()) + residualx_normx1 = np.linalg.norm(Rx1.toarray()) + residualx_normx2 = np.linalg.norm(Rx2.toarray()) + residualy_norm = np.linalg.norm(Ry.toarray()) TestRest1 = F1 - A11.dot(x_uzawa[0]) - B1T.dot(y_uzawa) - TestRest1val = xp.max(abs(TestRest1.toarray())) + TestRest1val = np.max(abs(TestRest1.toarray())) Testoldy1 = F1 - A11.dot(x_uzawa[0]) - B1T.dot(y1_rdm) - Testoldy1val = xp.max(abs(Testoldy1.toarray())) + Testoldy1val = np.max(abs(Testoldy1.toarray())) TestRest2 = F2 - A22.dot(x_uzawa[1]) - B2T.dot(y_uzawa) - TestRest2val = xp.max(abs(TestRest2.toarray())) + TestRest2val = np.max(abs(TestRest2.toarray())) Testoldy2 = F2 - A22.dot(x_uzawa[1]) - B2T.dot(y1_rdm) - Testoldy2val = xp.max(abs(Testoldy2.toarray())) + Testoldy2val = np.max(abs(Testoldy2.toarray())) # print(f"{TestRest1val =}") # print(f"{TestRest2val =}") # print(f"{Testoldy1val =}") @@ -372,15 +372,15 @@ def _plot_residual_norms(residual_norms): def _plot_velocity(data_reshaped): - import cunumpy as xp import matplotlib import matplotlib.pyplot as plt + import numpy as np matplotlib.use("Agg") - x = xp.linspace(0, 1, 30) - y = xp.linspace(0, 1, 30) - X, Y = xp.meshgrid(x, y) + x = np.linspace(0, 1, 30) + y = np.linspace(0, 1, 30) + X, Y = np.meshgrid(x, y) plt.figure(figsize=(6, 5)) plt.imshow(data_reshaped.T, cmap="viridis", origin="lower", extent=[0, 1, 0, 1]) diff --git a/src/struphy/linear_algebra/tests/test_stencil_dot_kernels.py b/src/struphy/linear_algebra/tests/test_stencil_dot_kernels.py index d2c2238ff..c66a67aa0 100644 --- a/src/struphy/linear_algebra/tests/test_stencil_dot_kernels.py +++ b/src/struphy/linear_algebra/tests/test_stencil_dot_kernels.py @@ -1,6 +1,7 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [12]) @pytest.mark.parametrize("p", [1, 2, 3]) @pytest.mark.parametrize("spl_kind", [False, True]) @@ -13,9 +14,9 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): a) the result from kernel in struphy.linear_algebra.stencil_dot_kernels.matvec_1d_kernel b) the result from Stencil .dot with precompiled=True""" - import cunumpy as xp + import numpy as np + from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL - from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix, StencilVector from struphy.feec.psydac_derham import Derham @@ -25,6 +26,7 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): PSYDAC_BACKEND_GPYCCEL["flags"] = "-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none" comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() if rank == 0: @@ -78,8 +80,8 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): mat_pre._data[p_out + i_loc, d1] = m - i # random vector - # xp.random.seed(123) - x[s_in : e_in + 1] = xp.random.rand(domain.coeff_space.npts[0]) + # np.random.seed(123) + x[s_in : e_in + 1] = np.random.rand(domain.coeff_space.npts[0]) if rank == 0: print(f"spl_kind={spl_kind}") @@ -118,10 +120,11 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): print("\nout_ker=", out_ker._data) print("\nout_pre=", out_pre._data) - assert xp.allclose(out_ker._data, out._data) - assert xp.allclose(out_pre._data, out._data) + assert np.allclose(out_ker._data, out._data) + assert np.allclose(out_pre._data, out._data) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[12, 16, 20]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[True, False, False]]) @@ -134,9 +137,9 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): a) the result from kernel in struphy.linear_algebra.stencil_dot_kernels.matvec_1d_kernel b) the result from Stencil .dot with precompiled=True""" - import cunumpy as xp + import numpy as np + from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL - from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix, StencilVector from struphy.feec.psydac_derham import Derham @@ -146,6 +149,7 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): PSYDAC_BACKEND_GPYCCEL["flags"] = "-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none" comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() if rank == 0: @@ -177,16 +181,16 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): x = StencilVector(domain.coeff_space) out_ker = StencilVector(codomain.coeff_space) - s_out = xp.array(mat.codomain.starts) - e_out = xp.array(mat.codomain.ends) - p_out = xp.array(mat.codomain.pads) - s_in = xp.array(mat.domain.starts) - e_in = xp.array(mat.domain.ends) - p_in = xp.array(mat.domain.pads) + s_out = np.array(mat.codomain.starts) + e_out = np.array(mat.codomain.ends) + p_out = np.array(mat.codomain.pads) + s_in = np.array(mat.domain.starts) + e_in = np.array(mat.domain.ends) + p_in = np.array(mat.domain.pads) # random matrix - xp.random.seed(123) - tmp1 = xp.random.rand(*codomain.coeff_space.npts, *[2 * q + 1 for q in p]) + np.random.seed(123) + tmp1 = np.random.rand(*codomain.coeff_space.npts, *[2 * q + 1 for q in p]) mat[ s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, @@ -207,7 +211,7 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): ] # random vector - tmp2 = xp.random.rand(*domain.coeff_space.npts) + tmp2 = np.random.rand(*domain.coeff_space.npts) x[ s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, @@ -226,7 +230,7 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): # kernel matvec add = [int(end_in >= end_out) for end_in, end_out in zip(mat.domain.ends, mat.codomain.ends)] - add = xp.array(add) + add = np.array(add) matvec_3d_kernel(mat._data, x._data, out_ker._data, s_in, p_in, add, s_out, e_out, p_out) # precompiled .dot @@ -253,12 +257,12 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): print("\nout_ker[2]=", out_ker._data[p_out[0], p_out[1], :]) print("\nout_pre[2]=", out_pre._data[p_out[0], p_out[1], :]) - assert xp.allclose( + assert np.allclose( out_ker[s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, s_out[2] : e_out[2] + 1], out[s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, s_out[2] : e_out[2] + 1], ) - assert xp.allclose( + assert np.allclose( out_pre[s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, s_out[2] : e_out[2] + 1], out[s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, s_out[2] : e_out[2] + 1], ) diff --git a/src/struphy/linear_algebra/tests/test_stencil_transpose_kernels.py b/src/struphy/linear_algebra/tests/test_stencil_transpose_kernels.py index 1125a980c..92f65410d 100644 --- a/src/struphy/linear_algebra/tests/test_stencil_transpose_kernels.py +++ b/src/struphy/linear_algebra/tests/test_stencil_transpose_kernels.py @@ -1,6 +1,7 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [12]) @pytest.mark.parametrize("p", [1, 2, 3]) @pytest.mark.parametrize("spl_kind", [False, True]) @@ -13,9 +14,9 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): a) the result from kernel in struphy.linear_algebra.stencil_transpose_kernels.transpose_1d_kernel b) the result from Stencil .transpose with precompiled=True""" - import cunumpy as xp + import numpy as np + from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL - from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix from struphy.feec.psydac_derham import Derham @@ -25,6 +26,7 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): PSYDAC_BACKEND_GPYCCEL["flags"] = "-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none" comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() if rank == 0: @@ -112,10 +114,11 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): print("\nmatT_pre=", matT_pre._data) print("\nmatT_pre.toarray=\n", matT_pre.toarray()) - assert xp.allclose(matT_ker[s_in : e_in + 1, :], matT[s_in : e_in + 1, :]) - assert xp.allclose(matT_pre[s_in : e_in + 1, :], matT[s_in : e_in + 1, :]) + assert np.allclose(matT_ker[s_in : e_in + 1, :], matT[s_in : e_in + 1, :]) + assert np.allclose(matT_pre[s_in : e_in + 1, :], matT[s_in : e_in + 1, :]) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[12, 16, 20]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[True, False, False]]) @@ -128,9 +131,9 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): a) the result from kernel in struphy.linear_algebra.stencil_transpose_kernels.transpose_3d_kernel b) the result from Stencil .transpose with precompiled=True""" - import cunumpy as xp + import numpy as np + from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL - from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix from struphy.feec.psydac_derham import Derham @@ -140,6 +143,7 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): PSYDAC_BACKEND_GPYCCEL["flags"] = "-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none" comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() if rank == 0: @@ -170,16 +174,16 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): mat_pre = StencilMatrix(domain.coeff_space, codomain.coeff_space, backend=PSYDAC_BACKEND_GPYCCEL, precompiled=True) matT_ker = StencilMatrix(codomain.coeff_space, domain.coeff_space) - s_out = xp.array(mat.codomain.starts) - e_out = xp.array(mat.codomain.ends) - p_out = xp.array(mat.codomain.pads) - s_in = xp.array(mat.domain.starts) - e_in = xp.array(mat.domain.ends) - p_in = xp.array(mat.domain.pads) + s_out = np.array(mat.codomain.starts) + e_out = np.array(mat.codomain.ends) + p_out = np.array(mat.codomain.pads) + s_in = np.array(mat.domain.starts) + e_in = np.array(mat.domain.ends) + p_in = np.array(mat.domain.pads) # random matrix - xp.random.seed(123) - tmp1 = xp.random.rand(*codomain.coeff_space.npts, *[2 * q + 1 for q in p]) + np.random.seed(123) + tmp1 = np.random.rand(*codomain.coeff_space.npts, *[2 * q + 1 for q in p]) mat[ s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, @@ -208,7 +212,7 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): # kernel transpose add = [int(end_out >= end_in) for end_in, end_out in zip(mat.domain.ends, mat.codomain.ends)] - add = xp.array(add) + add = np.array(add) transpose_3d_kernel(mat._data, matT_ker._data, s_out, p_out, add, s_in, e_in, p_in) # precompiled transpose @@ -237,12 +241,12 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): print("\nmatT_ker[2]=", matT_ker._data[p_in[0], p_in[1], :, 1, 1, :]) print("\nmatT_pre[2]=", matT_pre._data[p_in[0], p_in[1], :, 1, 1, :]) - assert xp.allclose( + assert np.allclose( matT_ker[s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, s_in[2] : e_in[2] + 1], matT[s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, s_in[2] : e_in[2] + 1], ) - assert xp.allclose( + assert np.allclose( matT_pre[s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, s_in[2] : e_in[2] + 1], matT[s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, s_in[2] : e_in[2] + 1], ) diff --git a/src/struphy/main.py b/src/struphy/main.py index 047abea95..5955f16f8 100644 --- a/src/struphy/main.py +++ b/src/struphy/main.py @@ -8,11 +8,10 @@ import time from typing import Optional, TypedDict -import cunumpy as xp import h5py +import numpy as np from line_profiler import profile -from psydac.ddm.mpi import MockMPI -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from pyevtk.hl import gridToVTK from struphy.fields_background.base import FluidEquilibrium, FluidEquilibriumWithB @@ -69,23 +68,10 @@ def run( Absolute path to .py parameter file. """ - if isinstance(MPI, MockMPI): - comm = None - rank = 0 - size = 1 - Barrier = lambda: None - else: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - size = comm.Get_size() - Barrier = comm.Barrier - - if rank == 0: - print("") - Barrier() + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + size = comm.Get_size() - # synchronize MPI processes to set same start time of simulation for all processes - Barrier() start_simulation = time.time() # check model @@ -103,7 +89,6 @@ def run( save_step = env.save_step sort_step = env.sort_step num_clones = env.num_clones - use_mpi = (not comm is None,) meta = {} meta["platform"] = sysconfig.get_platform() @@ -112,7 +97,6 @@ def run( meta["parameter file"] = params_path meta["output folder"] = path_out meta["MPI processes"] = size - meta["use MPI.COMM_WORLD"] = use_mpi meta["number of domain clones"] = num_clones meta["restart"] = restart meta["max wall-clock [min]"] = max_runtime @@ -185,7 +169,7 @@ def run( clone_config.print_particle_config() model.clone_config = clone_config - Barrier() + comm.Barrier() ## configure model instance @@ -207,7 +191,22 @@ def run( # domain and fluid background model.setup_domain_and_equil(domain, equil) - # feec + # default grid + if grid is None: + Nel = (16, 16, 16) + if rank == 0: + print(f"\nNo grid specified - using TensorProductGrid with {Nel = }.") + grid = grids.TensorProductGrid(Nel=Nel) + + # allocate derham-related objects + if derham_opts is None: + p = (3, 3, 3) + spl_kind = (False, False, False) + if rank == 0: + print( + f"\nNo Derham options specified - creating Derham with {p = } and {spl_kind = } for projecting equilibrium." + ) + derham_opts = DerhamOptions(p=p, spl_kind=spl_kind) model.allocate_feec(grid, derham_opts) # equation paramters @@ -226,7 +225,7 @@ def run( if rank < 32: if rank == 0: print("") - Barrier() + comm.Barrier() print(f"Rank {rank}: executing main.run() for model {model_name} ...") if size > 32 and rank == 32: @@ -235,9 +234,9 @@ def run( # store geometry vtk if rank == 0: grids_log = [ - xp.linspace(1e-6, 1.0, 32), - xp.linspace(0.0, 1.0, 32), - xp.linspace(0.0, 1.0, 32), + np.linspace(1e-6, 1.0, 32), + np.linspace(0.0, 1.0, 32), + np.linspace(0.0, 1.0, 32), ] tmp = model.domain(*grids_log) @@ -262,9 +261,9 @@ def run( # time quantities (current time value, value in seconds and index) time_state = {} - time_state["value"] = xp.zeros(1, dtype=float) - time_state["value_sec"] = xp.zeros(1, dtype=float) - time_state["index"] = xp.zeros(1, dtype=int) + time_state["value"] = np.zeros(1, dtype=float) + time_state["value_sec"] = np.zeros(1, dtype=float) + time_state["index"] = np.zeros(1, dtype=int) # add time quantities to data object for saving for key, val in time_state.items(): @@ -310,7 +309,7 @@ def run( # time loop run_time_now = 0.0 while True: - Barrier() + comm.Barrier() # stop time loop? break_cond_1 = time_state["value"][0] >= Tend @@ -338,23 +337,22 @@ def run( t1 = time.time() if rank == 0 and verbose: message = "Particles sorted | wall clock [s]: {0:8.4f} | sorting duration [s]: {1:8.4f}".format( - run_time_now * 60, - t1 - t0, + run_time_now * 60, t1 - t0 ) print(message, end="\n") print() - # update time and index (round time to 10 decimals for a clean time grid!) - time_state["value"][0] = round(time_state["value"][0] + dt, 10) - time_state["value_sec"][0] = round(time_state["value_sec"][0] + dt * model.units.t, 10) - time_state["index"][0] += 1 - # perform one time step dt t0 = time.time() with ProfileManager.profile_region("model.integrate"): model.integrate(dt, split_algo) t1 = time.time() + # update time and index (round time to 10 decimals for a clean time grid!) + time_state["value"][0] = round(time_state["value"][0] + dt, 10) + time_state["value_sec"][0] = round(time_state["value_sec"][0] + dt * model.units.t, 10) + time_state["index"][0] += 1 + run_time_now = (time.time() - start_simulation) / 60 # update diagnostics data and save data @@ -384,12 +382,10 @@ def run( message = "time step: " + step + "/" + str(total_steps) message += " | " + "time: {0:10.5f}/{1:10.5f}".format(time_state["value"][0], Tend) message += " | " + "phys. time [s]: {0:12.10f}/{1:12.10f}".format( - time_state["value_sec"][0], - Tend * model.units.t, + time_state["value_sec"][0], Tend * model.units.t ) message += " | " + "wall clock [s]: {0:8.4f} | last step duration [s]: {1:8.4f}".format( - run_time_now * 60, - t1 - t0, + run_time_now * 60, t1 - t0 ) print(message, end="\n") @@ -399,7 +395,7 @@ def run( # =================================================================== meta["wall-clock time[min]"] = (end_simulation - start_simulation) / 60 - Barrier() + comm.Barrier() if rank == 0: # save meta-data @@ -479,7 +475,7 @@ def pproc( file = h5py.File(os.path.join(path, "data/", "data_proc0.hdf5"), "r") # save time grid at which post-processing data is created - xp.save(os.path.join(path_pproc, "t_grid.npy"), file["time/value"][::step].copy()) + np.save(os.path.join(path_pproc, "t_grid.npy"), file["time/value"][::step].copy()) if "feec" in file.keys(): exist_fields = True @@ -516,10 +512,7 @@ def pproc( if physical: point_data_phy, grids_log, grids_phy = eval_femfields( - params_in, - fields, - celldivide=[celldivide] * 3, - physical=True, + params_in, fields, celldivide=[celldivide] * 3, physical=True ) # directory for field data @@ -641,28 +634,28 @@ def __init__(self, path: str): self._f = {} self._spline_values = {} self._n_sph = {} - self.grids_log: list[xp.ndarray] = None - self.grids_phy: list[xp.ndarray] = None - self.t_grid: xp.ndarray = None + self.grids_log: list[np.ndarray] = None + self.grids_phy: list[np.ndarray] = None + self.t_grid: np.ndarray = None @property - def orbits(self) -> dict[str, xp.ndarray]: + def orbits(self) -> dict[str, np.ndarray]: """Keys: species name. Values: 3d arrays indexed by (n, p, a), where 'n' is the time index, 'p' the particle index and 'a' the attribute index.""" return self._orbits @property - def f(self) -> dict[str, dict[str, dict[str, xp.ndarray]]]: - """Keys: species name. Values: dicts of slice names ('e1_v1' etc.) holding dicts of corresponding xp.arrays for plotting.""" + def f(self) -> dict[str, dict[str, dict[str, np.ndarray]]]: + """Keys: species name. Values: dicts of slice names ('e1_v1' etc.) holding dicts of corresponding np.arrays for plotting.""" return self._f @property - def spline_values(self) -> dict[str, dict[str, xp.ndarray]]: + def spline_values(self) -> dict[str, dict[str, np.ndarray]]: """Keys: species name. Values: dicts of variable names with values being 3d arrays on the grid.""" return self._spline_values @property - def n_sph(self) -> dict[str, dict[str, dict[str, xp.ndarray]]]: - """Keys: species name. Values: dicts of view names ('view_0' etc.) holding dicts of corresponding xp.arrays for plotting.""" + def n_sph(self) -> dict[str, dict[str, dict[str, np.ndarray]]]: + """Keys: species name. Values: dicts of view names ('view_0' etc.) holding dicts of corresponding np.arrays for plotting.""" return self._n_sph @property @@ -692,6 +685,18 @@ def Nattr(self) -> dict[str, int]: self._Nattr[spec] = orbs.shape[2] return self._Nattr + @property + def spline_grid_resolution(self): + if self.grids_log is not None: + res = [x.size for x in self.grids_log] + else: + res = None + return res + + @property + def time_grid_size(self): + return self.t_grid.size + def load_data(path: str) -> SimData: """Load data generated during post-processing. @@ -705,12 +710,12 @@ def load_data(path: str) -> SimData: path_pproc = os.path.join(path, "post_processing") assert os.path.exists(path_pproc), f"Path {path_pproc} does not exist, run 'pproc' first?" print("\n*** Loading post-processed simulation data:") - print(f"{path =}") + print(f"{path = }") simdata = SimData(path) # load time grid - simdata.t_grid = xp.load(os.path.join(path_pproc, "t_grid.npy")) + simdata.t_grid = np.load(os.path.join(path_pproc, "t_grid.npy")) # data paths path_fields = os.path.join(path_pproc, "fields_data") @@ -744,7 +749,7 @@ def load_data(path: str) -> SimData: if os.path.exists(path_kinetic): # species folders species = next(os.walk(path_kinetic))[1] - print(f"{species =}") + print(f"{species = }") for spec in species: path_spec = os.path.join(path_kinetic, spec) wlk = os.walk(path_spec) @@ -761,9 +766,9 @@ def load_data(path: str) -> SimData: # print(f"{file = }") if ".npy" in file: step = int(file.split(".")[0].split("_")[-1]) - tmp = xp.load(os.path.join(path_dat, file)) + tmp = np.load(os.path.join(path_dat, file)) if n == 0: - simdata._orbits[spec] = xp.zeros((Nt, *tmp.shape), dtype=float) + simdata._orbits[spec] = np.zeros((Nt, *tmp.shape), dtype=float) simdata._orbits[spec][step] = tmp n += 1 @@ -778,7 +783,7 @@ def load_data(path: str) -> SimData: # print(f"{files = }") for file in files: name = file.split(".")[0] - tmp = xp.load(os.path.join(path_dat, sli, file)) + tmp = np.load(os.path.join(path_dat, sli, file)) # print(f"{name = }") simdata._f[spec][sli][name] = tmp @@ -793,41 +798,33 @@ def load_data(path: str) -> SimData: # print(f"{files = }") for file in files: name = file.split(".")[0] - tmp = xp.load(os.path.join(path_dat, sli, file)) + tmp = np.load(os.path.join(path_dat, sli, file)) # print(f"{name = }") simdata._n_sph[spec][sli][name] = tmp else: - print(f"{folder =}") + print(f"{folder = }") raise NotImplementedError print("\nThe following data has been loaded:") - print("\ngrids:") - print(f"{simdata.t_grid.shape =}") - if simdata.grids_log is not None: - print(f"{simdata.grids_log[0].shape =}") - print(f"{simdata.grids_log[1].shape =}") - print(f"{simdata.grids_log[2].shape =}") - if simdata.grids_phy is not None: - print(f"{simdata.grids_phy[0].shape =}") - print(f"{simdata.grids_phy[1].shape =}") - print(f"{simdata.grids_phy[2].shape =}") - print("\nsimdata.spline_values:") + print(f"{simdata.time_grid_size = }") + print(f"{simdata.spline_grid_resolution = }") + print(f"\nsimdata.spline_values:") for k, v in simdata.spline_values.items(): print(f" {k}") for kk, vv in v.items(): print(f" {kk}") - print("\nsimdata.orbits:") + print(f"\nsimdata.orbits:") for k, v in simdata.orbits.items(): print(f" {k}") - print("\nsimdata.f:") + print(f"\nsimdata.f:") for k, v in simdata.f.items(): print(f" {k}") for kk, vv in v.items(): print(f" {kk}") for kkk, vvv in vv.items(): print(f" {kkk}") - print("\nsimdata.n_sph:") + print(f"\nsimdata.n_sph:") for k, v in simdata.n_sph.items(): print(f" {k}") for kk, vv in v.items(): diff --git a/src/struphy/models/__init__.py b/src/struphy/models/__init__.py index 0153467f6..3878287f4 100644 --- a/src/struphy/models/__init__.py +++ b/src/struphy/models/__init__.py @@ -1,74 +1,74 @@ -# from struphy.models.fluid import ( -# ColdPlasma, -# EulerSPH, -# HasegawaWakatani, -# LinearExtendedMHDuniform, -# LinearMHD, -# ViscoresistiveDeltafMHD, -# ViscoresistiveDeltafMHD_with_q, -# ViscoresistiveLinearMHD, -# ViscoresistiveLinearMHD_with_q, -# ViscoresistiveMHD, -# ViscoresistiveMHD_with_p, -# ViscoresistiveMHD_with_q, -# ViscousFluid, -# ) -# from struphy.models.hybrid import ColdPlasmaVlasov, LinearMHDDriftkineticCC, LinearMHDVlasovCC, LinearMHDVlasovPC -# from struphy.models.kinetic import ( -# DriftKineticElectrostaticAdiabatic, -# LinearVlasovAmpereOneSpecies, -# LinearVlasovMaxwellOneSpecies, -# VlasovAmpereOneSpecies, -# VlasovMaxwellOneSpecies, -# ) -# from struphy.models.toy import ( -# DeterministicParticleDiffusion, -# GuidingCenter, -# Maxwell, -# Poisson, -# PressureLessSPH, -# RandomParticleDiffusion, -# ShearAlfven, -# TwoFluidQuasiNeutralToy, -# VariationalBarotropicFluid, -# VariationalCompressibleFluid, -# VariationalPressurelessFluid, -# Vlasov, -# ) +from struphy.models.fluid import ( + ColdPlasma, + EulerSPH, + HasegawaWakatani, + LinearExtendedMHDuniform, + LinearMHD, + ViscoresistiveDeltafMHD, + ViscoresistiveDeltafMHD_with_q, + ViscoresistiveLinearMHD, + ViscoresistiveLinearMHD_with_q, + ViscoresistiveMHD, + ViscoresistiveMHD_with_p, + ViscoresistiveMHD_with_q, + ViscousFluid, +) +from struphy.models.hybrid import ColdPlasmaVlasov, LinearMHDDriftkineticCC, LinearMHDVlasovCC, LinearMHDVlasovPC +from struphy.models.kinetic import ( + DriftKineticElectrostaticAdiabatic, + LinearVlasovAmpereOneSpecies, + LinearVlasovMaxwellOneSpecies, + VlasovAmpereOneSpecies, + VlasovMaxwellOneSpecies, +) +from struphy.models.toy import ( + DeterministicParticleDiffusion, + GuidingCenter, + Maxwell, + Poisson, + PressureLessSPH, + RandomParticleDiffusion, + ShearAlfven, + TwoFluidQuasiNeutralToy, + VariationalBarotropicFluid, + VariationalCompressibleFluid, + VariationalPressurelessFluid, + Vlasov, +) -# __all__ = [ -# "Maxwell", -# "Vlasov", -# "GuidingCenter", -# "ShearAlfven", -# "VariationalPressurelessFluid", -# "VariationalBarotropicFluid", -# "VariationalCompressibleFluid", -# "Poisson", -# "DeterministicParticleDiffusion", -# "RandomParticleDiffusion", -# "PressureLessSPH", -# "TwoFluidQuasiNeutralToy", -# "LinearMHD", -# "LinearExtendedMHDuniform", -# "ColdPlasma", -# "ViscoresistiveMHD", -# "ViscousFluid", -# "ViscoresistiveMHD_with_p", -# "ViscoresistiveLinearMHD", -# "ViscoresistiveDeltafMHD", -# "ViscoresistiveMHD_with_q", -# "ViscoresistiveLinearMHD_with_q", -# "ViscoresistiveDeltafMHD_with_q", -# "EulerSPH", -# "HasegawaWakatani", -# "LinearMHDVlasovCC", -# "LinearMHDVlasovPC", -# "LinearMHDDriftkineticCC", -# "ColdPlasmaVlasov", -# "VlasovAmpereOneSpecies", -# "VlasovMaxwellOneSpecies", -# "LinearVlasovAmpereOneSpecies", -# "LinearVlasovMaxwellOneSpecies", -# "DriftKineticElectrostaticAdiabatic", -# ] +__all__ = [ + "Maxwell", + "Vlasov", + "GuidingCenter", + "ShearAlfven", + "VariationalPressurelessFluid", + "VariationalBarotropicFluid", + "VariationalCompressibleFluid", + "Poisson", + "DeterministicParticleDiffusion", + "RandomParticleDiffusion", + "PressureLessSPH", + "TwoFluidQuasiNeutralToy", + "LinearMHD", + "LinearExtendedMHDuniform", + "ColdPlasma", + "ViscoresistiveMHD", + "ViscousFluid", + "ViscoresistiveMHD_with_p", + "ViscoresistiveLinearMHD", + "ViscoresistiveDeltafMHD", + "ViscoresistiveMHD_with_q", + "ViscoresistiveLinearMHD_with_q", + "ViscoresistiveDeltafMHD_with_q", + "EulerSPH", + "HasegawaWakatani", + "LinearMHDVlasovCC", + "LinearMHDVlasovPC", + "LinearMHDDriftkineticCC", + "ColdPlasmaVlasov", + "VlasovAmpereOneSpecies", + "VlasovMaxwellOneSpecies", + "LinearVlasovAmpereOneSpecies", + "LinearVlasovMaxwellOneSpecies", + "DriftKineticElectrostaticAdiabatic", +] diff --git a/src/struphy/models/base.py b/src/struphy/models/base.py index b484397a0..780c3efff 100644 --- a/src/struphy/models/base.py +++ b/src/struphy/models/base.py @@ -5,11 +5,10 @@ from functools import reduce from textwrap import indent -import cunumpy as xp +import numpy as np import yaml from line_profiler import profile -from psydac.ddm.mpi import MockMPI -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from psydac.linalg.stencil import StencilVector import struphy @@ -106,7 +105,7 @@ def setup_domain_and_equil(self, domain: Domain, equil: FluidEquilibrium): if MPI.COMM_WORLD.Get_rank() == 0 and self.verbose: print("\nDOMAIN:") - print("type:".ljust(25), self.domain.__class__.__name__) + print(f"type:".ljust(25), self.domain.__class__.__name__) for key, val in self.domain.params.items(): if key not in {"cx", "cy", "cz"}: print((key + ":").ljust(25), val) @@ -172,59 +171,40 @@ def allocate_feec(self, grid: TensorProductGrid, derham_opts: DerhamOptions): else: derham_comm = self.clone_config.sub_comm - if grid is None or derham_opts is None: - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\n{grid =}, {derham_opts =}: no Derham object set up.") - self._derham = None - else: - self._derham = setup_derham( - grid, - derham_opts, - comm=derham_comm, - domain=self.domain, - verbose=self.verbose, - ) + self._derham = setup_derham( + grid, + derham_opts, + comm=derham_comm, + domain=self.domain, + verbose=self.verbose, + ) - # create weighted mass and basis operators - if self.derham is None: - self._mass_ops = None - self._basis_ops = None - else: - self._mass_ops = WeightedMassOperators( + # create weighted mass operators + self._mass_ops = WeightedMassOperators( + self.derham, + self.domain, + verbose=self.verbose, + eq_mhd=self.equil, + ) + + # create projected equilibrium + if isinstance(self.equil, MHDequilibrium): + self._projected_equil = ProjectedMHDequilibrium( + self.equil, self.derham, - self.domain, - verbose=self.verbose, - eq_mhd=self.equil, ) - - self._basis_ops = BasisProjectionOperators( + elif isinstance(self.equil, FluidEquilibriumWithB): + self._projected_equil = ProjectedFluidEquilibriumWithB( + self.equil, + self.derham, + ) + elif isinstance(self.equil, FluidEquilibrium): + self._projected_equil = ProjectedFluidEquilibrium( + self.equil, self.derham, - self.domain, - verbose=self.verbose, - eq_mhd=self.equil, ) - - # create projected equilibrium - if self.derham is None: - self._projected_equil = None else: - if isinstance(self.equil, MHDequilibrium): - self._projected_equil = ProjectedMHDequilibrium( - self.equil, - self.derham, - ) - elif isinstance(self.equil, FluidEquilibriumWithB): - self._projected_equil = ProjectedFluidEquilibriumWithB( - self.equil, - self.derham, - ) - elif isinstance(self.equil, FluidEquilibrium): - self._projected_equil = ProjectedFluidEquilibrium( - self.equil, - self.derham, - ) - else: - self._projected_equil = None + self._projected_equil = None def allocate_propagators(self): # set propagators base class attributes (then available to all propagators) @@ -232,7 +212,12 @@ def allocate_propagators(self): Propagator.domain = self.domain if self.derham is not None: Propagator.mass_ops = self.mass_ops - Propagator.basis_ops = self.basis_ops + Propagator.basis_ops = BasisProjectionOperators( + self.derham, + self.domain, + verbose=self.verbose, + eq_mhd=self.equil, + ) Propagator.projected_equil = self.projected_equil assert len(self.prop_list) > 0, "No propagators in this model, check the model class." @@ -275,6 +260,11 @@ def clone_config(self, new): assert isinstance(new, CloneConfig) or new is None self._clone_config = new + @property + def diagnostics(self): + """Dictionary of diagnostics.""" + return self._diagnostics + @property def domain(self): """Domain object, see :ref:`avail_mappings`.""" @@ -310,11 +300,6 @@ def mass_ops(self): """WeighteMassOperators object, see :ref:`mass_ops`.""" return self._mass_ops - @property - def basis_ops(self): - """Basis projection operators.""" - return self._basis_ops - @property def prop_list(self): """List of Propagator objects.""" @@ -346,8 +331,6 @@ def kwargs(self): @property def scalar_quantities(self): """A dictionary of scalar quantities to be saved during the simulation.""" - if not hasattr(self, "_scalar_quantities"): - self._scalar_quantities = {} return self._scalar_quantities @property @@ -428,13 +411,13 @@ def getFromDict(dataDict, mapList): def setInDict(dataDict, mapList, value): # Loop over dicitionary and creaty empty dicts where the path does not exist for k in range(len(mapList)): - if mapList[k] not in getFromDict(dataDict, mapList[:k]).keys(): + if not mapList[k] in getFromDict(dataDict, mapList[:k]).keys(): getFromDict(dataDict, mapList[:k])[mapList[k]] = {} getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value # make sure that the base keys are top-level keys for base_key in ["em_fields", "fluid", "kinetic"]: - if base_key not in dct.keys(): + if not base_key in dct.keys(): dct[base_key] = {} if isinstance(species, str): @@ -472,13 +455,13 @@ def add_scalar(self, name: str, variable: PICVariable | SPHVariable = None, comp assert isinstance(name, str), "name must be a string" if compute == "from_particles": - assert isinstance(variable, (PICVariable, SPHVariable)), f"Variable is needed when {compute =}" + assert isinstance(variable, (PICVariable, SPHVariable)), f"Variable is needed when {compute = }" if not hasattr(self, "_scalar_quantities"): self._scalar_quantities = {} self._scalar_quantities[name] = { - "value": xp.empty(1, dtype=float), + "value": np.empty(1, dtype=float), "variable": variable, "compute": compute, "summands": summands, @@ -524,17 +507,17 @@ def update_scalar(self, name, value=None): assert isinstance(value, float) # Create a numpy array to hold the scalar value - value_array = xp.array([value], dtype=xp.float64) + value_array = np.array([value], dtype=np.float64) # Perform MPI operations based on the compute flags - if "sum_world" in compute_operations and not isinstance(MPI, MockMPI): + if "sum_world" in compute_operations: MPI.COMM_WORLD.Allreduce( MPI.IN_PLACE, value_array, op=MPI.SUM, ) - if "sum_within_clone" in compute_operations and self.derham.comm is not None: + if "sum_within_clone" in compute_operations: self.derham.comm.Allreduce( MPI.IN_PLACE, value_array, @@ -562,7 +545,7 @@ def update_scalar(self, name, value=None): if "divide_n_mks" in compute_operations: # Initialize the total number of markers - n_mks_tot = xp.array([variable.particles.Np]) + n_mks_tot = np.array([variable.particles.Np]) value_array /= n_mks_tot # Update the scalar value @@ -640,18 +623,6 @@ def allocate_variables(self, verbose: bool = False): verbose=verbose, ) - # allocate memory for FE coeffs of fluid variables - if self.diagnostic_species: - for species, spec in self.diagnostic_species.items(): - assert isinstance(spec, DiagnosticSpecies) - for k, v in spec.variables.items(): - assert isinstance(v, FEECVariable) - v.allocate( - derham=self.derham, - domain=self.domain, - equil=self.equil, - ) - # TODO: allocate memory for FE coeffs of diagnostics # if self.params.diagnostic_fields is not None: # for key, val in self.diagnostics.items(): @@ -721,18 +692,18 @@ def update_markers_to_be_saved(self): for name, species in self.particle_species.items(): assert isinstance(species, ParticleSpecies) - assert len(species.variables) == 1, "More than 1 variable per kinetic species is not allowed." + assert len(species.variables) == 1, f"More than 1 variable per kinetic species is not allowed." for _, var in species.variables.items(): assert isinstance(var, PICVariable | SPHVariable) obj = var.particles assert isinstance(obj, Particles) if var.n_to_save > 0: - markers_on_proc = xp.logical_and( + markers_on_proc = np.logical_and( obj.markers[:, -1] >= 0.0, obj.markers[:, -1] < var.n_to_save, ) - n_markers_on_proc = xp.count_nonzero(markers_on_proc) + n_markers_on_proc = np.count_nonzero(markers_on_proc) var.saved_markers[:] = -1.0 var.saved_markers[:n_markers_on_proc] = obj.markers[markers_on_proc] @@ -746,7 +717,7 @@ def update_distr_functions(self): for name, species in self.particle_species.items(): assert isinstance(species, ParticleSpecies) - assert len(species.variables) == 1, "More than 1 variable per kinetic species is not allowed." + assert len(species.variables) == 1, f"More than 1 variable per kinetic species is not allowed." for _, var in species.variables.items(): assert isinstance(var, PICVariable | SPHVariable) obj = var.particles @@ -776,7 +747,7 @@ def update_distr_functions(self): h2 = 1 / obj.boxes_per_dim[1] h3 = 1 / obj.boxes_per_dim[2] - ndim = xp.count_nonzero([d > 1 for d in obj.boxes_per_dim]) + ndim = np.count_nonzero([d > 1 for d in obj.boxes_per_dim]) if ndim == 0: kernel_type = "gaussian_3d" else: @@ -800,7 +771,7 @@ def print_scalar_quantities(self): sq_str = "" for key, scalar_dict in self._scalar_quantities.items(): val = scalar_dict["value"] - assert not xp.isnan(val[0]), f"Scalar {key} is {val[0]}." + assert not np.isnan(val[0]), f"Scalar {key} is {val[0]}." sq_str += key + ": {:14.11f}".format(val[0]) + " " print(sq_str) @@ -962,9 +933,8 @@ def print_scalar_quantities(self): # if obj.coords == "vpara_mu": # obj.save_magnetic_moment() - # obj.draw_markers(sort=True, verbose=self.verbose) - # if self.comm_world is not None: - # obj.mpi_sort_markers(do_test=True) + # if val["space"] != "ParticlesSPH" and obj.f0.coords == "constants_of_motion": + # obj.save_constants_of_motion() # obj.initialize_weights( # reject_weights=obj.weights_params["reject_weights"], @@ -1014,8 +984,7 @@ def initialize_from_restart(self, data): obj._markers[:, :] = data.file["restart/" + key][-1, :, :] # important: sets holes attribute of markers! - if self.comm_world is not None: - obj.mpi_sort_markers(do_test=True) + obj.mpi_sort_markers(do_test=True) def initialize_data_output(self, data: DataContainer, size): """ @@ -1107,7 +1076,7 @@ def initialize_data_output(self, data: DataContainer, size): # save kinetic data in group 'kinetic/' for name, species in self.particle_species.items(): assert isinstance(species, ParticleSpecies) - assert len(species.variables) == 1, "More than 1 variable per kinetic species is not allowed." + assert len(species.variables) == 1, f"More than 1 variable per kinetic species is not allowed." for varname, var in species.variables.items(): assert isinstance(var, PICVariable | SPHVariable) obj = var.particles @@ -1172,7 +1141,7 @@ def show_options(cls): print( 'Options are given under the keyword "options" for each species dict. \ -Available options stand in lists as dict values.\nThe first entry of a list denotes the default value.', +Available options stand in lists as dict values.\nThe first entry of a list denotes the default value.' ) tab = " " @@ -1233,6 +1202,7 @@ def write_parameters_to_file(cls, parameters=None, file=None, save=True, prompt= import yaml + import struphy import struphy.utils.utils as utils # Read struphy state file @@ -1300,7 +1270,7 @@ def generate_default_parameter_file( file = open(path, "w") else: print("exiting ...") - exit() + return except FileNotFoundError: folder = os.path.join("/", *path.split("/")[:-1]) if not prompt: @@ -1312,7 +1282,7 @@ def generate_default_parameter_file( file = open(path, "x") else: print("exiting ...") - exit() + return file.write("from struphy.io.options import EnvironmentOptions, BaseUnits, Time\n") file.write("from struphy.geometry import domains\n") @@ -1331,15 +1301,15 @@ def generate_default_parameter_file( has_plasma = True species_params += f"model.{sn}.set_phys_params()\n" if isinstance(species, ParticleSpecies): - particle_params += "\nloading_params = LoadingParameters()\n" - particle_params += "weights_params = WeightsParameters()\n" - particle_params += "boundary_params = BoundaryParameters()\n" + particle_params += f"\nloading_params = LoadingParameters()\n" + particle_params += f"weights_params = WeightsParameters()\n" + particle_params += f"boundary_params = BoundaryParameters()\n" particle_params += f"model.{sn}.set_markers(loading_params=loading_params,\n" - txt = "weights_params=weights_params,\n" + txt = f"weights_params=weights_params,\n" particle_params += indent(txt, " " * len(f"model.{sn}.set_markers(")) - txt = "boundary_params=boundary_params,\n" + txt = f"boundary_params=boundary_params,\n" particle_params += indent(txt, " " * len(f"model.{sn}.set_markers(")) - txt = ")\n" + txt = f")\n" particle_params += indent(txt, " " * len(f"model.{sn}.set_markers(")) particle_params += f"model.{sn}.set_sorting_boxes()\n" particle_params += f"model.{sn}.set_save_data()\n" @@ -1360,40 +1330,32 @@ def generate_default_parameter_file( elif isinstance(var, PICVariable): has_pic = True - init_pert_pic = ( - "\n# if .add_initial_condition is not called, the background is the kinetic initial condition\n" - ) - init_pert_pic += "perturbation = perturbations.TorusModesCos()\n" + init_pert_pic = f"\n# if .add_initial_condition is not called, the background is the kinetic initial condition\n" + init_pert_pic += f"perturbation = perturbations.TorusModesCos()\n" if "6D" in var.space: - init_bckgr_pic = "maxwellian_1 = maxwellians.Maxwellian3D(n=(1.0, None))\n" - init_bckgr_pic += "maxwellian_2 = maxwellians.Maxwellian3D(n=(0.1, None))\n" - init_pert_pic += "maxwellian_1pt = maxwellians.Maxwellian3D(n=(1.0, perturbation))\n" - init_pert_pic += "init = maxwellian_1pt + maxwellian_2\n" - init_pert_pic += f"model.{sn}.{vn}.add_initial_condition(init)\n" + init_bckgr_pic = f"maxwellian_1 = maxwellians.Maxwellian3D(n=(1.0, None))\n" + init_bckgr_pic += f"maxwellian_2 = maxwellians.Maxwellian3D(n=(0.1, None))\n" + init_pert_pic += f"maxwellian_1pt = maxwellians.Maxwellian3D(n=(1.0, perturbation))\n" + init_pert_pic += f"init = maxwellian_1pt + maxwellian_2\n" + init_pert_pic += f"model.kinetic_ions.var.add_initial_condition(init)\n" elif "5D" in var.space: - init_bckgr_pic = "maxwellian_1 = maxwellians.GyroMaxwellian2D(n=(1.0, None), equil=equil)\n" - init_bckgr_pic += "maxwellian_2 = maxwellians.GyroMaxwellian2D(n=(0.1, None), equil=equil)\n" + init_bckgr_pic = f"maxwellian_1 = maxwellians.GyroMaxwellian2D(n=(1.0, None), equil=equil)\n" + init_bckgr_pic += f"maxwellian_2 = maxwellians.GyroMaxwellian2D(n=(0.1, None), equil=equil)\n" init_pert_pic += ( - "maxwellian_1pt = maxwellians.GyroMaxwellian2D(n=(1.0, perturbation), equil=equil)\n" + f"maxwellian_1pt = maxwellians.GyroMaxwellian2D(n=(1.0, perturbation), equil=equil)\n" ) - init_pert_pic += "init = maxwellian_1pt + maxwellian_2\n" - init_pert_pic += f"model.{sn}.{vn}.add_initial_condition(init)\n" - if "3D" in var.space: - init_bckgr_pic = "maxwellian_1 = maxwellians.ColdPlasma(n=(1.0, None))\n" - init_bckgr_pic += "maxwellian_2 = maxwellians.ColdPlasma(n=(0.1, None))\n" - init_pert_pic += "maxwellian_1pt = maxwellians.ColdPlasma(n=(1.0, perturbation))\n" - init_pert_pic += "init = maxwellian_1pt + maxwellian_2\n" - init_pert_pic += f"model.{sn}.{vn}.add_initial_condition(init)\n" - init_bckgr_pic += "background = maxwellian_1 + maxwellian_2\n" + init_pert_pic += f"init = maxwellian_1pt + maxwellian_2\n" + init_pert_pic += f"model.kinetic_ions.var.add_initial_condition(init)\n" + init_bckgr_pic += f"background = maxwellian_1 + maxwellian_2\n" init_bckgr_pic += f"model.{sn}.{vn}.add_background(background)\n" - exclude = "# model.....save_data = False\n" + exclude = f"# model.....save_data = False\n" elif isinstance(var, SPHVariable): has_sph = True - init_bckgr_sph = "background = equils.ConstantVelocity()\n" + init_bckgr_sph = f"background = equils.ConstantVelocity()\n" init_bckgr_sph += f"model.{sn}.{vn}.add_background(background)\n" - init_pert_sph = "perturbation = perturbations.TorusModesCos()\n" + init_pert_sph = f"perturbation = perturbations.TorusModesCos()\n" init_pert_sph += f"model.{sn}.{vn}.add_perturbation(del_n=perturbation)\n" exclude = f"# model.{sn}.{vn}.save_data = False\n" @@ -1409,12 +1371,13 @@ def generate_default_parameter_file( BoundaryParameters,\n\ BinningPlot,\n\ KernelDensityPlot,\n\ - )\n", + )\n" ) file.write("from struphy import main\n") file.write("\n# import model, set verbosity\n") file.write(f"from {self.__module__} import {self.__class__.__name__}\n") + file.write("verbose = True\n") file.write("\n# environment options\n") file.write("env = EnvironmentOptions()\n") @@ -1431,12 +1394,12 @@ def generate_default_parameter_file( file.write("\n# fluid equilibrium (can be used as part of initial conditions)\n") file.write("equil = equils.HomogenSlab()\n") - # if has_feec: - grid = "grid = grids.TensorProductGrid()\n" - derham = "derham_opts = DerhamOptions()\n" - # else: - # grid = "grid = None\n" - # derham = "derham_opts = None\n" + if has_feec: + grid = "grid = grids.TensorProductGrid()\n" + derham = "derham_opts = DerhamOptions()\n" + else: + grid = "grid = None\n" + derham = "derham_opts = None\n" file.write("\n# grid\n") file.write(grid) @@ -1473,7 +1436,6 @@ def generate_default_parameter_file( file.write('\nif __name__ == "__main__":\n') file.write(" # start run\n") - file.write(" verbose = True\n\n") file.write( " main.run(model,\n\ params_path=__file__,\n\ @@ -1485,14 +1447,14 @@ def generate_default_parameter_file( grid=grid,\n\ derham_opts=derham_opts,\n\ verbose=verbose,\n\ - )", + )" ) file.close() print( f"\nDefault parameter file for '{self.__class__.__name__}' has been created in the cwd ({path}).\n\ -You can now launch a simulation with 'python params_{self.__class__.__name__}.py'", +You can now launch a simulation with 'python params_{self.__class__.__name__}.py'" ) return path @@ -1501,6 +1463,131 @@ def generate_default_parameter_file( # Private methods : ################### + def _init_variable_dicts(self): + """ + Initialize em-fields, fluid and kinetic dictionaries for information on the model variables. + """ + + # electromagnetic fields, fluid and/or kinetic species + self._em_fields = {} + self._fluid = {} + self._kinetic = {} + self._diagnostics = {} + + if self.rank_world == 0 and self.verbose: + print("\nMODEL SPECIES:") + + # create dictionaries for each em-field/species and fill in space/class name and parameters + for var_name, space in self.species()["em_fields"].items(): + assert space in {"H1", "Hcurl", "Hdiv", "L2", "H1vec"} + assert self.params.em_fields is not None, '"em_fields" is missing in parameter file.' + + if self.rank_world == 0 and self.verbose: + print("em_field:".ljust(25), f'"{var_name}" ({space})') + + self._em_fields[var_name] = {} + + # space + self._em_fields[var_name]["space"] = space + + # initial conditions + if "background" in self.params.em_fields: + # background= self.params.em_fields["background"].get(var_name) + self._em_fields[var_name]["background"] = self.params.em_fields["background"].get(var_name) + # else: + # background = None + + if "perturbation" in self.params.em_fields: + # perturbation = self.params.em_fields["perturbation"].get(var_name) + self._em_fields[var_name]["perturbation"] = self.params.em_fields["perturbation"].get(var_name) + # else: + # perturbation = None + + # which components to save + if "save_data" in self.params.em_fields: + # save_data = self.params.em_fields["save_data"]["comps"][var_name] + self._em_fields[var_name]["save_data"] = self.params.em_fields["save_data"]["comps"][var_name] + else: + self._em_fields[var_name]["save_data"] = True + # save_data = True + + # self._em_fields[var_name] = Variable(name=var_name, + # space=space, + # background=background, + # perturbation=perturbation, + # save_data=save_data,) + + # overall parameters + # print(f'{self._em_fields = }') + self._em_fields["params"] = self.params.em_fields + + for var_name, space in self.species()["fluid"].items(): + assert isinstance(space, dict) + assert "fluid" in self.params, 'Top-level key "fluid" is missing in parameter file.' + assert var_name in self.params["fluid"], f"Fluid species {var_name} is missing in parameter file." + + if self.rank_world == 0 and self.verbose: + print("fluid:".ljust(25), f'"{var_name}" ({space})') + + self._fluid[var_name] = {} + for sub_var_name, sub_space in space.items(): + self._fluid[var_name][sub_var_name] = {} + + # space + self._fluid[var_name][sub_var_name]["space"] = sub_space + + # initial conditions + if "background" in self.params["fluid"][var_name]: + self._fluid[var_name][sub_var_name]["background"] = self.params["fluid"][var_name][ + "background" + ].get(sub_var_name) + if "perturbation" in self.params["fluid"][var_name]: + self._fluid[var_name][sub_var_name]["perturbation"] = self.params["fluid"][var_name][ + "perturbation" + ].get(sub_var_name) + + # which components to save + if "save_data" in self.params["fluid"][var_name]: + self._fluid[var_name][sub_var_name]["save_data"] = self.params["fluid"][var_name]["save_data"][ + "comps" + ][sub_var_name] + + else: + self._fluid[var_name][sub_var_name]["save_data"] = True + + # overall parameters + self._fluid[var_name]["params"] = self.params["fluid"][var_name] + + for var_name, space in self.species()["kinetic"].items(): + assert "Particles" in space + assert "kinetic" in self.params, 'Top-level key "kinetic" is missing in parameter file.' + assert var_name in self.params["kinetic"], f"Kinetic species {var_name} is missing in parameter file." + + if self.rank_world == 0 and self.verbose: + print("kinetic:".ljust(25), f'"{var_name}" ({space})') + + self._kinetic[var_name] = {} + self._kinetic[var_name]["space"] = space + self._kinetic[var_name]["params"] = self.params["kinetic"][var_name] + + if self.diagnostics_dct() is not None: + for var_name, space in self.diagnostics_dct().items(): + assert space in {"H1", "Hcurl", "Hdiv", "L2", "H1vec"} + + if self.rank_world == 0 and self.verbose: + print("diagnostics:".ljust(25), f'"{var_name}" ({space})') + + self._diagnostics[var_name] = {} + self._diagnostics[var_name]["space"] = space + self._diagnostics["params"] = self.params["diagnostics"][var_name] + + # which components to save + if "save_data" in self.params["diagnostics"][var_name]: + self._diagnostics[var_name]["save_data"] = self.params["diagnostics"][var_name]["save_data"] + + else: + self._diagnostics[var_name]["save_data"] = True + def compute_plasma_params(self, verbose=True): """ Compute and print volume averaged plasma parameters for each species of the model. @@ -1555,15 +1642,15 @@ def compute_plasma_params(self, verbose=True): units_affix["epsilon"] = "" h = 1 / 20 - eta1 = xp.linspace(h / 2.0, 1.0 - h / 2.0, 20) - eta2 = xp.linspace(h / 2.0, 1.0 - h / 2.0, 20) - eta3 = xp.linspace(h / 2.0, 1.0 - h / 2.0, 20) + eta1 = np.linspace(h / 2.0, 1.0 - h / 2.0, 20) + eta2 = np.linspace(h / 2.0, 1.0 - h / 2.0, 20) + eta3 = np.linspace(h / 2.0, 1.0 - h / 2.0, 20) ## global parameters # plasma volume (hat x^3) det_tmp = self.domain.jacobian_det(eta1, eta2, eta3) - vol1 = xp.mean(xp.abs(det_tmp)) + vol1 = np.mean(np.abs(det_tmp)) # plasma volume (m⁻³) plasma_volume = vol1 * self.units.x**3 # transit length (m) @@ -1572,35 +1659,35 @@ def compute_plasma_params(self, verbose=True): if isinstance(self.equil, FluidEquilibriumWithB): B_tmp = self.equil.absB0(eta1, eta2, eta3) else: - B_tmp = xp.zeros((eta1.size, eta2.size, eta3.size)) - magnetic_field = xp.mean(B_tmp * xp.abs(det_tmp)) / vol1 * self.units.B - B_max = xp.max(B_tmp) * self.units.B - B_min = xp.min(B_tmp) * self.units.B + B_tmp = np.zeros((eta1.size, eta2.size, eta3.size)) + magnetic_field = np.mean(B_tmp * np.abs(det_tmp)) / vol1 * self.units.B + B_max = np.max(B_tmp) * self.units.B + B_min = np.min(B_tmp) * self.units.B if magnetic_field < 1e-14: - magnetic_field = xp.nan + magnetic_field = np.nan # print("\n+++++++ WARNING +++++++ magnetic field is zero - set to nan !!") if verbose and MPI.COMM_WORLD.Get_rank() == 0: print("\nPLASMA PARAMETERS:") print( - "Plasma volume:".ljust(25), + f"Plasma volume:".ljust(25), "{:4.3e}".format(plasma_volume) + units_affix["plasma volume"], ) print( - "Transit length:".ljust(25), + f"Transit length:".ljust(25), "{:4.3e}".format(transit_length) + units_affix["transit length"], ) print( - "Avg. magnetic field:".ljust(25), + f"Avg. magnetic field:".ljust(25), "{:4.3e}".format(magnetic_field) + units_affix["magnetic field"], ) print( - "Max magnetic field:".ljust(25), + f"Max magnetic field:".ljust(25), "{:4.3e}".format(B_max) + units_affix["magnetic field"], ) print( - "Min magnetic field:".ljust(25), + f"Min magnetic field:".ljust(25), "{:4.3e}".format(B_min) + units_affix["magnetic field"], ) @@ -1618,13 +1705,13 @@ def compute_plasma_params(self, verbose=True): # self._pparams[species]["charge"] = val["params"]["phys_params"]["Z"] * e # # density (m⁻³) # self._pparams[species]["density"] = ( - # xp.mean( + # np.mean( # self.equil.n0( # eta1, # eta2, # eta3, # ) - # * xp.abs(det_tmp), + # * np.abs(det_tmp), # ) # * self.units.x ** 3 # / plasma_volume @@ -1632,13 +1719,13 @@ def compute_plasma_params(self, verbose=True): # ) # # pressure (bar) # self._pparams[species]["pressure"] = ( - # xp.mean( + # np.mean( # self.equil.p0( # eta1, # eta2, # eta3, # ) - # * xp.abs(det_tmp), + # * np.abs(det_tmp), # ) # * self.units.x ** 3 # / plasma_volume @@ -1649,7 +1736,7 @@ def compute_plasma_params(self, verbose=True): # self._pparams[species]["kBT"] = self._pparams[species]["pressure"] * 1e5 / self._pparams[species]["density"] / e * 1e-3 # if len(self.kinetic) > 0: - # eta1mg, eta2mg, eta3mg = xp.meshgrid( + # eta1mg, eta2mg, eta3mg = np.meshgrid( # eta1, # eta2, # eta3, @@ -1695,11 +1782,11 @@ def compute_plasma_params(self, verbose=True): # # density (m⁻³) # self._pparams[species]["density"] = ( - # xp.mean(tmp.n(psi) * xp.abs(det_tmp)) * self.units.x ** 3 / plasma_volume * self.units.n + # np.mean(tmp.n(psi) * np.abs(det_tmp)) * self.units.x ** 3 / plasma_volume * self.units.n # ) # # thermal speed (m/s) # self._pparams[species]["v_th"] = ( - # xp.mean(tmp.vth(psi) * xp.abs(det_tmp)) * self.units.x ** 3 / plasma_volume * self.units.v + # np.mean(tmp.vth(psi) * np.abs(det_tmp)) * self.units.x ** 3 / plasma_volume * self.units.v # ) # # thermal energy (keV) # self._pparams[species]["kBT"] = self._pparams[species]["mass"] * self._pparams[species]["v_th"] ** 2 / e * 1e-3 @@ -1710,8 +1797,8 @@ def compute_plasma_params(self, verbose=True): # else: # # density (m⁻³) - # # self._pparams[species]['density'] = xp.mean(tmp.n( - # # eta1mg, eta2mg, eta3mg) * xp.abs(det_tmp)) * units['x']**3 / plasma_volume * units['n'] + # # self._pparams[species]['density'] = np.mean(tmp.n( + # # eta1mg, eta2mg, eta3mg) * np.abs(det_tmp)) * units['x']**3 / plasma_volume * units['n'] # self._pparams[species]["density"] = 99.0 # # thermal speeds (m/s) # vth = [] @@ -1719,11 +1806,11 @@ def compute_plasma_params(self, verbose=True): # vths = [99.0] # for k in range(len(vths)): # vth += [ - # vths[k] * xp.abs(det_tmp) * self.units.x ** 3 / plasma_volume * self.units.v, + # vths[k] * np.abs(det_tmp) * self.units.x ** 3 / plasma_volume * self.units.v, # ] # thermal_speed = 0.0 # for dir in range(val["obj"].vdim): - # # self._pparams[species]['vth' + str(dir + 1)] = xp.mean(vth[dir]) + # # self._pparams[species]['vth' + str(dir + 1)] = np.mean(vth[dir]) # self._pparams[species]["vth" + str(dir + 1)] = 99.0 # thermal_speed += self._pparams[species]["vth" + str(dir + 1)] # # TODO: here it is assumed that background density parameter is called "n", @@ -1742,11 +1829,11 @@ def compute_plasma_params(self, verbose=True): # for species in self._pparams: # # alfvén speed (m/s) - # self._pparams[species]["v_A"] = magnetic_field / xp.sqrt( + # self._pparams[species]["v_A"] = magnetic_field / np.sqrt( # mu0 * self._pparams[species]["mass"] * self._pparams[species]["density"], # ) # # thermal speed (m/s) - # self._pparams[species]["v_th"] = xp.sqrt( + # self._pparams[species]["v_th"] = np.sqrt( # self._pparams[species]["kBT"] * 1e3 * e / self._pparams[species]["mass"], # ) # # thermal frequency (Mrad/s) @@ -1755,7 +1842,7 @@ def compute_plasma_params(self, verbose=True): # self._pparams[species]["Omega_c"] = self._pparams[species]["charge"] * magnetic_field / self._pparams[species]["mass"] * 1e-6 # # plasma frequency (Mrad/s) # self._pparams[species]["Omega_p"] = ( - # xp.sqrt( + # np.sqrt( # self._pparams[species]["density"] * (self._pparams[species]["charge"]) ** 2 / eps0 / self._pparams[species]["mass"], # ) # * 1e-6 @@ -1765,7 +1852,7 @@ def compute_plasma_params(self, verbose=True): # # Larmor radius (m) # self._pparams[species]["rho_th"] = self._pparams[species]["v_th"] / (self._pparams[species]["Omega_c"] * 1e6) # # MHD length scale (m) - # self._pparams[species]["v_A/Omega_c"] = self._pparams[species]["v_A"] / (xp.abs(self._pparams[species]["Omega_c"]) * 1e6) + # self._pparams[species]["v_A/Omega_c"] = self._pparams[species]["v_A"] / (np.abs(self._pparams[species]["Omega_c"]) * 1e6) # # dim-less ratios # self._pparams[species]["rho_th/L"] = self._pparams[species]["rho_th"] / transit_length diff --git a/src/struphy/models/fluid.py b/src/struphy/models/fluid.py index 405610b7b..26e6314ee 100644 --- a/src/struphy/models/fluid.py +++ b/src/struphy/models/fluid.py @@ -1,12 +1,9 @@ -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI from psydac.linalg.block import BlockVector -from psydac.linalg.stencil import StencilVector -from struphy.feec.projectors import L2Projector -from struphy.feec.variational_utilities import H1vecMassMatrix_density, InternalEnergyEvaluator from struphy.models.base import StruphyModel -from struphy.models.species import DiagnosticSpecies, FieldSpecies, FluidSpecies, ParticleSpecies +from struphy.models.species import FieldSpecies, FluidSpecies, ParticleSpecies from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.polar.basic import PolarVector from struphy.propagators import propagators_coupling, propagators_fields, propagators_markers @@ -157,7 +154,7 @@ def generate_default_parameter_file(self, path=None, prompt=True): for line in f: if "mag_sonic.Options" in line: new_file += [ - "model.propagators.mag_sonic.options = model.propagators.mag_sonic.Options(b_field=model.em_fields.b_field)\n", + "model.propagators.mag_sonic.options = model.propagators.mag_sonic.Options(b_field=model.em_fields.b_field)\n" ] else: new_file += [line] @@ -206,52 +203,87 @@ class LinearExtendedMHDuniform(StruphyModel): :ref:`Model info `: """ - ## species + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hcurl") - self.init_variables() + dct["em_fields"]["b_field"] = "Hcurl" + dct["fluid"]["mhd"] = { + "rho": "L2", + "u": "Hdiv", + "p": "L2", + } + return dct - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="Hdiv") - self.pressure = FEECVariable(space="L2") - self.init_variables() + @staticmethod + def bulk_species(): + return "mhd" - ## propagators + @staticmethod + def velocity_scale(): + return "alfvén" - class Propagators: - def __init__(self): - self.shear_alf = propagators_fields.ShearAlfvenB1() - self.hall = propagators_fields.Hall() - self.mag_sonic = propagators_fields.MagnetosonicUniform() + @staticmethod + def propagators_dct(): + return { + propagators_fields.ShearAlfvenB1: ["mhd_u", "b_field"], + propagators_fields.Hall: ["b_field"], + propagators_fields.MagnetosonicUniform: ["mhd_rho", "mhd_u", "mhd_p"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + from struphy.polar.basic import PolarVector + + # extract necessary parameters + alfven_solver = params["fluid"]["mhd"]["options"]["ShearAlfvenB1"]["solver"] + M1_inv = params["fluid"]["mhd"]["options"]["ShearAlfvenB1"]["solver_M1"] + hall_solver = params["em_fields"]["options"]["Hall"]["solver"] + sonic_solver = params["fluid"]["mhd"]["options"]["MagnetosonicUniform"]["solver"] + + # project background magnetic field (1-form) and pressure (3-form) + self._b_eq = self.projected_equil.b1 + self._a_eq = self.projected_equil.a1 + self._p_eq = self.projected_equil.p3 + self._ones = self.pointer["mhd_p"].space.zeros() - ## abstract methods + if isinstance(self._ones, PolarVector): + self._ones.tp[:] = 1.0 + else: + self._ones[:] = 1.0 - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + # compute coupling parameters + epsilon = self.equation_params["mhd"]["epsilon"] - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() + if abs(epsilon - 1) < 1e-6: + epsilon = 1.0 - # 2. instantiate all propagators - self.propagators = self.Propagators() + # set keyword arguments for propagators + self._kwargs[propagators_fields.ShearAlfvenB1] = { + "solver": alfven_solver, + "solver_M1": M1_inv, + } - # 3. assign variables to propagators - self.propagators.shear_alf.variables.u = self.mhd.velocity - self.propagators.shear_alf.variables.b = self.em_fields.b_field + self._kwargs[propagators_fields.Hall] = { + "solver": hall_solver, + "epsilon": epsilon, + } - self.propagators.hall.variables.b = self.em_fields.b_field + self._kwargs[propagators_fields.MagnetosonicUniform] = {"solver": sonic_solver} - self.propagators.mag_sonic.variables.n = self.mhd.density - self.propagators.mag_sonic.variables.u = self.mhd.velocity - self.propagators.mag_sonic.variables.p = self.mhd.pressure + # Initialize propagators used in splitting substeps + self.init_propagators() - # define scalars for update_scalar_quantities + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_p") self.add_scalar("en_B") @@ -261,45 +293,17 @@ def __init__(self): self.add_scalar("en_tot") self.add_scalar("helicity") - @property - def bulk_species(self): - return self.mhd - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - self._b_eq = self.projected_equil.b1 - self._a_eq = self.projected_equil.a1 - self._p_eq = self.projected_equil.p3 - - self._ones = self.projected_equil.p3.space.zeros() - if isinstance(self._ones, PolarVector): - self._ones.tp[:] = 1.0 - else: - self._ones[:] = 1.0 - - self._tmp_b1: BlockVector = self.derham.Vh["1"].zeros() # TODO: replace derham.Vh dict by class - self._tmp_b2: BlockVector = self.derham.Vh["1"].zeros() - - # adjust coupling parameters - epsilon = self.mhd.equation_params.epsilon - - if abs(epsilon - 1) < 1e-6: - self.mhd.equation_params.epsilon = 1.0 + # temporary vectors for scalar quantities + self._tmp_b1 = self.derham.Vh["1"].zeros() + self._tmp_b2 = self.derham.Vh["1"].zeros() def update_scalar_quantities(self): # perturbed fields - u = self.mhd.velocity.spline.vector - p = self.mhd.pressure.spline.vector - b = self.em_fields.b_field.spline.vector - - en_U = 0.5 * self.mass_ops.M2n.dot_inner(u, u) - b1 = self.mass_ops.M1.dot(b, out=self._tmp_b1) - en_B = 0.5 * b.inner(b1) + en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_u"], self.pointer["mhd_u"]) + b1 = self.mass_ops.M1.dot(self.pointer["b_field"], out=self._tmp_b1) + en_B = 0.5 * self.pointer["b_field"].inner(b1) helicity = 2.0 * self._a_eq.inner(b1) - en_p_i = p.inner(self._ones) / (5.0 / 3.0 - 1.0) + en_p_i = self.pointer["mhd_p"].inner(self._ones) / (5.0 / 3.0 - 1.0) self.update_scalar("en_U", en_U) self.update_scalar("en_B", en_B) @@ -317,30 +321,13 @@ def update_scalar_quantities(self): # total magnetic field b1 = self._b_eq.copy(out=self._tmp_b1) - self._tmp_b1 += b + self._tmp_b1 += self.pointer["b_field"] b2 = self.mass_ops.M1.dot(b1, apply_bc=False, out=self._tmp_b2) en_Btot = b1.inner(b2) / 2.0 self.update_scalar("en_B_tot", en_Btot) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "hall.Options" in line: - new_file += [ - "model.propagators.hall.options = model.propagators.hall.Options(epsilon_from=model.mhd)\n", - ] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - class ColdPlasma(StruphyModel): r"""Cold plasma model. @@ -377,74 +364,82 @@ class ColdPlasma(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.e_field = FEECVariable(space="Hcurl") - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class Electrons(FluidSpecies): - def __init__(self): - self.current = FEECVariable(space="Hcurl") - self.init_variables() - - ## propagators - - class Propagators: - def __init__(self): - self.maxwell = propagators_fields.Maxwell() - self.ohm = propagators_fields.OhmCold() - self.jxb = propagators_fields.JxBCold() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.electrons = self.Electrons() - - # 2. instantiate all propagators - self.propagators = self.Propagators() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - # 3. assign variables to propagators - self.propagators.maxwell.variables.e = self.em_fields.e_field - self.propagators.maxwell.variables.b = self.em_fields.b_field + dct["em_fields"]["e_field"] = "Hcurl" + dct["em_fields"]["b_field"] = "Hdiv" + dct["fluid"]["electrons"] = {"j": "Hcurl"} + return dct - self.propagators.ohm.variables.j = self.electrons.current - self.propagators.ohm.variables.e = self.em_fields.e_field + @staticmethod + def bulk_species(): + return "electrons" - self.propagators.jxb.variables.j = self.electrons.current + @staticmethod + def velocity_scale(): + return "light" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.Maxwell: ["e_field", "b_field"], + propagators_fields.OhmCold: ["electrons_j", "e_field"], + propagators_fields.JxBCold: ["electrons_j"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + # model parameters + self._alpha = self.equation_params["electrons"]["alpha"] + self._epsilon = self.equation_params["electrons"]["epsilon"] + + # solver parameters + params_maxwell = params["em_fields"]["options"]["Maxwell"]["solver"] + params_ohmcold = params["fluid"]["electrons"]["options"]["OhmCold"]["solver"] + params_jxbcold = params["fluid"]["electrons"]["options"]["JxBCold"]["solver"] + + # set keyword arguments for propagators + self._kwargs[propagators_fields.Maxwell] = {"solver": params_maxwell} + + self._kwargs[propagators_fields.OhmCold] = { + "alpha": self._alpha, + "epsilon": self._epsilon, + "solver": params_ohmcold, + } + + self._kwargs[propagators_fields.JxBCold] = { + "epsilon": self._epsilon, + "solver": params_jxbcold, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("electric energy") self.add_scalar("magnetic energy") self.add_scalar("kinetic energy") self.add_scalar("total energy") - @property - def bulk_species(self): - return self.electrons - - @property - def velocity_scale(self): - return "light" - - def allocate_helpers(self): - self._alpha = self.electrons.equation_params.alpha - def update_scalar_quantities(self): - e = self.em_fields.e_field.spline.vector - b = self.em_fields.b_field.spline.vector - j = self.electrons.current.spline.vector - - en_E = 0.5 * self.mass_ops.M1.dot_inner(e, e) - en_B = 0.5 * self.mass_ops.M2.dot_inner(b, b) - en_J = 0.5 * self._alpha**2 * self.mass_ops.M1ninv.dot_inner(j, j) + en_E = 0.5 * self.mass_ops.M1.dot_inner(self.pointer["e_field"], self.pointer["e_field"]) + en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) + en_J = ( + 0.5 + * self._alpha**2 + * self.mass_ops.M1ninv.dot_inner(self.pointer["electrons_j"], self.pointer["electrons_j"]) + ) self.update_scalar("electric energy", en_E) self.update_scalar("magnetic energy", en_B) @@ -452,7 +447,7 @@ def update_scalar_quantities(self): self.update_scalar("total energy", en_E + en_B + en_J) -class ViscoResistiveMHD(StruphyModel): +class ViscoresistiveMHD(StruphyModel): r"""Full (non-linear) visco-resistive MHD equations discretized with a variational method. :ref:`normalization`: @@ -488,73 +483,139 @@ class ViscoResistiveMHD(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.entropy = FEECVariable(space="L2") - self.init_variables() - - ## propagators - - class Propagators: - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - self.variat_ent = propagators_fields.VariationalEntropyEvolve() - self.variat_mag = propagators_fields.VariationalMagFieldEvolve() - if with_viscosity: - self.variat_viscous = propagators_fields.VariationalViscosity() - if with_resistivity: - self.variat_resist = propagators_fields.VariationalResistivity() - - ## abstract methods - - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["em_fields"]["b2"] = "Hdiv" + dct["fluid"]["mhd"] = {"rho3": "L2", "s3": "L2", "uv": "H1vec"} + return dct - # 2. instantiate all propagators - self.propagators = self.Propagators( - with_viscosity=with_viscosity, - with_resistivity=with_resistivity, - ) + @staticmethod + def bulk_species(): + return "mhd" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.mhd.density - self.propagators.variat_dens.variables.u = self.mhd.velocity - self.propagators.variat_mom.variables.u = self.mhd.velocity - self.propagators.variat_ent.variables.s = self.mhd.entropy - self.propagators.variat_ent.variables.u = self.mhd.velocity - self.propagators.variat_mag.variables.u = self.mhd.velocity - self.propagators.variat_mag.variables.b = self.em_fields.b_field - if with_viscosity: - self.propagators.variat_viscous.variables.s = self.mhd.entropy - self.propagators.variat_viscous.variables.u = self.mhd.velocity - if with_resistivity: - self.propagators.variat_resist.variables.s = self.mhd.entropy - self.propagators.variat_resist.variables.b = self.em_fields.b_field + @staticmethod + def velocity_scale(): + return "alfvén" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], + propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], + propagators_fields.VariationalEntropyEvolve: ["mhd_s3", "mhd_uv"], + propagators_fields.VariationalMagFieldEvolve: ["b2", "mhd_uv"], + propagators_fields.VariationalViscosity: ["mhd_s3", "mhd_uv"], + propagators_fields.VariationalResistivity: ["mhd_s3", "b2"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import H1vecMassMatrix_density, InternalEnergyEvaluator + from struphy.polar.basic import PolarVector + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_entropy = params["fluid"]["mhd"]["options"]["VariationalEntropyEvolve"]["lin_solver"] + nonlin_solver_entropy = params["fluid"]["mhd"]["options"]["VariationalEntropyEvolve"]["nonlin_solver"] + lin_solver_magfield = params["em_fields"]["options"]["VariationalMagFieldEvolve"]["lin_solver"] + nonlin_solver_magfield = params["em_fields"]["options"]["VariationalMagFieldEvolve"]["nonlin_solver"] + lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] + nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] + lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] + nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] + if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): + self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] + else: + self._linearize_current = False + self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] + self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] + self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] + self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] + self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] + model = "full" + + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "s": self.pointer["mhd_s3"], + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + "energy_evaluator": self._energy_evaluator, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + self._kwargs[propagators_fields.VariationalEntropyEvolve] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_entropy, + "nonlin_solver": nonlin_solver_entropy, + "energy_evaluator": self._energy_evaluator, + } + + self._kwargs[propagators_fields.VariationalMagFieldEvolve] = { + "model": model, + "mass_ops": self.WMM, + "lin_solver": lin_solver_magfield, + "nonlin_solver": nonlin_solver_magfield, + } + + self._kwargs[propagators_fields.VariationalViscosity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "mu": self._mu, + "mu_a": self._mu_a, + "alpha": self._alpha, + "mass_ops": self.WMM, + "lin_solver": lin_solver_viscosity, + "nonlin_solver": nonlin_solver_viscosity, + "energy_evaluator": self._energy_evaluator, + } + + self._kwargs[propagators_fields.VariationalResistivity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "eta": self._eta, + "eta_a": self._eta_a, + "lin_solver": lin_solver_resistivity, + "nonlin_solver": nonlin_solver_resistivity, + "linearize_current": self._linearize_current, + "energy_evaluator": self._energy_evaluator, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag") @@ -563,24 +624,16 @@ def __init__( self.add_scalar("entr_tot") self.add_scalar("tot_div_B") - @property - def bulk_species(self): - return self.mhd - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - projV3 = L2Projector("L2", self._mass_ops) + # temporary vectors for scalar quantities + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + tmp_dof = self.derham.Vh_pol["3"].zeros() + projV3 = L2Projector("L2", self.mass_ops) def f(e1, e2, e3): return 1 - f = xp.vectorize(f) - self._integrator = projV3(f) - - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self.propagators.variat_ent.options.gamma) + f = np.vectorize(f) + self._integrator = projV3(f, dofs=tmp_dof) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -588,18 +641,12 @@ def f(e1, e2, e3): else: self._ones[:] = 1.0 - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - def update_scalar_quantities(self): - rho = self.mhd.density.spline.vector - u = self.mhd.velocity.spline.vector - s = self.mhd.entropy.spline.vector - b = self.em_fields.b_field.spline.vector - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + # Update mass matrix + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) self.update_scalar("en_U", en_U) - en_mag = 0.5 * self.mass_ops.M2.dot_inner(b, b) + en_mag = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) self.update_scalar("en_mag", en_mag) en_thermo = self.update_thermo_energy() @@ -607,12 +654,12 @@ def update_scalar_quantities(self): en_tot = en_U + en_thermo + en_mag self.update_scalar("en_tot", en_tot) - dens_tot = self._ones.inner(rho) + dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) self.update_scalar("dens_tot", dens_tot) - entr_tot = self._ones.inner(s) + entr_tot = self._ones.inner(self.pointer["mhd_s3"]) self.update_scalar("entr_tot", entr_tot) - div_B = self.derham.div.dot(b, out=self._tmp_div_B) + div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) self.update_scalar("tot_div_B", L2_div_B) @@ -621,12 +668,9 @@ def update_thermo_energy(self): :meta private: """ - rho = self.mhd.density.spline.vector - s = self.mhd.entropy.spline.vector - en_prop = self.propagators.variat_dens - - self._energy_evaluator.sf.vector = s - self._energy_evaluator.rhof.vector = rho + en_prop = self._propagators[0] + self._energy_evaluator.sf.vector = self.pointer["mhd_s3"] + self._energy_evaluator.rhof.vector = self.pointer["mhd_rho3"] sf_values = self._energy_evaluator.sf.eval_tp_fixed_loc( self._energy_evaluator.integration_grid_spans, self._energy_evaluator.integration_grid_bd, @@ -644,44 +688,6 @@ def update_thermo_energy(self): self.update_scalar("en_thermo", en_thermo) return en_thermo - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='full',\n", - ] - new_file += [ - " s=model.mhd.entropy)\n", - ] - elif "variat_ent.Options" in line: - new_file += [ - "model.propagators.variat_ent.options = model.propagators.variat_ent.Options(model='full',\n", - ] - new_file += [ - " rho=model.mhd.density)\n", - ] - elif "variat_viscous.Options" in line: - new_file += [ - "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(rho=model.mhd.density)\n", - ] - elif "variat_resist.Options" in line: - new_file += [ - "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(rho=model.mhd.density)\n", - ] - elif "entropy.add_background" in line: - new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - class ViscousFluid(StruphyModel): r"""Full (non-linear) viscous Navier-Stokes equations discretized with a variational method. @@ -715,72 +721,122 @@ class ViscousFluid(StruphyModel): :ref:`Model info `: """ - ## species - - class Fluid(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.entropy = FEECVariable(space="L2") - self.init_variables() - - ## propagators - - class Propagators: - def __init__(self, with_viscosity: bool = True): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - self.variat_ent = propagators_fields.VariationalEntropyEvolve() - if with_viscosity: - self.variat_viscous = propagators_fields.VariationalViscosity() - - ## abstract methods - - def __init__(self, with_viscosity: bool = True): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.fluid = self.Fluid() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["fluid"]["fluid"] = {"rho3": "L2", "s3": "L2", "uv": "H1vec"} + return dct - # 2. instantiate all propagators - self.propagators = self.Propagators(with_viscosity=with_viscosity) + @staticmethod + def bulk_species(): + return "fluid" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.fluid.density - self.propagators.variat_dens.variables.u = self.fluid.velocity - self.propagators.variat_mom.variables.u = self.fluid.velocity - self.propagators.variat_ent.variables.s = self.fluid.entropy - self.propagators.variat_ent.variables.u = self.fluid.velocity - if with_viscosity: - self.propagators.variat_viscous.variables.s = self.fluid.entropy - self.propagators.variat_viscous.variables.u = self.fluid.velocity + @staticmethod + def velocity_scale(): + return "alfvén" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["fluid_rho3", "fluid_uv"], + propagators_fields.VariationalMomentumAdvection: ["fluid_uv"], + propagators_fields.VariationalEntropyEvolve: ["fluid_s3", "fluid_uv"], + propagators_fields.VariationalViscosity: ["fluid_s3", "fluid_uv"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.polar.basic import PolarVector + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + from struphy.feec.variational_utilities import H1vecMassMatrix_density, InternalEnergyEvaluator + + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_entropy = params["fluid"]["fluid"]["options"]["VariationalEntropyEvolve"]["lin_solver"] + nonlin_solver_entropy = params["fluid"]["fluid"]["options"]["VariationalEntropyEvolve"]["nonlin_solver"] + lin_solver_viscosity = params["fluid"]["fluid"]["options"]["VariationalViscosity"]["lin_solver"] + nonlin_solver_viscosity = params["fluid"]["fluid"]["options"]["VariationalViscosity"]["nonlin_solver"] + + self._gamma = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + self._mu = params["fluid"]["fluid"]["options"]["VariationalViscosity"]["physics"]["mu"] + self._mu_a = params["fluid"]["fluid"]["options"]["VariationalViscosity"]["physics"]["mu_a"] + model = "full" + + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "s": self.pointer["fluid_s3"], + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + "energy_evaluator": self._energy_evaluator, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + self._kwargs[propagators_fields.VariationalEntropyEvolve] = { + "model": model, + "rho": self.pointer["fluid_rho3"], + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_entropy, + "nonlin_solver": nonlin_solver_entropy, + "energy_evaluator": self._energy_evaluator, + } + + self._kwargs[propagators_fields.VariationalViscosity] = { + "model": model, + "gamma": self._gamma, + "rho": self.pointer["fluid_rho3"], + "mu": self._mu, + "mu_a": self._mu_a, + "mass_ops": self.WMM, + "lin_solver": lin_solver_viscosity, + "nonlin_solver": nonlin_solver_viscosity, + "energy_evaluator": self._energy_evaluator, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_tot") self.add_scalar("dens_tot") self.add_scalar("entr_tot") - @property - def bulk_species(self): - return self.fluid - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - projV3 = L2Projector("L2", self._mass_ops) + # temporary vectors for scalar quantities + tmp_dof = self.derham.Vh_pol["3"].zeros() + projV3 = L2Projector("L2", self.mass_ops) def f(e1, e2, e3): return 1 - f = xp.vectorize(f) - self._integrator = projV3(f) - - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self.propagators.variat_ent.options.gamma) + f = np.vectorize(f) + self._integrator = projV3(f, dofs=tmp_dof) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -789,11 +845,8 @@ def f(e1, e2, e3): self._ones[:] = 1.0 def update_scalar_quantities(self): - rho = self.fluid.density.spline.vector - u = self.fluid.velocity.spline.vector - s = self.fluid.entropy.spline.vector - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + # Update mass matrix + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["fluid_uv"], self.pointer["fluid_uv"]) self.update_scalar("en_U", en_U) en_thermo = self.update_thermo_energy() @@ -801,9 +854,9 @@ def update_scalar_quantities(self): en_tot = en_U + en_thermo self.update_scalar("en_tot", en_tot) - dens_tot = self._ones.inner(rho) + dens_tot = self._ones.inner(self.pointer["fluid_rho3"]) self.update_scalar("dens_tot", dens_tot) - entr_tot = self._ones.inner(s) + entr_tot = self._ones.inner(self.pointer["fluid_s3"]) self.update_scalar("entr_tot", entr_tot) def update_thermo_energy(self): @@ -811,12 +864,9 @@ def update_thermo_energy(self): :meta private: """ - rho = self.fluid.density.spline.vector - s = self.fluid.entropy.spline.vector - en_prop = self.propagators.variat_dens - - self._energy_evaluator.sf.vector = s - self._energy_evaluator.rhof.vector = rho + en_prop = self._propagators[0] + self._energy_evaluator.sf.vector = self.pointer["fluid_s3"] + self._energy_evaluator.rhof.vector = self.pointer["fluid_rho3"] sf_values = self._energy_evaluator.sf.eval_tp_fixed_loc( self._energy_evaluator.integration_grid_spans, self._energy_evaluator.integration_grid_bd, @@ -834,42 +884,8 @@ def update_thermo_energy(self): self.update_scalar("en_thermo", en_thermo) return en_thermo - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='full',\n", - ] - new_file += [ - " s=model.fluid.entropy)\n", - ] - elif "variat_ent.Options" in line: - new_file += [ - "model.propagators.variat_ent.options = model.propagators.variat_ent.Options(model='full',\n", - ] - new_file += [ - " rho=model.fluid.density)\n", - ] - elif "variat_viscous.Options" in line: - new_file += [ - "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(rho=model.fluid.density)\n", - ] - elif "entropy.add_background" in line: - new_file += ["model.fluid.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - -class ViscoResistiveMHD_with_p(StruphyModel): +class ViscoresistiveMHD_with_p(StruphyModel): r"""Full (non-linear) visco-resistive MHD equations, with the pressure variable discretized with a variational method. :ref:`normalization`: @@ -903,78 +919,121 @@ class ViscoResistiveMHD_with_p(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.pressure = FEECVariable(space="L2") - self.init_variables() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["em_fields"]["b2"] = "Hdiv" + dct["fluid"]["mhd"] = {"rho3": "L2", "p3": "L2", "uv": "H1vec"} + return dct - class Diagnostics(DiagnosticSpecies): - def __init__(self): - self.div_u = FEECVariable(space="L2") - self.u2 = FEECVariable(space="Hdiv") - self.init_variables() + @staticmethod + def bulk_species(): + return "mhd" - ## propagators + @staticmethod + def velocity_scale(): + return "alfvén" - class Propagators: - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - self.variat_pb = propagators_fields.VariationalPBEvolve() - if with_viscosity: - self.variat_viscous = propagators_fields.VariationalViscosity() - if with_resistivity: - self.variat_resist = propagators_fields.VariationalResistivity() - - ## abstract methods - - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.diagnostics = self.Diagnostics() - - # 2. instantiate all propagators - self.propagators = self.Propagators( - with_viscosity=with_viscosity, - with_resistivity=with_resistivity, - ) - - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.mhd.density - self.propagators.variat_dens.variables.u = self.mhd.velocity - self.propagators.variat_mom.variables.u = self.mhd.velocity - self.propagators.variat_pb.variables.u = self.mhd.velocity - self.propagators.variat_pb.variables.p = self.mhd.pressure - self.propagators.variat_pb.variables.b = self.em_fields.b_field - if with_viscosity: - self.propagators.variat_viscous.variables.s = self.mhd.pressure - self.propagators.variat_viscous.variables.u = self.mhd.velocity - if with_resistivity: - self.propagators.variat_resist.variables.s = self.mhd.pressure - self.propagators.variat_resist.variables.b = self.em_fields.b_field - - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], + propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], + propagators_fields.VariationalPBEvolve: ["mhd_p3", "b2", "mhd_uv"], + propagators_fields.VariationalViscosity: ["mhd_p3", "mhd_uv"], + propagators_fields.VariationalResistivity: ["mhd_p3", "b2"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import H1vecMassMatrix_density + from struphy.polar.basic import PolarVector + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["lin_solver"] + nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["nonlin_solver"] + lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] + nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] + lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] + nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] + if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): + self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] + else: + self._linearize_current = False + self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] + self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] + self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] + self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] + self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] + model = "full_p" + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + self._kwargs[propagators_fields.VariationalPBEvolve] = { + "model": model, + "mass_ops": self.WMM, + "lin_solver": lin_solver_magfield, + "nonlin_solver": nonlin_solver_magfield, + "gamma": self._gamma, + } + + self._kwargs[propagators_fields.VariationalViscosity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "mu": self._mu, + "mu_a": self._mu_a, + "alpha": self._alpha, + "mass_ops": self.WMM, + "lin_solver": lin_solver_viscosity, + "nonlin_solver": nonlin_solver_viscosity, + } + + self._kwargs[propagators_fields.VariationalResistivity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "eta": self._eta, + "eta_a": self._eta_a, + "lin_solver": lin_solver_resistivity, + "nonlin_solver": nonlin_solver_resistivity, + "linearize_current": self._linearize_current, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag") @@ -982,22 +1041,12 @@ def __init__( self.add_scalar("dens_tot") self.add_scalar("tot_div_B") - @property - def bulk_species(self): - return self.mhd - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - projV3 = L2Projector("L2", self._mass_ops) - - def f(e1, e2, e3): - return 1 + # temporary vectors for scalar quantities + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + tmp_dof = self.derham.Vh_pol["3"].zeros() + projV3 = L2Projector("L2", self.mass_ops) - f = xp.vectorize(f) - self._integrator = projV3(f) + self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1005,68 +1054,39 @@ def f(e1, e2, e3): else: self._ones[:] = 1.0 - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - def update_scalar_quantities(self): - rho = self.mhd.density.spline.vector - u = self.mhd.velocity.spline.vector - p = self.mhd.pressure.spline.vector - b = self.em_fields.b_field.spline.vector - - gamma = self.propagators.variat_pb.options.gamma - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + # Update mass matrix + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) self.update_scalar("en_U", en_U) - en_mag = 0.5 * self.mass_ops.M2.dot_inner(b, b) + en_mag = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) self.update_scalar("en_mag", en_mag) - en_thermo = self.mass_ops.M3.dot_inner(p, self._integrator) / (gamma - 1.0) + en_thermo = self.mass_ops.M3.dot_inner(self.pointer["mhd_p3"], self._integrator) / (self._gamma - 1.0) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo + en_mag self.update_scalar("en_tot", en_tot) - dens_tot = self._ones.inner(rho) + dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) self.update_scalar("dens_tot", dens_tot) - div_B = self.derham.div.dot(b, out=self._tmp_div_B) + div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) self.update_scalar("tot_div_B", L2_div_B) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_pb.Options" in line: - new_file += [ - "model.propagators.variat_pb.options = model.propagators.variat_pb.Options(div_u=model.diagnostics.div_u,\n", - ] - new_file += [ - " u2=model.diagnostics.u2)\n", - ] - elif "variat_viscous.Options" in line: - new_file += [ - "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(rho=model.mhd.density)\n", - ] - elif "variat_resist.Options" in line: - new_file += [ - "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(rho=model.mhd.density)\n", - ] - elif "pressure.add_background" in line: - new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] + @staticmethod + def diagnostics_dct(): + dct = {} - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + dct["div_u"] = "L2" + dct["u2"] = "Hdiv" + return dct + + __diagnostics__ = diagnostics_dct() -class ViscoResistiveLinearMHD(StruphyModel): +class ViscoresistiveLinearMHD(StruphyModel): r"""Linear visco-resistive MHD equations discretized with a variational method. :ref:`normalization`: @@ -1099,104 +1119,136 @@ class ViscoResistiveLinearMHD(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.pressure = FEECVariable(space="L2") - self.init_variables() - - class Diagnostics(DiagnosticSpecies): - def __init__(self): - self.div_u = FEECVariable(space="L2") - self.u2 = FEECVariable(space="Hdiv") - self.pt3 = FEECVariable(space="L2") - self.bt2 = FEECVariable(space="Hdiv") - self.init_variables() - - ## propagators - - class Propagators: - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_pb = propagators_fields.VariationalPBEvolve() - if with_viscosity: - self.variat_viscous = propagators_fields.VariationalViscosity() - if with_resistivity: - self.variat_resist = propagators_fields.VariationalResistivity() - - ## abstract methods - - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.diagnostics = self.Diagnostics() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["em_fields"]["b2"] = "Hdiv" + dct["fluid"]["mhd"] = {"rho3": "L2", "p3": "L2", "uv": "H1vec"} + return dct - # 2. instantiate all propagators - self.propagators = self.Propagators( - with_viscosity=with_viscosity, - with_resistivity=with_resistivity, - ) + @staticmethod + def bulk_species(): + return "mhd" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.mhd.density - self.propagators.variat_dens.variables.u = self.mhd.velocity - self.propagators.variat_pb.variables.u = self.mhd.velocity - self.propagators.variat_pb.variables.p = self.mhd.pressure - self.propagators.variat_pb.variables.b = self.em_fields.b_field - if with_viscosity: - self.propagators.variat_viscous.variables.s = self.mhd.pressure - self.propagators.variat_viscous.variables.u = self.mhd.velocity - if with_resistivity: - self.propagators.variat_resist.variables.s = self.mhd.pressure - self.propagators.variat_resist.variables.b = self.em_fields.b_field + @staticmethod + def velocity_scale(): + return "alfvén" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], + propagators_fields.VariationalPBEvolve: ["mhd_p3", "b2", "mhd_uv"], + propagators_fields.VariationalViscosity: ["mhd_p3", "mhd_uv"], + propagators_fields.VariationalResistivity: ["mhd_p3", "b2"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import H1vecMassMatrix_density + from struphy.polar.basic import PolarVector + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["lin_solver"] + nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["nonlin_solver"] + lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] + nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] + lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] + nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] + if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): + self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] + else: + self._linearize_current = False + self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] + self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] + self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] + self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] + self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] + model = "linear" + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + } + + self._kwargs[propagators_fields.VariationalPBEvolve] = { + "model": model, + "mass_ops": self.WMM, + "lin_solver": lin_solver_magfield, + "nonlin_solver": nonlin_solver_magfield, + "gamma": self._gamma, + "div_u": self.pointer["div_u"], + "u2": self.pointer["u2"], + "bt2": self.pointer["bt2"], + "pt3": self.pointer["pt3"], + } + + self._kwargs[propagators_fields.VariationalViscosity] = { + "model": "linear_p", + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "mu": self._mu, + "mu_a": self._mu_a, + "alpha": self._alpha, + "mass_ops": self.WMM, + "lin_solver": lin_solver_viscosity, + "nonlin_solver": nonlin_solver_viscosity, + } + + self._kwargs[propagators_fields.VariationalResistivity] = { + "model": "linear_p", + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "eta": self._eta, + "eta_a": self._eta_a, + "lin_solver": lin_solver_resistivity, + "nonlin_solver": nonlin_solver_resistivity, + "linearize_current": self._linearize_current, + "pt3": self.pointer["pt3"], + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag_1") self.add_scalar("en_mag_2") self.add_scalar("en_tot") + # self.add_scalar("dens_tot") + # self.add_scalar("tot_div_B") + self.add_scalar("en_tot_l1") self.add_scalar("en_thermo_l1") self.add_scalar("en_mag_l1") - @property - def bulk_species(self): - return self.mhd - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - projV3 = L2Projector("L2", self._mass_ops) - - def f(e1, e2, e3): - return 1 + # temporary vectors for scalar quantities + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + tmp_dof = self.derham.Vh_pol["3"].zeros() + projV3 = L2Projector("L2", self.mass_ops) - f = xp.vectorize(f) - self._integrator = projV3(f) + self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1204,104 +1256,52 @@ def f(e1, e2, e3): else: self._ones[:] = 1.0 - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - def update_scalar_quantities(self): - rho = self.mhd.density.spline.vector - u = self.mhd.velocity.spline.vector - p = self.mhd.pressure.spline.vector - b = self.em_fields.b_field.spline.vector - bt2 = self.propagators.variat_pb.options.bt2.spline.vector - pt3 = self.propagators.variat_pb.options.pt3.spline.vector - - gamma = self.propagators.variat_pb.options.gamma - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + # Update mass matrix + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) self.update_scalar("en_U", en_U) - en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(b, b) + en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) self.update_scalar("en_mag_1", en_mag1) - en_mag2 = self.mass_ops.M2.dot_inner(bt2, self.projected_equil.b2) + en_mag2 = self.mass_ops.M2.dot_inner(self.pointer["bt2"], self.projected_equil.b2) self.update_scalar("en_mag_2", en_mag2) - en_thermo = self.mass_ops.M3.dot_inner(pt3, self._integrator) / (gamma - 1.0) + en_thermo = self.mass_ops.M3.dot_inner(self.pointer["pt3"], self._integrator) / (self._gamma - 1.0) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo + en_mag1 + en_mag2 self.update_scalar("en_tot", en_tot) - # dens_tot = self._ones.inner(rho) + # dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) # self.update_scalar("dens_tot", dens_tot) - # div_B = self.derham.div.dot(b, out=self._tmp_div_B) + # div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) # L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) # self.update_scalar("tot_div_B", L2_div_B) - en_thermo_l1 = self.mass_ops.M3.dot_inner(p, self._integrator) / (gamma - 1.0) + en_thermo_l1 = self.mass_ops.M3.dot_inner(self.pointer["mhd_p3"], self._integrator) / (self._gamma - 1.0) self.update_scalar("en_thermo_l1", en_thermo_l1) - en_mag_l1 = self.mass_ops.M2.dot_inner(b, self.projected_equil.b2) + en_mag_l1 = self.mass_ops.M2.dot_inner(self.pointer["b2"], self.projected_equil.b2) self.update_scalar("en_mag_l1", en_mag_l1) en_tot_l1 = en_thermo_l1 + en_mag_l1 self.update_scalar("en_tot_l1", en_tot_l1) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='linear')\n", - ] - elif "variat_pb.Options" in line: - new_file += [ - "model.propagators.variat_pb.options = model.propagators.variat_pb.Options(model='linear',\n", - ] - new_file += [ - " div_u=model.diagnostics.div_u,\n", - ] - new_file += [ - " u2=model.diagnostics.u2,\n", - ] - new_file += [ - " pt3=model.diagnostics.pt3,\n", - ] - new_file += [ - " bt2=model.diagnostics.bt2)\n", - ] - elif "variat_viscous.Options" in line: - new_file += [ - "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='linear_p',\n", - ] - new_file += [ - " rho=model.mhd.density)\n", - ] - elif "variat_resist.Options" in line: - new_file += [ - "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='linear_p',\n", - ] - new_file += [ - " rho=model.mhd.density,\n", - ] - new_file += [ - " pt3=model.diagnostics.pt3)\n", - ] - elif "pressure.add_background" in line: - new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] + @staticmethod + def diagnostics_dct(): + dct = {} + dct["bt2"] = "Hdiv" + dct["pt3"] = "L2" + dct["div_u"] = "L2" + dct["u2"] = "Hdiv" + return dct - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + __diagnostics__ = diagnostics_dct() -class ViscoResistiveDeltafMHD(StruphyModel): +class ViscoresistiveDeltafMHD(StruphyModel): r""":math:`\delta f` visco-resistive MHD equations discretized with a variational method. :ref:`normalization`: @@ -1335,106 +1335,141 @@ class ViscoResistiveDeltafMHD(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.pressure = FEECVariable(space="L2") - self.init_variables() - - class Diagnostics(DiagnosticSpecies): - def __init__(self): - self.div_u = FEECVariable(space="L2") - self.u2 = FEECVariable(space="Hdiv") - self.pt3 = FEECVariable(space="L2") - self.bt2 = FEECVariable(space="Hdiv") - self.init_variables() - - ## propagators - - class Propagators: - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - self.variat_pb = propagators_fields.VariationalPBEvolve() - if with_viscosity: - self.variat_viscous = propagators_fields.VariationalViscosity() - if with_resistivity: - self.variat_resist = propagators_fields.VariationalResistivity() - - ## abstract methods - - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.diagnostics = self.Diagnostics() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["em_fields"]["b2"] = "Hdiv" + dct["fluid"]["mhd"] = {"rho3": "L2", "p3": "L2", "uv": "H1vec"} + return dct - # 2. instantiate all propagators - self.propagators = self.Propagators( - with_viscosity=with_viscosity, - with_resistivity=with_resistivity, - ) + @staticmethod + def bulk_species(): + return "mhd" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.mhd.density - self.propagators.variat_dens.variables.u = self.mhd.velocity - self.propagators.variat_mom.variables.u = self.mhd.velocity - self.propagators.variat_pb.variables.u = self.mhd.velocity - self.propagators.variat_pb.variables.p = self.mhd.pressure - self.propagators.variat_pb.variables.b = self.em_fields.b_field - if with_viscosity: - self.propagators.variat_viscous.variables.s = self.mhd.pressure - self.propagators.variat_viscous.variables.u = self.mhd.velocity - if with_resistivity: - self.propagators.variat_resist.variables.s = self.mhd.pressure - self.propagators.variat_resist.variables.b = self.em_fields.b_field + @staticmethod + def velocity_scale(): + return "alfvén" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], + propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], + propagators_fields.VariationalPBEvolve: ["mhd_p3", "b2", "mhd_uv"], + propagators_fields.VariationalViscosity: ["mhd_p3", "mhd_uv"], + propagators_fields.VariationalResistivity: ["mhd_p3", "b2"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import H1vecMassMatrix_density + from struphy.polar.basic import PolarVector + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["lin_solver"] + nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["nonlin_solver"] + lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] + nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] + lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] + nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] + if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): + self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] + else: + self._linearize_current = False + self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] + self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] + self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] + self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] + self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] + model = "deltaf" + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + self._kwargs[propagators_fields.VariationalPBEvolve] = { + "model": model, + "mass_ops": self.WMM, + "lin_solver": lin_solver_magfield, + "nonlin_solver": nonlin_solver_magfield, + "gamma": self._gamma, + "bt2": self.pointer["bt2"], + "pt3": self.pointer["pt3"], + } + + self._kwargs[propagators_fields.VariationalViscosity] = { + "model": "full_p", + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "mu": self._mu, + "mu_a": self._mu_a, + "alpha": self._alpha, + "mass_ops": self.WMM, + "lin_solver": lin_solver_viscosity, + "nonlin_solver": nonlin_solver_viscosity, + } + + self._kwargs[propagators_fields.VariationalResistivity] = { + "model": "delta_p", + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "eta": self._eta, + "eta_a": self._eta_a, + "lin_solver": lin_solver_resistivity, + "nonlin_solver": nonlin_solver_resistivity, + "linearize_current": self._linearize_current, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag_1") self.add_scalar("en_mag_2") self.add_scalar("en_tot") + # self.add_scalar("dens_tot") + # self.add_scalar("tot_div_B") + self.add_scalar("en_tot_l1") self.add_scalar("en_thermo_l1") self.add_scalar("en_mag_l1") - @property - def bulk_species(self): - return self.mhd - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - projV3 = L2Projector("L2", self._mass_ops) + # temporary vectors for scalar quantities + tmp_dof = self.derham.Vh_pol["3"].zeros() + projV3 = L2Projector("L2", self.mass_ops) - def f(e1, e2, e3): - return 1 - - f = xp.vectorize(f) - self._integrator = projV3(f) + self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1442,95 +1477,52 @@ def f(e1, e2, e3): else: self._ones[:] = 1.0 - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - def update_scalar_quantities(self): - rho = self.mhd.density.spline.vector - u = self.mhd.velocity.spline.vector - p = self.mhd.pressure.spline.vector - b = self.em_fields.b_field.spline.vector - bt2 = self.propagators.variat_pb.options.bt2.spline.vector - pt3 = self.propagators.variat_pb.options.pt3.spline.vector - - gamma = self.propagators.variat_pb.options.gamma - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + # Update mass matrix + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) self.update_scalar("en_U", en_U) - en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(b, b) + en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) self.update_scalar("en_mag_1", en_mag1) - en_mag2 = self.mass_ops.M2.dot_inner(bt2, self.projected_equil.b2) + en_mag2 = self.mass_ops.M2.dot_inner(self.pointer["bt2"], self.projected_equil.b2) self.update_scalar("en_mag_2", en_mag2) - en_thermo = self.mass_ops.M3.dot_inner(pt3, self._integrator) / (gamma - 1.0) + en_thermo = self.mass_ops.M3.dot_inner(self.pointer["pt3"], self._integrator) / (self._gamma - 1.0) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo + en_mag1 + en_mag2 self.update_scalar("en_tot", en_tot) - # dens_tot = self._ones.inner(rho) + # dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) # self.update_scalar("dens_tot", dens_tot) - # div_B = self.derham.div.dot(b, out=self._tmp_div_B) + # div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) # L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) # self.update_scalar("tot_div_B", L2_div_B) - en_thermo_l1 = self.mass_ops.M3.dot_inner(p, self._integrator) / (gamma - 1.0) + en_thermo_l1 = self.mass_ops.M3.dot_inner(self.pointer["mhd_p3"], self._integrator) / (self._gamma - 1.0) self.update_scalar("en_thermo_l1", en_thermo_l1) - en_mag_l1 = self.mass_ops.M2.dot_inner(b, self.projected_equil.b2) + en_mag_l1 = self.mass_ops.M2.dot_inner(self.pointer["b2"], self.projected_equil.b2) self.update_scalar("en_mag_l1", en_mag_l1) en_tot_l1 = en_thermo_l1 + en_mag_l1 self.update_scalar("en_tot_l1", en_tot_l1) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='deltaf')\n", - ] - elif "variat_pb.Options" in line: - new_file += [ - "model.propagators.variat_pb.options = model.propagators.variat_pb.Options(model='deltaf',\n", - ] - new_file += [ - " pt3=model.diagnostics.pt3,\n", - ] - new_file += [ - " bt2=model.diagnostics.bt2)\n", - ] - elif "variat_viscous.Options" in line: - new_file += [ - "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='full_p',\n", - ] - new_file += [ - " rho=model.mhd.density)\n", - ] - elif "variat_resist.Options" in line: - new_file += [ - "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='full_p',\n", - ] - new_file += [ - " rho=model.mhd.density)\n", - ] - elif "pressure.add_background" in line: - new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] + @staticmethod + def diagnostics_dct(): + dct = {} + dct["bt2"] = "Hdiv" + dct["pt3"] = "L2" + dct["div_u"] = "L2" + dct["u2"] = "Hdiv" + return dct - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + __diagnostics__ = diagnostics_dct() -class ViscoResistiveMHD_with_q(StruphyModel): +class ViscoresistiveMHD_with_q(StruphyModel): r"""Full (non-linear) visco-resistive MHD equations, with the q variable (square root of the pressure) discretized with a variational method. :ref:`normalization`: @@ -1566,78 +1558,121 @@ class ViscoResistiveMHD_with_q(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.sqrt_p = FEECVariable(space="L2") - self.init_variables() - - class Diagnostics(DiagnosticSpecies): - def __init__(self): - self.div_u = FEECVariable(space="L2") - self.u2 = FEECVariable(space="Hdiv") - self.init_variables() - - ## propagators - - class Propagators: - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - self.variat_qb = propagators_fields.VariationalQBEvolve() - if with_viscosity: - self.variat_viscous = propagators_fields.VariationalViscosity() - if with_resistivity: - self.variat_resist = propagators_fields.VariationalResistivity() - - ## abstract methods - - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.diagnostics = self.Diagnostics() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["em_fields"]["b2"] = "Hdiv" + dct["fluid"]["mhd"] = {"rho3": "L2", "q3": "L2", "uv": "H1vec"} + return dct - # 2. instantiate all propagators - self.propagators = self.Propagators( - with_viscosity=with_viscosity, - with_resistivity=with_resistivity, - ) + @staticmethod + def bulk_species(): + return "mhd" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.mhd.density - self.propagators.variat_dens.variables.u = self.mhd.velocity - self.propagators.variat_mom.variables.u = self.mhd.velocity - self.propagators.variat_qb.variables.u = self.mhd.velocity - self.propagators.variat_qb.variables.q = self.mhd.sqrt_p - self.propagators.variat_qb.variables.b = self.em_fields.b_field - if with_viscosity: - self.propagators.variat_viscous.variables.s = self.mhd.sqrt_p - self.propagators.variat_viscous.variables.u = self.mhd.velocity - if with_resistivity: - self.propagators.variat_resist.variables.s = self.mhd.sqrt_p - self.propagators.variat_resist.variables.b = self.em_fields.b_field + @staticmethod + def velocity_scale(): + return "alfvén" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], + propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], + propagators_fields.VariationalQBEvolve: ["mhd_q3", "b2", "mhd_uv"], + propagators_fields.VariationalViscosity: ["mhd_q3", "mhd_uv"], + propagators_fields.VariationalResistivity: ["mhd_q3", "b2"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import H1vecMassMatrix_density + from struphy.polar.basic import PolarVector + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["lin_solver"] + nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["nonlin_solver"] + lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] + nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] + lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] + nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] + if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): + self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] + else: + self._linearize_current = False + self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] + self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] + self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] + self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] + self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] + model = "full_q" + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + self._kwargs[propagators_fields.VariationalQBEvolve] = { + "model": model, + "mass_ops": self.WMM, + "lin_solver": lin_solver_magfield, + "nonlin_solver": nonlin_solver_magfield, + "gamma": self._gamma, + } + + self._kwargs[propagators_fields.VariationalViscosity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "mu": self._mu, + "mu_a": self._mu_a, + "alpha": self._alpha, + "mass_ops": self.WMM, + "lin_solver": lin_solver_viscosity, + "nonlin_solver": nonlin_solver_viscosity, + } + + self._kwargs[propagators_fields.VariationalResistivity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "eta": self._eta, + "eta_a": self._eta_a, + "lin_solver": lin_solver_resistivity, + "nonlin_solver": nonlin_solver_resistivity, + "linearize_current": self._linearize_current, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag") @@ -1645,22 +1680,12 @@ def __init__( self.add_scalar("dens_tot") self.add_scalar("tot_div_B") - @property - def bulk_species(self): - return self.mhd - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - projV3 = L2Projector("L2", self._mass_ops) - - def f(e1, e2, e3): - return 1 + # temporary vectors for scalar quantities + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + tmp_dof = self.derham.Vh_pol["3"].zeros() + projV3 = L2Projector("L2", self.mass_ops) - f = xp.vectorize(f) - self._integrator = projV3(f) + self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1668,75 +1693,39 @@ def f(e1, e2, e3): else: self._ones[:] = 1.0 - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - def update_scalar_quantities(self): - rho = self.mhd.density.spline.vector - u = self.mhd.velocity.spline.vector - q = self.mhd.sqrt_p.spline.vector - b = self.em_fields.b_field.spline.vector - - gamma = self.propagators.variat_qb.options.gamma - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + # Update mass matrix + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) self.update_scalar("en_U", en_U) - en_mag = 0.5 * self.mass_ops.M2.dot_inner(b, b) + en_mag = 0.5 * self._mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) self.update_scalar("en_mag", en_mag) - en_thermo = 1.0 / (gamma - 1.0) * self._mass_ops.M3.dot_inner(q, q) + en_thermo = 1 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["mhd_q3"], self.pointer["mhd_q3"]) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo + en_mag self.update_scalar("en_tot", en_tot) - dens_tot = self._ones.inner(rho) + dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) self.update_scalar("dens_tot", dens_tot) - div_B = self.derham.div.dot(b, out=self._tmp_div_B) + div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) self.update_scalar("tot_div_B", L2_div_B) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='full_q')\n", - ] - elif "variat_qb.Options" in line: - new_file += [ - "model.propagators.variat_qb.options = model.propagators.variat_qb.Options(model='full_q')\n", - ] - elif "variat_viscous.Options" in line: - new_file += [ - "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='full_q',\n", - ] - new_file += [ - " rho=model.mhd.density)\n", - ] - elif "variat_resist.Options" in line: - new_file += [ - "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='full_q',\n", - ] - new_file += [ - " rho=model.mhd.density)\n", - ] - elif "sqrt_p.add_background" in line: - new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + @staticmethod + def diagnostics_dct(): + dct = {} + + dct["div_u"] = "L2" + dct["u2"] = "Hdiv" + return dct + __diagnostics__ = diagnostics_dct() -class ViscoResistiveLinearMHD_with_q(StruphyModel): + +class ViscoresistiveLinearMHD_with_q(StruphyModel): r"""Linear visco-resistive MHD equations, with the q variable (square root of the pressure), discretized with a variational method. :ref:`normalization`: @@ -1769,101 +1758,138 @@ class ViscoResistiveLinearMHD_with_q(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.sqrt_p = FEECVariable(space="L2") - self.init_variables() - - class Diagnostics(DiagnosticSpecies): - def __init__(self): - self.div_u = FEECVariable(space="L2") - self.u2 = FEECVariable(space="Hdiv") - self.qt3 = FEECVariable(space="L2") - self.bt2 = FEECVariable(space="Hdiv") - self.init_variables() - - ## propagators - - class Propagators: - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_qb = propagators_fields.VariationalQBEvolve() - if with_viscosity: - self.variat_viscous = propagators_fields.VariationalViscosity() - if with_resistivity: - self.variat_resist = propagators_fields.VariationalResistivity() - - ## abstract methods - - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.diagnostics = self.Diagnostics() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["em_fields"]["b2"] = "Hdiv" + dct["fluid"]["mhd"] = {"rho3": "L2", "q3": "L2", "uv": "H1vec"} + return dct - # 2. instantiate all propagators - self.propagators = self.Propagators( - with_viscosity=with_viscosity, - with_resistivity=with_resistivity, - ) + @staticmethod + def bulk_species(): + return "mhd" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.mhd.density - self.propagators.variat_dens.variables.u = self.mhd.velocity - self.propagators.variat_qb.variables.u = self.mhd.velocity - self.propagators.variat_qb.variables.q = self.mhd.sqrt_p - self.propagators.variat_qb.variables.b = self.em_fields.b_field - if with_viscosity: - self.propagators.variat_viscous.variables.s = self.mhd.sqrt_p - self.propagators.variat_viscous.variables.u = self.mhd.velocity - if with_resistivity: - self.propagators.variat_resist.variables.s = self.mhd.sqrt_p - self.propagators.variat_resist.variables.b = self.em_fields.b_field + @staticmethod + def velocity_scale(): + return "alfvén" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], + propagators_fields.VariationalQBEvolve: ["mhd_q3", "b2", "mhd_uv"], + propagators_fields.VariationalViscosity: ["mhd_q3", "mhd_uv"], + propagators_fields.VariationalResistivity: ["mhd_q3", "b2"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import H1vecMassMatrix_density + from struphy.polar.basic import PolarVector + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["lin_solver"] + nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["nonlin_solver"] + lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] + nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] + lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] + nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] + if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): + self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] + else: + self._linearize_current = False + self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] + self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] + self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] + self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] + self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] + model = "linear_q" + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + } + + self._kwargs[propagators_fields.VariationalQBEvolve] = { + "model": model, + "mass_ops": self.WMM, + "lin_solver": lin_solver_magfield, + "nonlin_solver": nonlin_solver_magfield, + "gamma": self._gamma, + "div_u": self.pointer["div_u"], + "u2": self.pointer["u2"], + "bt2": self.pointer["bt2"], + "qt3": self.pointer["qt3"], + } + + self._kwargs[propagators_fields.VariationalViscosity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "mu": self._mu, + "mu_a": self._mu_a, + "alpha": self._alpha, + "mass_ops": self.WMM, + "lin_solver": lin_solver_viscosity, + "nonlin_solver": nonlin_solver_viscosity, + "pt3": self.pointer["qt3"], + } + + self._kwargs[propagators_fields.VariationalResistivity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "eta": self._eta, + "eta_a": self._eta_a, + "lin_solver": lin_solver_resistivity, + "nonlin_solver": nonlin_solver_resistivity, + "linearize_current": self._linearize_current, + "pt3": self.pointer["qt3"], + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_U") - self.add_scalar("en_mag_1") - self.add_scalar("en_mag_2") - self.add_scalar("en_thermo_1") - self.add_scalar("en_thermo_2") + # self.add_scalar("en_thermo_1") + # self.add_scalar("en_thermo_2") + # self.add_scalar("en_mag_1") + # self.add_scalar("en_mag_2") self.add_scalar("en_tot") - @property - def bulk_species(self): - return self.mhd - - @property - def velocity_scale(self): - return "alfvén" + # self.add_scalar("dens_tot") + # self.add_scalar("tot_div_B") - def allocate_helpers(self): - projV3 = L2Projector("L2", self._mass_ops) + # self.add_scalar("en_tot_l1") + # self.add_scalar("en_thermo_l1") + # self.add_scalar("en_mag_l1") - def f(e1, e2, e3): - return 1 + # temporary vectors for scalar quantities + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + tmp_dof = self.derham.Vh_pol["3"].zeros() + projV3 = L2Projector("L2", self.mass_ops) - f = xp.vectorize(f) - self._integrator = projV3(f) + self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1871,94 +1897,56 @@ def f(e1, e2, e3): else: self._ones[:] = 1.0 - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - def update_scalar_quantities(self): - rho = self.mhd.density.spline.vector - u = self.mhd.velocity.spline.vector - q = self.mhd.sqrt_p.spline.vector - b = self.em_fields.b_field.spline.vector - bt2 = self.propagators.variat_qb.options.bt2.spline.vector - qt3 = self.propagators.variat_qb.options.qt3.spline.vector - - gamma = self.propagators.variat_qb.options.gamma - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + # Update mass matrix + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) self.update_scalar("en_U", en_U) - en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(b, b) - self.update_scalar("en_mag_1", en_mag1) + en_mag1 = self._mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) + # self.update_scalar("en_mag_1", en_mag1) - en_mag2 = self.mass_ops.M2.dot_inner(bt2, self.projected_equil.b2) - self.update_scalar("en_mag_2", en_mag2) + en_mag2 = self._mass_ops.M2.dot_inner(self.pointer["bt2"], self.projected_equil.b2) + # self.update_scalar("en_mag_2", en_mag2) - en_th_1 = 1.0 / (gamma - 1.0) * self.mass_ops.M3.dot_inner(q, q) - self.update_scalar("en_thermo_1", en_th_1) + en_th_1 = 1 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["mhd_q3"], self.pointer["mhd_q3"]) + # self.update_scalar("en_thermo_1", en_th_1) - en_th_2 = 2.0 / (gamma - 1.0) * self.mass_ops.M3.dot_inner(qt3, self.projected_equil.q3) - self.update_scalar("en_thermo_2", en_th_2) + en_th_2 = 2 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["qt3"], self.projected_equil.q3) + # self.update_scalar("en_thermo_2", en_th_2) en_tot = en_U + en_th_1 + en_th_2 + en_mag1 + en_mag2 self.update_scalar("en_tot", en_tot) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='linear_q')\n", - ] - elif "variat_qb.Options" in line: - new_file += [ - "model.propagators.variat_qb.options = model.propagators.variat_qb.Options(model='linear_q',\n", - ] - new_file += [ - " div_u=model.diagnostics.div_u,\n", - ] - new_file += [ - " u2=model.diagnostics.u2,\n", - ] - new_file += [ - " qt3=model.diagnostics.qt3,\n", - ] - new_file += [ - " bt2=model.diagnostics.bt2)\n", - ] - elif "variat_viscous.Options" in line: - new_file += [ - "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='linear_q',\n", - ] - new_file += [ - " rho=model.mhd.density,\n", - ] - new_file += [ - " pt3=model.diagnostics.qt3)\n", - ] - elif "variat_resist.Options" in line: - new_file += [ - "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='linear_q',\n", - ] - new_file += [ - " rho=model.mhd.density,\n", - ] - new_file += [ - " pt3=model.diagnostics.qt3)\n", - ] - elif "sqrt_p.add_background" in line: - new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] + # dens_tot = self._ones.dot(self.pointer["mhd_rho3"]) + # self.update_scalar("dens_tot", dens_tot) - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + # div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) + # L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) + # self.update_scalar("tot_div_B", L2_div_B) + + # en_thermo_l1 = self._integrator.dot(self.mass_ops.M3.dot(self.pointer["mhd_p3"])) / (self._gamma - 1.0) + # self.update_scalar("en_thermo_l1", en_thermo_l1) + + # wb2 = self._mass_ops.M2.dot(self.pointer["b2"], out=self._tmp_wb2) + # en_mag_l1 = wb2.dot(self.projected_equil.b2) + # self.update_scalar("en_mag_l1", en_mag_l1) + + # en_tot_l1 = en_thermo_l1 + en_mag_l1 + # self.update_scalar("en_tot_l1", en_tot_l1) + + @staticmethod + def diagnostics_dct(): + dct = {} + dct["bt2"] = "Hdiv" + dct["qt3"] = "L2" + dct["div_u"] = "L2" + dct["u2"] = "Hdiv" + return dct + + __diagnostics__ = diagnostics_dct() -class ViscoResistiveDeltafMHD_with_q(StruphyModel): +class ViscoresistiveDeltafMHD_with_q(StruphyModel): r"""Linear visco-resistive MHD equations discretized with a variational method. :ref:`normalization`: @@ -1992,103 +1980,147 @@ class ViscoResistiveDeltafMHD_with_q(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.sqrt_p = FEECVariable(space="L2") - self.init_variables() - - class Diagnostics(DiagnosticSpecies): - def __init__(self): - self.div_u = FEECVariable(space="L2") - self.u2 = FEECVariable(space="Hdiv") - self.qt3 = FEECVariable(space="L2") - self.bt2 = FEECVariable(space="Hdiv") - self.init_variables() - - ## propagators - - class Propagators: - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - self.variat_qb = propagators_fields.VariationalQBEvolve() - if with_viscosity: - self.variat_viscous = propagators_fields.VariationalViscosity() - if with_resistivity: - self.variat_resist = propagators_fields.VariationalResistivity() - - ## abstract methods - - def __init__( - self, - with_viscosity: bool = True, - with_resistivity: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.diagnostics = self.Diagnostics() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["em_fields"]["b2"] = "Hdiv" + dct["fluid"]["mhd"] = {"rho3": "L2", "q3": "L2", "uv": "H1vec"} + return dct - # 2. instantiate all propagators - self.propagators = self.Propagators( - with_viscosity=with_viscosity, - with_resistivity=with_resistivity, - ) + @staticmethod + def bulk_species(): + return "mhd" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.mhd.density - self.propagators.variat_dens.variables.u = self.mhd.velocity - self.propagators.variat_mom.variables.u = self.mhd.velocity - self.propagators.variat_qb.variables.u = self.mhd.velocity - self.propagators.variat_qb.variables.q = self.mhd.sqrt_p - self.propagators.variat_qb.variables.b = self.em_fields.b_field - if with_viscosity: - self.propagators.variat_viscous.variables.s = self.mhd.sqrt_p - self.propagators.variat_viscous.variables.u = self.mhd.velocity - if with_resistivity: - self.propagators.variat_resist.variables.s = self.mhd.sqrt_p - self.propagators.variat_resist.variables.b = self.em_fields.b_field + @staticmethod + def velocity_scale(): + return "alfvén" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], + propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], + propagators_fields.VariationalQBEvolve: ["mhd_q3", "b2", "mhd_uv"], + propagators_fields.VariationalViscosity: ["mhd_q3", "mhd_uv"], + propagators_fields.VariationalResistivity: ["mhd_q3", "b2"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import H1vecMassMatrix_density + from struphy.polar.basic import PolarVector + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["lin_solver"] + nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["nonlin_solver"] + lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] + nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] + lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] + nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] + if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): + self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] + else: + self._linearize_current = False + self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] + self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] + self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] + self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] + self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] + model = "deltaf_q" + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + self._kwargs[propagators_fields.VariationalQBEvolve] = { + "model": model, + "mass_ops": self.WMM, + "lin_solver": lin_solver_magfield, + "nonlin_solver": nonlin_solver_magfield, + "gamma": self._gamma, + "div_u": self.pointer["div_u"], + "u2": self.pointer["u2"], + "bt2": self.pointer["bt2"], + "qt3": self.pointer["qt3"], + } + + self._kwargs[propagators_fields.VariationalViscosity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "mu": self._mu, + "mu_a": self._mu_a, + "alpha": self._alpha, + "mass_ops": self.WMM, + "lin_solver": lin_solver_viscosity, + "nonlin_solver": nonlin_solver_viscosity, + "pt3": self.pointer["qt3"], + } + + self._kwargs[propagators_fields.VariationalResistivity] = { + "model": model, + "rho": self.pointer["mhd_rho3"], + "gamma": self._gamma, + "eta": self._eta, + "eta_a": self._eta_a, + "lin_solver": lin_solver_resistivity, + "nonlin_solver": nonlin_solver_resistivity, + "linearize_current": self._linearize_current, + "pt3": self.pointer["qt3"], + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_U") - self.add_scalar("en_mag_1") - self.add_scalar("en_mag_2") self.add_scalar("en_thermo_1") self.add_scalar("en_thermo_2") + self.add_scalar("en_mag_1") + self.add_scalar("en_mag_2") self.add_scalar("en_tot") - @property - def bulk_species(self): - return self.mhd - - @property - def velocity_scale(self): - return "alfvén" + # self.add_scalar("dens_tot") + # self.add_scalar("tot_div_B") - def allocate_helpers(self): - projV3 = L2Projector("L2", self._mass_ops) + # self.add_scalar("en_tot_l1") + # self.add_scalar("en_thermo_l1") + # self.add_scalar("en_mag_l1") - def f(e1, e2, e3): - return 1 + # temporary vectors for scalar quantities + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + tmp_dof = self.derham.Vh_pol["3"].zeros() + projV3 = L2Projector("L2", self.mass_ops) - f = xp.vectorize(f) - self._integrator = projV3(f) + self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -2096,91 +2128,53 @@ def f(e1, e2, e3): else: self._ones[:] = 1.0 - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - def update_scalar_quantities(self): - rho = self.mhd.density.spline.vector - u = self.mhd.velocity.spline.vector - q = self.mhd.sqrt_p.spline.vector - b = self.em_fields.b_field.spline.vector - bt2 = self.propagators.variat_qb.options.bt2.spline.vector - qt3 = self.propagators.variat_qb.options.qt3.spline.vector - - gamma = self.propagators.variat_qb.options.gamma - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + # Update mass matrix + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) self.update_scalar("en_U", en_U) - en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(b, b) + en_mag1 = 0.5 * self._mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) self.update_scalar("en_mag_1", en_mag1) - en_mag2 = self.mass_ops.M2.dot_inner(bt2, self.projected_equil.b2) + en_mag2 = 0.5 * self._mass_ops.M2.dot_inner(self.pointer["bt2"], self.projected_equil.b2) self.update_scalar("en_mag_2", en_mag2) - en_th_1 = 1.0 / (gamma - 1.0) * self.mass_ops.M3.dot_inner(q, q) + en_th_1 = 1 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["mhd_q3"], self.pointer["mhd_q3"]) self.update_scalar("en_thermo_1", en_th_1) - en_th_2 = 2.0 / (gamma - 1.0) * self.mass_ops.M3.dot_inner(qt3, self.projected_equil.q3) + en_th_2 = 2 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["qt3"], self.projected_equil.q3) self.update_scalar("en_thermo_2", en_th_2) en_tot = en_U + en_th_1 + en_th_2 + en_mag1 + en_mag2 self.update_scalar("en_tot", en_tot) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='deltaf_q')\n", - ] - elif "variat_qb.Options" in line: - new_file += [ - "model.propagators.variat_qb.options = model.propagators.variat_qb.Options(model='deltaf_q',\n", - ] - new_file += [ - " div_u=model.diagnostics.div_u,\n", - ] - new_file += [ - " u2=model.diagnostics.u2,\n", - ] - new_file += [ - " qt3=model.diagnostics.qt3,\n", - ] - new_file += [ - " bt2=model.diagnostics.bt2)\n", - ] - elif "variat_viscous.Options" in line: - new_file += [ - "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='deltaf_q',\n", - ] - new_file += [ - " rho=model.mhd.density,\n", - ] - new_file += [ - " pt3=model.diagnostics.qt3)\n", - ] - elif "variat_resist.Options" in line: - new_file += [ - "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='deltaf_q',\n", - ] - new_file += [ - " rho=model.mhd.density,\n", - ] - new_file += [ - " pt3=model.diagnostics.qt3)\n", - ] - elif "sqrt_p.add_background" in line: - new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] + # dens_tot = self._ones.dot(self.pointer["mhd_rho3"]) + # self.update_scalar("dens_tot", dens_tot) - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + # div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) + # L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) + # self.update_scalar("tot_div_B", L2_div_B) + + # en_thermo_l1 = self._integrator.dot(self.mass_ops.M3.dot(self.pointer["mhd_p3"])) / (self._gamma - 1.0) + # self.update_scalar("en_thermo_l1", en_thermo_l1) + + # wb2 = self._mass_ops.M2.dot(self.pointer["b2"], out=self._tmp_wb2) + # en_mag_l1 = wb2.dot(self.projected_equil.b2) + # self.update_scalar("en_mag_l1", en_mag_l1) + + # en_tot_l1 = en_thermo_l1 + en_mag_l1 + # self.update_scalar("en_tot_l1", en_tot_l1) + + @staticmethod + def diagnostics_dct(): + dct = {} + dct["bt2"] = "Hdiv" + dct["qt3"] = "L2" + dct["div_u"] = "L2" + dct["u2"] = "Hdiv" + return dct + + __diagnostics__ = diagnostics_dct() class EulerSPH(StruphyModel): @@ -2280,7 +2274,7 @@ def update_scalar_quantities(self): particles = self.euler_fluid.var.particles valid_markers = particles.markers_wo_holes_and_ghost en_kin = valid_markers[:, 6].dot( - valid_markers[:, 3] ** 2 + valid_markers[:, 4] ** 2 + valid_markers[:, 5] ** 2, + valid_markers[:, 3] ** 2 + valid_markers[:, 4] ** 2 + valid_markers[:, 5] ** 2 ) / (2.0 * particles.Np) self.update_scalar("en_kin", en_kin) @@ -2336,99 +2330,119 @@ class HasegawaWakatani(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.phi = FEECVariable(space="H1") - self.init_variables() - - class Plasma(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="H1") - self.vorticity = FEECVariable(space="H1") - self.init_variables() - - ## propagators - - class Propagators: - def __init__(self): - self.poisson = propagators_fields.Poisson() - self.hw = propagators_fields.HasegawaWakatani() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.plasma = self.Plasma() - - # 2. instantiate all propagators - self.propagators = self.Propagators() - - # 3. assign variables to propagators - self.propagators.poisson.variables.phi = self.em_fields.phi - self.propagators.hw.variables.n = self.plasma.density - self.propagators.hw.variables.omega = self.plasma.vorticity + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - # define scalars for update_scalar_quantities + dct["em_fields"] = {"phi0": "H1"} + dct["fluid"]["hw"] = { + "n0": "H1", + "omega0": "H1", + } + return dct - @property - def bulk_species(self): - return self.plasma + @staticmethod + def bulk_species(): + return "hw" - @property - def velocity_scale(self): + @staticmethod + def velocity_scale(): return "alfvén" - def allocate_helpers(self): - self._rho: StencilVector = self.derham.Vh["0"].zeros() + # @staticmethod + # def diagnostics_dct(): + # dct = {} + # dct["projected_density"] = "L2" + # return dct + + @staticmethod + def propagators_dct(): + return { + propagators_fields.Poisson: ["phi0"], + propagators_fields.HasegawaWakatani: ["hw_n0", "hw_omega0"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + from struphy.polar.basic import PolarVector + + # extract necessary parameters + self._stab_eps = params["em_fields"]["options"]["Poisson"]["stabilization"]["stab_eps"] + self._stab_mat = params["em_fields"]["options"]["Poisson"]["stabilization"]["stab_mat"] + self._solver = params["em_fields"]["options"]["Poisson"]["solver"] + c_fun = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["c_fun"] + kappa = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["kappa"] + nu = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["nu"] + algo = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["algo"] + M0_solver = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["M0_solver"] + + # rhs of Poisson + self._rho = self.derham.Vh["0"].zeros() self.update_rho() + # set keyword arguments for propagators + self._kwargs[propagators_fields.Poisson] = { + "stab_eps": self._stab_eps, + "stab_mat": self._stab_mat, + "rho": self.update_rho, + "solver": self._solver, + } + + self._kwargs[propagators_fields.HasegawaWakatani] = { + "phi": self.em_fields["phi0"]["obj"], + "c_fun": c_fun, + "kappa": kappa, + "nu": nu, + "algo": algo, + "M0_solver": M0_solver, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + def update_rho(self): - omega = self.plasma.vorticity.spline.vector - self._rho = self.mass_ops.M0.dot(omega, out=self._rho) + self._rho = self.mass_ops.M0.dot(self.pointer["hw_omega0"], out=self._rho) self._rho.update_ghost_regions() return self._rho - def allocate_propagators(self): + def initialize_from_params(self): """Solve initial Poisson equation. :meta private: """ # initialize fields and particles - super().allocate_propagators() + super().initialize_from_params() - if MPI.COMM_WORLD.Get_rank() == 0: + if self.rank_world == 0: print("\nINITIAL POISSON SOLVE:") + # Instantiate Poisson solver + poisson_solver = propagators_fields.Poisson( + self.pointer["phi0"], + stab_eps=self._stab_eps, + stab_mat=self._stab_mat, + rho=self._rho, + solver=self._solver, + ) + + # Solve with dt=1. and compute electric field + if self.rank_world == 0: + print("\nSolving initial Poisson problem...") + self.update_rho() - self.propagators.poisson(1.0) + poisson_solver(1.0) - if MPI.COMM_WORLD.Get_rank() == 0: + if self.rank_world == 0: print("Done.") def update_scalar_quantities(self): pass - - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "hw.Options" in line: - new_file += [ - "model.propagators.hw.options = model.propagators.hw.Options(phi=model.em_fields.phi)\n", - ] - elif "vorticity.add_background" in line: - new_file += ["model.plasma.density.add_background(FieldsBackground())\n"] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) diff --git a/src/struphy/models/hybrid.py b/src/struphy/models/hybrid.py index c1952f59c..ee93662ca 100644 --- a/src/struphy/models/hybrid.py +++ b/src/struphy/models/hybrid.py @@ -1,16 +1,8 @@ -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np from struphy.models.base import StruphyModel -from struphy.models.species import FieldSpecies, FluidSpecies, ParticleSpecies -from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.pic.accumulation import accum_kernels, accum_kernels_gc -from struphy.pic.accumulation.particles_to_grid import AccumulatorVector -from struphy.polar.basic import PolarVector from struphy.propagators import propagators_coupling, propagators_fields, propagators_markers -from struphy.utils.pyccel import Pyccelkernel - -rank = MPI.COMM_WORLD.Get_rank() class LinearMHDVlasovCC(StruphyModel): @@ -69,109 +61,187 @@ class LinearMHDVlasovCC(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="Hdiv") - self.pressure = FEECVariable(space="L2") - self.init_variables() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class EnergeticIons(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="Particles6D") - self.init_variables() + dct["em_fields"]["b_field"] = "Hdiv" + dct["fluid"]["mhd"] = {"density": "L2", "velocity": "Hdiv", "pressure": "L2"} + dct["kinetic"]["energetic_ions"] = "Particles6D" + return dct - ## propagators + @staticmethod + def bulk_species(): + return "mhd" - class Propagators: - def __init__(self): - self.couple_dens = propagators_fields.CurrentCoupling6DDensity() - self.shear_alf = propagators_fields.ShearAlfven() - self.couple_curr = propagators_coupling.CurrentCoupling6DCurrent() - self.push_eta = propagators_markers.PushEta() - self.push_vxb = propagators_markers.PushVxB() - self.mag_sonic = propagators_fields.Magnetosonic() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + @staticmethod + def velocity_scale(): + return "alfvén" - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.energetic_ions = self.EnergeticIons() + @staticmethod + def propagators_dct(): + return { + propagators_fields.CurrentCoupling6DDensity: ["mhd_velocity"], + propagators_fields.ShearAlfven: ["mhd_velocity", "b_field"], + propagators_coupling.CurrentCoupling6DCurrent: ["energetic_ions", "mhd_velocity"], + propagators_markers.PushEta: ["energetic_ions"], + propagators_markers.PushVxB: ["energetic_ions"], + propagators_fields.Magnetosonic: ["mhd_density", "mhd_velocity", "mhd_pressure"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + # add special options + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["fluid", "mhd"], + key="u_space", + option="Hdiv", + dct=dct, + ) + return dct - # 2. instantiate all propagators - self.propagators = self.Propagators() + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) - # 3. assign variables to propagators - self.propagators.couple_dens.variables.u = self.mhd.velocity + from mpi4py.MPI import IN_PLACE, SUM - self.propagators.shear_alf.variables.u = self.mhd.velocity - self.propagators.shear_alf.variables.b = self.em_fields.b_field + from struphy.polar.basic import PolarVector - self.propagators.couple_curr.variables.ions = self.energetic_ions.var - self.propagators.couple_curr.variables.u = self.mhd.velocity + # prelim + e_ions_params = self.kinetic["energetic_ions"]["params"] - self.propagators.push_eta.variables.var = self.energetic_ions.var - self.propagators.push_vxb.variables.ions = self.energetic_ions.var + # extract necessary parameters + u_space = params["fluid"]["mhd"]["options"]["u_space"] + params_alfven = params["fluid"]["mhd"]["options"]["ShearAlfven"] + params_sonic = params["fluid"]["mhd"]["options"]["Magnetosonic"] + params_eta = params["kinetic"]["energetic_ions"]["options"]["PushEta"] + params_vxb = params["kinetic"]["energetic_ions"]["options"]["PushVxB"] + params_density = params["fluid"]["mhd"]["options"]["CurrentCoupling6DDensity"] + params_current = params["kinetic"]["energetic_ions"]["options"]["CurrentCoupling6DCurrent"] - self.propagators.mag_sonic.variables.n = self.mhd.density - self.propagators.mag_sonic.variables.u = self.mhd.velocity - self.propagators.mag_sonic.variables.p = self.mhd.pressure + # compute coupling parameters + Ab = params["fluid"]["mhd"]["phys_params"]["A"] + Ah = params["kinetic"]["energetic_ions"]["phys_params"]["A"] + epsilon = self.equation_params["energetic_ions"]["epsilon"] - # define scalars for update_scalar_quantities - self.add_scalar("en_U", compute="from_field") - self.add_scalar("en_p", compute="from_field") - self.add_scalar("en_B", compute="from_field") - self.add_scalar("en_f", compute="from_particles", variable=self.energetic_ions.var) - self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_f"]) - self.add_scalar("n_lost_particles", compute="from_particles", variable=self.energetic_ions.var) + if abs(epsilon - 1) < 1e-6: + epsilon = 1.0 - @property - def bulk_species(self): - return self.mhd + self._Ab = Ab + self._Ah = Ah - @property - def velocity_scale(self): - return "alfvén" + # add control variate to mass_ops object + if self.pointer["energetic_ions"].control_variate: + self.mass_ops.weights["f0"] = self.pointer["energetic_ions"].f0 + + # project background magnetic field (2-form) and background pressure (3-form) + self._b_eq = self.derham.P["2"]( + [ + self.equil.b2_1, + self.equil.b2_2, + self.equil.b2_3, + ] + ) + self._p_eq = self.derham.P["3"](self.equil.p3) + self._ones = self._p_eq.space.zeros() - def allocate_helpers(self): - self._ones = self.projected_equil.p3.space.zeros() if isinstance(self._ones, PolarVector): self._ones.tp[:] = 1.0 else: self._ones[:] = 1.0 - self._tmp = xp.empty(1, dtype=float) - self._n_lost_particles = xp.empty(1, dtype=float) + # set keyword arguments for propagators + if params_density["turn_off"]: + self._kwargs[propagators_fields.CurrentCoupling6DDensity] = None + else: + self._kwargs[propagators_fields.CurrentCoupling6DDensity] = { + "particles": self.pointer["energetic_ions"], + "u_space": u_space, + "b_eq": self._b_eq, + "b_tilde": self.pointer["b_field"], + "Ab": Ab, + "Ah": Ah, + "epsilon": epsilon, + "solver": params_density["solver"], + "filter": params_density["filter"], + "boundary_cut": params_density["boundary_cut"], + } + + if params_alfven["turn_off"]: + self._kwargs[propagators_fields.ShearAlfven] = None + else: + self._kwargs[propagators_fields.ShearAlfven] = { + "u_space": u_space, + "solver": params_alfven["solver"], + } - # add control variate to mass_ops object - if self.energetic_ions.var.particles.control_variate: - self.mass_ops.weights["f0"] = self.energetic_ions.var.particles.f0 + if params_current["turn_off"]: + self._kwargs[propagators_coupling.CurrentCoupling6DCurrent] = None + else: + self._kwargs[propagators_coupling.CurrentCoupling6DCurrent] = { + "u_space": u_space, + "b_eq": self._b_eq, + "b_tilde": self.pointer["b_field"], + "Ab": Ab, + "Ah": Ah, + "epsilon": epsilon, + "solver": params_current["solver"], + "filter": params_current["filter"], + "boundary_cut": params_current["boundary_cut"], + } + + self._kwargs[propagators_markers.PushEta] = { + "algo": params_eta["algo"], + } + + self._kwargs[propagators_markers.PushVxB] = { + "algo": params_vxb["algo"], + "kappa": 1.0 / epsilon, + "b2": self.pointer["b_field"], + "b2_add": self._b_eq, + } + + if params_sonic["turn_off"]: + self._kwargs[propagators_fields.Magnetosonic] = None + else: + self._kwargs[propagators_fields.Magnetosonic] = { + "u_space": u_space, + "b": self.pointer["b_field"], + "solver": params_sonic["solver"], + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation: + self.add_scalar("en_U", compute="from_field") + self.add_scalar("en_p", compute="from_field") + self.add_scalar("en_B", compute="from_field") + self.add_scalar("en_f", compute="from_particles", species="energetic_ions") + self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_f"]) - self._Ah = self.energetic_ions.mass_number - self._Ab = self.mhd.mass_number + # temporary vectors for scalar quantities: + self._tmp = np.empty(1, dtype=float) + self._n_lost_particles = np.empty(1, dtype=float) + + # MPI operations needed for scalar variables + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE def update_scalar_quantities(self): # perturbed fields - u = self.mhd.velocity.spline.vector - p = self.mhd.pressure.spline.vector - b = self.em_fields.b_field.spline.vector - particles = self.energetic_ions.var.particles - - en_U = 0.5 * self.mass_ops.M2n.dot_inner(u, u) - en_B = 0.5 * self.mass_ops.M2.dot_inner(b, b) - en_p = p.inner(self._ones) / (5 / 3 - 1) + en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_velocity"], self.pointer["mhd_velocity"]) + en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) + en_p = self.pointer["mhd_pressure"].inner(self._ones) / (5 / 3 - 1) self.update_scalar("en_U", en_U) self.update_scalar("en_B", en_B) @@ -181,10 +251,12 @@ def update_scalar_quantities(self): self._tmp[0] = ( self._Ah / self._Ab - * particles.markers_wo_holes[:, 6].dot( - particles.markers_wo_holes[:, 3] ** 2 - + particles.markers_wo_holes[:, 4] ** 2 - + particles.markers_wo_holes[:, 5] ** 2, + * self.pointer["energetic_ions"] + .markers_wo_holes[:, 6] + .dot( + self.pointer["energetic_ions"].markers_wo_holes[:, 3] ** 2 + + self.pointer["energetic_ions"].markers_wo_holes[:, 4] ** 2 + + self.pointer["energetic_ions"].markers_wo_holes[:, 5] ** 2, ) / (2) ) @@ -193,47 +265,15 @@ def update_scalar_quantities(self): self.update_scalar("en_tot", en_U + en_B + en_p + self._tmp[0]) # Print number of lost ions - self._n_lost_particles[0] = particles.n_lost_markers - self.update_scalar("n_lost_particles", self._n_lost_particles[0]) - - if rank == 0: + self._n_lost_particles[0] = self.pointer["energetic_ions"].n_lost_markers + self.derham.comm.Allreduce(self._mpi_in_place, self._n_lost_particles, op=self._mpi_sum) + if self.derham.comm.Get_rank() == 0: print( "ratio of lost particles: ", - self._n_lost_particles[0] / particles.Np * 100, + self._n_lost_particles[0] / self.pointer["energetic_ions"].Np * 100, "%", ) - ## default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "mag_sonic.Options" in line: - new_file += [ - "model.propagators.mag_sonic.options = model.propagators.mag_sonic.Options(b_field=model.em_fields.b_field)\n", - ] - elif "couple_dens.Options" in line: - new_file += [ - "model.propagators.couple_dens.options = model.propagators.couple_dens.Options(energetic_ions=model.energetic_ions.var,\n", - ] - new_file += [ - " b_tilde=model.em_fields.b_field)\n", - ] - elif "couple_curr.Options" in line: - new_file += [ - "model.propagators.couple_curr.options = model.propagators.couple_curr.Options(b_tilde=model.em_fields.b_field)\n", - ] - elif "set_save_data" in line: - new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] - new_file += ["model.energetic_ions.set_save_data(binning_plots=(binplot,))\n"] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - class LinearMHDVlasovPC(StruphyModel): r""" @@ -297,192 +337,214 @@ class LinearMHDVlasovPC(StruphyModel): :ref:`Model info `: """ - ## species - class EnergeticIons(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="Particles6D") - self.init_variables() - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.pressure = FEECVariable(space="L2") - self.velocity = FEECVariable(space="Hdiv") - self.init_variables() - - ## propagators - - class Propagators: - def __init__(self, turn_off: tuple[str, ...] = (None,)): - if "PushEtaPC" not in turn_off: - self.push_eta_pc = propagators_markers.PushEtaPC() - if "PushVxB" not in turn_off: - self.push_vxb = propagators_markers.PushVxB() - if "PressureCoupling6D" not in turn_off: - self.pc6d = propagators_coupling.PressureCoupling6D() - if "ShearAlfven" not in turn_off: - self.shearalfven = propagators_fields.ShearAlfven() - if "Magnetosonic" not in turn_off: - self.magnetosonic = propagators_fields.Magnetosonic() - - def __init__(self, turn_off: tuple[str, ...] = (None,)): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.energetic_ions = self.EnergeticIons() - - # 2. instantiate all propagators - self.propagators = self.Propagators(turn_off) - - # 3. assign variables to propagators - if "ShearAlfven" not in turn_off: - self.propagators.shearalfven.variables.u = self.mhd.velocity - self.propagators.shearalfven.variables.b = self.em_fields.b_field - if "Magnetosonic" not in turn_off: - self.propagators.magnetosonic.variables.n = self.mhd.density - self.propagators.magnetosonic.variables.u = self.mhd.velocity - self.propagators.magnetosonic.variables.p = self.mhd.pressure - if "PressureCoupling6D" not in turn_off: - self.propagators.pc6d.variables.u = self.mhd.velocity - self.propagators.pc6d.variables.energetic_ions = self.energetic_ions.var - if "PushEtaPC" not in turn_off: - self.propagators.push_eta_pc.variables.var = self.energetic_ions.var - if "PushVxB" not in turn_off: - self.propagators.push_vxb.variables.ions = self.energetic_ions.var - - # define scalars for update_scalar_quantities - self.add_scalar("en_U") - self.add_scalar("en_p") - self.add_scalar("en_B") - self.add_scalar("en_f", compute="from_particles", variable=self.energetic_ions.var) - self.add_scalar( - "en_tot", - summands=[ - "en_U", - "en_p", - "en_B", - "en_f", - ], + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + + dct["em_fields"]["b_field"] = "Hdiv" + dct["fluid"]["mhd"] = { + "density": "L2", + "velocity": "Hdiv", + "pressure": "L2", + } + dct["kinetic"]["energetic_ions"] = "Particles6D" + return dct + + @staticmethod + def bulk_species(): + return "mhd" + + @staticmethod + def velocity_scale(): + return "alfvén" + + @staticmethod + def propagators_dct(): + return { + propagators_markers.PushEtaPC: ["energetic_ions"], + propagators_markers.PushVxB: ["energetic_ions"], + propagators_coupling.PressureCoupling6D: ["energetic_ions", "mhd_velocity"], + propagators_fields.ShearAlfven: ["mhd_velocity", "b_field"], + propagators_fields.Magnetosonic: ["mhd_density", "mhd_velocity", "mhd_pressure"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + # add special options + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["fluid", "mhd"], + key="u_space", + option="Hdiv", + dct=dct, ) + return dct - @property - def bulk_species(self): - return self.mhd + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) - @property - def velocity_scale(self): - return "alfvén" + from mpi4py.MPI import IN_PLACE, SUM + + from struphy.polar.basic import PolarVector + + # extract necessary parameters + u_space = params["fluid"]["mhd"]["options"]["u_space"] + params_alfven = params["fluid"]["mhd"]["options"]["ShearAlfven"] + params_sonic = params["fluid"]["mhd"]["options"]["Magnetosonic"] + params_vxb = params["kinetic"]["energetic_ions"]["options"]["PushVxB"] + params_pressure = params["kinetic"]["energetic_ions"]["options"]["PressureCoupling6D"] + + # use perp model + assert ( + params["kinetic"]["energetic_ions"]["options"]["PressureCoupling6D"]["use_perp_model"] + == params["kinetic"]["energetic_ions"]["options"]["PressureCoupling6D"]["use_perp_model"] + ) + use_perp_model = params["kinetic"]["energetic_ions"]["options"]["PressureCoupling6D"]["use_perp_model"] + + # compute coupling parameters + Ab = params["fluid"]["mhd"]["phys_params"]["A"] + Ah = params["kinetic"]["energetic_ions"]["phys_params"]["A"] + epsilon = self.equation_params["energetic_ions"]["epsilon"] + + if abs(epsilon - 1) < 1e-6: + epsilon = 1.0 + + self._coupling_params = {} + self._coupling_params["Ab"] = Ab + self._coupling_params["Ah"] = Ah + self._coupling_params["epsilon"] = epsilon + + # add control variate to mass_ops object + if self.pointer["energetic_ions"].control_variate: + self.mass_ops.weights["f0"] = self.pointer["energetic_ions"].f0 + + # Project magnetic field + self._b_eq = self.derham.P["2"]( + [ + self.equil.b2_1, + self.equil.b2_2, + self.equil.b2_3, + ] + ) + self._p_eq = self.derham.P["3"](self.equil.p3) + self._ones = self._p_eq.space.zeros() - def allocate_helpers(self): - self._ones = self.projected_equil.p3.space.zeros() if isinstance(self._ones, PolarVector): self._ones.tp[:] = 1.0 else: self._ones[:] = 1.0 - self._en_f = xp.empty(1, dtype=float) - self._n_lost_particles = xp.empty(1, dtype=float) + # set keyword arguments for propagators + self._kwargs[propagators_markers.PushEtaPC] = { + "u": self.pointer["mhd_velocity"], + "use_perp_model": use_perp_model, + "u_space": u_space, + } + + self._kwargs[propagators_markers.PushVxB] = { + "algo": params_vxb["algo"], + "kappa": epsilon, + "b2": self.pointer["b_field"], + "b2_add": self._b_eq, + } + + if params_pressure["turn_off"]: + self._kwargs[propagators_coupling.PressureCoupling6D] = None + else: + self._kwargs[propagators_coupling.PressureCoupling6D] = { + "use_perp_model": use_perp_model, + "u_space": u_space, + "solver": params_pressure["solver"], + "coupling_params": self._coupling_params, + "filter": params_pressure["filter"], + "boundary_cut": params_pressure["boundary_cut"], + } + + if params_alfven["turn_off"]: + self._kwargs[propagators_fields.ShearAlfven] = None + else: + self._kwargs[propagators_fields.ShearAlfven] = { + "u_space": u_space, + "solver": params_alfven["solver"], + } - def update_scalar_quantities(self): - # scaling factor - Ab = self.mhd.mass_number - Ah = self.energetic_ions.var.species.mass_number + if params_sonic["turn_off"]: + self._kwargs[propagators_fields.Magnetosonic] = None + else: + self._kwargs[propagators_fields.Magnetosonic] = { + "b": self.pointer["b_field"], + "u_space": u_space, + "solver": params_sonic["solver"], + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation: + self.add_scalar("en_U", compute="from_field") + self.add_scalar("en_p", compute="from_field") + self.add_scalar("en_B", compute="from_field") + self.add_scalar("en_f", compute="from_particles", species="energetic_ions") + self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_f"]) + + # temporary vectors for scalar quantities + self._tmp_u = self.derham.Vh["2"].zeros() + self._tmp_b1 = self.derham.Vh["2"].zeros() + self._tmp = np.empty(1, dtype=float) + self._n_lost_particles = np.empty(1, dtype=float) + + # MPI operations needed for scalar variables + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE + def update_scalar_quantities(self): # perturbed fields - en_U = 0.5 * self.mass_ops.M2n.dot_inner( - self.mhd.velocity.spline.vector, - self.mhd.velocity.spline.vector, - ) - en_B = 0.5 * self.mass_ops.M2.dot_inner( - self.em_fields.b_field.spline.vector, - self.em_fields.b_field.spline.vector, - ) - en_p = self.mhd.pressure.spline.vector.inner(self._ones) / (5 / 3 - 1) + if "Hdiv" == "Hdiv": + en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_velocity"], self.pointer["mhd_velocity"]) + else: + en_U = 0.5 * self.mass_ops.Mvn.dot_inner(self.pointer["mhd_velocity"], self.pointer["mhd_velocity"]) + en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) + en_p = self.pointer["mhd_pressure"].inner(self._ones) / (5 / 3 - 1) self.update_scalar("en_U", en_U) self.update_scalar("en_B", en_B) self.update_scalar("en_p", en_p) - # particles' energy - particles = self.energetic_ions.var.particles - - self._en_f[0] = ( - particles.markers[~particles.holes, 6].dot( - particles.markers[~particles.holes, 3] ** 2 - + particles.markers[~particles.holes, 4] ** 2 - + particles.markers[~particles.holes, 5] ** 2, + # particles + self._tmp[0] = ( + self._coupling_params["Ah"] + / self._coupling_params["Ab"] + * self.pointer["energetic_ions"] + .markers_wo_holes[:, 6] + .dot( + self.pointer["energetic_ions"].markers_wo_holes[:, 3] ** 2 + + self.pointer["energetic_ions"].markers_wo_holes[:, 4] ** 2 + + self.pointer["energetic_ions"].markers_wo_holes[:, 5] ** 2, ) - / 2.0 - * Ah - / Ab + / (2.0) ) - self.update_scalar("en_f", self._en_f[0]) - self.update_scalar("en_tot") - - # print number of lost particles - n_lost_markers = xp.array(particles.n_lost_markers) - - if self.derham.comm is not None: - self.derham.comm.Allreduce( - MPI.IN_PLACE, - n_lost_markers, - op=MPI.SUM, - ) - - if self.clone_config is not None: - self.clone_config.inter_comm.Allreduce( - MPI.IN_PLACE, - n_lost_markers, - op=MPI.SUM, - ) + self.update_scalar("en_f", self._tmp[0]) + self.update_scalar("en_tot", en_U + en_B + en_p + self._tmp[0]) - if rank == 0: + # Print number of lost ions + self._n_lost_particles[0] = self.pointer["energetic_ions"].n_lost_markers + self.derham.comm.Allreduce(self._mpi_in_place, self._n_lost_particles, op=self._mpi_sum) + if self.derham.comm.Get_rank() == 0: print( - "Lost particle ratio: ", - n_lost_markers / particles.Np * 100, - "% \n", + "ratio of lost particles: ", + self._n_lost_particles[0] / self.pointer["energetic_ions"].Np * 100, + "%", ) - ## default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "magnetosonic.Options" in line: - new_file += [ - """model.propagators.magnetosonic.options = model.propagators.magnetosonic.Options( - b_field=model.em_fields.b_field,)\n""", - ] - - elif "push_eta_pc.Options" in line: - new_file += [ - """model.propagators.push_eta_pc.options = model.propagators.push_eta_pc.Options( - u_tilde = model.mhd.velocity,)\n""", - ] - - elif "push_vxb.Options" in line: - new_file += [ - """model.propagators.push_vxb.options = model.propagators.push_vxb.Options( - b2_var = model.em_fields.b_field,)\n""", - ] - - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - class LinearMHDDriftkineticCC(StruphyModel): r"""Hybrid linear ideal MHD + energetic ions (5D Driftkinetic) with **current coupling scheme**. @@ -505,13 +567,13 @@ class LinearMHDDriftkineticCC(StruphyModel): &\frac{\partial \tilde{\rho}}{\partial t}+\nabla\cdot(\rho_{0} \tilde{\mathbf{U}})=0\,, \\ \rho_{0} &\frac{\partial \tilde{\mathbf{U}}}{\partial t} - \tilde p\, \nabla - = (\nabla \times \tilde{\mathbf{B}}) \times \mathbf{B} + (\nabla \times \mathbf B_0) \times \tilde{\mathbf{B}} + = (\nabla \times \tilde{\mathbf{B}}) \times (\mathbf{B}_0 + (\nabla \times \mathbf B_0) \times \tilde{\mathbf{B}} + \frac{A_\textnormal{h}}{A_\textnormal{b}} \left[ \frac{1}{\epsilon} n_\textnormal{gc} \tilde{\mathbf{U}} - \frac{1}{\epsilon} \mathbf{J}_\textnormal{gc} - \nabla \times \mathbf{M}_\textnormal{gc} \right] \times \mathbf{B} \,, \\ &\frac{\partial \tilde p}{\partial t} + \nabla\cdot(p_0 \tilde{\mathbf{U}}) + \frac{2}{3}\,p_0\nabla\cdot \tilde{\mathbf{U}}=0\,, \\ - &\frac{\partial \tilde{\mathbf{B}}}{\partial t} - \nabla\times(\tilde{\mathbf{U}} \times \mathbf{B}) + &\frac{\partial \tilde{\mathbf{B}}}{\partial t} - \nabla\times(\tilde{\mathbf{U}} \times \mathbf{B}_0) = 0\,, \end{aligned} \right. @@ -524,7 +586,7 @@ class LinearMHDDriftkineticCC(StruphyModel): \\ & n_\textnormal{gc} = \int f_\textnormal{h} B_\parallel^* \,\textnormal dv_\parallel \textnormal d\mu \,, \\ - & \mathbf{J}_\textnormal{gc} = \int \frac{f_\textnormal{h}}{B_\parallel^*}(v_\parallel \mathbf{B}^* - \mathbf{b}_0 \times \mathbf{E}^*) \,\textnormal dv_\parallel \textnormal d\mu \,, + & \mathbf{J}_\textnormal{gc} = \int f_\textnormal{h}(v_\parallel \mathbf{B}^* - \mathbf{b}_0 \times \mathbf{E}^*) \,\textnormal dv_\parallel \textnormal d\mu \,, \\ & \mathbf{M}_\textnormal{gc} = - \int f_\textnormal{h} B_\parallel^* \mu \mathbf{b}_0 \,\textnormal dv_\parallel \textnormal d\mu \,, \end{aligned} @@ -536,11 +598,9 @@ class LinearMHDDriftkineticCC(StruphyModel): .. math:: \begin{align} - B^*_\parallel = \mathbf{b}_0 \cdot \mathbf{B}^*\,, - \\[2mm] - \mathbf{B}^* &= \mathbf{B} + \epsilon v_\parallel \nabla \times \mathbf{b}_0 \,, + \mathbf{B}^* &= \mathbf{B} + \epsilon v_\parallel \nabla \times \mathbf{b}_0 \,,\qquad B^*_\parallel = \mathbf{b}_0 \cdot \mathbf{B}^*\,, \\[2mm] - \mathbf{E}^* &= - \tilde{\mathbf{U}} \times \mathbf{B} - \epsilon \mu \nabla (\mathbf{b}_0 \cdot \mathbf{B}) \,, + \mathbf{E}^* &= - \tilde{\mathbf{U}} \times \mathbf{B} - \epsilon \mu \nabla B_\parallel \,, \end{align} with the normalization parameter @@ -557,238 +617,340 @@ class LinearMHDDriftkineticCC(StruphyModel): 4. :class:`~struphy.propagators.propagators_coupling.CurrentCoupling5DCurlb` 5. :class:`~struphy.propagators.propagators_fields.CurrentCoupling5DDensity` 6. :class:`~struphy.propagators.propagators_fields.ShearAlfvenCurrentCoupling5D` - 7. :class:`~struphy.propagators.propagators_fields.Magnetosonic` + 7. :class:`~struphy.propagators.propagators_fields.MagnetosonicCurrentCoupling5D` :ref:`Model info `: """ - ## species - class EnergeticIons(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="Particles5D") - self.init_variables() - - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.pressure = FEECVariable(space="L2") - self.velocity = FEECVariable(space="Hdiv") - self.init_variables() - - ## propagators - - class Propagators: - def __init__(self, turn_off: tuple[str, ...] = (None,)): - if "PushGuidingCenterBxEstar" not in turn_off: - self.push_bxe = propagators_markers.PushGuidingCenterBxEstar() - if "PushGuidingCenterParallel" not in turn_off: - self.push_parallel = propagators_markers.PushGuidingCenterParallel() - if "ShearAlfvenCurrentCoupling5D" not in turn_off: - self.shearalfen_cc5d = propagators_fields.ShearAlfvenCurrentCoupling5D() - if "Magnetosonic" not in turn_off: - self.magnetosonic = propagators_fields.Magnetosonic() - if "CurrentCoupling5DDensity" not in turn_off: - self.cc5d_density = propagators_fields.CurrentCoupling5DDensity() - if "CurrentCoupling5DGradB" not in turn_off: - self.cc5d_gradb = propagators_coupling.CurrentCoupling5DGradB() - if "CurrentCoupling5DCurlb" not in turn_off: - self.cc5d_curlb = propagators_coupling.CurrentCoupling5DCurlb() - - def __init__(self, turn_off: tuple[str, ...] = (None,)): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() - self.energetic_ions = self.EnergeticIons() - - # 2. instantiate all propagators - self.propagators = self.Propagators(turn_off) - - # 3. assign variables to propagators - if "ShearAlfvenCurrentCoupling5D" not in turn_off: - self.propagators.shearalfen_cc5d.variables.u = self.mhd.velocity - self.propagators.shearalfen_cc5d.variables.b = self.em_fields.b_field - if "Magnetosonic" not in turn_off: - self.propagators.magnetosonic.variables.n = self.mhd.density - self.propagators.magnetosonic.variables.u = self.mhd.velocity - self.propagators.magnetosonic.variables.p = self.mhd.pressure - if "CurrentCoupling5DDensity" not in turn_off: - self.propagators.cc5d_density.variables.u = self.mhd.velocity - if "CurrentCoupling5DGradB" not in turn_off: - self.propagators.cc5d_gradb.variables.u = self.mhd.velocity - self.propagators.cc5d_gradb.variables.energetic_ions = self.energetic_ions.var - if "CurrentCoupling5DCurlb" not in turn_off: - self.propagators.cc5d_curlb.variables.u = self.mhd.velocity - self.propagators.cc5d_curlb.variables.energetic_ions = self.energetic_ions.var - if "PushGuidingCenterBxEstar" not in turn_off: - self.propagators.push_bxe.variables.ions = self.energetic_ions.var - if "PushGuidingCenterParallel" not in turn_off: - self.propagators.push_parallel.variables.ions = self.energetic_ions.var - - # define scalars for update_scalar_quantities - self.add_scalar("en_U") - self.add_scalar("en_p") - self.add_scalar("en_B") - self.add_scalar("en_fv", compute="from_particles", variable=self.energetic_ions.var) - self.add_scalar("en_fB", compute="from_particles", variable=self.energetic_ions.var) - self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_fv", "en_fB"]) + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + + dct["em_fields"]["b_field"] = "Hdiv" + dct["fluid"]["mhd"] = { + "density": "L2", + "velocity": "Hdiv", + "pressure": "L2", + } + dct["kinetic"]["energetic_ions"] = "Particles5D" + return dct + + @staticmethod + def bulk_species(): + return "mhd" + + @staticmethod + def velocity_scale(): + return "alfvén" - @property - def bulk_species(self): - return self.mhd + @staticmethod + def propagators_dct(): + return { + propagators_markers.PushGuidingCenterBxEstar: ["energetic_ions"], + propagators_markers.PushGuidingCenterParallel: ["energetic_ions"], + propagators_coupling.CurrentCoupling5DGradB: ["energetic_ions", "mhd_velocity"], + propagators_coupling.CurrentCoupling5DCurlb: ["energetic_ions", "mhd_velocity"], + propagators_fields.CurrentCoupling5DDensity: ["mhd_velocity"], + propagators_fields.ShearAlfvenCurrentCoupling5D: ["mhd_velocity", "b_field"], + propagators_fields.MagnetosonicCurrentCoupling5D: ["mhd_density", "mhd_velocity", "mhd_pressure"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + # add special options + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["fluid", "mhd"], + key="u_space", + option="Hdiv", + dct=dct, + ) + return dct - @property - def velocity_scale(self): - return "alfvén" + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + from mpi4py.MPI import IN_PLACE, SUM + + from struphy.polar.basic import PolarVector + + # extract necessary parameters + u_space = params["fluid"]["mhd"]["options"]["u_space"] + params_alfven = params["fluid"]["mhd"]["options"]["ShearAlfvenCurrentCoupling5D"] + params_sonic = params["fluid"]["mhd"]["options"]["MagnetosonicCurrentCoupling5D"] + params_density = params["fluid"]["mhd"]["options"]["CurrentCoupling5DDensity"] + + params_bxE = params["kinetic"]["energetic_ions"]["options"]["PushGuidingCenterBxEstar"] + params_parallel = params["kinetic"]["energetic_ions"]["options"]["PushGuidingCenterParallel"] + params_cc_gradB = params["kinetic"]["energetic_ions"]["options"]["CurrentCoupling5DGradB"] + params_cc_curlb = params["kinetic"]["energetic_ions"]["options"]["CurrentCoupling5DCurlb"] + params_cc_gradB = params["kinetic"]["energetic_ions"]["options"]["CurrentCoupling5DGradB"] + + # compute coupling parameters + Ab = params["fluid"]["mhd"]["phys_params"]["A"] + Ah = params["kinetic"]["energetic_ions"]["phys_params"]["A"] + epsilon = self.equation_params["energetic_ions"]["epsilon"] + + self._coupling_params = {} + self._coupling_params["Ab"] = Ab + self._coupling_params["Ah"] = Ah + + # add control variate to mass_ops object + if self.pointer["energetic_ions"].control_variate: + self.mass_ops.weights["f0"] = self.pointer["energetic_ions"].f0 + + # Project magnetic field + self._b_eq = self.derham.P["2"]( + [ + self.equil.b2_1, + self.equil.b2_2, + self.equil.b2_3, + ] + ) + + self._absB0 = self.derham.P["0"](self.equil.absB0) + + self._unit_b1 = self.derham.P["1"]( + [ + self.equil.unit_b1_1, + self.equil.unit_b1_2, + self.equil.unit_b1_3, + ] + ) + + self._unit_b2 = self.derham.P["2"]( + [ + self.equil.unit_b2_1, + self.equil.unit_b2_2, + self.equil.unit_b2_3, + ] + ) + + self._gradB1 = self.derham.P["1"]( + [ + self.equil.gradB1_1, + self.equil.gradB1_2, + self.equil.gradB1_3, + ] + ) + + self._curl_unit_b2 = self.derham.P["2"]( + [ + self.equil.curl_unit_b2_1, + self.equil.curl_unit_b2_2, + self.equil.curl_unit_b2_3, + ] + ) + + self._p_eq = self.derham.P["3"](self.equil.p3) + self._ones = self._p_eq.space.zeros() - def allocate_helpers(self): - self._ones = self.projected_equil.p3.space.zeros() if isinstance(self._ones, PolarVector): self._ones.tp[:] = 1.0 else: self._ones[:] = 1.0 - self._en_fv = xp.empty(1, dtype=float) - self._en_fB = xp.empty(1, dtype=float) - self._en_tot = xp.empty(1, dtype=float) - self._n_lost_particles = xp.empty(1, dtype=float) + # set keyword arguments for propagators + self._kwargs[propagators_markers.PushGuidingCenterBxEstar] = { + "b_tilde": self.pointer["b_field"], + "algo": params_bxE["algo"], + "epsilon": epsilon, + } + + self._kwargs[propagators_markers.PushGuidingCenterParallel] = { + "b_tilde": self.pointer["b_field"], + "algo": params_parallel["algo"], + "epsilon": epsilon, + } + + if params_cc_gradB["turn_off"]: + self._kwargs[propagators_coupling.CurrentCoupling5DGradB] = None + else: + self._kwargs[propagators_coupling.CurrentCoupling5DGradB] = { + "b": self.pointer["b_field"], + "b_eq": self._b_eq, + "unit_b1": self._unit_b1, + "unit_b2": self._unit_b2, + "absB0": self._absB0, + "gradB1": self._gradB1, + "curl_unit_b2": self._curl_unit_b2, + "u_space": u_space, + "solver": params_cc_gradB["solver"], + "algo": params_cc_gradB["algo"], + "filter": params_cc_gradB["filter"], + "coupling_params": self._coupling_params, + "epsilon": epsilon, + "boundary_cut": params_cc_gradB["boundary_cut"], + } + + if params_cc_curlb["turn_off"]: + self._kwargs[propagators_coupling.CurrentCoupling5DCurlb] = None + else: + self._kwargs[propagators_coupling.CurrentCoupling5DCurlb] = { + "b": self.pointer["b_field"], + "b_eq": self._b_eq, + "unit_b1": self._unit_b1, + "absB0": self._absB0, + "gradB1": self._gradB1, + "curl_unit_b2": self._curl_unit_b2, + "u_space": u_space, + "solver": params_cc_curlb["solver"], + "filter": params_cc_curlb["filter"], + "coupling_params": self._coupling_params, + "epsilon": epsilon, + "boundary_cut": params_cc_curlb["boundary_cut"], + } + + if params_density["turn_off"]: + self._kwargs[propagators_fields.CurrentCoupling5DDensity] = None + else: + self._kwargs[propagators_fields.CurrentCoupling5DDensity] = { + "particles": self.pointer["energetic_ions"], + "b": self.pointer["b_field"], + "b_eq": self._b_eq, + "unit_b1": self._unit_b1, + "curl_unit_b2": self._curl_unit_b2, + "u_space": u_space, + "solver": params_density["solver"], + "coupling_params": self._coupling_params, + "epsilon": epsilon, + "boundary_cut": params_density["boundary_cut"], + } + + if params_alfven["turn_off"]: + self._kwargs[propagators_fields.ShearAlfvenCurrentCoupling5D] = None + else: + self._kwargs[propagators_fields.ShearAlfvenCurrentCoupling5D] = { + "particles": self.pointer["energetic_ions"], + "unit_b1": self._unit_b1, + "absB0": self._absB0, + "u_space": u_space, + "solver": params_alfven["solver"], + "filter": params_alfven["filter"], + "coupling_params": self._coupling_params, + "accumulated_magnetization": self.pointer["accumulated_magnetization"], + "boundary_cut": params_alfven["boundary_cut"], + } + + if params_sonic["turn_off"]: + self._kwargs[propagators_fields.MagnetosonicCurrentCoupling5D] = None + else: + self._kwargs[propagators_fields.MagnetosonicCurrentCoupling5D] = { + "particles": self.pointer["energetic_ions"], + "b": self.pointer["b_field"], + "unit_b1": self._unit_b1, + "absB0": self._absB0, + "u_space": u_space, + "solver": params_sonic["solver"], + "filter": params_sonic["filter"], + "coupling_params": self._coupling_params, + "boundary_cut": params_sonic["boundary_cut"], + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + # Scalar variables to be saved during simulation + self.add_scalar("en_U", compute="from_field") + self.add_scalar("en_p", compute="from_field") + self.add_scalar("en_B", compute="from_field") + self.add_scalar("en_fv", compute="from_particles", species="energetic_ions") + self.add_scalar("en_fB", compute="from_particles", species="energetic_ions") + # self.add_scalar('en_fv_lost', compute = 'from_particles', species='energetic_ions') + # self.add_scalar('en_fB_lost', compute = 'from_particles', species='energetic_ions') + # self.add_scalar('en_tot',summands = ['en_U','en_p','en_B','en_fv','en_fB','en_fv_lost','en_fB_lost']) + self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_fv", "en_fB"]) - self._PB = getattr(self.basis_ops, "PB") - self._PBb = self._PB.codomain.zeros() + # things needed in update_scalar_quantities + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE - def update_scalar_quantities(self): - # scaling factor - Ab = self.mhd.mass_number - Ah = self.energetic_ions.var.species.mass_number + # temporaries + self._b_full1 = self._b_eq.space.zeros() + self._PBb = self._absB0.space.zeros() - # perturbed fields - en_U = 0.5 * self.mass_ops.M2n.dot_inner( - self.mhd.velocity.spline.vector, - self.mhd.velocity.spline.vector, - ) - en_B = 0.5 * self.mass_ops.M2.dot_inner( - self.em_fields.b_field.spline.vector, - self.em_fields.b_field.spline.vector, - ) - en_p = self.mhd.pressure.spline.vector.inner(self._ones) / (5 / 3 - 1) + self._en_fv = np.empty(1, dtype=float) + self._en_fB = np.empty(1, dtype=float) + # self._en_fv_lost = np.empty(1, dtype=float) + # self._en_fB_lost = np.empty(1, dtype=float) + self._n_lost_particles = np.empty(1, dtype=float) + + def update_scalar_quantities(self): + en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_velocity"], self.pointer["mhd_velocity"]) + en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) + en_p = self.pointer["mhd_pressure"].inner(self._ones) / (5 / 3 - 1) self.update_scalar("en_U", en_U) - self.update_scalar("en_B", en_B) self.update_scalar("en_p", en_p) - - # particles' energy - particles = self.energetic_ions.var.particles + self.update_scalar("en_B", en_B) self._en_fv[0] = ( - particles.markers[~particles.holes, 5].dot( - particles.markers[~particles.holes, 3] ** 2, + self.pointer["energetic_ions"] + .markers[~self.pointer["energetic_ions"].holes, 5] + .dot( + self.pointer["energetic_ions"].markers[~self.pointer["energetic_ions"].holes, 3] ** 2, ) / (2.0) - * Ah - / Ab + * self._coupling_params["Ah"] + / self._coupling_params["Ab"] ) - self._PBb = self._PB.dot(self.em_fields.b_field.spline.vector) - particles.save_magnetic_energy(self._PBb) + self.update_scalar("en_fv", self._en_fv[0]) + + # self._en_fv_lost[0] = self.pointer['energetic_ions'].lost_markers[:self.pointer['energetic_ions'].n_lost_markers, 5].dot( + # self.pointer['energetic_ions'].lost_markers[:self.pointer['energetic_ions'].n_lost_markers, 3]**2) / (2.0) * self._coupling_params['Ah']/self._coupling_params['Ab'] + + # self.update_scalar('en_fv_lost', self._en_fv_lost[0]) + + # calculate particle magnetic energy + self.pointer["energetic_ions"].save_magnetic_energy( + self.pointer["b_field"], + ) self._en_fB[0] = ( - particles.markers[~particles.holes, 5].dot( - particles.markers[~particles.holes, 8], + self.pointer["energetic_ions"] + .markers[~self.pointer["energetic_ions"].holes, 5] + .dot( + self.pointer["energetic_ions"].markers[~self.pointer["energetic_ions"].holes, 8], ) - * Ah - / Ab + * self._coupling_params["Ah"] + / self._coupling_params["Ab"] ) - self.update_scalar("en_fv", self._en_fv[0]) self.update_scalar("en_fB", self._en_fB[0]) - self.update_scalar("en_tot") - # print number of lost particles - n_lost_markers = xp.array(particles.n_lost_markers) + # self._en_fB_lost[0] = self.pointer['energetic_ions'].lost_markers[:self.pointer['energetic_ions'].n_lost_markers, 5].dot( + # self.pointer['energetic_ions'] .lost_markers[:self.pointer['energetic_ions'].n_lost_markers, 8]) * self._coupling_params['Ah']/self._coupling_params['Ab'] - if self.derham.comm is not None: - self.derham.comm.Allreduce( - MPI.IN_PLACE, - n_lost_markers, - op=MPI.SUM, - ) + # self.update_scalar('en_fB_lost', self._en_fB_lost[0]) - if self.clone_config is not None: - self.clone_config.inter_comm.Allreduce( - MPI.IN_PLACE, - n_lost_markers, - op=MPI.SUM, - ) + self.update_scalar("en_tot") - if rank == 0: + # Print number of lost ions + self._n_lost_particles[0] = self.pointer["energetic_ions"].n_lost_markers + self.derham.comm.Allreduce(self._mpi_in_place, self._n_lost_particles, op=self._mpi_sum) + if self.derham.comm.Get_rank() == 0: print( - "Lost particle ratio: ", - n_lost_markers / particles.Np * 100, - "% \n", + "ratio of lost particles: ", + self._n_lost_particles[0] / self.pointer["energetic_ions"].Np * 100, + "%", ) - ## default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "shearalfen_cc5d.Options" in line: - new_file += [ - """model.propagators.shearalfen_cc5d.options = model.propagators.shearalfen_cc5d.Options( - energetic_ions = model.energetic_ions.var,)\n""", - ] - - elif "magnetosonic.Options" in line: - new_file += [ - """model.propagators.magnetosonic.options = model.propagators.magnetosonic.Options( - b_field=model.em_fields.b_field,)\n""", - ] - - elif "cc5d_density.Options" in line: - new_file += [ - """model.propagators.cc5d_density.options = model.propagators.cc5d_density.Options( - energetic_ions = model.energetic_ions.var, - b_tilde = model.em_fields.b_field,)\n""", - ] - - elif "cc5d_curlb.Options" in line: - new_file += [ - """model.propagators.cc5d_curlb.options = model.propagators.cc5d_curlb.Options( - b_tilde = model.em_fields.b_field,)\n""", - ] - - elif "cc5d_gradb.Options" in line: - new_file += [ - """model.propagators.cc5d_gradb.options = model.propagators.cc5d_gradb.Options( - b_tilde = model.em_fields.b_field,)\n""", - ] - - elif "push_bxe.Options" in line: - new_file += [ - """model.propagators.push_bxe.options = model.propagators.push_bxe.Options( - b_tilde = model.em_fields.b_field,)\n""", - ] - - elif "push_parallel.Options" in line: - new_file += [ - """model.propagators.push_parallel.options = model.propagators.push_parallel.Options( - b_tilde = model.em_fields.b_field,)\n""", - ] - - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + @staticmethod + def diagnostics_dct(): + dct = {} + + dct["accumulated_magnetization"] = "Hdiv" + return dct + + __diagnostics__ = diagnostics_dct() class ColdPlasmaVlasov(StruphyModel): @@ -838,175 +1000,209 @@ class ColdPlasmaVlasov(StruphyModel): 6. :class:`~struphy.propagators.propagators_coupling.VlasovAmpere` """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.e_field = FEECVariable(space="Hcurl") - self.b_field = FEECVariable(space="Hdiv") - self.phi = FEECVariable(space="H1") - self.init_variables() - - class ThermalElectrons(FluidSpecies): - def __init__(self): - self.current = FEECVariable(space="Hcurl") - self.init_variables() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class HotElectrons(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="Particles6D") - self.init_variables() + dct["em_fields"]["e_field"] = "Hcurl" + dct["em_fields"]["b_field"] = "Hdiv" + dct["fluid"]["cold_electrons"] = {"j": "Hcurl"} + dct["kinetic"]["hot_electrons"] = "Particles6D" + return dct - ## propagators + @staticmethod + def bulk_species(): + return "cold_electrons" - class Propagators: - def __init__(self): - self.maxwell = propagators_fields.Maxwell() - self.ohm = propagators_fields.OhmCold() - self.jxb = propagators_fields.JxBCold() - self.push_eta = propagators_markers.PushEta() - self.push_vxb = propagators_markers.PushVxB() - self.coupling_va = propagators_coupling.VlasovAmpere() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.thermal_elec = self.ThermalElectrons() - self.hot_elec = self.HotElectrons() + @staticmethod + def velocity_scale(): + return "light" - # 2. instantiate all propagators - self.propagators = self.Propagators() + @staticmethod + def propagators_dct(): + return { + propagators_fields.Maxwell: ["e_field", "b_field"], + propagators_fields.OhmCold: ["cold_electrons_j", "e_field"], + propagators_fields.JxBCold: ["cold_electrons_j"], + propagators_markers.PushEta: ["hot_electrons"], + propagators_markers.PushVxB: ["hot_electrons"], + propagators_coupling.VlasovAmpere: ["e_field", "hot_electrons"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + # add special options + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["em_fields"], + option=propagators_fields.ImplicitDiffusion, + dct=dct, + ) + return dct - # 3. assign variables to propagators - self.propagators.maxwell.variables.e = self.em_fields.e_field - self.propagators.maxwell.variables.b = self.em_fields.b_field + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) - self.propagators.ohm.variables.j = self.thermal_elec.current - self.propagators.ohm.variables.e = self.em_fields.e_field + from mpi4py.MPI import IN_PLACE, SUM - self.propagators.jxb.variables.j = self.thermal_elec.current + # Get rank and size + self._rank = comm.Get_rank() - self.propagators.push_eta.variables.var = self.hot_elec.var - self.propagators.push_vxb.variables.ions = self.hot_elec.var + # prelim + hot_params = params["kinetic"]["hot_electrons"] - self.propagators.coupling_va.variables.e = self.em_fields.e_field - self.propagators.coupling_va.variables.ions = self.hot_elec.var + # model parameters + self._alpha = np.abs( + self.equation_params["cold_electrons"]["alpha"], + ) + self._epsilon_cold = self.equation_params["cold_electrons"]["epsilon"] + self._epsilon_hot = self.equation_params["hot_electrons"]["epsilon"] + + self._nu = hot_params["phys_params"]["Z"] / params["fluid"]["cold_electrons"]["phys_params"]["Z"] + + # Initialize background magnetic field from MHD equilibrium + self._b_background = self.derham.P["2"]( + [ + self.equil.b2_1, + self.equil.b2_2, + self.equil.b2_3, + ] + ) - # define scalars for update_scalar_quantities + # propagator parameters + params_maxwell = params["em_fields"]["options"]["Maxwell"]["solver"] + params_ohmcold = params["fluid"]["cold_electrons"]["options"]["OhmCold"]["solver"] + params_jxbcold = params["fluid"]["cold_electrons"]["options"]["JxBCold"]["solver"] + algo_eta = params["kinetic"]["hot_electrons"]["options"]["PushEta"]["algo"] + algo_vxb = params["kinetic"]["hot_electrons"]["options"]["PushVxB"]["algo"] + params_coupling = params["em_fields"]["options"]["VlasovAmpere"]["solver"] + self._poisson_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] + + # set keyword arguments for propagators + self._kwargs[propagators_fields.Maxwell] = {"solver": params_maxwell} + + self._kwargs[propagators_fields.OhmCold] = { + "alpha": self._alpha, + "epsilon": self._epsilon_cold, + "solver": params_ohmcold, + } + + self._kwargs[propagators_fields.JxBCold] = { + "epsilon": self._epsilon_cold, + "solver": params_jxbcold, + } + + self._kwargs[propagators_markers.PushEta] = {"algo": algo_eta} + + self._kwargs[propagators_markers.PushVxB] = { + "algo": algo_vxb, + "kappa": 1.0 / self._epsilon_hot, + "b2": self.pointer["b_field"], + "b2_add": self._b_background, + } + + self._kwargs[propagators_coupling.VlasovAmpere] = { + "c1": self._alpha**2 / self._epsilon_hot, + "c2": 1.0 / self._epsilon_hot, + "solver": params_coupling, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during simulation self.add_scalar("en_E") self.add_scalar("en_B") self.add_scalar("en_J") - self.add_scalar("en_f", compute="from_particles", variable=self.hot_elec.var) + self.add_scalar("en_f") self.add_scalar("en_tot") - # initial Poisson (not a propagator used in time stepping) - self.initial_poisson = propagators_fields.Poisson() - self.initial_poisson.variables.phi = self.em_fields.phi - - @property - def bulk_species(self): - return self.thermal_elec + # MPI operations needed for scalar variables + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE - @property - def velocity_scale(self): - return "light" + # temporaries + self._tmp = np.empty(1, dtype=float) - def allocate_helpers(self): - self._tmp = xp.empty(1, dtype=float) + def initialize_from_params(self): + """:meta private:""" + from psydac.linalg.stencil import StencilVector - def update_scalar_quantities(self): - # e*M1*e/2 - e = self.em_fields.e_field.spline.vector - en_E = 0.5 * self.mass_ops.M1.dot_inner(e, e) - self.update_scalar("en_E", en_E) - - # alpha^2 / 2 / N * sum_p w_p v_p^2 - particles = self.hot_elec.var.particles - alpha = self.hot_elec.equation_params.alpha - self._tmp[0] = ( - alpha**2 - / (2 * particles.Np) - * xp.dot( - particles.markers_wo_holes[:, 3] ** 2 - + particles.markers_wo_holes[:, 4] ** 2 - + particles.markers_wo_holes[:, 5] ** 2, - particles.markers_wo_holes[:, 6], - ) - ) - self.update_scalar("en_f", self._tmp[0]) + from struphy.pic.accumulation.particles_to_grid import AccumulatorVector - # en_tot = en_w + en_e - self.update_scalar("en_tot", en_E + self._tmp[0]) + # Initialize fields and particles + super().initialize_from_params() - def allocate_propagators(self): - """Solve initial Poisson equation. - - :meta private: - """ - - # initialize fields and particles - super().allocate_propagators() - - if MPI.COMM_WORLD.Get_rank() == 0: - print("\nINITIAL POISSON SOLVE:") - - # use control variate method - particles = self.hot_elec.var.particles - particles.update_weights() - - # sanity check - # self.pointer['species1'].show_distribution_function( - # [True] + [False]*5, [xp.linspace(0, 1, 32)]) - - # accumulate charge density + # Accumulate charge density charge_accum = AccumulatorVector( - particles, + self.pointer["hot_electrons"], "H1", - Pyccelkernel(accum_kernels.charge_density_0form), + accum_kernels.vlasov_maxwell_poisson, self.mass_ops, self.domain.args_domain, ) + charge_accum() - # another sanity check: compute FE coeffs of density - # charge_accum.show_accumulated_spline_field(self.mass_ops) - - alpha = self.hot_elec.equation_params.alpha - epsilon = self.hot_elec.equation_params.epsilon + # Locally subtract mean charge for solvability with periodic bc + if np.all(charge_accum.vectors[0].space.periods): + charge_accum._vectors[0][:] -= np.mean( + charge_accum.vectors[0].toarray()[charge_accum.vectors[0].toarray() != 0], + ) - self.initial_poisson.options.rho = charge_accum - self.initial_poisson.options.rho_coeffs = alpha**2 / epsilon - self.initial_poisson.allocate() + # Instantiate Poisson solver + _phi = StencilVector(self.derham.Vh["0"]) + poisson_solver = propagators_fields.ImplicitDiffusion( + _phi, + sigma_1=0, + rho=self._nu * self._alpha**2 / self._epsilon_cold * charge_accum.vectors[0], + x0=self._nu * self._alpha**2 / self._epsilon_cold * charge_accum.vectors[0], + solver=self._poisson_params, + ) # Solve with dt=1. and compute electric field - if MPI.COMM_WORLD.Get_rank() == 0: - print("\nSolving initial Poisson problem...") - self.initial_poisson(1.0) - - phi = self.initial_poisson.variables.phi.spline.vector - self.derham.grad.dot(-phi, out=self.em_fields.e_field.spline.vector) - if MPI.COMM_WORLD.Get_rank() == 0: - print("Done.") - - ## default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "coupling_va.Options" in line: - new_file += [line] - new_file += ["model.initial_poisson.options = model.initial_poisson.Options()\n"] - elif "set_save_data" in line: - new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] - new_file += ["model.hot_elec.set_save_data(binning_plots=(binplot,))\n"] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + poisson_solver(1.0) + self.derham.grad.dot(-_phi, out=self.pointer["e_field"]) + + def update_scalar_quantities(self): + en_E = 0.5 * self.mass_ops.M1.dot_inner(self.pointer["e_field"], self.pointer["e_field"]) + en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) + en_J = ( + 0.5 + * self._alpha**2 + * self.mass_ops.M1ninv.dot_inner(self.pointer["cold_electrons_j"], self.pointer["cold_electrons_j"]) + ) + self.update_scalar("en_E", en_E) + self.update_scalar("en_B", en_B) + self.update_scalar("en_J", en_J) + + # nu alpha^2 eps_h / eps_c / 2 / N * sum_p w_p v_p^2 + self._tmp[0] = ( + self._nu + * self._alpha**2 + * self._epsilon_hot + / self._epsilon_cold + / (2 * self.pointer["hot_electrons"].Np) + * np.dot( + self.pointer["hot_electrons"].markers_wo_holes[:, 3] ** 2 + + self.pointer["hot_electrons"].markers_wo_holes[:, 4] ** 2 + + self.pointer["hot_electrons"].markers_wo_holes[:, 5] ** 2, + self.pointer["hot_electrons"].markers_wo_holes[:, 6], + ) + ) + self.derham.comm.Allreduce( + self._mpi_in_place, + self._tmp, + op=self._mpi_sum, + ) + self.update_scalar("en_f", self._tmp[0]) + + # en_tot = en_E + en_B + en_J + en_w + self.update_scalar("en_tot", en_E + en_B + en_J + self._tmp[0]) diff --git a/src/struphy/models/kinetic.py b/src/struphy/models/kinetic.py index 3f327e9b2..4f5dd89bd 100644 --- a/src/struphy/models/kinetic.py +++ b/src/struphy/models/kinetic.py @@ -1,16 +1,14 @@ -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI from struphy.feec.projectors import L2Projector from struphy.kinetic_background.base import KineticBackground -from struphy.kinetic_background.maxwellians import Maxwellian3D from struphy.models.base import StruphyModel from struphy.models.species import FieldSpecies, FluidSpecies, ParticleSpecies from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.pic.accumulation import accum_kernels, accum_kernels_gc from struphy.pic.accumulation.particles_to_grid import AccumulatorVector from struphy.propagators import propagators_coupling, propagators_fields, propagators_markers -from struphy.utils.pyccel import Pyccelkernel rank = MPI.COMM_WORLD.Get_rank() @@ -146,7 +144,7 @@ def velocity_scale(self): return "light" def allocate_helpers(self): - self._tmp = xp.empty(1, dtype=float) + self._tmp = np.empty(1, dtype=float) def update_scalar_quantities(self): # e*M1*e/2 @@ -160,7 +158,7 @@ def update_scalar_quantities(self): self._tmp[0] = ( alpha**2 / (2 * particles.Np) - * xp.dot( + * np.dot( particles.markers_wo_holes[:, 3] ** 2 + particles.markers_wo_holes[:, 4] ** 2 + particles.markers_wo_holes[:, 5] ** 2, @@ -190,25 +188,29 @@ def allocate_propagators(self): # sanity check # self.pointer['species1'].show_distribution_function( - # [True] + [False]*5, [xp.linspace(0, 1, 32)]) + # [True] + [False]*5, [np.linspace(0, 1, 32)]) # accumulate charge density charge_accum = AccumulatorVector( particles, "H1", - Pyccelkernel(accum_kernels.charge_density_0form), + accum_kernels.charge_density_0form, self.mass_ops, self.domain.args_domain, ) + charge_accum(particles.vdim) + # another sanity check: compute FE coeffs of density # charge_accum.show_accumulated_spline_field(self.mass_ops) alpha = self.kinetic_ions.equation_params.alpha epsilon = self.kinetic_ions.equation_params.epsilon - self.initial_poisson.options.rho = charge_accum - self.initial_poisson.options.rho_coeffs = alpha**2 / epsilon + l2_proj = L2Projector(space_id="H1", mass_ops=self.mass_ops) + rho_coeffs = l2_proj.solve(charge_accum.vectors[0]) + + self.initial_poisson.options.rho = alpha**2 / epsilon * rho_coeffs self.initial_poisson.allocate() # Solve with dt=1. and compute electric field @@ -323,171 +325,208 @@ class VlasovMaxwellOneSpecies(StruphyModel): :ref:`Model info `: """ - ## species + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class EMFields(FieldSpecies): - def __init__(self): - self.e_field = FEECVariable(space="Hcurl") - self.b_field = FEECVariable(space="Hdiv") - self.phi = FEECVariable(space="H1") - self.init_variables() + dct["em_fields"]["e_field"] = "Hcurl" + dct["em_fields"]["b_field"] = "Hdiv" + dct["kinetic"]["species1"] = "Particles6D" + return dct - class KineticIons(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="Particles6D") - self.init_variables() + @staticmethod + def bulk_species(): + return "species1" - ## propagators - - class Propagators: - def __init__(self): - self.maxwell = propagators_fields.Maxwell() - self.push_eta = propagators_markers.PushEta() - self.push_vxb = propagators_markers.PushVxB() - self.coupling_va = propagators_coupling.VlasovAmpere() - - ## abstract methods + @staticmethod + def velocity_scale(): + return "light" - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + @staticmethod + def propagators_dct(): + return { + propagators_fields.Maxwell: ["e_field", "b_field"], + propagators_markers.PushEta: ["species1"], + propagators_markers.PushVxB: ["species1"], + propagators_coupling.VlasovAmpere: ["e_field", "species1"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + # add special options + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["em_fields"], + option=propagators_fields.ImplicitDiffusion, + dct=dct, + ) + cls.add_option( + species=["kinetic", "species1"], + key="override_eq_params", + option=[False, {"alpha": 1.0, "epsilon": -1.0}], + dct=dct, + ) + return dct - # 1. instantiate all species - self.em_fields = self.EMFields() - self.kinetic_ions = self.KineticIons() + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) - # 2. instantiate all propagators - self.propagators = self.Propagators() + from mpi4py.MPI import IN_PLACE, SUM - # 3. assign variables to propagators - self.propagators.maxwell.variables.e = self.em_fields.e_field - self.propagators.maxwell.variables.b = self.em_fields.b_field - self.propagators.push_eta.variables.var = self.kinetic_ions.var - self.propagators.push_vxb.variables.ions = self.kinetic_ions.var - self.propagators.coupling_va.variables.e = self.em_fields.e_field - self.propagators.coupling_va.variables.ions = self.kinetic_ions.var + # get species paramaters + species1_params = params["kinetic"]["species1"] - # define scalars for update_scalar_quantities + # equation parameters + if species1_params["options"]["override_eq_params"]: + self._alpha = species1_params["options"]["override_eq_params"]["alpha"] + self._epsilon = species1_params["options"]["override_eq_params"]["epsilon"] + print( + f"\n!!! Override equation parameters: {self._alpha = } and {self._epsilon = }.", + ) + else: + self._alpha = self.equation_params["species1"]["alpha"] + self._epsilon = self.equation_params["species1"]["epsilon"] + + # set background density and mean velocity factors + self.pointer["species1"].f0.moment_factors["u"] = [ + self._epsilon / self._alpha**2, + ] * 3 + + # Initialize background magnetic field from MHD equilibrium + if self.projected_equil: + self._b_background = self.projected_equil.b2 + else: + self._b_background = None + + # propagator parameters + params_maxwell = params["em_fields"]["options"]["Maxwell"]["solver"] + algo_eta = params["kinetic"]["species1"]["options"]["PushEta"]["algo"] + algo_vxb = params["kinetic"]["species1"]["options"]["PushVxB"]["algo"] + params_coupling = params["em_fields"]["options"]["VlasovAmpere"]["solver"] + self._poisson_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] + + # set keyword arguments for propagators + self._kwargs[propagators_fields.Maxwell] = {"solver": params_maxwell} + + self._kwargs[propagators_markers.PushEta] = {"algo": algo_eta} + + self._kwargs[propagators_markers.PushVxB] = { + "algo": algo_vxb, + "kappa": 1.0 / self._epsilon, + "b2": self.pointer["b_field"], + "b2_add": self._b_background, + } + + self._kwargs[propagators_coupling.VlasovAmpere] = { + "c1": self._alpha**2 / self._epsilon, + "c2": 1.0 / self._epsilon, + "solver": params_coupling, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # Scalar variables to be saved during the simulation self.add_scalar("en_E") self.add_scalar("en_B") - self.add_scalar("en_f", compute="from_particles", variable=self.kinetic_ions.var) + self.add_scalar("en_f") self.add_scalar("en_tot") - # initial Poisson (not a propagator used in time stepping) - self.initial_poisson = propagators_fields.Poisson() - self.initial_poisson.variables.phi = self.em_fields.phi - - @property - def bulk_species(self): - return self.kinetic_ions - - @property - def velocity_scale(self): - return "light" - - def allocate_helpers(self): - self._tmp = xp.empty(1, dtype=float) + # MPI operations needed for scalar variables + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE - def update_scalar_quantities(self): - # e*M1*e/2 - e = self.em_fields.e_field.spline.vector - b = self.em_fields.b_field.spline.vector + # temporaries + self._tmp = np.empty(1, dtype=float) - en_E = 0.5 * self.mass_ops.M1.dot_inner(e, e) - self.update_scalar("en_E", en_E) + def initialize_from_params(self): + """:meta private:""" - en_B = 0.5 * self.mass_ops.M2.dot_inner(b, b) - self.update_scalar("en_B", en_B) - - # alpha^2 / 2 / N * sum_p w_p v_p^2 - particles = self.kinetic_ions.var.particles - alpha = self.kinetic_ions.equation_params.alpha - self._tmp[0] = ( - alpha**2 - / (2 * particles.Np) - * xp.dot( - particles.markers_wo_holes[:, 3] ** 2 - + particles.markers_wo_holes[:, 4] ** 2 - + particles.markers_wo_holes[:, 5] ** 2, - particles.markers_wo_holes[:, 6], - ) - ) - self.update_scalar("en_f", self._tmp[0]) - - # en_tot = en_w + en_e - self.update_scalar("en_tot", en_E + self._tmp[0]) - - def allocate_propagators(self): - """Solve initial Poisson equation. - - :meta private: - """ + from struphy.pic.accumulation.particles_to_grid import AccumulatorVector # initialize fields and particles - super().allocate_propagators() + super().initialize_from_params() - if MPI.COMM_WORLD.Get_rank() == 0: + if self.rank_world == 0: print("\nINITIAL POISSON SOLVE:") # use control variate method - particles = self.kinetic_ions.var.particles - particles.update_weights() + self.pointer["species1"].update_weights() # sanity check # self.pointer['species1'].show_distribution_function( - # [True] + [False]*5, [xp.linspace(0, 1, 32)]) + # [True] + [False]*5, [np.linspace(0, 1, 32)]) # accumulate charge density charge_accum = AccumulatorVector( - particles, + self.pointer["species1"], "H1", - Pyccelkernel(accum_kernels.charge_density_0form), + accum_kernels.charge_density_0form, self.mass_ops, self.domain.args_domain, ) + charge_accum(self.pointer["species1"].vdim) + # another sanity check: compute FE coeffs of density # charge_accum.show_accumulated_spline_field(self.mass_ops) - alpha = self.kinetic_ions.equation_params.alpha - epsilon = self.kinetic_ions.equation_params.epsilon - - self.initial_poisson.options.rho = charge_accum - self.initial_poisson.options.rho_coeffs = alpha**2 / epsilon - self.initial_poisson.allocate() + # Instantiate Poisson solver + _phi = self.derham.Vh["0"].zeros() + poisson_solver = propagators_fields.ImplicitDiffusion( + _phi, + sigma_1=0.0, + sigma_2=0.0, + sigma_3=1.0, + rho=self._alpha**2 / self._epsilon * charge_accum.vectors[0], + solver=self._poisson_params, + ) # Solve with dt=1. and compute electric field - if MPI.COMM_WORLD.Get_rank() == 0: + if self.rank_world == 0: print("\nSolving initial Poisson problem...") - self.initial_poisson(1.0) + poisson_solver(1.0) - phi = self.initial_poisson.variables.phi.spline.vector - self.derham.grad.dot(-phi, out=self.em_fields.e_field.spline.vector) - if MPI.COMM_WORLD.Get_rank() == 0: + self.derham.grad.dot(-_phi, out=self.pointer["e_field"]) + if self.rank_world == 0: print("Done.") - ## default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "coupling_va.Options" in line: - new_file += [line] - new_file += ["model.initial_poisson.options = model.initial_poisson.Options()\n"] - elif "push_vxb.Options" in line: - new_file += [ - "model.propagators.push_vxb.options = model.propagators.push_vxb.Options(b2_var=model.em_fields.b_field)\n", - ] - elif "set_save_data" in line: - new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] - new_file += ["model.kinetic_ions.set_save_data(binning_plots=(binplot,))\n"] - else: - new_file += [line] + def update_scalar_quantities(self): + # e*M1*e and b*M2*b + en_E = 0.5 * self.mass_ops.M1.dot_inner(self.pointer["e_field"], self.pointer["e_field"]) + en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) + self.update_scalar("en_E", en_E) + self.update_scalar("en_B", en_B) - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + # alpha^2 / 2 / N * sum_p w_p v_p^2 + self._tmp[0] = ( + self._alpha**2 + / (2 * self.pointer["species1"].Np) + * np.dot( + self.pointer["species1"].markers_wo_holes[:, 3] ** 2 + + self.pointer["species1"].markers_wo_holes[:, 4] ** 2 + + self.pointer["species1"].markers_wo_holes[:, 5] ** 2, + self.pointer["species1"].markers_wo_holes[:, 6], + ) + ) + if self.comm_world is not None: + self.comm_world.Allreduce( + self._mpi_in_place, + self._tmp, + op=self._mpi_sum, + ) + self.update_scalar("en_f", self._tmp[0]) + + # en_tot = en_w + en_e + en_b + self.update_scalar("en_tot", en_E + en_B + self._tmp[0]) class LinearVlasovAmpereOneSpecies(StruphyModel): @@ -557,188 +596,257 @@ class LinearVlasovAmpereOneSpecies(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.e_field = FEECVariable(space="Hcurl") - self.phi = FEECVariable(space="H1") - self.init_variables() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class KineticIons(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="DeltaFParticles6D") - self.init_variables() + dct["em_fields"]["e_field"] = "Hcurl" + dct["kinetic"]["species1"] = "DeltaFParticles6D" + return dct - ## propagators + @staticmethod + def bulk_species(): + return "species1" - class Propagators: - def __init__( - self, - with_B0: bool = True, - with_E0: bool = True, - ): - self.push_eta = propagators_markers.PushEta() - if with_E0: - self.push_vinE = propagators_markers.PushVinEfield() - self.coupling_Eweights = propagators_coupling.EfieldWeights() - if with_B0: - self.push_vxb = propagators_markers.PushVxB() - - ## abstract methods - - def __init__( - self, - with_B0: bool = True, - with_E0: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.kinetic_ions = self.KineticIons() - - # 2. instantiate all propagators - self.propagators = self.Propagators(with_B0=with_B0, with_E0=with_E0) + @staticmethod + def velocity_scale(): + return "light" - # 3. assign variables to propagators - self.propagators.push_eta.variables.var = self.kinetic_ions.var - if with_E0: - self.propagators.push_vinE.variables.var = self.kinetic_ions.var - self.propagators.coupling_Eweights.variables.e = self.em_fields.e_field - self.propagators.coupling_Eweights.variables.ions = self.kinetic_ions.var - if with_B0: - self.propagators.push_vxb.variables.ions = self.kinetic_ions.var + @staticmethod + def propagators_dct(): + return { + propagators_markers.PushEta: ["species1"], + propagators_markers.PushVinEfield: ["species1"], + propagators_coupling.EfieldWeights: ["e_field", "species1"], + propagators_markers.PushVxB: ["species1"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["em_fields"], + option=propagators_fields.ImplicitDiffusion, + dct=dct, + ) + cls.add_option( + species=["kinetic", "species1"], + key="override_eq_params", + option=[False, {"epsilon": -1.0, "alpha": 1.0}], + dct=dct, + ) + return dct - # define scalars for update_scalar_quantities - self.add_scalar("en_E") - self.add_scalar("en_w", compute="from_particles", variable=self.kinetic_ions.var) - self.add_scalar("en_tot") + def __init__(self, params, comm, clone_config=None, baseclass=False): + """Initializes the model either as the full model or as a baseclass to inherit from. + In case of being a baseclass, the propagators will not be initialized in the __init__ which allows other propagators to be added. - # initial Poisson (not a propagator used in time stepping) - self.initial_poisson = propagators_fields.Poisson() - self.initial_poisson.variables.phi = self.em_fields.phi + Parameters + ---------- + baseclass : Boolean [optional] + If this model should be used as a baseclass. Default value is False. + """ - @property - def bulk_species(self): - return self.kinetic_ions + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) - @property - def velocity_scale(self): - return "light" + from mpi4py.MPI import IN_PLACE, SUM - def allocate_helpers(self): - self._tmp = xp.empty(1, dtype=float) + from struphy.kinetic_background import maxwellians - def update_scalar_quantities(self): - # e*M1*e/2 - e = self.em_fields.e_field.spline.vector - particles = self.kinetic_ions.var.particles + # if model is used as a baseclass + self._baseclass = baseclass - en_E = 0.5 * self.mass_ops.M1.dot_inner(e, e) - self.update_scalar("en_E", en_E) + # kinetic parameters + self._species_params = params["kinetic"]["species1"] - # evaluate f0 - if not hasattr(self, "_f0"): - backgrounds = self.kinetic_ions.var.backgrounds - if isinstance(backgrounds, list): - self._f0 = backgrounds[0] - else: - self._f0 = backgrounds - self._f0_values = xp.zeros( - self.kinetic_ions.var.particles.markers.shape[0], - dtype=float, + # Assert Maxwellian background (if list, the first entry is taken) + bckgr_params = self._species_params["background"] + li_bp = list(bckgr_params) + assert li_bp[0] == "Maxwellian3D", "The background distribution function must be a uniform Maxwellian!" + if len(li_bp) > 1: + # overwrite f0 with single Maxwellian + self._f0 = getattr(maxwellians, li_bp[0][:-2])( + maxw_params=bckgr_params[li_bp[0]], ) - assert isinstance(self._f0, Maxwellian3D) - - self._f0_values[particles.valid_mks] = self._f0(*particles.phasespace_coords.T) + else: + # keep allocated background + self._f0 = self.pointer["species1"].f0 + + # Assert uniformity of the Maxwellian background + assert self._f0.maxw_params["u1"] == 0.0, "The background Maxwellian cannot have shifts in velocity space!" + assert self._f0.maxw_params["u2"] == 0.0, "The background Maxwellian cannot have shifts in velocity space!" + assert self._f0.maxw_params["u3"] == 0.0, "The background Maxwellian cannot have shifts in velocity space!" + assert self._f0.maxw_params["vth1"] == self._f0.maxw_params["vth2"] == self._f0.maxw_params["vth3"], ( + "The background Maxwellian must be isotropic in velocity space!" + ) + self.vth = self._f0.maxw_params["vth1"] + + # Get coupling strength + if self._species_params["options"]["override_eq_params"]: + self.epsilon = self._species_params["options"]["override_eq_params"]["epsilon"] + self.alpha = self._species_params["options"]["override_eq_params"]["alpha"] + if self.rank_world == 0: + print( + f"\n!!! Override equation parameters: {self.epsilon = }, {self.alpha = }.\n", + ) + else: + self.epsilon = self.equation_params["species1"]["epsilon"] + self.alpha = self.equation_params["species1"]["alpha"] + + # allocate memory for evaluating f0 in energy computation + self._f0_values = np.zeros( + self.pointer["species1"].markers.shape[0], + dtype=float, + ) - # alpha^2 * v_th^2 / (2*N) * sum_p s_0 * w_p^2 / f_{0,p} - alpha = self.kinetic_ions.equation_params.alpha - vth = self._f0.maxw_params["vth1"][0] + # ==================================================================================== + # Create pointers to background electric potential and field + self._has_background_e = False + if "external_E0" in self.params["em_fields"]["options"].keys(): + e0 = self.params["em_fields"]["options"]["external_E0"] + if e0 != 0.0: + self._has_background_e = True + self._e_background = self.derham.Vh["1"].zeros() + for block in self._e_background._blocks: + block._data[:, :, :] += e0 + + # Get parameters of the background magnetic field + if self.projected_equil: + self._b_background = self.projected_equil.b2 + else: + self._b_background = None + # ==================================================================================== + + # propagator parameters + self._poisson_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] + algo_eta = params["kinetic"]["species1"]["options"]["PushEta"]["algo"] + params_coupling = params["em_fields"]["options"]["EfieldWeights"]["solver"] + + # Initialize propagators/integrators used in splitting substeps + self._kwargs[propagators_markers.PushEta] = { + "algo": algo_eta, + } + + # Only add PushVinEfield if e-field is non-zero, otherwise it is more expensive + if self._has_background_e: + self._kwargs[propagators_markers.PushVinEfield] = { + "e_field": self._e_background, + "kappa": 1.0 / self.epsilon, + } + else: + self._kwargs[propagators_markers.PushVinEfield] = None + + self._kwargs[propagators_coupling.EfieldWeights] = { + "alpha": self.alpha, + "kappa": 1.0 / self.epsilon, + "f0": self._f0, + "solver": params_coupling, + } + + # Only add PushVxB if magnetic field is not zero + self._kwargs[propagators_markers.PushVxB] = None + if self._b_background: + self._kwargs[propagators_markers.PushVxB] = { + "kappa": 1.0 / self.epsilon, + "b2": self._b_background, + } + + # Initialize propagators used in splitting substeps + if not self._baseclass: + self.init_propagators() + + # Scalar variables to be saved during the simulation + self.add_scalar("en_E") + self.add_scalar("en_w") + self.add_scalar("en_tot") - self._tmp[0] = ( - alpha**2 - * vth**2 - / (2 * particles.Np) - * xp.dot( - particles.weights**2, # w_p^2 - particles.sampling_density / self._f0_values[particles.valid_mks], # s_{0,p} / f_{0,p} - ) - ) + # MPI operations needed for scalar variables + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE - self.update_scalar("en_w", self._tmp[0]) - self.update_scalar("en_tot", self._tmp[0] + en_E) + # temporaries + self._tmp = np.empty(1, dtype=float) + self.en_E = 0.0 - def allocate_propagators(self): + def initialize_from_params(self): """Solve initial Poisson equation. :meta private: """ + from struphy.pic.accumulation.particles_to_grid import AccumulatorVector - # initialize fields and particles - super().allocate_propagators() - - if MPI.COMM_WORLD.Get_rank() == 0: - print("\nINITIAL POISSON SOLVE:") - - # use control variate method - particles = self.kinetic_ions.var.particles - particles.update_weights() - - # sanity check - # self.pointer['species1'].show_distribution_function( - # [True] + [False]*5, [xp.linspace(0, 1, 32)]) + # Initialize fields and particles + super().initialize_from_params() - # accumulate charge density + # Accumulate charge density charge_accum = AccumulatorVector( - particles, + self.pointer["species1"], "H1", - Pyccelkernel(accum_kernels.charge_density_0form), + accum_kernels.charge_density_0form, self.mass_ops, self.domain.args_domain, ) - # another sanity check: compute FE coeffs of density - # charge_accum.show_accumulated_spline_field(self.mass_ops) - - alpha = self.kinetic_ions.equation_params.alpha - epsilon = self.kinetic_ions.equation_params.epsilon - - self.initial_poisson.options.rho = charge_accum - self.initial_poisson.options.rho_coeffs = alpha**2 / epsilon - self.initial_poisson.allocate() + charge_accum(self.pointer["species1"].vdim) + + # Instantiate Poisson solver + _phi = self.derham.Vh["0"].zeros() + poisson_solver = propagators_fields.ImplicitDiffusion( + _phi, + sigma_1=0.0, + sigma_2=0.0, + sigma_3=1.0, + rho=self.alpha**2 / self.epsilon * charge_accum.vectors[0], + solver=self._poisson_params, + ) # Solve with dt=1. and compute electric field - if MPI.COMM_WORLD.Get_rank() == 0: + if self.rank_world == 0: print("\nSolving initial Poisson problem...") - self.initial_poisson(1.0) - - phi = self.initial_poisson.variables.phi.spline.vector - self.derham.grad.dot(-phi, out=self.em_fields.e_field.spline.vector) - if MPI.COMM_WORLD.Get_rank() == 0: + poisson_solver(1.0) + self.derham.grad.dot(-_phi, out=self.pointer["e_field"]) + if self.rank_world == 0: print("Done.") - ## default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "maxwellian_1 + maxwellian_2" in line: - new_file += ["background = maxwellian_1\n"] - elif "maxwellian_1pt =" in line: - new_file += ["maxwellian_1pt = maxwellians.Maxwellian3D(n=(0.0, perturbation))\n"] - elif "set_save_data" in line: - new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] - new_file += ["model.kinetic_ions.set_save_data(binning_plots=(binplot,))\n"] - else: - new_file += [line] + def update_scalar_quantities(self): + # 0.5 * e^T * M_1 * e + self.en_E = 0.5 * self.mass_ops.M1.dot_inner(self.pointer["e_field"], self.pointer["e_field"]) + self.update_scalar("en_E", self.en_E) - with open(params_path, "w") as f: - for line in new_file: - f.write(line) + # evaluate f0 + self._f0_values[self.pointer["species1"].valid_mks] = self._f0(*self.pointer["species1"].phasespace_coords.T) + + # alpha^2 * v_th^2 / (2*N) * sum_p s_0 * w_p^2 / f_{0,p} + self._tmp[0] = ( + self.alpha**2 + * self.vth**2 + / (2 * self.pointer["species1"].Np) + * np.dot( + self.pointer["species1"].weights ** 2, # w_p^2 + self.pointer["species1"].sampling_density + / self._f0_values[self.pointer["species1"].valid_mks], # s_{0,p} / f_{0,p} + ) + ) + + self.derham.comm.Allreduce( + self._mpi_in_place, + self._tmp, + op=self._mpi_sum, + ) + + self.update_scalar("en_w", self._tmp[0]) + + # en_tot = en_w + en_e + if not self._baseclass: + self.update_scalar("en_tot", self._tmp[0] + self.en_E) class LinearVlasovMaxwellOneSpecies(LinearVlasovAmpereOneSpecies): @@ -811,82 +919,80 @@ class LinearVlasovMaxwellOneSpecies(LinearVlasovAmpereOneSpecies): :ref:`Model info `: """ - ## species + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class EMFields(FieldSpecies): - def __init__(self): - self.e_field = FEECVariable(space="Hcurl") - self.b_field = FEECVariable(space="Hdiv") - self.phi = FEECVariable(space="H1") - self.init_variables() + dct["em_fields"]["e_field"] = "Hcurl" + dct["em_fields"]["b_field"] = "Hdiv" + dct["kinetic"]["species1"] = "DeltaFParticles6D" + return dct - class KineticIons(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="DeltaFParticles6D") - self.init_variables() + @staticmethod + def bulk_species(): + return "species1" - ## propagators + @staticmethod + def velocity_scale(): + return "light" - class Propagators: - def __init__( - self, - with_B0: bool = True, - with_E0: bool = True, - ): - self.push_eta = propagators_markers.PushEta() - if with_E0: - self.push_vinE = propagators_markers.PushVinEfield() - self.coupling_Eweights = propagators_coupling.EfieldWeights() - if with_B0: - self.push_vxb = propagators_markers.PushVxB() - self.maxwell = propagators_fields.Maxwell() + @staticmethod + def propagators_dct(): + return { + propagators_markers.PushEta: ["species1"], + propagators_markers.PushVinEfield: ["species1"], + propagators_coupling.EfieldWeights: ["e_field", "species1"], + propagators_markers.PushVxB: ["species1"], + propagators_fields.Maxwell: ["e_field", "b_field"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["em_fields"], + option=propagators_fields.ImplicitDiffusion, + dct=dct, + ) + cls.add_option( + species=["kinetic", "species1"], + key="override_eq_params", + option=[False, {"epsilon": -1.0, "alpha": 1.0}], + dct=dct, + ) + return dct - ## abstract methods + def __init__(self, params, comm, clone_config=None): + super().__init__(params=params, comm=comm, clone_config=clone_config, baseclass=True) - def __init__( - self, - with_B0: bool = True, - with_E0: bool = True, - ): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + # propagator parameters + params_maxwell = params["em_fields"]["options"]["Maxwell"]["solver"] - # 1. instantiate all species - self.em_fields = self.EMFields() - self.kinetic_ions = self.KineticIons() + # set keyword arguments for propagators + self._kwargs[propagators_fields.Maxwell] = {"solver": params_maxwell} - # 2. instantiate all propagators - self.propagators = self.Propagators(with_B0=with_B0, with_E0=with_E0) + # Initialize propagators used in splitting substeps + self.init_propagators() - # 3. assign variables to propagators - self.propagators.push_eta.variables.var = self.kinetic_ions.var - if with_E0: - self.propagators.push_vinE.variables.var = self.kinetic_ions.var - self.propagators.coupling_Eweights.variables.e = self.em_fields.e_field - self.propagators.coupling_Eweights.variables.ions = self.kinetic_ions.var - if with_B0: - self.propagators.push_vxb.variables.ions = self.kinetic_ions.var - self.propagators.maxwell.variables.e = self.em_fields.e_field - self.propagators.maxwell.variables.b = self.em_fields.b_field - - # define scalars for update_scalar_quantities - self.add_scalar("en_E") - self.add_scalar("en_B") - self.add_scalar("en_w", compute="from_particles", variable=self.kinetic_ions.var) - self.add_scalar("en_tot") + # magnetic energy + self.add_scalar("en_b") - # initial Poisson (not a propagator used in time stepping) - self.initial_poisson = propagators_fields.Poisson() - self.initial_poisson.variables.phi = self.em_fields.phi + def initialize_from_params(self): + super().initialize_from_params() def update_scalar_quantities(self): super().update_scalar_quantities() # 0.5 * b^T * M_2 * b - b = self.em_fields.b_field.spline.vector - - en_B = 0.5 * self._mass_ops.M2.dot_inner(b, b) - self.update_scalar("en_tot", self.scalar_quantities["en_tot"]["value"][0] + en_B) + en_B = 0.5 * self._mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) + self.update_scalar("en_tot", self._tmp[0] + self.en_E + en_B) class DriftKineticElectrostaticAdiabatic(StruphyModel): @@ -938,159 +1044,164 @@ class DriftKineticElectrostaticAdiabatic(StruphyModel): :ref:`Model info `: """ - ## species + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class EMFields(FieldSpecies): - def __init__(self): - self.phi = FEECVariable(space="H1") - self.init_variables() + dct["em_fields"]["phi"] = "H1" + dct["kinetic"]["ions"] = "Particles5D" + return dct - class KineticIons(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="Particles5D") - self.init_variables() - - ## propagators + @staticmethod + def bulk_species(): + return "ions" - class Propagators: - def __init__(self): - self.gc_poisson = propagators_fields.ImplicitDiffusion() - self.push_gc_bxe = propagators_markers.PushGuidingCenterBxEstar() - self.push_gc_para = propagators_markers.PushGuidingCenterParallel() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.kinetic_ions = self.KineticIons() - - # 2. instantiate all propagators - self.propagators = self.Propagators() - - # 3. assign variables to propagators - self.propagators.gc_poisson.variables.phi = self.em_fields.phi - self.propagators.push_gc_bxe.variables.ions = self.kinetic_ions.var - self.propagators.push_gc_para.variables.ions = self.kinetic_ions.var - - # define scalars for update_scalar_quantities - self.add_scalar("en_phi") - self.add_scalar("en_particles", compute="from_particles", variable=self.kinetic_ions.var) - self.add_scalar("en_tot") - - @property - def bulk_species(self): - return self.kinetic_ions - - @property - def velocity_scale(self): + @staticmethod + def velocity_scale(): return "thermal" - def allocate_helpers(self): - self._tmp3 = xp.empty(1, dtype=float) - self._e_field = self.derham.Vh["1"].zeros() + @staticmethod + def propagators_dct(): + return { + propagators_fields.ImplicitDiffusion: ["phi"], + propagators_markers.PushGuidingCenterBxEstar: ["ions"], + propagators_markers.PushGuidingCenterParallel: ["ions"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + # add special options + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["kinetic", "ions"], + key="override_eq_params", + option=[False, {"epsilon": 1.0}], + dct=dct, + ) + return dct - assert self.kinetic_ions.charge_number > 0, "Model written only for positive ions." + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) - def allocate_propagators(self): - """Solve initial Poisson equation. + from mpi4py.MPI import IN_PLACE, SUM - :meta private: - """ + from struphy.feec.projectors import L2Projector + from struphy.pic.accumulation.particles_to_grid import AccumulatorVector - # initialize fields and particles - super().allocate_propagators() + # prelim + solver_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] + ions_params = params["kinetic"]["ions"] - # Poisson right-hand side - particles = self.kinetic_ions.var.particles - Z = self.kinetic_ions.charge_number - epsilon = self.kinetic_ions.equation_params.epsilon + Z = ions_params["phys_params"]["Z"] + assert Z > 0 # must be positive ions + # Poisson right-hand side charge_accum = AccumulatorVector( - particles, + self.pointer["ions"], "H1", - Pyccelkernel(accum_kernels_gc.gc_density_0form), + accum_kernels_gc.gc_density_0form, self.mass_ops, self.domain.args_domain, ) - rho = charge_accum + rho = (charge_accum, self.pointer["ions"]) # get neutralizing background density - if not particles.control_variate: + if not self.pointer["ions"].control_variate: l2_proj = L2Projector("H1", self.mass_ops) - f0e = Z * particles.f0 + f0e = Z * self.pointer["ions"].f0 assert isinstance(f0e, KineticBackground) - rho_eh = FEECVariable(space="H1") - rho_eh.allocate(derham=self.derham, domain=self.domain) - rho_eh.spline.vector = l2_proj.get_dofs(f0e.n) + rho_eh = l2_proj.get_dofs(f0e.n) rho = [rho] rho += [rho_eh] - self.propagators.gc_poisson.options.sigma_1 = 1.0 / epsilon**2 / Z - self.propagators.gc_poisson.options.sigma_2 = 0.0 - self.propagators.gc_poisson.options.sigma_3 = 1.0 / epsilon - self.propagators.gc_poisson.options.stab_mat = "M0ad" - self.propagators.gc_poisson.options.diffusion_mat = "M1perp" - self.propagators.gc_poisson.options.rho = rho - self.propagators.gc_poisson.allocate() + # Get coupling strength + if ions_params["options"]["override_eq_params"]: + self.epsilon = ions_params["options"]["override_eq_params"]["epsilon"] + print( + f"\n!!! Override equation parameters: {self.epsilon = }.", + ) + else: + self.epsilon = self.equation_params["ions"]["epsilon"] + + # set keyword arguments for propagators + self._kwargs[propagators_fields.ImplicitDiffusion] = { + "sigma_1": 1.0 / self.epsilon**2 / Z, # set to zero for Landau damping test + "sigma_2": 0.0, + "sigma_3": 1.0 / self.epsilon, + "stab_mat": "M0ad", + "diffusion_mat": "M1gyro", + "rho": rho, + "solver": solver_params, + } + + self._kwargs[propagators_markers.PushGuidingCenterBxEstar] = { + "phi": self.pointer["phi"], + "evaluate_e_field": True, + "epsilon": self.epsilon / Z, + "algo": ions_params["options"]["PushGuidingCenterBxEstar"]["algo"], + } + + self._kwargs[propagators_markers.PushGuidingCenterParallel] = { + "phi": self.pointer["phi"], + "evaluate_e_field": True, + "epsilon": self.epsilon / Z, + "algo": ions_params["options"]["PushGuidingCenterParallel"]["algo"], + } + + # Initialize propagators used in splitting substeps + self.init_propagators() + + # scalar quantities + self.add_scalar("en_phi") + self.add_scalar("en_particles") + self.add_scalar("en_tot") - def update_scalar_quantities(self): - phi = self.em_fields.phi.spline.vector - particles = self.kinetic_ions.var.particles - epsilon = self.kinetic_ions.equation_params.epsilon + # MPI operations needed for scalar variables + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE + self._tmp3 = np.empty(1, dtype=float) + self._e_field = self.derham.Vh["1"].zeros() + def update_scalar_quantities(self): # energy from polarization - e1 = self.derham.grad.dot(-phi, out=self._e_field) + e1 = self.derham.grad.dot(-self.pointer["phi"], out=self._e_field) en_phi1 = 0.5 * self.mass_ops.M1gyro.dot_inner(e1, e1) # energy from adiabatic electrons - en_phi = 0.5 / epsilon**2 * self.mass_ops.M0ad.dot_inner(phi, phi) + en_phi = 0.5 / self.epsilon**2 * self.mass_ops.M0ad.dot_inner(self.pointer["phi"], self.pointer["phi"]) # for Landau damping test # en_phi = 0. # mu_p * |B0(eta_p)| - particles.save_magnetic_background_energy() + self.pointer["ions"].save_magnetic_background_energy() # 1/N sum_p (w_p v_p^2/2 + mu_p |B0|_p) self._tmp3[0] = ( 1 - / particles.Np - * xp.sum( - particles.weights * particles.velocities[:, 0] ** 2 / 2.0 + particles.markers_wo_holes_and_ghost[:, 8], + / self.pointer["ions"].Np + * np.sum( + self.pointer["ions"].weights * self.pointer["ions"].velocities[:, 0] ** 2 / 2.0 + + self.pointer["ions"].markers_wo_holes_and_ghost[:, 8], ) ) + if self.comm_world is not None: + self.comm_world.Allreduce( + self._mpi_in_place, + self._tmp3, + op=self._mpi_sum, + ) + self.update_scalar("en_phi", en_phi + en_phi1) self.update_scalar("en_particles", self._tmp3[0]) self.update_scalar("en_tot", en_phi + en_phi1 + self._tmp3[0]) - - ## default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "BaseUnits(" in line: - new_file += ["base_units = BaseUnits(kBT=1.0)\n"] - elif "push_gc_bxe.Options" in line: - new_file += [ - "model.propagators.push_gc_bxe.options = model.propagators.push_gc_bxe.Options(phi=model.em_fields.phi)\n", - ] - elif "push_gc_para.Options" in line: - new_file += [ - "model.propagators.push_gc_para.options = model.propagators.push_gc_para.Options(phi=model.em_fields.phi)\n", - ] - elif "set_save_data" in line: - new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] - new_file += ["model.kinetic_ions.set_save_data(binning_plots=(binplot,))\n"] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) diff --git a/src/struphy/models/species.py b/src/struphy/models/species.py index 8a9f11008..0c1a2311e 100644 --- a/src/struphy/models/species.py +++ b/src/struphy/models/species.py @@ -1,10 +1,15 @@ import warnings from abc import ABCMeta, abstractmethod +from copy import deepcopy +from dataclasses import dataclass +from typing import Callable -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI +from struphy.fields_background.base import FluidEquilibrium from struphy.io.options import Units +from struphy.kinetic_background.base import KineticBackground from struphy.models.variables import Variable from struphy.physics.physics import ConstantsOfNature from struphy.pic.utilities import ( @@ -82,7 +87,7 @@ def __init__( con = ConstantsOfNature() # relevant frequencies - om_p = xp.sqrt(units.n * (Z * con.e) ** 2 / (con.eps0 * A * con.mH)) + om_p = np.sqrt(units.n * (Z * con.e) ** 2 / (con.eps0 * A * con.mH)) om_c = Z * con.e * units.B / (A * con.mH) # compute equation parameters @@ -90,22 +95,19 @@ def __init__( self.alpha = om_p / om_c else: self.alpha = alpha - if MPI.COMM_WORLD.Get_rank() == 0: - warnings.warn(f"Override equation parameter {self.alpha =}") + warnings.warn(f"Override equation parameter {self.alpha = }") if epsilon is None: self.epsilon = 1.0 / (om_c * units.t) else: self.epsilon = epsilon - if MPI.COMM_WORLD.Get_rank() == 0: - warnings.warn(f"Override equation parameter {self.epsilon =}") + warnings.warn(f"Override equation parameter {self.epsilon = }") if kappa is None: self.kappa = om_p * units.t else: self.kappa = kappa - if MPI.COMM_WORLD.Get_rank() == 0: - warnings.warn(f"Override equation parameter {self.kappa =}") + warnings.warn(f"Override equation parameter {self.kappa = }") if verbose and MPI.COMM_WORLD.Get_rank() == 0: print(f"\nSet normalization parameters for species {species.__class__.__name__}:") @@ -140,6 +142,8 @@ class FieldSpecies(Species): class FluidSpecies(Species): """Single fluid species in 3d configuration space.""" + pass + class ParticleSpecies(Species): """Single kinetic species in 3d + vdim phase space.""" @@ -209,3 +213,5 @@ def set_save_data( class DiagnosticSpecies(Species): """Diagnostic species (fields) without mass and charge.""" + + pass diff --git a/src/struphy/models/tests/test_models.py b/src/struphy/models/tests/test_models.py index b9802abdc..24aa086f1 100644 --- a/src/struphy/models/tests/test_models.py +++ b/src/struphy/models/tests/test_models.py @@ -1,9 +1,8 @@ -import inspect import os from types import ModuleType import pytest -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy import main from struphy.io.options import EnvironmentOptions @@ -14,33 +13,43 @@ rank = MPI.COMM_WORLD.Get_rank() # available models -toy_models = [] -for name, obj in inspect.getmembers(toy): - if inspect.isclass(obj) and "models.toy" in obj.__module__: - toy_models += [name] +toy_models = [ + "Maxwell", + "Vlasov", + "GuidingCenter", + "PressureLessSPH", +] +# for name, obj in inspect.getmembers(toy): +# if inspect.isclass(obj) and "models.toy" in obj.__module__: +# toy_models += [name] if rank == 0: - print(f"\n{toy_models =}") - -fluid_models = [] -for name, obj in inspect.getmembers(fluid): - if inspect.isclass(obj) and "models.fluid" in obj.__module__: - fluid_models += [name] + print(f"\n{toy_models = }") + +fluid_models = [ + "LinearMHD", + "EulerSPH", +] +# for name, obj in inspect.getmembers(fluid): +# if inspect.isclass(obj) and "models.fluid" in obj.__module__: +# fluid_models += [name] if rank == 0: - print(f"\n{fluid_models =}") - -kinetic_models = [] -for name, obj in inspect.getmembers(kinetic): - if inspect.isclass(obj) and "models.kinetic" in obj.__module__: - kinetic_models += [name] + print(f"\n{fluid_models = }") + +kinetic_models = [ + "VlasovAmpereOneSpecies", +] +# for name, obj in inspect.getmembers(kinetic): +# if inspect.isclass(obj) and "models.kinetic" in obj.__module__: +# kinetic_models += [name] if rank == 0: - print(f"\n{kinetic_models =}") + print(f"\n{kinetic_models = }") hybrid_models = [] -for name, obj in inspect.getmembers(hybrid): - if inspect.isclass(obj) and "models.hybrid" in obj.__module__: - hybrid_models += [name] +# for name, obj in inspect.getmembers(hybrid): +# if inspect.isclass(obj) and "models.hybrid" in obj.__module__: +# hybrid_models += [name] if rank == 0: - print(f"\n{hybrid_models =}") + print(f"\n{hybrid_models = }") # folder for test simulations @@ -52,17 +61,12 @@ def call_test(model_name: str, module: ModuleType = None, verbose=True): if rank == 0: print(f"\n*** Testing '{model_name}':") - # exceptions - if model_name == "TwoFluidQuasiNeutralToy" and MPI.COMM_WORLD.Get_size() > 1: - print(f"WARNING: Model {model_name} cannot be tested for {MPI.COMM_WORLD.Get_size() =}") - return - if module is None: submods = [toy, fluid, kinetic, hybrid] for submod in submods: try: model = getattr(submod, model_name)() - except AttributeError: + except: continue else: diff --git a/src/struphy/models/tests/test_verif_LinearMHD.py b/src/struphy/models/tests/test_verif_LinearMHD.py index 475b11aef..8ff0d28b8 100644 --- a/src/struphy/models/tests/test_verif_LinearMHD.py +++ b/src/struphy/models/tests/test_verif_LinearMHD.py @@ -1,8 +1,8 @@ import os -import cunumpy as xp +import numpy as np import pytest -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy import main from struphy.diagnostics.diagn_tools import power_spectrum_2d @@ -113,10 +113,10 @@ def test_slab_waves_1d(algo: str, do_plot: bool = False): ) # assert - vA = xp.sqrt(Bsquare / n0) - v_alfven = vA * B0z / xp.sqrt(Bsquare) - print(f"{v_alfven =}") - assert xp.abs(coeffs[0][0] - v_alfven) < 0.07 + vA = np.sqrt(Bsquare / n0) + v_alfven = vA * B0z / np.sqrt(Bsquare) + print(f"{v_alfven = }") + assert np.abs(coeffs[0][0] - v_alfven) < 0.07 # second fft p_of_t = simdata.spline_values["mhd"]["pressure_log"] @@ -139,15 +139,15 @@ def test_slab_waves_1d(algo: str, do_plot: bool = False): # assert gamma = 5 / 3 - cS = xp.sqrt(gamma * p0 / n0) + cS = np.sqrt(gamma * p0 / n0) delta = (4 * B0z**2 * cS**2 * vA**2) / ((cS**2 + vA**2) ** 2 * Bsquare) - v_slow = xp.sqrt(1 / 2 * (cS**2 + vA**2) * (1 - xp.sqrt(1 - delta))) - v_fast = xp.sqrt(1 / 2 * (cS**2 + vA**2) * (1 + xp.sqrt(1 - delta))) - print(f"{v_slow =}") - print(f"{v_fast =}") - assert xp.abs(coeffs[0][0] - v_slow) < 0.05 - assert xp.abs(coeffs[1][0] - v_fast) < 0.19 + v_slow = np.sqrt(1 / 2 * (cS**2 + vA**2) * (1 - np.sqrt(1 - delta))) + v_fast = np.sqrt(1 / 2 * (cS**2 + vA**2) * (1 + np.sqrt(1 - delta))) + print(f"{v_slow = }") + print(f"{v_fast = }") + assert np.abs(coeffs[0][0] - v_slow) < 0.05 + assert np.abs(coeffs[1][0] - v_fast) < 0.19 if __name__ == "__main__": diff --git a/src/struphy/models/tests/test_verif_Maxwell.py b/src/struphy/models/tests/test_verif_Maxwell.py index ccea67c18..f191a93c4 100644 --- a/src/struphy/models/tests/test_verif_Maxwell.py +++ b/src/struphy/models/tests/test_verif_Maxwell.py @@ -1,9 +1,9 @@ import os -import cunumpy as xp +import numpy as np import pytest from matplotlib import pyplot as plt -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from scipy.special import jv, yn from struphy import main @@ -13,7 +13,6 @@ from struphy.initial import perturbations from struphy.io.options import BaseUnits, DerhamOptions, EnvironmentOptions, FieldsBackground, Time from struphy.kinetic_background import maxwellians -from struphy.models.toy import Maxwell from struphy.topology import grids test_folder = os.path.join(os.getcwd(), "struphy_verification_tests") @@ -22,6 +21,11 @@ @pytest.mark.mpi(min_size=3) @pytest.mark.parametrize("algo", ["implicit", "explicit"]) def test_light_wave_1d(algo: str, do_plot: bool = False): + # import model, set verbosity + from struphy.models.toy import Maxwell + + verbose = True + # environment options out_folders = os.path.join(test_folder, "Maxwell") env = EnvironmentOptions(out_folders=out_folders, sim_folder="light_wave_1d") @@ -54,9 +58,7 @@ def test_light_wave_1d(algo: str, do_plot: bool = False): model.em_fields.e_field.add_perturbation(perturbations.Noise(amp=0.1, comp=0, seed=123)) model.em_fields.e_field.add_perturbation(perturbations.Noise(amp=0.1, comp=1, seed=123)) - # start run - verbose = True - + # # start run main.run( model, params_path=None, @@ -97,7 +99,7 @@ def test_light_wave_1d(algo: str, do_plot: bool = False): # assert c_light_speed = 1.0 - assert xp.abs(coeffs[0][0] - c_light_speed) < 0.02 + assert np.abs(coeffs[0][0] - c_light_speed) < 0.02 @pytest.mark.mpi(min_size=4) @@ -189,32 +191,32 @@ def test_coaxial(do_plot: bool = False): def B_z(X, Y, Z, m, t): """Magnetic field in z direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return (jv(m, r) - 0.28 * yn(m, r)) * xp.cos(m * theta - t) + theta = np.arctan2(Y, X) + return (jv(m, r) - 0.28 * yn(m, r)) * np.cos(m * theta - t) def E_r(X, Y, Z, m, t): """Electrical field in radial direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return -m / r * (jv(m, r) - 0.28 * yn(m, r)) * xp.cos(m * theta - t) + theta = np.arctan2(Y, X) + return -m / r * (jv(m, r) - 0.28 * yn(m, r)) * np.cos(m * theta - t) def E_theta(X, Y, Z, m, t): """Electrical field in azimuthal direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return ((m / r * jv(m, r) - jv(m + 1, r)) - 0.28 * (m / r * yn(m, r) - yn(m + 1, r))) * xp.sin( - m * theta - t, + theta = np.arctan2(Y, X) + return ((m / r * jv(m, r) - jv(m + 1, r)) - 0.28 * (m / r * yn(m, r) - yn(m + 1, r))) * np.sin( + m * theta - t ) def to_E_r(X, Y, E_x, E_y): r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return xp.cos(theta) * E_x + xp.sin(theta) * E_y + theta = np.arctan2(Y, X) + return np.cos(theta) * E_x + np.sin(theta) * E_y def to_E_theta(X, Y, E_x, E_y): r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return -xp.sin(theta) * E_x + xp.cos(theta) * E_y + theta = np.arctan2(Y, X) + return -np.sin(theta) * E_x + np.cos(theta) * E_y # plot if do_plot: @@ -222,13 +224,7 @@ def to_E_theta(X, Y, E_x, E_y): vmax = E_theta(X, Y, grids_phy[0], modes, 0).max() fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) plot_exac = ax1.contourf( - X, - Y, - E_theta(X, Y, grids_phy[0], modes, t_grid[-1]), - cmap="plasma", - levels=100, - vmin=vmin, - vmax=vmax, + X, Y, E_theta(X, Y, grids_phy[0], modes, t_grid[-1]), cmap="plasma", levels=100, vmin=vmin, vmax=vmax ) ax2.contourf( X, @@ -253,21 +249,21 @@ def to_E_theta(X, Y, E_x, E_y): Bz_tend = b_field_phy[t_grid[-1]][2][:, :, 0] Bz_exact = B_z(X, Y, grids_phy[0], modes, t_grid[-1]) - error_Er = xp.max(xp.abs((to_E_r(X, Y, Ex_tend, Ey_tend) - Er_exact))) - error_Etheta = xp.max(xp.abs((to_E_theta(X, Y, Ex_tend, Ey_tend) - Etheta_exact))) - error_Bz = xp.max(xp.abs((Bz_tend - Bz_exact))) + error_Er = np.max(np.abs((to_E_r(X, Y, Ex_tend, Ey_tend) - Er_exact))) + error_Etheta = np.max(np.abs((to_E_theta(X, Y, Ex_tend, Ey_tend) - Etheta_exact))) + error_Bz = np.max(np.abs((Bz_tend - Bz_exact))) - rel_err_Er = error_Er / xp.max(xp.abs(Er_exact)) - rel_err_Etheta = error_Etheta / xp.max(xp.abs(Etheta_exact)) - rel_err_Bz = error_Bz / xp.max(xp.abs(Bz_exact)) + rel_err_Er = error_Er / np.max(np.abs(Er_exact)) + rel_err_Etheta = error_Etheta / np.max(np.abs(Etheta_exact)) + rel_err_Bz = error_Bz / np.max(np.abs(Bz_exact)) print("") - assert rel_err_Bz < 0.0021, f"Assertion for magnetic field Maxwell failed: {rel_err_Bz =}" - print(f"Assertion for magnetic field Maxwell passed ({rel_err_Bz =}).") - assert rel_err_Etheta < 0.0021, f"Assertion for electric (E_theta) field Maxwell failed: {rel_err_Etheta =}" - print(f"Assertion for electric field Maxwell passed ({rel_err_Etheta =}).") - assert rel_err_Er < 0.0021, f"Assertion for electric (E_r) field Maxwell failed: {rel_err_Er =}" - print(f"Assertion for electric field Maxwell passed ({rel_err_Er =}).") + assert rel_err_Bz < 0.0021, f"Assertion for magnetic field Maxwell failed: {rel_err_Bz = }" + print(f"Assertion for magnetic field Maxwell passed ({rel_err_Bz = }).") + assert rel_err_Etheta < 0.0021, f"Assertion for electric (E_theta) field Maxwell failed: {rel_err_Etheta = }" + print(f"Assertion for electric field Maxwell passed ({rel_err_Etheta = }).") + assert rel_err_Er < 0.0021, f"Assertion for electric (E_r) field Maxwell failed: {rel_err_Er = }" + print(f"Assertion for electric field Maxwell passed ({rel_err_Er = }).") if __name__ == "__main__": diff --git a/src/struphy/models/tests/verification.py b/src/struphy/models/tests/verification.py index d28fc3d6e..c88e2ff32 100644 --- a/src/struphy/models/tests/verification.py +++ b/src/struphy/models/tests/verification.py @@ -2,12 +2,12 @@ import pickle from pathlib import Path -import cunumpy as xp import h5py +import numpy as np import yaml from matplotlib import pyplot as plt from matplotlib.ticker import FormatStrFormatter -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from scipy.special import jv, yn import struphy @@ -40,7 +40,7 @@ def E_exact(t): r = 0.3677 omega = 1.4156 phi = 0.5362 - return 2 * eps**2 * xp.pi / k**2 * r**2 * xp.exp(2 * gamma * t) * xp.cos(omega * t - phi) ** 2 + return 2 * eps**2 * np.pi / k**2 * r**2 * np.exp(2 * gamma * t) * np.cos(omega * t - phi) ** 2 # get parameters with open(os.path.join(path_out, "parameters.yml")) as f: @@ -56,24 +56,24 @@ def E_exact(t): with h5py.File(os.path.join(pa_data, "data_proc0.hdf5"), "r") as f: time = f["time"]["value"][()] E = f["scalar"]["en_E"][()] - logE = xp.log10(E) + logE = np.log10(E) # find where time derivative of E is zero - dEdt = (xp.roll(logE, -1) - xp.roll(logE, 1))[1:-1] / (2.0 * dt) - zeros = dEdt * xp.roll(dEdt, -1) < 0.0 - maxima_inds = xp.logical_and(zeros, dEdt > 0.0) + dEdt = (np.roll(logE, -1) - np.roll(logE, 1))[1:-1] / (2.0 * dt) + zeros = dEdt * np.roll(dEdt, -1) < 0.0 + maxima_inds = np.logical_and(zeros, dEdt > 0.0) maxima = logE[1:-1][maxima_inds] t_maxima = time[1:-1][maxima_inds] # linear fit - linfit = xp.polyfit(t_maxima[:5], maxima[:5], 1) + linfit = np.polyfit(t_maxima[:5], maxima[:5], 1) gamma_num = linfit[0] # plot if show_plots and rank == 0: plt.figure(figsize=(18, 12)) plt.plot(time, logE, label="numerical") - plt.plot(time, xp.log10(E_exact(time)), label="exact") + plt.plot(time, np.log10(E_exact(time)), label="exact") plt.legend() plt.title(f"{dt=}, {algo=}, {Nel=}, {p=}, {ppc=}") plt.xlabel("time [m/c]") @@ -84,9 +84,9 @@ def E_exact(t): plt.show() # assert - rel_error = xp.abs(gamma_num - gamma) / xp.abs(gamma) - assert rel_error < 0.25, f"{rank =}: Assertion for weak Landau damping failed: {gamma_num =} vs. {gamma =}." - print(f"{rank =}: Assertion for weak Landau damping passed ({rel_error =}).") + rel_error = np.abs(gamma_num - gamma) / np.abs(gamma) + assert rel_error < 0.25, f"{rank = }: Assertion for weak Landau damping failed: {gamma_num = } vs. {gamma = }." + print(f"{rank = }: Assertion for weak Landau damping passed ({rel_error = }).") def LinearVlasovAmpereOneSpecies_weakLandau( @@ -115,7 +115,7 @@ def E_exact(t): r = 0.3677 omega = 1.4156 phi = 0.5362 - return 2 * eps**2 * xp.pi / k**2 * r**2 * xp.exp(2 * gamma * t) * xp.cos(omega * t - phi) ** 2 + return 2 * eps**2 * np.pi / k**2 * r**2 * np.exp(2 * gamma * t) * np.cos(omega * t - phi) ** 2 # get parameters with open(os.path.join(path_out, "parameters.yml")) as f: @@ -131,24 +131,24 @@ def E_exact(t): with h5py.File(os.path.join(pa_data, "data_proc0.hdf5"), "r") as f: time = f["time"]["value"][()] E = f["scalar"]["en_E"][()] - logE = xp.log10(E) + logE = np.log10(E) # find where time derivative of E is zero - dEdt = (xp.roll(logE, -1) - xp.roll(logE, 1))[1:-1] / (2.0 * dt) - zeros = dEdt * xp.roll(dEdt, -1) < 0.0 - maxima_inds = xp.logical_and(zeros, dEdt > 0.0) + dEdt = (np.roll(logE, -1) - np.roll(logE, 1))[1:-1] / (2.0 * dt) + zeros = dEdt * np.roll(dEdt, -1) < 0.0 + maxima_inds = np.logical_and(zeros, dEdt > 0.0) maxima = logE[1:-1][maxima_inds] t_maxima = time[1:-1][maxima_inds] # linear fit - linfit = xp.polyfit(t_maxima[:5], maxima[:5], 1) + linfit = np.polyfit(t_maxima[:5], maxima[:5], 1) gamma_num = linfit[0] # plot if show_plots and rank == 0: plt.figure(figsize=(18, 12)) plt.plot(time, logE, label="numerical") - plt.plot(time, xp.log10(E_exact(time)), label="exact") + plt.plot(time, np.log10(E_exact(time)), label="exact") plt.legend() plt.title(f"{dt=}, {algo=}, {Nel=}, {p=}, {ppc=}") plt.xlabel("time [m/c]") @@ -160,9 +160,9 @@ def E_exact(t): # plt.show() # assert - rel_error = xp.abs(gamma_num - gamma) / xp.abs(gamma) - assert rel_error < 0.25, f"{rank =}: Assertion for weak Landau damping failed: {gamma_num =} vs. {gamma =}." - print(f"{rank =}: Assertion for weak Landau damping passed ({rel_error =}).") + rel_error = np.abs(gamma_num - gamma) / np.abs(gamma) + assert rel_error < 0.25, f"{rank = }: Assertion for weak Landau damping failed: {gamma_num = } vs. {gamma = }." + print(f"{rank = }: Assertion for weak Landau damping passed ({rel_error = }).") def IsothermalEulerSPH_soundwave( @@ -190,8 +190,8 @@ def IsothermalEulerSPH_soundwave( MPI.COMM_WORLD.Barrier() path_n_sph = os.path.join(path_pp, "kinetic_data/euler_fluid/n_sph/view_0/") - ee1, ee2, ee3 = xp.load(os.path.join(path_n_sph, "grid_n_sph.npy")) - n_sph = xp.load(os.path.join(path_n_sph, "n_sph.npy")) + ee1, ee2, ee3 = np.load(os.path.join(path_n_sph, "grid_n_sph.npy")) + n_sph = np.load(os.path.join(path_n_sph, "n_sph.npy")) # print(f'{ee1.shape = }, {n_sph.shape = }') if show_plots and rank == 0: @@ -207,7 +207,7 @@ def IsothermalEulerSPH_soundwave( plot_ct = 0 for i in range(0, Nt + 1): if i % interval == 0: - print(f"{i =}") + print(f"{i = }") plot_ct += 1 ax = plt.gca() @@ -218,21 +218,21 @@ def IsothermalEulerSPH_soundwave( plt.plot(x.squeeze(), n_sph[i, :, 0, 0], style, label=f"time={i * dt:4.2f}") plt.xlim(0, 2.5) plt.legend() - ax.set_xticks(xp.linspace(0, 2.5, nx + 1)) + ax.set_xticks(np.linspace(0, 2.5, nx + 1)) ax.xaxis.set_major_formatter(FormatStrFormatter("%.2f")) plt.grid(c="k") plt.xlabel("x") plt.ylabel(r"$\rho$") - plt.title(f"standing sound wave ($c_s = 1$) for {nx =} and {ppb =}") + plt.title(f"standing sound wave ($c_s = 1$) for {nx = } and {ppb = }") if plot_ct == 11: break plt.show() # assert - error = xp.max(xp.abs(n_sph[0] - n_sph[-1])) - print(f"{rank =}: Assertion for SPH sound wave passed ({error =}).") + error = np.max(np.abs(n_sph[0] - n_sph[-1])) + print(f"{rank = }: Assertion for SPH sound wave passed ({error = }).") assert error < 1.3e-3 @@ -262,30 +262,30 @@ def Maxwell_coaxial( def B_z(X, Y, Z, m, t): """Magnetic field in z direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return (jv(m, r) - 0.28 * yn(m, r)) * xp.cos(m * theta - t) + theta = np.arctan2(Y, X) + return (jv(m, r) - 0.28 * yn(m, r)) * np.cos(m * theta - t) def E_r(X, Y, Z, m, t): """Electrical field in radial direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return -m / r * (jv(m, r) - 0.28 * yn(m, r)) * xp.cos(m * theta - t) + theta = np.arctan2(Y, X) + return -m / r * (jv(m, r) - 0.28 * yn(m, r)) * np.cos(m * theta - t) def E_theta(X, Y, Z, m, t): """Electrical field in azimuthal direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return ((m / r * jv(m, r) - jv(m + 1, r)) - 0.28 * (m / r * yn(m, r) - yn(m + 1, r))) * xp.sin(m * theta - t) + theta = np.arctan2(Y, X) + return ((m / r * jv(m, r) - jv(m + 1, r)) - 0.28 * (m / r * yn(m, r) - yn(m + 1, r))) * np.sin(m * theta - t) def to_E_r(X, Y, E_x, E_y): r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return xp.cos(theta) * E_x + xp.sin(theta) * E_y + theta = np.arctan2(Y, X) + return np.cos(theta) * E_x + np.sin(theta) * E_y def to_E_theta(X, Y, E_x, E_y): r = (X**2 + Y**2) ** 0.5 - theta = xp.arctan2(Y, X) - return -xp.sin(theta) * E_x + xp.cos(theta) * E_y + theta = np.arctan2(Y, X) + return -np.sin(theta) * E_x + np.cos(theta) * E_y # get parameters with open(os.path.join(path_out, "parameters.yml")) as f: @@ -297,7 +297,7 @@ def to_E_theta(X, Y, E_x, E_y): pproc_path = os.path.join(path_out, "post_processing/") em_fields_path = os.path.join(pproc_path, "fields_data/em_fields/") - t_grid = xp.load(os.path.join(pproc_path, "t_grid.npy")) + t_grid = np.load(os.path.join(pproc_path, "t_grid.npy")) grids_phy = pickle.loads(Path(os.path.join(pproc_path, "fields_data/grids_phy.bin")).read_bytes()) b_field_phy = pickle.loads(Path(os.path.join(em_fields_path, "b_field_phy.bin")).read_bytes()) e_field_phy = pickle.loads(Path(os.path.join(em_fields_path, "e_field_phy.bin")).read_bytes()) @@ -311,13 +311,7 @@ def to_E_theta(X, Y, E_x, E_y): vmax = E_theta(X, Y, grids_phy[0], modes, 0).max() fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) plot_exac = ax1.contourf( - X, - Y, - E_theta(X, Y, grids_phy[0], modes, t_grid[-1]), - cmap="plasma", - levels=100, - vmin=vmin, - vmax=vmax, + X, Y, E_theta(X, Y, grids_phy[0], modes, t_grid[-1]), cmap="plasma", levels=100, vmin=vmin, vmax=vmax ) ax2.contourf( X, @@ -342,26 +336,26 @@ def to_E_theta(X, Y, E_x, E_y): Bz_tend = b_field_phy[t_grid[-1]][2][:, :, 0] Bz_exact = B_z(X, Y, grids_phy[0], modes, t_grid[-1]) - error_Er = xp.max(xp.abs((to_E_r(X, Y, Ex_tend, Ey_tend) - Er_exact))) - error_Etheta = xp.max(xp.abs((to_E_theta(X, Y, Ex_tend, Ey_tend) - Etheta_exact))) - error_Bz = xp.max(xp.abs((Bz_tend - Bz_exact))) + error_Er = np.max(np.abs((to_E_r(X, Y, Ex_tend, Ey_tend) - Er_exact))) + error_Etheta = np.max(np.abs((to_E_theta(X, Y, Ex_tend, Ey_tend) - Etheta_exact))) + error_Bz = np.max(np.abs((Bz_tend - Bz_exact))) - rel_err_Er = error_Er / xp.max(xp.abs(Er_exact)) - rel_err_Etheta = error_Etheta / xp.max(xp.abs(Etheta_exact)) - rel_err_Bz = error_Bz / xp.max(xp.abs(Bz_exact)) + rel_err_Er = error_Er / np.max(np.abs(Er_exact)) + rel_err_Etheta = error_Etheta / np.max(np.abs(Etheta_exact)) + rel_err_Bz = error_Bz / np.max(np.abs(Bz_exact)) - print(f"{rel_err_Er =}") - print(f"{rel_err_Etheta =}") - print(f"{rel_err_Bz =}") + print(f"{rel_err_Er = }") + print(f"{rel_err_Etheta = }") + print(f"{rel_err_Bz = }") - assert rel_err_Bz < 0.0021, f"{rank =}: Assertion for magnetic field Maxwell failed: {rel_err_Bz =}" - print(f"{rank =}: Assertion for magnetic field Maxwell passed ({rel_err_Bz =}).") + assert rel_err_Bz < 0.0021, f"{rank = }: Assertion for magnetic field Maxwell failed: {rel_err_Bz = }" + print(f"{rank = }: Assertion for magnetic field Maxwell passed ({rel_err_Bz = }).") assert rel_err_Etheta < 0.0021, ( - f"{rank =}: Assertion for electric (E_theta) field Maxwell failed: {rel_err_Etheta =}" + f"{rank = }: Assertion for electric (E_theta) field Maxwell failed: {rel_err_Etheta = }" ) - print(f"{rank =}: Assertion for electric field Maxwell passed ({rel_err_Etheta =}).") - assert rel_err_Er < 0.0021, f"{rank =}: Assertion for electric (E_r) field Maxwell failed: {rel_err_Er =}" - print(f"{rank =}: Assertion for electric field Maxwell passed ({rel_err_Er =}).") + print(f"{rank = }: Assertion for electric field Maxwell passed ({rel_err_Etheta = }).") + assert rel_err_Er < 0.0021, f"{rank = }: Assertion for electric (E_r) field Maxwell failed: {rel_err_Er = }" + print(f"{rank = }: Assertion for electric field Maxwell passed ({rel_err_Er = }).") if __name__ == "__main__": diff --git a/src/struphy/models/toy.py b/src/struphy/models/toy.py index fd36b5d5f..6e0ad8879 100644 --- a/src/struphy/models/toy.py +++ b/src/struphy/models/toy.py @@ -1,12 +1,13 @@ -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +from dataclasses import dataclass + +import numpy as np +from mpi4py import MPI -from struphy.feec.projectors import L2Projector -from struphy.feec.variational_utilities import InternalEnergyEvaluator from struphy.models.base import StruphyModel from struphy.models.species import FieldSpecies, FluidSpecies, ParticleSpecies from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.propagators import propagators_coupling, propagators_fields, propagators_markers +from struphy.propagators.base import Propagator rank = MPI.COMM_WORLD.Get_rank() @@ -14,19 +15,33 @@ class Maxwell(StruphyModel): r"""Maxwell's equations in vacuum. - :ref:`normalization`: + :ref:`Equations `: .. math:: - \hat E = c \hat B\,. + &\frac{\partial \mathbf E}{\partial t} - \nabla\times\mathbf B = 0\,, - :ref:`Equations `: + &\frac{\partial \mathbf B}{\partial t} + \nabla\times\mathbf E = 0\,. + + Unknowns: .. math:: - &\frac{\partial \mathbf E}{\partial t} - \nabla\times\mathbf B = 0\,, + & \mathbf E(t, \mathbf{x}) \ \ldots \ \textrm{electric field} - &\frac{\partial \mathbf B}{\partial t} + \nabla\times\mathbf E = 0\,. + & \mathbf B(t, \mathbf{x}) \ \ldots \ \textrm{magnetic field} + + Parameters: + + .. math:: + + & \textrm{None} + + :ref:`normalization`: + + .. math:: + + \hat E = c \hat B\,. :ref:`propagators` (called in sequence): @@ -81,12 +96,10 @@ def allocate_helpers(self): def update_scalar_quantities(self): en_E = 0.5 * self.mass_ops.M1.dot_inner( - self.em_fields.e_field.spline.vector, - self.em_fields.e_field.spline.vector, + self.em_fields.e_field.spline.vector, self.em_fields.e_field.spline.vector ) en_B = 0.5 * self.mass_ops.M2.dot_inner( - self.em_fields.b_field.spline.vector, - self.em_fields.b_field.spline.vector, + self.em_fields.b_field.spline.vector, self.em_fields.b_field.spline.vector ) self.update_scalar("electric energy", en_E) @@ -97,17 +110,30 @@ def update_scalar_quantities(self): class Vlasov(StruphyModel): r"""Vlasov equation in static background magnetic field. - :ref:`normalization`: + + :ref:`Equations `: .. math:: - \hat v = \hat \Omega_\textnormal{c} \hat x\,. + \frac{\partial f}{\partial t} + \mathbf{v} \cdot \nabla f + \left(\mathbf{v}\times\mathbf{B}_0 \right) \cdot \frac{\partial f}{\partial \mathbf{v}} = 0\,. - :ref:`Equations `: + Unknowns: .. math:: - \frac{\partial f}{\partial t} + \mathbf{v} \cdot \nabla f + \left(\mathbf{v}\times\mathbf{B}_0 \right) \cdot \frac{\partial f}{\partial \mathbf{v}} = 0\,. + & f(t, \mathbf{x}, \mathbf{v}) \ \ldots \ \textrm{phase space distribution (6D)} + + Parameters: + + .. math:: + + \mathbf{B}_0 \ \ldots \ \textrm{background magnetic field} + + :ref:`normalization`: + + .. math:: + + \hat v = \hat \Omega_\textnormal{c} \hat x\,. :ref:`propagators` (called in sequence): @@ -157,7 +183,7 @@ def velocity_scale(self): return "cyclotron" def allocate_helpers(self): - self._tmp = xp.empty(1, dtype=float) + self._tmp = np.empty(1, dtype=float) def update_scalar_quantities(self): particles = self.kinetic_ions.var.particles @@ -173,11 +199,6 @@ def update_scalar_quantities(self): class GuidingCenter(StruphyModel): r"""Guiding-center equation in static background magnetic field. - :ref:`normalization`: - - .. math:: - - \hat v = \hat v_\textnormal{A} \,. :ref:`Equations `: @@ -185,17 +206,31 @@ class GuidingCenter(StruphyModel): \frac{\partial f}{\partial t} + \left[ v_\parallel \frac{\mathbf{B}^*}{B^*_\parallel} + \frac{\mathbf{E}^* \times \mathbf{b}_0}{B^*_\parallel}\right] \cdot \frac{\partial f}{\partial \mathbf{X}} + \left[\frac{1}{\epsilon} \frac{\mathbf{B}^*}{B^*_\parallel} \cdot \mathbf{E}^*\right] \cdot \frac{\partial f}{\partial v_\parallel} = 0\,. - where :math:`f(\mathbf{X}, v_\parallel, \mu, t)` is the guiding center distribution and + where .. math:: \mathbf{E}^* = -\epsilon \mu \nabla |B_0| \,, \qquad \mathbf{B}^* = \mathbf{B}_0 + \epsilon v_\parallel \nabla \times \mathbf{b}_0 \,,\qquad B^*_\parallel = \mathbf B^* \cdot \mathbf b_0 \,. - Moreover, + Unknowns: .. math:: - \epsilon = \frac{1 }{ \hat \Omega_{\textnormal{c}} \hat t}\,,\qquad \textnormal{with} \qquad\hat \Omega_{\textnormal{c}} = \frac{Ze \hat B}{A m_\textnormal{H}}\,. + & f(\mathbf{X}, v_\parallel, \mu) \ \ldots \ \textrm{guiding center distribution (5D)} + + Parameters: + + .. math:: + + & \epsilon = \frac{1 }{ \hat \Omega_{\textnormal{c}} \hat t} \ \ldots \ \textrm{inverse of cyclotron frequency times time unit} + + & \mathbf{B}_0 \ \ldots \ \textrm{background magnetic field} + + :ref:`normalization`: + + .. math:: + + \hat v = \hat v_\textnormal{A} \,. :ref:`propagators` (called in sequence): @@ -247,10 +282,10 @@ def velocity_scale(self): return "alfvén" def allocate_helpers(self): - self._en_fv = xp.empty(1, dtype=float) - self._en_fB = xp.empty(1, dtype=float) - self._en_tot = xp.empty(1, dtype=float) - self._n_lost_particles = xp.empty(1, dtype=float) + self._en_fv = np.empty(1, dtype=float) + self._en_fB = np.empty(1, dtype=float) + self._en_tot = np.empty(1, dtype=float) + self._n_lost_particles = np.empty(1, dtype=float) def update_scalar_quantities(self): particles = self.kinetic_ions.var.particles @@ -308,59 +343,67 @@ class ShearAlfven(StruphyModel): :ref:`Model info `: """ - ## species - class EMFields(FieldSpecies): - def __init__(self): - self.b_field = FEECVariable(space="Hdiv") - self.init_variables() - - class MHD(FluidSpecies): - def __init__(self): - self.velocity = FEECVariable(space="Hdiv") - self.init_variables() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class Propagators: - def __init__(self) -> None: - self.shear_alf = propagators_fields.ShearAlfven() + dct["em_fields"]["b2"] = "Hdiv" + dct["fluid"]["mhd"] = {"u2": "Hdiv"} + return dct - @property - def bulk_species(self): - return self.mhd + @staticmethod + def bulk_species(): + return "mhd" - @property - def velocity_scale(self): + @staticmethod + def velocity_scale(): return "alfvén" - def allocate_helpers(self): + @staticmethod + def propagators_dct(): + return {propagators_fields.ShearAlfven: ["mhd_u2", "b2"]} + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + from struphy.polar.basic import PolarVector + + # extract necessary parameters + alfven_solver = params["fluid"]["mhd"]["options"]["ShearAlfven"]["solver"] + alfven_algo = params["fluid"]["mhd"]["options"]["ShearAlfven"]["algo"] + # project background magnetic field (2-form) and pressure (3-form) self._b_eq = self.derham.P["2"]( [ self.equil.b2_1, self.equil.b2_2, self.equil.b2_3, - ], + ] ) - # temporary vectors for scalar quantities - self._tmp_b1 = self.derham.Vh["2"].zeros() - self._tmp_b2 = self.derham.Vh["2"].zeros() - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - self.mhd = self.MHD() + # set keyword arguments for propagators + self._kwargs[propagators_fields.ShearAlfven] = { + "u_space": "Hdiv", + "solver": alfven_solver, + "algo": alfven_algo, + } - # 2. instantiate all propagators - self.propagators = self.Propagators() - - # 3. assign variables to propagators - self.propagators.shear_alf.variables.u = self.mhd.velocity - self.propagators.shear_alf.variables.b = self.em_fields.b_field + # Initialize propagators used in splitting substeps + self.init_propagators() # Scalar variables to be saved during simulation + # self.add_scalar('en_U') + # self.add_scalar('en_B') + # self.add_scalar('en_B_eq') + # self.add_scalar('en_B_tot') self.add_scalar("en_tot") self.add_scalar("en_U", compute="from_field") @@ -369,13 +412,14 @@ def __init__(self): self.add_scalar("en_B_tot", compute="from_field") self.add_scalar("en_tot2", summands=["en_U", "en_B", "en_B_eq"]) + # temporary vectors for scalar quantities + self._tmp_b1 = self.derham.Vh["2"].zeros() + self._tmp_b2 = self.derham.Vh["2"].zeros() + def update_scalar_quantities(self): # perturbed fields - en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.mhd.velocity.spline.vector, self.mhd.velocity.spline.vector) - en_B = 0.5 * self.mass_ops.M2.dot_inner( - self.em_fields.b_field.spline.vector, - self.em_fields.b_field.spline.vector, - ) + en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_u2"], self.pointer["mhd_u2"]) + en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) self.update_scalar("en_U", en_U) self.update_scalar("en_B", en_B) @@ -388,7 +432,7 @@ def update_scalar_quantities(self): # total magnetic field self._b_eq.copy(out=self._tmp_b1) - self._tmp_b1 += self.em_fields.b_field.spline.vector + self._tmp_b1 += self.pointer["b2"] self.mass_ops.M2.dot(self._tmp_b1, apply_bc=False, out=self._tmp_b2) en_Btot = self._tmp_b1.inner(self._tmp_b2) / 2 @@ -421,74 +465,77 @@ class VariationalPressurelessFluid(StruphyModel): :ref:`Model info `: """ - ## species - - class Fluid(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.init_variables() - - ## propagators - - class Propagators: - def __init__(self): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["fluid"]["fluid"] = {"rho3": "L2", "uv": "H1vec"} + return dct - # 1. instantiate all species - self.fluid = self.Fluid() + @staticmethod + def bulk_species(): + return "fluid" - # 2. instantiate all propagators - self.propagators = self.Propagators() + @staticmethod + def velocity_scale(): + return "alfvén" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.fluid.density - self.propagators.variat_dens.variables.u = self.fluid.velocity - self.propagators.variat_mom.variables.u = self.fluid.velocity + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["fluid_rho3", "fluid_uv"], + propagators_fields.VariationalMomentumAdvection: ["fluid_uv"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.mass import WeightedMassOperator + from struphy.feec.variational_utilities import H1vecMassMatrix_density + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + # Initialize mass matrix + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + + gamma = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": "pressureless", + "gamma": gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() - # define scalars for update_scalar_quantities + # Scalar variables to be saved during simulation self.add_scalar("en_U") - @property - def bulk_species(self): - return self.fluid - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - pass - def update_scalar_quantities(self): - u = self.fluid.velocity.spline.vector - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["fluid_uv"], self.pointer["fluid_uv"]) self.update_scalar("en_U", en_U) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='pressureless')\n", - ] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - class VariationalBarotropicFluid(StruphyModel): r"""Barotropic fluid equations discretized with a variational method. @@ -517,84 +564,84 @@ class VariationalBarotropicFluid(StruphyModel): :ref:`Model info `: """ - ## species - - class Fluid(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.init_variables() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["fluid"]["fluid"] = {"rho3": "L2", "uv": "H1vec"} + return dct - ## propagators + @staticmethod + def bulk_species(): + return "fluid" - class Propagators: - def __init__(self): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.fluid = self.Fluid() - - # 2. instantiate all propagators - self.propagators = self.Propagators() + @staticmethod + def velocity_scale(): + return "alfvén" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.fluid.density - self.propagators.variat_dens.variables.u = self.fluid.velocity - self.propagators.variat_mom.variables.u = self.fluid.velocity + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["fluid_rho3", "fluid_uv"], + propagators_fields.VariationalMomentumAdvection: ["fluid_uv"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.variational_utilities import H1vecMassMatrix_density + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + # Initialize mass matrix + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + + gamma = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": "barotropic", + "gamma": gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() - # define scalars for update_scalar_quantities + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_tot") - @property - def bulk_species(self): - return self.fluid - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): - pass - def update_scalar_quantities(self): - rho = self.fluid.density.spline.vector - u = self.fluid.velocity.spline.vector - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["fluid_uv"], self.pointer["fluid_uv"]) self.update_scalar("en_U", en_U) - en_thermo = 0.5 * self.mass_ops.M3.dot_inner(rho, rho) + en_thermo = 0.5 * self.mass_ops.M3.dot_inner(self.pointer["fluid_rho3"], self.pointer["fluid_rho3"]) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo self.update_scalar("en_tot", en_tot) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='barotropic')\n", - ] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - class VariationalCompressibleFluid(StruphyModel): r"""Fully compressible fluid equations discretized with a variational method. @@ -626,71 +673,106 @@ class VariationalCompressibleFluid(StruphyModel): :ref:`Model info `: """ - ## species + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + dct["fluid"]["fluid"] = {"rho3": "L2", "s3": "L2", "uv": "H1vec"} + return dct - class Fluid(FluidSpecies): - def __init__(self): - self.density = FEECVariable(space="L2") - self.velocity = FEECVariable(space="H1vec") - self.entropy = FEECVariable(space="L2") - self.init_variables() + @staticmethod + def bulk_species(): + return "fluid" - ## propagators - - class Propagators: - def __init__(self): - self.variat_dens = propagators_fields.VariationalDensityEvolve() - self.variat_mom = propagators_fields.VariationalMomentumAdvection() - self.variat_ent = propagators_fields.VariationalEntropyEvolve() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.fluid = self.Fluid() - - # 2. instantiate all propagators - self.propagators = self.Propagators() + @staticmethod + def velocity_scale(): + return "alfvén" - # 3. assign variables to propagators - self.propagators.variat_dens.variables.rho = self.fluid.density - self.propagators.variat_dens.variables.u = self.fluid.velocity - self.propagators.variat_mom.variables.u = self.fluid.velocity - self.propagators.variat_ent.variables.s = self.fluid.entropy - self.propagators.variat_ent.variables.u = self.fluid.velocity + @staticmethod + def propagators_dct(): + return { + propagators_fields.VariationalDensityEvolve: ["fluid_rho3", "fluid_uv"], + propagators_fields.VariationalMomentumAdvection: ["fluid_uv"], + propagators_fields.VariationalEntropyEvolve: ["fluid_s3", "fluid_uv"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import H1vecMassMatrix_density + + # initialize base class + super().__init__(params, comm=comm, clone_config=clone_config) + + # Initialize mass matrix + self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) + + # Initialize propagators/integrators used in splitting substeps + lin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["lin_solver"] + nonlin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] + lin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["lin_solver"] + nonlin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] + lin_solver_entropy = params["fluid"]["fluid"]["options"]["VariationalEntropyEvolve"]["lin_solver"] + nonlin_solver_entropy = params["fluid"]["fluid"]["options"]["VariationalEntropyEvolve"]["nonlin_solver"] + + self._gamma = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] + model = "full" + + from struphy.feec.variational_utilities import InternalEnergyEvaluator + + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) + + # set keyword arguments for propagators + self._kwargs[propagators_fields.VariationalDensityEvolve] = { + "model": model, + "s": self.pointer["fluid_s3"], + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_density, + "nonlin_solver": nonlin_solver_density, + "energy_evaluator": self._energy_evaluator, + } + + self._kwargs[propagators_fields.VariationalMomentumAdvection] = { + "mass_ops": self.WMM, + "lin_solver": lin_solver_momentum, + "nonlin_solver": nonlin_solver_momentum, + } + + self._kwargs[propagators_fields.VariationalEntropyEvolve] = { + "model": model, + "rho": self.pointer["fluid_rho3"], + "gamma": self._gamma, + "mass_ops": self.WMM, + "lin_solver": lin_solver_entropy, + "nonlin_solver": nonlin_solver_entropy, + "energy_evaluator": self._energy_evaluator, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() - # define scalars for update_scalar_quantities + # Scalar variables to be saved during simulation self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_tot") - @property - def bulk_species(self): - return self.fluid - - @property - def velocity_scale(self): - return "alfvén" - - def allocate_helpers(self): + # temporary vectors for scalar quantities projV3 = L2Projector("L2", self._mass_ops) def f(e1, e2, e3): return 1 - f = xp.vectorize(f) + f = np.vectorize(f) self._integrator = projV3(f) - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self.propagators.variat_ent.options.gamma) - def update_scalar_quantities(self): - rho = self.fluid.density.spline.vector - u = self.fluid.velocity.spline.vector - - en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) + en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["fluid_uv"], self.pointer["fluid_uv"]) self.update_scalar("en_U", en_U) en_thermo = self.update_thermo_energy() @@ -698,45 +780,15 @@ def update_scalar_quantities(self): en_tot = en_U + en_thermo self.update_scalar("en_tot", en_tot) - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "variat_dens.Options" in line: - new_file += [ - "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='full',\n", - ] - new_file += [ - " s=model.fluid.entropy)\n", - ] - elif "variat_ent.Options" in line: - new_file += [ - "model.propagators.variat_ent.options = model.propagators.variat_ent.Options(model='full',\n", - ] - new_file += [ - " rho=model.fluid.density)\n", - ] - elif "entropy.add_background" in line: - new_file += ["model.fluid.density.add_background(FieldsBackground())\n"] - new_file += [line] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - def update_thermo_energy(self): """Reuse tmp used in VariationalEntropyEvolve to compute the thermodynamical energy. :meta private: """ - en_prop = self.propagators.variat_ent + en_prop = self._propagators[2] - self._energy_evaluator.sf.vector = self.fluid.entropy.spline.vector - self._energy_evaluator.rhof.vector = self.fluid.density.spline.vector + self._energy_evaluator.sf.vector = self.pointer["fluid_s3"] + self._energy_evaluator.rhof.vector = self.pointer["fluid_rho3"] sf_values = self._energy_evaluator.sf.eval_tp_fixed_loc( self._energy_evaluator.integration_grid_spans, self._energy_evaluator.integration_grid_bd, @@ -757,7 +809,7 @@ def update_thermo_energy(self): def __ener(self, rho, s): """Themodynamical energy as a function of rho and s, usign the perfect gaz hypothesis E(rho, s) = rho^gamma*exp(s/rho)""" - return xp.power(rho, self.propagators.variat_ent.options.gamma) * xp.exp(s / rho) + return np.power(rho, self._gamma) * np.exp(s / rho) class Poisson(StruphyModel): @@ -788,89 +840,65 @@ class Poisson(StruphyModel): :ref:`Model info `: """ - ## species - - class EMFields(FieldSpecies): - def __init__(self): - self.phi = FEECVariable(space="H1") - self.source = FEECVariable(space="H1") - self.init_variables() - - ## propagators - - class Propagators: - def __init__(self): - self.source = propagators_fields.TimeDependentSource() - self.poisson = propagators_fields.Poisson() + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - ## abstract methods + dct["em_fields"]["phi"] = "H1" + dct["em_fields"]["source"] = "H1" + return dct - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMFields() - - # 2. instantiate all propagators - self.propagators = self.Propagators() - - # 3. assign variables to propagators - self.propagators.source.variables.source = self.em_fields.source - self.propagators.poisson.variables.phi = self.em_fields.phi - - @property - def bulk_species(self): + @staticmethod + def bulk_species(): return None - @property - def velocity_scale(self): + @staticmethod + def velocity_scale(): return None - def allocate_helpers(self): - pass + @staticmethod + def propagators_dct(): + return { + propagators_fields.TimeDependentSource: ["source"], + propagators_fields.ImplicitDiffusion: ["phi"], + } + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + def __init__(self, params, comm, clone_config=None): + super().__init__(params, comm=comm, clone_config=clone_config) + + # extract necessary parameters + model_params = params["em_fields"]["options"]["ImplicitDiffusion"]["model"] + solver_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] + omega = params["em_fields"]["options"]["TimeDependentSource"]["omega"] + hfun = params["em_fields"]["options"]["TimeDependentSource"]["hfun"] + + # set keyword arguments for propagators + self._kwargs[propagators_fields.TimeDependentSource] = { + "omega": omega, + "hfun": hfun, + } + + self._kwargs[propagators_fields.ImplicitDiffusion] = { + "sigma_1": model_params["sigma_1"], + "stab_mat": model_params["stab_mat"], + "diffusion_mat": model_params["diffusion_mat"], + "rho": self.pointer["source"], + "solver": solver_params, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() def update_scalar_quantities(self): pass - def allocate_propagators(self): - """Solve initial Poisson equation. - - :meta private: - """ - - # initialize fields and particles - super().allocate_propagators() - - # # use setter to assign source - # self.propagators.poisson.rho = self.mass_ops.M0.dot(self.em_fields.source.spline.vector) - - # Solve with dt=1. and compute electric field - if MPI.COMM_WORLD.Get_rank() == 0: - print("\nSolving initial Poisson problem...") - - self.propagators.poisson(1.0) - - if MPI.COMM_WORLD.Get_rank() == 0: - print("Done.") - - # default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "poisson.Options" in line: - new_file += [ - "model.propagators.poisson.options = model.propagators.poisson.Options(rho=model.em_fields.source)\n", - ] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) - class DeterministicParticleDiffusion(StruphyModel): r"""Diffusion equation discretized with a deterministic particle method; @@ -898,49 +926,64 @@ class DeterministicParticleDiffusion(StruphyModel): :ref:`Model info `: """ - ## species + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class Hydrogen(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="Particles3D") - self.init_variables() + dct["kinetic"]["species1"] = "Particles3D" + return dct - ## propagators + @staticmethod + def bulk_species(): + return "species1" - class Propagators: - def __init__(self): - self.det_diff = propagators_markers.PushDeterministicDiffusion() + @staticmethod + def velocity_scale(): + return None - ## abstract methods + @staticmethod + def propagators_dct(): + return {propagators_markers.PushDeterministicDiffusion: ["species1"]} - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] - # 1. instantiate all species - self.hydrogen = self.Hydrogen() + def __init__(self, params, comm, clone_config=None): + super().__init__(params, comm=comm, clone_config=clone_config) - # 2. instantiate all propagators - self.propagators = self.Propagators() + from mpi4py.MPI import IN_PLACE, SUM - # 3. assign variables to propagators - self.propagators.det_diff.variables.var = self.hydrogen.var + # prelim + params = self.kinetic["species1"]["params"] + algo = params["options"]["PushDeterministicDiffusion"]["algo"] + diffusion_coefficient = params["options"]["PushDeterministicDiffusion"]["diffusion_coefficient"] - # define scalars for update_scalar_quantities - # self.add_scalar("electric energy") - # self.add_scalar("magnetic energy") - # self.add_scalar("total energy") + # # project magnetic background + # self._b_eq = self.derham.P['2']([self.equil.b2_1, + # self.equil.b2_2, + # self.equil.b2_3]) - @property - def bulk_species(self): - return self.hydrogen + # set keyword arguments for propagators + self._kwargs[propagators_markers.PushDeterministicDiffusion] = { + "algo": algo, + "bc_type": params["markers"]["bc"], + "diffusion_coefficient": diffusion_coefficient, + } - @property - def velocity_scale(self): - return None + # Initialize propagators used in splitting substeps + self.init_propagators() - def allocate_helpers(self): - pass + # Scalar variables to be saved during simulation + self.add_scalar("en_f") + + # MPI operations needed for scalar variables + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE + self._tmp = np.empty(1, dtype=float) def update_scalar_quantities(self): pass @@ -971,49 +1014,64 @@ class RandomParticleDiffusion(StruphyModel): :ref:`Model info `: """ - ## species + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - class Hydrogen(ParticleSpecies): - def __init__(self): - self.var = PICVariable(space="Particles3D") - self.init_variables() + dct["kinetic"]["species1"] = "Particles3D" + return dct - ## propagators + @staticmethod + def bulk_species(): + return "species1" - class Propagators: - def __init__(self): - self.rand_diff = propagators_markers.PushRandomDiffusion() + @staticmethod + def velocity_scale(): + return None - ## abstract methods + @staticmethod + def propagators_dct(): + return {propagators_markers.PushRandomDiffusion: ["species1"]} - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] - # 1. instantiate all species - self.hydrogen = self.Hydrogen() + def __init__(self, params, comm, clone_config=None): + super().__init__(params, comm=comm, clone_config=clone_config) - # 2. instantiate all propagators - self.propagators = self.Propagators() + from mpi4py.MPI import IN_PLACE, SUM - # 3. assign variables to propagators - self.propagators.rand_diff.variables.var = self.hydrogen.var + # prelim + species1_params = self.kinetic["species1"]["params"] + algo = species1_params["options"]["PushRandomDiffusion"]["algo"] + diffusion_coefficient = species1_params["options"]["PushRandomDiffusion"]["diffusion_coefficient"] - # define scalars for update_scalar_quantities - # self.add_scalar("electric energy") - # self.add_scalar("magnetic energy") - # self.add_scalar("total energy") + # # project magnetic background + # self._b_eq = self.derham.P['2']([self.equil.b2_1, + # self.equil.b2_2, + # self.equil.b2_3]) - @property - def bulk_species(self): - return self.hydrogen + # set keyword arguments for propagators + self._kwargs[propagators_markers.PushRandomDiffusion] = { + "algo": algo, + "bc_type": species1_params["markers"]["bc"], + "diffusion_coefficient": diffusion_coefficient, + } - @property - def velocity_scale(self): - return None + # Initialize propagators used in splitting substeps + self.init_propagators() - def allocate_helpers(self): - pass + # Scalar variables to be saved during simulation + self.add_scalar("en_f") + + # MPI operations needed for scalar variables + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE + self._tmp = np.empty(1, dtype=float) def update_scalar_quantities(self): pass @@ -1153,75 +1211,116 @@ class TwoFluidQuasiNeutralToy(StruphyModel): in plasma physics, Journal of Computational Physics 2018. """ - ## species - - class EMfields(FieldSpecies): - def __init__(self): - self.phi = FEECVariable(space="L2") - self.init_variables() - - class Ions(FluidSpecies): - def __init__(self): - self.u = FEECVariable(space="Hdiv") - self.init_variables() - - class Electrons(FluidSpecies): - def __init__(self): - self.u = FEECVariable(space="Hdiv") - self.init_variables() - - ## propagators - - class Propagators: - def __init__(self): - self.qn_full = propagators_fields.TwoFluidQuasiNeutralFull() - - ## abstract methods - - def __init__(self): - if rank == 0: - print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - - # 1. instantiate all species - self.em_fields = self.EMfields() - self.ions = self.Ions() - self.electrons = self.Electrons() - - # 2. instantiate all propagators - self.propagators = self.Propagators() - - # 3. assign variables to propagators - self.propagators.qn_full.variables.u = self.ions.u - self.propagators.qn_full.variables.ue = self.electrons.u - self.propagators.qn_full.variables.phi = self.em_fields.phi + @staticmethod + def species(): + dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + + dct["em_fields"]["potential"] = "L2" + dct["fluid"]["ions"] = { + "u": "Hdiv", + } + dct["fluid"]["electrons"] = { + "u": "Hdiv", + } + return dct + + @staticmethod + def bulk_species(): + return "ions" + + @staticmethod + def velocity_scale(): + return "thermal" - # define scalars for update_scalar_quantities + @staticmethod + def propagators_dct(): + return {propagators_fields.TwoFluidQuasiNeutralFull: ["ions_u", "electrons_u", "potential"]} + + __em_fields__ = species()["em_fields"] + __fluid_species__ = species()["fluid"] + __kinetic_species__ = species()["kinetic"] + __bulk_species__ = bulk_species() + __velocity_scale__ = velocity_scale() + __propagators__ = [prop.__name__ for prop in propagators_dct()] + + # add special options + @classmethod + def options(cls): + dct = super().options() + cls.add_option( + species=["fluid", "electrons"], + option=propagators_fields.TwoFluidQuasiNeutralFull, + dct=dct, + ) + return dct - @property - def bulk_species(self): - return self.ions + def __init__(self, params, comm, clone_config=None): + super().__init__(params, comm=comm, clone_config=clone_config) - @property - def velocity_scale(self): - return "thermal" + # get species paramaters + electrons_params = params["fluid"]["electrons"] - def allocate_helpers(self): - pass + # Get coupling strength + if electrons_params["options"]["TwoFluidQuasiNeutralFull"]["override_eq_params"]: + self._epsilon = electrons_params["options"]["TwoFluidQuasiNeutralFull"]["eps_norm"] + print( + f"\n!!! Override equation parameters: {self._epsilon = }.", + ) + else: + self._epsilon = self.equation_params["electrons"]["epsilon"] + + # extract necessary parameters + stokes_solver = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["solver"] + stokes_nu = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["nu"] + stokes_nu_e = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["nu_e"] + stokes_a = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["a"] + stokes_R0 = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["R0"] + stokes_B0 = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["B0"] + stokes_Bp = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["Bp"] + stokes_alpha = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["alpha"] + stokes_beta = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["beta"] + stokes_sigma = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["stab_sigma"] + stokes_variant = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["variant"] + stokes_method_to_solve = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["method_to_solve"] + stokes_preconditioner = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["preconditioner"] + stokes_spectralanalysis = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"][ + "spectralanalysis" + ] + stokes_lifting = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["lifting"] + stokes_dimension = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["dimension"] + stokes_1D_dt = params["time"]["dt"] + + # Check MPI size to ensure only one MPI process + size = comm.Get_size() + if size != 1 and stokes_variant == "Uzawa": + if comm.Get_rank() == 0: + print(f"Error: TwoFluidQuasiNeutralToy only runs with one MPI process.") + return # Early return to stop execution for multiple MPI processes + + # set keyword arguments for propagators + self._kwargs[propagators_fields.TwoFluidQuasiNeutralFull] = { + "solver": stokes_solver, + "nu": stokes_nu, + "nu_e": stokes_nu_e, + "eps_norm": self._epsilon, + "a": stokes_a, + "R0": stokes_R0, + "B0": stokes_B0, + "Bp": stokes_Bp, + "alpha": stokes_alpha, + "beta": stokes_beta, + "stab_sigma": stokes_sigma, + "variant": stokes_variant, + "method_to_solve": stokes_method_to_solve, + "preconditioner": stokes_preconditioner, + "spectralanalysis": stokes_spectralanalysis, + "dimension": stokes_dimension, + "D1_dt": stokes_1D_dt, + "lifting": stokes_lifting, + } + + # Initialize propagators used in splitting substeps + self.init_propagators() def update_scalar_quantities(self): pass - - ## default parameters - def generate_default_parameter_file(self, path=None, prompt=True): - params_path = super().generate_default_parameter_file(path=path, prompt=prompt) - new_file = [] - with open(params_path, "r") as f: - for line in f: - if "BaseUnits()" in line: - new_file += ["base_units = BaseUnits(kBT=1.0)\n"] - else: - new_file += [line] - - with open(params_path, "w") as f: - for line in new_file: - f.write(line) diff --git a/src/struphy/models/variables.py b/src/struphy/models/variables.py index e1c310db0..101e0d497 100644 --- a/src/struphy/models/variables.py +++ b/src/struphy/models/variables.py @@ -4,8 +4,8 @@ from abc import ABCMeta, abstractmethod from typing import TYPE_CHECKING -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI from struphy.feec.psydac_derham import Derham, SplineFunction from struphy.fields_background.base import FluidEquilibrium @@ -82,7 +82,7 @@ def add_background(self, background, verbose=True): if verbose and MPI.COMM_WORLD.Get_rank() == 0: print( - f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added background '{background.__class__.__name__}' with:", + f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added background '{background.__class__.__name__}' with:" ) for k, v in background.__dict__.items(): print(f" {k}: {v}") @@ -120,7 +120,7 @@ def add_perturbation(self, perturbation: Perturbation, verbose=True): if verbose and MPI.COMM_WORLD.Get_rank() == 0: print( - f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added perturbation '{perturbation.__class__.__name__}' with:", + f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added perturbation '{perturbation.__class__.__name__}' with:" ) for k, v in perturbation.__dict__.items(): print(f" {k}: {v}") @@ -175,7 +175,7 @@ def add_initial_condition(self, init: KineticBackground, verbose=True): self._initial_condition = init if verbose and MPI.COMM_WORLD.Get_rank() == 0: print( - f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added initial condition '{init.__class__.__name__}' with:", + f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added initial condition '{init.__class__.__name__}' with:" ) for k, v in init.__dict__.items(): print(f" {k}: {v}") @@ -197,7 +197,7 @@ def allocate( ): # assert isinstance(self.species, KineticSpecies) assert isinstance(self.backgrounds, KineticBackground), ( - "List input not allowed, you can sum Kineticbackgrounds before passing them to add_background." + f"List input not allowed, you can sum Kineticbackgrounds before passing them to add_background." ) if derham is None: @@ -257,7 +257,7 @@ def allocate( f"The number of markers for which data should be stored (={self._n_to_save}) murst be <= than the total number of markers (={obj.Np})" ) if self._n_to_save > 0: - self._saved_markers = xp.zeros( + self._saved_markers = np.zeros( (self._n_to_save, self.particles.markers.shape[1]), dtype=float, ) @@ -270,7 +270,7 @@ def n_to_save(self) -> int: return self._n_to_save @property - def saved_markers(self) -> xp.ndarray: + def saved_markers(self) -> np.ndarray: return self._saved_markers @@ -340,7 +340,7 @@ def allocate( verbose: bool = False, ): assert isinstance(self.backgrounds, FluidEquilibrium), ( - "List input not allowed, you can sum Kineticbackgrounds before passing them to add_background." + f"List input not allowed, you can sum Kineticbackgrounds before passing them to add_background." ) self.backgrounds.domain = domain @@ -398,7 +398,7 @@ def allocate( f"The number of markers for which data should be stored (={self._n_to_save}) murst be <= than the total number of markers (={obj.Np})" ) if self._n_to_save > 0: - self._saved_markers = xp.zeros( + self._saved_markers = np.zeros( (self._n_to_save, self.particles.markers.shape[1]), dtype=float, ) @@ -411,5 +411,5 @@ def n_to_save(self) -> int: return self._n_to_save @property - def saved_markers(self) -> xp.ndarray: + def saved_markers(self) -> np.ndarray: return self._saved_markers diff --git a/src/struphy/ode/solvers.py b/src/struphy/ode/solvers.py index c6d6366b9..12900a878 100644 --- a/src/struphy/ode/solvers.py +++ b/src/struphy/ode/solvers.py @@ -1,6 +1,6 @@ from inspect import signature -import cunumpy as xp +import numpy as np from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector diff --git a/src/struphy/ode/tests/test_ode_feec.py b/src/struphy/ode/tests/test_ode_feec.py index 7dfa87a46..d03af5a95 100644 --- a/src/struphy/ode/tests/test_ode_feec.py +++ b/src/struphy/ode/tests/test_ode_feec.py @@ -5,6 +5,7 @@ from struphy.ode.utils import OptsButcher +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize( "spaces", [ @@ -20,9 +21,9 @@ def test_exp_growth(spaces, algo, show_plots=False): """Solve dy/dt = omega*y for different feec variables y and with all available solvers from the ButcherTableau.""" - import cunumpy as xp + import numpy as np from matplotlib import pyplot as plt - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -40,7 +41,7 @@ def test_exp_growth(spaces, algo, show_plots=False): c0 = 1.2 omega = 2.3 - y_exact = lambda t: c0 * xp.exp(omega * t) + y_exact = lambda t: c0 * np.exp(omega * t) vector_field = {} for i, space in enumerate(spaces): @@ -100,9 +101,9 @@ def f(t, y1, y2, y3, out=out): vector_field[var] = f - print(f"{vector_field =}") + print(f"{vector_field = }") butcher = ButcherTableau(algo=algo) - print(f"{butcher =}") + print(f"{butcher = }") solver = ODEsolverFEEC(vector_field, butcher=butcher) @@ -117,8 +118,8 @@ def f(t, y1, y2, y3, out=out): errors = {} for i, h in enumerate(hs): errors[h] = {} - time = xp.linspace(0, Tend, int(Tend / h) + 1) - print(f"{h =}, {time.size =}") + time = np.linspace(0, Tend, int(Tend / h) + 1) + print(f"{h = }, {time.size = }") yvec = y_exact(time) ymax = {} for var in vector_field: @@ -129,17 +130,17 @@ def f(t, y1, y2, y3, out=out): for b in var.blocks: b[:] = c0 var.update_ghost_regions() - ymax[var] = c0 * xp.ones_like(time) + ymax[var] = c0 * np.ones_like(time) for n in range(time.size - 1): tn = h * n solver(tn, h) for var in vector_field: - ymax[var][n + 1] = xp.max(var.toarray()) + ymax[var][n + 1] = np.max(var.toarray()) # checks for var in vector_field: - errors[h][var] = h * xp.sum(xp.abs(yvec - ymax[var])) / (h * xp.sum(xp.abs(yvec))) - print(f"{errors[h][var] =}") + errors[h][var] = h * np.sum(np.abs(yvec - ymax[var])) / (h * np.sum(np.abs(yvec))) + print(f"{errors[h][var] = }") assert errors[h][var] < 0.31 if rank == 0: @@ -161,16 +162,16 @@ def f(t, y1, y2, y3, out=out): h_vec += [h] err_vec += [dct[var]] - m, _ = xp.polyfit(xp.log(h_vec), xp.log(err_vec), deg=1) - print(f"{spaces[j]}-space, fitted convergence rate = {m} for {algo =} with {solver.butcher.conv_rate =}") - assert xp.abs(m - solver.butcher.conv_rate) < 0.1 - print(f"Convergence check passed on {rank =}.") + m, _ = np.polyfit(np.log(h_vec), np.log(err_vec), deg=1) + print(f"{spaces[j]}-space, fitted convergence rate = {m} for {algo = } with {solver.butcher.conv_rate = }") + assert np.abs(m - solver.butcher.conv_rate) < 0.1 + print(f"Convergence check passed on {rank = }.") if rank == 0: - plt.loglog(h_vec, h_vec, "--", label="h") - plt.loglog(h_vec, [h**2 for h in h_vec], "--", label="h^2") - plt.loglog(h_vec, [h**3 for h in h_vec], "--", label="h^3") - plt.loglog(h_vec, [h**4 for h in h_vec], "--", label="h^4") + plt.loglog(h_vec, h_vec, "--", label=f"h") + plt.loglog(h_vec, [h**2 for h in h_vec], "--", label=f"h^2") + plt.loglog(h_vec, [h**3 for h in h_vec], "--", label=f"h^3") + plt.loglog(h_vec, [h**4 for h in h_vec], "--", label=f"h^4") plt.loglog(h_vec, err_vec, "o-k", label=f"{spaces[j]}-space, {algo}") if rank == 0: plt.xlabel("log(h)") diff --git a/src/struphy/ode/utils.py b/src/struphy/ode/utils.py index 6748d07f1..83300ae58 100644 --- a/src/struphy/ode/utils.py +++ b/src/struphy/ode/utils.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Literal, get_args -import cunumpy as xp +import numpy as np OptsButcher = Literal[ "rk4", @@ -67,14 +67,14 @@ def __post_init__(self): else: raise NotImplementedError(f"Chosen algorithm {self.algo} is not implemented.") - self._b = xp.array(b) - self._c = xp.array(c) + self._b = np.array(b) + self._c = np.array(c) assert self._b.size == self._c.size self._n_stages = self._b.size assert len(a) == self.n_stages - 1 - self._a = xp.tri(self.n_stages, k=-1) + self._a = np.tri(self.n_stages, k=-1) for l, st in enumerate(a): assert len(st) == l + 1 self._a[l + 1, : l + 1] = st diff --git a/src/struphy/pic/accumulation/accum_kernels.py b/src/struphy/pic/accumulation/accum_kernels.py index 2a82a9bcf..8d3c2923b 100644 --- a/src/struphy/pic/accumulation/accum_kernels.py +++ b/src/struphy/pic/accumulation/accum_kernels.py @@ -33,6 +33,7 @@ def charge_density_0form( args_derham: "DerhamArguments", args_domain: "DomainArguments", vec: "float[:,:,:]", + vdim: "int", ): r""" Kernel for :class:`~struphy.pic.accumulation.particles_to_grid.AccumulatorVector` into V0 with the filling @@ -44,7 +45,6 @@ def charge_density_0form( markers = args_markers.markers Np = args_markers.Np - weight_idx = args_markers.weight_idx # -- removed omp: #$ omp parallel private (ip, eta1, eta2, eta3, filling) # -- removed omp: #$ omp for reduction ( + :vec) @@ -59,7 +59,7 @@ def charge_density_0form( eta3 = markers[ip, 2] # filling = w_p/N - filling = markers[ip, weight_idx] / Np + filling = markers[ip, 3 + vdim] / Np particle_to_mat_kernels.vec_fill_b_v0( args_derham, @@ -487,6 +487,57 @@ def linear_vlasov_ampere( # -- removed omp: #$ omp end parallel +def vlasov_maxwell_poisson( + args_markers: "MarkerArguments", + args_derham: "DerhamArguments", + args_domain: "DomainArguments", + vec: "float[:,:,:]", +): + r""" + Accumulates the charge density in V0 + + .. math:: + + \rho_p^\mu = w_p \,. + + Parameters + ---------- + + Note + ---- + The above parameter list contains only the model specific input arguments. + """ + + markers = args_markers.markers + Np = args_markers.Np + + # -- removed omp: #$ omp parallel private (ip, eta1, eta2, eta3, filling) + # -- removed omp: #$ omp for reduction ( + :vec) + for ip in range(shape(markers)[0]): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + # marker positions + eta1 = markers[ip, 0] + eta2 = markers[ip, 1] + eta3 = markers[ip, 2] + + # filling = w_p + filling = markers[ip, 6] / Np + + particle_to_mat_kernels.vec_fill_b_v0( + args_derham, + eta1, + eta2, + eta3, + vec, + filling, + ) + + # -- removed omp: #$ omp end parallel + + @stack_array("dfm", "df_inv", "df_inv_t", "g_inv", "v", "df_inv_times_v", "filling_m", "filling_v") def vlasov_maxwell( args_markers: "MarkerArguments", @@ -1112,7 +1163,9 @@ def pc_lin_mhd_6d_full( vec1_3: "float[:,:,:]", vec2_3: "float[:,:,:]", vec3_3: "float[:,:,:]", - ep_scale: "float", + scale_mat: "float", + scale_vec: "float", + boundary_cut: "float", ): r"""Accumulates into V1 with the filling functions @@ -1156,6 +1209,10 @@ def pc_lin_mhd_6d_full( if markers[ip, 0] == -1.0: continue + # boundary cut + if markers[ip, 0] < boundary_cut or markers[ip, 0] > 1.0 - boundary_cut: + continue + # marker positions eta1 = markers[ip, 0] eta2 = markers[ip, 1] @@ -1186,8 +1243,8 @@ def pc_lin_mhd_6d_full( weight = markers[ip, 8] - filling_m[:, :] = weight * tmp1 / Np * ep_scale - filling_v[:] = weight * tmp_v / Np * ep_scale + filling_m[:, :] = weight * tmp1 / Np * scale_mat + filling_v[:] = weight * tmp_v / Np * scale_vec # call the appropriate matvec filler particle_to_mat_kernels.m_v_fill_v1_pressure_full( @@ -1305,7 +1362,9 @@ def pc_lin_mhd_6d( vec1_3: "float[:,:,:]", vec2_3: "float[:,:,:]", vec3_3: "float[:,:,:]", - ep_scale: "float", + scale_mat: "float", + scale_vec: "float", + boundary_cut: "float", ): r"""Accumulates into V1 with the filling functions @@ -1348,6 +1407,10 @@ def pc_lin_mhd_6d( if markers[ip, 0] == -1.0: continue + # boundary cut + if markers[ip, 0] < boundary_cut or markers[ip, 0] > 1.0 - boundary_cut: + continue + # marker positions eta1 = markers[ip, 0] eta2 = markers[ip, 1] @@ -1378,8 +1441,8 @@ def pc_lin_mhd_6d( linalg_kernels.matrix_matrix(df_inv, df_inv_t, tmp1) linalg_kernels.matrix_vector(df_inv, v, tmp_v) - filling_m[:, :] = weight * tmp1 * ep_scale - filling_v[:] = weight * tmp_v * ep_scale + filling_m[:, :] = weight * tmp1 * scale_mat + filling_v[:] = weight * tmp_v * scale_vec # call the appropriate matvec filler particle_to_mat_kernels.m_v_fill_v1_pressure( diff --git a/src/struphy/pic/accumulation/accum_kernels_gc.py b/src/struphy/pic/accumulation/accum_kernels_gc.py index fecf6a255..628eeeab7 100644 --- a/src/struphy/pic/accumulation/accum_kernels_gc.py +++ b/src/struphy/pic/accumulation/accum_kernels_gc.py @@ -8,7 +8,7 @@ These kernels are passed to :class:`struphy.pic.accumulation.particles_to_grid.Accumulator`. """ -from numpy import empty, mod, shape, zeros +from numpy import empty, shape, zeros from pyccel.decorators import stack_array import struphy.bsplines.bsplines_kernels as bsplines_kernels @@ -67,46 +67,6 @@ def gc_density_0form( # -- removed omp: #$ omp end parallel -def gc_mag_density_0form( - args_markers: "MarkerArguments", - args_derham: "DerhamArguments", - args_domain: "DomainArguments", - vec: "float[:,:,:]", - scale: "float", # model specific argument -): - r""" - Kernel for :class:`~struphy.pic.accumulation.particles_to_grid.AccumulatorVector` into V0 with the filling - - .. math:: - - B_p^\mu = \mu \frac{w_p}{N} \,. - """ - - markers = args_markers.markers - Np = args_markers.Np - - # -- removed omp: #$ omp parallel private (ip, eta1, eta2, eta3, filling) - # -- removed omp: #$ omp for reduction ( + :vec) - for ip in range(shape(markers)[0]): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - # marker positions - eta1 = markers[ip, 0] - eta2 = markers[ip, 1] - eta3 = markers[ip, 2] - - # marker weight and magnetic moment - weight = markers[ip, 5] - mu = markers[ip, 9] - - # filling =mu*w_p/N - filling = mu * weight / Np * scale - - particle_to_mat_kernels.vec_fill_b_v0(args_derham, eta1, eta2, eta3, vec, filling) - - @stack_array("dfm", "df_inv", "df_inv_t", "g_inv", "tmp1", "tmp2", "b", "b_prod", "bstar", "norm_b1", "curl_norm_b") def cc_lin_mhd_5d_D( args_markers: "MarkerArguments", @@ -115,19 +75,22 @@ def cc_lin_mhd_5d_D( mat12: "float[:,:,:,:,:,:]", mat13: "float[:,:,:,:,:,:]", mat23: "float[:,:,:,:,:,:]", - epsilon: float, - ep_scale: "float", - b2_1: "float[:,:,:]", - b2_2: "float[:,:,:]", - b2_3: "float[:,:,:]", + epsilon: float, # model specific argument + b2_1: "float[:,:,:]", # model specific argument + b2_2: "float[:,:,:]", # model specific argument + b2_3: "float[:,:,:]", # model specific argument + # model specific argument norm_b11: "float[:,:,:]", norm_b12: "float[:,:,:]", norm_b13: "float[:,:,:]", + # model specific argument curl_norm_b1: "float[:,:,:]", curl_norm_b2: "float[:,:,:]", curl_norm_b3: "float[:,:,:]", basis_u: "int", -): + scale_mat: "float", + boundary_cut: float, +): # model specific argument r"""Accumulation kernel for the propagator :class:`~struphy.propagators.propagators_fields.CurrentCoupling5DDensity`. Accumulates :math:`\alpha`-form matrix with the filling functions (:math:`\alpha = 2`) @@ -194,6 +157,9 @@ def cc_lin_mhd_5d_D( v = markers[ip, 3] + if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: + continue + # b-field evaluation span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) @@ -220,9 +186,11 @@ def cc_lin_mhd_5d_D( # calculate Bstar and transform to H1vec b_star[:] = b + epsilon * v * curl_norm_b + b_star /= det_df # calculate b_para and b_star_para b_para = linalg_kernels.scalar_dot(norm_b1, b) + b_para /= det_df b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) @@ -234,22 +202,13 @@ def cc_lin_mhd_5d_D( if basis_u == 0: # filling functions - filling_m12 = -weight * density_const * b_prod[0, 1] * ep_scale / epsilon - filling_m13 = -weight * density_const * b_prod[0, 2] * ep_scale / epsilon - filling_m23 = -weight * density_const * b_prod[1, 2] * ep_scale / epsilon + filling_m12 = -weight * density_const * b_prod[0, 1] * scale_mat + filling_m13 = -weight * density_const * b_prod[0, 2] * scale_mat + filling_m23 = -weight * density_const * b_prod[1, 2] * scale_mat # call the appropriate matvec filler particle_to_mat_kernels.mat_fill_v0vec_asym( - args_derham, - span1, - span2, - span3, - mat12, - mat13, - mat23, - filling_m12, - filling_m13, - filling_m23, + args_derham, span1, span2, span3, mat12, mat13, mat23, filling_m12, filling_m13, filling_m23 ) elif basis_u == 1: @@ -260,42 +219,24 @@ def cc_lin_mhd_5d_D( linalg_kernels.matrix_matrix(g_inv, b_prod, tmp1) linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) - filling_m12 = -weight * density_const * tmp2[0, 1] * ep_scale / epsilon - filling_m13 = -weight * density_const * tmp2[0, 2] * ep_scale / epsilon - filling_m23 = -weight * density_const * tmp2[1, 2] * ep_scale / epsilon + filling_m12 = -weight * density_const * tmp2[0, 1] * scale_mat + filling_m13 = -weight * density_const * tmp2[0, 2] * scale_mat + filling_m23 = -weight * density_const * tmp2[1, 2] * scale_mat # call the appropriate matvec filler particle_to_mat_kernels.mat_fill_v1_asym( - args_derham, - span1, - span2, - span3, - mat12, - mat13, - mat23, - filling_m12, - filling_m13, - filling_m23, + args_derham, span1, span2, span3, mat12, mat13, mat23, filling_m12, filling_m13, filling_m23 ) elif basis_u == 2: # filling functions - filling_m12 = -weight * density_const * b_prod[0, 1] * ep_scale / epsilon / det_df**2 - filling_m13 = -weight * density_const * b_prod[0, 2] * ep_scale / epsilon / det_df**2 - filling_m23 = -weight * density_const * b_prod[1, 2] * ep_scale / epsilon / det_df**2 + filling_m12 = -weight * density_const * b_prod[0, 1] * scale_mat / det_df**2 + filling_m13 = -weight * density_const * b_prod[0, 2] * scale_mat / det_df**2 + filling_m23 = -weight * density_const * b_prod[1, 2] * scale_mat / det_df**2 # call the appropriate matvec filler particle_to_mat_kernels.mat_fill_v2_asym( - args_derham, - span1, - span2, - span3, - mat12, - mat13, - mat23, - filling_m12, - filling_m13, - filling_m23, + args_derham, span1, span2, span3, mat12, mat13, mat23, filling_m12, filling_m13, filling_m23 ) # -- removed omp: #$ omp end parallel @@ -307,23 +248,23 @@ def cc_lin_mhd_5d_D( @stack_array( "dfm", - "df_inv", "df_inv_t", + "df_inv", "g_inv", "filling_m", "filling_v", "tmp", "tmp1", + "tmp2", "tmp_m", "tmp_v", "b", - "bfull_star", "b_prod", - "b_prod_neg", + "b_prod_negb_star", "norm_b1", "curl_norm_b", ) -def cc_lin_mhd_5d_curlb( +def cc_lin_mhd_5d_J1( args_markers: "MarkerArguments", args_derham: "DerhamArguments", args_domain: "DomainArguments", @@ -336,19 +277,21 @@ def cc_lin_mhd_5d_curlb( vec1: "float[:,:,:]", vec2: "float[:,:,:]", vec3: "float[:,:,:]", - epsilon: float, - ep_scale: float, - b1: "float[:,:,:]", - b2: "float[:,:,:]", - b3: "float[:,:,:]", - norm_b11: "float[:,:,:]", - norm_b12: "float[:,:,:]", - norm_b13: "float[:,:,:]", - curl_norm_b1: "float[:,:,:]", - curl_norm_b2: "float[:,:,:]", - curl_norm_b3: "float[:,:,:]", - basis_u: "int", -): + epsilon: float, # model specific argument + b1: "float[:,:,:]", # model specific argument + b2: "float[:,:,:]", # model specific argument + b3: "float[:,:,:]", # model specific argument + norm_b11: "float[:,:,:]", # model specific argument + norm_b12: "float[:,:,:]", # model specific argument + norm_b13: "float[:,:,:]", # model specific argument + curl_norm_b1: "float[:,:,:]", # model specific argument + curl_norm_b2: "float[:,:,:]", # model specific argument + curl_norm_b3: "float[:,:,:]", # model specific argument + basis_u: "int", # model specific argument + scale_mat: "float", # model specific argument + scale_vec: "float", # model specific argument + boundary_cut: "float", +): # model specific argument r"""Accumulation kernel for the propagator :class:`~struphy.propagators.propagators_coupling.CurrentCoupling5DCurlb`. Accumulates :math:`\alpha`-form matrix and vector with the filling functions (:math:`\alpha = 2`) @@ -360,6 +303,21 @@ def cc_lin_mhd_5d_curlb( B_p^\mu &= w_p \left( \frac{v^2_{\parallel,p}}{g\hat B^*_\parallel} \mathbf B^2_{\times} \right)_\mu \,, where :math:`\mathbf B^2_{\times} \mathbf a := \hat{\mathbf B}^2 \times \mathbf a` for :math:`a \in \mathbb R^3`. + + Parameters + ---------- + b1, b2, b3 : array[float] + FE coefficients c_ijk of the magnetic field as a 2-form. + + norm_b11, norm_b12, norm_b13 : array[float] + FE coefficients c_ijk of the normalized magnetic field as a 1-form. + + curl_norm_b1, curl_norm_b2, curl_norm_b3 : array[float] + FE coefficients c_ijk of the curl of normalized magnetic field as a 2-form. + + Note + ---- + The above parameter list contains only the model specific input arguments. """ markers = args_markers.markers @@ -367,7 +325,7 @@ def cc_lin_mhd_5d_curlb( # allocate for magnetic field evaluation b = empty(3, dtype=float) - bfull_star = empty(3, dtype=float) + b_star = empty(3, dtype=float) b_prod = zeros((3, 3), dtype=float) b_prod_neg = zeros((3, 3), dtype=float) norm_b1 = empty(3, dtype=float) @@ -380,11 +338,12 @@ def cc_lin_mhd_5d_curlb( g_inv = empty((3, 3), dtype=float) # allocate for filling - filling_m = zeros((3, 3), dtype=float) - filling_v = zeros(3, dtype=float) + filling_m = empty((3, 3), dtype=float) + filling_v = empty(3, dtype=float) tmp = empty((3, 3), dtype=float) tmp1 = empty((3, 3), dtype=float) + tmp2 = empty((3, 3), dtype=float) tmp_m = empty((3, 3), dtype=float) tmp_v = empty(3, dtype=float) @@ -392,6 +351,8 @@ def cc_lin_mhd_5d_curlb( # get number of markers n_markers_loc = shape(markers)[0] + # -- removed omp: #$ omp parallel firstprivate(b_prod) private(ip, boundary_cut, eta1, eta2, eta3, v, weight, span1, span2, span3, b1, b2, b3, b, b_star, b_prod_neg, norm_b1, curl_norm_b, abs_b_star_para, dfm, df_inv, df_inv_t, g_inv, det_df, tmp, tmp1, tmp2, tmp_m, tmp_v, filling_m, filling_v) + # -- removed omp: #$ omp for reduction ( + : mat11, mat12, mat13, mat22, mat23, mat33, vec1, vec2, vec3) for ip in range(n_markers_loc): # only do something if particle is a "true" particle (i.e. not a hole) if markers[ip, 0] == -1.0: @@ -406,6 +367,9 @@ def cc_lin_mhd_5d_curlb( weight = markers[ip, 5] v = markers[ip, 3] + if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: + continue + # b-field evaluation span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) @@ -423,11 +387,11 @@ def cc_lin_mhd_5d_curlb( # curl_norm_b; 2form eval_2form_spline_mpi(span1, span2, span3, args_derham, curl_norm_b1, curl_norm_b2, curl_norm_b3, curl_norm_b) - # b_star; 2form - bfull_star[:] = b + curl_norm_b * v * epsilon + # b_star; 2form in H1vec + b_star[:] = (b + curl_norm_b * v * epsilon) / det_df # calculate abs_b_star_para - abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, bfull_star) + abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) # calculate tensor product of two curl_norm_b linalg_kernels.outer(curl_norm_b, curl_norm_b, tmp) @@ -447,8 +411,8 @@ def cc_lin_mhd_5d_curlb( linalg_kernels.matrix_matrix(tmp1, b_prod_neg, tmp_m) linalg_kernels.matrix_vector(b_prod, curl_norm_b, tmp_v) - filling_m[:, :] += weight * tmp_m * v**2 / abs_b_star_para**2 * ep_scale - filling_v[:] += weight * tmp_v * v**2 / abs_b_star_para * ep_scale + filling_m[:, :] = weight * tmp_m * v**2 / abs_b_star_para**2 / det_df**2 * scale_mat + filling_v[:] = weight * tmp_v * v**2 / abs_b_star_para / det_df * scale_vec # call the appropriate matvec filler particle_to_mat_kernels.m_v_fill_v0vec_symm( @@ -476,13 +440,54 @@ def cc_lin_mhd_5d_curlb( filling_v[2], ) + elif basis_u == 1: + # needed metric coefficients + linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) + linalg_kernels.transpose(df_inv, df_inv_t) + linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) + linalg_kernels.matrix_matrix(g_inv, b_prod, tmp1) + linalg_kernels.matrix_vector(tmp1, curl_norm_b, tmp_v) + + linalg_kernels.matrix_matrix(tmp1, tmp, tmp2) + linalg_kernels.matrix_matrix(tmp2, b_prod_neg, tmp1) + linalg_kernels.matrix_matrix(tmp1, g_inv, tmp_m) + + filling_m[:, :] = weight * tmp_m * v**2 / abs_b_star_para**2 / det_df**2 * scale_mat + filling_v[:] = weight * tmp_v * v**2 / abs_b_star_para / det_df * scale_vec + + # call the appropriate matvec filler + particle_to_mat_kernels.m_v_fill_v1_symm( + args_derham, + span1, + span2, + span3, + mat11, + mat12, + mat13, + mat22, + mat23, + mat33, + filling_m[0, 0], + filling_m[0, 1], + filling_m[0, 2], + filling_m[1, 1], + filling_m[1, 2], + filling_m[2, 2], + vec1, + vec2, + vec3, + filling_v[0], + filling_v[1], + filling_v[2], + ) + elif basis_u == 2: linalg_kernels.matrix_matrix(b_prod, tmp, tmp1) linalg_kernels.matrix_matrix(tmp1, b_prod_neg, tmp_m) linalg_kernels.matrix_vector(b_prod, curl_norm_b, tmp_v) - filling_m[:, :] = weight * tmp_m * v**2 / abs_b_star_para**2 / det_df**2 * ep_scale - filling_v[:] = weight * tmp_v * v**2 / abs_b_star_para / det_df * ep_scale + filling_m[:, :] = weight * tmp_m * v**2 / abs_b_star_para**2 / det_df**4 * scale_mat + filling_v[:] = weight * tmp_v * v**2 / abs_b_star_para / det_df**2 * scale_vec # call the appropriate matvec filler particle_to_mat_kernels.m_v_fill_v2_symm( @@ -521,6 +526,8 @@ def cc_lin_mhd_5d_curlb( vec2 /= Np vec3 /= Np + # -- removed omp: #$ omp end parallel + @stack_array("dfm", "norm_b1", "filling_v") def cc_lin_mhd_5d_M( @@ -540,7 +547,8 @@ def cc_lin_mhd_5d_M( norm_b12: "float[:,:,:]", # model specific argument norm_b13: "float[:,:,:]", # model specific argument scale_vec: "float", # model specific argument -): + boundary_cut: "float", +): # model specific argument r"""Accumulation kernel for the propagator :class:`~struphy.propagators.propagators_fields.ShearAlfvenCurrentCoupling5D` and :class:`~struphy.propagators.propagators_fields.MagnetosonicCurrentCoupling5D`. Accumulates 2-form vector with the filling functions: @@ -592,6 +600,9 @@ def cc_lin_mhd_5d_M( weight = markers[ip, 5] mu = markers[ip, 9] + if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: + continue + # b-field evaluation span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) @@ -606,16 +617,7 @@ def cc_lin_mhd_5d_M( filling_v[:] = weight * mu / det_df * scale_vec * norm_b1 particle_to_mat_kernels.vec_fill_v2( - args_derham, - span1, - span2, - span3, - vec1, - vec2, - vec3, - filling_v[0], - filling_v[1], - filling_v[2], + args_derham, span1, span2, span3, vec1, vec2, vec3, filling_v[0], filling_v[1], filling_v[2] ) vec1 /= Np @@ -631,17 +633,19 @@ def cc_lin_mhd_5d_M( "df_inv", "g_inv", "filling_v", - "tmp", + "tmp1", + "tmp2", "tmp_v", "b", "b_prod", - "norm_b_prod", + "norm_b2_prod", "b_star", "curl_norm_b", "norm_b1", + "norm_b2", "grad_PB", ) -def cc_lin_mhd_5d_gradB( +def cc_lin_mhd_5d_J2( args_markers: "MarkerArguments", args_derham: "DerhamArguments", args_domain: "DomainArguments", @@ -654,22 +658,27 @@ def cc_lin_mhd_5d_gradB( vec1: "float[:,:,:]", vec2: "float[:,:,:]", vec3: "float[:,:,:]", - epsilon: float, - ep_scale: float, - b1: "float[:,:,:]", - b2: "float[:,:,:]", - b3: "float[:,:,:]", - norm_b11: "float[:,:,:]", - norm_b12: "float[:,:,:]", - norm_b13: "float[:,:,:]", - curl_norm_b1: "float[:,:,:]", - curl_norm_b2: "float[:,:,:]", - curl_norm_b3: "float[:,:,:]", - grad_PB1: "float[:,:,:]", - grad_PB2: "float[:,:,:]", - grad_PB3: "float[:,:,:]", + epsilon: float, # model specific argument + b1: "float[:,:,:]", # model specific argument + b2: "float[:,:,:]", # model specific argument + b3: "float[:,:,:]", # model specific argument + norm_b11: "float[:,:,:]", # model specific argument + norm_b12: "float[:,:,:]", # model specific argument + norm_b13: "float[:,:,:]", # model specific argument + norm_b21: "float[:,:,:]", # model specific argument + norm_b22: "float[:,:,:]", # model specific argument + norm_b23: "float[:,:,:]", # model specific argument + curl_norm_b1: "float[:,:,:]", # model specific argument + curl_norm_b2: "float[:,:,:]", # model specific argument + curl_norm_b3: "float[:,:,:]", # model specific argument + grad_PB1: "float[:,:,:]", # model specific argument + grad_PB2: "float[:,:,:]", # model specific argument + grad_PB3: "float[:,:,:]", # model specific argument basis_u: "int", -): + scale_mat: "float", + scale_vec: "float", + boundary_cut: float, +): # model specific argument r"""Accumulation kernel for the propagator :class:`~struphy.propagators.propagators_coupling.CurrentCoupling5DGradB`. Accumulates math:`\alpha` -form vector with the filling functions @@ -688,6 +697,9 @@ def cc_lin_mhd_5d_gradB( norm_b11, norm_b12, norm_b13 : array[float] FE coefficients c_ijk of the normalized magnetic field as a 1-form. + norm_b21, norm_b22, norm_b23 : array[float] + FE coefficients c_ijk of the normalized magnetic field as a 2-form. + curl_norm_b1, curl_norm_b2, curl_norm_b3 : array[float] FE coefficients c_ijk of the curl of normalized magnetic field as a 2-form. @@ -706,9 +718,10 @@ def cc_lin_mhd_5d_gradB( b = empty(3, dtype=float) b_star = empty(3, dtype=float) b_prod = zeros((3, 3), dtype=float) - norm_b_prod = zeros((3, 3), dtype=float) + norm_b2_prod = zeros((3, 3), dtype=float) curl_norm_b = empty(3, dtype=float) norm_b1 = empty(3, dtype=float) + norm_b2 = empty(3, dtype=float) grad_PB = empty(3, dtype=float) # allocate for metric coeffs @@ -719,13 +732,17 @@ def cc_lin_mhd_5d_gradB( # allocate for filling filling_v = empty(3, dtype=float) - tmp = empty((3, 3), dtype=float) + + tmp1 = empty((3, 3), dtype=float) + tmp2 = empty((3, 3), dtype=float) tmp_v = empty(3, dtype=float) # get number of markers n_markers_loc = shape(markers)[0] + # -- removed omp: #$ omp parallel firstprivate(b_prod) private(ip, boundary_cut, eta1, eta2, eta3, v, mu, weight, span1, span2, span3, b1, b2, b3, b, b_star, norm_b1, norm_b2, norm_b2_prod, curl_norm_b, grad_PB, abs_b_star_para, dfm, df_inv, df_inv_t, g_inv, det_df, tmp1, tmp2, tmp_v, filling_v) + # -- removed omp: #$ omp for reduction ( + : mat11, mat12, mat13, mat22, mat23, mat33, vec1, vec2, vec3) for ip in range(n_markers_loc): # only do something if particle is a "true" particle (i.e. not a hole) if markers[ip, 0] == -1.0: @@ -736,191 +753,9 @@ def cc_lin_mhd_5d_gradB( eta2 = markers[ip, 1] eta3 = markers[ip, 2] - # marker weight and velocity - weight = markers[ip, 5] - v = markers[ip, 3] - mu = markers[ip, 9] - - # b-field evaluation - span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) - - # evaluate Jacobian, result in dfm - evaluation_kernels.df(eta1, eta2, eta3, args_domain, dfm) - - det_df = linalg_kernels.det(dfm) - - # needed metric coefficients - linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) - linalg_kernels.transpose(df_inv, df_inv_t) - linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) - - # b; 2form - eval_2form_spline_mpi(span1, span2, span3, args_derham, b1, b2, b3, b) - - # norm_b1; 1form - eval_1form_spline_mpi(span1, span2, span3, args_derham, norm_b11, norm_b12, norm_b13, norm_b1) - - # curl_norm_b; 2form - eval_2form_spline_mpi(span1, span2, span3, args_derham, curl_norm_b1, curl_norm_b2, curl_norm_b3, curl_norm_b) - - # grad_PB; 1form - eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PB1, grad_PB2, grad_PB3, grad_PB) - - # b_star; 2form transformed into H1vec - b_star[:] = b + curl_norm_b * v * epsilon - - # calculate abs_b_star_para - abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) - - # operator bx() as matrix - b_prod[0, 1] = -b[2] - b_prod[0, 2] = +b[1] - b_prod[1, 0] = +b[2] - b_prod[1, 2] = -b[0] - b_prod[2, 0] = -b[1] - b_prod[2, 1] = +b[0] - - norm_b_prod[0, 1] = -norm_b1[2] - norm_b_prod[0, 2] = +norm_b1[1] - norm_b_prod[1, 0] = +norm_b1[2] - norm_b_prod[1, 2] = -norm_b1[0] - norm_b_prod[2, 0] = -norm_b1[1] - norm_b_prod[2, 1] = +norm_b1[0] - - if basis_u == 0: - linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - - filling_v[:] = weight * tmp_v * mu / abs_b_star_para * ep_scale - - # call the appropriate matvec filler - particle_to_mat_kernels.vec_fill_v0vec( - args_derham, - span1, - span2, - span3, - vec1, - vec2, - vec3, - filling_v[0], - filling_v[1], - filling_v[2], - ) - - elif basis_u == 2: - linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - - filling_v[:] = weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - - # call the appropriate matvec filler - particle_to_mat_kernels.vec_fill_v2( - args_derham, - span1, - span2, - span3, - vec1, - vec2, - vec3, - filling_v[0], - filling_v[1], - filling_v[2], - ) - vec1 /= Np - vec2 /= Np - vec3 /= Np - - -@stack_array( - "dfm", - "df_inv_t", - "df_inv", - "g_inv", - "filling_v", - "tmp", - "tmp_v", - "b", - "b_prod", - "beq", - "beq_prod", - "norm_b_prod", - "bfull_star", - "curl_norm_b", - "norm_b1", - "grad_PB", - "grad_PBeq", -) -def cc_lin_mhd_5d_gradB_dg_init( - args_markers: "MarkerArguments", - args_derham: "DerhamArguments", - args_domain: "DomainArguments", - vec1: "float[:,:,:]", - vec2: "float[:,:,:]", - vec3: "float[:,:,:]", - epsilon: float, - ep_scale: float, - b1: "float[:,:,:]", - b2: "float[:,:,:]", - b3: "float[:,:,:]", - beq1: "float[:,:,:]", - beq2: "float[:,:,:]", - beq3: "float[:,:,:]", - norm_b11: "float[:,:,:]", - norm_b12: "float[:,:,:]", - norm_b13: "float[:,:,:]", - curl_norm_b1: "float[:,:,:]", - curl_norm_b2: "float[:,:,:]", - curl_norm_b3: "float[:,:,:]", - grad_PB1: "float[:,:,:]", - grad_PB2: "float[:,:,:]", - grad_PB3: "float[:,:,:]", - grad_PBeq1: "float[:,:,:]", - grad_PBeq2: "float[:,:,:]", - grad_PBeq3: "float[:,:,:]", - basis_u: "int", -): - r"""TODO""" - - markers = args_markers.markers - Np = args_markers.Np - - # allocate for magnetic field evaluation - b = empty(3, dtype=float) - beq = empty(3, dtype=float) - bfull_star = empty(3, dtype=float) - b_prod = zeros((3, 3), dtype=float) - beq_prod = zeros((3, 3), dtype=float) - norm_b_prod = zeros((3, 3), dtype=float) - curl_norm_b = empty(3, dtype=float) - norm_b1 = empty(3, dtype=float) - grad_PB = empty(3, dtype=float) - grad_PBeq = empty(3, dtype=float) - - # allocate for metric coeffs - dfm = empty((3, 3), dtype=float) - df_inv = empty((3, 3), dtype=float) - df_inv_t = empty((3, 3), dtype=float) - g_inv = empty((3, 3), dtype=float) - - # allocate for filling - filling_v = empty(3, dtype=float) - tmp = empty((3, 3), dtype=float) - - tmp_v = empty(3, dtype=float) - - # get number of markers - n_markers_loc = shape(markers)[0] - - for ip in range(n_markers_loc): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: + if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: continue - # marker positions - eta1 = markers[ip, 0] - eta2 = markers[ip, 1] - eta3 = markers[ip, 2] - # marker weight and velocity weight = markers[ip, 5] v = markers[ip, 3] @@ -942,26 +777,23 @@ def cc_lin_mhd_5d_gradB_dg_init( # b; 2form eval_2form_spline_mpi(span1, span2, span3, args_derham, b1, b2, b3, b) - # beq; 2form - eval_2form_spline_mpi(span1, span2, span3, args_derham, beq1, beq2, beq3, beq) - # norm_b1; 1form eval_1form_spline_mpi(span1, span2, span3, args_derham, norm_b11, norm_b12, norm_b13, norm_b1) + # norm_b2; 2form + eval_2form_spline_mpi(span1, span2, span3, args_derham, norm_b21, norm_b22, norm_b23, norm_b2) + # curl_norm_b; 2form eval_2form_spline_mpi(span1, span2, span3, args_derham, curl_norm_b1, curl_norm_b2, curl_norm_b3, curl_norm_b) # grad_PB; 1form eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PB1, grad_PB2, grad_PB3, grad_PB) - # grad_PBeq; 1form - eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PBeq1, grad_PBeq2, grad_PBeq3, grad_PBeq) - # b_star; 2form transformed into H1vec - bfull_star[:] = b + beq + curl_norm_b * v * epsilon + b_star[:] = (b + curl_norm_b * v * epsilon) / det_df # calculate abs_b_star_para - abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, bfull_star) + abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) # operator bx() as matrix b_prod[0, 1] = -b[2] @@ -971,346 +803,58 @@ def cc_lin_mhd_5d_gradB_dg_init( b_prod[2, 0] = -b[1] b_prod[2, 1] = +b[0] - beq_prod[0, 1] = -beq[2] - beq_prod[0, 2] = +beq[1] - beq_prod[1, 0] = +beq[2] - beq_prod[1, 2] = -beq[0] - beq_prod[2, 0] = -beq[1] - beq_prod[2, 1] = +beq[0] - - norm_b_prod[0, 1] = -norm_b1[2] - norm_b_prod[0, 2] = +norm_b1[1] - norm_b_prod[1, 0] = +norm_b1[2] - norm_b_prod[1, 2] = -norm_b1[0] - norm_b_prod[2, 0] = -norm_b1[1] - norm_b_prod[2, 1] = +norm_b1[0] + norm_b2_prod[0, 1] = -norm_b2[2] + norm_b2_prod[0, 2] = +norm_b2[1] + norm_b2_prod[1, 0] = +norm_b2[2] + norm_b2_prod[1, 2] = -norm_b2[0] + norm_b2_prod[2, 0] = -norm_b2[1] + norm_b2_prod[2, 1] = +norm_b2[0] if basis_u == 0: - # beq contribution - linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) + linalg_kernels.matrix_matrix(b_prod, g_inv, tmp1) + linalg_kernels.matrix_matrix(tmp1, norm_b2_prod, tmp2) + linalg_kernels.matrix_matrix(tmp2, g_inv, tmp1) - filling_v[:] = weight * tmp_v * mu / abs_b_star_para * ep_scale + linalg_kernels.matrix_vector(tmp1, grad_PB, tmp_v) - # b contribution - linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - - filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale - - linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - - filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale - - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - - filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale + filling_v[:] = weight * tmp_v * mu / abs_b_star_para * scale_vec # call the appropriate matvec filler particle_to_mat_kernels.vec_fill_v0vec( - args_derham, - span1, - span2, - span3, - vec1, - vec2, - vec3, - filling_v[0], - filling_v[1], - filling_v[2], - ) - - elif basis_u == 2: - # beq contribution - linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - - filling_v[:] = weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - - # b contribution - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - - filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - - linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - - filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - - filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - - # call the appropriate matvec filler - particle_to_mat_kernels.vec_fill_v2( - args_derham, - span1, - span2, - span3, - vec1, - vec2, - vec3, - filling_v[0], - filling_v[1], - filling_v[2], + args_derham, span1, span2, span3, vec1, vec2, vec3, filling_v[0], filling_v[1], filling_v[2] ) - vec1 /= Np - vec2 /= Np - vec3 /= Np - - -@stack_array( - "dfm", - "df_inv_t", - "df_inv", - "g_inv", - "filling_v", - "tmp", - "tmp_v", - "b", - "b_prod", - "eta_diff", - "beq", - "beq_prod", - "norm_b_prod", - "bfull_star", - "curl_norm_b", - "norm_b1", - "grad_PB", - "grad_PBeq", - "eta_mid", - "eta_diff", -) -def cc_lin_mhd_5d_gradB_dg( - args_markers: "MarkerArguments", - args_derham: "DerhamArguments", - args_domain: "DomainArguments", - vec1: "float[:,:,:]", - vec2: "float[:,:,:]", - vec3: "float[:,:,:]", - epsilon: float, - ep_scale: float, - b1: "float[:,:,:]", - b2: "float[:,:,:]", - b3: "float[:,:,:]", - beq1: "float[:,:,:]", - beq2: "float[:,:,:]", - beq3: "float[:,:,:]", - norm_b11: "float[:,:,:]", - norm_b12: "float[:,:,:]", - norm_b13: "float[:,:,:]", - curl_norm_b1: "float[:,:,:]", - curl_norm_b2: "float[:,:,:]", - curl_norm_b3: "float[:,:,:]", - grad_PB1: "float[:,:,:]", - grad_PB2: "float[:,:,:]", - grad_PB3: "float[:,:,:]", - grad_PBeq1: "float[:,:,:]", - grad_PBeq2: "float[:,:,:]", - grad_PBeq3: "float[:,:,:]", - basis_u: "int", - const: "float", -): - r"""TODO""" - - markers = args_markers.markers - Np = args_markers.Np - - # allocate for magnetic field evaluation - eta_diff = empty(3, dtype=float) - eta_mid = empty(3, dtype=float) - b = empty(3, dtype=float) - beq = empty(3, dtype=float) - bfull_star = empty(3, dtype=float) - b_prod = zeros((3, 3), dtype=float) - beq_prod = zeros((3, 3), dtype=float) - norm_b_prod = zeros((3, 3), dtype=float) - curl_norm_b = empty(3, dtype=float) - norm_b1 = empty(3, dtype=float) - grad_PB = empty(3, dtype=float) - grad_PBeq = empty(3, dtype=float) - - # allocate for metric coeffs - dfm = empty((3, 3), dtype=float) - df_inv = empty((3, 3), dtype=float) - df_inv_t = empty((3, 3), dtype=float) - g_inv = empty((3, 3), dtype=float) - - # allocate for filling - filling_v = empty(3, dtype=float) - tmp = empty((3, 3), dtype=float) - - tmp_v = empty(3, dtype=float) - - # get number of markers - n_markers_loc = shape(markers)[0] - - for ip in range(n_markers_loc): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - # marker positions, mid point - eta_mid[:] = (markers[ip, 0:3] + markers[ip, 11:14]) / 2.0 - eta_mid[:] = mod(eta_mid[:], 1.0) - - eta_diff[:] = markers[ip, 0:3] - markers[ip, 11:14] - - # marker weight and velocity - weight = markers[ip, 5] - v = markers[ip, 3] - mu = markers[ip, 9] - - # b-field evaluation - span1, span2, span3 = get_spans(eta_mid[0], eta_mid[1], eta_mid[2], args_derham) - - # evaluate Jacobian, result in dfm - evaluation_kernels.df(eta_mid[0], eta_mid[1], eta_mid[2], args_domain, dfm) - - det_df = linalg_kernels.det(dfm) - - # needed metric coefficients - linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) - linalg_kernels.transpose(df_inv, df_inv_t) - linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) - - # b; 2form - eval_2form_spline_mpi(span1, span2, span3, args_derham, b1, b2, b3, b) - - # beq; 2form - eval_2form_spline_mpi(span1, span2, span3, args_derham, beq1, beq2, beq3, beq) - - # norm_b1; 1form - eval_1form_spline_mpi(span1, span2, span3, args_derham, norm_b11, norm_b12, norm_b13, norm_b1) - - # curl_norm_b; 2form - eval_2form_spline_mpi(span1, span2, span3, args_derham, curl_norm_b1, curl_norm_b2, curl_norm_b3, curl_norm_b) - - # grad_PB; 1form - eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PB1, grad_PB2, grad_PB3, grad_PB) - - # grad_PBeq; 1form - eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PBeq1, grad_PBeq2, grad_PBeq3, grad_PBeq) - - # b_star; 2form transformed into H1vec - bfull_star[:] = b + beq + curl_norm_b * v * epsilon - - # calculate abs_b_star_para - abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, bfull_star) - - # operator bx() as matrix - b_prod[0, 1] = -b[2] - b_prod[0, 2] = +b[1] - b_prod[1, 0] = +b[2] - b_prod[1, 2] = -b[0] - b_prod[2, 0] = -b[1] - b_prod[2, 1] = +b[0] - - beq_prod[0, 1] = -beq[2] - beq_prod[0, 2] = +beq[1] - beq_prod[1, 0] = +beq[2] - beq_prod[1, 2] = -beq[0] - beq_prod[2, 0] = -beq[1] - beq_prod[2, 1] = +beq[0] - - norm_b_prod[0, 1] = -norm_b1[2] - norm_b_prod[0, 2] = +norm_b1[1] - norm_b_prod[1, 0] = +norm_b1[2] - norm_b_prod[1, 2] = -norm_b1[0] - norm_b_prod[2, 0] = -norm_b1[1] - norm_b_prod[2, 1] = +norm_b1[0] - - if basis_u == 0: - # beq * gradPBeq contribution - linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - - filling_v[:] = weight * tmp_v * mu / abs_b_star_para * ep_scale - - # beq * gradPB contribution - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale - - # beq * dg term contribution - linalg_kernels.matrix_vector(tmp, eta_diff, tmp_v) - filling_v[:] += tmp_v / abs_b_star_para * const - - # b * gradPBeq contribution - linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale + elif basis_u == 1: + linalg_kernels.matrix_matrix(g_inv, b_prod, tmp1) + linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) + linalg_kernels.matrix_matrix(tmp2, norm_b2_prod, tmp1) + linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) - # b * gradPB contribution - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale + linalg_kernels.matrix_vector(tmp2, grad_PB, tmp_v) - # b * dg term contribution - linalg_kernels.matrix_vector(tmp, eta_diff, tmp_v) - filling_v[:] += tmp_v / abs_b_star_para * const + filling_v[:] = weight * tmp_v * mu / abs_b_star_para * scale_vec # call the appropriate matvec filler - particle_to_mat_kernels.vec_fill_v0vec( - args_derham, - span1, - span2, - span3, - vec1, - vec2, - vec3, - filling_v[0], - filling_v[1], - filling_v[2], + particle_to_mat_kernels.vec_fill_v1( + args_derham, span1, span2, span3, vec1, vec2, vec3, filling_v[0], filling_v[1], filling_v[2] ) elif basis_u == 2: - # beq * gradPBeq contribution - linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - - filling_v[:] = weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - - # beq * gradPB contribution - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - - filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - - # beq * dg term contribution - linalg_kernels.matrix_vector(tmp, eta_diff, tmp_v) - - filling_v[:] += tmp_v / abs_b_star_para / det_df * const - - # b * gradPBeq contribtuion - linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) - linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) + linalg_kernels.matrix_matrix(b_prod, g_inv, tmp1) + linalg_kernels.matrix_matrix(tmp1, norm_b2_prod, tmp2) + linalg_kernels.matrix_matrix(tmp2, g_inv, tmp1) - filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale + linalg_kernels.matrix_vector(tmp1, grad_PB, tmp_v) - # b * gradPB contribution - linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) - - filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - - # b * dg term contribution - linalg_kernels.matrix_vector(tmp, eta_diff, tmp_v) - - filling_v[:] += tmp_v / abs_b_star_para / det_df * const + filling_v[:] = weight * tmp_v * mu / abs_b_star_para / det_df * scale_vec # call the appropriate matvec filler particle_to_mat_kernels.vec_fill_v2( - args_derham, - span1, - span2, - span3, - vec1, - vec2, - vec3, - filling_v[0], - filling_v[1], - filling_v[2], + args_derham, span1, span2, span3, vec1, vec2, vec3, filling_v[0], filling_v[1], filling_v[2] ) vec1 /= Np vec2 /= Np vec3 /= Np + + # -- removed omp: #$ omp end parallel diff --git a/src/struphy/pic/accumulation/filter_kernels.py b/src/struphy/pic/accumulation/filter_kernels.py index a6c498ca8..e24c7ad5d 100644 --- a/src/struphy/pic/accumulation/filter_kernels.py +++ b/src/struphy/pic/accumulation/filter_kernels.py @@ -5,10 +5,8 @@ @stack_array("vec_copy", "mask1d", "mask", "top", "i_bottom", "i_top", "fi", "ir") -def apply_three_point_filter_3d( +def apply_three_point_filter( vec: "float[:,:,:]", - dir: "int", - form: "int", Nel: "int[:]", spl_kind: "bool[:]", pn: "int[:]", @@ -49,7 +47,6 @@ def apply_three_point_filter_3d( i_top = zeros(3, dtype=int) fi = empty(3, dtype=int) ir = empty(3, dtype=int) - isDspline = zeros(3, dtype=int) # copy vectors vec_copy[:, :, :] = vec[:, :, :] @@ -65,33 +62,22 @@ def apply_three_point_filter_3d( mask[i, j, k] *= mask1d[i] * mask1d[j] * mask1d[k] # consider left and right boundary - if form == 1: - isDspline[dir] = 1 - elif form == 2: - isDspline[:] = 1 - isDspline[dir] = 0 - elif form == 3: - isDspline[:] = 1 - for i in range(3): if spl_kind[i]: top[i] = Nel[i] - 1 else: - if isDspline[i] == 1: - top[i] = Nel[i] + pn[i] - 2 - else: - top[i] = Nel[i] + pn[i] - 1 + top[i] = Nel[i] + pn[i] - 1 for i in range(3): if starts[i] == 0: if spl_kind[i]: - i_bottom[i] = 0 + i_bottom[i] = -1 else: i_bottom[i] = +1 if ends[i] == top[i]: if spl_kind[i]: - i_top[i] = 0 + i_top[i] = +1 else: i_top[i] = -1 diff --git a/src/struphy/pic/accumulation/particle_to_mat_kernels.py b/src/struphy/pic/accumulation/particle_to_mat_kernels.py index bc9364f6a..576d4571c 100644 --- a/src/struphy/pic/accumulation/particle_to_mat_kernels.py +++ b/src/struphy/pic/accumulation/particle_to_mat_kernels.py @@ -5834,12 +5834,7 @@ def m_v_fill_v2_full( def mat_fill_b_v0( - args_derham: "DerhamArguments", - eta1: float, - eta2: float, - eta3: float, - mat: "float[:,:,:,:,:,:]", - fill: float, + args_derham: "DerhamArguments", eta1: float, eta2: float, eta3: float, mat: "float[:,:,:,:,:,:]", fill: float ): """ Adds the contribution of one particle to the elements of an accumulation matrix V0 -> V0. The result is returned in mat. @@ -5974,12 +5969,7 @@ def m_v_fill_b_v0( def mat_fill_b_v3( - args_derham: "DerhamArguments", - eta1: float, - eta2: float, - eta3: float, - mat: "float[:,:,:,:,:,:]", - fill: float, + args_derham: "DerhamArguments", eta1: float, eta2: float, eta3: float, mat: "float[:,:,:,:,:,:]", fill: float ): """ Adds the contribution of one particle to the elements of an accumulation matrix V3 -> V3. The result is returned in mat. @@ -6122,12 +6112,7 @@ def m_v_fill_b_v3( def mat_fill_v0( - args_derham: "DerhamArguments", - span1: int, - span2: int, - span3: int, - mat: "float[:,:,:,:,:,:]", - fill: float, + args_derham: "DerhamArguments", span1: int, span2: int, span3: int, mat: "float[:,:,:,:,:,:]", fill: float ): """ Adds the contribution of one particle to the elements of an accumulation matrix V0 -> V0. The result is returned in mat. @@ -6254,12 +6239,7 @@ def m_v_fill_v0( def mat_fill_v3( - args_derham: "DerhamArguments", - span1: int, - span2: int, - span3: int, - mat: "float[:,:,:,:,:,:]", - fill: float, + args_derham: "DerhamArguments", span1: int, span2: int, span3: int, mat: "float[:,:,:,:,:,:]", fill: float ): """ Adds the contribution of one particle to the elements of an accumulation block matrix V3 -> V3. The result is returned in mat. @@ -12969,12 +12949,7 @@ def vec_fill_v0vec( def vec_fill_b_v0( - args_derham: "DerhamArguments", - eta1: float, - eta2: float, - eta3: float, - vec: "float[:,:,:]", - fill: float, + args_derham: "DerhamArguments", eta1: float, eta2: float, eta3: float, vec: "float[:,:,:]", fill: float ): """TODO""" @@ -13152,12 +13127,7 @@ def vec_fill_b_v2( def vec_fill_b_v3( - args_derham: "DerhamArguments", - eta1: float, - eta2: float, - eta3: float, - vec: "float[:,:,:]", - fill: float, + args_derham: "DerhamArguments", eta1: float, eta2: float, eta3: float, vec: "float[:,:,:]", fill: float ): """TODO""" diff --git a/src/struphy/pic/accumulation/particles_to_grid.py b/src/struphy/pic/accumulation/particles_to_grid.py index 06d67a6df..23345df23 100644 --- a/src/struphy/pic/accumulation/particles_to_grid.py +++ b/src/struphy/pic/accumulation/particles_to_grid.py @@ -1,19 +1,18 @@ "Base classes for particle deposition (accumulation) on the grid." -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilMatrix, StencilVector import struphy.pic.accumulation.accum_kernels as accums import struphy.pic.accumulation.accum_kernels_gc as accums_gc +import struphy.pic.accumulation.filter_kernels as filters from struphy.feec.mass import WeightedMassOperators from struphy.feec.psydac_derham import Derham from struphy.kernel_arguments.pusher_args_kernels import DerhamArguments, DomainArguments -from struphy.pic.accumulation.filter import AccumFilter, FilterParameters from struphy.pic.base import Particles from struphy.profiling.profiling import ProfileManager -from struphy.utils.pyccel import Pyccelkernel class Accumulator: @@ -67,7 +66,6 @@ class Accumulator: filter_params : dict Params for the accumulation filter: use_filter(string, either `three_point or `fourier), repeat(int), alpha(float) and modes(list with int). - Note ---- Struphy accumulation kernels called by ``Accumulator`` objects must be added to ``struphy/pic/accumulation/accum_kernels.py`` @@ -79,23 +77,29 @@ def __init__( self, particles: Particles, space_id: str, - kernel: Pyccelkernel, + kernel, mass_ops: WeightedMassOperators, args_domain: DomainArguments, *, add_vector: bool = False, symmetry: str = None, - filter_params: FilterParameters = None, + filter_params: dict = { + "use_filter": None, + "modes": None, + "repeat": None, + "alpha": None, + }, ): self._particles = particles self._space_id = space_id - assert isinstance(kernel, Pyccelkernel), f"{kernel} is not of type Pyccelkernel" self._kernel = kernel self._derham = mass_ops.derham self._args_domain = args_domain self._symmetry = symmetry + self._filter_params = filter_params + self._form = self.derham.space_to_form[space_id] # initialize matrices (instances of WeightedMassOperator) @@ -172,9 +176,6 @@ def __init__( for bl in vec.blocks: self._args_data += (bl._data,) - # initialize filter - self._accfilter = AccumFilter(filter_params, self._derham, self._space_id) - def __call__(self, *optional_args, **args_control): """ Performs the accumulation into the matrix/vector by calling the chosen accumulation kernel and additional analytical contributions (control variate, optional). @@ -191,7 +192,7 @@ def __call__(self, *optional_args, **args_control): Entries must be pyccel-conform types. args_control : any - Keyword arguments for an analytical control variate correction in the accumulation step. Possible keywords are 'control_vec' for a vector correction or 'control_mat' for a matrix correction. Values are a 1d (vector) or 2d (matrix) list with callables or xp.ndarrays used for the correction. + Keyword arguments for an analytical control variate correction in the accumulation step. Possible keywords are 'control_vec' for a vector correction or 'control_mat' for a matrix correction. Values are a 1d (vector) or 2d (matrix) list with callables or np.ndarrays used for the correction. """ # flags for break @@ -203,7 +204,7 @@ def __call__(self, *optional_args, **args_control): dat[:] = 0.0 # accumulate into matrix (and vector) with markers - with ProfileManager.profile_region("kernel: " + self.kernel.name): + with ProfileManager.profile_region("kernel: " + self.kernel.__name__): self.kernel( self.particles.args_markers, self.derham.args_derham, @@ -213,13 +214,52 @@ def __call__(self, *optional_args, **args_control): ) # apply filter - if self.accfilter.params.use_filter is not None: + if self.filter_params["use_filter"] is not None: for vec in self._vectors: vec.exchange_assembly_data() vec.update_ghost_regions() - self.accfilter(vec) - vec_finished = True + if self.filter_params["use_filter"] == "fourier_in_tor": + self.apply_toroidal_fourier_filter(vec, self.filter_params["modes"]) + + elif self.filter_params["use_filter"] == "three_point": + for _ in range(self.filter_params["repeat"]): + for i in range(3): + filters.apply_three_point_filter( + vec[i]._data, + np.array(self.derham.Nel), + np.array(self.derham.spl_kind), + np.array(self.derham.p), + np.array(self.derham.Vh[self.form][i].starts), + np.array(self.derham.Vh[self.form][i].ends), + alpha=self.filter_params["alpha"], + ) + + vec.update_ghost_regions() + + elif self.filter_params["use_filter"] == "hybrid": + self.apply_toroidal_fourier_filter(vec, self.filter_params["modes"]) + + for _ in range(self.filter_params["repeat"]): + for i in range(2): + filters.apply_three_point_filter( + vec[i]._data, + np.array(self.derham.Nel), + np.array(self.derham.spl_kind), + np.array(self.derham.p), + np.array(self.derham.Vh[self.form][i].starts), + np.array(self.derham.Vh[self.form][i].ends), + alpha=self.filter_params["alpha"], + ) + + vec.update_ghost_regions() + + else: + raise NotImplemented( + "The type of filter must be fourier or three_point.", + ) + + vec_finished = True if self.particles.clone_config is None: num_clones = 1 @@ -307,7 +347,7 @@ def particles(self): return self._particles @property - def kernel(self) -> Pyccelkernel: + def kernel(self): """The accumulation kernel.""" return self._kernel @@ -353,9 +393,14 @@ def vectors(self): return out @property - def accfilter(self): - """Callable filters""" - return self._accfilter + def filter_params(self): + """Dict of three components for the accumulation filter parameters: use_filter(string), repeat(int) and alpha(float).""" + return self._filter_params + + @property + def filter_params(self): + """Dict of three components for the accumulation filter parameters: use_filter(string), repeat(int) and alpha(float).""" + return self._filter_params def init_control_variate(self, mass_ops): """Set up the use of noise reduction by control variate.""" @@ -365,6 +410,55 @@ def init_control_variate(self, mass_ops): # L2 projector for dofs self._get_L2dofs = L2Projector(self.space_id, mass_ops).get_dofs + def apply_toroidal_fourier_filter(self, vec, modes): + """ + Applying fourier filter to the spline coefficients of the accumulated vector (toroidal direction). + + Parameters + ---------- + vec : BlockVector + + modes : list + Mode numbers which are not filtered out. + """ + + from scipy.fft import irfft, rfft + + tor_Nel = self.derham.Nel[2] + + # Nel along the toroidal direction must be equal or bigger than 2*maximum mode + assert tor_Nel >= 2 * max(modes) + + pn = self.derham.p + ir = np.empty(3, dtype=int) + + if (tor_Nel % 2) == 0: + vec_temp = np.zeros(int(tor_Nel / 2) + 1, dtype=complex) + else: + vec_temp = np.zeros(int((tor_Nel - 1) / 2) + 1, dtype=complex) + + # no domain decomposition along the toroidal direction + assert self.derham.domain_decomposition.nprocs[2] == 1 + + for axis in range(3): + starts = self.derham.Vh[ſelf.form][axis].starts + ends = self.derham.Vh[self.form][axis].ends + + # index range + for i in range(3): + ir[i] = ends[i] + 1 - starts[i] + + # filtering + for i in range(ir[0]): + for j in range(ir[1]): + vec_temp[:] = 0 + vec_temp[modes] = rfft( + vec[axis]._data[pn[0] + i, pn[1] + j, pn[2] : pn[2] + ir[2]], + )[modes] + vec[axis]._data[pn[0] + i, pn[1] + j, pn[2] : pn[2] + ir[2]] = irfft(vec_temp, n=tor_Nel) + + vec.update_ghost_regions() + def show_accumulated_spline_field(self, mass_ops: WeightedMassOperators, eta_direction=0, component=0): r"""1D plot of the spline field corresponding to the accumulated vector. The latter can be viewed as the rhs of an L2-projection: @@ -388,7 +482,7 @@ def show_accumulated_spline_field(self, mass_ops: WeightedMassOperators, eta_dir field.vector = a # plot field - eta = xp.linspace(0, 1, 100) + eta = np.linspace(0, 1, 100) if eta_direction == 0: args = (eta, 0.5, 0.5) elif eta_direction == 1: @@ -427,21 +521,18 @@ class AccumulatorVector: args_domain : DomainArguments Mapping infos. - """ def __init__( self, particles: Particles, space_id: str, - kernel: Pyccelkernel, + kernel, mass_ops: WeightedMassOperators, args_domain: DomainArguments, - filter_params: FilterParameters = None, ): self._particles = particles self._space_id = space_id - assert isinstance(kernel, Pyccelkernel), f"{kernel} is not of type Pyccelkernel" self._kernel = kernel self._derham = mass_ops.derham self._args_domain = args_domain @@ -491,9 +582,6 @@ def __init__( for bl in vec.blocks: self._args_data += (bl._data,) - # initialize filter - self._accfilter = AccumFilter(filter_params, self._derham, self._space_id) - def __call__(self, *optional_args, **args_control): """ Performs the accumulation into the vector by calling the chosen accumulation kernel @@ -510,7 +598,7 @@ def __call__(self, *optional_args, **args_control): args_control : any Keyword arguments for an analytical control variate correction in the accumulation step. Possible keywords are 'control_vec' for a vector correction or 'control_mat' for a matrix correction. - Values are a 1d (vector) or 2d (matrix) list with callables or xp.ndarrays used for the correction. + Values are a 1d (vector) or 2d (matrix) list with callables or np.ndarrays used for the correction. """ # flags for break @@ -521,24 +609,15 @@ def __call__(self, *optional_args, **args_control): dat[:] = 0.0 # accumulate into matrix (and vector) with markers - with ProfileManager.profile_region("kernel: " + self.kernel.name): + with ProfileManager.profile_region("kernel: " + self.kernel.__name__): self.kernel( self.particles.args_markers, - self.derham.args_derham, + self.derham._args_derham, self.args_domain, *self._args_data, *optional_args, ) - # apply filter - if self.accfilter.params.use_filter is not None: - for vec in self._vectors: - vec.exchange_assembly_data() - vec.update_ghost_regions() - - self.accfilter(vec) - vec_finished = True - if self.particles.clone_config is None: num_clones = 1 else: @@ -573,7 +652,7 @@ def particles(self): return self._particles @property - def kernel(self) -> Pyccelkernel: + def kernel(self): """The accumulation kernel.""" return self._kernel @@ -608,11 +687,6 @@ def vectors(self): return out - @property - def accfilter(self): - """Callable filters""" - return self._accfilter - def init_control_variate(self, mass_ops): """Set up the use of noise reduction by control variate.""" @@ -644,7 +718,7 @@ def show_accumulated_spline_field(self, mass_ops, eta_direction=0): field.vector = a # plot field - eta = xp.linspace(0, 1, 100) + eta = np.linspace(0, 1, 100) if eta_direction == 0: args = (eta, 0.5, 0.5) elif eta_direction == 1: diff --git a/src/struphy/pic/base.py b/src/struphy/pic/base.py index 84900418d..91c41437a 100644 --- a/src/struphy/pic/base.py +++ b/src/struphy/pic/base.py @@ -4,20 +4,11 @@ from abc import ABCMeta, abstractmethod import h5py +import numpy as np import scipy.special as sp - -try: - from mpi4py.MPI import Intracomm -except ModuleNotFoundError: - - class Intracomm: - x = None - - -import cunumpy as xp from line_profiler import profile -from psydac.ddm.mpi import MockComm -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI +from mpi4py.MPI import Intracomm from sympy.ntheory import factorint from struphy.bsplines.bsplines import quadrature_grid @@ -55,7 +46,6 @@ class Intracomm: ) from struphy.utils import utils from struphy.utils.clone_config import CloneConfig -from struphy.utils.pyccel import Pyccelkernel class Particles(metaclass=ABCMeta): @@ -153,7 +143,7 @@ def __init__( domain_decomp: tuple = None, mpi_dims_mask: tuple | list = None, boxes_per_dim: tuple | list = None, - box_bufsize: float = 5.0, + box_bufsize: float = 2.0, type: str = "full_f", name: str = "some_name", loading_params: LoadingParameters = None, @@ -204,11 +194,9 @@ def __init__( if self.mpi_comm is None: self._mpi_size = 1 self._mpi_rank = 0 - self._Barrier = lambda: None else: self._mpi_size = self.mpi_comm.Get_size() self._mpi_rank = self.mpi_comm.Get_rank() - self._Barrier = self.mpi_comm.Barrier # domain decomposition (MPI) and cell information self._boxes_per_dim = boxes_per_dim @@ -221,7 +209,7 @@ def __init__( self._nprocs = domain_decomp[1] # total number of cells (equal to mpi_size if no grid) - n_cells = xp.sum(xp.prod(self.domain_array[:, 2::3], axis=1, dtype=int)) * self.num_clones + n_cells = np.sum(np.prod(self.domain_array[:, 2::3], axis=1, dtype=int)) * self.num_clones # if verbose: # print(f"\n{self.mpi_rank = }, {n_cells = }") @@ -230,12 +218,12 @@ def __init__( n_boxes = self.mpi_size * self.num_clones else: assert all([nboxes >= nproc for nboxes, nproc in zip(self.boxes_per_dim, self.nprocs)]), ( - f"There must be at least one box {self.boxes_per_dim =} on each process {self.nprocs =} in each direction." + f"There must be at least one box {self.boxes_per_dim = } on each process {self.nprocs = } in each direction." ) assert all([nboxes % nproc == 0 for nboxes, nproc in zip(self.boxes_per_dim, self.nprocs)]), ( - f"Number of boxes {self.boxes_per_dim =} must be divisible by number of processes {self.nprocs =} in each direction." + f"Number of boxes {self.boxes_per_dim = } must be divisible by number of processes {self.nprocs = } in each direction." ) - n_boxes = xp.prod(self.boxes_per_dim, dtype=int) * self.num_clones + n_boxes = np.prod(self.boxes_per_dim, dtype=int) * self.num_clones # if verbose: # print(f"\n{self.mpi_rank = }, {n_boxes = }") @@ -342,13 +330,14 @@ def __init__( self._generate_sampling_moments() # create buffers for mpi_sort_markers - self._sorting_etas = xp.zeros(self.markers.shape, dtype=float) - self._is_on_proc_domain = xp.zeros((self.markers.shape[0], 3), dtype=bool) - self._can_stay = xp.zeros(self.markers.shape[0], dtype=bool) - self._reqs = [None] * self.mpi_size - self._recvbufs = [None] * self.mpi_size - self._send_to_i = [None] * self.mpi_size - self._send_list = [None] * self.mpi_size + if self.mpi_comm is not None: + self._sorting_etas = np.zeros(self.markers.shape, dtype=float) + self._is_on_proc_domain = np.zeros((self.markers.shape[0], 3), dtype=bool) + self._can_stay = np.zeros(self.markers.shape[0], dtype=bool) + self._reqs = [None] * self.mpi_size + self._recvbufs = [None] * self.mpi_size + self._send_to_i = [None] * self.mpi_size + self._send_list = [None] * self.mpi_size @classmethod @abstractmethod @@ -726,16 +715,16 @@ def index(self): def valid_mks(self): """Array of booleans stating if an entry in the markers array is a true local particle (not a hole or ghost).""" if not hasattr(self, "_valid_mks"): - self._valid_mks = ~xp.logical_or(self.holes, self.ghost_particles) + self._valid_mks = ~np.logical_or(self.holes, self.ghost_particles) return self._valid_mks def update_valid_mks(self): - self._valid_mks[:] = ~xp.logical_or(self.holes, self.ghost_particles) + self._valid_mks[:] = ~np.logical_or(self.holes, self.ghost_particles) @property def n_mks_loc(self): """Number of valid markers on process (without holes and ghosts).""" - return xp.count_nonzero(self.valid_mks) + return np.count_nonzero(self.valid_mks) @property def n_mks_on_each_proc(self): @@ -745,7 +734,7 @@ def n_mks_on_each_proc(self): @property def n_mks_on_clone(self): """Number of valid markers on current clone (without holes and ghosts).""" - return xp.sum(self.n_mks_on_each_proc) + return np.sum(self.n_mks_on_each_proc) @property def n_mks_on_each_clone(self): @@ -755,7 +744,7 @@ def n_mks_on_each_clone(self): @property def n_mks_global(self): """Number of valid markers on current clone (without holes and ghosts).""" - return xp.sum(self.n_mks_on_each_clone) + return np.sum(self.n_mks_on_each_clone) @property def positions(self): @@ -764,7 +753,7 @@ def positions(self): @positions.setter def positions(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) assert new.shape == (self.n_mks_loc, 3) self._markers[self.valid_mks, self.index["pos"]] = new @@ -775,8 +764,8 @@ def velocities(self): @velocities.setter def velocities(self, new): - assert isinstance(new, xp.ndarray) - assert new.shape == (self.n_mks_loc, self.vdim), f"{self.n_mks_loc =} and {self.vdim =} but {new.shape =}" + assert isinstance(new, np.ndarray) + assert new.shape == (self.n_mks_loc, self.vdim), f"{self.n_mks_loc = } and {self.vdim = } but {new.shape = }" self._markers[self.valid_mks, self.index["vel"]] = new @property @@ -786,7 +775,7 @@ def phasespace_coords(self): @phasespace_coords.setter def phasespace_coords(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) assert new.shape == (self.n_mks_loc, 3 + self.vdim) self._markers[self.valid_mks, self.index["coords"]] = new @@ -797,7 +786,7 @@ def weights(self): @weights.setter def weights(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) assert new.shape == (self.n_mks_loc,) self._markers[self.valid_mks, self.index["weights"]] = new @@ -808,7 +797,7 @@ def sampling_density(self): @sampling_density.setter def sampling_density(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) assert new.shape == (self.n_mks_loc,) self._markers[self.valid_mks, self.index["s0"]] = new @@ -819,7 +808,7 @@ def weights0(self): @weights0.setter def weights0(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) assert new.shape == (self.n_mks_loc,) self._markers[self.valid_mks, self.index["w0"]] = new @@ -830,7 +819,7 @@ def marker_ids(self): @marker_ids.setter def marker_ids(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) assert new.shape == (self.n_mks_loc,) self._markers[self.valid_mks, self.index["ids"]] = new @@ -861,7 +850,7 @@ def f_coords(self): @f_coords.setter def f_coords(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) self.markers[self.valid_mks, self.f_coords_index] = new @property @@ -873,16 +862,16 @@ def args_markers(self): def f_jacobian_coords(self): """Coordinates of the velocity jacobian determinant of the distribution fuction.""" if isinstance(self.f_jacobian_coords_index, list): - return self.markers[xp.ix_(~self.holes, self.f_jacobian_coords_index)] + return self.markers[np.ix_(~self.holes, self.f_jacobian_coords_index)] else: return self.markers[~self.holes, self.f_jacobian_coords_index] @f_jacobian_coords.setter def f_jacobian_coords(self, new): - assert isinstance(new, xp.ndarray) + assert isinstance(new, np.ndarray) if isinstance(self.f_jacobian_coords_index, list): self.markers[ - xp.ix_( + np.ix_( ~self.holes, self.f_jacobian_coords_index, ) @@ -929,7 +918,7 @@ def _get_domain_decomp(self, mpi_dims_mask: tuple | list = None): Returns ------- - dom_arr : xp.ndarray + dom_arr : np.ndarray A 2d array of shape (#MPI processes, 9). The row index denotes the process rank. The columns are for n=0,1,2: - arr[i, 3*n + 0] holds the LEFT domain boundary of process i in direction eta_(n+1). - arr[i, 3*n + 1] holds the RIGHT domain boundary of process i in direction eta_(n+1). @@ -941,7 +930,7 @@ def _get_domain_decomp(self, mpi_dims_mask: tuple | list = None): if mpi_dims_mask is None: mpi_dims_mask = [True, True, True] - dom_arr = xp.zeros((self.mpi_size, 9), dtype=float) + dom_arr = np.zeros((self.mpi_size, 9), dtype=float) # factorize mpi size factors = factorint(self.mpi_size) @@ -965,10 +954,10 @@ def _get_domain_decomp(self, mpi_dims_mask: tuple | list = None): mm = (mm + 1) % 3 nprocs[mm] *= fac - assert xp.prod(nprocs) == self.mpi_size + assert np.prod(nprocs) == self.mpi_size # domain decomposition - breaks = [xp.linspace(0.0, 1.0, nproc + 1) for nproc in nprocs] + breaks = [np.linspace(0.0, 1.0, nproc + 1) for nproc in nprocs] # fill domain array for n in range(self.mpi_size): @@ -1057,14 +1046,14 @@ def _n_mks_load_and_Np_per_clone(self): """Return two arrays: 1) an array of sub_comm.size where the i-th entry corresponds to the number of markers drawn on process i, and 2) an array of size num_clones where the i-th entry corresponds to the number of markers on clone i.""" # number of cells on current process - n_cells_loc = xp.prod( + n_cells_loc = np.prod( self.domain_array[self.mpi_rank, 2::3], dtype=int, ) # array of number of markers on each process at loading stage if self.clone_config is not None: - _n_cells_clone = xp.sum(xp.prod(self.domain_array[:, 2::3], axis=1, dtype=int)) + _n_cells_clone = np.sum(np.prod(self.domain_array[:, 2::3], axis=1, dtype=int)) _n_mks_load_tot = self.clone_config.get_Np_clone(self.Np) _ppc = _n_mks_load_tot / _n_cells_clone else: @@ -1074,14 +1063,14 @@ def _n_mks_load_and_Np_per_clone(self): n_mks_load = self._gather_scalar_in_subcomm_array(int(_ppc * n_cells_loc)) # add deviation from Np to rank 0 - n_mks_load[0] += _n_mks_load_tot - xp.sum(n_mks_load) + n_mks_load[0] += _n_mks_load_tot - np.sum(n_mks_load) # check if all markers are there - assert xp.sum(n_mks_load) == _n_mks_load_tot + assert np.sum(n_mks_load) == _n_mks_load_tot # Np on each clone Np_per_clone = self._gather_scalar_in_intercomm_array(_n_mks_load_tot) - assert xp.sum(Np_per_clone) == self.Np + assert np.sum(Np_per_clone) == self.Np return n_mks_load, Np_per_clone @@ -1092,23 +1081,23 @@ def _allocate_marker_array(self): # number of markers on the local process at loading stage n_mks_load_loc = self.n_mks_load[self._mpi_rank] - bufsize = self.bufsize + 1.0 / xp.sqrt(n_mks_load_loc) + bufsize = self.bufsize + 1.0 / np.sqrt(n_mks_load_loc) # allocate markers array (3 x positions, vdim x velocities, weight, s0, w0, ..., ID) with buffer self._n_rows = round(n_mks_load_loc * (1 + bufsize)) - self._markers = xp.zeros((self.n_rows, self.n_cols), dtype=float) + self._markers = np.zeros((self.n_rows, self.n_cols), dtype=float) # allocate auxiliary arrays - self._holes = xp.zeros(self.n_rows, dtype=bool) - self._ghost_particles = xp.zeros(self.n_rows, dtype=bool) - self._valid_mks = xp.zeros(self.n_rows, dtype=bool) - self._is_outside_right = xp.zeros(self.n_rows, dtype=bool) - self._is_outside_left = xp.zeros(self.n_rows, dtype=bool) - self._is_outside = xp.zeros(self.n_rows, dtype=bool) + self._holes = np.zeros(self.n_rows, dtype=bool) + self._ghost_particles = np.zeros(self.n_rows, dtype=bool) + self._valid_mks = np.zeros(self.n_rows, dtype=bool) + self._is_outside_right = np.zeros(self.n_rows, dtype=bool) + self._is_outside_left = np.zeros(self.n_rows, dtype=bool) + self._is_outside = np.zeros(self.n_rows, dtype=bool) # create array container (3 x positions, vdim x velocities, weight, s0, w0, ID) for removed markers self._n_lost_markers = 0 - self._lost_markers = xp.zeros((int(self.n_rows * 0.5), 10), dtype=float) + self._lost_markers = np.zeros((int(self.n_rows * 0.5), 10), dtype=float) # arguments for kernels self._args_markers = MarkerArguments( @@ -1126,7 +1115,7 @@ def _allocate_marker_array(self): # Have at least 3 spare places in markers array assert self.args_markers.first_free_idx + 2 < self.n_cols - 1, ( - f"{self.args_markers.first_free_idx + 2} is not smaller than {self.n_cols - 1 =}; not enough columns in marker array !!" + f"{self.args_markers.first_free_idx + 2} is not smaller than {self.n_cols - 1 = }; not enough columns in marker array !!" ) def _initialize_sorting_boxes(self): @@ -1223,16 +1212,16 @@ def _generate_sampling_moments(self): # assert len(ns) == len(us) == len(vths) - # ns = xp.array(ns) - # us = xp.array(us) - # vths = xp.array(vths) + # ns = np.array(ns) + # us = np.array(us) + # vths = np.array(vths) # Use the mean of shifts and thermal velocity such that outermost shift+thermal is # new shift + new thermal - # mean_us = xp.mean(us, axis=0) - # us_ext = us + vths * xp.where(us >= 0, 1, -1) + # mean_us = np.mean(us, axis=0) + # us_ext = us + vths * np.where(us >= 0, 1, -1) # us_ext_dist = us_ext - mean_us[None, :] - # new_vths = xp.max(xp.abs(us_ext_dist), axis=0) + # new_vths = np.max(np.abs(us_ext_dist), axis=0) # new_moments = [] @@ -1307,7 +1296,7 @@ def _f_init(*etas, flat_eval=False): out = out0 + out1 if flat_eval: - out = xp.squeeze(out) + out = np.squeeze(out) return out @@ -1316,7 +1305,7 @@ def _f_init(*etas, flat_eval=False): def _load_external( self, n_mks_load_loc: int, - n_mks_load_cum_sum: xp.ndarray, + n_mks_load_cum_sum: np.ndarray, ): """Load markers from external .hdf5 file. @@ -1325,7 +1314,7 @@ def _load_external( n_mks_load_loc: int Number of markers on the local process at loading stage. - n_mks_load_cum_sum: xp.ndarray + n_mks_load_cum_sum: np.ndarray Cumulative sum of number of markers on each process at loading stage. """ if self.mpi_rank == 0: @@ -1349,7 +1338,7 @@ def _load_external( file.close() else: - recvbuf = xp.zeros( + recvbuf = np.zeros( (n_mks_load_loc, self.markers.shape[1]), dtype=float, ) @@ -1491,8 +1480,8 @@ def draw_markers( self.update_ghost_particles() # cumulative sum of number of markers on each process at loading stage. - n_mks_load_cum_sum = xp.cumsum(self.n_mks_load) - Np_per_clone_cum_sum = xp.cumsum(self.Np_per_clone) + n_mks_load_cum_sum = np.cumsum(self.n_mks_load) + Np_per_clone_cum_sum = np.cumsum(self.Np_per_clone) _first_marker_id = (Np_per_clone_cum_sum - self.Np_per_clone)[self.clone_id] + ( n_mks_load_cum_sum - self.n_mks_load )[self._mpi_rank] @@ -1520,9 +1509,9 @@ def draw_markers( self._load_tesselation() if self.type == "sph": self._set_initial_condition() - self.velocities = xp.array(self.u_init(self.positions)[0]).T + self.velocities = np.array(self.u_init(self.positions)[0]).T # set markers ID in last column - self.marker_ids = _first_marker_id + xp.arange(n_mks_load_loc, dtype=float) + self.marker_ids = _first_marker_id + np.arange(n_mks_load_loc, dtype=float) else: if self.mpi_rank == 0 and verbose: print("\nLoading fresh markers:") @@ -1534,7 +1523,7 @@ def draw_markers( # set seed _seed = self.loading_params.seed if _seed is not None: - xp.random.seed(_seed) + np.random.seed(_seed) # counting integers num_loaded_particles_loc = 0 # number of particles alreday loaded (local) @@ -1545,15 +1534,15 @@ def draw_markers( while num_loaded_particles_glob < int(self.Np): # Generate a chunk of random particles num_to_add_glob = min(chunk_size, int(self.Np) - num_loaded_particles_glob) - temp = xp.random.rand(num_to_add_glob, 3 + self.vdim) + temp = np.random.rand(num_to_add_glob, 3 + self.vdim) # check which particles are on the current process domain - is_on_proc_domain = xp.logical_and( + is_on_proc_domain = np.logical_and( temp[:, :3] > self.domain_array[self.mpi_rank, 0::3], temp[:, :3] < self.domain_array[self.mpi_rank, 1::3], ) - valid_idx = xp.nonzero(xp.all(is_on_proc_domain, axis=1))[0] + valid_idx = np.nonzero(np.all(is_on_proc_domain, axis=1))[0] valid_particles = temp[valid_idx] - valid_particles = xp.array_split(valid_particles, self.num_clones)[self.clone_id] + valid_particles = np.array_split(valid_particles, self.num_clones)[self.clone_id] num_valid = valid_particles.shape[0] # Add the valid particles to the phasespace_coords array @@ -1565,12 +1554,12 @@ def draw_markers( num_loaded_particles_loc += num_valid # make sure all particles are loaded - assert self.Np == int(num_loaded_particles_glob), f"{self.Np =}, {int(num_loaded_particles_glob) =}" + assert self.Np == int(num_loaded_particles_glob), f"{self.Np = }, {int(num_loaded_particles_glob) = }" # set new n_mks_load self._gather_scalar_in_subcomm_array(num_loaded_particles_loc, out=self.n_mks_load) n_mks_load_loc = self.n_mks_load[self.mpi_rank] - n_mks_load_cum_sum = xp.cumsum(self.n_mks_load) + n_mks_load_cum_sum = np.cumsum(self.n_mks_load) # set new holes in markers array to -1 self._markers[num_loaded_particles_loc:] = -1.0 @@ -1610,11 +1599,11 @@ def draw_markers( # initial velocities - SPH case: v(0) = u(x(0)) for given velocity u(x) if self.type == "sph": self._set_initial_condition() - self.velocities = xp.array(self.u_init(self.positions)[0]).T + self.velocities = np.array(self.u_init(self.positions)[0]).T else: # inverse transform sampling in velocity space - u_mean = xp.array(self.loading_params.moments[: self.vdim]) - v_th = xp.array(self.loading_params.moments[self.vdim :]) + u_mean = np.array(self.loading_params.moments[: self.vdim]) + v_th = np.array(self.loading_params.moments[self.vdim :]) # Particles6D: (1d Maxwellian, 1d Maxwellian, 1d Maxwellian) if self.vdim == 3: @@ -1622,7 +1611,7 @@ def draw_markers( sp.erfinv( 2 * self.velocities - 1, ) - * xp.sqrt(2) + * np.sqrt(2) * v_th + u_mean ) @@ -1632,16 +1621,16 @@ def draw_markers( sp.erfinv( 2 * self.velocities[:, 0] - 1, ) - * xp.sqrt(2) + * np.sqrt(2) * v_th[0] + u_mean[0] ) self._markers[:n_mks_load_loc, 4] = ( - xp.sqrt( - -1 * xp.log(1 - self.velocities[:, 1]), + np.sqrt( + -1 * np.log(1 - self.velocities[:, 1]), ) - * xp.sqrt(2) + * np.sqrt(2) * v_th[1] + u_mean[1] ) @@ -1654,13 +1643,13 @@ def draw_markers( # inversion method for drawing uniformly on the disc if self.spatial == "disc": - self._markers[:n_mks_load_loc, 0] = xp.sqrt( + self._markers[:n_mks_load_loc, 0] = np.sqrt( self._markers[:n_mks_load_loc, 0], ) else: assert self.spatial == "uniform", f'Spatial drawing must be "uniform" or "disc", is {self.spatial}.' - self.marker_ids = _first_marker_id + xp.arange(n_mks_load_loc, dtype=float) + self.marker_ids = _first_marker_id + np.arange(n_mks_load_loc, dtype=float) # set specific initial condition for some particles if self.loading_params.specific_markers is not None: @@ -1681,14 +1670,13 @@ def draw_markers( # check if all particle positions are inside the unit cube [0, 1]^3 n_mks_load_loc = self.n_mks_load[self._mpi_rank] - assert xp.all(~self.holes[:n_mks_load_loc]) - assert xp.all(self.holes[n_mks_load_loc:]) + assert np.all(~self.holes[:n_mks_load_loc]) + assert np.all(self.holes[n_mks_load_loc:]) if self._initialized_sorting and sort: if self.mpi_rank == 0 and verbose: print("Sorting the markers after initial draw") - if self.mpi_comm is not None: - self.mpi_sort_markers() + self.mpi_sort_markers() self.do_sort() @profile @@ -1726,7 +1714,7 @@ def mpi_sort_markers( if remove_ghost: self.remove_ghost_particles() - self._Barrier() + self.mpi_comm.Barrier() # before sorting, apply kinetic bc if apply_bc: @@ -1755,8 +1743,8 @@ def mpi_sort_markers( # check if all markers are on the right process after sorting if do_test: - all_on_right_proc = xp.all( - xp.logical_and( + all_on_right_proc = np.all( + np.logical_and( self.positions > self.domain_array[self.mpi_rank, 0::3], self.positions < self.domain_array[self.mpi_rank, 1::3], ), @@ -1765,7 +1753,7 @@ def mpi_sort_markers( assert all_on_right_proc # assert self.phasespace_coords.size > 0, f'No particles on process {self.mpi_rank}, please rebalance, aborting ...' - self._Barrier() + self.mpi_comm.Barrier() def initialize_weights( self, @@ -1843,7 +1831,7 @@ def initialize_weights( self.update_holes() self.reset_marker_ids() print( - f"\nWeights < {self.threshold} have been rejected, number of valid markers on process {self.mpi_rank} is {self.n_mks_loc}.", + f"\nWeights < {self.threshold} have been rejected, number of valid markers on process {self.mpi_rank} is {self.n_mks_loc}." ) # compute (time-dependent) weights at vdim + 3 @@ -1880,18 +1868,18 @@ def update_weights(self): def reset_marker_ids(self): """Reset the marker ids (last column in marker array) according to the current distribution of particles. The first marker on rank 0 gets the id '0', the last marker on the last rank gets the id 'n_mks_global - 1'.""" - n_mks_proc_cumsum = xp.cumsum(self.n_mks_on_each_proc) - n_mks_clone_cumsum = xp.cumsum(self.n_mks_on_each_clone) + n_mks_proc_cumsum = np.cumsum(self.n_mks_on_each_proc) + n_mks_clone_cumsum = np.cumsum(self.n_mks_on_each_clone) first_marker_id = (n_mks_clone_cumsum - self.n_mks_on_each_clone)[self.clone_id] + ( n_mks_proc_cumsum - self.n_mks_on_each_proc )[self.mpi_rank] - self.marker_ids = first_marker_id + xp.arange(self.n_mks_loc, dtype=int) + self.marker_ids = first_marker_id + np.arange(self.n_mks_loc, dtype=int) @profile def binning( self, components: tuple[bool], - bin_edges: tuple[xp.ndarray], + bin_edges: tuple[np.ndarray], divide_by_jac: bool = True, ): r"""Computes full-f and delta-f distribution functions via marker binning in logical space. @@ -1917,7 +1905,7 @@ def binning( The reconstructed delta-f distribution function. """ - assert xp.count_nonzero(components) == len(bin_edges) + assert np.count_nonzero(components) == len(bin_edges) # volume of a bin bin_vol = 1.0 @@ -1939,13 +1927,13 @@ def binning( _weights0 /= self.domain.jacobian_det(self.positions, remove_outside=False) # _weights0 /= self.velocity_jacobian_det(*self.phasespace_coords.T) - f_slice = xp.histogramdd( + f_slice = np.histogramdd( self.markers_wo_holes_and_ghost[:, slicing], bins=bin_edges, weights=_weights0, )[0] - df_slice = xp.histogramdd( + df_slice = np.histogramdd( self.markers_wo_holes_and_ghost[:, slicing], bins=bin_edges, weights=_weights, @@ -1972,7 +1960,7 @@ def show_distribution_function(self, components, bin_edges): import matplotlib.pyplot as plt - n_dim = xp.count_nonzero(components) + n_dim = np.count_nonzero(components) assert n_dim == 1 or n_dim == 2, f"Distribution function can only be shown in 1D or 2D slices, not {n_dim}." @@ -1988,7 +1976,7 @@ def show_distribution_function(self, components, bin_edges): 4: "$v_2$", 5: "$v_3$", } - indices = xp.nonzero(components)[0] + indices = np.nonzero(components)[0] if n_dim == 1: plt.plot(bin_centers[0], f_slice) @@ -2012,13 +2000,13 @@ def _find_outside_particles(self, axis): self._is_outside_left[self.holes] = False self._is_outside_left[self.ghost_particles] = False - self._is_outside[:] = xp.logical_or( + self._is_outside[:] = np.logical_or( self._is_outside_right, self._is_outside_left, ) # indices or particles that are outside of the logical unit cube - outside_inds = xp.nonzero(self._is_outside)[0] + outside_inds = np.nonzero(self._is_outside)[0] return outside_inds @@ -2045,7 +2033,7 @@ def apply_kinetic_bc(self, newton=False): self.particle_refilling() self._markers[self._is_outside, :-1] = -1.0 - self._n_lost_markers += len(xp.nonzero(self._is_outside)[0]) + self._n_lost_markers += len(np.nonzero(self._is_outside)[0]) for axis in self._periodic_axes: outside_inds = self._find_outside_particles(axis) @@ -2056,8 +2044,8 @@ def apply_kinetic_bc(self, newton=False): self.markers[outside_inds, axis] = self.markers[outside_inds, axis] % 1.0 # set shift for alpha-weighted mid-point computation - outside_right_inds = xp.nonzero(self._is_outside_right)[0] - outside_left_inds = xp.nonzero(self._is_outside_left)[0] + outside_right_inds = np.nonzero(self._is_outside_right)[0] + outside_left_inds = np.nonzero(self._is_outside_left)[0] if newton: self.markers[ outside_right_inds, @@ -2125,12 +2113,12 @@ def particle_refilling(self): for kind in self.bc_refill: # sorting out particles which are out of the domain if kind == "inner": - outside_inds = xp.nonzero(self._is_outside_left)[0] + outside_inds = np.nonzero(self._is_outside_left)[0] self.markers[outside_inds, 0] = 1e-4 r_loss = self.domain.params["a1"] else: - outside_inds = xp.nonzero(self._is_outside_right)[0] + outside_inds = np.nonzero(self._is_outside_right)[0] self.markers[outside_inds, 0] = 1 - 1e-4 r_loss = 1.0 @@ -2179,12 +2167,12 @@ def gyro_transfer(self, outside_inds): Parameters ---------- - outside_inds : xp.array (int) + outside_inds : np.array (int) An array of indices of particles which are outside of the domain. Returns ------- - out : xp.array (bool) + out : np.array (bool) An array of indices of particles where its guiding centers are outside of the domain. """ @@ -2201,18 +2189,18 @@ def gyro_transfer(self, outside_inds): b_cart, xyz = self.equil.b_cart(self.markers[outside_inds, :]) # calculate magnetic field amplitude and normalized magnetic field - absB0 = xp.sqrt(b_cart[0] ** 2 + b_cart[1] ** 2 + b_cart[2] ** 2) + absB0 = np.sqrt(b_cart[0] ** 2 + b_cart[1] ** 2 + b_cart[2] ** 2) norm_b_cart = b_cart / absB0 # calculate parallel and perpendicular velocities - v_parallel = xp.einsum("ij,ij->j", v, norm_b_cart) - v_perp = xp.cross(norm_b_cart, xp.cross(v, norm_b_cart, axis=0), axis=0) - v_perp_square = xp.sqrt(v_perp[0] ** 2 + v_perp[1] ** 2 + v_perp[2] ** 2) + v_parallel = np.einsum("ij,ij->j", v, norm_b_cart) + v_perp = np.cross(norm_b_cart, np.cross(v, norm_b_cart, axis=0), axis=0) + v_perp_square = np.sqrt(v_perp[0] ** 2 + v_perp[1] ** 2 + v_perp[2] ** 2) - assert xp.all(xp.isclose(v_perp, v - norm_b_cart * v_parallel)) + assert np.all(np.isclose(v_perp, v - norm_b_cart * v_parallel)) # calculate Larmor radius - Larmor_r = xp.cross(norm_b_cart, v_perp, axis=0) / absB0 * self._epsilon + Larmor_r = np.cross(norm_b_cart, v_perp, axis=0) / absB0 * self._epsilon # transform cartesian coordinates to logical coordinates # TODO: currently only possible with the geomoetry where its inverse map is defined. @@ -2231,17 +2219,17 @@ def gyro_transfer(self, outside_inds): b_cart = self.equil.b_cart(self.markers[outside_inds, :])[0] # calculate magnetic field amplitude and normalized magnetic field - absB0 = xp.sqrt(b_cart[0] ** 2 + b_cart[1] ** 2 + b_cart[2] ** 2) + absB0 = np.sqrt(b_cart[0] ** 2 + b_cart[1] ** 2 + b_cart[2] ** 2) norm_b_cart = b_cart / absB0 Larmor_r = new_xyz - xyz - Larmor_r /= xp.sqrt(Larmor_r[0] ** 2 + Larmor_r[1] ** 2 + Larmor_r[2] ** 2) + Larmor_r /= np.sqrt(Larmor_r[0] ** 2 + Larmor_r[1] ** 2 + Larmor_r[2] ** 2) - new_v_perp = xp.cross(Larmor_r, norm_b_cart, axis=0) * v_perp_square + new_v_perp = np.cross(Larmor_r, norm_b_cart, axis=0) * v_perp_square self.markers[outside_inds, 3:6] = (norm_b_cart * v_parallel).T + new_v_perp.T - return xp.logical_and(1.0 > gc_etas[0], gc_etas[0] > 0.0) + return np.logical_and(1.0 > gc_etas[0], gc_etas[0] > 0.0) class SortingBoxes: """Boxes used for the sorting of the particles. @@ -2424,26 +2412,26 @@ def _set_boxes(self): n_particles = self._markers_shape[0] n_mkr = int(n_particles / n_box_in) + 1 n_cols = round( - n_mkr * (1 + 1 / xp.sqrt(n_mkr) + self._box_bufsize), + n_mkr * (1 + 1 / np.sqrt(n_mkr) + self._box_bufsize), ) # cartesian boxes - self._boxes = xp.zeros((self._n_boxes + 1, n_cols), dtype=int) + self._boxes = np.zeros((self._n_boxes + 1, n_cols), dtype=int) # TODO: there is still a bug here # the row number in self._boxes should not be n_boxes + 1; this is just a temporary fix to avoid an error that I dont understand. # Must be fixed soon! - self._next_index = xp.zeros((self._n_boxes + 1), dtype=int) - self._cumul_next_index = xp.zeros((self._n_boxes + 2), dtype=int) - self._neighbours = xp.zeros((self._n_boxes, 27), dtype=int) + self._next_index = np.zeros((self._n_boxes + 1), dtype=int) + self._cumul_next_index = np.zeros((self._n_boxes + 2), dtype=int) + self._neighbours = np.zeros((self._n_boxes, 27), dtype=int) # A particle on box i only sees particles in boxes that belong to neighbours[i] initialize_neighbours(self._neighbours, self.nx, self.ny, self.nz) # print(f"{self._rank = }\n{self._neighbours = }") - self._swap_line_1 = xp.zeros(self._markers_shape[1]) - self._swap_line_2 = xp.zeros(self._markers_shape[1]) + self._swap_line_1 = np.zeros(self._markers_shape[1]) + self._swap_line_2 = np.zeros(self._markers_shape[1]) def _set_boundary_boxes(self): """Gather all the boxes that are part of a boundary""" @@ -2464,7 +2452,7 @@ def _set_boundary_boxes(self): self._bnd_boxes_x_p.append(flatten_index(self.nx, j, k, self.nx, self.ny, self.nz)) if self._verbose: - print(f"eta1 boundary on {self._rank =}:\n{self._bnd_boxes_x_m =}\n{self._bnd_boxes_x_p =}") + print(f"eta1 boundary on {self._rank = }:\n{self._bnd_boxes_x_m = }\n{self._bnd_boxes_x_p = }") # y boundary # negative direction @@ -2479,7 +2467,7 @@ def _set_boundary_boxes(self): self._bnd_boxes_y_p.append(flatten_index(i, self.ny, k, self.nx, self.ny, self.nz)) if self._verbose: - print(f"eta2 boundary on {self._rank =}:\n{self._bnd_boxes_y_m =}\n{self._bnd_boxes_y_p =}") + print(f"eta2 boundary on {self._rank = }:\n{self._bnd_boxes_y_m = }\n{self._bnd_boxes_y_p = }") # z boundary # negative direction @@ -2494,7 +2482,7 @@ def _set_boundary_boxes(self): self._bnd_boxes_z_p.append(flatten_index(i, j, self.nz, self.nx, self.ny, self.nz)) if self._verbose: - print(f"eta3 boundary on {self._rank =}:\n{self._bnd_boxes_z_m =}\n{self._bnd_boxes_z_p =}") + print(f"eta3 boundary on {self._rank = }:\n{self._bnd_boxes_z_m = }\n{self._bnd_boxes_z_p = }") # x-y edges self._bnd_boxes_x_m_y_m = [] @@ -2512,11 +2500,11 @@ def _set_boundary_boxes(self): if self._verbose: print( ( - f"eta1-eta2 edge on {self._rank =}:\n{self._bnd_boxes_x_m_y_m =}" - f"\n{self._bnd_boxes_x_m_y_p =}" - f"\n{self._bnd_boxes_x_p_y_m =}" - f"\n{self._bnd_boxes_x_p_y_p =}" - ), + f"eta1-eta2 edge on {self._rank = }:\n{self._bnd_boxes_x_m_y_m = }" + f"\n{self._bnd_boxes_x_m_y_p = }" + f"\n{self._bnd_boxes_x_p_y_m = }" + f"\n{self._bnd_boxes_x_p_y_p = }" + ) ) # x-z edges @@ -2535,11 +2523,11 @@ def _set_boundary_boxes(self): if self._verbose: print( ( - f"eta1-eta3 edge on {self._rank =}:\n{self._bnd_boxes_x_m_z_m =}" - f"\n{self._bnd_boxes_x_m_z_p =}" - f"\n{self._bnd_boxes_x_p_z_m =}" - f"\n{self._bnd_boxes_x_p_z_p =}" - ), + f"eta1-eta3 edge on {self._rank = }:\n{self._bnd_boxes_x_m_z_m = }" + f"\n{self._bnd_boxes_x_m_z_p = }" + f"\n{self._bnd_boxes_x_p_z_m = }" + f"\n{self._bnd_boxes_x_p_z_p = }" + ) ) # y-z edges @@ -2558,11 +2546,11 @@ def _set_boundary_boxes(self): if self._verbose: print( ( - f"eta2-eta3 edge on {self._rank =}:\n{self._bnd_boxes_y_m_z_m =}" - f"\n{self._bnd_boxes_y_m_z_p =}" - f"\n{self._bnd_boxes_y_p_z_m =}" - f"\n{self._bnd_boxes_y_p_z_p =}" - ), + f"eta2-eta3 edge on {self._rank = }:\n{self._bnd_boxes_y_m_z_m = }" + f"\n{self._bnd_boxes_y_m_z_p = }" + f"\n{self._bnd_boxes_y_p_z_m = }" + f"\n{self._bnd_boxes_y_p_z_p = }" + ) ) # corners @@ -2588,15 +2576,15 @@ def _set_boundary_boxes(self): if self._verbose: print( ( - f"corners on {self._rank =}:\n{self._bnd_boxes_x_m_y_m_z_m =}" - f"\n{self._bnd_boxes_x_m_y_m_z_p =}" - f"\n{self._bnd_boxes_x_m_y_p_z_m =}" - f"\n{self._bnd_boxes_x_p_y_m_z_m =}" - f"\n{self._bnd_boxes_x_m_y_p_z_p =}" - f"\n{self._bnd_boxes_x_p_y_m_z_p =}" - f"\n{self._bnd_boxes_x_p_y_p_z_m =}" - f"\n{self._bnd_boxes_x_p_y_p_z_p =}" - ), + f"corners on {self._rank = }:\n{self._bnd_boxes_x_m_y_m_z_m = }" + f"\n{self._bnd_boxes_x_m_y_m_z_p = }" + f"\n{self._bnd_boxes_x_m_y_p_z_m = }" + f"\n{self._bnd_boxes_x_p_y_m_z_m = }" + f"\n{self._bnd_boxes_x_m_y_p_z_p = }" + f"\n{self._bnd_boxes_x_p_y_m_z_p = }" + f"\n{self._bnd_boxes_x_p_y_p_z_m = }" + f"\n{self._bnd_boxes_x_p_y_p_z_p = }" + ) ) def _sort_boxed_particles_numpy(self): @@ -2604,7 +2592,7 @@ def _sort_boxed_particles_numpy(self): sorting_axis = self._sorting_boxes.box_index if not hasattr(self, "_argsort_array"): - self._argsort_array = xp.zeros(self.markers.shape[0], dtype=int) + self._argsort_array = np.zeros(self.markers.shape[0], dtype=int) self._argsort_array[:] = self._markers[:, sorting_axis].argsort() self._markers[:, :] = self._markers[self._argsort_array] @@ -2633,24 +2621,24 @@ def put_particles_in_boxes(self): self.update_ghost_particles() # if self.verbose: - # valid_box_ids = xp.nonzero(self._sorting_boxes._boxes[:, 0] != -1)[0] + # valid_box_ids = np.nonzero(self._sorting_boxes._boxes[:, 0] != -1)[0] # print(f"Boxes holding at least one particle: {valid_box_ids}") # for i in valid_box_ids: - # n_mks_box = xp.count_nonzero(self._sorting_boxes._boxes[i] != -1) + # n_mks_box = np.count_nonzero(self._sorting_boxes._boxes[i] != -1) # print(f"Number of markers in box {i} is {n_mks_box}") def check_and_assign_particles_to_boxes(self): """Check whether the box array has enough columns (detect load imbalance wrt to sorting boxes), and then assigne the particles to boxes.""" - bcount = xp.bincount(xp.int64(self.markers_wo_holes[:, -2])) - max_in_box = xp.max(bcount) + bcount = np.bincount(np.int64(self.markers_wo_holes[:, -2])) + max_in_box = np.max(bcount) if max_in_box > self._sorting_boxes.boxes.shape[1]: warnings.warn( f'Strong load imbalance detected in sorting boxes: \ max number of markers in a box ({max_in_box}) on rank {self.mpi_rank} \ exceeds the column-size of the box array ({self._sorting_boxes.boxes.shape[1]}). \ -Increasing the value of "box_bufsize" in the markers parameters for the next run.', +Increasing the value of "box_bufsize" in the markers parameters for the next run.' ) self.mpi_comm.Abort() @@ -2688,7 +2676,7 @@ def do_sort(self, use_numpy_argsort=False): def remove_ghost_particles(self): self.update_ghost_particles() - new_holes = xp.nonzero(self.ghost_particles) + new_holes = np.nonzero(self.ghost_particles) self._markers[new_holes] = -1.0 self.update_holes() @@ -2734,23 +2722,17 @@ def prepare_ghost_particles(self): # Mirror position for boundary condition if self.bc_sph[0] in ("mirror", "fixed"): self._mirror_particles( - "_markers_x_m", - "_markers_x_p", - is_domain_boundary=self.sorting_boxes.is_domain_boundary, + "_markers_x_m", "_markers_x_p", is_domain_boundary=self.sorting_boxes.is_domain_boundary ) if self.bc_sph[1] in ("mirror", "fixed"): self._mirror_particles( - "_markers_y_m", - "_markers_y_p", - is_domain_boundary=self.sorting_boxes.is_domain_boundary, + "_markers_y_m", "_markers_y_p", is_domain_boundary=self.sorting_boxes.is_domain_boundary ) if self.bc_sph[2] in ("mirror", "fixed"): self._mirror_particles( - "_markers_z_m", - "_markers_z_p", - is_domain_boundary=self.sorting_boxes.is_domain_boundary, + "_markers_z_m", "_markers_z_p", is_domain_boundary=self.sorting_boxes.is_domain_boundary ) ## Edges x-y @@ -2905,8 +2887,7 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 0] *= -1.0 if self.bc_sph[0] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, - flat_eval=True, + *arr[:, :3].T, flat_eval=True ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2918,8 +2899,7 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 0] = 2.0 - arr[:, 0] if self.bc_sph[0] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, - flat_eval=True, + *arr[:, :3].T, flat_eval=True ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2934,8 +2914,7 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 1] *= -1.0 if self.bc_sph[1] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, - flat_eval=True, + *arr[:, :3].T, flat_eval=True ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2947,8 +2926,7 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 1] = 2.0 - arr[:, 1] if self.bc_sph[1] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, - flat_eval=True, + *arr[:, :3].T, flat_eval=True ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2963,8 +2941,7 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 2] *= -1.0 if self.bc_sph[2] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, - flat_eval=True, + *arr[:, :3].T, flat_eval=True ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2976,8 +2953,7 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 2] = 2.0 - arr[:, 2] if self.bc_sph[2] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, - flat_eval=True, + *arr[:, :3].T, flat_eval=True ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2992,162 +2968,162 @@ def determine_markers_in_box(self, list_boxes): for i in list_boxes: indices += list(self._sorting_boxes._boxes[i][self._sorting_boxes._boxes[i] != -1]) - indices = xp.array(indices, dtype=int) + indices = np.array(indices, dtype=int) markers_in_box = self.markers[indices] return markers_in_box def get_destinations_box(self): """Find the destination proc for the particles to communicate for the box structure.""" - self._send_info_box = xp.zeros(self.mpi_size, dtype=int) - self._send_list_box = [xp.zeros((0, self.n_cols))] * self.mpi_size + self._send_info_box = np.zeros(self.mpi_size, dtype=int) + self._send_list_box = [np.zeros((0, self.n_cols))] * self.mpi_size # Faces # if self._x_m_proc is not None: self._send_info_box[self._x_m_proc] += len(self._markers_x_m) - self._send_list_box[self._x_m_proc] = xp.concatenate((self._send_list_box[self._x_m_proc], self._markers_x_m)) + self._send_list_box[self._x_m_proc] = np.concatenate((self._send_list_box[self._x_m_proc], self._markers_x_m)) # if self._x_p_proc is not None: self._send_info_box[self._x_p_proc] += len(self._markers_x_p) - self._send_list_box[self._x_p_proc] = xp.concatenate((self._send_list_box[self._x_p_proc], self._markers_x_p)) + self._send_list_box[self._x_p_proc] = np.concatenate((self._send_list_box[self._x_p_proc], self._markers_x_p)) # if self._y_m_proc is not None: self._send_info_box[self._y_m_proc] += len(self._markers_y_m) - self._send_list_box[self._y_m_proc] = xp.concatenate((self._send_list_box[self._y_m_proc], self._markers_y_m)) + self._send_list_box[self._y_m_proc] = np.concatenate((self._send_list_box[self._y_m_proc], self._markers_y_m)) # if self._y_p_proc is not None: self._send_info_box[self._y_p_proc] += len(self._markers_y_p) - self._send_list_box[self._y_p_proc] = xp.concatenate((self._send_list_box[self._y_p_proc], self._markers_y_p)) + self._send_list_box[self._y_p_proc] = np.concatenate((self._send_list_box[self._y_p_proc], self._markers_y_p)) # if self._z_m_proc is not None: self._send_info_box[self._z_m_proc] += len(self._markers_z_m) - self._send_list_box[self._z_m_proc] = xp.concatenate((self._send_list_box[self._z_m_proc], self._markers_z_m)) + self._send_list_box[self._z_m_proc] = np.concatenate((self._send_list_box[self._z_m_proc], self._markers_z_m)) # if self._z_p_proc is not None: self._send_info_box[self._z_p_proc] += len(self._markers_z_p) - self._send_list_box[self._z_p_proc] = xp.concatenate((self._send_list_box[self._z_p_proc], self._markers_z_p)) + self._send_list_box[self._z_p_proc] = np.concatenate((self._send_list_box[self._z_p_proc], self._markers_z_p)) # x-y edges # if self._x_m_y_m_proc is not None: self._send_info_box[self._x_m_y_m_proc] += len(self._markers_x_m_y_m) - self._send_list_box[self._x_m_y_m_proc] = xp.concatenate( - (self._send_list_box[self._x_m_y_m_proc], self._markers_x_m_y_m), + self._send_list_box[self._x_m_y_m_proc] = np.concatenate( + (self._send_list_box[self._x_m_y_m_proc], self._markers_x_m_y_m) ) # if self._x_m_y_p_proc is not None: self._send_info_box[self._x_m_y_p_proc] += len(self._markers_x_m_y_p) - self._send_list_box[self._x_m_y_p_proc] = xp.concatenate( - (self._send_list_box[self._x_m_y_p_proc], self._markers_x_m_y_p), + self._send_list_box[self._x_m_y_p_proc] = np.concatenate( + (self._send_list_box[self._x_m_y_p_proc], self._markers_x_m_y_p) ) # if self._x_p_y_m_proc is not None: self._send_info_box[self._x_p_y_m_proc] += len(self._markers_x_p_y_m) - self._send_list_box[self._x_p_y_m_proc] = xp.concatenate( - (self._send_list_box[self._x_p_y_m_proc], self._markers_x_p_y_m), + self._send_list_box[self._x_p_y_m_proc] = np.concatenate( + (self._send_list_box[self._x_p_y_m_proc], self._markers_x_p_y_m) ) # if self._x_p_y_p_proc is not None: self._send_info_box[self._x_p_y_p_proc] += len(self._markers_x_p_y_p) - self._send_list_box[self._x_p_y_p_proc] = xp.concatenate( - (self._send_list_box[self._x_p_y_p_proc], self._markers_x_p_y_p), + self._send_list_box[self._x_p_y_p_proc] = np.concatenate( + (self._send_list_box[self._x_p_y_p_proc], self._markers_x_p_y_p) ) # x-z edges # if self._x_m_z_m_proc is not None: self._send_info_box[self._x_m_z_m_proc] += len(self._markers_x_m_z_m) - self._send_list_box[self._x_m_z_m_proc] = xp.concatenate( - (self._send_list_box[self._x_m_z_m_proc], self._markers_x_m_z_m), + self._send_list_box[self._x_m_z_m_proc] = np.concatenate( + (self._send_list_box[self._x_m_z_m_proc], self._markers_x_m_z_m) ) # if self._x_m_z_p_proc is not None: self._send_info_box[self._x_m_z_p_proc] += len(self._markers_x_m_z_p) - self._send_list_box[self._x_m_z_p_proc] = xp.concatenate( - (self._send_list_box[self._x_m_z_p_proc], self._markers_x_m_z_p), + self._send_list_box[self._x_m_z_p_proc] = np.concatenate( + (self._send_list_box[self._x_m_z_p_proc], self._markers_x_m_z_p) ) # if self._x_p_z_m_proc is not None: self._send_info_box[self._x_p_z_m_proc] += len(self._markers_x_p_z_m) - self._send_list_box[self._x_p_z_m_proc] = xp.concatenate( - (self._send_list_box[self._x_p_z_m_proc], self._markers_x_p_z_m), + self._send_list_box[self._x_p_z_m_proc] = np.concatenate( + (self._send_list_box[self._x_p_z_m_proc], self._markers_x_p_z_m) ) # if self._x_p_z_p_proc is not None: self._send_info_box[self._x_p_z_p_proc] += len(self._markers_x_p_z_p) - self._send_list_box[self._x_p_z_p_proc] = xp.concatenate( - (self._send_list_box[self._x_p_z_p_proc], self._markers_x_p_z_p), + self._send_list_box[self._x_p_z_p_proc] = np.concatenate( + (self._send_list_box[self._x_p_z_p_proc], self._markers_x_p_z_p) ) # y-z edges # if self._y_m_z_m_proc is not None: self._send_info_box[self._y_m_z_m_proc] += len(self._markers_y_m_z_m) - self._send_list_box[self._y_m_z_m_proc] = xp.concatenate( - (self._send_list_box[self._y_m_z_m_proc], self._markers_y_m_z_m), + self._send_list_box[self._y_m_z_m_proc] = np.concatenate( + (self._send_list_box[self._y_m_z_m_proc], self._markers_y_m_z_m) ) # if self._y_m_z_p_proc is not None: self._send_info_box[self._y_m_z_p_proc] += len(self._markers_y_m_z_p) - self._send_list_box[self._y_m_z_p_proc] = xp.concatenate( - (self._send_list_box[self._y_m_z_p_proc], self._markers_y_m_z_p), + self._send_list_box[self._y_m_z_p_proc] = np.concatenate( + (self._send_list_box[self._y_m_z_p_proc], self._markers_y_m_z_p) ) # if self._y_p_z_m_proc is not None: self._send_info_box[self._y_p_z_m_proc] += len(self._markers_y_p_z_m) - self._send_list_box[self._y_p_z_m_proc] = xp.concatenate( - (self._send_list_box[self._y_p_z_m_proc], self._markers_y_p_z_m), + self._send_list_box[self._y_p_z_m_proc] = np.concatenate( + (self._send_list_box[self._y_p_z_m_proc], self._markers_y_p_z_m) ) # if self._y_p_z_p_proc is not None: self._send_info_box[self._y_p_z_p_proc] += len(self._markers_y_p_z_p) - self._send_list_box[self._y_p_z_p_proc] = xp.concatenate( - (self._send_list_box[self._y_p_z_p_proc], self._markers_y_p_z_p), + self._send_list_box[self._y_p_z_p_proc] = np.concatenate( + (self._send_list_box[self._y_p_z_p_proc], self._markers_y_p_z_p) ) # corners # if self._x_m_y_m_z_m_proc is not None: self._send_info_box[self._x_m_y_m_z_m_proc] += len(self._markers_x_m_y_m_z_m) - self._send_list_box[self._x_m_y_m_z_m_proc] = xp.concatenate( - (self._send_list_box[self._x_m_y_m_z_m_proc], self._markers_x_m_y_m_z_m), + self._send_list_box[self._x_m_y_m_z_m_proc] = np.concatenate( + (self._send_list_box[self._x_m_y_m_z_m_proc], self._markers_x_m_y_m_z_m) ) # if self._x_m_y_m_z_p_proc is not None: self._send_info_box[self._x_m_y_m_z_p_proc] += len(self._markers_x_m_y_m_z_p) - self._send_list_box[self._x_m_y_m_z_p_proc] = xp.concatenate( - (self._send_list_box[self._x_m_y_m_z_p_proc], self._markers_x_m_y_m_z_p), + self._send_list_box[self._x_m_y_m_z_p_proc] = np.concatenate( + (self._send_list_box[self._x_m_y_m_z_p_proc], self._markers_x_m_y_m_z_p) ) # if self._x_m_y_p_z_m_proc is not None: self._send_info_box[self._x_m_y_p_z_m_proc] += len(self._markers_x_m_y_p_z_m) - self._send_list_box[self._x_m_y_p_z_m_proc] = xp.concatenate( - (self._send_list_box[self._x_m_y_p_z_m_proc], self._markers_x_m_y_p_z_m), + self._send_list_box[self._x_m_y_p_z_m_proc] = np.concatenate( + (self._send_list_box[self._x_m_y_p_z_m_proc], self._markers_x_m_y_p_z_m) ) # if self._x_m_y_p_z_p_proc is not None: self._send_info_box[self._x_m_y_p_z_p_proc] += len(self._markers_x_m_y_p_z_p) - self._send_list_box[self._x_m_y_p_z_p_proc] = xp.concatenate( - (self._send_list_box[self._x_m_y_p_z_p_proc], self._markers_x_m_y_p_z_p), + self._send_list_box[self._x_m_y_p_z_p_proc] = np.concatenate( + (self._send_list_box[self._x_m_y_p_z_p_proc], self._markers_x_m_y_p_z_p) ) # if self._x_p_y_m_z_m_proc is not None: self._send_info_box[self._x_p_y_m_z_m_proc] += len(self._markers_x_p_y_m_z_m) - self._send_list_box[self._x_p_y_m_z_m_proc] = xp.concatenate( - (self._send_list_box[self._x_p_y_m_z_m_proc], self._markers_x_p_y_m_z_m), + self._send_list_box[self._x_p_y_m_z_m_proc] = np.concatenate( + (self._send_list_box[self._x_p_y_m_z_m_proc], self._markers_x_p_y_m_z_m) ) # if self._x_p_y_m_z_p_proc is not None: self._send_info_box[self._x_p_y_m_z_p_proc] += len(self._markers_x_p_y_m_z_p) - self._send_list_box[self._x_p_y_m_z_p_proc] = xp.concatenate( - (self._send_list_box[self._x_p_y_m_z_p_proc], self._markers_x_p_y_m_z_p), + self._send_list_box[self._x_p_y_m_z_p_proc] = np.concatenate( + (self._send_list_box[self._x_p_y_m_z_p_proc], self._markers_x_p_y_m_z_p) ) # if self._x_p_y_p_z_m_proc is not None: self._send_info_box[self._x_p_y_p_z_m_proc] += len(self._markers_x_p_y_p_z_m) - self._send_list_box[self._x_p_y_p_z_m_proc] = xp.concatenate( - (self._send_list_box[self._x_p_y_p_z_m_proc], self._markers_x_p_y_p_z_m), + self._send_list_box[self._x_p_y_p_z_m_proc] = np.concatenate( + (self._send_list_box[self._x_p_y_p_z_m_proc], self._markers_x_p_y_p_z_m) ) # if self._x_p_y_p_z_p_proc is not None: self._send_info_box[self._x_p_y_p_z_p_proc] += len(self._markers_x_p_y_p_z_p) - self._send_list_box[self._x_p_y_p_z_p_proc] = xp.concatenate( - (self._send_list_box[self._x_p_y_p_z_p_proc], self._markers_x_p_y_p_z_p), + self._send_list_box[self._x_p_y_p_z_p_proc] = np.concatenate( + (self._send_list_box[self._x_p_y_p_z_p_proc], self._markers_x_p_y_p_z_p) ) def self_communication_boxes(self): @@ -3156,14 +3132,14 @@ def self_communication_boxes(self): if self._send_info_box[self.mpi_rank] > 0: self.update_holes() - holes_inds = xp.nonzero(self.holes)[0] + holes_inds = np.nonzero(self.holes)[0] if holes_inds.size < self._send_info_box[self.mpi_rank]: warnings.warn( f'Strong load imbalance detected: \ number of holes ({holes_inds.size}) on rank {self.mpi_rank} \ is smaller than number of incoming particles ({self._send_info_box[self.mpi_rank]}). \ -Increasing the value of "bufsize" in the markers parameters for the next run.', +Increasing the value of "bufsize" in the markers parameters for the next run.' ) self.mpi_comm.Abort() @@ -3178,16 +3154,16 @@ def self_communication_boxes(self): # self.update_holes() # self.update_ghost_particles() # self.update_valid_mks() - # holes_inds = xp.nonzero(self.holes)[0] + # holes_inds = np.nonzero(self.holes)[0] - self.markers[holes_inds[xp.arange(self._send_info_box[self.mpi_rank])]] = self._send_list_box[self.mpi_rank] + self.markers[holes_inds[np.arange(self._send_info_box[self.mpi_rank])]] = self._send_list_box[self.mpi_rank] @profile def communicate_boxes(self, verbose=False): # if verbose: - # n_valid = xp.count_nonzero(self.valid_mks) - # n_holes = xp.count_nonzero(self.holes) - # n_ghosts = xp.count_nonzero(self.ghost_particles) + # n_valid = np.count_nonzero(self.valid_mks) + # n_holes = np.count_nonzero(self.holes) + # n_ghosts = np.count_nonzero(self.ghost_particles) # print(f"before communicate_boxes: {self.mpi_rank = }, {n_valid = } {n_holes = }, {n_ghosts = }") self.prepare_ghost_particles() @@ -3195,16 +3171,16 @@ def communicate_boxes(self, verbose=False): self.self_communication_boxes() self.update_holes() if self.mpi_comm is not None: - self._Barrier() + self.mpi_comm.Barrier() self.sendrecv_all_to_all_boxes() self.sendrecv_markers_boxes() self.update_holes() self.update_ghost_particles() # if verbose: - # n_valid = xp.count_nonzero(self.valid_mks) - # n_holes = xp.count_nonzero(self.holes) - # n_ghosts = xp.count_nonzero(self.ghost_particles) + # n_valid = np.count_nonzero(self.valid_mks) + # n_holes = np.count_nonzero(self.holes) + # n_ghosts = np.count_nonzero(self.ghost_particles) # print(f"after communicate_boxes: {self.mpi_rank = }, {n_valid = }, {n_holes = }, {n_ghosts = }") def sendrecv_all_to_all_boxes(self): @@ -3213,7 +3189,7 @@ def sendrecv_all_to_all_boxes(self): for the communication of particles in boundary boxes. """ - self._recv_info_box = xp.zeros(self.mpi_comm.Get_size(), dtype=int) + self._recv_info_box = np.zeros(self.mpi_comm.Get_size(), dtype=int) self.mpi_comm.Alltoall(self._send_info_box, self._recv_info_box) @@ -3224,8 +3200,8 @@ def sendrecv_markers_boxes(self): """ # i-th entry holds the number (not the index) of the first hole to be filled by data from process i - first_hole = xp.cumsum(self._recv_info_box) - self._recv_info_box - hole_inds = xp.nonzero(self._holes)[0] + first_hole = np.cumsum(self._recv_info_box) - self._recv_info_box + hole_inds = np.nonzero(self._holes)[0] # Initialize send and receive commands reqs = [] recvbufs = [] @@ -3236,7 +3212,7 @@ def sendrecv_markers_boxes(self): else: self.mpi_comm.Isend(data, dest=i, tag=self.mpi_comm.Get_rank()) - recvbufs += [xp.zeros((N_recv, self._markers.shape[1]), dtype=float)] + recvbufs += [np.zeros((N_recv, self._markers.shape[1]), dtype=float)] reqs += [self.mpi_comm.Irecv(recvbufs[-1], source=i, tag=i)] # Wait for buffer, then put markers into holes @@ -3254,17 +3230,17 @@ def sendrecv_markers_boxes(self): f'Strong load imbalance detected: \ number of holes ({hole_inds.size}) on rank {self.mpi_rank} \ is smaller than number of incoming particles ({first_hole[i] + self._recv_info_box[i]}). \ -Increasing the value of "bufsize" in the markers parameters for the next run.', +Increasing the value of "bufsize" in the markers parameters for the next run.' ) self.mpi_comm.Abort() # exit() - self._markers[hole_inds[first_hole[i] + xp.arange(self._recv_info_box[i])]] = recvbufs[i] + self._markers[hole_inds[first_hole[i] + np.arange(self._recv_info_box[i])]] = recvbufs[i] test_reqs.pop() reqs[i] = None - self._Barrier() + self.mpi_comm.Barrier() def _get_neighbouring_proc(self): """Find the neighbouring processes for the sending of boxes. @@ -3734,11 +3710,11 @@ def eval_density( def eval_sph( self, - eta1: xp.ndarray, - eta2: xp.ndarray, - eta3: xp.ndarray, + eta1: np.ndarray, + eta2: np.ndarray, + eta3: np.ndarray, index: int, - out: xp.ndarray = None, + out: np.ndarray = None, fast: bool = True, kernel_type: str = "gaussian_1d", derivative: int = "0", @@ -3784,12 +3760,12 @@ def eval_sph( h1, h2, h3 : float Radius of the smoothing kernel in each dimension. """ - _shp = xp.shape(eta1) - assert _shp == xp.shape(eta2) == xp.shape(eta3) + _shp = np.shape(eta1) + assert _shp == np.shape(eta2) == np.shape(eta3) if out is not None: - assert _shp == xp.shape(out) + assert _shp == np.shape(out) else: - out = xp.zeros_like(eta1) + out = np.zeros_like(eta1) assert derivative in {0, 1, 2, 3}, f"derivative must be 0, 1, 2 or 3, but is {derivative}." @@ -3803,7 +3779,7 @@ def eval_sph( self.put_particles_in_boxes() if len(_shp) == 1: - func = Pyccelkernel(box_based_evaluation_flat) + func = box_based_evaluation_flat elif len(_shp) == 3: if _shp[0] > 1: assert eta1[0, 0, 0] != eta1[1, 0, 0], "Meshgrids must be obtained with indexing='ij'!" @@ -3811,7 +3787,7 @@ def eval_sph( assert eta2[0, 0, 0] != eta2[0, 1, 0], "Meshgrids must be obtained with indexing='ij'!" if _shp[2] > 1: assert eta3[0, 0, 0] != eta3[0, 0, 1], "Meshgrids must be obtained with indexing='ij'!" - func = Pyccelkernel(box_based_evaluation_meshgrid) + func = box_based_evaluation_meshgrid func( self.args_markers, @@ -3837,9 +3813,9 @@ def eval_sph( ) else: if len(_shp) == 1: - func = Pyccelkernel(naive_evaluation_flat) + func = naive_evaluation_flat elif len(_shp) == 3: - func = Pyccelkernel(naive_evaluation_meshgrid) + func = naive_evaluation_meshgrid func( self.args_markers, eta1, @@ -3872,7 +3848,7 @@ def update_ghost_particles(self): def sendrecv_determine_mtbs( self, - alpha: list | tuple | xp.ndarray = (1.0, 1.0, 1.0), + alpha: list | tuple | np.ndarray = (1.0, 1.0, 1.0), ): """ Determine which markers have to be sent from current process and put them in a new array. @@ -3894,34 +3870,34 @@ def sendrecv_determine_mtbs( Eta-values of shape (n_send, :) according to which the sorting is performed. """ # position that determines the sorting (including periodic shift of boundary conditions) - if not isinstance(alpha, xp.ndarray): - alpha = xp.array(alpha, dtype=float) + if not isinstance(alpha, np.ndarray): + alpha = np.array(alpha, dtype=float) assert alpha.size == 3 - assert xp.all(alpha >= 0.0) and xp.all(alpha <= 1.0) + assert np.all(alpha >= 0.0) and np.all(alpha <= 1.0) bi = self.first_pusher_idx - self._sorting_etas = xp.mod( + self._sorting_etas = np.mod( alpha * (self.markers[:, :3] + self.markers[:, bi + 3 + self.vdim : bi + 3 + self.vdim + 3]) + (1.0 - alpha) * self.markers[:, bi : bi + 3], 1.0, ) # check which particles are on the current process domain - self._is_on_proc_domain = xp.logical_and( + self._is_on_proc_domain = np.logical_and( self._sorting_etas > self.domain_array[self.mpi_rank, 0::3], self._sorting_etas < self.domain_array[self.mpi_rank, 1::3], ) # to stay on the current process, all three columns must be True - self._can_stay = xp.all(self._is_on_proc_domain, axis=1) + self._can_stay = np.all(self._is_on_proc_domain, axis=1) # holes and ghosts can stay, too self._can_stay[self.holes] = True self._can_stay[self.ghost_particles] = True # True values can stay on the process, False must be sent, already empty rows (-1) cannot be sent - send_inds = xp.nonzero(~self._can_stay)[0] + send_inds = np.nonzero(~self._can_stay)[0] - hole_inds_after_send = xp.nonzero(xp.logical_or(~self._can_stay, self.holes))[0] + hole_inds_after_send = np.nonzero(np.logical_or(~self._can_stay, self.holes))[0] return hole_inds_after_send, send_inds @@ -3940,16 +3916,16 @@ def sendrecv_get_destinations(self, send_inds): """ # One entry for each process - send_info = xp.zeros(self.mpi_size, dtype=int) + send_info = np.zeros(self.mpi_size, dtype=int) # TODO: do not loop over all processes, start with neighbours and work outwards (using while) for i in range(self.mpi_size): - conds = xp.logical_and( + conds = np.logical_and( self._sorting_etas[send_inds] > self.domain_array[i, 0::3], self._sorting_etas[send_inds] < self.domain_array[i, 1::3], ) - self._send_to_i[i] = xp.nonzero(xp.all(conds, axis=1))[0] + self._send_to_i[i] = np.nonzero(np.all(conds, axis=1))[0] send_info[i] = self._send_to_i[i].size self._send_list[i] = self.markers[send_inds][self._send_to_i[i]] @@ -3971,7 +3947,7 @@ def sendrecv_all_to_all(self, send_info): Amount of marticles to be received from i-th process. """ - recv_info = xp.zeros(self.mpi_size, dtype=int) + recv_info = np.zeros(self.mpi_size, dtype=int) self.mpi_comm.Alltoall(send_info, recv_info) @@ -3991,7 +3967,7 @@ def sendrecv_markers(self, recv_info, hole_inds_after_send): """ # i-th entry holds the number (not the index) of the first hole to be filled by data from process i - first_hole = xp.cumsum(recv_info) - recv_info + first_hole = np.cumsum(recv_info) - recv_info # Initialize send and receive commands for i, (data, N_recv) in enumerate(zip(self._send_list, list(recv_info))): @@ -4001,7 +3977,7 @@ def sendrecv_markers(self, recv_info, hole_inds_after_send): else: self.mpi_comm.Isend(data, dest=i, tag=self.mpi_rank) - self._recvbufs[i] = xp.zeros((N_recv, self.markers.shape[1]), dtype=float) + self._recvbufs[i] = np.zeros((N_recv, self.markers.shape[1]), dtype=float) self._reqs[i] = self.mpi_comm.Irecv(self._recvbufs[i], source=i, tag=i) # Wait for buffer, then put markers into holes @@ -4019,16 +3995,16 @@ def sendrecv_markers(self, recv_info, hole_inds_after_send): f'Strong load imbalance detected: \ number of holes ({hole_inds_after_send.size}) on rank {self.mpi_rank} \ is smaller than number of incoming particles ({first_hole[i] + recv_info[i]}). \ -Increasing the value of "bufsize" in the markers parameters for the next run.', +Increasing the value of "bufsize" in the markers parameters for the next run.' ) self.mpi_comm.Abort() - self.markers[hole_inds_after_send[first_hole[i] + xp.arange(recv_info[i])]] = self._recvbufs[i] + self.markers[hole_inds_after_send[first_hole[i] + np.arange(recv_info[i])]] = self._recvbufs[i] test_reqs.pop() self._reqs[i] = None - def _gather_scalar_in_subcomm_array(self, scalar: int, out: xp.ndarray = None): + def _gather_scalar_in_subcomm_array(self, scalar: int, out: np.ndarray = None): """Return an array of length sub_comm.size, where the i-th entry corresponds to the value of the scalar on process i. @@ -4037,11 +4013,11 @@ def _gather_scalar_in_subcomm_array(self, scalar: int, out: xp.ndarray = None): scalar : int The scalar value on each process. - out : xp.ndarray + out : np.ndarray The returned array (optional). """ if out is None: - _tmp = xp.zeros(self.mpi_size, dtype=int) + _tmp = np.zeros(self.mpi_size, dtype=int) else: assert out.size == self.mpi_size _tmp = out @@ -4056,7 +4032,7 @@ def _gather_scalar_in_subcomm_array(self, scalar: int, out: xp.ndarray = None): return _tmp - def _gather_scalar_in_intercomm_array(self, scalar: int, out: xp.ndarray = None): + def _gather_scalar_in_intercomm_array(self, scalar: int, out: np.ndarray = None): """Return an array of length inter_comm.size, where the i-th entry corresponds to the value of the scalar on clone i. @@ -4065,11 +4041,11 @@ def _gather_scalar_in_intercomm_array(self, scalar: int, out: xp.ndarray = None) scalar : int The scalar value on each clone. - out : xp.ndarray + out : np.ndarray The returned array (optional). """ if out is None: - _tmp = xp.zeros(self.num_clones, dtype=int) + _tmp = np.zeros(self.num_clones, dtype=int) else: assert out.size == self.num_clones _tmp = out @@ -4098,7 +4074,7 @@ class Tesselation: comm : Intracomm MPI communicator. - domain_array : xp.ndarray + domain_array : np.ndarray A 2d array[float] of shape (comm.Get_size(), 9) holding info on the domain decomposition. sorting_boxes : Particles.SortingBoxes @@ -4110,7 +4086,7 @@ def __init__( tiles_pb: int | float, *, comm: Intracomm = None, - domain_array: xp.ndarray = None, + domain_array: np.ndarray = None, sorting_boxes: Particles.SortingBoxes = None, ): if isinstance(tiles_pb, int): @@ -4128,8 +4104,8 @@ def __init__( assert domain_array is not None if domain_array is None: - self._starts = xp.zeros(3) - self._ends = xp.ones(3) + self._starts = np.zeros(3) + self._ends = np.ones(3) else: self._starts = domain_array[self.rank, 0::3] self._ends = domain_array[self.rank, 1::3] @@ -4152,9 +4128,9 @@ def __init__( if n_boxes == 1: self._dims_mask = [True] * 3 else: - self._dims_mask = xp.array(self.boxes_per_dim) > 1 + self._dims_mask = np.array(self.boxes_per_dim) > 1 - min_tiles = 2 ** xp.count_nonzero(self.dims_mask) + min_tiles = 2 ** np.count_nonzero(self.dims_mask) assert self.tiles_pb >= min_tiles, ( f"At least {min_tiles} tiles per sorting box is enforced, but you have {self.tiles_pb}!" ) @@ -4177,19 +4153,19 @@ def get_tiles(self): # print(f'{self.dims_mask = }') # tiles in one sorting box - self._nt_per_dim = xp.array([1, 1, 1]) - _ids = xp.nonzero(self._dims_mask)[0] + self._nt_per_dim = np.array([1, 1, 1]) + _ids = np.nonzero(self._dims_mask)[0] for fac in factors_vec: _nt = self.nt_per_dim[self._dims_mask] - d = _ids[xp.argmin(_nt)] + d = _ids[np.argmin(_nt)] self._nt_per_dim[d] *= fac # print(f'{_nt = }, {d = }, {self.nt_per_dim = }') - assert xp.prod(self.nt_per_dim) == self.tiles_pb + assert np.prod(self.nt_per_dim) == self.tiles_pb # tiles between [0, box_width] in each direction - self._tile_breaks = [xp.linspace(0.0, bw, nt + 1) for bw, nt in zip(self.box_widths, self.nt_per_dim)] - self._tile_midpoints = [(xp.roll(tbs, -1)[:-1] + tbs[:-1]) / 2 for tbs in self.tile_breaks] + self._tile_breaks = [np.linspace(0.0, bw, nt + 1) for bw, nt in zip(self.box_widths, self.nt_per_dim)] + self._tile_midpoints = [(np.roll(tbs, -1)[:-1] + tbs[:-1]) / 2 for tbs in self.tile_breaks] self._tile_volume = 1.0 for tb in self.tile_breaks: self._tile_volume *= tb[1] @@ -4197,8 +4173,8 @@ def get_tiles(self): def draw_markers(self): """Draw markers on the tile midpoints.""" _, eta1 = self._tile_output_arrays() - eta2 = xp.zeros_like(eta1) - eta3 = xp.zeros_like(eta1) + eta2 = np.zeros_like(eta1) + eta3 = np.zeros_like(eta1) nt_x, nt_y, nt_z = self.nt_per_dim @@ -4209,7 +4185,7 @@ def draw_markers(self): for k in range(self.boxes_per_dim[2]): z_midpoints = self._get_midpoints(k, 2) - xx, yy, zz = xp.meshgrid( + xx, yy, zz = np.meshgrid( x_midpoints, y_midpoints, z_midpoints, @@ -4246,7 +4222,7 @@ def _get_quad_pts(self, n_quad=None): self._tile_quad_pts = [] self._tile_quad_wts = [] for nq, tb in zip(n_quad, self.tile_breaks): - pts_loc, wts_loc = xp.polynomial.legendre.leggauss(nq) + pts_loc, wts_loc = np.polynomial.legendre.leggauss(nq) pts, wts = quadrature_grid(tb[:2], pts_loc, wts_loc) self._tile_quad_pts += [pts[0]] self._tile_quad_wts += [wts[0]] @@ -4273,7 +4249,7 @@ def cell_averages(self, fun, n_quad=None): for k in range(self.boxes_per_dim[2]): z_pts = self._get_box_quad_pts(k, 2) - xx, yy, zz = xp.meshgrid( + xx, yy, zz = np.meshgrid( x_pts.flatten(), y_pts.flatten(), z_pts.flatten(), @@ -4302,9 +4278,9 @@ def _tile_output_arrays(self): * the first with one entry for each tile on one sorting box * the second with one entry for each tile on current process """ - # self._quad_pts = [xp.zeros((nt, nq)).flatten() for nt, nq in zip(self.nt_per_dim, self.tile_quad_pts)] - single_box_out = xp.zeros(self.nt_per_dim) - out = xp.tile(single_box_out, self.boxes_per_dim) + # self._quad_pts = [np.zeros((nt, nq)).flatten() for nt, nq in zip(self.nt_per_dim, self.tile_quad_pts)] + single_box_out = np.zeros(self.nt_per_dim) + out = np.tile(single_box_out, self.boxes_per_dim) return single_box_out, out def _get_midpoints(self, i: int, dim: int): @@ -4325,13 +4301,13 @@ def _get_box_quad_pts(self, i: int, dim: int): Returns ------- - x_pts : xp.array + x_pts : np.array 2d array of shape (n_tiles_pb, n_tile_quad_pts) """ xl = self.starts[dim] + i * self.box_widths[dim] x_tile_breaks = xl + self.tile_breaks[dim][:-1] x_tile_pts = self.tile_quad_pts[dim] - x_pts = xp.tile(x_tile_breaks, (x_tile_pts.size, 1)).T + x_tile_pts + x_pts = np.tile(x_tile_breaks, (x_tile_pts.size, 1)).T + x_tile_pts return x_pts @property diff --git a/src/struphy/pic/particles.py b/src/struphy/pic/particles.py index 6c818b3ee..35df68d04 100644 --- a/src/struphy/pic/particles.py +++ b/src/struphy/pic/particles.py @@ -1,6 +1,6 @@ import copy -import cunumpy as xp +import numpy as np from struphy.fields_background import equils from struphy.fields_background.base import FluidEquilibrium, FluidEquilibriumWithB @@ -142,7 +142,7 @@ def s0(self, eta1, eta2, eta3, *v, flat_eval=False, remove_holes=True): The 0-form sampling density. ------- """ - assert self.domain, "self.domain must be set to call the sampling density 0-form." + assert self.domain, f"self.domain must be set to call the sampling density 0-form." return self.domain.transform( self.svol(eta1, eta2, eta3, *v), @@ -219,11 +219,9 @@ def save_constants_of_motion(self): # send particles to the guiding center positions self.markers[~self.holes, self.first_pusher_idx : self.first_pusher_idx + 3] = self.markers[ - ~self.holes, - slice_gc, + ~self.holes, slice_gc ] - if self.mpi_comm is not None: - self.mpi_sort_markers(alpha=1) + self.mpi_sort_markers(alpha=1) utilities_kernels.eval_canonical_toroidal_moment_6d( self.markers, @@ -236,8 +234,7 @@ def save_constants_of_motion(self): ) # send back and clear buffer - if self.mpi_comm is not None: - self.mpi_sort_markers() + self.mpi_sort_markers() self.markers[~self.holes, self.first_pusher_idx : self.first_pusher_idx + 3] = 0 @@ -353,7 +350,6 @@ def __init__( self._unit_b1_h = self.projected_equil.unit_b1 self._derham = self.projected_equil.derham - self._tmp0 = self.derham.Vh["0"].zeros() self._tmp2 = self.derham.Vh["2"].zeros() @property @@ -563,7 +559,7 @@ def save_constants_of_motion(self): self.absB0_h._data, ) - def save_magnetic_energy(self, PBb): + def save_magnetic_energy(self, b2): r""" Calculate magnetic field energy at each particles' position and assign it into markers[:,self.first_diagnostics_idx]. @@ -574,17 +570,22 @@ def save_magnetic_energy(self, PBb): Finite element coefficients of the time-dependent magnetic field. """ - E0T = self.derham.extraction_ops["0"].transpose() - PBbt = E0T.dot(PBb, out=self._tmp0) - PBbt.update_ghost_regions() + E2T = self.derham.extraction_ops["2"].transpose() + b2t = E2T.dot(b2, out=self._tmp2) + b2t.update_ghost_regions() - utilities_kernels.eval_magnetic_energy_PBb( + utilities_kernels.eval_magnetic_energy( self.markers, self.derham.args_derham, self.domain.args_domain, self.first_diagnostics_idx, self.absB0_h._data, - PBbt._data, + self.unit_b1_h[0]._data, + self.unit_b1_h[1]._data, + self.unit_b1_h[2]._data, + b2t[0]._data, + b2t[1]._data, + b2t[2]._data, ) def save_magnetic_background_energy(self): @@ -646,8 +647,8 @@ class Particles3D(Particles): """ @classmethod - def default_background(cls): - return maxwellians.ColdPlasma() + def default_bckgr_params(cls): + return {"ColdPlasma": {}} def __init__( self, @@ -655,10 +656,8 @@ def __init__( ): kwargs["type"] = "full_f" - if "background" not in kwargs: - kwargs["background"] = self.default_background() - elif kwargs["background"] is None: - kwargs["background"] = self.default_background() + if "bckgr_params" not in kwargs: + kwargs["bckgr_params"] = self.default_bckgr_params() # default number of diagnostics and auxiliary columns self._n_cols_diagnostics = kwargs.pop("n_cols_diagn", 0) diff --git a/src/struphy/pic/pushing/pusher.py b/src/struphy/pic/pushing/pusher.py index 14c756b31..acaae39c2 100644 --- a/src/struphy/pic/pushing/pusher.py +++ b/src/struphy/pic/pushing/pusher.py @@ -1,13 +1,12 @@ "Accelerated particle pushing." -import cunumpy as xp +import numpy as np from line_profiler import profile -from psydac.ddm.mpi import mpi as MPI +from mpi4py.MPI import IN_PLACE, SUM from struphy.kernel_arguments.pusher_args_kernels import DerhamArguments, DomainArguments from struphy.pic.base import Particles from struphy.profiling.profiling import ProfileManager -from struphy.utils.pyccel import Pyccelkernel class Pusher: @@ -99,7 +98,7 @@ class Pusher: def __init__( self, particles: Particles, - kernel: Pyccelkernel, + kernel, args_kernel: tuple, args_domain: DomainArguments, *, @@ -113,9 +112,8 @@ def __init__( verbose: bool = False, ): self._particles = particles - assert isinstance(kernel, Pyccelkernel), f"{kernel} is not of type Pyccelkernel" self._kernel = kernel - self._newton = "newton" in kernel.name + self._newton = "newton" in kernel.__name__ self._args_kernel = args_kernel self._args_domain = args_domain @@ -134,9 +132,9 @@ def __init__( comps = ker_args[2] # check marker array column number - assert isinstance(comps, xp.ndarray) + assert isinstance(comps, np.ndarray) assert column_nr + comps.size < particles.n_cols, ( - f"{column_nr + comps.size} not smaller than {particles.n_cols =}; not enough columns in marker array !!" + f"{column_nr + comps.size} not smaller than {particles.n_cols = }; not enough columns in marker array !!" ) # prepare and check eval_kernels @@ -146,15 +144,18 @@ def __init__( comps = ker_args[3] # check marker array column number - assert isinstance(comps, xp.ndarray) + assert isinstance(comps, np.ndarray) assert column_nr + comps.size < particles.n_cols, ( - f"{column_nr + comps.size} not smaller than {particles.n_cols =}; not enough columns in marker array !!" + f"{column_nr + comps.size} not smaller than {particles.n_cols = }; not enough columns in marker array !!" ) self._init_kernels = init_kernels self._eval_kernels = eval_kernels - self._residuals = xp.zeros(self.particles.markers.shape[0]) + self._mpi_sum = SUM + self._mpi_in_place = IN_PLACE + + self._residuals = np.zeros(self.particles.markers.shape[0]) self._converged_loc = self._residuals == 1.0 self._not_converged_loc = self._residuals == 0.0 @@ -178,10 +179,10 @@ def __call__(self, dt: float): residual_idx = self.particles.residual_idx if self.verbose: - print(f"{first_pusher_idx =}") - print(f"{first_shift_idx =}") - print(f"{residual_idx =}") - print(f"{self.particles.n_cols =}") + print(f"{first_pusher_idx = }") + print(f"{first_shift_idx = }") + print(f"{residual_idx = }") + print(f"{self.particles.n_cols = }") init_slice = slice(first_pusher_idx, first_shift_idx) shift_slice = slice(first_shift_idx, residual_idx) @@ -209,7 +210,7 @@ def __call__(self, dt: float): add_args = ker_args[3] ker( - xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), column_nr, comps, self.particles.args_markers, @@ -224,14 +225,14 @@ def __call__(self, dt: float): # start stages (e.g. n_stages=4 for RK4) for stage in range(self.n_stages): # start iteration (maxiter=1 for explicit schemes) - n_not_converged = xp.empty(1, dtype=int) + n_not_converged = np.empty(1, dtype=int) n_not_converged[0] = self.particles.n_mks_loc k = 0 if self.verbose and self.maxiter > 1: max_res = 1.0 print( - f"rank {rank}: {k =}, tol: {self._tol}, {n_not_converged[0] =}, {max_res =}", + f"rank {rank}: {k = }, tol: {self._tol}, {n_not_converged[0] = }, {max_res = }", ) if self.particles.mpi_comm is not None: self.particles.mpi_comm.Barrier() @@ -279,7 +280,7 @@ def __call__(self, dt: float): ) # push markers - with ProfileManager.profile_region("kernel: " + self.kernel.name): + with ProfileManager.profile_region("kernel: " + self.kernel.__name__): self.kernel( dt, stage, @@ -298,27 +299,27 @@ def __call__(self, dt: float): # compute number of non-converged particles (maxiter=1 for explicit schemes) if self.maxiter > 1: self._residuals[:] = markers[:, residual_idx] - max_res = xp.max(self._residuals) + max_res = np.max(self._residuals) if max_res < 0.0: max_res = None self._converged_loc[:] = self._residuals < self._tol self._not_converged_loc[:] = ~self._converged_loc - n_not_converged[0] = xp.count_nonzero( + n_not_converged[0] = np.count_nonzero( self._not_converged_loc, ) if self.verbose: print( - f"rank {rank}: {k =}, tol: {self._tol}, {n_not_converged[0] =}, {max_res =}", + f"rank {rank}: {k = }, tol: {self._tol}, {n_not_converged[0] = }, {max_res = }", ) if self.particles.mpi_comm is not None: self.particles.mpi_comm.Barrier() if self.particles.mpi_comm is not None: self.particles.mpi_comm.Allreduce( - MPI.IN_PLACE, + self._mpi_in_place, n_not_converged, - op=MPI.SUM, + op=self._mpi_sum, ) # take converged markers out of the loop @@ -329,7 +330,7 @@ def __call__(self, dt: float): if self.maxiter > 1: rank = self.particles.mpi_rank print( - f"rank {rank}: {k =}, maxiter={self.maxiter} reached! tol: {self._tol}, {n_not_converged[0] =}, {max_res =}", + f"rank {rank}: {k = }, maxiter={self.maxiter} reached! tol: {self._tol}, {n_not_converged[0] = }, {max_res = }", ) # sort markers according to domain decomposition if self.mpi_sort == "each": diff --git a/src/struphy/pic/pushing/pusher_kernels.py b/src/struphy/pic/pushing/pusher_kernels.py index 47b6a71ba..429e1c722 100644 --- a/src/struphy/pic/pushing/pusher_kernels.py +++ b/src/struphy/pic/pushing/pusher_kernels.py @@ -1615,15 +1615,6 @@ def push_bxu_Hdiv_pauli( # -- removed omp: #$ omp end parallel -@stack_array( - "dfm", - "dfinv", - "dfinv_t", - "e", - "e_cart", - "GXu", - "v", -) def push_pc_GXu_full( dt: float, stage: int, @@ -1639,6 +1630,7 @@ def push_pc_GXu_full( GXu_31: "float[:,:,:]", GXu_32: "float[:,:,:]", GXu_33: "float[:,:,:]", + boundary_cut: "float", ): r"""Updates @@ -1679,6 +1671,10 @@ def push_pc_GXu_full( if markers[ip, 0] == -1.0: continue + # boundary cut + if markers[ip, 0] < boundary_cut or markers[ip, 0] > 1.0 - boundary_cut: + continue + eta1 = markers[ip, 0] eta2 = markers[ip, 1] eta3 = markers[ip, 2] @@ -1744,15 +1740,6 @@ def push_pc_GXu_full( markers[ip, 3:6] -= dt * e_cart / 2.0 -@stack_array( - "dfm", - "dfinv", - "dfinv_t", - "e", - "e_cart", - "GXu", - "v", -) def push_pc_GXu( dt: float, stage: int, @@ -1768,6 +1755,7 @@ def push_pc_GXu( GXu_31: "float[:,:,:]", GXu_32: "float[:,:,:]", GXu_33: "float[:,:,:]", + boundary_cut: "float", ): r"""Updates @@ -1795,6 +1783,7 @@ def push_pc_GXu( e = empty(3, dtype=float) e_cart = empty(3, dtype=float) GXu = empty((3, 3), dtype=float) + GXu_t = empty((3, 3), dtype=float) # particle velocity v = empty(3, dtype=float) @@ -1808,6 +1797,10 @@ def push_pc_GXu( if markers[ip, 0] == -1.0: continue + # boundary cut + if markers[ip, 0] < boundary_cut or markers[ip, 0] > 1.0 - boundary_cut: + continue + eta1 = markers[ip, 0] eta2 = markers[ip, 1] eta3 = markers[ip, 2] @@ -1946,7 +1939,7 @@ def push_eta_stage( @stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v", "k_u") -def push_pc_eta_stage_Hcurl( +def push_pc_eta_rk4_Hcurl_full( dt: float, stage: int, args_markers: "MarkerArguments", @@ -1955,10 +1948,6 @@ def push_pc_eta_stage_Hcurl( u_1: "float[:,:,:]", u_2: "float[:,:,:]", u_3: "float[:,:,:]", - use_perp_model: "bool", - a: "float[:]", - b: "float[:]", - c: "float[:]", ): r"""Fourth order Runge-Kutta solve of @@ -1971,6 +1960,14 @@ def push_pc_eta_stage_Hcurl( .. math:: \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. + + Parameters + ---------- + u_1, u_2, u_3: array[float] + 3d array of FE coeffs of U-field, either as 1-form or as 2-form. + + u_basis : int + U is 1-form (u_basis=1) or a 2-form (u_basis=2). """ # allocate metric coeffs @@ -1996,13 +1993,22 @@ def push_pc_eta_stage_Hcurl( first_init_idx = args_markers.first_init_idx first_free_idx = args_markers.first_free_idx - # get number of stages - n_stages = shape(b)[0] + # assign factor of k for each stage + if stage == 0 or stage == 3: + nk = 1.0 + else: + nk = 2.0 - if stage == n_stages - 1: + # which stage + if stage == 3: last = 1.0 + cont = 0.0 + elif stage == 2: + last = 0.0 + cont = 2.0 else: last = 0.0 + cont = 1.0 for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) @@ -2047,9 +2053,6 @@ def push_pc_eta_stage_Hcurl( u, ) - if use_perp_model: - u[2] = 0.0 - # transform to vector field linalg_kernels.matrix_vector(ginv, u, k_u) @@ -2057,18 +2060,18 @@ def push_pc_eta_stage_Hcurl( k[:] = k_v + k_u # accum k - markers[ip, first_free_idx : first_free_idx + 3] += dt * b[stage] * k + markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 # update markers for the next stage markers[ip, 0:3] = ( markers[ip, first_init_idx : first_init_idx + 3] - + dt * k * a[stage] - + last * markers[ip, first_free_idx : first_free_idx + 3] + + dt * k / 2 * cont + + dt * markers[ip, first_free_idx : first_free_idx + 3] * last ) @stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v", "k_u") -def push_pc_eta_stage_Hdiv( +def push_pc_eta_rk4_Hdiv_full( dt: float, stage: int, args_markers: "MarkerArguments", @@ -2077,10 +2080,6 @@ def push_pc_eta_stage_Hdiv( u_1: "float[:,:,:]", u_2: "float[:,:,:]", u_3: "float[:,:,:]", - use_perp_model: "bool", - a: "float[:]", - b: "float[:]", - c: "float[:]", ): r"""Fourth order Runge-Kutta solve of @@ -2093,6 +2092,14 @@ def push_pc_eta_stage_Hdiv( .. math:: \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. + + Parameters + ---------- + u_1, u_2, u_3: array[float] + 3d array of FE coeffs of U-field, either as 1-form or as 2-form. + + u_basis : int + U is 1-form (u_basis=1) or a 2-form (u_basis=2). """ # allocate metric coeffs @@ -2118,13 +2125,19 @@ def push_pc_eta_stage_Hdiv( first_init_idx = args_markers.first_init_idx first_free_idx = args_markers.first_free_idx - # get number of stages - n_stages = shape(b)[0] + # assign factor of k for each stage + if stage == 0 or stage == 3: + nk = 1.0 + else: + nk = 2.0 - if stage == n_stages - 1: + # is it the last stage? + if stage == 3: last = 1.0 + cont = 0.0 else: last = 0.0 + cont = 1.0 for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) @@ -2170,8 +2183,397 @@ def push_pc_eta_stage_Hdiv( u, ) - if use_perp_model: - u[2] = 0.0 + # transform to vector field + k_u[:] = u / det_df + + # sum contribs + k[:] = k_v + k_u + + # accum k + markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 + + # update markers for the next stage + markers[ip, 0:3] = ( + markers[ip, first_init_idx : first_init_idx + 3] + + dt * k / 2 * cont + + dt * markers[ip, first_free_idx : first_free_idx + 3] * last + ) + + +@stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v") +def push_pc_eta_rk4_H1vec_full( + dt: float, + stage: int, + args_markers: "MarkerArguments", + args_domain: "DomainArguments", + args_derham: "DerhamArguments", + u_1: "float[:,:,:]", + u_2: "float[:,:,:]", + u_3: "float[:,:,:]", +): + r"""Fourth order Runge-Kutta solve of + + .. math:: + + \frac{\textnormal d \boldsymbol \eta_p(t)}{\textnormal d t} = DF^{-1}(\boldsymbol \eta_p(t)) \mathbf v + \textnormal{vec}( \hat{\mathbf U}^{1(2)}) + + for each marker :math:`p` in markers array, where :math:`\mathbf v` is constant and + + .. math:: + + \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. + + Parameters + ---------- + u_1, u_2, u_3 : array[float] + 3d array of FE coeffs of U-field, either as 1-form or as 2-form. + + u_basis : int + U is 1-form (u_basis=1) or a 2-form (u_basis=2). + """ + + # allocate metric coeffs + dfm = empty((3, 3), dtype=float) + dfinv = empty((3, 3), dtype=float) + dfinv_t = empty((3, 3), dtype=float) + ginv = empty((3, 3), dtype=float) + + # marker and velocity + v = empty(3, dtype=float) + + # U-fiels + u = empty(3, dtype=float) + + # intermediate stages in RK4 + k = empty(3, dtype=float) + k_v = empty(3, dtype=float) + + # get marker arguments + markers = args_markers.markers + n_markers = args_markers.n_markers + first_init_idx = args_markers.first_init_idx + first_free_idx = args_markers.first_free_idx + + # assign factor of k for each stage + if stage == 0 or stage == 3: + nk = 1.0 + else: + nk = 2.0 + + # which stage + if stage == 3: + last = 1.0 + cont = 0.0 + elif stage == 2: + last = 0.0 + cont = 2.0 + else: + last = 0.0 + cont = 1.0 + + for ip in range(n_markers): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + e1 = markers[ip, 0] + e2 = markers[ip, 1] + e3 = markers[ip, 2] + v[:] = markers[ip, 3:6] + + # ----------------- stage n in Runge-Kutta method ------------------- + # evaluate Jacobian, result in dfm + evaluation_kernels.df( + e1, + e2, + e3, + args_domain, + dfm, + ) + + # metric coeffs + linalg_kernels.matrix_inv(dfm, dfinv) + linalg_kernels.transpose(dfinv, dfinv_t) + linalg_kernels.matrix_matrix(dfinv, dfinv_t, ginv) + + # pull-back of velocity + linalg_kernels.matrix_vector(dfinv, v, k_v) + + # spline evaluation + span1, span2, span3 = get_spans(e1, e2, e3, args_derham) + + # U-field + eval_vectorfield_spline_mpi( + span1, + span2, + span3, + args_derham, + u_1, + u_2, + u_3, + u, + ) + + # sum contribs + k[:] = k_v + u + + # accum k + markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 + + # update markers for the next stage + markers[ip, 0:3] = ( + markers[ip, first_init_idx : first_init_idx + 3] + + dt * k / 2 * cont + + dt * markers[ip, first_free_idx : first_free_idx + 3] * last + ) + + +@stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v", "k_u") +def push_pc_eta_rk4_Hcurl( + dt: float, + stage: int, + args_markers: "MarkerArguments", + args_domain: "DomainArguments", + args_derham: "DerhamArguments", + u_1: "float[:,:,:]", + u_2: "float[:,:,:]", + u_3: "float[:,:,:]", +): + r"""Fourth order Runge-Kutta solve of + + .. math:: + + \frac{\textnormal d \boldsymbol \eta_p(t)}{\textnormal d t} = DF^{-1}(\boldsymbol \eta_p(t)) \mathbf v + \textnormal{vec}( \hat{\mathbf U}^{1(2)}) + + for each marker :math:`p` in markers array, where :math:`\mathbf v` is constant and + + .. math:: + + \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. + + Parameters + ---------- + u_1, u_2, u_3 : array[float] + 3d array of FE coeffs of U-field, either as 1-form or as 2-form. + + u_basis : int + U is 1-form (u_basis=1) or a 2-form (u_basis=2). + """ + + # allocate metric coeffs + dfm = empty((3, 3), dtype=float) + dfinv = empty((3, 3), dtype=float) + dfinv_t = empty((3, 3), dtype=float) + ginv = empty((3, 3), dtype=float) + + # marker velocity + v = empty(3, dtype=float) + + # U-fiels + u = empty(3, dtype=float) + + # intermediate stages in RK4 + k = empty(3, dtype=float) + k_v = empty(3, dtype=float) + k_u = empty(3, dtype=float) + + # get marker arguments + markers = args_markers.markers + n_markers = args_markers.n_markers + first_init_idx = args_markers.first_init_idx + first_free_idx = args_markers.first_free_idx + + # assign factor of k for each stage + if stage == 0 or stage == 3: + nk = 1.0 + else: + nk = 2.0 + + # which stage + if stage == 3: + last = 1.0 + cont = 0.0 + elif stage == 2: + last = 0.0 + cont = 2.0 + else: + last = 0.0 + cont = 1.0 + + for ip in range(n_markers): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + e1 = markers[ip, 0] + e2 = markers[ip, 1] + e3 = markers[ip, 2] + v[:] = markers[ip, 3:6] + + # ----------------- stage n in Runge-Kutta method ------------------- + # evaluate Jacobian, result in dfm + evaluation_kernels.df( + e1, + e2, + e3, + args_domain, + dfm, + ) + + # metric coeffs + linalg_kernels.matrix_inv(dfm, dfinv) + linalg_kernels.transpose(dfinv, dfinv_t) + linalg_kernels.matrix_matrix(dfinv, dfinv_t, ginv) + + # pull-back of velocity + linalg_kernels.matrix_vector(dfinv, v, k_v) + + # spline evaluation + span1, span2, span3 = get_spans(e1, e2, e3, args_derham) + + # U-field + eval_1form_spline_mpi( + span1, + span2, + span3, + args_derham, + u_1, + u_2, + u_3, + u, + ) + u[2] = 0.0 + + # transform to vector field + linalg_kernels.matrix_vector(ginv, u, k_u) + + # sum contribs + k[:] = k_v + k_u + + # accum k + markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 + + # update markers for the next stage + markers[ip, 0:3] = ( + markers[ip, first_init_idx : first_init_idx + 3] + + dt * k / 2 * cont + + dt * markers[ip, first_free_idx : first_free_idx + 3] * last + ) + + +@stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v", "k_u") +def push_pc_eta_rk4_Hdiv( + dt: float, + stage: int, + args_markers: "MarkerArguments", + args_domain: "DomainArguments", + args_derham: "DerhamArguments", + u_1: "float[:,:,:]", + u_2: "float[:,:,:]", + u_3: "float[:,:,:]", +): + r"""Fourth order Runge-Kutta solve of + + .. math:: + + \frac{\textnormal d \boldsymbol \eta_p(t)}{\textnormal d t} = DF^{-1}(\boldsymbol \eta_p(t)) \mathbf v + \textnormal{vec}( \hat{\mathbf U}^{1(2)}) + + for each marker :math:`p` in markers array, where :math:`\mathbf v` is constant and + + .. math:: + + \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. + + Parameters + ---------- + u_1, u_2, u_3 : array[float] + 3d array of FE coeffs of U-field, either as 1-form or as 2-form. + + u_basis : int + U is 1-form (u_basis=1) or a 2-form (u_basis=2). + """ + + # allocate metric coeffs + dfm = empty((3, 3), dtype=float) + dfinv = empty((3, 3), dtype=float) + dfinv_t = empty((3, 3), dtype=float) + ginv = empty((3, 3), dtype=float) + + # marker velocity + v = empty(3, dtype=float) + + # U-fiels + u = empty(3, dtype=float) + + # intermediate stages in RK4 + k = empty(3, dtype=float) + k_v = empty(3, dtype=float) + k_u = empty(3, dtype=float) + + # get marker arguments + markers = args_markers.markers + n_markers = args_markers.n_markers + first_init_idx = args_markers.first_init_idx + first_free_idx = args_markers.first_free_idx + + # assign factor of k for each stage + if stage == 0 or stage == 3: + nk = 1.0 + else: + nk = 2.0 + + # is it the last stage? + if stage == 3: + last = 1.0 + cont = 0.0 + else: + last = 0.0 + cont = 1.0 + + for ip in range(n_markers): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + e1 = markers[ip, 0] + e2 = markers[ip, 1] + e3 = markers[ip, 2] + v[:] = markers[ip, 3:6] + + # ----------------- stage n in Runge-Kutta method ------------------- + # evaluate Jacobian, result in dfm + evaluation_kernels.df( + e1, + e2, + e3, + args_domain, + dfm, + ) + + # metric coeffs + det_df = linalg_kernels.det(dfm) + linalg_kernels.matrix_inv(dfm, dfinv) + linalg_kernels.transpose(dfinv, dfinv_t) + linalg_kernels.matrix_matrix(dfinv, dfinv_t, ginv) + + # pull-back of velocity + linalg_kernels.matrix_vector(dfinv, v, k_v) + + # spline evaluation + span1, span2, span3 = get_spans(e1, e2, e3, args_derham) + + # U-field + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + u_1, + u_2, + u_3, + u, + ) + u[2] = 0.0 # transform to vector field k_u[:] = u / det_df @@ -2180,18 +2582,18 @@ def push_pc_eta_stage_Hdiv( k[:] = k_v + k_u # accum k - markers[ip, first_free_idx : first_free_idx + 3] += dt * b[stage] * k + markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 # update markers for the next stage markers[ip, 0:3] = ( markers[ip, first_init_idx : first_init_idx + 3] - + dt * k * a[stage] - + last * markers[ip, first_free_idx : first_free_idx + 3] + + dt * k / 2 * cont + + dt * markers[ip, first_free_idx : first_free_idx + 3] * last ) @stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v") -def push_pc_eta_stage_H1vec( +def push_pc_eta_rk4_H1vec( dt: float, stage: int, args_markers: "MarkerArguments", @@ -2200,10 +2602,6 @@ def push_pc_eta_stage_H1vec( u_1: "float[:,:,:]", u_2: "float[:,:,:]", u_3: "float[:,:,:]", - use_perp_model: "bool", - a: "float[:]", - b: "float[:]", - c: "float[:]", ): r"""Fourth order Runge-Kutta solve of @@ -2248,13 +2646,22 @@ def push_pc_eta_stage_H1vec( first_init_idx = args_markers.first_init_idx first_free_idx = args_markers.first_free_idx - # get number of stages - n_stages = shape(b)[0] + # assign factor of k for each stage + if stage == 0 or stage == 3: + nk = 1.0 + else: + nk = 2.0 - if stage == n_stages - 1: + # which stage + if stage == 3: last = 1.0 + cont = 0.0 + elif stage == 2: + last = 0.0 + cont = 2.0 else: last = 0.0 + cont = 1.0 for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) @@ -2298,21 +2705,19 @@ def push_pc_eta_stage_H1vec( u_3, u, ) - - if use_perp_model: - u[2] = 0.0 + u[2] = 0.0 # sum contribs k[:] = k_v + u # accum k - markers[ip, first_free_idx : first_free_idx + 3] += dt * b[stage] * k + markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 # update markers for the next stage markers[ip, 0:3] = ( markers[ip, first_init_idx : first_init_idx + 3] - + dt * k * a[stage] - + last * markers[ip, first_free_idx : first_free_idx + 3] + + dt * k / 2 * cont + + dt * markers[ip, first_free_idx : first_free_idx + 3] * last ) @@ -2630,7 +3035,7 @@ def push_v_sph_pressure( h1, h2, h3 : float Kernel width in respective dimension. - gravity: xp.ndarray + gravity: np.ndarray Constant gravitational force as 3-vector. """ # allocate arrays @@ -2861,7 +3266,7 @@ def push_v_sph_pressure_ideal_gas( h1, h2, h3 : float Kernel width in respective dimension. - gravity: xp.ndarray + gravity: np.ndarray Constant gravitational force as 3-vector. """ # allocate arrays @@ -3093,7 +3498,7 @@ def push_v_viscosity( h1, h2, h3 : float Kernel width in respective dimension. - gravity: xp.ndarray + gravity: np.ndarray Constant gravitational force as 3-vector. """ # allocate arrays diff --git a/src/struphy/pic/pushing/pusher_kernels_gc.py b/src/struphy/pic/pushing/pusher_kernels_gc.py index 5dfee707b..0b6c9b3c7 100644 --- a/src/struphy/pic/pushing/pusher_kernels_gc.py +++ b/src/struphy/pic/pushing/pusher_kernels_gc.py @@ -1896,7 +1896,7 @@ def push_gc_cc_J1_H1vec( ) # b_star; in H1vec - b_star[:] = b + curl_norm_b * v * epsilon + b_star[:] = (b + curl_norm_b * v * epsilon) / det_df # calculate abs_b_star_para abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) @@ -1905,7 +1905,7 @@ def push_gc_cc_J1_H1vec( linalg_kernels.cross(b, u, e) # curl_norm_b dot electric field - temp = linalg_kernels.scalar_dot(e, curl_norm_b) + temp = linalg_kernels.scalar_dot(e, curl_norm_b) / det_df markers[ip, 3] += temp / abs_b_star_para * v * dt @@ -2077,6 +2077,7 @@ def push_gc_cc_J1_Hdiv( u1: "float[:,:,:]", u2: "float[:,:,:]", u3: "float[:,:,:]", + boundary_cut: float, ): r"""Velocity update step for the `CurrentCoupling5DCurlb `_ @@ -2104,6 +2105,8 @@ def push_gc_cc_J1_Hdiv( markers = args_markers.markers n_markers = args_markers.n_markers + # -- removed omp: #$ omp parallel private(ip, boundary_cut, eta1, eta2, eta3, v, det_df, dfm, span1, span2, span3, b, u, e, curl_norm_b, norm_b1, b_star, temp, abs_b_star_para) + # -- removed omp: #$ omp for for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) if markers[ip, 0] == -1.0: @@ -2114,6 +2117,9 @@ def push_gc_cc_J1_Hdiv( eta3 = markers[ip, 2] v = markers[ip, 3] + if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: + continue + # evaluate Jacobian, result in dfm evaluation_kernels.df( eta1, @@ -2177,10 +2183,10 @@ def push_gc_cc_J1_Hdiv( curl_norm_b, ) - # b_star; 2form - b_star[:] = b + curl_norm_b * v * epsilon + # b_star; 2form in H1vec + b_star[:] = (b + curl_norm_b * v * epsilon) / det_df - # calculate 3form abs_b_star_para + # calculate abs_b_star_para abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) # transform u into H1vec @@ -2190,10 +2196,12 @@ def push_gc_cc_J1_Hdiv( linalg_kernels.cross(b, u, e) # curl_norm_b dot electric field - temp = linalg_kernels.scalar_dot(e, curl_norm_b) + temp = linalg_kernels.scalar_dot(e, curl_norm_b) / det_df markers[ip, 3] += temp / abs_b_star_para * v * dt + # -- removed omp: #$ omp end parallel + @stack_array( "dfm", @@ -2204,11 +2212,13 @@ def push_gc_cc_J1_Hdiv( "u", "bb", "b_star", - "norm_b", + "norm_b1", + "norm_b2", "curl_norm_b", - "tmp", + "tmp1", + "tmp2", "b_prod", - "norm_b_prod", + "norm_b2_prod", ) def push_gc_cc_J2_stage_H1vec( dt: float, @@ -2223,6 +2233,9 @@ def push_gc_cc_J2_stage_H1vec( norm_b11: "float[:,:,:]", norm_b12: "float[:,:,:]", norm_b13: "float[:,:,:]", + norm_b21: "float[:,:,:]", + norm_b22: "float[:,:,:]", + norm_b23: "float[:,:,:]", curl_norm_b1: "float[:,:,:]", curl_norm_b2: "float[:,:,:]", curl_norm_b3: "float[:,:,:]", @@ -2251,14 +2264,16 @@ def push_gc_cc_J2_stage_H1vec( g_inv = empty((3, 3), dtype=float) # containers for fields - tmp = empty((3, 3), dtype=float) + tmp1 = empty((3, 3), dtype=float) + tmp2 = empty((3, 3), dtype=float) b_prod = zeros((3, 3), dtype=float) - norm_b_prod = empty((3, 3), dtype=float) + norm_b2_prod = empty((3, 3), dtype=float) e = empty(3, dtype=float) u = empty(3, dtype=float) bb = empty(3, dtype=float) b_star = empty(3, dtype=float) norm_b1 = empty(3, dtype=float) + norm_b2 = empty(3, dtype=float) curl_norm_b = empty(3, dtype=float) # get marker arguments @@ -2339,6 +2354,18 @@ def push_gc_cc_J2_stage_H1vec( norm_b1, ) + # norm_b; 2form + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + norm_b21, + norm_b22, + norm_b23, + norm_b2, + ) + # curl_norm_b; 2form eval_2form_spline_mpi( span1, @@ -2359,21 +2386,24 @@ def push_gc_cc_J2_stage_H1vec( b_prod[2, 0] = -bb[1] b_prod[2, 1] = +bb[0] - norm_b_prod[0, 1] = -norm_b1[2] - norm_b_prod[0, 2] = +norm_b1[1] - norm_b_prod[1, 0] = +norm_b1[2] - norm_b_prod[1, 2] = -norm_b1[0] - norm_b_prod[2, 0] = -norm_b1[1] - norm_b_prod[2, 1] = +norm_b1[0] + norm_b2_prod[0, 1] = -norm_b2[2] + norm_b2_prod[0, 2] = +norm_b2[1] + norm_b2_prod[1, 0] = +norm_b2[2] + norm_b2_prod[1, 2] = -norm_b2[0] + norm_b2_prod[2, 0] = -norm_b2[1] + norm_b2_prod[2, 1] = +norm_b2[0] # b_star; 2form in H1vec - b_star[:] = bb + curl_norm_b * v * epsilon + b_star[:] = (bb + curl_norm_b * v * epsilon) / det_df - # calculate 3form abs_b_star_para + # calculate abs_b_star_para abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) - linalg_kernels.matrix_matrix(norm_b_prod, b_prod, tmp) - linalg_kernels.matrix_vector(tmp, u, e) + linalg_kernels.matrix_matrix(g_inv, norm_b2_prod, tmp1) + linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) + linalg_kernels.matrix_matrix(tmp2, b_prod, tmp1) + + linalg_kernels.matrix_vector(tmp1, u, e) e /= abs_b_star_para @@ -2398,10 +2428,12 @@ def push_gc_cc_J2_stage_H1vec( "bb", "b_star", "norm_b1", + "norm_b2", "curl_norm_b", - "tmp", + "tmp1", + "tmp2", "b_prod", - "norm_b_prod", + "norm_b2_prod", ) def push_gc_cc_J2_stage_Hdiv( dt: float, @@ -2416,6 +2448,9 @@ def push_gc_cc_J2_stage_Hdiv( norm_b11: "float[:,:,:]", norm_b12: "float[:,:,:]", norm_b13: "float[:,:,:]", + norm_b21: "float[:,:,:]", + norm_b22: "float[:,:,:]", + norm_b23: "float[:,:,:]", curl_norm_b1: "float[:,:,:]", curl_norm_b2: "float[:,:,:]", curl_norm_b3: "float[:,:,:]", @@ -2425,6 +2460,7 @@ def push_gc_cc_J2_stage_Hdiv( a: "float[:]", b: "float[:]", c: "float[:]", + boundary_cut: float, ): r"""Single stage of a s-stage explicit pushing step for the `CurrentCoupling5DGradB `_ @@ -2444,14 +2480,16 @@ def push_gc_cc_J2_stage_Hdiv( g_inv = empty((3, 3), dtype=float) # containers for fields - tmp = zeros((3, 3), dtype=float) + tmp1 = zeros((3, 3), dtype=float) + tmp2 = zeros((3, 3), dtype=float) b_prod = zeros((3, 3), dtype=float) - norm_b_prod = zeros((3, 3), dtype=float) + norm_b2_prod = zeros((3, 3), dtype=float) e = empty(3, dtype=float) u = empty(3, dtype=float) bb = empty(3, dtype=float) b_star = empty(3, dtype=float) norm_b1 = empty(3, dtype=float) + norm_b2 = empty(3, dtype=float) curl_norm_b = empty(3, dtype=float) # get marker arguments @@ -2469,6 +2507,8 @@ def push_gc_cc_J2_stage_Hdiv( else: last = 0.0 + # -- removed omp: #$ omp parallel firstprivate(b_prod, norm_b2_prod) private(ip, boundary_cut, eta1, eta2, eta3, v, det_df, dfm, df_inv, df_inv_t, g_inv, span1, span2, span3, bb, u, e, curl_norm_b, norm_b1, norm_b2, b_star, tmp1, tmp2, abs_b_star_para) + # -- removed omp: #$ omp for for ip in range(n_markers): # check if marker is a hole if markers[ip, first_init_idx] == -1.0: @@ -2479,6 +2519,9 @@ def push_gc_cc_J2_stage_Hdiv( eta3 = markers[ip, 2] v = markers[ip, 3] + if eta1 < boundary_cut or eta2 > 1.0 - boundary_cut: + continue + # evaluate Jacobian, result in dfm evaluation_kernels.df( eta1, @@ -2533,178 +2576,16 @@ def push_gc_cc_J2_stage_Hdiv( norm_b1, ) - # curl_norm_b; 2form + # norm_b; 2form eval_2form_spline_mpi( span1, span2, span3, args_derham, - curl_norm_b1, - curl_norm_b2, - curl_norm_b3, - curl_norm_b, - ) - - # operator bx() as matrix - b_prod[0, 1] = -bb[2] - b_prod[0, 2] = +bb[1] - b_prod[1, 0] = +bb[2] - b_prod[1, 2] = -bb[0] - b_prod[2, 0] = -bb[1] - b_prod[2, 1] = +bb[0] - - norm_b_prod[0, 1] = -norm_b1[2] - norm_b_prod[0, 2] = +norm_b1[1] - norm_b_prod[1, 0] = +norm_b1[2] - norm_b_prod[1, 2] = -norm_b1[0] - norm_b_prod[2, 0] = -norm_b1[1] - norm_b_prod[2, 1] = +norm_b1[0] - - # b_star; 2form - b_star[:] = bb + curl_norm_b * v * epsilon - - # calculate abs_b_star_para - abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) - - linalg_kernels.matrix_matrix(norm_b_prod, b_prod, tmp) - linalg_kernels.matrix_vector(tmp, u, e) - - e /= abs_b_star_para - e /= det_df - - # accumulation for last stage - markers[ip, first_free_idx : first_free_idx + 3] -= dt * b[stage] * e - - # update positions for intermediate stages or last stage - markers[ip, 0:3] = ( - markers[ip, first_init_idx : first_init_idx + 3] - - dt * a[stage] * e - + last * markers[ip, first_free_idx : first_free_idx + 3] - ) - - -@stack_array( - "dfm", - "df_inv", - "df_inv_t", - "g_inv", - "e", - "u", - "bb", - "b_star", - "norm_b1", - "curl_norm_b", - "tmp1", - "b_prod", - "norm_b_prod", -) -def push_gc_cc_J2_dg_init_Hdiv( - dt: float, - args_markers: "MarkerArguments", - args_domain: "DomainArguments", - args_derham: "DerhamArguments", - epsilon: float, - b1: "float[:,:,:]", - b2: "float[:,:,:]", - b3: "float[:,:,:]", - norm_b11: "float[:,:,:]", - norm_b12: "float[:,:,:]", - norm_b13: "float[:,:,:]", - curl_norm_b1: "float[:,:,:]", - curl_norm_b2: "float[:,:,:]", - curl_norm_b3: "float[:,:,:]", - u1: "float[:,:,:]", - u2: "float[:,:,:]", - u3: "float[:,:,:]", -): - r"""TODO""" - - # allocate metric coeffs - dfm = empty((3, 3), dtype=float) - df_inv = empty((3, 3), dtype=float) - df_inv_t = empty((3, 3), dtype=float) - g_inv = empty((3, 3), dtype=float) - - # containers for fields - tmp1 = zeros((3, 3), dtype=float) - b_prod = zeros((3, 3), dtype=float) - norm_b_prod = zeros((3, 3), dtype=float) - e = empty(3, dtype=float) - u = empty(3, dtype=float) - bb = empty(3, dtype=float) - b_star = empty(3, dtype=float) - norm_b1 = empty(3, dtype=float) - curl_norm_b = empty(3, dtype=float) - - # get marker arguments - markers = args_markers.markers - n_markers = args_markers.n_markers - mu_idx = args_markers.mu_idx - first_init_idx = args_markers.first_init_idx - first_free_idx = args_markers.first_free_idx - - for ip in range(n_markers): - # check if marker is a hole - if markers[ip, first_init_idx] == -1.0: - continue - - eta1 = markers[ip, 0] - eta2 = markers[ip, 1] - eta3 = markers[ip, 2] - v = markers[ip, 3] - - # evaluate Jacobian, result in dfm - evaluation_kernels.df( - eta1, - eta2, - eta3, - args_domain, - dfm, - ) - - # metric coeffs - det_df = linalg_kernels.det(dfm) - linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) - linalg_kernels.transpose(df_inv, df_inv_t) - linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) - - # spline evaluation - span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) - - # b; 2form - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - b1, - b2, - b3, - bb, - ) - - # u; 2form - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - u1, - u2, - u3, - u, - ) - - # norm_b1; 1form - eval_1form_spline_mpi( - span1, - span2, - span3, - args_derham, - norm_b11, - norm_b12, - norm_b13, - norm_b1, + norm_b21, + norm_b22, + norm_b23, + norm_b2, ) # curl_norm_b; 2form @@ -2727,222 +2608,36 @@ def push_gc_cc_J2_dg_init_Hdiv( b_prod[2, 0] = -bb[1] b_prod[2, 1] = +bb[0] - norm_b_prod[0, 1] = -norm_b1[2] - norm_b_prod[0, 2] = +norm_b1[1] - norm_b_prod[1, 0] = +norm_b1[2] - norm_b_prod[1, 2] = -norm_b1[0] - norm_b_prod[2, 0] = -norm_b1[1] - norm_b_prod[2, 1] = +norm_b1[0] + norm_b2_prod[0, 1] = -norm_b2[2] + norm_b2_prod[0, 2] = +norm_b2[1] + norm_b2_prod[1, 0] = +norm_b2[2] + norm_b2_prod[1, 2] = -norm_b2[0] + norm_b2_prod[2, 0] = -norm_b2[1] + norm_b2_prod[2, 1] = +norm_b2[0] - # b_star; 2form - b_star[:] = bb + curl_norm_b * v * epsilon + # b_star; 2form in H1vec + b_star[:] = (bb + curl_norm_b * v * epsilon) / det_df - # calculate 3form abs_b_star_para + # calculate abs_b_star_para abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) - linalg_kernels.matrix_matrix(norm_b_prod, b_prod, tmp1) + linalg_kernels.matrix_matrix(g_inv, norm_b2_prod, tmp1) + linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) + linalg_kernels.matrix_matrix(tmp2, b_prod, tmp1) + linalg_kernels.matrix_vector(tmp1, u, e) e /= abs_b_star_para e /= det_df - markers[ip, 0:3] -= dt * e - - -@stack_array( - "dfm", - "df_inv", - "df_inv_t", - "g_inv", - "e", - "u", - "ud", - "bb", - "b_star", - "norm_b1", - "curl_norm_b", - "tmp1", - "tmp2", - "b_prod", - "norm_b_prod", - "eta_old", - "eta_mid", -) -def push_gc_cc_J2_dg_Hdiv( - dt: float, - args_markers: "MarkerArguments", - args_domain: "DomainArguments", - args_derham: "DerhamArguments", - epsilon: float, - b1: "float[:,:,:]", - b2: "float[:,:,:]", - b3: "float[:,:,:]", - norm_b11: "float[:,:,:]", - norm_b12: "float[:,:,:]", - norm_b13: "float[:,:,:]", - curl_norm_b1: "float[:,:,:]", - curl_norm_b2: "float[:,:,:]", - curl_norm_b3: "float[:,:,:]", - u1: "float[:,:,:]", - u2: "float[:,:,:]", - u3: "float[:,:,:]", - ud1: "float[:,:,:]", - ud2: "float[:,:,:]", - ud3: "float[:,:,:]", - const: float, - alpha: float, -): - r"""TODO""" - - # allocate metric coeffs - dfm = empty((3, 3), dtype=float) - df_inv = empty((3, 3), dtype=float) - df_inv_t = empty((3, 3), dtype=float) - g_inv = empty((3, 3), dtype=float) - - # containers for fields - tmp1 = zeros((3, 3), dtype=float) - tmp2 = zeros(3, dtype=float) - b_prod = zeros((3, 3), dtype=float) - norm_b_prod = zeros((3, 3), dtype=float) - e = empty(3, dtype=float) - u = empty(3, dtype=float) - ud = empty(3, dtype=float) - bb = empty(3, dtype=float) - b_star = empty(3, dtype=float) - norm_b1 = empty(3, dtype=float) - curl_norm_b = empty(3, dtype=float) - eta_old = empty(3, dtype=float) - eta_mid = empty(3, dtype=float) - - # get marker arguments - markers = args_markers.markers - n_markers = args_markers.n_markers - mu_idx = args_markers.mu_idx - first_init_idx = args_markers.first_init_idx - first_free_idx = args_markers.first_free_idx - - for ip in range(n_markers): - # check if marker is a hole - if markers[ip, 0] == -1.0: - continue - - # marker positions, mid point - eta_old[:] = markers[ip, 0:3] - eta_mid[:] = (markers[ip, 0:3] + markers[ip, first_init_idx : first_init_idx + 3]) / 2.0 - eta_mid[:] = mod(eta_mid[:], 1.0) - - v = markers[ip, 3] - - # evaluate Jacobian, result in dfm - evaluation_kernels.df( - eta_mid[0], - eta_mid[1], - eta_mid[2], - args_domain, - dfm, - ) - - # metric coeffs - det_df = linalg_kernels.det(dfm) - linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) - linalg_kernels.transpose(df_inv, df_inv_t) - linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) - - # spline evaluation - span1, span2, span3 = get_spans(eta_mid[0], eta_mid[1], eta_mid[2], args_derham) - - # b; 2form - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - b1, - b2, - b3, - bb, - ) - - # u; 2form - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - u1, - u2, - u3, - u, - ) - - # ud; 2form - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - ud1, - ud2, - ud3, - ud, - ) - - # norm_b1; 1form - eval_1form_spline_mpi( - span1, - span2, - span3, - args_derham, - norm_b11, - norm_b12, - norm_b13, - norm_b1, - ) + # accumulation for last stage + markers[ip, first_free_idx : first_free_idx + 3] -= dt * b[stage] * e - # curl_norm_b; 2form - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - curl_norm_b1, - curl_norm_b2, - curl_norm_b3, - curl_norm_b, + # update positions for intermediate stages or last stage + markers[ip, 0:3] = ( + markers[ip, first_init_idx : first_init_idx + 3] + - dt * a[stage] * e + + last * markers[ip, first_free_idx : first_free_idx + 3] ) - # operator bx() as matrix - b_prod[0, 1] = -bb[2] - b_prod[0, 2] = +bb[1] - b_prod[1, 0] = +bb[2] - b_prod[1, 2] = -bb[0] - b_prod[2, 0] = -bb[1] - b_prod[2, 1] = +bb[0] - - norm_b_prod[0, 1] = -norm_b1[2] - norm_b_prod[0, 2] = +norm_b1[1] - norm_b_prod[1, 0] = +norm_b1[2] - norm_b_prod[1, 2] = -norm_b1[0] - norm_b_prod[2, 0] = -norm_b1[1] - norm_b_prod[2, 1] = +norm_b1[0] - - # b_star; 2form - b_star[:] = bb + curl_norm_b * v * epsilon - - # calculate 3form abs_b_star_para - abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) - - linalg_kernels.matrix_matrix(norm_b_prod, b_prod, tmp1) - linalg_kernels.matrix_vector(tmp1, u, e) - linalg_kernels.matrix_vector(tmp1, ud, tmp2) - tmp2 *= const - - e += tmp2 - - e /= abs_b_star_para - e /= det_df - - markers[ip, 0:3] = markers[ip, first_init_idx : first_init_idx + 3] - dt * e - markers[ip, 0:3] *= alpha - markers[ip, 0:3] += eta_old * (1.0 - alpha) + # -- removed omp: #$ omp end parallel diff --git a/src/struphy/pic/pushing/pusher_utilities_kernels.py b/src/struphy/pic/pushing/pusher_utilities_kernels.py index 0d3749660..a5a70ace4 100644 --- a/src/struphy/pic/pushing/pusher_utilities_kernels.py +++ b/src/struphy/pic/pushing/pusher_utilities_kernels.py @@ -77,3 +77,466 @@ def reflect( # update the particle velocities markers[ip, 3:6] = v[:] + + +@pure +def quicksort(a: "float[:]", lo: "int", hi: "int"): + """ + Implementation of the quicksort sorting algorithm. Ref? + + Parameters + ---------- + a : array + list that is to be sorted + + lo : integer + lower index from which the sort to start + + hi : integer + upper index until which the sort is to be done + """ + i = lo + j = hi + while i < hi: + pivot = a[(lo + hi) // 2] + while i <= j: + while a[i] < pivot: + i += 1 + while a[j] > pivot: + j -= 1 + if i <= j: + tmp = a[i] + a[i] = a[j] + a[j] = tmp + i += 1 + j -= 1 + if lo < j: + quicksort(a, lo, j) + lo = i + j = hi + + +def find_taus(eta: "float", eta_next: "float", Nel: "int", breaks: "float[:]", uniform: "int", tau_list: "float[:]"): + """ + Find the values of tau for which the particle crosses the cell boundaries while going from eta to eta_next + + Parameters + ---------- + eta : float + old position + + eta_next : float + new position + + Nel : integer + contains the number of elements in this direction + + breaks : array + break points in this direction + + uniform : integer + 0 if the grid is non-uniform, 1 if the grid is uniform + """ + + if uniform == 1: + index = int(floor(eta * Nel)) + index_next = int(floor(eta_next * Nel)) + length = int(abs(index_next - index)) + + # break = eta / dx = eta * Nel + + for i in range(length): + if index_next > index: + tau_list[i] = (1.0 / Nel * (index + i + 1) - eta) / (eta_next - eta) + elif index > index_next: + tau_list[i] = (eta - 1.0 / Nel * (index - i)) / (eta - eta_next) + + elif uniform == 0: + # TODO + print("Not implemented yet") + + else: + print("ValueError, uniform must be 1 or 0 !") + + +@stack_array("Nel") +def aux_fun_x_v_stat_e( + particle: "float[:]", + args_derham: "DerhamArguments", + args_domain: "DomainArguments", + n_quad1: "int", + n_quad2: "int", + n_quad3: "int", + dfm: "float[:,:]", + df_inv: "float[:,:]", + taus: "float[:]", + dt: "float", + loc1: "float[:]", + loc2: "float[:]", + loc3: "float[:]", + weight1: "float[:]", + weight2: "float[:]", + weight3: "float[:]", + e1_1: "float[:,:,:]", + e1_2: "float[:,:,:]", + e1_3: "float[:,:,:]", + kappa: "float", + eps: "float[:]", + maxiter: "int", +) -> "int": + """ + Auxiliary function for the pusher_x_v_static_efield, introduced to enable time-step splitting if scheme does not converge for the standard dt + + Parameters + ---------- + particle : array + shape(7), contains the values for the positions [0:3], velocities [3:6], and weights [8] + + dt2 : double + time stepping of substep + + loc1, loc2, loc3 : array + contain the positions of the Legendre-Gauss quadrature points of necessary order to integrate basis splines exactly in each direction + + weight1, weight2, weight3 : array + contain the values of the weights for the Legendre-Gauss quadrature in each direction + + e1_1, e1_2, e1_3: array[float] + 3d array of FE coeffs of the background E-field as 1-form. + + eps: array + determines the accuracy for the position (0th element) and velocity (1st element) with which the implicit scheme is executed + + maxiter : integer + sets the maximum number of iterations for the iterative scheme + """ + + # Find number of elements in each direction + Nel = empty(3, dtype=int) + Nel[0] = len(args_derham.tn1) + Nel[1] = len(args_derham.tn2) + Nel[2] = len(args_derham.tn3) + + # total number of basis functions : B-splines (pn) and D-splines (pd) + pn1 = int(args_derham.pn[0]) + pn2 = int(args_derham.pn[1]) + pn3 = int(args_derham.pn[2]) + + pd1 = pn1 - 1 + pd2 = pn2 - 1 + pd3 = pn3 - 1 + + eps_pos = eps[0] + eps_vel = eps[1] + + # position + eta1 = particle[0] + eta2 = particle[1] + eta3 = particle[2] + + # velocities + v1 = particle[3] + v2 = particle[4] + v3 = particle[5] + + # set initial value for x_k^{n+1} + eta1_curr = eta1 + eta2_curr = eta2 + eta3_curr = eta3 + + # set initial value for v_k^{n+1} + v1_curr = v1 + v2_curr = v2 + v3_curr = v3 + + # Use Euler method as a predictor for positions + evaluation_kernels.df( + eta1, + eta2, + eta3, + args_domain, + dfm, + ) + + linalg_kernels.matrix_inv(dfm, df_inv) + + v1_curv = kappa * (df_inv[0, 0] * (v1_curr + v1) + df_inv[0, 1] * (v2_curr + v2) + df_inv[0, 2] * (v3_curr + v3)) + v2_curv = kappa * (df_inv[1, 0] * (v1_curr + v1) + df_inv[1, 1] * (v2_curr + v2) + df_inv[1, 2] * (v3_curr + v3)) + v3_curv = kappa * (df_inv[2, 0] * (v1_curr + v1) + df_inv[2, 1] * (v2_curr + v2) + df_inv[2, 2] * (v3_curr + v3)) + + eta1_next = (eta1 + dt * v1_curv / 2.0) % 1 + eta2_next = (eta2 + dt * v2_curv / 2.0) % 1 + eta3_next = (eta3 + dt * v3_curv / 2.0) % 1 + + # set some initial value for v_next + v1_next = v1_curr + v2_next = v2_curr + v3_next = v3_curr + + runs = 0 + + while ( + abs(eta1_next - eta1_curr) > eps_pos + or abs(eta2_next - eta2_curr) > eps_pos + or abs(eta3_next - eta3_curr) > eps_pos + or abs(v1_next - v1_curr) > eps_vel + or abs(v2_next - v2_curr) > eps_vel + or abs(v3_next - v3_curr) > eps_vel + ): + taus[:] = 0.0 + + # update the positions and velocities + eta1_curr = eta1_next + eta2_curr = eta2_next + eta3_curr = eta3_next + + v1_curr = v1_next + v2_curr = v2_next + v3_curr = v3_next + + # find Jacobian matrix + evaluation_kernels.df( + (eta1_curr + eta1) / 2, + (eta2_curr + eta2) / 2, + (eta3_curr + eta3) / 2, + args_domain, + dfm, + ) + + # evaluate inverse Jacobian matrix + linalg_kernels.matrix_inv(dfm, df_inv) + + # ====================================================================================== + # update the positions and place them back into the computational domain + v1_curv = kappa * ( + df_inv[0, 0] * (v1_curr + v1) + df_inv[0, 1] * (v2_curr + v2) + df_inv[0, 2] * (v3_curr + v3) + ) + v2_curv = kappa * ( + df_inv[1, 0] * (v1_curr + v1) + df_inv[1, 1] * (v2_curr + v2) + df_inv[1, 2] * (v3_curr + v3) + ) + v3_curv = kappa * ( + df_inv[2, 0] * (v1_curr + v1) + df_inv[2, 1] * (v2_curr + v2) + df_inv[2, 2] * (v3_curr + v3) + ) + + # x_{n+1} = x_n + dt/2 * DF^{-1}(x_{n+1}/2 + x_n/2) * (v_{n+1} + v_n) + eta1_next = (eta1 + dt * v1_curv / 2.0) % 1 + eta2_next = (eta2 + dt * v2_curv / 2.0) % 1 + eta3_next = (eta3 + dt * v3_curv / 2.0) % 1 + + # ====================================================================================== + # Compute tau-values in [0,1] for crossings of cell-boundaries + + index1 = int(floor(eta1_curr * Nel[0])) + index1_next = int(floor(eta1_next * Nel[0])) + length1 = int(abs(index1_next - index1)) + + index2 = int(floor(eta2_curr * Nel[1])) + index2_next = int(floor(eta2_next * Nel[1])) + length2 = int(abs(index2_next - index2)) + + index3 = int(floor(eta3_curr * Nel[2])) + index3_next = int(floor(eta3_next * Nel[2])) + length3 = int(abs(index3_next - index3)) + + length = length1 + length2 + length3 + + taus[0] = 0.0 + taus[length + 1] = 1.0 + + tmp1 = taus[1 : length1 + 1] + find_taus(eta1_curr, eta1_next, Nel[0], args_derham.tn1, 1, tmp1) + taus[1 : length1 + 1] = tmp1 + + tmp2 = taus[length1 + 1 : length1 + length2 + 1] + find_taus(eta2_curr, eta2_next, Nel[1], args_derham.tn2, 1, tmp2) + taus[length1 + 1 : length1 + length2 + 1] = tmp2 + + tmp3 = taus[length1 + length2 + 1 : length + 1] + find_taus(eta3_curr, eta3_next, Nel[2], args_derham.tn3, 1, tmp3) + taus[length1 + length2 + 1 : length + 1] = tmp3 + + del tmp1, tmp2, tmp3 + + if length != 0: + tmp4 = taus[0 : length + 1] + quicksort(tmp4, 1, length) + taus[0 : length + 1] = tmp4 + del tmp4 + + # ====================================================================================== + # update velocity in direction 1 + + temp1 = 0.0 + + # loop over the cells + for k in range(length + 1): + a = eta1 + taus[k] * (eta1_curr - eta1) + b = eta1 + taus[k + 1] * (eta1_curr - eta1) + factor = (b - a) / 2 + adding = (a + b) / 2 + + for n in range(n_quad1): + quad_pos1 = factor * loc1[n] + adding + quad_pos2 = factor * loc1[n] + adding + quad_pos3 = factor * loc1[n] + adding + + # spline evaluation + span1, span2, span3 = get_spans( + quad_pos1, + quad_pos2, + quad_pos3, + args_derham, + ) + + # find global index where non-zero basis functions begin + ie1 = span1 - args_derham.pn[0] + ie2 = span2 - args_derham.pn[1] + ie3 = span3 - args_derham.pn[2] + + # (DNN) + for il1 in range(pd1 + 1): + i1 = ie1 + il1 + bi1 = args_derham.bd1[il1] + for il2 in range(pn2 + 1): + i2 = ie2 + il2 + bi2 = bi1 * args_derham.bn2[il2] + for il3 in range(pn3 + 1): + i3 = ie3 + il3 + bi3 = ( + bi2 + * args_derham.bn3[il3] + * e1_1[ + i1 - args_derham.starts[0] + pn1, + i2 - args_derham.starts[1] + pn2, + i3 - args_derham.starts[2] + pn3, + ] + ) + + temp1 += bi3 * weight1[n] + + # ====================================================================================== + # update velocity in direction 2 + + temp2 = 0.0 + + # loop over the cells + for k in range(length + 1): + a = eta2 + taus[k] * (eta2_curr - eta2) + b = eta2 + taus[k + 1] * (eta2_curr - eta2) + factor = (b - a) / 2 + adding = (a + b) / 2 + + for n in range(n_quad2): + quad_pos1 = factor * loc2[n] + adding + quad_pos2 = factor * loc2[n] + adding + quad_pos3 = factor * loc2[n] + adding + + # spline evaluation + span1, span2, span3 = get_spans( + quad_pos1, + quad_pos2, + quad_pos3, + args_derham, + ) + + # find global index where non-zero basis functions begin + ie1 = span1 - args_derham.pn[0] + ie2 = span2 - args_derham.pn[1] + ie3 = span3 - args_derham.pn[2] + + # (NDN) + for il1 in range(pn1 + 1): + i1 = ie1 + il1 + bi1 = args_derham.bn1[il1] + for il2 in range(pd2 + 1): + i2 = ie2 + il2 + bi2 = bi1 * args_derham.bd2[il2] + for il3 in range(pn3 + 1): + i3 = ie3 + il3 + bi3 = ( + bi2 + * args_derham.bn3[il3] + * e1_2[ + i1 - args_derham.starts[0] + pn1, + i2 - args_derham.starts[1] + pn2, + i3 - args_derham.starts[2] + pn3, + ] + ) + + temp2 += bi3 * weight2[n] + + # ====================================================================================== + # update velocity in direction 3 + + temp3 = 0.0 + + # loop over the cells + for k in range(length + 1): + a = eta3 + taus[k] * (eta3_curr - eta3) + b = eta3 + taus[k + 1] * (eta3_curr - eta3) + factor = (b - a) / 2 + adding = (a + b) / 2 + + for n in range(n_quad3): + quad_pos1 = factor * loc3[n] + adding + quad_pos2 = factor * loc3[n] + adding + quad_pos3 = factor * loc3[n] + adding + + # spline evaluation + span1, span2, span3 = get_spans( + quad_pos1, + quad_pos2, + quad_pos3, + args_derham, + ) + + # find global index where non-zero basis functions begin + ie1 = span1 - args_derham.pn[0] + ie2 = span2 - args_derham.pn[1] + ie3 = span3 - args_derham.pn[2] + + # (NND) + for il1 in range(pn1 + 1): + i1 = ie1 + il1 + bi1 = args_derham.bn1[il1] + for il2 in range(pn2 + 1): + i2 = ie2 + il2 + bi2 = bi1 * args_derham.bn2[il2] + for il3 in range(pd3 + 1): + i3 = ie3 + il3 + bi3 = ( + bi2 + * args_derham.bd3[il3] + * e1_3[ + i1 - args_derham.starts[0] + pn1, + i2 - args_derham.starts[1] + pn2, + i3 - args_derham.starts[2] + pn3, + ] + ) + + temp3 += bi3 * weight3[n] + + # v_{n+1} = v_n + dt * DF^{-T}(x_n) * int_0^1 d tau ( E(x_n + tau*(x_{n+1} - x_n) ) ) + v1_next = v1 + dt * kappa * (df_inv[0, 0] * temp1 + df_inv[1, 0] * temp2 + df_inv[2, 0] * temp3) + v2_next = v2 + dt * kappa * (df_inv[0, 1] * temp1 + df_inv[1, 1] * temp2 + df_inv[2, 1] * temp3) + v3_next = v3 + dt * kappa * (df_inv[0, 2] * temp1 + df_inv[1, 2] * temp2 + df_inv[2, 2] * temp3) + + runs += 1 + + if runs == maxiter: + break + + if runs < maxiter: + # print('For convergence this took runs:', runs) + # print() + runs = 0 + + # write the results in the particle array and impose periodic boundary conditions on the particles by taking modulo 1 + particle[0] = eta1_next % 1 + particle[1] = eta2_next % 1 + particle[2] = eta3_next % 1 + particle[3] = v1_next + particle[4] = v2_next + particle[5] = v3_next + + return runs diff --git a/src/struphy/pic/sampling_kernels.py b/src/struphy/pic/sampling_kernels.py index ce68d5aff..821363a97 100644 --- a/src/struphy/pic/sampling_kernels.py +++ b/src/struphy/pic/sampling_kernels.py @@ -93,13 +93,13 @@ def tile_int_kernel( Parameters ---------- - fun: xp.ndarray + fun: np.ndarray The integrand evaluated at the quadrature points (meshgrid). - x_wts, y_wts, z_wts: xp.ndarray + x_wts, y_wts, z_wts: np.ndarray Quadrature weights for tile integral. - out: xp.ndarray + out: np.ndarray The result holding all tile integrals in one sorting box.""" _shp = shape(out) diff --git a/src/struphy/pic/sobol_seq.py b/src/struphy/pic/sobol_seq.py index f4c01347a..430b5442b 100644 --- a/src/struphy/pic/sobol_seq.py +++ b/src/struphy/pic/sobol_seq.py @@ -17,7 +17,7 @@ from __future__ import division -import cunumpy as xp +import numpy as np from scipy.stats import norm __all__ = ["i4_bit_hi1", "i4_bit_lo0", "i4_sobol_generate", "i4_sobol", "i4_uniform", "prime_ge", "is_prime"] @@ -59,7 +59,7 @@ def i4_bit_hi1(n): Output, integer BIT, the number of bits base 2. """ - i = xp.floor(n) + i = np.floor(n) bit = 0 while i > 0: bit += 1 @@ -104,7 +104,7 @@ def i4_bit_lo0(n): Output, integer BIT, the position of the low 1 bit. """ bit = 1 - i = xp.floor(n) + i = np.floor(n) while i != 2 * (i // 2): bit += 1 i //= 2 @@ -122,7 +122,7 @@ def i4_sobol_generate(dim_num, n, skip=1): Output, real R(M,N), the points. """ - r = xp.full((n, dim_num), xp.nan) + r = np.full((n, dim_num), np.nan) for j in range(n): seed = j + skip r[j, 0:dim_num], next_seed = i4_sobol(dim_num, seed) @@ -221,8 +221,8 @@ def i4_sobol(dim_num, seed): seed_save = -1 # Initialize (part of) V. - v = xp.zeros((dim_max, log_max)) - v[0:40, 0] = xp.transpose( + v = np.zeros((dim_max, log_max)) + v[0:40, 0] = np.transpose( [ 1, 1, @@ -264,10 +264,10 @@ def i4_sobol(dim_num, seed): 1, 1, 1, - ], + ] ) - v[2:40, 1] = xp.transpose( + v[2:40, 1] = np.transpose( [ 1, 3, @@ -307,10 +307,10 @@ def i4_sobol(dim_num, seed): 3, 1, 3, - ], + ] ) - v[3:40, 2] = xp.transpose( + v[3:40, 2] = np.transpose( [ 7, 5, @@ -349,10 +349,10 @@ def i4_sobol(dim_num, seed): 1, 3, 3, - ], + ] ) - v[5:40, 3] = xp.transpose( + v[5:40, 3] = np.transpose( [ 1, 7, @@ -389,10 +389,10 @@ def i4_sobol(dim_num, seed): 1, 7, 9, - ], + ] ) - v[7:40, 4] = xp.transpose( + v[7:40, 4] = np.transpose( [ 9, 3, @@ -427,18 +427,18 @@ def i4_sobol(dim_num, seed): 9, 31, 9, - ], + ] ) - v[13:40, 5] = xp.transpose( - [37, 33, 7, 5, 11, 39, 63, 27, 17, 15, 23, 29, 3, 21, 13, 31, 25, 9, 49, 33, 19, 29, 11, 19, 27, 15, 25], + v[13:40, 5] = np.transpose( + [37, 33, 7, 5, 11, 39, 63, 27, 17, 15, 23, 29, 3, 21, 13, 31, 25, 9, 49, 33, 19, 29, 11, 19, 27, 15, 25] ) - v[19:40, 6] = xp.transpose( - [13, 33, 115, 41, 79, 17, 29, 119, 75, 73, 105, 7, 59, 65, 21, 3, 113, 61, 89, 45, 107], + v[19:40, 6] = np.transpose( + [13, 33, 115, 41, 79, 17, 29, 119, 75, 73, 105, 7, 59, 65, 21, 3, 113, 61, 89, 45, 107] ) - v[37:40, 7] = xp.transpose([7, 23, 39]) + v[37:40, 7] = np.transpose([7, 23, 39]) # Set POLY. poly = [ @@ -517,7 +517,7 @@ def i4_sobol(dim_num, seed): # Expand this bit pattern to separate components of the logical array INCLUD. j = poly[i - 1] - includ = xp.zeros(m) + includ = np.zeros(m) for k in range(m, 0, -1): j2 = j // 2 includ[k - 1] = j != 2 * j2 @@ -531,7 +531,7 @@ def i4_sobol(dim_num, seed): for k in range(1, m + 1): l *= 2 if includ[k - 1]: - newv = xp.bitwise_xor(int(newv), int(l * v[i - 1, j - k - 1])) + newv = np.bitwise_xor(int(newv), int(l * v[i - 1, j - k - 1])) v[i - 1, j - 1] = newv # Multiply columns of V by appropriate power of 2. @@ -542,16 +542,16 @@ def i4_sobol(dim_num, seed): # RECIPD is 1/(common denominator of the elements in V). recipd = 1.0 / (2 * l) - lastq = xp.zeros(dim_num) + lastq = np.zeros(dim_num) - seed = int(xp.floor(seed)) + seed = int(np.floor(seed)) if seed < 0: seed = 0 l = 1 if seed == 0: - lastq = xp.zeros(dim_num) + lastq = np.zeros(dim_num) elif seed == seed_save + 1: # Find the position of the right-hand zero in SEED. @@ -559,12 +559,12 @@ def i4_sobol(dim_num, seed): elif seed <= seed_save: seed_save = 0 - lastq = xp.zeros(dim_num) + lastq = np.zeros(dim_num) for seed_temp in range(int(seed_save), int(seed)): l = i4_bit_lo0(seed_temp) for i in range(1, dim_num + 1): - lastq[i - 1] = xp.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) + lastq[i - 1] = np.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) l = i4_bit_lo0(seed) @@ -572,7 +572,7 @@ def i4_sobol(dim_num, seed): for seed_temp in range(int(seed_save + 1), int(seed)): l = i4_bit_lo0(seed_temp) for i in range(1, dim_num + 1): - lastq[i - 1] = xp.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) + lastq[i - 1] = np.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) l = i4_bit_lo0(seed) @@ -585,10 +585,10 @@ def i4_sobol(dim_num, seed): return # Calculate the new components of QUASI. - quasi = xp.zeros(dim_num) + quasi = np.zeros(dim_num) for i in range(1, dim_num + 1): quasi[i - 1] = lastq[i - 1] * recipd - lastq[i - 1] = xp.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) + lastq[i - 1] = np.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) seed_save = seed seed += 1 @@ -638,11 +638,11 @@ def i4_uniform(a, b, seed): print("I4_UNIFORM - Fatal error!") print(" Input SEED = 0!") - seed = xp.floor(seed) + seed = np.floor(seed) a = round(a) b = round(b) - seed = xp.mod(seed, 2147483647) + seed = np.mod(seed, 2147483647) if seed < 0: seed += 2147483647 @@ -696,7 +696,7 @@ def prime_ge(n): Output, integer P, the smallest prime number that is greater than or equal to N. """ - p = max(xp.ceil(n), 2) + p = max(np.ceil(n), 2) while not is_prime(p): p += 1 @@ -720,7 +720,7 @@ def is_prime(n): return False # All primes >3 are of the form 6n+1 or 6n+5 (6n, 6n+2, 6n+4 are 2-divisible, 6n+3 is 3-divisible) p = 5 - root = int(xp.ceil(xp.sqrt(n))) + root = int(np.ceil(np.sqrt(n))) while p <= root: if n % p == 0 or n % (p + 2) == 0: return False diff --git a/src/struphy/pic/sph_eval_kernels.py b/src/struphy/pic/sph_eval_kernels.py index 4c63e0156..37414f447 100644 --- a/src/struphy/pic/sph_eval_kernels.py +++ b/src/struphy/pic/sph_eval_kernels.py @@ -297,19 +297,7 @@ def naive_evaluation_meshgrid( e2 = eta2[i, j, k] e3 = eta3[i, j, k] out[i, j, k] = naive_evaluation_kernel( - args_markers, - e1, - e2, - e3, - holes, - periodic1, - periodic2, - periodic3, - index, - kernel_type, - h1, - h2, - h3, + args_markers, e1, e2, e3, holes, periodic1, periodic2, periodic3, index, kernel_type, h1, h2, h3 ) diff --git a/src/struphy/pic/tests/test_accum_vec_H1.py b/src/struphy/pic/tests/test_accum_vec_H1.py index cb5cbb17e..3f2ce4923 100644 --- a/src/struphy/pic/tests/test_accum_vec_H1.py +++ b/src/struphy/pic/tests/test_accum_vec_H1.py @@ -1,13 +1,11 @@ import pytest -from struphy.utils.pyccel import Pyccelkernel - +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[2, 3, 4]]) @pytest.mark.parametrize( - "spl_kind", - [[False, False, True], [False, True, True], [True, False, True], [True, True, True]], + "spl_kind", [[False, False, True], [False, True, True], [True, False, True], [True, True, True]] ) @pytest.mark.parametrize( "mapping", @@ -48,9 +46,8 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): import copy - import cunumpy as xp - from psydac.ddm.mpi import MockComm - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.psydac_derham import Derham @@ -61,12 +58,8 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): from struphy.pic.utilities import BoundaryParameters, LoadingParameters, WeightsParameters from struphy.utils.clone_config import CloneConfig - if isinstance(MPI.COMM_WORLD, MockComm): - mpi_comm = None - mpi_rank = 0 - else: - mpi_comm = MPI.COMM_WORLD - mpi_rank = mpi_comm.Get_rank() + mpi_comm = MPI.COMM_WORLD + mpi_rank = mpi_comm.Get_rank() # domain object dom_type = mapping[0] @@ -77,26 +70,17 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): params = { "grid": {"Nel": Nel}, - "kinetic": {"test_particles": {"markers": {"Np": Np, "ppc": Np / xp.prod(Nel)}}}, + "kinetic": {"test_particles": {"markers": {"Np": Np, "ppc": Np / np.prod(Nel)}}}, } - if mpi_comm is None: - clone_config = None - - derham = Derham( - Nel, - p, - spl_kind, - comm=None, - ) - else: - clone_config = CloneConfig(comm=mpi_comm, params=params, num_clones=num_clones) - - derham = Derham( - Nel, - p, - spl_kind, - comm=clone_config.sub_comm, - ) + clone_config = CloneConfig(comm=mpi_comm, params=params, num_clones=num_clones) + + # DeRham object + derham = Derham( + Nel, + p, + spl_kind, + comm=clone_config.sub_comm, + ) domain_array = derham.domain_array nprocs = derham.domain_decomposition.nprocs @@ -122,20 +106,19 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): ) particles.draw_markers() - if mpi_comm is not None: - particles.mpi_sort_markers() + particles.mpi_sort_markers() particles.initialize_weights() _vdim = particles.vdim _w0 = particles.weights print("Test weights:") - print(f"rank {mpi_rank}:", _w0.shape, xp.min(_w0), xp.max(_w0)) + print(f"rank {mpi_rank}:", _w0.shape, np.min(_w0), np.max(_w0)) _sqrtg = domain.jacobian_det(0.5, 0.5, 0.5) - assert xp.isclose(xp.min(_w0), _sqrtg) - assert xp.isclose(xp.max(_w0), _sqrtg) + assert np.isclose(np.min(_w0), _sqrtg) + assert np.isclose(np.max(_w0), _sqrtg) # mass operators mass_ops = WeightedMassOperators(derham, domain) @@ -144,36 +127,33 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): acc = AccumulatorVector( particles, "H1", - Pyccelkernel(accum_kernels.charge_density_0form), + accum_kernels.charge_density_0form, mass_ops, domain.args_domain, ) - acc() + acc(particles.vdim) # sum all MC integrals - _sum_within_clone = xp.empty(1, dtype=float) - _sum_within_clone[0] = xp.sum(acc.vectors[0].toarray()) - if clone_config is not None: - clone_config.sub_comm.Allreduce(MPI.IN_PLACE, _sum_within_clone, op=MPI.SUM) + _sum_within_clone = np.empty(1, dtype=float) + _sum_within_clone[0] = np.sum(acc.vectors[0].toarray()) + clone_config.sub_comm.Allreduce(MPI.IN_PLACE, _sum_within_clone, op=MPI.SUM) - print(f"rank {mpi_rank}: {_sum_within_clone =}, {_sqrtg =}") + print(f"rank {mpi_rank}: {_sum_within_clone = }, {_sqrtg = }") # Check within clone - assert xp.isclose(_sum_within_clone, _sqrtg) + assert np.isclose(_sum_within_clone, _sqrtg) # Check for all clones - _sum_between_clones = xp.empty(1, dtype=float) - _sum_between_clones[0] = xp.sum(acc.vectors[0].toarray()) - - if mpi_comm is not None: - mpi_comm.Allreduce(MPI.IN_PLACE, _sum_between_clones, op=MPI.SUM) - clone_config.inter_comm.Allreduce(MPI.IN_PLACE, _sqrtg, op=MPI.SUM) + _sum_between_clones = np.empty(1, dtype=float) + _sum_between_clones[0] = np.sum(acc.vectors[0].toarray()) + mpi_comm.Allreduce(MPI.IN_PLACE, _sum_between_clones, op=MPI.SUM) + clone_config.inter_comm.Allreduce(MPI.IN_PLACE, _sqrtg, op=MPI.SUM) - print(f"rank {mpi_rank}: {_sum_between_clones =}, {_sqrtg =}") + print(f"rank {mpi_rank}: {_sum_between_clones = }, {_sqrtg = }") # Check within clone - assert xp.isclose(_sum_between_clones, _sqrtg) + assert np.isclose(_sum_between_clones, _sqrtg) if __name__ == "__main__": diff --git a/src/struphy/pic/tests/test_accumulation.py b/src/struphy/pic/tests/test_accumulation.py index ed3a41ff4..7fcd8f039 100644 --- a/src/struphy/pic/tests/test_accumulation.py +++ b/src/struphy/pic/tests/test_accumulation.py @@ -1,13 +1,11 @@ import pytest -from struphy.utils.pyccel import Pyccelkernel - +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[2, 3, 4]]) @pytest.mark.parametrize( - "spl_kind", - [[False, False, True], [False, True, False], [True, False, True], [True, True, False]], + "spl_kind", [[False, False, True], [False, True, False], [True, False, True], [True, True, False]] ) @pytest.mark.parametrize( "mapping", @@ -37,7 +35,7 @@ def test_accumulation(Nel, p, spl_kind, mapping, Np=40, verbose=False): The times for both legacy and the new way are printed if verbose == True. This comparison only makes sense if the ..test_pic_legacy_files/ are also all compiled. """ - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI rank = MPI.COMM_WORLD.Get_rank() @@ -49,9 +47,8 @@ def test_accumulation(Nel, p, spl_kind, mapping, Np=40, verbose=False): def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): from time import time - import cunumpy as xp - from psydac.ddm.mpi import MockComm - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.mass import WeightedMassOperators @@ -64,15 +61,10 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): from struphy.pic.tests.test_pic_legacy_files.accumulation_kernels_3d import kernel_step_ph_full from struphy.pic.utilities import BoundaryParameters, LoadingParameters, WeightsParameters - if isinstance(MPI.COMM_WORLD, MockComm): - mpi_comm = None - rank = 0 - mpi_size = 1 - else: - mpi_comm = MPI.COMM_WORLD - # assert mpi_comm.size >= 2 - rank = mpi_comm.Get_rank() - mpi_size = mpi_comm.Get_size() + mpi_comm = MPI.COMM_WORLD + # assert mpi_comm.size >= 2 + rank = mpi_comm.Get_rank() + mpi_size = mpi_comm.Get_size() # DOMAIN object dom_type = mapping[0] @@ -108,17 +100,15 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): particles.markers[ ~particles.holes, 6, - ] = xp.random.rand(particles.n_mks_loc) + ] = np.random.rand(particles.n_mks_loc) # gather all particles for legacy kernel - if mpi_comm is None: - marker_shapes = xp.array([particles.markers.shape[0]]) - else: - marker_shapes = xp.zeros(mpi_size, dtype=int) - mpi_comm.Allgather(xp.array([particles.markers.shape[0]]), marker_shapes) + marker_shapes = np.zeros(mpi_size, dtype=int) + + mpi_comm.Allgather(np.array([particles.markers.shape[0]]), marker_shapes) print(rank, marker_shapes) - particles_leg = xp.zeros( + particles_leg = np.zeros( (sum(marker_shapes), particles.markers.shape[1]), dtype=float, ) @@ -129,7 +119,7 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): cumulative_lengths = marker_shapes[0] for i in range(1, mpi_size): - arr_recv = xp.zeros( + arr_recv = np.zeros( (marker_shapes[i], particles.markers.shape[1]), dtype=float, ) @@ -140,8 +130,7 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): else: mpi_comm.Send(particles.markers, dest=0) - if mpi_comm is not None: - mpi_comm.Bcast(particles_leg, root=0) + mpi_comm.Bcast(particles_leg, root=0) # sort new particles if particles.mpi_comm: @@ -162,10 +151,10 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): for a in range(3): Ni = SPACES.Nbase_1form[a] - vec[a] = xp.zeros((Ni[0], Ni[1], Ni[2], 3), dtype=float) + vec[a] = np.zeros((Ni[0], Ni[1], Ni[2], 3), dtype=float) for b in range(3): - mat[a][b] = xp.zeros( + mat[a][b] = np.zeros( ( Ni[0], Ni[1], @@ -187,21 +176,21 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): SPACES.T[0], SPACES.T[1], SPACES.T[2], - xp.array(SPACES.p), - xp.array(Nel), - xp.array(SPACES.NbaseN), - xp.array(SPACES.NbaseD), + np.array(SPACES.p), + np.array(Nel), + np.array(SPACES.NbaseN), + np.array(SPACES.NbaseD), particles_leg.shape[0], domain.kind_map, domain.params_numpy, domain.T[0], domain.T[1], domain.T[2], - xp.array(domain.p), - xp.array( + np.array(domain.p), + np.array( domain.Nel, ), - xp.array(domain.NbaseN), + np.array(domain.NbaseN), domain.cx, domain.cy, domain.cz, @@ -218,7 +207,7 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): ) end_time = time() - tot_time = xp.round(end_time - start_time, 3) + tot_time = np.round(end_time - start_time, 3) mat[0][0] /= Np mat[0][1] /= Np @@ -240,7 +229,7 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): ACC = Accumulator( particles, "Hcurl", - Pyccelkernel(accum_kernels.pc_lin_mhd_6d_full), + accum_kernels.pc_lin_mhd_6d_full, mass_ops, domain.args_domain, add_vector=True, @@ -248,12 +237,10 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): ) start_time = time() - ACC( - 1.0, - ) + ACC(1.0, 1.0, 0.0) end_time = time() - tot_time = xp.round(end_time - start_time, 3) + tot_time = np.round(end_time - start_time, 3) if rank == 0 and verbose: print(f"Step ph New took {tot_time} seconds.") diff --git a/src/struphy/pic/tests/test_binning.py b/src/struphy/pic/tests/test_binning.py index cda2524e7..2e7500e01 100644 --- a/src/struphy/pic/tests/test_binning.py +++ b/src/struphy/pic/tests/test_binning.py @@ -35,9 +35,9 @@ def test_binning_6D_full_f(mapping, show_plot=False): name and specification of the mapping """ - import cunumpy as xp import matplotlib.pyplot as plt - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.geometry import domains from struphy.initial import perturbations @@ -79,7 +79,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): # test weights particles.initialize_weights() - v1_bins = xp.linspace(-5.0, 5.0, 200, endpoint=True) + v1_bins = np.linspace(-5.0, 5.0, 200, endpoint=True) dv = v1_bins[1] - v1_bins[0] binned_res, r2 = particles.binning( @@ -89,7 +89,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): v1_plot = v1_bins[:-1] + dv / 2 - ana_res = 1.0 / xp.sqrt(2.0 * xp.pi) * xp.exp(-(v1_plot**2) / 2.0) + ana_res = 1.0 / np.sqrt(2.0 * np.pi) * np.exp(-(v1_plot**2) / 2.0) if show_plot: plt.plot(v1_plot, ana_res, label="Analytical result") @@ -100,7 +100,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.02, f"Error between binned data and analytical result was {l2_error}" @@ -122,7 +122,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -132,7 +132,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): e1_plot = e1_bins[:-1] + de / 2 - ana_res = 1.0 + amp_n * xp.cos(2 * xp.pi * l_n * e1_plot) + ana_res = 1.0 + amp_n * np.cos(2 * np.pi * l_n * e1_plot) if show_plot: plt.plot(e1_plot, ana_res, label="Analytical result") @@ -143,7 +143,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.02, f"Error between binned data and analytical result was {l2_error}" @@ -182,7 +182,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -192,7 +192,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): e1_plot = e1_bins[:-1] + de / 2 - ana_res = n1 + amp_n1 * xp.cos(2 * xp.pi * l_n1 * e1_plot) + n2 + amp_n2 * xp.cos(2 * xp.pi * l_n2 * e1_plot) + ana_res = n1 + amp_n1 * np.cos(2 * np.pi * l_n1 * e1_plot) + n2 + amp_n2 * np.cos(2 * np.pi * l_n2 * e1_plot) # Compare s0 and the sum of two Maxwellians if show_plot: @@ -206,14 +206,14 @@ def test_binning_6D_full_f(mapping, show_plot=False): vth3=(particles.loading_params.moments[5], None), ) - v1 = xp.linspace(-10.0, 10.0, 400) - phase_space = xp.meshgrid( - xp.array([0.0]), - xp.array([0.0]), - xp.array([0.0]), + v1 = np.linspace(-10.0, 10.0, 400) + phase_space = np.meshgrid( + np.array([0.0]), + np.array([0.0]), + np.array([0.0]), v1, - xp.array([0.0]), - xp.array([0.0]), + np.array([0.0]), + np.array([0.0]), ) s0_vals = s0(*phase_space).squeeze() @@ -235,7 +235,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.04, f"Error between binned data and analytical result was {l2_error}" @@ -268,9 +268,9 @@ def test_binning_6D_delta_f(mapping, show_plot=False): name and specification of the mapping """ - import cunumpy as xp import matplotlib.pyplot as plt - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.geometry import domains from struphy.initial import perturbations @@ -316,7 +316,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -326,7 +326,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): e1_plot = e1_bins[:-1] + de / 2 - ana_res = amp_n * xp.cos(2 * xp.pi * l_n * e1_plot) + ana_res = amp_n * np.cos(2 * np.pi * l_n * e1_plot) if show_plot: plt.plot(e1_plot, ana_res, label="Analytical result") @@ -337,7 +337,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.02, f"Error between binned data and analytical result was {l2_error}" @@ -376,7 +376,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -386,7 +386,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): e1_plot = e1_bins[:-1] + de / 2 - ana_res = amp_n1 * xp.cos(2 * xp.pi * l_n1 * e1_plot) + amp_n2 * xp.cos(2 * xp.pi * l_n2 * e1_plot) + ana_res = amp_n1 * np.cos(2 * np.pi * l_n1 * e1_plot) + amp_n2 * np.cos(2 * np.pi * l_n2 * e1_plot) # Compare s0 and the sum of two Maxwellians if show_plot: @@ -400,14 +400,14 @@ def test_binning_6D_delta_f(mapping, show_plot=False): vth3=(particles.loading_params.moments[5], None), ) - v1 = xp.linspace(-10.0, 10.0, 400) - phase_space = xp.meshgrid( - xp.array([0.0]), - xp.array([0.0]), - xp.array([0.0]), + v1 = np.linspace(-10.0, 10.0, 400) + phase_space = np.meshgrid( + np.array([0.0]), + np.array([0.0]), + np.array([0.0]), v1, - xp.array([0.0]), - xp.array([0.0]), + np.array([0.0]), + np.array([0.0]), ) s0_vals = s0(*phase_space).squeeze() @@ -429,7 +429,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.04, f"Error between binned data and analytical result was {l2_error}" @@ -437,6 +437,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): # ========================================== # ========== multi-threaded tests ========== # ========================================== +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize( "mapping", [ @@ -464,10 +465,9 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): name and specification of the mapping """ - import cunumpy as xp import matplotlib.pyplot as plt - from psydac.ddm.mpi import MockComm - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.geometry import domains from struphy.initial import perturbations @@ -490,14 +490,10 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): domain = domain_class(**mapping[1]) # Psydac discrete Derham sequence - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - size = 1 - rank = 0 - else: - comm = MPI.COMM_WORLD - size = comm.Get_size() - rank = comm.Get_rank() + comm = MPI.COMM_WORLD + size = comm.Get_size() + rank = comm.Get_rank() + assert size > 1 # create particles bc_params = ("periodic", "periodic", "periodic") @@ -519,7 +515,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): # test weights particles.initialize_weights() - v1_bins = xp.linspace(-5.0, 5.0, 200, endpoint=True) + v1_bins = np.linspace(-5.0, 5.0, 200, endpoint=True) dv = v1_bins[1] - v1_bins[0] binned_res, r2 = particles.binning( @@ -528,16 +524,13 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - if comm is None: - mpi_res = binned_res - else: - mpi_res = xp.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + mpi_res = np.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() v1_plot = v1_bins[:-1] + dv / 2 - ana_res = 1.0 / xp.sqrt(2.0 * xp.pi) * xp.exp(-(v1_plot**2) / 2.0) + ana_res = 1.0 / np.sqrt(2.0 * np.pi) * np.exp(-(v1_plot**2) / 2.0) if show_plot and rank == 0: plt.plot(v1_plot, ana_res, label="Analytical result") @@ -548,7 +541,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.03, f"Error between binned data and analytical result was {l2_error}" @@ -571,7 +564,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -580,16 +573,13 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - if comm is None: - mpi_res = binned_res - else: - mpi_res = xp.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + mpi_res = np.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() e1_plot = e1_bins[:-1] + de / 2 - ana_res = 1.0 + amp_n * xp.cos(2 * xp.pi * l_n * e1_plot) + ana_res = 1.0 + amp_n * np.cos(2 * np.pi * l_n * e1_plot) if show_plot and rank == 0: plt.plot(e1_plot, ana_res, label="Analytical result") @@ -600,7 +590,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.03, f"Error between binned data and analytical result was {l2_error}" @@ -631,8 +621,8 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n1], "amps": [amp_n1], - }, - }, + } + } }, "Maxwellian3D_2": { "n": { @@ -640,8 +630,8 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n2], "amps": [amp_n2], - }, - }, + } + } }, } pert_1 = perturbations.ModesCos(ls=(l_n1,), amps=(amp_n1,)) @@ -668,7 +658,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -677,16 +667,13 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - if comm is None: - mpi_res = binned_res - else: - mpi_res = xp.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + mpi_res = np.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() e1_plot = e1_bins[:-1] + de / 2 - ana_res = n1 + amp_n1 * xp.cos(2 * xp.pi * l_n1 * e1_plot) + n2 + amp_n2 * xp.cos(2 * xp.pi * l_n2 * e1_plot) + ana_res = n1 + amp_n1 * np.cos(2 * np.pi * l_n1 * e1_plot) + n2 + amp_n2 * np.cos(2 * np.pi * l_n2 * e1_plot) # Compare s0 and the sum of two Maxwellians if show_plot and rank == 0: @@ -700,14 +687,14 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): vth3=(particles.loading_params.moments[5], None), ) - v1 = xp.linspace(-10.0, 10.0, 400) - phase_space = xp.meshgrid( - xp.array([0.0]), - xp.array([0.0]), - xp.array([0.0]), + v1 = np.linspace(-10.0, 10.0, 400) + phase_space = np.meshgrid( + np.array([0.0]), + np.array([0.0]), + np.array([0.0]), v1, - xp.array([0.0]), - xp.array([0.0]), + np.array([0.0]), + np.array([0.0]), ) s0_vals = s0(*phase_space).squeeze() @@ -729,11 +716,12 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.04, f"Error between binned data and analytical result was {l2_error}" +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize( "mapping", [ @@ -761,10 +749,9 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): name and specification of the mapping """ - import cunumpy as xp import matplotlib.pyplot as plt - from psydac.ddm.mpi import MockComm - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.geometry import domains from struphy.initial import perturbations @@ -787,14 +774,10 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): domain = domain_class(**mapping[1]) # Psydac discrete Derham sequence - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - size = 1 - rank = 0 - else: - comm = MPI.COMM_WORLD - size = comm.Get_size() - rank = comm.Get_rank() + comm = MPI.COMM_WORLD + size = comm.Get_size() + rank = comm.Get_rank() + assert size > 1 # create particles bc_params = ("periodic", "periodic", "periodic") @@ -814,8 +797,8 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n], "amps": [amp_n], - }, - }, + } + } } pert = perturbations.ModesCos(ls=(l_n,), amps=(amp_n,)) background = Maxwellian3D(n=(1.0, pert)) @@ -830,7 +813,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -839,16 +822,13 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - if comm is None: - mpi_res = binned_res - else: - mpi_res = xp.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + mpi_res = np.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() e1_plot = e1_bins[:-1] + de / 2 - ana_res = amp_n * xp.cos(2 * xp.pi * l_n * e1_plot) + ana_res = amp_n * np.cos(2 * np.pi * l_n * e1_plot) if show_plot and rank == 0: plt.plot(e1_plot, ana_res, label="Analytical result") @@ -859,7 +839,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.02, f"Error between binned data and analytical result was {l2_error}" @@ -891,7 +871,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n1], "amps": [amp_n1], - }, + } }, }, "Maxwellian3D_2": { @@ -901,7 +881,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n2], "amps": [amp_n2], - }, + } }, }, } @@ -929,7 +909,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -938,16 +918,13 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - if comm is None: - mpi_res = binned_res - else: - mpi_res = xp.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + mpi_res = np.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() e1_plot = e1_bins[:-1] + de / 2 - ana_res = amp_n1 * xp.cos(2 * xp.pi * l_n1 * e1_plot) + amp_n2 * xp.cos(2 * xp.pi * l_n2 * e1_plot) + ana_res = amp_n1 * np.cos(2 * np.pi * l_n1 * e1_plot) + amp_n2 * np.cos(2 * np.pi * l_n2 * e1_plot) # Compare s0 and the sum of two Maxwellians if show_plot and rank == 0: @@ -961,14 +938,14 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): vth3=(particles.loading_params.moments[5], None), ) - v1 = xp.linspace(-10.0, 10.0, 400) - phase_space = xp.meshgrid( - xp.array([0.0]), - xp.array([0.0]), - xp.array([0.0]), + v1 = np.linspace(-10.0, 10.0, 400) + phase_space = np.meshgrid( + np.array([0.0]), + np.array([0.0]), + np.array([0.0]), v1, - xp.array([0.0]), - xp.array([0.0]), + np.array([0.0]), + np.array([0.0]), ) s0_vals = s0(*phase_space).squeeze() @@ -990,23 +967,16 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) + l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) assert l2_error <= 0.04, f"Error between binned data and analytical result was {l2_error}" if __name__ == "__main__": - from psydac.ddm.mpi import MockComm - from psydac.ddm.mpi import mpi as MPI + from mpi4py import MPI - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - size = 1 - rank = 0 - else: - comm = MPI.COMM_WORLD - size = comm.Get_size() - rank = comm.Get_rank() + comm = MPI.COMM_WORLD + size = comm.Get_size() if comm is None or size == 1: test_binning_6D_full_f( diff --git a/src/struphy/pic/tests/test_draw_parallel.py b/src/struphy/pic/tests/test_draw_parallel.py index cf95f4dc7..c4520eeab 100644 --- a/src/struphy/pic/tests/test_draw_parallel.py +++ b/src/struphy/pic/tests/test_draw_parallel.py @@ -1,6 +1,7 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -35,8 +36,8 @@ def test_draw(Nel, p, spl_kind, mapping, ppc=10): """Asserts whether all particles are on the correct process after `particles.mpi_sort_markers()`.""" - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.geometry import domains @@ -44,6 +45,7 @@ def test_draw(Nel, p, spl_kind, mapping, ppc=10): from struphy.pic.utilities import BoundaryParameters, LoadingParameters, WeightsParameters comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() seed = 1234 @@ -85,7 +87,7 @@ def test_draw(Nel, p, spl_kind, mapping, ppc=10): particles.initialize_weights() _w0 = particles.weights print("Test weights:") - print(f"rank {rank}:", _w0.shape, xp.min(_w0), xp.max(_w0)) + print(f"rank {rank}:", _w0.shape, np.min(_w0), np.max(_w0)) comm.Barrier() print("Number of particles w/wo holes on each process before sorting : ") @@ -106,17 +108,17 @@ def test_draw(Nel, p, spl_kind, mapping, ppc=10): print("Rank", rank, ":", particles.n_mks_loc, particles.markers.shape[0]) # are all markers in the correct domain? - conds = xp.logical_and( + conds = np.logical_and( particles.markers[:, :3] > derham.domain_array[rank, 0::3], particles.markers[:, :3] < derham.domain_array[rank, 1::3], ) holes = particles.markers[:, 0] == -1.0 - stay = xp.all(conds, axis=1) + stay = np.all(conds, axis=1) - error_mks = particles.markers[xp.logical_and(~stay, ~holes)] + error_mks = particles.markers[np.logical_and(~stay, ~holes)] assert error_mks.size == 0, ( - f"rank {rank} | markers not on correct process: {xp.nonzero(xp.logical_and(~stay, ~holes))} \n corresponding positions:\n {error_mks[:, :3]}" + f"rank {rank} | markers not on correct process: {np.nonzero(np.logical_and(~stay, ~holes))} \n corresponding positions:\n {error_mks[:, :3]}" ) diff --git a/src/struphy/pic/tests/test_mat_vec_filler.py b/src/struphy/pic/tests/test_mat_vec_filler.py index 073d52ae7..36f3924f0 100644 --- a/src/struphy/pic/tests/test_mat_vec_filler.py +++ b/src/struphy/pic/tests/test_mat_vec_filler.py @@ -1,7 +1,8 @@ -import cunumpy as xp +import numpy as np import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -14,8 +15,8 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): from time import sleep + from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL - from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix, StencilVector from struphy.bsplines import bsplines_kernels as bsp @@ -23,6 +24,7 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): from struphy.pic.accumulation import particle_to_mat_kernels as ptomat comm = MPI.COMM_WORLD + assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -32,12 +34,12 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): print(f"\nNel={Nel}, p={p}, spl_kind={spl_kind}\n") # DR attributes - pn = xp.array(DR.p) + pn = np.array(DR.p) tn1, tn2, tn3 = DR.Vh_fem["0"].knots starts1 = {} - starts1["v0"] = xp.array(DR.Vh["0"].starts) + starts1["v0"] = np.array(DR.Vh["0"].starts) comm.Barrier() sleep(0.02 * (rank + 1)) @@ -70,11 +72,8 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): for j in range(3): mat["v1"][-1] += [ StencilMatrix( - DR.Vh["1"].spaces[i], - DR.Vh["1"].spaces[j], - backend=PSYDAC_BACKEND_GPYCCEL, - precompiled=True, - )._data, + DR.Vh["1"].spaces[i], DR.Vh["1"].spaces[j], backend=PSYDAC_BACKEND_GPYCCEL, precompiled=True + )._data ] vec["v1"] = [] @@ -87,11 +86,8 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): for j in range(3): mat["v2"][-1] += [ StencilMatrix( - DR.Vh["2"].spaces[i], - DR.Vh["2"].spaces[j], - backend=PSYDAC_BACKEND_GPYCCEL, - precompiled=True, - )._data, + DR.Vh["2"].spaces[i], DR.Vh["2"].spaces[j], backend=PSYDAC_BACKEND_GPYCCEL, precompiled=True + )._data ] vec["v2"] = [] @@ -99,14 +95,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): vec["v2"] += [StencilVector(DR.Vh["2"].spaces[i])._data] # Some filling for testing - fill_mat = xp.reshape(xp.arange(9, dtype=float), (3, 3)) + 1.0 - fill_vec = xp.arange(3, dtype=float) + 1.0 + fill_mat = np.reshape(np.arange(9, dtype=float), (3, 3)) + 1.0 + fill_vec = np.arange(3, dtype=float) + 1.0 # Random points in domain of process (VERY IMPORTANT to be in the right domain, otherwise NON-TRACKED errors occur in filler_kernels !!) dom = DR.domain_array[rank] - eta1s = xp.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] - eta2s = xp.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] - eta3s = xp.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] + eta1s = np.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] + eta2s = np.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] + eta3s = np.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] for eta1, eta2, eta3 in zip(eta1s, eta2s, eta3s): comm.Barrier() @@ -123,13 +119,13 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): span3 = bsp.find_span(tn3, DR.p[2], eta3) # non-zero spline values at eta - bn1 = xp.empty(DR.p[0] + 1, dtype=float) - bn2 = xp.empty(DR.p[1] + 1, dtype=float) - bn3 = xp.empty(DR.p[2] + 1, dtype=float) + bn1 = np.empty(DR.p[0] + 1, dtype=float) + bn2 = np.empty(DR.p[1] + 1, dtype=float) + bn3 = np.empty(DR.p[2] + 1, dtype=float) - bd1 = xp.empty(DR.p[0], dtype=float) - bd2 = xp.empty(DR.p[1], dtype=float) - bd3 = xp.empty(DR.p[2], dtype=float) + bd1 = np.empty(DR.p[0], dtype=float) + bd2 = np.empty(DR.p[1], dtype=float) + bd3 = np.empty(DR.p[2], dtype=float) bsp.b_d_splines_slim(tn1, DR.p[0], eta1, span1, bn1, bd1) bsp.b_d_splines_slim(tn2, DR.p[1], eta2, span2, bn2, bd2) @@ -141,9 +137,9 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): ie3 = span3 - pn[2] # global indices of non-vanishing B- and D-splines (no modulo) - glob_n1 = xp.arange(ie1, ie1 + pn[0] + 1) - glob_n2 = xp.arange(ie2, ie2 + pn[1] + 1) - glob_n3 = xp.arange(ie3, ie3 + pn[2] + 1) + glob_n1 = np.arange(ie1, ie1 + pn[0] + 1) + glob_n2 = np.arange(ie2, ie2 + pn[1] + 1) + glob_n3 = np.arange(ie3, ie3 + pn[2] + 1) glob_d1 = glob_n1[:-1] glob_d2 = glob_n2[:-1] @@ -169,10 +165,10 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): # local column indices in _data of non-vanishing B- and D-splines, as sets for comparison cols = [{}, {}, {}] for n in range(3): - cols[n]["NN"] = set(xp.arange(2 * pn[n] + 1)) - cols[n]["ND"] = set(xp.arange(2 * pn[n])) - cols[n]["DN"] = set(xp.arange(1, 2 * pn[n] + 1)) - cols[n]["DD"] = set(xp.arange(1, 2 * pn[n])) + cols[n]["NN"] = set(np.arange(2 * pn[n] + 1)) + cols[n]["ND"] = set(np.arange(2 * pn[n])) + cols[n]["DN"] = set(np.arange(1, 2 * pn[n] + 1)) + cols[n]["DD"] = set(np.arange(1, 2 * pn[n])) # testing vector-valued spaces spaces_vector = ["v1", "v2"] @@ -219,13 +215,7 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): for n, ij in enumerate(ind_pairs): assert_mat( - args[n], - rows, - cols, - basis[space][ij[0]], - basis[space][ij[1]], - rank, - verbose=False, + args[n], rows, cols, basis[space][ij[0]], basis[space][ij[1]], rank, verbose=False ) # assertion test of mat if mv == "m_v": for i in range(3): @@ -242,13 +232,7 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): for n, ij in enumerate(ind_pairs): assert_mat( - args[n], - rows, - cols, - basis[space][ij[0]], - basis[space][ij[1]], - rank, - verbose=False, + args[n], rows, cols, basis[space][ij[0]], basis[space][ij[1]], rank, verbose=False ) # assertion test of mat if mv == "m_v": for i in range(3): @@ -261,14 +245,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): # testing salar spaces if rank == 0: - print("\nTesting mat_fill_b_v0 ...") + print(f"\nTesting mat_fill_b_v0 ...") ptomat.mat_fill_b_v0(DR.args_derham, eta1, eta2, eta3, mat["v0"], fill_mat[0, 0]) assert_mat(mat["v0"], rows, cols, basis["v0"], basis["v0"], rank) # assertion test of mat count += 1 comm.Barrier() if rank == 0: - print("\nTesting m_v_fill_b_v0 ...") + print(f"\nTesting m_v_fill_b_v0 ...") ptomat.m_v_fill_b_v0(DR.args_derham, eta1, eta2, eta3, mat["v0"], fill_mat[0, 0], vec["v0"], fill_vec[0]) assert_mat(mat["v0"], rows, cols, basis["v0"], basis["v0"], rank) # assertion test of mat assert_vec(vec["v0"], rows, basis["v0"], rank) # assertion test of vec @@ -276,14 +260,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): comm.Barrier() if rank == 0: - print("\nTesting mat_fill_b_v3 ...") + print(f"\nTesting mat_fill_b_v3 ...") ptomat.mat_fill_b_v3(DR.args_derham, eta1, eta2, eta3, mat["v3"], fill_mat[0, 0]) assert_mat(mat["v3"], rows, cols, basis["v3"], basis["v3"], rank) # assertion test of mat count += 1 comm.Barrier() if rank == 0: - print("\nTesting m_v_fill_b_v3 ...") + print(f"\nTesting m_v_fill_b_v3 ...") ptomat.m_v_fill_b_v3(DR.args_derham, eta1, eta2, eta3, mat["v3"], fill_mat[0, 0], vec["v3"], fill_vec[0]) assert_mat(mat["v3"], rows, cols, basis["v3"], basis["v3"], rank) # assertion test of mat assert_vec(vec["v3"], rows, basis["v3"], rank) # assertion test of vec @@ -291,14 +275,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): comm.Barrier() if rank == 0: - print("\nTesting mat_fill_v0 ...") + print(f"\nTesting mat_fill_v0 ...") ptomat.mat_fill_v0(DR.args_derham, span1, span2, span3, mat["v0"], fill_mat[0, 0]) assert_mat(mat["v0"], rows, cols, basis["v0"], basis["v0"], rank) # assertion test of mat count += 1 comm.Barrier() if rank == 0: - print("\nTesting m_v_fill_v0 ...") + print(f"\nTesting m_v_fill_v0 ...") ptomat.m_v_fill_v0(DR.args_derham, span1, span2, span3, mat["v0"], fill_mat[0, 0], vec["v0"], fill_vec[0]) assert_mat(mat["v0"], rows, cols, basis["v0"], basis["v0"], rank) # assertion test of mat assert_vec(vec["v0"], rows, basis["v0"], rank) # assertion test of vec @@ -306,14 +290,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): comm.Barrier() if rank == 0: - print("\nTesting mat_fill_v3 ...") + print(f"\nTesting mat_fill_v3 ...") ptomat.mat_fill_v3(DR.args_derham, span1, span2, span3, mat["v3"], fill_mat[0, 0]) assert_mat(mat["v3"], rows, cols, basis["v3"], basis["v3"], rank) # assertion test of mat count += 1 comm.Barrier() if rank == 0: - print("\nTesting m_v_fill_v3 ...") + print(f"\nTesting m_v_fill_v3 ...") ptomat.m_v_fill_v3(DR.args_derham, span1, span2, span3, mat["v3"], fill_mat[0, 0], vec["v3"], fill_vec[0]) assert_mat(mat["v3"], rows, cols, basis["v3"], basis["v3"], rank) # assertion test of mat assert_vec(vec["v3"], rows, basis["v3"], rank) # assertion test of vec @@ -354,23 +338,23 @@ def assert_mat(mat, rows, cols, row_str, col_str, rank, verbose=False): """ assert len(mat.shape) == 6 # assert non NaN - assert ~xp.isnan(mat).any() + assert ~np.isnan(mat).any() atol = 1e-14 if verbose: print(f"\n({row_str}) ({col_str})") - print(f"rank {rank} | ind_row1: {set(xp.where(mat > atol)[0])}") - print(f"rank {rank} | ind_row2: {set(xp.where(mat > atol)[1])}") - print(f"rank {rank} | ind_row3: {set(xp.where(mat > atol)[2])}") - print(f"rank {rank} | ind_col1: {set(xp.where(mat > atol)[3])}") - print(f"rank {rank} | ind_col2: {set(xp.where(mat > atol)[4])}") - print(f"rank {rank} | ind_col3: {set(xp.where(mat > atol)[5])}") + print(f"rank {rank} | ind_row1: {set(np.where(mat > atol)[0])}") + print(f"rank {rank} | ind_row2: {set(np.where(mat > atol)[1])}") + print(f"rank {rank} | ind_row3: {set(np.where(mat > atol)[2])}") + print(f"rank {rank} | ind_col1: {set(np.where(mat > atol)[3])}") + print(f"rank {rank} | ind_col2: {set(np.where(mat > atol)[4])}") + print(f"rank {rank} | ind_col3: {set(np.where(mat > atol)[5])}") # check if correct indices are non-zero for n, (r, c) in enumerate(zip(row_str, col_str)): - assert set(xp.where(mat > atol)[n]) == rows[n][r] - assert set(xp.where(mat > atol)[n + 3]) == cols[n][r + c] + assert set(np.where(mat > atol)[n]) == rows[n][r] + assert set(np.where(mat > atol)[n + 3]) == cols[n][r + c] # Set matrix back to zero mat[:, :] = 0.0 @@ -401,19 +385,19 @@ def assert_vec(vec, rows, row_str, rank, verbose=False): """ assert len(vec.shape) == 3 # assert non Nan - assert ~xp.isnan(vec).any() + assert ~np.isnan(vec).any() atol = 1e-14 if verbose: print(f"\n({row_str})") - print(f"rank {rank} | ind_row1: {set(xp.where(vec > atol)[0])}") - print(f"rank {rank} | ind_row2: {set(xp.where(vec > atol)[1])}") - print(f"rank {rank} | ind_row3: {set(xp.where(vec > atol)[2])}") + print(f"rank {rank} | ind_row1: {set(np.where(vec > atol)[0])}") + print(f"rank {rank} | ind_row2: {set(np.where(vec > atol)[1])}") + print(f"rank {rank} | ind_row3: {set(np.where(vec > atol)[2])}") # check if correct indices are non-zero for n, r in enumerate(row_str): - assert set(xp.where(vec > atol)[n]) == rows[n][r] + assert set(np.where(vec > atol)[n]) == rows[n][r] # Set vector back to zero vec[:] = 0.0 diff --git a/src/struphy/pic/tests/test_pic_legacy_files/accumulation.py b/src/struphy/pic/tests/test_pic_legacy_files/accumulation.py index 6bb225571..5687f6287 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/accumulation.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/accumulation.py @@ -8,9 +8,9 @@ import time -import cunumpy as xp +import numpy as np import scipy.sparse as spa -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI import struphy.pic.tests.test_pic_legacy_files.accumulation_kernels_3d as pic_ker_3d @@ -69,22 +69,22 @@ def __init__(self, tensor_space_FEM, domain, basis_u, mpi_comm, use_control, cv_ else: Ni = getattr(self.space, "Nbase_" + str(self.basis_u) + "form")[a] - self.vecs_loc[a] = xp.empty((Ni[0], Ni[1], Ni[2]), dtype=float) - self.vecs_glo[a] = xp.empty((Ni[0], Ni[1], Ni[2]), dtype=float) + self.vecs_loc[a] = np.empty((Ni[0], Ni[1], Ni[2]), dtype=float) + self.vecs_glo[a] = np.empty((Ni[0], Ni[1], Ni[2]), dtype=float) for b in range(3): if self.space.dim == 2: - self.blocks_loc[a][b] = xp.empty( + self.blocks_loc[a][b] = np.empty( (Ni[0], Ni[1], Ni[2], 2 * self.space.p[0] + 1, 2 * self.space.p[1] + 1, self.space.NbaseN[2]), dtype=float, ) - self.blocks_glo[a][b] = xp.empty( + self.blocks_glo[a][b] = np.empty( (Ni[0], Ni[1], Ni[2], 2 * self.space.p[0] + 1, 2 * self.space.p[1] + 1, self.space.NbaseN[2]), dtype=float, ) else: - self.blocks_loc[a][b] = xp.empty( + self.blocks_loc[a][b] = np.empty( ( Ni[0], Ni[1], @@ -95,7 +95,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u, mpi_comm, use_control, cv_ ), dtype=float, ) - self.blocks_glo[a][b] = xp.empty( + self.blocks_glo[a][b] = np.empty( ( Ni[0], Ni[1], @@ -134,16 +134,16 @@ def to_sparse_step1(self): Ni = self.space.Nbase_2form[a] Nj = self.space.Nbase_2form[b] - indices = xp.indices(self.blocks_glo[a][b].shape) + indices = np.indices(self.blocks_glo[a][b].shape) row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni[:2], self.space.p[:2])] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni[:2], self.space.p[:2])] if self.space.dim == 2: - shift += [xp.zeros(self.space.NbaseN[2], dtype=int)] + shift += [np.zeros(self.space.NbaseN[2], dtype=int)] else: - shift += [xp.arange(Ni[2]) - self.space.p[2]] + shift += [np.arange(Ni[2]) - self.space.p[2]] col1 = (indices[3] + shift[0][:, None, None, None, None, None]) % Nj[0] col2 = (indices[4] + shift[1][None, :, None, None, None, None]) % Nj[1] @@ -159,8 +159,7 @@ def to_sparse_step1(self): # final block matrix M = spa.bmat( - [[None, M[0][1], M[0][2]], [-M[0][1].T, None, M[1][2]], [-M[0][2].T, -M[1][2].T, None]], - format="csr", + [[None, M[0][1], M[0][2]], [-M[0][1].T, None, M[1][2]], [-M[0][2].T, -M[1][2].T, None]], format="csr" ) # apply extraction operator @@ -202,16 +201,16 @@ def to_sparse_step3(self): Ni = self.space.Nbase_2form[a] Nj = self.space.Nbase_2form[b] - indices = xp.indices(self.blocks_glo[a][b].shape) + indices = np.indices(self.blocks_glo[a][b].shape) row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() - shift = [xp.arange(Ni) - p for Ni, p in zip(Ni[:2], self.space.p[:2])] + shift = [np.arange(Ni) - p for Ni, p in zip(Ni[:2], self.space.p[:2])] if self.space.dim == 2: - shift += [xp.zeros(self.space.NbaseN[2], dtype=int)] + shift += [np.zeros(self.space.NbaseN[2], dtype=int)] else: - shift += [xp.arange(Ni[2]) - self.space.p[2]] + shift += [np.arange(Ni[2]) - self.space.p[2]] col1 = (indices[3] + shift[0][:, None, None, None, None, None]) % Nj[0] col2 = (indices[4] + shift[1][None, :, None, None, None, None]) % Nj[1] @@ -227,8 +226,7 @@ def to_sparse_step3(self): # final block matrix M = spa.bmat( - [[M[0][0], M[0][1], M[0][2]], [M[0][1].T, M[1][1], M[1][2]], [M[0][2].T, M[1][2].T, M[2][2]]], - format="csr", + [[M[0][0], M[0][1], M[0][2]], [M[0][1].T, M[1][1], M[1][2]], [M[0][2].T, M[1][2].T, M[2][2]]], format="csr" ) # apply extraction operator @@ -530,15 +528,15 @@ def assemble_step3(self, b2_eq, b2): # build global sparse matrix and global vector if self.basis_u == 0: return self.to_sparse_step3(), self.space.Ev_0.dot( - xp.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())), + np.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())) ) elif self.basis_u == 1: return self.to_sparse_step3(), self.space.E1_0.dot( - xp.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())), + np.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())) ) elif self.basis_u == 2: return self.to_sparse_step3(), self.space.E2_0.dot( - xp.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())), + np.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())) ) diff --git a/src/struphy/pic/tests/test_pic_legacy_files/accumulation_kernels_3d.py b/src/struphy/pic/tests/test_pic_legacy_files/accumulation_kernels_3d.py index 349cca379..c70261023 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/accumulation_kernels_3d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/accumulation_kernels_3d.py @@ -185,49 +185,13 @@ def kernel_step1( bsp.b_d_splines_slim(t3, int(pn3), eta3, int(span3), bn3, bd3) b[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - b2_1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 ) b[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - b2_2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 ) b[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - b2_3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 ) b_prod[0, 1] = -b[2] @@ -590,49 +554,13 @@ def kernel_step3( bsp.b_d_splines_slim(t3, int(pn3), eta3, int(span3), bn3, bd3) b[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - b2_1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 ) b[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - b2_2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 ) b[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - b2_3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 ) b_prod[0, 1] = -b[2] @@ -1202,14 +1130,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat11[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] for jl1 in range(pn1 + 1): @@ -1221,14 +1142,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat12[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] for jl1 in range(pn1 + 1): @@ -1240,14 +1154,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat13[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] # add contribution to 22 component (NDN NDN) and 23 component (NDN NND) @@ -1272,14 +1179,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat22[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] for jl1 in range(pn1 + 1): @@ -1291,14 +1191,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat23[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] # add contribution to 33 component (NND NND) @@ -1323,14 +1216,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat33[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] elif basis_u == 2: @@ -1356,14 +1242,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat11[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] for jl1 in range(pd1 + 1): @@ -1375,14 +1254,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat12[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] for jl1 in range(pd1 + 1): @@ -1394,14 +1266,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat13[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] # add contribution to 22 component (DND DND) and 23 component (DND DDN) @@ -1426,14 +1291,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat22[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] for jl1 in range(pd1 + 1): @@ -1445,14 +1303,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat23[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] # add contribution to 33 component (DDN DDN) @@ -1477,14 +1328,7 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat33[ - i1, - i2, - i3, - pn1 + jl1 - il1, - pn2 + jl2 - il2, - pn3 + jl3 - il3, - vp, - vq, + i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq ] += bj3 * v[vp] * v[vq] # -- removed omp: #$ omp end parallel diff --git a/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d.py b/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d.py index 2e54d34dd..587b8b15f 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d.py @@ -74,53 +74,17 @@ def f( if kind_map == 0: if component == 1: value = eva_3d.evaluate_n_n_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cx, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cx, eta1, eta2, eta3 ) elif component == 2: value = eva_3d.evaluate_n_n_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cy, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cy, eta1, eta2, eta3 ) elif component == 3: value = eva_3d.evaluate_n_n_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cz, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cz, eta1, eta2, eta3 ) # ==== 2d spline (straight in 3rd direction) === @@ -146,7 +110,7 @@ def f( elif kind_map == 2: if component == 1: value = eva_2d.evaluate_n_n(tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2) * cos( - 2 * pi * eta3, + 2 * pi * eta3 ) if eta1 == 0.0 and cx[0, 0, 0] == cx[0, 1, 0]: @@ -160,7 +124,7 @@ def f( elif component == 3: value = eva_2d.evaluate_n_n(tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2) * sin( - 2 * pi * eta3, + 2 * pi * eta3 ) if eta1 == 0.0 and cx[0, 0, 0] == cx[0, 1, 0]: @@ -335,147 +299,39 @@ def df( if kind_map == 0: if component == 11: value = eva_3d.evaluate_diffn_n_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cx, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cx, eta1, eta2, eta3 ) elif component == 12: value = eva_3d.evaluate_n_diffn_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cx, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cx, eta1, eta2, eta3 ) elif component == 13: value = eva_3d.evaluate_n_n_diffn( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cx, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cx, eta1, eta2, eta3 ) elif component == 21: value = eva_3d.evaluate_diffn_n_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cy, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cy, eta1, eta2, eta3 ) elif component == 22: value = eva_3d.evaluate_n_diffn_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cy, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cy, eta1, eta2, eta3 ) elif component == 23: value = eva_3d.evaluate_n_n_diffn( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cy, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cy, eta1, eta2, eta3 ) elif component == 31: value = eva_3d.evaluate_diffn_n_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cz, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cz, eta1, eta2, eta3 ) elif component == 32: value = eva_3d.evaluate_n_diffn_n( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cz, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cz, eta1, eta2, eta3 ) elif component == 33: value = eva_3d.evaluate_n_n_diffn( - tn1, - tn2, - tn3, - pn[0], - pn[1], - pn[2], - nbase_n[0], - nbase_n[1], - nbase_n[2], - cz, - eta1, - eta2, - eta3, + tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cz, eta1, eta2, eta3 ) # ==== 2d spline (straight in 3rd direction) === @@ -513,27 +369,11 @@ def df( elif kind_map == 2: if component == 11: value = eva_2d.evaluate_diffn_n( - tn1, - tn2, - pn[0], - pn[1], - nbase_n[0], - nbase_n[1], - cx[:, :, 0], - eta1, - eta2, + tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2 ) * cos(2 * pi * eta3) elif component == 12: value = eva_2d.evaluate_n_diffn( - tn1, - tn2, - pn[0], - pn[1], - nbase_n[0], - nbase_n[1], - cx[:, :, 0], - eta1, - eta2, + tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2 ) * cos(2 * pi * eta3) if eta1 == 0.0 and cx[0, 0, 0] == cx[0, 1, 0]: @@ -557,27 +397,11 @@ def df( value = 0.0 elif component == 31: value = eva_2d.evaluate_diffn_n( - tn1, - tn2, - pn[0], - pn[1], - nbase_n[0], - nbase_n[1], - cx[:, :, 0], - eta1, - eta2, + tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2 ) * sin(2 * pi * eta3) elif component == 32: value = eva_2d.evaluate_n_diffn( - tn1, - tn2, - pn[0], - pn[1], - nbase_n[0], - nbase_n[1], - cx[:, :, 0], - eta1, - eta2, + tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2 ) * sin(2 * pi * eta3) if eta1 == 0.0 and cx[0, 0, 0] == cx[0, 1, 0]: diff --git a/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d_fast.py b/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d_fast.py index f87380685..fbd912b39 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d_fast.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d_fast.py @@ -264,51 +264,19 @@ def df_all( if mat_or_vec == 0 or mat_or_vec == 2: # sum-up non-vanishing contributions (line 1: df_11, df_12 and df_13) mat_out[0, 0] = evaluation_kernel_2d( - pn[0], - pn[1], - der1, - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) mat_out[0, 1] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - der2, - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) mat_out[0, 2] = 0.0 # sum-up non-vanishing contributions (line 2: df_21, df_22 and df_23) mat_out[1, 0] = evaluation_kernel_2d( - pn[0], - pn[1], - der1, - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cy[:, :, 0], + pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] ) mat_out[1, 1] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - der2, - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cy[:, :, 0], + pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] ) mat_out[1, 2] = 0.0 @@ -320,26 +288,10 @@ def df_all( # evaluate mapping if mat_or_vec == 1 or mat_or_vec == 2: vec_out[0] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) vec_out[1] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cy[:, :, 0], + pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] ) vec_out[2] = lz * eta3 @@ -353,38 +305,14 @@ def df_all( if mat_or_vec == 0 or mat_or_vec == 2: # sum-up non-vanishing contributions (line 1: df_11, df_12 and df_13) mat_out[0, 0] = evaluation_kernel_2d( - pn[0], - pn[1], - der1, - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) * cos(2 * pi * eta3) mat_out[0, 1] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - der2, - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) * cos(2 * pi * eta3) mat_out[0, 2] = ( evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) * sin(2 * pi * eta3) * (-2 * pi) @@ -392,63 +320,23 @@ def df_all( # sum-up non-vanishing contributions (line 2: df_21, df_22 and df_23) mat_out[1, 0] = evaluation_kernel_2d( - pn[0], - pn[1], - der1, - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cy[:, :, 0], + pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] ) mat_out[1, 1] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - der2, - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cy[:, :, 0], + pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] ) mat_out[1, 2] = 0.0 # sum-up non-vanishing contributions (line 3: df_31, df_32 and df_33) mat_out[2, 0] = evaluation_kernel_2d( - pn[0], - pn[1], - der1, - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) * sin(2 * pi * eta3) mat_out[2, 1] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - der2, - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) * sin(2 * pi * eta3) mat_out[2, 2] = ( evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) * cos(2 * pi * eta3) * 2 @@ -458,37 +346,13 @@ def df_all( # evaluate mapping if mat_or_vec == 1 or mat_or_vec == 2: vec_out[0] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) * cos(2 * pi * eta3) vec_out[1] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cy[:, :, 0], + pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] ) vec_out[2] = evaluation_kernel_2d( - pn[0], - pn[1], - b1[pn[0]], - b2[pn[1]], - span_n1, - span_n2, - nbase_n[0], - nbase_n[1], - cx[:, :, 0], + pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] ) * sin(2 * pi * eta3) # analytical mapping @@ -496,150 +360,33 @@ def df_all( # evaluate Jacobian matrix if mat_or_vec == 0 or mat_or_vec == 2: mat_out[0, 0] = mapping.df( - eta1, - eta2, - eta3, - 11, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 11, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) mat_out[0, 1] = mapping.df( - eta1, - eta2, - eta3, - 12, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 12, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) mat_out[0, 2] = mapping.df( - eta1, - eta2, - eta3, - 13, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 13, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) mat_out[1, 0] = mapping.df( - eta1, - eta2, - eta3, - 21, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 21, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) mat_out[1, 1] = mapping.df( - eta1, - eta2, - eta3, - 22, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 22, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) mat_out[1, 2] = mapping.df( - eta1, - eta2, - eta3, - 23, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 23, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) mat_out[2, 0] = mapping.df( - eta1, - eta2, - eta3, - 31, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 31, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) mat_out[2, 1] = mapping.df( - eta1, - eta2, - eta3, - 32, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 32, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) mat_out[2, 2] = mapping.df( - eta1, - eta2, - eta3, - 33, - kind_map, - params_map, - tn1, - tn2, - tn3, - pn, - nbase_n, - cx, - cy, - cz, + eta1, eta2, eta3, 33, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz ) # evaluate mapping diff --git a/src/struphy/pic/tests/test_pic_legacy_files/pusher.py b/src/struphy/pic/tests/test_pic_legacy_files/pusher.py index 518e19ee0..1da52c793 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/pusher.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/pusher.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np import struphy.pic.tests.test_pic_legacy_files.pusher_pos as push_pos import struphy.pic.tests.test_pic_legacy_files.pusher_vel_2d as push_vel_2d diff --git a/src/struphy/pic/tests/test_pic_legacy_files/pusher_pos.py b/src/struphy/pic/tests/test_pic_legacy_files/pusher_pos.py index 81b5e1e53..78631440e 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/pusher_pos.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/pusher_pos.py @@ -532,52 +532,13 @@ def pusher_step4_pcart( # compute old pseudo-cartesian coordinates fx_pseudo[0] = mapping.f( - eta[0], - eta[1], - eta[2], - 1, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 1, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) fx_pseudo[1] = mapping.f( - eta[0], - eta[1], - eta[2], - 2, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 2, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) fx_pseudo[2] = mapping.f( - eta[0], - eta[1], - eta[2], - 3, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 3, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) # evaluate old Jacobian matrix of mapping F @@ -627,150 +588,33 @@ def pusher_step4_pcart( # evaluate old Jacobian matrix of mapping F_pseudo df_pseudo_old[0, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 11, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 11, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo_old[0, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 12, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 12, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo_old[0, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 13, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 13, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo_old[1, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 21, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 21, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo_old[1, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 22, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 22, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo_old[1, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 23, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 23, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo_old[2, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 31, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 31, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo_old[2, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 32, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 32, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo_old[2, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 33, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 33, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) while True: @@ -843,150 +687,33 @@ def pusher_step4_pcart( # evaluate Jacobian matrix of mapping F_pseudo df_pseudo[0, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 11, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 11, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[0, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 12, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 12, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[0, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 13, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 13, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 21, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 21, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 22, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 22, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 23, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 23, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 31, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 31, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 32, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 32, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 33, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 33, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) # compute df_pseudo*df_inv*v @@ -1057,150 +784,33 @@ def pusher_step4_pcart( # evaluate Jacobian matrix of mapping F_pseudo df_pseudo[0, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 11, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 11, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[0, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 12, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 12, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[0, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 13, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 13, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 21, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 21, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 22, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 22, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 23, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 23, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 31, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 31, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 32, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 32, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 33, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 33, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) # compute df_pseudo*df_inv*v @@ -1271,150 +881,33 @@ def pusher_step4_pcart( # evaluate Jacobian matrix of mapping F_pseudo df_pseudo[0, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 11, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 11, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[0, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 12, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 12, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[0, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 13, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 13, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 21, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 21, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 22, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 22, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[1, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 23, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 23, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 0] = mapping.df( - eta[0], - eta[1], - eta[2], - 31, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 31, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 1] = mapping.df( - eta[0], - eta[1], - eta[2], - 32, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 32, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) df_pseudo[2, 2] = mapping.df( - eta[0], - eta[1], - eta[2], - 33, - map_pseudo, - params_pseudo, - tf1, - tf2, - tf3, - pf, - nbasef, - cx, - cy, - cz, + eta[0], eta[1], eta[2], 33, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz ) # compute df_pseudo*df_inv*v @@ -1881,98 +1374,26 @@ def pusher_rk4_pc_full( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(Ginv, u, k1_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) k1_u[:] = u / det_df @@ -2070,98 +1491,26 @@ def pusher_rk4_pc_full( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(Ginv, u, k2_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) k2_u[:] = u / det_df @@ -2259,98 +1608,26 @@ def pusher_rk4_pc_full( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(Ginv, u, k3_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) k3_u[:] = u / det_df @@ -2445,98 +1722,26 @@ def pusher_rk4_pc_full( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(Ginv, u, k4_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) k4_u[:] = u / det_df @@ -2787,98 +1992,26 @@ def pusher_rk4_pc_perp( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(Ginv, u, k1_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) k1_u[:] = u / det_df @@ -2975,98 +2108,26 @@ def pusher_rk4_pc_perp( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(Ginv, u, k2_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) k2_u[:] = u / det_df @@ -3162,98 +2223,26 @@ def pusher_rk4_pc_perp( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(Ginv, u, k3_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) k3_u[:] = u / det_df @@ -3349,98 +2338,26 @@ def pusher_rk4_pc_perp( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(Ginv, u, k4_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) k4_u[:] = u / det_df diff --git a/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_2d.py b/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_2d.py index 0fcc29751..43e320311 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_2d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_2d.py @@ -248,43 +248,19 @@ def pusher_step3( for i in range(nbase_n[2]): u[0] += ( eva2.evaluation_kernel_2d( - pd1, - pn2, - bd1, - bn2, - span1 - 1, - span2 - 0, - nbase_d[0], - nbase_n[1], - u1[:, :, i], + pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], u1[:, :, i] ) * cs[i] ) u[1] += ( eva2.evaluation_kernel_2d( - pn1, - pd2, - bn1, - bd2, - span1 - 0, - span2 - 1, - nbase_n[0], - nbase_d[1], - u2[:, :, i], + pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], u2[:, :, i] ) * cs[i] ) u[2] += ( eva2.evaluation_kernel_2d( - pn1, - pn2, - bn1, - bn2, - span1 - 0, - span2 - 0, - nbase_n[0], - nbase_n[1], - u3[:, :, i], + pn1, pn2, bn1, bn2, span1 - 0, span2 - 0, nbase_n[0], nbase_n[1], u3[:, :, i] ) * cs[i] ) @@ -298,43 +274,19 @@ def pusher_step3( for i in range(nbase_n[2]): u[0] += ( eva2.evaluation_kernel_2d( - pn1, - pd2, - bn1, - bd2, - span1 - 0, - span2 - 1, - nbase_n[0], - nbase_d[1], - u1[:, :, i], + pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], u1[:, :, i] ) * cs[i] ) u[1] += ( eva2.evaluation_kernel_2d( - pd1, - pn2, - bd1, - bn2, - span1 - 1, - span2 - 0, - nbase_d[0], - nbase_n[1], - u2[:, :, i], + pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], u2[:, :, i] ) * cs[i] ) u[2] += ( eva2.evaluation_kernel_2d( - pd1, - pd2, - bd1, - bd2, - span1 - 1, - span2 - 1, - nbase_d[0], - nbase_d[1], - u3[:, :, i], + pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], u3[:, :, i] ) * cs[i] ) @@ -347,80 +299,32 @@ def pusher_step3( # equilibrium magnetic field (2-form) b[0] = eva2.evaluation_kernel_2d( - pn1, - pd2, - bn1, - bd2, - span1 - 0, - span2 - 1, - nbase_n[0], - nbase_d[1], - b_eq_1[:, :, 0], + pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], b_eq_1[:, :, 0] ) b[1] = eva2.evaluation_kernel_2d( - pd1, - pn2, - bd1, - bn2, - span1 - 1, - span2 - 0, - nbase_d[0], - nbase_n[1], - b_eq_2[:, :, 0], + pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], b_eq_2[:, :, 0] ) b[2] = eva2.evaluation_kernel_2d( - pd1, - pd2, - bd1, - bd2, - span1 - 1, - span2 - 1, - nbase_d[0], - nbase_d[1], - b_eq_3[:, :, 0], + pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], b_eq_3[:, :, 0] ) # perturbed magnetic field (2-form) for i in range(nbase_n[2]): b[0] += ( eva2.evaluation_kernel_2d( - pn1, - pd2, - bn1, - bd2, - span1 - 0, - span2 - 1, - nbase_n[0], - nbase_d[1], - b_p_1[:, :, i], + pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], b_p_1[:, :, i] ) * cs[i] ) b[1] += ( eva2.evaluation_kernel_2d( - pd1, - pn2, - bd1, - bn2, - span1 - 1, - span2 - 0, - nbase_d[0], - nbase_n[1], - b_p_2[:, :, i], + pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], b_p_2[:, :, i] ) * cs[i] ) b[2] += ( eva2.evaluation_kernel_2d( - pd1, - pd2, - bd1, - bd2, - span1 - 1, - span2 - 1, - nbase_d[0], - nbase_d[1], - b_p_3[:, :, i], + pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], b_p_3[:, :, i] ) * cs[i] ) @@ -434,26 +338,10 @@ def pusher_step3( # gradient of absolute value of magnetic field (1-form) b_grad[0] = eva2.evaluation_kernel_2d( - pn1, - pn2, - der1, - bn2, - span1, - span2, - nbase_n[0], - nbase_n[1], - b_norm[:, :, 0], + pn1, pn2, der1, bn2, span1, span2, nbase_n[0], nbase_n[1], b_norm[:, :, 0] ) b_grad[1] = eva2.evaluation_kernel_2d( - pn1, - pn2, - bn1, - der2, - span1, - span2, - nbase_n[0], - nbase_n[1], - b_norm[:, :, 0], + pn1, pn2, bn1, der2, span1, span2, nbase_n[0], nbase_n[1], b_norm[:, :, 0] ) b_grad[2] = 0.0 @@ -674,80 +562,32 @@ def pusher_step5( # equilibrium magnetic field (2-form) b[0] = eva2.evaluation_kernel_2d( - pn1, - pd2, - bn1, - bd2, - span1 - 0, - span2 - 1, - nbase_n[0], - nbase_d[1], - b_eq_1[:, :, 0], + pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], b_eq_1[:, :, 0] ) b[1] = eva2.evaluation_kernel_2d( - pd1, - pn2, - bd1, - bn2, - span1 - 1, - span2 - 0, - nbase_d[0], - nbase_n[1], - b_eq_2[:, :, 0], + pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], b_eq_2[:, :, 0] ) b[2] = eva2.evaluation_kernel_2d( - pd1, - pd2, - bd1, - bd2, - span1 - 1, - span2 - 1, - nbase_d[0], - nbase_d[1], - b_eq_3[:, :, 0], + pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], b_eq_3[:, :, 0] ) # perturbed magnetic field (2-form) for i in range(nbase_n[2]): b[0] += ( eva2.evaluation_kernel_2d( - pn1, - pd2, - bn1, - bd2, - span1 - 0, - span2 - 1, - nbase_n[0], - nbase_d[1], - b_p_1[:, :, i], + pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], b_p_1[:, :, i] ) * cs[i] ) b[1] += ( eva2.evaluation_kernel_2d( - pd1, - pn2, - bd1, - bn2, - span1 - 1, - span2 - 0, - nbase_d[0], - nbase_n[1], - b_p_2[:, :, i], + pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], b_p_2[:, :, i] ) * cs[i] ) b[2] += ( eva2.evaluation_kernel_2d( - pd1, - pd2, - bd1, - bd2, - span1 - 1, - span2 - 1, - nbase_d[0], - nbase_d[1], - b_p_3[:, :, i], + pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], b_p_3[:, :, i] ) * cs[i] ) diff --git a/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_3d.py b/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_3d.py index cd3884209..4eabb26dd 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_3d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_3d.py @@ -227,49 +227,13 @@ def pusher_step3( # velocity field (0-form, push-forward with df) if basis_u == 0: u[0] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span1, - span2, - span3, - nbase_n[0], - nbase_n[1], - nbase_n[2], - u1, + pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span1, - span2, - span3, - nbase_n[0], - nbase_n[1], - nbase_n[2], - u2, + pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span1, - span2, - span3, - nbase_n[0], - nbase_n[1], - nbase_n[2], - u3, + pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], u3 ) linalg.matrix_vector(df, u, u_cart) @@ -277,49 +241,13 @@ def pusher_step3( # velocity field (1-form, push forward with df^(-T)) elif basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span1 - 1, - span2, - span3, - nbase_d[0], - nbase_n[1], - nbase_n[2], - u1, + pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span1, - span2 - 1, - span3, - nbase_n[0], - nbase_d[1], - nbase_n[2], - u2, + pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span1, - span2, - span3 - 1, - nbase_n[0], - nbase_n[1], - nbase_d[2], - u3, + pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 ) linalg.matrix_vector(dfinv_t, u, u_cart) @@ -327,49 +255,13 @@ def pusher_step3( # velocity field (2-form, push forward with df/|det df|) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - u1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 ) u[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - u2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 ) u[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - u3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 ) linalg.matrix_vector(df, u, u_cart) @@ -380,49 +272,13 @@ def pusher_step3( # magnetic field (2-form) b[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - b2_1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 ) b[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - b2_2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 ) b[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - b2_3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 ) # push-forward to physical domain @@ -434,49 +290,13 @@ def pusher_step3( # gradient of absolute value of magnetic field (1-form) b_grad[0] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pn3, - der1, - bn2, - bn3, - span1, - span2, - span3, - nbase_n[0], - nbase_n[1], - nbase_n[2], - b0, + pn1, pn2, pn3, der1, bn2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], b0 ) b_grad[1] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - der2, - bn3, - span1, - span2, - span3, - nbase_n[0], - nbase_n[1], - nbase_n[2], - b0, + pn1, pn2, pn3, bn1, der2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], b0 ) b_grad[2] = eva3.evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - bn2, - der3, - span1, - span2, - span3, - nbase_n[0], - nbase_n[1], - nbase_n[2], - b0, + pn1, pn2, pn3, bn1, bn2, der3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], b0 ) # push-forward to physical domain @@ -717,49 +537,13 @@ def pusher_step5_old( # magnetic field (2-form) b[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - b2_1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 ) b[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - b2_2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 ) b[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - b2_3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 ) b_prod[0, 1] = -b[2] @@ -1010,49 +794,13 @@ def pusher_step5( # magnetic field (2-form) b[0] = eva3.evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span1, - span2 - 1, - span3 - 1, - nbase_n[0], - nbase_d[1], - nbase_d[2], - b2_1, + pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 ) b[1] = eva3.evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span1 - 1, - span2, - span3 - 1, - nbase_d[0], - nbase_n[1], - nbase_d[2], - b2_2, + pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 ) b[2] = eva3.evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span1 - 1, - span2 - 1, - span3, - nbase_d[0], - nbase_d[1], - nbase_n[2], - b2_3, + pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 ) # push-forward to physical domain diff --git a/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_2d.py b/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_2d.py index fdd4485b5..ba32b93bf 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_2d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_2d.py @@ -400,7 +400,7 @@ def evaluate_tensor_product( Returns: -------- - values: double[:, :] values of spline at points from xp.meshgrid(eta1, eta2, indexing='ij'). + values: double[:, :] values of spline at points from np.meshgrid(eta1, eta2, indexing='ij'). """ for i1 in range(len(eta1)): diff --git a/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_3d.py b/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_3d.py index 7923b3966..28e2b5d9c 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_3d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_3d.py @@ -127,19 +127,7 @@ def evaluate_n_n_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span_n1, - span_n2, - span_n3, - nbase_n1, - nbase_n2, - nbase_n3, - coeff, + pn1, pn2, pn3, bn1, bn2, bn3, span_n1, span_n2, span_n3, nbase_n1, nbase_n2, nbase_n3, coeff ) return value @@ -201,19 +189,7 @@ def evaluate_diffn_n_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span_n1, - span_n2, - span_n3, - nbase_n1, - nbase_n2, - nbase_n3, - coeff, + pn1, pn2, pn3, bn1, bn2, bn3, span_n1, span_n2, span_n3, nbase_n1, nbase_n2, nbase_n3, coeff ) return value @@ -275,19 +251,7 @@ def evaluate_n_diffn_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span_n1, - span_n2, - span_n3, - nbase_n1, - nbase_n2, - nbase_n3, - coeff, + pn1, pn2, pn3, bn1, bn2, bn3, span_n1, span_n2, span_n3, nbase_n1, nbase_n2, nbase_n3, coeff ) return value @@ -349,19 +313,7 @@ def evaluate_n_n_diffn( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, - pn2, - pn3, - bn1, - bn2, - bn3, - span_n1, - span_n2, - span_n3, - nbase_n1, - nbase_n2, - nbase_n3, - coeff, + pn1, pn2, pn3, bn1, bn2, bn3, span_n1, span_n2, span_n3, nbase_n1, nbase_n2, nbase_n3, coeff ) return value @@ -425,19 +377,7 @@ def evaluate_d_n_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pd1, - pn2, - pn3, - bd1, - bn2, - bn3, - span_d1, - span_n2, - span_n3, - nbase_d1, - nbase_n2, - nbase_n3, - coeff, + pd1, pn2, pn3, bd1, bn2, bn3, span_d1, span_n2, span_n3, nbase_d1, nbase_n2, nbase_n3, coeff ) return value @@ -501,19 +441,7 @@ def evaluate_n_d_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, - pd2, - pn3, - bn1, - bd2, - bn3, - span_n1, - span_d2, - span_n3, - nbase_n1, - nbase_d2, - nbase_n3, - coeff, + pn1, pd2, pn3, bn1, bd2, bn3, span_n1, span_d2, span_n3, nbase_n1, nbase_d2, nbase_n3, coeff ) return value @@ -577,19 +505,7 @@ def evaluate_n_n_d( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, - pn2, - pd3, - bn1, - bn2, - bd3, - span_n1, - span_n2, - span_d3, - nbase_n1, - nbase_n2, - nbase_d3, - coeff, + pn1, pn2, pd3, bn1, bn2, bd3, span_n1, span_n2, span_d3, nbase_n1, nbase_n2, nbase_d3, coeff ) return value @@ -654,19 +570,7 @@ def evaluate_n_d_d( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, - pd2, - pd3, - bn1, - bd2, - bd3, - span_n1, - span_d2, - span_d3, - nbase_n1, - nbase_d2, - nbase_d3, - coeff, + pn1, pd2, pd3, bn1, bd2, bd3, span_n1, span_d2, span_d3, nbase_n1, nbase_d2, nbase_d3, coeff ) return value @@ -731,19 +635,7 @@ def evaluate_d_n_d( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pd1, - pn2, - pd3, - bd1, - bn2, - bd3, - span_d1, - span_n2, - span_d3, - nbase_d1, - nbase_n2, - nbase_d3, - coeff, + pd1, pn2, pd3, bd1, bn2, bd3, span_d1, span_n2, span_d3, nbase_d1, nbase_n2, nbase_d3, coeff ) return value @@ -808,19 +700,7 @@ def evaluate_d_d_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pd1, - pd2, - pn3, - bd1, - bd2, - bn3, - span_d1, - span_d2, - span_n3, - nbase_d1, - nbase_d2, - nbase_n3, - coeff, + pd1, pd2, pn3, bd1, bd2, bn3, span_d1, span_d2, span_n3, nbase_d1, nbase_d2, nbase_n3, coeff ) return value @@ -886,19 +766,7 @@ def evaluate_d_d_d( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pd1, - pd2, - pd3, - bd1, - bd2, - bd3, - span_d1, - span_d2, - span_d3, - nbase_d1, - nbase_d2, - nbase_d3, - coeff, + pd1, pd2, pd3, bd1, bd2, bd3, span_d1, span_d2, span_d3, nbase_d1, nbase_d2, nbase_d3, coeff ) return value @@ -938,7 +806,7 @@ def evaluate_tensor_product( Returns: -------- values: double[:, :, :] values of spline at points from - xp.meshgrid(eta1, eta2, eta3, indexing='ij'). + np.meshgrid(eta1, eta2, eta3, indexing='ij'). """ for i1 in range(len(eta1)): @@ -947,137 +815,41 @@ def evaluate_tensor_product( # V0 - space if kind == 0: values[i1, i2, i3] = evaluate_n_n_n( - t1, - t2, - t3, - p1, - p2, - p3, - nbase_1, - nbase_2, - nbase_3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] ) # V1 - space elif kind == 11: values[i1, i2, i3] = evaluate_d_n_n( - t1, - t2, - t3, - p1, - p2, - p3, - nbase_1, - nbase_2, - nbase_3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 12: values[i1, i2, i3] = evaluate_n_d_n( - t1, - t2, - t3, - p1, - p2, - p3, - nbase_1, - nbase_2, - nbase_3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 13: values[i1, i2, i3] = evaluate_n_n_d( - t1, - t2, - t3, - p1, - p2, - p3, - nbase_1, - nbase_2, - nbase_3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] ) # V2 - space elif kind == 21: values[i1, i2, i3] = evaluate_n_d_d( - t1, - t2, - t3, - p1, - p2, - p3, - nbase_1, - nbase_2, - nbase_3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 22: values[i1, i2, i3] = evaluate_d_n_d( - t1, - t2, - t3, - p1, - p2, - p3, - nbase_1, - nbase_2, - nbase_3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] ) elif kind == 23: values[i1, i2, i3] = evaluate_d_d_n( - t1, - t2, - t3, - p1, - p2, - p3, - nbase_1, - nbase_2, - nbase_3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] ) # V3 - space elif kind == 3: values[i1, i2, i3] = evaluate_d_d_d( - t1, - t2, - t3, - p1, - p2, - p3, - nbase_1, - nbase_2, - nbase_3, - coeff, - eta1[i1], - eta2[i2], - eta3[i3], + t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] ) diff --git a/src/struphy/pic/tests/test_pushers.py b/src/struphy/pic/tests/test_pushers.py index 321ab9aba..acb89f2e9 100644 --- a/src/struphy/pic/tests/test_pushers.py +++ b/src/struphy/pic/tests/test_pushers.py @@ -1,13 +1,11 @@ import pytest -from struphy.utils.pyccel import Pyccelkernel - +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", - [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], + "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] ) @pytest.mark.parametrize( "mapping", @@ -24,8 +22,8 @@ ], ) def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -114,7 +112,7 @@ def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy = Pusher_psy( particles, - Pyccelkernel(pusher_kernels.push_vxb_analytic), + pusher_kernels.push_vxb_analytic, ( derham.args_derham, b2_eq_psy[0]._data + b2_psy[0]._data, @@ -126,7 +124,7 @@ def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert xp.allclose(particles.markers, markers_str.T) + assert np.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -136,14 +134,14 @@ def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", - [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], + "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] ) @pytest.mark.parametrize( "mapping", @@ -160,8 +158,8 @@ def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -252,12 +250,12 @@ def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): basis_u=2, bc_pos=0, ) - mu0_str = xp.zeros(markers_str.shape[1], dtype=float) - pow_str = xp.zeros(markers_str.shape[1], dtype=float) + mu0_str = np.zeros(markers_str.shape[1], dtype=float) + pow_str = np.zeros(markers_str.shape[1], dtype=float) pusher_psy = Pusher_psy( particles, - Pyccelkernel(pusher_kernels.push_bxu_Hdiv), + pusher_kernels.push_bxu_Hdiv, ( derham.args_derham, b2_eq_psy[0]._data + b2_psy[0]._data, @@ -273,7 +271,7 @@ def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert xp.allclose(particles.markers, markers_str.T) + assert np.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -283,14 +281,14 @@ def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", - [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], + "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] ) @pytest.mark.parametrize( "mapping", @@ -307,8 +305,8 @@ def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -399,12 +397,12 @@ def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): basis_u=1, bc_pos=0, ) - mu0_str = xp.zeros(markers_str.shape[1], dtype=float) - pow_str = xp.zeros(markers_str.shape[1], dtype=float) + mu0_str = np.zeros(markers_str.shape[1], dtype=float) + pow_str = np.zeros(markers_str.shape[1], dtype=float) pusher_psy = Pusher_psy( particles, - Pyccelkernel(pusher_kernels.push_bxu_Hcurl), + pusher_kernels.push_bxu_Hcurl, ( derham.args_derham, b2_eq_psy[0]._data + b2_psy[0]._data, @@ -420,7 +418,7 @@ def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert xp.allclose(particles.markers, markers_str.T) + assert np.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -430,14 +428,14 @@ def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", - [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], + "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] ) @pytest.mark.parametrize( "mapping", @@ -454,8 +452,8 @@ def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -546,12 +544,12 @@ def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): basis_u=0, bc_pos=0, ) - mu0_str = xp.zeros(markers_str.shape[1], dtype=float) - pow_str = xp.zeros(markers_str.shape[1], dtype=float) + mu0_str = np.zeros(markers_str.shape[1], dtype=float) + pow_str = np.zeros(markers_str.shape[1], dtype=float) pusher_psy = Pusher_psy( particles, - Pyccelkernel(pusher_kernels.push_bxu_H1vec), + pusher_kernels.push_bxu_H1vec, ( derham.args_derham, b2_eq_psy[0]._data + b2_psy[0]._data, @@ -567,7 +565,7 @@ def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert xp.allclose(particles.markers, markers_str.T) + assert np.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -577,14 +575,14 @@ def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", - [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], + "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] ) @pytest.mark.parametrize( "mapping", @@ -601,8 +599,8 @@ def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -693,12 +691,12 @@ def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): basis_u=2, bc_pos=0, ) - mu0_str = xp.random.rand(markers_str.shape[1]) - pow_str = xp.zeros(markers_str.shape[1], dtype=float) + mu0_str = np.random.rand(markers_str.shape[1]) + pow_str = np.zeros(markers_str.shape[1], dtype=float) pusher_psy = Pusher_psy( particles, - Pyccelkernel(pusher_kernels.push_bxu_Hdiv_pauli), + pusher_kernels.push_bxu_Hdiv_pauli, ( derham.args_derham, *derham.p, @@ -716,7 +714,7 @@ def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert xp.allclose(particles.markers, markers_str.T) + assert np.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -726,14 +724,14 @@ def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", - [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], + "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] ) @pytest.mark.parametrize( "mapping", @@ -750,8 +748,8 @@ def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -836,12 +834,12 @@ def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): butcher = ButcherTableau("rk4") # temp fix due to refactoring of ButcherTableau: - butcher._a = xp.diag(butcher.a, k=-1) - butcher._a = xp.array(list(butcher._a) + [0.0]) + butcher._a = np.diag(butcher.a, k=-1) + butcher._a = np.array(list(butcher._a) + [0.0]) pusher_psy = Pusher_psy( particles, - Pyccelkernel(pusher_kernels.push_eta_stage), + pusher_kernels.push_eta_stage, (butcher.a, butcher.b, butcher.c), domain.args_domain, alpha_in_kernel=1.0, @@ -849,7 +847,7 @@ def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert xp.allclose(particles.markers, markers_str.T) + assert np.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -857,12 +855,12 @@ def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): pusher_str.push_step4(markers_str, dt) pusher_psy(dt) - n_mks_load = xp.zeros(size, dtype=int) + n_mks_load = np.zeros(size, dtype=int) - comm.Allgather(xp.array(xp.shape(particles.markers)[0]), n_mks_load) + comm.Allgather(np.array(np.shape(particles.markers)[0]), n_mks_load) - sendcounts = xp.zeros(size, dtype=int) - displacements = xp.zeros(size, dtype=int) + sendcounts = np.zeros(size, dtype=int) + displacements = np.zeros(size, dtype=int) accum_sendcounts = 0.0 for i in range(size): @@ -870,27 +868,23 @@ def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): displacements[i] = accum_sendcounts accum_sendcounts += sendcounts[i] - all_particles_psy = xp.zeros((int(accum_sendcounts) * 3,), dtype=float) - all_particles_str = xp.zeros((int(accum_sendcounts) * 3,), dtype=float) + all_particles_psy = np.zeros((int(accum_sendcounts) * 3,), dtype=float) + all_particles_str = np.zeros((int(accum_sendcounts) * 3,), dtype=float) comm.Barrier() - comm.Allgatherv(xp.array(particles.markers[:, :3]), [all_particles_psy, sendcounts, displacements, MPI.DOUBLE]) - comm.Allgatherv(xp.array(markers_str.T[:, :3]), [all_particles_str, sendcounts, displacements, MPI.DOUBLE]) + comm.Allgatherv(np.array(particles.markers[:, :3]), [all_particles_psy, sendcounts, displacements, MPI.DOUBLE]) + comm.Allgatherv(np.array(markers_str.T[:, :3]), [all_particles_str, sendcounts, displacements, MPI.DOUBLE]) comm.Barrier() - unique_psy = xp.unique(all_particles_psy) - unique_str = xp.unique(all_particles_str) + unique_psy = np.unique(all_particles_psy) + unique_str = np.unique(all_particles_str) - assert xp.allclose(unique_psy, unique_str) + assert np.allclose(unique_psy, unique_str) if __name__ == "__main__": test_push_vxb_analytic( - [8, 9, 5], - [4, 2, 3], - [False, True, True], - ["Colella", {"Lx": 2.0, "Ly": 2.0, "alpha": 0.1, "Lz": 4.0}], - False, + [8, 9, 5], [4, 2, 3], [False, True, True], ["Colella", {"Lx": 2.0, "Ly": 2.0, "alpha": 0.1, "Lz": 4.0}], False ) # test_push_bxu_Hdiv([8, 9, 5], [4, 2, 3], [False, True, True], ['Colella', { # 'Lx': 2., 'Ly': 2., 'alpha': 0.1, 'Lz': 4.}], False) diff --git a/src/struphy/pic/tests/test_sorting.py b/src/struphy/pic/tests/test_sorting.py index 0daf8f4c9..85f8f2935 100644 --- a/src/struphy/pic/tests/test_sorting.py +++ b/src/struphy/pic/tests/test_sorting.py @@ -1,8 +1,8 @@ from time import time -import cunumpy as xp +import numpy as np import pytest -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.geometry import domains @@ -14,12 +14,12 @@ @pytest.mark.parametrize("ny", [16, 80]) @pytest.mark.parametrize("nz", [32, 90]) @pytest.mark.parametrize("algo", ["fortran_ordering", "c_ordering"]) -def test_flattening_1(nx, ny, nz, algo): +def test_flattening(nx, ny, nz, algo): from struphy.pic.sorting_kernels import flatten_index, unflatten_index - n1s = xp.array(xp.random.rand(10) * (nx + 1), dtype=int) - n2s = xp.array(xp.random.rand(10) * (ny + 1), dtype=int) - n3s = xp.array(xp.random.rand(10) * (nz + 1), dtype=int) + n1s = np.array(np.random.rand(10) * (nx + 1), dtype=int) + n2s = np.array(np.random.rand(10) * (ny + 1), dtype=int) + n3s = np.array(np.random.rand(10) * (nz + 1), dtype=int) for n1 in n1s: for n2 in n2s: for n3 in n3s: @@ -34,32 +34,12 @@ def test_flattening_1(nx, ny, nz, algo): @pytest.mark.parametrize("ny", [16, 80]) @pytest.mark.parametrize("nz", [32, 90]) @pytest.mark.parametrize("algo", ["fortran_ordering", "c_ordering"]) -def test_flattening_2(nx, ny, nz, algo): +def test_flattening(nx, ny, nz, algo): from struphy.pic.sorting_kernels import flatten_index, unflatten_index - n1s = xp.array(xp.random.rand(10) * (nx + 1), dtype=int) - n2s = xp.array(xp.random.rand(10) * (ny + 1), dtype=int) - n3s = xp.array(xp.random.rand(10) * (nz + 1), dtype=int) - for n1 in n1s: - for n2 in n2s: - for n3 in n3s: - n_glob = flatten_index(int(n1), int(n2), int(n3), nx, ny, nz, algo) - n1n, n2n, n3n = unflatten_index(n_glob, nx, ny, nz, algo) - assert n1n == n1 - assert n2n == n2 - assert n3n == n3 - - -@pytest.mark.parametrize("nx", [8, 70]) -@pytest.mark.parametrize("ny", [16, 80]) -@pytest.mark.parametrize("nz", [32, 90]) -@pytest.mark.parametrize("algo", ["fortran_ordering", "c_ordering"]) -def test_flattening_3(nx, ny, nz, algo): - from struphy.pic.sorting_kernels import flatten_index, unflatten_index - - n1s = xp.array(xp.random.rand(10) * (nx + 1), dtype=int) - n2s = xp.array(xp.random.rand(10) * (ny + 1), dtype=int) - n3s = xp.array(xp.random.rand(10) * (nz + 1), dtype=int) + n1s = np.array(np.random.rand(10) * (nx + 1), dtype=int) + n2s = np.array(np.random.rand(10) * (ny + 1), dtype=int) + n3s = np.array(np.random.rand(10) * (nz + 1), dtype=int) for n1 in n1s: for n2 in n2s: for n3 in n3s: @@ -70,11 +50,11 @@ def test_flattening_3(nx, ny, nz, algo): assert n3n == n3 +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[2, 3, 4]]) @pytest.mark.parametrize( - "spl_kind", - [[False, False, True], [False, True, False], [True, False, True], [True, True, False]], + "spl_kind", [[False, False, True], [False, True, False], [True, False, True], [True, True, False]] ) @pytest.mark.parametrize( "mapping", @@ -136,7 +116,7 @@ def test_sorting(Nel, p, spl_kind, mapping, Np, verbose=False): if __name__ == "__main__": - test_flattening_1(8, 8, 8, "c_orderwding") + test_flattening(8, 8, 8, "c_orderwding") # test_sorting( # [8, 9, 10], # [2, 3, 4], diff --git a/src/struphy/pic/tests/test_sph.py b/src/struphy/pic/tests/test_sph.py index 294e7f9dc..abb38aff6 100644 --- a/src/struphy/pic/tests/test_sph.py +++ b/src/struphy/pic/tests/test_sph.py @@ -1,8 +1,7 @@ -import cunumpy as xp +import numpy as np import pytest from matplotlib import pyplot as plt -from psydac.ddm.mpi import MockComm -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy.fields_background.equils import ConstantVelocity from struphy.geometry import domains @@ -26,12 +25,7 @@ def test_sph_evaluation_1d( tesselation, show_plot=False, ): - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - rank = 0 - else: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() + comm = MPI.COMM_WORLD # DOMAIN object dom_type = "Cuboid" @@ -59,9 +53,9 @@ def test_sph_evaluation_1d( pert = {"n": perturbations.ModesCos(ls=(1,), amps=(1e-0,))} if derivative == 0: - fun_exact = lambda e1, e2, e3: 1.5 + xp.cos(2 * xp.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 + np.cos(2 * np.pi * e1) else: - fun_exact = lambda e1, e2, e3: -2 * xp.pi * xp.sin(2 * xp.pi * e1) + fun_exact = lambda e1, e2, e3: -2 * np.pi * np.sin(2 * np.pi * e1) boundary_params = BoundaryParameters(bc_sph=(bc_x, "periodic", "periodic")) @@ -78,18 +72,17 @@ def test_sph_evaluation_1d( ) # eval points - eta1 = xp.linspace(0, 1.0, eval_pts) - eta2 = xp.array([0.0]) - eta3 = xp.array([0.0]) + eta1 = np.linspace(0, 1.0, eval_pts) + eta2 = np.array([0.0]) + eta3 = np.array([0.0]) particles.draw_markers(sort=False, verbose=False) - if comm is not None: - particles.mpi_sort_markers() + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] - ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") + ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") test_eval = particles.eval_density( ee1, ee2, @@ -100,20 +93,17 @@ def test_sph_evaluation_1d( kernel_type=kernel, derivative=derivative, ) + all_eval = np.zeros_like(test_eval) - if comm is None: - all_eval = test_eval - else: - all_eval = xp.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) exact_eval = fun_exact(ee1, ee2, ee3) - err_max_norm = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) + err_max_norm = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) - if rank == 0: - print(f"\n{boxes_per_dim =}") - print(f"{kernel =}, {derivative =}") - print(f"{bc_x =}, {eval_pts =}, {tesselation =}, {err_max_norm =}") + if comm.Get_rank() == 0: + print(f"\n{boxes_per_dim = }") + print(f"{kernel = }, {derivative =}") + print(f"{bc_x = }, {eval_pts = }, {tesselation = }, {err_max_norm = }") if show_plot: plt.figure(figsize=(12, 8)) plt.plot(ee1.squeeze(), fun_exact(ee1, ee2, ee3).squeeze(), label="exact") @@ -149,12 +139,7 @@ def test_sph_evaluation_2d( eval_pts, show_plot=False, ): - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - rank = 0 - else: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() + comm = MPI.COMM_WORLD tesselation = True @@ -178,19 +163,19 @@ def test_sph_evaluation_2d( pert = {"n": perturbations.ModesCosCos(ls=(1,), ms=(1,), amps=(1e-0,))} if derivative == 0: - fun_exact = lambda e1, e2, e3: 1.5 + xp.cos(2 * xp.pi * e1) * xp.cos(2 * xp.pi * e2) + fun_exact = lambda e1, e2, e3: 1.5 + np.cos(2 * np.pi * e1) * np.cos(2 * np.pi * e2) elif derivative == 1: - fun_exact = lambda e1, e2, e3: -2 * xp.pi * xp.sin(2 * xp.pi * e1) * xp.cos(2 * xp.pi * e2) + fun_exact = lambda e1, e2, e3: -2 * np.pi * np.sin(2 * np.pi * e1) * np.cos(2 * np.pi * e2) else: - fun_exact = lambda e1, e2, e3: -2 * xp.pi * xp.cos(2 * xp.pi * e1) * xp.sin(2 * xp.pi * e2) + fun_exact = lambda e1, e2, e3: -2 * np.pi * np.cos(2 * np.pi * e1) * np.sin(2 * np.pi * e2) # boundary conditions boundary_params = BoundaryParameters(bc_sph=(bc_x, bc_y, "periodic")) # eval points - eta1 = xp.linspace(0, 1.0, eval_pts) - eta2 = xp.linspace(0, 1.0, eval_pts) - eta3 = xp.array([0.0]) + eta1 = np.linspace(0, 1.0, eval_pts) + eta2 = np.linspace(0, 1.0, eval_pts) + eta3 = np.array([0.0]) # particles object particles = ParticlesSPH( @@ -207,13 +192,12 @@ def test_sph_evaluation_2d( ) particles.draw_markers(sort=False, verbose=False) - if comm is not None: - particles.mpi_sort_markers() + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] - ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") + ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") test_eval = particles.eval_density( ee1, ee2, @@ -224,20 +208,17 @@ def test_sph_evaluation_2d( kernel_type=kernel, derivative=derivative, ) + all_eval = np.zeros_like(test_eval) - if comm is None: - all_eval = test_eval - else: - all_eval = xp.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) exact_eval = fun_exact(ee1, ee2, ee3) - err_max_norm = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) + err_max_norm = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) - if rank == 0: - print(f"\n{boxes_per_dim =}") - print(f"{kernel =}, {derivative =}") - print(f"{bc_x =}, {bc_y =}, {eval_pts =}, {tesselation =}, {err_max_norm =}") + if comm.Get_rank() == 0: + print(f"\n{boxes_per_dim = }") + print(f"{kernel = }, {derivative =}") + print(f"{bc_x = }, {bc_y = }, {eval_pts = }, {tesselation = }, {err_max_norm = }") if show_plot: plt.figure(figsize=(12, 24)) plt.subplot(2, 1, 1) @@ -273,12 +254,7 @@ def test_sph_evaluation_3d( eval_pts, show_plot=False, ): - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - rank = 0 - else: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() + comm = MPI.COMM_WORLD tesselation = True @@ -308,9 +284,9 @@ def test_sph_evaluation_3d( boundary_params = BoundaryParameters(bc_sph=(bc_x, bc_y, bc_z)) # eval points - eta1 = xp.linspace(0, 1.0, eval_pts) - eta2 = xp.linspace(0, 1.0, eval_pts) - eta3 = xp.linspace(0, 1.0, eval_pts) + eta1 = np.linspace(0, 1.0, eval_pts) + eta2 = np.linspace(0, 1.0, eval_pts) + eta3 = np.linspace(0, 1.0, eval_pts) # particles object particles = ParticlesSPH( @@ -326,13 +302,12 @@ def test_sph_evaluation_3d( ) particles.draw_markers(sort=False, verbose=False) - if comm is not None: - particles.mpi_sort_markers() + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] - ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") + ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") test_eval = particles.eval_density( ee1, ee2, @@ -343,39 +318,36 @@ def test_sph_evaluation_3d( kernel_type=kernel, derivative=derivative, ) + all_eval = np.zeros_like(test_eval) - if comm is None: - all_eval = test_eval - else: - all_eval = xp.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) exact_eval = fun_exact(ee1, ee2, ee3) - err_max_norm = xp.max(xp.abs(all_eval - exact_eval)) + err_max_norm = np.max(np.abs(all_eval - exact_eval)) - if rank == 0: - print(f"\n{boxes_per_dim =}") - print(f"{kernel =}, {derivative =}") - print(f"{bc_x =}, {bc_y =}, {bc_z =}, {eval_pts =}, {tesselation =}, {err_max_norm =}") + if comm.Get_rank() == 0: + print(f"\n{boxes_per_dim = }") + print(f"{kernel = }, {derivative =}") + print(f"{bc_x = }, {bc_y = }, {bc_z = }, {eval_pts = }, {tesselation = }, {err_max_norm = }") if show_plot: - print(f"\n{fun_exact(ee1, ee2, ee3)[5, 5, 5] =}") - print(f"{ee1[5, 5, 5] =}, {ee2[5, 5, 5] =}, {ee3[5, 5, 5] =}") - print(f"{all_eval[5, 5, 5] =}") + print(f"\n{fun_exact(ee1, ee2, ee3)[5, 5, 5] = }") + print(f"{ee1[5, 5, 5] = }, {ee2[5, 5, 5] = }, {ee3[5, 5, 5] = }") + print(f"{all_eval[5, 5, 5] = }") - print(f"\n{ee1[4, 4, 4] =}, {ee2[4, 4, 4] =}, {ee3[4, 4, 4] =}") - print(f"{all_eval[4, 4, 4] =}") + print(f"\n{ee1[4, 4, 4] = }, {ee2[4, 4, 4] = }, {ee3[4, 4, 4] = }") + print(f"{all_eval[4, 4, 4] = }") - print(f"\n{ee1[3, 3, 3] =}, {ee2[3, 3, 3] =}, {ee3[3, 3, 3] =}") - print(f"{all_eval[3, 3, 3] =}") + print(f"\n{ee1[3, 3, 3] = }, {ee2[3, 3, 3] = }, {ee3[3, 3, 3] = }") + print(f"{all_eval[3, 3, 3] = }") - print(f"\n{ee1[2, 2, 2] =}, {ee2[2, 2, 2] =}, {ee3[2, 2, 2] =}") - print(f"{all_eval[2, 2, 2] =}") + print(f"\n{ee1[2, 2, 2] = }, {ee2[2, 2, 2] = }, {ee3[2, 2, 2] = }") + print(f"{all_eval[2, 2, 2] = }") - print(f"\n{ee1[1, 1, 1] =}, {ee2[1, 1, 1] =}, {ee3[1, 1, 1] =}") - print(f"{all_eval[1, 1, 1] =}") + print(f"\n{ee1[1, 1, 1] = }, {ee2[1, 1, 1] = }, {ee3[1, 1, 1] = }") + print(f"{all_eval[1, 1, 1] = }") - print(f"\n{ee1[0, 0, 0] =}, {ee2[0, 0, 0] =}, {ee3[0, 0, 0] =}") - print(f"{all_eval[0, 0, 0] =}") + print(f"\n{ee1[0, 0, 0] = }, {ee2[0, 0, 0] = }, {ee3[0, 0, 0] = }") + print(f"{all_eval[0, 0, 0] = }") # plt.figure(figsize=(12, 24)) # plt.subplot(2, 1, 1) # plt.pcolor(ee1[0, :, :], ee2[0, :, :], fun_exact(ee1, ee2, ee3)[0, :, :]) @@ -395,12 +367,7 @@ def test_sph_evaluation_3d( @pytest.mark.parametrize("eval_pts", [11, 16]) @pytest.mark.parametrize("tesselation", [False, True]) def test_evaluation_SPH_Np_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselation, show_plot=False): - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - rank = 0 - else: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() + comm = MPI.COMM_WORLD # DOMAIN object dom_type = "Cuboid" @@ -421,17 +388,17 @@ def test_evaluation_SPH_Np_convergence_1d(boxes_per_dim, bc_x, eval_pts, tessela # perturbation]} if bc_x in ("periodic", "fixed"): - fun_exact = lambda e1, e2, e3: 1.5 - xp.sin(2 * xp.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - np.sin(2 * np.pi * e1) pert = {"n": perturbations.ModesSin(ls=(1,), amps=(-1e-0,))} elif bc_x == "mirror": - fun_exact = lambda e1, e2, e3: 1.5 - xp.cos(2 * xp.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - np.cos(2 * np.pi * e1) pert = {"n": perturbations.ModesCos(ls=(1,), amps=(-1e-0,))} # exact solution - eta1 = xp.linspace(0, 1.0, eval_pts) # add offset for non-periodic boundary conditions, TODO: implement Neumann - eta2 = xp.array([0.0]) - eta3 = xp.array([0.0]) - ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") + eta1 = np.linspace(0, 1.0, eval_pts) # add offset for non-periodic boundary conditions, TODO: implement Neumann + eta2 = np.array([0.0]) + eta3 = np.array([0.0]) + ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") exact_eval = fun_exact(ee1, ee2, ee3) # boundary conditions @@ -459,54 +426,50 @@ def test_evaluation_SPH_Np_convergence_1d(boxes_per_dim, bc_x, eval_pts, tessela ) particles.draw_markers(sort=False, verbose=False) - if comm is not None: - particles.mpi_sort_markers() + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] test_eval = particles.eval_density(ee1, ee2, ee3, h1=h1, h2=h2, h3=h3) + all_eval = np.zeros_like(test_eval) - if comm is None: - all_eval = test_eval - else: - all_eval = xp.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) - if show_plot and rank == 0: + if show_plot and comm.Get_rank() == 0: plt.figure() plt.plot(ee1.squeeze(), exact_eval.squeeze(), label="exact") plt.plot(ee1.squeeze(), all_eval.squeeze(), "--.", label="eval_sph") - plt.title(f"{Np =}, {ppb =}") + plt.title(f"{Np = }, {ppb = }") # plt.savefig(f"fun_{Np}_{ppb}.png") - diff = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) + diff = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) err_vec += [diff] - print(f"{Np =}, {ppb =}, {diff =}") + print(f"{Np = }, {ppb = }, {diff = }") if tesselation: - fit = xp.polyfit(xp.log(ppbs), xp.log(err_vec), 1) + fit = np.polyfit(np.log(ppbs), np.log(err_vec), 1) xvec = ppbs else: - fit = xp.polyfit(xp.log(Nps), xp.log(err_vec), 1) + fit = np.polyfit(np.log(Nps), np.log(err_vec), 1) xvec = Nps - if show_plot and rank == 0: + if show_plot and comm.Get_rank() == 0: plt.figure(figsize=(12, 8)) plt.loglog(xvec, err_vec, label="Convergence") - plt.loglog(xvec, xp.exp(fit[1]) * xp.array(xvec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") + plt.loglog(xvec, np.exp(fit[1]) * np.array(xvec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") plt.legend() plt.show() # plt.savefig(f"Convergence_SPH_{tesselation=}") - if rank == 0: - print(f"\n{bc_x =}, {eval_pts =}, {tesselation =}, {fit[0] =}") + if comm.Get_rank() == 0: + print(f"\n{bc_x = }, {eval_pts = }, {tesselation = }, {fit[0] = }") if tesselation: assert fit[0] < 2e-3 else: - assert xp.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate + assert np.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate @pytest.mark.parametrize("boxes_per_dim", [(12, 1, 1)]) @@ -514,12 +477,7 @@ def test_evaluation_SPH_Np_convergence_1d(boxes_per_dim, bc_x, eval_pts, tessela @pytest.mark.parametrize("eval_pts", [11, 16]) @pytest.mark.parametrize("tesselation", [False, True]) def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselation, show_plot=False): - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - rank = 0 - else: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() + comm = MPI.COMM_WORLD # DOMAIN object dom_type = "Cuboid" @@ -542,17 +500,17 @@ def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselat # perturbation if bc_x in ("periodic", "fixed"): - fun_exact = lambda e1, e2, e3: 1.5 - xp.sin(2 * xp.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - np.sin(2 * np.pi * e1) pert = {"n": perturbations.ModesSin(ls=(1,), amps=(-1e-0,))} elif bc_x == "mirror": - fun_exact = lambda e1, e2, e3: 1.5 - xp.cos(2 * xp.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - np.cos(2 * np.pi * e1) pert = {"n": perturbations.ModesCos(ls=(1,), amps=(-1e-0,))} # exact solution - eta1 = xp.linspace(0, 1.0, eval_pts) # add offset for non-periodic boundary conditions, TODO: implement Neumann - eta2 = xp.array([0.0]) - eta3 = xp.array([0.0]) - ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") + eta1 = np.linspace(0, 1.0, eval_pts) # add offset for non-periodic boundary conditions, TODO: implement Neumann + eta2 = np.array([0.0]) + eta3 = np.array([0.0]) + ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") exact_eval = fun_exact(ee1, ee2, ee3) # boundary conditions @@ -576,31 +534,27 @@ def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselat ) particles.draw_markers(sort=False, verbose=False) - if comm is not None: - particles.mpi_sort_markers() + particles.mpi_sort_markers() particles.initialize_weights() h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] test_eval = particles.eval_density(ee1, ee2, ee3, h1=h1, h2=h2, h3=h3) + all_eval = np.zeros_like(test_eval) - if comm is None: - all_eval = test_eval - else: - all_eval = xp.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) - if show_plot and rank == 0: + if show_plot and comm.Get_rank() == 0: plt.figure() plt.plot(ee1.squeeze(), exact_eval.squeeze(), label="exact") plt.plot(ee1.squeeze(), all_eval.squeeze(), "--.", label="eval_sph") - plt.title(f"{h1 =}") + plt.title(f"{h1 = }") # plt.savefig(f"fun_{h1}.png") # error in max-norm - diff = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) + diff = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) - print(f"{h1 =}, {diff =}") + print(f"{h1 = }, {diff = }") if tesselation and h1 < 0.256: assert diff < 0.036 @@ -608,23 +562,23 @@ def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselat err_vec += [diff] if tesselation: - fit = xp.polyfit(xp.log(h_vec[1:5]), xp.log(err_vec[1:5]), 1) + fit = np.polyfit(np.log(h_vec[1:5]), np.log(err_vec[1:5]), 1) else: - fit = xp.polyfit(xp.log(h_vec[:-2]), xp.log(err_vec[:-2]), 1) + fit = np.polyfit(np.log(h_vec[:-2]), np.log(err_vec[:-2]), 1) - if show_plot and rank == 0: + if show_plot and comm.Get_rank() == 0: plt.figure(figsize=(12, 8)) plt.loglog(h_vec, err_vec, label="Convergence") - plt.loglog(h_vec, xp.exp(fit[1]) * xp.array(h_vec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") + plt.loglog(h_vec, np.exp(fit[1]) * np.array(h_vec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") plt.legend() plt.show() # plt.savefig("Convergence_SPH") - if rank == 0: - print(f"\n{bc_x =}, {eval_pts =}, {tesselation =}, {fit[0] =}") + if comm.Get_rank() == 0: + print(f"\n{bc_x = }, {eval_pts = }, {tesselation = }, {fit[0] = }") if not tesselation: - assert xp.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate + assert np.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate @pytest.mark.parametrize("boxes_per_dim", [(12, 1, 1)]) @@ -632,12 +586,7 @@ def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselat @pytest.mark.parametrize("eval_pts", [11, 16]) @pytest.mark.parametrize("tesselation", [False, True]) def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselation, show_plot=False): - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - rank = 0 - else: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() + comm = MPI.COMM_WORLD # DOMAIN object dom_type = "Cuboid" @@ -658,17 +607,17 @@ def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, te # perturbation if bc_x in ("periodic", "fixed"): - fun_exact = lambda e1, e2, e3: 1.5 - xp.sin(2 * xp.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - np.sin(2 * np.pi * e1) pert = {"n": perturbations.ModesSin(ls=(1,), amps=(-1e-0,))} elif bc_x == "mirror": - fun_exact = lambda e1, e2, e3: 1.5 - xp.cos(2 * xp.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - np.cos(2 * np.pi * e1) pert = {"n": perturbations.ModesCos(ls=(1,), amps=(-1e-0,))} # exact solution - eta1 = xp.linspace(0, 1.0, eval_pts) - eta2 = xp.array([0.0]) - eta3 = xp.array([0.0]) - ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") + eta1 = np.linspace(0, 1.0, eval_pts) + eta2 = np.array([0.0]) + eta3 = np.array([0.0]) + ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") exact_eval = fun_exact(ee1, ee2, ee3) # boundary conditions @@ -698,27 +647,22 @@ def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, te ) particles.draw_markers(sort=False, verbose=False) - if comm is not None: - particles.mpi_sort_markers() + particles.mpi_sort_markers() particles.initialize_weights() h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] test_eval = particles.eval_density(ee1, ee2, ee3, h1=h, h2=h2, h3=h3) - - if comm is None: - all_eval = test_eval - else: - all_eval = xp.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + all_eval = np.zeros_like(test_eval) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) # error in max-norm - diff = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) + diff = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) err_vec[-1] += [diff] - if rank == 0: - print(f"{Np =}, {ppb =}, {diff =}") + if comm.Get_rank() == 0: + print(f"{Np = }, {ppb = }, {diff = }") # if show_plot: # plt.figure() # plt.plot(ee1.squeeze(), fun_exact(ee1, ee2, ee3).squeeze(), label="exact") @@ -726,41 +670,41 @@ def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, te # plt.title(f"{h = }, {Np = }") # # plt.savefig(f"fun_h{h}_N{Np}_ppb{ppb}.png") - err_vec = xp.array(err_vec) - err_min = xp.min(err_vec) + err_vec = np.array(err_vec) + err_min = np.min(err_vec) - if show_plot and rank == 0: + if show_plot and comm.Get_rank() == 0: if tesselation: - h_mesh, n_mesh = xp.meshgrid(xp.log10(h_arr), xp.log10(ppbs), indexing="ij") + h_mesh, n_mesh = np.meshgrid(np.log10(h_arr), np.log10(ppbs), indexing="ij") if not tesselation: - h_mesh, n_mesh = xp.meshgrid(xp.log10(h_arr), xp.log10(Nps), indexing="ij") + h_mesh, n_mesh = np.meshgrid(np.log10(h_arr), np.log10(Nps), indexing="ij") plt.figure(figsize=(6, 6)) - plt.pcolor(h_mesh, n_mesh, xp.log10(err_vec), shading="auto") + plt.pcolor(h_mesh, n_mesh, np.log10(err_vec), shading="auto") plt.title("Error") plt.colorbar(label="log10(error)") plt.xlabel("log10(h)") plt.ylabel("log10(particles)") - min_indices = xp.argmin(err_vec, axis=0) + min_indices = np.argmin(err_vec, axis=0) min_h_values = [] for mi in min_indices: - min_h_values += [xp.log10(h_arr[mi])] + min_h_values += [np.log10(h_arr[mi])] if tesselation: - log_particles = xp.log10(ppbs) + log_particles = np.log10(ppbs) else: - log_particles = xp.log10(Nps) + log_particles = np.log10(Nps) plt.plot(min_h_values, log_particles, "r-", label="Min error h for each Np", linewidth=2) plt.legend() # plt.savefig("SPH_conv_in_h_and_N.png") plt.show() - if rank == 0: - print(f"\n{tesselation =}, {bc_x =}, {err_min =}") + if comm.Get_rank() == 0: + print(f"\n{tesselation = }, {bc_x = }, {err_min = }") if tesselation: if bc_x == "periodic": - assert xp.min(err_vec) < 7.7e-5 + assert np.min(err_vec) < 7.7e-5 elif bc_x == "fixed": assert err_min < 7.7e-5 else: @@ -777,12 +721,7 @@ def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, te @pytest.mark.parametrize("bc_y", ["periodic", "fixed", "mirror"]) @pytest.mark.parametrize("tesselation", [False, True]) def test_evaluation_SPH_Np_convergence_2d(boxes_per_dim, bc_x, bc_y, tesselation, show_plot=False): - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - rank = 0 - else: - comm = MPI.COMM_WORLD - rank = comm.Get_rank() + comm = MPI.COMM_WORLD # DOMAIN object dom_type = "Cuboid" @@ -807,25 +746,25 @@ def test_evaluation_SPH_Np_convergence_2d(boxes_per_dim, bc_x, bc_y, tesselation # perturbation if bc_x in ("periodic", "fixed"): if bc_y in ("periodic", "fixed"): - fun_exact = lambda x, y, z: 1.5 - xp.sin(2 * xp.pi / Lx * x) * xp.sin(2 * xp.pi / Ly * y) + fun_exact = lambda x, y, z: 1.5 - np.sin(2 * np.pi / Lx * x) * np.sin(2 * np.pi / Ly * y) pert = {"n": perturbations.ModesSinSin(ls=(1,), ms=(1,), amps=(-1e-0,))} elif bc_y == "mirror": - fun_exact = lambda x, y, z: 1.5 - xp.sin(2 * xp.pi / Lx * x) * xp.cos(2 * xp.pi / Ly * y) + fun_exact = lambda x, y, z: 1.5 - np.sin(2 * np.pi / Lx * x) * np.cos(2 * np.pi / Ly * y) pert = {"n": perturbations.ModesSinCos(ls=(1,), ms=(1,), amps=(-1e-0,))} elif bc_x == "mirror": if bc_y in ("periodic", "fixed"): - fun_exact = lambda x, y, z: 1.5 - xp.cos(2 * xp.pi / Lx * x) * xp.sin(2 * xp.pi / Ly * y) + fun_exact = lambda x, y, z: 1.5 - np.cos(2 * np.pi / Lx * x) * np.sin(2 * np.pi / Ly * y) pert = {"n": perturbations.ModesCosSin(ls=(1,), ms=(1,), amps=(-1e-0,))} elif bc_y == "mirror": - fun_exact = lambda x, y, z: 1.5 - xp.cos(2 * xp.pi / Lx * x) * xp.cos(2 * xp.pi / Ly * y) + fun_exact = lambda x, y, z: 1.5 - np.cos(2 * np.pi / Lx * x) * np.cos(2 * np.pi / Ly * y) pert = {"n": perturbations.ModesCosCos(ls=(1,), ms=(1,), amps=(-1e-0,))} # exact solution - eta1 = xp.linspace(0, 1.0, 41) - eta2 = xp.linspace(0, 1.0, 86) - eta3 = xp.array([0.0]) - ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") + eta1 = np.linspace(0, 1.0, 41) + eta2 = np.linspace(0, 1.0, 86) + eta3 = np.array([0.0]) + ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") x, y, z = domain(eta1, eta2, eta3) exact_eval = fun_exact(x, y, z) @@ -852,63 +791,60 @@ def test_evaluation_SPH_Np_convergence_2d(boxes_per_dim, bc_x, bc_y, tesselation n_as_volume_form=True, verbose=False, ) - if rank == 0: + + if comm.Get_rank() == 0: print(f"{particles.domain_array}") particles.draw_markers(sort=False, verbose=False) - if comm is not None: - particles.mpi_sort_markers() + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] test_eval = particles.eval_density(ee1, ee2, ee3, h1=h1, h2=h2, h3=h3, kernel_type="gaussian_2d") + all_eval = np.zeros_like(test_eval) - if comm is None: - all_eval = test_eval - else: - all_eval = xp.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) # error in max-norm - diff = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) + diff = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) err_vec += [diff] if tesselation: assert diff < 0.06 - if rank == 0: - print(f"{Np =}, {ppb =}, {diff =}") + if comm.Get_rank() == 0: + print(f"{Np = }, {ppb = }, {diff = }") if show_plot: fig, ax = plt.subplots() d = ax.pcolor(ee1.squeeze(), ee2.squeeze(), all_eval.squeeze(), label="eval_sph", vmin=1.0, vmax=2.0) fig.colorbar(d, ax=ax, label="2d_SPH") ax.set_xlabel("ee1") ax.set_ylabel("ee2") - ax.set_title(f"{Np}_{ppb =}") + ax.set_title(f"{Np}_{ppb = }") # fig.savefig(f"2d_sph_{Np}_{ppb}.png") if tesselation: - fit = xp.polyfit(xp.log(ppbs), xp.log(err_vec), 1) + fit = np.polyfit(np.log(ppbs), np.log(err_vec), 1) xvec = ppbs else: - fit = xp.polyfit(xp.log(Nps), xp.log(err_vec), 1) + fit = np.polyfit(np.log(Nps), np.log(err_vec), 1) xvec = Nps - if show_plot and rank == 0: + if show_plot and comm.Get_rank() == 0: plt.figure(figsize=(12, 8)) plt.loglog(xvec, err_vec, label="Convergence") - plt.loglog(xvec, xp.exp(fit[1]) * xp.array(xvec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") + plt.loglog(xvec, np.exp(fit[1]) * np.array(xvec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") plt.legend() plt.show() # plt.savefig(f"Convergence_SPH_{tesselation=}") - if rank == 0: - print(f"\n{bc_x =}, {tesselation =}, {fit[0] =}") + if comm.Get_rank() == 0: + print(f"\n{bc_x = }, {tesselation = }, {fit[0] = }") if not tesselation: - assert xp.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate + assert np.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate if __name__ == "__main__": diff --git a/src/struphy/pic/tests/test_tesselation.py b/src/struphy/pic/tests/test_tesselation.py index b138af50a..adc67447b 100644 --- a/src/struphy/pic/tests/test_tesselation.py +++ b/src/struphy/pic/tests/test_tesselation.py @@ -1,9 +1,9 @@ from time import time -import cunumpy as xp +import numpy as np import pytest from matplotlib import pyplot as plt -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy.feec.psydac_derham import Derham from struphy.fields_background.equils import ConstantVelocity @@ -13,6 +13,7 @@ from struphy.pic.utilities import BoundaryParameters, LoadingParameters, WeightsParameters +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("ppb", [8, 12]) @pytest.mark.parametrize("nx", [16, 10, 24]) @pytest.mark.parametrize("ny", [1, 16, 10]) @@ -56,22 +57,23 @@ def test_draw(ppb, nx, ny, nz): zl = particles.domain_array[rank, 6] zr = particles.domain_array[rank, 7] - eta1 = xp.linspace(xl, xr, tiles_x + 1)[:-1] + (xr - xl) / (2 * tiles_x) - eta2 = xp.linspace(yl, yr, tiles_y + 1)[:-1] + (yr - yl) / (2 * tiles_y) - eta3 = xp.linspace(zl, zr, tiles_z + 1)[:-1] + (zr - zl) / (2 * tiles_z) + eta1 = np.linspace(xl, xr, tiles_x + 1)[:-1] + (xr - xl) / (2 * tiles_x) + eta2 = np.linspace(yl, yr, tiles_y + 1)[:-1] + (yr - yl) / (2 * tiles_y) + eta3 = np.linspace(zl, zr, tiles_z + 1)[:-1] + (zr - zl) / (2 * tiles_z) - ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") + ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") e1 = ee1.flatten() e2 = ee2.flatten() e3 = ee3.flatten() # print(f'\n{rank = }, {e1 = }') - assert xp.allclose(particles.positions[:, 0], e1) - assert xp.allclose(particles.positions[:, 1], e2) - assert xp.allclose(particles.positions[:, 2], e3) + assert np.allclose(particles.positions[:, 0], e1) + assert np.allclose(particles.positions[:, 1], e2) + assert np.allclose(particles.positions[:, 2], e3) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("ppb", [8, 12]) @pytest.mark.parametrize("nx", [10, 8, 6]) @pytest.mark.parametrize("ny", [1, 16, 10]) @@ -119,20 +121,20 @@ def test_cell_average(ppb, nx, ny, nz, n_quad, show_plot=False): yl = particles.domain_array[rank, 3] yr = particles.domain_array[rank, 4] - eta1 = xp.linspace(xl, xr, tiles_x + 1) - eta2 = xp.linspace(yl, yr, tiles_y + 1) + eta1 = np.linspace(xl, xr, tiles_x + 1) + eta2 = np.linspace(yl, yr, tiles_y + 1) if ny == nz == 1: plt.figure(figsize=(15, 10)) - plt.plot(particles.positions[:, 0], xp.zeros_like(particles.weights), "o", label="markers") + plt.plot(particles.positions[:, 0], np.zeros_like(particles.weights), "o", label="markers") plt.plot(particles.positions[:, 0], particles.weights, "-o", label="weights") plt.plot( - xp.linspace(xl, xr, 100), - particles.f_init(xp.linspace(xl, xr, 100), 0.5, 0.5).squeeze(), + np.linspace(xl, xr, 100), + particles.f_init(np.linspace(xl, xr, 100), 0.5, 0.5).squeeze(), "--", label="f_init", ) - plt.vlines(xp.linspace(xl, xr, nx + 1), 0, 2, label="sorting boxes", color="k") + plt.vlines(np.linspace(xl, xr, nx + 1), 0, 2, label="sorting boxes", color="k") ax = plt.gca() ax.set_xticks(eta1) ax.set_yticks(eta2) @@ -146,8 +148,8 @@ def test_cell_average(ppb, nx, ny, nz, n_quad, show_plot=False): plt.subplot(1, 2, 1) ax = plt.gca() - ax.set_xticks(xp.linspace(0, 1, nx + 1)) - ax.set_yticks(xp.linspace(0, 1, ny + 1)) + ax.set_xticks(np.linspace(0, 1, nx + 1)) + ax.set_yticks(np.linspace(0, 1, ny + 1)) coloring = particles.weights plt.scatter(particles.positions[:, 0], particles.positions[:, 1], c=coloring, s=40) plt.grid(c="k") @@ -159,12 +161,12 @@ def test_cell_average(ppb, nx, ny, nz, n_quad, show_plot=False): plt.subplot(1, 2, 2) ax = plt.gca() - ax.set_xticks(xp.linspace(0, 1, nx + 1)) - ax.set_yticks(xp.linspace(0, 1, ny + 1)) + ax.set_xticks(np.linspace(0, 1, nx + 1)) + ax.set_yticks(np.linspace(0, 1, ny + 1)) coloring = particles.weights - pos1 = xp.linspace(xl, xr, 100) - pos2 = xp.linspace(yl, yr, 100) - pp1, pp2 = xp.meshgrid(pos1, pos2, indexing="ij") + pos1 = np.linspace(xl, xr, 100) + pos2 = np.linspace(yl, yr, 100) + pp1, pp2 = np.meshgrid(pos1, pos2, indexing="ij") plt.pcolor(pp1, pp2, particles.f_init(pp1, pp2, 0.5).squeeze()) plt.grid(c="k") plt.axis("square") @@ -176,8 +178,8 @@ def test_cell_average(ppb, nx, ny, nz, n_quad, show_plot=False): plt.show() # test - print(f"\n{rank =}, {xp.max(xp.abs(particles.weights - particles.f_init(particles.positions))) =}") - assert xp.max(xp.abs(particles.weights - particles.f_init(particles.positions))) < 0.012 + print(f"\n{rank = }, {np.max(np.abs(particles.weights - particles.f_init(particles.positions))) = }") + assert np.max(np.abs(particles.weights - particles.f_init(particles.positions))) < 0.012 if __name__ == "__main__": diff --git a/src/struphy/pic/utilities.py b/src/struphy/pic/utilities.py index 3ae645557..99b597c9b 100644 --- a/src/struphy/pic/utilities.py +++ b/src/struphy/pic/utilities.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np import struphy.pic.utilities_kernels as utils from struphy.io.options import ( @@ -183,23 +183,23 @@ def __init__( # computations and allocations self._bin_edges = [] for nb, rng in zip(n_bins, ranges): - self._bin_edges += [xp.linspace(rng[0], rng[1], nb + 1)] + self._bin_edges += [np.linspace(rng[0], rng[1], nb + 1)] self._bin_edges = tuple(self.bin_edges) - self._f = xp.zeros(n_bins, dtype=float) - self._df = xp.zeros(n_bins, dtype=float) + self._f = np.zeros(n_bins, dtype=float) + self._df = np.zeros(n_bins, dtype=float) @property def bin_edges(self) -> tuple: return self._bin_edges @property - def f(self) -> xp.ndarray: + def f(self) -> np.ndarray: """The binned distribution function (full-f).""" return self._f @property - def df(self) -> xp.ndarray: + def df(self) -> np.ndarray: """The binned distribution function minus the background (delta-f).""" return self._df @@ -215,23 +215,23 @@ class KernelDensityPlot: def __init__( self, - pts_e1: int = 16, - pts_e2: int = 16, - pts_e3: int = 1, + pts_e1: int = 11, + pts_e2: int = 11, + pts_e3: int = 11, ): - e1 = xp.linspace(0.0, 1.0, pts_e1) - e2 = xp.linspace(0.0, 1.0, pts_e2) - e3 = xp.linspace(0.0, 1.0, pts_e3) - ee1, ee2, ee3 = xp.meshgrid(e1, e2, e3, indexing="ij") + e1 = np.linspace(0.0, 1.0, pts_e1) + e2 = np.linspace(0.0, 1.0, pts_e2) + e3 = np.linspace(0.0, 1.0, pts_e3) + ee1, ee2, ee3 = np.meshgrid(e1, e2, e3, indexing="ij") self._plot_pts = (ee1, ee2, ee3) - self._n_sph = xp.zeros(ee1.shape, dtype=float) + self._n_sph = np.zeros(ee1.shape, dtype=float) @property def plot_pts(self) -> tuple: return self._plot_pts @property - def n_sph(self) -> xp.ndarray: + def n_sph(self) -> np.ndarray: """The evaluated density.""" return self._n_sph @@ -252,15 +252,15 @@ def get_kinetic_energy_particles(fe_coeffs, derham, domain, particles): Particles object. """ - res = xp.empty(1, dtype=float) + res = np.empty(1, dtype=float) utils.canonical_kinetic_particles( res, particles.markers, - xp.array(derham.p), + np.array(derham.p), derham.Vh_fem["0"].knots[0], derham.Vh_fem["0"].knots[1], derham.Vh_fem["0"].knots[2], - xp.array( + np.array( derham.V0.coeff_space.starts, ), *domain.args_map, @@ -285,7 +285,7 @@ def get_electron_thermal_energy(density_0_form, derham, domain, nel1, nel2, nel3 Discrete Derham complex. """ - res = xp.empty(1, dtype=float) + res = np.empty(1, dtype=float) utils.thermal_energy( res, density_0_form._operators[0].matrix._data, diff --git a/src/struphy/pic/utilities_kernels.py b/src/struphy/pic/utilities_kernels.py index cb25cc05f..d0f3c4e92 100644 --- a/src/struphy/pic/utilities_kernels.py +++ b/src/struphy/pic/utilities_kernels.py @@ -1,4 +1,4 @@ -from numpy import abs, empty, log, mod, pi, shape, sign, sqrt, zeros +from numpy import abs, empty, log, pi, shape, sign, sqrt, zeros from pyccel.decorators import stack_array import struphy.bsplines.bsplines_kernels as bsplines_kernels @@ -14,7 +14,7 @@ eval_vectorfield_spline_mpi, get_spans, ) -from struphy.kernel_arguments.pusher_args_kernels import DerhamArguments, DomainArguments, MarkerArguments +from struphy.kernel_arguments.pusher_args_kernels import DerhamArguments, DomainArguments def eval_magnetic_moment_5d( @@ -331,71 +331,6 @@ def eval_magnetic_energy( # -- removed omp: #$ omp end parallel -@stack_array("dfm", "eta") -def eval_magnetic_energy_PBb( - markers: "float[:,:]", - args_derham: "DerhamArguments", - args_domain: "DomainArguments", - first_diagnostics_idx: int, - abs_B0: "float[:,:,:]", - PBb: "float[:,:,:]", -): - r""" - Evaluate :math:`mu_p |B(\boldsymbol \eta_p)_\parallel|` for each marker. - The result is stored at markers[:, first_diagnostics_idx]. - """ - eta = empty(3, dtype=float) - - dfm = empty((3, 3), dtype=float) - - # get number of markers - n_markers = shape(markers)[0] - - for ip in range(n_markers): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - eta[:] = mod(markers[ip, 0:3], 1.0) - - weight = markers[ip, 7] - dweight = markers[ip, 5] - - mu = markers[ip, first_diagnostics_idx + 1] - - # spline evaluation - span1, span2, span3 = get_spans(eta[0], eta[1], eta[2], args_derham) - - # evaluate Jacobian, result in dfm - evaluation_kernels.df( - eta[0], - eta[1], - eta[2], - args_domain, - dfm, - ) - - # abs_B0; 0form - abs_B = eval_0form_spline_mpi( - span1, - span2, - span3, - args_derham, - abs_B0, - ) - - # PBb; 0form - PB_b = eval_0form_spline_mpi( - span1, - span2, - span3, - args_derham, - PBb, - ) - - markers[ip, first_diagnostics_idx] = mu * (abs_B + PB_b) - - @stack_array("v", "dfm", "b2", "norm_b_cart", "temp", "v_perp", "Larmor_r") def eval_guiding_center_from_6d( markers: "float[:,:]", @@ -506,101 +441,189 @@ def eval_guiding_center_from_6d( markers[ip, first_diagnostics_idx + 2] = z - Larmor_r[2] -@stack_array("dfm", "df_t", "g", "g_inv", "gradB, grad_PB_b", "tmp", "eta_mid", "eta_diff") -def eval_gradB_ediff( - args_markers: "MarkerArguments", - args_domain: "DomainArguments", +@stack_array("grad_PB", "tmp") +def accum_gradI_const( + markers: "float[:,:]", + Np: "int", args_derham: "DerhamArguments", - gradB1: "float[:,:,:]", - gradB2: "float[:,:,:]", - gradB3: "float[:,:,:]", - grad_PB_b1: "float[:,:,:]", - grad_PB_b2: "float[:,:,:]", - grad_PB_b3: "float[:,:,:]", - idx: int, + grad_PB1: "float[:,:,:]", + grad_PB2: "float[:,:,:]", + grad_PB3: "float[:,:,:]", + scale: "float", ): r"""TODO""" - - # allocate metric coeffs - dfm = empty((3, 3), dtype=float) - df_t = empty((3, 3), dtype=float) - g = empty((3, 3), dtype=float) - g_inv = empty((3, 3), dtype=float) - # allocate for magnetic field evaluation - gradB = empty(3, dtype=float) - grad_PB_b = empty(3, dtype=float) + grad_PB = empty(3, dtype=float) tmp = empty(3, dtype=float) - eta_mid = empty(3, dtype=float) - eta_diff = empty(3, dtype=float) - # get marker arguments - markers = args_markers.markers - n_markers = args_markers.n_markers - mu_idx = args_markers.mu_idx - first_init_idx = args_markers.first_init_idx - first_free_idx = args_markers.first_free_idx + # allocate for filling + res = zeros(1, dtype=float) - for ip in range(n_markers): + # get number of markers + n_markers_loc = shape(markers)[0] + + for ip in range(n_markers_loc): # only do something if particle is a "true" particle (i.e. not a hole) if markers[ip, 0] == -1.0: continue - # marker positions, mid point - eta_mid[:] = (markers[ip, 0:3] + markers[ip, first_init_idx : first_init_idx + 3]) / 2.0 - eta_mid[:] = mod(eta_mid[:], 1.0) - - eta_diff = markers[ip, 0:3] - markers[ip, first_init_idx : first_init_idx + 3] + # marker positions + eta1 = markers[ip, 0] # mid + eta2 = markers[ip, 1] # mid + eta3 = markers[ip, 2] # mid # marker weight and velocity weight = markers[ip, 5] - mu = markers[ip, mu_idx] + mu = markers[ip, 9] # b-field evaluation - span1, span2, span3 = get_spans(eta_mid[0], eta_mid[1], eta_mid[2], args_derham) - # print(span1, span2, span3) - - # evaluate Jacobian, result in dfm - evaluation_kernels.df( - eta_mid[0], - eta_mid[1], - eta_mid[2], - args_domain, - dfm, - ) - - linalg_kernels.transpose(dfm, df_t) - linalg_kernels.matrix_matrix(df_t, dfm, g) - linalg_kernels.matrix_inv(g, g_inv) + span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) - # gradB; 1form + # grad_PB; 1form eval_1form_spline_mpi( span1, span2, span3, args_derham, - gradB1, - gradB2, - gradB3, - gradB, + grad_PB1, + grad_PB2, + grad_PB3, + grad_PB, ) - # grad_PB_b; 1form - eval_1form_spline_mpi( + tmp[:] = markers[ip, 15:18] + res += linalg_kernels.scalar_dot(tmp, grad_PB) * weight * mu * scale + + return res / Np + + +def accum_en_fB( + markers: "float[:,:]", + Np: "int", + args_derham: "DerhamArguments", + PB: "float[:,:,:]", +): + r"""TODO""" + + # allocate for filling + res = zeros(1, dtype=float) + + # get number of markers + n_markers_loc = shape(markers)[0] + + for ip in range(n_markers_loc): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + # marker positions + eta1 = markers[ip, 0] + eta2 = markers[ip, 1] + eta3 = markers[ip, 2] + + # marker weight and velocity + mu = markers[ip, 9] + weight = markers[ip, 5] + + # b-field evaluation + span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) + + B0 = eval_0form_spline_mpi( span1, span2, span3, args_derham, - grad_PB_b1, - grad_PB_b2, - grad_PB_b3, - grad_PB_b, + PB, ) - tmp = gradB + grad_PB_b + res += abs(B0) * mu * weight + + return res / Np + + +@stack_array("e", "e_diff") +def check_eta_diff(markers: "float[:,:]"): + r"""TODO""" + # marker position e + e = empty(3, dtype=float) + e_diff = empty(3, dtype=float) + + # get number of markers + n_markers_loc = shape(markers)[0] + + for ip in range(n_markers_loc): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + e[:] = markers[ip, 0:3] + e_diff[:] = e[:] - markers[ip, 9:12] + + for axis in range(3): + if e_diff[axis] > 0.5: + e_diff[axis] -= 1.0 + elif e_diff[axis] < -0.5: + e_diff[axis] += 1.0 + + markers[ip, 15:18] = e_diff[:] + + +@stack_array("e", "e_diff") +def check_eta_diff2(markers: "float[:,:]"): + r"""TODO""" + # marker position e + e = empty(3, dtype=float) + e_diff = empty(3, dtype=float) + + # get number of markers + n_markers_loc = shape(markers)[0] + + for ip in range(n_markers_loc): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + e[:] = markers[ip, 0:3] + e_diff[:] = e[:] - markers[ip, 12:15] + + for axis in range(3): + if e_diff[axis] > 0.5: + e_diff[axis] -= 1.0 + elif e_diff[axis] < -0.5: + e_diff[axis] += 1.0 + + markers[ip, 15:18] = e_diff[:] + + +@stack_array("e", "e_diff", "e_mid") +def check_eta_mid(markers: "float[:,:]"): + r"""TODO""" + # marker position e + e = empty(3, dtype=float) + e_diff = empty(3, dtype=float) + e_mid = empty(3, dtype=float) + + # get number of markers + n_markers_loc = shape(markers)[0] + + for ip in range(n_markers_loc): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + e[:] = markers[ip, 0:3] + markers[ip, 12:15] = e[:] + + e_diff[:] = e[:] - markers[ip, 9:12] + e_mid[:] = (e[:] + markers[ip, 9:12]) / 2.0 + + for axis in range(3): + if e_diff[axis] > 0.5: + e_mid[axis] += 0.5 + elif e_diff[axis] < -0.5: + e_mid[axis] += 0.5 - markers[ip, idx] = linalg_kernels.scalar_dot(eta_diff, tmp) - markers[ip, idx] *= mu + markers[ip, 0:3] = e_mid[:] @stack_array("dfm", "dfinv", "dfinv_t", "v", "a_form", "dfta_form") diff --git a/src/struphy/polar/basic.py b/src/struphy/polar/basic.py index 99a95cc47..d863997db 100644 --- a/src/struphy/polar/basic.py +++ b/src/struphy/polar/basic.py @@ -1,5 +1,5 @@ -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI from psydac.linalg.basic import Vector, VectorSpace from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -19,7 +19,7 @@ class PolarDerhamSpace(VectorSpace): """ def __init__(self, derham, space_id): - assert not derham.spl_kind[0], "Spline basis in eta1 must be clamped" + assert derham.spl_kind[0] == False, "Spline basis in eta1 must be clamped" assert derham.spl_kind[1], "Spline basis in eta2 must be periodic" assert (derham.Nel[1] / 3) % 1 == 0.0, "Number of elements in eta2 must be a multiple of 3" @@ -208,7 +208,7 @@ class PolarVector(Vector): Element of a PolarDerhamSpace. An instance of a PolarVector consists of two parts: - 1. a list of xp.arrays of the polar coeffs (not distributed) + 1. a list of np.arrays of the polar coeffs (not distributed) 2. a tensor product StencilVector/BlockVector of the parent space with inner rings set to zero (distributed). Parameters @@ -223,7 +223,7 @@ def __init__(self, V): self._dtype = V.dtype # initialize polar coeffs - self._pol = [xp.zeros((m, n)) for m, n in zip(V.n_polar, V.n3)] + self._pol = [np.zeros((m, n)) for m, n in zip(V.n_polar, V.n3)] # full tensor product vector self._tp = V.parent_space.zeros() @@ -240,7 +240,7 @@ def dtype(self): @property def pol(self): - """Polar coefficients as xp.array.""" + """Polar coefficients as np.array.""" return self._pol @pol.setter @@ -326,7 +326,7 @@ def toarray(self, allreduce=False): if self.space.comm is not None and allreduce: self.space.comm.Allreduce(MPI.IN_PLACE, out, op=MPI.SUM) - out = xp.concatenate((self.pol[0].flatten(), out)) + out = np.concatenate((self.pol[0].flatten(), out)) else: out1 = self.tp[0].toarray()[self.space.n_rings[0] * self.space.n[1] * self.space.n3[0] :] @@ -339,7 +339,7 @@ def toarray(self, allreduce=False): self.space.comm.Allreduce(MPI.IN_PLACE, out2, op=MPI.SUM) self.space.comm.Allreduce(MPI.IN_PLACE, out3, op=MPI.SUM) - out = xp.concatenate( + out = np.concatenate( ( self.pol[0].flatten(), out1, @@ -347,7 +347,7 @@ def toarray(self, allreduce=False): out2, self.pol[2].flatten(), out3, - ), + ) ) return out @@ -365,7 +365,7 @@ def copy(self, out=None): self._tp.copy(out=w.tp) # copy polar part for n, pl in enumerate(self._pol): - xp.copyto(w._pol[n], pl, casting="no") + np.copyto(w._pol[n], pl, casting="no") return w def __neg__(self): diff --git a/src/struphy/polar/extraction_operators.py b/src/struphy/polar/extraction_operators.py index 1c2a461ce..14af375e4 100644 --- a/src/struphy/polar/extraction_operators.py +++ b/src/struphy/polar/extraction_operators.py @@ -1,4 +1,4 @@ -import cunumpy as xp +import numpy as np # ============================= 2D polar splines (C1) =================================== @@ -47,8 +47,8 @@ def __init__(self, domain, derham): self._pole = (cx[0, 0], cy[0, 0]) - assert xp.all(cx[0] == self.pole[0]) - assert xp.all(cy[0] == self.pole[1]) + assert np.all(cx[0] == self.pole[0]) + assert np.all(cy[0] == self.pole[1]) self._n0 = cx.shape[0] self._n1 = cx.shape[1] @@ -70,14 +70,14 @@ def __init__(self, domain, derham): self._tau = max( [ ((self.cx[1] - self.pole[0]) * (-2)).max(), - ((self.cx[1] - self.pole[0]) - xp.sqrt(3) * (self.cy[1] - self.pole[1])).max(), - ((self.cx[1] - self.pole[0]) + xp.sqrt(3) * (self.cy[1] - self.pole[1])).max(), - ], + ((self.cx[1] - self.pole[0]) - np.sqrt(3) * (self.cy[1] - self.pole[1])).max(), + ((self.cx[1] - self.pole[0]) + np.sqrt(3) * (self.cy[1] - self.pole[1])).max(), + ] ) # barycentric coordinates - self._xi_0 = xp.zeros((3, self.n1), dtype=float) - self._xi_1 = xp.zeros((3, self.n1), dtype=float) + self._xi_0 = np.zeros((3, self.n1), dtype=float) + self._xi_1 = np.zeros((3, self.n1), dtype=float) self._xi_0[:, :] = 1 / 3 @@ -85,12 +85,12 @@ def __init__(self, domain, derham): self._xi_1[1, :] = ( 1 / 3 - 1 / (3 * self.tau) * (self.cx[1] - self.pole[0]) - + xp.sqrt(3) / (3 * self.tau) * (self.cy[1] - self.pole[1]) + + np.sqrt(3) / (3 * self.tau) * (self.cy[1] - self.pole[1]) ) self._xi_1[2, :] = ( 1 / 3 - 1 / (3 * self.tau) * (self.cx[1] - self.pole[0]) - - xp.sqrt(3) / (3 * self.tau) * (self.cy[1] - self.pole[1]) + - np.sqrt(3) / (3 * self.tau) * (self.cy[1] - self.pole[1]) ) # remove small values @@ -102,17 +102,17 @@ def __init__(self, domain, derham): # ============= basis extraction operator for discrete 0-forms ================ # first n_rings tp rings --> "polar coeffs" - e0_blocks_ten_to_pol = xp.block([self.xi_0, self.xi_1]) + e0_blocks_ten_to_pol = np.block([self.xi_0, self.xi_1]) self._e_ten_to_pol["0"] = [[csr(e0_blocks_ten_to_pol)]] # ============ basis extraction operator for discrete 1-forms (Hcurl) ========= # first n_rings tp rings --> "polar coeffs" - e1_11_blocks_ten_to_pol = xp.zeros((self.n_polar[1][0], self.n_rings[1][0] * self.n1), dtype=float) - e1_12_blocks_ten_to_pol = xp.zeros((self.n_polar[1][0], self.n_rings[1][1] * self.d1), dtype=float) + e1_11_blocks_ten_to_pol = np.zeros((self.n_polar[1][0], self.n_rings[1][0] * self.n1), dtype=float) + e1_12_blocks_ten_to_pol = np.zeros((self.n_polar[1][0], self.n_rings[1][1] * self.d1), dtype=float) - e1_21_blocks_ten_to_pol = xp.zeros((self.n_polar[1][1], self.n_rings[1][0] * self.n1), dtype=float) - e1_22_blocks_ten_to_pol = xp.zeros((self.n_polar[1][1], self.n_rings[1][1] * self.d1), dtype=float) + e1_21_blocks_ten_to_pol = np.zeros((self.n_polar[1][1], self.n_rings[1][0] * self.n1), dtype=float) + e1_22_blocks_ten_to_pol = np.zeros((self.n_polar[1][1], self.n_rings[1][1] * self.d1), dtype=float) # 1st component for l in range(2): @@ -135,7 +135,7 @@ def __init__(self, domain, derham): # =============== basis extraction operator for discrete 1-forms (Hdiv) ========= # first n_rings tp rings --> "polar coeffs" - e3_blocks_ten_to_pol = xp.zeros((self.n_polar[3][0], self.n_rings[3][0] * self.d1), dtype=float) + e3_blocks_ten_to_pol = np.zeros((self.n_polar[3][0], self.n_rings[3][0] * self.d1), dtype=float) self._e_ten_to_pol["2"] = [ [csr(e1_22_blocks_ten_to_pol), csr(-e1_21_blocks_ten_to_pol), None], @@ -161,7 +161,7 @@ def __init__(self, domain, derham): self._p_ten_to_ten = {} # first n_rings tp rings --> "polar coeffs" - p0_blocks_ten_to_pol = xp.zeros((self.n_polar[0][0], self.n_rings[0][0] * self.n1), dtype=float) + p0_blocks_ten_to_pol = np.zeros((self.n_polar[0][0], self.n_rings[0][0] * self.n1), dtype=float) # !! NOTE: for odd spline degrees and periodic splines the first Greville point sometimes does NOT start at zero!! if domain.p[1] % 2 != 0 and not (abs(derham.Vh_fem["0"].spaces[1].interpolation_grid[0]) < 1e-14): @@ -176,15 +176,15 @@ def __init__(self, domain, derham): self._p_ten_to_pol["0"] = [[csr(p0_blocks_ten_to_pol)]] # first n_rings + 1 tp rings --> "first tp ring" - p0_blocks_ten_to_ten = xp.block([0 * xp.identity(self.n1)] * self.n_rings[0][0] + [xp.identity(self.n1)]) + p0_blocks_ten_to_ten = np.block([0 * np.identity(self.n1)] * self.n_rings[0][0] + [np.identity(self.n1)]) self._p_ten_to_ten["0"] = [[csr(p0_blocks_ten_to_ten)]] # =========== projection extraction operator for discrete 1-forms (Hcurl) ======== # first n_rings tp rings --> "polar coeffs" - p1_11_blocks_ten_to_pol = xp.zeros((self.n_polar[1][0], self.n_rings[1][0] * self.n1), dtype=float) - p1_22_blocks_ten_to_pol = xp.zeros((self.n_polar[1][1], self.n_rings[1][1] * self.d1), dtype=float) + p1_11_blocks_ten_to_pol = np.zeros((self.n_polar[1][0], self.n_rings[1][0] * self.n1), dtype=float) + p1_22_blocks_ten_to_pol = np.zeros((self.n_polar[1][1], self.n_rings[1][1] * self.d1), dtype=float) # !! NOTE: PSYDAC's first integration interval sometimes start at < 0 !! if derham.Vh_fem["3"].spaces[1].histopolation_grid[0] < -1e-14: @@ -196,8 +196,8 @@ def __init__(self, domain, derham): p1_22_blocks_ten_to_pol[1, (self.d1 + 0 * self.d1 // 3) : (self.d1 + 1 * self.d1 // 3)] = 1.0 p1_22_blocks_ten_to_pol[1, (self.d1 + 1 * self.d1 // 3) : (self.d1 + 2 * self.d1 // 3)] = 1.0 - p1_12_blocks_ten_to_pol = xp.zeros((self.n_polar[1][0], self.n_rings[1][1] * self.d1), dtype=float) - p1_21_blocks_ten_to_pol = xp.zeros((self.n_polar[1][1], self.n_rings[1][0] * self.d1), dtype=float) + p1_12_blocks_ten_to_pol = np.zeros((self.n_polar[1][0], self.n_rings[1][1] * self.d1), dtype=float) + p1_21_blocks_ten_to_pol = np.zeros((self.n_polar[1][1], self.n_rings[1][0] * self.d1), dtype=float) self._p_ten_to_pol["1"] = [ [csr(p1_11_blocks_ten_to_pol), csr(p1_12_blocks_ten_to_pol), None], @@ -206,26 +206,26 @@ def __init__(self, domain, derham): ] # first n_rings + 1 tp rings --> "first tp ring" - p1_11_blocks_ten_to_ten = xp.zeros((self.n1, self.n1), dtype=float) + p1_11_blocks_ten_to_ten = np.zeros((self.n1, self.n1), dtype=float) # !! NOTE: for odd spline degrees and periodic splines the first Greville point sometimes does NOT start at zero!! if domain.p[1] % 2 != 0 and not (abs(derham.Vh_fem["0"].spaces[1].interpolation_grid[0]) < 1e-14): - p1_11_blocks_ten_to_ten[:, 3 * self.n1 // 3 - 1] = -xp.roll(self.xi_1[0], -1) - p1_11_blocks_ten_to_ten[:, 1 * self.n1 // 3 - 1] = -xp.roll(self.xi_1[1], -1) - p1_11_blocks_ten_to_ten[:, 2 * self.n1 // 3 - 1] = -xp.roll(self.xi_1[2], -1) + p1_11_blocks_ten_to_ten[:, 3 * self.n1 // 3 - 1] = -np.roll(self.xi_1[0], -1) + p1_11_blocks_ten_to_ten[:, 1 * self.n1 // 3 - 1] = -np.roll(self.xi_1[1], -1) + p1_11_blocks_ten_to_ten[:, 2 * self.n1 // 3 - 1] = -np.roll(self.xi_1[2], -1) else: p1_11_blocks_ten_to_ten[:, 0 * self.n1 // 3] = -self.xi_1[0] p1_11_blocks_ten_to_ten[:, 1 * self.n1 // 3] = -self.xi_1[1] p1_11_blocks_ten_to_ten[:, 2 * self.n1 // 3] = -self.xi_1[2] - p1_11_blocks_ten_to_ten += xp.identity(self.n1) + p1_11_blocks_ten_to_ten += np.identity(self.n1) - p1_11_blocks_ten_to_ten = xp.block([p1_11_blocks_ten_to_ten, xp.identity(self.n1)]) + p1_11_blocks_ten_to_ten = np.block([p1_11_blocks_ten_to_ten, np.identity(self.n1)]) - p1_22_blocks_ten_to_ten = xp.block([0 * xp.identity(self.d1)] * self.n_rings[1][1] + [xp.identity(self.d1)]) + p1_22_blocks_ten_to_ten = np.block([0 * np.identity(self.d1)] * self.n_rings[1][1] + [np.identity(self.d1)]) - p1_12_blocks_ten_to_ten = xp.zeros((self.d1, (self.n_rings[1][1] + 1) * self.d1), dtype=float) - p1_21_blocks_ten_to_ten = xp.zeros((self.n1, (self.n_rings[1][0] + 1) * self.n1), dtype=float) + p1_12_blocks_ten_to_ten = np.zeros((self.d1, (self.n_rings[1][1] + 1) * self.d1), dtype=float) + p1_21_blocks_ten_to_ten = np.zeros((self.n1, (self.n_rings[1][0] + 1) * self.n1), dtype=float) self._p_ten_to_ten["1"] = [ [csr(p1_11_blocks_ten_to_ten), csr(p1_12_blocks_ten_to_ten), None], @@ -236,7 +236,7 @@ def __init__(self, domain, derham): # ========== projection extraction operator for discrete 1-forms (Hdiv) ========== # first n_rings tp rings --> "polar coeffs" - p3_blocks_ten_to_pol = xp.zeros((self.n_polar[3][0], self.n_rings[3][0] * self.d1), dtype=float) + p3_blocks_ten_to_pol = np.zeros((self.n_polar[3][0], self.n_rings[3][0] * self.d1), dtype=float) self._p_ten_to_pol["2"] = [ [csr(p1_22_blocks_ten_to_pol), csr(p1_21_blocks_ten_to_pol), None], @@ -245,24 +245,24 @@ def __init__(self, domain, derham): ] # first n_rings + 1 tp rings --> "first tp ring" - p3_blocks_ten_to_ten = xp.zeros((self.d1, self.d1), dtype=float) + p3_blocks_ten_to_ten = np.zeros((self.d1, self.d1), dtype=float) - a0 = xp.diff(self.xi_1[1], append=self.xi_1[1, 0]) - a1 = xp.diff(self.xi_1[2], append=self.xi_1[2, 0]) + a0 = np.diff(self.xi_1[1], append=self.xi_1[1, 0]) + a1 = np.diff(self.xi_1[2], append=self.xi_1[2, 0]) # !! NOTE: PSYDAC's first integration interval sometimes start at < 0 !! if derham.Vh_fem["3"].spaces[1].histopolation_grid[0] < -1e-14: p3_blocks_ten_to_ten[:, (0 * self.n1 // 3 + 1) : (1 * self.n1 // 3 + 1)] = ( - -xp.roll(a0, +1)[:, None] - xp.roll(a1, +1)[:, None] + -np.roll(a0, +1)[:, None] - np.roll(a1, +1)[:, None] ) - p3_blocks_ten_to_ten[:, (1 * self.n1 // 3 + 1) : (2 * self.n1 // 3 + 1)] = -xp.roll(a1, +1)[:, None] + p3_blocks_ten_to_ten[:, (1 * self.n1 // 3 + 1) : (2 * self.n1 // 3 + 1)] = -np.roll(a1, +1)[:, None] else: p3_blocks_ten_to_ten[:, 0 * self.n1 // 3 : 1 * self.n1 // 3] = -a0[:, None] - a1[:, None] p3_blocks_ten_to_ten[:, 1 * self.n1 // 3 : 2 * self.n1 // 3] = -a1[:, None] - p3_blocks_ten_to_ten += xp.identity(self.d1) + p3_blocks_ten_to_ten += np.identity(self.d1) - p3_blocks_ten_to_ten = xp.block([p3_blocks_ten_to_ten, xp.identity(self.d1)]) + p3_blocks_ten_to_ten = np.block([p3_blocks_ten_to_ten, np.identity(self.d1)]) self._p_ten_to_ten["2"] = [ [csr(p1_22_blocks_ten_to_ten), csr(p1_21_blocks_ten_to_ten), None], @@ -295,24 +295,24 @@ def __init__(self, domain, derham): # ======================= discrete gradient ====================================== # "polar coeffs" to "polar coeffs" - grad_pol_to_pol_1 = xp.zeros((self.n_polar[1][0], self.n_polar[0][0]), dtype=float) - grad_pol_to_pol_2 = xp.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) - grad_pol_to_pol_3 = xp.identity(self.n_polar[0][0], dtype=float) + grad_pol_to_pol_1 = np.zeros((self.n_polar[1][0], self.n_polar[0][0]), dtype=float) + grad_pol_to_pol_2 = np.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) + grad_pol_to_pol_3 = np.identity(self.n_polar[0][0], dtype=float) self._grad_pol_to_pol = [[csr(grad_pol_to_pol_1)], [csr(grad_pol_to_pol_2)], [csr(grad_pol_to_pol_3)]] # "polar coeffs" to "first tp ring" - grad_pol_to_ten_1 = xp.zeros(((self.n_rings[1][0] + 1) * self.n1, self.n_polar[0][0])) - grad_pol_to_ten_2 = xp.zeros(((self.n_rings[1][1] + 1) * self.d1, self.n_polar[0][0])) - grad_pol_to_ten_3 = xp.zeros(((self.n_rings[0][0] + 1) * self.n1, self.n_polar[0][0])) + grad_pol_to_ten_1 = np.zeros(((self.n_rings[1][0] + 1) * self.n1, self.n_polar[0][0])) + grad_pol_to_ten_2 = np.zeros(((self.n_rings[1][1] + 1) * self.d1, self.n_polar[0][0])) + grad_pol_to_ten_3 = np.zeros(((self.n_rings[0][0] + 1) * self.n1, self.n_polar[0][0])) grad_pol_to_ten_1[-self.n1 :, :] = -self.xi_1.T self._grad_pol_to_ten = [[csr(grad_pol_to_ten_1)], [csr(grad_pol_to_ten_2)], [csr(grad_pol_to_ten_3)]] # eta_3 direction - grad_e3_1 = xp.identity(self.n2, dtype=float) - grad_e3_2 = xp.identity(self.n2, dtype=float) + grad_e3_1 = np.identity(self.n2, dtype=float) + grad_e3_2 = np.identity(self.n2, dtype=float) grad_e3_3 = grad_1d_matrix(derham.spl_kind[2], self.n2) self._grad_e3 = [[csr(grad_e3_1)], [csr(grad_e3_2)], [csr(grad_e3_3)]] @@ -320,14 +320,14 @@ def __init__(self, domain, derham): # =========================== discrete curl ====================================== # "polar coeffs" to "polar coeffs" - curl_pol_to_pol_12 = xp.identity(self.n_polar[1][1], dtype=float) - curl_pol_to_pol_13 = xp.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) + curl_pol_to_pol_12 = np.identity(self.n_polar[1][1], dtype=float) + curl_pol_to_pol_13 = np.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) - curl_pol_to_pol_21 = xp.identity(self.n_polar[1][0], dtype=float) - curl_pol_to_pol_23 = xp.zeros((self.n_polar[2][1], self.n_polar[0][0]), dtype=float) + curl_pol_to_pol_21 = np.identity(self.n_polar[1][0], dtype=float) + curl_pol_to_pol_23 = np.zeros((self.n_polar[2][1], self.n_polar[0][0]), dtype=float) - curl_pol_to_pol_31 = xp.zeros((self.n_polar[3][0], self.n_polar[1][0]), dtype=float) - curl_pol_to_pol_32 = xp.zeros((self.n_polar[3][0], self.n_polar[1][1]), dtype=float) + curl_pol_to_pol_31 = np.zeros((self.n_polar[3][0], self.n_polar[1][0]), dtype=float) + curl_pol_to_pol_32 = np.zeros((self.n_polar[3][0], self.n_polar[1][1]), dtype=float) self._curl_pol_to_pol = [ [None, csr(-curl_pol_to_pol_12), csr(curl_pol_to_pol_13)], @@ -336,14 +336,14 @@ def __init__(self, domain, derham): ] # "polar coeffs" to "first tp ring" - curl_pol_to_ten_12 = xp.zeros(((self.n_rings[2][0] + 1) * self.d1, self.n_polar[1][1])) - curl_pol_to_ten_13 = xp.zeros(((self.n_rings[2][0] + 1) * self.d1, self.n_polar[0][0])) + curl_pol_to_ten_12 = np.zeros(((self.n_rings[2][0] + 1) * self.d1, self.n_polar[1][1])) + curl_pol_to_ten_13 = np.zeros(((self.n_rings[2][0] + 1) * self.d1, self.n_polar[0][0])) - curl_pol_to_ten_21 = xp.zeros(((self.n_rings[2][1] + 1) * self.n1, self.n_polar[1][0])) - curl_pol_to_ten_23 = xp.zeros(((self.n_rings[2][1] + 1) * self.n1, self.n_polar[0][0])) + curl_pol_to_ten_21 = np.zeros(((self.n_rings[2][1] + 1) * self.n1, self.n_polar[1][0])) + curl_pol_to_ten_23 = np.zeros(((self.n_rings[2][1] + 1) * self.n1, self.n_polar[0][0])) - curl_pol_to_ten_31 = xp.zeros(((self.n_rings[3][0] + 1) * self.n1, self.n_polar[1][0])) - curl_pol_to_ten_32 = xp.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[1][1])) + curl_pol_to_ten_31 = np.zeros(((self.n_rings[3][0] + 1) * self.n1, self.n_polar[1][0])) + curl_pol_to_ten_32 = np.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[1][1])) curl_pol_to_ten_23[-self.n1 :, :] = -self.xi_1.T @@ -361,13 +361,13 @@ def __init__(self, domain, derham): # eta_3 direction curl_e3_12 = grad_1d_matrix(derham.spl_kind[2], self.n2) - curl_e3_13 = xp.identity(self.d2) + curl_e3_13 = np.identity(self.d2) curl_e3_21 = grad_1d_matrix(derham.spl_kind[2], self.n2) - curl_e3_23 = xp.identity(self.d2) + curl_e3_23 = np.identity(self.d2) - curl_e3_31 = xp.identity(self.n2) - curl_e3_32 = xp.identity(self.n2) + curl_e3_31 = np.identity(self.n2) + curl_e3_32 = np.identity(self.n2) self._curl_e3 = [ [None, csr(curl_e3_12), csr(curl_e3_13)], @@ -378,16 +378,16 @@ def __init__(self, domain, derham): # =========================== discrete div ====================================== # "polar coeffs" to "polar coeffs" - div_pol_to_pol_1 = xp.zeros((self.n_polar[3][0], self.n_polar[2][0]), dtype=float) - div_pol_to_pol_2 = xp.zeros((self.n_polar[3][0], self.n_polar[2][1]), dtype=float) - div_pol_to_pol_3 = xp.identity(self.n_polar[3][0], dtype=float) + div_pol_to_pol_1 = np.zeros((self.n_polar[3][0], self.n_polar[2][0]), dtype=float) + div_pol_to_pol_2 = np.zeros((self.n_polar[3][0], self.n_polar[2][1]), dtype=float) + div_pol_to_pol_3 = np.identity(self.n_polar[3][0], dtype=float) self._div_pol_to_pol = [[csr(div_pol_to_pol_1), csr(div_pol_to_pol_2), csr(div_pol_to_pol_3)]] # "polar coeffs" to "first tp ring" - div_pol_to_ten_1 = xp.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[2][0])) - div_pol_to_ten_2 = xp.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[2][1])) - div_pol_to_ten_3 = xp.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[3][0])) + div_pol_to_ten_1 = np.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[2][0])) + div_pol_to_ten_2 = np.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[2][1])) + div_pol_to_ten_3 = np.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[3][0])) for l in range(2): for j in range(self.d1, 2 * self.d1): @@ -398,8 +398,8 @@ def __init__(self, domain, derham): self._div_pol_to_ten = [[csr(div_pol_to_ten_1), csr(div_pol_to_ten_2), csr(div_pol_to_ten_3)]] # eta_3 direction - div_e3_1 = xp.identity(self.d2, dtype=float) - div_e3_2 = xp.identity(self.d2, dtype=float) + div_e3_1 = np.identity(self.d2, dtype=float) + div_e3_2 = np.identity(self.d2, dtype=float) div_e3_3 = grad_1d_matrix(derham.spl_kind[2], self.n2) self._div_e3 = [[csr(div_e3_1), csr(div_e3_2), csr(div_e3_3)]] @@ -539,13 +539,13 @@ def __init__(self, n0, n1): # =========== extraction operators for discrete 0-forms ================== # extraction operator for basis functions - self.E0_11 = spa.csr_matrix(xp.ones((1, n1), dtype=float)) + self.E0_11 = spa.csr_matrix(np.ones((1, n1), dtype=float)) self.E0_22 = spa.identity((n0 - 1) * n1, format="csr") self.E0 = spa.bmat([[self.E0_11, None], [None, self.E0_22]], format="csr") # global projection extraction operator for interpolation points - self.P0_11 = xp.zeros((1, n1), dtype=float) + self.P0_11 = np.zeros((1, n1), dtype=float) self.P0_11[0, 0] = 1.0 @@ -598,7 +598,7 @@ def __init__(self, n0, n1): # ========= discrete polar gradient matrix =============================== # radial dofs (DN) - G11 = xp.zeros(((d0 - 0) * n1, 1), dtype=float) + G11 = np.zeros(((d0 - 0) * n1, 1), dtype=float) G11[:n1, 0] = -1.0 G12 = spa.kron(grad_1d_1[:, 1:], spa.identity(n1)) @@ -606,7 +606,7 @@ def __init__(self, n0, n1): self.G1 = spa.bmat([[G11, G12]], format="csr") # angular dofs (ND) - G21 = xp.zeros(((n0 - 1) * d1, 1), dtype=float) + G21 = np.zeros(((n0 - 1) * d1, 1), dtype=float) G22 = spa.kron(spa.identity(n0 - 1), grad_1d_2, format="csr") self.G2 = spa.bmat([[G21, G22]], format="csr") @@ -619,13 +619,13 @@ def __init__(self, n0, n1): # 2D vector curl (NN --> ND DN) # angular dofs (ND) - VC11 = xp.zeros(((n0 - 1) * d1, 1), dtype=float) + VC11 = np.zeros(((n0 - 1) * d1, 1), dtype=float) VC12 = spa.kron(spa.identity(n0 - 1), grad_1d_2, format="csr") self.VC1 = spa.bmat([[VC11, VC12]], format="csr") # radial dofs (DN) - VC21 = xp.zeros(((d0 - 0) * n1, 1), dtype=float) + VC21 = np.zeros(((d0 - 0) * n1, 1), dtype=float) VC21[:n1, 0] = 1.0 VC22 = -spa.kron(grad_1d_1[:, 1:], spa.identity(n1)) @@ -687,26 +687,26 @@ def __init__(self, cx, cy): self.Nbase2 = (d0 - 1) * d1 # size of control triangle - self.tau = xp.array( + self.tau = np.array( [ (-2 * (cx[1] - self.x0)).max(), - ((cx[1] - self.x0) - xp.sqrt(3) * (cy[1] - self.y0)).max(), - ((cx[1] - self.x0) + xp.sqrt(3) * (cy[1] - self.y0)).max(), - ], + ((cx[1] - self.x0) - np.sqrt(3) * (cy[1] - self.y0)).max(), + ((cx[1] - self.x0) + np.sqrt(3) * (cy[1] - self.y0)).max(), + ] ).max() - self.Xi_0 = xp.zeros((3, n1), dtype=float) - self.Xi_1 = xp.zeros((3, n1), dtype=float) + self.Xi_0 = np.zeros((3, n1), dtype=float) + self.Xi_1 = np.zeros((3, n1), dtype=float) # barycentric coordinates self.Xi_0[:, :] = 1 / 3 self.Xi_1[0, :] = 1 / 3 + 2 / (3 * self.tau) * (cx[1] - self.x0) self.Xi_1[1, :] = ( - 1 / 3 - 1 / (3 * self.tau) * (cx[1] - self.x0) + xp.sqrt(3) / (3 * self.tau) * (cy[1] - self.y0) + 1 / 3 - 1 / (3 * self.tau) * (cx[1] - self.x0) + np.sqrt(3) / (3 * self.tau) * (cy[1] - self.y0) ) self.Xi_1[2, :] = ( - 1 / 3 - 1 / (3 * self.tau) * (cx[1] - self.x0) - xp.sqrt(3) / (3 * self.tau) * (cy[1] - self.y0) + 1 / 3 - 1 / (3 * self.tau) * (cx[1] - self.x0) - np.sqrt(3) / (3 * self.tau) * (cy[1] - self.y0) ) # remove small values @@ -714,13 +714,13 @@ def __init__(self, cx, cy): # =========== extraction operators for discrete 0-forms ================== # extraction operator for basis functions - self.E0_11 = spa.csr_matrix(xp.hstack((self.Xi_0, self.Xi_1))) + self.E0_11 = spa.csr_matrix(np.hstack((self.Xi_0, self.Xi_1))) self.E0_22 = spa.identity((n0 - 2) * n1, format="csr") self.E0 = spa.bmat([[self.E0_11, None], [None, self.E0_22]], format="csr") # global projection extraction operator for interpolation points - self.P0_11 = xp.zeros((3, 2 * n1), dtype=float) + self.P0_11 = np.zeros((3, 2 * n1), dtype=float) self.P0_11[0, n1 + 0 * n1 // 3] = 1.0 self.P0_11[1, n1 + 1 * n1 // 3] = 1.0 @@ -737,8 +737,8 @@ def __init__(self, cx, cy): self.E1C_12 = spa.identity((d0 - 1) * n1) self.E1C_34 = spa.identity((n0 - 2) * d1) - self.E1C_21 = xp.zeros((2, 1 * n1), dtype=float) - self.E1C_23 = xp.zeros((2, 2 * d1), dtype=float) + self.E1C_21 = np.zeros((2, 1 * n1), dtype=float) + self.E1C_23 = np.zeros((2, 2 * d1), dtype=float) # 1st component for s in range(2): @@ -760,22 +760,22 @@ def __init__(self, cx, cy): # extraction operator for interpolation/histopolation in global projector # 1st component - self.P1C_11 = xp.zeros((n1, n1), dtype=float) + self.P1C_11 = np.zeros((n1, n1), dtype=float) self.P1C_12 = spa.identity(n1) self.P1C_23 = spa.identity((d0 - 2) * n1) self.P1C_11[:, 0 * n1 // 3] = -self.Xi_1[0] self.P1C_11[:, 1 * n1 // 3] = -self.Xi_1[1] self.P1C_11[:, 2 * n1 // 3] = -self.Xi_1[2] - self.P1C_11 += xp.identity(n1) + self.P1C_11 += np.identity(n1) # 2nd component - self.P1C_34 = xp.zeros((2, 2 * d1), dtype=float) + self.P1C_34 = np.zeros((2, 2 * d1), dtype=float) self.P1C_45 = spa.identity((n0 - 2) * d1) - self.P1C_34[0, (d1 + 0 * d1 // 3) : (d1 + 1 * d1 // 3)] = xp.ones(d1 // 3, dtype=float) - self.P1C_34[1, (d1 + 0 * d1 // 3) : (d1 + 1 * d1 // 3)] = xp.ones(d1 // 3, dtype=float) - self.P1C_34[1, (d1 + 1 * d1 // 3) : (d1 + 2 * d1 // 3)] = xp.ones(d1 // 3, dtype=float) + self.P1C_34[0, (d1 + 0 * d1 // 3) : (d1 + 1 * d1 // 3)] = np.ones(d1 // 3, dtype=float) + self.P1C_34[1, (d1 + 0 * d1 // 3) : (d1 + 1 * d1 // 3)] = np.ones(d1 // 3, dtype=float) + self.P1C_34[1, (d1 + 1 * d1 // 3) : (d1 + 2 * d1 // 3)] = np.ones(d1 // 3, dtype=float) # combined first and second component self.P1C = spa.bmat( @@ -790,8 +790,8 @@ def __init__(self, cx, cy): # ========================================================================= # ========= extraction operators for discrete 1-forms (H_div) ============= - self.E1D_11 = xp.zeros((2, 2 * d1), dtype=float) - self.E1D_13 = xp.zeros((2, 1 * n1), dtype=float) + self.E1D_11 = np.zeros((2, 2 * d1), dtype=float) + self.E1D_13 = np.zeros((2, 1 * n1), dtype=float) self.E1D_22 = spa.identity((n0 - 2) * d1) self.E1D_34 = spa.identity((d0 - 1) * n1) @@ -834,13 +834,13 @@ def __init__(self, cx, cy): # ========================================================================= # =========== extraction operators for discrete 2-forms =================== - self.E2_1 = xp.zeros(((d0 - 1) * d1, d1), dtype=float) + self.E2_1 = np.zeros(((d0 - 1) * d1, d1), dtype=float) self.E2_2 = spa.identity((d0 - 1) * d1) self.E2 = spa.bmat([[self.E2_1, self.E2_2]], format="csr") # extraction operator for histopolation in global projector - self.P2_11 = xp.zeros((d1, d1), dtype=float) + self.P2_11 = np.zeros((d1, d1), dtype=float) self.P2_12 = spa.identity(d1) self.P2_23 = spa.identity((d0 - 2) * d1) @@ -853,7 +853,7 @@ def __init__(self, cx, cy): # block B self.P2_11[i, 1 * n1 // 3 : 2 * n1 // 3] = -(self.Xi_1[2, (i + 1) % n1] - self.Xi_1[2, i]) - self.P2_11 += xp.identity(d1) + self.P2_11 += np.identity(d1) self.P2 = spa.bmat([[self.P2_11, self.P2_12, None], [None, None, self.P2_23]], format="csr") # ========================================================================= @@ -864,14 +864,14 @@ def __init__(self, cx, cy): # ========================================================================= # ========= discrete polar gradient matrix ================================ - self.G1_1 = xp.zeros(((d0 - 1) * n1, 3), dtype=float) + self.G1_1 = np.zeros(((d0 - 1) * n1, 3), dtype=float) self.G1_1[:n1, :] = -self.Xi_1.T self.G1_2 = spa.kron(grad_1d_1[1:, 2:], spa.identity(n1)) self.G1 = spa.bmat([[self.G1_1, self.G1_2]], format="csr") - self.G2_11 = xp.zeros((2, 3), dtype=float) + self.G2_11 = np.zeros((2, 3), dtype=float) self.G2_11[0, 0] = -1.0 self.G2_11[0, 1] = 1.0 @@ -888,7 +888,7 @@ def __init__(self, cx, cy): # ========= discrete polar curl matrix =================================== # 2D vector curl - self.VC1_11 = xp.zeros((2, 3), dtype=float) + self.VC1_11 = np.zeros((2, 3), dtype=float) self.VC1_11[0, 0] = -1.0 self.VC1_11[0, 1] = 1.0 @@ -900,7 +900,7 @@ def __init__(self, cx, cy): self.VC1 = spa.bmat([[self.VC1_11, None], [None, self.VC1_22]], format="csr") - self.VC2_11 = xp.zeros(((d0 - 1) * n1, 3), dtype=float) + self.VC2_11 = np.zeros(((d0 - 1) * n1, 3), dtype=float) self.VC2_11[:n1, :] = -self.Xi_1.T self.VC2_22 = spa.kron(grad_1d_1[1:, 2:], spa.identity(n1)) @@ -912,7 +912,7 @@ def __init__(self, cx, cy): # 2D scalar curl self.SC1 = -spa.kron(spa.identity(d0 - 1), grad_1d_2) - self.SC2_1 = xp.zeros(((d0 - 1) * d1, 2), dtype=float) + self.SC2_1 = np.zeros(((d0 - 1) * d1, 2), dtype=float) for s in range(2): for j in range(d1): @@ -926,7 +926,7 @@ def __init__(self, cx, cy): # ========================================================================= # ========= discrete polar div matrix ===================================== - self.D1_1 = xp.zeros(((d0 - 1) * d1, 2), dtype=float) + self.D1_1 = np.zeros(((d0 - 1) * d1, 2), dtype=float) for s in range(2): for j in range(d1): @@ -965,25 +965,24 @@ def __init__(self, tensor_space, cx, cy): self.Nbase3_pol = (d0 - 1) * d1 # size of control triangle - self.tau = xp.array( - [(-2 * cx[1]).max(), (cx[1] - xp.sqrt(3) * cy[1]).max(), (cx[1] + xp.sqrt(3) * cy[1]).max()], + self.tau = np.array( + [(-2 * cx[1]).max(), (cx[1] - np.sqrt(3) * cy[1]).max(), (cx[1] + np.sqrt(3) * cy[1]).max()] ).max() - self.Xi_0 = xp.zeros((3, n1), dtype=float) - self.Xi_1 = xp.zeros((3, n1), dtype=float) + self.Xi_0 = np.zeros((3, n1), dtype=float) + self.Xi_1 = np.zeros((3, n1), dtype=float) # barycentric coordinates self.Xi_0[:, :] = 1 / 3 self.Xi_1[0, :] = 1 / 3 + 2 / (3 * self.tau) * cx[1, :, 0] - self.Xi_1[1, :] = 1 / 3 - 1 / (3 * self.tau) * cx[1, :, 0] + xp.sqrt(3) / (3 * self.tau) * cy[1, :, 0] - self.Xi_1[2, :] = 1 / 3 - 1 / (3 * self.tau) * cx[1, :, 0] - xp.sqrt(3) / (3 * self.tau) * cy[1, :, 0] + self.Xi_1[1, :] = 1 / 3 - 1 / (3 * self.tau) * cx[1, :, 0] + np.sqrt(3) / (3 * self.tau) * cy[1, :, 0] + self.Xi_1[2, :] = 1 / 3 - 1 / (3 * self.tau) * cx[1, :, 0] - np.sqrt(3) / (3 * self.tau) * cy[1, :, 0] # =========== extraction operators for discrete 0-forms ================== # extraction operator for basis functions self.E0_pol = spa.bmat( - [[xp.hstack((self.Xi_0, self.Xi_1)), None], [None, spa.identity((n0 - 2) * n1)]], - format="csr", + [[np.hstack((self.Xi_0, self.Xi_1)), None], [None, spa.identity((n0 - 2) * n1)]], format="csr" ) self.E0 = spa.kron(self.E0_pol, spa.identity(n2), format="csr") @@ -1006,7 +1005,7 @@ def __init__(self, tensor_space, cx, cy): for j in range(n1): self.E1_1_pol[(d0 - 1) * n1 + s, j] = self.Xi_1[s + 1, j] - self.Xi_0[s + 1, j] - self.E1_1_pol[: (d0 - 1) * n1, n1:] = xp.identity((d0 - 1) * n1) + self.E1_1_pol[: (d0 - 1) * n1, n1:] = np.identity((d0 - 1) * n1) self.E1_1_pol = self.E1_1_pol.tocsr() # 2nd component @@ -1015,7 +1014,7 @@ def __init__(self, tensor_space, cx, cy): self.E1_2_pol[(d0 - 1) * n1 + s, j] = 0.0 self.E1_2_pol[(d0 - 1) * n1 + s, n1 + j] = self.Xi_1[s + 1, (j + 1) % n1] - self.Xi_1[s + 1, j] - self.E1_2_pol[((d0 - 1) * n1 + 2) :, 2 * d1 :] = xp.identity((n0 - 2) * d1) + self.E1_2_pol[((d0 - 1) * n1 + 2) :, 2 * d1 :] = np.identity((n0 - 2) * d1) self.E1_2_pol = self.E1_2_pol.tocsr() # 3rd component @@ -1044,9 +1043,9 @@ def __init__(self, tensor_space, cx, cy): self.P1_1_pol = self.P1_1_pol.tocsr() # 2nd component - self.P1_2_pol[0, (n1 + 0 * n1 // 3) : (n1 + 1 * n1 // 3)] = xp.ones((1, n1 // 3), dtype=float) - self.P1_2_pol[1, (n1 + 0 * n1 // 3) : (n1 + 1 * n1 // 3)] = xp.ones((1, n1 // 3), dtype=float) - self.P1_2_pol[1, (n1 + 1 * n1 // 3) : (n1 + 2 * n1 // 3)] = xp.ones((1, n1 // 3), dtype=float) + self.P1_2_pol[0, (n1 + 0 * n1 // 3) : (n1 + 1 * n1 // 3)] = np.ones((1, n1 // 3), dtype=float) + self.P1_2_pol[1, (n1 + 0 * n1 // 3) : (n1 + 1 * n1 // 3)] = np.ones((1, n1 // 3), dtype=float) + self.P1_2_pol[1, (n1 + 1 * n1 // 3) : (n1 + 2 * n1 // 3)] = np.ones((1, n1 // 3), dtype=float) self.P1_2_pol[2:, 2 * n1 :] = spa.identity((n0 - 2) * d1) self.P1_2_pol = self.P1_2_pol.tocsr() @@ -1074,7 +1073,7 @@ def __init__(self, tensor_space, cx, cy): self.E2_1_pol[s, j] = 0.0 self.E2_1_pol[s, n1 + j] = self.Xi_1[s + 1, (j + 1) % n1] - self.Xi_1[s + 1, j] - self.E2_1_pol[2 : (2 + (n0 - 2) * d1), 2 * n1 :] = xp.identity((n0 - 2) * d1) + self.E2_1_pol[2 : (2 + (n0 - 2) * d1), 2 * n1 :] = np.identity((n0 - 2) * d1) self.E2_1_pol = self.E2_1_pol.tocsr() # 2nd component @@ -1082,11 +1081,11 @@ def __init__(self, tensor_space, cx, cy): for j in range(n1): self.E2_2_pol[s, j] = -(self.Xi_1[s + 1, j] - self.Xi_0[s + 1, j]) - self.E2_2_pol[(2 + (n0 - 2) * d1) :, 1 * n1 :] = xp.identity((d0 - 1) * n1) + self.E2_2_pol[(2 + (n0 - 2) * d1) :, 1 * n1 :] = np.identity((d0 - 1) * n1) self.E2_2_pol = self.E2_2_pol.tocsr() # 3rd component - self.E2_3_pol[:, 1 * d1 :] = xp.identity((d0 - 1) * d1) + self.E2_3_pol[:, 1 * d1 :] = np.identity((d0 - 1) * d1) self.E2_3_pol = self.E2_3_pol.tocsr() # combined first and second component @@ -1216,7 +1215,7 @@ def __init__(self, tensor_space, cx, cy): [ [None, -spa.kron(spa.identity((n0 - 2) * d1 + 2), grad_1d_3)], [spa.kron(spa.identity((d0 - 1) * n1), grad_1d_3), None], - ], + ] ) # total polar curl diff --git a/src/struphy/polar/linear_operators.py b/src/struphy/polar/linear_operators.py index 0e37c7e76..f6253dd52 100644 --- a/src/struphy/polar/linear_operators.py +++ b/src/struphy/polar/linear_operators.py @@ -1,5 +1,5 @@ -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI from psydac.linalg.block import BlockVector, BlockVectorSpace from psydac.linalg.stencil import StencilVector, StencilVectorSpace from scipy.sparse import csr_matrix, identity @@ -334,14 +334,7 @@ class PolarLinearOperator(LinOpWithTransp): """ def __init__( - self, - V, - W, - tp_operator=None, - blocks_pol_to_ten=None, - blocks_pol_to_pol=None, - blocks_e3=None, - transposed=False, + self, V, W, tp_operator=None, blocks_pol_to_ten=None, blocks_pol_to_pol=None, blocks_e3=None, transposed=False ): assert isinstance(V, PolarDerhamSpace) assert isinstance(W, PolarDerhamSpace) @@ -675,7 +668,7 @@ def dot_inner_tp_rings(blocks_e1_e2, blocks_e3, v, out): # loop over codomain components for m, (row_e1_e2, row_e3) in enumerate(zip(blocks_e1_e2, blocks_e3)): - res = xp.zeros((n_rows[m], n3_out[m]), dtype=float) + res = np.zeros((n_rows[m], n3_out[m]), dtype=float) # loop over domain components for n, (block_e1_e2, block_e3) in enumerate(zip(row_e1_e2, row_e3)): @@ -684,7 +677,7 @@ def dot_inner_tp_rings(blocks_e1_e2, blocks_e3, v, out): e1, e2, e3 = in_ends[n] if block_e1_e2 is not None: - tmp = xp.zeros((n_rings_in[n], n2, n3_in[n]), dtype=float) + tmp = np.zeros((n_rings_in[n], n2, n3_in[n]), dtype=float) tmp[:, s2 : e2 + 1, s3 : e3 + 1] = in_vec[n][0 : n_rings_in[n], s2 : e2 + 1, s3 : e3 + 1] res += kron_matvec_2d([block_e1_e2, block_e3], tmp.reshape(n_rings_in[n] * n2, n3_in[n])) @@ -792,7 +785,7 @@ def dot_parts_of_polar(blocks_e1_e2, blocks_e3, v, out): # loop over codomain components for m, (row_e1_e2, row_e3) in enumerate(zip(blocks_e1_e2, blocks_e3)): - res = xp.zeros((n_rings_out[m], n2, n3_out[m]), dtype=float) + res = np.zeros((n_rings_out[m], n2, n3_out[m]), dtype=float) # loop over domain components for n, (block_e1_e2, block_e3) in enumerate(zip(row_e1_e2, row_e3)): @@ -801,7 +794,7 @@ def dot_parts_of_polar(blocks_e1_e2, blocks_e3, v, out): if in_starts[n][0] == 0: s1, s2, s3 = in_starts[n] e1, e2, e3 = in_ends[n] - tmp = xp.zeros((n2, n3_in[n]), dtype=float) + tmp = np.zeros((n2, n3_in[n]), dtype=float) tmp[s2 : e2 + 1, s3 : e3 + 1] = in_tp[n][n_rings_in[n], s2 : e2 + 1, s3 : e3 + 1] res += kron_matvec_2d([block_e1_e2, block_e3], tmp).reshape(n_rings_out[m], n2, n3_out[m]) else: diff --git a/src/struphy/polar/tests/test_legacy_polar_splines.py b/src/struphy/polar/tests/test_legacy_polar_splines.py index be2bfb654..eda4c378a 100644 --- a/src/struphy/polar/tests/test_legacy_polar_splines.py +++ b/src/struphy/polar/tests/test_legacy_polar_splines.py @@ -7,8 +7,8 @@ def test_polar_splines_2D(plot=False): sys.path.append("..") - import cunumpy as xp import matplotlib.pyplot as plt + import numpy as np from mpl_toolkits.mplot3d import Axes3D from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space @@ -42,8 +42,8 @@ def test_polar_splines_2D(plot=False): fig.set_figheight(10) fig.set_figwidth(10) - el_b_1 = xp.linspace(0.0, 1.0, Nel[0] + 1) - el_b_2 = xp.linspace(0.0, 1.0, Nel[1] + 1) + el_b_1 = np.linspace(0.0, 1.0, Nel[0] + 1) + el_b_2 = np.linspace(0.0, 1.0, Nel[1] + 1) grid_x = domain(el_b_1, el_b_2, 0.0, squeeze_out=True)[0] grid_y = domain(el_b_1, el_b_2, 0.0, squeeze_out=True)[1] @@ -108,7 +108,7 @@ def test_polar_splines_2D(plot=False): ) # plot three new polar splines in V0 - etaplot = [xp.linspace(0.0, 1.0, 200), xp.linspace(0.0, 1.0, 200)] + etaplot = [np.linspace(0.0, 1.0, 200), np.linspace(0.0, 1.0, 200)] xplot = [ domain(etaplot[0], etaplot[1], 0.0, squeeze_out=True)[0], domain(etaplot[0], etaplot[1], 0.0, squeeze_out=True)[1], @@ -123,9 +123,9 @@ def test_polar_splines_2D(plot=False): ax3 = fig.add_subplot(133, projection="3d") # coeffs in polar basis - c0_pol1 = xp.zeros(space_2d.E0.shape[0], dtype=float) - c0_pol2 = xp.zeros(space_2d.E0.shape[0], dtype=float) - c0_pol3 = xp.zeros(space_2d.E0.shape[0], dtype=float) + c0_pol1 = np.zeros(space_2d.E0.shape[0], dtype=float) + c0_pol2 = np.zeros(space_2d.E0.shape[0], dtype=float) + c0_pol3 = np.zeros(space_2d.E0.shape[0], dtype=float) c0_pol1[0] = 1.0 c0_pol2[1] = 1.0 @@ -134,7 +134,7 @@ def test_polar_splines_2D(plot=False): ax1.plot_surface( xplot[0], xplot[1], - space_2d.evaluate_NN(etaplot[0], etaplot[1], xp.array([0.0]), c0_pol1, "V0")[:, :, 0], + space_2d.evaluate_NN(etaplot[0], etaplot[1], np.array([0.0]), c0_pol1, "V0")[:, :, 0], cmap="jet", ) ax1.set_xlabel("R [m]", labelpad=5) @@ -144,7 +144,7 @@ def test_polar_splines_2D(plot=False): ax2.plot_surface( xplot[0], xplot[1], - space_2d.evaluate_NN(etaplot[0], etaplot[1], xp.array([0.0]), c0_pol2, "V0")[:, :, 0], + space_2d.evaluate_NN(etaplot[0], etaplot[1], np.array([0.0]), c0_pol2, "V0")[:, :, 0], cmap="jet", ) ax2.set_xlabel("R [m]", labelpad=5) @@ -154,7 +154,7 @@ def test_polar_splines_2D(plot=False): ax3.plot_surface( xplot[0], xplot[1], - space_2d.evaluate_NN(etaplot[0], etaplot[1], xp.array([0.0]), c0_pol3, "V0")[:, :, 0], + space_2d.evaluate_NN(etaplot[0], etaplot[1], np.array([0.0]), c0_pol3, "V0")[:, :, 0], cmap="jet", ) ax3.set_xlabel("R [m]", labelpad=5) diff --git a/src/struphy/polar/tests/test_polar.py b/src/struphy/polar/tests/test_polar.py index ac0113c4f..0c8f597d3 100644 --- a/src/struphy/polar/tests/test_polar.py +++ b/src/struphy/polar/tests/test_polar.py @@ -1,6 +1,7 @@ import pytest +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 6]]) @pytest.mark.parametrize("p", [[3, 2, 4]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -167,8 +168,8 @@ def test_spaces(Nel, p, spl_kind): @pytest.mark.parametrize("p", [[3, 2, 2]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) def test_extraction_ops_and_derivatives(Nel, p, spl_kind): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -222,11 +223,11 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): b2_pol.tp = b2_tp p3_pol.tp = p3_tp - xp.random.seed(1607) - f0_pol.pol = [xp.random.rand(f0_pol.pol[0].shape[0], f0_pol.pol[0].shape[1])] - e1_pol.pol = [xp.random.rand(e1_pol.pol[n].shape[0], e1_pol.pol[n].shape[1]) for n in range(3)] - b2_pol.pol = [xp.random.rand(b2_pol.pol[n].shape[0], b2_pol.pol[n].shape[1]) for n in range(3)] - p3_pol.pol = [xp.random.rand(p3_pol.pol[0].shape[0], p3_pol.pol[0].shape[1])] + np.random.seed(1607) + f0_pol.pol = [np.random.rand(f0_pol.pol[0].shape[0], f0_pol.pol[0].shape[1])] + e1_pol.pol = [np.random.rand(e1_pol.pol[n].shape[0], e1_pol.pol[n].shape[1]) for n in range(3)] + b2_pol.pol = [np.random.rand(b2_pol.pol[n].shape[0], b2_pol.pol[n].shape[1]) for n in range(3)] + p3_pol.pol = [np.random.rand(p3_pol.pol[0].shape[0], p3_pol.pol[0].shape[1])] f0_pol_leg = f0_pol.toarray(True) e1_pol_leg = e1_pol.toarray(True) @@ -243,10 +244,10 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): r2_pol = derham.extraction_ops["2"].dot(b2_tp) r3_pol = derham.extraction_ops["3"].dot(p3_tp) - assert xp.allclose(r0_pol.toarray(True), space.E0.dot(f0_tp_leg)) - assert xp.allclose(r1_pol.toarray(True), space.E1.dot(e1_tp_leg)) - assert xp.allclose(r2_pol.toarray(True), space.E2.dot(b2_tp_leg)) - assert xp.allclose(r3_pol.toarray(True), space.E3.dot(p3_tp_leg)) + assert np.allclose(r0_pol.toarray(True), space.E0.dot(f0_tp_leg)) + assert np.allclose(r1_pol.toarray(True), space.E1.dot(e1_tp_leg)) + assert np.allclose(r2_pol.toarray(True), space.E2.dot(b2_tp_leg)) + assert np.allclose(r3_pol.toarray(True), space.E3.dot(p3_tp_leg)) # test transposed extraction operators E0T = derham.extraction_ops["0"].transpose() @@ -277,9 +278,9 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): r2_pol = derham.curl.dot(e1_pol) r3_pol = derham.div.dot(b2_pol) - assert xp.allclose(r1_pol.toarray(True), space.G.dot(f0_pol_leg)) - assert xp.allclose(r2_pol.toarray(True), space.C.dot(e1_pol_leg)) - assert xp.allclose(r3_pol.toarray(True), space.D.dot(b2_pol_leg)) + assert np.allclose(r1_pol.toarray(True), space.G.dot(f0_pol_leg)) + assert np.allclose(r2_pol.toarray(True), space.C.dot(e1_pol_leg)) + assert np.allclose(r3_pol.toarray(True), space.D.dot(b2_pol_leg)) # test transposed derivatives GT = derham.grad.transpose() @@ -290,9 +291,9 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): r1_pol = CT.dot(b2_pol) r2_pol = DT.dot(p3_pol) - assert xp.allclose(r0_pol.toarray(True), space.G.T.dot(e1_pol_leg)) - assert xp.allclose(r1_pol.toarray(True), space.C.T.dot(b2_pol_leg)) - assert xp.allclose(r2_pol.toarray(True), space.D.T.dot(p3_pol_leg)) + assert np.allclose(r0_pol.toarray(True), space.G.T.dot(e1_pol_leg)) + assert np.allclose(r1_pol.toarray(True), space.C.T.dot(b2_pol_leg)) + assert np.allclose(r2_pol.toarray(True), space.D.T.dot(p3_pol_leg)) if rank == 0: print("------------- Test passed ---------------------------") @@ -302,8 +303,8 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): @pytest.mark.parametrize("p", [[4, 3, 2]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) def test_projectors(Nel, p, spl_kind): - import cunumpy as xp - from psydac.ddm.mpi import mpi as MPI + import numpy as np + from mpi4py import MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -338,7 +339,7 @@ def test_projectors(Nel, p, spl_kind): # function to project on physical domain def fun_scalar(x, y, z): - return xp.sin(2 * xp.pi * (x)) * xp.cos(2 * xp.pi * y) * xp.sin(2 * xp.pi * z) + return np.sin(2 * np.pi * (x)) * np.cos(2 * np.pi * y) * np.sin(2 * np.pi * z) fun_vector = [fun_scalar, fun_scalar, fun_scalar] @@ -369,7 +370,7 @@ def fun3(e1, e2, e3): r0_pol_leg = space.projectors.pi_0(fun0) - assert xp.allclose(r0_pol.toarray(True), r0_pol_leg) + assert np.allclose(r0_pol.toarray(True), r0_pol_leg) if rank == 0: print("Test passed for PI_0 polar projector") @@ -385,7 +386,7 @@ def fun3(e1, e2, e3): r1_pol_leg = space.projectors.pi_1(fun1, with_subs=False) - assert xp.allclose(r1_pol.toarray(True), r1_pol_leg) + assert np.allclose(r1_pol.toarray(True), r1_pol_leg) if rank == 0: print("Test passed for PI_1 polar projector") @@ -401,7 +402,7 @@ def fun3(e1, e2, e3): r2_pol_leg = space.projectors.pi_2(fun2, with_subs=False) - assert xp.allclose(r2_pol.toarray(True), r2_pol_leg) + assert np.allclose(r2_pol.toarray(True), r2_pol_leg) if rank == 0: print("Test passed for PI_2 polar projector") @@ -417,7 +418,7 @@ def fun3(e1, e2, e3): r3_pol_leg = space.projectors.pi_3(fun3, with_subs=False) - assert xp.allclose(r3_pol.toarray(True), r3_pol_leg) + assert np.allclose(r3_pol.toarray(True), r3_pol_leg) if rank == 0: print("Test passed for PI_3 polar projector") diff --git a/src/struphy/post_processing/likwid/plot_likwidproject.py b/src/struphy/post_processing/likwid/plot_likwidproject.py index f4c3bb442..4dfae20e7 100644 --- a/src/struphy/post_processing/likwid/plot_likwidproject.py +++ b/src/struphy/post_processing/likwid/plot_likwidproject.py @@ -7,8 +7,8 @@ import re import sys -import cunumpy as xp import matplotlib.pyplot as plt +import numpy as np import pandas as pd import plotly.express as px import plotly.graph_objects as go @@ -196,16 +196,16 @@ def plot_roofline( fig.update_xaxes( type="log", # Ensure the x-axis is logarithmic - range=[xp.log10(xmin), xp.log10(xmax)], + range=[np.log10(xmin), np.log10(xmax)], title="Operational intensity (FLOP/Byte)", tickvals=xtick_values, # Set where ticks appear ticktext=[str(t) for t in xtick_values], - # ticktext=[f'$10^{{{int(xp.log10(t))}}}$' for t in xtick_values] # Set tick labels + # ticktext=[f'$10^{{{int(np.log10(t))}}}$' for t in xtick_values] # Set tick labels ) fig.update_yaxes( type="log", # Ensure the x-axis is logarithmic - range=[xp.log10(ymin), xp.log10(ymax)], + range=[np.log10(ymin), np.log10(ymax)], title="Performance [GFLOP/s]", tickvals=ytick_values, # Set where ticks appear ticktext=[str(t) for t in ytick_values], @@ -387,7 +387,7 @@ def plot_speedup( fig.update_layout( # xaxis_title='Job name', - xaxis_title="MPI tasks (#)", + xaxis_title=f"MPI tasks (#)", yaxis_title=re.sub(r"\[.*?\]", "[relative]", metric2), showlegend=True, xaxis_tickformat=".1f", @@ -818,7 +818,7 @@ def load_projects(data_paths, procs_per_clone="any"): ) if (procs_per_clone != "any") and (procs_per_clone != project.procs_per_clone): print( - f"Incorrect number of procs_per_clone: {project.procs_per_clone =} {procs_per_clone =}", + f"Incorrect number of procs_per_clone: {project.procs_per_clone = } {procs_per_clone = }", ) continue project.read_project() diff --git a/src/struphy/post_processing/likwid/plot_time_traces.py b/src/struphy/post_processing/likwid/plot_time_traces.py index 7451833cb..818d96935 100644 --- a/src/struphy/post_processing/likwid/plot_time_traces.py +++ b/src/struphy/post_processing/likwid/plot_time_traces.py @@ -2,9 +2,8 @@ import pickle import re -import cunumpy as xp import matplotlib.pyplot as plt -import plotly.graph_objects as go +import numpy as np import plotly.io as pio # pio.kaleido.scope.mathjax = None @@ -17,31 +16,19 @@ def glob_to_regex(pat: str) -> str: return "^" + esc.replace(r"\*", ".*").replace(r"\?", ".") + "$" -# def plot_region(region_name, groups_include=["*"], groups_skip=[]): -# # skips first -# for pat in groups_skip: -# rx = glob_to_regex(pat) -# if re.fullmatch(rx, region_name): -# return False - -# # includes next -# for pat in groups_include: -# rx = glob_to_regex(pat) -# if re.fullmatch(rx, region_name): -# return True - -# return False - - def plot_region(region_name, groups_include=["*"], groups_skip=[]): - from fnmatch import fnmatch - - for pattern in groups_skip: - if fnmatch(region_name, pattern): + # skips first + for pat in groups_skip: + rx = glob_to_regex(pat) + if re.fullmatch(rx, region_name): return False - for pattern in groups_include: - if fnmatch(region_name, pattern): + + # includes next + for pat in groups_include: + rx = glob_to_regex(pat) + if re.fullmatch(rx, region_name): return True + return False @@ -67,7 +54,7 @@ def plot_time_vs_duration( plt.figure(figsize=(10, 6)) for path in paths: - print(f"{path =}") + print(f"{path = }") with open(path, "rb") as file: profiling_data = pickle.load(file) @@ -134,9 +121,9 @@ def plot_avg_duration_bar_chart( # Compute statistics per region regions = sorted(region_durations.keys()) - avg_durations = [xp.mean(region_durations[r]) for r in regions] - min_durations = [xp.min(region_durations[r]) for r in regions] - max_durations = [xp.max(region_durations[r]) for r in regions] + avg_durations = [np.mean(region_durations[r]) for r in regions] + min_durations = [np.min(region_durations[r]) for r in regions] + max_durations = [np.max(region_durations[r]) for r in regions] yerr = [ [avg - min_ for avg, min_ in zip(avg_durations, min_durations)], [max_ - avg for avg, max_ in zip(avg_durations, max_durations)], @@ -144,7 +131,7 @@ def plot_avg_duration_bar_chart( # Plot bar chart with error bars (min-max spans) plt.figure(figsize=(12, 6)) - x = xp.arange(len(regions)) + x = np.arange(len(regions)) plt.bar(x, avg_durations, yerr=yerr, capsize=5, color="skyblue", edgecolor="k") plt.yscale("log") plt.xticks(x, regions, rotation=45, ha="right") @@ -159,6 +146,21 @@ def plot_avg_duration_bar_chart( print(f"Saved average duration bar chart to: {figure_path}") +import plotly.graph_objects as go + + +def plot_region(region_name, groups_include=["*"], groups_skip=[]): + from fnmatch import fnmatch + + for pattern in groups_skip: + if fnmatch(region_name, pattern): + return False + for pattern in groups_include: + if fnmatch(region_name, pattern): + return True + return False + + def plot_gantt_chart_plotly( path: str, output_path: str, @@ -173,7 +175,7 @@ def plot_gantt_chart_plotly( region_start_times = {} for rank_data in profiling_data["rank_data"].values(): for region_name, info in rank_data.items(): - first_start_time = xp.min(info["start_times"]) + first_start_time = np.min(info["start_times"]) if region_name not in region_start_times or first_start_time < region_start_times[region_name]: region_start_times[region_name] = first_start_time @@ -202,7 +204,7 @@ def plot_gantt_chart_plotly( Start=start_times[i], Finish=end_times[i], Duration=durations[i], - ), + ) ) if len(bars) == 0: @@ -224,7 +226,7 @@ def plot_gantt_chart_plotly( name=bar["Rank"], marker_color=rank_color_map[bar["Rank"]], hovertemplate=f"Rank: {bar['Rank']}
Start: {bar['Start']:.3f}s
Duration: {bar['Duration']:.3f}s", - ), + ) ) fig.update_layout( @@ -289,7 +291,7 @@ def plot_gantt_chart( region_start_times = {} for rank_data in profiling_data["rank_data"].values(): for region_name, info in rank_data.items(): - first_start_time = xp.min(info["start_times"]) + first_start_time = np.min(info["start_times"]) if region_name not in region_start_times or first_start_time < region_start_times[region_name]: region_start_times[region_name] = first_start_time diff --git a/src/struphy/post_processing/likwid/roofline_plotter.py b/src/struphy/post_processing/likwid/roofline_plotter.py index 3a4808bdc..1621e5d8d 100644 --- a/src/struphy/post_processing/likwid/roofline_plotter.py +++ b/src/struphy/post_processing/likwid/roofline_plotter.py @@ -1,7 +1,7 @@ import glob import pickle -import cunumpy as xp +import numpy as np import pandas as pd import yaml @@ -142,14 +142,14 @@ def add_plot_diagonal( bandwidth_GBps, label="", ymax=1e4, - operational_intensity_FLOPpMB=xp.arange(0, 1000, 1), + operational_intensity_FLOPpMB=np.arange(0, 1000, 1), ): max_performance_GFLOP = operational_intensity_FLOPpMB * bandwidth_GBps (line,) = mfig.axs.plot(operational_intensity_FLOPpMB, max_performance_GFLOP) # Specify the y-value where you want to place the text specific_y = ymax # Interpolate to find the corresponding x-value - specific_x = xp.interp( + specific_x = np.interp( specific_y, max_performance_GFLOP, operational_intensity_FLOPpMB, @@ -209,10 +209,10 @@ def get_average_val( xvec.append(x) yvec.append(y) # print('xvec', xvec, 'yvec', yvec) - xvec = xp.array(xvec) - yvec = xp.array(yvec) + xvec = np.array(xvec) + yvec = np.array(yvec) # print('xvec', xvec, 'yvec', yvec) - return xp.average(xvec), xp.average(yvec), xp.std(xvec), xp.std(yvec) + return np.average(xvec), np.average(yvec), np.std(xvec), np.std(yvec) def get_maximum(path, df_index=-1, metric="DP [MFLOP/s] STAT", column_name="Sum"): diff --git a/src/struphy/post_processing/orbits/orbits_tools.py b/src/struphy/post_processing/orbits/orbits_tools.py index 97eee89af..357e13d4f 100644 --- a/src/struphy/post_processing/orbits/orbits_tools.py +++ b/src/struphy/post_processing/orbits/orbits_tools.py @@ -1,8 +1,8 @@ import os import shutil -import cunumpy as xp import h5py +import numpy as np import yaml from tqdm import tqdm @@ -61,7 +61,7 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): if file.endswith(".npy") ] pproc_nt = len(npy_files_list) - n_markers = xp.load(os.path.join(path_orbits, npy_files_list[0])).shape[0] + n_markers = np.load(os.path.join(path_orbits, npy_files_list[0])).shape[0] # re-ordering npy_files npy_files_list = sorted(npy_files_list) @@ -76,10 +76,10 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): os.mkdir(path_gc) # temporary marker array - temp = xp.empty((n_markers, 7), dtype=float) - etas = xp.empty((n_markers, 3), dtype=float) - B_cart = xp.empty((n_markers, 3), dtype=float) - lost_particles_mask = xp.empty(n_markers, dtype=bool) + temp = np.empty((n_markers, 7), dtype=float) + etas = np.empty((n_markers, 3), dtype=float) + B_cart = np.empty((n_markers, 3), dtype=float) + lost_particles_mask = np.empty(n_markers, dtype=bool) print("Evaluation of guiding center for " + str(species)) @@ -94,13 +94,13 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): file_txt = os.path.join(path_gc, npy_files_list[n][:-4] + ".txt") # call .npy file - temp[:, :] = xp.load(os.path.join(path_orbits, npy_files_list[n])) + temp[:, :] = np.load(os.path.join(path_orbits, npy_files_list[n])) # move ids to last column and save - temp = xp.roll(temp, -1, axis=1) + temp = np.roll(temp, -1, axis=1) # sorting out lost particles - lost_particles_mask = xp.all(temp[:, :-1] == 0, axis=1) + lost_particles_mask = np.all(temp[:, :-1] == 0, axis=1) # domain inverse map etas[~lost_particles_mask, :] = domain.inverse_map( @@ -110,7 +110,7 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): # eval cartesian magnetic filed at marker positions B_cart[~lost_particles_mask, :] = equil.b_cart( - *xp.concatenate( + *np.concatenate( ( etas[:, 0][:, None], etas[:, 1][:, None], @@ -123,10 +123,10 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): calculate_guiding_center_from_6d(temp, B_cart) # move ids to first column and save - temp = xp.roll(temp, 1, axis=1) + temp = np.roll(temp, 1, axis=1) - xp.save(file_npy, temp) - xp.savetxt(file_txt, temp[:, :4], fmt="%12.6f", delimiter=", ") + np.save(file_npy, temp) + np.savetxt(file_txt, temp[:, :4], fmt="%12.6f", delimiter=", ") def post_process_orbit_classification(path_kinetics_species, species): @@ -168,16 +168,16 @@ def post_process_orbit_classification(path_kinetics_species, species): if file.endswith(".npy") ] pproc_nt = len(npy_files_list) - n_markers = xp.load(os.path.join(path_gc, npy_files_list[0])).shape[0] + n_markers = np.load(os.path.join(path_gc, npy_files_list[0])).shape[0] # re-ordering npy_files npy_files_list = sorted(npy_files_list) # temporary marker array - temp = xp.empty((n_markers, 8), dtype=float) - v_parallel = xp.empty(n_markers, dtype=float) - trapped_particle_mask = xp.empty(n_markers, dtype=bool) - lost_particle_mask = xp.empty(n_markers, dtype=bool) + temp = np.empty((n_markers, 8), dtype=float) + v_parallel = np.empty(n_markers, dtype=float) + trapped_particle_mask = np.empty(n_markers, dtype=bool) + lost_particle_mask = np.empty(n_markers, dtype=bool) print("Classifying guiding center orbits for " + str(species)) @@ -188,16 +188,16 @@ def post_process_orbit_classification(path_kinetics_species, species): # load .npy files file_npy = os.path.join(path_gc, npy_files_list[n]) - temp[:, :-1] = xp.load(file_npy) + temp[:, :-1] = np.load(file_npy) # initial time step if n == 0: v_init = temp[:, 4] - xp.save(file_npy, temp) + np.save(file_npy, temp) continue # synchronizing with former time step - temp[:, -1] = xp.load( + temp[:, -1] = np.load( os.path.join( path_gc, npy_files_list[n - 1], @@ -205,10 +205,10 @@ def post_process_orbit_classification(path_kinetics_species, species): )[:, -1] # call parallel velocity data from .npy file - v_parallel = xp.load(os.path.join(path_gc, npy_files_list[n]))[:, 4] + v_parallel = np.load(os.path.join(path_gc, npy_files_list[n]))[:, 4] # sorting out lost particles - lost_particle_mask = xp.all(temp[:, 1:-1] == 0, axis=1) + lost_particle_mask = np.all(temp[:, 1:-1] == 0, axis=1) # check reverse of parallel velocity trapped_particle_mask[:] = False @@ -221,4 +221,4 @@ def post_process_orbit_classification(path_kinetics_species, species): # assign "-1" at the last index of lost particles temp[lost_particle_mask, -1] = -1 - xp.save(file_npy, temp) + np.save(file_npy, temp) diff --git a/src/struphy/post_processing/post_processing_tools.py b/src/struphy/post_processing/post_processing_tools.py index e0759bb63..0c7893acb 100644 --- a/src/struphy/post_processing/post_processing_tools.py +++ b/src/struphy/post_processing/post_processing_tools.py @@ -2,8 +2,8 @@ import pickle import shutil -import cunumpy as xp import h5py +import numpy as np import yaml from tqdm import tqdm @@ -138,7 +138,7 @@ def create_femfields( fields : dict Nested dictionary holding :class:`~struphy.feec.psydac_derham.SplineFunction`: fields[t][name] contains the Field with the name "name" in the hdf5 file at time t. - t_grid : xp.ndarray + t_grid : np.ndarray Time grid. """ @@ -156,7 +156,7 @@ def create_femfields( # get fields names, space IDs and time grid from 0-th rank hdf5 file file = h5py.File(os.path.join(path, "data/", "data_proc0.hdf5"), "r") space_ids = {} - print("\nReading hdf5 data of following species:") + print(f"\nReading hdf5 data of following species:") for species, dset in file["feec"].items(): space_ids[species] = {} print(f"{species}:") @@ -276,7 +276,7 @@ def eval_femfields( Returns ------- point_data : dict - Nested dictionary holding values of FemFields on the grid as list of 3d xp.arrays: + Nested dictionary holding values of FemFields on the grid as list of 3d np.arrays: point_data[name][t] contains the values of the field with name "name" in fields[t].keys() at time t. If physical is True, physical components of fields are saved. @@ -299,7 +299,7 @@ def eval_femfields( Nel = params_in.grid.Nel - grids_log = [xp.linspace(0.0, 1.0, Nel_i * n_i + 1) for Nel_i, n_i in zip(Nel, celldivide)] + grids_log = [np.linspace(0.0, 1.0, Nel_i * n_i + 1) for Nel_i, n_i in zip(Nel, celldivide)] grids_phy = [ domain(*grids_log)[0], domain(*grids_log)[1], @@ -326,7 +326,7 @@ def eval_femfields( point_data[species][name][t] = [] # scalar spaces - if isinstance(temp_val, xp.ndarray): + if isinstance(temp_val, np.ndarray): if physical: # push-forward if space_id == "H1": @@ -387,7 +387,7 @@ def eval_femfields( def create_vtk( path: str, - t_grid: xp.ndarray, + t_grid: np.ndarray, grids_phy: list, point_data: dict, *, @@ -400,7 +400,7 @@ def create_vtk( path : str Absolute path of where to store the .vts files. Will then be in path/vtk/step_.vts. - t_grid : xp.ndarray + t_grid : np.ndarray Time grid. grids_phy : 3-list @@ -425,7 +425,7 @@ def create_vtk( # time loop nt = len(t_grid) - 1 - log_nt = int(xp.log10(nt)) + 1 + log_nt = int(np.log10(nt)) + 1 print(f"\nCreating vtk in {path} ...") for n, t in enumerate(tqdm(t_grid)): @@ -542,7 +542,7 @@ def post_process_markers( # get number of time steps and markers nt, n_markers, n_cols = files[0]["kinetic/" + species + "/markers"].shape - log_nt = int(xp.log10(int(((nt - 1) / step)))) + 1 + log_nt = int(np.log10(int(((nt - 1) / step)))) + 1 # directory for .txt files and marker index which will be saved path_orbits = os.path.join(path_out, "orbits") @@ -561,8 +561,8 @@ def post_process_markers( os.mkdir(path_orbits) # temporary array - temp = xp.empty((n_markers, len(save_index)), order="C") - lost_particles_mask = xp.empty(n_markers, dtype=bool) + temp = np.empty((n_markers, len(save_index)), order="C") + lost_particles_mask = np.empty(n_markers, dtype=bool) print(f"Evaluation of {n_markers} marker orbits for {species}") @@ -589,28 +589,28 @@ def post_process_markers( # sorting out lost particles ids = temp[:, -1].astype("int") - ids_lost_particles = xp.setdiff1d(xp.arange(n_markers), ids) - ids_removed_particles = xp.nonzero(temp[:, 0] == -1.0)[0] - ids_lost_particles = xp.array(list(set(ids_lost_particles) | set(ids_removed_particles)), dtype=int) + ids_lost_particles = np.setdiff1d(np.arange(n_markers), ids) + ids_removed_particles = np.nonzero(temp[:, 0] == -1.0)[0] + ids_lost_particles = np.array(list(set(ids_lost_particles) | set(ids_removed_particles)), dtype=int) lost_particles_mask[:] = False lost_particles_mask[ids_lost_particles] = True if len(ids_lost_particles) > 0: # lost markers are saved as [0, ..., 0, ids] temp[lost_particles_mask, -1] = ids_lost_particles - ids = xp.unique(xp.append(ids, ids_lost_particles)) + ids = np.unique(np.append(ids, ids_lost_particles)) - assert xp.all(sorted(ids) == xp.arange(n_markers)) + assert np.all(sorted(ids) == np.arange(n_markers)) # compute physical positions (x, y, z) - pos_phys = domain(xp.array(temp[~lost_particles_mask, :3]), change_out_order=True) + pos_phys = domain(np.array(temp[~lost_particles_mask, :3]), change_out_order=True) temp[~lost_particles_mask, :3] = pos_phys # save numpy - xp.save(file_npy, temp) + np.save(file_npy, temp) # move ids to first column and save txt - temp = xp.roll(temp, 1, axis=1) - xp.savetxt(file_txt, temp[:, (0, 1, 2, 3, -1)], fmt="%12.6f", delimiter=", ") + temp = np.roll(temp, 1, axis=1) + np.savetxt(file_txt, temp[:, (0, 1, 2, 3, -1)], fmt="%12.6f", delimiter=", ") # close hdf5 files for file in files: @@ -692,7 +692,7 @@ def post_process_f( path_slice, "grid_" + slice_names[n_gr] + ".npy", ) - xp.save(grid_path, grid[:]) + np.save(grid_path, grid[:]) # compute distribution function for slice_name in tqdm(files[0]["kinetic/" + species + "/f"]): @@ -713,8 +713,8 @@ def post_process_f( data_df += files[rank]["kinetic/" + species + "/df/" + slice_name][::step] # save distribution functions - xp.save(os.path.join(path_slice, "f_binned.npy"), data) - xp.save(os.path.join(path_slice, "delta_f_binned.npy"), data_df) + np.save(os.path.join(path_slice, "f_binned.npy"), data) + np.save(os.path.join(path_slice, "delta_f_binned.npy"), data_df) if compute_bckgr: # bckgr_params = params["kinetic"][species]["background"] @@ -753,11 +753,11 @@ def post_process_f( # check if file exists and is in slice_name if os.path.exists(filename) and current_slice in slice_names: - grid_tot += [xp.load(filename)] + grid_tot += [np.load(filename)] # otherwise evaluate at zero else: - grid_tot += [xp.zeros(1)] + grid_tot += [np.zeros(1)] # v-grid for comp in range(1, f_bckgr.vdim + 1): @@ -769,15 +769,15 @@ def post_process_f( # check if file exists and is in slice_name if os.path.exists(filename) and current_slice in slice_names: - grid_tot += [xp.load(filename)] + grid_tot += [np.load(filename)] # otherwise evaluate at zero else: - grid_tot += [xp.zeros(1)] + grid_tot += [np.zeros(1)] # correct integrating out in v-direction, TODO: check for 5D Maxwellians - factor *= xp.sqrt(2 * xp.pi) + factor *= np.sqrt(2 * np.pi) - grid_eval = xp.meshgrid(*grid_tot, indexing="ij") + grid_eval = np.meshgrid(*grid_tot, indexing="ij") data_bckgr = f_bckgr(*grid_eval).squeeze() @@ -788,9 +788,9 @@ def post_process_f( data_delta_f = data_df # save distribution function - xp.save(os.path.join(path_slice, "delta_f_binned.npy"), data_delta_f) + np.save(os.path.join(path_slice, "delta_f_binned.npy"), data_delta_f) # add extra axis for data_bckgr since data_delta_f has axis for time series - xp.save( + np.save( os.path.join(path_slice, "f_binned.npy"), data_delta_f + data_bckgr[tuple([None])], ) @@ -866,7 +866,7 @@ def post_process_n_sph( eta2 = files[0]["kinetic/" + species + "/n_sph/" + view].attrs["eta2"] eta3 = files[0]["kinetic/" + species + "/n_sph/" + view].attrs["eta3"] - ee1, ee2, ee3 = xp.meshgrid( + ee1, ee2, ee3 = np.meshgrid( eta1, eta2, eta3, @@ -877,7 +877,7 @@ def post_process_n_sph( path_view, "grid_n_sph.npy", ) - xp.save(grid_path, (ee1, ee2, ee3)) + np.save(grid_path, (ee1, ee2, ee3)) # load n_sph data data = files[0]["kinetic/" + species + "/n_sph/" + view][::step].copy() @@ -885,4 +885,4 @@ def post_process_n_sph( data += files[rank]["kinetic/" + species + "/n_sph/" + view][::step] # save distribution functions - xp.save(os.path.join(path_view, "n_sph.npy"), data) + np.save(os.path.join(path_view, "n_sph.npy"), data) diff --git a/src/struphy/post_processing/pproc_struphy.py b/src/struphy/post_processing/pproc_struphy.py index 940c94ab3..8d26c8c8d 100644 --- a/src/struphy/post_processing/pproc_struphy.py +++ b/src/struphy/post_processing/pproc_struphy.py @@ -2,8 +2,8 @@ import pickle import shutil -import cunumpy as xp import h5py +import numpy as np import yaml import struphy.post_processing.orbits.orbits_tools as orbits_pproc @@ -64,7 +64,7 @@ def main( file = h5py.File(os.path.join(path, "data/", "data_proc0.hdf5"), "r") # save time grid at which post-processing data is created - xp.save(os.path.join(path_pproc, "t_grid.npy"), file["time/value"][::step].copy()) + np.save(os.path.join(path_pproc, "t_grid.npy"), file["time/value"][::step].copy()) if "feec" in file.keys(): exist_fields = True @@ -96,17 +96,12 @@ def main( fields, t_grid = pproc.create_femfields(path, params_in, step=step) point_data, grids_log, grids_phy = pproc.eval_femfields( - params_in, - fields, - celldivide=[celldivide, celldivide, celldivide], + params_in, fields, celldivide=[celldivide, celldivide, celldivide] ) if physical: point_data_phy, grids_log, grids_phy = pproc.eval_femfields( - params_in, - fields, - celldivide=[celldivide, celldivide, celldivide], - physical=True, + params_in, fields, celldivide=[celldivide, celldivide, celldivide], physical=True ) # directory for field data @@ -201,19 +196,14 @@ def main( libpath = struphy.__path__[0] parser = argparse.ArgumentParser( - description="Post-process data of finished Struphy runs to prepare for diagnostics.", + description="Post-process data of finished Struphy runs to prepare for diagnostics." ) # paths of simulation folders parser.add_argument("dir", type=str, metavar="DIR", help="absolute path of simulation ouput folder to post-process") parser.add_argument( - "-s", - "--step", - type=int, - metavar="N", - help="do post-processing every N-th time step (default=1)", - default=1, + "-s", "--step", type=int, metavar="N", help="do post-processing every N-th time step (default=1)", default=1 ) parser.add_argument( @@ -231,15 +221,11 @@ def main( ) parser.add_argument( - "--guiding-center", - help="compute guiding-center coordinates (only from Particles6D)", - action="store_true", + "--guiding-center", help="compute guiding-center coordinates (only from Particles6D)", action="store_true" ) parser.add_argument( - "--classify", - help="classify guiding-center trajectories (passing, trapped or lost)", - action="store_true", + "--classify", help="classify guiding-center trajectories (passing, trapped or lost)", action="store_true" ) parser.add_argument("--no-vtk", help="whether vtk files creation should be skipped", action="store_true") diff --git a/src/struphy/post_processing/profile_struphy.py b/src/struphy/post_processing/profile_struphy.py index 43d4be47d..0c0d77e73 100644 --- a/src/struphy/post_processing/profile_struphy.py +++ b/src/struphy/post_processing/profile_struphy.py @@ -1,7 +1,7 @@ import pickle import sys -import cunumpy as xp +import numpy as np import yaml from matplotlib import pyplot as plt @@ -93,7 +93,7 @@ def main(): + "ncalls".ljust(15) + "totime".ljust(15) + "percall".ljust(15) - + "cumtime".ljust(15), + + "cumtime".ljust(15) ) print("-" * 154) for position, key in enumerate(dicts[0].keys()): @@ -150,17 +150,17 @@ def main(): plt.ylabel("time [s]") plt.title("Strong scaling for Nel=" + str(val["Nel"][0]) + " cells") plt.legend(loc="lower left") - plt.loglog(val["mpi_size"], val["time"][0] / 2 ** xp.arange(len(val["time"])), "k--", alpha=0.3) + plt.loglog(val["mpi_size"], val["time"][0] / 2 ** np.arange(len(val["time"])), "k--", alpha=0.3) # weak scaling plot else: plt.plot(val["mpi_size"], val["time"], label=key) plt.xlabel("mpi_size") plt.ylabel("time [s]") plt.title( - "Weak scaling for cells/mpi_size=" + str(xp.prod(val["Nel"][0]) / val["mpi_size"][0]) + "=const.", + "Weak scaling for cells/mpi_size=" + str(np.prod(val["Nel"][0]) / val["mpi_size"][0]) + "=const." ) plt.legend(loc="upper left") - # plt.loglog(val['mpi_size'], val['time'][0]*xp.ones_like(val['time']), 'k--', alpha=0.3) + # plt.loglog(val['mpi_size'], val['time'][0]*np.ones_like(val['time']), 'k--', alpha=0.3) plt.xscale("log") plt.show() diff --git a/src/struphy/profiling/profiling.py b/src/struphy/profiling/profiling.py index e96749614..160002c15 100644 --- a/src/struphy/profiling/profiling.py +++ b/src/struphy/profiling/profiling.py @@ -17,8 +17,8 @@ # Import the profiling configuration class and context manager from functools import lru_cache -import cunumpy as xp -from psydac.ddm.mpi import mpi as MPI +import numpy as np +from mpi4py import MPI @lru_cache(maxsize=None) # Cache the import result to avoid repeated imports @@ -170,9 +170,9 @@ def save_to_pickle(cls, file_path): for name, region in cls._regions.items(): local_data[name] = { "ncalls": region.ncalls, - "durations": xp.array(region.durations, dtype=xp.float64), - "start_times": xp.array(region.start_times, dtype=xp.float64), - "end_times": xp.array(region.end_times, dtype=xp.float64), + "durations": np.array(region.durations, dtype=np.float64), + "start_times": np.array(region.start_times, dtype=np.float64), + "end_times": np.array(region.end_times, dtype=np.float64), "config": { "likwid": region.config.likwid, "simulation_label": region.config.simulation_label, @@ -246,7 +246,7 @@ def print_summary(cls): average_duration = total_duration / region.ncalls min_duration = min(region.durations) max_duration = max(region.durations) - std_duration = xp.std(region.durations) + std_duration = np.std(region.durations) else: total_duration = average_duration = min_duration = max_duration = std_duration = 0 @@ -270,16 +270,16 @@ def __init__(self, region_name, time_trace=False): self._region_name = self.config.simulation_label + region_name self._time_trace = time_trace self._ncalls = 0 - self._start_times = xp.empty(1, dtype=float) - self._end_times = xp.empty(1, dtype=float) - self._durations = xp.empty(1, dtype=float) + self._start_times = np.empty(1, dtype=float) + self._end_times = np.empty(1, dtype=float) + self._durations = np.empty(1, dtype=float) self._started = False def __enter__(self): if self._ncalls == len(self._start_times): - self._start_times = xp.append(self._start_times, xp.zeros_like(self._start_times)) - self._end_times = xp.append(self._end_times, xp.zeros_like(self._end_times)) - self._durations = xp.append(self._durations, xp.zeros_like(self._durations)) + self._start_times = np.append(self._start_times, np.zeros_like(self._start_times)) + self._end_times = np.append(self._end_times, np.zeros_like(self._end_times)) + self._durations = np.append(self._durations, np.zeros_like(self._durations)) if self.config.likwid: self._pylikwid().markerstartregion(self.region_name) diff --git a/src/struphy/propagators/__init__.py b/src/struphy/propagators/__init__.py index 72067e021..2dbef2b10 100644 --- a/src/struphy/propagators/__init__.py +++ b/src/struphy/propagators/__init__.py @@ -1,98 +1,97 @@ -# from struphy.propagators.propagators_coupling import ( -# CurrentCoupling5DCurlb, -# CurrentCoupling5DGradB, -# CurrentCoupling6DCurrent, -# EfieldWeights, -# PressureCoupling6D, -# VlasovAmpere, -# ) -# from struphy.propagators.propagators_fields import ( -# AdiabaticPhi, -# CurrentCoupling5DDensity, -# CurrentCoupling6DDensity, -# FaradayExtended, -# Hall, -# HasegawaWakatani, -# ImplicitDiffusion, -# JxBCold, -# Magnetosonic, -# MagnetosonicCurrentCoupling5D, -# MagnetosonicUniform, -# Maxwell, -# OhmCold, -# Poisson, -# ShearAlfven, -# ShearAlfvenB1, -# ShearAlfvenCurrentCoupling5D, -# TimeDependentSource, -# TwoFluidQuasiNeutralFull, -# VariationalDensityEvolve, -# VariationalEntropyEvolve, -# VariationalMagFieldEvolve, -# VariationalMomentumAdvection, -# VariationalPBEvolve, -# VariationalQBEvolve, -# VariationalResistivity, -# VariationalViscosity, -# ) -# from struphy.propagators.propagators_markers import ( -# PushDeterministicDiffusion, -# PushEta, -# PushEtaPC, -# PushGuidingCenterBxEstar, -# PushGuidingCenterParallel, -# PushRandomDiffusion, -# PushVinEfield, -# PushVinSPHpressure, -# PushVinViscousPotential2D, -# PushVinViscousPotential3D, -# PushVxB, -# StepStaticEfield, -# ) +from struphy.propagators.propagators_coupling import ( + CurrentCoupling5DCurlb, + CurrentCoupling5DGradB, + CurrentCoupling6DCurrent, + EfieldWeights, + PressureCoupling6D, + VlasovAmpere, +) +from struphy.propagators.propagators_fields import ( + AdiabaticPhi, + CurrentCoupling5DDensity, + CurrentCoupling6DDensity, + FaradayExtended, + Hall, + HasegawaWakatani, + ImplicitDiffusion, + JxBCold, + Magnetosonic, + MagnetosonicCurrentCoupling5D, + MagnetosonicUniform, + Maxwell, + OhmCold, + Poisson, + ShearAlfven, + ShearAlfvenB1, + ShearAlfvenCurrentCoupling5D, + TimeDependentSource, + TwoFluidQuasiNeutralFull, + VariationalDensityEvolve, + VariationalEntropyEvolve, + VariationalMagFieldEvolve, + VariationalMomentumAdvection, + VariationalPBEvolve, + VariationalQBEvolve, + VariationalResistivity, + VariationalViscosity, +) +from struphy.propagators.propagators_markers import ( + PushDeterministicDiffusion, + PushEta, + PushEtaPC, + PushGuidingCenterBxEstar, + PushGuidingCenterParallel, + PushRandomDiffusion, + PushVinEfield, + PushVinSPHpressure, + PushVinViscousPotential, + PushVxB, + StepStaticEfield, +) -# __all__ = [ -# "VlasovAmpere", -# "EfieldWeights", -# "PressureCoupling6D", -# "CurrentCoupling6DCurrent", -# "CurrentCoupling5DCurlb", -# "CurrentCoupling5DGradB", -# "Maxwell", -# "OhmCold", -# "JxBCold", -# "ShearAlfven", -# "ShearAlfvenB1", -# "Hall", -# "Magnetosonic", -# "MagnetosonicUniform", -# "FaradayExtended", -# "CurrentCoupling6DDensity", -# "ShearAlfvenCurrentCoupling5D", -# "CurrentCoupling5DDensity", -# "ImplicitDiffusion", -# "Poisson", -# "VariationalMomentumAdvection", -# "VariationalDensityEvolve", -# "VariationalEntropyEvolve", -# "VariationalMagFieldEvolve", -# "VariationalPBEvolve", -# "VariationalQBEvolve", -# "VariationalViscosity", -# "VariationalResistivity", -# "TimeDependentSource", -# "AdiabaticPhi", -# "HasegawaWakatani", -# "TwoFluidQuasiNeutralFull", -# "PushEta", -# "PushVxB", -# "PushVinEfield", -# "PushEtaPC", -# "PushGuidingCenterBxEstar", -# "PushGuidingCenterParallel", -# "StepStaticEfield", -# "PushDeterministicDiffusion", -# "PushRandomDiffusion", -# "PushVinSPHpressure", -# "PushVinViscousPotential2D", -# "PushVinViscousPotential3D", -# ] +__all__ = [ + "VlasovAmpere", + "EfieldWeights", + "PressureCoupling6D", + "CurrentCoupling6DCurrent", + "CurrentCoupling5DCurlb", + "CurrentCoupling5DGradB", + "Maxwell", + "OhmCold", + "JxBCold", + "ShearAlfven", + "ShearAlfvenB1", + "Hall", + "Magnetosonic", + "MagnetosonicUniform", + "FaradayExtended", + "CurrentCoupling6DDensity", + "ShearAlfvenCurrentCoupling5D", + "MagnetosonicCurrentCoupling5D", + "CurrentCoupling5DDensity", + "ImplicitDiffusion", + "Poisson", + "VariationalMomentumAdvection", + "VariationalDensityEvolve", + "VariationalEntropyEvolve", + "VariationalMagFieldEvolve", + "VariationalPBEvolve", + "VariationalQBEvolve", + "VariationalViscosity", + "VariationalResistivity", + "TimeDependentSource", + "AdiabaticPhi", + "HasegawaWakatani", + "TwoFluidQuasiNeutralFull", + "PushEta", + "PushVxB", + "PushVinEfield", + "PushEtaPC", + "PushGuidingCenterBxEstar", + "PushGuidingCenterParallel", + "StepStaticEfield", + "PushDeterministicDiffusion", + "PushRandomDiffusion", + "PushVinSPHpressure", + "PushVinViscousPotential", +] diff --git a/src/struphy/propagators/base.py b/src/struphy/propagators/base.py index 945107bbd..7a8878eb5 100644 --- a/src/struphy/propagators/base.py +++ b/src/struphy/propagators/base.py @@ -4,7 +4,8 @@ from dataclasses import dataclass from typing import Literal -import cunumpy as xp +import numpy as np +from mpi4py import MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -71,7 +72,7 @@ def options(self) -> Options: @abstractmethod def options(self, new): assert isinstance(new, self.Options) - if True: + if MPI.COMM_WORLD.Get_rank() == 0: print(f"\nNew options for propagator '{self.__class__.__name__}':") for k, v in new.__dict__.items(): print(f" {k}: {v}") @@ -111,7 +112,7 @@ def update_feec_variables(self, **new_coeffs): assert new.space == old.space # calculate maximum of difference abs(new - old) - diffs[var] = xp.max(xp.abs(new.toarray() - old.toarray())) + diffs[var] = np.max(np.abs(new.toarray() - old.toarray())) # copy new coeffs into old new.copy(out=old) @@ -246,9 +247,9 @@ def add_init_kernel( The arguments for the kernel function. """ if comps is None: - comps = xp.array([0]) # case for scalar evaluation + comps = np.array([0]) # case for scalar evaluation else: - comps = xp.array(comps, dtype=int) + comps = np.array(comps, dtype=int) if not hasattr(self, "_init_kernels"): self._init_kernels = [] @@ -259,7 +260,7 @@ def add_init_kernel( column_nr, comps, args_init, - ), + ) ] def add_eval_kernel( @@ -297,12 +298,12 @@ def add_eval_kernel( """ if isinstance(alpha, int) or isinstance(alpha, float): alpha = [alpha] * 6 - alpha = xp.array(alpha) + alpha = np.array(alpha) if comps is None: - comps = xp.array([0]) # case for scalar evaluation + comps = np.array([0]) # case for scalar evaluation else: - comps = xp.array(comps, dtype=int) + comps = np.array(comps, dtype=int) if not hasattr(self, "_eval_kernels"): self._eval_kernels = [] @@ -314,5 +315,5 @@ def add_eval_kernel( column_nr, comps, args_eval, - ), + ) ] diff --git a/src/struphy/propagators/propagators_coupling.py b/src/struphy/propagators/propagators_coupling.py index 0b8760b6a..cd629ffe4 100644 --- a/src/struphy/propagators/propagators_coupling.py +++ b/src/struphy/propagators/propagators_coupling.py @@ -3,11 +3,10 @@ from dataclasses import dataclass from typing import Literal -import cunumpy as xp +import numpy as np from line_profiler import profile -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from psydac.linalg.block import BlockVector -from psydac.linalg.solvers import inverse from psydac.linalg.stencil import StencilVector from struphy.feec import preconditioner @@ -17,19 +16,15 @@ from struphy.kinetic_background.base import Maxwellian from struphy.kinetic_background.maxwellians import Maxwellian3D from struphy.linear_algebra.schur_solver import SchurSolver -from struphy.linear_algebra.solver import DiscreteGradientSolverParameters, SolverParameters +from struphy.linear_algebra.solver import SolverParameters from struphy.models.variables import FEECVariable, PICVariable -from struphy.ode.utils import ButcherTableau -from struphy.pic import utilities_kernels from struphy.pic.accumulation import accum_kernels, accum_kernels_gc -from struphy.pic.accumulation.filter import FilterParameters -from struphy.pic.accumulation.particles_to_grid import Accumulator, AccumulatorVector +from struphy.pic.accumulation.particles_to_grid import Accumulator from struphy.pic.particles import Particles5D, Particles6D from struphy.pic.pushing import pusher_kernels, pusher_kernels_gc from struphy.pic.pushing.pusher import Pusher from struphy.polar.basic import PolarVector from struphy.propagators.base import Propagator -from struphy.utils.pyccel import Pyccelkernel class VlasovAmpere(Propagator): @@ -146,7 +141,7 @@ def allocate(self): self._info = self.options.solver_params.info # get accumulation kernel - accum_kernel = Pyccelkernel(accum_kernels.vlasov_maxwell) + accum_kernel = accum_kernels.vlasov_maxwell # Initialize Accumulator object particles = self.variables.ions.particles @@ -201,7 +196,7 @@ def allocate(self): self._pusher = Pusher( particles, - Pyccelkernel(pusher_kernels.push_v_with_efield), + pusher_kernels.push_v_with_efield, args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, @@ -252,14 +247,14 @@ def __call__(self, dt): print("Maxdiff e1 for VlasovMaxwell:", max_de) particles = self.variables.ions.particles buffer_idx = particles.bufferindex - max_diff = xp.max( - xp.abs( - xp.sqrt( + max_diff = np.max( + np.abs( + np.sqrt( particles.markers_wo_holes[:, 3] ** 2 + particles.markers_wo_holes[:, 4] ** 2 + particles.markers_wo_holes[:, 5] ** 2, ) - - xp.sqrt( + - np.sqrt( particles.markers_wo_holes[:, buffer_idx + 3] ** 2 + particles.markers_wo_holes[:, buffer_idx + 4] ** 2 + particles.markers_wo_holes[:, buffer_idx + 5] ** 2, @@ -326,89 +321,54 @@ class EfieldWeights(Propagator): """ - class Variables: - def __init__(self): - self._e: FEECVariable = None - self._ions: PICVariable = None - - @property - def e(self) -> FEECVariable: - return self._e - - @e.setter - def e(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hcurl" - self._e = new - - @property - def ions(self) -> PICVariable: - return self._ions - - @ions.setter - def ions(self, new): - assert isinstance(new, PICVariable) - assert new.space in ("Particles6D", "DeltaFParticles6D") - self._ions = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - alpha: float = 1.0 - kappa: float = 1.0 - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - - def __post_init__(self): - # checks - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - if self.solver_params is None: - self.solver_params = SolverParameters() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options - - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new - - @profile - def allocate(self): - self._alpha = self.options.alpha - self._kappa = self.options.kappa - - backgrounds = self.variables.ions.backgrounds - # use single Maxwellian - if isinstance(backgrounds, list): - self._f0 = backgrounds[0] - else: - self._f0 = backgrounds - assert isinstance(self._f0, Maxwellian3D), "The background distribution function must be a uniform Maxwellian!" - self._vth = self._f0.maxw_params["vth1"][0] - - self._info = self.options.solver_params.info + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + if default: + dct = descend_options_dict(dct, []) + + return dct + + def __init__( + self, + e: BlockVector, + particles: Particles6D, + *, + alpha: float = 1.0, + kappa: float = 1.0, + f0: Maxwellian = None, + solver=options(default=True)["solver"], + ): + super().__init__(e, particles) + + if f0 is None: + f0 = Maxwellian3D() + assert isinstance(f0, Maxwellian3D) + + self._alpha = alpha + self._kappa = kappa + self._f0 = f0 + assert self._f0.maxw_params["vth1"] == self._f0.maxw_params["vth2"] == self._f0.maxw_params["vth3"] + self._vth = self._f0.maxw_params["vth1"] + + self._info = solver["info"] # Initialize Accumulator object - e = self.variables.e.spline.vector - particles = self.variables.ions.particles - self._accum = Accumulator( particles, "Hcurl", - Pyccelkernel(accum_kernels.linear_vlasov_ampere), + accum_kernels.linear_vlasov_ampere, self.mass_ops, self.domain.args_domain, add_vector=True, @@ -421,18 +381,18 @@ def allocate(self): self._e_sum = e.space.zeros() # marker storage - self._f0_values = xp.zeros(particles.markers.shape[0], dtype=float) - self._old_weights = xp.empty(particles.markers.shape[0], dtype=float) + self._f0_values = np.zeros(particles.markers.shape[0], dtype=float) + self._old_weights = np.empty(particles.markers.shape[0], dtype=float) # ================================ # ========= Schur Solver ========= # ================================ # Preconditioner - if self.options.precond == None: + if solver["type"][1] == None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(self.mass_ops.M1) # Define block matrix [[A B], [C I]] (without time step size dt in the diagonals) @@ -443,9 +403,11 @@ def allocate(self): self._schur_solver = SchurSolver( _A, _BC, - self.options.solver, - precond=pc, - solver_params=self.options.solver_params, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], ) # Instantiate particle pusher @@ -461,24 +423,21 @@ def allocate(self): self._pusher = Pusher( particles, - Pyccelkernel(pusher_kernels.push_weights_with_efield_lin_va), + pusher_kernels.push_weights_with_efield_lin_va, args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, ) def __call__(self, dt): - en = self.variables.e.spline.vector - particles = self.variables.ions.particles - # evaluate f0 and accumulate self._f0_values[:] = self._f0( - particles.markers[:, 0], - particles.markers[:, 1], - particles.markers[:, 2], - particles.markers[:, 3], - particles.markers[:, 4], - particles.markers[:, 5], + self.particles[0].markers[:, 0], + self.particles[0].markers[:, 1], + self.particles[0].markers[:, 2], + self.particles[0].markers[:, 3], + self.particles[0].markers[:, 4], + self.particles[0].markers[:, 5], ) self._accum(self._f0_values) @@ -494,34 +453,35 @@ def __call__(self, dt): # new e-field (no tmps created here) self._e_tmp, info = self._schur_solver( - xn=en, + xn=self.feec_vars[0], Byn=self._e_scale, dt=dt, out=self._e_tmp, ) # Store old weights - self._old_weights[~particles.holes] = particles.markers_wo_holes[:, 6] + self._old_weights[~self.particles[0].holes] = self.particles[0].markers_wo_holes[:, 6] # Compute (e^{n+1} + e^n) (no tmps created here) self._e_sum *= 0.0 - self._e_sum += en + self._e_sum += self.feec_vars[0] self._e_sum += self._e_tmp # Update weights self._pusher(dt) # write new coeffs into self.variables - max_de = self.update_feec_variables(e=self._e_tmp) + (max_de,) = self.feec_vars_update(self._e_tmp) # Print out max differences for weights and e-field if self._info: print("Status for StepEfieldWeights:", info["success"]) print("Iterations for StepEfieldWeights:", info["niter"]) print("Maxdiff e1 for StepEfieldWeights:", max_de) - max_diff = xp.max( - xp.abs( - self._old_weights[~particles.holes] - particles.markers[~particles.holes, 6], + max_diff = np.max( + np.abs( + self._old_weights[~self.particles[0].holes] + - self.particles[0].markers[~self.particles[0].holes, 6], ), ) print("Maxdiff weights for StepEfieldWeights:", max_diff) @@ -551,133 +511,116 @@ class PressureCoupling6D(Propagator): \begin{bmatrix} {\mathbb M^n}(u^{n+1} + u^n) \\ \bar W (V^{n+1} + V^{n} \end{bmatrix} \,. """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - self._energetic_ions: PICVariable = None - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hcurl", "Hdiv", "H1vec") - self._u = new - - @property - def energetic_ions(self) -> PICVariable: - return self._energetic_ions - - @energetic_ions.setter - def energetic_ions(self, new): - assert isinstance(new, PICVariable) - assert new.space == "Particles6D" - self._energetic_ions = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # propagator options - ep_scale: float = 1.0 - u_space: OptsVecSpace = "Hdiv" - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - filter_params: FilterParameters = None - use_perp_model: bool = True - - def __post_init__(self): - # checks - check_option(self.u_space, OptsVecSpace) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - assert isinstance(self.ep_scale, float) - assert isinstance(self.use_perp_model, bool) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - if self.filter_params is None: - self.filter_params = FilterParameters() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options - - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new - - @profile - def allocate(self): - if self.options.u_space == "H1vec": - self._u_form_int = 0 - else: - self._u_form_int = int(self.derham.space_to_form[self.options.u_space]) - - if self.options.u_space == "Hcurl": + @staticmethod + def options(default=False): + dct = {} + dct["use_perp_model"] = [True, False] + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["filter"] = { + "use_filter": None, + "modes": (1), + "repeat": 1, + "alpha": 0.5, + } + dct["boundary_cut"] = { + "e1": 0.0, + "e2": 0.0, + "e3": 0.0, + } + dct["turn_off"] = False + + if default: + dct = descend_options_dict(dct, []) + + return dct + + def __init__( + self, + particles: Particles5D, + u: BlockVector | PolarVector, + *, + use_perp_model: bool = options(default=True)["use_perp_model"], + u_space: str, + solver: dict = options(default=True)["solver"], + coupling_params: dict, + filter: dict = options(default=True)["filter"], + boundary_cut: dict = options(default=True)["boundary_cut"], + ): + super().__init__(particles, u) + + self._G = self.derham.grad + self._GT = self.derham.grad.transpose() + + self._info = solver["info"] + self._rank = self.derham.comm.Get_rank() + + assert u_space in {"Hcurl", "Hdiv", "H1vec"} + + if u_space == "Hcurl": id_Mn = "M1n" id_X = "X1" - elif self.options.u_space == "Hdiv": + elif u_space == "Hdiv": id_Mn = "M2n" id_X = "X2" - elif self.options.u_space == "H1vec": + elif u_space == "H1vec": id_Mn = "Mvn" id_X = "Xv" - # call operatros - id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n" - _A = getattr(self.mass_ops, id_M) - self._X = getattr(self.basis_ops, id_X) - self._XT = self._X.transpose() - grad = self.derham.grad - gradT = grad.transpose() + if u_space == "H1vec": + self._space_key_int = 0 + else: + self._space_key_int = int( + self.derham.space_to_form[u_space], + ) # Preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) - pc = pc_class(getattr(self.mass_ops, id_M)) + pc_class = getattr(preconditioner, solver["type"][1]) + pc = pc_class(getattr(self.mass_ops, id_Mn)) # Call the accumulation and Pusher class - if self.options.use_perp_model: - accum_ker = Pyccelkernel(accum_kernels.pc_lin_mhd_6d) - pusher_ker = Pyccelkernel(pusher_kernels.push_pc_GXu) + if use_perp_model: + accum_ker = accum_kernels.pc_lin_mhd_6d + pusher_ker = pusher_kernels.push_pc_GXu else: - accum_ker = Pyccelkernel(accum_kernels.pc_lin_mhd_6d_full) - pusher_ker = Pyccelkernel(pusher_kernels.push_pc_GXu_full) + accum_ker = accum_kernels.pc_lin_mhd_6d_full + pusher_ker = pusher_kernels.push_pc_GXu_full + + self._coupling_mat = coupling_params["Ah"] / coupling_params["Ab"] + self._coupling_vec = coupling_params["Ah"] / coupling_params["Ab"] + self._scale_push = 1 + + self._boundary_cut_e1 = boundary_cut["e1"] - # define Accumulator and arguments self._ACC = Accumulator( - self.variables.energetic_ions.particles, - "Hcurl", # TODO:check + particles, + "Hcurl", accum_ker, self.mass_ops, self.domain.args_domain, add_vector=True, symmetry="pressure", - filter_params=self.options.filter_params, + filter_params=filter, ) - self._tmp_g1 = grad.codomain.zeros() - self._tmp_g2 = grad.codomain.zeros() - self._tmp_g3 = grad.codomain.zeros() + self._tmp_g1 = self._G.codomain.zeros() + self._tmp_g2 = self._G.codomain.zeros() + self._tmp_g3 = self._G.codomain.zeros() # instantiate Pusher - args_pusher_kernel = ( + args_kernel = ( self.derham.args_derham, self._tmp_g1[0]._data, self._tmp_g1[1]._data, @@ -688,20 +631,38 @@ def allocate(self): self._tmp_g3[0]._data, self._tmp_g3[1]._data, self._tmp_g3[2]._data, + self._boundary_cut_e1, ) self._pusher = Pusher( - self.variables.energetic_ions.particles, + particles, pusher_ker, - args_pusher_kernel, + args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, ) - self.u_temp = self.variables.u.spline.vector.space.zeros() - self.u_temp2 = self.variables.u.spline.vector.space.zeros() + # Define operators + self._A = getattr(self.mass_ops, id_Mn) + self._X = getattr(self.basis_ops, id_X) + self._XT = self._X.transpose() + + # Instantiate schur solver with dummy BC + self._schur_solver = SchurSolver( + self._A, + self._XT @ self._X, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], + recycle=solver["recycle"], + ) + + self.u_temp = u.space.zeros() + self.u_temp2 = u.space.zeros() self._tmp = self._X.codomain.zeros() - self._BV = self.variables.u.spline.vector.space.zeros() + self._BV = u.space.zeros() self._MAT = [ [self._ACC.operators[0], self._ACC.operators[1], self._ACC.operators[2]], @@ -711,32 +672,20 @@ def allocate(self): self._GT_VEC = BlockVector(self.derham.Vh["v"]) - _BC = -1 / 4 * self._XT @ self.GT_MAT_G(self.derham, self._MAT) @ self._X - - self._schur_solver = SchurSolver( - _A, - _BC, - self.options.solver, - precond=pc, - solver_params=self.options.solver_params, - ) - def __call__(self, dt): - # current FE coeffs - un = self.variables.u.spline.vector - - # operators - grad = self.derham.grad - gradT = grad.transpose() + # current u + un = self.feec_vars[0] + un.update_ghost_regions() # acuumulate MAT and VEC - self._ACC( - self.options.ep_scale, - ) + self._ACC(self._coupling_mat, self._coupling_vec, self._boundary_cut_e1) # update GT_VEC for i in range(3): - self._GT_VEC[i] = gradT.dot(self._ACC.vectors[i]) + self._GT_VEC[i] = self._GT.dot(self._ACC.vectors[i]) + + # define BC and B dot V of the Schur block matrix [[A, B], [C, I]] + self._schur_solver.BC = -1 / 4 * self._XT @ self.GT_MAT_G(self.derham, self._MAT) @ self._X self._BV = self._XT.dot(self._GT_VEC) * (-1 / 2) @@ -749,9 +698,9 @@ def __call__(self, dt): # calculate GXu Xu = self._X.dot(_u, out=self._tmp) - GXu_1 = grad.dot(Xu[0], out=self._tmp_g1) - GXu_2 = grad.dot(Xu[1], out=self._tmp_g2) - GXu_3 = grad.dot(Xu[2], out=self._tmp_g3) + GXu_1 = self._G.dot(Xu[0], out=self._tmp_g1) + GXu_2 = self._G.dot(Xu[1], out=self._tmp_g2) + GXu_3 = self._G.dot(Xu[2], out=self._tmp_g3) GXu_1.update_ghost_regions() GXu_2.update_ghost_regions() @@ -761,16 +710,16 @@ def __call__(self, dt): self._pusher(dt) # write new coeffs into Propagator.variables - diffs = self.update_feec_variables(u=un1) + (max_du,) = self.feec_vars_update(un1) # update weights in case of control variate - if self.variables.energetic_ions.species.weights_params.control_variate: - self.variables.energetic_ions.particles.update_weights() + if self.particles[0].control_variate: + self.particles[0].update_weights() - if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: + if self._info and self._rank == 0: print("Status for StepPressurecoupling:", info["success"]) print("Iterations for StepPressurecoupling:", info["niter"]) - print("Maxdiff u1 for StepPressurecoupling:", diffs["u"]) + print("Maxdiff u1 for StepPressurecoupling:", max_du) print() class GT_MAT_G(LinOpWithTransp): @@ -789,8 +738,8 @@ class GT_MAT_G(LinOpWithTransp): def __init__(self, derham, MAT, transposed=False): self._derham = derham - self._grad = derham.grad - self._gradT = derham.grad.transpose() + self._G = derham.grad + self._GT = derham.grad.transpose() self._domain = derham.Vh["v"] self._codomain = derham.Vh["v"] @@ -844,9 +793,9 @@ def dot(self, v, out=None): for i in range(3): for j in range(3): - self._temp += self._MAT[i][j].dot(self._grad.dot(v[j])) + self._temp += self._MAT[i][j].dot(self._G.dot(v[j])) - self._vector[i] = self._gradT.dot(self._temp) + self._vector[i] = self._GT.dot(self._temp) self._temp *= 0.0 self._vector.update_ghost_regions() @@ -876,107 +825,84 @@ class CurrentCoupling6DCurrent(Propagator): :ref:`time_discret`: Crank-Nicolson (implicit mid-point). System size reduction via :class:`~struphy.linear_algebra.schur_solver.SchurSolver`. """ - class Variables: - def __init__(self): - self._ions: PICVariable = None - self._u: FEECVariable = None - - @property - def ions(self) -> PICVariable: - return self._ions - - @ions.setter - def ions(self, new): - assert isinstance(new, PICVariable) - assert new.space in ("Particles6D") - self._ions = new - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hcurl", "Hdiv", "H1vec") - self._u = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # propagator options - b_tilde: FEECVariable = None - u_space: OptsVecSpace = "Hdiv" - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - filter_params: FilterParameters = None - boundary_cut: tuple = (0.0, 0.0, 0.0) - - def __post_init__(self): - # checks - check_option(self.u_space, OptsVecSpace) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - assert self.b_tilde.space == "Hdiv" - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options - - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new - - @profile - def allocate(self): - self._space_key_int = int(self.derham.space_to_form[self.options.u_space]) - - particles = self.variables.ions.particles - u = self.variables.u.spline.vector - self._b_eq = self.projected_equil.b2 - self._b_tilde = self.options.b_tilde.spline.vector - - self._info = self.options.solver_params.info - - if self.derham.comm is None: - self._rank = 0 + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["filter"] = { + "use_filter": None, + "modes": (1), + "repeat": 1, + "alpha": 0.5, + } + dct["boundary_cut"] = { + "e1": 0.0, + "e2": 0.0, + "e3": 0.0, + } + dct["turn_off"] = False + + if default: + dct = descend_options_dict(dct, []) + + return dct + + def __init__( + self, + particles: Particles6D, + u: BlockVector, + *, + u_space: str, + b_eq: BlockVector | PolarVector, + b_tilde: BlockVector | PolarVector, + Ab: int = 1, + Ah: int = 1, + epsilon: float = 1.0, + solver: dict = options(default=True)["solver"], + filter: dict = options(default=True)["filter"], + boundary_cut: dict = options(default=True)["boundary_cut"], + ): + super().__init__(particles, u) + + if u_space == "H1vec": + self._space_key_int = 0 else: - self._rank = self.derham.comm.Get_rank() + self._space_key_int = int( + self.derham.space_to_form[u_space], + ) - Ah = self.variables.ions.species.mass_number - Ab = self.variables.u.species.mass_number - epsilon = self.variables.ions.species.equation_params.epsilon + self._b_eq = b_eq + self._b_tilde = b_tilde + + self._info = solver["info"] + self._rank = self.derham.comm.Get_rank() self._coupling_mat = Ah / Ab / epsilon**2 self._coupling_vec = Ah / Ab / epsilon self._scale_push = 1.0 / epsilon - self._boundary_cut_e1 = self.options.boundary_cut[0] + self._boundary_cut_e1 = boundary_cut["e1"] # load accumulator self._accumulator = Accumulator( particles, - self.options.u_space, - Pyccelkernel(accum_kernels.cc_lin_mhd_6d_2), + u_space, + accum_kernels.cc_lin_mhd_6d_2, self.mass_ops, self.domain.args_domain, add_vector=True, symmetry="symm", - filter_params=self.options.filter_params, + filter_params=filter, ) # if self.particles[0].control_variate: @@ -1005,17 +931,17 @@ def allocate(self): # self.particles[0].f0.n, *quad_pts, kind='0', squeeze_out=False, coordinates='logical') # # memory allocation for magnetic field at quadrature points - # self._b_quad1 = xp.zeros_like(self._nuh0_at_quad[0]) - # self._b_quad2 = xp.zeros_like(self._nuh0_at_quad[0]) - # self._b_quad3 = xp.zeros_like(self._nuh0_at_quad[0]) + # self._b_quad1 = np.zeros_like(self._nuh0_at_quad[0]) + # self._b_quad2 = np.zeros_like(self._nuh0_at_quad[0]) + # self._b_quad3 = np.zeros_like(self._nuh0_at_quad[0]) # # memory allocation for (self._b_quad x self._nuh0_at_quad) * self._coupling_vec - # self._vec1 = xp.zeros_like(self._nuh0_at_quad[0]) - # self._vec2 = xp.zeros_like(self._nuh0_at_quad[0]) - # self._vec3 = xp.zeros_like(self._nuh0_at_quad[0]) + # self._vec1 = np.zeros_like(self._nuh0_at_quad[0]) + # self._vec2 = np.zeros_like(self._nuh0_at_quad[0]) + # self._vec3 = np.zeros_like(self._nuh0_at_quad[0]) # FEM spaces and basis extraction operators for u and b - u_id = self.derham.space_to_form[self.options.u_space] + u_id = self.derham.space_to_form[u_space] self._EuT = self.derham.extraction_ops[u_id].transpose() self._EbT = self.derham.extraction_ops["2"].transpose() @@ -1029,15 +955,15 @@ def allocate(self): self._u_avg2 = self._EuT.codomain.zeros() # load particle pusher kernel - if self.options.u_space == "Hcurl": - kernel = Pyccelkernel(pusher_kernels.push_bxu_Hcurl) - elif self.options.u_space == "Hdiv": - kernel = Pyccelkernel(pusher_kernels.push_bxu_Hdiv) - elif self.options.u_space == "H1vec": - kernel = Pyccelkernel(pusher_kernels.push_bxu_H1vec) + if u_space == "Hcurl": + kernel = pusher_kernels.push_bxu_Hcurl + elif u_space == "Hdiv": + kernel = pusher_kernels.push_bxu_Hdiv + elif u_space == "H1vec": + kernel = pusher_kernels.push_bxu_H1vec else: raise ValueError( - f'{self.options.u_space =} not valid, choose from "Hcurl", "Hdiv" or "H1vec.', + f'{u_space = } not valid, choose from "Hcurl", "Hdiv" or "H1vec.', ) # instantiate Pusher @@ -1064,10 +990,10 @@ def allocate(self): _A = getattr(self.mass_ops, "M" + u_id + "n") # preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(_A) _BC = -1 / 4 * self._accumulator.operators[0] @@ -1075,15 +1001,17 @@ def allocate(self): self._schur_solver = SchurSolver( _A, _BC, - self.options.solver, - precond=pc, - solver_params=self.options.solver_params, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], + recycle=solver["recycle"], ) def __call__(self, dt): # pointer to old coefficients - particles = self.variables.ions.particles - un = self.variables.u.spline.vector + un = self.feec_vars[0] # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) self._b_eq.copy(out=self._b_full1) @@ -1153,11 +1081,11 @@ def __call__(self, dt): self._pusher(self._scale_push * dt) # write new coeffs into Propagator.variables - max_du = self.update_feec_variables(u=un1) + max_du = self.feec_vars_update(un1) # update weights in case of control variate - if particles.control_variate: - particles.update_weights() + if self.particles[0].control_variate: + self.particles[0].update_weights() if self._info and self._rank == 0: print("Status for CurrentCoupling6DCurrent:", info["success"]) @@ -1202,198 +1130,288 @@ class CurrentCoupling5DCurlb(Propagator): For the detail explanation of the notations, see `2022_DriftKineticCurrentCoupling `_. """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - self._energetic_ions: PICVariable = None - - @property - def u(self) -> FEECVariable: - return self._u + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["filter"] = { + "use_filter": None, + "modes": (1), + "repeat": 1, + "alpha": 0.5, + } + dct["boundary_cut"] = { + "e1": 0.0, + "e2": 0.0, + "e3": 0.0, + } + dct["turn_off"] = False + + if default: + dct = descend_options_dict(dct, []) + + return dct + + def __init__( + self, + particles: Particles5D, + u: BlockVector, + *, + b: BlockVector, + b_eq: BlockVector, + unit_b1: BlockVector, + absB0: StencilVector, + gradB1: BlockVector, + curl_unit_b2: BlockVector, + u_space: str, + solver: dict = options(default=True)["solver"], + filter: dict = options(default=True)["filter"], + coupling_params: dict, + epsilon: float = 1.0, + boundary_cut: dict = options(default=True)["boundary_cut"], + ): + super().__init__(particles, u) + + assert u_space in {"Hcurl", "Hdiv", "H1vec"} + + if u_space == "H1vec": + self._space_key_int = 0 + else: + self._space_key_int = int( + self.derham.space_to_form[u_space], + ) - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hcurl", "Hdiv", "H1vec") - self._u = new + self._epsilon = epsilon + self._b = b + self._b_eq = b_eq + self._unit_b1 = unit_b1 + self._absB0 = absB0 + self._gradB1 = gradB1 + self._curl_norm_b = curl_unit_b2 - @property - def energetic_ions(self) -> PICVariable: - return self._energetic_ions + self._info = solver["info"] + self._rank = self.derham.comm.Get_rank() - @energetic_ions.setter - def energetic_ions(self, new): - assert isinstance(new, PICVariable) - assert new.space == "Particles5D" - self._energetic_ions = new + self._coupling_mat = coupling_params["Ah"] / coupling_params["Ab"] + self._coupling_vec = coupling_params["Ah"] / coupling_params["Ab"] + self._scale_push = 1 - def __init__(self): - self.variables = self.Variables() + self._boundary_cut_e1 = boundary_cut["e1"] - @dataclass - class Options: - # propagator options - b_tilde: FEECVariable = None - ep_scale: float = 1.0 - u_space: OptsVecSpace = "Hdiv" - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - filter_params: FilterParameters = None + u_id = self.derham.space_to_form[u_space] + self._E0T = self.derham.extraction_ops["0"].transpose() + self._EuT = self.derham.extraction_ops[u_id].transpose() + self._E2T = self.derham.extraction_ops["2"].transpose() + self._E1T = self.derham.extraction_ops["1"].transpose() - def __post_init__(self): - # checks - check_option(self.u_space, OptsVecSpace) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - assert isinstance(self.b_tilde, FEECVariable) - assert isinstance(self.ep_scale, float) + self._unit_b1 = self._E1T.dot(self._unit_b1) + self._curl_norm_b = self._E2T.dot(self._curl_norm_b) + self._curl_norm_b.update_ghost_regions() + self._absB0 = self._E0T.dot(self._absB0) - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + # define system [[A B], [C I]] [u_new, v_new] = [[A -B], [-C I]] [u_old, v_old] (without time step size dt) + _A = getattr(self.mass_ops, "M" + u_id + "n") - if self.filter_params is None: - self.filter_params = FilterParameters() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options - - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new - - @profile - def allocate(self): - if self.options.u_space == "H1vec": - self._u_form_int = 0 - else: - self._u_form_int = int(self.derham.space_to_form[self.options.u_space]) - - # call operatros - id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n" - _A = getattr(self.mass_ops, id_M) - - # Preconditioner - if self.options.precond is None: - pc = None - else: - pc_class = getattr(preconditioner, self.options.precond) - pc = pc_class(getattr(self.mass_ops, id_M)) - - # magnetic equilibrium field - unit_b1 = self.projected_equil.unit_b1 - curl_unit_b1 = self.projected_equil.curl_unit_b1 - self._b2 = self.projected_equil.b2 - - # magnetic field - self._b_tilde = self.options.b_tilde.spline.vector - - # scaling factor - epsilon = self.variables.energetic_ions.species.equation_params.epsilon + # preconditioner + if solver["type"][1] is None: + pc = None + else: + pc_class = getattr(preconditioner, solver["type"][1]) + pc = pc_class(_A) # temporary vectors to avoid memory allocation - self._b_full = self._b2.space.zeros() - self._u_new = self.variables.u.spline.vector.space.zeros() - self._u_avg = self.variables.u.spline.vector.space.zeros() + self._b_full1 = self._b_eq.space.zeros() + self._b_full2 = self._E2T.codomain.zeros() + self._u_new = u.space.zeros() + self._u_avg1 = u.space.zeros() + self._u_avg2 = self._EuT.codomain.zeros() - # define Accumulator and arguments + # Call the accumulation and Pusher class self._ACC = Accumulator( - self.variables.energetic_ions.particles, - self.options.u_space, - Pyccelkernel(accum_kernels_gc.cc_lin_mhd_5d_curlb), + particles, + u_space, + accum_kernels_gc.cc_lin_mhd_5d_J1, self.mass_ops, self.domain.args_domain, add_vector=True, symmetry="symm", - filter_params=self.options.filter_params, + filter_params=filter, ) - self._args_accum_kernel = ( - epsilon, - self.options.ep_scale, - self._b_full[0]._data, - self._b_full[1]._data, - self._b_full[2]._data, - unit_b1[0]._data, - unit_b1[1]._data, - unit_b1[2]._data, - curl_unit_b1[0]._data, - curl_unit_b1[1]._data, - curl_unit_b1[2]._data, - self._u_form_int, - ) - - # define Pusher - if self.options.u_space == "Hcurl": - pusher_kernel = Pyccelkernel(pusher_kernels_gc.push_gc_cc_J1_Hcurl) - elif self.options.u_space == "Hdiv": - pusher_kernel = Pyccelkernel(pusher_kernels_gc.push_gc_cc_J1_Hdiv) - elif self.options.u_space == "H1vec": - pusher_kernel = Pyccelkernel(pusher_kernels_gc.push_gc_cc_J1_H1vec) + if u_space == "Hcurl": + kernel = pusher_kernels_gc.push_gc_cc_J1_Hcurl + elif u_space == "Hdiv": + kernel = pusher_kernels_gc.push_gc_cc_J1_Hdiv + elif u_space == "H1vec": + kernel = pusher_kernels_gc.push_gc_cc_J1_H1vec else: raise ValueError( - f'{self.options.u_space =} not valid, choose from "Hcurl", "Hdiv" or "H1vec.', + f'{u_space = } not valid, choose from "Hcurl", "Hdiv" or "H1vec.', ) - args_pusher_kernel = ( + # instantiate Pusher + args_kernel = ( self.derham.args_derham, - epsilon, - self._b_full[0]._data, - self._b_full[1]._data, - self._b_full[2]._data, - unit_b1[0]._data, - unit_b1[1]._data, - unit_b1[2]._data, - curl_unit_b1[0]._data, - curl_unit_b1[1]._data, - curl_unit_b1[2]._data, - self._u_avg[0]._data, - self._u_avg[1]._data, - self._u_avg[2]._data, + self._epsilon, + self._b_full2[0]._data, + self._b_full2[1]._data, + self._b_full2[2]._data, + self._unit_b1[0]._data, + self._unit_b1[1]._data, + self._unit_b1[2]._data, + self._curl_norm_b[0]._data, + self._curl_norm_b[1]._data, + self._curl_norm_b[2]._data, + self._u_avg2[0]._data, + self._u_avg2[1]._data, + self._u_avg2[2]._data, + 0.0, ) self._pusher = Pusher( - self.variables.energetic_ions.particles, - pusher_kernel, - args_pusher_kernel, + particles, + kernel, + args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, ) + # define BC and B dot V of the Schur block matrix [[A, B], [C, I]] _BC = -1 / 4 * self._ACC.operators[0] + # call SchurSolver class self._schur_solver = SchurSolver( _A, _BC, - self.options.solver, - precond=pc, - solver_params=self.options.solver_params, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], + recycle=solver["recycle"], ) def __call__(self, dt): - # current FE coeffs - un = self.variables.u.spline.vector + un = self.feec_vars[0] # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) - b_full = self._b2.copy(out=self._b_full) + b_full = self._b_eq.copy(out=self._b_full1) - b_full += self._b_tilde - b_full.update_ghost_regions() + if self._b is not None: + self._b_full1 += self._b + + # extract coefficients to tensor product space (in-place) + Eb_full = self._E2T.dot(b_full, out=self._b_full2) + + # update ghost regions because of non-local access in accumulation kernel! + Eb_full.update_ghost_regions() + + # perform accumulation (either with or without control variate) + # if self.particles[0].control_variate: + + # # evaluate magnetic field at quadrature points (in-place) + # WeightedMassOperator.eval_quad(self.derham.Vh_fem['2'], self._b_full2, + # out=[self._b_at_quad[0], self._b_at_quad[1], self._b_at_quad[2]]) + + # # evaluate B_parallel + # self._B_para_at_quad = np.sum( + # p * q for p, q in zip(self._unit_b1_at_quad, self._b_at_quad)) + # self._B_para_at_quad += self._unit_b1_dot_curl_norm_b_at_quad + + # # assemble (B x)(curl norm_b)(curl norm_b)(B x) / B_star_para² / det_df³ * (f0.u_para² + f0.vth_para²) * f0.n + # self._mat11[:, :, :] = (self._b_at_quad[1]*self._curl_norm_b_at_quad[2] - + # self._b_at_quad[2]*self._curl_norm_b_at_quad[1])**2 * \ + # self._control_const * self._coupling_mat / \ + # self._det_df_at_quad**3 / self._B_para_at_quad**2 + # self._mat12[:, :, :] = (self._b_at_quad[1]*self._curl_norm_b_at_quad[2] - + # self._b_at_quad[2]*self._curl_norm_b_at_quad[1]) * \ + # (self._b_at_quad[2]*self._curl_norm_b_at_quad[0] - + # self._b_at_quad[0]*self._curl_norm_b_at_quad[2]) * \ + # self._control_const * self._coupling_mat / \ + # self._det_df_at_quad**3 / self._B_para_at_quad**2 + # self._mat13[:, :, :] = (self._b_at_quad[1]*self._curl_norm_b_at_quad[2] - + # self._b_at_quad[2]*self._curl_norm_b_at_quad[1]) * \ + # (self._b_at_quad[0]*self._curl_norm_b_at_quad[1] - + # self._b_at_quad[1]*self._curl_norm_b_at_quad[0]) * \ + # self._control_const * self._coupling_mat / \ + # self._det_df_at_quad**3 / self._B_para_at_quad**2 + # self._mat22[:, :, :] = (self._b_at_quad[2]*self._curl_norm_b_at_quad[0] - + # self._b_at_quad[0]*self._curl_norm_b_at_quad[2])**2 * \ + # self._control_const * self._coupling_mat / \ + # self._det_df_at_quad**3 / self._B_para_at_quad**2 + # self._mat23[:, :, :] = (self._b_at_quad[2]*self._curl_norm_b_at_quad[0] - + # self._b_at_quad[0]*self._curl_norm_b_at_quad[2]) * \ + # (self._b_at_quad[0]*self._curl_norm_b_at_quad[1] - + # self._b_at_quad[1]*self._curl_norm_b_at_quad[0]) * \ + # self._control_const * self._coupling_mat / \ + # self._det_df_at_quad**3 / self._B_para_at_quad**2 + # self._mat33[:, :, :] = (self._b_at_quad[0]*self._curl_norm_b_at_quad[1] - + # self._b_at_quad[1]*self._curl_norm_b_at_quad[0])**2 * \ + # self._control_const * self._coupling_mat / \ + # self._det_df_at_quad**3 / self._B_para_at_quad**2 + + # self._mat21[:, :, :] = -self._mat12 + # self._mat31[:, :, :] = -self._mat13 + # self._mat32[:, :, :] = -self._mat23 + + # # assemble (B x)(curl norm_b) / B_star_para / det_df * (f0.u_para² + f0.vth_para²) * f0.n + # self._vec1[:, :, :] = (self._b_at_quad[1]*self._curl_norm_b_at_quad[2] - + # self._b_at_quad[2]*self._curl_norm_b_at_quad[1]) * \ + # self._control_const * self._coupling_vec / \ + # self._det_df_at_quad / self._B_para_at_quad + # self._vec2[:, :, :] = (self._b_at_quad[2]*self._curl_norm_b_at_quad[0] - + # self._b_at_quad[0]*self._curl_norm_b_at_quad[2]) * \ + # self._control_const * self._coupling_vec / \ + # self._det_df_at_quad / self._B_para_at_quad + # self._vec3[:, :, :] = (self._b_at_quad[0]*self._curl_norm_b_at_quad[1] - + # self._b_at_quad[1]*self._curl_norm_b_at_quad[0]) * \ + # self._control_const * self._coupling_vec / \ + # self._det_df_at_quad / self._B_para_at_quad + + # self._ACC.accumulate(self.particles[0], self._epsilon, + # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, + # self._space_key_int, self._coupling_mat, self._coupling_vec, 0.1, + # control_mat=[[None, self._mat12, self._mat13], + # [self._mat21, None, self._mat23], + # [self._mat31, self._mat32, None]], + # control_vec=[self._vec1, self._vec2, self._vec3]) + # else: + # self._ACC.accumulate(self.particles[0], self._epsilon, + # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, + # self._space_key_int, self._coupling_mat, self._coupling_vec, 0.1) self._ACC( - *self._args_accum_kernel, + self._epsilon, + Eb_full[0]._data, + Eb_full[1]._data, + Eb_full[2]._data, + self._unit_b1[0]._data, + self._unit_b1[1]._data, + self._unit_b1[2]._data, + self._curl_norm_b[0]._data, + self._curl_norm_b[1]._data, + self._curl_norm_b[2]._data, + self._space_key_int, + self._coupling_mat, + self._coupling_vec, + self._boundary_cut_e1, ) - # solve + # update u coefficients un1, info = self._schur_solver( un, -self._ACC.vectors[0] / 2, @@ -1402,25 +1420,27 @@ def __call__(self, dt): ) # call pusher kernel with average field (u_new + u_old)/2 and update ghost regions because of non-local access in kernel - _u = un.copy(out=self._u_avg) + _u = un.copy(out=self._u_avg1) _u += un1 _u *= 0.5 - _u.update_ghost_regions() + _Eu = self._EuT.dot(_u, out=self._u_avg2) - self._pusher(dt) + _Eu.update_ghost_regions() - # update u coefficients - diffs = self.update_feec_variables(u=un1) + self._pusher(self._scale_push * dt) + + # write new coeffs into Propagator.variables + (max_du,) = self.feec_vars_update(un1) # update_weights - if self.variables.energetic_ions.species.weights_params.control_variate: - self.variables.energetic_ions.particles.update_weights() + if self.particles[0].control_variate: + self.particles[0].update_weights() - if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: + if self._info and self._rank == 0: print("Status for CurrentCoupling5DCurlb:", info["success"]) print("Iterations for CurrentCoupling5DCurlb:", info["niter"]) - print("Maxdiff up for CurrentCoupling5DCurlb:", diffs["u"]) + print("Maxdiff up for CurrentCoupling5DCurlb:", max_du) print() @@ -1460,722 +1480,435 @@ class CurrentCoupling5DGradB(Propagator): For the detail explanation of the notations, see `2022_DriftKineticCurrentCoupling `_. """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - self._energetic_ions: PICVariable = None - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hcurl", "Hdiv", "H1vec") - self._u = new - - @property - def energetic_ions(self) -> PICVariable: - return self._energetic_ions - - @energetic_ions.setter - def energetic_ions(self, new): - assert isinstance(new, PICVariable) - assert new.space == "Particles5D" - self._energetic_ions = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsAlgo = Literal[ - "discrete_gradient", - "explicit", - ] - # propagator options - b_tilde: FEECVariable = None - ep_scale: float = 1.0 - algo: OptsAlgo = "explicit" - butcher: ButcherTableau = None - u_space: OptsVecSpace = "Hdiv" - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - filter_params: FilterParameters = None - dg_solver_params: DiscreteGradientSolverParameters = None - - def __post_init__(self): - # checks - check_option(self.algo, self.OptsAlgo) - check_option(self.u_space, OptsVecSpace) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - assert isinstance(self.b_tilde, FEECVariable) - assert isinstance(self.ep_scale, float) + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["algo"] = ["rk4", "forward_euler", "heun2", "rk2", "heun3"] + dct["filter"] = { + "use_filter": None, + "modes": (1), + "repeat": 1, + "alpha": 0.5, + } + dct["boundary_cut"] = { + "e1": 0.0, + "e2": 0.0, + "e3": 0.0, + } + dct["turn_off"] = False + + if default: + dct = descend_options_dict(dct, []) + + return dct + + def __init__( + self, + particles: Particles5D, + u: BlockVector, + *, + b: BlockVector, + b_eq: BlockVector, + unit_b1: BlockVector, + unit_b2: BlockVector, + absB0: StencilVector, + gradB1: BlockVector, + curl_unit_b2: BlockVector, + u_space: str, + solver: dict = options(default=True)["solver"], + algo: dict = options(default=True)["algo"], + filter: dict = options(default=True)["filter"], + coupling_params: dict, + epsilon: float = 1.0, + boundary_cut: dict = options(default=True)["boundary_cut"], + ): + from psydac.linalg.solvers import inverse + + from struphy.ode.utils import ButcherTableau + + super().__init__(particles, u) + + assert u_space in {"Hcurl", "Hdiv", "H1vec"} + + if u_space == "H1vec": + self._space_key_int = 0 + else: + self._space_key_int = int( + self.derham.space_to_form[u_space], + ) - # defaults - if self.algo == "explicit" and self.butcher is None: - self.butcher = ButcherTableau() + self._epsilon = epsilon + self._b = b + self._b_eq = b_eq + self._unit_b1 = unit_b1 + self._unit_b2 = unit_b2 + self._absB0 = absB0 + self._gradB1 = gradB1 + self._curl_norm_b = curl_unit_b2 - if self.algo == "discrete_gradient" and self.dg_solver_params is None: - self.dg_solver_params = DiscreteGradientSolverParameters() + self._info = solver["info"] + self._rank = self.derham.comm.Get_rank() - if self.solver_params is None: - self.solver_params = SolverParameters() + self._coupling_mat = coupling_params["Ah"] / coupling_params["Ab"] + self._coupling_vec = coupling_params["Ah"] / coupling_params["Ab"] + self._scale_push = 1 - if self.filter_params is None: - self.filter_params = FilterParameters() + self._boundary_cut_e1 = boundary_cut["e1"] - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + u_id = self.derham.space_to_form[u_space] + self._E0T = self.derham.extraction_ops["0"].transpose() + self._EuT = self.derham.extraction_ops[u_id].transpose() + self._E1T = self.derham.extraction_ops["1"].transpose() + self._E2T = self.derham.extraction_ops["2"].transpose() - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + self._PB = getattr(self.basis_ops, "PB") - @profile - def allocate(self): - if self.options.u_space == "H1vec": - self._u_form_int = 0 - else: - self._u_form_int = int(self.derham.space_to_form[self.options.u_space]) + self._unit_b1 = self._E1T.dot(self._unit_b1) + self._unit_b2 = self._E2T.dot(self._unit_b2) + self._curl_norm_b = self._E2T.dot(self._curl_norm_b) + self._absB0 = self._E0T.dot(self._absB0) - # call operatros - id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n" - self._A = getattr(self.mass_ops, id_M) - self._PB = getattr(self.basis_ops, "PB") + _A = getattr(self.mass_ops, "M" + u_id + "n") - # Preconditioner - if self.options.precond is None: + # preconditioner + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) - pc = pc_class(getattr(self.mass_ops, id_M)) + pc_class = getattr(preconditioner, solver["type"][1]) + pc = pc_class(_A) - # linear solver - self._A_inv = inverse( - self._A, - self.options.solver, + self._solver = inverse( + _A, + solver["type"][0], pc=pc, - tol=self.options.solver_params.tol, - maxiter=self.options.solver_params.maxiter, - verbose=self.options.solver_params.verbose, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], + recycle=solver["recycle"], ) - # magnetic equilibrium field - unit_b1 = self.projected_equil.unit_b1 - curl_unit_b1 = self.projected_equil.curl_unit_b1 - self._b2 = self.projected_equil.b2 - gradB1 = self.projected_equil.gradB1 - absB0 = self.projected_equil.absB0 - - # magnetic field - self._b_tilde = self.options.b_tilde.spline.vector - - # scaling factor - epsilon = self.variables.energetic_ions.species.equation_params.epsilon - - if self.options.algo == "explicit": - # temporary vectors to avoid memory allocation - self._b_full = self._b2.space.zeros() - self._u_new = self.variables.u.spline.vector.space.zeros() - self._u_temp = self.variables.u.spline.vector.space.zeros() - self._ku = self.variables.u.spline.vector.space.zeros() - self._PB_b = self._PB.codomain.zeros() - self._grad_PB_b = self.derham.grad.codomain.zeros() - - # define Accumulator and arguments - self._ACC = Accumulator( - self.variables.energetic_ions.particles, - self.options.u_space, - Pyccelkernel(accum_kernels_gc.cc_lin_mhd_5d_gradB), - self.mass_ops, - self.domain.args_domain, - add_vector=True, - symmetry="symm", - filter_params=self.options.filter_params, - ) - self._args_accum_kernel = ( - epsilon, - self.options.ep_scale, - self._b_full[0]._data, - self._b_full[1]._data, - self._b_full[2]._data, - unit_b1[0]._data, - unit_b1[1]._data, - unit_b1[2]._data, - curl_unit_b1[0]._data, - curl_unit_b1[1]._data, - curl_unit_b1[2]._data, - self._grad_PB_b[0]._data, - self._grad_PB_b[1]._data, - self._grad_PB_b[2]._data, - self._u_form_int, - ) - - # define Pusher - if self.options.u_space == "Hdiv": - self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_stage_Hdiv - elif self.options.u_space == "H1vec": - self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_stage_H1vec - else: - raise ValueError( - f'{self.options.u_space =} not valid, choose from "Hdiv" or "H1vec.', - ) - - # temp fix due to refactoring of ButcherTableau: - butcher = self.options.butcher - import numpy as np - - butcher._a = xp.diag(butcher.a, k=-1) - butcher._a = xp.array(list(butcher.a) + [0.0]) - - self._args_pusher_kernel = ( - self.domain.args_domain, - self.derham.args_derham, - epsilon, - self._b_full[0]._data, - self._b_full[1]._data, - self._b_full[2]._data, - unit_b1[0]._data, - unit_b1[1]._data, - unit_b1[2]._data, - curl_unit_b1[0]._data, - curl_unit_b1[1]._data, - curl_unit_b1[2]._data, - self._u_temp[0]._data, - self._u_temp[1]._data, - self._u_temp[2]._data, - self.options.butcher.a, - self.options.butcher.b, - self.options.butcher.c, - ) - - else: - # temporary vectors to avoid memory allocation - self._b_full = self._b2.space.zeros() - self._PB_b = self._PB.codomain.zeros() - self._grad_PB_b = self.derham.grad.codomain.zeros() - self._u_old = self.variables.u.spline.vector.space.zeros() - self._u_new = self.variables.u.spline.vector.space.zeros() - self._u_diff = self.variables.u.spline.vector.space.zeros() - self._u_mid = self.variables.u.spline.vector.space.zeros() - self._M2n_dot_u = self.variables.u.spline.vector.space.zeros() - self._ku = self.variables.u.spline.vector.space.zeros() - self._u_temp = self.variables.u.spline.vector.space.zeros() - - # Call the accumulation and Pusher class - accum_kernel_init = accum_kernels_gc.cc_lin_mhd_5d_gradB_dg_init - accum_kernel = accum_kernels_gc.cc_lin_mhd_5d_gradB_dg - self._accum_kernel_en_fB_mid = utilities_kernels.eval_gradB_ediff - - self._args_accum_kernel = ( - epsilon, - self.options.ep_scale, - self._b_tilde[0]._data, - self._b_tilde[1]._data, - self._b_tilde[2]._data, - self._b2[0]._data, - self._b2[1]._data, - self._b2[2]._data, - unit_b1[0]._data, - unit_b1[1]._data, - unit_b1[2]._data, - curl_unit_b1[0]._data, - curl_unit_b1[1]._data, - curl_unit_b1[2]._data, - self._grad_PB_b[0]._data, - self._grad_PB_b[1]._data, - self._grad_PB_b[2]._data, - gradB1[0]._data, - gradB1[1]._data, - gradB1[2]._data, - self._u_form_int, - ) - - self._args_accum_kernel_en_fB_mid = ( - self.domain.args_domain, - self.derham.args_derham, - gradB1[0]._data, - gradB1[1]._data, - gradB1[2]._data, - self._grad_PB_b[0]._data, - self._grad_PB_b[1]._data, - self._grad_PB_b[2]._data, - ) - - self._ACC_init = AccumulatorVector( - self.variables.energetic_ions.particles, - self.options.u_space, - accum_kernel_init, - self.mass_ops, - self.domain.args_domain, - filter_params=self.options.filter_params, - ) + # Call the accumulation and Pusher class + self._ACC = Accumulator( + particles, + u_space, + accum_kernels_gc.cc_lin_mhd_5d_J2, + self.mass_ops, + self.domain.args_domain, + add_vector=True, + symmetry="symm", + filter_params=filter, + ) - self._ACC = AccumulatorVector( - self.variables.energetic_ions.particles, - self.options.u_space, - accum_kernel, - self.mass_ops, - self.domain.args_domain, - filter_params=self.options.filter_params, - ) + # if self.particles[0].control_variate: - self._args_pusher_kernel_init = ( - self.domain.args_domain, - self.derham.args_derham, - epsilon, - self._b_full[0]._data, - self._b_full[1]._data, - self._b_full[2]._data, - unit_b1[0]._data, - unit_b1[1]._data, - unit_b1[2]._data, - curl_unit_b1[0]._data, - curl_unit_b1[1]._data, - curl_unit_b1[2]._data, - self.variables.u.spline.vector[0]._data, - self.variables.u.spline.vector[1]._data, - self.variables.u.spline.vector[2]._data, - ) + # # control variate method is only valid with Maxwellian distributions + # assert isinstance(self.particles[0].f0, Maxwellian) + # assert params['u_space'] == 'Hdiv' - self._args_pusher_kernel = ( - self.domain.args_domain, - self.derham.args_derham, - epsilon, - self._b_full[0]._data, - self._b_full[1]._data, - self._b_full[2]._data, - unit_b1[0]._data, - unit_b1[1]._data, - unit_b1[2]._data, - curl_unit_b1[0]._data, - curl_unit_b1[1]._data, - curl_unit_b1[2]._data, - self._u_mid[0]._data, - self._u_mid[1]._data, - self._u_mid[2]._data, - self._u_temp[0]._data, - self._u_temp[1]._data, - self._u_temp[2]._data, - ) + # self._ACC.init_control_variate(self.mass_ops) - self._pusher_kernel_init = pusher_kernels_gc.push_gc_cc_J2_dg_init_Hdiv - self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_dg_Hdiv + # # evaluate and save n0 at quadrature points + # quad_pts = [quad_grid[nquad].points.flatten() + # for quad_grid, nquad in zip(self.derham.get_quad_grids(self.derham.Vh_fem['0']), self.derham.nquads)] - def __call__(self, dt): - # current FE coeffs - un = self.variables.u.spline.vector + # self._n0_at_quad = self.domain.push( + # self.particles[0].f0.n, *quad_pts, kind='0', squeeze_out=False) - # particle markers and idx - particles = self.variables.energetic_ions.particles - holes = particles.holes - args_markers = particles.args_markers - markers = args_markers.markers - first_init_idx = args_markers.first_init_idx - first_free_idx = args_markers.first_free_idx + # # evaluate unit_b1 (1form) dot epsilon * u0_parallel * curl_norm_b/|det(DF)| at quadrature points + # quad_pts_array = self.domain.prepare_eval_pts(*quad_pts)[:3] - # clear buffer - markers[:, first_init_idx:-2] = 0.0 + # u0_parallel_at_quad = self.particles[0].f0.u( + # *quad_pts_array)[0] - # save old marker positions - markers[:, first_init_idx : first_init_idx + 3] = markers[:, :3] + # vth_perp = self.particles[0].f0.vth(*quad_pts_array)[1] - # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) - b_full = self._b2.copy(out=self._b_full) + # absB0_at_quad = WeightedMassOperator.eval_quad( + # self.derham.Vh_fem['0'], self._absB0) - b_full += self._b_tilde - b_full.update_ghost_regions() + # self._det_df_at_quad = self.domain.jacobian_det( + # *quad_pts, squeeze_out=False) - if self.options.algo == "explicit": - PB_b = self._PB.dot(b_full, out=self._PB_b) - grad_PB_b = self.derham.grad.dot(PB_b, out=self._grad_PB_b) - grad_PB_b.update_ghost_regions() + # self._unit_b1_at_quad = WeightedMassOperator.eval_quad( + # self.derham.Vh_fem['1'], self._unit_b1) - # save old u - u_new = un.copy(out=self._u_new) + # curl_norm_b_at_quad = WeightedMassOperator.eval_quad( + # self.derham.Vh_fem['2'], self._curl_norm_b) - for stage in range(self.options.butcher.n_stages): - # accumulate - self._ACC( - *self._args_accum_kernel, - ) + # self._unit_b1_dot_curl_norm_b_at_quad = np.sum( + # p * q for p, q in zip(self._unit_b1_at_quad, curl_norm_b_at_quad)) - # push particles - self._pusher_kernel( - dt, - stage, - args_markers, - *self._args_pusher_kernel, - ) + # self._unit_b1_dot_curl_norm_b_at_quad /= self._det_df_at_quad + # self._unit_b1_dot_curl_norm_b_at_quad *= self._epsilon + # self._unit_b1_dot_curl_norm_b_at_quad *= u0_parallel_at_quad - if particles.mpi_comm is not None: - particles.mpi_sort_markers() - else: - particles.apply_kinetic_bc() + # # precalculate constant 2 * f0.vth_perp² / B0 * f0.n for control MAT and VEC + # self._control_const = vth_perp**2 / absB0_at_quad * self._n0_at_quad - # solve linear system for updating u coefficients - ku = self._A_inv.dot(self._ACC.vectors[0], out=self._ku) - info = self._A_inv._info + # # assemble the matrix (G_inv)(unit_b1 x)(G_inv) + # G_inv_at_quad = self.domain.metric_inv( + # *quad_pts, squeeze_out=False) - # calculate u^{n+1}_k - u_temp = un.copy(out=self._u_temp) - u_temp += ku * dt * self.options.butcher.a[stage] + # self._G_inv_bx_G_inv_at_quad = [[np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad)], + # [np.zeros_like(self._n0_at_quad), np.zeros_like( + # self._n0_at_quad), np.zeros_like(self._n0_at_quad)], + # [np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad)]] - u_temp.update_ghost_regions() + # for j in range(3): + # temp = (-self._unit_b1_at_quad[2]*G_inv_at_quad[1, j] + self._unit_b1_at_quad[1]*G_inv_at_quad[2, j], + # self._unit_b1_at_quad[2]*G_inv_at_quad[0, j] - + # self._unit_b1_at_quad[0]*G_inv_at_quad[2, j], + # -self._unit_b1_at_quad[1]*G_inv_at_quad[0, j] + self._unit_b1_at_quad[0]*G_inv_at_quad[1, j]) - # calculate u^{n+1} - u_new += ku * dt * self.options.butcher.b[stage] + # for i in range(3): + # self._G_inv_bx_G_inv_at_quad[i][j] = np.sum( + # p * q for p, q in zip(G_inv_at_quad[i], temp[:])) - if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: - print("Stage: ", stage) - print("Status for CurrentCoupling5DGradB:", info["success"]) - print("Iterations for CurrentCoupling5DGradB:", info["niter"]) - print() + # # memory allocation of magnetic field at quadrature points + # self._b_at_quad = [np.zeros_like(self._n0_at_quad), + # np.zeros_like(self._n0_at_quad), + # np.zeros_like(self._n0_at_quad)] - # update u coefficients - diffs = self.update_feec_variables(u=u_new) + # # memory allocation of parallel magnetic field at quadrature points + # self._B_para_at_quad = np.zeros_like(self._n0_at_quad) - # clear the buffer - markers[:, first_init_idx:-2] = 0.0 + # # memory allocation of gradient of parallel magnetic field at quadrature points + # self._grad_PBb_at_quad = (np.zeros_like(self._n0_at_quad), + # np.zeros_like(self._n0_at_quad), + # np.zeros_like(self._n0_at_quad)) + # # memory allocation for temporary matrix + # self._temp = [[np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad)], + # [np.zeros_like(self._n0_at_quad), np.zeros_like( + # self._n0_at_quad), np.zeros_like(self._n0_at_quad)], + # [np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad)]] - # update_weights - if self.variables.energetic_ions.species.weights_params.control_variate: - particles.update_weights() + # # memory allocation for control VEC + # self._vec1 = np.zeros_like(self._n0_at_quad) + # self._vec2 = np.zeros_like(self._n0_at_quad) + # self._vec3 = np.zeros_like(self._n0_at_quad) - if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: - print("Maxdiff up for CurrentCoupling5DGradB:", diffs["u"]) - print() + # choose algorithm + self._butcher = ButcherTableau(algo) + # temp fix due to refactoring of ButcherTableau: + self._butcher._a = np.diag(self._butcher.a, k=-1) + self._butcher._a = np.array(list(self._butcher.a) + [0.0]) + # instantiate Pusher + if u_space == "Hdiv": + kernel = pusher_kernels_gc.push_gc_cc_J2_stage_Hdiv + elif u_space == "H1vec": + kernel = pusher_kernels_gc.push_gc_cc_J2_stage_H1vec else: - # total number of markers - n_mks_tot = particles.Np - - # relaxation factor - alpha = self.options.dg_solver_params.relaxation_factor - - # eval parallel tilde b and its gradient - PB_b = self._PB.dot(self._b_tilde, out=self._PB_b) - PB_b.update_ghost_regions() - grad_PB_b = self.derham.grad.dot(PB_b, out=self._grad_PB_b) - grad_PB_b.update_ghost_regions() - - # save old u - u_old = un.copy(out=self._u_old) - u_new = un.copy(out=self._u_new) - - # save en_U_old - self._A.dot(un, out=self._M2n_dot_u) - en_U_old = un.inner(self._M2n_dot_u) / 2.0 - - # save en_fB_old - particles.save_magnetic_energy(PB_b) - en_fB_old = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale - en_fB_old /= n_mks_tot - - buffer_array = xp.array([en_fB_old]) - - if particles.mpi_comm is not None: - particles.mpi_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - if particles.clone_config is not None: - particles.clone_config.inter_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - en_fB_old = buffer_array[0] - en_tot_old = en_U_old + en_fB_old - - # initial guess - self._ACC_init(*self._args_accum_kernel) - - ku = self._A_inv.dot(self._ACC_init.vectors[0], out=self._ku) - u_new += ku * dt - - u_new.update_ghost_regions() - - # save en_U_new - self._A.dot(u_new, out=self._M2n_dot_u) - en_U_new = u_new.inner(self._M2n_dot_u) / 2.0 - - # push eta - self._pusher_kernel_init( - dt, - args_markers, - *self._args_pusher_kernel_init, + raise ValueError( + f'{u_space = } not valid, choose from "Hdiv" or "H1vec.', ) - if particles.mpi_comm is not None: - particles.mpi_sort_markers(apply_bc=False) - - # save en_fB_new - particles.save_magnetic_energy(PB_b) - en_fB_new = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale - en_fB_new /= n_mks_tot - - buffer_array = xp.array([en_fB_new]) - - if particles.mpi_comm is not None: - particles.mpi_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - if particles.clone_config is not None: - particles.clone_config.inter_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - en_fB_new = buffer_array[0] - - # fixed-point iterations - iter_num = 0 + args_kernel = (self.derham.args_derham,) - while True: - iter_num += 1 - - if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0: - print("# of iteration: ", iter_num) - - # calculate discrete gradient - # save u^{n+1, k} - u_old = u_new.copy(out=self._u_old) - - u_diff = u_old.copy(out=self._u_diff) - u_diff -= un - u_diff.update_ghost_regions() - - u_mid = u_old.copy(out=self._u_mid) - u_mid += un - u_mid /= 2.0 - u_mid.update_ghost_regions() - - # save H^{n+1, k} - markers[~holes, first_free_idx : first_free_idx + 3] = markers[~holes, 0:3] - - # calculate denominator ||z^{n+1, k} - z^n||^2 - sum_u_diff_loc = xp.sum((u_diff.toarray() ** 2)) - - sum_H_diff_loc = xp.sum( - (markers[~holes, :3] - markers[~holes, first_init_idx : first_init_idx + 3]) ** 2, - ) - - buffer_array = xp.array([sum_u_diff_loc]) - - if particles.mpi_comm is not None: - particles.mpi_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - denominator = buffer_array[0] - - buffer_array = xp.array([sum_H_diff_loc]) + self._pusher = Pusher( + particles, + kernel, + args_kernel, + self.domain.args_domain, + alpha_in_kernel=1.0, + ) - if particles.mpi_comm is not None: - particles.mpi_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) + # temporary vectors to avoid memory allocation + self._b_full1 = self._b_eq.space.zeros() + self._b_full2 = self._E2T.codomain.zeros() + self._u_new = u.space.zeros() + self._Eu_new = self._EuT.codomain.zeros() + self._u_temp1 = u.space.zeros() + self._u_temp2 = u.space.zeros() + self._Eu_temp = self._EuT.codomain.zeros() + self._tmp1 = self._E0T.codomain.zeros() + self._tmp2 = self._gradB1.space.zeros() + self._tmp3 = self._E1T.codomain.zeros() - if particles.clone_config is not None: - particles.clone_config.inter_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) + def __call__(self, dt): + un = self.feec_vars[0] - denominator += buffer_array[0] + # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) + b_full = self._b_eq.copy(out=self._b_full1) - # sorting markers at mid-point - if particles.mpi_comm is not None: - particles.mpi_sort_markers(apply_bc=False, alpha=0.5) + if self._b is not None: + self._b_full1 += self._b - self._accum_kernel_en_fB_mid( - args_markers, - *self._args_accum_kernel_en_fB_mid, - first_free_idx + 3, - ) - en_fB_mid = xp.sum(markers[~holes, first_free_idx + 3].dot(markers[~holes, 5])) * self.options.ep_scale + PBb = self._PB.dot(self._b, out=self._tmp1) + grad_PBb = self.derham.grad.dot(PBb, out=self._tmp2) + grad_PBb += self._gradB1 - en_fB_mid /= n_mks_tot + Eb_full = self._E2T.dot(b_full, out=self._b_full2) + Eb_full.update_ghost_regions() - buffer_array = xp.array([en_fB_mid]) + Egrad_PBb = self._E1T.dot(grad_PBb, out=self._tmp3) + Egrad_PBb.update_ghost_regions() - if particles.mpi_comm is not None: - particles.mpi_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) + # perform accumulation (either with or without control variate) + # if self.particles[0].control_variate: - if particles.clone_config is not None: - particles.clone_config.inter_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) + # # evaluate magnetic field at quadrature points (in-place) + # WeightedMassOperator.eval_quad(self.derham.Vh_fem['2'], self._b_full2, + # out=[self._b_at_quad[0], self._b_at_quad[1], self._b_at_quad[2]]) + + # # evaluate B_parallel + # self._B_para_at_quad = np.sum( + # p * q for p, q in zip(self._unit_b1_at_quad, self._b_at_quad)) + # self._B_para_at_quad += self._unit_b1_dot_curl_norm_b_at_quad + + # # evaluate grad B_parallel + # WeightedMassOperator.eval_quad(self.derham.Vh_fem['1'], self._tmp3, + # out=[self._grad_PBb_at_quad[0], self._grad_PBb_at_quad[1], self._grad_PBb_at_quad[2]]) + + # # assemble temp = (B x)(G_inv)(unit_b1 x)(G_inv) + # for i in range(3): + # self._temp[0][i] = -self._b_at_quad[2]*self._G_inv_bx_G_inv_at_quad[1][i] + \ + # self._b_at_quad[1]*self._G_inv_bx_G_inv_at_quad[2][i] + # self._temp[1][i] = +self._b_at_quad[2]*self._G_inv_bx_G_inv_at_quad[0][i] - \ + # self._b_at_quad[0]*self._G_inv_bx_G_inv_at_quad[2][i] + # self._temp[2][i] = -self._b_at_quad[1]*self._G_inv_bx_G_inv_at_quad[0][i] + \ + # self._b_at_quad[0]*self._G_inv_bx_G_inv_at_quad[1][i] + + # # assemble (temp)(grad B_parallel) / B_star_para * 2 * f0.vth_perp² / B0 * f0.n + # self._vec1[:, :, :] = np.sum(p * q for p, q in zip(self._temp[0][:], self._grad_PBb_at_quad)) * \ + # self._control_const * self._coupling_vec / self._B_para_at_quad + # self._vec2[:, :, :] = np.sum(p * q for p, q in zip(self._temp[1][:], self._grad_PBb_at_quad)) * \ + # self._control_const * self._coupling_vec / self._B_para_at_quad + # self._vec3[:, :, :] = np.sum(p * q for p, q in zip(self._temp[2][:], self._grad_PBb_at_quad)) * \ + # self._control_const * self._coupling_vec / self._B_para_at_quad + + # save old u + _u_new = un.copy(out=self._u_new) + _u_temp = un.copy(out=self._u_temp1) - en_fB_mid = buffer_array[0] + # save old marker positions + self.particles[0].markers[ + ~self.particles[0].holes, + 11:14, + ] = self.particles[0].markers[~self.particles[0].holes, 0:3] + + for stage in range(self._butcher.n_stages): + # accumulate RHS + # if self.particles[0].control_variate: + # self._ACC.accumulate(self.particles[0], self._epsilon, + # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._unit_b2[0]._data, self._unit_b2[1]._data, self._unit_b2[2]._data, + # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, + # Egrad_PBb[0]._data, Egrad_PBb[1]._data, Egrad_PBb[2]._data, + # self._space_key_int, self._coupling_mat, self._coupling_vec, 0., + # control_vec=[self._vec1, self._vec2, self._vec3]) + # else: + # self._ACC.accumulate(self.particles[0], self._epsilon, + # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._unit_b2[0]._data, self._unit_b2[1]._data, self._unit_b2[2]._data, + # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, + # Egrad_PBb[0]._data, Egrad_PBb[1]._data, Egrad_PBb[2]._data, + # self._space_key_int, self._coupling_mat, self._coupling_vec, 0.) + + self._ACC( + self._epsilon, + Eb_full[0]._data, + Eb_full[1]._data, + Eb_full[2]._data, + self._unit_b1[0]._data, + self._unit_b1[1]._data, + self._unit_b1[2]._data, + self._unit_b2[0]._data, + self._unit_b2[1]._data, + self._unit_b2[2]._data, + self._curl_norm_b[0]._data, + self._curl_norm_b[1]._data, + self._curl_norm_b[2]._data, + Egrad_PBb[0]._data, + Egrad_PBb[1]._data, + Egrad_PBb[2]._data, + self._space_key_int, + self._coupling_mat, + self._coupling_vec, + self._boundary_cut_e1, + ) - if denominator == 0.0: - const = 0.0 - else: - const = (en_fB_new - en_fB_old - en_fB_mid) / denominator + # push particles + Eu = self._EuT.dot(_u_temp, out=self._Eu_temp) + Eu.update_ghost_regions() - # update u^{n+1, k} - self._ACC(*self._args_accum_kernel, const) + self._pusher.kernel( + dt, + stage, + self.particles[0].args_markers, + self.domain.args_domain, + self.derham.args_derham, + self._epsilon, + Eb_full[0]._data, + Eb_full[1]._data, + Eb_full[2]._data, + self._unit_b1[0]._data, + self._unit_b1[1]._data, + self._unit_b1[2]._data, + self._unit_b2[0]._data, + self._unit_b2[1]._data, + self._unit_b2[2]._data, + self._curl_norm_b[0]._data, + self._curl_norm_b[1]._data, + self._curl_norm_b[2]._data, + Eu[0]._data, + Eu[1]._data, + Eu[2]._data, + self._butcher.a, + self._butcher.b, + self._butcher.c, + self._boundary_cut_e1, + ) - ku = self._A_inv.dot(self._ACC.vectors[0], out=self._ku) + self.particles[0].mpi_sort_markers() - u_new = un.copy(out=self._u_new) - u_new += ku * dt - u_new *= alpha - u_new += u_old * (1.0 - alpha) + # solve linear system for updated u coefficients + _ku = self._solver.dot(self._ACC.vectors[0], out=self._u_temp2) - u_new.update_ghost_regions() + # calculate u^{n+1}_k + _u_temp = un.copy(out=self._u_temp1) + _u_temp += _ku * dt * self._butcher.a[stage] - # update en_U_new - self._A.dot(u_new, out=self._M2n_dot_u) - en_U_new = u_new.inner(self._M2n_dot_u) / 2.0 + # calculate u^{n+1} + _u_new += _ku * dt * self._butcher.b[stage] - # update H^{n+1, k} - self._pusher_kernel( - dt, - args_markers, - *self._args_pusher_kernel, - const, - alpha, + if self._info and self._rank == 0: + print("Stage:", stage) + print( + "Status for CurrentCoupling5DGradB:", + self._solver._info["success"], ) - - sum_H_diff_loc = xp.sum( - xp.abs(markers[~holes, 0:3] - markers[~holes, first_free_idx : first_free_idx + 3]), + print( + "Iterations for CurrentCoupling5DGradB:", + self._solver._info["niter"], ) - if particles.mpi_comm is not None: - particles.mpi_sort_markers(apply_bc=False) - - # update en_fB_new - particles.save_magnetic_energy(PB_b) - en_fB_new = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale - en_fB_new /= n_mks_tot - - buffer_array = xp.array([en_fB_new]) - - if particles.mpi_comm is not None: - particles.mpi_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - if particles.clone_config is not None: - particles.clone_config.inter_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - en_fB_new = buffer_array[0] - - # calculate total energy difference - e_diff = xp.abs(en_U_new + en_fB_new - en_tot_old) - - # calculate ||z^{n+1, k} - z^{n+1, k-1|| - sum_u_diff_loc = xp.sum(xp.abs(u_new.toarray() - u_old.toarray())) - - buffer_array = xp.array([sum_u_diff_loc]) - - if particles.mpi_comm is not None: - particles.mpi_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - diff = buffer_array[0] - - buffer_array = xp.array([sum_H_diff_loc]) - - if particles.mpi_comm is not None: - particles.mpi_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - if particles.clone_config is not None: - particles.clone_config.inter_comm.Allreduce( - MPI.IN_PLACE, - buffer_array, - op=MPI.SUM, - ) - - diff += buffer_array[0] - - # check convergence - if diff < self.options.dg_solver_params.tol: - if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0: - print("converged diff: ", diff) - print("converged e_diff: ", e_diff) - - if particles.mpi_comm is not None: - particles.mpi_comm.Barrier() - break - - else: - if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0: - print("not converged diff: ", diff) - print("not converged e_diff: ", e_diff) - - if iter_num == self.options.dg_solver_params.maxiter: - if self.options.dg_solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: - print( - f"{iter_num =}, maxiter={self.options.dg_solver_params.maxiter} reached! diff: {diff}, e_diff: {e_diff}", - ) - if particles.mpi_comm is not None: - particles.mpi_comm.Barrier() - break - - # sorting markers - if particles.mpi_comm is not None: - particles.mpi_sort_markers() - else: - particles.apply_kinetic_bc() - - # update u coefficients - diffs = self.update_feec_variables(u=u_new) - # clear the buffer - markers[:, first_init_idx:-2] = 0.0 + if stage == self._butcher.n_stages - 1: + self.particles[0].markers[ + ~self.particles[0].holes, + 11:-1, + ] = 0.0 + + # write new coeffs into Propagator.variables + (max_du,) = self.feec_vars_update(_u_new) - # update_weights - if self.variables.energetic_ions.species.weights_params.control_variate: - particles.update_weights() + # update_weights + if self.particles[0].control_variate: + self.particles[0].update_weights() - if self.options.dg_solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: - print("Maxdiff up for CurrentCoupling5DGradB:", diffs["u"]) - print() + if self._info and self._rank == 0: + print("Maxdiff up for CurrentCoupling5DGradB:", max_du) + print() diff --git a/src/struphy/propagators/propagators_fields.py b/src/struphy/propagators/propagators_fields.py index 462c58f26..a41170a34 100644 --- a/src/struphy/propagators/propagators_fields.py +++ b/src/struphy/propagators/propagators_fields.py @@ -1,17 +1,18 @@ "Only FEEC variables are updated." import copy +from collections.abc import Callable from copy import deepcopy from dataclasses import dataclass -from typing import Callable, Literal, get_args +from typing import Literal, get_args -import cunumpy as xp +import numpy as np import scipy as sc from line_profiler import profile from matplotlib import pyplot as plt +from mpi4py import MPI from numpy import zeros from psydac.api.essential_bc import apply_essential_bc_stencil -from psydac.ddm.mpi import mpi as MPI from psydac.linalg.basic import ComposedLinearOperator, IdentityOperator, ZeroOperator from psydac.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace from psydac.linalg.solvers import inverse @@ -28,47 +29,34 @@ ) from struphy.feec.linear_operators import BoundaryOperator from struphy.feec.mass import WeightedMassOperator, WeightedMassOperators -from struphy.feec.preconditioner import MassMatrixDiagonalPreconditioner, MassMatrixPreconditioner +from struphy.feec.preconditioner import MassMatrixPreconditioner from struphy.feec.projectors import L2Projector from struphy.feec.psydac_derham import Derham, SplineFunction from struphy.feec.variational_utilities import ( BracketOperator, - Hdiv0_transport_operator, + H1vecMassMatrix_density, InternalEnergyEvaluator, KineticEnergyEvaluator, - Pressure_transport_operator, ) from struphy.fields_background.equils import set_defaults from struphy.geometry.utilities import TransformedPformComponent from struphy.initial import perturbations -from struphy.io.options import ( - OptsDirectSolver, - OptsGenSolver, - OptsMassPrecond, - OptsNonlinearSolver, - OptsSaddlePointSolver, - OptsSymmSolver, - OptsVecSpace, - check_option, -) +from struphy.io.options import OptsGenSolver, OptsMassPrecond, OptsSymmSolver, OptsVecSpace, check_option from struphy.io.setup import descend_options_dict from struphy.kinetic_background.base import Maxwellian from struphy.kinetic_background.maxwellians import GyroMaxwellian2D, Maxwellian3D from struphy.linear_algebra.saddle_point import SaddlePointSolver -from struphy.linear_algebra.schur_solver import SchurSolver, SchurSolverFull -from struphy.linear_algebra.solver import NonlinearSolverParameters, SolverParameters -from struphy.models.species import Species +from struphy.linear_algebra.schur_solver import SchurSolver +from struphy.linear_algebra.solver import SolverParameters from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.ode.solvers import ODEsolverFEEC from struphy.ode.utils import ButcherTableau, OptsButcher from struphy.pic.accumulation import accum_kernels, accum_kernels_gc -from struphy.pic.accumulation.filter import FilterParameters from struphy.pic.accumulation.particles_to_grid import Accumulator, AccumulatorVector from struphy.pic.base import Particles from struphy.pic.particles import Particles5D, Particles6D from struphy.polar.basic import PolarVector from struphy.propagators.base import Propagator -from struphy.utils.pyccel import Pyccelkernel class Maxwell(Propagator): @@ -284,71 +272,39 @@ class OhmCold(Propagator): \end{bmatrix} \,. """ - class Variables: - def __init__(self): - self._j: FEECVariable = None - self._e: FEECVariable = None - - @property - def j(self) -> FEECVariable: - return self._j - - @j.setter - def j(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hcurl" - self._j = new - - @property - def e(self) -> FEECVariable: - return self._e - - @e.setter - def e(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hcurl" - self._e = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # propagator options - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - - def __post_init__(self): - # checks - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + if default: + dct = descend_options_dict(dct, []) - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + return dct - @profile - def allocate(self): - self._info = self.options.solver_params.info + def __init__( + self, + j: BlockVector, + e: BlockVector, + *, + alpha: float = 1.0, + epsilon: float = 1.0, + solver: dict = options(default=True)["solver"], + ): + super().__init__(e, j) - self._alpha = self.variables.j.species.equation_params.alpha - self._epsilon = self.variables.j.species.equation_params.epsilon + self._info = solver["info"] + self._alpha = alpha + self._epsilon = epsilon # Define block matrix [[A B], [C I]] (without time step size dt in the diagonals) _A = self.mass_ops.M1ninv @@ -356,10 +312,10 @@ def allocate(self): self._B = -1 / 2 * 1 / self._epsilon * self.mass_ops.M1 # no dt # Preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(self.mass_ops.M1ninv) # Instantiate Schur solver (constant in this case) @@ -368,14 +324,13 @@ def allocate(self): self._schur_solver = SchurSolver( _A, _BC, - self.options.solver, - precond=pc, - solver_params=self.options.solver_params, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], ) - j = self.variables.j.spline.vector - e = self.variables.e.spline.vector - self._tmp_j1 = j.space.zeros() self._tmp_j2 = j.space.zeros() self._tmp_e1 = e.space.zeros() @@ -383,8 +338,8 @@ def allocate(self): def __call__(self, dt): # current variables - jn = self.variables.j.spline.vector - en = self.variables.e.spline.vector + en = self.feec_vars[0] + jn = self.feec_vars[1] # in-place solution (no tmps created here) Ben = self._B.dot(en, out=self._tmp_e1) @@ -398,13 +353,13 @@ def __call__(self, dt): en1 += en # write new coeffs into Propagator.variables - diffs = self.update_feec_variables(e=en1, j=jn1) + max_de, max_dj = self.feec_vars_update(en1, jn1) if self._info: print("Status for OhmCold:", info["success"]) print("Iterations for OhmCold:", info["niter"]) - print("Maxdiff e1 for OhmCold:", diffs["e"]) - print("Maxdiff j1 for OhmCold:", diffs["j"]) + print("Maxdiff e1 for OhmCold:", max_de) + print("Maxdiff j1 for OhmCold:", max_dj) print() @@ -424,90 +379,65 @@ class JxBCold(Propagator): \mathbb M_{1/n_0} \left( \mathbf j^{n+1} - \mathbf j^n \right) = \frac{\Delta t}{2} \frac{1}{\varepsilon} \mathbb M_{B_0/n_0} \left( \mathbf j^{n+1} - \mathbf j^n \right)\,. """ - class Variables: - def __init__(self): - self._j: FEECVariable = None - - @property - def j(self) -> FEECVariable: - return self._j - - @j.setter - def j(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hcurl" - self._j = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # propagator options - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - - def __post_init__(self): - # checks - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + if default: + dct = descend_options_dict(dct, []) - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + return dct - @profile - def allocate(self): - self._info = self.options.solver_params.info + def __init__( + self, + j: BlockVector, + *, + epsilon: float = 1.0, + solver: dict = options(default=True)["solver"], + ): + super().__init__(j) - epsilon = self.variables.j.species.equation_params.epsilon + self._info = solver["info"] # mass matrix in system (M - dt/2 * A)*j^(n + 1) = (M + dt/2 * A)*j^n self._M = self.mass_ops.M1ninv self._A = -1 / epsilon * self.mass_ops.M1Bninv # no dt # Preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(self.mass_ops.M1ninv) # Instantiate linear solver self._solver = inverse( self._M, - self.options.solver, + solver["type"][0], pc=pc, - x0=self.variables.j.spline.vector, - tol=self.options.solver_params.tol, - maxiter=self.options.solver_params.maxiter, - verbose=self.options.solver_params.verbose, + x0=self.feec_vars[0], + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], ) # allocate dummy vectors to avoid temporary array allocations self._rhs_j = self._M.codomain.zeros() - self._j_new = self.variables.j.spline.vector.space.zeros() + self._j_new = j.space.zeros() - @profile def __call__(self, dt): # current variables - jn = self.variables.j.spline.vector + jn = self.feec_vars[0] # define system (M - dt/2 * A)*b^(n + 1) = (M + dt/2 * A)*b^n lhs = self._M - dt / 2.0 * self._A @@ -522,7 +452,7 @@ def __call__(self, dt): info = self._solver._info # write new coeffs into Propagator.variables - max_dj = self.update_feec_variables(j=jn1) + max_dj = self.feec_vars_update(jn1)[0] if self._info: print("Status for FluidCold:", info["success"]) @@ -758,91 +688,62 @@ class ShearAlfvenB1(Propagator): the MHD equilibirum density. """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - self._b: FEECVariable = None - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hdiv") - self._u = new - - @property - def b(self) -> FEECVariable: - return self._b - - @b.setter - def b(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hcurl" - self._b = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # propagator options - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - solver_M1: OptsSymmSolver = "pcg" - precond_M1: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params_M1: SolverParameters = None - - def __post_init__(self): - # checks - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - check_option(self.solver_M1, OptsSymmSolver) - check_option(self.precond_M1, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - if self.solver_params_M1 is None: - self.solver_params_M1 = SolverParameters() + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["solver_M1"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + if default: + dct = descend_options_dict(dct, []) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + u: BlockVector, + b: BlockVector, + *, + solver: dict = options(default=True)["solver"], + solver_M1: dict = options(default=True)["solver_M1"], + ): + super().__init__(u, b) - @profile - def allocate(self): - self._info = self.options.solver_params.info + self._info = solver["info"] # define inverse of M1 - if self.options.precond_M1 is None: + if solver_M1["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond_M1) + pc_class = getattr(preconditioner, solver_M1["type"][1]) pc = pc_class(self.mass_ops.M1) M1_inv = inverse( self.mass_ops.M1, - self.options.solver_M1, + solver_M1["type"][0], pc=pc, - tol=self.options.solver_params_M1.tol, - maxiter=self.options.solver_params_M1.maxiter, - verbose=self.options.solver_params_M1.verbose, + tol=solver_M1["tol"], + maxiter=solver_M1["maxiter"], + verbose=solver_M1["verbose"], ) # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) @@ -852,10 +753,10 @@ def allocate(self): self._C = 1 / 2 * M1_inv @ self.derham.curl.T @ self.mass_ops.M2B # Preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(getattr(self.mass_ops, "M2n")) # instantiate Schur solver (constant in this case) @@ -864,26 +765,24 @@ def allocate(self): self._schur_solver = SchurSolver( _A, _BC, - self.options.solver, - precond=pc, - solver_params=self.options.solver_params, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], ) # allocate dummy vectors to avoid temporary array allocations - u = self.variables.u.spline.vector - b = self.variables.b.spline.vector - self._u_tmp1 = u.space.zeros() self._u_tmp2 = u.space.zeros() self._b_tmp1 = b.space.zeros() self._byn = self._B.codomain.zeros() - @profile def __call__(self, dt): # current variables - un = self.variables.u.spline.vector - bn = self.variables.b.spline.vector + un = self.feec_vars[0] + bn = self.feec_vars[1] # solve for new u coeffs byn = self._B.dot(bn, out=self._byn) @@ -898,13 +797,13 @@ def __call__(self, dt): bn1 += bn # write new coeffs into self.feec_vars - max_diffs = self.update_feec_variables(u=un1, b=bn1) + max_du, max_db = self.feec_vars_update(un1, bn1) if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for ShearAlfvenB1:", info["success"]) print("Iterations for ShearAlfvenB1:", info["niter"]) - print("Maxdiff up for ShearAlfvenB1:", max_diffs["u"]) - print("Maxdiff b2 for ShearAlfvenB1:", max_diffs["b"]) + print("Maxdiff up for ShearAlfvenB1:", max_du) + print("Maxdiff b2 for ShearAlfvenB1:", max_db) print() @@ -928,66 +827,38 @@ class Hall(Propagator): The solution of the above system is based on the Pre-conditioned Biconjugate Gradient Stabilized algortihm (PBiConjugateGradientStab). """ - class Variables: - def __init__(self): - self._b: FEECVariable = None + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pbicgstab", "MassMatrixPreconditioner"), + ("bicgstab", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + if default: + dct = descend_options_dict(dct, []) - @property - def b(self) -> FEECVariable: - return self._b - - @b.setter - def b(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hcurl" - self._b = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # propagator options - solver: OptsGenSolver = "pbicgstab" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - epsilon_from: Species = None - - def __post_init__(self): - # checks - check_option(self.solver, OptsGenSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options - - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + return dct - @profile - def allocate(self): - if self.options.epsilon_from is None: - epsilon = 1.0 - else: - epsilon = self.options.epsilon_from.equation_params.epsilon + def __init__( + self, + b: BlockVector, + *, + epsilon: float = 1.0, + solver: dict = options(default=True)["solver"], + ): + super().__init__(b) - self._info = self.options.solver_params.info - self._tol = self.options.solver_params.tol - self._maxiter = self.options.solver_params.maxiter - self._verbose = self.options.solver_params.verbose + self._info = solver["info"] + self._tol = solver["tol"] + self._maxiter = solver["maxiter"] + self._verbose = solver["verbose"] # mass matrix in system (M - dt/2 * A)*b^(n + 1) = (M + dt/2 * A)*b^n id_M = "M1" @@ -997,18 +868,18 @@ def allocate(self): self._A = 1.0 / epsilon * self.derham.curl.T @ self._M2Bn @ self.derham.curl # Preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(getattr(self.mass_ops, id_M)) # Instantiate linear solver self._solver = inverse( self._M, - self.options.solver, + solver["type"][0], pc=pc, - x0=self.variables.b.spline.vector, + x0=self.feec_vars[0], tol=self._tol, maxiter=self._maxiter, verbose=self._verbose, @@ -1016,11 +887,11 @@ def allocate(self): # allocate dummy vectors to avoid temporary array allocations self._rhs_b = self._M.codomain.zeros() - self._b_new = self.variables.b.spline.vector.space.zeros() + self._b_new = b.space.zeros() def __call__(self, dt): # current variables - bn = self.variables.b.spline.vector + bn = self.feec_vars[0] # define system (M - dt/2 * A)*b^(n + 1) = (M + dt/2 * A)*b^n lhs = self._M - dt / 2.0 * self._A @@ -1034,12 +905,12 @@ def __call__(self, dt): info = self._solver._info # write new coeffs into self.feec_vars - max_db = self.update_feec_variables(b=bn1) + max_db = self.feec_vars_update(bn1) if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for Hall:", info["success"]) print("Iterations for Hall:", info["niter"]) - print("Maxdiff b1 for Hall:", max_db["b"]) + print("Maxdiff b1 for Hall:", max_db) print() @@ -1305,78 +1176,36 @@ class MagnetosonicUniform(Propagator): Solver- and/or other parameters for this splitting step. """ - class Variables: - def __init__(self): - self._n: FEECVariable = None - self._u: FEECVariable = None - self._p: FEECVariable = None - - @property - def n(self) -> FEECVariable: - return self._n - - @n.setter - def n(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._n = new - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hcurl", "Hdiv", "H1vec") - self._u = new - - @property - def p(self) -> FEECVariable: - return self._p - - @p.setter - def p(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._p = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - solver: OptsGenSolver = "pbicgstab" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - - def __post_init__(self): - # checks - check_option(self.solver, OptsGenSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pbicgstab", "MassMatrixPreconditioner"), + ("bicgstab", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + if default: + dct = descend_options_dict(dct, []) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + n: StencilVector, + u: BlockVector, + p: StencilVector, + *, + solver: dict = options(default=True)["solver"], + ): + super().__init__(n, u, p) - @profile - def allocate(self): - self._info = self.options.solver_params.info + self._info = solver["info"] self._bc = self.derham.dirichlet_bc # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) @@ -1392,10 +1221,10 @@ def allocate(self): self._QD = getattr(self.basis_ops, id_Q) @ self.derham.div # preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(getattr(self.mass_ops, id_Mn)) # instantiate Schur solver (constant in this case) @@ -1404,16 +1233,14 @@ def allocate(self): self._schur_solver = SchurSolver( _A, _BC, - self.options.solver, - precond=pc, - solver_params=self.options.solver_params, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], ) # allocate dummy vectors to avoid temporary array allocations - n = self.variables.n.spline.vector - u = self.variables.u.spline.vector - p = self.variables.p.spline.vector - self._u_tmp1 = u.space.zeros() self._u_tmp2 = u.space.zeros() self._p_tmp1 = p.space.zeros() @@ -1421,12 +1248,11 @@ def allocate(self): self._byn1 = self._B.codomain.zeros() - @profile def __call__(self, dt): # current variables - nn = self.variables.n.spline.vector - un = self.variables.u.spline.vector - pn = self.variables.p.spline.vector + nn = self.feec_vars[0] + un = self.feec_vars[1] + pn = self.feec_vars[2] # solve for new u coeffs byn1 = self._B.dot(pn, out=self._byn1) @@ -1445,14 +1271,18 @@ def __call__(self, dt): nn1 += nn # write new coeffs into self.feec_vars - diffs = self.update_feec_variables(n=nn1, u=un1, p=pn1) + max_dn, max_du, max_dp = self.feec_vars_update( + nn1, + un1, + pn1, + ) if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for Magnetosonic:", info["success"]) print("Iterations for Magnetosonic:", info["niter"]) - print("Maxdiff n3 for Magnetosonic:", diffs["n"]) - print("Maxdiff up for Magnetosonic:", diffs["u"]) - print("Maxdiff p3 for Magnetosonic:", diffs["p"]) + print("Maxdiff n3 for Magnetosonic:", max_dn) + print("Maxdiff up for Magnetosonic:", max_du) + print("Maxdiff p3 for Magnetosonic:", max_dp) print() @@ -1540,13 +1370,13 @@ def __init__(self, a, **params): ] # Initialize Accumulator object for getting density from particles - self._pts_x = 1.0 / (2.0 * self.derham.Nel[0]) * xp.polynomial.legendre.leggauss( + self._pts_x = 1.0 / (2.0 * self.derham.Nel[0]) * np.polynomial.legendre.leggauss( self._nqs[0], )[0] + 1.0 / (2.0 * self.derham.Nel[0]) - self._pts_y = 1.0 / (2.0 * self.derham.Nel[1]) * xp.polynomial.legendre.leggauss( + self._pts_y = 1.0 / (2.0 * self.derham.Nel[1]) * np.polynomial.legendre.leggauss( self._nqs[1], )[0] + 1.0 / (2.0 * self.derham.Nel[1]) - self._pts_z = 1.0 / (2.0 * self.derham.Nel[2]) * xp.polynomial.legendre.leggauss( + self._pts_z = 1.0 / (2.0 * self.derham.Nel[2]) * np.polynomial.legendre.leggauss( self._nqs[2], )[0] + 1.0 / (2.0 * self.derham.Nel[2]) @@ -1586,15 +1416,15 @@ def __call__(self, dt): self._accum_density.accumulate( self._particles, - xp.array(self.derham.Nel), - xp.array(self._nqs), - xp.array( + np.array(self.derham.Nel), + np.array(self._nqs), + np.array( self._pts_x, ), - xp.array(self._pts_y), - xp.array(self._pts_z), - xp.array(self._p_shape), - xp.array(self._p_size), + np.array(self._pts_y), + np.array(self._pts_z), + np.array(self._p_shape), + np.array(self._p_size), ) self._accum_potential.accumulate(self._particles) @@ -1687,70 +1517,65 @@ class CurrentCoupling6DDensity(Propagator): :ref:`time_discret`: Crank-Nicolson (implicit mid-point). """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hcurl", "Hdiv", "H1vec") - self._u = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # propagator options - energetic_ions: PICVariable = None - b_tilde: FEECVariable = None - u_space: OptsVecSpace = "Hdiv" - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - filter_params: FilterParameters = None - boundary_cut: tuple = (0.0, 0.0, 0.0) - - def __post_init__(self): - # checks - check_option(self.u_space, OptsVecSpace) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - assert self.energetic_ions.space == "Particles6D" - assert self.b_tilde.space == "Hdiv" - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pbicgstab", "MassMatrixPreconditioner"), + ("bicgstab", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["filter"] = { + "use_filter": None, + "modes": (1), + "repeat": 1, + "alpha": 0.5, + } + dct["boundary_cut"] = { + "e1": 0.0, + "e2": 0.0, + "e3": 0.0, + } + dct["turn_off"] = False + if default: + dct = descend_options_dict(dct, []) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + u: BlockVector, + *, + particles: Particles6D, + u_space: str, + b_eq: BlockVector | PolarVector, + b_tilde: BlockVector | PolarVector, + Ab: int = 1, + Ah: int = 1, + epsilon: float = 1.0, + solver: dict = options(default=True)["solver"], + filter: dict = options(default=True)["filter"], + boundary_cut: dict = options(default=True)["boundary_cut"], + ): + super().__init__(u) - @profile - def allocate(self): - self._space_key_int = int(self.derham.space_to_form[self.options.u_space]) + # assert parameters and expose some quantities to self + if u_space == "H1vec": + self._space_key_int = 0 + else: + self._space_key_int = int( + self.derham.space_to_form[u_space], + ) - particles = self.options.energetic_ions.particles - u = self.variables.u.spline.vector - self._b_eq = self.projected_equil.b2 - self._b_tilde = self.options.b_tilde.spline.vector + self._particles = particles + self._b_eq = b_eq + self._b_tilde = b_tilde # if self._particles.control_variate: @@ -1766,70 +1591,65 @@ def allocate(self): # self._particles.f0.n, *quad_pts, kind='3', squeeze_out=False) # # memory allocation of magnetic field at quadrature points - # self._b_quad1 = xp.zeros_like(self._nh0_at_quad) - # self._b_quad2 = xp.zeros_like(self._nh0_at_quad) - # self._b_quad3 = xp.zeros_like(self._nh0_at_quad) + # self._b_quad1 = np.zeros_like(self._nh0_at_quad) + # self._b_quad2 = np.zeros_like(self._nh0_at_quad) + # self._b_quad3 = np.zeros_like(self._nh0_at_quad) # # memory allocation for self._b_quad x self._nh0_at_quad * self._coupling_const - # self._mat12 = xp.zeros_like(self._nh0_at_quad) - # self._mat13 = xp.zeros_like(self._nh0_at_quad) - # self._mat23 = xp.zeros_like(self._nh0_at_quad) + # self._mat12 = np.zeros_like(self._nh0_at_quad) + # self._mat13 = np.zeros_like(self._nh0_at_quad) + # self._mat23 = np.zeros_like(self._nh0_at_quad) - # self._mat21 = xp.zeros_like(self._nh0_at_quad) - # self._mat31 = xp.zeros_like(self._nh0_at_quad) - # self._mat32 = xp.zeros_like(self._nh0_at_quad) + # self._mat21 = np.zeros_like(self._nh0_at_quad) + # self._mat31 = np.zeros_like(self._nh0_at_quad) + # self._mat32 = np.zeros_like(self._nh0_at_quad) - self._type = self.options.solver - self._tol = self.options.solver_params.tol - self._maxiter = self.options.solver_params.maxiter - self._info = self.options.solver_params.info - self._verbose = self.options.solver_params.verbose - self._recycle = self.options.solver_params.recycle - - Ah = self.options.energetic_ions.species.mass_number - Ab = self.variables.u.species.mass_number - epsilon = self.options.energetic_ions.species.equation_params.epsilon + self._type = solver["type"][0] + self._tol = solver["tol"] + self._maxiter = solver["maxiter"] + self._info = solver["info"] + self._verbose = solver["verbose"] self._coupling_const = Ah / Ab / epsilon - self._boundary_cut_e1 = self.options.boundary_cut[0] + self._boundary_cut_e1 = boundary_cut["e1"] # load accumulator self._accumulator = Accumulator( particles, - self.options.u_space, - Pyccelkernel(accum_kernels.cc_lin_mhd_6d_1), + u_space, + accum_kernels.cc_lin_mhd_6d_1, self.mass_ops, self.domain.args_domain, add_vector=False, symmetry="asym", - filter_params=self.options.filter_params, + filter_params=filter, ) # transposed extraction operator PolarVector --> BlockVector (identity map in case of no polar splines) self._E2T = self.derham.extraction_ops["2"].transpose() # mass matrix in system (M - dt/2 * A)*u^(n + 1) = (M + dt/2 * A)*u^n - u_id = self.derham.space_to_form[self.options.u_space] + u_id = self.derham.space_to_form[u_space] self._M = getattr(self.mass_ops, "M" + u_id + "n") # preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(self._M) # linear solver self._solver = inverse( self._M, - self.options.solver, + solver["type"][0], pc=pc, - x0=self.variables.u.spline.vector, + x0=self.feec_vars[0], tol=self._tol, maxiter=self._maxiter, verbose=self._verbose, - recycle=self._recycle, + recycle=solver["recycle"], ) # temporary vectors to avoid memory allocation @@ -1841,7 +1661,7 @@ def allocate(self): def __call__(self, dt): # pointer to old coefficients - un = self.variables.u.spline.vector + un = self.feec_vars[0] # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) self._b_eq.copy(out=self._b_full1) @@ -1904,7 +1724,7 @@ def __call__(self, dt): info = self._solver._info # write new coeffs into Propagator.variables - max_du = self.update_feec_variables(u=un1) + max_du = self.feec_vars_update(un1) if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for CurrentCoupling6DDensity:", info["success"]) @@ -1921,9 +1741,9 @@ class ShearAlfvenCurrentCoupling5D(Propagator): \left\{ \begin{aligned} - \int \rho_0 &\frac{\partial \tilde{\mathbf U}}{\partial t} \cdot \mathbf V \, \textnormal{d} \mathbf{x} = \int \left(\tilde{\mathbf B} - \frac{A_\textnormal{h}}{A_b} \iint f^\text{vol} \mu \mathbf{b}_0\textnormal{d} \mu \textnormal{d} v_\parallel \right) \cdot \nabla \times (\tilde{\mathbf B} \times \mathbf V) \, \textnormal{d} \mathbf{x} \quad \forall \, \mathbf V \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}\,, \,, + \int \rho_0 &\frac{\partial \tilde{\mathbf U}}{\partial t} \cdot \mathbf V \, \textnormal{d} \mathbf{x} = \int \left(\tilde{\mathbf B} - \frac{A_\textnormal{h}}{A_b} \iint f^\text{vol} \mu \mathbf{b}_0\textnormal{d} \mu \textnormal{d} v_\parallel \right) \cdot \nabla \times (\mathbf B_0 \times \mathbf V) \, \textnormal{d} \mathbf{x} \quad \forall \, \mathbf V \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}\,, \,, \\ - &\frac{\partial \tilde{\mathbf B}}{\partial t} = - \nabla \times (\tilde{\mathbf B} \times \tilde{\mathbf U}) \,. + &\frac{\partial \tilde{\mathbf B}}{\partial t} = - \nabla \times (\mathbf B_0 \times \tilde{\mathbf U}) \,. \end{aligned} \right. @@ -1936,242 +1756,499 @@ class ShearAlfvenCurrentCoupling5D(Propagator): \end{bmatrix} = \frac{\Delta t}{2} \,. \begin{bmatrix} - 0 & (\mathbb M^{2,n})^{-1} \mathcal {T^2}^\top \mathbb C^\top \\ - \mathbb C \mathcal {T^2} (\mathbb M^{2,n})^{-1} & 0 + 0 & (\mathbb M^{\alpha,n})^{-1} \mathcal {T^\alpha}^\top \mathbb C^\top \\ - \mathbb C \mathcal {T^\alpha} (\mathbb M^{\alpha,n})^{-1} & 0 \end{bmatrix} \begin{bmatrix} - {\mathbb M^{2,n}}(\mathbf u^{n+1} + \mathbf u^n) \\ \mathbb M_2(\mathbf b^{n+1} + \mathbf b^n) + \sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k) \right) + {\mathbb M^{\alpha,n}}(\mathbf u^{n+1} + \mathbf u^n) \\ \mathbb M_2(\mathbf b^{n+1} + \mathbf b^n) + \sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k) \right) \end{bmatrix} \,, where - :math:`\mathcal{T}^2 = \hat \Pi \left[\frac{\tilde{\mathbf B}^2}{\sqrt{g} \times \vec \Lambda^2\right]` and - :math:`\mathbb M^{2,n}` is a :class:`~struphy.feec.mass.WeightedMassOperators` being weighted with :math:`\rho_\text{eq}`, the MHD equilibirum density. + :math:`\mathcal{T}^\alpha` is a :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators` and + :math:`\mathbb M^{\alpha,n}` is a :class:`~struphy.feec.mass.WeightedMassOperators` being weighted with :math:`\rho_\text{eq}`, the MHD equilibirum density. + :math:`\alpha \in \{1, 2, v\}` denotes the :math:`\alpha`-form space where the operators correspond to. + Moreover, :math:`\sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k)\right)` is accumulated by the kernel :class:`~struphy.pic.accumulation.accum_kernels_gc.cc_lin_mhd_5d_M`. """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - self._b: FEECVariable = None + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["filter"] = { + "use_filter": None, + "modes": (1), + "repeat": 1, + "alpha": 0.5, + } + dct["boundary_cut"] = { + "e1": 0.0, + "e2": 0.0, + "e3": 0.0, + } + dct["turn_off"] = False - @property - def u(self) -> FEECVariable: - return self._u + if default: + dct = descend_options_dict(dct, []) - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hcurl", "Hdiv", "H1vec") - self._u = new + return dct - @property - def b(self) -> FEECVariable: - return self._b + def __init__( + self, + u: BlockVector, + b: BlockVector, + *, + particles: Particles5D, + absB0: StencilVector, + unit_b1: BlockVector, + u_space: str, + solver: dict = options(default=True)["solver"], + filter: dict = options(default=True)["filter"], + coupling_params: dict, + accumulated_magnetization: BlockVector, + boundary_cut: dict = options(default=True)["boundary_cut"], + ): + super().__init__(u, b) - @b.setter - def b(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hdiv" - self._b = new + self._particles = particles + self._unit_b1 = unit_b1 + self._absB0 = absB0 - def __init__(self): - self.variables = self.Variables() + self._info = solver["info"] - @dataclass - class Options: - # specific literals - OptsAlgo = Literal["implicit", "explicit"] - # propagator options - energetic_ions: PICVariable = None - ep_scale: float = 1.0 - u_space: OptsVecSpace = "Hdiv" - algo: OptsAlgo = "implicit" - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixDiagonalPreconditioner" - solver_params: SolverParameters = None - filter_params: FilterParameters = None - butcher: ButcherTableau = None - nonlinear: bool = True + self._scale_vec = coupling_params["Ah"] / coupling_params["Ab"] - def __post_init__(self): - # checks - check_option(self.u_space, OptsVecSpace) - check_option(self.algo, self.OptsAlgo) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - assert isinstance(self.energetic_ions, PICVariable) - assert self.energetic_ions.space == "Particles5D" - assert isinstance(self.ep_scale, float) - assert isinstance(self.nonlinear, bool) + self._E1T = self.derham.extraction_ops["1"].transpose() + self._unit_b1 = self._E1T.dot(self._unit_b1) - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + self._accumulated_magnetization = accumulated_magnetization - if self.filter_params is None: - self.filter_params = FilterParameters() + self._boundary_cut_e1 = boundary_cut["e1"] - if self.algo == "explicit" and self.butcher is None: - self.butcher = ButcherTableau() + self._ACC = Accumulator( + particles, + u_space, + accum_kernels_gc.cc_lin_mhd_5d_M, + self.mass_ops, + self.domain.args_domain, + add_vector=True, + symmetry="symm", + filter_params=filter, + ) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + # if self._particles.control_variate: - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + # # control variate method is only valid with Maxwellian distributions with "zero perp mean velocity". + # assert isinstance(self._particles.f0, Maxwellian) - @profile - def allocate(self): - self._u_form = self.derham.space_to_form[self.options.u_space] + # self._ACC.init_control_variate(self.mass_ops) + + # # evaluate and save f0.n at quadrature points + # quad_pts = [quad_grid[nquad].points.flatten() + # for quad_grid, nquad in zip(self.derham.get_quad_grids(self.derham.Vh_fem['0']), self.derham.nquads)] + + # n0_at_quad = self.domain.push( + # self._particles.f0.n, *quad_pts, kind='0', squeeze_out=False) + + # # evaluate M0 = unit_b1 (1form) / absB0 (0form) * 2 * vth_perp² at quadrature points + # quad_pts_array = self.domain.prepare_eval_pts(*quad_pts)[:3] + + # vth_perp = self.particles.f0.vth(*quad_pts_array)[1] - # call operatros - id_M = "M" + self._u_form + "n" - id_T = "T" + self._u_form + # absB0_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['0'], self._absB0) + + # unit_b1_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['1'], self._unit_b1) + + # self._M0_at_quad = unit_b1_at_quad / absB0_at_quad * vth_perp**2 * n0_at_quad * self._scale_vec + + # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) + id_M = "M" + self.derham.space_to_form[u_space] + "n" + id_T = "T" + self.derham.space_to_form[u_space] _A = getattr(self.mass_ops, id_M) _T = getattr(self.basis_ops, id_T) - M2 = self.mass_ops.M2 - curl = self.derham.curl - PB = getattr(self.basis_ops, "PB") - # define Accumulator and arguments - self._ACC = AccumulatorVector( - self.options.energetic_ions.particles, - "H1", - Pyccelkernel(accum_kernels_gc.gc_mag_density_0form), - self.mass_ops, - self.domain.args_domain, - filter_params=self.options.filter_params, - ) + self._B = -1 / 2 * _T.T @ self.derham.curl.T @ self.mass_ops.M2 + self._C = 1 / 2 * self.derham.curl @ _T + self._B2 = -1 / 2 * _T.T @ self.derham.curl.T # Preconditioner - if self.options.precond is None: + if solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, solver["type"][1]) pc = pc_class(getattr(self.mass_ops, id_M)) - if self.options.nonlinear: - # initialize operator TB - self._initialize_projection_operator_TB() + # Instantiate Schur solver (constant in this case) + _BC = self._B @ self._C - _T = _T + self._TB - _TT = _T.T + self._TBT + self._schur_solver = SchurSolver( + _A, + _BC, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], + recycle=solver["recycle"], + ) + + # allocate dummy vectors to avoid temporary array allocations + self._u_tmp1 = u.space.zeros() + self._u_tmp2 = u.space.zeros() + self._b_tmp1 = b.space.zeros() + self._byn = self._B.codomain.zeros() + self._tmp_acc = self._B2.codomain.zeros() + + def __call__(self, dt): + # current variables + un = self.feec_vars[0] + bn = self.feec_vars[1] + + # perform accumulation (either with or without control variate) + # if self._particles.control_variate: + + # self._ACC.accumulate(self._particles, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._scale_vec, 0., + # control_vec=[self._M0_at_quad[0], self._M0_at_quad[1], self._M0_at_quad[2]]) + # else: + # self._ACC.accumulate(self._particles, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._scale_vec, 0.) + + self._ACC( + self._unit_b1[0]._data, + self._unit_b1[1]._data, + self._unit_b1[2]._data, + self._scale_vec, + self._boundary_cut_e1, + ) + + self._ACC.vectors[0].copy(out=self._accumulated_magnetization) + + # solve for new u coeffs (no tmps created here) + byn = self._B.dot(bn, out=self._byn) + b2acc = self._B2.dot(self._ACC.vectors[0], out=self._tmp_acc) + byn += b2acc + + # b2acc.copy(out=self._accumulated_magnetization) + + un1, info = self._schur_solver(un, byn, dt, out=self._u_tmp1) + + # new b coeffs (no tmps created here) + _u = un.copy(out=self._u_tmp2) + _u += un1 + bn1 = self._C.dot(_u, out=self._b_tmp1) + bn1 *= -dt + bn1 += bn + + # write new coeffs into self.feec_vars + max_du, max_db = self.feec_vars_update(un1, bn1) + + if self._info and MPI.COMM_WORLD.Get_rank() == 0: + print("Status for ShearAlfven:", info["success"]) + print("Iterations for ShearAlfven:", info["niter"]) + print("Maxdiff up for ShearAlfven:", max_du) + print("Maxdiff b2 for ShearAlfven:", max_db) + print() + + +class MagnetosonicCurrentCoupling5D(Propagator): + r""" + :ref:`FEEC ` discretization of the following equations: + find :math:`\tilde \rho \in L^2, \tilde{\mathbf U} \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}, \tilde p \in L^2` such that + + .. math:: + + \left\{ + \begin{aligned} + &\frac{\partial \tilde{\rho}}{\partial t} = - \nabla \cdot (\rho_0 \tilde{\mathbf U}) \,, + \\ + \int \rho_0 &\frac{\partial \tilde{\mathbf U}}{\partial t} \cdot \mathbf V \, \textnormal{d} \mathbf{x} = \int (\nabla \times \mathbf B_0) \times \tilde{\mathbf B} \cdot \mathbf V \, \textnormal{d} \mathbf x + \frac{A_\textnormal{h}}{A_b}\iint f^\text{vol} \mu \mathbf b_0 \cdot \nabla \times (\tilde{\mathbf B} \times \mathbf V) \, \textnormal{d} \mathbf x \textnormal{d} v_\parallel \textnormal{d} \mu + \int \tilde p \nabla \cdot \mathbf V \, \textnormal{d} \mathbf x \qquad \forall \, \mathbf V \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}\,, + \\ + &\frac{\partial \tilde p}{\partial t} = - \nabla \cdot (p_0 \tilde{\mathbf U}) - (\gamma - 1) p_0 \nabla \cdot \tilde{\mathbf U} \,. + \end{aligned} + \right. + + :ref:`time_discret`: Crank-Nicolson (implicit mid-point). System size reduction via :class:`~struphy.linear_algebra.schur_solver.SchurSolver`: + + .. math:: + + \boldsymbol{\rho}^{n+1} - \boldsymbol{\rho}^n = - \frac{\Delta t}{2} \mathbb D \mathcal Q^\alpha (\mathbf u^{n+1} + \mathbf u^n) \,, + + .. math:: + + \begin{bmatrix} + \mathbf u^{n+1} - \mathbf u^n \\ \mathbf p^{n+1} - \mathbf p^n + \end{bmatrix} + = \frac{\Delta t}{2} + \begin{bmatrix} + 0 & (\mathbb M^{\alpha,n})^{-1} {\mathcal U^\alpha}^\top \mathbb D^\top \mathbb M_3 \\ - \mathbb D \mathcal S^\alpha - (\gamma - 1) \mathcal K^\alpha \mathbb D \mathcal U^\alpha & 0 + \end{bmatrix} + \begin{bmatrix} + (\mathbf u^{n+1} + \mathbf u^n) \\ (\mathbf p^{n+1} + \mathbf p^n) + \end{bmatrix} + + \begin{bmatrix} + \Delta t (\mathbb M^{\alpha,n})^{-1}\left[\mathbb M^{\alpha,J} \mathbf b^n + \frac{A_\textnormal{h}}{A_b}{\mathcal{T}^B}^\top \mathbb{C}^\top \sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k) \right)\right] \\ 0 + \end{bmatrix} \,, + + where + :math:`\mathcal U^\alpha`, :math:`\mathcal S^\alpha`, :math:`\mathcal K^\alpha` and :math:`\mathcal Q^\alpha` are :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators` and + :math:`\mathbb M^{\alpha,n}` and :math:`\mathbb M^{\alpha,J}` are :class:`~struphy.feec.mass.WeightedMassOperators` being weighted with :math:`\rho_0` the MHD equilibrium density. + :math:`\alpha \in \{1, 2, v\}` denotes the :math:`\alpha`-form space where the operators correspond to. + Moreover, :math:`\sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k)\right)` is accumulated by the kernel :class:`~struphy.pic.accumulation.accum_kernels_gc.cc_lin_mhd_5d_M` and + the time-varying projection operator :math:`\mathcal{T}^B` is defined as + + .. math:: + + \mathcal{T}^B_{(\mu,ijk),(\nu,mno)} := \hat \Pi¹_{(\mu,ijk)} \left[ \epsilon_{\mu \alpha \nu} \frac{\tilde{B}^2_\alpha}{\sqrt{g}} \Lambda²_{\nu,mno} \right] \,. + """ + + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pbicgstab", "MassMatrixPreconditioner"), + ("bicgstab", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["filter"] = { + "use_filter": None, + "modes": (0, 1), + "repeat": 3, + "alpha": 0.5, + } + dct["boundary_cut"] = { + "e1": 0.0, + "e2": 0.0, + "e3": 0.0, + } + dct["turn_off"] = False + + if default: + dct = descend_options_dict(dct, []) + + return dct + + def __init__( + self, + n: StencilVector, + u: BlockVector, + p: StencilVector, + *, + particles: Particles5D, + b: BlockVector, + absB0: StencilVector, + unit_b1: BlockVector, + u_space: str, + solver: dict = options(default=True)["solver"], + filter: dict = options(default=True)["filter"], + coupling_params: dict, + boundary_cut: dict = options(default=True)["boundary_cut"], + ): + super().__init__(n, u, p) + + self._particles = particles + self._b = b + self._unit_b1 = unit_b1 + self._absB0 = absB0 + + self._info = solver["info"] + + self._scale_vec = coupling_params["Ah"] / coupling_params["Ab"] + + self._E1T = self.derham.extraction_ops["1"].transpose() + self._unit_b1 = self._E1T.dot(self._unit_b1) + + self._u_id = self.derham.space_to_form[u_space] + if self._u_id == "v": + self._space_key_int = 0 else: - _TT = _T.T + self._space_key_int = int(self._u_id) - if self.options.algo == "implicit": - self._info = self.options.solver_params.info + self._boundary_cut_e1 = boundary_cut["e1"] - # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) - self._B = -1 / 2 * _TT @ curl.T @ M2 - self._B2 = -1 / 2 * _TT @ curl.T @ PB.T + self._ACC = Accumulator( + particles, + u_space, + accum_kernels_gc.cc_lin_mhd_5d_M, + self.mass_ops, + self.domain.args_domain, + add_vector=True, + symmetry="symm", + filter_params=filter, + ) - self._C = 1 / 2 * curl @ _T + # if self._particles.control_variate: - # Instantiate Schur solver (constant in this case) - _BC = self._B @ self._C + # # control variate method is only valid with Maxwellian distributions with "zero perp mean velocity". + # assert isinstance(self._particles.f0, Maxwellian) - self._schur_solver = SchurSolver( - _A, - _BC, - self.options.solver, - precond=pc, - solver_params=self.options.solver_params, - ) + # self._ACC.init_control_variate(self.mass_ops) - # allocate dummy vectors to avoid temporary array allocations - self._u_tmp1 = self.variables.u.spline.vector.space.zeros() - self._u_tmp2 = self.variables.u.spline.vector.space.zeros() - self._b_tmp1 = self.variables.b.spline.vector.space.zeros() + # # evaluate and save f0.n at quadrature points + # quad_pts = [quad_grid[nquad].points.flatten() + # for quad_grid, nquad in zip(self.derham.get_quad_grids(self.derham.Vh_fem['0']), self.derham.nquads)] - self._byn = self._B.codomain.zeros() - self._tmp_acc = self._B2.codomain.zeros() + # n0_at_quad = self.domain.push( + # self._particles.f0.n, *quad_pts, kind='0', squeeze_out=False) + + # # evaluate M0 = unit_b1 (1form) / absB0 (0form) * 2 * vth_perp² at quadrature points + # quad_pts_array = self.domain.prepare_eval_pts(*quad_pts)[:3] + # vth_perp = self.particles.f0.vth(*quad_pts_array)[1] + + # absB0_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['0'], self._absB0) + + # unit_b1_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['1'], self._unit_b1) + + # self._M0_at_quad = unit_b1_at_quad / absB0_at_quad * vth_perp**2 * n0_at_quad * self._scale_vec + + # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) + id_Mn = "M" + self._u_id + "n" + id_MJ = "M" + self._u_id + "J" + + if self._u_id == "1": + id_S, id_U, id_K, id_Q = "S1", "U1", "K3", "Q1" + elif self._u_id == "2": + id_S, id_U, id_K, id_Q = "S2", None, "K3", "Q2" + elif self._u_id == "v": + id_S, id_U, id_K, id_Q = "Sv", "Uv", "K3", "Qv" + + self._E2T = self.derham.extraction_ops["2"].transpose() + + _A = getattr(self.mass_ops, id_Mn) + _S = getattr(self.basis_ops, id_S) + _K = getattr(self.basis_ops, id_K) + + # initialize projection operator TB + self._initialize_projection_operator_TB() + + if id_U is None: + _U, _UT = IdentityOperator(u.space), IdentityOperator(u.space) else: - self._info = False + _U = getattr(self.basis_ops, id_U) + _UT = _U.T - # define vector field - A_inv = inverse( - _A, - self.options.solver, - pc=pc, - tol=self.options.solver_params.tol, - maxiter=self.options.solver_params.maxiter, - verbose=self.options.solver_params.verbose, - ) - _f1 = A_inv @ _TT @ curl.T @ M2 - _f1_acc = A_inv @ _TT @ curl.T @ PB.T - _f2 = curl @ _T + self._B = -1 / 2.0 * _UT @ self.derham.div.T @ self.mass_ops.M3 + self._C = 1 / 2.0 * (self.derham.div @ _S + 2 / 3.0 * _K @ self.derham.div @ _U) - # allocate output of vector field - out_acc = self.variables.u.spline.vector.space.zeros() - out1 = self.variables.u.spline.vector.space.zeros() - out2 = self.variables.b.spline.vector.space.zeros() + self._MJ = getattr(self.mass_ops, id_MJ) + self._DQ = self.derham.div @ getattr(self.basis_ops, id_Q) - def f1(t, y1, y2, out: BlockVector = out1): - _f1.dot(y2, out=out) - _f1_acc.dot(self._ACC.vectors[0], out=out_acc) - out += out_acc - out.update_ghost_regions() - return out + self._TC = self._TB.T @ self.derham.curl.T - def f2(t, y1, y2, out: BlockVector = out2): - _f2.dot(y1, out=out) - out *= -1.0 - out.update_ghost_regions() - return out + # preconditioner + if solver["type"][1] is None: + pc = None + else: + pc_class = getattr(preconditioner, solver["type"][1]) + pc = pc_class(getattr(self.mass_ops, id_Mn)) - vector_field = {self.variables.u.spline.vector: f1, self.variables.b.spline.vector: f2} - self._ode_solver = ODEsolverFEEC(vector_field, butcher=self.options.butcher) + # instantiate Schur solver (constant in this case) + _BC = self._B @ self._C + + self._schur_solver = SchurSolver( + _A, + _BC, + solver["type"][0], + pc=pc, + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], + recycle=solver["recycle"], + ) + + # allocate dummy vectors to avoid temporary array allocations + self._u_tmp1 = u.space.zeros() + self._u_tmp2 = u.space.zeros() + self._p_tmp1 = p.space.zeros() + self._n_tmp1 = n.space.zeros() + self._byn1 = self._B.codomain.zeros() + self._byn2 = self._B.codomain.zeros() + self._tmp_acc = self._TC.codomain.zeros() def __call__(self, dt): - # update time-dependent operator TB - if self.options.nonlinear: - self._update_weights_TB() + # current variables + nn = self.feec_vars[0] + un = self.feec_vars[1] + pn = self.feec_vars[2] - # current FE coeffs - un = self.variables.u.spline.vector - bn = self.variables.b.spline.vector + # perform accumulation (either with or without control variate) + # if self._particles.control_variate: - # accumulate - self._ACC(self.options.ep_scale) + # self._ACC.accumulate(self._particles, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._scale_vec, 0., + # control_vec=[self._M0_at_quad[0], self._M0_at_quad[1], self._M0_at_quad[2]]) + # else: + # self._ACC.accumulate(self._particles, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._scale_vec, 0.) - if self.options.algo == "implicit": - # solve for new u coeffs (no tmps created here) - byn = self._B.dot(bn, out=self._byn) - b2acc = self._B2.dot(self._ACC.vectors[0], out=self._tmp_acc) - byn += b2acc + self._ACC( + self._unit_b1[0]._data, + self._unit_b1[1]._data, + self._unit_b1[2]._data, + self._scale_vec, + self._boundary_cut_e1, + ) - un1, info = self._schur_solver(un, byn, dt, out=self._u_tmp1) + # update time-dependent operator + self._b.update_ghost_regions() + self._update_weights_TB() - # new b coeffs (no tmps created here) - _u = un.copy(out=self._u_tmp2) - _u += un1 - bn1 = self._C.dot(_u, out=self._b_tmp1) - bn1 *= -dt - bn1 += bn + # solve for new u coeffs (no tmps created here) + byn1 = self._B.dot(pn, out=self._byn1) + byn2 = self._MJ.dot(self._b, out=self._byn2) + b2acc = self._TC.dot(self._ACC.vectors[0], out=self._tmp_acc) + byn2 += b2acc + byn2 *= 1 / 2 + byn1 -= byn2 - diffs = self.update_feec_variables(u=un1, b=bn1) + un1, info = self._schur_solver(un, byn1, dt, out=self._u_tmp1) - else: - self._ode_solver(0.0, dt) + # new p, n, b coeffs (no tmps created here) + _u = un.copy(out=self._u_tmp2) + _u += un1 + pn1 = self._C.dot(_u, out=self._p_tmp1) + pn1 *= -dt + pn1 += pn + + nn1 = self._DQ.dot(_u, out=self._n_tmp1) + nn1 *= -dt / 2 + nn1 += nn + + # write new coeffs into self.feec_vars + max_dn, max_du, max_dp = self.feec_vars_update( + nn1, + un1, + pn1, + ) if self._info and MPI.COMM_WORLD.Get_rank() == 0: - if self.options.algo == "implicit": - print("Status for ShearAlfvenCurrentCoupling5D:", info["success"]) - print("Iterations for ShearAlfvenCurrentCoupling5D:", info["niter"]) - print("Maxdiff up for ShearAlfvenCurrentCoupling5D:", diffs["u"]) - print("Maxdiff b2 for ShearAlfvenCurrentCoupling5D:", diffs["b"]) - print() + print("Status for Magnetosonic:", info["success"]) + print("Iterations for Magnetosonic:", info["niter"]) + print("Maxdiff n3 for Magnetosonic:", max_dn) + print("Maxdiff up for Magnetosonic:", max_du) + print("Maxdiff p3 for Magnetosonic:", max_dp) + print() def _initialize_projection_operator_TB(self): r"""Initialize BasisProjectionOperator TB with the time-varying weight. @@ -2184,80 +2261,27 @@ def _initialize_projection_operator_TB(self): # Call the projector and the space P1 = self.derham.P["1"] - Vh = self.derham.Vh_fem[self._u_form] + Vh = self.derham.Vh_fem[self._u_id] # Femfield for the field evaluation self._bf = self.derham.create_spline_function("bf", "Hdiv") + # define temp callable + def tmp(x, y, z): + return 0 * x + # Initialize BasisProjectionOperator if self.derham._with_local_projectors: - self._TB = BasisProjectionOperatorLocal( - P1, - Vh, - [ - [None, None, None], - [None, None, None], - [None, None, None], - ], - transposed=False, - use_cache=True, - polar_shift=True, - V_extraction_op=self.derham.extraction_ops[self._u_form], - V_boundary_op=self.derham.boundary_ops[self._u_form], - P_boundary_op=self.derham.boundary_ops["1"], - ) - self._TBT = BasisProjectionOperatorLocal( - P1, - Vh, - [ - [None, None, None], - [None, None, None], - [None, None, None], - ], - transposed=True, - use_cache=True, - polar_shift=True, - V_extraction_op=self.derham.extraction_ops[self._u_form], - V_boundary_op=self.derham.boundary_ops[self._u_form], - P_boundary_op=self.derham.boundary_ops["1"], - ) + self._TB = BasisProjectionOperatorLocal(P1, Vh, [[tmp, tmp, tmp]]) else: - self._TB = BasisProjectionOperator( - P1, - Vh, - [ - [None, None, None], - [None, None, None], - [None, None, None], - ], - transposed=False, - use_cache=True, - polar_shift=True, - V_extraction_op=self.derham.extraction_ops[self._u_form], - V_boundary_op=self.derham.boundary_ops[self._u_form], - P_boundary_op=self.derham.boundary_ops["1"], - ) - self._TBT = BasisProjectionOperator( - P1, - Vh, - [ - [None, None, None], - [None, None, None], - [None, None, None], - ], - transposed=True, - use_cache=True, - polar_shift=True, - V_extraction_op=self.derham.extraction_ops[self._u_form], - V_boundary_op=self.derham.boundary_ops[self._u_form], - P_boundary_op=self.derham.boundary_ops["1"], - ) + self._TB = BasisProjectionOperator(P1, Vh, [[tmp, tmp, tmp]]) def _update_weights_TB(self): """Updats time-dependent weights of the BasisProjectionOperator TB""" # Update Femfield - self.variables.b.spline.vector.copy(out=self._bf.vector) + self._bf.vector = self._b + self._bf.vector.update_ghost_regions() # define callable weights def bf1(x, y, z): @@ -2275,7 +2299,7 @@ def bf3(x, y, z): fun = [] - if self._u_form == "v": + if self._u_id == "v": for m in range(3): fun += [[]] for n in range(3): @@ -2283,7 +2307,7 @@ def bf3(x, y, z): lambda e1, e2, e3, m=m, n=n: rot_B(e1, e2, e3)[:, :, :, m, n], ] - elif self._u_form == "1": + elif self._u_id == "1": for m in range(3): fun += [[]] for n in range(3): @@ -2309,9 +2333,8 @@ def bf3(x, y, z): / abs(self.domain.jacobian_det(e1, e2, e3, squeeze_out=False)), ] - # update BasisProjectionOperator + # Initialize BasisProjectionOperator self._TB.update_weights(fun) - self._TBT.update_weights(fun) class CurrentCoupling5DDensity(Propagator): @@ -2331,166 +2354,268 @@ class CurrentCoupling5DDensity(Propagator): For the detail explanation of the notations, see `2022_DriftKineticCurrentCoupling `_. """ - class Variables: - def __init__(self): - self._u: FEECVariable = None + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("pbicgstab", "MassMatrixPreconditioner"), + ("bicgstab", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["filter"] = { + "use_filter": None, + "modes": (1), + "repeat": 1, + "alpha": 0.5, + } + dct["boundary_cut"] = { + "e1": 0.0, + "e2": 0.0, + "e3": 0.0, + } + dct["turn_off"] = False - @property - def u(self) -> FEECVariable: - return self._u + if default: + dct = descend_options_dict(dct, []) - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space in ("Hcurl", "Hdiv", "H1vec") - self._u = new + return dct - def __init__(self): - self.variables = self.Variables() + def __init__( + self, + u: BlockVector, + *, + particles: Particles5D, + b: BlockVector, + b_eq: BlockVector, + unit_b1: BlockVector, + curl_unit_b2: BlockVector, + u_space: str, + solver: dict = options(default=True)["solver"], + coupling_params: dict, + epsilon: float = 1.0, + filter: dict = options(default=True)["filter"], + boundary_cut: dict = options(default=True)["boundary_cut"], + ): + super().__init__(u) - @dataclass - class Options: - # propagator options - energetic_ions: PICVariable = None - b_tilde: FEECVariable = None - ep_scale: float = 1.0 - u_space: OptsVecSpace = "Hdiv" - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - filter_params: FilterParameters = None + # assert parameters and expose some quantities to self + assert isinstance(particles, (Particles5D)) - def __post_init__(self): - # checks - check_option(self.u_space, OptsVecSpace) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - assert isinstance(self.energetic_ions, PICVariable) - assert self.energetic_ions.space == "Particles5D" - assert isinstance(self.b_tilde, FEECVariable) - assert isinstance(self.ep_scale, float) + assert u_space in {"Hcurl", "Hdiv", "H1vec"} - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + if u_space == "H1vec": + self._space_key_int = 0 + else: + self._space_key_int = int( + self.derham.space_to_form[u_space], + ) - if self.filter_params is None: - self.filter_params = FilterParameters() + self._epsilon = epsilon + self._particles = particles + self._b = b + self._b_eq = b_eq + self._unit_b1 = unit_b1 + self._curl_norm_b = curl_unit_b2 - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + self._info = solver["info"] + + self._scale_mat = coupling_params["Ah"] / coupling_params["Ab"] / self._epsilon + + self._boundary_cut_e1 = boundary_cut["e1"] + + self._accumulator = Accumulator( + particles, + u_space, + accum_kernels_gc.cc_lin_mhd_5d_D, + self.mass_ops, + self.domain.args_domain, + add_vector=False, + symmetry="asym", + filter_params=filter, + ) + + # if self._particles.control_variate: + + # # control variate method is only valid with Maxwellian distributions + # assert isinstance(self._particles.f0, Maxwellian) + # assert params['u_space'] == 'Hdiv' + + # # evaluate and save f0.n / |det(DF)| at quadrature points + # quad_pts = [quad_grid[nquad].points.flatten() + # for quad_grid, nquad in zip(self.derham.get_quad_grids(self.derham.Vh_fem['0']), self.derham.nquads)] + + # self._n0_at_quad = self.domain.push( + # self._particles.f0.n, *quad_pts, kind='3', squeeze_out=False) + + # # prepare field evaluation + # quad_pts_array = self.domain.prepare_eval_pts(*quad_pts)[:3] + + # u0_parallel = self._particles.f0.u(*quad_pts_array)[0] + + # det_df_at_quad = self.domain.jacobian_det(*quad_pts, squeeze_out=False) + + # # evaluate unit_b1 / |det(DF)| at quadrature points + # self._unit_b1_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['1'], self._unit_b1) + # self._unit_b1_at_quad /= det_df_at_quad + + # # evaluate unit_b1 (1form) dot epsilon * f0.u * curl_norm_b (2form) / |det(DF)| at quadrature points + # curl_norm_b_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['2'], self._curl_norm_b) - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + # self._unit_b1_dot_curl_norm_b_at_quad = np.sum(p * q for p, q in zip(self._unit_b1_at_quad, curl_norm_b_at_quad)) - @profile - def allocate(self): - if self.options.u_space == "H1vec": - self._u_form_int = 0 - else: - self._u_form_int = int(self.derham.space_to_form[self.options.u_space]) + # self._unit_b1_dot_curl_norm_b_at_quad /= det_df_at_quad + # self._unit_b1_dot_curl_norm_b_at_quad *= self._epsilon + # self._unit_b1_dot_curl_norm_b_at_quad *= u0_parallel - # call operatros - id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n" - self._A = getattr(self.mass_ops, id_M) + # # memory allocation for magnetic field at quadrature points + # self._b_quad1 = np.zeros_like(self._n0_at_quad) + # self._b_quad2 = np.zeros_like(self._n0_at_quad) + # self._b_quad3 = np.zeros_like(self._n0_at_quad) - # magnetic equilibrium field - unit_b1 = self.projected_equil.unit_b1 - curl_unit_b1 = self.projected_equil.curl_unit_b1 - self._b2 = self.projected_equil.b2 + # # memory allocation for parallel magnetic field at quadrature points + # self._B_para = np.zeros_like(self._n0_at_quad) - # scaling factor - epsilon = self.options.energetic_ions.species.equation_params.epsilon + # # memory allocation for control_const at quadrature points + # self._control_const = np.zeros_like(self._n0_at_quad) - # temporary vectors to avoid memory allocation - self._b_full = self._b2.space.zeros() - self._rhs_v = self.variables.u.spline.vector.space.zeros() - self._u_new = self.variables.u.spline.vector.space.zeros() + # # memory allocation for self._b_quad x self._nh0_at_quad * self._coupling_const + # self._mat12 = np.zeros_like(self._n0_at_quad) + # self._mat13 = np.zeros_like(self._n0_at_quad) + # self._mat23 = np.zeros_like(self._n0_at_quad) - # define Accumulator and arguments - self._ACC = Accumulator( - self.options.energetic_ions.particles, - self.options.u_space, - Pyccelkernel(accum_kernels_gc.cc_lin_mhd_5d_D), - self.mass_ops, - self.domain.args_domain, - add_vector=False, - symmetry="asym", - filter_params=self.options.filter_params, - ) + # self._mat21 = np.zeros_like(self._n0_at_quad) + # self._mat31 = np.zeros_like(self._n0_at_quad) + # self._mat32 = np.zeros_like(self._n0_at_quad) - self._args_accum_kernel = ( - epsilon, - self.options.ep_scale, - self._b_full[0]._data, - self._b_full[1]._data, - self._b_full[2]._data, - unit_b1[0]._data, - unit_b1[1]._data, - unit_b1[2]._data, - curl_unit_b1[0]._data, - curl_unit_b1[1]._data, - curl_unit_b1[2]._data, - self._u_form_int, - ) + u_id = self.derham.space_to_form[u_space] + self._M = getattr(self.mass_ops, "M" + u_id + "n") - # Preconditioner - if self.options.precond is None: - pc = None + self._E0T = self.derham.extraction_ops["0"].transpose() + self._EuT = self.derham.extraction_ops[u_id].transpose() + self._E1T = self.derham.extraction_ops["1"].transpose() + self._E2T = self.derham.extraction_ops["2"].transpose() + + self._PB = getattr(self.basis_ops, "PB") + self._unit_b1 = self._E1T.dot(self._unit_b1) + + # preconditioner + if solver["type"][1] is None: + self._pc = None else: - pc_class = getattr(preconditioner, self.options.precond) - pc = pc_class(getattr(self.mass_ops, id_M)) + pc_class = getattr(preconditioner, solver["type"][1]) + self._pc = pc_class(self._M) # linear solver - self._A_inv = inverse( - self._A, - self.options.solver, - pc=pc, - tol=self.options.solver_params.tol, - maxiter=self.options.solver_params.maxiter, - verbose=self.options.solver_params.verbose, + self._solver = inverse( + self._M, + solver["type"][0], + pc=self._pc, + x0=self.feec_vars[0], + tol=solver["tol"], + maxiter=solver["maxiter"], + verbose=solver["verbose"], + recycle=solver["recycle"], ) + # temporary vectors to avoid memory allocation + self._b_full1 = self._b_eq.space.zeros() + self._b_full2 = self._E2T.codomain.zeros() + self._rhs_v = u.space.zeros() + self._u_new = u.space.zeros() + def __call__(self, dt): - # current FE coeffs - un = self.variables.u.spline.vector + # pointer to old coefficients + un = self.feec_vars[0] # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) - b_full = self._b2.copy(out=self._b_full) + b_full = self._b_eq.copy(out=self._b_full1) - b_full += self.options.b_tilde.spline.vector - b_full.update_ghost_regions() + if self._b is not None: + b_full += self._b - self._ACC( - *self._args_accum_kernel, + Eb_full = self._E2T.dot(b_full, out=self._b_full2) + Eb_full.update_ghost_regions() + + # perform accumulation (either with or without control variate) + # if self._particles.control_variate: + + # # evaluate magnetic field at quadrature points (in-place) + # WeightedMassOperator.eval_quad(self.derham.Vh_fem['2'], self._b_full2, + # out=[self._b_quad1, self._b_quad2, self._b_quad3]) + + # # evaluate B_parallel + # self._B_para = np.sum(p * q for p, q in zip(self._unit_b1_at_quad, [self._b_quad1, self._b_quad2, self._b_quad3])) + + # # evaluate coupling_const 1 - B_parallel / B^star_parallel + # self._control_const = 1 - (self._B_para / (self._B_para + self._unit_b1_dot_curl_norm_b_at_quad)) + + # # assemble (B x) + # self._mat12[:, :, :] = self._scale_mat * \ + # self._b_quad3 * self._n0_at_quad * self._control_const + # self._mat13[:, :, :] = -self._scale_mat * \ + # self._b_quad2 * self._n0_at_quad * self._control_const + # self._mat23[:, :, :] = self._scale_mat * \ + # self._b_quad1 * self._n0_at_quad * self._control_const + + # self._mat21[:, :, :] = -self._mat12 + # self._mat31[:, :, :] = -self._mat13 + # self._mat32[:, :, :] = -self._mat23 + + # self._accumulator.accumulate(self._particles, self._epsilon, + # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, + # self._space_key_int, self._scale_mat, 0.1, + # control_mat=[[None, self._mat12, self._mat13], + # [self._mat21, None, self._mat23], + # [self._mat31, self._mat32, None]]) + # else: + # self._accumulator.accumulate(self._particles, self._epsilon, + # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, + # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, + # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, + # self._space_key_int, self._scale_mat, 0.) + + self._accumulator( + self._epsilon, + Eb_full[0]._data, + Eb_full[1]._data, + Eb_full[2]._data, + self._unit_b1[0]._data, + self._unit_b1[1]._data, + self._unit_b1[2]._data, + self._curl_norm_b[0]._data, + self._curl_norm_b[1]._data, + self._curl_norm_b[2]._data, + self._space_key_int, + self._scale_mat, + self._boundary_cut_e1, ) # define system (M - dt/2 * A)*u^(n + 1) = (M + dt/2 * A)*u^n - lhs = self._A - dt / 2 * self._ACC.operators[0] - rhs = self._A + dt / 2 * self._ACC.operators[0] + lhs = self._M - dt / 2 * self._accumulator.operators[0] + rhs = self._M + dt / 2 * self._accumulator.operators[0] # solve linear system for updated u coefficients (in-place) rhs = rhs.dot(un, out=self._rhs_v) - self._A_inv.linop = lhs + self._solver.linop = lhs - _u = self._A_inv.solve(rhs, out=self._u_new) - info = self._A_inv._info + un1 = self._solver.solve(rhs, out=self._u_new) + info = self._solver._info - diffs = self.update_feec_variables(u=_u) + # write new coeffs into Propagator.variables + max_du = self.feec_vars_update(un1) - if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: + if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for CurrentCoupling5DDensity:", info["success"]) print("Iterations for CurrentCoupling5DDensity:", info["niter"]) - print("Maxdiff up for CurrentCoupling5DDensity:", diffs["u"]) + print("Maxdiff up for CurrentCoupling5DDensity:", max_du) print() @@ -2524,6 +2649,35 @@ class ImplicitDiffusion(Propagator): * :math:`\sigma_1=\sigma_2=0` and :math:`\sigma_3 = \Delta t`: **Poisson solver** with a given charge density :math:`\sum_i\rho_i`. * :math:`\sigma_2=0` and :math:`\sigma_1 = \sigma_3 = \Delta t` : Poisson with **adiabatic electrons**. * :math:`\sigma_1=\sigma_2=1` and :math:`\sigma_3 = 0`: **Implicit heat equation**. + + Parameters + ---------- + phi : StencilVector + FE coefficients of the solution as a discrete 0-form. + + sigma_1, sigma_2, sigma_3 : float | int + Equation parameters. + + divide_by_dt : bool + Whether to divide the sigmas by dt during __call__. + + stab_mat : str + Name of the matrix :math:`M^0_{n_0}`. + + diffusion_mat : str + Name of the matrix :math:`M^1_{D_0}`. + + rho : StencilVector or tuple or list + (List of) right-hand side FE coefficients of a 0-form (optional, can be set with a setter later). + Can be either a) StencilVector or b) 2-tuple, or a list of those. + In case b) the first tuple entry must be :class:`~struphy.pic.accumulation.particles_to_grid.AccumulatorVector`, + and the second entry must be :class:`~struphy.pic.base.Particles`. + + x0 : StencilVector + Initial guess for the iterative solver (optional, can be set with a setter later). + + solver : dict + Parameters for the iterative solver (see ``__init__`` for details). """ class Variables: @@ -2555,8 +2709,7 @@ class Options: divide_by_dt: bool = False stab_mat: OptsStabMat = "M0" diffusion_mat: OptsDiffusionMat = "M1" - rho: FEECVariable | Callable | tuple[AccumulatorVector, Particles] | list = None - rho_coeffs: float | list = None + rho: StencilVector | tuple | list | Callable = None x0: StencilVector = None solver: OptsSymmSolver = "pcg" precond: OptsMassPrecond = "MassMatrixPreconditioner" @@ -2591,10 +2744,10 @@ def options(self, new): @profile def allocate(self): # always stabilize - if xp.abs(self.options.sigma_1) < 1e-14: + if np.abs(self.options.sigma_1) < 1e-14: self.options.sigma_1 = 1e-14 if MPI.COMM_WORLD.Get_rank() == 0: - print(f"Stabilizing Poisson solve with {self.options.sigma_1 =}") + print(f"Stabilizing Poisson solve with {self.options.sigma_1 = }") # model parameters self._sigma_1 = self.options.sigma_1 @@ -2605,39 +2758,30 @@ def allocate(self): phi = self.variables.phi.spline.vector # collect rhs - def verify_rhs(rho) -> StencilVector | FEECVariable | AccumulatorVector: - """Perform preliminary operations on rho to comute the rhs and return the result.""" - if rho is None: - rhs = phi.space.zeros() - elif isinstance(rho, FEECVariable): - assert rho.space == "H1" - rhs = rho - elif isinstance(rho, AccumulatorVector): - rhs = rho - elif isinstance(rho, Callable): - rhs = L2Projector("H1", self.mass_ops).get_dofs(rho, apply_bc=True) - else: - raise TypeError(f"{type(rho) =} is not accepted.") - - return rhs - rho = self.options.rho - if isinstance(rho, list): - self._sources = [] - for r in rho: - self._sources += [verify_rhs(r)] - else: - self._sources = [verify_rhs(rho)] - # coeffs of rhs - if self.options.rho_coeffs is not None: - if isinstance(self.options.rho_coeffs, (list, tuple)): - self._coeffs = self.options.rho_coeffs - else: - self._coeffs = [self.options.rho_coeffs] - assert len(self._coeffs) == len(self._sources) + if rho is None: + self._rho = [phi.space.zeros()] else: - self._coeffs = [1.0 for src in self.sources] + if isinstance(rho, list): + for r in rho: + if isinstance(r, tuple): + assert isinstance(r[0], AccumulatorVector) + assert isinstance(r[1], Particles) + # assert r.space_id == 'H1' + else: + assert r.space == phi.space + elif isinstance(rho, tuple): + assert isinstance(rho[0], AccumulatorVector) + assert isinstance(rho[1], Particles) + # assert rho[0].space_id == 'H1' + rho = [rho] + elif isinstance(rho, Callable): + rho = [rho()] + else: + assert rho.space == phi.space + rho = [rho] + self._rho = rho # initial guess and solver params self._x0 = self.options.x0 @@ -2683,41 +2827,65 @@ def verify_rhs(rho) -> StencilVector | FEECVariable | AccumulatorVector: self._tmp = phi.space.zeros() self._rhs = phi.space.zeros() self._rhs2 = phi.space.zeros() - self._tmp_src = phi.space.zeros() @property - def sources(self) -> list[StencilVector | FEECVariable | AccumulatorVector]: + def rho(self): """ - Right-hand side of the equation (sources). + (List of) right-hand side FE coefficients of a 0-form. + The list entries can be either a) StencilVectors or b) 2-tuples; + in the latter case, the first tuple entry must be :class:`~struphy.pic.accumulation.particles_to_grid.AccumulatorVector`, + and the second entry must be :class:`~struphy.pic.base.Particles`. """ - return self._sources + return self._rho - @property - def coeffs(self) -> list[float]: - """ - Same length as self.sources. Coefficients multiplied with sources before solve (default is 1.0). + @rho.setter + def rho(self, value): + """In-place setter for StencilVector/PolarVector. + If rho is a list, len(value) msut be len(rho) and value can contain None. """ - return self._coeffs + if isinstance(value, list): + assert len(value) == len(self.rho) + for i, (val, r) in enumerate(zip(value, self.rho)): + if val is None: + continue + elif isinstance(val, tuple): + assert isinstance(val[0], AccumulatorVector) + assert isinstance(val[1], Particles) + assert isinstance(r, tuple) + self._rho[i] = val + else: + assert val.space == r.space + r[:] = val[:] + elif isinstance(ValueError, tuple): + assert isinstance(value[0], AccumulatorVector) + assert isinstance(value[1], Particles) + assert len(self.rho) == 1 + # assert rho[0].space_id == 'H1' + self._rho[0] = value + else: + assert value.space == self.derham.Vh["0"] + assert len(self.rho) == 1 + self._rho[0][:] = value[:] @property def x0(self): """ psydac.linalg.stencil.StencilVector or struphy.polar.basic.PolarVector. First guess of the iterative solver. """ - return self.options.x0 + return self._x0 @x0.setter - def x0(self, value: StencilVector): + def x0(self, value): """In-place setter for StencilVector/PolarVector. First guess of the iterative solver.""" assert value.space == self.derham.Vh["0"] assert value.space.symbolic_space == "H1", ( f"Right-hand side must be in H1, but is in {value.space.symbolic_space}." ) - if self.options.x0 is None: - self.options.x0 = value + if self._x0 is None: + self._x0 = value else: - self.options.x0[:] = value[:] + self._x0[:] = value[:] @profile def __call__(self, dt): @@ -2737,15 +2905,12 @@ def __call__(self, dt): rhs *= sig_2 self._rhs2 *= 0.0 - for src, coeff in zip(self.sources, self.coeffs): - if isinstance(src, StencilVector): - self._rhs2 += sig_3 * coeff * src - elif isinstance(src, FEECVariable): - v = src.spline.vector - self._rhs2 += sig_3 * coeff * self.mass_ops.M0.dot(v, out=self._tmp_src) - elif isinstance(src, AccumulatorVector): - src() # accumulate - self._rhs2 += sig_3 * coeff * src.vectors[0] + for rho in self._rho: + if isinstance(rho, tuple): + rho[0]() # accumulate + self._rhs2 += sig_3 * rho[0].vectors[0] + else: + self._rhs2 += sig_3 * rho rhs += self._rhs2 @@ -2814,8 +2979,7 @@ class Options: # propagator options stab_eps: float = 0.0 stab_mat: OptsStabMat = "Id" - rho: FEECVariable | Callable | tuple[AccumulatorVector, Particles] | list = None - rho_coeffs: float | list = None + rho: StencilVector | tuple | list | Callable = None x0: StencilVector = None solver: OptsSymmSolver = "pcg" precond: OptsMassPrecond = "MassMatrixPreconditioner" @@ -2885,73 +3049,50 @@ class VariationalMomentumAdvection(Propagator): \hat{\mathbf{u}}_h^{n+1/2} = (\mathbf{u}^{n+1/2})^\top \vec{\boldsymbol \Lambda}^v \in (V_h^0)^3 \,, \qquad \hat{\mathbf A}^1_{\mu,h} = \nabla P_\mu((\mathbf u^{n+1/2})^\top \vec{\boldsymbol \Lambda}^v)] \in V_h^1\,, \qquad \hat{\rho}_h^{n} = (\rho^{n})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \,. """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1vec" - self._u = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # propagator options - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - nonlin_solver: NonlinearSolverParameters = None - - def __post_init__(self): - # checks - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - if self.nonlin_solver is None: - self.nonlin_solver = NonlinearSolverParameters() + @staticmethod + def options(default=False): + dct = {} + dct["lin_solver"] = { + "tol": 1e-12, + "maxiter": 500, + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "verbose": False, + } + dct["nonlin_solver"] = { + "tol": 1e-8, + "maxiter": 100, + "type": ["Newton", "Picard"], + "info": False, + } + if default: + dct = descend_options_dict(dct, []) + return dct - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + def __init__( + self, + u: BlockVector, + *, + mass_ops: H1vecMassMatrix_density, + lin_solver: dict = options(default=True)["lin_solver"], + nonlin_solver: dict = options(default=True)["nonlin_solver"], + ): + super().__init__(u) - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + assert mass_ops is not None - @profile - def allocate(self): - self._lin_solver = self.options.solver_params - self._nonlin_solver = self.options.nonlin_solver + self._lin_solver = lin_solver + self._nonlin_solver = nonlin_solver - self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = self.mass_ops.WMM - self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) + self._Mrho = mass_ops self._initialize_mass() # bunch of temporaries to avoid allocating in the loop - u = self.variables.u.spline.vector - self._tmp_un1 = u.space.zeros() self._tmp_un12 = u.space.zeros() self._tmp_diff = u.space.zeros() @@ -2967,25 +3108,25 @@ def allocate(self): self.inv_derivative = inverse( self._Mrho.inv @ self.derivative, "gmres", - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, - verbose=self._lin_solver.verbose, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], + verbose=self._lin_solver["verbose"], recycle=True, ) def __call__(self, dt): - if self._nonlin_solver.type == "Newton": + if self._nonlin_solver["type"] == "Newton": self.__call_newton(dt) - elif self._nonlin_solver.type == "Picard": + elif self._nonlin_solver["type"] == "Picard": self.__call_picard(dt) def __call_newton(self, dt): # Initialize variable for Newton iteration - un = self.variables.u.spline.vector + un = self.feec_vars[0] mn = self._Mrho.massop.dot(un, out=self._tmp_mn) mn1 = mn.copy(out=self._tmp_mn1) un1 = un.copy(out=self._tmp_un1) - tol = self.options.nonlin_solver.tol + tol = self._nonlin_solver["tol"] err = tol + 1 # Jacobian matrix for Newton solve self._dt2_brack._scalar = dt / 2 @@ -2993,7 +3134,7 @@ def __call_newton(self, dt): print() print("Newton iteration in VariationalMomentumAdvection") - for it in range(self.options.nonlin_solver.maxiter): + for it in range(self._nonlin_solver["maxiter"]): un12 = un.copy(out=self._tmp_un12) un12 += un1 un12 *= 0.5 @@ -3013,7 +3154,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or xp.isnan(err): + if err < tol**2 or np.isnan(err): break # Newton step @@ -3027,26 +3168,26 @@ def __call_newton(self, dt): un1 -= update mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self.options.nonlin_solver.maxiter - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!WARNING: Maximum iteration in VariationalMomentumAdvection reached - not converged \n {err =} \n {tol**2 =}", + f"!!!WARNING: Maximum iteration in VariationalMomentumAdvection reached - not converged \n {err = } \n {tol**2 = }", ) - self.update_feec_variables(u=un1) + self.feec_vars_update(un1) def __call_picard(self, dt): # Initialize variable for Picard iteration - un = self.variables.u.spline.vector + un = self.feec_vars[0] mn = self._Mrho.massop.dot(un, out=self._tmp_mn) mn1 = mn.copy(out=self._tmp_mn1) un1 = un.copy(out=self._tmp_un1) - tol = self.options.nonlin_solver.tol + tol = self._nonlin_solver["tol"] err = tol + 1 # Jacobian matrix for Newton solve - for it in range(self.options.nonlin_solver.maxiter): + for it in range(self._nonlin_solver["maxiter"]): # Picard iteration - if err < tol**2 or xp.isnan(err): + if err < tol**2 or np.isnan(err): break # half time step approximation un12 = un.copy(out=self._tmp_un12) @@ -3073,12 +3214,12 @@ def __call_picard(self, dt): # Inverse the mass matrix to get the velocity un1 = self._Mrho.inv.dot(mn1, out=self._tmp_un1) - if it == self.options.nonlin_solver.maxiter - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!WARNING: Maximum iteration in VariationalMomentumAdvection reached - not converged \n {err =} \n {tol**2 =}", + f"!!!WARNING: Maximum iteration in VariationalMomentumAdvection reached - not converged \n {err = } \n {tol**2 = }", ) - self.update_feec_variables(u=un1) + self.feec_vars_update(un1) def _initialize_mass(self): """Initialization of the mass matrix solver""" @@ -3151,111 +3292,79 @@ class VariationalDensityEvolve(Propagator): \hat{\mathbf{u}}_h^{k} = (\mathbf{u}^{k})^\top \vec{\boldsymbol \Lambda}^v \in (V_h^0)^3 \, \text{for k in} \{n, n+1/2, n+1\}, \qquad \hat{\rho}_h^{k} = (\rho^{k})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \, \text{for k in} \{n, n+1/2, n+1\} . """ - class Variables: - def __init__(self): - self._rho: FEECVariable = None - self._u: FEECVariable = None - - @property - def rho(self) -> FEECVariable: - return self._rho - - @rho.setter - def rho(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._rho = new - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1vec" - self._u = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsModel = Literal[ - "pressureless", - "barotropic", - "full", - "full_p", - "full_q", - "linear", - "deltaf", - "linear_q", - "deltaf_q", - ] - # propagator options - model: OptsModel = "barotropic" - gamma: float = 5.0 / 3.0 - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - nonlin_solver: NonlinearSolverParameters = None - s: FEECVariable = None - - def __post_init__(self): - # checks - check_option(self.model, self.OptsModel) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + @staticmethod + def options(default=False): + dct = {} + dct["lin_solver"] = { + "tol": 1e-12, + "maxiter": 500, + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "verbose": False, + "recycle": True, + } + dct["nonlin_solver"] = { + "tol": 1e-8, + "maxiter": 100, + "info": False, + "linearize": False, + } + dct["physics"] = {"gamma": 5 / 3} - if self.nonlin_solver is None: - self.nonlin_solver = NonlinearSolverParameters() + if default: + dct = descend_options_dict(dct, []) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + rho: StencilVector, + u: BlockVector, + *, + model: str = "barotropic", + gamma: float = options()["physics"]["gamma"], + s: StencilVector = None, + mass_ops: H1vecMassMatrix_density, + lin_solver: dict = options(default=True)["lin_solver"], + nonlin_solver: dict = options(default=True)["nonlin_solver"], + energy_evaluator: InternalEnergyEvaluator = None, + ): + super().__init__(rho, u) - @profile - def allocate(self): - if self.options.model == "full": - assert self.options.s is not None + assert model in [ + "pressureless", + "barotropic", + "full", + "full_p", + "full_q", + "linear", + "deltaf", + "linear_q", + "deltaf_q", + ] + if model == "full": + assert s is not None + assert mass_ops is not None - self._model = self.options.model - self._gamma = self.options.gamma - self._s = self.options.s - self._lin_solver = self.options.solver_params - self._nonlin_solver = self.options.nonlin_solver - self._linearize = self.options.nonlin_solver.linearize + self._model = model + self._gamma = gamma + self._s = s + self._lin_solver = lin_solver + self._nonlin_solver = nonlin_solver + self._linearize = self._nonlin_solver["linearize"] - self._info = self.options.nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = self.mass_ops.WMM - self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) + self._Mrho = mass_ops # Femfields for the projector self.rhof = self.derham.create_spline_function("rhof", "L2") self.rhof1 = self.derham.create_spline_function("rhof1", "L2") - rho = self.variables.rho.spline.vector - u = self.variables.u.spline.vector - # Projector - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) + self._energy_evaluator = energy_evaluator self._kinetic_evaluator = KineticEnergyEvaluator(self.derham, self.domain, self.mass_ops) self._initialize_projectors_and_mass() if self._model in ["linear", "linear_q"]: @@ -3291,7 +3400,6 @@ def allocate(self): if self._model in ["linear", "linear_q"]: self._update_Pirho(self.projected_equil.n3) - @profile def __call__(self, dt): self.__call_newton(dt) @@ -3303,15 +3411,15 @@ def __call_newton(self, dt): print("Newton iteration in VariationalDensityEvolve") # Initial variables - rhon = self.variables.rho.spline.vector - un = self.variables.u.spline.vector + rhon = self.feec_vars[0] + un = self.feec_vars[1] if self._model in ["linear", "linear_q"]: advection = self.divPirho.dot(un, out=self._tmp_rho_advection) advection *= dt rhon1 = rhon.copy(out=self._tmp_rhon1) rhon1 -= advection - self.update_feec_variables(rho=rhon1, u=un) + self.feec_vars_update(rhon1, un) return if self._model in ["deltaf", "deltaf_q"]: @@ -3324,7 +3432,7 @@ def __call_newton(self, dt): # Initialize variable for Newton iteration if self._model == "full": - s = self._s.spline.vector + s = self._s else: s = None @@ -3346,10 +3454,10 @@ def __call_newton(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver.tol + tol = self._nonlin_solver["tol"] err = tol + 1 - for it in range(self._nonlin_solver.maxiter): + for it in range(self._nonlin_solver["maxiter"]): # Newton iteration un12 = un.copy(out=self._tmp_un12) @@ -3395,7 +3503,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or xp.isnan(err): + if err < tol**2 or np.isnan(err): break # Derivative for Newton @@ -3425,14 +3533,14 @@ def __call_newton(self, dt): mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalDensityEvolve reached - not converged:\n {err =} \n {tol**2 =}", + f"!!!Warning: Maximum iteration in VariationalDensityEvolve reached - not converged:\n {err = } \n {tol**2 = }", ) self._tmp_un_diff = un1 - un self._tmp_rhon_diff = rhon1 - rhon - self.update_feec_variables(rho=rhon1, u=un1) + self.feec_vars_update(rhon1, un1) def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and `CoordinateProjector` needed to compute the bracket term""" @@ -3468,7 +3576,7 @@ def _initialize_projectors_and_mass(self): # tmps grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._rhof_values = xp.zeros(grid_shape, dtype=float) + self._rhof_values = np.zeros(grid_shape, dtype=float) # Other mass matrices for newton solve self._M_drho = self.mass_ops.create_weighted_mass("L2", "L2") @@ -3501,17 +3609,17 @@ def _initialize_projectors_and_mass(self): self._Jacobian, "pbicgstab", pc=self._Mrho.inv, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, - verbose=self._lin_solver.verbose, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], + verbose=self._lin_solver["verbose"], recycle=True, ) # self._inv_Jacobian = inverse(self._Jacobian, # 'gmres', - # tol=self._lin_solver.tol, - # maxiter=self._lin_solver.maxiter, - # verbose=self._lin_solver.verbose, + # tol=self._lin_solver['tol'], + # maxiter=self._lin_solver['maxiter'], + # verbose=self._lin_solver['verbose'], # recycle=True) # L2-projector for V3 @@ -3520,20 +3628,20 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) # tmps - self._eval_dl_drho = xp.zeros(grid_shape, dtype=float) + self._eval_dl_drho = np.zeros(grid_shape, dtype=float) - self._uf_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._uf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) - self._tmp_int_grid2 = xp.zeros(grid_shape, dtype=float) - self._rhof_values = xp.zeros(grid_shape, dtype=float) - self._rhof1_values = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid2 = np.zeros(grid_shape, dtype=float) + self._rhof_values = np.zeros(grid_shape, dtype=float) + self._rhof1_values = np.zeros(grid_shape, dtype=float) if self._model == "full": - self._tmp_de_drho = xp.zeros(grid_shape, dtype=float) + self._tmp_de_drho = np.zeros(grid_shape, dtype=float) gam = self._gamma - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -3541,7 +3649,7 @@ def _initialize_projectors_and_mass(self): ) self._proj_rho2_metric_term = deepcopy(metric) - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -3550,7 +3658,7 @@ def _initialize_projectors_and_mass(self): self._proj_drho_metric_term = deepcopy(metric) if self._linearize: - self._init_dener_drho = xp.zeros(grid_shape, dtype=float) + self._init_dener_drho = np.zeros(grid_shape, dtype=float) def _update_Pirho(self, rho): """Update the weights of the `BasisProjectionOperator` Pirho""" @@ -3564,7 +3672,7 @@ def _update_weighted_MM(self, rho): self._Mrho.update_weight(rho) def _update_linear_form_dl_drho(self, rhon, rhon1, un, un1, sn): - """Update the linearform representing integration in V3 against kinetic energy""" + """Update the linearform representing integration in V3 against kynetic energy""" self._kinetic_evaluator.get_u2_grid(un, un1, self._eval_dl_drho) @@ -3605,15 +3713,11 @@ def _update_linear_form_dl_drho(self, rhon, rhon1, un, un1, sn): def _compute_init_linear_form(self): if abs(self._gamma - 5 / 3) < 1e-3: self._energy_evaluator.evaluate_exact_de_drho_grid( - self.projected_equil.n3, - self.projected_equil.s3_monoatomic, - out=self._init_dener_drho, + self.projected_equil.n3, self.projected_equil.s3_monoatomic, out=self._init_dener_drho ) elif abs(self._gamma - 7 / 5) < 1e-3: self._energy_evaluator.evaluate_exact_de_drho_grid( - self.projected_equil.n3, - self.projected_equil.s3_diatomic, - out=self._init_dener_drho, + self.projected_equil.n3, self.projected_equil.s3_diatomic, out=self._init_dener_drho ) else: raise ValueError("Gamma should be 7/5 or 5/3 for if you want to linearize") @@ -3694,100 +3798,67 @@ class VariationalEntropyEvolve(Propagator): \hat{\mathbf{u}}_h^{k} = (\mathbf{u}^{k})^\top \vec{\boldsymbol \Lambda}^v \in (V_h^0)^3 \, \text{for k in} \{n, n+1/2, n+1\}, \qquad \hat{s}_h^{k} = (s^{k})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \, \text{for k in} \{n, n+1/2, n+1\} \qquad \hat{\rho}_h^{n} = (\rho^{n})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \. """ - class Variables: - def __init__(self): - self._s: FEECVariable = None - self._u: FEECVariable = None - - @property - def s(self) -> FEECVariable: - return self._s - - @s.setter - def s(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._s = new - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1vec" - self._u = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsModel = Literal["full"] - # propagator options - model: OptsModel = "full" - gamma: float = 5.0 / 3.0 - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - nonlin_solver: NonlinearSolverParameters = None - rho: FEECVariable = None - - def __post_init__(self): - # checks - check_option(self.model, self.OptsModel) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + @staticmethod + def options(default=False): + dct = {} + dct["lin_solver"] = { + "tol": 1e-12, + "maxiter": 500, + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "verbose": False, + } + dct["nonlin_solver"] = { + "tol": 1e-8, + "maxiter": 100, + "info": False, + "linearize": "False", + } + dct["physics"] = {"gamma": 5 / 3} - if self.nonlin_solver is None: - self.nonlin_solver = NonlinearSolverParameters() + if default: + dct = descend_options_dict(dct, []) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + s: StencilVector, + u: BlockVector, + *, + model: str = "full", + gamma: float = options()["physics"]["gamma"], + rho: StencilVector, + mass_ops: H1vecMassMatrix_density, + lin_solver: dict = options(default=True)["lin_solver"], + nonlin_solver: dict = options(default=True)["nonlin_solver"], + energy_evaluator: InternalEnergyEvaluator = None, + ): + super().__init__(s, u) - @profile - def allocate(self): - if self.options.model == "full": - assert self.options.rho is not None + assert model in ["full"] + if model == "full": + assert rho is not None + assert mass_ops is not None - self._model = self.options.model - self._gamma = self.options.gamma - self._rho = self.options.rho - self._lin_solver = self.options.solver_params - self._nonlin_solver = self.options.nonlin_solver - self._linearize = self.options.nonlin_solver.linearize + self._model = model + self._gamma = gamma + self._rho = rho + self._lin_solver = lin_solver + self._nonlin_solver = nonlin_solver + self._linearize = self._nonlin_solver["linearize"] - self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = self.mass_ops.WMM - self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) + self._Mrho = mass_ops # Projector - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) + self._energy_evaluator = energy_evaluator self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop - s = self.variables.s.spline.vector - u = self.variables.u.spline.vector - self._tmp_un1 = u.space.zeros() self._tmp_un2 = u.space.zeros() self._tmp_un12 = u.space.zeros() @@ -3815,12 +3886,12 @@ def __call_newton(self, dt): if self._info: print() print("Newton iteration in VariationalEntropyEvolve") - sn = self.variables.s.spline.vector - un = self.variables.u.spline.vector + sn = self.feec_vars[0] + un = self.feec_vars[1] sn1 = sn.copy(out=self._tmp_sn1) # Initialize variable for Newton iteration - rho = self._rho.spline.vector + rho = self._rho self._update_Pis(sn) mn = self._Mrho.massop.dot(un, out=self._tmp_mn) @@ -3829,10 +3900,10 @@ def __call_newton(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver.tol + tol = self._nonlin_solver["tol"] err = tol + 1 - for it in range(self._nonlin_solver.maxiter): + for it in range(self._nonlin_solver["maxiter"]): # Newton iteration un12 = un.copy(out=self._tmp_un12) @@ -3870,7 +3941,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or xp.isnan(err): + if err < tol**2 or np.isnan(err): break # Derivative for Newton @@ -3892,13 +3963,13 @@ def __call_newton(self, dt): # Multiply by the mass matrix to get the momentum mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalEntropyEvolve reached - not converged:\n {err =} \n {tol**2 =}", + f"!!!Warning: Maximum iteration in VariationalEntropyEvolve reached - not converged:\n {err = } \n {tol**2 = }", ) self._tmp_sn_diff = sn1 - sn self._tmp_un_diff = un1 - un - self.update_feec_variables(s=sn1, u=un1) + self.feec_vars_update(sn1, un1) def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and `CoordinateProjector` needed to compute the bracket term""" @@ -3951,19 +4022,19 @@ def _initialize_projectors_and_mass(self): self._inv_Jacobian = SchurSolverFull( self._Jacobian, - self.options.solver, + self._lin_solver["type"][0], pc=self._Mrho.inv, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, - verbose=self._lin_solver.verbose, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], + verbose=self._lin_solver["verbose"], recycle=True, ) # self._inv_Jacobian = inverse(self._Jacobian, # 'gmres', - # tol=self._lin_solver.tol, - # maxiter=self._lin_solver.maxiter, - # verbose=self._lin_solver.verbose, + # tol=self._lin_solver['tol'], + # maxiter=self._lin_solver['maxiter'], + # verbose=self._lin_solver['verbose'], # recycle=True) # prepare for integration of linear form @@ -3979,15 +4050,15 @@ def _initialize_projectors_and_mass(self): ) grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid = np.zeros(grid_shape, dtype=float) if self._model == "full": - self._tmp_de_ds = xp.zeros(grid_shape, dtype=float) + self._tmp_de_ds = np.zeros(grid_shape, dtype=float) if self._linearize: - self._init_dener_ds = xp.zeros(grid_shape, dtype=float) + self._init_dener_ds = np.zeros(grid_shape, dtype=float) gam = self._gamma - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -3995,7 +4066,7 @@ def _initialize_projectors_and_mass(self): ) self._proj_rho2_metric_term = deepcopy(metric) - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -4028,15 +4099,11 @@ def _update_linear_form_dl_ds(self, rhon, sn, sn1): def _compute_init_linear_form(self): if abs(self._gamma - 5 / 3) < 1e-3: self._energy_evaluator.evaluate_exact_de_ds_grid( - self.projected_equil.n3, - self.projected_equil.s3_monoatomic, - out=self._init_dener_ds, + self.projected_equil.n3, self.projected_equil.s3_monoatomic, out=self._init_dener_ds ) elif abs(self._gamma - 7 / 5) < 1e-3: self._energy_evaluator.evaluate_exact_de_ds_grid( - self.projected_equil.n3, - self.projected_equil.s3_diatomic, - out=self._init_dener_ds, + self.projected_equil.n3, self.projected_equil.s3_diatomic, out=self._init_dener_ds ) else: raise ValueError("Gamma should be 7/5 or 5/3 for if you want to linearize") @@ -4101,91 +4168,58 @@ class VariationalMagFieldEvolve(Propagator): """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - self._b: FEECVariable = None - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1vec" - self._u = new - - @property - def b(self) -> FEECVariable: - return self._b - - @b.setter - def b(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hdiv" - self._b = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - OptsModel = Literal["full", "full_p", "linear"] - # propagator options - model: OptsModel = "full" - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - nonlin_solver: NonlinearSolverParameters = None - - def __post_init__(self): - # checks - check_option(self.model, self.OptsModel) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + @staticmethod + def options(default=False): + dct = {} + dct["lin_solver"] = { + "tol": 1e-12, + "maxiter": 500, + "non_linear_maxiter": 100, + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "verbose": False, + } + dct["nonlin_solver"] = { + "tol": 1e-8, + "maxiter": 100, + "info": False, + "linearize": False, + } - if self.nonlin_solver is None: - self.nonlin_solver = NonlinearSolverParameters(type="Newton") + if default: + dct = descend_options_dict(dct, []) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + b: BlockVector, + u: BlockVector, + *, + model: str = "full", + mass_ops: H1vecMassMatrix_density, + lin_solver: dict = options(default=True)["lin_solver"], + nonlin_solver: dict = options(default=True)["nonlin_solver"], + ): + super().__init__(b, u) - @profile - def allocate(self): - self._model = self.options.model - self._lin_solver = self.options.solver_params - self._nonlin_solver = self.options.nonlin_solver - self._linearize = self._nonlin_solver.linearize + assert model in ["full", "full_p", "linear"] + self._model = model + self._mass_ops = mass_ops + self._lin_solver = lin_solver + self._nonlin_solver = nonlin_solver + self._linearize = self._nonlin_solver["linearize"] - self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = self.mass_ops.WMM - self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) + self._Mrho = mass_ops # Projector self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop - u = self.variables.u.spline.vector - b = self.variables.b.spline.vector - self._tmp_un1 = u.space.zeros() self._tmp_un2 = u.space.zeros() self._tmp_un12 = u.space.zeros() @@ -4216,8 +4250,8 @@ def __call_newton(self, dt): print() print("Newton iteration in VariationalMagFieldEvolve") # Compute implicit approximation of s^{n+1} - un = self.variables.u.spline.vector - bn = self.variables.b.spline.vector + bn = self.feec_vars[0] + un = self.feec_vars[1] bn1 = bn.copy(out=self._tmp_bn1) # Initialize variable for Newton iteration @@ -4230,10 +4264,10 @@ def __call_newton(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver.tol + tol = self._nonlin_solver["tol"] err = tol + 1 - for it in range(self._nonlin_solver.maxiter): + for it in range(self._nonlin_solver["maxiter"]): # Newton iteration # half time step approximation bn12 = bn.copy(out=self._tmp_bn12) @@ -4294,7 +4328,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or xp.isnan(err): + if err < tol**2 or np.isnan(err): break # Derivative for Newton @@ -4316,18 +4350,20 @@ def __call_newton(self, dt): # Multiply by the mass matrix to get the momentum mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalMagFieldEvolve reached - not converged:\n {err =} \n {tol**2 =}", + f"!!!Warning: Maximum iteration in VariationalMagFieldEvolve reached - not converged:\n {err = } \n {tol**2 = }", ) self._tmp_un_diff = un1 - un self._tmp_bn_diff = bn1 - bn - self.update_feec_variables(b=bn1, u=un1) + self.feec_vars_update(bn1, un1) def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" + from struphy.feec.variational_utilities import Hdiv0_transport_operator + self.curlPib = Hdiv0_transport_operator(self.derham) self.curlPibT = self.curlPib.T @@ -4376,13 +4412,15 @@ def _initialize_projectors_and_mass(self): self._Jacobian[1, 0] = self._dt2_curlPib self._Jacobian[1, 1] = self._I2 + from struphy.linear_algebra.schur_solver import SchurSolverFull + self._inv_Jacobian = SchurSolverFull( self._Jacobian, - self.options.solver, + self._lin_solver["type"][0], pc=self._Mrho.inv, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, - verbose=self._lin_solver.verbose, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], + verbose=self._lin_solver["verbose"], recycle=True, ) @@ -4400,6 +4438,8 @@ def _update_Pib(self, b): self.curlPibT.update_coeffs(b) def _create_Pib0(self): + from struphy.feec.variational_utilities import Hdiv0_transport_operator + self.curlPib0 = Hdiv0_transport_operator(self.derham) self.curlPibT0 = self.curlPib0.T @@ -4476,139 +4516,81 @@ class VariationalPBEvolve(Propagator): where weights in the the :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperator` and the :class:`~struphy.feec.mass.WeightedMassOperator` are given by - .. math:: - - \hat{\mathbf{B}}_h^{n+1/2} = (\mathbf{b}^{n+\frac{1}{2}})^\top \vec{\boldsymbol \Lambda}^2 \in V_h^2 \, - \qquad \hat{\rho}_h^{n} = (\boldsymbol \rho^{n})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \, - \qquad \hat{p}_h^{n+1/2} = (\boldsymbol p^{n+1/2})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \,. - - and :math:`\mathcal{U}^v` is :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators`. - """ - - class Variables: - def __init__(self): - self._p: FEECVariable = None - self._u: FEECVariable = None - self._b: FEECVariable = None - - @property - def p(self) -> FEECVariable: - return self._p - - @p.setter - def p(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._p = new - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1vec" - self._u = new - - @property - def b(self) -> FEECVariable: - return self._b - - @b.setter - def b(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hdiv" - self._b = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsModel = Literal["full_p", "linear", "deltaf"] - # propagator options - model: OptsModel = "full_p" - gamma: float = 5.0 / 3.0 - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - nonlin_solver: NonlinearSolverParameters = None - div_u: FEECVariable = None - u2: FEECVariable = None - pt3: FEECVariable = None - bt2: FEECVariable = None - - def __post_init__(self): - # checks - check_option(self.model, self.OptsModel) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + .. math:: - if self.nonlin_solver is None: - self.nonlin_solver = NonlinearSolverParameters() + \hat{\mathbf{B}}_h^{n+1/2} = (\mathbf{b}^{n+\frac{1}{2}})^\top \vec{\boldsymbol \Lambda}^2 \in V_h^2 \, + \qquad \hat{\rho}_h^{n} = (\boldsymbol \rho^{n})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \, + \qquad \hat{p}_h^{n+1/2} = (\boldsymbol p^{n+1/2})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \,. - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + and :math:`\mathcal{U}^v` is :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators`. + """ - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + @staticmethod + def options(default=False): + dct = {} + dct["lin_solver"] = { + "tol": 1e-12, + "maxiter": 500, + "non_linear_maxiter": 100, + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "verbose": False, + } + dct["nonlin_solver"] = { + "tol": 1e-8, + "maxiter": 100, + "type": ["Picard"], + "info": False, + "linearize": False, + } + dct["physics"] = {"gamma": 5 / 3} - @profile - def allocate(self): - self._model = self.options.model - self._lin_solver = self.options.solver_params - self._nonlin_solver = self.options.nonlin_solver - self._linearize = self.options.nonlin_solver.linearize - self._gamma = self.options.gamma - - if self.options.div_u is None: - self._divu = None - else: - self._divu = self.options.div_u.spline.vector + if default: + dct = descend_options_dict(dct, []) - if self.options.u2 is None: - self._u2 = None - else: - self._u2 = self.options.u2.spline.vector + return dct - if self.options.pt3 is None: - self._pt3 = None - else: - self._pt3 = self.options.pt3.spline.vector + def __init__( + self, + p: StencilVector, + b: BlockVector, + u: BlockVector, + *, + model: str = "full", + gamma: float = options()["physics"]["gamma"], + mass_ops: H1vecMassMatrix_density, + lin_solver: dict = options(default=True)["lin_solver"], + nonlin_solver: dict = options(default=True)["nonlin_solver"], + div_u: StencilVector | None = None, + u2: BlockVector | None = None, + pt3: StencilVector | None = None, + bt2: BlockVector | None = None, + ): + super().__init__(p, b, u) - if self.options.bt2 is None: - self._bt2 = None - else: - self._bt2 = self.options.bt2.spline.vector + assert model in ["full_p", "linear", "deltaf"] + self._model = model + self._mass_ops = mass_ops + self._lin_solver = lin_solver + self._nonlin_solver = nonlin_solver + self._linearize = self._nonlin_solver["linearize"] + self._gamma = gamma - self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + self._divu = div_u + self._u2 = u2 + self._pt3 = pt3 + self._bt2 = bt2 - self._Mrho = self.mass_ops.WMM - self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) + self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) + + self._Mrho = mass_ops # Projector self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop - u = self.variables.u.spline.vector - p = self.variables.p.spline.vector - b = self.variables.b.spline.vector - self._tmp_un1 = u.space.zeros() self._tmp_un2 = u.space.zeros() self._tmp_un12 = u.space.zeros() @@ -4642,7 +4624,7 @@ def allocate(self): self._extracted_b2 = self.derham.extraction_ops["2"].dot(self.projected_equil.b2) def __call__(self, dt): - if self._nonlin_solver.type == "Picard": + if self._nonlin_solver["type"] == "Picard": self.__call_picard(dt) else: raise ValueError("Only Picard solver is implemented for VariationalPBEvolve") @@ -4654,9 +4636,9 @@ def __call_picard(self, dt): print() print("Newton iteration in VariationalPBEvolve") - un = self.variables.u.spline.vector - pn = self.variables.p.spline.vector - bn = self.variables.b.spline.vector + pn = self.feec_vars[0] + bn = self.feec_vars[1] + un = self.feec_vars[2] self._update_Pib(bn) self._update_Projp(pn) @@ -4669,10 +4651,10 @@ def __call_picard(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver.tol + tol = self._nonlin_solver["tol"] err = tol + 1 - for it in range(self._nonlin_solver.maxiter): + for it in range(self._nonlin_solver["maxiter"]): # Picard iteration # half time step approximation @@ -4797,7 +4779,7 @@ def __call_picard(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or xp.isnan(err): + if err < tol**2 or np.isnan(err): break # Derivative for Newton @@ -4819,15 +4801,15 @@ def __call_picard(self, dt): # Multiply by the mass matrix to get the momentum mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalPBEvolve reached - not converged:\n {err =} \n {tol**2 =}", + f"!!!Warning: Maximum iteration in VariationalPBEvolve reached - not converged:\n {err = } \n {tol**2 = }", ) self._tmp_un_diff = un1 - un self._tmp_bn_diff = bn1 - bn self._tmp_pn_diff = pn1 - pn - self.update_feec_variables(p=pn1, b=bn1, u=un1) + self.feec_vars_update(pn1, bn1, un1) self._transop_p.div.dot(un12, out=self._divu) self._transop_p._Uv.dot(un1, out=self._u2) @@ -4853,6 +4835,9 @@ def __call_picard(self, dt): def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import Hdiv0_transport_operator, Pressure_transport_operator + self.curlPib = Hdiv0_transport_operator(self.derham) self.curlPibT = self.curlPib.T self._transop_p = Pressure_transport_operator(self.derham, self.domain, self.basis_ops.Uv, self._gamma) @@ -4868,7 +4853,7 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid = np.zeros(grid_shape, dtype=float) # Inverse mass matrix needed to compute the error self.pc_Mv = preconditioner.MassMatrixDiagonalPreconditioner( @@ -4940,11 +4925,11 @@ def _initialize_projectors_and_mass(self): self._inv_Jacobian = SchurSolverFull( self._Jacobian, - self.options.solver, + self._lin_solver["type"][0], pc=self._Mrho.inv, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, - verbose=self._lin_solver.verbose, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], + verbose=self._lin_solver["verbose"], recycle=True, ) @@ -4963,6 +4948,8 @@ def _update_Pib(self, b): self.curlPibT.update_coeffs(b) def _create_Pib0(self): + from struphy.feec.variational_utilities import Hdiv0_transport_operator + self.curlPib0 = Hdiv0_transport_operator(self.derham) self.curlPibT0 = self.curlPib.T self.curlPib0.update_coeffs(self.projected_equil.b2) @@ -4975,6 +4962,7 @@ def _update_Projp(self, p): def _create_transop0(self): """Update the weights of the `BasisProjectionOperator`""" + from struphy.feec.variational_utilities import Pressure_transport_operator self._transop_p0 = Pressure_transport_operator(self.derham, self.domain, self.basis_ops.Uv, self._gamma) self._transop_p0T = self._transop_p0.T @@ -5083,130 +5071,72 @@ class VariationalQBEvolve(Propagator): and :math:`\mathcal{U}^v` is :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators`. """ - class Variables: - def __init__(self): - self._q: FEECVariable = None - self._u: FEECVariable = None - self._b: FEECVariable = None - - @property - def q(self) -> FEECVariable: - return self._q - - @q.setter - def q(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._q = new - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1vec" - self._u = new - - @property - def b(self) -> FEECVariable: - return self._b - - @b.setter - def b(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hdiv" - self._b = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsModel = Literal["full_q", "linear_q", "deltaf_q"] - # propagator options - model: OptsModel = "full_q" - gamma: float = 5.0 / 3.0 - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - nonlin_solver: NonlinearSolverParameters = None - div_u: FEECVariable = None - u2: FEECVariable = None - qt3: FEECVariable = None - bt2: FEECVariable = None - - def __post_init__(self): - # checks - check_option(self.model, self.OptsModel) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - if self.nonlin_solver is None: - self.nonlin_solver = NonlinearSolverParameters() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + @staticmethod + def options(default=False): + dct = {} + dct["lin_solver"] = { + "tol": 1e-12, + "maxiter": 500, + "non_linear_maxiter": 100, + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "verbose": False, + } + dct["nonlin_solver"] = { + "tol": 1e-8, + "maxiter": 100, + "type": ["Picard"], + "info": False, + "linearize": False, + } + dct["physics"] = {"gamma": 5 / 3} - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + if default: + dct = descend_options_dict(dct, []) - @profile - def allocate(self): - self._model = self.options.model - self._lin_solver = self.options.solver_params - self._nonlin_solver = self.options.nonlin_solver - self._linearize = self.options.nonlin_solver.linearize - self._gamma = self.options.gamma - - if self.options.div_u is None: - self._divu = None - else: - self._divu = self.options.div_u.spline.vector + return dct - if self.options.u2 is None: - self._u2 = None - else: - self._u2 = self.options.u2.spline.vector + def __init__( + self, + q: StencilVector, + b: BlockVector, + u: BlockVector, + *, + model: str = "full", + gamma: float = options()["physics"]["gamma"], + mass_ops: H1vecMassMatrix_density, + lin_solver: dict = options(default=True)["lin_solver"], + nonlin_solver: dict = options(default=True)["nonlin_solver"], + div_u: StencilVector | None = None, + u2: BlockVector | None = None, + qt3: StencilVector | None = None, + bt2: BlockVector | None = None, + ): + super().__init__(q, b, u) - if self.options.qt3 is None: - self._qt3 = None - else: - self._qt3 = self.options.qt3.spline.vector + assert model in ["full_q", "linear_q", "deltaf_q"] + self._model = model + self._mass_ops = mass_ops + self._lin_solver = lin_solver + self._nonlin_solver = nonlin_solver + self._linearize = self._nonlin_solver["linearize"] + self._gamma = gamma - if self.options.bt2 is None: - self._bt2 = None - else: - self._bt2 = self.options.bt2.spline.vector + self._divu = div_u + self._u2 = u2 + self._qt3 = qt3 + self._bt2 = bt2 - self._info = self._nonlin_solver.info and (self.rank == 0) + self._info = self._nonlin_solver["info"] and (self.rank == 0) - self._Mrho = self.mass_ops.WMM - self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) + self._Mrho = mass_ops # Projector self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop - u = self.variables.u.spline.vector - q = self.variables.q.spline.vector - b = self.variables.b.spline.vector - self._tmp_un1 = u.space.zeros() self._tmp_un12 = u.space.zeros() self._tmp_bn1 = b.space.zeros() @@ -5238,7 +5168,7 @@ def allocate(self): self._extracted_q3 = self.derham.extraction_ops["3"].dot(self.projected_equil.q3) def __call__(self, dt): - if self._nonlin_solver.type == "Picard": + if self._nonlin_solver["type"] == "Picard": self.__call_picard(dt) else: raise ValueError("Only Picard solver is implemented for VariationalQBEvolve") @@ -5250,9 +5180,9 @@ def __call_picard(self, dt): print() print("Newton iteration in VariationalQBEvolve") - un = self.variables.u.spline.vector - qn = self.variables.q.spline.vector - bn = self.variables.b.spline.vector + qn = self.feec_vars[0] + bn = self.feec_vars[1] + un = self.feec_vars[2] self._update_Pib(bn) self._update_Projq(qn) @@ -5265,10 +5195,10 @@ def __call_picard(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver.tol + tol = self._nonlin_solver["tol"] err = tol + 1 - for it in range(self._nonlin_solver.maxiter): + for it in range(self._nonlin_solver["maxiter"]): # Picard iteration # half time step approximation @@ -5386,7 +5316,7 @@ def __call_picard(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or xp.isnan(err): + if err < tol**2 or np.isnan(err): break # Derivative for Newton @@ -5410,15 +5340,15 @@ def __call_picard(self, dt): # Multiply by the mass matrix to get the momentum mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalPBEvolve reached - not converged:\n {err =} \n {tol**2 =}", + f"!!!Warning: Maximum iteration in VariationalPBEvolve reached - not converged:\n {err = } \n {tol**2 = }", ) self._tmp_un_diff = un1 - un self._tmp_bn_diff = bn1 - bn self._tmp_qn_diff = qn1 - qn - self.update_feec_variables(q=qn1, b=bn1, u=un1) + self.feec_vars_update(qn1, bn1, un1) self._transop_q.div.dot(un12, out=self._divu) self._transop_q._Uv.dot(un1, out=self._u2) @@ -5444,6 +5374,9 @@ def __call_picard(self, dt): def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" + from struphy.feec.projectors import L2Projector + from struphy.feec.variational_utilities import Hdiv0_transport_operator, Pressure_transport_operator + self.curlPib = Hdiv0_transport_operator(self.derham) self.curlPibT = self.curlPib.T self._transop_q = Pressure_transport_operator(self.derham, self.domain, self.basis_ops.Uv, self._gamma / 2.0) @@ -5459,7 +5392,7 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid = np.zeros(grid_shape, dtype=float) # Inverse mass matrix needed to compute the error self.pc_Mv = preconditioner.MassMatrixDiagonalPreconditioner( @@ -5550,11 +5483,11 @@ def _initialize_projectors_and_mass(self): self._inv_Jacobian = SchurSolverFull3( self._Jacobian, - self.options.solver, + self._lin_solver["type"][0], pc=self._Mrho.inv, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, - verbose=self._lin_solver.verbose, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], + verbose=self._lin_solver["verbose"], recycle=True, ) @@ -5573,6 +5506,8 @@ def _update_Pib(self, b): self.curlPibT.update_coeffs(b) def _create_Pib0(self): + from struphy.feec.variational_utilities import Hdiv0_transport_operator + self.curlPib0 = Hdiv0_transport_operator(self.derham) self.curlPibT0 = self.curlPib.T self.curlPib0.update_coeffs(self.projected_equil.b2) @@ -5585,6 +5520,7 @@ def _update_Projq(self, q): def _create_transop0(self): """Update the weights of the `BasisProjectionOperator`""" + from struphy.feec.variational_utilities import Pressure_transport_operator self._transop_q0 = Pressure_transport_operator(self.derham, self.domain, self.basis_ops.Uv, self._gamma / 2.0) self._transop_q0T = self._transop_q0.T @@ -5689,95 +5625,72 @@ class VariationalViscosity(Propagator): """ - class Variables: - def __init__(self): - self._s: FEECVariable = None - self._u: FEECVariable = None - - @property - def s(self) -> FEECVariable: - return self._s - - @s.setter - def s(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._s = new - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1vec" - self._u = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsModel = Literal["full", "full_p", "full_q", "linear_p", "linear_q", "deltaf_q"] - # propagator options - model: OptsModel = "full" - gamma: float = 5.0 / 3.0 - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixDiagonalPreconditioner" - solver_params: SolverParameters = None - nonlin_solver: NonlinearSolverParameters = None - rho: FEECVariable = None - pt3: FEECVariable = None - mu: float = 0.0 - mu_a: float = 0.0 - alpha: float = 0.0 - - def __post_init__(self): - # checks - check_option(self.model, self.OptsModel) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) + @staticmethod + def options(default=False): + dct = {} + dct["lin_solver"] = { + "tol": 1e-12, + "maxiter": 500, + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "verbose": False, + } + dct["nonlin_solver"] = { + "tol": 1e-8, + "maxiter": 100, + "type": ["Newton"], + "info": False, + "fast": False, + } + dct["physics"] = { + "gamma": 1.66666666667, + "mu": 0.0, + "mu_a": 0.0, + "alpha": 0.0, + } - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + if default: + dct = descend_options_dict(dct, []) - if self.nonlin_solver is None: - self.nonlin_solver = NonlinearSolverParameters(type="Newton") + return dct - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + def __init__( + self, + s: StencilVector, + u: BlockVector, + *, + model: str = "barotropic", + gamma: float = options()["physics"]["gamma"], + rho: StencilVector, + mu: float = options()["physics"]["mu"], + mu_a: float = options()["physics"]["mu_a"], + alpha: float = options()["physics"]["alpha"], + mass_ops: H1vecMassMatrix_density, + lin_solver: dict = options(default=True)["lin_solver"], + nonlin_solver: dict = options(default=True)["nonlin_solver"], + energy_evaluator: InternalEnergyEvaluator = None, + pt3: StencilVector | None = None, + ): + super().__init__(s, u) - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + assert model in ["full", "full_p", "full_q", "linear_p", "linear_q", "deltaf_q"] - @profile - def allocate(self): - self._model = self.options.model - self._gamma = self.options.gamma - self._lin_solver = self.options.solver_params - self._nonlin_solver = self.options.nonlin_solver - self._mu_a = self.options.mu_a - self._alpha = self.options.alpha - self._mu = self.options.mu - self._rho = self.options.rho - self._pt3 = self.options.pt3 + self._model = model + self._gamma = gamma + self._lin_solver = lin_solver + self._nonlin_solver = nonlin_solver + self._mu_a = mu_a + self._alpha = alpha + self._mu = mu + self._rho = rho + self._pt3 = pt3 + self._energy_evaluator = energy_evaluator - self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = self.mass_ops.WMM - self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) + self._Mrho = mass_ops # Femfields for the projector self.sf = self.derham.create_spline_function("sf", "L2") @@ -5792,13 +5705,9 @@ def allocate(self): self.gu122f = self.derham.create_spline_function("gu122", "Hcurl") # Projector - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop - u = self.variables.u.spline.vector - s = self.variables.s.spline.vector - self._tmp_un1 = u.space.zeros() self._tmp_un12 = u.space.zeros() self._tmp_sn1 = s.space.zeros() @@ -5815,7 +5724,7 @@ def allocate(self): self.tot_rhs = s.space.zeros() def __call__(self, dt): - if self._nonlin_solver.type == "Newton": + if self._nonlin_solver["type"] == "Newton": self.__call_newton(dt) else: raise ValueError( @@ -5825,11 +5734,10 @@ def __call__(self, dt): def __call_newton(self, dt): """Solve the non linear system for updating the variables using Newton iteration method""" # Compute dissipation implicitely - sn = self.variables.s.spline.vector - un = self.variables.u.spline.vector - + sn = self.feec_vars[0] + un = self.feec_vars[1] if self._mu < 1.0e-15 and self._mu_a < 1.0e-15 and self._alpha < 1.0e-15: - self.update_feec_variables(s=sn, u=un) + self.feec_vars_update(sn, un) return if self._info: @@ -5847,7 +5755,7 @@ def __call_newton(self, dt): print("information on the linear solver : ", self.inv_lop._info) if self._model == "linear_p" or (self._model == "linear_q" and self._nonlin_solver["fast"]): - self.update_feec_variables(s=sn, u=un1) + self.feec_vars_update(sn, un1) return # Energy balance term @@ -5856,7 +5764,7 @@ def __call_newton(self, dt): # 2) Initial energy and linear form rho = self._rho if self._model in ["deltaf_q", "linear_q"]: - self.sf.vector = self._pt3.spline.vector + self.sf.vector = self._pt3 else: self.sf.vector = sn @@ -5912,7 +5820,7 @@ def __call_newton(self, dt): for it in range(self._nonlin_solver["maxiter"]): if self._model in ["deltaf_q", "linear_q"]: - self.sf1.vector = self._pt3.spline.vector + self.sf1.vector = self._pt3 else: self.sf1.vector = sn1 @@ -5964,7 +5872,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if (err < tol**2 and it > 0) or xp.isnan(err): + if (err < tol**2 and it > 0) or np.isnan(err): # force at least one iteration break @@ -6002,16 +5910,18 @@ def __call_newton(self, dt): else: sn1 += incr - if it == self._nonlin_solver["maxiter"] - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalViscosity reached - not converged:\n {err =} \n {tol**2 =}", + f"!!!Warning: Maximum iteration in VariationalViscosity reached - not converged:\n {err = } \n {tol**2 = }", ) - self.update_feec_variables(s=sn1, u=un1) + self.feec_vars_update(sn1, un1) def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" + from struphy.feec.projectors import L2Projector + Xv = getattr(self.basis_ops, "Xv") Pcoord0 = CoordinateProjector( 0, @@ -6046,12 +5956,12 @@ def _initialize_projectors_and_mass(self): self.M_de_ds = self.mass_ops.create_weighted_mass("L2", "L2") - if self.options.precond is None: + if self._lin_solver["type"][1] is None: self.pc_jac = None else: pc_class = getattr( preconditioner, - self.options.precond, + self._lin_solver["type"][1], ) self.pc_jac = pc_class(self.M_de_ds) @@ -6059,8 +5969,8 @@ def _initialize_projectors_and_mass(self): self.M_de_ds, "pcg", pc=self.pc_jac, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], verbose=False, recycle=True, ) @@ -6101,8 +6011,8 @@ def _initialize_projectors_and_mass(self): self.l_op, "pcg", pc=self._Mrho.inv, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], verbose=False, recycle=True, ) @@ -6138,35 +6048,35 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._guf0_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf2_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf0_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf2_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf120_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf121_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf122_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf120_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf121_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf122_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._uf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._uf12_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf12_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._gu_sq_values = xp.zeros(grid_shape, dtype=float) - self._u_sq_values = xp.zeros(grid_shape, dtype=float) - self._gu_init_values = xp.zeros(grid_shape, dtype=float) + self._gu_sq_values = np.zeros(grid_shape, dtype=float) + self._u_sq_values = np.zeros(grid_shape, dtype=float) + self._gu_init_values = np.zeros(grid_shape, dtype=float) - self._sf_values = xp.zeros(grid_shape, dtype=float) - self._sf1_values = xp.zeros(grid_shape, dtype=float) - self._rhof_values = xp.zeros(grid_shape, dtype=float) + self._sf_values = np.zeros(grid_shape, dtype=float) + self._sf1_values = np.zeros(grid_shape, dtype=float) + self._rhof_values = np.zeros(grid_shape, dtype=float) - self._e_n1 = xp.zeros(grid_shape, dtype=float) - self._e_n = xp.zeros(grid_shape, dtype=float) + self._e_n1 = np.zeros(grid_shape, dtype=float) + self._e_n = np.zeros(grid_shape, dtype=float) - self._de_s1_values = xp.zeros(grid_shape, dtype=float) + self._de_s1_values = np.zeros(grid_shape, dtype=float) - self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid = np.zeros(grid_shape, dtype=float) gam = self._gamma if self._model == "full": - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -6174,7 +6084,7 @@ def _initialize_projectors_and_mass(self): ) self._mass_metric_term = deepcopy(metric) - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -6207,7 +6117,7 @@ def _initialize_projectors_and_mass(self): self.pc_jac.update_mass_operator(self.M_de_ds) elif self._model in ["full_q", "linear_q", "deltaf_q"]: - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -6215,7 +6125,7 @@ def _initialize_projectors_and_mass(self): ) self._mass_metric_term = deepcopy(metric) - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -6223,7 +6133,7 @@ def _initialize_projectors_and_mass(self): ) self._energy_metric = deepcopy(metric) - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -6289,7 +6199,7 @@ def _update_artificial_viscosity(self, un, dt): gu_sq_v += gu1_v[i] gu_sq_v += gu2_v[i] - xp.sqrt(gu_sq_v, out=gu_sq_v) + np.sqrt(gu_sq_v, out=gu_sq_v) gu_sq_v *= dt * self._mu_a # /2 @@ -6447,92 +6357,63 @@ class VariationalResistivity(Propagator): """ - class Variables: - def __init__(self): - self._s: FEECVariable = None - self._b: FEECVariable = None - - @property - def s(self) -> FEECVariable: - return self._s - - @s.setter - def s(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._s = new - - @property - def b(self) -> FEECVariable: - return self._b - - @b.setter - def b(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hdiv" - self._b = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsModel = Literal["full", "full_p", "full_q", "linear_p", "linear_q", "deltaf_q"] - # propagator options - model: OptsModel = "full" - gamma: float = 5.0 / 3.0 - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixDiagonalPreconditioner" - solver_params: SolverParameters = None - nonlin_solver: NonlinearSolverParameters = None - linearize_current: bool = False - rho: FEECVariable = None - pt3: FEECVariable = None - eta: float = 0.0 - eta_a: float = 0.0 + @staticmethod + def options(default=False): + dct = {} + dct["lin_solver"] = { + "tol": 1e-12, + "maxiter": 500, + "type": [ + ("pcg", "MassMatrixDiagonalPreconditioner"), + ("cg", None), + ], + "verbose": False, + } + dct["nonlin_solver"] = {"tol": 1e-8, "maxiter": 100, "type": ["Newton"], "info": False, "fast": False} + dct["physics"] = { + "eta": 0.0, + "eta_a": 0.0, + "gamma": 5 / 3, + } + dct["linearize_current"] = False - def __post_init__(self): - # checks - check_option(self.model, self.OptsModel) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) + if default: + dct = descend_options_dict(dct, []) - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + return dct - if self.nonlin_solver is None: - self.nonlin_solver = NonlinearSolverParameters(type="Newton") + def __init__( + self, + s: StencilVector, + b: BlockVector, + *, + model: str = "full", + gamma: float = options()["physics"]["gamma"], + rho: StencilVector, + eta: float = options()["physics"]["eta"], + eta_a: float = options()["physics"]["eta_a"], + lin_solver: dict = options(default=True)["lin_solver"], + nonlin_solver: dict = options(default=True)["nonlin_solver"], + linearize_current: dict = options(default=True)["linearize_current"], + energy_evaluator: InternalEnergyEvaluator = None, + pt3: StencilVector | None = None, + ): + super().__init__(s, b) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + assert model in ["full", "full_p", "full_q", "linear_p", "delta_p", "linear_q", "deltaf_q"] - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + self._energy_evaluator = energy_evaluator + self._model = model + self._gamma = gamma + self._eta = eta + self._eta_a = eta_a + self._lin_solver = lin_solver + self._nonlin_solver = nonlin_solver + self._rho = rho + self._linearize_current = linearize_current + self._pt3 = pt3 - @profile - def allocate(self): - self._model = self.options.model - self._gamma = self.options.gamma - self._eta = self.options.eta - self._eta_a = self.options.eta_a - self._lin_solver = self.options.solver_params - self._nonlin_solver = self.options.nonlin_solver - self._linearize_current = self.options.linearize_current - self._rho = self.options.rho - self._pt3 = self.options.pt3 - - self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) # Femfields for the projector self.rhof = self.derham.create_spline_function("rhof", "L2") @@ -6544,13 +6425,9 @@ def allocate(self): self.cbf12 = self.derham.create_spline_function("cBf", "Hcurl") # Projector - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop - s = self.variables.s.spline.vector - b = self.variables.b.spline.vector - self._tmp_bn1 = b.space.zeros() self._tmp_bn12 = b.space.zeros() self._tmp_sn1 = s.space.zeros() @@ -6567,7 +6444,7 @@ def allocate(self): ) def __call__(self, dt): - if self._nonlin_solver.type == "Newton": + if self._nonlin_solver["type"] == "Newton": self.__call_newton(dt) else: raise ValueError( @@ -6577,11 +6454,10 @@ def __call__(self, dt): def __call_newton(self, dt): """Solve the non linear system for updating the variables using Newton iteration method""" # Compute dissipation implicitely - sn = self.variables.s.spline.vector - bn = self.variables.b.spline.vector - + sn = self.feec_vars[0] + bn = self.feec_vars[1] if self._eta < 1.0e-15 and self._eta_a < 1.0e-15: - self.update_feec_variables(s=sn, b=bn) + self.feec_vars_update(sn, bn) return if self._info: @@ -6608,17 +6484,17 @@ def __call_newton(self, dt): print("information on the linear solver : ", self.inv_lop._info) if self._model == "linear_p" or (self._model == "linear_q" and self._nonlin_solver["fast"]): - self.update_feec_variables(s=sn, b=bn1) + self.feec_vars_update(sn, bn1) return # Energy balance term # 1) Pointwize energy change energy_change = self._get_energy_change(bn, bn1, total_resistivity) # 2) Initial energy and linear form - rho = self._rho.spline.vector + rho = self._rho self.rhof.vector = rho if self._model in ["deltaf_q", "linear_q"]: - self.sf.vector = self._pt3.spline.vector + self.sf.vector = self._pt3 else: self.sf.vector = sn @@ -6678,7 +6554,7 @@ def __call_newton(self, dt): for it in range(self._nonlin_solver["maxiter"]): if self._model in ["deltaf_q", "linear_q"]: - self.sf1.vector = self._pt3.spline.vector + self.sf1.vector = self._pt3 else: self.sf1.vector = sn1 @@ -6730,7 +6606,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if (err < tol**2 and it > 0) or xp.isnan(err): + if (err < tol**2 and it > 0) or np.isnan(err): break if self._model == "full": @@ -6766,12 +6642,12 @@ def __call_newton(self, dt): else: sn1 += incr - if it == self._nonlin_solver["maxiter"] - 1 or xp.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalResistivity reached - not converged:\n {err =} \n {tol**2 =}", + f"!!!Warning: Maximum iteration in VariationalResistivity reached - not converged:\n {err = } \n {tol**2 = }", ) - self.update_feec_variables(s=sn1, b=bn1) + self.feec_vars_update(sn1, bn1) # if self._pt3 is not None: # bn12 = bn.copy(out=self._tmp_bn12) @@ -6849,7 +6725,7 @@ def __call_newton(self, dt): # if self._info: # print("iteration : ", it, " error : ", err) - # if (err < tol**2 and it > 0) or xp.isnan(err): + # if (err < tol**2 and it > 0) or np.isnan(err): # break # incr = self.inv_jac.dot(self.tot_rhs, out=self._tmp_sn_incr) @@ -6862,6 +6738,8 @@ def __call_newton(self, dt): def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" + from struphy.feec.projectors import L2Projector + pc_M1 = preconditioner.MassMatrixDiagonalPreconditioner( self.mass_ops.M1, ) @@ -6892,12 +6770,12 @@ def _initialize_projectors_and_mass(self): D = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] self.M1_cb = self.mass_ops.create_weighted_mass("Hcurl", "Hcurl", weights=[D, "sqrt_g"]) - if self.options.precond is None: + if self._lin_solver["type"][1] is None: self.pc = None else: pc_class = getattr( preconditioner, - self.options.precond, + self._lin_solver["type"][1], ) self.pc_jac = pc_class(self.M_de_ds) @@ -6905,8 +6783,8 @@ def _initialize_projectors_and_mass(self): self.M_de_ds, "pcg", pc=self.pc_jac, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], verbose=False, recycle=True, ) @@ -6922,12 +6800,12 @@ def _initialize_projectors_and_mass(self): self.r_op = M2 # - self._scaled_stiffness self.l_op = M2 + self._scaled_stiffness + self.phy_cb_stiffness - if self.options.precond is None: + if self._lin_solver["type"][1] is None: self.pc = None else: pc_class = getattr( preconditioner, - self.options.precond, + self._lin_solver["type"][1], ) self.pc = pc_class(M2) @@ -6935,8 +6813,8 @@ def _initialize_projectors_and_mass(self): self.l_op, "pcg", pc=self.pc, - tol=self._lin_solver.tol, - maxiter=self._lin_solver.maxiter, + tol=self._lin_solver["tol"], + maxiter=self._lin_solver["maxiter"], verbose=False, recycle=True, ) @@ -6962,26 +6840,26 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._cb12_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._cb1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._cb12_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._cb1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._cb_sq_values = xp.zeros(grid_shape, dtype=float) - self._cb_sq_values_init = xp.zeros(grid_shape, dtype=float) + self._cb_sq_values = np.zeros(grid_shape, dtype=float) + self._cb_sq_values_init = np.zeros(grid_shape, dtype=float) - self._sf_values = xp.zeros(grid_shape, dtype=float) - self._sf1_values = xp.zeros(grid_shape, dtype=float) - self._rhof_values = xp.zeros(grid_shape, dtype=float) + self._sf_values = np.zeros(grid_shape, dtype=float) + self._sf1_values = np.zeros(grid_shape, dtype=float) + self._rhof_values = np.zeros(grid_shape, dtype=float) - self._e_n1 = xp.zeros(grid_shape, dtype=float) - self._e_n = xp.zeros(grid_shape, dtype=float) + self._e_n1 = np.zeros(grid_shape, dtype=float) + self._e_n = np.zeros(grid_shape, dtype=float) - self._de_s1_values = xp.zeros(grid_shape, dtype=float) + self._de_s1_values = np.zeros(grid_shape, dtype=float) - self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid = np.zeros(grid_shape, dtype=float) gam = self._gamma if self._model == "full": - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -6989,7 +6867,7 @@ def _initialize_projectors_and_mass(self): ) self._mass_metric_term = deepcopy(metric) - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -7022,7 +6900,7 @@ def _initialize_projectors_and_mass(self): self.pc_jac.update_mass_operator(self.M_de_ds) elif self._model in ["full_q", "linear_q", "deltaf_q"]: - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -7030,7 +6908,7 @@ def _initialize_projectors_and_mass(self): ) self._mass_metric_term = deepcopy(metric) - metric = xp.power( + metric = np.power( self.domain.jacobian_det( *integration_grid, ), @@ -7077,7 +6955,7 @@ def _update_artificial_resistivity(self, bn, dt): for j in range(3): cb_sq_v += cb_v[i] * self._sq_term_metric_no_jac[i, j] * cb_v[j] - xp.sqrt(cb_sq_v, out=cb_sq_v) + np.sqrt(cb_sq_v, out=cb_sq_v) cb_sq_v *= dt * self._eta_a @@ -7175,74 +7053,48 @@ class TimeDependentSource(Propagator): * :math:`h(\omega t) = \sin(\omega t)` """ - class Variables: - def __init__(self): - self._source: FEECVariable = None - - @property - def source(self) -> FEECVariable: - return self._source - - @source.setter - def source(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1" - self._source = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsTimeSource = Literal["cos", "sin"] - # propagator options - omega: float = 2.0 * xp.pi - hfun: OptsTimeSource = "cos" - - def __post_init__(self): - # checks - check_option(self.hfun, self.OptsTimeSource) - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + @staticmethod + def options(default=False): + dct = {} + dct["omega"] = 1.0 + dct["hfun"] = ["cos", "sin"] + if default: + dct = descend_options_dict(dct, []) + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + c: StencilVector, + *, + omega: float = options()["omega"], + hfun: str = options(default=True)["hfun"], + ): + super().__init__(c) - @profile - def allocate(self): - if self.options.hfun == "cos": + if hfun == "cos": def hfun(t): - return xp.cos(self.options.omega * t) - elif self.options.hfun == "sin": + return np.cos(omega * t) + elif hfun == "sin": def hfun(t): - return xp.sin(self.options.omega * t) + return np.sin(omega * t) else: - raise NotImplementedError(f"{self.options.hfun =} not implemented.") + raise NotImplementedError(f"{hfun = } not implemented.") self._hfun = hfun - self._c0 = self.variables.source.spline.vector.copy() - @profile def __call__(self, dt): + print(f"{self.time_state[0] = }") + if self.time_state[0] == 0.0: + self._c0 = self.feec_vars[0].copy() + print("Initial source coeffs set.") + # new coeffs cn1 = self._c0 * self._hfun(self.time_state[0]) # write new coeffs into self.feec_vars - # max_dc = self.feec_vars_update(cn1) - self.update_feec_variables(source=cn1) + max_dc = self.feec_vars_update(cn1) class AdiabaticPhi(Propagator): @@ -7480,100 +7332,63 @@ class HasegawaWakatani(Propagator): Solver parameters for M0 inversion. """ - class Variables: - def __init__(self): - self._n: FEECVariable = None - self._omega: FEECVariable = None - - @property - def n(self) -> FEECVariable: - return self._n - - @n.setter - def n(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1" - self._n = new - - @property - def omega(self) -> FEECVariable: - return self._omega - - @omega.setter - def omega(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "H1" - self._omega = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsCfun = Literal["const"] - # propagator options - phi: FEECVariable = None - c_fun: OptsCfun = "const" - kappa: float = 1.0 - nu: float = 0.01 - butcher: ButcherTableau = None - solver: OptsSymmSolver = "pcg" - precond: OptsMassPrecond = "MassMatrixPreconditioner" - solver_params: SolverParameters = None - - def __post_init__(self): - # checks - check_option(self.c_fun, self.OptsCfun) - check_option(self.solver, OptsSymmSolver) - check_option(self.precond, OptsMassPrecond) - - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() - - if self.butcher is None: - self.butcher = ButcherTableau() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + @staticmethod + def options(default=False): + dct = {} + dct["c_fun"] = ["const"] + dct["kappa"] = 1.0 + dct["nu"] = 0.01 + dct["algo"] = get_args(OptsButcher) + dct["M0_solver"] = { + "type": [ + ("pcg", "MassMatrixPreconditioner"), + ("cg", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + if default: + dct = descend_options_dict(dct, []) + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + n0: StencilVector, + omega0: StencilVector, + *, + phi: SplineFunction = None, + c_fun: str = options(default=True)["c_fun"], + kappa: float = options(default=True)["kappa"], + nu: float = options(default=True)["nu"], + algo: str = options(default=True)["algo"], + M0_solver: dict = options(default=True)["M0_solver"], + ): + super().__init__(n0, omega0) - @profile - def allocate(self): # default phi - if self.options.phi is None: - self.options.phi = FEECVariable(space="H1") - self.options.phi.allocate(derham=self.derham, domain=self.domain) - - self._phi = self.options.phi.spline - self._phi.vector[:] = 1.0 - self._phi.vector.update_ghost_regions() + if phi is None: + self._phi = self.derham.create_spline_function("phi", "H1") + self._phi.vector[:] = 1.0 + self._phi.vector.update_ghost_regions() + else: + self._phi = phi # default c-function - if self.options.c_fun == "const": + if c_fun == "const": c_fun = lambda e1, e2, e3: 0.0 + 0.0 * e1 else: - raise NotImplementedError(f"{self.options.c_fun =} is not available.") + raise NotImplementedError(f"{c_fun = } is not available.") # expose equation parameters - self._kappa = self.options.kappa - self._nu = self.options.nu + self._kappa = kappa + self._nu = nu # get quadrature grid of V0 pts = [grid.flatten() for grid in self.derham.quad_grid_pts["0"]] - mesh_pts = xp.meshgrid(*pts, indexing="ij") + mesh_pts = np.meshgrid(*pts, indexing="ij") # evaluate c(x, y) and metric coeff at local quadrature grid and multiply self._weights = c_fun(*mesh_pts) @@ -7606,13 +7421,13 @@ def allocate(self): for m in range(3): self._M1hw_weights += [[None, None, None]] - self._phi_5d = xp.zeros((*self._phi_at_pts.shape, 3, 3), dtype=float) - self._tmp_5d = xp.zeros((*self._phi_at_pts.shape, 3, 3), dtype=float) - self._tmp_5dT = xp.zeros((3, 3, *self._phi_at_pts.shape), dtype=float) + self._phi_5d = np.zeros((*self._phi_at_pts.shape, 3, 3), dtype=float) + self._tmp_5d = np.zeros((*self._phi_at_pts.shape, 3, 3), dtype=float) + self._tmp_5dT = np.zeros((3, 3, *self._phi_at_pts.shape), dtype=float) self._phi_5d[:, :, :, 0, 1] = self._phi_at_pts * self._jac_det self._phi_5d[:, :, :, 1, 0] = -self._phi_at_pts * self._jac_det self._tmp_5d[:] = self._jac_inv @ self._phi_5d @ self._jac_invT - self._tmp_5dT[:] = xp.transpose(self._tmp_5d, axes=(3, 4, 0, 1, 2)) + self._tmp_5dT[:] = np.transpose(self._tmp_5d, axes=(3, 4, 0, 1, 2)) self._M1hw_weights[0][1] = self._tmp_5dT[0, 1, :, :, :] self._M1hw_weights[1][0] = self._tmp_5dT[1, 0, :, :, :] @@ -7627,24 +7442,16 @@ def allocate(self): ) # inverse M0 mass matrix - solver = self.options.solver - if self.options.precond is None: + solver = M0_solver["type"][0] + if M0_solver["type"][1] is None: pc = None else: - pc_class = getattr(preconditioner, self.options.precond) + pc_class = getattr(preconditioner, M0_solver["type"][1]) pc = pc_class(self.mass_ops.M0) - # solver_params = deepcopy(M0_solver) # need a copy to pop, otherwise testing fails - # solver_params.pop("type") - self._info = self.options.solver_params.info - M0_inv = inverse( - M0, - solver, - pc=pc, - tol=self.options.solver_params.tol, - maxiter=self.options.solver_params.maxiter, - verbose=self.options.solver_params.verbose, - recycle=self.options.solver_params.recycle, - ) + solver_params = deepcopy(M0_solver) # need a copy to pop, otherwise testing fails + solver_params.pop("type") + self._info = solver_params.pop("info") + M0_inv = inverse(M0, solver, pc=pc, **solver_params) # basis projection operator df_12 = lambda e1, e2, e3: self.domain.jacobian_inv(e1, e2, e3)[0, 1, :, :, :] @@ -7662,9 +7469,6 @@ def allocate(self): # print(f"{self._BPO._dof_mat.blocks = }") # pre-allocated helper arrays - n0 = self.variables.n.spline.vector - omega0 = self.variables.omega.spline.vector - self._tmp1 = n0.space.zeros() tmp2 = n0.space.zeros() self._tmp3 = n0.space.zeros() @@ -7672,11 +7476,11 @@ def allocate(self): tmp5 = n0.space.zeros() # rhs-callables for explicit ode solve - terms1_n = -M0c + grad.T @ self._M1hw @ grad - self.options.nu * grad.T @ M1 @ grad + terms1_n = -M0c + grad.T @ self._M1hw @ grad - nu * grad.T @ M1 @ grad terms1_phi = M0c - terms1_phi_strong = -self.options.kappa * self._BPO @ grad + terms1_phi_strong = -kappa * self._BPO @ grad - terms2_omega = grad.T @ self._M1hw @ grad - self.options.nu * grad.T @ M1 @ grad + terms2_omega = grad.T @ self._M1hw @ grad - nu * grad.T @ M1 @ grad terms2_n = -M0c terms2_phi = M0c @@ -7704,7 +7508,7 @@ def f2(t, n, omega, out=out2): return out vector_field = {n0: f1, omega0: f2} - self._ode_solver = ODEsolverFEEC(vector_field, butcher=self.options.butcher) + self._ode_solver = ODEsolverFEEC(vector_field, algo=algo) def __call__(self, dt): # update time-dependent mass operator @@ -7713,7 +7517,7 @@ def __call__(self, dt): self._phi_5d[:, :, :, 0, 1] = self._phi_at_pts * self._jac_det self._phi_5d[:, :, :, 1, 0] = -self._phi_at_pts * self._jac_det self._tmp_5d[:] = self._jac_inv @ self._phi_5d @ self._jac_invT - self._tmp_5dT[:] = xp.transpose(self._tmp_5d, axes=(3, 4, 0, 1, 2)) + self._tmp_5dT[:] = np.transpose(self._tmp_5d, axes=(3, 4, 0, 1, 2)) self._M1hw_weights[0][1] = self._tmp_5dT[0, 1, :, :, :] self._M1hw_weights[1][0] = self._tmp_5dT[1, 0, :, :, :] @@ -7742,122 +7546,97 @@ class TwoFluidQuasiNeutralFull(Propagator): :ref:`time_discret`: fully implicit. """ - class Variables: - def __init__(self): - self._u: FEECVariable = None - self._ue: FEECVariable = None - self._phi: FEECVariable = None - - @property - def u(self) -> FEECVariable: - return self._u - - @u.setter - def u(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hdiv" - self._u = new - - @property - def ue(self) -> FEECVariable: - return self._ue - - @ue.setter - def ue(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "Hdiv" - self._ue = new - - @property - def phi(self) -> FEECVariable: - return self._phi - - @phi.setter - def phi(self, new): - assert isinstance(new, FEECVariable) - assert new.space == "L2" - self._phi = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - # specific literals - OptsDimension = Literal["1D", "2D", "Restelli", "Tokamak"] - # propagator options - nu: float = 1.0 - nu_e: float = 0.01 - eps_norm: float = 1.0 - solver: OptsGenSolver = "GMRES" - solver_params: SolverParameters = None - a: float = 1.0 - R0: float = 1.0 - B0: float = 10.0 - Bp: float = 12.0 - alpha: float = 0.1 - beta: float = 1.0 - stab_sigma: float = 1e-5 - variant: OptsSaddlePointSolver = "Uzawa" - method_to_solve: OptsDirectSolver = "DirectNPInverse" - preconditioner: bool = False - spectralanalysis: bool = False - lifting: bool = False - dimension: OptsDimension = "2D" - D1_dt: float = 1e-3 + def allocate(self): + pass - def __post_init__(self): - # checks - check_option(self.solver, OptsGenSolver) - check_option(self.variant, OptsSaddlePointSolver) - check_option(self.method_to_solve, OptsDirectSolver) - check_option(self.dimension, self.OptsDimension) + def set_options(self, **kwargs): + pass - # defaults - if self.solver_params is None: - self.solver_params = SolverParameters() + @staticmethod + def options(default=False): + dct = {} + dct["solver"] = { + "type": [ + ("gmres", None), + ], + "tol": 1.0e-8, + "maxiter": 3000, + "info": False, + "verbose": False, + "recycle": True, + } + dct["nu"] = 1.0 + dct["nu_e"] = 0.01 + dct["override_eq_params"] = [False, {"epsilon": 1.0}] + dct["eps_norm"] = 1.0 + dct["a"] = 1.0 + dct["R0"] = 1.0 + dct["B0"] = 10.0 + dct["Bp"] = 12.5 + dct["alpha"] = 0.1 + dct["beta"] = 1.0 + dct["stab_sigma"] = 0.00001 + dct["variant"] = "Uzawa" + dct["method_to_solve"] = "DirectNPInverse" + dct["preconditioner"] = False + dct["spectralanalysis"] = False + dct["lifting"] = False + dct["dimension"] = "2D" + dct["1D_dt"] = 0.001 + if default: + dct = descend_options_dict(dct, []) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + return dct - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + def __init__( + self, + u: BlockVector, + ue: BlockVector, + phi: BlockVector, + *, + nu: float = options(default=True)["nu"], + nu_e: float = options(default=True)["nu_e"], + eps_norm: float = options(default=True)["eps_norm"], + solver: dict = options(default=True)["solver"], + a: float = options(default=True)["a"], + R0: float = options(default=True)["R0"], + B0: float = options(default=True)["B0"], + Bp: float = options(default=True)["Bp"], + alpha: float = options(default=True)["alpha"], + beta: float = options(default=True)["beta"], + stab_sigma: float = options(default=True)["stab_sigma"], + variant: str = options(default=True)["variant"], + method_to_solve: str = options(default=True)["method_to_solve"], + preconditioner: bool = options(default=True)["preconditioner"], + spectralanalysis: bool = options(default=True)["spectralanalysis"], + lifting: bool = options(default=False)["lifting"], + dimension: str = options(default=True)["dimension"], + D1_dt: float = options(default=True)["1D_dt"], + ): + super().__init__(u, ue, phi) - @profile - def allocate(self): - self._info = self.options.solver_params.info + self._info = solver["info"] if self.derham.comm is not None: self._rank = self.derham.comm.Get_rank() else: self._rank = 0 - self._nu = self.options.nu - self._nu_e = self.options.nu_e - self._eps_norm = self.options.eps_norm - self._a = self.options.a - self._R0 = self.options.R0 - self._B0 = self.options.B0 - self._Bp = self.options.Bp - self._alpha = self.options.alpha - self._beta = self.options.beta - self._stab_sigma = self.options.stab_sigma - self._variant = self.options.variant - self._method_to_solve = self.options.method_to_solve - self._preconditioner = self.options.preconditioner - self._dimension = self.options.dimension - self._spectralanalysis = self.options.spectralanalysis - self._lifting = self.options.lifting - - solver_params = self.options.solver_params + self._nu = nu + self._nu_e = nu_e + self._eps_norm = eps_norm + self._a = a + self._R0 = R0 + self._B0 = B0 + self._Bp = Bp + self._alpha = alpha + self._beta = beta + self._stab_sigma = stab_sigma + self._variant = variant + self._method_to_solve = method_to_solve + self._preconditioner = preconditioner + self._dimension = dimension + self._spectralanalysis = spectralanalysis + self._lifting = lifting # Lifting for nontrivial boundary conditions # derham had boundary conditions in eta1 direction, the following is in space Hdiv_0 @@ -7873,13 +7652,13 @@ def allocate(self): self._mass_opsv0 = WeightedMassOperators( self.derhamv0, self.domain, - verbose=solver_params.verbose, + verbose=solver["verbose"], eq_mhd=self.mass_ops.weights["eq_mhd"], ) self._basis_opsv0 = BasisProjectionOperators( self.derhamv0, self.domain, - verbose=solver_params.verbose, + verbose=solver["verbose"], eq_mhd=self.basis_ops.weights["eq_mhd"], ) else: @@ -7908,7 +7687,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, ) _funy = getattr(callables, "ManufacturedSolutionForceterm")( species="Ions", @@ -7918,7 +7697,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, ) _funelectronsx = getattr(callables, "ManufacturedSolutionForceterm")( species="Electrons", @@ -7928,7 +7707,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, ) _funelectronsy = getattr(callables, "ManufacturedSolutionForceterm")( species="Electrons", @@ -7938,7 +7717,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, ) # get callable(s) for specified init type @@ -7947,32 +7726,16 @@ def allocate(self): # pullback callable funx = TransformedPformComponent( - forceterm_class, - given_in_basis="physical", - out_form="2", - comp=0, - domain=self.domain, + forceterm_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain ) funy = TransformedPformComponent( - forceterm_class, - given_in_basis="physical", - out_form="2", - comp=1, - domain=self.domain, + forceterm_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain ) fun_electronsx = TransformedPformComponent( - forcetermelectrons_class, - given_in_basis="physical", - out_form="2", - comp=0, - domain=self.domain, + forcetermelectrons_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain ) fun_electronsy = TransformedPformComponent( - forcetermelectrons_class, - given_in_basis="physical", - out_form="2", - comp=1, - domain=self.domain, + forcetermelectrons_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain ) l2_proj = L2Projector(space_id="Hdiv", mass_ops=self.mass_ops) self._F1 = l2_proj([funx, funy, _forceterm_logical]) @@ -8007,46 +7770,22 @@ def allocate(self): # pullback callable fun_pb_1 = TransformedPformComponent( - forceterm_class, - given_in_basis="physical", - out_form="2", - comp=0, - domain=self.domain, + forceterm_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain ) fun_pb_2 = TransformedPformComponent( - forceterm_class, - given_in_basis="physical", - out_form="2", - comp=1, - domain=self.domain, + forceterm_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain ) fun_pb_3 = TransformedPformComponent( - forceterm_class, - given_in_basis="physical", - out_form="2", - comp=2, - domain=self.domain, + forceterm_class, fun_basis="physical", out_form="2", comp=2, domain=self.domain ) fun_electrons_pb_1 = TransformedPformComponent( - forcetermelectrons_class, - given_in_basis="physical", - out_form="2", - comp=0, - domain=self.domain, + forcetermelectrons_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain ) fun_electrons_pb_2 = TransformedPformComponent( - forcetermelectrons_class, - given_in_basis="physical", - out_form="2", - comp=1, - domain=self.domain, + forcetermelectrons_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain ) fun_electrons_pb_3 = TransformedPformComponent( - forcetermelectrons_class, - given_in_basis="physical", - out_form="2", - comp=2, - domain=self.domain, + forcetermelectrons_class, fun_basis="physical", out_form="2", comp=2, domain=self.domain ) if self._lifting: l2_proj = L2Projector(space_id="Hdiv", mass_ops=self._mass_opsv0) @@ -8069,7 +7808,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -8083,7 +7822,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -8097,7 +7836,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -8111,7 +7850,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -8125,7 +7864,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -8139,7 +7878,7 @@ def allocate(self): dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=self.options.D1_dt, + dt=D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -8152,46 +7891,22 @@ def allocate(self): # pullback callable fun_pb_1 = TransformedPformComponent( - forceterm_class, - given_in_basis="physical", - out_form="2", - comp=0, - domain=self.domain, + forceterm_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain ) fun_pb_2 = TransformedPformComponent( - forceterm_class, - given_in_basis="physical", - out_form="2", - comp=1, - domain=self.domain, + forceterm_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain ) fun_pb_3 = TransformedPformComponent( - forceterm_class, - given_in_basis="physical", - out_form="2", - comp=2, - domain=self.domain, + forceterm_class, fun_basis="physical", out_form="2", comp=2, domain=self.domain ) fun_electrons_pb_1 = TransformedPformComponent( - forcetermelectrons_class, - given_in_basis="physical", - out_form="2", - comp=0, - domain=self.domain, + forcetermelectrons_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain ) fun_electrons_pb_2 = TransformedPformComponent( - forcetermelectrons_class, - given_in_basis="physical", - out_form="2", - comp=1, - domain=self.domain, + forcetermelectrons_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain ) fun_electrons_pb_3 = TransformedPformComponent( - forcetermelectrons_class, - given_in_basis="physical", - out_form="2", - comp=2, - domain=self.domain, + forcetermelectrons_class, fun_basis="physical", out_form="2", comp=2, domain=self.domain ) if self._lifting: l2_proj = L2Projector(space_id="Hdiv", mass_ops=self._mass_opsv0) @@ -8272,10 +7987,7 @@ def allocate(self): self._S21 = None if self.derhamv0.with_local_projectors: self._S21 = BasisProjectionOperatorLocal( - self.derhamv0._Ploc["1"], - self.derhamv0.Vh_fem["2"], - fun, - transposed=False, + self.derhamv0._Ploc["1"], self.derhamv0.Vh_fem["2"], fun, transposed=False ) if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): @@ -8385,10 +8097,7 @@ def allocate(self): self._S21 = None if self.derham.with_local_projectors: self._S21 = BasisProjectionOperatorLocal( - self.derham._Ploc["1"], - self.derham.Vh_fem["2"], - fun, - transposed=False, + self.derham._Ploc["1"], self.derham.Vh_fem["2"], fun, transposed=False ) if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): @@ -8434,9 +8143,9 @@ def allocate(self): A11np = self._M2np + self._A11np_notimedependency if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - A11np += self._stab_sigma * xp.identity(A11np.shape[0]) + A11np += self._stab_sigma * np.identity(A11np.shape[0]) self.A22np = ( - self._stab_sigma * xp.identity(A11np.shape[0]) + self._stab_sigma * np.identity(A11np.shape[0]) + self._nu_e * ( self._Dnp.T @ self._M3np @ self._Dnp @@ -8445,7 +8154,7 @@ def allocate(self): + self._M2Bnp / self._eps_norm ) self._A22prenp = ( - xp.identity(self.A22np.shape[0]) * self._stab_sigma + np.identity(self.A22np.shape[0]) * self._stab_sigma ) # + self._nu_e * (self._Dnp.T @ self._M3np @ self._Dnp) elif self._method_to_solve in ("SparseSolver", "ScipySparse"): A11np += self._stab_sigma * sc.sparse.eye(A11np.shape[0], format="csr") @@ -8478,10 +8187,10 @@ def allocate(self): A=_A, B=_B, F=_F, - solver_name=self.options.solver, - tol=self.options.solver_params.tol, - max_iter=self.options.solver_params.maxiter, - verbose=self.options.solver_params.verbose, + solver_name=solver["type"][0], + tol=solver["tol"], + max_iter=solver["maxiter"], + verbose=solver["verbose"], pc=None, ) # Allocate memory for call @@ -8495,17 +8204,17 @@ def allocate(self): F=_Fnp, method_to_solve=self._method_to_solve, preconditioner=self._preconditioner, - spectralanalysis=self.options.spectralanalysis, - tol=self.options.solver_params.tol, - max_iter=self.options.solver_params.maxiter, - verbose=self.options.solver_params.verbose, + spectralanalysis=spectralanalysis, + tol=solver["tol"], + max_iter=solver["maxiter"], + verbose=solver["verbose"], ) def __call__(self, dt): # current variables - unfeec = self.variables.u.spline.vector - uenfeec = self.variables.ue.spline.vector - phinfeec = self.variables.phi.spline.vector + unfeec = self.feec_vars[0] + uenfeec = self.feec_vars[1] + phinfeec = self.feec_vars[2] if self._variant == "GMRES": if self._lifting: @@ -8622,13 +8331,13 @@ def __call__(self, dt): uen = _sol1[1] phin = _sol2 # write new coeffs into self.feec_vars - max_du, max_due, max_dphi = self.update_feec_variables(u=un, ue=uen, phi=phin) + max_du, max_due, max_dphi = self.feec_vars_update(un, uen, phin) elif self._variant == "Uzawa": # Numpy A11np = self._M2np / dt + self._A11np_notimedependency if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - A11np += self._stab_sigma * xp.identity(A11np.shape[0]) + A11np += self._stab_sigma * np.identity(A11np.shape[0]) _A22prenp = self._A22prenp A22np = self.A22np elif self._method_to_solve in ("SparseSolver", "ScipySparse"): @@ -8638,7 +8347,7 @@ def __call__(self, dt): # _Anp[1] and _Anppre[1] remain unchanged _Anp = [A11np, A22np] - if self._preconditioner: + if self._preconditioner == True: _A11prenp = self._M2np / dt # + self._A11prenp_notimedependency _Anppre = [_A11prenp, _A22prenp] @@ -8675,24 +8384,20 @@ def __call__(self, dt): _Fnp = [_F1np, _F2np] if self.rank == 0: - if self._preconditioner: + if self._preconditioner == True: self._solver_UzawaNumpy.Apre = _Anppre self._solver_UzawaNumpy.A = _Anp self._solver_UzawaNumpy.F = _Fnp if self._lifting: un, uen, phin, info, residual_norms, spectralresult = self._solver_UzawaNumpy( - u0.vector, - ue0.vector, - phinfeec, + u0.vector, ue0.vector, phinfeec ) un += u_prime.vector.toarray() uen += ue_prime.vector.toarray() else: un, uen, phin, info, residual_norms, spectralresult = self._solver_UzawaNumpy( - unfeec, - uenfeec, - phinfeec, + unfeec, uenfeec, phinfeec ) dimlist = [[shp - 2 * pi for shp, pi in zip(unfeec[i][:].shape, self.derham.p)] for i in range(3)] @@ -8722,10 +8427,10 @@ def __call__(self, dt): e = phi_temp.ends phi_temp[s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1] = phin.reshape(*dimphi) else: - print("TwoFluidQuasiNeutralFull is only running on one MPI.") + print(f"TwoFluidQuasiNeutralFull is only running on one MPI.") # write new coeffs into self.feec_vars - max_du, max_due, max_dphi = self.update_feec_variables(u=u_temp, ue=ue_temp, phi=phi_temp) + max_du, max_due, max_dphi = self.feec_vars_update(u_temp, ue_temp, phi_temp) if self._info and self._rank == 0: print("Status for TwoFluidQuasiNeutralFull:", info["success"]) diff --git a/src/struphy/propagators/propagators_markers.py b/src/struphy/propagators/propagators_markers.py index 0360f39de..a890e5d27 100644 --- a/src/struphy/propagators/propagators_markers.py +++ b/src/struphy/propagators/propagators_markers.py @@ -4,10 +4,10 @@ from dataclasses import dataclass from typing import Callable, Literal, get_args -import cunumpy as xp +import numpy as np from line_profiler import profile +from mpi4py import MPI from numpy import array, polynomial, random -from psydac.ddm.mpi import mpi as MPI from psydac.linalg.basic import LinearOperator from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -18,21 +18,18 @@ from struphy.io.options import ( OptsKernel, OptsMPIsort, - OptsVecSpace, check_option, ) from struphy.io.setup import descend_options_dict from struphy.models.variables import FEECVariable, PICVariable, SPHVariable from struphy.ode.utils import ButcherTableau from struphy.pic.accumulation import accum_kernels, accum_kernels_gc -from struphy.pic.accumulation.particles_to_grid import AccumulatorVector from struphy.pic.base import Particles from struphy.pic.particles import Particles3D, Particles5D, Particles6D, ParticlesSPH from struphy.pic.pushing import eval_kernels_gc, pusher_kernels, pusher_kernels_gc from struphy.pic.pushing.pusher import Pusher from struphy.polar.basic import PolarVector from struphy.propagators.base import Propagator -from struphy.utils.pyccel import Pyccelkernel class PushEta(Propagator): @@ -96,15 +93,15 @@ def options(self, new): @profile def allocate(self): # get kernel - kernel = Pyccelkernel(pusher_kernels.push_eta_stage) + kernel = pusher_kernels.push_eta_stage # define algorithm butcher = self.options.butcher # temp fix due to refactoring of ButcherTableau: - import cunumpy as xp + import numpy as np - butcher._a = xp.diag(butcher.a, k=-1) - butcher._a = xp.array(list(butcher.a) + [0.0]) + butcher._a = np.diag(butcher.a, k=-1) + butcher._a = np.array(list(butcher.a) + [0.0]) args_kernel = ( butcher.a, @@ -159,7 +156,7 @@ def ions(self) -> PICVariable | SPHVariable: @ions.setter def ions(self, new): assert isinstance(new, PICVariable | SPHVariable) - assert new.space in ("Particles6D", "DeltaFParticles6D", "ParticlesSPH") + assert new.space in ("Particles6D", "ParticlesSPH") self._ions = new def __init__(self): @@ -196,7 +193,6 @@ def options(self, new): def allocate(self): # scaling factor self._epsilon = self.variables.ions.species.equation_params.epsilon - assert self.derham is not None, f"{self.__class__.__name__} needs a Derham object." # TODO: treat PolarVector as well, but polar splines are being reworked at the moment if self.projected_equil is not None: @@ -217,11 +213,11 @@ def allocate(self): # define pusher kernel if self.options.algo == "analytic": - kernel = Pyccelkernel(pusher_kernels.push_vxb_analytic) + kernel = pusher_kernels.push_vxb_analytic elif self.options.algo == "implicit": - kernel = Pyccelkernel(pusher_kernels.push_vxb_implicit) + kernel = pusher_kernels.push_vxb_implicit else: - raise ValueError(f"{self.options.algo =} not supported.") + raise ValueError(f"{self.options.algo = } not supported.") # instantiate Pusher args_kernel = ( @@ -290,7 +286,7 @@ def var(self) -> PICVariable | SPHVariable: @var.setter def var(self, new): assert isinstance(new, PICVariable | SPHVariable) - assert new.space in ("Particles6D", "DeltaFParticles6D", "ParticlesSPH") + assert new.space in ("Particles6D", "ParticlesSPH") self._var = new def __init__(self): @@ -359,7 +355,7 @@ def allocate(self): self._pusher = Pusher( self.variables.var.particles, - Pyccelkernel(pusher_kernels.push_v_with_efield), + pusher_kernels.push_v_with_efield, args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, @@ -398,106 +394,85 @@ class PushEtaPC(Propagator): * ``heun3`` (3rd order) """ - class Variables: - def __init__(self): - self._var: PICVariable | SPHVariable = None - - @property - def var(self) -> PICVariable | SPHVariable: - return self._var - - @var.setter - def var(self, new): - assert isinstance(new, PICVariable | SPHVariable) - self._var = new - - def __init__(self): - self.variables = self.Variables() + @staticmethod + def options(default=False): + dct = {} + dct["use_perp_model"] = [True, False] - @dataclass - class Options: - butcher: ButcherTableau = None - use_perp_model: bool = True - u_tilde: FEECVariable = None - u_space: OptsVecSpace = "Hdiv" + if default: + dct = descend_options_dict(dct, []) - def __post_init__(self): - # checks - check_option(self.u_space, OptsVecSpace) - assert isinstance(self.u_tilde, FEECVariable) + return dct - # defaults - if self.butcher is None: - self.butcher = ButcherTableau() + def __init__( + self, + particles: Particles, + *, + u: BlockVector | PolarVector, + use_perp_model: bool = options(default=True)["use_perp_model"], + u_space: str, + ): + super().__init__(particles) - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + assert isinstance(u, (BlockVector, PolarVector)) - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + self._u = u - @profile - def allocate(self): - self._u_tilde = self.options.u_tilde.spline.vector - - # get kernell: - if self.options.u_space == "Hcurl": - kernel = Pyccelkernel(pusher_kernels.push_pc_eta_stage_Hcurl) - elif self.options.u_space == "Hdiv": - kernel = Pyccelkernel(pusher_kernels.push_pc_eta_stage_Hdiv) - elif self.options.u_space == "H1vec": - kernel = Pyccelkernel(pusher_kernels.push_pc_eta_stage_H1vec) + # call Pusher class + if use_perp_model: + if u_space == "Hcurl": + kernel = pusher_kernels.push_pc_eta_rk4_Hcurl + elif u_space == "Hdiv": + kernel = pusher_kernels.push_pc_eta_rk4_Hdiv + elif u_space == "H1vec": + kernel = pusher_kernels.push_pc_eta_rk4_H1vec + else: + raise ValueError( + f'{u_space = } not valid, choose from "Hcurl", "Hdiv" or "H1vec.', + ) else: - raise ValueError( - f'{self.options.u_space =} not valid, choose from "Hcurl", "Hdiv" or "H1vec.', - ) - - # define algorithm - butcher = self.options.butcher - # temp fix due to refactoring of ButcherTableau: - import cunumpy as xp - - butcher._a = xp.diag(butcher.a, k=-1) - butcher._a = xp.array(list(butcher.a) + [0.0]) + if u_space == "Hcurl": + kernel = pusher_kernels.push_pc_eta_rk4_Hcurl_full + elif u_space == "Hdiv": + kernel = pusher_kernels.push_pc_eta_rk4_Hdiv_full + elif u_space == "H1vec": + kernel = pusher_kernels.push_pc_eta_rk4_H1vec_full + else: + raise ValueError( + f'{u_space = } not valid, choose from "Hcurl", "Hdiv" or "H1vec.', + ) args_kernel = ( self.derham.args_derham, - self._u_tilde[0]._data, - self._u_tilde[1]._data, - self._u_tilde[2]._data, - self.options.use_perp_model, - butcher.a, - butcher.b, - butcher.c, + self._u[0]._data, + self._u[1]._data, + self._u[2]._data, ) self._pusher = Pusher( - self.variables.var.particles, + particles, kernel, args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, - n_stages=butcher.n_stages, + n_stages=4, mpi_sort="each", ) def __call__(self, dt): - self._u_tilde.update_ghost_regions() + # check if ghost regions are synchronized + if not self._u[0].ghost_regions_in_sync: + self._u[0].update_ghost_regions() + if not self._u[1].ghost_regions_in_sync: + self._u[1].update_ghost_regions() + if not self._u[2].ghost_regions_in_sync: + self._u[2].update_ghost_regions() self._pusher(dt) # update_weights - if self.variables.var.particles.control_variate: - self.variables.var.particles.update_weights() + if self.particles[0].control_variate: + self.particles[0].update_weights() class PushGuidingCenterBxEstar(Propagator): @@ -743,7 +718,7 @@ def allocate(self): ) # pusher kernel - kernel = Pyccelkernel(pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_1st_order_newton) + kernel = pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_1st_order_newton alpha_in_kernel = 1.0 # evaluate at eta^{n+1,k} and save args_kernel = ( @@ -777,7 +752,7 @@ def allocate(self): ) # evaluate at eta^{n+1,k} and save # pusher kernel - kernel = Pyccelkernel(pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_1st_order) + kernel = pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_1st_order alpha_in_kernel = 0.5 # evaluate at mid-point args_kernel = ( @@ -823,7 +798,7 @@ def allocate(self): ) # evaluate at eta^{n+1,k} and save) # pusher kernel - kernel = Pyccelkernel(pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_2nd_order) + kernel = pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_2nd_order alpha_in_kernel = 0.5 # evaluate at mid-point args_kernel = ( @@ -864,12 +839,12 @@ def allocate(self): else: butcher = self.options.butcher # temp fix due to refactoring of ButcherTableau: - import cunumpy as xp + import numpy as np - butcher._a = xp.diag(butcher.a, k=-1) - butcher._a = xp.array(list(butcher.a) + [0.0]) + butcher._a = np.diag(butcher.a, k=-1) + butcher._a = np.array(list(butcher.a) + [0.0]) - kernel = Pyccelkernel(pusher_kernels_gc.push_gc_bxEstar_explicit_multistage) + kernel = pusher_kernels_gc.push_gc_bxEstar_explicit_multistage args_kernel = ( self.derham.args_derham, @@ -1189,7 +1164,7 @@ def allocate(self): ) # pusher kernel - kernel = Pyccelkernel(pusher_kernels_gc.push_gc_Bstar_discrete_gradient_1st_order_newton) + kernel = pusher_kernels_gc.push_gc_Bstar_discrete_gradient_1st_order_newton alpha_in_kernel = 1.0 # evaluate at eta^{n+1,k} and save args_kernel = ( @@ -1222,7 +1197,7 @@ def allocate(self): ) # evaluate at Z^{n+1,k} and save # pusher kernel - kernel = Pyccelkernel(pusher_kernels_gc.push_gc_Bstar_discrete_gradient_1st_order) + kernel = pusher_kernels_gc.push_gc_Bstar_discrete_gradient_1st_order alpha_in_kernel = 0.5 # evaluate at mid-point args_kernel = ( @@ -1268,7 +1243,7 @@ def allocate(self): ) # evaluate at Z^{n+1,k} and save # pusher kernel - kernel = Pyccelkernel(pusher_kernels_gc.push_gc_Bstar_discrete_gradient_2nd_order) + kernel = pusher_kernels_gc.push_gc_Bstar_discrete_gradient_2nd_order alpha_in_kernel = 0.5 # evaluate at mid-point args_kernel = ( @@ -1312,12 +1287,12 @@ def allocate(self): else: butcher = self.options.butcher # temp fix due to refactoring of ButcherTableau: - import cunumpy as xp + import numpy as np - butcher._a = xp.diag(butcher.a, k=-1) - butcher._a = xp.array(list(butcher.a) + [0.0]) + butcher._a = np.diag(butcher.a, k=-1) + butcher._a = np.array(list(butcher.a) + [0.0]) - kernel = Pyccelkernel(pusher_kernels_gc.push_gc_Bstar_explicit_multistage) + kernel = pusher_kernels_gc.push_gc_Bstar_explicit_multistage args_kernel = ( self.derham.args_derham, @@ -1380,6 +1355,99 @@ def __call__(self, dt): self.variables.ions.particles.update_weights() +class StepStaticEfield(Propagator): + r"""Solve the following system + + .. math:: + + \frac{\text{d} \mathbf{\eta}_p}{\text{d} t} & = DL^{-1} \mathbf{v}_p \,, + + \frac{\text{d} \mathbf{v}_p}{\text{d} t} & = \kappa \, DL^{-T} \mathbf{E} + + which is solved by an average discrete gradient method, implicitly iterating + over :math:`k` (for every particle :math:`p`): + + .. math:: + + \mathbf{\eta}^{n+1}_{k+1} = \mathbf{\eta}^n + \frac{\Delta t}{2} DL^{-1} + \left( \frac{\mathbf{\eta}^{n+1}_k + \mathbf{\eta}^n }{2} \right) \left( \mathbf{v}^{n+1}_k + \mathbf{v}^n \right) \,, + + \mathbf{v}^{n+1}_{k+1} = \mathbf{v}^n + \Delta t \, \kappa \, DL^{-1}\left(\mathbf{\eta}^n\right) + \int_0^1 \left[ \mathbb{\Lambda}\left( \eta^n + \tau (\mathbf{\eta}^{n+1}_k - \mathbf{\eta}^n) \right) \right]^T \mathbf{e} \, \text{d} \tau + + Parameters + ---------- + particles : struphy.pic.particles.Particles6D + Holdes the markers to push. + + **params : dict + Solver- and/or other parameters for this splitting step. + """ + + def __init__(self, particles, **params): + from numpy import floor, polynomial + + super().__init__(particles) + + # parameters + params_default = { + "e_field": BlockVector(self.derham.Vh_fem["1"].coeff_space), + "kappa": 1e2, + } + + params = set_defaults(params, params_default) + self.kappa = params["kappa"] + + assert isinstance(params["e_field"], (BlockVector, PolarVector)) + self._e_field = params["e_field"] + + pn1 = self.derham.p[0] + pd1 = pn1 - 1 + pn2 = self.derham.p[1] + pd2 = pn2 - 1 + pn3 = self.derham.p[2] + pd3 = pn3 - 1 + + # number of quadrature points in direction 1 + n_quad1 = int(floor(pd1 * pn2 * pn3 / 2 + 1)) + # number of quadrature points in direction 2 + n_quad2 = int(floor(pn1 * pd2 * pn3 / 2 + 1)) + # number of quadrature points in direction 3 + n_quad3 = int(floor(pn1 * pn2 * pd3 / 2 + 1)) + + # get quadrature weights and locations + self._loc1, self._weight1 = polynomial.legendre.leggauss(n_quad1) + self._loc2, self._weight2 = polynomial.legendre.leggauss(n_quad2) + self._loc3, self._weight3 = polynomial.legendre.leggauss(n_quad3) + + self._pusher = Pusher( + self.derham, + self.domain, + "push_x_v_static_efield", + ) + + def __call__(self, dt): + """ + TODO + """ + self._pusher( + self.particles[0], + dt, + self._loc1, + self._loc2, + self._loc3, + self._weight1, + self._weight2, + self._weight3, + self._e_field.blocks[0]._data, + self._e_field.blocks[1]._data, + self._e_field.blocks[2]._data, + self.kappa, + array([1e-10, 1e-10]), + 100, + ) + + class PushDeterministicDiffusion(Propagator): r"""For each marker :math:`p`, solves @@ -1401,70 +1469,44 @@ class PushDeterministicDiffusion(Propagator): * Explicit from :class:`~struphy.ode.utils.ButcherTableau` """ - class Variables: - def __init__(self): - self._var: PICVariable = None - - @property - def var(self) -> PICVariable: - return self._var - - @var.setter - def var(self, new): - assert isinstance(new, PICVariable) - assert new.space == "Particles3D" - self._var = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - butcher: ButcherTableau = None - bc_type: tuple = ("periodic", "periodic", "periodic") - diff_coeff: float = 1.0 - - def __post_init__(self): - # defaults - if self.butcher is None: - self.butcher = ButcherTableau() + @staticmethod + def options(default=False): + dct = {} + dct["algo"] = ["rk4", "forward_euler", "heun2", "rk2", "heun3"] + dct["diffusion_coefficient"] = 1.0 + if default: + dct = descend_options_dict(dct, []) + return dct - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options + def __init__( + self, + particles: Particles3D, + *, + algo: str = options(default=True)["algo"], + bc_type: list = ["periodic", "periodic", "periodic"], + diffusion_coefficient: float = options()["diffusion_coefficient"], + ): + from struphy.pic.accumulation.particles_to_grid import AccumulatorVector - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + super().__init__(particles) - @profile - def allocate(self): - self._bc_type = self.options.bc_type - self._diffusion = self.options.diff_coeff + self._bc_type = bc_type + self._diffusion = diffusion_coefficient self._tmp = self.derham.Vh["1"].zeros() # choose algorithm - self._butcher = self.options.butcher + self._butcher = ButcherTableau(algo) # temp fix due to refactoring of ButcherTableau: - import cunumpy as xp - - self._butcher._a = xp.diag(self._butcher.a, k=-1) - self._butcher._a = xp.array(list(self._butcher.a) + [0.0]) + import numpy as np - particles = self.variables.var.particles + self._butcher._a = np.diag(self._butcher.a, k=-1) + self._butcher._a = np.array(list(self._butcher.a) + [0.0]) self._u_on_grid = AccumulatorVector( particles, "H1", - Pyccelkernel(accum_kernels.charge_density_0form), + accum_kernels.charge_density_0form, self.mass_ops, self.domain.args_domain, ) @@ -1484,7 +1526,7 @@ def allocate(self): self._pusher = Pusher( particles, - Pyccelkernel(pusher_kernels.push_deterministic_diffusion_stage), + pusher_kernels.push_deterministic_diffusion_stage, args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, @@ -1496,10 +1538,9 @@ def __call__(self, dt): """ TODO """ - particles = self.variables.var.particles # accumulate - self._u_on_grid() + self._u_on_grid(self.particles[0].vdim) # take gradient pi_u = self._u_on_grid.vectors[0] @@ -1510,8 +1551,8 @@ def __call__(self, dt): self._pusher(dt) # update_weights - if particles.control_variate: - particles.update_weights() + if self.particles[0].control_variate: + self.particles[0].update_weights() class PushRandomDiffusion(Propagator): @@ -1534,64 +1575,36 @@ class PushRandomDiffusion(Propagator): * ``forward_euler`` (1st order) """ - class Variables: - def __init__(self): - self._var: PICVariable = None - - @property - def var(self) -> PICVariable: - return self._var - - @var.setter - def var(self, new): - assert isinstance(new, PICVariable) - assert new.space == "Particles3D" - self._var = new - - def __init__(self): - self.variables = self.Variables() - - @dataclass - class Options: - butcher: ButcherTableau = None - bc_type: tuple = ("periodic", "periodic", "periodic") - diff_coeff: float = 1.0 - - def __post_init__(self): - # defaults - if self.butcher is None: - self.butcher = ButcherTableau() - - @property - def options(self) -> Options: - if not hasattr(self, "_options"): - self._options = self.Options() - return self._options - - @options.setter - def options(self, new): - assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: - print(f"\nNew options for propagator '{self.__class__.__name__}':") - for k, v in new.__dict__.items(): - print(f" {k}: {v}") - self._options = new + @staticmethod + def options(default=False): + dct = {} + dct["algo"] = ["forward_euler"] + dct["diffusion_coefficient"] = 1.0 + if default: + dct = descend_options_dict(dct, []) + return dct - @profile - def allocate(self): - self._bc_type = self.options.bc_type - self._diffusion = self.options.diff_coeff + def __init__( + self, + particles: Particles3D, + algo: str = options(default=True)["algo"], + bc_type: list = ["periodic", "periodic", "periodic"], + diffusion_coefficient: float = options()["diffusion_coefficient"], + ): + super().__init__(particles) - particles = self.variables.var.particles + self._bc_type = bc_type + self._diffusion = diffusion_coefficient - self._noise = array(particles.markers[:, :3]) + self._noise = array(self.particles[0].markers[:, :3]) - self._butcher = self.options.butcher + # choose algorithm + self._butcher = ButcherTableau("forward_euler") # temp fix due to refactoring of ButcherTableau: - import cunumpy as xp + import numpy as np - self._butcher._a = xp.diag(self._butcher.a, k=-1) - self._butcher._a = xp.array(list(self._butcher.a) + [0.0]) + self._butcher._a = np.diag(self._butcher.a, k=-1) + self._butcher._a = np.array(list(self._butcher.a) + [0.0]) # instantiate Pusher args_kernel = ( @@ -1604,7 +1617,7 @@ def allocate(self): self._pusher = Pusher( particles, - Pyccelkernel(pusher_kernels.push_random_diffusion_stage), + pusher_kernels.push_random_diffusion_stage, args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, @@ -1621,20 +1634,18 @@ def __call__(self, dt): TODO """ - particles = self.variables.var.particles - self._noise[:] = random.multivariate_normal( self._mean, self._cov, - len(particles.markers), + len(self.particles[0].markers), ) # push markers self._pusher(dt) # update_weights - if particles.control_variate: - particles.update_weights() + if self.particles[0].control_variate: + self.particles[0].update_weights() class PushVinSPHpressure(Propagator): @@ -1746,11 +1757,11 @@ def allocate(self): # pusher kernel if self.options.thermodynamics == "isothermal": - kernel = Pyccelkernel(pusher_kernels.push_v_sph_pressure) + kernel = pusher_kernels.push_v_sph_pressure elif self.options.thermodynamics == "polytropic": - kernel = Pyccelkernel(pusher_kernels.push_v_sph_pressure_ideal_gas) + kernel = pusher_kernels.push_v_sph_pressure_ideal_gas - gravity = xp.array(self.options.gravity, dtype=float) + gravity = np.array(self.options.gravity, dtype=float) args_kernel = ( boxes, @@ -1778,7 +1789,7 @@ def __call__(self, dt): self._pusher(dt) -class PushVinViscousPotential2D(Propagator): +class PushVinViscousPotential(Propagator): r"""For each marker :math:`p`, solves .. math:: @@ -1820,137 +1831,6 @@ def __init__( # base class constructor call super().__init__(particles) - # init kernel for evaluating density etc. before each time step. - init_kernel_1 = Pyccelkernel(eval_kernels_gc.sph_mean_velocity_coeffs) - first_free_idx = particles.args_markers.first_free_idx - comps = (0, 1, 2) - - init_kernel_2 = Pyccelkernel(eval_kernels_gc.sph_mean_velocity) - # first_free_idx = particles.args_markers.first_free_idx - # comps = (0, 1, 2) - - init_kernel_3 = Pyccelkernel(eval_kernels_gc.sph_grad_mean_velocity) - comps_tensor = (0, 1, 2, 3, 4, 5, 6, 7, 8) - - init_kernel_4 = Pyccelkernel(eval_kernels_gc.sph_viscosity_tensor) - - boxes = particles.sorting_boxes.boxes - neighbours = particles.sorting_boxes.neighbours - holes = particles.holes - periodic = [bci == "periodic" for bci in particles.bc] - kernel_nr = particles.ker_dct()[kernel_type] - - if kernel_width is None: - kernel_width = tuple([1 / ni for ni in self.particles[0].boxes_per_dim]) - else: - assert all([hi <= 1 / ni for hi, ni in zip(kernel_width, self.particles[0].boxes_per_dim)]) - - # init kernel - args_init = ( - boxes, - neighbours, - holes, - *periodic, - kernel_nr, - *kernel_width, - ) - - self.add_init_kernel( - init_kernel_1, - first_free_idx, - comps, - args_init, - ) - - self.add_init_kernel( - init_kernel_2, - first_free_idx + 3, # +3 so that the previous one is not overwritten - comps, - args_init, - ) - - self.add_init_kernel( - init_kernel_3, - first_free_idx + 6, # +3 so that the previous one is not overwritten - comps_tensor, - args_init, - ) - - self.add_init_kernel( - init_kernel_4, - first_free_idx + 15, - comps_tensor, - args_init, - ) - - kernel = Pyccelkernel(pusher_kernels.push_v_viscosity) - - args_kernel = ( - boxes, - neighbours, - holes, - *periodic, - kernel_nr, - *kernel_width, - ) - - # the Pusher class wraps around all kernels - self._pusher = Pusher( - particles, - kernel, - args_kernel, - self.domain.args_domain, - alpha_in_kernel=0.0, - init_kernels=self.init_kernels, - ) - - def __call__(self, dt): - self.particles[0].put_particles_in_boxes() - self._pusher(dt) - - -class PushVinViscousPotential3D(Propagator): - r"""For each marker :math:`p`, solves - - .. math:: - - \frac{\textnormal d \mathbf v_p(t)}{\textnormal d t} = \kappa_p \sum_{i=1}^N w_i \left( \frac{1}{\rho^{N,h}(\boldsymbol \eta_p)} + \frac{1}{\rho^{N,h}(\boldsymbol \eta_i)} \right) DF^{-\top}\nabla W_h(\boldsymbol \eta_p - \boldsymbol \eta_i) \,, - - where :math:`DF^{-\top}` denotes the inverse transpose Jacobian, and with the smoothed density - - .. math:: - - \rho^{N,h}(\boldsymbol \eta) = \frac 1N \sum_{j=1}^N w_j \, W_h(\boldsymbol \eta - \boldsymbol \eta_j)\,, - - where :math:`W_h(\boldsymbol \eta)` is a smoothing kernel from :mod:`~struphy.pic.sph_smoothing_kernels`. - Time stepping: - - * Explicit from :class:`~struphy.ode.utils.ButcherTableau` - """ - - @staticmethod - def options(default=False): - dct = {} - dct["kernel_type"] = [ker for ker in list(Particles.ker_dct()) if "3d" in ker] - dct["kernel_width"] = None - dct["algo"] = [ - "forward_euler", - ] # "heun2", "rk2", "heun3", "rk4"] - if default: - dct = descend_options_dict(dct, []) - return dct - - def __init__( - self, - particles: ParticlesSPH, - *, - kernel_type: str = "gaussian_3d", - kernel_width: tuple = None, - algo: str = options(default=True)["algo"], # TODO: implement other algos than forward Euler - ): - # base class constructor call - super().__init__(particles) - # init kernel for evaluating density etc. before each time step. init_kernel_1 = eval_kernels_gc.sph_mean_velocity_coeffs first_free_idx = particles.args_markers.first_free_idx @@ -2014,7 +1894,7 @@ def __init__( args_init, ) - kernel = Pyccelkernel(pusher_kernels.push_v_viscosity) + kernel = pusher_kernels.push_v_viscosity args_kernel = ( boxes, diff --git a/src/struphy/propagators/tests/test_gyrokinetic_poisson.py b/src/struphy/propagators/tests/test_gyrokinetic_poisson.py index 747ec65c7..4d2d7087e 100644 --- a/src/struphy/propagators/tests/test_gyrokinetic_poisson.py +++ b/src/struphy/propagators/tests/test_gyrokinetic_poisson.py @@ -1,23 +1,23 @@ -import cunumpy as xp import matplotlib.pyplot as plt +import numpy as np import pytest -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.projectors import L2Projector from struphy.feec.psydac_derham import Derham from struphy.geometry import domains -from struphy.geometry.base import Domain from struphy.linear_algebra.solver import SolverParameters from struphy.models.variables import FEECVariable +from struphy.propagators import ImplicitDiffusion from struphy.propagators.base import Propagator -from struphy.propagators.propagators_fields import ImplicitDiffusion comm = MPI.COMM_WORLD rank = comm.Get_rank() # plt.rcParams.update({'font.size': 22}) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("direction", [0, 1]) @pytest.mark.parametrize("bc_type", ["periodic", "dirichlet", "neumann"]) @pytest.mark.parametrize( @@ -27,8 +27,7 @@ ["Orthogonal", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 3.0}], ], ) -@pytest.mark.parametrize("projected_rhs", [False, True]) -def test_poisson_M1perp_1d(direction, bc_type, mapping, projected_rhs, show_plot=False): +def test_poisson_M1perp_1d(direction, bc_type, mapping, show_plot=False): """ Test the convergence of Poisson solver with M1perp diffusion matrix in 1D by means of manufactured solutions. @@ -39,7 +38,7 @@ def test_poisson_M1perp_1d(direction, bc_type, mapping, projected_rhs, show_plot dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain: Domain = domain_class(**dom_params) + domain = domain_class(**dom_params) if dom_type == "Cuboid": Lx = dom_params["r1"] - dom_params["l1"] @@ -56,9 +55,9 @@ def test_poisson_M1perp_1d(direction, bc_type, mapping, projected_rhs, show_plot errors = [] h_vec = [] if show_plot: - plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) - plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) - plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) + plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) + plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) + plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) for n, Neli in enumerate(Nels): # boundary conditions (overwritten below) @@ -72,16 +71,16 @@ def test_poisson_M1perp_1d(direction, bc_type, mapping, projected_rhs, show_plot if direction == 0: Nel = [Neli, 1, 1] p = [pi, 1, 1] - e1 = xp.linspace(0.0, 1.0, 50) + e1 = np.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [False, True, True] def sol1_xyz(x, y, z): - return xp.cos(xp.pi / Lx * x) + return np.cos(np.pi / Lx * x) def rho1_xyz(x, y, z): - return xp.cos(xp.pi / Lx * x) * (xp.pi / Lx) ** 2 + return np.cos(np.pi / Lx * x) * (np.pi / Lx) ** 2 else: if bc_type == "dirichlet": spl_kind = [False, True, True] @@ -89,24 +88,24 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lx * x) + return np.sin(2 * np.pi / Lx * x) def rho1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lx * x) * (2 * xp.pi / Lx) ** 2 + return np.sin(2 * np.pi / Lx * x) * (2 * np.pi / Lx) ** 2 elif direction == 1: Nel = [1, Neli, 1] p = [1, pi, 1] - e2 = xp.linspace(0.0, 1.0, 50) + e2 = np.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [True, False, True] def sol1_xyz(x, y, z): - return xp.cos(xp.pi / Ly * y) + return np.cos(np.pi / Ly * y) def rho1_xyz(x, y, z): - return xp.cos(xp.pi / Ly * y) * (xp.pi / Ly) ** 2 + return np.cos(np.pi / Ly * y) * (np.pi / Ly) ** 2 else: if bc_type == "dirichlet": spl_kind = [True, False, True] @@ -114,15 +113,15 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Ly * y) + return np.sin(2 * np.pi / Ly * y) def rho1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Ly * y) * (2 * xp.pi / Ly) ** 2 + return np.sin(2 * np.pi / Ly * y) * (2 * np.pi / Ly) ** 2 else: print("Direction should be either 0 or 1") # create derham object - print(f"{dirichlet_bc =}") + print(f"{dirichlet_bc = }") derham = Derham(Nel, p, spl_kind, dirichlet_bc=dirichlet_bc, comm=comm) # mass matrices @@ -133,16 +132,10 @@ def rho1_xyz(x, y, z): Propagator.mass_ops = mass_ops # pullbacks of right-hand side - def rho_pulled(e1, e2, e3): - return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=False) - - # define how to pass rho - if projected_rhs: - rho = FEECVariable(space="H1") - rho.allocate(derham=derham, domain=domain) - rho.spline.vector = derham.P["0"](rho_pulled) - else: - rho = rho_pulled + def rho1(e1, e2, e3): + return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=True) + + rho_vec = L2Projector("H1", mass_ops).get_dofs(rho1, apply_bc=True) # create Poisson solver solver_params = SolverParameters( @@ -165,7 +158,7 @@ def rho_pulled(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho, + rho=rho_vec, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -183,7 +176,7 @@ def rho_pulled(e1, e2, e3): analytic_value1 = sol1_xyz(x, y, z) if show_plot: - plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}") + plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }") plt.subplot(2, 3, n + 1) if direction == 0: plt.plot(x[:, 0, 0], sol_val1[:, 0, 0], "ob", label="numerical") @@ -193,25 +186,24 @@ def rho_pulled(e1, e2, e3): plt.plot(y[0, :, 0], sol_val1[0, :, 0], "ob", label="numerical") plt.plot(y[0, :, 0], analytic_value1[0, :, 0], "r--", label="exact") plt.xlabel("y") - plt.title(f"{Nel =}") + plt.title(f"{Nel = }") plt.legend() - error = xp.max(xp.abs(analytic_value1 - sol_val1)) - print(f"{direction =}, {pi =}, {Neli =}, {error=}") + error = np.max(np.abs(analytic_value1 - sol_val1)) + print(f"{direction = }, {pi = }, {Neli = }, {error=}") errors.append(error) h = 1 / (Neli) h_vec.append(h) - m, _ = xp.polyfit(xp.log(Nels), xp.log(errors), deg=1) - print(f"For {pi =}, solution converges in {direction=} with rate {-m =} ") - assert -m > (pi + 1 - 0.07) + m, _ = np.polyfit(np.log(Nels), np.log(errors), deg=1) + print(f"For {pi = }, solution converges in {direction=} with rate {-m = } ") + assert -m > (pi + 1 - 0.06) # Plot convergence in 1D if show_plot: plt.figure( - f"Convergence for degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", - figsize=(12, 8), + f"Convergence for degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(12, 8) ) plt.plot(h_vec, errors, "o", label=f"p={p[direction]}") plt.plot( @@ -224,13 +216,14 @@ def rho_pulled(e1, e2, e3): plt.xscale("log") plt.xlabel("Grid Spacing h") plt.ylabel("Error") - plt.title("Poisson solver") + plt.title(f"Poisson solver") plt.legend() if show_plot and rank == 0: plt.show() +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[64, 64, 1]]) @pytest.mark.parametrize("p", [[1, 1, 1], [2, 2, 1]]) @pytest.mark.parametrize("bc_type", ["periodic", "dirichlet", "neumann"]) @@ -241,8 +234,7 @@ def rho_pulled(e1, e2, e3): ["Orthogonal", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 1.0}], ], ) -@pytest.mark.parametrize("projected_rhs", [False, True]) -def test_poisson_M1perp_2d(Nel, p, bc_type, mapping, projected_rhs, show_plot=False): +def test_poisson_M1perp_2d(Nel, p, bc_type, mapping, show_plot=False): """ Test the Poisson solver with M1perp diffusion matrix by means of manufactured solutions in 2D . @@ -253,7 +245,7 @@ def test_poisson_M1perp_2d(Nel, p, bc_type, mapping, projected_rhs, show_plot=Fa dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain: Domain = domain_class(**dom_params) + domain = domain_class(**dom_params) if dom_type == "Cuboid": Lx = dom_params["r1"] - dom_params["l1"] @@ -264,10 +256,10 @@ def test_poisson_M1perp_2d(Nel, p, bc_type, mapping, projected_rhs, show_plot=Fa # manufactured solution in 1D (overwritten for "neumann") def sol1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lx * x) + return np.sin(2 * np.pi / Lx * x) def rho1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lx * x) * (2 * xp.pi / Lx) ** 2 + return np.sin(2 * np.pi / Lx * x) * (2 * np.pi / Lx) ** 2 # boundary conditions dirichlet_bc = None @@ -277,26 +269,26 @@ def rho1_xyz(x, y, z): # manufactured solution in 2D def sol2_xyz(x, y, z): - return xp.sin(2 * xp.pi * x / Lx + 4 * xp.pi / Ly * y) + return np.sin(2 * np.pi * x / Lx + 4 * np.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = xp.sin(2 * xp.pi / Lx * x + 4 * xp.pi / Ly * y) * (2 * xp.pi / Lx) ** 2 - ddy = xp.sin(2 * xp.pi / Lx * x + 4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 + ddx = np.sin(2 * np.pi / Lx * x + 4 * np.pi / Ly * y) * (2 * np.pi / Lx) ** 2 + ddy = np.sin(2 * np.pi / Lx * x + 4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 return ddx + ddy elif bc_type == "dirichlet": spl_kind = [False, True, True] dirichlet_bc = [(not kd,) * 2 for kd in spl_kind] dirichlet_bc = tuple(dirichlet_bc) - print(f"{dirichlet_bc =}") + print(f"{dirichlet_bc = }") # manufactured solution in 2D def sol2_xyz(x, y, z): - return xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) + return np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (xp.pi / Lx) ** 2 - ddy = xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 + ddx = np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (np.pi / Lx) ** 2 + ddy = np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 return ddx + ddy elif bc_type == "neumann": @@ -304,19 +296,19 @@ def rho2_xyz(x, y, z): # manufactured solution in 2D def sol2_xyz(x, y, z): - return xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) + return np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (xp.pi / Lx) ** 2 - ddy = xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 + ddx = np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (np.pi / Lx) ** 2 + ddy = np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 return ddx + ddy # manufactured solution in 1D def sol1_xyz(x, y, z): - return xp.cos(xp.pi / Lx * x) + return np.cos(np.pi / Lx * x) def rho1_xyz(x, y, z): - return xp.cos(xp.pi / Lx * x) * (xp.pi / Lx) ** 2 + return np.cos(np.pi / Lx * x) * (np.pi / Lx) ** 2 # create derham object derham = Derham(Nel, p, spl_kind, dirichlet_bc=dirichlet_bc, comm=comm) @@ -329,29 +321,21 @@ def rho1_xyz(x, y, z): Propagator.mass_ops = mass_ops # evaluation grid - e1 = xp.linspace(0.0, 1.0, 50) - e2 = xp.linspace(0.0, 1.0, 50) - e3 = xp.linspace(0.0, 1.0, 1) + e1 = np.linspace(0.0, 1.0, 50) + e2 = np.linspace(0.0, 1.0, 50) + e3 = np.linspace(0.0, 1.0, 1) # pullbacks of right-hand side - def rho1_pulled(e1, e2, e3): - return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=False) + def rho1(e1, e2, e3): + return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=True) - def rho2_pulled(e1, e2, e3): - return domain.pull(rho2_xyz, e1, e2, e3, kind="0", squeeze_out=False) + def rho2(e1, e2, e3): + return domain.pull(rho2_xyz, e1, e2, e3, kind="0", squeeze_out=True) - # how to pass right-hand sides - if projected_rhs: - rho1 = FEECVariable(space="H1") - rho1.allocate(derham=derham, domain=domain) - rho1.spline.vector = derham.P["0"](rho1_pulled) - - rho2 = FEECVariable(space="H1") - rho2.allocate(derham=derham, domain=domain) - rho2.spline.vector = derham.P["0"](rho2_pulled) - else: - rho1 = rho1_pulled - rho2 = rho2_pulled + # discrete right-hand sides + l2_proj = L2Projector("H1", mass_ops) + rho_vec1 = l2_proj.get_dofs(rho1, apply_bc=True) + rho_vec2 = l2_proj.get_dofs(rho2, apply_bc=True) # Create Poisson solvers solver_params = SolverParameters( @@ -374,7 +358,7 @@ def rho2_pulled(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho1, + rho=rho_vec1, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -394,7 +378,7 @@ def rho2_pulled(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho2, + rho=rho_vec2, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -416,12 +400,12 @@ def rho2_pulled(e1, e2, e3): analytic_value2 = sol2_xyz(x, y, z) # compute error - error1 = xp.max(xp.abs(analytic_value1 - sol_val1)) - error2 = xp.max(xp.abs(analytic_value2 - sol_val2)) + error1 = np.max(np.abs(analytic_value1 - sol_val1)) + error2 = np.max(np.abs(analytic_value2 - sol_val2)) - print(f"{p =}, {bc_type =}, {mapping =}") - print(f"{error1 =}") - print(f"{error2 =}") + print(f"{p = }, {bc_type = }, {mapping = }") + print(f"{error1 = }") + print(f"{error2 = }") print("") if show_plot and rank == 0: @@ -447,10 +431,11 @@ def rho2_pulled(e1, e2, e3): plt.show() assert error1 < 0.0044 - assert error2 < 0.023 + assert error2 < 0.021 @pytest.mark.skip(reason="Not clear if the 2.5d strategy is sound.") +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[32, 32, 16]]) @pytest.mark.parametrize("p", [[1, 1, 1], [2, 2, 1]]) @pytest.mark.parametrize( @@ -474,21 +459,21 @@ def test_poisson_M1perp_3d_compare_2p5d(Nel, p, mapping, show_plot=False): dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain: Domain = domain_class(**dom_params) + domain = domain_class(**dom_params) # boundary conditions spl_kind = [False, True, True] dirichlet_bc = ((True, True), (False, False), (False, False)) # evaluation grid - e1 = xp.linspace(0.0, 1.0, 50) - e2 = xp.linspace(0.0, 1.0, 60) - e3 = xp.linspace(0.0, 1.0, 30) + e1 = np.linspace(0.0, 1.0, 50) + e2 = np.linspace(0.0, 1.0, 60) + e3 = np.linspace(0.0, 1.0, 30) # solution and right-hand side on unit cube def rho(e1, e2, e3): - dd1 = xp.sin(xp.pi * e1) * xp.sin(4 * xp.pi * e2) * xp.cos(2 * xp.pi * e3) * (xp.pi) ** 2 - dd2 = xp.sin(xp.pi * e1) * xp.sin(4 * xp.pi * e2) * xp.cos(2 * xp.pi * e3) * (4 * xp.pi) ** 2 + dd1 = np.sin(np.pi * e1) * np.sin(4 * np.pi * e2) * np.cos(2 * np.pi * e3) * (np.pi) ** 2 + dd2 = np.sin(np.pi * e1) * np.sin(4 * np.pi * e2) * np.cos(2 * np.pi * e3) * (4 * np.pi) ** 2 return dd1 + dd2 # create 3d derham object @@ -504,7 +489,7 @@ def rho(e1, e2, e3): l2_proj = L2Projector("H1", mass_ops) rho_vec = l2_proj.get_dofs(rho, apply_bc=True) - print(f"{rho_vec[:].shape =}") + print(f"{rho_vec[:].shape = }") # Create 3d Poisson solver solver_params = SolverParameters( @@ -530,7 +515,7 @@ def rho(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho, + rho=rho_vec, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -554,6 +539,7 @@ def rho(e1, e2, e3): _phi_small = FEECVariable(space="H1") _phi_small.allocate(derham=derham, domain=domain) + rhs = derham.create_spline_function("rhs", "H1") poisson_solver_2p5d = ImplicitDiffusion() poisson_solver_2p5d.variables.phi = _phi_small @@ -564,7 +550,7 @@ def rho(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho, + rho=rhs.vector, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -583,6 +569,8 @@ def rho(e1, e2, e3): t0 = time() t_inner = 0.0 for n in range(s[2], e[2] + 1): + # scale the rhs with Nel[2] !! + rhs.vector[s[0] : e[0] + 1, s[1] : e[1] + 1, 0] = rho_vec[s[0] : e[0] + 1, s[1] : e[1] + 1, n] * Nel[2] t0i = time() poisson_solver_2p5d(dt) t1i = time() @@ -599,8 +587,8 @@ def rho(e1, e2, e3): sol_val_2p5d = domain.push(_phi_2p5d.spline, e1, e2, e3, kind="0") x, y, z = domain(e1, e2, e3) - print("max diff:", xp.max(xp.abs(sol_val - sol_val_2p5d))) - assert xp.max(xp.abs(sol_val - sol_val_2p5d)) < 0.026 + print("max diff:", np.max(np.abs(sol_val - sol_val_2p5d))) + assert np.max(np.abs(sol_val - sol_val_2p5d)) < 0.026 if show_plot and rank == 0: plt.figure("e1-e2 plane", figsize=(24, 16)) @@ -649,7 +637,7 @@ def rho(e1, e2, e3): # mapping = ['Orthogonal', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 1.}] # test_poisson_M1perp_2d(Nel, p, bc_type, mapping, show_plot=True) - # Nel = [64, 64, 16] - # p = [2, 2, 1] - # mapping = ["Cuboid", {"l1": 0.0, "r1": 1.0, "l2": 0.0, "r2": 1.0, "l3": 0.0, "r3": 1.0}] + Nel = [64, 64, 16] + p = [2, 2, 1] + mapping = ["Cuboid", {"l1": 0.0, "r1": 1.0, "l2": 0.0, "r2": 1.0, "l3": 0.0, "r3": 1.0}] # test_poisson_M1perp_3d_compare_2p5d(Nel, p, mapping, show_plot=True) diff --git a/src/struphy/propagators/tests/test_poisson.py b/src/struphy/propagators/tests/test_poisson.py index 588aa2aa1..5a1de11ba 100644 --- a/src/struphy/propagators/tests/test_poisson.py +++ b/src/struphy/propagators/tests/test_poisson.py @@ -1,35 +1,23 @@ -import cunumpy as xp import matplotlib.pyplot as plt +import numpy as np import pytest -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.projectors import L2Projector from struphy.feec.psydac_derham import Derham from struphy.geometry import domains -from struphy.geometry.base import Domain -from struphy.initial import perturbations -from struphy.kinetic_background.maxwellians import Maxwellian3D from struphy.linear_algebra.solver import SolverParameters from struphy.models.variables import FEECVariable -from struphy.pic.accumulation.accum_kernels import charge_density_0form -from struphy.pic.accumulation.particles_to_grid import AccumulatorVector -from struphy.pic.particles import Particles6D -from struphy.pic.utilities import ( - BinningPlot, - BoundaryParameters, - LoadingParameters, - WeightsParameters, -) +from struphy.propagators import ImplicitDiffusion from struphy.propagators.base import Propagator -from struphy.propagators.propagators_fields import ImplicitDiffusion, Poisson -from struphy.utils.pyccel import Pyccelkernel comm = MPI.COMM_WORLD rank = comm.Get_rank() plt.rcParams.update({"font.size": 22}) +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("direction", [0, 1, 2]) @pytest.mark.parametrize("bc_type", ["periodic", "dirichlet", "neumann"]) @pytest.mark.parametrize( @@ -39,14 +27,7 @@ ["Orthogonal", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 3.0}], ], ) -@pytest.mark.parametrize("projected_rhs", [False, True]) -def test_poisson_1d( - direction: int, - bc_type: str, - mapping: list[str, dict], - projected_rhs: bool, - show_plot: bool = False, -): +def test_poisson_1d(direction, bc_type, mapping, show_plot=False): """ Test the convergence of Poisson solver in 1D by means of manufactured solutions. """ @@ -56,7 +37,7 @@ def test_poisson_1d( dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain: Domain = domain_class(**dom_params) + domain = domain_class(**dom_params) if dom_type == "Cuboid": Lx = dom_params["r1"] - dom_params["l1"] @@ -73,9 +54,9 @@ def test_poisson_1d( errors = [] h_vec = [] if show_plot: - plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) - plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) - plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) + plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) + plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) + plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) for n, Neli in enumerate(Nels): # boundary conditions (overwritten below) @@ -89,16 +70,16 @@ def test_poisson_1d( if direction == 0: Nel = [Neli, 1, 1] p = [pi, 1, 1] - e1 = xp.linspace(0.0, 1.0, 50) + e1 = np.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [False, True, True] def sol1_xyz(x, y, z): - return xp.cos(xp.pi / Lx * x) + return np.cos(np.pi / Lx * x) def rho1_xyz(x, y, z): - return xp.cos(xp.pi / Lx * x) * (xp.pi / Lx) ** 2 + return np.cos(np.pi / Lx * x) * (np.pi / Lx) ** 2 else: if bc_type == "dirichlet": spl_kind = [False, True, True] @@ -106,24 +87,24 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lx * x) + return np.sin(2 * np.pi / Lx * x) def rho1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lx * x) * (2 * xp.pi / Lx) ** 2 + return np.sin(2 * np.pi / Lx * x) * (2 * np.pi / Lx) ** 2 elif direction == 1: Nel = [1, Neli, 1] p = [1, pi, 1] - e2 = xp.linspace(0.0, 1.0, 50) + e2 = np.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [True, False, True] def sol1_xyz(x, y, z): - return xp.cos(xp.pi / Ly * y) + return np.cos(np.pi / Ly * y) def rho1_xyz(x, y, z): - return xp.cos(xp.pi / Ly * y) * (xp.pi / Ly) ** 2 + return np.cos(np.pi / Ly * y) * (np.pi / Ly) ** 2 else: if bc_type == "dirichlet": spl_kind = [True, False, True] @@ -131,24 +112,24 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Ly * y) + return np.sin(2 * np.pi / Ly * y) def rho1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Ly * y) * (2 * xp.pi / Ly) ** 2 + return np.sin(2 * np.pi / Ly * y) * (2 * np.pi / Ly) ** 2 elif direction == 2: Nel = [1, 1, Neli] p = [1, 1, pi] - e3 = xp.linspace(0.0, 1.0, 50) + e3 = np.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [True, True, False] def sol1_xyz(x, y, z): - return xp.cos(xp.pi / Lz * z) + return np.cos(np.pi / Lz * z) def rho1_xyz(x, y, z): - return xp.cos(xp.pi / Lz * z) * (xp.pi / Lz) ** 2 + return np.cos(np.pi / Lz * z) * (np.pi / Lz) ** 2 else: if bc_type == "dirichlet": spl_kind = [True, True, False] @@ -156,10 +137,10 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lz * z) + return np.sin(2 * np.pi / Lz * z) def rho1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lz * z) * (2 * xp.pi / Lz) ** 2 + return np.sin(2 * np.pi / Lz * z) * (2 * np.pi / Lz) ** 2 else: print("Direction should be either 0, 1 or 2") @@ -174,16 +155,10 @@ def rho1_xyz(x, y, z): Propagator.mass_ops = mass_ops # pullbacks of right-hand side - def rho_pulled(e1, e2, e3): - return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=False) - - # define how to pass rho - if projected_rhs: - rho = FEECVariable(space="H1") - rho.allocate(derham=derham, domain=domain) - rho.spline.vector = derham.P["0"](rho_pulled) - else: - rho = rho_pulled + def rho1(e1, e2, e3): + return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=True) + + rho_vec = L2Projector("H1", mass_ops).get_dofs(rho1, apply_bc=True) # create Poisson solver solver_params = SolverParameters( @@ -197,14 +172,14 @@ def rho_pulled(e1, e2, e3): _phi = FEECVariable(space="H1") _phi.allocate(derham=derham, domain=domain) - poisson_solver = Poisson() + poisson_solver = ImplicitDiffusion() poisson_solver.variables.phi = _phi poisson_solver.options = poisson_solver.Options( - stab_eps=1e-12, - # sigma_2=0.0, - # sigma_3=1.0, - rho=rho, + sigma_1=1e-12, + sigma_2=0.0, + sigma_3=1.0, + rho=rho_vec, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -222,7 +197,7 @@ def rho_pulled(e1, e2, e3): analytic_value1 = sol1_xyz(x, y, z) if show_plot: - plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}") + plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }") plt.subplot(2, 3, n + 1) if direction == 0: plt.plot(x[:, 0, 0], sol_val1[:, 0, 0], "ob", label="numerical") @@ -236,25 +211,24 @@ def rho_pulled(e1, e2, e3): plt.plot(z[0, 0, :], sol_val1[0, 0, :], "ob", label="numerical") plt.plot(z[0, 0, :], analytic_value1[0, 0, :], "r--", label="exact") plt.xlabel("z") - plt.title(f"{Nel =}") + plt.title(f"{Nel = }") plt.legend() - error = xp.max(xp.abs(analytic_value1 - sol_val1)) - print(f"{direction =}, {pi =}, {Neli =}, {error=}") + error = np.max(np.abs(analytic_value1 - sol_val1)) + print(f"{direction = }, {pi = }, {Neli = }, {error=}") errors.append(error) h = 1 / (Neli) h_vec.append(h) - m, _ = xp.polyfit(xp.log(Nels), xp.log(errors), deg=1) - print(f"For {pi =}, solution converges in {direction=} with rate {-m =} ") - assert -m > (pi + 1 - 0.07) + m, _ = np.polyfit(np.log(Nels), np.log(errors), deg=1) + print(f"For {pi = }, solution converges in {direction=} with rate {-m = } ") + assert -m > (pi + 1 - 0.06) # Plot convergence in 1D if show_plot: plt.figure( - f"Convergence for degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", - figsize=(12, 8), + f"Convergence for degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(12, 8) ) plt.plot(h_vec, errors, "o", label=f"p={p[direction]}") plt.plot( @@ -267,168 +241,13 @@ def rho_pulled(e1, e2, e3): plt.xscale("log") plt.xlabel("Grid Spacing h") plt.ylabel("Error") - plt.title("Poisson solver") + plt.title(f"Poisson solver") plt.legend() if show_plot and rank == 0: plt.show() -@pytest.mark.parametrize( - "mapping", - [ - ["Cuboid", {"l1": 0.0, "r1": 4.0, "l2": 0.0, "r2": 2.0, "l3": 0.0, "r3": 3.0}], - # ["Orthogonal", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 3.0}], - ], -) -def test_poisson_accum_1d(mapping, do_plot=False): - """Pass accumulators as rhs.""" - # create domain object - dom_type = mapping[0] - dom_params = mapping[1] - - domain_class = getattr(domains, dom_type) - domain: Domain = domain_class(**dom_params) - - if dom_type == "Cuboid": - Lx = dom_params["r1"] - dom_params["l1"] - else: - Lx = dom_params["Lx"] - - # create derham object - Nel = (16, 1, 1) - p = (2, 1, 1) - spl_kind = (True, True, True) - derham = Derham(Nel, p, spl_kind, comm=comm) - - # mass matrices - mass_ops = WeightedMassOperators(derham, domain) - - Propagator.derham = derham - Propagator.domain = domain - Propagator.mass_ops = mass_ops - - # 6D particle object - domain_array = derham.domain_array - nprocs = derham.domain_decomposition.nprocs - domain_decomp = (domain_array, nprocs) - - lp = LoadingParameters(ppc=4000, seed=765) - wp = WeightsParameters(control_variate=True) - bp = BoundaryParameters() - - backgr = Maxwellian3D(n=(1.0, None)) - l = 1 - amp = 1e-1 - pert = perturbations.ModesCos(ls=(l,), amps=(amp,)) - maxw = Maxwellian3D(n=(1.0, pert)) - - pert_exact = lambda x, y, z: amp * xp.cos(l * 2 * xp.pi / Lx * x) - phi_exact = lambda x, y, z: amp / (l * 2 * xp.pi / Lx) ** 2 * xp.cos(l * 2 * xp.pi / Lx * x) - e_exact = lambda x, y, z: amp / (l * 2 * xp.pi / Lx) * xp.sin(l * 2 * xp.pi / Lx * x) - - particles = Particles6D( - comm_world=comm, - domain_decomp=domain_decomp, - loading_params=lp, - weights_params=wp, - boundary_params=bp, - domain=domain, - background=backgr, - initial_condition=maxw, - ) - particles.draw_markers() - particles.initialize_weights() - - # particle to grid coupling - kernel = Pyccelkernel(charge_density_0form) - accum = AccumulatorVector(particles, "H1", kernel, mass_ops, domain.args_domain) - # accum() - # if do_plot: - # accum.show_accumulated_spline_field(mass_ops) - - rho = accum - - # create Poisson solver - solver_params = SolverParameters( - tol=1.0e-13, - maxiter=3000, - info=True, - verbose=False, - recycle=False, - ) - - _phi = FEECVariable(space="H1") - _phi.allocate(derham=derham, domain=domain) - - poisson_solver = Poisson() - poisson_solver.variables.phi = _phi - - poisson_solver.options = poisson_solver.Options( - stab_eps=1e-6, - # sigma_2=0.0, - # sigma_3=1.0, - rho=rho, - solver="pcg", - precond="MassMatrixPreconditioner", - solver_params=solver_params, - ) - - poisson_solver.allocate() - - # Solve Poisson (call propagator with dt=1.) - dt = 1.0 - poisson_solver(dt) - - # push numerical solution and compare - e1 = xp.linspace(0.0, 1.0, 50) - e2 = 0.0 - e3 = 0.0 - - num_values = domain.push(_phi.spline, e1, e2, e3, kind="0") - x, y, z = domain(e1, e2, e3) - pert_values = pert_exact(x, y, z) - analytic_values = phi_exact(x, y, z) - e_values = e_exact(x, y, z) - - _e = FEECVariable(space="Hcurl") - _e.allocate(derham=derham, domain=domain) - derham.grad.dot(-_phi.spline.vector, out=_e.spline.vector) - num_values_e = domain.push(_e.spline, e1, e2, e3, kind="1") - - if do_plot: - field = derham.create_spline_function("accum_field", "H1") - field.vector = accum.vectors[0] - accum_values = field(e1, e2, e3) - - plt.figure(figsize=(18, 12)) - plt.subplot(1, 3, 1) - plt.plot(x[:, 0, 0], num_values[:, 0, 0], "ob", label="numerical") - plt.plot(x[:, 0, 0], analytic_values[:, 0, 0], "r--", label="exact") - plt.xlabel("x") - plt.title("phi") - plt.legend() - plt.subplot(1, 3, 2) - plt.plot(x[:, 0, 0], accum_values[:, 0, 0], "ob", label="numerical, without L2-proj") - plt.plot(x[:, 0, 0], pert_values[:, 0, 0], "r--", label="exact") - plt.xlabel("x") - plt.title("rhs") - plt.legend() - plt.subplot(1, 3, 3) - plt.plot(x[:, 0, 0], num_values_e[0][:, 0, 0], "ob", label="numerical") - plt.plot(x[:, 0, 0], e_values[:, 0, 0], "r--", label="exact") - plt.xlabel("x") - plt.title("e_field") - plt.legend() - - plt.show() - - error = xp.max(xp.abs(num_values_e[0][:, 0, 0] - e_values[:, 0, 0])) / xp.max(xp.abs(e_values[:, 0, 0])) - print(f"{error=}") - - assert error < 0.0086 - - @pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[64, 64, 1]]) @pytest.mark.parametrize("p", [[1, 1, 1], [2, 2, 1]]) @@ -440,8 +259,7 @@ def test_poisson_accum_1d(mapping, do_plot=False): ["Colella", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 1.0}], ], ) -@pytest.mark.parametrize("projected_rhs", [False, True]) -def test_poisson_2d(Nel, p, bc_type, mapping, projected_rhs, show_plot=False): +def test_poisson_2d(Nel, p, bc_type, mapping, show_plot=False): """ Test the Poisson solver by means of manufactured solutions in 2D . """ @@ -451,7 +269,7 @@ def test_poisson_2d(Nel, p, bc_type, mapping, projected_rhs, show_plot=False): dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain: Domain = domain_class(**dom_params) + domain = domain_class(**dom_params) if dom_type == "Cuboid": Lx = dom_params["r1"] - dom_params["l1"] @@ -462,10 +280,10 @@ def test_poisson_2d(Nel, p, bc_type, mapping, projected_rhs, show_plot=False): # manufactured solution in 1D (overwritten for "neumann") def sol1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lx * x) + return np.sin(2 * np.pi / Lx * x) def rho1_xyz(x, y, z): - return xp.sin(2 * xp.pi / Lx * x) * (2 * xp.pi / Lx) ** 2 + return np.sin(2 * np.pi / Lx * x) * (2 * np.pi / Lx) ** 2 # boundary conditions dirichlet_bc = None @@ -475,26 +293,26 @@ def rho1_xyz(x, y, z): # manufactured solution in 2D def sol2_xyz(x, y, z): - return xp.sin(2 * xp.pi * x / Lx + 4 * xp.pi / Ly * y) + return np.sin(2 * np.pi * x / Lx + 4 * np.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = xp.sin(2 * xp.pi / Lx * x + 4 * xp.pi / Ly * y) * (2 * xp.pi / Lx) ** 2 - ddy = xp.sin(2 * xp.pi / Lx * x + 4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 + ddx = np.sin(2 * np.pi / Lx * x + 4 * np.pi / Ly * y) * (2 * np.pi / Lx) ** 2 + ddy = np.sin(2 * np.pi / Lx * x + 4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 return ddx + ddy elif bc_type == "dirichlet": spl_kind = [False, True, True] dirichlet_bc = [(not kd,) * 2 for kd in spl_kind] dirichlet_bc = tuple(dirichlet_bc) - print(f"{dirichlet_bc =}") + print(f"{dirichlet_bc = }") # manufactured solution in 2D def sol2_xyz(x, y, z): - return xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) + return np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (xp.pi / Lx) ** 2 - ddy = xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 + ddx = np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (np.pi / Lx) ** 2 + ddy = np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 return ddx + ddy elif bc_type == "neumann": @@ -502,19 +320,19 @@ def rho2_xyz(x, y, z): # manufactured solution in 2D def sol2_xyz(x, y, z): - return xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) + return np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (xp.pi / Lx) ** 2 - ddy = xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 + ddx = np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (np.pi / Lx) ** 2 + ddy = np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 return ddx + ddy # manufactured solution in 1D def sol1_xyz(x, y, z): - return xp.cos(xp.pi / Lx * x) + return np.cos(np.pi / Lx * x) def rho1_xyz(x, y, z): - return xp.cos(xp.pi / Lx * x) * (xp.pi / Lx) ** 2 + return np.cos(np.pi / Lx * x) * (np.pi / Lx) ** 2 # create derham object derham = Derham(Nel, p, spl_kind, dirichlet_bc=dirichlet_bc, comm=comm) @@ -527,29 +345,21 @@ def rho1_xyz(x, y, z): Propagator.mass_ops = mass_ops # evaluation grid - e1 = xp.linspace(0.0, 1.0, 50) - e2 = xp.linspace(0.0, 1.0, 50) - e3 = xp.linspace(0.0, 1.0, 1) + e1 = np.linspace(0.0, 1.0, 50) + e2 = np.linspace(0.0, 1.0, 50) + e3 = np.linspace(0.0, 1.0, 1) # pullbacks of right-hand side - def rho1_pulled(e1, e2, e3): - return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=False) + def rho1(e1, e2, e3): + return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=True) - def rho2_pulled(e1, e2, e3): - return domain.pull(rho2_xyz, e1, e2, e3, kind="0", squeeze_out=False) + def rho2(e1, e2, e3): + return domain.pull(rho2_xyz, e1, e2, e3, kind="0", squeeze_out=True) - # how to pass right-hand sides - if projected_rhs: - rho1 = FEECVariable(space="H1") - rho1.allocate(derham=derham, domain=domain) - rho1.spline.vector = derham.P["0"](rho1_pulled) - - rho2 = FEECVariable(space="H1") - rho2.allocate(derham=derham, domain=domain) - rho2.spline.vector = derham.P["0"](rho2_pulled) - else: - rho1 = rho1_pulled - rho2 = rho2_pulled + # discrete right-hand sides + l2_proj = L2Projector("H1", mass_ops) + rho_vec1 = l2_proj.get_dofs(rho1, apply_bc=True) + rho_vec2 = l2_proj.get_dofs(rho2, apply_bc=True) # Create Poisson solvers solver_params = SolverParameters( @@ -563,14 +373,14 @@ def rho2_pulled(e1, e2, e3): _phi1 = FEECVariable(space="H1") _phi1.allocate(derham=derham, domain=domain) - poisson_solver1 = Poisson() + poisson_solver1 = ImplicitDiffusion() poisson_solver1.variables.phi = _phi1 poisson_solver1.options = poisson_solver1.Options( - stab_eps=1e-8, - # sigma_2=0.0, - # sigma_3=1.0, - rho=rho1, + sigma_1=1e-8, + sigma_2=0.0, + sigma_3=1.0, + rho=rho_vec1, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -579,27 +389,21 @@ def rho2_pulled(e1, e2, e3): poisson_solver1.allocate() # _phi1 = derham.create_spline_function("test1", "H1") - # poisson_solver1 = Poisson( + # poisson_solver1 = ImplicitDiffusion( # _phi1.vector, sigma_1=1e-8, sigma_2=0.0, sigma_3=1.0, rho=rho_vec1, solver=solver_params # ) _phi2 = FEECVariable(space="H1") _phi2.allocate(derham=derham, domain=domain) - poisson_solver2 = Poisson() + poisson_solver2 = ImplicitDiffusion() poisson_solver2.variables.phi = _phi2 - stab_eps = 1e-8 - err_lim = 0.03 - if bc_type == "neumann" and dom_type == "Colella": - stab_eps = 1e-4 - err_lim = 0.046 - poisson_solver2.options = poisson_solver2.Options( - stab_eps=stab_eps, - # sigma_2=0.0, - # sigma_3=1.0, - rho=rho2, + sigma_1=1e-8, + sigma_2=0.0, + sigma_3=1.0, + rho=rho_vec2, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -608,7 +412,7 @@ def rho2_pulled(e1, e2, e3): poisson_solver2.allocate() # _phi2 = derham.create_spline_function("test2", "H1") - # poisson_solver2 = Poisson( + # poisson_solver2 = ImplicitDiffusion( # _phi2.vector, sigma_1=1e-8, sigma_2=0.0, sigma_3=1.0, rho=rho_vec2, solver=solver_params # ) @@ -626,12 +430,12 @@ def rho2_pulled(e1, e2, e3): analytic_value2 = sol2_xyz(x, y, z) # compute error - error1 = xp.max(xp.abs(analytic_value1 - sol_val1)) - error2 = xp.max(xp.abs(analytic_value2 - sol_val2)) + error1 = np.max(np.abs(analytic_value1 - sol_val1)) + error2 = np.max(np.abs(analytic_value2 - sol_val2)) - print(f"{p =}, {bc_type =}, {mapping =}") - print(f"{error1 =}") - print(f"{error2 =}") + print(f"{p = }, {bc_type = }, {mapping = }") + print(f"{error1 = }") + print(f"{error2 = }") print("") if show_plot and rank == 0: @@ -659,23 +463,21 @@ def rho2_pulled(e1, e2, e3): if p[0] == 1 and bc_type == "neumann" and mapping[0] == "Colella": pass else: - assert error1 < 0.0053 - assert error2 < err_lim + assert error1 < 0.0044 + assert error2 < 0.021 if __name__ == "__main__": - # direction = 0 - # bc_type = "dirichlet" + direction = 2 + bc_type = "dirichlet" mapping = ["Cuboid", {"l1": 0.0, "r1": 4.0, "l2": 0.0, "r2": 2.0, "l3": 0.0, "r3": 3.0}] # mapping = ['Orthogonal', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 3.}] - # test_poisson_1d(direction, bc_type, mapping, projected_rhs=True, show_plot=True) + test_poisson_1d(direction, bc_type, mapping, show_plot=True) # Nel = [64, 64, 1] # p = [2, 2, 1] # bc_type = 'neumann' - # # mapping = ['Cuboid', {'l1': 0., 'r1': 4., 'l2': 0., 'r2': 2., 'l3': 0., 'r3': 3.}] - # # mapping = ['Orthogonal', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 1.}] + # #mapping = ['Cuboid', {'l1': 0., 'r1': 4., 'l2': 0., 'r2': 2., 'l3': 0., 'r3': 3.}] + # #mapping = ['Orthogonal', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 1.}] # mapping = ['Colella', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 1.}] - # test_poisson_2d(Nel, p, bc_type, mapping, projected_rhs=True, show_plot=True) - - test_poisson_accum_1d(mapping, do_plot=True) + # test_poisson_2d(Nel, p, bc_type, mapping, show_plot=True) diff --git a/src/struphy/psydac-2.4.5.dev0-py3-none-any.whl b/src/struphy/psydac-2.4.5.dev0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..71dcd07a078831413d1045ef2bc999ff82e392eb GIT binary patch literal 240986 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

U3#tHNA=O_> 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

  • 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\s*([\w\.\-]+)", entry) + if match: + op, version = match.groups() + return f"{op}{version}" + return None + + +def get_max_bound(entry): + match = re.search(r"(<=|<)\s*([\w\.\-]+)", entry) + if match: + op, version = match.groups() + return f"{op}{version}" + return None + + +def get_package_name(entry): + return re.split(r"[<>=~]", entry.strip())[0].replace(" ", "") + + +def generate_updated_entry(package_name, package_deps): + ver_def = package_name + # Always set max version to the currently installed version + ver_def += f"<={package_deps['installed']}" + + if package_deps["min"]: + ver_def += f", {package_deps['min']}" + return ver_def + + +def update_dependencies(dependencies): + for i, entry in enumerate(dependencies): + package_name = get_package_name(entry) + + try: + installed_version = importlib.metadata.version(package_name) + + package_deps = {"installed": installed_version, "min": get_min_bound(entry), "max": get_max_bound(entry)} + + if package_deps["installed"]: + dependencies[i] = generate_updated_entry(package_name, package_deps) + + except importlib.metadata.PackageNotFoundError: + print(f"Warning: {package_name} not installed, skipping...") + continue + + # Remove psydac from the dependencies + for i, entry in enumerate(dependencies): + if "psydac" in entry: + dependencies.pop(i) + + +def main(): + with open("pyproject.toml", "rb") as f: + pyproject_data = tomllib.load(f) + + mandatory_dependencies = pyproject_data["project"]["dependencies"] + optional_dependency_groups = pyproject_data["project"]["optional-dependencies"] + + update_dependencies(mandatory_dependencies) + for group_name, group_deps in optional_dependency_groups.items(): + update_dependencies(group_deps) + + with open("pyproject.toml", "wb") as f: + tomli_w.dump(pyproject_data, f) + + +if __name__ == "__main__": + main() diff --git a/src/struphy/utils/test_clone_config.py b/src/struphy/utils/test_clone_config.py index b1c84139b..14b590a1e 100644 --- a/src/struphy/utils/test_clone_config.py +++ b/src/struphy/utils/test_clone_config.py @@ -1,20 +1,15 @@ import pytest -from psydac.ddm.mpi import MockComm -from psydac.ddm.mpi import mpi as MPI +from mpi4py import MPI +@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("Np", [1000, 999]) @pytest.mark.parametrize("num_clones", [1, 2]) def test_clone_config(Nel, Np, num_clones): from struphy.utils.clone_config import CloneConfig - if isinstance(MPI.COMM_WORLD, MockComm): - comm = None - num_clones = 1 - else: - comm = MPI.COMM_WORLD - + comm = MPI.COMM_WORLD species = "ions" params = { "grid": { @@ -24,8 +19,8 @@ def test_clone_config(Nel, Np, num_clones): species: { "markers": { "Np": Np, - }, - }, + } + } }, } @@ -37,7 +32,7 @@ def test_clone_config(Nel, Np, num_clones): # Print outputs pconf.print_clone_config() pconf.print_particle_config() - print(f"{pconf.get_Np_clone(Np) =}") + print(f"{pconf.get_Np_clone(Np) = }") if __name__ == "__main__": diff --git a/src/struphy/utils/utils.py b/src/struphy/utils/utils.py index 171b61c23..aac94fd10 100644 --- a/src/struphy/utils/utils.py +++ b/src/struphy/utils/utils.py @@ -61,12 +61,12 @@ def save_state(state, libpath=STRUPHY_LIBPATH): def print_all_attr(obj): """Print all object's attributes that do not start with "_" to screen.""" - import cunumpy as xp + import numpy as np for k in dir(obj): if k[0] != "_": v = getattr(obj, k) - if isinstance(v, xp.ndarray): + if isinstance(v, np.ndarray): v = f"{type(getattr(obj, k))} of shape {v.shape}" if "proj_" in k or "quad_grid_" in k: v = "(arrays not displayed)" @@ -203,6 +203,6 @@ def subp_run(cmd, cwd="libpath", check=True): for k, val in state.items(): print(k, val) i_path, o_path, b_path = get_paths(state) - print(f"{i_path =}") - print(f"{o_path =}") - print(f"{b_path =}") + print(f"{i_path = }") + print(f"{o_path = }") + print(f"{b_path = }") From 7b3e5372d71d864f9ad5690894c697d97b6aa30a Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Fri, 14 Nov 2025 10:00:36 +0100 Subject: [PATCH 2/4] Restored to devel --- src/struphy/bsplines/bsplines.py | 62 +- src/struphy/bsplines/bsplines_kernels.py | 16 +- src/struphy/bsplines/evaluation_kernels_1d.py | 7 +- src/struphy/bsplines/evaluation_kernels_3d.py | 289 +- .../bsplines/tests/test_bsplines_kernels.py | 54 +- .../bsplines/tests/test_eval_spline_mpi.py | 179 +- src/struphy/console/compile.py | 16 +- src/struphy/console/format.py | 1279 ++--- src/struphy/console/main.py | 4 +- src/struphy/console/params.py | 4 +- src/struphy/console/profile.py | 14 +- src/struphy/console/test.py | 111 +- src/struphy/console/tests/test_console.py | 29 +- src/struphy/diagnostics/console_diagn.py | 6 +- src/struphy/diagnostics/continuous_spectra.py | 41 +- src/struphy/diagnostics/diagn_tools.py | 138 +- src/struphy/diagnostics/diagnostics_pic.ipynb | 86 +- .../diagnostics/paraview/mesh_creator.py | 74 +- src/struphy/dispersion_relations/analytic.py | 100 +- src/struphy/dispersion_relations/base.py | 10 +- src/struphy/dispersion_relations/utilities.py | 4 +- src/struphy/eigenvalue_solvers/derivatives.py | 8 +- .../kernels_projectors_global.py | 62 +- .../legacy/MHD_eigenvalues_cylinder_1D.py | 417 +- .../control_variates/control_variate.py | 42 +- .../fB_massless_control_variate.py | 4 +- .../fB_massless_kernels_control_variate.py | 42 +- .../fnB_massless_control_variate.py | 6 +- .../fnB_massless_kernels_control_variate.py | 56 +- .../massless_control_variate.py | 6 +- .../massless_kernels_control_variate.py | 129 +- .../legacy/emw_operators.py | 14 +- .../legacy/inner_products_1d.py | 10 +- .../legacy/inner_products_2d.py | 38 +- .../legacy/inner_products_3d.py | 38 +- .../eigenvalue_solvers/legacy/l2_error_1d.py | 10 +- .../eigenvalue_solvers/legacy/l2_error_2d.py | 50 +- .../eigenvalue_solvers/legacy/l2_error_3d.py | 50 +- .../legacy/mass_matrices_3d_pre.py | 92 +- .../legacy/massless_operators/fB_arrays.py | 212 +- .../legacy/massless_operators/fB_bv_kernel.py | 24 +- .../fB_massless_linear_operators.py | 119 +- .../legacy/massless_operators/fB_vv_kernel.py | 8 +- .../legacy/mhd_operators_MF.py | 226 +- .../pro_local/mhd_operators_3d_local.py | 618 +-- .../pro_local/projectors_local.py | 686 +-- .../shape_L2_projector_kernel.py | 54 +- .../shape_function_projectors_L2.py | 167 +- .../shape_function_projectors_local.py | 273 +- .../shape_local_projector_kernel.py | 141 +- .../eigenvalue_solvers/mass_matrices_1d.py | 26 +- .../eigenvalue_solvers/mass_matrices_2d.py | 92 +- .../eigenvalue_solvers/mass_matrices_3d.py | 101 +- .../mhd_axisymmetric_main.py | 34 +- .../mhd_axisymmetric_pproc.py | 15 +- .../eigenvalue_solvers/mhd_operators.py | 85 +- .../eigenvalue_solvers/mhd_operators_core.py | 658 +-- .../eigenvalue_solvers/projectors_global.py | 369 +- .../eigenvalue_solvers/spline_space.py | 451 +- src/struphy/examples/_draw_parallel.py | 14 +- .../examples/restelli2018/callables.py | 66 +- src/struphy/feec/basis_projection_ops.py | 67 +- src/struphy/feec/linear_operators.py | 112 +- src/struphy/feec/local_projectors_kernels.py | 66 +- src/struphy/feec/mass.py | 243 +- src/struphy/feec/mass_kernels.py | 19 +- src/struphy/feec/preconditioner.py | 52 +- src/struphy/feec/projectors.py | 168 +- src/struphy/feec/psydac_derham.py | 353 +- src/struphy/feec/tests/test_basis_ops.py | 78 +- src/struphy/feec/tests/test_derham.py | 54 +- src/struphy/feec/tests/test_eval_field.py | 213 +- src/struphy/feec/tests/test_field_init.py | 253 +- src/struphy/feec/tests/test_l2_projectors.py | 66 +- .../feec/tests/test_local_projectors.py | 421 +- .../feec/tests/test_lowdim_nel_is_1.py | 74 +- src/struphy/feec/tests/test_mass_matrices.py | 166 +- .../feec/tests/test_toarray_struphy.py | 36 +- .../feec/tests/test_tosparse_struphy.py | 74 +- src/struphy/feec/tests/xx_test_preconds.py | 16 +- src/struphy/feec/utilities.py | 40 +- .../feec/utilities_local_projectors.py | 127 +- src/struphy/feec/variational_utilities.py | 143 +- src/struphy/fields_background/base.py | 129 +- src/struphy/fields_background/equils.py | 260 +- .../mhd_equil/eqdsk/readeqdsk.py | 6 +- .../tests/test_desc_equil.py | 80 +- .../tests/test_generic_equils.py | 28 +- .../tests/test_mhd_equils.py | 83 +- .../tests/test_numerical_mhd_equil.py | 72 +- src/struphy/geometry/base.py | 628 +-- src/struphy/geometry/domains.py | 41 +- src/struphy/geometry/evaluation_kernels.py | 24 +- src/struphy/geometry/mappings_kernels.py | 192 +- src/struphy/geometry/tests/test_domain.py | 128 +- src/struphy/geometry/transform_kernels.py | 24 +- src/struphy/geometry/utilities.py | 43 +- src/struphy/geometry/utilities_kernels.py | 8 +- src/struphy/initial/eigenfunctions.py | 49 +- src/struphy/initial/perturbations.py | 192 +- .../initial/tests/test_init_perturbations.py | 43 +- src/struphy/initial/utilities.py | 4 +- src/struphy/io/inp/parameters.yml | 2 +- src/struphy/io/inp/params_Maxwell.py | 8 +- src/struphy/io/options.py | 19 +- src/struphy/io/output_handling.py | 16 +- src/struphy/io/setup.py | 55 +- src/struphy/kinetic_background/base.py | 72 +- src/struphy/kinetic_background/maxwellians.py | 101 +- .../kinetic_background/tests/test_base.py | 38 +- .../tests/test_maxwellians.py | 534 ++- src/struphy/linear_algebra/linalg_kron.py | 23 +- src/struphy/linear_algebra/saddle_point.py | 133 +- src/struphy/linear_algebra/solver.py | 25 + .../tests/test_saddle_point_propagator.py | 6 +- .../tests/test_saddlepoint_massmatrices.py | 70 +- .../tests/test_stencil_dot_kernels.py | 44 +- .../tests/test_stencil_transpose_kernels.py | 38 +- src/struphy/main.py | 147 +- src/struphy/models/__init__.py | 146 +- src/struphy/models/base.py | 413 +- src/struphy/models/fluid.py | 2710 ++++++----- src/struphy/models/hybrid.py | 1466 +++--- src/struphy/models/kinetic.py | 1015 ++-- src/struphy/models/species.py | 24 +- src/struphy/models/tests/test_models.py | 62 +- .../models/tests/test_verif_LinearMHD.py | 26 +- .../models/tests/test_verif_Maxwell.py | 70 +- src/struphy/models/tests/verification.py | 118 +- src/struphy/models/toy.py | 1053 ++--- src/struphy/models/variables.py | 22 +- src/struphy/ode/solvers.py | 2 +- src/struphy/ode/tests/test_ode_feec.py | 39 +- src/struphy/ode/utils.py | 8 +- src/struphy/pic/accumulation/accum_kernels.py | 79 +- .../pic/accumulation/accum_kernels_gc.py | 848 +++- .../pic/accumulation/filter_kernels.py | 22 +- .../accumulation/particle_to_mat_kernels.py | 42 +- .../pic/accumulation/particles_to_grid.py | 168 +- src/struphy/pic/base.py | 610 +-- src/struphy/pic/particles.py | 41 +- src/struphy/pic/pushing/pusher.py | 51 +- src/struphy/pic/pushing/pusher_kernels.py | 529 +-- src/struphy/pic/pushing/pusher_kernels_gc.py | 493 +- .../pic/pushing/pusher_utilities_kernels.py | 463 -- src/struphy/pic/sampling_kernels.py | 6 +- src/struphy/pic/sobol_seq.py | 68 +- src/struphy/pic/sph_eval_kernels.py | 14 +- src/struphy/pic/tests/test_accum_vec_H1.py | 86 +- src/struphy/pic/tests/test_accumulation.py | 71 +- src/struphy/pic/tests/test_binning.py | 234 +- src/struphy/pic/tests/test_draw_parallel.py | 16 +- src/struphy/pic/tests/test_mat_vec_filler.py | 120 +- .../test_pic_legacy_files/accumulation.py | 44 +- .../accumulation_kernels_3d.py | 192 +- .../test_pic_legacy_files/mappings_3d.py | 212 +- .../test_pic_legacy_files/mappings_3d_fast.py | 305 +- .../pic/tests/test_pic_legacy_files/pusher.py | 2 +- .../tests/test_pic_legacy_files/pusher_pos.py | 1257 ++++- .../test_pic_legacy_files/pusher_vel_2d.py | 200 +- .../test_pic_legacy_files/pusher_vel_3d.py | 294 +- .../spline_evaluation_2d.py | 2 +- .../spline_evaluation_3d.py | 268 +- src/struphy/pic/tests/test_pushers.py | 132 +- src/struphy/pic/tests/test_sorting.py | 46 +- src/struphy/pic/tests/test_sph.py | 368 +- src/struphy/pic/tests/test_tesselation.py | 50 +- src/struphy/pic/utilities.py | 38 +- src/struphy/pic/utilities_kernels.py | 285 +- src/struphy/polar/basic.py | 20 +- src/struphy/polar/extraction_operators.py | 245 +- src/struphy/polar/linear_operators.py | 21 +- .../polar/tests/test_legacy_polar_splines.py | 20 +- src/struphy/polar/tests/test_polar.py | 49 +- .../likwid/plot_likwidproject.py | 12 +- .../likwid/plot_time_traces.py | 68 +- .../likwid/roofline_plotter.py | 12 +- .../post_processing/orbits/orbits_tools.py | 48 +- .../post_processing/post_processing_tools.py | 70 +- src/struphy/post_processing/pproc_struphy.py | 30 +- .../post_processing/profile_struphy.py | 10 +- src/struphy/profiling/profiling.py | 24 +- src/struphy/propagators/__init__.py | 193 +- src/struphy/propagators/base.py | 21 +- .../propagators/propagators_coupling.py | 2083 +++++---- src/struphy/propagators/propagators_fields.py | 4131 +++++++++-------- .../propagators/propagators_markers.py | 590 ++- .../tests/test_gyrokinetic_poisson.py | 188 +- src/struphy/propagators/tests/test_poisson.py | 386 +- src/struphy/topology/grids.py | 2 +- src/struphy/utils/clone_config.py | 49 +- src/struphy/utils/test_clone_config.py | 17 +- src/struphy/utils/utils.py | 10 +- 193 files changed, 20588 insertions(+), 16783 deletions(-) diff --git a/src/struphy/bsplines/bsplines.py b/src/struphy/bsplines/bsplines.py index 31738fe74..9974a9ff2 100644 --- a/src/struphy/bsplines/bsplines.py +++ b/src/struphy/bsplines/bsplines.py @@ -16,7 +16,7 @@ """ -import numpy as np +import cunumpy as xp __all__ = [ "find_span", @@ -105,7 +105,7 @@ def scaling_vector(knots, degree, span): Scaling vector with elements (p + 1)/(t[i + p + 1] - t[i]) """ - x = np.zeros(degree + 1, dtype=float) + x = xp.zeros(degree + 1, dtype=float) for il in range(degree + 1): i = span - il @@ -148,9 +148,9 @@ def basis_funs(knots, degree, x, span, normalize=False): by using 'left' and 'right' temporary arrays that are one element shorter. """ - left = np.empty(degree, dtype=float) - right = np.empty(degree, dtype=float) - values = np.empty(degree + 1, dtype=float) + left = xp.empty(degree, dtype=float) + right = xp.empty(degree, dtype=float) + values = xp.empty(degree + 1, dtype=float) values[0] = 1.0 @@ -164,7 +164,7 @@ def basis_funs(knots, degree, x, span, normalize=False): saved = left[j - r] * temp values[j + 1] = saved - if normalize == True: + if normalize: values = values * scaling_vector(knots, degree, span) return values @@ -205,7 +205,7 @@ def basis_funs_1st_der(knots, degree, x, span): # Compute derivatives at x using formula based on difference of splines of degree deg - 1 # ------- # j = 0 - ders = np.empty(degree + 1, dtype=float) + ders = xp.empty(degree + 1, dtype=float) saved = degree * values[0] / (knots[span + 1] - knots[span + 1 - degree]) ders[0] = -saved @@ -261,11 +261,11 @@ def basis_funs_all_ders(knots, degree, x, span, n): - innermost loops are replaced with vector operations on slices. """ - left = np.empty(degree) - right = np.empty(degree) - ndu = np.empty((degree + 1, degree + 1)) - a = np.empty((2, degree + 1)) - ders = np.zeros((n + 1, degree + 1)) # output array + left = xp.empty(degree) + right = xp.empty(degree) + ndu = xp.empty((degree + 1, degree + 1)) + a = xp.empty((2, degree + 1)) + ders = xp.zeros((n + 1, degree + 1)) # output array # Number of derivatives that need to be effectively computed # Derivatives higher than degree are = 0. @@ -304,7 +304,7 @@ def basis_funs_all_ders(knots, degree, x, span, n): j1 = 1 if (rk > -1) else -rk j2 = k - 1 if (r - 1 <= pk) else degree - r a[s2, j1 : j2 + 1] = (a[s1, j1 : j2 + 1] - a[s1, j1 - 1 : j2]) * ndu[pk + 1, rk + j1 : rk + j2 + 1] - d += np.dot(a[s2, j1 : j2 + 1], ndu[rk + j1 : rk + j2 + 1, pk]) + d += xp.dot(a[s2, j1 : j2 + 1], ndu[rk + j1 : rk + j2 + 1, pk]) if r <= pk: a[s2, k] = -a[s1, k - 1] * ndu[pk + 1, r] d += a[s2, k] * ndu[r, pk] @@ -362,7 +362,7 @@ def collocation_matrix(knots, degree, xgrid, periodic, normalize=False): nx = len(xgrid) # Collocation matrix as 2D Numpy array (dense storage) - mat = np.zeros((nx, nb), dtype=float) + mat = xp.zeros((nx, nb), dtype=float) # Indexing of basis functions (periodic or not) for a given span if periodic: @@ -418,12 +418,12 @@ def histopolation_matrix(knots, degree, xgrid, periodic): # Number of integrals if periodic: el_b = breakpoints(knots, degree) - xgrid = np.array([el_b[0]] + list(xgrid) + [el_b[-1]]) + xgrid = xp.array([el_b[0]] + list(xgrid) + [el_b[-1]]) ni = len(xgrid) - 1 # Histopolation matrix of M-splines as 2D Numpy array (dense storage) - his = np.zeros((ni, nbD), dtype=float) + his = xp.zeros((ni, nbD), dtype=float) # Collocation matrix of B-splines col = collocation_matrix(knots, degree, xgrid, False, normalize=False) @@ -434,7 +434,7 @@ def histopolation_matrix(knots, degree, xgrid, periodic): for k in range(j + 1): his[i, j % nbD] += col[i, k] - col[i + 1, k] - if np.abs(his[i, j % nbD]) < 1e-14: + if xp.abs(his[i, j % nbD]) < 1e-14: his[i, j % nbD] = 0.0 # add first to last integration interval in case of periodic splines @@ -470,7 +470,7 @@ def breakpoints(knots, degree): else: endsl = -degree - return np.unique(knots[slice(degree, endsl)]) + return xp.unique(knots[slice(degree, endsl)]) # ============================================================================== @@ -501,13 +501,13 @@ def greville(knots, degree, periodic): n = len(T) - 2 * p - 1 if periodic else len(T) - p - 1 # Compute greville abscissas as average of p consecutive knot values - xg = np.around([sum(T[i : i + p]) / p for i in range(s, s + n)], decimals=15) + xg = xp.around([sum(T[i : i + p]) / p for i in range(s, s + n)], decimals=15) # If needed apply periodic boundary conditions if periodic: a = T[p] b = T[-p] - xg = np.around((xg - a) % (b - a) + a, decimals=15) + xg = xp.around((xg - a) % (b - a) + a, decimals=15) return xg @@ -537,7 +537,7 @@ def elements_spans(knots, degree): >>> from psydac.core.bsplines import make_knots, elements_spans >>> p = 3 ; n = 8 - >>> grid = np.arange( n-p+1 ) + >>> grid = xp.arange( n-p+1 ) >>> knots = make_knots( breaks=grid, degree=p, periodic=False ) >>> spans = elements_spans( knots=knots, degree=p ) >>> spans @@ -549,13 +549,13 @@ def elements_spans(knots, degree): 2) This function could be written in two lines: breaks = breakpoints( knots, degree ) - spans = np.searchsorted( knots, breaks[:-1], side='right' ) - 1 + spans = xp.searchsorted( knots, breaks[:-1], side='right' ) - 1 """ breaks = breakpoints(knots, degree) nk = len(knots) ne = len(breaks) - 1 - spans = np.zeros(ne, dtype=int) + spans = xp.zeros(ne, dtype=int) ie = 0 for ik in range(degree, nk - degree): @@ -600,13 +600,13 @@ def make_knots(breaks, degree, periodic): # Consistency checks assert len(breaks) > 1 - assert all(np.diff(breaks) > 0) + assert all(xp.diff(breaks) > 0) assert degree > 0 if periodic: assert len(breaks) > degree p = degree - T = np.zeros(len(breaks) + 2 * p, dtype=float) + T = xp.zeros(len(breaks) + 2 * p, dtype=float) T[p:-p] = breaks if periodic: @@ -671,13 +671,13 @@ def quadrature_grid(breaks, quad_rule_x, quad_rule_w): assert min(quad_rule_x) >= -1 assert max(quad_rule_x) <= +1 - quad_rule_x = np.asarray(quad_rule_x) - quad_rule_w = np.asarray(quad_rule_w) + quad_rule_x = xp.asarray(quad_rule_x) + quad_rule_w = xp.asarray(quad_rule_w) ne = len(breaks) - 1 nq = len(quad_rule_x) - quad_x = np.zeros((ne, nq), dtype=float) - quad_w = np.zeros((ne, nq), dtype=float) + quad_x = xp.zeros((ne, nq), dtype=float) + quad_w = xp.zeros((ne, nq), dtype=float) # Compute location and weight of quadrature points from basic rule for ie, (a, b) in enumerate(zip(breaks[:-1], breaks[1:])): @@ -724,7 +724,7 @@ def basis_ders_on_quad_grid(knots, degree, quad_grid, nders, normalize=False): # TODO: check if it is safe to compute span only once for each element ne, nq = quad_grid.shape - basis = np.zeros((ne, degree + 1, nders + 1, nq), dtype=float) + basis = xp.zeros((ne, degree + 1, nders + 1, nq), dtype=float) # Loop over elements for ie in range(ne): @@ -735,7 +735,7 @@ def basis_ders_on_quad_grid(knots, degree, quad_grid, nders, normalize=False): span = find_span(knots, degree, xq) ders = basis_funs_all_ders(knots, degree, xq, span, nders) - if normalize == True: + if normalize: ders = ders * scaling_vector(knots, degree, span) basis[ie, :, :, iq] = ders.transpose() diff --git a/src/struphy/bsplines/bsplines_kernels.py b/src/struphy/bsplines/bsplines_kernels.py index 17374f178..9fa1a9521 100644 --- a/src/struphy/bsplines/bsplines_kernels.py +++ b/src/struphy/bsplines/bsplines_kernels.py @@ -83,7 +83,13 @@ def find_span(t: "Final[float[:]]", p: "int", eta: "float") -> "int": @pure def basis_funs( - t: "Final[float[:]]", p: "int", eta: "float", span: "int", left: "float[:]", right: "float[:]", values: "float[:]" + t: "Final[float[:]]", + p: "int", + eta: "float", + span: "int", + left: "float[:]", + right: "float[:]", + values: "float[:]", ): """ Parameters @@ -595,7 +601,13 @@ def basis_funs_and_der( @pure @stack_array("values_b") def basis_funs_1st_der( - t: "Final[float[:]]", p: "int", eta: "float", span: "int", left: "float[:]", right: "float[:]", values: "float[:]" + t: "Final[float[:]]", + p: "int", + eta: "float", + span: "int", + left: "float[:]", + right: "float[:]", + values: "float[:]", ): """ Parameters diff --git a/src/struphy/bsplines/evaluation_kernels_1d.py b/src/struphy/bsplines/evaluation_kernels_1d.py index a6ec8b7a5..6510eafff 100644 --- a/src/struphy/bsplines/evaluation_kernels_1d.py +++ b/src/struphy/bsplines/evaluation_kernels_1d.py @@ -61,7 +61,12 @@ def evaluation_kernel_1d(p1: int, basis1: "Final[float[:]]", ind1: "Final[int[:] @pure @stack_array("tmp1", "tmp2") def evaluate( - kind1: int, t1: "Final[float[:]]", p1: int, ind1: "Final[int[:,:]]", coeff: "Final[float[:]]", eta1: float + kind1: int, + t1: "Final[float[:]]", + p1: int, + ind1: "Final[int[:,:]]", + coeff: "Final[float[:]]", + eta1: float, ) -> float: """ Point-wise evaluation of a spline. diff --git a/src/struphy/bsplines/evaluation_kernels_3d.py b/src/struphy/bsplines/evaluation_kernels_3d.py index 8ccaa252b..a6c900616 100644 --- a/src/struphy/bsplines/evaluation_kernels_3d.py +++ b/src/struphy/bsplines/evaluation_kernels_3d.py @@ -246,47 +246,212 @@ def evaluate_tensor_product( for i3 in range(len(eta3)): if kind == 0: spline_values[i1, i2, i3] = evaluate_3d( - 1, 1, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 1, + 1, + 1, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 11: spline_values[i1, i2, i3] = evaluate_3d( - 2, 1, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 2, + 1, + 1, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 12: spline_values[i1, i2, i3] = evaluate_3d( - 1, 2, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 1, + 2, + 1, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 13: spline_values[i1, i2, i3] = evaluate_3d( - 1, 1, 2, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 1, + 1, + 2, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 21: spline_values[i1, i2, i3] = evaluate_3d( - 1, 2, 2, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 1, + 2, + 2, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 22: spline_values[i1, i2, i3] = evaluate_3d( - 2, 1, 2, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 2, + 1, + 2, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 23: spline_values[i1, i2, i3] = evaluate_3d( - 2, 2, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 2, + 2, + 1, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 3: spline_values[i1, i2, i3] = evaluate_3d( - 2, 2, 2, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 2, + 2, + 2, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 41: spline_values[i1, i2, i3] = evaluate_3d( - 3, 1, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 3, + 1, + 1, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 42: spline_values[i1, i2, i3] = evaluate_3d( - 1, 3, 1, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 1, + 3, + 1, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 43: spline_values[i1, i2, i3] = evaluate_3d( - 1, 1, 3, t1, t2, t3, p1, p2, p3, ind1, ind2, ind3, coeff, eta1[i1], eta2[i2], eta3[i3] + 1, + 1, + 3, + t1, + t2, + t3, + p1, + p2, + p3, + ind1, + ind2, + ind3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) @@ -1051,7 +1216,17 @@ def eval_spline_mpi( b3 = bd3 value = eval_spline_mpi_kernel( - pn[0] - kind[0], pn[1] - kind[1], pn[2] - kind[2], b1, b2, b3, span1, span2, span3, _data, starts + pn[0] - kind[0], + pn[1] - kind[1], + pn[2] - kind[2], + b1, + b2, + b3, + span1, + span2, + span3, + _data, + starts, ) return value @@ -1196,7 +1371,17 @@ def eval_spline_mpi_tensor_product_fast( b3 = bd3 values[i, j, k] = eval_spline_mpi_kernel( - pn[0] - kind[0], pn[1] - kind[1], pn[2] - kind[2], b1, b2, b3, span1, span2, span3, _data, starts + pn[0] - kind[0], + pn[1] - kind[1], + pn[2] - kind[2], + b1, + b2, + b3, + span1, + span2, + span3, + _data, + starts, ) @@ -1262,7 +1447,17 @@ def eval_spline_mpi_tensor_product_fixed( b3[:] = b3s[k, :] values[i, j, k] = eval_spline_mpi_kernel( - pn[0] - kind[0], pn[1] - kind[1], pn[2] - kind[2], b1, b2, b3, span1, span2, span3, _data, starts + pn[0] - kind[0], + pn[1] - kind[1], + pn[2] - kind[2], + b1, + b2, + b3, + span1, + span2, + span3, + _data, + starts, ) @@ -1320,7 +1515,16 @@ def eval_spline_mpi_matrix( continue # point not in process domain values[i, j, k] = eval_spline_mpi( - eta1[i, j, k], eta2[i, j, k], eta3[i, j, k], _data, kind, pn, tn1, tn2, tn3, starts + eta1[i, j, k], + eta2[i, j, k], + eta3[i, j, k], + _data, + kind, + pn, + tn1, + tn2, + tn3, + starts, ) @@ -1382,7 +1586,16 @@ def eval_spline_mpi_sparse_meshgrid( continue # point not in process domain values[i, j, k] = eval_spline_mpi( - eta1[i, 0, 0], eta2[0, j, 0], eta3[0, 0, k], _data, kind, pn, tn1, tn2, tn3, starts + eta1[i, 0, 0], + eta2[0, j, 0], + eta3[0, 0, k], + _data, + kind, + pn, + tn1, + tn2, + tn3, + starts, ) @@ -1432,7 +1645,16 @@ def eval_spline_mpi_markers( continue # point not in process domain values[ip] = eval_spline_mpi( - markers[ip, 0], markers[ip, 1], markers[ip, 2], _data, kind, pn, tn1, tn2, tn3, starts + markers[ip, 0], + markers[ip, 1], + markers[ip, 2], + _data, + kind, + pn, + tn1, + tn2, + tn3, + starts, ) @@ -1453,20 +1675,39 @@ def get_spans(eta1: float, eta2: float, eta3: float, args_derham: "DerhamArgumen # get spline values at eta bsplines_kernels.b_d_splines_slim( - args_derham.tn1, args_derham.pn[0], eta1, int(span1), args_derham.bn1, args_derham.bd1 + args_derham.tn1, + args_derham.pn[0], + eta1, + int(span1), + args_derham.bn1, + args_derham.bd1, ) bsplines_kernels.b_d_splines_slim( - args_derham.tn2, args_derham.pn[1], eta2, int(span2), args_derham.bn2, args_derham.bd2 + args_derham.tn2, + args_derham.pn[1], + eta2, + int(span2), + args_derham.bn2, + args_derham.bd2, ) bsplines_kernels.b_d_splines_slim( - args_derham.tn3, args_derham.pn[2], eta3, int(span3), args_derham.bn3, args_derham.bd3 + args_derham.tn3, + args_derham.pn[2], + eta3, + int(span3), + args_derham.bn3, + args_derham.bd3, ) return span1, span2, span3 def eval_0form_spline_mpi( - span1: int, span2: int, span3: int, args_derham: "DerhamArguments", form_coeffs: "float[:,:,:]" + span1: int, + span2: int, + span3: int, + args_derham: "DerhamArguments", + form_coeffs: "float[:,:,:]", ) -> float: """Single-point evaluation of Derham 0-form spline defined by form_coeffs, given N-spline values (in bn) and knot span indices span.""" @@ -1602,7 +1843,11 @@ def eval_2form_spline_mpi( def eval_3form_spline_mpi( - span1: int, span2: int, span3: int, args_derham: "DerhamArguments", form_coeffs: "float[:,:,:]" + span1: int, + span2: int, + span3: int, + args_derham: "DerhamArguments", + form_coeffs: "float[:,:,:]", ) -> float: """Single-point evaluation of Derham 0-form spline defined by form_coeffs, given D-spline values (in bd) and knot span indices span.""" diff --git a/src/struphy/bsplines/tests/test_bsplines_kernels.py b/src/struphy/bsplines/tests/test_bsplines_kernels.py index 96e3a0a21..c1010dd08 100644 --- a/src/struphy/bsplines/tests/test_bsplines_kernels.py +++ b/src/struphy/bsplines/tests/test_bsplines_kernels.py @@ -1,11 +1,10 @@ import time -import numpy as np +import cunumpy as xp import pytest -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 1], [2, 1, 2], [3, 4, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -22,7 +21,6 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -35,9 +33,9 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): # Random points in domain of process n_pts = 100 dom = derham.domain_array[rank] - eta1s = np.random.rand(n_pts) * (dom[1] - dom[0]) + dom[0] - eta2s = np.random.rand(n_pts) * (dom[4] - dom[3]) + dom[3] - eta3s = np.random.rand(n_pts) * (dom[7] - dom[6]) + dom[6] + eta1s = xp.random.rand(n_pts) * (dom[1] - dom[0]) + dom[0] + eta2s = xp.random.rand(n_pts) * (dom[4] - dom[3]) + dom[3] + eta3s = xp.random.rand(n_pts) * (dom[7] - dom[6]) + dom[6] # struphy find_span t0 = time.time() @@ -61,18 +59,18 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): if rank == 0: print(f"psydac find_span_p : {t1 - t0}") - assert np.allclose(span1s, span1s_psy) - assert np.allclose(span2s, span2s_psy) - assert np.allclose(span3s, span3s_psy) + assert xp.allclose(span1s, span1s_psy) + assert xp.allclose(span2s, span2s_psy) + assert xp.allclose(span3s, span3s_psy) # allocate tmps - bn1 = np.empty(derham.p[0] + 1, dtype=float) - bn2 = np.empty(derham.p[1] + 1, dtype=float) - bn3 = np.empty(derham.p[2] + 1, dtype=float) + bn1 = xp.empty(derham.p[0] + 1, dtype=float) + bn2 = xp.empty(derham.p[1] + 1, dtype=float) + bn3 = xp.empty(derham.p[2] + 1, dtype=float) - bd1 = np.empty(derham.p[0], dtype=float) - bd2 = np.empty(derham.p[1], dtype=float) - bd3 = np.empty(derham.p[2], dtype=float) + bd1 = xp.empty(derham.p[0], dtype=float) + bd2 = xp.empty(derham.p[1], dtype=float) + bd3 = xp.empty(derham.p[2], dtype=float) # struphy b_splines_slim val1s, val2s, val3s = [], [], [] @@ -104,13 +102,13 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): # compare for val1, val1_psy in zip(val1s, val1s_psy): - assert np.allclose(val1, val1_psy) + assert xp.allclose(val1, val1_psy) for val2, val2_psy in zip(val2s, val2s_psy): - assert np.allclose(val2, val2_psy) + assert xp.allclose(val2, val2_psy) for val3, val3_psy in zip(val3s, val3s_psy): - assert np.allclose(val3, val3_psy) + assert xp.allclose(val3, val3_psy) # struphy b_d_splines_slim val1s_n, val2s_n, val3s_n = [], [], [] @@ -132,13 +130,13 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): # compare for val1, val1_psy in zip(val1s_n, val1s_psy): - assert np.allclose(val1, val1_psy) + assert xp.allclose(val1, val1_psy) for val2, val2_psy in zip(val2s_n, val2s_psy): - assert np.allclose(val2, val2_psy) + assert xp.allclose(val2, val2_psy) for val3, val3_psy in zip(val3s_n, val3s_psy): - assert np.allclose(val3, val3_psy) + assert xp.allclose(val3, val3_psy) # struphy d_splines_slim span1s, span2s, span3s = [], [], [] @@ -176,22 +174,22 @@ def test_bsplines_span_and_basis(Nel, p, spl_kind): # compare for val1, val1_psy in zip(val1s, val1s_psy): - assert np.allclose(val1, val1_psy) + assert xp.allclose(val1, val1_psy) for val2, val2_psy in zip(val2s, val2s_psy): - assert np.allclose(val2, val2_psy) + assert xp.allclose(val2, val2_psy) for val3, val3_psy in zip(val3s, val3s_psy): - assert np.allclose(val3, val3_psy) + assert xp.allclose(val3, val3_psy) for val1, val1_psy in zip(val1s_d, val1s_psy): - assert np.allclose(val1, val1_psy) + assert xp.allclose(val1, val1_psy) for val2, val2_psy in zip(val2s_d, val2s_psy): - assert np.allclose(val2, val2_psy) + assert xp.allclose(val2, val2_psy) for val3, val3_psy in zip(val3s_d, val3s_psy): - assert np.allclose(val3, val3_psy) + assert xp.allclose(val3, val3_psy) if __name__ == "__main__": diff --git a/src/struphy/bsplines/tests/test_eval_spline_mpi.py b/src/struphy/bsplines/tests/test_eval_spline_mpi.py index 2c00eb459..923fc8ea6 100644 --- a/src/struphy/bsplines/tests/test_eval_spline_mpi.py +++ b/src/struphy/bsplines/tests/test_eval_spline_mpi.py @@ -1,12 +1,11 @@ from sys import int_info from time import sleep -import numpy as np +import cunumpy as xp import pytest -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3], [3, 1, 2]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -20,7 +19,6 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -39,9 +37,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): # Random points in domain of process dom = derham.domain_array[rank] - eta1s = np.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] - eta2s = np.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] - eta3s = np.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] + eta1s = xp.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] + eta2s = xp.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] + eta3s = xp.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] for eta1, eta2, eta3 in zip(eta1s, eta2s, eta3s): comm.Barrier() @@ -57,13 +55,13 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span3 = bsp.find_span(tn3, derham.p[2], eta3) # non-zero spline values at eta - bn1 = np.empty(derham.p[0] + 1, dtype=float) - bn2 = np.empty(derham.p[1] + 1, dtype=float) - bn3 = np.empty(derham.p[2] + 1, dtype=float) + bn1 = xp.empty(derham.p[0] + 1, dtype=float) + bn2 = xp.empty(derham.p[1] + 1, dtype=float) + bn3 = xp.empty(derham.p[2] + 1, dtype=float) - bd1 = np.empty(derham.p[0], dtype=float) - bd2 = np.empty(derham.p[1], dtype=float) - bd3 = np.empty(derham.p[2], dtype=float) + bd1 = xp.empty(derham.p[0], dtype=float) + bd2 = xp.empty(derham.p[1], dtype=float) + bd3 = xp.empty(derham.p[2], dtype=float) bsp.b_d_splines_slim(tn1, derham.p[0], eta1, span1, bn1, bd1) bsp.b_d_splines_slim(tn2, derham.p[1], eta2, span2, bn2, bd2) @@ -84,8 +82,8 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): # compare spline evaluation routines in V0 val = eval3d(*derham.p, bn1, bn2, bn3, ind_n1, ind_n2, ind_n3, x0[0]) - val_mpi = eval3d_mpi(*derham.p, bn1, bn2, bn3, span1, span2, span3, x0_psy._data, np.array(x0_psy.starts)) - assert np.allclose(val, val_mpi) + val_mpi = eval3d_mpi(*derham.p, bn1, bn2, bn3, span1, span2, span3, x0_psy._data, xp.array(x0_psy.starts)) + assert xp.allclose(val, val_mpi) # compare spline evaluation routines in V1 val = eval3d(derham.p[0] - 1, derham.p[1], derham.p[2], bd1, bn2, bn3, ind_d1, ind_n2, ind_n3, x1[0]) @@ -100,9 +98,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x1_psy[0]._data, - np.array(x1_psy[0].starts), + xp.array(x1_psy[0].starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) val = eval3d(derham.p[0], derham.p[1] - 1, derham.p[2], bn1, bd2, bn3, ind_n1, ind_d2, ind_n3, x1[1]) val_mpi = eval3d_mpi( @@ -116,9 +114,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x1_psy[1]._data, - np.array(x1_psy[1].starts), + xp.array(x1_psy[1].starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) val = eval3d(derham.p[0], derham.p[1], derham.p[2] - 1, bn1, bn2, bd3, ind_n1, ind_n2, ind_d3, x1[2]) val_mpi = eval3d_mpi( @@ -132,9 +130,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x1_psy[2]._data, - np.array(x1_psy[2].starts), + xp.array(x1_psy[2].starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # compare spline evaluation routines in V2 val = eval3d(derham.p[0], derham.p[1] - 1, derham.p[2] - 1, bn1, bd2, bd3, ind_n1, ind_d2, ind_d3, x2[0]) @@ -149,9 +147,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x2_psy[0]._data, - np.array(x2_psy[0].starts), + xp.array(x2_psy[0].starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) val = eval3d(derham.p[0] - 1, derham.p[1], derham.p[2] - 1, bd1, bn2, bd3, ind_d1, ind_n2, ind_d3, x2[1]) val_mpi = eval3d_mpi( @@ -165,9 +163,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x2_psy[1]._data, - np.array(x2_psy[1].starts), + xp.array(x2_psy[1].starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) val = eval3d(derham.p[0] - 1, derham.p[1] - 1, derham.p[2], bd1, bd2, bn3, ind_d1, ind_d2, ind_n3, x2[2]) val_mpi = eval3d_mpi( @@ -181,9 +179,9 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x2_psy[2]._data, - np.array(x2_psy[2].starts), + xp.array(x2_psy[2].starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # compare spline evaluation routines in V3 val = eval3d(derham.p[0] - 1, derham.p[1] - 1, derham.p[2] - 1, bd1, bd2, bd3, ind_d1, ind_d2, ind_d3, x3[0]) @@ -198,12 +196,11 @@ def test_eval_kernels(Nel, p, spl_kind, n_markers=10): span2, span3, x3_psy._data, - np.array(x3_psy.starts), + xp.array(x3_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3], [3, 1, 2]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -216,7 +213,6 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -233,9 +229,9 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): # Random points in domain of process dom = derham.domain_array[rank] - eta1s = np.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] - eta2s = np.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] - eta3s = np.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] + eta1s = xp.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] + eta2s = xp.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] + eta3s = xp.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] for eta1, eta2, eta3 in zip(eta1s, eta2s, eta3s): comm.Barrier() @@ -254,14 +250,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x0_psy._data, derham.spline_types_pyccel["0"], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # compare spline evaluation routines in V1 # 1st component @@ -290,14 +286,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x1_psy[0]._data, derham.spline_types_pyccel["1"][0], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # 2nd component val = evaluate_3d( @@ -325,14 +321,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x1_psy[1]._data, derham.spline_types_pyccel["1"][1], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # 3rd component val = evaluate_3d( @@ -360,14 +356,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x1_psy[2]._data, derham.spline_types_pyccel["1"][2], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # compare spline evaluation routines in V2 # 1st component @@ -396,14 +392,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x2_psy[0]._data, derham.spline_types_pyccel["2"][0], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # 2nd component val = evaluate_3d( @@ -431,14 +427,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x2_psy[1]._data, derham.spline_types_pyccel["2"][1], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # 3rd component val = evaluate_3d( @@ -466,14 +462,14 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x2_psy[2]._data, derham.spline_types_pyccel["2"][2], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) # compare spline evaluation routines in V3 val = evaluate_3d( @@ -499,17 +495,16 @@ def test_eval_pointwise(Nel, p, spl_kind, n_markers=10): eta3, x3_psy._data, derham.spline_types_pyccel["3"], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), ) - assert np.allclose(val, val_mpi) + assert xp.allclose(val, val_mpi) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3], [3, 1, 2]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -534,7 +529,6 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -549,13 +543,13 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): # Random points in domain of process dom = derham.domain_array[rank] - eta1s = np.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] - eta2s = np.random.rand(n_markers + 1) * (dom[4] - dom[3]) + dom[3] - eta3s = np.random.rand(n_markers + 2) * (dom[7] - dom[6]) + dom[6] + eta1s = xp.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] + eta2s = xp.random.rand(n_markers + 1) * (dom[4] - dom[3]) + dom[3] + eta3s = xp.random.rand(n_markers + 2) * (dom[7] - dom[6]) + dom[6] - vals = np.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) - vals_mpi = np.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) - vals_mpi_fast = np.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) + vals = xp.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) + vals_mpi = xp.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) + vals_mpi_fast = xp.zeros((n_markers, n_markers + 1, n_markers + 2), dtype=float) comm.Barrier() sleep(0.02 * (rank + 1)) @@ -578,11 +572,11 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): eta3s, x0_psy._data, derham.spline_types_pyccel["0"], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), vals_mpi, ) t1 = time.time() @@ -596,19 +590,19 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): eta3s, x0_psy._data, derham.spline_types_pyccel["0"], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), vals_mpi_fast, ) t1 = time.time() if rank == 0: print("v0 eval_spline_mpi_tensor_product_fast:".ljust(40), t1 - t0) - assert np.allclose(vals, vals_mpi) - assert np.allclose(vals, vals_mpi_fast) + assert xp.allclose(vals, vals_mpi) + assert xp.allclose(vals, vals_mpi_fast) # compare spline evaluation routines in V3 t0 = time.time() @@ -638,11 +632,11 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): eta3s, x3_psy._data, derham.spline_types_pyccel["3"], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), vals_mpi, ) t1 = time.time() @@ -656,22 +650,21 @@ def test_eval_tensor_product(Nel, p, spl_kind, n_markers=10): eta3s, x3_psy._data, derham.spline_types_pyccel["3"], - np.array(derham.p), + xp.array(derham.p), tn1, tn2, tn3, - np.array(x0_psy.starts), + xp.array(x0_psy.starts), vals_mpi_fast, ) t1 = time.time() if rank == 0: print("v3 eval_spline_mpi_tensor_product_fast:".ljust(40), t1 - t0) - assert np.allclose(vals, vals_mpi) - assert np.allclose(vals, vals_mpi_fast) + assert xp.allclose(vals, vals_mpi) + assert xp.allclose(vals, vals_mpi_fast) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 1], [2, 1, 2], [3, 4, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -692,7 +685,6 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): from struphy.feec.utilities import create_equal_random_arrays as cera comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -708,7 +700,10 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): # Histopolation grids spaces = derham.Vh_fem["3"].spaces ptsG, wtsG, spans, bases, subs = prepare_projection_of_basis( - spaces, spaces, derham.Vh["3"].starts, derham.Vh["3"].ends + spaces, + spaces, + derham.Vh["3"].starts, + derham.Vh["3"].ends, ) eta1s = ptsG[0].flatten() eta2s = ptsG[1].flatten() @@ -717,15 +712,15 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): spans_f, bns_f, bds_f = derham.prepare_eval_tp_fixed([eta1s, eta2s, eta3s]) # output arrays - vals = np.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) - vals_mpi_fixed = np.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) - vals_mpi_grid = np.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) + vals = xp.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) + vals_mpi_fixed = xp.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) + vals_mpi_grid = xp.zeros((eta1s.size, eta2s.size, eta3s.size), dtype=float) comm.Barrier() sleep(0.02 * (rank + 1)) - print(f"rank {rank} | {eta1s = }") - print(f"rank {rank} | {eta2s = }") - print(f"rank {rank} | {eta3s = }\n") + print(f"rank {rank} | {eta1s =}") + print(f"rank {rank} | {eta2s =}") + print(f"rank {rank} | {eta3s =}\n") comm.Barrier() # compare spline evaluation routines @@ -755,20 +750,20 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): *bds_f, x3_psy._data, derham.spline_types_pyccel["3"], - np.array(derham.p), - np.array(x0_psy.starts), + xp.array(derham.p), + xp.array(x0_psy.starts), vals_mpi_fixed, ) t1 = time.time() if rank == 0: print("v3 eval_spline_mpi_tensor_product_fixed:".ljust(40), t1 - t0) - assert np.allclose(vals, vals_mpi_fixed) + assert xp.allclose(vals, vals_mpi_fixed) field = derham.create_spline_function("test", "L2") field.vector = x3_psy - assert np.allclose(field.vector._data, x3_psy._data) + assert xp.allclose(field.vector._data, x3_psy._data) t0 = time.time() field.eval_tp_fixed_loc(spans_f, bds_f, out=vals_mpi_fixed) @@ -776,7 +771,7 @@ def test_eval_tensor_product_grid(Nel, p, spl_kind, n_markers=10): if rank == 0: print("v3 field.eval_tp_fixed:".ljust(40), t1 - t0) - assert np.allclose(vals, vals_mpi_fixed) + assert xp.allclose(vals, vals_mpi_fixed) if __name__ == "__main__": diff --git a/src/struphy/console/compile.py b/src/struphy/console/compile.py index 660677608..8992b02e5 100644 --- a/src/struphy/console/compile.py +++ b/src/struphy/console/compile.py @@ -4,7 +4,17 @@ def struphy_compile( - language, compiler, compiler_config, omp_pic, omp_feec, delete, status, verbose, dependencies, time_execution, yes + language, + compiler, + compiler_config, + omp_pic, + omp_feec, + delete, + status, + verbose, + dependencies, + time_execution, + yes, ): """Compile Struphy kernels. All files that contain "kernels" are detected automatically and saved to state.yml. @@ -187,9 +197,9 @@ def struphy_compile( deps = depmod.get_dependencies(ker.replace(".py", so_suffix)) deps_li = deps.split(" ") print("-" * 28) - print(f"{ker = }") + print(f"{ker =}") for dep in deps_li: - print(f"{dep = }") + print(f"{dep =}") else: # struphy and psydac (change dir not to be in source path) diff --git a/src/struphy/console/format.py b/src/struphy/console/format.py index 7db28daed..7ba6795c4 100644 --- a/src/struphy/console/format.py +++ b/src/struphy/console/format.py @@ -125,6 +125,34 @@ def check_omp_flags(file_path, verbose=False): raise ValueError(f"Error reading file: {e}") +def check_ssort(file_path, verbose=False): + """Check if a file is sorted according to ssort. + + Parameters + ---------- + file_path : str + Path to the Python file. + + verbose : bool, optional + If True, enables detailed output (default=False). + + Returns + ------- + bool + True if ssort check passes, False otherwise. + """ + result = subprocess.run( + ["ssort", "--check", file_path], + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if verbose: + print("stdout:", result.stdout.decode("utf-8")) + print("stderr:", result.stderr.decode("utf-8")) + return result.returncode == 0 + + def check_ruff(file_path, verbose=False): """Check if a file passes Ruff linting. @@ -379,9 +407,12 @@ def parse_path(directory): python_files = [] for root, _, files in os.walk(directory): for filename in files: - if filename.endswith(".py") and not re.search(r"__\w+__", filename): + if re.search(r"__\w+__", root): + continue + if (filename.endswith(".py") or filename.endswith(".ipynb")) and not re.search(r"__\w+__", filename): file_path = os.path.join(root, filename) python_files.append(file_path) + # exit() return python_files @@ -453,7 +484,9 @@ def get_python_files(input_type, path=None): # python_files = [f for f in files if f.endswith(".py") and os.path.isfile(f)] python_files = [ - os.path.join(repopath, f) for f in files if f.endswith(".py") and os.path.isfile(os.path.join(repopath, f)) + os.path.join(repopath, f) + for f in files + if (f.endswith(".py") or f.endswith(".ipynb")) and os.path.isfile(os.path.join(repopath, f)) ] if not python_files: @@ -476,292 +509,415 @@ def get_python_files(input_type, path=None): return python_files -def struphy_lint(config, verbose): - """Lint Python files based on the given configuration and specified linters. +def replace_backticks_with_code_tags(text): + """Recursively replaces inline backticks with tags. + Handles multiple or nested occurrences. - Parameters - ---------- - config : dict - Configuration dictionary containing the following keys: - - input_type : str, optional - The type of files to lint ('all', 'path', 'staged', or 'branch'). Defaults to 'all'. - - output_format: str, optional - The format of the lint output ('table', or 'plain'). Defaults to 'table' - - path : str, optional - Directory or file path to lint. - - linters : list - List of linter names to apply. + Args: + text (str): Input string with backticks to be replaced. - verbose : bool - If True, enables detailed output. + Returns: + str: Formatted string with tags. """ + # Regular expression to match text inside single backtick pairs + pattern = r"`([^`]*)`" - # Extract individual settings from config - input_type = config.get("input_type", "all") - path = config.get("path") - output_format = config.get("output_format", "table") - linters = config.get("linters", []) - - if input_type is None and path is not None: - input_type = "path" - # Define standard linters which will be checked in the CI - ci_linters = ["ruff", "omp_flags"] - python_files = get_python_files(input_type, path) - if len(python_files) == 0: - sys.exit(0) - - print( - tabulate( - [[file] for file in python_files], - headers=[f"The following files will be linted with {linters}"], - ), - ) - print("\n") - - if output_format == "report": - generate_report(python_files, linters=linters, verbose=verbose) - sys.exit(0) - - max_pathlen = max(len(os.path.relpath(file_path)) for file_path in python_files) - stats_list = [] - - # Check if all ci_linters are included in linters - if all(ci_linter in linters for ci_linter in ci_linters): - print(f"Passes CI if {ci_linters} passes") - print("-" * 40) - check_ci_pass = True - else: - skipped_ci_linters = [ci_linter for ci_linter in ci_linters if ci_linter not in linters] - print( - f'The "Pass CI" check is skipped since not --linters {" ".join(skipped_ci_linters)} is used.', - ) - check_ci_pass = False - # Collect statistics for each file - for ifile, file_path in enumerate(python_files): - stats = analyze_file(file_path, linters=linters, verbose=verbose) - stats_list.append(stats) - - # Print the statistics in a table - if output_format == "table": - print_stats_table( - [stats], - linters, - print_header=(ifile == 0), - pathlen=max_pathlen, - ) - elif output_format == "plain": - print_stats_plain(stats, linters) - - if check_ci_pass: - passes_ci = True - for stats in stats_list: - if not all(stats[f"passes_{ci_linter}"] for ci_linter in ci_linters): - passes_ci = False - if passes_ci: - print("All files will pass CI") - sys.exit(0) - else: - print("Not all files will pass CI") - sys.exit(1) - print("Not all CI linters were checked, unknown if all files will pass CI") - sys.exit(1) - - -def generate_report(python_files, linters=["ruff"], verbose=False): - for linter in linters: - if linter == "ruff": - for python_file in python_files: - report_json_filename = "code_analysis_report.json" - report_html_filename = "code_analysis_report.html" - command = [ - "ruff", - "check", - "--preview", - "--select", - "ALL", - "--ignore", - "D211,D213", - "--output-format", - "json", - "-o", - report_json_filename, - ] + python_files - subprocess.run(command, check=False) - parse_json_file_to_html(report_json_filename, report_html_filename) - if os.path.exists(report_json_filename): - os.remove(report_json_filename) - sys.exit(0) + # Replace one level of backticks with tags + new_text = re.sub(pattern, r"\1", text) + # If additional backticks are found, process recursively + if "`" in new_text: + return replace_backticks_with_code_tags(new_text) -def confirm_formatting(python_files, linters, yes): - """Confirm with the user whether to format the listed Python files.""" - print( - tabulate( - [[file] for file in python_files], - headers=[f"The following files will be formatted with {linters}"], - ), - ) - print("\n") - if not yes: - ans = input("Format files (Y/n)?\n") - if ans.lower() not in ("y", "yes", ""): - print("Exiting...") - sys.exit(1) + return new_text -def files_require_formatting(python_files, linters): - """Check if any of the specified files still require formatting based on the specified linters. +def generate_html_table_from_combined_data(combined_data, sort_descending=True): + html = "" + html += "" + sorted_items = sorted(combined_data.items(), reverse=sort_descending) + for count, info in sorted_items: + codes_links = ", ".join(info["Codes"]) + html += f"" + html += "
    CountCodes
    {count}{codes_links}
    " + return html - Parameters - ---------- - python_files : list - List of Python file paths to check. - linters : list - List of linter names to check against (e.g., ['autopep8', 'isort']). +def parse_json_file_to_html(json_file_path, html_output_path): + """Parses a JSON file containing code issues and writes an HTML report. + Parses a JSON file containing code issues, groups them by filename, + reads the source code to extract context, and writes an HTML report. + Each file's section is foldable using
    and tags. - Returns - ------- - bool - True if any files still require formatting, False otherwise. + Args: + json_file_path (str): The path to the JSON file containing code issues. + html_output_path (str): The path where the HTML report will be saved. """ - linter_check_functions = { - "autopep8": check_autopep8, - "isort": check_isort, - "add-trailing-comma": check_trailing_commas, - } - - for file_path in python_files: - for linter in linters: - check_function = linter_check_functions.get(linter) - if check_function and not check_function(file_path): - return True - return False - - -def run_linters_on_files(linters, python_files, flags, verbose): - """Run each linter on the specified files with appropriate flags.""" - for linter in linters: - for python_file in python_files: - print(f"Formatting {python_file}") - linter_flags = flags.get(linter, []) - if isinstance(linter_flags[0], list): - # If linter_flags is a list, run each separately - for flag in linter_flags: - command = [linter] + flag + [python_file] - if verbose: - print(f"Running command: {' '.join(command)}") - subprocess.run(command, check=False) - else: - # If linter_flags is not a list, treat it as a single value - command = [linter] + linter_flags + [python_file] - if verbose: - print(f"Running command: {' '.join(command)}") - subprocess.run(command, check=False) + try: + with open(json_file_path, "r") as file: + data = json.load(file) - # Loop over each line and replace '# $' with '#$' in place - for line in fileinput.input(python_file, inplace=True): - if line.lstrip().startswith("# $"): - print(line.replace("# $", "#$"), end="") - else: - print(line, end="") + if not isinstance(data, list): + print("Invalid JSON format: Expected a list of objects.") + return + # Group issues by filename + issues_by_file = defaultdict(list) + for issue in data: + filename = issue.get("filename", "Unknown file") + issues_by_file[filename].append(issue) -def struphy_format(config, verbose, yes=False): - """Format Python files with specified linters, optionally iterating multiple times. + # Start building the HTML content + html_content = [] + html_content.extend( + [ + "", + "", + "", + "", + "", + "Code Analysis Report", + ], + ) - Parameters - ---------- - config : dict - Configuration dictionary containing the following keys: - - input_type : str, optional - The type of files to format ('all', 'path', 'staged', 'branch', or '__init__.py'). Defaults to 'all'. - - path : str, optional - Directory or file path where files will be formatted. - - linters : list - List of formatter names to apply. - - iterations : int, optional - Maximum number of times to apply formatting (default=5). - - verbose : bool - If True, enables detailed output, showing each command and iteration. - - yes : bool, optional - If True, skips the confirmation prompt before formatting. - """ + # Include external CSS and JS libraries + html_content.extend( + [ + "", + "", + "", + "", + ], + ) - # Extract individual settings from config - input_type = config.get("input_type", "all") - path = config.get("path") - linters = config.get("linters", []) - iterations = config.get("iterations", 5) + # Custom CSS for light mode and code prettification + html_content.append("") - if input_type is None and path is not None: - input_type = "path" + # JavaScript to initialize Highlight.js with custom options + html_content.append( + """ + +""", + ) - if input_type == "__init__.py": - print(f"Rewriting {PROPAGATORS_INIT_PATH}") - propagators_init = construct_propagators_init_file() - with open(PROPAGATORS_INIT_PATH, "w") as f: - f.write(propagators_init) + html_content.extend(["", "", "

    Code Issues Report

    "]) - print(f"Rewriting {MODELS_INIT_PATH}") - models_init = construct_models_init_file() - with open(MODELS_INIT_PATH, "w") as f: - f.write(models_init) + # Add summary statistics + total_issues = sum(len(issues) for issues in issues_by_file.values()) + total_files = len(issues_by_file) + html_content.append( + f""" +
    +

    Total Issues: {total_issues}

    +

    Number of files: {total_files}

    +
    +""", + ) - python_files = [PROPAGATORS_INIT_PATH, MODELS_INIT_PATH] - input_type = "path" - else: - python_files = get_python_files(input_type, path) + # Navigation menu + # html_content.append("") - if len(python_files) == 0: - print("No Python files to format.") - sys.exit(0) + for filename, issues in issues_by_file.items(): + print(f"Parsing {filename}") + # Start foldable section for the file + anchor = filename.replace(LIBPATH, "src/struphy").replace("/", "_").replace("\\", "_") + display_name = filename.replace(LIBPATH, "src/struphy") + html_content.append( + f""" +
    + File: {display_name} +""", + ) - confirm_formatting(python_files, linters, yes) + issue_data = {} + for issue in issues: + code = issue.get("code", "Unknown code") + message = replace_backticks_with_code_tags(issue.get("message", "No message")) + url = issue.get("url", "No URL provided") + if code in issue_data: + issue_data[code]["Count"] += 1 + else: + issue_data[code] = { + "Count": 1, + "Message": message, + "url": url, + } - flags = { - "autopep8": ["--in-place"], - "isort": [], - "add-trailing-comma": ["--exit-zero-even-if-changed"], - "ruff": [["check", "--fix", "--select", "I"], ["format"]], - } + combined_data = {} + for code, info in issue_data.items(): + count = info["Count"] + url = info["url"] + link = f"{code}" + if count in combined_data: + combined_data[count]["Codes"].append(link) + else: + combined_data[count] = { + "Codes": [link], + } + # Generate the HTML table + html_content.append(generate_html_table_from_combined_data(combined_data, sort_descending=True)) - # Skip linting with add-trailing-comma since it disagrees with autopep8 - skip_linters = ["add-trailing-comma"] + for issue in issues: + code = issue.get("code", "Unknown code") + message = replace_backticks_with_code_tags(issue.get("message", "No message")) + location = issue.get("location", {}) + row = location.get("row", None) + column = location.get("column", None) + end_location = issue.get("end_location", {}) + # end_row = end_location.get("row", row) + end_column = end_location.get("column", column) + fix = issue.get("fix", None) + url = issue.get("url", "No URL provided") - if python_files: - for iteration in range(iterations): - if verbose: - print(f"Iteration {iteration + 1}: Running formatters...") + html_content.append("
    ") + html_content.append("

    ") + html_content.append( + f"Issue: " + f"{code} - " + f"{message}
    " + f"Location: " + f"{display_name}:{row}:{column}
    ", + ) + html_content.append("

    ") - run_linters_on_files( - linters, - python_files, - flags, - verbose, - ) + # Read the file and extract the code snippet + if os.path.exists(filename) and row is not None: + with open(filename, "r") as source_file: + lines = source_file.readlines() + total_lines = len(lines) + # Adjust indices for zero-based indexing + context_radius = 2 # Number of lines before and after the issue line + start_line = max(row - context_radius - 1, 0) + end_line = min(row + context_radius, total_lines) + snippet_lines = lines[start_line:end_line] - # Check if any files still require changes - if not files_require_formatting( - python_files, - [lint for lint in linters if lint not in skip_linters], - ): - print("All files are properly formatted.") - break - else: - if verbose: - print( - "Max iterations reached. The following files may still require manual checks:", - ) - for file_path in python_files: - if files_require_formatting([file_path], linters): - print(f" - {file_path}") - print("Contact Max about this") - else: - print("No Python files to format.") + # Build the code snippet + code_lines = [] + for idx, line_content in enumerate(snippet_lines, start=start_line + 1): + line_content = line_content.rstrip("\n") + # Fix HTML special characters + line_content = line_content.replace("&", "&").replace("<", "<").replace(">", ">") + # Highlight the error + if idx == row and column is not None and end_column is not None: + start_col = column - 1 # Adjust for zero-based indexing + end_col = end_column - 1 + + start_col = max(start_col, 0) + end_col = min(end_col, len(line_content)) + + before = line_content[:start_col] + problem = line_content[start_col:end_col] + after = line_content[end_col:] + # Wrap the problematic part with + highlighted_line = f"{before}{problem}{after}" + code_lines.append((idx, highlighted_line)) + else: + code_lines.append((idx, line_content)) + # Make code block with line numbers + html_content.append("
    ")
    +                        for line_number, line_content in code_lines:
    +                            html_content.append(
    +                                # f"
    " + # f"{line_number}{line_content}
    " + f"{line_number}: {line_content}", + ) + html_content.append("
    ") + # Include fix details if available + if fix: + html_content.append("
    ") + html_content.append( + f"

    Fix Available ({fix.get('applicability', 'Unknown')}): " + f"ruff check --select ALL --fix {display_name}

    ", + ) + html_content.append("
    ") + else: + html_content.append( + f"

    Cannot read file {filename} or invalid row {row}.

    ", + ) + + html_content.append("
    ") + html_content.append("
    ") + + html_content.append("
    ") + + # Footer + html_content.append( + f""" +
    +

    Generated by on {time.strftime("%Y-%m-%d %H:%M:%S")}

    +
    +""", + ) + + html_content.extend(["", ""]) + + # Write the HTML content to the output file + with open(html_output_path, "w") as html_file: + html_file.write("\n".join(html_content)) + + print(f"HTML report generated at {html_output_path}") + + except FileNotFoundError as e: + print(f"Error: {e}") + except json.JSONDecodeError as e: + print(f"Error: Failed to parse JSON file. {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + + +def generate_report(python_files, linters=["ruff"], verbose=False): + for linter in linters: + if linter == "ruff": + for python_file in python_files: + report_json_filename = "code_analysis_report.json" + report_html_filename = "code_analysis_report.html" + command = [ + "ruff", + "check", + "--preview", + "--select", + "ALL", + "--ignore", + "D211,D213", + "--output-format", + "json", + "-o", + report_json_filename, + ] + python_files + subprocess.run(command, check=False) + parse_json_file_to_html(report_json_filename, report_html_filename) + if os.path.exists(report_json_filename): + os.remove(report_json_filename) + sys.exit(0) def print_stats_plain(stats, linters, ci_linters=["ruff"]): @@ -897,6 +1053,7 @@ def analyze_file(file_path, linters=None, verbose=False): "passes_add-trailing-comma": False, "passes_ruff": False, "passes_omp_flags": False, + "passes_ssort": False, } # Read the file content @@ -938,393 +1095,178 @@ def analyze_file(file_path, linters=None, verbose=False): file_path, verbose=verbose, ) - + if "ssort" in linters: + stats["passes_ssort"] = check_ssort( + file_path, + verbose=verbose, + ) return stats -def replace_backticks_with_code_tags(text): - """Recursively replaces inline backticks with tags. - Handles multiple or nested occurrences. - - Args: - text (str): Input string with backticks to be replaced. - - Returns: - str: Formatted string with tags. - """ - # Regular expression to match text inside single backtick pairs - pattern = r"`([^`]*)`" - - # Replace one level of backticks with tags - new_text = re.sub(pattern, r"\1", text) - - # If additional backticks are found, process recursively - if "`" in new_text: - return replace_backticks_with_code_tags(new_text) - - return new_text - - -def generate_html_table_from_combined_data(combined_data, sort_descending=True): - html = "" - html += "" - sorted_items = sorted(combined_data.items(), reverse=sort_descending) - for count, info in sorted_items: - codes_links = ", ".join(info["Codes"]) - html += f"" - html += "
    CountCodes
    {count}{codes_links}
    " - return html - +def struphy_lint(config, verbose): + """Lint Python files based on the given configuration and specified linters. -def parse_json_file_to_html(json_file_path, html_output_path): - """Parses a JSON file containing code issues and writes an HTML report. - Parses a JSON file containing code issues, groups them by filename, - reads the source code to extract context, and writes an HTML report. - Each file's section is foldable using
    and tags. + Parameters + ---------- + config : dict + Configuration dictionary containing the following keys: + - input_type : str, optional + The type of files to lint ('all', 'path', 'staged', or 'branch'). Defaults to 'all'. + - output_format: str, optional + The format of the lint output ('table', or 'plain'). Defaults to 'table' + - path : str, optional + Directory or file path to lint. + - linters : list + List of linter names to apply. - Args: - json_file_path (str): The path to the JSON file containing code issues. - html_output_path (str): The path where the HTML report will be saved. + verbose : bool + If True, enables detailed output. """ - try: - with open(json_file_path, "r") as file: - data = json.load(file) - - if not isinstance(data, list): - print("Invalid JSON format: Expected a list of objects.") - return - - # Group issues by filename - issues_by_file = defaultdict(list) - for issue in data: - filename = issue.get("filename", "Unknown file") - issues_by_file[filename].append(issue) - - # Start building the HTML content - html_content = [] - html_content.extend( - [ - "", - "", - "", - "", - "", - "Code Analysis Report", - ] - ) + # Extract individual settings from config + input_type = config.get("input_type", "all") + path = config.get("path") + output_format = config.get("output_format", "table") + linters = config.get("linters", []) - # Include external CSS and JS libraries - html_content.extend( - [ - "", - "", - "", - "", - ] - ) + if input_type is None and path is not None: + input_type = "path" + # Define standard linters which will be checked in the CI + ci_linters = ["ruff", "omp_flags"] + python_files = get_python_files(input_type, path) + if len(python_files) == 0: + sys.exit(0) - # Custom CSS for light mode and code prettification - html_content.append("") + print( + tabulate( + [[file] for file in python_files], + headers=[f"The following files will be linted with {linters}"], + ), + ) + print("\n") - # JavaScript to initialize Highlight.js with custom options - html_content.append( - """ - -""" - ) + if output_format == "report": + generate_report(python_files, linters=linters, verbose=verbose) + sys.exit(0) - html_content.extend(["", "", "

    Code Issues Report

    "]) + max_pathlen = max(len(os.path.relpath(file_path)) for file_path in python_files) + stats_list = [] - # Add summary statistics - total_issues = sum(len(issues) for issues in issues_by_file.values()) - total_files = len(issues_by_file) - html_content.append( - f""" -
    -

    Total Issues: {total_issues}

    -

    Number of files: {total_files}

    -
    -""" + # Check if all ci_linters are included in linters + if all(ci_linter in linters for ci_linter in ci_linters): + print(f"Passes CI if {ci_linters} passes") + print("-" * 40) + check_ci_pass = True + else: + skipped_ci_linters = [ci_linter for ci_linter in ci_linters if ci_linter not in linters] + print( + f'The "Pass CI" check is skipped since not --linters {" ".join(skipped_ci_linters)} is used.', ) + check_ci_pass = False + # Collect statistics for each file + for ifile, file_path in enumerate(python_files): + stats = analyze_file(file_path, linters=linters, verbose=verbose) + stats_list.append(stats) - # Navigation menu - # html_content.append("") - - for filename, issues in issues_by_file.items(): - print(f"Parsing {filename}") - # Start foldable section for the file - anchor = filename.replace(LIBPATH, "src/struphy").replace("/", "_").replace("\\", "_") - display_name = filename.replace(LIBPATH, "src/struphy") - html_content.append( - f""" -
    - File: {display_name} -""" - ) - - issue_data = {} - for issue in issues: - code = issue.get("code", "Unknown code") - message = replace_backticks_with_code_tags(issue.get("message", "No message")) - url = issue.get("url", "No URL provided") - if code in issue_data: - issue_data[code]["Count"] += 1 - else: - issue_data[code] = { - "Count": 1, - "Message": message, - "url": url, - } - - combined_data = {} - for code, info in issue_data.items(): - count = info["Count"] - url = info["url"] - link = f"{code}" - if count in combined_data: - combined_data[count]["Codes"].append(link) - else: - combined_data[count] = { - "Codes": [link], - } - # Generate the HTML table - html_content.append(generate_html_table_from_combined_data(combined_data, sort_descending=True)) - - for issue in issues: - code = issue.get("code", "Unknown code") - message = replace_backticks_with_code_tags(issue.get("message", "No message")) - location = issue.get("location", {}) - row = location.get("row", None) - column = location.get("column", None) - end_location = issue.get("end_location", {}) - # end_row = end_location.get("row", row) - end_column = end_location.get("column", column) - fix = issue.get("fix", None) - url = issue.get("url", "No URL provided") + # Print the statistics in a table + if output_format == "table": + print_stats_table( + [stats], + linters, + print_header=(ifile == 0), + pathlen=max_pathlen, + ) + elif output_format == "plain": + print_stats_plain(stats, linters) - html_content.append("
    ") - html_content.append("

    ") - html_content.append( - f"Issue: " - f"{code} - " - f"{message}
    " - f"Location: " - f"{display_name}:{row}:{column}
    " - ) - html_content.append("

    ") + if check_ci_pass: + passes_ci = True + for stats in stats_list: + if not all(stats[f"passes_{ci_linter}"] for ci_linter in ci_linters): + passes_ci = False + if passes_ci: + print("All files will pass CI") + sys.exit(0) + else: + print("Not all files will pass CI") + sys.exit(1) + print("Not all CI linters were checked, unknown if all files will pass CI") + sys.exit(1) - # Read the file and extract the code snippet - if os.path.exists(filename) and row is not None: - with open(filename, "r") as source_file: - lines = source_file.readlines() - total_lines = len(lines) - # Adjust indices for zero-based indexing - context_radius = 2 # Number of lines before and after the issue line - start_line = max(row - context_radius - 1, 0) - end_line = min(row + context_radius, total_lines) - snippet_lines = lines[start_line:end_line] - # Build the code snippet - code_lines = [] - for idx, line_content in enumerate(snippet_lines, start=start_line + 1): - line_content = line_content.rstrip("\n") - # Fix HTML special characters - line_content = line_content.replace("&", "&").replace("<", "<").replace(">", ">") - # Highlight the error - if idx == row and column is not None and end_column is not None: - start_col = column - 1 # Adjust for zero-based indexing - end_col = end_column - 1 +def confirm_formatting(python_files, linters, yes): + """Confirm with the user whether to format the listed Python files.""" + print( + tabulate( + [[file] for file in python_files], + headers=[f"The following files will be formatted with {linters}"], + ), + ) + print("\n") + if not yes: + ans = input("Format files (Y/n)?\n") + if ans.lower() not in ("y", "yes", ""): + print("Exiting...") + sys.exit(1) - start_col = max(start_col, 0) - end_col = min(end_col, len(line_content)) - before = line_content[:start_col] - problem = line_content[start_col:end_col] - after = line_content[end_col:] - # Wrap the problematic part with - highlighted_line = f"{before}{problem}{after}" - code_lines.append((idx, highlighted_line)) - else: - code_lines.append((idx, line_content)) - # Make code block with line numbers - html_content.append("
    ")
    -                        for line_number, line_content in code_lines:
    -                            html_content.append(
    -                                # f"
    " - # f"{line_number}{line_content}
    " - f"{line_number}: {line_content}" - ) - html_content.append("
    ") - # Include fix details if available - if fix: - html_content.append("
    ") - html_content.append( - f"

    Fix Available ({fix.get('applicability', 'Unknown')}): " - f"ruff check --select ALL --fix {display_name}

    " - ) - html_content.append("
    ") - else: - html_content.append( - f"

    Cannot read file {filename} or invalid row {row}.

    " - ) +def files_require_formatting(python_files, linters): + """Check if any of the specified files still require formatting based on the specified linters. - html_content.append("
    ") - html_content.append("
    ") + Parameters + ---------- + python_files : list + List of Python file paths to check. - html_content.append("
    ") + linters : list + List of linter names to check against (e.g., ['autopep8', 'isort']). - # Footer - html_content.append( - f""" -
    -

    Generated by on {time.strftime("%Y-%m-%d %H:%M:%S")}

    -
    -""" - ) + Returns + ------- + bool + True if any files still require formatting, False otherwise. + """ + linter_check_functions = { + "autopep8": check_autopep8, + "isort": check_isort, + "add-trailing-comma": check_trailing_commas, + } - html_content.extend(["", ""]) + for file_path in python_files: + for linter in linters: + check_function = linter_check_functions.get(linter) + if check_function and not check_function(file_path): + return True + return False - # Write the HTML content to the output file - with open(html_output_path, "w") as html_file: - html_file.write("\n".join(html_content)) - print(f"HTML report generated at {html_output_path}") +def run_linters_on_files(linters, python_files, flags, verbose): + """Run each linter on the specified files with appropriate flags.""" + for linter in linters: + for python_file in python_files: + print(f"Formatting {python_file}") + linter_flags = flags.get(linter, []) + if len(linter_flags) > 0 and isinstance(linter_flags[0], list): + # If linter_flags is a list, run each separately + for flag in linter_flags: + command = [linter] + flag + [python_file] + if verbose: + print(f"Running command: {' '.join(command)}") - except FileNotFoundError as e: - print(f"Error: {e}") - except json.JSONDecodeError as e: - print(f"Error: Failed to parse JSON file. {e}") - except Exception as e: - print(f"An unexpected error occurred: {e}") + subprocess.run(command, check=False) + else: + # If linter_flags is not a list, treat it as a single value + command = [linter] + linter_flags + [python_file] + if verbose: + print(f"Running command: {' '.join(command)}") + subprocess.run(command, check=False) + + # Loop over each line and replace '# $' with '#$' in place + for line in fileinput.input(python_file, inplace=True): + if line.lstrip().startswith("# $"): + print(line.replace("# $", "#$"), end="") + else: + print(line, end="") def construct_models_init_file() -> str: @@ -1375,3 +1317,100 @@ def construct_propagators_init_file() -> str: propagators_init += "\n\n" propagators_init += f"__all__ = {propagators_names}\n" return propagators_init + + +def struphy_format(config, verbose, yes=False): + """Format Python files with specified linters, optionally iterating multiple times. + + Parameters + ---------- + config : dict + Configuration dictionary containing the following keys: + - input_type : str, optional + The type of files to format ('all', 'path', 'staged', 'branch', or '__init__.py'). Defaults to 'all'. + - path : str, optional + Directory or file path where files will be formatted. + - linters : list + List of formatter names to apply. + - iterations : int, optional + Maximum number of times to apply formatting (default=5). + + verbose : bool + If True, enables detailed output, showing each command and iteration. + + yes : bool, optional + If True, skips the confirmation prompt before formatting. + """ + + # Extract individual settings from config + input_type = config.get("input_type", "all") + path = config.get("path") + linters = config.get("linters", []) + iterations = config.get("iterations", 5) + + if input_type is None and path is not None: + input_type = "path" + + if input_type == "__init__.py": + print(f"Rewriting {PROPAGATORS_INIT_PATH}") + propagators_init = construct_propagators_init_file() + with open(PROPAGATORS_INIT_PATH, "w") as f: + f.write(propagators_init) + + print(f"Rewriting {MODELS_INIT_PATH}") + models_init = construct_models_init_file() + with open(MODELS_INIT_PATH, "w") as f: + f.write(models_init) + + python_files = [PROPAGATORS_INIT_PATH, MODELS_INIT_PATH] + input_type = "path" + else: + python_files = get_python_files(input_type, path) + + if len(python_files) == 0: + print("No Python files to format.") + sys.exit(0) + + confirm_formatting(python_files, linters, yes) + + flags = { + "autopep8": ["--in-place"], + "isort": [], + "add-trailing-comma": ["--exit-zero-even-if-changed"], + "ruff": [["check", "--fix", "--select", "I"], ["format"]], + "ssort": [], + } + + # Skip linting with add-trailing-comma since it disagrees with autopep8 + skip_linters = ["add-trailing-comma"] + + if python_files: + for iteration in range(iterations): + if verbose: + print(f"Iteration {iteration + 1}: Running formatters...") + + run_linters_on_files( + linters, + python_files, + flags, + verbose, + ) + + # Check if any files still require changes + if not files_require_formatting( + python_files, + [lint for lint in linters if lint not in skip_linters], + ): + print("All files are properly formatted.") + break + else: + if verbose: + print( + "Max iterations reached. The following files may still require manual checks:", + ) + for file_path in python_files: + if files_require_formatting([file_path], linters): + print(f" - {file_path}") + print("Contact Max about this") + else: + print("No Python files to format.") diff --git a/src/struphy/console/main.py b/src/struphy/console/main.py index 52c541326..65172d610 100644 --- a/src/struphy/console/main.py +++ b/src/struphy/console/main.py @@ -486,8 +486,8 @@ def add_parser_test(subparsers, list_models): "--mpi", type=int, metavar="N", - help="set number of MPI processes used in tests (default=2))", - default=2, + help="set number of MPI processes used in tests (default=1))", + default=1, ) parser_test.add_argument( diff --git a/src/struphy/console/params.py b/src/struphy/console/params.py index c269ffc7c..496438214 100644 --- a/src/struphy/console/params.py +++ b/src/struphy/console/params.py @@ -1,7 +1,7 @@ import sys import yaml -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from struphy.models import fluid, hybrid, kinetic, toy from struphy.models.base import StruphyModel @@ -26,6 +26,8 @@ def struphy_params(model_name: str, yes: bool = False, check_file: bool = False) except AttributeError: pass + print(f"{model_name =}") + # print units if check_file: print(f"Checking {check_file} with model {model_class}") diff --git a/src/struphy/console/profile.py b/src/struphy/console/profile.py index 76f7d1322..a2f10ce66 100644 --- a/src/struphy/console/profile.py +++ b/src/struphy/console/profile.py @@ -6,7 +6,7 @@ def struphy_profile(dirs, replace, all, n_lines, print_callers, savefig): import os import pickle - import numpy as np + import cunumpy as xp import yaml from matplotlib import pyplot as plt @@ -106,7 +106,7 @@ def struphy_profile(dirs, replace, all, n_lines, print_callers, savefig): + "ncalls".ljust(15) + "tottime".ljust(15) + "percall".ljust(15) - + "cumtime".ljust(15) + + "cumtime".ljust(15), ) print("-" * 154) for position, key in enumerate(dicts[0].keys()): @@ -167,10 +167,10 @@ def struphy_profile(dirs, replace, all, n_lines, print_callers, savefig): ratio.append(str(int(float(t) / runtime * 100)) + "%") # strong scaling plot - if np.all([Nel == val["Nel"][0] for Nel in val["Nel"]]): + if xp.all([Nel == val["Nel"][0] for Nel in val["Nel"]]): # ideal scaling if n == 0: - ax.loglog(val["mpi_size"], 1 / 2 ** np.arange(len(val["time"])), "k--", alpha=0.3, label="ideal") + ax.loglog(val["mpi_size"], 1 / 2 ** xp.arange(len(val["time"])), "k--", alpha=0.3, label="ideal") # print average time per one time step if "integrate" in key: @@ -206,11 +206,11 @@ def struphy_profile(dirs, replace, all, n_lines, print_callers, savefig): ax.set_ylabel("time [s]") ax.set( title="Weak scaling for cells/mpi_size=" - + str(np.prod(val["Nel"][0]) / val["mpi_size"][0]) - + "=const." + + str(xp.prod(val["Nel"][0]) / val["mpi_size"][0]) + + "=const.", ) ax.legend(loc="upper left") - # ax.loglog(val['mpi_size'], val['time'][0]*np.ones_like(val['time']), 'k--', alpha=0.3) + # ax.loglog(val['mpi_size'], val['time'][0]*xp.ones_like(val['time']), 'k--', alpha=0.3) ax.set_xscale("log") if savefig is None: diff --git a/src/struphy/console/test.py b/src/struphy/console/test.py index 09a0872ac..ab64707e3 100644 --- a/src/struphy/console/test.py +++ b/src/struphy/console/test.py @@ -4,7 +4,7 @@ def struphy_test( group: str, *, - mpi: int = 2, + mpi: int = 1, with_desc: bool = False, vrbose: bool = False, show_plots: bool = False, @@ -19,7 +19,7 @@ def struphy_test( Test identifier: "unit", "models", "fluid", "kinetic", "hybrid", "toy", "verification" or a model name. mpi : int - Number of MPI processes used in tests (must be >1, default=2). + Number of MPI processes used in tests (default=1). with_desc : bool Whether to include DESC equilibrium in unit tests (mem consuming). @@ -35,51 +35,57 @@ def struphy_test( """ if "unit" in group: - # first run only tests that require single process - cmd = [ - "pytest", - "-k", - "not _models and not _tutorial and not pproc", - ] - if with_desc: - cmd += ["--with-desc"] - if vrbose: - cmd += ["--vrbose"] - if show_plots: - cmd += ["--show-plots"] - subp_run(cmd) + if mpi > 1: + cmd = [ + "mpirun", + "-n", + str(mpi), + "pytest", + "-k", + "not _models and not _tutorial and not pproc and not _verif_", + "--with-mpi", + ] + else: + cmd = [ + "pytest", + "-k", + "not _models and not _tutorial and not pproc and not _verif_", + ] - # now run parallel unit tests - cmd = [ - "mpirun", - "-n", - str(mpi), - "pytest", - "-k", - "not _models and not _tutorial and not pproc", - "--with-mpi", - ] if with_desc: cmd += ["--with-desc"] if vrbose: cmd += ["--vrbose"] if show_plots: cmd += ["--show-plots"] + subp_run(cmd) elif group in {"models", "fluid", "kinetic", "hybrid", "toy"}: - cmd = [ - "mpirun", - "-n", - str(mpi), - "pytest", - "-k", - "_models", - "-m", - group, - "-s", - "--with-mpi", - ] + if mpi > 1: + cmd = [ + "mpirun", + "--oversubscribe", + "-n", + str(mpi), + "pytest", + "-k", + "_models", + "-m", + group, + "-s", + "--with-mpi", + ] + else: + cmd = [ + "pytest", + "-k", + "_models", + "-m", + group, + "-s", + ] + if vrbose: cmd += ["--vrbose"] if nclones > 1: @@ -89,16 +95,26 @@ def struphy_test( subp_run(cmd) elif "verification" in group: - cmd = [ - "mpirun", - "-n", - str(mpi), - "pytest", - "-k", - "_verif_", - "-s", - "--with-mpi", - ] + if mpi > 1: + cmd = [ + "mpirun", + "--oversubscribe", + "-n", + str(mpi), + "pytest", + "-k", + "_verif_", + "-s", + "--with-mpi", + ] + else: + cmd = [ + "pytest", + "-k", + "_verif_", + "-s", + ] + if vrbose: cmd += ["--vrbose"] if nclones > 1: @@ -110,6 +126,7 @@ def struphy_test( else: cmd = [ "mpirun", + "--oversubscribe", "-n", str(mpi), "pytest", diff --git a/src/struphy/console/tests/test_console.py b/src/struphy/console/tests/test_console.py index 8058e826e..3e742ddaa 100644 --- a/src/struphy/console/tests/test_console.py +++ b/src/struphy/console/tests/test_console.py @@ -6,8 +6,7 @@ import pytest -# from mpi4py import MPI -import struphy +# from psydac.ddm.mpi import mpi as MPI import struphy as struphy_lib from struphy.console.compile import struphy_compile from struphy.console.main import struphy @@ -175,7 +174,7 @@ def mock_remove(path): # Otherwise, we will not remove all the *_tmp.py files # We can not use the real os.remove becuase then # the state and all compiled files will be removed - print(f"{path = }") + print(f"{path =}") if "_tmp.py" in path: print("Not mock remove") os_remove(path) @@ -203,19 +202,19 @@ def mock_remove(path): time_execution=time_execution, yes=yes, ) - print(f"{language = }") - print(f"{compiler = }") - print(f"{omp_pic = }") - print(f"{omp_feec = }") - print(f"{delete = }") + print(f"{language =}") + print(f"{compiler =}") + print(f"{omp_pic =}") + print(f"{omp_feec =}") + print(f"{delete =}") print(f"{status} = ") - print(f"{verbose = }") - print(f"{dependencies = }") - print(f"{time_execution = }") - print(f"{yes = }") - print(f"{mock_save_state.call_count = }") - print(f"{mock_subprocess_run.call_count = }") - print(f"{mock_os_remove.call_count = }") + print(f"{verbose =}") + print(f"{dependencies =}") + print(f"{time_execution =}") + print(f"{yes =}") + print(f"{mock_save_state.call_count =}") + print(f"{mock_subprocess_run.call_count =}") + print(f"{mock_os_remove.call_count =}") if delete: print("if delete") diff --git a/src/struphy/diagnostics/console_diagn.py b/src/struphy/diagnostics/console_diagn.py index 81be7bb38..e110d1497 100644 --- a/src/struphy/diagnostics/console_diagn.py +++ b/src/struphy/diagnostics/console_diagn.py @@ -5,8 +5,8 @@ import os import subprocess +import cunumpy as xp import h5py -import numpy as np import yaml import struphy @@ -301,7 +301,7 @@ def main(): bckgr_fun = getattr(maxwellians, default_bckgr_type)() # Get values of background shifts in velocity space - positions = [np.array([grid_slices["e" + str(k)]]) for k in range(1, 4)] + positions = [xp.array([grid_slices["e" + str(k)]]) for k in range(1, 4)] u = bckgr_fun.u(*positions) eval_params = {"u" + str(k + 1): u[k][0] for k in range(3)} @@ -315,7 +315,7 @@ def main(): # Plot the distribution function if "plot_distr" in actions: # Get index of where to plot in time - time_idx = np.argmin(np.abs(time - saved_time)) + time_idx = xp.argmin(xp.abs(time - saved_time)) plot_distr_fun( path=os.path.join( diff --git a/src/struphy/diagnostics/continuous_spectra.py b/src/struphy/diagnostics/continuous_spectra.py index ec52b4744..c31d789c7 100644 --- a/src/struphy/diagnostics/continuous_spectra.py +++ b/src/struphy/diagnostics/continuous_spectra.py @@ -37,7 +37,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, the radial location s_spec[m][0], squared eigenfrequencis s_spec[m][1] and global mode index s_spec[m][2] corresponding to slow sound modes for each poloidal mode number m in m_range. """ - import numpy as np + import cunumpy as xp import struphy.bsplines.bsplines as bsp @@ -50,7 +50,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, gD_2 = bsp.greville(space.t[1], space.p[1] - 1, space.spl_kind[1]) # poloidal mode numbers - ms = np.arange(m_range[1] - m_range[0] + 1) + m_range[0] + ms = xp.arange(m_range[1] - m_range[0] + 1) + m_range[0] # grid for normalized Jacobian determinant det_df = domain.jacobian_det(gD_1, gD_2, 0.0) @@ -66,7 +66,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, s_spec = [[[], [], []] for m in ms] # only consider eigenmodes in range omega^2/omega_A^2 = [0, 1] - modes_ind = np.where((np.real(omega2) / omega_A**2 < 1.0) & (np.real(omega2) / omega_A**2 > 0.0))[0] + modes_ind = xp.where((xp.real(omega2) / omega_A**2 < 1.0) & (xp.real(omega2) / omega_A**2 > 0.0))[0] for i in range(modes_ind.size): # determine whether it's an Alfvén branch or sound branch by checking DIV(U) @@ -86,14 +86,14 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, U2_1_coeff = (U2_1_coeff[:, :, 0] - 1j * U2_1_coeff[:, :, 1]) / 2 # determine radial location of singularity by looking for a peak in eigenfunction U2_1 - s_ind = np.unravel_index(np.argmax(abs(U2_1_coeff)), U2_1_coeff.shape)[0] + s_ind = xp.unravel_index(xp.argmax(abs(U2_1_coeff)), U2_1_coeff.shape)[0] s = gN_1[s_ind] # perform fft to determine m - U2_1_fft = np.fft.fft(U2_1_coeff) + U2_1_fft = xp.fft.fft(U2_1_coeff) # determine m by looking for peak in Fourier spectrum at singularity - m = int((np.fft.fftfreq(U2_1_fft[s_ind].size) * U2_1_fft[s_ind].size)[np.argmax(abs(U2_1_fft[s_ind]))]) + m = int((xp.fft.fftfreq(U2_1_fft[s_ind].size) * U2_1_fft[s_ind].size)[xp.argmax(abs(U2_1_fft[s_ind]))]) ## perform shift for negative m # if m >= (space.Nel[1] + 1)//2: @@ -103,7 +103,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, for j in range(ms.size): if ms[j] == m: a_spec[j][0].append(s) - a_spec[j][1].append(np.real(omega2[modes_ind[i]])) + a_spec[j][1].append(xp.real(omega2[modes_ind[i]])) a_spec[j][2].append(modes_ind[i]) # Sound branch @@ -117,14 +117,14 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, U2_coeff = (U2_coeff[:, :, 0] - 1j * U2_coeff[:, :, 1]) / 2 # determine radial location of singularity by looking for a peak in eigenfunction (U2_2 or U2_3) - s_ind = np.unravel_index(np.argmax(abs(U2_coeff)), U2_coeff.shape)[0] + s_ind = xp.unravel_index(xp.argmax(abs(U2_coeff)), U2_coeff.shape)[0] s = gD_1[s_ind] # perform fft to determine m - U2_fft = np.fft.fft(U2_coeff) + U2_fft = xp.fft.fft(U2_coeff) # determine m by looking for peak in Fourier spectrum at singularity - m = int((np.fft.fftfreq(U2_fft[s_ind].size) * U2_fft[s_ind].size)[np.argmax(abs(U2_fft[s_ind]))]) + m = int((xp.fft.fftfreq(U2_fft[s_ind].size) * U2_fft[s_ind].size)[xp.argmax(abs(U2_fft[s_ind]))]) ## perform shift for negative m # if m >= (space.Nel[1] + 1)//2: @@ -134,13 +134,13 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, for j in range(ms.size): if ms[j] == m: s_spec[j][0].append(s) - s_spec[j][1].append(np.real(omega2[modes_ind[i]])) + s_spec[j][1].append(xp.real(omega2[modes_ind[i]])) s_spec[j][2].append(modes_ind[i]) # convert to array for j in range(ms.size): - a_spec[j] = np.array(a_spec[j]) - s_spec[j] = np.array(s_spec[j]) + a_spec[j] = xp.array(a_spec[j]) + s_spec[j] = xp.array(s_spec[j]) return a_spec, s_spec @@ -152,12 +152,12 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, import os import shutil - import numpy as np + import cunumpy as xp import yaml # parse arguments parser = argparse.ArgumentParser( - description="Looks for eigenmodes in a given MHD eigenspectrum in a certain poloidal mode number range and plots the continuous shear Alfvén and slow sound spectra (frequency versus radial-like coordinate)." + description="Looks for eigenmodes in a given MHD eigenspectrum in a certain poloidal mode number range and plots the continuous shear Alfvén and slow sound spectra (frequency versus radial-like coordinate).", ) parser.add_argument("m_l_alfvén", type=int, help="lower bound of poloidal mode number range for Alfvénic modes") @@ -252,11 +252,16 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, fem_1d_2 = Spline_space_1d(Nel[1], p[1], spl_kind[1], nq_el[1], dirichlet_bc[1]) fem_2d = Tensor_spline_space( - [fem_1d_1, fem_1d_2], polar_ck, domain.cx[:, :, 0], domain.cy[:, :, 0], n_tor=n_tor, basis_tor="i" + [fem_1d_1, fem_1d_2], + polar_ck, + domain.cx[:, :, 0], + domain.cy[:, :, 0], + n_tor=n_tor, + basis_tor="i", ) # load and analyze spectrum - omega2, U2_eig = np.split(np.load(spec_path), [1], axis=0) + omega2, U2_eig = xp.split(xp.load(spec_path), [1], axis=0) omega2 = omega2.flatten() m_range_alfven = [args.m_l_alfvén, args.m_u_alfvén] @@ -282,7 +287,7 @@ def get_mhd_continua_2d(space, domain, omega2, U_eig, m_range, omega_A, div_tol, fig.set_figheight(12) fig.set_figwidth(14) - etaplot = [np.linspace(0.0, 1.0, 201), np.linspace(0.0, 1.0, 101)] + etaplot = [xp.linspace(0.0, 1.0, 201), xp.linspace(0.0, 1.0, 101)] etaplot[0][0] += 1e-5 diff --git a/src/struphy/diagnostics/diagn_tools.py b/src/struphy/diagnostics/diagn_tools.py index dc50161fb..e7a9d8ee3 100644 --- a/src/struphy/diagnostics/diagn_tools.py +++ b/src/struphy/diagnostics/diagn_tools.py @@ -3,9 +3,9 @@ import shutil import subprocess +import cunumpy as xp import matplotlib.colors as colors import matplotlib.pyplot as plt -import numpy as np from scipy.fft import fftfreq, fftn from scipy.signal import argrelextrema from tqdm import tqdm @@ -37,7 +37,7 @@ def power_spectrum_2d( Parameters ---------- values : dict - Dictionary holding values of a B-spline FemField on the grid as 3d np.arrays: + Dictionary holding values of a B-spline FemField on the grid as 3d xp.arrays: values[n] contains the values at time step n, where n = 0:Nt-1:step with 0 0: @@ -162,9 +162,9 @@ def power_spectrum_2d( for n in range(fit_branches): omega_fit[n] = [] for k, f_of_omega in zip(kvec[k_start:k_end], dispersion[:, k_start:k_end].T): - threshold = np.max(f_of_omega) * noise_level - extrms = argrelextrema(f_of_omega, np.greater, order=extr_order)[0] - above_noise = np.nonzero(f_of_omega > threshold)[0] + threshold = xp.max(f_of_omega) * noise_level + extrms = argrelextrema(f_of_omega, xp.greater, order=extr_order)[0] + above_noise = xp.nonzero(f_of_omega > threshold)[0] intersec = list(set(extrms) & set(above_noise)) # intersec = list(set(extrms)) if not intersec: @@ -173,7 +173,7 @@ def power_spectrum_2d( # print(f"{intersec = }") # print(f"{[omega[intersec[n]] for n in range(fit_branches)]}") assert len(intersec) == fit_branches, ( - f"Number of found branches {len(intersec)} is not {fit_branches = }! \ + f"Number of found branches {len(intersec)} is not {fit_branches =}! \ Try to lower 'noise_level' or increase 'extr_order'." ) k_fit += [k] @@ -183,14 +183,14 @@ def power_spectrum_2d( # fit coeffs = [] for m, om in omega_fit.items(): - coeffs += [np.polyfit(k_fit, om, deg=fit_degree[n])] - print(f"\nFitted {coeffs = }") + coeffs += [xp.polyfit(k_fit, om, deg=fit_degree[n])] + print(f"\nFitted {coeffs =}") if do_plot: _, ax = plt.subplots(1, 1, figsize=(10, 10)) colormap = "plasma" - K, W = np.meshgrid(kvec, omega) - lvls = np.logspace(-15, -1, 27) + K, W = xp.meshgrid(kvec, omega) + lvls = xp.logspace(-15, -1, 27) disp_plot = ax.contourf( K, W, @@ -214,7 +214,7 @@ def power_spectrum_2d( def fun(k): out = k * 0.0 - for i, c in enumerate(np.flip(cs)): + for i, c in enumerate(xp.flip(cs)): out += c * k**i return out @@ -230,12 +230,12 @@ def fun(k): set_min = 0.0 set_max = 0.0 for key, branch in branches.items(): - vals = np.real(branch) + vals = xp.real(branch) ax.plot(kvec, vals, "--", label=key) - tmp = np.min(vals) + tmp = xp.min(vals) if tmp < set_min: set_min = tmp - tmp = np.max(vals) + tmp = xp.max(vals) if tmp > set_max: set_max = tmp @@ -331,8 +331,8 @@ def plot_scalars( plt.figure("en_tot_rel_err") plt.plot( time[1:], - np.divide( - np.abs(en_tot[1:] - en_tot[0]), + xp.divide( + xp.abs(en_tot[1:] - en_tot[0]), en_tot[0], ), ) @@ -363,9 +363,9 @@ def plot_scalars( for key, plot_quantity in plot_quantities.items(): # Get the indices of the extrema if do_fit: - inds_exs = argrelextrema(plot_quantity, np.greater, order=order) + inds_exs = argrelextrema(plot_quantity, xp.greater, order=order) elif fit_minima: - inds_exs = argrelextrema(plot_quantity, np.less, order=order) + inds_exs = argrelextrema(plot_quantity, xp.less, order=order) else: inds_exs = None @@ -376,10 +376,10 @@ def plot_scalars( # for plotting take a bit more time at start and end if len(inds_exs[0]) >= 2: - time_start_idx = np.max( + time_start_idx = xp.max( [0, 2 * inds_exs[0][start_extremum] - inds_exs[0][start_extremum + 1]], ) - time_end_idx = np.min( + time_end_idx = xp.min( [ len(time) - 1, 2 * inds_exs[0][start_extremum + no_extrema - 1] - inds_exs[0][start_extremum + no_extrema - 2], @@ -395,9 +395,9 @@ def plot_scalars( if inds_exs is not None: # do the fitting - coeffs = np.polyfit( + coeffs = xp.polyfit( times_extrema, - np.log( + xp.log( quantity_extrema, ), deg=degree, @@ -410,15 +410,15 @@ def plot_scalars( ) plt.plot( time_cut, - np.exp(coeffs[0] * time_cut + coeffs[1]), - label=r"$a * \exp(m x)$ with" + f"\na={np.round(np.exp(coeffs[1]), 3)} m={np.round(coeffs[0], 3)}", + xp.exp(coeffs[0] * time_cut + coeffs[1]), + label=r"$a * \exp(m x)$ with" + f"\na={xp.round(xp.exp(coeffs[1]), 3)} m={xp.round(coeffs[0], 3)}", ) else: plt.plot(time, plot_quantity[:], ".", label=key, markersize=2) if inds_exs is not None: # do the fitting - coeffs = np.polyfit( + coeffs = xp.polyfit( times_extrema, quantity_extrema, deg=degree, @@ -433,8 +433,8 @@ def plot_scalars( ) plt.plot( time_cut, - np.exp(coeffs[0] * time_cut + coeffs[1]), - label=r"$a x + b$ with" + f"\na={np.round(coeffs[1], 3)} b={np.round(coeffs[0], 3)}", + xp.exp(coeffs[0] * time_cut + coeffs[1]), + label=r"$a x + b$ with" + f"\na={xp.round(coeffs[1], 3)} b={xp.round(coeffs[0], 3)}", ) plt.legend() @@ -496,11 +496,11 @@ def plot_distr_fun( # load full distribution functions if filename == "f_binned.npy": - f = np.load(filepath) + f = xp.load(filepath) # load delta f elif filename == "delta_f_binned.npy": - delta_f = np.load(filepath) + delta_f = xp.load(filepath) assert f is not None, "No distribution function file found!" @@ -508,7 +508,7 @@ def plot_distr_fun( directions = folder.split("_") for direction in directions: grids += [ - np.load( + xp.load( os.path.join( subpath, "grid_" + direction + ".npy", @@ -519,8 +519,8 @@ def plot_distr_fun( # Get indices of where to plot in other directions grid_idxs = {} for k in range(f.ndim - 1): - grid_idxs[directions[k]] = np.argmin( - np.abs(grids[k] - grid_slices[directions[k]]), + grid_idxs[directions[k]] = xp.argmin( + xp.abs(grids[k] - grid_slices[directions[k]]), ) for k in range(f.ndim - 1): @@ -655,17 +655,17 @@ def plots_videos_2d( grid_idxs = {} for k in range(df_data.ndim - 1): direc = directions[k] - grid_idxs[direc] = np.argmin( - np.abs(grids[direc] - grid_slices[direc]), + grid_idxs[direc] = xp.argmin( + xp.abs(grids[direc] - grid_slices[direc]), ) - grid_1 = np.load( + grid_1 = xp.load( os.path.join( data_path, "grid_" + label_1 + ".npy", ), ) - grid_2 = np.load( + grid_2 = xp.load( os.path.join( data_path, "grid_" + label_2 + ".npy", @@ -683,7 +683,7 @@ def plots_videos_2d( df_binned = df_data[tuple(f_slicing)].squeeze() - assert t_grid.ndim == grid_1.ndim == grid_2.ndim == 1, f"Input arrays must be 1D!" + assert t_grid.ndim == grid_1.ndim == grid_2.ndim == 1, "Input arrays must be 1D!" assert df_binned.shape[0] == t_grid.size, f"{df_binned.shape =}, {t_grid.shape =}" assert df_binned.shape[1] == grid_1.size, f"{df_binned.shape =}, {grid_1.shape =}" assert df_binned.shape[2] == grid_2.size, f"{df_binned.shape =}, {grid_2.shape =}" @@ -696,9 +696,9 @@ def plots_videos_2d( var *= polar_params["r_max"] - polar_params["r_min"] var += polar_params["r_min"] elif polar_params["angular_coord"] == sl: - var *= 2 * np.pi + var *= 2 * xp.pi - grid_1_mesh, grid_2_mesh = np.meshgrid(grid_1, grid_2, indexing="ij") + grid_1_mesh, grid_2_mesh = xp.meshgrid(grid_1, grid_2, indexing="ij") if output == "video": plots_2d_video( @@ -745,7 +745,7 @@ def video_2d(slc, diagn_path, images_path): Parameters ---------- - t_grid : np.ndarray + t_grid : xp.ndarray 1D-array containing all the times grid_slices : dict @@ -833,15 +833,15 @@ def plots_2d_video( # Get parameters for time and labelling for it nt = len(t_grid) - log_nt = int(np.log10(nt)) + 1 + log_nt = int(xp.log10(nt)) + 1 len_dt = len(str(t_grid[1]).split(".")[1]) # Get the correct scale for the plots - vmin += [np.min(df_binned[:]) / 3] - vmax += [np.max(df_binned[:]) / 3] - vmin = np.min(vmin) - vmax = np.max(vmax) - vscale = np.max(np.abs([vmin, vmax])) + vmin += [xp.min(df_binned[:]) / 3] + vmax += [xp.max(df_binned[:]) / 3] + vmin = xp.min(vmin) + vmax = xp.max(vmax) + vscale = xp.max(xp.abs([vmin, vmax])) # Set up the figure and axis once if do_polar: @@ -939,18 +939,18 @@ def plots_2d_overview( fig_height = 8.5 else: n_cols = 3 - n_rows = int(np.ceil(n_times / n_cols)) + n_rows = int(xp.ceil(n_times / n_cols)) fig_height = 4 * n_rows fig_size = (4 * n_cols, fig_height) # Get the correct scale for the plots for time in times: - vmin += [np.min(df_binned[time]) / 3] - vmax += [np.max(df_binned[time]) / 3] - vmin = np.min(vmin) - vmax = np.max(vmax) - vscale = np.max(np.abs([vmin, vmax])) + vmin += [xp.min(df_binned[time]) / 3] + vmax += [xp.max(df_binned[time]) / 3] + vmin = xp.min(vmin) + vmax = xp.max(vmax) + vscale = xp.max(xp.abs([vmin, vmax])) # Plot options for polar plots subplot_kw = dict(projection="polar") if do_polar else None @@ -959,8 +959,8 @@ def plots_2d_overview( fig, axes = plt.subplots(n_rows, n_cols, figsize=fig_size, subplot_kw=subplot_kw) # So we an use .flatten() even for just 1 plot - if not isinstance(axes, np.ndarray): - axes = np.array([axes]) + if not isinstance(axes, xp.ndarray): + axes = xp.array([axes]) # fig.tight_layout(h_pad=5.0, w_pad=5.0) # fig.tight_layout(pad=5.0) @@ -976,7 +976,7 @@ def plots_2d_overview( # Set the suptitle fig.suptitle(f"Struphy model '{model_name}'") - for k in np.arange(n_times): + for k in xp.arange(n_times): obj = axes.flatten()[k] n = times[k] t = f"%.{len_dt}f" % t_grid[n] @@ -1048,13 +1048,13 @@ def get_slices_grids_directions_and_df_data(plot_full_f, grid_slices, data_path, slices_2d : list[string] A list of all the slicings - grids : list[np.ndarray] + grids : list[xp.ndarray] A list of all grids according to the slices directions : list[string] A list of the directions that appear in all slices - df_data : np.ndarray + df_data : xp.ndarray The data of delta-f (in case of full-f: distribution function minus background) """ @@ -1063,7 +1063,7 @@ def get_slices_grids_directions_and_df_data(plot_full_f, grid_slices, data_path, # Load all the grids grids = {} for direction in directions: - grids[direction] = np.load( + grids[direction] = xp.load( os.path.join(data_path, "grid_" + direction + ".npy"), ) @@ -1072,7 +1072,7 @@ def get_slices_grids_directions_and_df_data(plot_full_f, grid_slices, data_path, _name = "f_binned.npy" else: _name = "delta_f_binned.npy" - _data = np.load(os.path.join(data_path, _name)) + _data = xp.load(os.path.join(data_path, _name)) # Check how many slicings have been given and make slices_2d for all # combinations of spatial and velocity dimensions diff --git a/src/struphy/diagnostics/diagnostics_pic.ipynb b/src/struphy/diagnostics/diagnostics_pic.ipynb index d4b2f2e0f..f41425141 100644 --- a/src/struphy/diagnostics/diagnostics_pic.ipynb +++ b/src/struphy/diagnostics/diagnostics_pic.ipynb @@ -7,11 +7,13 @@ "outputs": [], "source": [ "import os\n", - "import struphy\n", + "\n", "import numpy as np\n", "from matplotlib import pyplot as plt\n", "\n", - "path_out = os.path.join(struphy.__path__[0], 'io/out', 'sim_1')\n", + "import struphy\n", + "\n", + "path_out = os.path.join(struphy.__path__[0], \"io/out\", \"sim_1\")\n", "\n", "print(path_out)\n", "os.listdir(path_out)" @@ -28,7 +30,7 @@ "metadata": {}, "outputs": [], "source": [ - "data_path = os.path.join(path_out, 'post_processing')\n", + "data_path = os.path.join(path_out, \"post_processing\")\n", "\n", "os.listdir(data_path)" ] @@ -39,7 +41,7 @@ "metadata": {}, "outputs": [], "source": [ - "t_grid = np.load(os.path.join(data_path, 't_grid.npy'))\n", + "t_grid = np.load(os.path.join(data_path, \"t_grid.npy\"))\n", "t_grid" ] }, @@ -49,7 +51,7 @@ "metadata": {}, "outputs": [], "source": [ - "f_path = os.path.join(data_path, 'kinetic_data', 'ions', 'distribution_function')\n", + "f_path = os.path.join(data_path, \"kinetic_data\", \"ions\", \"distribution_function\")\n", "\n", "print(os.listdir(f_path))" ] @@ -60,7 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "path = os.path.join(f_path, 'e1')\n", + "path = os.path.join(f_path, \"e1\")\n", "print(os.listdir(path))" ] }, @@ -70,9 +72,9 @@ "metadata": {}, "outputs": [], "source": [ - "grid = np.load(os.path.join(f_path, 'e1/', 'grid_e1.npy'))\n", - "f_binned = np.load(os.path.join(f_path, 'e1/', 'f_binned.npy'))\n", - "delta_f_e1_binned = np.load(os.path.join(f_path, 'e1/', 'delta_f_binned.npy'))\n", + "grid = np.load(os.path.join(f_path, \"e1/\", \"grid_e1.npy\"))\n", + "f_binned = np.load(os.path.join(f_path, \"e1/\", \"f_binned.npy\"))\n", + "delta_f_e1_binned = np.load(os.path.join(f_path, \"e1/\", \"delta_f_binned.npy\"))\n", "\n", "print(grid.shape)\n", "print(f_binned.shape)\n", @@ -87,18 +89,18 @@ "source": [ "steps = list(np.arange(10))\n", "\n", - "plt.figure(figsize=(12, 5*len(steps)))\n", + "plt.figure(figsize=(12, 5 * len(steps)))\n", "for n, step in enumerate(steps):\n", - " plt.subplot(len(steps), 2, 2*n + 1)\n", - " plt.plot(grid, f_binned[step], label=f'time = {t_grid[step]}')\n", - " plt.xlabel('e1')\n", - " #plt.ylim([.5, 1.5])\n", - " plt.title('full-f')\n", - " plt.subplot(len(steps), 2, 2*n + 2)\n", - " plt.plot(grid, delta_f_e1_binned[step], label=f'time = {t_grid[step]}')\n", - " plt.xlabel('e1')\n", - " #plt.ylim([-3e-3, 3e-3])\n", - " plt.title(r'$\\delta f$')\n", + " plt.subplot(len(steps), 2, 2 * n + 1)\n", + " plt.plot(grid, f_binned[step], label=f\"time = {t_grid[step]}\")\n", + " plt.xlabel(\"e1\")\n", + " # plt.ylim([.5, 1.5])\n", + " plt.title(\"full-f\")\n", + " plt.subplot(len(steps), 2, 2 * n + 2)\n", + " plt.plot(grid, delta_f_e1_binned[step], label=f\"time = {t_grid[step]}\")\n", + " plt.xlabel(\"e1\")\n", + " # plt.ylim([-3e-3, 3e-3])\n", + " plt.title(r\"$\\delta f$\")\n", " plt.legend()" ] }, @@ -108,7 +110,7 @@ "metadata": {}, "outputs": [], "source": [ - "path = os.path.join(f_path, 'e1_v1')\n", + "path = os.path.join(f_path, \"e1_v1\")\n", "print(os.listdir(path))" ] }, @@ -118,10 +120,10 @@ "metadata": {}, "outputs": [], "source": [ - "grid_e1 = np.load(os.path.join(f_path, 'e1_v1/', 'grid_e1.npy'))\n", - "grid_v1 = np.load(os.path.join(f_path, 'e1_v1/', 'grid_v1.npy'))\n", - "f_binned = np.load(os.path.join(f_path, 'e1_v1/', 'f_binned.npy'))\n", - "delta_f_binned = np.load(os.path.join(f_path, 'e1_v1/', 'delta_f_binned.npy'))\n", + "grid_e1 = np.load(os.path.join(f_path, \"e1_v1/\", \"grid_e1.npy\"))\n", + "grid_v1 = np.load(os.path.join(f_path, \"e1_v1/\", \"grid_v1.npy\"))\n", + "f_binned = np.load(os.path.join(f_path, \"e1_v1/\", \"f_binned.npy\"))\n", + "delta_f_binned = np.load(os.path.join(f_path, \"e1_v1/\", \"delta_f_binned.npy\"))\n", "\n", "print(grid_e1.shape)\n", "print(grid_v1.shape)\n", @@ -137,20 +139,20 @@ "source": [ "steps = list(np.arange(10))\n", "\n", - "plt.figure(figsize=(12, 5*len(steps)))\n", + "plt.figure(figsize=(12, 5 * len(steps)))\n", "for n, step in enumerate(steps):\n", - " plt.subplot(len(steps), 2, 2*n + 1)\n", - " plt.pcolor(grid_e1, grid_v1, f_binned[step].T, label=f'time = {t_grid[step]}')\n", - " plt.xlabel('$e1$')\n", - " plt.ylabel(r'$v_\\parallel$')\n", - " plt.title('full-f')\n", + " plt.subplot(len(steps), 2, 2 * n + 1)\n", + " plt.pcolor(grid_e1, grid_v1, f_binned[step].T, label=f\"time = {t_grid[step]}\")\n", + " plt.xlabel(\"$e1$\")\n", + " plt.ylabel(r\"$v_\\parallel$\")\n", + " plt.title(\"full-f\")\n", " plt.legend()\n", " plt.colorbar()\n", - " plt.subplot(len(steps), 2, 2*n + 2)\n", - " plt.pcolor(grid_e1, grid_v1, delta_f_binned[step].T, label=f'time = {t_grid[step]}')\n", - " plt.xlabel('$e1$')\n", - " plt.ylabel(r'$v_\\parallel$')\n", - " plt.title(r'$\\delta f$')\n", + " plt.subplot(len(steps), 2, 2 * n + 2)\n", + " plt.pcolor(grid_e1, grid_v1, delta_f_binned[step].T, label=f\"time = {t_grid[step]}\")\n", + " plt.xlabel(\"$e1$\")\n", + " plt.ylabel(r\"$v_\\parallel$\")\n", + " plt.title(r\"$\\delta f$\")\n", " plt.legend()\n", " plt.colorbar()" ] @@ -161,7 +163,7 @@ "metadata": {}, "outputs": [], "source": [ - "fields_path = os.path.join(data_path, 'fields_data')\n", + "fields_path = os.path.join(data_path, \"fields_data\")\n", "\n", "print(os.listdir(fields_path))" ] @@ -174,7 +176,7 @@ "source": [ "import pickle\n", "\n", - "with open(os.path.join(fields_path, 'grids_phy.bin'), 'rb') as file:\n", + "with open(os.path.join(fields_path, \"grids_phy.bin\"), \"rb\") as file:\n", " x_grid, y_grid, z_grid = pickle.load(file)\n", "\n", "print(type(x_grid))\n", @@ -187,7 +189,7 @@ "metadata": {}, "outputs": [], "source": [ - "with open(os.path.join(fields_path, 'em_fields', 'phi_phy.bin'), 'rb') as file:\n", + "with open(os.path.join(fields_path, \"em_fields\", \"phi_phy.bin\"), \"rb\") as file:\n", " phi = pickle.load(file)\n", "\n", "plt.figure(figsize=(12, 12))\n", @@ -197,9 +199,9 @@ " t = t_grid[step]\n", " print(phi[t][0].shape)\n", " plt.subplot(2, 2, n + 1)\n", - " plt.plot(x_grid[:, 0, 0], phi[t][0][:, 0, 0], label=f'time = {t}')\n", - " plt.xlabel('x')\n", - " plt.ylabel(r'$\\phi$(x)')\n", + " plt.plot(x_grid[:, 0, 0], phi[t][0][:, 0, 0], label=f\"time = {t}\")\n", + " plt.xlabel(\"x\")\n", + " plt.ylabel(r\"$\\phi$(x)\")\n", " plt.legend()" ] }, diff --git a/src/struphy/diagnostics/paraview/mesh_creator.py b/src/struphy/diagnostics/paraview/mesh_creator.py index aad81c3ba..4bf83211c 100644 --- a/src/struphy/diagnostics/paraview/mesh_creator.py +++ b/src/struphy/diagnostics/paraview/mesh_creator.py @@ -1,6 +1,5 @@ -import numpy as np - # from tqdm import tqdm +import cunumpy as xp import vtkmodules.all as vtk from vtkmodules.util.numpy_support import numpy_to_vtk as np2vtk from vtkmodules.util.numpy_support import vtk_to_numpy as vtk2np @@ -38,7 +37,12 @@ def make_ugrid_and_write_vtu(filename: str, writer, vtk_dir, gvec, s_range, u_ra point_data = {} cell_data = {} vtk_points, suv_points, xyz_points, point_indices = gen_vtk_points( - gvec, s_range, u_range, v_range, point_data, cell_data + gvec, + s_range, + u_range, + v_range, + point_data, + cell_data, ) print("vtk_points.GetNumberOfPoints()", vtk_points.GetNumberOfPoints(), flush=True) @@ -81,43 +85,43 @@ def gen_vtk_points(gvec, s_range, u_range, v_range, point_data, cell_data): pt_idx = 0 vtk_points = vtk.vtkPoints() - suv_points = np.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0], 3)) - xyz_points = np.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0], 3)) - point_indices = np.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0]), dtype=np.int_) + suv_points = xp.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0], 3)) + xyz_points = xp.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0], 3)) + point_indices = xp.zeros((s_range.shape[0], u_range.shape[0], v_range.shape[0]), dtype=xp.int_) # Add metadata to grid. num_pts = s_range.shape[0] * u_range.shape[0] * v_range.shape[0] - point_data["s"] = np.zeros(num_pts, dtype=np.float_) - point_data["u"] = np.zeros(num_pts, dtype=np.float_) - point_data["v"] = np.zeros(num_pts, dtype=np.float_) - point_data["x"] = np.zeros(num_pts, dtype=np.float_) - point_data["y"] = np.zeros(num_pts, dtype=np.float_) - point_data["z"] = np.zeros(num_pts, dtype=np.float_) - point_data["theta"] = np.zeros(num_pts, dtype=np.float_) - point_data["zeta"] = np.zeros(num_pts, dtype=np.float_) - point_data["Point ID"] = np.zeros(num_pts, dtype=np.int_) - point_data["pressure"] = np.zeros(num_pts, dtype=np.float_) - point_data["phi"] = np.zeros(num_pts, dtype=np.float_) - point_data["chi"] = np.zeros(num_pts, dtype=np.float_) - point_data["iota"] = np.zeros(num_pts, dtype=np.float_) - point_data["q"] = np.zeros(num_pts, dtype=np.float_) - point_data["det"] = np.zeros(num_pts, dtype=np.float_) - point_data["det/(2pi)^2"] = np.zeros(num_pts, dtype=np.float_) - point_data["A"] = np.zeros((num_pts, 3), dtype=np.float_) - point_data["A_vec"] = np.zeros((num_pts, 3), dtype=np.float_) - point_data["A_1"] = np.zeros((num_pts, 3), dtype=np.float_) - point_data["A_2"] = np.zeros((num_pts, 3), dtype=np.float_) - point_data["B"] = np.zeros((num_pts, 3), dtype=np.float_) - point_data["B_vec"] = np.zeros((num_pts, 3), dtype=np.float_) - point_data["B_1"] = np.zeros((num_pts, 3), dtype=np.float_) - point_data["B_2"] = np.zeros((num_pts, 3), dtype=np.float_) + point_data["s"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["u"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["v"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["x"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["y"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["z"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["theta"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["zeta"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["Point ID"] = xp.zeros(num_pts, dtype=xp.int_) + point_data["pressure"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["phi"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["chi"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["iota"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["q"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["det"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["det/(2pi)^2"] = xp.zeros(num_pts, dtype=xp.float_) + point_data["A"] = xp.zeros((num_pts, 3), dtype=xp.float_) + point_data["A_vec"] = xp.zeros((num_pts, 3), dtype=xp.float_) + point_data["A_1"] = xp.zeros((num_pts, 3), dtype=xp.float_) + point_data["A_2"] = xp.zeros((num_pts, 3), dtype=xp.float_) + point_data["B"] = xp.zeros((num_pts, 3), dtype=xp.float_) + point_data["B_vec"] = xp.zeros((num_pts, 3), dtype=xp.float_) + point_data["B_1"] = xp.zeros((num_pts, 3), dtype=xp.float_) + point_data["B_2"] = xp.zeros((num_pts, 3), dtype=xp.float_) # pbar = tqdm(total=num_pts) for s_idx, s in enumerate(s_range): for u_idx, u in enumerate(u_range): for v_idx, v in enumerate(v_range): point = gvec.f(s, u, v) - suv_points[s_idx, u_idx, v_idx, :] = np.array([s, u, v]) + suv_points[s_idx, u_idx, v_idx, :] = xp.array([s, u, v]) xyz_points[s_idx, u_idx, v_idx, :] = point point_indices[s_idx, u_idx, v_idx] = pt_idx vtk_points.InsertPoint(pt_idx, point) @@ -149,10 +153,10 @@ def gen_vtk_points(gvec, s_range, u_range, v_range, point_data, cell_data): pt_idx += 1 # pbar.close() - point_data["theta"] = 2 * np.pi * point_data["u"] - point_data["zeta"] = 2 * np.pi * point_data["v"] + point_data["theta"] = 2 * xp.pi * point_data["u"] + point_data["zeta"] = 2 * xp.pi * point_data["v"] point_data["q"] = 1 / point_data["iota"] - point_data["det/(2pi)^2"] = point_data["det"] / (2 * np.pi) ** 2 + point_data["det/(2pi)^2"] = point_data["det"] / (2 * xp.pi) ** 2 return vtk_points, suv_points, xyz_points, point_indices @@ -312,4 +316,4 @@ def connect_cell(s_range, u_range, v_range, point_indices, ugrid, point_data, ce cell_data["Cell ID"].append(cell_idx) cell_idx += 1 - cell_data["Cell ID"] = np.array(cell_data["Cell ID"], dtype=np.int_) + cell_data["Cell ID"] = xp.array(cell_data["Cell ID"], dtype=xp.int_) diff --git a/src/struphy/dispersion_relations/analytic.py b/src/struphy/dispersion_relations/analytic.py index f68ca5774..a54355428 100644 --- a/src/struphy/dispersion_relations/analytic.py +++ b/src/struphy/dispersion_relations/analytic.py @@ -1,6 +1,6 @@ "Analytic dispersion relations." -import numpy as np +import cunumpy as xp from numpy.polynomial import Polynomial from scipy.optimize import fsolve @@ -108,18 +108,18 @@ def __call__(self, k): Bsquare = self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2 # Alfvén velocity and speed of sound - vA = np.sqrt(Bsquare / self.params["n0"]) + vA = xp.sqrt(Bsquare / self.params["n0"]) - cS = np.sqrt(self.params["gamma"] * self.params["p0"] / self.params["n0"]) + cS = xp.sqrt(self.params["gamma"] * self.params["p0"] / self.params["n0"]) # shear Alfvén branch - self._branches["shear Alfvén"] = vA * k * self.params["B0z"] / np.sqrt(Bsquare) + self._branches["shear Alfvén"] = vA * k * self.params["B0z"] / xp.sqrt(Bsquare) # slow/fast magnetosonic branch delta = (4 * self.params["B0z"] ** 2 * cS**2 * vA**2) / ((cS**2 + vA**2) ** 2 * Bsquare) - self._branches["slow magnetosonic"] = np.sqrt(1 / 2 * k**2 * (cS**2 + vA**2) * (1 - np.sqrt(1 - delta))) - self._branches["fast magnetosonic"] = np.sqrt(1 / 2 * k**2 * (cS**2 + vA**2) * (1 + np.sqrt(1 - delta))) + self._branches["slow magnetosonic"] = xp.sqrt(1 / 2 * k**2 * (cS**2 + vA**2) * (1 - xp.sqrt(1 - delta))) + self._branches["fast magnetosonic"] = xp.sqrt(1 / 2 * k**2 * (cS**2 + vA**2) * (1 + xp.sqrt(1 - delta))) return self.branches @@ -186,14 +186,14 @@ def __call__(self, k): Bsquare = self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2 - cos_theta = self.params["B0z"] / np.sqrt(Bsquare) + cos_theta = self.params["B0z"] / xp.sqrt(Bsquare) # Alfvén velocity, speed of sound and cyclotron frequency - vA = np.sqrt(Bsquare / self.params["n0"]) + vA = xp.sqrt(Bsquare / self.params["n0"]) - cS = np.sqrt(self.params["gamma"] * self.params["p0"] / self.params["n0"]) + cS = xp.sqrt(self.params["gamma"] * self.params["p0"] / self.params["n0"]) - Omega_i = np.sqrt(Bsquare) / self.params["eps"] + Omega_i = xp.sqrt(Bsquare) / self.params["eps"] # auxiliary functions def omega_0(k): @@ -218,7 +218,7 @@ def discriminant(k): ) # solve - out = np.zeros((k.size, 4), dtype=complex) + out = xp.zeros((k.size, 4), dtype=complex) for i, ki in enumerate(k): p0 = Polynomial([-(omega_0(ki) ** 2), 1.0]) p1 = Polynomial([d(ki), c(ki), b(ki), 1.0]) @@ -261,7 +261,15 @@ class FluidSlabITG(DispersionRelations1D): def __init__(self, vstar=10.0, vi=1.0, Z=1.0, kz=1.0, gamma=5 / 3): super().__init__( - "wave 1", "wave 2", "wave 3", velocity_scale="thermal", vstar=vstar, vi=vi, Z=Z, kz=kz, gamma=gamma + "wave 1", + "wave 2", + "wave 3", + velocity_scale="thermal", + vstar=vstar, + vi=vi, + Z=Z, + kz=kz, + gamma=gamma, ) def __call__(self, k): @@ -302,7 +310,7 @@ def discriminant(k): return -4.0 * p**3 - 27.0 * q(k) ** 2 # solve - out = np.zeros((k.size, 3), dtype=complex) + out = xp.zeros((k.size, 3), dtype=complex) for i, ki in enumerate(k): poly = Polynomial([q(ki), p, 0.0, 1.0]) out[i] = poly.roots() @@ -342,17 +350,17 @@ def __call__(self, kvec): # One complex array for each branch tmps = [] for n in range(self.nbranches): - tmps += [np.zeros_like(kvec, dtype=complex)] + tmps += [xp.zeros_like(kvec, dtype=complex)] ########### Model specific part ############################## # angle between k and magnetic field if self.params["B0z"] == 0: - theta = np.pi / 2 + theta = xp.pi / 2 else: - theta = np.arctan(np.sqrt(self.params["B0x"] ** 2 + self.params["B0y"] ** 2) / self.params["B0z"]) + theta = xp.arctan(xp.sqrt(self.params["B0x"] ** 2 + self.params["B0y"] ** 2) / self.params["B0z"]) print(theta) - cos2 = np.cos(theta) ** 2 + cos2 = xp.cos(theta) ** 2 neq = self.params["n0"] @@ -393,10 +401,10 @@ def __call__(self, kvec): e = eps6 # determinant in polynomial form - det = np.polynomial.Polynomial([a, b, c, d, e]) + det = xp.polynomial.Polynomial([a, b, c, d, e]) # solutions - sol = np.sqrt(np.abs(det.roots())) + sol = xp.sqrt(xp.abs(det.roots())) # Ion-cyclotron branch tmps[0][n] = sol[0] # Electron-cyclotron branch @@ -489,7 +497,7 @@ def __init__(self, **params): ee = 1.602176634e-19 # calculate coupling parameter alpha_c from bulk number density and mass number - self._kappa = ee * np.sqrt(mu * self.params["Ab"] * self.params["nb"] * 1e20 / mp) + self._kappa = ee * xp.sqrt(mu * self.params["Ab"] * self.params["nb"] * 1e20 / mp) def __call__(self, k, method="newton", tol=1e-10, max_it=100): """ @@ -518,7 +526,7 @@ def __call__(self, k, method="newton", tol=1e-10, max_it=100): # One complex array for each branch tmps = [] for _ in range(self.nbranches): - tmps += [np.zeros_like(k, dtype=complex)] + tmps += [xp.zeros_like(k, dtype=complex)] ########### Model specific part ############################## @@ -532,8 +540,8 @@ def __call__(self, k, method="newton", tol=1e-10, max_it=100): wR = [self.params["B0"] * ki, 0.0] wL = [self.params["B0"] * ki, 0.0] else: - wR = [np.real(tmps[0][i - 1]), np.imag(tmps[0][i - 1])] - wL = [np.real(tmps[1][i - 1]), np.imag(tmps[1][i - 1])] + wR = [xp.real(tmps[0][i - 1]), xp.imag(tmps[0][i - 1])] + wL = [xp.real(tmps[1][i - 1]), xp.imag(tmps[1][i - 1])] # apply solver if method == "newton": @@ -542,13 +550,13 @@ def __call__(self, k, method="newton", tol=1e-10, max_it=100): Dr, Di = self.D_RL(wR, ki, +1) - while np.abs(Dr + Di * 1j) > tol or counter == max_it: + while xp.abs(Dr + Di * 1j) > tol or counter == max_it: # derivative Drp, Dip = self.D_RL(wR, ki, +1, 1) # update - wR[0] = wR[0] - np.real((Dr + Di * 1j) / (Drp + Dip * 1j)) - wR[1] = wR[1] - np.imag((Dr + Di * 1j) / (Drp + Dip * 1j)) + wR[0] = wR[0] - xp.real((Dr + Di * 1j) / (Drp + Dip * 1j)) + wR[1] = wR[1] - xp.imag((Dr + Di * 1j) / (Drp + Dip * 1j)) Dr, Di = self.D_RL(wR, ki, +1) counter += 1 @@ -558,13 +566,13 @@ def __call__(self, k, method="newton", tol=1e-10, max_it=100): Dr, Di = self.D_RL(wL, ki, -1) - while np.abs(Dr + Di * 1j) > tol or counter == max_it: + while xp.abs(Dr + Di * 1j) > tol or counter == max_it: # derivative Drp, Dip = self.D_RL(wL, ki, -1, 1) # update - wL[0] = wL[0] - np.real((Dr + Di * 1j) / (Drp + Dip * 1j)) - wL[1] = wL[1] - np.imag((Dr + Di * 1j) / (Drp + Dip * 1j)) + wL[0] = wL[0] - xp.real((Dr + Di * 1j) / (Drp + Dip * 1j)) + wL[1] = wL[1] - xp.imag((Dr + Di * 1j) / (Drp + Dip * 1j)) Dr, Di = self.D_RL(wL, ki, -1) counter += 1 @@ -651,7 +659,7 @@ def D_RL(self, w, k, pol, der=0): * (Zplasma(xi, 0) + (w - k * v0) * Zplasma(xi, 1) * xip) ) - return np.real(out), np.imag(out) + return xp.real(out), xp.imag(out) class PressureCouplingFull6DParallel(DispersionRelations1D): @@ -723,7 +731,7 @@ def __call__(self, k, tol=1e-10): # One complex array for each branch tmps = [] for n in range(self.nbranches): - tmps += [np.zeros_like(k, dtype=complex)] + tmps += [xp.zeros_like(k, dtype=complex)] ########### Model specific part ############################## @@ -735,9 +743,9 @@ def __call__(self, k, tol=1e-10): wL = [1 * ki, 0.0] # TODO: use vA wS = [1 * ki, 0.0] # TODO: use cS else: - wR = [np.real(tmps[0][i - 1]), np.imag(tmps[0][i - 1])] - wL = [np.real(tmps[1][i - 1]), np.imag(tmps[1][i - 1])] - wS = [np.real(tmps[2][i - 1]), np.imag(tmps[2][i - 1])] + wR = [xp.real(tmps[0][i - 1]), xp.imag(tmps[0][i - 1])] + wL = [xp.real(tmps[1][i - 1]), xp.imag(tmps[1][i - 1])] + wS = [xp.real(tmps[2][i - 1]), xp.imag(tmps[2][i - 1])] # R/L shear Alfvén wave sol_R = fsolve(self.D_RL, x0=wR, args=(ki, +1), xtol=tol) @@ -796,8 +804,8 @@ def D_RL(self, w, k, pol): vperp = 1.0 # TODO vth = 1.0 - vA = np.sqrt((self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2) / self.params["n0"]) - # cS = np.sqrt(self.params['beta']*vA) + vA = xp.sqrt((self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2) / self.params["n0"]) + # cS = xp.sqrt(self.params['beta']*vA) cS = 1.0 a0 = u0 / vpara # TODO @@ -840,7 +848,7 @@ def D_RL(self, w, k, pol): ) ) - return np.real(c1), np.imag(c1) + return xp.real(c1), xp.imag(c1) def D_sonic(self, w, k): r""" @@ -873,8 +881,8 @@ def D_sonic(self, w, k): vperp = 1.0 # TODO vth = 1.0 - vA = np.sqrt((self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2) / self.params["n0"]) - # cS = np.sqrt(self.params['beta']*vA) + vA = xp.sqrt((self.params["B0x"] ** 2 + self.params["B0y"] ** 2 + self.params["B0z"] ** 2) / self.params["n0"]) + # cS = xp.sqrt(self.params['beta']*vA) cS = 1.0 a0 = u0 / vpara # TODO @@ -885,7 +893,7 @@ def D_sonic(self, w, k): c1 = w**2 - k**2 * cS**2 + 2 * w * k * nu * vpara * x4 - return np.real(c1), np.imag(c1) + return xp.real(c1), xp.imag(c1) # private methods: # ---------------- @@ -1014,11 +1022,11 @@ def __call__(self, x, m, n): specs = {} # shear Alfvén continuum - specs["shear_Alfvén"] = np.sqrt(F(x, m, n) ** 2 / rho(x)) + specs["shear_Alfvén"] = xp.sqrt(F(x, m, n) ** 2 / rho(x)) # slow sound continuum - specs["slow_sound"] = np.sqrt( - gamma * p(x) * F(x, m, n) ** 2 / (rho(x) * (gamma * p(x) + By(x) ** 2 + Bz(x) ** 2)) + specs["slow_sound"] = xp.sqrt( + gamma * p(x) * F(x, m, n) ** 2 / (rho(x) * (gamma * p(x) + By(x) ** 2 + Bz(x) ** 2)), ) return specs @@ -1121,11 +1129,11 @@ def __call__(self, r, m, n): specs = {} # shear Alfvén continuum - specs["shear_Alfvén"] = np.sqrt(F(r, m, n) ** 2 / rho(r)) + specs["shear_Alfvén"] = xp.sqrt(F(r, m, n) ** 2 / rho(r)) # slow sound continuum - specs["slow_sound"] = np.sqrt( - gamma * p(r) * F(r, m, n) ** 2 / (rho(r) * (gamma * p(r) + Bt(r) ** 2 + Bz(r) ** 2)) + specs["slow_sound"] = xp.sqrt( + gamma * p(r) * F(r, m, n) ** 2 / (rho(r) * (gamma * p(r) + Bt(r) ** 2 + Bz(r) ** 2)), ) return specs diff --git a/src/struphy/dispersion_relations/base.py b/src/struphy/dispersion_relations/base.py index 1700955c2..6994ae2fb 100644 --- a/src/struphy/dispersion_relations/base.py +++ b/src/struphy/dispersion_relations/base.py @@ -2,7 +2,7 @@ from abc import ABCMeta, abstractmethod -import numpy as np +import cunumpy as xp from matplotlib import pyplot as plt @@ -99,18 +99,18 @@ def plot(self, k): plt.ylabel(rf"Im($\omega$) [{unit_om}]") for name, omega in self.branches.items(): plt.subplot(2, 1, 1) - plt.plot(k, np.real(omega), label=name) + plt.plot(k, xp.real(omega), label=name) plt.subplot(2, 1, 2) - plt.plot(k, np.imag(omega), label=name) + plt.plot(k, xp.imag(omega), label=name) plt.subplot(2, 1, 1) for lab, kc in self.k_crit.items(): - if kc > np.min(k) and kc < np.max(k): + if kc > xp.min(k) and kc < xp.max(k): plt.axvline(kc, color="k", linestyle="--", linewidth=0.5, label=lab) plt.legend() plt.subplot(2, 1, 2) for lab, kc in self.k_crit.items(): - if kc > np.min(k) and kc < np.max(k): + if kc > xp.min(k) and kc < xp.max(k): plt.axvline(kc, color="k", linestyle="--", linewidth=0.5, label=lab) diff --git a/src/struphy/dispersion_relations/utilities.py b/src/struphy/dispersion_relations/utilities.py index 40c2b57f0..b796eb321 100644 --- a/src/struphy/dispersion_relations/utilities.py +++ b/src/struphy/dispersion_relations/utilities.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp from scipy.special import erfi @@ -23,7 +23,7 @@ def Zplasma(xi, der=0): assert der == 0 or der == 1, 'Parameter "der" must be either 0 or 1' if der == 0: - z = np.sqrt(np.pi) * np.exp(-(xi**2)) * (1j - erfi(xi)) + z = xp.sqrt(xp.pi) * xp.exp(-(xi**2)) * (1j - erfi(xi)) else: z = -2 * (1 + xi * Zplasma(xi, 0)) diff --git a/src/struphy/eigenvalue_solvers/derivatives.py b/src/struphy/eigenvalue_solvers/derivatives.py index 122dade81..0e34cceb1 100644 --- a/src/struphy/eigenvalue_solvers/derivatives.py +++ b/src/struphy/eigenvalue_solvers/derivatives.py @@ -6,7 +6,7 @@ Modules to assemble discrete derivatives. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa @@ -31,7 +31,7 @@ def grad_1d_matrix(spl_kind, NbaseN): NbaseD = NbaseN - 1 + spl_kind - grad = np.zeros((NbaseD, NbaseN), dtype=float) + grad = xp.zeros((NbaseD, NbaseN), dtype=float) for i in range(NbaseD): grad[i, i] = -1.0 @@ -79,9 +79,9 @@ def discrete_derivatives_3d(space): grad_1d_3 = 0 * spa.identity(1, format="csr") else: if space.basis_tor == "r": - grad_1d_3 = 2 * np.pi * space.n_tor * spa.csr_matrix(np.array([[0.0, 1.0], [-1.0, 0.0]])) + grad_1d_3 = 2 * xp.pi * space.n_tor * spa.csr_matrix(xp.array([[0.0, 1.0], [-1.0, 0.0]])) else: - grad_1d_3 = 1j * 2 * np.pi * space.n_tor * spa.identity(1, format="csr") + grad_1d_3 = 1j * 2 * xp.pi * space.n_tor * spa.identity(1, format="csr") # standard tensor-product derivatives if space.ck == -1: diff --git a/src/struphy/eigenvalue_solvers/kernels_projectors_global.py b/src/struphy/eigenvalue_solvers/kernels_projectors_global.py index 4f6c01392..f01cefc1c 100644 --- a/src/struphy/eigenvalue_solvers/kernels_projectors_global.py +++ b/src/struphy/eigenvalue_solvers/kernels_projectors_global.py @@ -24,7 +24,13 @@ def kernel_int_2d(nq1: "int", nq2: "int", w1: "float[:]", w2: "float[:]", mat_f: # ========= kernel for integration in 3d ================== def kernel_int_3d( - nq1: "int", nq2: "int", nq3: "int", w1: "float[:]", w2: "float[:]", w3: "float[:]", mat_f: "float[:,:,:]" + nq1: "int", + nq2: "int", + nq3: "int", + w1: "float[:]", + w2: "float[:]", + w3: "float[:]", + mat_f: "float[:,:,:]", ) -> "float": f_loc = 0.0 @@ -47,7 +53,11 @@ def kernel_int_3d( # ========= kernel for integration along eta1 direction, reducing to a 2d array ============================ def kernel_int_2d_eta1( - subs1: "int[:]", subs_cum1: "int[:]", w1: "float[:,:]", mat_f: "float[:,:,:]", f_int: "float[:,:]" + subs1: "int[:]", + subs_cum1: "int[:]", + w1: "float[:,:]", + mat_f: "float[:,:,:]", + f_int: "float[:,:]", ): n1, n2 = shape(f_int) @@ -66,7 +76,11 @@ def kernel_int_2d_eta1( # ========= kernel for integration along eta2 direction, reducing to a 2d array ============================ def kernel_int_2d_eta2( - subs2: "int[:]", subs_cum2: "int[:]", w2: "float[:,:]", mat_f: "float[:,:,:]", f_int: "float[:,:]" + subs2: "int[:]", + subs_cum2: "int[:]", + w2: "float[:,:]", + mat_f: "float[:,:,:]", + f_int: "float[:,:]", ): n1, n2 = shape(f_int) @@ -167,7 +181,11 @@ def kernel_int_2d_eta1_eta2_old(w1: "float[:,:]", w2: "float[:,:]", mat_f: "floa # ========= kernel for integration along eta1 direction, reducing to a 3d array ============================ def kernel_int_3d_eta1( - subs1: "int[:]", subs_cum1: "int[:]", w1: "float[:,:]", mat_f: "float[:,:,:,:]", f_int: "float[:,:,:]" + subs1: "int[:]", + subs_cum1: "int[:]", + w1: "float[:,:]", + mat_f: "float[:,:,:,:]", + f_int: "float[:,:,:]", ): n1, n2, n3 = shape(f_int) @@ -186,7 +204,11 @@ def kernel_int_3d_eta1( def kernel_int_3d_eta1_transpose( - subs1: "int[:]", subs_cum1: "int[:]", w1: "float[:,:]", f_int: "float[:,:,:]", mat_f: "float[:,:,:,:]" + subs1: "int[:]", + subs_cum1: "int[:]", + w1: "float[:,:]", + f_int: "float[:,:,:]", + mat_f: "float[:,:,:,:]", ): n1, n2, n3 = shape(f_int) @@ -205,7 +227,11 @@ def kernel_int_3d_eta1_transpose( # ========= kernel for integration along eta2 direction, reducing to a 3d array ============================ def kernel_int_3d_eta2( - subs2: "int[:]", subs_cum2: "int[:]", w2: "float[:,:]", mat_f: "float[:,:,:,:]", f_int: "float[:,:,:]" + subs2: "int[:]", + subs_cum2: "int[:]", + w2: "float[:,:]", + mat_f: "float[:,:,:,:]", + f_int: "float[:,:,:]", ): n1, n2, n3 = shape(f_int) @@ -224,7 +250,11 @@ def kernel_int_3d_eta2( def kernel_int_3d_eta2_transpose( - subs2: "int[:]", subs_cum2: "int[:]", w2: "float[:,:]", f_int: "float[:,:,:]", mat_f: "float[:,:,:,:]" + subs2: "int[:]", + subs_cum2: "int[:]", + w2: "float[:,:]", + f_int: "float[:,:,:]", + mat_f: "float[:,:,:,:]", ): n1, n2, n3 = shape(f_int) @@ -243,7 +273,11 @@ def kernel_int_3d_eta2_transpose( # ========= kernel for integration along eta3 direction, reducing to a 3d array ============================ def kernel_int_3d_eta3( - subs3: "int[:]", subs_cum3: "int[:]", w3: "float[:,:]", mat_f: "float[:,:,:,:]", f_int: "float[:,:,:]" + subs3: "int[:]", + subs_cum3: "int[:]", + w3: "float[:,:]", + mat_f: "float[:,:,:,:]", + f_int: "float[:,:,:]", ): n1, n2, n3 = shape(f_int) @@ -262,7 +296,11 @@ def kernel_int_3d_eta3( def kernel_int_3d_eta3_transpose( - subs3: "int[:]", subs_cum3: "int[:]", w3: "float[:,:]", f_int: "float[:,:,:]", mat_f: "float[:,:,:,:]" + subs3: "int[:]", + subs_cum3: "int[:]", + w3: "float[:,:]", + f_int: "float[:,:,:]", + mat_f: "float[:,:,:,:]", ): n1, n2, n3 = shape(f_int) @@ -655,7 +693,11 @@ def kernel_int_3d_eta1_eta2_eta3_transpose( # ========= kernel for integration in eta1-eta2-eta3 cell, reducing to a 3d array ======================= def kernel_int_3d_eta1_eta2_eta3_old( - w1: "float[:,:]", w2: "float[:,:]", w3: "float[:,:]", mat_f: "float[:,:,:,:,:,:]", f_int: "float[:,:,:]" + w1: "float[:,:]", + w2: "float[:,:]", + w3: "float[:,:]", + mat_f: "float[:,:,:,:,:,:]", + f_int: "float[:,:,:]", ): ne1, nq1, ne2, nq2, ne3, nq3 = shape(mat_f) diff --git a/src/struphy/eigenvalue_solvers/legacy/MHD_eigenvalues_cylinder_1D.py b/src/struphy/eigenvalue_solvers/legacy/MHD_eigenvalues_cylinder_1D.py index 4d2bd5fa2..a16b44e3d 100644 --- a/src/struphy/eigenvalue_solvers/legacy/MHD_eigenvalues_cylinder_1D.py +++ b/src/struphy/eigenvalue_solvers/legacy/MHD_eigenvalues_cylinder_1D.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp import scipy as sc import scipy.sparse as spa import scipy.special as sp @@ -21,7 +21,7 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ r = lambda eta: a * eta # jacobian for integration - jac = lambda eta1: a * np.ones(eta1.shape, dtype=float) + jac = lambda eta1: a * xp.ones(eta1.shape, dtype=float) # ========================== kinetic energy functional ============================== # integrands (multiplied by -2/omega**2) @@ -46,11 +46,11 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ # Bspline_A = Bsp.Bspline(splines.T, splines.p ) # Bspline_B = Bsp.Bspline(splines.t, splines.p - 1) # - # K_11_scipy = np.zeros((splines.NbaseN, splines.NbaseN), dtype=float) - # K_22_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # K_33_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # K_23_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # K_32_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # K_11_scipy = xp.zeros((splines.NbaseN, splines.NbaseN), dtype=float) + # K_22_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # K_33_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # K_23_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # K_32_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) # # for i in range(1, Bspline_A.N - 1): # for j in range(1, Bspline_A.N - 1): @@ -76,11 +76,11 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ # integrand = lambda eta : a*K_ZV(eta)*Bspline_B(eta, i)*Bspline_B(eta, j) # K_32_scipy[i, j] = integrate.quad(integrand, 0., 1.)[0] - # assert np.allclose(K_11.toarray(), K_11_scipy[1:-1, 1:-1]) - # assert np.allclose(K_22.toarray(), K_22_scipy ) - # assert np.allclose(K_33.toarray(), K_33_scipy[bcZ:, bcZ:]) - # assert np.allclose(K_23.toarray(), K_23_scipy[ : , bcZ:]) - # assert np.allclose(K_32.toarray(), K_32_scipy[bcZ:, :]) + # assert xp.allclose(K_11.toarray(), K_11_scipy[1:-1, 1:-1]) + # assert xp.allclose(K_22.toarray(), K_22_scipy ) + # assert xp.allclose(K_33.toarray(), K_33_scipy[bcZ:, bcZ:]) + # assert xp.allclose(K_23.toarray(), K_23_scipy[ : , bcZ:]) + # assert xp.allclose(K_32.toarray(), K_32_scipy[bcZ:, :]) # ========================== potential energy functional =========================== # integrands (multiplied by 2) @@ -120,17 +120,17 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ ) W_dXZ = lambda eta: B_phi(r(eta)) * gamma * m * p(r(eta)) / r(eta) ** 2 + B_z(r(eta)) * gamma * k * p(r(eta)) / r( - eta + eta, ) W_ZdX = lambda eta: B_phi(r(eta)) * gamma * m * p(r(eta)) / r(eta) ** 2 + B_z(r(eta)) * gamma * k * p(r(eta)) / r( - eta + eta, ) W_VZ = lambda eta: B_phi(r(eta)) * gamma * m**2 * p(r(eta)) / r(eta) ** 2 + B_z(r(eta)) * gamma * k * m * p( - r(eta) + r(eta), ) / r(eta) W_ZV = lambda eta: B_phi(r(eta)) * gamma * m**2 * p(r(eta)) / r(eta) ** 2 + B_z(r(eta)) * gamma * k * m * p( - r(eta) + r(eta), ) / r(eta) # compute matrices @@ -163,15 +163,15 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ # return W_22 ## test correct computation - # W_11_scipy = np.zeros((splines.NbaseN, splines.NbaseN), dtype=float) - # W_22_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # W_33_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # W_12_scipy = np.zeros((splines.NbaseN, splines.NbaseD), dtype=float) - # W_21_scipy = np.zeros((splines.NbaseD, splines.NbaseN), dtype=float) - # W_13_scipy = np.zeros((splines.NbaseN, splines.NbaseD), dtype=float) - # W_31_scipy = np.zeros((splines.NbaseD, splines.NbaseN), dtype=float) - # W_23_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) - # W_32_scipy = np.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # W_11_scipy = xp.zeros((splines.NbaseN, splines.NbaseN), dtype=float) + # W_22_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # W_33_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # W_12_scipy = xp.zeros((splines.NbaseN, splines.NbaseD), dtype=float) + # W_21_scipy = xp.zeros((splines.NbaseD, splines.NbaseN), dtype=float) + # W_13_scipy = xp.zeros((splines.NbaseN, splines.NbaseD), dtype=float) + # W_31_scipy = xp.zeros((splines.NbaseD, splines.NbaseN), dtype=float) + # W_23_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) + # W_32_scipy = xp.zeros((splines.NbaseD, splines.NbaseD), dtype=float) # # for i in range(1, Bspline_A.N - 1): # for j in range(1, Bspline_A.N - 1): @@ -187,15 +187,15 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ # integrand = lambda eta : W_XdX(eta) * Bspline_A(eta, i, 0) * Bspline_A(eta, j, 1) # W_11_scipy[i, j] += integrate.quad(integrand, 0., 1.)[0] # - # assert np.allclose(W_11.toarray(), W_11_scipy[1:-1, 1:-1]) + # assert xp.allclose(W_11.toarray(), W_11_scipy[1:-1, 1:-1]) - # print(np.allclose(K, K.T)) - # print(np.allclose(W, W.T)) + # print(xp.allclose(K, K.T)) + # print(xp.allclose(W, W.T)) # solve eigenvalue problem omega**2*K*xi = W*xi - A = np.linalg.inv(K).dot(W) + A = xp.linalg.inv(K).dot(W) - omega2, XVZ_eig = np.linalg.eig(A) + omega2, XVZ_eig = xp.linalg.eig(A) # extract components X_eig = XVZ_eig[: (splines.NbaseN - 2), :] @@ -203,11 +203,11 @@ def solve_ev_problem(rho, B_phi, dB_phi, B_z, p, gamma, a, k, m, num_params, bcZ Z_eig = XVZ_eig[(splines.NbaseN - 2 + splines.NbaseD) :, :] # add boundary conditions X(0) = X(1) = 0 - X_eig = np.vstack((np.zeros(X_eig.shape[1], dtype=float), X_eig, np.zeros(X_eig.shape[1], dtype=float))) + X_eig = xp.vstack((xp.zeros(X_eig.shape[1], dtype=float), X_eig, xp.zeros(X_eig.shape[1], dtype=float))) # add boundary condition Z(0) = 0 if bcZ == 1: - Z_eig = np.vstack((np.zeros(Z_eig.shape[1], dtype=float), Z_eig)) + Z_eig = xp.vstack((xp.zeros(Z_eig.shape[1], dtype=float), Z_eig)) return omega2, X_eig, V_eig, Z_eig @@ -225,43 +225,43 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, # components of metric tensor and Jacobian determinant G_r = a**2 - G_phi = lambda eta: 4 * np.pi**2 * r(eta) ** 2 - G_z = 4 * np.pi**2 * R0**2 - J = lambda eta: 4 * np.pi**2 * R0 * a * r(eta) + G_phi = lambda eta: 4 * xp.pi**2 * r(eta) ** 2 + G_z = 4 * xp.pi**2 * R0**2 + J = lambda eta: 4 * xp.pi**2 * R0 * a * r(eta) # 2-from components of equilibrium magnetic field and its projection - B2_phi = lambda eta: 2 * np.pi * R0 * a * B_phi(r(eta)) - B2_z = lambda eta: 2 * np.pi * a * r(eta) * B_z(r(eta)) + B2_phi = lambda eta: 2 * xp.pi * R0 * a * B_phi(r(eta)) + B2_z = lambda eta: 2 * xp.pi * a * r(eta) * B_z(r(eta)) - b2_eq_phi = np.linalg.solve(proj.D.toarray(), proj.rhs_1(B2_phi)) - b2_eq_z = np.append(np.array([0.0]), np.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(B2_z)[1:])) + b2_eq_phi = xp.linalg.solve(proj.D.toarray(), proj.rhs_1(B2_phi)) + b2_eq_z = xp.append(xp.array([0.0]), xp.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(B2_z)[1:])) # 3-form components of equilibrium density and pessure and its projection Rho3 = lambda eta: J(eta) * Rho(r(eta)) P3 = lambda eta: J(eta) * P(r(eta)) - rho3_eq = np.append(np.array([0.0]), np.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(Rho3)[1:])) - p3_eq = np.append(np.array([0.0]), np.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(P3)[1:])) + rho3_eq = xp.append(xp.array([0.0]), xp.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(Rho3)[1:])) + p3_eq = xp.append(xp.array([0.0]), xp.linalg.solve(proj.D.toarray()[1:, 1:], proj.rhs_1(P3)[1:])) # 2-form components of initial velocity and its projection U2_r = lambda eta: J(eta) * eta * (1 - eta) u2_r = proj.pi_0(U2_r) - u2_phi = -1 / (2 * np.pi * m) * GRAD_all.dot(u2_r) - u2_z = np.zeros(len(u2_phi), dtype=float) + u2_phi = -1 / (2 * xp.pi * m) * GRAD_all.dot(u2_r) + u2_z = xp.zeros(len(u2_phi), dtype=float) - b2_r = np.zeros(len(u2_r), dtype=float) - b2_phi = np.zeros(len(u2_phi), dtype=float) - b2_z = np.zeros(len(u2_z), dtype=float) + b2_r = xp.zeros(len(u2_r), dtype=float) + b2_phi = xp.zeros(len(u2_phi), dtype=float) + b2_z = xp.zeros(len(u2_z), dtype=float) - p3 = np.zeros(len(u2_z), dtype=float) + p3 = xp.zeros(len(u2_z), dtype=float) # projection matrices pi0_N_i, pi0_D_i, pi1_N_i, pi1_D_i = proj.projection_matrices_1d_reduced() pi0_NN_i, pi0_DN_i, pi0_ND_i, pi0_DD_i, pi1_NN_i, pi1_DN_i, pi1_ND_i, pi1_DD_i = proj.projection_matrices_1d() # 1D collocation matrices for interpolation in format (point, global basis function) - x_int = np.copy(proj.x_int) + x_int = xp.copy(proj.x_int) kind_splines = [False, True] @@ -270,29 +270,35 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, # 1D integration sub-intervals, quadrature points and weights if splines.p % 2 == 0: - x_his = np.union1d(x_int, splines.el_b) + x_his = xp.union1d(x_int, splines.el_b) else: - x_his = np.copy(x_int) + x_his = xp.copy(x_int) pts, wts = bsp.quadrature_grid(x_his, proj.pts_loc, proj.wts_loc) # compute number of sub-intervals for integrations (even degree) if splines.p % 2 == 0: - subs = 2 * np.ones(proj.pts.shape[0], dtype=int) + subs = 2 * xp.ones(proj.pts.shape[0], dtype=int) subs[: splines.p // 2] = 1 subs[-splines.p // 2 :] = 1 # compute number of sub-intervals for integrations (odd degree) else: - subs = np.ones(proj.pts.shape[0], dtype=int) + subs = xp.ones(proj.pts.shape[0], dtype=int) # evaluate basis functions on quadrature points in format (interval, local quad. point, global basis function) basis_his_N = bsp.collocation_matrix(splines.T, splines.p, pts.flatten(), False, normalize=kind_splines[0]).reshape( - pts.shape[0], pts.shape[1], splines.NbaseN + pts.shape[0], + pts.shape[1], + splines.NbaseN, ) basis_his_D = bsp.collocation_matrix( - splines.t, splines.p - 1, pts.flatten(), False, normalize=kind_splines[1] + splines.t, + splines.p - 1, + pts.flatten(), + False, + normalize=kind_splines[1], ).reshape(pts.shape[0], pts.shape[1], splines.NbaseD) # shift first interpolation point away from pole @@ -308,26 +314,26 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, M2_z = mass.get_M1(splines, mapping=lambda eta: J(eta) / G_z).toarray() # === matrices for curl of equilibrium field (with integration by parts) ========== - MB_12_eq = np.empty((splines.NbaseN, splines.NbaseD), dtype=float) - MB_13_eq = np.empty((splines.NbaseN, splines.NbaseD), dtype=float) + MB_12_eq = xp.empty((splines.NbaseN, splines.NbaseD), dtype=float) + MB_13_eq = xp.empty((splines.NbaseN, splines.NbaseD), dtype=float) - MB_21_eq = np.empty((splines.NbaseD, splines.NbaseN), dtype=float) - MB_31_eq = np.empty((splines.NbaseD, splines.NbaseN), dtype=float) + MB_21_eq = xp.empty((splines.NbaseD, splines.NbaseN), dtype=float) + MB_31_eq = xp.empty((splines.NbaseD, splines.NbaseN), dtype=float) - f_phi = np.linalg.inv(proj.N.toarray()).T.dot(GRAD_all.T.dot(M2_phi.dot(b2_eq_phi))) - f_z = np.linalg.inv(proj.N.toarray()).T.dot(GRAD_all.T.dot(M2_z.dot(b2_eq_z))) + f_phi = xp.linalg.inv(proj.N.toarray()).T.dot(GRAD_all.T.dot(M2_phi.dot(b2_eq_phi))) + f_z = xp.linalg.inv(proj.N.toarray()).T.dot(GRAD_all.T.dot(M2_z.dot(b2_eq_z))) - pi0_ND_phi = np.empty(pi0_ND_i[3].max() + 1, dtype=float) - pi0_ND_z = np.empty(pi0_ND_i[3].max() + 1, dtype=float) + pi0_ND_phi = xp.empty(pi0_ND_i[3].max() + 1, dtype=float) + pi0_ND_z = xp.empty(pi0_ND_i[3].max() + 1, dtype=float) - row_ND = np.empty(pi0_ND_i[3].max() + 1, dtype=int) - col_ND = np.empty(pi0_ND_i[3].max() + 1, dtype=int) + row_ND = xp.empty(pi0_ND_i[3].max() + 1, dtype=int) + col_ND = xp.empty(pi0_ND_i[3].max() + 1, dtype=int) - pi0_DN_phi = np.empty(pi0_DN_i[3].max() + 1, dtype=float) - pi0_DN_z = np.empty(pi0_DN_i[3].max() + 1, dtype=float) + pi0_DN_phi = xp.empty(pi0_DN_i[3].max() + 1, dtype=float) + pi0_DN_z = xp.empty(pi0_DN_i[3].max() + 1, dtype=float) - row_DN = np.empty(pi0_DN_i[3].max() + 1, dtype=int) - col_DN = np.empty(pi0_DN_i[3].max() + 1, dtype=int) + row_DN = xp.empty(pi0_DN_i[3].max() + 1, dtype=int) + col_DN = xp.empty(pi0_DN_i[3].max() + 1, dtype=int) ker.rhs0_f_1d(pi0_ND_i, basis_int_N, basis_int_D, 1 / J(x_int), f_phi, pi0_ND_phi, row_ND, col_ND) ker.rhs0_f_1d(pi0_ND_i, basis_int_N, basis_int_D, 1 / J(x_int), f_z, pi0_ND_z, row_ND, col_ND) @@ -348,23 +354,23 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, MB_31_eq[:, :] = -pi0_DN_z # === matrices for curl of equilibrium field (without integration by parts) ====== - MB_12_eq = np.empty((splines.NbaseN, splines.NbaseD), dtype=float) - MB_13_eq = np.empty((splines.NbaseN, splines.NbaseD), dtype=float) + MB_12_eq = xp.empty((splines.NbaseN, splines.NbaseD), dtype=float) + MB_13_eq = xp.empty((splines.NbaseN, splines.NbaseD), dtype=float) - MB_21_eq = np.empty((splines.NbaseD, splines.NbaseN), dtype=float) - MB_31_eq = np.empty((splines.NbaseD, splines.NbaseN), dtype=float) + MB_21_eq = xp.empty((splines.NbaseD, splines.NbaseN), dtype=float) + MB_31_eq = xp.empty((splines.NbaseD, splines.NbaseN), dtype=float) - cN = np.empty(splines.NbaseN, dtype=float) - cD = np.empty(splines.NbaseD, dtype=float) + cN = xp.empty(splines.NbaseN, dtype=float) + cD = xp.empty(splines.NbaseD, dtype=float) for j in range(splines.NbaseD): cD[:] = 0.0 cD[j] = 1.0 integrand2 = ( - lambda eta: splines.evaluate_D(eta, cD) / J(eta) * 2 * np.pi * a * (B_phi(r(eta)) + r(eta) * dB_phi(r(eta))) + lambda eta: splines.evaluate_D(eta, cD) / J(eta) * 2 * xp.pi * a * (B_phi(r(eta)) + r(eta) * dB_phi(r(eta))) ) - integrand3 = lambda eta: splines.evaluate_D(eta, cD) / J(eta) * 2 * np.pi * a * R0 * dB_z(r(eta)) + integrand3 = lambda eta: splines.evaluate_D(eta, cD) / J(eta) * 2 * xp.pi * a * R0 * dB_z(r(eta)) MB_12_eq[:, j] = inner.inner_prod_V0(splines, integrand2) MB_13_eq[:, j] = inner.inner_prod_V0(splines, integrand3) @@ -374,39 +380,39 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, cN[j] = 1.0 integrand2 = ( - lambda eta: splines.evaluate_N(eta, cN) / J(eta) * 2 * np.pi * a * (B_phi(r(eta)) + r(eta) * dB_phi(r(eta))) + lambda eta: splines.evaluate_N(eta, cN) / J(eta) * 2 * xp.pi * a * (B_phi(r(eta)) + r(eta) * dB_phi(r(eta))) ) - integrand3 = lambda eta: splines.evaluate_N(eta, cN) / J(eta) * 2 * np.pi * a * R0 * dB_z(r(eta)) + integrand3 = lambda eta: splines.evaluate_N(eta, cN) / J(eta) * 2 * xp.pi * a * R0 * dB_z(r(eta)) MB_21_eq[:, j] = inner.inner_prod_V1(splines, integrand2) MB_31_eq[:, j] = inner.inner_prod_V1(splines, integrand3) # ===== right-hand sides of projection matrices =============== - rhs0_N_phi = np.empty(pi0_N_i[0].size, dtype=float) - rhs0_N_z = np.empty(pi0_N_i[0].size, dtype=float) + rhs0_N_phi = xp.empty(pi0_N_i[0].size, dtype=float) + rhs0_N_z = xp.empty(pi0_N_i[0].size, dtype=float) - rhs1_D_phi = np.empty(pi1_D_i[0].size, dtype=float) - rhs1_D_z = np.empty(pi1_D_i[0].size, dtype=float) + rhs1_D_phi = xp.empty(pi1_D_i[0].size, dtype=float) + rhs1_D_z = xp.empty(pi1_D_i[0].size, dtype=float) - rhs0_N_pr = np.empty(pi0_N_i[0].size, dtype=float) - rhs1_D_pr = np.empty(pi1_D_i[0].size, dtype=float) + rhs0_N_pr = xp.empty(pi0_N_i[0].size, dtype=float) + rhs1_D_pr = xp.empty(pi1_D_i[0].size, dtype=float) - rhs0_N_rho = np.empty(pi0_N_i[0].size, dtype=float) - rhs1_D_rho = np.empty(pi1_D_i[0].size, dtype=float) + rhs0_N_rho = xp.empty(pi0_N_i[0].size, dtype=float) + rhs1_D_rho = xp.empty(pi1_D_i[0].size, dtype=float) # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, b2_eq_phi)/J(x_int), rhs0_N_phi) # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, b2_eq_z )/J(x_int), rhs0_N_z ) # - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), b2_eq_z )/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_z) - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), b2_eq_phi)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_phi) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), b2_eq_z )/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_z) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), b2_eq_phi)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_phi) # # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, p3_eq)/J(x_int), rhs0_N_pr) - # temp = np.empty(pi0_N_i[0].size, dtype=float) + # temp = xp.empty(pi0_N_i[0].size, dtype=float) # temp[:] = rhs0_N_pr - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), p3_eq)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_pr) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), p3_eq)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_pr) # # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, rho3)/J(x_int), rhs0_N_rho) - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), rho3)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_rho) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), rho3)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_rho) ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, B2_phi(x_int) / J(x_int), rhs0_N_phi) ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, B2_z(x_int) / J(x_int), rhs0_N_z) @@ -415,7 +421,7 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, pi1_D_i[0], pi1_D_i[1], subs, - np.append(0, np.cumsum(subs - 1)[:-1]), + xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (B2_phi(pts.flatten()) / J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), @@ -426,20 +432,20 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, pi1_D_i[0], pi1_D_i[1], subs, - np.append(0, np.cumsum(subs - 1)[:-1]), + xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (B2_z(pts.flatten()) / J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_z, ) - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, np.ones(pts.shape, dtype=float), rhs1_D_z) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, xp.ones(pts.shape, dtype=float), rhs1_D_z) ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, P3(x_int) / J(x_int), rhs0_N_pr) ker.rhs1_1d( pi1_D_i[0], pi1_D_i[1], subs, - np.append(0, np.cumsum(subs - 1)[:-1]), + xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (P3(pts.flatten()) / J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), @@ -451,7 +457,7 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, pi1_D_i[0], pi1_D_i[1], subs, - np.append(0, np.cumsum(subs - 1)[:-1]), + xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (Rho3(pts.flatten()) / J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), @@ -459,12 +465,14 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, ) rhs0_N_phi = spa.csr_matrix( - (rhs0_N_phi, (pi0_N_i[0], pi0_N_i[1])), shape=(splines.NbaseN, splines.NbaseN) + (rhs0_N_phi, (pi0_N_i[0], pi0_N_i[1])), + shape=(splines.NbaseN, splines.NbaseN), ).toarray() rhs0_N_z = spa.csr_matrix((rhs0_N_z, (pi0_N_i[0], pi0_N_i[1])), shape=(splines.NbaseN, splines.NbaseN)).toarray() rhs1_D_phi = spa.csr_matrix( - (rhs1_D_phi, (pi1_D_i[0], pi1_D_i[1])), shape=(splines.NbaseD, splines.NbaseD) + (rhs1_D_phi, (pi1_D_i[0], pi1_D_i[1])), + shape=(splines.NbaseD, splines.NbaseD), ).toarray() rhs1_D_z = spa.csr_matrix((rhs1_D_z, (pi1_D_i[0], pi1_D_i[1])), shape=(splines.NbaseD, splines.NbaseD)).toarray() @@ -474,142 +482,144 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, rhs1_D_pr = spa.csr_matrix((rhs1_D_pr, (pi1_D_i[0], pi1_D_i[1])), shape=(splines.NbaseD, splines.NbaseD)).toarray() rhs0_N_rho = spa.csr_matrix( - (rhs0_N_rho, (pi0_N_i[0], pi0_N_i[1])), shape=(splines.NbaseN, splines.NbaseN) + (rhs0_N_rho, (pi0_N_i[0], pi0_N_i[1])), + shape=(splines.NbaseN, splines.NbaseN), ).toarray() rhs1_D_rho = spa.csr_matrix( - (rhs1_D_rho, (pi1_D_i[0], pi1_D_i[1])), shape=(splines.NbaseD, splines.NbaseD) + (rhs1_D_rho, (pi1_D_i[0], pi1_D_i[1])), + shape=(splines.NbaseD, splines.NbaseD), ).toarray() - pi0_N_phi = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_phi[1:-1, 1:-1]) - pi0_N_z = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_z[1:-1, 1:-1]) + pi0_N_phi = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_phi[1:-1, 1:-1]) + pi0_N_z = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_z[1:-1, 1:-1]) - pi1_D_phi = np.linalg.inv(proj.D.toarray()).dot(rhs1_D_phi) - pi1_D_z = np.linalg.inv(proj.D.toarray()).dot(rhs1_D_z) + pi1_D_phi = xp.linalg.inv(proj.D.toarray()).dot(rhs1_D_phi) + pi1_D_z = xp.linalg.inv(proj.D.toarray()).dot(rhs1_D_z) - pi0_N_pr = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_pr[1:-1, 1:-1]) - pi1_D_pr = np.linalg.inv(proj.D.toarray()).dot(rhs1_D_pr) + pi0_N_pr = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_pr[1:-1, 1:-1]) + pi1_D_pr = xp.linalg.inv(proj.D.toarray()).dot(rhs1_D_pr) - pi0_N_rho = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_rho[1:-1, 1:-1]) - pi1_D_rho = np.linalg.inv(proj.D.toarray()).dot(rhs1_D_rho) + pi0_N_rho = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_rho[1:-1, 1:-1]) + pi1_D_rho = xp.linalg.inv(proj.D.toarray()).dot(rhs1_D_rho) # ======= matrices in strong induction equation ================ # 11 block - I_11 = -2 * np.pi * m * pi0_N_phi - 2 * np.pi * n * pi0_N_z + I_11 = -2 * xp.pi * m * pi0_N_phi - 2 * xp.pi * n * pi0_N_z # 21 block and 31 block I_21 = -GRAD.dot(pi0_N_phi) I_31 = -GRAD.dot(pi0_N_z) # 22 block and 32 block - I_22 = 2 * np.pi * n * pi1_D_z - I_32 = -2 * np.pi * m * pi1_D_z + I_22 = 2 * xp.pi * n * pi1_D_z + I_32 = -2 * xp.pi * m * pi1_D_z # 23 block and 33 block - I_23 = -2 * np.pi * n * pi1_D_phi - I_33 = 2 * np.pi * m * pi1_D_phi + I_23 = -2 * xp.pi * n * pi1_D_phi + I_33 = 2 * xp.pi * m * pi1_D_phi # total - I_all = np.block( + I_all = xp.block( [ - [I_11, np.zeros((len(u2_r) - 2, len(u2_phi))), np.zeros((len(u2_r) - 2, len(u2_z) - 1))], + [I_11, xp.zeros((len(u2_r) - 2, len(u2_phi))), xp.zeros((len(u2_r) - 2, len(u2_z) - 1))], [I_21, I_22, I_23[:, 1:]], [I_31[1:, :], I_32[1:, :], I_33[1:, 1:]], - ] + ], ) # ======= matrices in strong pressure equation ================ P_1 = -GRAD.dot(pi0_N_pr) - (gamma - 1) * pi1_D_pr.dot(GRAD) - P_2 = -2 * np.pi * m * gamma * pi1_D_pr - P_3 = -2 * np.pi * n * gamma * pi1_D_pr + P_2 = -2 * xp.pi * m * gamma * pi1_D_pr + P_3 = -2 * xp.pi * n * gamma * pi1_D_pr - P_all = np.block([[P_1[1:, :], P_2[1:, :], P_3[1:, 1:]]]) + P_all = xp.block([[P_1[1:, :], P_2[1:, :], P_3[1:, 1:]]]) # ========== matrices in weak momentum balance equation ====== A_1 = 1 / 2 * (pi0_N_rho.T.dot(M2_r) + M2_r.dot(pi0_N_rho)) A_2 = 1 / 2 * (pi1_D_rho.T.dot(M2_phi) + M2_phi.dot(pi1_D_rho)) A_3 = 1 / 2 * (pi1_D_rho.T.dot(M2_z) + M2_z.dot(pi1_D_rho))[:, :] - A_all = np.block( + A_all = xp.block( [ - [A_1, np.zeros((A_1.shape[0], A_2.shape[1])), np.zeros((A_1.shape[0], A_3.shape[1]))], - [np.zeros((A_2.shape[0], A_1.shape[1])), A_2, np.zeros((A_2.shape[0], A_3.shape[1]))], - [np.zeros((A_3.shape[0], A_1.shape[1])), np.zeros((A_3.shape[0], A_2.shape[1])), A_3], - ] + [A_1, xp.zeros((A_1.shape[0], A_2.shape[1])), xp.zeros((A_1.shape[0], A_3.shape[1]))], + [xp.zeros((A_2.shape[0], A_1.shape[1])), A_2, xp.zeros((A_2.shape[0], A_3.shape[1]))], + [xp.zeros((A_3.shape[0], A_1.shape[1])), xp.zeros((A_3.shape[0], A_2.shape[1])), A_3], + ], ) - MB_11 = 2 * np.pi * n * pi0_N_z.T.dot(M2_r) + 2 * np.pi * m * pi0_N_phi.T.dot(M2_r) + MB_11 = 2 * xp.pi * n * pi0_N_z.T.dot(M2_r) + 2 * xp.pi * m * pi0_N_phi.T.dot(M2_r) MB_12 = pi0_N_phi.T.dot(GRAD.T.dot(M2_phi)) - MB_12_eq[1:-1, :] MB_13 = pi0_N_z.T.dot(GRAD.T.dot(M2_z)) - MB_13_eq[1:-1, :] MB_14 = GRAD.T.dot(M3) MB_21 = MB_21_eq[:, 1:-1] - MB_22 = -2 * np.pi * n * pi1_D_z.T.dot(M2_phi) - MB_23 = 2 * np.pi * m * pi1_D_z.T.dot(M2_z) - MB_24 = 2 * np.pi * m * M3 + MB_22 = -2 * xp.pi * n * pi1_D_z.T.dot(M2_phi) + MB_23 = 2 * xp.pi * m * pi1_D_z.T.dot(M2_z) + MB_24 = 2 * xp.pi * m * M3 MB_31 = MB_31_eq[:, 1:-1] - MB_32 = 2 * np.pi * n * pi1_D_phi.T.dot(M2_phi) - MB_33 = -2 * np.pi * m * pi1_D_phi.T.dot(M2_z) - MB_34 = 2 * np.pi * n * M3 + MB_32 = 2 * xp.pi * n * pi1_D_phi.T.dot(M2_phi) + MB_33 = -2 * xp.pi * m * pi1_D_phi.T.dot(M2_z) + MB_34 = 2 * xp.pi * n * M3 - MB_b_all = np.block( - [[MB_11, MB_12, MB_13[:, 1:]], [MB_21, MB_22, MB_23[:, 1:]], [MB_31[1:, :], MB_32[1:, :], MB_33[1:, 1:]]] + MB_b_all = xp.block( + [[MB_11, MB_12, MB_13[:, 1:]], [MB_21, MB_22, MB_23[:, 1:]], [MB_31[1:, :], MB_32[1:, :], MB_33[1:, 1:]]], ) - MB_p_all = np.block([[MB_14[:, 1:]], [MB_24[:, 1:]], [MB_34[1:, 1:]]]) + MB_p_all = xp.block([[MB_14[:, 1:]], [MB_24[:, 1:]], [MB_34[1:, 1:]]]) ## ======= matrices in strong induction equation ================ ## 11 block - # I_11 = np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(-2*np.pi*m*rhs0_N_phi[1:-1, 1:-1] - 2*np.pi*n*rhs0_N_z[1:-1, 1:-1]) + # I_11 = xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(-2*xp.pi*m*rhs0_N_phi[1:-1, 1:-1] - 2*xp.pi*n*rhs0_N_z[1:-1, 1:-1]) # ## 21 block and 31 block - # I_21 = -GRAD[: , 1:-1].dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_phi[1:-1, 1:-1])) - # I_31 = -GRAD[1:, 1:-1].dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_z[1:-1, 1:-1])) + # I_21 = -GRAD[: , 1:-1].dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_phi[1:-1, 1:-1])) + # I_31 = -GRAD[1:, 1:-1].dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_z[1:-1, 1:-1])) # ## 22 block and 32 block - # I_22 = 2*np.pi*n*np.linalg.inv(proj.D.toarray()[ :, :]).dot(rhs1_D_z[ :, :]) - # I_32 = -2*np.pi*m*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_z[1:, :]) + # I_22 = 2*xp.pi*n*xp.linalg.inv(proj.D.toarray()[ :, :]).dot(rhs1_D_z[ :, :]) + # I_32 = -2*xp.pi*m*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_z[1:, :]) # ## 23 block and 33 block - # I_23 = -2*np.pi*n*np.linalg.inv(proj.D.toarray()[ :, :]).dot(rhs1_D_phi[ :, 1:]) - # I_33 = 2*np.pi*m*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_phi[1:, 1:]) + # I_23 = -2*xp.pi*n*xp.linalg.inv(proj.D.toarray()[ :, :]).dot(rhs1_D_phi[ :, 1:]) + # I_33 = 2*xp.pi*m*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_phi[1:, 1:]) # # ## ======= matrices in strong pressure equation ================ - # P_1 = -GRAD[1:, 1:-1].dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_pr[1:-1, 1:-1])) - (gamma - 1)*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, :].dot(GRAD[:, 1:-1])) - # P_2 = -2*np.pi*m*gamma*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, :]) - # P_3 = -2*np.pi*n*gamma*np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, 1:]) + # P_1 = -GRAD[1:, 1:-1].dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_pr[1:-1, 1:-1])) - (gamma - 1)*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, :].dot(GRAD[:, 1:-1])) + # P_2 = -2*xp.pi*m*gamma*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, :]) + # P_3 = -2*xp.pi*n*gamma*xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_pr[1:, 1:]) # # ## ========== matrices in weak momentum balance equation ====== - # rhs0_N_rho = np.empty(pi0_N_i[0].size, dtype=float) + # rhs0_N_rho = xp.empty(pi0_N_i[0].size, dtype=float) # ker.rhs0_1d(pi0_N_i[0], pi0_N_i[1], basis_int_N, splines.evaluate_D(x_int, rho3)/J(x_int), rhs0_N_rho) # # - # rhs1_D_rho = np.empty(pi1_D_i[0].size, dtype=float) - # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, np.append(0, np.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), rho3)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_rho) + # rhs1_D_rho = xp.empty(pi1_D_i[0].size, dtype=float) + # ker.rhs1_1d(pi1_D_i[0], pi1_D_i[1], subs, xp.append(0, xp.cumsum(subs - 1)[:-1]), wts, basis_his_D, (splines.evaluate_D(pts.flatten(), rho3)/J(pts.flatten())).reshape(pts.shape[0], pts.shape[1]), rhs1_D_rho) # # # - # A_1 = 1/2*(rhs0_N_rho[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + M2_r[1:-1, 1:-1].dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_rho[1:-1, 1:-1]))) - # A_2 = 1/2*(rhs1_D_rho.T.dot(np.linalg.inv(proj.D.toarray()[:, :]).T.dot(M2_phi)) + M2_phi.dot(np.linalg.inv(proj.D.toarray()[:, :]).dot(rhs1_D_rho))) - # A_3 = 1/2*(rhs1_D_rho[1:, 1:].T.dot(np.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) + M2_z[1:, 1:].dot(np.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_rho[1:, 1:]))) + # A_1 = 1/2*(rhs0_N_rho[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + M2_r[1:-1, 1:-1].dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).dot(rhs0_N_rho[1:-1, 1:-1]))) + # A_2 = 1/2*(rhs1_D_rho.T.dot(xp.linalg.inv(proj.D.toarray()[:, :]).T.dot(M2_phi)) + M2_phi.dot(xp.linalg.inv(proj.D.toarray()[:, :]).dot(rhs1_D_rho))) + # A_3 = 1/2*(rhs1_D_rho[1:, 1:].T.dot(xp.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) + M2_z[1:, 1:].dot(xp.linalg.inv(proj.D.toarray()[1:, 1:]).dot(rhs1_D_rho[1:, 1:]))) # # - # MB_11 = 2*np.pi*n*rhs0_N_z[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + 2*np.pi*m*rhs0_N_phi[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + # MB_11 = 2*xp.pi*n*rhs0_N_z[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) + 2*xp.pi*m*rhs0_N_phi[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(M2_r[1:-1, 1:-1])) # - # MB_12 = rhs0_N_phi[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(GRAD[:, 1:-1].T.dot(M2_phi))) - # MB_13 = rhs0_N_z[1:-1, 1:-1].T.dot(np.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(GRAD[1:, 1:-1].T.dot(M2_z[1:, 1:]))) + # MB_12 = rhs0_N_phi[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(GRAD[:, 1:-1].T.dot(M2_phi))) + # MB_13 = rhs0_N_z[1:-1, 1:-1].T.dot(xp.linalg.inv(proj.N.toarray()[1:-1, 1:-1]).T.dot(GRAD[1:, 1:-1].T.dot(M2_z[1:, 1:]))) # # MB_14 = GRAD[1:, 1:-1].T.dot(M3[1:, 1:]) # # - # MB_22 = -2*np.pi*n*rhs1_D_z.T.dot(np.linalg.inv(proj.D.toarray()).T.dot(M2_phi)) - # MB_23 = 2*np.pi*m*rhs1_D_z[1:, :].T.dot(np.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) - # MB_24 = 2*np.pi*m*M3[ :, 1:] + # MB_22 = -2*xp.pi*n*rhs1_D_z.T.dot(xp.linalg.inv(proj.D.toarray()).T.dot(M2_phi)) + # MB_23 = 2*xp.pi*m*rhs1_D_z[1:, :].T.dot(xp.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) + # MB_24 = 2*xp.pi*m*M3[ :, 1:] # - # MB_32 = 2*np.pi*n*rhs1_D_phi[:, 1:].T.dot(np.linalg.inv(proj.D.toarray()).T.dot(M2_phi)) - # MB_33 = -2*np.pi*m*rhs1_D_phi[1:, 1:].T.dot(np.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) - # MB_34 = 2*np.pi*n*M3[1:, 1:] + # MB_32 = 2*xp.pi*n*rhs1_D_phi[:, 1:].T.dot(xp.linalg.inv(proj.D.toarray()).T.dot(M2_phi)) + # MB_33 = -2*xp.pi*m*rhs1_D_phi[1:, 1:].T.dot(xp.linalg.inv(proj.D.toarray()[1:, 1:]).T.dot(M2_z[1:, 1:])) + # MB_34 = 2*xp.pi*n*M3[1:, 1:] # # # ==== matrices in eigenvalue problem ======== @@ -625,17 +635,17 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, W_32 = MB_32.dot(I_22) + MB_33.dot(I_32) + MB_34.dot(P_2) W_33 = MB_32.dot(I_23) + MB_33.dot(I_33) + MB_34.dot(P_3) - # W = np.block([[W_11, W_12, W_13[:, 1:]], [W_21, W_22, W_23[:, 1:]], [W_31[1:, :], W_32[1:, :], W_33[1:, 1:]]]) - W = np.block([[W_11, W_12, W_13[:, :]], [W_21, W_22, W_23[:, :]], [W_31[:, :], W_32[:, :], W_33[:, :]]]) + # W = xp.block([[W_11, W_12, W_13[:, 1:]], [W_21, W_22, W_23[:, 1:]], [W_31[1:, :], W_32[1:, :], W_33[1:, 1:]]]) + W = xp.block([[W_11, W_12, W_13[:, :]], [W_21, W_22, W_23[:, :]], [W_31[:, :], W_32[:, :], W_33[:, :]]]) - # print(np.allclose(K, K.T)) - # print(np.allclose(W, W.T)) + # print(xp.allclose(K, K.T)) + # print(xp.allclose(W, W.T)) # solve eigenvalue problem omega**2*K*xi = W*xi - MAT = np.linalg.inv(-A_all).dot(W) + MAT = xp.linalg.inv(-A_all).dot(W) - omega2, XYZ_eig = np.linalg.eig(MAT) - # omega2, XYZ_eig = np.linalg.eig(np.linalg.inv(-A_all).dot(MB_b_all.dot(I_all) + MB_p_all.dot(P_all))) + omega2, XYZ_eig = xp.linalg.eig(MAT) + # omega2, XYZ_eig = xp.linalg.eig(xp.linalg.inv(-A_all).dot(MB_b_all.dot(I_all) + MB_p_all.dot(P_all))) # extract components X_eig = XYZ_eig[: (splines.NbaseN - 2), :] @@ -643,71 +653,71 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, Z_eig = XYZ_eig[(splines.NbaseN - 2 + splines.NbaseD) :, :] # add boundary conditions X(0) = X(1) = 0 - X_eig = np.vstack((np.zeros(X_eig.shape[1], dtype=float), X_eig, np.zeros(X_eig.shape[1], dtype=float))) + X_eig = xp.vstack((xp.zeros(X_eig.shape[1], dtype=float), X_eig, xp.zeros(X_eig.shape[1], dtype=float))) # add boundary condition Z(0) = 0 - Z_eig = np.vstack((np.zeros(Z_eig.shape[1], dtype=float), Z_eig)) + Z_eig = xp.vstack((xp.zeros(Z_eig.shape[1], dtype=float), Z_eig)) return omega2, X_eig, Y_eig, Z_eig ## ========== matrices in initial value problem === - LHS = np.block( + LHS = xp.block( [ - [A_all, np.zeros((A_all.shape[0], A_all.shape[1])), np.zeros((A_all.shape[0], len(p3) - 1))], + [A_all, xp.zeros((A_all.shape[0], A_all.shape[1])), xp.zeros((A_all.shape[0], len(p3) - 1))], [ - np.zeros((A_all.shape[0], A_all.shape[1])), - np.identity(A_all.shape[0]), - np.zeros((A_all.shape[0], len(p3) - 1)), + xp.zeros((A_all.shape[0], A_all.shape[1])), + xp.identity(A_all.shape[0]), + xp.zeros((A_all.shape[0], len(p3) - 1)), ], [ - np.zeros((len(p3) - 1, A_all.shape[1])), - np.zeros((len(p3) - 1, A_all.shape[1])), - np.identity(len(p3) - 1), + xp.zeros((len(p3) - 1, A_all.shape[1])), + xp.zeros((len(p3) - 1, A_all.shape[1])), + xp.identity(len(p3) - 1), ], - ] + ], ) - RHS = np.block( + RHS = xp.block( [ - [np.zeros((MB_b_all.shape[0], I_all.shape[1])), MB_b_all, MB_p_all], - [I_all, np.zeros((I_all.shape[0], MB_b_all.shape[1])), np.zeros((I_all.shape[0], MB_p_all.shape[1]))], - [P_all, np.zeros((P_all.shape[0], MB_b_all.shape[1])), np.zeros((P_all.shape[0], MB_p_all.shape[1]))], - ] + [xp.zeros((MB_b_all.shape[0], I_all.shape[1])), MB_b_all, MB_p_all], + [I_all, xp.zeros((I_all.shape[0], MB_b_all.shape[1])), xp.zeros((I_all.shape[0], MB_p_all.shape[1]))], + [P_all, xp.zeros((P_all.shape[0], MB_b_all.shape[1])), xp.zeros((P_all.shape[0], MB_p_all.shape[1]))], + ], ) dt = 0.05 T = 200.0 Nt = int(T / dt) - UPDATE = np.linalg.inv(LHS - dt / 2 * RHS).dot(LHS + dt / 2 * RHS) - ##UPDATE = np.linalg.inv(LHS).dot(LHS + dt*RHS) + UPDATE = xp.linalg.inv(LHS - dt / 2 * RHS).dot(LHS + dt / 2 * RHS) + ##UPDATE = xp.linalg.inv(LHS).dot(LHS + dt*RHS) # - # lambdas, eig_vecs = np.linalg.eig(UPDATE) + # lambdas, eig_vecs = xp.linalg.eig(UPDATE) # return lambdas # # return lambdas # - u2_r_all = np.zeros((Nt + 1, len(u2_r)), dtype=float) - u2_phi_all = np.zeros((Nt + 1, len(u2_phi)), dtype=float) - u2_z_all = np.zeros((Nt + 1, len(u2_z)), dtype=float) + u2_r_all = xp.zeros((Nt + 1, len(u2_r)), dtype=float) + u2_phi_all = xp.zeros((Nt + 1, len(u2_phi)), dtype=float) + u2_z_all = xp.zeros((Nt + 1, len(u2_z)), dtype=float) - b2_r_all = np.zeros((Nt + 1, len(b2_r)), dtype=float) - b2_phi_all = np.zeros((Nt + 1, len(b2_phi)), dtype=float) - b2_z_all = np.zeros((Nt + 1, len(b2_z)), dtype=float) + b2_r_all = xp.zeros((Nt + 1, len(b2_r)), dtype=float) + b2_phi_all = xp.zeros((Nt + 1, len(b2_phi)), dtype=float) + b2_z_all = xp.zeros((Nt + 1, len(b2_z)), dtype=float) - p3_all = np.zeros((Nt + 1, len(p3)), dtype=float) + p3_all = xp.zeros((Nt + 1, len(p3)), dtype=float) # initialization # u2_r_all[0, :] = u2_r # u2_phi_all[0, :] = u2_phi - u2_r_all[0, 1:-1] = np.random.rand(len(u2_r) - 2) - p3_all[0, 1:] = np.random.rand(len(p3) - 1) + u2_r_all[0, 1:-1] = xp.random.rand(len(u2_r) - 2) + p3_all[0, 1:] = xp.random.rand(len(p3) - 1) # time integration for n in range(Nt): - old = np.concatenate( + old = xp.concatenate( ( u2_r_all[n, 1:-1], u2_phi_all[n, :], @@ -716,23 +726,24 @@ def solve_ev_problem_FEEC(Rho, B_phi, dB_phi, B_z, dB_z, P, gamma, a, R0, n, m, b2_phi_all[n, :], b2_z_all[n, 1:], p3_all[n, 1:], - ) + ), ) new = UPDATE.dot(old) # extract components - unew, bnew, pnew = np.split( - new, [len(u2_r) - 2 + len(u2_phi) + len(u2_z) - 1, 2 * (len(u2_r) - 2 + len(u2_phi) + len(u2_z) - 1)] + unew, bnew, pnew = xp.split( + new, + [len(u2_r) - 2 + len(u2_phi) + len(u2_z) - 1, 2 * (len(u2_r) - 2 + len(u2_phi) + len(u2_z) - 1)], ) - u2_r_all[n + 1, :] = np.array([0.0] + list(unew[: (splines.NbaseN - 2)]) + [0.0]) + u2_r_all[n + 1, :] = xp.array([0.0] + list(unew[: (splines.NbaseN - 2)]) + [0.0]) u2_phi_all[n + 1, :] = unew[(splines.NbaseN - 2) : (splines.NbaseN - 2 + splines.NbaseD)] - u2_z_all[n + 1, :] = np.array([0.0] + list(unew[(splines.NbaseN - 2 + splines.NbaseD) :])) + u2_z_all[n + 1, :] = xp.array([0.0] + list(unew[(splines.NbaseN - 2 + splines.NbaseD) :])) - b2_r_all[n + 1, :] = np.array([0.0] + list(bnew[: (splines.NbaseN - 2)]) + [0.0]) + b2_r_all[n + 1, :] = xp.array([0.0] + list(bnew[: (splines.NbaseN - 2)]) + [0.0]) b2_phi_all[n + 1, :] = bnew[(splines.NbaseN - 2) : (splines.NbaseN - 2 + splines.NbaseD)] - b2_z_all[n + 1, :] = np.array([0.0] + list(bnew[(splines.NbaseN - 2 + splines.NbaseD) :])) + b2_z_all[n + 1, :] = xp.array([0.0] + list(bnew[(splines.NbaseN - 2 + splines.NbaseD) :])) - p3_all[n + 1, :] = np.array([0.0] + list(pnew)) + p3_all[n + 1, :] = xp.array([0.0] + list(pnew)) return u2_r_all, u2_phi_all, u2_z_all, b2_r_all, b2_phi_all, b2_z_all, p3_all, omega2 diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/control_variate.py index ca5134edc..a4b95eb06 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/control_variate.py @@ -6,7 +6,7 @@ Class for control variates in delta-f method for current coupling scheme. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.feec.basics.kernels_3d as ker @@ -40,7 +40,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): kind_fun_eq = [11, 12, 13, 14] # ========= evaluation of DF^(-1) * jh_eq_phys * |det(DF)| at quadrature points ========= - self.mat_jh1 = np.empty( + self.mat_jh1 = xp.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -51,7 +51,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.mat_jh2 = np.empty( + self.mat_jh2 = xp.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -62,7 +62,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.mat_jh3 = np.empty( + self.mat_jh3 = xp.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -133,7 +133,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ) # ========= evaluation of nh_eq_phys * |det(DF)| at quadrature points =================== - self.mat_nh = np.empty( + self.mat_nh = xp.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -166,7 +166,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ) # =========== 2-form magnetic field at quadrature points ================================= - self.B2_1 = np.empty( + self.B2_1 = xp.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -177,7 +177,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.B2_2 = np.empty( + self.B2_2 = xp.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -188,7 +188,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.B2_3 = np.empty( + self.B2_3 = xp.empty( ( self.space.Nel[0], self.space.n_quad[0], @@ -202,7 +202,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): # ================== correction matrices in step 1 ======================== if self.basis_u == 0: - self.M12 = np.empty( + self.M12 = xp.empty( ( self.space.NbaseN[0], self.space.NbaseN[1], @@ -213,7 +213,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.M13 = np.empty( + self.M13 = xp.empty( ( self.space.NbaseN[0], self.space.NbaseN[1], @@ -224,7 +224,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.M23 = np.empty( + self.M23 = xp.empty( ( self.space.NbaseN[0], self.space.NbaseN[1], @@ -237,7 +237,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ) elif self.basis_u == 2: - self.M12 = np.empty( + self.M12 = xp.empty( ( self.space.NbaseN[0], self.space.NbaseD[1], @@ -248,7 +248,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.M13 = np.empty( + self.M13 = xp.empty( ( self.space.NbaseN[0], self.space.NbaseD[1], @@ -259,7 +259,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u): ), dtype=float, ) - self.M23 = np.empty( + self.M23 = xp.empty( ( self.space.NbaseD[0], self.space.NbaseN[1], @@ -273,14 +273,14 @@ def __init__(self, tensor_space_FEM, domain, basis_u): # ==================== correction vectors in step 3 ======================= if self.basis_u == 0: - self.F1 = np.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) - self.F2 = np.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) - self.F3 = np.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) + self.F1 = xp.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) + self.F2 = xp.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) + self.F3 = xp.empty((self.space.NbaseN[0], self.space.NbaseN[1], self.space.NbaseN[2]), dtype=float) elif self.basis_u == 2: - self.F1 = np.empty((self.space.NbaseN[0], self.space.NbaseD[1], self.space.NbaseD[2]), dtype=float) - self.F2 = np.empty((self.space.NbaseD[0], self.space.NbaseN[1], self.space.NbaseD[2]), dtype=float) - self.F3 = np.empty((self.space.NbaseD[0], self.space.NbaseD[1], self.space.NbaseN[2]), dtype=float) + self.F1 = xp.empty((self.space.NbaseN[0], self.space.NbaseD[1], self.space.NbaseD[2]), dtype=float) + self.F2 = xp.empty((self.space.NbaseD[0], self.space.NbaseN[1], self.space.NbaseD[2]), dtype=float) + self.F3 = xp.empty((self.space.NbaseD[0], self.space.NbaseD[1], self.space.NbaseN[2]), dtype=float) # ===== inner product in V0^3 resp. V2 of (B x jh_eq) - term ========== def inner_prod_jh_eq(self, b1, b2, b3): @@ -511,7 +511,7 @@ def inner_prod_jh_eq(self, b1, b2, b3): self.B2_1 * self.mat_jh2 - self.B2_2 * self.mat_jh1, ) - return np.concatenate((self.F1.flatten(), self.F2.flatten(), self.F3.flatten())) + return xp.concatenate((self.F1.flatten(), self.F2.flatten(), self.F3.flatten())) # ===== mass matrix in V0^3 resp. V2 of -(rhoh_eq * (B x U)) - term ======= def mass_nh_eq(self, b1, b2, b3): diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_control_variate.py index 562df0691..39156f985 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_control_variate.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.feec.basics.kernels_3d as ker @@ -204,7 +204,7 @@ def bv_right( ) # ========================= C.T =========================== return tensor_space_FEM.C.T.dot( - np.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())) + xp.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())), ) diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_kernels_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_kernels_control_variate.py index 9220cdc69..7f15931ec 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_kernels_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fB_massless_kernels_control_variate.py @@ -584,13 +584,49 @@ def vv( bd3[:] = b3[pd3, :pn3] * d3[:] vel[0] = eva.evaluation_kernel( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, NbaseD[0], NbaseN[1], NbaseN[2], bb1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + NbaseD[0], + NbaseN[1], + NbaseN[2], + bb1, ) vel[1] = eva.evaluation_kernel( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, NbaseN[0], NbaseD[1], NbaseN[2], bb2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + NbaseN[0], + NbaseD[1], + NbaseN[2], + bb2, ) vel[2] = eva.evaluation_kernel( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, NbaseN[0], NbaseN[1], NbaseD[2], bb3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + NbaseN[0], + NbaseN[1], + NbaseD[2], + bb3, ) # ======= here we use the linear hat function =========== ie1 = int(eta1 * Nel[0]) diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_control_variate.py index f0279fbcf..5e9c04eb0 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_control_variate.py @@ -1,6 +1,6 @@ +import cunumpy as xp import hylife.utilitis_FEEC.basics.kernels_3d as ker import hylife.utilitis_FEEC.control_variates.fnB_massless_kernels_control_variate as ker_cv -import numpy as np import scipy.sparse as spa @@ -248,7 +248,7 @@ def bv_right( ) # ========================= C.T =========================== return tensor_space_FEM.C.T.dot( - np.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())) + xp.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())), ) @@ -429,7 +429,7 @@ def uv_right( ) # ========================= C.T =========================== temp_final = temp_final_0.flatten() + tensor_space_FEM.G.T.dot( - np.concatenate((temp_final_1.flatten(), temp_final_2.flatten(), temp_final_3.flatten())) + xp.concatenate((temp_final_1.flatten(), temp_final_2.flatten(), temp_final_3.flatten())), ) return temp_final diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_kernels_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_kernels_control_variate.py index f3b6fca0e..965a7af33 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_kernels_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/fnB_massless_kernels_control_variate.py @@ -212,17 +212,65 @@ def vv( bd3[:] = b3[pd3, :pn3] * d3[:] vel[0] = eva.evaluation_kernel( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, NbaseD[0], NbaseN[1], NbaseN[2], bb1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + NbaseD[0], + NbaseN[1], + NbaseN[2], + bb1, ) vel[1] = eva.evaluation_kernel( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, NbaseN[0], NbaseD[1], NbaseN[2], bb2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + NbaseN[0], + NbaseD[1], + NbaseN[2], + bb2, ) vel[2] = eva.evaluation_kernel( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, NbaseN[0], NbaseN[1], NbaseD[2], bb3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + NbaseN[0], + NbaseN[1], + NbaseD[2], + bb3, ) tt = eva.evaluation_kernel( - pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, NbaseN[0], NbaseN[1], NbaseN[2], n + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span1, + span2, + span3, + NbaseN[0], + NbaseN[1], + NbaseN[2], + n, ) if abs(tt) > tol: U_value = 1.0 / tt diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_control_variate.py index 09deb07f1..3459ff7b2 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_control_variate.py @@ -1,6 +1,6 @@ +import cunumpy as xp import hylife.utilitis_FEEC.basics.kernels_3d as ker import hylife.utilitis_FEEC.control_variates.massless_kernels_control_variate as ker_cv -import numpy as np import scipy.sparse as spa @@ -247,7 +247,7 @@ def bv_right( ) # ========================= C.T =========================== return tensor_space_FEM.C.T.dot( - np.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())) + xp.concatenate((temp_twoform1.flatten(), temp_twoform2.flatten(), temp_twoform3.flatten())), ) @@ -430,7 +430,7 @@ def uv_right( ) # ========================= C.T =========================== temp_final = temp_final_0.flatten() + tensor_space_FEM.G.T.dot( - np.concatenate((temp_final_1.flatten(), temp_final_2.flatten(), temp_final_3.flatten())) + xp.concatenate((temp_final_1.flatten(), temp_final_2.flatten(), temp_final_3.flatten())), ) return temp_final diff --git a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_kernels_control_variate.py b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_kernels_control_variate.py index c56b67711..bfff64b2a 100644 --- a/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_kernels_control_variate.py +++ b/src/struphy/eigenvalue_solvers/legacy/control_variates/kinetic_extended/massless_kernels_control_variate.py @@ -27,6 +27,7 @@ def uvpre( bn3: "float[:,:,:,:]", ): from numpy import empty, exp, zeros + # -- removed omp: #$ omp parallel # -- removed omp: #$ omp do private (ie1, ie2, ie3, q1, q2, q3, il1, il2, il3, value) @@ -195,34 +196,84 @@ def uvright( for q2 in range(nq2): for q3 in range(nq3): dft[0, 0] = DFI_11[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[0, 0]) dft[0, 1] = DFI_21[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[0, 1]) dft[0, 2] = DFI_31[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[0, 2]) dft[1, 0] = DFI_12[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[1, 0]) dft[1, 1] = DFI_22[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[1, 1]) dft[1, 2] = DFI_32[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[1, 2]) dft[2, 0] = DFI_13[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[2, 0]) dft[2, 1] = DFI_23[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[2, 1]) dft[2, 2] = DFI_33[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.df_inv(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map, components[2, 2]) detdet = df_det[ - ie1, ie2, ie3, q1, q2, q3 + ie1, + ie2, + ie3, + q1, + q2, + q3, ] # mappings_analytical.det_df(pts1[ie1, q1], pts2[ie2,q2], pts3[ie3,q3], kind_map, params_map) Jeq[0] = Jeqx[ie1, ie2, ie3, q1, q2, q3] Jeq[1] = Jeqy[ie1, ie2, ie3, q1, q2, q3] @@ -705,19 +756,67 @@ def vv( bd3[:] = b3[pd3, :pn3] * d3[:] vel[0] = eva.evaluation_kernel( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, NbaseD[0], NbaseN[1], NbaseN[2], bb1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + NbaseD[0], + NbaseN[1], + NbaseN[2], + bb1, ) vel[1] = eva.evaluation_kernel( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, NbaseN[0], NbaseD[1], NbaseN[2], bb2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + NbaseN[0], + NbaseD[1], + NbaseN[2], + bb2, ) vel[2] = eva.evaluation_kernel( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, NbaseN[0], NbaseN[1], NbaseD[2], bb3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + NbaseN[0], + NbaseN[1], + NbaseD[2], + bb3, ) U_value = exp( -eva.evaluation_kernel( - pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, NbaseN[0], NbaseN[1], NbaseN[2], u - ) + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span1, + span2, + span3, + NbaseN[0], + NbaseN[1], + NbaseN[2], + u, + ), ) # ========= mapping evaluation ============= diff --git a/src/struphy/eigenvalue_solvers/legacy/emw_operators.py b/src/struphy/eigenvalue_solvers/legacy/emw_operators.py index 171ad21f8..9e8c95b3f 100755 --- a/src/struphy/eigenvalue_solvers/legacy/emw_operators.py +++ b/src/struphy/eigenvalue_solvers/legacy/emw_operators.py @@ -6,7 +6,7 @@ Class for 2D/3D linear MHD projection operators. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_3d as ker @@ -134,7 +134,7 @@ def __assemble_M1_cross(self, weight): Ni = self.SPACES.Nbase_1form[a] Nj = self.SPACES.Nbase_1form[b] - M[a][b] = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M[a][b] = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) # evaluate metric tensor at quadrature points if a == 1 and b == 2: @@ -185,9 +185,9 @@ def __assemble_M1_cross(self, weight): mat_w, ) # convert to sparse matrix - indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -198,12 +198,14 @@ def __assemble_M1_cross(self, weight): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M[a][b] = spa.csr_matrix( - (M[a][b].flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (M[a][b].flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M[a][b].eliminate_zeros() M = spa.bmat( - [[M[0][0], M[0][1], M[0][2]], [M[1][0], M[1][1], M[1][2]], [M[2][0], M[2][1], M[2][2]]], format="csr" + [[M[0][0], M[0][1], M[0][2]], [M[1][0], M[1][1], M[1][2]], [M[2][0], M[2][1], M[2][2]]], + format="csr", ) self.R1_mat = -self.SPACES.E1_0.dot(M.dot(self.SPACES.E1_0.T)).tocsr() diff --git a/src/struphy/eigenvalue_solvers/legacy/inner_products_1d.py b/src/struphy/eigenvalue_solvers/legacy/inner_products_1d.py index c894496b5..b4f019995 100644 --- a/src/struphy/eigenvalue_solvers/legacy/inner_products_1d.py +++ b/src/struphy/eigenvalue_solvers/legacy/inner_products_1d.py @@ -6,7 +6,7 @@ Modules to compute inner products in 1d. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa @@ -39,7 +39,7 @@ def inner_prod_V0(spline_space, fun, mapping=None): # evaluation of mapping at quadrature points if mapping == None: - mat_map = np.ones(pts.shape, dtype=float) + mat_map = xp.ones(pts.shape, dtype=float) else: mat_map = mapping(pts.flatten()).reshape(pts.shape) @@ -47,7 +47,7 @@ def inner_prod_V0(spline_space, fun, mapping=None): mat_f = fun(pts.flatten()).reshape(pts.shape) # assembly - F = np.zeros(NbaseN, dtype=float) + F = xp.zeros(NbaseN, dtype=float) for ie in range(Nel): for il in range(p + 1): @@ -90,7 +90,7 @@ def inner_prod_V1(spline_space, fun, mapping=None): # evaluation of mapping at quadrature points if mapping == None: - mat_map = np.ones(pts.shape, dtype=float) + mat_map = xp.ones(pts.shape, dtype=float) else: mat_map = 1 / mapping(pts.flatten()).reshape(pts.shape) @@ -98,7 +98,7 @@ def inner_prod_V1(spline_space, fun, mapping=None): mat_f = fun(pts.flatten()).reshape(pts.shape) # assembly - F = np.zeros(NbaseD, dtype=float) + F = xp.zeros(NbaseD, dtype=float) for ie in range(Nel): for il in range(p): diff --git a/src/struphy/eigenvalue_solvers/legacy/inner_products_2d.py b/src/struphy/eigenvalue_solvers/legacy/inner_products_2d.py index ab8ce9f04..05df4725f 100644 --- a/src/struphy/eigenvalue_solvers/legacy/inner_products_2d.py +++ b/src/struphy/eigenvalue_solvers/legacy/inner_products_2d.py @@ -6,7 +6,7 @@ Modules to compute inner products with given functions in 2D. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_2d as ker @@ -25,7 +25,7 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : callable or np.ndarray + fun : callable or xp.ndarray the 0-form with which the inner products shall be computed (either callable or 2D array with values at quadrature points) """ @@ -46,10 +46,10 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): det_df = det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # evaluation of given 0-form at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size), dtype=float) if callable(fun): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") mat_f[:, :] = fun(quad_mesh[0], quad_mesh[1], 0.0) else: mat_f[:, :] = fun @@ -57,7 +57,7 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): # assembly Ni = tensor_space_FEM.Nbase_0form - F = np.zeros((Ni[0], Ni[1]), dtype=float) + F = xp.zeros((Ni[0], Ni[1]), dtype=float) mat_f = mat_f.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) @@ -94,7 +94,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : list of callables or np.ndarrays + fun : list of callables or xp.ndarrays the 1-form components with which the inner products shall be computed (either list of 3 callables or 2D arrays with values at quadrature points) """ @@ -127,10 +127,10 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): g_inv = domain.metric_inv(pts[0].flatten(), pts[1].flatten(), 0.0) # 1-form components at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size), dtype=float) if callable(fun[0]): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") # components of global inner product F = [0, 0, 0] @@ -138,7 +138,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): # assembly for a in range(3): Ni = tensor_space_FEM.Nbase_1form[a] - F[a] = np.zeros((Ni[0], Ni[1]), dtype=float) + F[a] = xp.zeros((Ni[0], Ni[1]), dtype=float) mat_f[:, :] = 0.0 @@ -170,7 +170,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): mat_f * det_df, ) - F1 = tensor_space_FEM.E1_pol_0.dot(np.concatenate((F[0].flatten(), F[1].flatten()))) + F1 = tensor_space_FEM.E1_pol_0.dot(xp.concatenate((F[0].flatten(), F[1].flatten()))) F2 = tensor_space_FEM.E0_pol_0.dot(F[2].flatten()) return F1, F2 @@ -187,7 +187,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : list of callables or np.ndarrays + fun : list of callables or xp.ndarrays the 2-form components with which the inner products shall be computed (either list of 3 callables or 2D arrays with values at quadrature points) """ @@ -220,10 +220,10 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): g = domain.metric(pts[0].flatten(), pts[1].flatten(), 0.0) # 2-form components at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size), dtype=float) if callable(fun[0]): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") # components of global inner product F = [0, 0, 0] @@ -231,7 +231,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): # assembly for a in range(3): Ni = tensor_space_FEM.Nbase_2form[a] - F[a] = np.zeros((Ni[0], Ni[1]), dtype=float) + F[a] = xp.zeros((Ni[0], Ni[1]), dtype=float) mat_f[:, :] = 0.0 @@ -263,7 +263,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): mat_f / det_df, ) - F1 = tensor_space_FEM.E2_pol_0.dot(np.concatenate((F[0].flatten(), F[1].flatten()))) + F1 = tensor_space_FEM.E2_pol_0.dot(xp.concatenate((F[0].flatten(), F[1].flatten()))) F2 = tensor_space_FEM.E3_pol_0.dot(F[2].flatten()) return F1, F2 @@ -280,7 +280,7 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : callable or np.ndarray + fun : callable or xp.ndarray the 3-form component with which the inner products shall be computed (either callable or 2D array with values at quadrature points) """ @@ -301,10 +301,10 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): det_df = det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # evaluation of given 3-form at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size), dtype=float) if callable(fun): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") mat_f[:, :] = fun(quad_mesh[0], quad_mesh[1], 0.0) else: mat_f[:, :] = fun @@ -312,7 +312,7 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): # assembly Ni = tensor_space_FEM.Nbase_3form - F = np.zeros((Ni[0], Ni[1]), dtype=float) + F = xp.zeros((Ni[0], Ni[1]), dtype=float) mat_f = mat_f.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) diff --git a/src/struphy/eigenvalue_solvers/legacy/inner_products_3d.py b/src/struphy/eigenvalue_solvers/legacy/inner_products_3d.py index 29394e7e8..20d95c05c 100644 --- a/src/struphy/eigenvalue_solvers/legacy/inner_products_3d.py +++ b/src/struphy/eigenvalue_solvers/legacy/inner_products_3d.py @@ -6,7 +6,7 @@ Modules to compute inner products with given functions in 3D. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_3d as ker @@ -25,7 +25,7 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : callable or np.ndarray + fun : callable or xp.ndarray the 0-form with which the inner products shall be computed (either callable or 3D array with values at quadrature points) """ @@ -46,10 +46,10 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): det_df = det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # evaluation of given 0-form at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun(quad_mesh[0], quad_mesh[1], quad_mesh[2]) else: mat_f[:, :, :] = fun @@ -57,7 +57,7 @@ def inner_prod_V0(tensor_space_FEM, domain, fun): # assembly Ni = tensor_space.Nbase_0form - F = np.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) + F = xp.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) mat_f = mat_f.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) @@ -101,7 +101,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : list of callables or np.ndarrays + fun : list of callables or xp.ndarrays the 1-form components with which the inner products shall be computed (either list of 3 callables or 3D arrays with values at quadrature points) """ @@ -134,10 +134,10 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): g_inv = domain.metric_inv(pts[0].flatten(), pts[1].flatten(), pts[2].flatten()) # 1-form components at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun[0]): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") # components of global inner product F = [0, 0, 0] @@ -146,7 +146,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): for a in range(3): Ni = tensor_space_FEM.Nbase_1form[a] - F[a] = np.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) + F[a] = xp.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) mat_f[:, :, :] = 0.0 @@ -185,7 +185,7 @@ def inner_prod_V1(tensor_space_FEM, domain, fun): mat_f * det_df, ) - return tensor_space_FEM.E1_0.dot(np.concatenate((F[0].flatten(), F[1].flatten(), F[2].flatten()))) + return tensor_space_FEM.E1_0.dot(xp.concatenate((F[0].flatten(), F[1].flatten(), F[2].flatten()))) # ================ inner product in V2 =========================== @@ -199,7 +199,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : list of callables or np.ndarrays + fun : list of callables or xp.ndarrays the 2-form components with which the inner products shall be computed (either list of 3 callables or 3D arrays with values at quadrature points) """ @@ -232,10 +232,10 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): g = domain.metric(pts[0].flatten(), pts[1].flatten(), pts[2].flatten()) # 2-form components at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun[0]): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") # components of global inner product F = [0, 0, 0] @@ -244,7 +244,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): for a in range(3): Ni = tensor_space_FEM.Nbase_2form[a] - F[a] = np.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) + F[a] = xp.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) mat_f[:, :, :] = 0.0 @@ -283,7 +283,7 @@ def inner_prod_V2(tensor_space_FEM, domain, fun): mat_f / det_df, ) - return tensor_space_FEM.E2_0.dot(np.concatenate((F[0].flatten(), F[1].flatten(), F[2].flatten()))) + return tensor_space_FEM.E2_0.dot(xp.concatenate((F[0].flatten(), F[1].flatten(), F[2].flatten()))) # ================ inner product in V3 =========================== @@ -297,7 +297,7 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): domain : domain domain object defining the geometry - fun : callable or np.ndarray + fun : callable or xp.ndarray the 3-form component with which the inner products shall be computed (either callable or 3D array with values at quadrature points) """ @@ -318,10 +318,10 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): det_df = det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # evaluation of given 3-form at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun(quad_mesh[0], quad_mesh[1], quad_mesh[2]) else: mat_f[:, :, :] = fun @@ -329,7 +329,7 @@ def inner_prod_V3(tensor_space_FEM, domain, fun): # assembly Ni = tensor_space.Nbase_3form - F = np.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) + F = xp.zeros((Ni[0], Ni[1], Ni[2]), dtype=float) ker.kernel_inner( Nel[0], diff --git a/src/struphy/eigenvalue_solvers/legacy/l2_error_1d.py b/src/struphy/eigenvalue_solvers/legacy/l2_error_1d.py index 3fcc3d55f..d568d3207 100644 --- a/src/struphy/eigenvalue_solvers/legacy/l2_error_1d.py +++ b/src/struphy/eigenvalue_solvers/legacy/l2_error_1d.py @@ -6,7 +6,7 @@ Modules to compute L2-errors in 1d. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa @@ -47,7 +47,7 @@ def l2_error_V0(spline_space, mapping, coeff, fun): mat_f = fun(pts) # assembly - error = np.zeros(Nel, dtype=float) + error = xp.zeros(Nel, dtype=float) for ie in range(Nel): for q in range(n_quad): @@ -58,7 +58,7 @@ def l2_error_V0(spline_space, mapping, coeff, fun): error[ie] += wts[ie, q] * (bi - mat_f[ie, q]) ** 2 - return np.sqrt(error.sum()) + return xp.sqrt(error.sum()) # ======= error in V1 ==================== @@ -98,7 +98,7 @@ def l2_error_V1(spline_space, mapping, coeff, fun): mat_f = fun(pts) # assembly - error = np.zeros(Nel, dtype=float) + error = xp.zeros(Nel, dtype=float) for ie in range(Nel): for q in range(n_quad): @@ -109,4 +109,4 @@ def l2_error_V1(spline_space, mapping, coeff, fun): error[ie] += wts[ie, q] * (bi - mat_f[ie, q]) ** 2 - return np.sqrt(error.sum()) + return xp.sqrt(error.sum()) diff --git a/src/struphy/eigenvalue_solvers/legacy/l2_error_2d.py b/src/struphy/eigenvalue_solvers/legacy/l2_error_2d.py index f7224061b..452dd570b 100644 --- a/src/struphy/eigenvalue_solvers/legacy/l2_error_2d.py +++ b/src/struphy/eigenvalue_solvers/legacy/l2_error_2d.py @@ -6,7 +6,7 @@ Modules to compute L2-errors of discrete p-forms with analytical forms in 2D. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_2d as ker @@ -25,7 +25,7 @@ def l2_error_V0(tensor_space_FEM, domain, f0, c0, method="standard"): domain : domain domain object defining the geometry - f0 : callable or np.ndarray + f0 : callable or xp.ndarray the 0-form with which the error shall be computed c0 : array_like @@ -63,12 +63,12 @@ def l2_error_V0(tensor_space_FEM, domain, f0, c0, method="standard"): # evaluation of exact 0-form at quadrature points if callable(f0): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") f0 = f0(quad_mesh[0], quad_mesh[1], 0.0) if method == "standard": # evaluation of discrete 0-form at quadrature points - f0_h = tensor_space_FEM.evaluate_NN(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c0, "V0")[:, :, 0] + f0_h = tensor_space_FEM.evaluate_NN(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c0, "V0")[:, :, 0] # compute error error = 0.0 @@ -78,7 +78,7 @@ def l2_error_V0(tensor_space_FEM, domain, f0, c0, method="standard"): else: # compute error in each element - error = np.zeros(Nel[:2], dtype=float) + error = xp.zeros(Nel[:2], dtype=float) ker.kernel_l2error( Nel, @@ -106,7 +106,7 @@ def l2_error_V0(tensor_space_FEM, domain, f0, c0, method="standard"): error = error.sum() - return np.sqrt(error) + return xp.sqrt(error) # ======= error in V1 ==================== @@ -122,7 +122,7 @@ def l2_error_V1(tensor_space_FEM, domain, f1, c1, method="standard"): domain : domain domain object defining the geometry - f1 : list of callables or np.ndarrays + f1 : list of callables or xp.ndarrays the three 1-form components with which the error shall be computed c1 : list of array_like @@ -162,16 +162,16 @@ def l2_error_V1(tensor_space_FEM, domain, f1, c1, method="standard"): # evaluation of exact 1-form components at quadrature points if callable(f1[0]): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") f1_1 = f1[0](quad_mesh[0], quad_mesh[1], 0.0) f1_2 = f1[1](quad_mesh[0], quad_mesh[1], 0.0) f1_3 = f1[2](quad_mesh[0], quad_mesh[1], 0.0) if method == "standard": # evaluation of discrete 1-form components at quadrature points - f1_h_1 = tensor_space_FEM.evaluate_DN(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c1_1, "V1")[:, :, 0] - f1_h_2 = tensor_space_FEM.evaluate_ND(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c1_2, "V1")[:, :, 0] - f1_h_3 = tensor_space_FEM.evaluate_NN(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c1_3, "V1")[:, :, 0] + f1_h_1 = tensor_space_FEM.evaluate_DN(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c1_1, "V1")[:, :, 0] + f1_h_2 = tensor_space_FEM.evaluate_ND(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c1_2, "V1")[:, :, 0] + f1_h_3 = tensor_space_FEM.evaluate_NN(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c1_3, "V1")[:, :, 0] # compute error error = 0.0 @@ -194,7 +194,7 @@ def l2_error_V1(tensor_space_FEM, domain, f1, c1, method="standard"): else: # compute error in each element - error = np.zeros(Nel[:2], dtype=float) + error = xp.zeros(Nel[:2], dtype=float) # 1 * d_f1 * G^11 * |det(DF)| * d_f1 ker.kernel_l2error( @@ -298,7 +298,7 @@ def l2_error_V1(tensor_space_FEM, domain, f1, c1, method="standard"): error = error.sum() - return np.sqrt(error) + return xp.sqrt(error) # ======= error in V2 ==================== @@ -314,7 +314,7 @@ def l2_error_V2(tensor_space_FEM, domain, f2, c2, method="standard"): domain : domain domain object defining the geometry - f2 : list of callables or np.ndarrays + f2 : list of callables or xp.ndarrays the three 2-form components with which the error shall be computed c2 : list of array_like @@ -354,16 +354,16 @@ def l2_error_V2(tensor_space_FEM, domain, f2, c2, method="standard"): # evaluation of exact 2-form components at quadrature points if callable(f2[0]): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") f2_1 = f2[0](quad_mesh[0], quad_mesh[1], 0.0) f2_2 = f2[1](quad_mesh[0], quad_mesh[1], 0.0) f2_3 = f2[2](quad_mesh[0], quad_mesh[1], 0.0) if method == "standard": # evaluation of discrete 2-form components at quadrature points - f2_h_1 = tensor_space_FEM.evaluate_ND(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c2_1, "V2")[:, :, 0] - f2_h_2 = tensor_space_FEM.evaluate_DN(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c2_2, "V2")[:, :, 0] - f2_h_3 = tensor_space_FEM.evaluate_DD(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c2_3, "V2")[:, :, 0] + f2_h_1 = tensor_space_FEM.evaluate_ND(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c2_1, "V2")[:, :, 0] + f2_h_2 = tensor_space_FEM.evaluate_DN(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c2_2, "V2")[:, :, 0] + f2_h_3 = tensor_space_FEM.evaluate_DD(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c2_3, "V2")[:, :, 0] # compute error error = 0.0 @@ -386,7 +386,7 @@ def l2_error_V2(tensor_space_FEM, domain, f2, c2, method="standard"): else: # compute error in each element - error = np.zeros(Nel[:2], dtype=float) + error = xp.zeros(Nel[:2], dtype=float) # 1 * d_f1 * G_11 / |det(DF)| * d_f1 ker.kernel_l2error( @@ -490,7 +490,7 @@ def l2_error_V2(tensor_space_FEM, domain, f2, c2, method="standard"): error = error.sum() - return np.sqrt(error) + return xp.sqrt(error) # ======= error in V3 ==================== @@ -506,7 +506,7 @@ def l2_error_V3(tensor_space_FEM, domain, f3, c3, method="standard"): domain : domain domain object defining the geometry - f3 : callable or np.ndarray + f3 : callable or xp.ndarray the 3-form component with which the error shall be computed c3 : array_like @@ -544,12 +544,12 @@ def l2_error_V3(tensor_space_FEM, domain, f3, c3, method="standard"): # evaluation of exact 3-form at quadrature points if callable(f3): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), indexing="ij") f3 = f3(quad_mesh[0], quad_mesh[1], 0.0) if method == "standard": # evaluation of discrete 3-form at quadrature points - f3_h = tensor_space_FEM.evaluate_DD(pts[0].flatten(), pts[1].flatten(), np.array([0.0]), c3, "V3")[:, :, 0] + f3_h = tensor_space_FEM.evaluate_DD(pts[0].flatten(), pts[1].flatten(), xp.array([0.0]), c3, "V3")[:, :, 0] # compute error error = 0.0 @@ -559,7 +559,7 @@ def l2_error_V3(tensor_space_FEM, domain, f3, c3, method="standard"): else: # compute error in each element - error = np.zeros(Nel[:2], dtype=float) + error = xp.zeros(Nel[:2], dtype=float) ker.kernel_l2error( Nel, @@ -587,4 +587,4 @@ def l2_error_V3(tensor_space_FEM, domain, f3, c3, method="standard"): error = error.sum() - return np.sqrt(error) + return xp.sqrt(error) diff --git a/src/struphy/eigenvalue_solvers/legacy/l2_error_3d.py b/src/struphy/eigenvalue_solvers/legacy/l2_error_3d.py index bc246310a..7553e3a83 100644 --- a/src/struphy/eigenvalue_solvers/legacy/l2_error_3d.py +++ b/src/struphy/eigenvalue_solvers/legacy/l2_error_3d.py @@ -6,7 +6,7 @@ Modules to compute L2-errors of discrete p-forms with analytical forms in 3D. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_3d as ker @@ -25,7 +25,7 @@ def l2_error_V0(tensor_space_FEM, domain, fun, coeff): domain : domain domain object defining the geometry - fun : callable or np.ndarray + fun : callable or xp.ndarray the 0-form with which the error shall be computed coeff : array_like @@ -54,16 +54,16 @@ def l2_error_V0(tensor_space_FEM, domain, fun, coeff): det_df = abs(domain.jacobian_det(pts[0].flatten(), pts[1].flatten(), pts[2].flatten())) # evaluation of given 0-form at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun(quad_mesh[0], quad_mesh[1], quad_mesh[2]) else: mat_f[:, :, :] = fun # compute error - error = np.zeros(Nel, dtype=float) + error = xp.zeros(Nel, dtype=float) ker.kernel_l2error( Nel, @@ -94,7 +94,7 @@ def l2_error_V0(tensor_space_FEM, domain, fun, coeff): det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]), ) - return np.sqrt(error.sum()) + return xp.sqrt(error.sum()) # ======= error in V1 ==================== @@ -110,7 +110,7 @@ def l2_error_V1(tensor_space_FEM, domain, fun, coeff): domain : domain domain object defining the geometry - fun : list of callables or np.ndarrays + fun : list of callables or xp.ndarrays the three 1-form components with which the error shall be computed coeff : list of array_like @@ -141,12 +141,12 @@ def l2_error_V1(tensor_space_FEM, domain, fun, coeff): metric_coeffs *= abs(domain.jacobian_det(pts[0].flatten(), pts[1].flatten(), pts[2].flatten())) # evaluation of given 1-form components at quadrature points - mat_f1 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) - mat_f2 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) - mat_f3 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f1 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f2 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f3 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun[0]): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f1[:, :, :] = fun[0](quad_mesh[0], quad_mesh[1], quad_mesh[2]) mat_f2[:, :, :] = fun[1](quad_mesh[0], quad_mesh[1], quad_mesh[2]) mat_f3[:, :, :] = fun[2](quad_mesh[0], quad_mesh[1], quad_mesh[2]) @@ -156,7 +156,7 @@ def l2_error_V1(tensor_space_FEM, domain, fun, coeff): mat_f3[:, :, :] = fun[2] # compute error - error = np.zeros(Nel, dtype=float) + error = xp.zeros(Nel, dtype=float) # 1 * f1 * G^11 * |det(DF)| * f1 ker.kernel_l2error( @@ -314,7 +314,7 @@ def l2_error_V1(tensor_space_FEM, domain, fun, coeff): 1 * metric_coeffs[2, 2].reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]), ) - return np.sqrt(error.sum()) + return xp.sqrt(error.sum()) # ======= error in V2 ==================== @@ -330,7 +330,7 @@ def l2_error_V2(tensor_space_FEM, domain, fun, coeff): domain : domain domain object defining the geometry - fun : list of callables or np.ndarrays + fun : list of callables or xp.ndarrays the three 2-form components with which the error shall be computed coeff : list of array_like @@ -361,12 +361,12 @@ def l2_error_V2(tensor_space_FEM, domain, fun, coeff): metric_coeffs /= abs(domain.jacobian_det(pts[0].flatten(), pts[1].flatten(), pts[2].flatten())) # evaluation of given 2-form components at quadrature points - mat_f1 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) - mat_f2 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) - mat_f3 = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f1 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f2 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f3 = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun[0]): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f1[:, :, :] = fun[0](quad_mesh[0], quad_mesh[1], quad_mesh[2]) mat_f2[:, :, :] = fun[1](quad_mesh[0], quad_mesh[1], quad_mesh[2]) mat_f3[:, :, :] = fun[2](quad_mesh[0], quad_mesh[1], quad_mesh[2]) @@ -376,7 +376,7 @@ def l2_error_V2(tensor_space_FEM, domain, fun, coeff): mat_f3[:, :, :] = fun[2] # compute error - error = np.zeros(Nel, dtype=float) + error = xp.zeros(Nel, dtype=float) # 1 * f1 * G_11 / |det(DF)| * f1 ker.kernel_l2error( @@ -534,7 +534,7 @@ def l2_error_V2(tensor_space_FEM, domain, fun, coeff): 1 * metric_coeffs[2, 2].reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]), ) - return np.sqrt(error.sum()) + return xp.sqrt(error.sum()) # ======= error in V3 ==================== @@ -550,7 +550,7 @@ def l2_error_V3(tensor_space_FEM, domain, fun, coeff): domain : domain domain object defining the geometry - fun : callable or np.ndarray + fun : callable or xp.ndarray the 3-form component with which the error shall be computed coeff : array_like @@ -579,16 +579,16 @@ def l2_error_V3(tensor_space_FEM, domain, fun, coeff): det_df = abs(domain.jacobian_det(pts[0].flatten(), pts[1].flatten(), pts[2].flatten())) # evaluation of given 3-form component at quadrature points - mat_f = np.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) + mat_f = xp.empty((pts[0].size, pts[1].size, pts[2].size), dtype=float) if callable(fun): - quad_mesh = np.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") + quad_mesh = xp.meshgrid(pts[0].flatten(), pts[1].flatten(), pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun(quad_mesh[0], quad_mesh[1], quad_mesh[2]) else: mat_f[:, :, :] = fun # compute error - error = np.zeros(Nel, dtype=float) + error = xp.zeros(Nel, dtype=float) ker.kernel_l2error( Nel, @@ -619,4 +619,4 @@ def l2_error_V3(tensor_space_FEM, domain, fun, coeff): 1 / det_df.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]), ) - return np.sqrt(error.sum()) + return xp.sqrt(error.sum()) diff --git a/src/struphy/eigenvalue_solvers/legacy/mass_matrices_3d_pre.py b/src/struphy/eigenvalue_solvers/legacy/mass_matrices_3d_pre.py index 3b6d2a31b..a46097a8f 100644 --- a/src/struphy/eigenvalue_solvers/legacy/mass_matrices_3d_pre.py +++ b/src/struphy/eigenvalue_solvers/legacy/mass_matrices_3d_pre.py @@ -6,7 +6,7 @@ Modules to obtain preconditioners for mass matrices in 3D. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.spline_space as spl @@ -32,9 +32,9 @@ def get_M0_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) c_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] @@ -63,20 +63,20 @@ def get_M1_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) c11_pre = [spaces_pre[0].M1.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] c22_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M1.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] c33_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M1.toarray()[:, 0]] def solve(x): - x1, x2, x3 = np.split(x, 3) + x1, x2, x3 = xp.split(x, 3) x1 = x1.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) x2 = x2.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) @@ -86,7 +86,7 @@ def solve(x): r2 = linkron.kron_fftsolve_3d(c22_pre, x2).flatten() r3 = linkron.kron_fftsolve_3d(c33_pre, x3).flatten() - return np.concatenate((r1, r2, r3)) + return xp.concatenate((r1, r2, r3)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.M1.shape, matvec=solve) @@ -110,20 +110,20 @@ def get_M2_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) - spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) c11_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M1.toarray()[:, 0], spaces_pre[2].M1.toarray()[:, 0]] c22_pre = [spaces_pre[0].M1.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M1.toarray()[:, 0]] c33_pre = [spaces_pre[0].M1.toarray()[:, 0], spaces_pre[1].M1.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] def solve(x): - x1, x2, x3 = np.split(x, 3) + x1, x2, x3 = xp.split(x, 3) x1 = x1.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) x2 = x2.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) @@ -133,7 +133,7 @@ def solve(x): r2 = linkron.kron_fftsolve_3d(c22_pre, x2).flatten() r3 = linkron.kron_fftsolve_3d(c33_pre, x3).flatten() - return np.concatenate((r1, r2, r3)) + return xp.concatenate((r1, r2, r3)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.M2.shape, matvec=solve) @@ -157,9 +157,9 @@ def get_M3_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M1(lambda eta: 1 / (domain.params[1] - domain.params[0]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M1(lambda eta: 1 / (domain.params[3] - domain.params[2]) * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M1(lambda eta: 1 / (domain.params[5] - domain.params[4]) * xp.ones(eta.shape, dtype=float)) c_pre = [spaces_pre[0].M1.toarray()[:, 0], spaces_pre[1].M1.toarray()[:, 0], spaces_pre[2].M1.toarray()[:, 0]] @@ -188,26 +188,26 @@ def get_Mv_PRE(tensor_space_FEM, domain): # spaces_pre[1].set_extraction_operators() # spaces_pre[2].set_extraction_operators() - spaces_pre[0].assemble_M0(lambda eta: domain.params[0] ** 3 * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: domain.params[1] * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: domain.params[2] * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: domain.params[0] ** 3 * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: domain.params[1] * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: domain.params[2] * xp.ones(eta.shape, dtype=float)) c11_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] - spaces_pre[0].assemble_M0(lambda eta: domain.params[0] * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: domain.params[1] ** 3 * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: domain.params[2] * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: domain.params[0] * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: domain.params[1] ** 3 * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: domain.params[2] * xp.ones(eta.shape, dtype=float)) c22_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] - spaces_pre[0].assemble_M0(lambda eta: domain.params[0] * np.ones(eta.shape, dtype=float)) - spaces_pre[1].assemble_M0(lambda eta: domain.params[1] * np.ones(eta.shape, dtype=float)) - spaces_pre[2].assemble_M0(lambda eta: domain.params[2] ** 3 * np.ones(eta.shape, dtype=float)) + spaces_pre[0].assemble_M0(lambda eta: domain.params[0] * xp.ones(eta.shape, dtype=float)) + spaces_pre[1].assemble_M0(lambda eta: domain.params[1] * xp.ones(eta.shape, dtype=float)) + spaces_pre[2].assemble_M0(lambda eta: domain.params[2] ** 3 * xp.ones(eta.shape, dtype=float)) c33_pre = [spaces_pre[0].M0.toarray()[:, 0], spaces_pre[1].M0.toarray()[:, 0], spaces_pre[2].M0.toarray()[:, 0]] def solve(x): - x1, x2, x3 = np.split(x, 3) + x1, x2, x3 = xp.split(x, 3) x1 = x1.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) x2 = x2.reshape(Nel_pre[0], Nel_pre[1], Nel_pre[2]) @@ -217,7 +217,7 @@ def solve(x): r2 = linkron.kron_fftsolve_3d(c22_pre, x2).flatten() r3 = linkron.kron_fftsolve_3d(c33_pre, x3).flatten() - return np.concatenate((r1, r2, r3)) + return xp.concatenate((r1, r2, r3)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.Mv.shape, matvec=solve) @@ -273,16 +273,18 @@ def get_M1_PRE_3(tensor_space_FEM, mats_pol=None): def solve(x): x1 = x[: tensor_space_FEM.E1_pol_0.shape[0] * tensor_space_FEM.NbaseN[2]].reshape( - tensor_space_FEM.E1_pol_0.shape[0], tensor_space_FEM.NbaseN[2] + tensor_space_FEM.E1_pol_0.shape[0], + tensor_space_FEM.NbaseN[2], ) x2 = x[tensor_space_FEM.E1_pol_0.shape[0] * tensor_space_FEM.NbaseN[2] :].reshape( - tensor_space_FEM.E0_pol_0.shape[0], tensor_space_FEM.NbaseD[2] + tensor_space_FEM.E0_pol_0.shape[0], + tensor_space_FEM.NbaseD[2], ) r1 = linkron.kron_fftsolve_2d(M1_pol_0_11_LU, tor_vec0, x1).flatten() r2 = linkron.kron_fftsolve_2d(M1_pol_0_22_LU, tor_vec1, x2).flatten() - return np.concatenate((r1, r2)) + return xp.concatenate((r1, r2)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.M1_0.shape, matvec=solve) @@ -311,16 +313,18 @@ def get_M2_PRE_3(tensor_space_FEM, mats_pol=None): def solve(x): x1 = x[: tensor_space_FEM.E2_pol_0.shape[0] * tensor_space_FEM.NbaseD[2]].reshape( - tensor_space_FEM.E2_pol_0.shape[0], tensor_space_FEM.NbaseD[2] + tensor_space_FEM.E2_pol_0.shape[0], + tensor_space_FEM.NbaseD[2], ) x2 = x[tensor_space_FEM.E2_pol_0.shape[0] * tensor_space_FEM.NbaseD[2] :].reshape( - tensor_space_FEM.E3_pol_0.shape[0], tensor_space_FEM.NbaseN[2] + tensor_space_FEM.E3_pol_0.shape[0], + tensor_space_FEM.NbaseN[2], ) r1 = linkron.kron_fftsolve_2d(M2_pol_0_11_LU, tor_vec1, x1).flatten() r2 = linkron.kron_fftsolve_2d(M2_pol_0_22_LU, tor_vec0, x2).flatten() - return np.concatenate((r1, r2)) + return xp.concatenate((r1, r2)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.M2_0.shape, matvec=solve) @@ -373,15 +377,17 @@ def get_Mv_PRE_3(tensor_space_FEM, mats_pol=None): def solve(x): x1 = x[: tensor_space_FEM.Ev_pol_0.shape[0] * tensor_space_FEM.NbaseN[2]].reshape( - tensor_space_FEM.Ev_pol_0.shape[0], tensor_space_FEM.NbaseN[2] + tensor_space_FEM.Ev_pol_0.shape[0], + tensor_space_FEM.NbaseN[2], ) x2 = x[tensor_space_FEM.Ev_pol_0.shape[0] * tensor_space_FEM.NbaseN[2] :].reshape( - tensor_space_FEM.E0_pol.shape[0], tensor_space_FEM.NbaseN[2] + tensor_space_FEM.E0_pol.shape[0], + tensor_space_FEM.NbaseN[2], ) r1 = linkron.kron_fftsolve_2d(Mv_pol_0_11_LU, tor_vec0, x1).flatten() r2 = linkron.kron_fftsolve_2d(Mv_pol_0_22_LU, tor_vec0, x2).flatten() - return np.concatenate((r1, r2)) + return xp.concatenate((r1, r2)) return spa.linalg.LinearOperator(shape=tensor_space_FEM.Mv_0.shape, matvec=solve) diff --git a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_arrays.py b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_arrays.py index 7705da576..65faf9209 100644 --- a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_arrays.py +++ b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_arrays.py @@ -1,9 +1,9 @@ import time import timeit -import numpy as np +import cunumpy as xp import scipy.sparse as spa -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI import struphy.geometry.mappings_3d as mapping3d import struphy.geometry.mappings_3d_fast as mapping_fast @@ -39,67 +39,67 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): self.Ntot_1form = TENSOR_SPACE_FEM.Ntot_1form self.Ntot_2form = TENSOR_SPACE_FEM.Ntot_2form - self.b1_old = np.empty(TENSOR_SPACE_FEM.Nbase_1form[0], dtype=float) - self.b2_old = np.empty(TENSOR_SPACE_FEM.Nbase_1form[1], dtype=float) - self.b3_old = np.empty(TENSOR_SPACE_FEM.Nbase_1form[2], dtype=float) + self.b1_old = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[0], dtype=float) + self.b2_old = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[1], dtype=float) + self.b3_old = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[2], dtype=float) - self.b1_iter = np.empty(TENSOR_SPACE_FEM.Nbase_1form[0], dtype=float) - self.b2_iter = np.empty(TENSOR_SPACE_FEM.Nbase_1form[1], dtype=float) - self.b3_iter = np.empty(TENSOR_SPACE_FEM.Nbase_1form[2], dtype=float) + self.b1_iter = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[0], dtype=float) + self.b2_iter = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[1], dtype=float) + self.b3_iter = xp.empty(TENSOR_SPACE_FEM.Nbase_1form[2], dtype=float) - self.temp_dft = np.empty((3, 3), dtype=float) - self.temp_generate_weight1 = np.empty(3, dtype=float) - self.temp_generate_weight2 = np.empty(3, dtype=float) - self.temp_generate_weight3 = np.empty(3, dtype=float) + self.temp_dft = xp.empty((3, 3), dtype=float) + self.temp_generate_weight1 = xp.empty(3, dtype=float) + self.temp_generate_weight2 = xp.empty(3, dtype=float) + self.temp_generate_weight3 = xp.empty(3, dtype=float) - self.zerosform_temp_long = np.empty(TENSOR_SPACE_FEM.Ntot_0form, dtype=float) - self.oneform_temp1_long = np.empty(TENSOR_SPACE_FEM.Ntot_1form[0], dtype=float) - self.oneform_temp2_long = np.empty(TENSOR_SPACE_FEM.Ntot_1form[1], dtype=float) - self.oneform_temp3_long = np.empty(TENSOR_SPACE_FEM.Ntot_1form[2], dtype=float) + self.zerosform_temp_long = xp.empty(TENSOR_SPACE_FEM.Ntot_0form, dtype=float) + self.oneform_temp1_long = xp.empty(TENSOR_SPACE_FEM.Ntot_1form[0], dtype=float) + self.oneform_temp2_long = xp.empty(TENSOR_SPACE_FEM.Ntot_1form[1], dtype=float) + self.oneform_temp3_long = xp.empty(TENSOR_SPACE_FEM.Ntot_1form[2], dtype=float) - self.oneform_temp_long = np.empty( + self.oneform_temp_long = xp.empty( TENSOR_SPACE_FEM.Ntot_1form[0] + TENSOR_SPACE_FEM.Ntot_1form[1] + TENSOR_SPACE_FEM.Ntot_1form[2], dtype=float, ) - self.twoform_temp1_long = np.empty(TENSOR_SPACE_FEM.Ntot_2form[0], dtype=float) - self.twoform_temp2_long = np.empty(TENSOR_SPACE_FEM.Ntot_2form[1], dtype=float) - self.twoform_temp3_long = np.empty(TENSOR_SPACE_FEM.Ntot_2form[2], dtype=float) + self.twoform_temp1_long = xp.empty(TENSOR_SPACE_FEM.Ntot_2form[0], dtype=float) + self.twoform_temp2_long = xp.empty(TENSOR_SPACE_FEM.Ntot_2form[1], dtype=float) + self.twoform_temp3_long = xp.empty(TENSOR_SPACE_FEM.Ntot_2form[2], dtype=float) - self.twoform_temp_long = np.empty( + self.twoform_temp_long = xp.empty( TENSOR_SPACE_FEM.Ntot_2form[0] + TENSOR_SPACE_FEM.Ntot_2form[1] + TENSOR_SPACE_FEM.Ntot_2form[2], dtype=float, ) - self.temp_twoform1 = np.empty(TENSOR_SPACE_FEM.Nbase_2form[0], dtype=float) - self.temp_twoform2 = np.empty(TENSOR_SPACE_FEM.Nbase_2form[1], dtype=float) - self.temp_twoform3 = np.empty(TENSOR_SPACE_FEM.Nbase_2form[2], dtype=float) + self.temp_twoform1 = xp.empty(TENSOR_SPACE_FEM.Nbase_2form[0], dtype=float) + self.temp_twoform2 = xp.empty(TENSOR_SPACE_FEM.Nbase_2form[1], dtype=float) + self.temp_twoform3 = xp.empty(TENSOR_SPACE_FEM.Nbase_2form[2], dtype=float) # arrays used to store intermidaite values - self.form_0_flatten = np.empty(self.Ntot_0form, dtype=float) + self.form_0_flatten = xp.empty(self.Ntot_0form, dtype=float) - self.form_1_1_flatten = np.empty(self.Ntot_1form[0], dtype=float) - self.form_1_2_flatten = np.empty(self.Ntot_1form[1], dtype=float) - self.form_1_3_flatten = np.empty(self.Ntot_1form[2], dtype=float) + self.form_1_1_flatten = xp.empty(self.Ntot_1form[0], dtype=float) + self.form_1_2_flatten = xp.empty(self.Ntot_1form[1], dtype=float) + self.form_1_3_flatten = xp.empty(self.Ntot_1form[2], dtype=float) - self.form_1_tot_flatten = np.empty(self.Ntot_1form[0] + self.Ntot_1form[1] + self.Ntot_1form[2], dtype=float) + self.form_1_tot_flatten = xp.empty(self.Ntot_1form[0] + self.Ntot_1form[1] + self.Ntot_1form[2], dtype=float) - self.form_2_1_flatten = np.empty(self.Ntot_2form[0], dtype=float) - self.form_2_2_flatten = np.empty(self.Ntot_2form[1], dtype=float) - self.form_2_3_flatten = np.empty(self.Ntot_2form[2], dtype=float) + self.form_2_1_flatten = xp.empty(self.Ntot_2form[0], dtype=float) + self.form_2_2_flatten = xp.empty(self.Ntot_2form[1], dtype=float) + self.form_2_3_flatten = xp.empty(self.Ntot_2form[2], dtype=float) - self.form_2_tot_flatten = np.empty(self.Ntot_2form[0] + self.Ntot_2form[1] + self.Ntot_2form[2], dtype=float) + self.form_2_tot_flatten = xp.empty(self.Ntot_2form[0] + self.Ntot_2form[1] + self.Ntot_2form[2], dtype=float) - self.bulkspeed_loc = np.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) - self.temperature_loc = np.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) - self.bulkspeed = np.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) + self.bulkspeed_loc = xp.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) + self.temperature_loc = xp.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) + self.bulkspeed = xp.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) if self.mpi_rank == 0: - temperature = np.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) + temperature = xp.zeros((3, self.Nel[0], self.Nel[1], self.Nel[2]), dtype=float) else: temperature = None # values of magnetic fields at all quadrature points - self.LO_inv = np.empty( + self.LO_inv = xp.empty( ( self.Nel[0], self.Nel[1], @@ -111,7 +111,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.LO_b1 = np.empty( + self.LO_b1 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -122,7 +122,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_b2 = np.empty( + self.LO_b2 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -133,7 +133,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_b3 = np.empty( + self.LO_b3 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -145,7 +145,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # values of weights (used in the linear operators) - self.LO_w1 = np.empty( + self.LO_w1 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -156,7 +156,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_w2 = np.empty( + self.LO_w2 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -167,7 +167,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_w3 = np.empty( + self.LO_w3 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -179,7 +179,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # values of a function (given its finite element coefficients) at all quadrature points - self.LO_r1 = np.empty( + self.LO_r1 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -190,7 +190,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_r2 = np.empty( + self.LO_r2 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -201,7 +201,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.LO_r3 = np.empty( + self.LO_r3 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -213,7 +213,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # values of determinant of Jacobi matrix of the map at all quadrature points - self.df_det = np.empty( + self.df_det = xp.empty( ( self.Nel[0], self.Nel[1], @@ -225,8 +225,8 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # when using delta f method, the values of current equilibrium at all quadrature points - if control == True: - self.Jeqx = np.empty( + if control: + self.Jeqx = xp.empty( ( self.Nel[0], self.Nel[1], @@ -237,7 +237,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.Jeqy = np.empty( + self.Jeqy = xp.empty( ( self.Nel[0], self.Nel[1], @@ -248,7 +248,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.Jeqz = np.empty( + self.Jeqz = xp.empty( ( self.Nel[0], self.Nel[1], @@ -260,7 +260,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) # values of DF and inverse of DF at all quadrature points - self.DF_11 = np.empty( + self.DF_11 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -271,7 +271,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_12 = np.empty( + self.DF_12 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -282,7 +282,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_13 = np.empty( + self.DF_13 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -293,7 +293,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_21 = np.empty( + self.DF_21 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -304,7 +304,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_22 = np.empty( + self.DF_22 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -315,7 +315,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_23 = np.empty( + self.DF_23 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -326,7 +326,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_31 = np.empty( + self.DF_31 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -337,7 +337,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_32 = np.empty( + self.DF_32 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -348,7 +348,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DF_33 = np.empty( + self.DF_33 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -360,7 +360,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.DFI_11 = np.empty( + self.DFI_11 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -371,7 +371,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_12 = np.empty( + self.DFI_12 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -382,7 +382,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_13 = np.empty( + self.DFI_13 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -393,7 +393,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_21 = np.empty( + self.DFI_21 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -404,7 +404,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_22 = np.empty( + self.DFI_22 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -415,7 +415,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_23 = np.empty( + self.DFI_23 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -426,7 +426,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_31 = np.empty( + self.DFI_31 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -437,7 +437,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_32 = np.empty( + self.DFI_32 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -448,7 +448,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFI_33 = np.empty( + self.DFI_33 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -460,7 +460,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.DFIT_11 = np.empty( + self.DFIT_11 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -471,7 +471,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_12 = np.empty( + self.DFIT_12 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -482,7 +482,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_13 = np.empty( + self.DFIT_13 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -493,7 +493,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_21 = np.empty( + self.DFIT_21 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -504,7 +504,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_22 = np.empty( + self.DFIT_22 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -515,7 +515,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_23 = np.empty( + self.DFIT_23 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -526,7 +526,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_31 = np.empty( + self.DFIT_31 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -537,7 +537,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_32 = np.empty( + self.DFIT_32 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -548,7 +548,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.DFIT_33 = np.empty( + self.DFIT_33 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -560,7 +560,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.G_inv_11 = np.empty( + self.G_inv_11 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -571,7 +571,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.G_inv_12 = np.empty( + self.G_inv_12 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -582,7 +582,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.G_inv_13 = np.empty( + self.G_inv_13 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -594,7 +594,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.G_inv_22 = np.empty( + self.G_inv_22 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -605,7 +605,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): ), dtype=float, ) - self.G_inv_23 = np.empty( + self.G_inv_23 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -617,7 +617,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.G_inv_33 = np.empty( + self.G_inv_33 = xp.empty( ( self.Nel[0], self.Nel[1], @@ -629,7 +629,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): dtype=float, ) - self.temp_particle = np.empty(3, dtype=float) + self.temp_particle = xp.empty(3, dtype=float) # initialization of DF and its inverse # ================ for mapping evaluation ================== # spline degrees @@ -638,34 +638,34 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): pf3 = DOMAIN.p[2] # pf + 1 non-vanishing basis functions up tp degree pf - b1f = np.empty((pf1 + 1, pf1 + 1), dtype=float) - b2f = np.empty((pf2 + 1, pf2 + 1), dtype=float) - b3f = np.empty((pf3 + 1, pf3 + 1), dtype=float) + b1f = xp.empty((pf1 + 1, pf1 + 1), dtype=float) + b2f = xp.empty((pf2 + 1, pf2 + 1), dtype=float) + b3f = xp.empty((pf3 + 1, pf3 + 1), dtype=float) # left and right values for spline evaluation - l1f = np.empty(pf1, dtype=float) - l2f = np.empty(pf2, dtype=float) - l3f = np.empty(pf3, dtype=float) + l1f = xp.empty(pf1, dtype=float) + l2f = xp.empty(pf2, dtype=float) + l3f = xp.empty(pf3, dtype=float) - r1f = np.empty(pf1, dtype=float) - r2f = np.empty(pf2, dtype=float) - r3f = np.empty(pf3, dtype=float) + r1f = xp.empty(pf1, dtype=float) + r2f = xp.empty(pf2, dtype=float) + r3f = xp.empty(pf3, dtype=float) # scaling arrays for M-splines - d1f = np.empty(pf1, dtype=float) - d2f = np.empty(pf2, dtype=float) - d3f = np.empty(pf3, dtype=float) + d1f = xp.empty(pf1, dtype=float) + d2f = xp.empty(pf2, dtype=float) + d3f = xp.empty(pf3, dtype=float) # pf + 1 derivatives - der1f = np.empty(pf1 + 1, dtype=float) - der2f = np.empty(pf2 + 1, dtype=float) - der3f = np.empty(pf3 + 1, dtype=float) + der1f = xp.empty(pf1 + 1, dtype=float) + der2f = xp.empty(pf2 + 1, dtype=float) + der3f = xp.empty(pf3 + 1, dtype=float) # needed mapping quantities - df = np.empty((3, 3), dtype=float) - fx = np.empty(3, dtype=float) - ginv = np.empty((3, 3), dtype=float) - dfinv = np.empty((3, 3), dtype=float) + df = xp.empty((3, 3), dtype=float) + fx = xp.empty(3, dtype=float) + ginv = xp.empty((3, 3), dtype=float) + dfinv = xp.empty((3, 3), dtype=float) for ie1 in range(self.Nel[0]): for ie2 in range(self.Nel[1]): @@ -761,7 +761,7 @@ def __init__(self, TENSOR_SPACE_FEM, DOMAIN, control, mpi_comm): self.df_det[ie1, ie2, ie3, q1, q2, q3] = det_number - if control == True: + if control: x1 = mapping3d.f( TENSOR_SPACE_FEM.pts[0][ie1, q1], TENSOR_SPACE_FEM.pts[1][ie2, q2], diff --git a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_bv_kernel.py b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_bv_kernel.py index 5cf3830a4..e477940e6 100644 --- a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_bv_kernel.py +++ b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_bv_kernel.py @@ -226,7 +226,9 @@ def right_hand2( * bd2[ie2, il2, 0, q2] * bd3[ie3, il3, 0, q3] * temp_vector_1[ - N_index_x[ie1, il1], D_index_y[ie2, il2], D_index_z[ie3, il3] + N_index_x[ie1, il1], + D_index_y[ie2, il2], + D_index_z[ie3, il3], ] ) @@ -252,7 +254,9 @@ def right_hand2( * bn2[ie2, il2, 0, q2] * bd3[ie3, il3, 0, q3] * temp_vector_2[ - D_index_x[ie1, il1], N_index_y[ie2, il2], D_index_z[ie3, il3] + D_index_x[ie1, il1], + N_index_y[ie2, il2], + D_index_z[ie3, il3], ] ) @@ -278,7 +282,9 @@ def right_hand2( * bd2[ie2, il2, 0, q2] * bn3[ie3, il3, 0, q3] * temp_vector_3[ - D_index_x[ie1, il1], D_index_y[ie2, il2], N_index_z[ie3, il3] + D_index_x[ie1, il1], + D_index_y[ie2, il2], + N_index_z[ie3, il3], ] ) @@ -338,7 +344,9 @@ def right_hand1( * bn2[ie2, il2, 0, q2] * bn3[ie3, il3, 0, q3] * temp_vector_1[ - D_index_x[ie1, il1], N_index_y[ie2, il2], N_index_z[ie3, il3] + D_index_x[ie1, il1], + N_index_y[ie2, il2], + N_index_z[ie3, il3], ] ) @@ -364,7 +372,9 @@ def right_hand1( * bd2[ie2, il2, 0, q2] * bn3[ie3, il3, 0, q3] * temp_vector_2[ - N_index_x[ie1, il1], D_index_y[ie2, il2], N_index_z[ie3, il3] + N_index_x[ie1, il1], + D_index_y[ie2, il2], + N_index_z[ie3, il3], ] ) @@ -390,7 +400,9 @@ def right_hand1( * bn2[ie2, il2, 0, q2] * bd3[ie3, il3, 0, q3] * temp_vector_3[ - N_index_x[ie1, il1], N_index_y[ie2, il2], D_index_z[ie3, il3] + N_index_x[ie1, il1], + N_index_y[ie2, il2], + D_index_z[ie3, il3], ] ) diff --git a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_massless_linear_operators.py b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_massless_linear_operators.py index a26a22679..bdf01bf8b 100644 --- a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_massless_linear_operators.py +++ b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_massless_linear_operators.py @@ -1,6 +1,6 @@ import time -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.feec.massless_operators.fB_bb_kernel as bb_kernel @@ -49,15 +49,15 @@ def linearoperator_step_vv(self, M2_PRE, M2, M1_PRE, M1, TEMP, ACC_VV): This function is used in substep vv with L2 projector. """ - dft = np.empty((3, 3), dtype=float) - generate_weight1 = np.zeros(3, dtype=float) - generate_weight2 = np.zeros(3, dtype=float) - generate_weight3 = np.zeros(3, dtype=float) + dft = xp.empty((3, 3), dtype=float) + generate_weight1 = xp.zeros(3, dtype=float) + generate_weight2 = xp.zeros(3, dtype=float) + generate_weight3 = xp.zeros(3, dtype=float) # =========================inverse of M1 =========================== - ACC_VV.temp1[:], ACC_VV.temp2[:], ACC_VV.temp3[:] = np.split( + ACC_VV.temp1[:], ACC_VV.temp2[:], ACC_VV.temp3[:] = xp.split( spa.linalg.cg( M1, - 1.0 / self.Np * np.concatenate((ACC_VV.vec1.flatten(), ACC_VV.vec2.flatten(), ACC_VV.vec3.flatten())), + 1.0 / self.Np * xp.concatenate((ACC_VV.vec1.flatten(), ACC_VV.vec2.flatten(), ACC_VV.vec3.flatten())), tol=10 ** (-14), M=M1_PRE, )[0], @@ -153,10 +153,10 @@ def linearoperator_step_vv(self, M2_PRE, M2, M1_PRE, M1, TEMP, ACC_VV): ) # =========================inverse of M1 =========================== - ACC_VV.temp1[:], ACC_VV.temp2[:], ACC_VV.temp3[:] = np.split( + ACC_VV.temp1[:], ACC_VV.temp2[:], ACC_VV.temp3[:] = xp.split( spa.linalg.cg( M1, - np.concatenate((ACC_VV.one_form1.flatten(), ACC_VV.one_form2.flatten(), ACC_VV.one_form3.flatten())), + xp.concatenate((ACC_VV.one_form1.flatten(), ACC_VV.one_form2.flatten(), ACC_VV.one_form3.flatten())), tol=10 ** (-14), M=M1_PRE, )[0], @@ -310,10 +310,10 @@ def linearoperator_pre_step_vv( indN = tensor_space_FEM.indN indD = tensor_space_FEM.indD - dft = np.empty((3, 3), dtype=float) - generate_weight1 = np.zeros(3, dtype=float) - generate_weight2 = np.zeros(3, dtype=float) - generate_weight3 = np.zeros(3, dtype=float) + dft = xp.empty((3, 3), dtype=float) + generate_weight1 = xp.zeros(3, dtype=float) + generate_weight2 = xp.zeros(3, dtype=float) + generate_weight3 = xp.zeros(3, dtype=float) vv_kernel.prepre( indN[0], @@ -716,16 +716,17 @@ def linearoperator_step3( Ntot_2form = tensor_space_FEM.Ntot_2form Nbase_2form = tensor_space_FEM.Nbase_2form - dft = np.empty((3, 3), dtype=float) - generate_weight1 = np.empty(3, dtype=float) - generate_weight2 = np.empty(3, dtype=float) - generate_weight3 = np.empty(3, dtype=float) + dft = xp.empty((3, 3), dtype=float) + generate_weight1 = xp.empty(3, dtype=float) + generate_weight2 = xp.empty(3, dtype=float) + generate_weight3 = xp.empty(3, dtype=float) # ================================================================== # ========================= C =========================== # time1 = time.time() - twoform_temp1_long[:], twoform_temp2_long[:], twoform_temp3_long[:] = np.split( - tensor_space_FEM.C.dot(input_vector), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]] + twoform_temp1_long[:], twoform_temp2_long[:], twoform_temp3_long[:] = xp.split( + tensor_space_FEM.C.dot(input_vector), + [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) temp_vector_1[:, :, :] = twoform_temp1_long.reshape(Nbase_2form[0]) temp_vector_2[:, :, :] = twoform_temp2_long.reshape(Nbase_2form[1]) @@ -826,7 +827,7 @@ def linearoperator_step3( # ========================= C.T =========================== # time1 = time.time() temp_final = tensor_space_FEM.M1.dot(input_vector) - dt / 2.0 * tensor_space_FEM.C.T.dot( - np.concatenate((temp_vector_1.flatten(), temp_vector_2.flatten(), temp_vector_3.flatten())) + xp.concatenate((temp_vector_1.flatten(), temp_vector_2.flatten(), temp_vector_3.flatten())), ) # time2 = time.time() # print('second_curl_time', time2 - time1) @@ -921,15 +922,16 @@ def linearoperator_right_step3( Ntot_2form = tensor_space_FEM.Ntot_2form Nbase_2form = tensor_space_FEM.Nbase_2form - dft = np.empty((3, 3), dtype=float) - generate_weight1 = np.empty(3, dtype=float) - generate_weight2 = np.empty(3, dtype=float) - generate_weight3 = np.empty(3, dtype=float) + dft = xp.empty((3, 3), dtype=float) + generate_weight1 = xp.empty(3, dtype=float) + generate_weight2 = xp.empty(3, dtype=float) + generate_weight3 = xp.empty(3, dtype=float) # ================================================================== # ========================= C =========================== - twoform_temp1_long[:], twoform_temp2_long[:], twoform_temp3_long[:] = np.split( - tensor_space_FEM.C.dot(input_vector), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]] + twoform_temp1_long[:], twoform_temp2_long[:], twoform_temp3_long[:] = xp.split( + tensor_space_FEM.C.dot(input_vector), + [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) temp_vector_1[:, :, :] = twoform_temp1_long.reshape(Nbase_2form[0]) temp_vector_2[:, :, :] = twoform_temp2_long.reshape(Nbase_2form[1]) @@ -1082,7 +1084,7 @@ def linearoperator_right_step3( # print('final_bb', time2 - time1) # ========================= C.T =========================== temp_final = tensor_space_FEM.M1.dot(input_vector) + dt / 2.0 * tensor_space_FEM.C.T.dot( - np.concatenate((temp_vector_1.flatten(), temp_vector_2.flatten(), temp_vector_3.flatten())) + xp.concatenate((temp_vector_1.flatten(), temp_vector_2.flatten(), temp_vector_3.flatten())), ) return temp_final @@ -1145,8 +1147,9 @@ def substep4_linear_operator( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( - tensor_space_FEM.C.dot(input), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]] + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( + tensor_space_FEM.C.dot(input), + [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) acc.twoform_temp2[:, :, :] = acc.twoform_temp2_long.reshape(Nbase_2form[1]) @@ -1249,12 +1252,12 @@ def substep4_linear_operator( ) acc.oneform_temp_long[:] = spa.linalg.gmres( M1, - np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), + xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), tol=10 ** (-10), M=M1_PRE, )[0] - acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = np.split( + acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = xp.split( spa.linalg.gmres(M1, mat.dot(acc.oneform_temp_long), tol=10 ** (-10), M=M1_PRE)[0], [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]], ) @@ -1359,7 +1362,7 @@ def substep4_linear_operator( ) return M1.dot(input) + dt**2 / 4.0 * tensor_space_FEM.C.T.dot( - np.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())) + xp.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())), ) # ========================================================================================================== @@ -1422,8 +1425,8 @@ def substep4_linear_operator_right( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( - CURL.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( + CURL.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) @@ -1529,13 +1532,13 @@ def substep4_linear_operator_right( acc.oneform_temp_long[:] = mat.dot( spa.linalg.gmres( M1, - np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), + xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), tol=10 ** (-10), M=M1_PRE, - )[0] + )[0], ) - acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = np.split( + acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = xp.split( spa.linalg.gmres(M1, dt**2.0 / 4.0 * acc.oneform_temp_long + dt * vec, tol=10 ** (-10), M=M1_PRE)[0], [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]], ) @@ -1639,8 +1642,8 @@ def substep4_linear_operator_right( tensor_space_FEM.basisD[2], ) - return M1.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))) - CURL.T.dot( - np.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())) + return M1.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))) - CURL.T.dot( + xp.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())), ) # ========================================================================================================== @@ -1792,8 +1795,8 @@ def substep4_pusher_field( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( - CURL.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( + CURL.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) @@ -1898,7 +1901,7 @@ def substep4_pusher_field( return spa.linalg.cg( M1, - np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), + xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), tol=10 ** (-13), M=M1_PRE, )[0] @@ -1960,8 +1963,9 @@ def substep4_localproj_linear_operator( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( - tensor_space_FEM.C.dot(input), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]] + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( + tensor_space_FEM.C.dot(input), + [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) acc.twoform_temp2[:, :, :] = acc.twoform_temp2_long.reshape(Nbase_2form[1]) @@ -2063,9 +2067,9 @@ def substep4_localproj_linear_operator( tensor_space_FEM.basisD[2], ) - acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = np.split( + acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = xp.split( mat.dot( - np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())) + xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), ), [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]], ) @@ -2171,7 +2175,7 @@ def substep4_localproj_linear_operator( ) return M1.dot(input) + dt**2 / 4.0 * tensor_space_FEM.C.T.dot( - np.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())) + xp.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())), ) # ========================================================================================================== @@ -2234,8 +2238,8 @@ def substep4_localproj_linear_operator_right( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( - CURL.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( + CURL.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) @@ -2339,11 +2343,12 @@ def substep4_localproj_linear_operator_right( tensor_space_FEM.basisD[2], ) acc.oneform_temp_long[:] = mat.dot( - np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())) + xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())), ) - acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = np.split( - (dt**2.0 / 4.0 * acc.oneform_temp_long + dt * vec), [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]] + acc.oneform_temp1_long[:], acc.oneform_temp2_long[:], acc.oneform_temp3_long[:] = xp.split( + (dt**2.0 / 4.0 * acc.oneform_temp_long + dt * vec), + [Ntot_1form[0], Ntot_1form[0] + Ntot_1form[1]], ) acc.oneform_temp1[:, :, :] = acc.oneform_temp1_long.reshape(Nbase_1form[0]) @@ -2446,8 +2451,8 @@ def substep4_localproj_linear_operator_right( tensor_space_FEM.basisD[2], ) - return M1.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))) - CURL.T.dot( - np.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())) + return M1.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))) - CURL.T.dot( + xp.concatenate((acc.twoform_temp1.flatten(), acc.twoform_temp2.flatten(), acc.twoform_temp3.flatten())), ) # ========================================================================================================== @@ -2509,8 +2514,8 @@ def substep4_localproj_pusher_field( wts = tensor_space_FEM.wts # global quadrature weights # ========================================== - acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = np.split( - CURL.dot(np.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), + acc.twoform_temp1_long[:], acc.twoform_temp2_long[:], acc.twoform_temp3_long[:] = xp.split( + CURL.dot(xp.concatenate((bb1.flatten(), bb2.flatten(), bb3.flatten()))), [Ntot_2form[0], Ntot_2form[0] + Ntot_2form[1]], ) acc.twoform_temp1[:, :, :] = acc.twoform_temp1_long.reshape(Nbase_2form[0]) @@ -2613,4 +2618,4 @@ def substep4_localproj_pusher_field( tensor_space_FEM.basisD[2], ) - return np.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())) + return xp.concatenate((acc.oneform_temp1.flatten(), acc.oneform_temp2.flatten(), acc.oneform_temp3.flatten())) diff --git a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_vv_kernel.py b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_vv_kernel.py index 7adfaf080..1ef3376dc 100644 --- a/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_vv_kernel.py +++ b/src/struphy/eigenvalue_solvers/legacy/massless_operators/fB_vv_kernel.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp from numpy import empty, exp, floor, zeros import struphy.bsplines.bsplines_kernels as bsp @@ -542,7 +542,8 @@ def piecewise_gather( for q2 in range(n_quad[1]): for q3 in range(n_quad[2]): temp1[0] = (cell_left[0] + il1) / Nel[0] + pts1[ - 0, q1 + 0, + q1, ] # quadrature points in the cell x direction temp4[0] = abs(temp1[0] - eta1) - compact[0] / 2.0 # if > 0, result is 0 @@ -741,7 +742,8 @@ def piecewise_scatter( for q2 in range(n_quad[1]): for q3 in range(n_quad[2]): temp1[0] = (cell_left[0] + il1) / Nel[0] + pts1[ - 0, q1 + 0, + q1, ] # quadrature points in the cell x direction temp4[0] = abs(temp1[0] - eta1) - compact[0] / 2 # if > 0, result is 0 diff --git a/src/struphy/eigenvalue_solvers/legacy/mhd_operators_MF.py b/src/struphy/eigenvalue_solvers/legacy/mhd_operators_MF.py index 6cdfc04b1..560314458 100644 --- a/src/struphy/eigenvalue_solvers/legacy/mhd_operators_MF.py +++ b/src/struphy/eigenvalue_solvers/legacy/mhd_operators_MF.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp import scipy.sparse as spa from struphy.eigenvalue_solvers.projectors_global import Projectors_tensor_3d @@ -107,9 +107,9 @@ def __init__(self, space, eq_MHD): self.pts1_D_2 = self.space.spaces[1].projectors.D_pts self.pts1_D_3 = self.space.spaces[2].projectors.D_pts - # assert np.allclose(self.N_1.toarray(), self.pts0_N_1.toarray(), atol=1e-14) - # assert np.allclose(self.N_2.toarray(), self.pts0_N_2.toarray(), atol=1e-14) - # assert np.allclose(self.N_3.toarray(), self.pts0_N_3.toarray(), atol=1e-14) + # assert xp.allclose(self.N_1.toarray(), self.pts0_N_1.toarray(), atol=1e-14) + # assert xp.allclose(self.N_2.toarray(), self.pts0_N_2.toarray(), atol=1e-14) + # assert xp.allclose(self.N_3.toarray(), self.pts0_N_3.toarray(), atol=1e-14) # ===== call equilibrium_mhd values at the projection points ===== # projection points @@ -210,11 +210,11 @@ def __init__(self, space, eq_MHD): # # Operator A # if self.basis_u == 1: # self.A = spa.linalg.LinearOperator((self.dim_1, self.dim_1), matvec = lambda x : (self.M1.dot(self.W1_dot(x)) + self.transpose_W1_dot(self.M1.dot(x))) / 2 ) - # self.A_mat = spa.csc_matrix(self.A.dot(np.identity(self.dim_1))) + # self.A_mat = spa.csc_matrix(self.A.dot(xp.identity(self.dim_1))) # elif self.basis_u == 2: # self.A = spa.linalg.LinearOperator((self.dim_2, self.dim_2), matvec = lambda x : (self.M2.dot(self.Q2_dot(x)) + self.transpose_Q2_dot(self.M2.dot(x))) / 2 ) - # self.A_mat = spa.csc_matrix(self.A.dot(np.identity(self.dim_2))) + # self.A_mat = spa.csc_matrix(self.A.dot(xp.identity(self.dim_2))) # self.A_inv = spa.linalg.inv(self.A_mat) @@ -228,12 +228,12 @@ def Q1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^1} Returns ---------- - res : np.array + res : xp.array dim R^{N^2} Notes @@ -320,7 +320,7 @@ def Q1_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ================================================================== def transpose_Q1_dot(self, x): @@ -329,12 +329,12 @@ def transpose_Q1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- - res : np.array + res : xp.array dim R^{N^1} Notes @@ -403,7 +403,7 @@ def transpose_Q1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # =================================================================== def W1_dot(self, x): @@ -412,12 +412,12 @@ def W1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^1} Returns ---------- - res : np.array + res : xp.array dim R^{N^1} Notes @@ -482,7 +482,7 @@ def W1_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # =================================================================== def transpose_W1_dot(self, x): @@ -491,12 +491,12 @@ def transpose_W1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^1} Returns ---------- - res : np.array + res : xp.array dim R{N^1} Notes @@ -545,7 +545,7 @@ def transpose_W1_dot(self, x): res_2 = kron_matvec_3d([self.pts0_N_1.T, self.pts1_D_2.T, self.pts0_N_3.T], mat_f_2_c) res_3 = kron_matvec_3d([self.pts0_N_1.T, self.pts0_N_2.T, self.pts1_D_3.T], mat_f_3_c) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def U1_dot(self, x): @@ -554,12 +554,12 @@ def U1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^1} Returns ---------- - res : np.array + res : xp.array dim R^{N^2} Notes @@ -645,7 +645,7 @@ def U1_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_U1_dot(self, x): @@ -654,12 +654,12 @@ def transpose_U1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^2} Returns ---------- - res : np.array + res : xp.array dim R{N^1} Notes @@ -728,7 +728,7 @@ def transpose_U1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def P1_dot(self, x): @@ -737,12 +737,12 @@ def P1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- - res : np.array + res : xp.array dim R^{N^1} Notes @@ -831,7 +831,7 @@ def P1_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_P1_dot(self, x): @@ -840,12 +840,12 @@ def transpose_P1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^1} Returns ---------- - res : np.array + res : xp.array dim R{N^2} Notes @@ -914,7 +914,7 @@ def transpose_P1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def S1_dot(self, x): @@ -923,12 +923,12 @@ def S1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^1} Returns ---------- - res : np.array + res : xp.array dim R^{N^2} Notes @@ -1015,7 +1015,7 @@ def S1_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_S1_dot(self, x): @@ -1024,12 +1024,12 @@ def transpose_S1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^2} Returns ---------- - res : np.array + res : xp.array dim R{N^1} Notes @@ -1098,7 +1098,7 @@ def transpose_S1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # =================================================================== def S10_dot(self, x): @@ -1107,12 +1107,12 @@ def S10_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^1} Returns ---------- - res : np.array + res : xp.array dim R^{N^1} Notes @@ -1178,7 +1178,7 @@ def S10_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # =================================================================== def transpose_S10_dot(self, x): @@ -1187,12 +1187,12 @@ def transpose_S10_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^1} Returns ---------- - res : np.array + res : xp.array dim R{N^1} Notes @@ -1241,7 +1241,7 @@ def transpose_S10_dot(self, x): res_2 = kron_matvec_3d([self.pts0_N_1.T, self.pts1_D_2.T, self.pts0_N_3.T], mat_f_2_c) res_3 = kron_matvec_3d([self.pts0_N_1.T, self.pts0_N_2.T, self.pts1_D_3.T], mat_f_3_c) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ================================================================= def K1_dot(self, x): @@ -1250,12 +1250,12 @@ def K1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^3} Returns ---------- - res : np.array + res : xp.array dim R^{N^3} Notes @@ -1307,7 +1307,7 @@ def transpose_K1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^3} Returns @@ -1350,12 +1350,12 @@ def K10_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^0} Returns ---------- - res : np.array + res : xp.array dim R^{N^0} Notes @@ -1406,7 +1406,7 @@ def transpose_K10_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^0} Returns @@ -1449,12 +1449,12 @@ def T1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^1} Returns ---------- - res : np.array + res : xp.array dim R^{N^1} Notes @@ -1543,7 +1543,7 @@ def T1_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ================================================================= def transpose_T1_dot(self, x): @@ -1552,12 +1552,12 @@ def transpose_T1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^1} Returns ---------- - res : np.array + res : xp.array dim R{N^1} Notes @@ -1626,7 +1626,7 @@ def transpose_T1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ================================================================= def X1_dot(self, x): @@ -1635,13 +1635,13 @@ def X1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^1} Returns ---------- res : list - 3 np.arrays of dim R^{N^0} + 3 xp.arrays of dim R^{N^0} Notes ----- @@ -1718,12 +1718,12 @@ def transpose_X1_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^0 x 3} Returns ---------- - res : np.array + res : xp.array dim R{N^1} Notes @@ -1738,9 +1738,9 @@ def transpose_X1_dot(self, x): # x dim check # x should be R{N^0 * 3} # assert len(x) == self.space.Ntot_0form * 3 - # x_loc_1 = self.space.extract_0(np.split(x,3)[0]) - # x_loc_2 = self.space.extract_0(np.split(x,3)[1]) - # x_loc_3 = self.space.extract_0(np.split(x,3)[2]) + # x_loc_1 = self.space.extract_0(xp.split(x,3)[0]) + # x_loc_2 = self.space.extract_0(xp.split(x,3)[1]) + # x_loc_3 = self.space.extract_0(xp.split(x,3)[2]) # x_loc = list((x_loc_1, x_loc_2, x_loc_3)) x_loc_1 = self.space.extract_0(x[0]) @@ -1794,7 +1794,7 @@ def transpose_X1_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) ######################################## ########## 2-form formulation ########## @@ -1806,12 +1806,12 @@ def Q2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- - res : np.array + res : xp.array dim R^{N^2} Notes @@ -1882,7 +1882,7 @@ def Q2_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_Q2_dot(self, x): @@ -1891,12 +1891,12 @@ def transpose_Q2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^2} Returns ---------- - res : np.array + res : xp.array dim R{N^2} Notes @@ -1946,7 +1946,7 @@ def transpose_Q2_dot(self, x): res_2 = kron_matvec_3d([self.pts1_D_1.T, self.pts0_N_2.T, self.pts1_D_3.T], mat_f_2_c) res_3 = kron_matvec_3d([self.pts1_D_1.T, self.pts1_D_2.T, self.pts0_N_3.T], mat_f_3_c) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def T2_dot(self, x): @@ -1955,12 +1955,12 @@ def T2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- - res : np.array + res : xp.array dim R^{N^1} Notes @@ -2049,7 +2049,7 @@ def T2_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_T2_dot(self, x): @@ -2058,12 +2058,12 @@ def transpose_T2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^1} Returns ---------- - res : np.array + res : xp.array dim R{N^2} Notes @@ -2132,7 +2132,7 @@ def transpose_T2_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def P2_dot(self, x): @@ -2141,12 +2141,12 @@ def P2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- - res : np.array + res : xp.array dim R^{N^2} Notes @@ -2235,7 +2235,7 @@ def P2_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_P2_dot(self, x): @@ -2244,12 +2244,12 @@ def transpose_P2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^2} Returns ---------- - res : np.array + res : xp.array dim R{N^2} Notes @@ -2317,7 +2317,7 @@ def transpose_P2_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def S2_dot(self, x): @@ -2326,12 +2326,12 @@ def S2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- - res : np.array + res : xp.array dim R^{N^2} Notes @@ -2402,7 +2402,7 @@ def S2_dot(self, x): # xi3 : histo(xi1)-histo(xi2)-inter(xi3)-polation. res_3 = self.space.projectors.PI_mat("23", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_S2_dot(self, x): @@ -2411,12 +2411,12 @@ def transpose_S2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^2} Returns ---------- - res : np.array + res : xp.array dim R{N^2} Notes @@ -2466,7 +2466,7 @@ def transpose_S2_dot(self, x): res_2 = kron_matvec_3d([self.pts1_D_1.T, self.pts0_N_2.T, self.pts1_D_3.T], mat_f_2_c) res_3 = kron_matvec_3d([self.pts1_D_1.T, self.pts1_D_2.T, self.pts0_N_3.T], mat_f_3_c) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def K2_dot(self, x): @@ -2475,12 +2475,12 @@ def K2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^3} Returns ---------- - res : np.array + res : xp.array dim R^{N^3} Notes @@ -2532,7 +2532,7 @@ def transpose_K2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^3} Returns @@ -2575,13 +2575,13 @@ def X2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- res : list - 3 np.arrays of dim R^{N^0} + 3 xp.arrays of dim R^{N^0} Notes ----- @@ -2658,12 +2658,12 @@ def transpose_X2_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^0 x 3} Returns ---------- - res : np.array + res : xp.array dim R{N^2} Notes @@ -2678,9 +2678,9 @@ def transpose_X2_dot(self, x): # x dim check # x should be R{N^0 * 3} # assert len(x) == self.space.Ntot_0form * 3 - # x_loc_1 = self.space.extract_0(np.split(x,3)[0]) - # x_loc_2 = self.space.extract_0(np.split(x,3)[1]) - # x_loc_3 = self.space.extract_0(np.split(x,3)[2]) + # x_loc_1 = self.space.extract_0(xp.split(x,3)[0]) + # x_loc_2 = self.space.extract_0(xp.split(x,3)[1]) + # x_loc_3 = self.space.extract_0(xp.split(x,3)[2]) # x_loc = list((x_loc_1, x_loc_2, x_loc_3)) x_loc_1 = self.space.extract_0(x[0]) @@ -2734,7 +2734,7 @@ def transpose_X2_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def Z20_dot(self, x): @@ -2743,12 +2743,12 @@ def Z20_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- - res : np.array + res : xp.array dim R^{N^1} Notes @@ -2835,7 +2835,7 @@ def Z20_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_Z20_dot(self, x): @@ -2844,12 +2844,12 @@ def transpose_Z20_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^2} Returns ---------- - res : np.array + res : xp.array dim R{N^1} Notes @@ -2918,7 +2918,7 @@ def transpose_Z20_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def Y20_dot(self, x): @@ -2927,12 +2927,12 @@ def Y20_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^0} Returns ---------- - res : np.array + res : xp.array dim R^{N^3} Notes @@ -2984,12 +2984,12 @@ def transpose_Y20_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^3} Returns ---------- - res : np.array + res : xp.array dim R{N^0} Notes @@ -3027,12 +3027,12 @@ def S20_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R^{N^2} Returns ---------- - res : np.array + res : xp.array dim R^{N^1} Notes @@ -3119,7 +3119,7 @@ def S20_dot(self, x): # xi3 : inter(xi1)-inter(xi2)-histo(xi3)-polation. res_3 = self.space.projectors.PI_mat("13", DOF_3) - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) # ==================================================================== def transpose_S20_dot(self, x): @@ -3128,12 +3128,12 @@ def transpose_S20_dot(self, x): Parameters ---------- - x : np.array + x : xp.array dim R{N^1} Returns ---------- - res : np.array + res : xp.array dim R{N^2} Notes @@ -3202,4 +3202,4 @@ def transpose_S20_dot(self, x): res_2 = res_12 + res_22 + res_32 res_3 = res_13 + res_23 + res_33 - return np.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) + return xp.concatenate((res_1.flatten(), res_2.flatten(), res_3.flatten())) diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/mhd_operators_3d_local.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/mhd_operators_3d_local.py index aef91b87d..6734a11b0 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/mhd_operators_3d_local.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/mhd_operators_3d_local.py @@ -8,7 +8,7 @@ import sys -import numpy as np +import cunumpy as xp import scipy.sparse as spa import source_run.kernels_projectors_evaluation as ker_eva @@ -44,87 +44,87 @@ def __init__(self, tensor_space, n_quad): self.n_quad = n_quad # number of quadrature point per integration interval # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = [np.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] - self.wts_loc = [np.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] + self.pts_loc = [xp.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] + self.wts_loc = [xp.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] # set interpolation and histopolation coefficients self.coeff_i = [0, 0, 0] self.coeff_h = [0, 0, 0] for a in range(3): - if self.bc[a] == True: - self.coeff_i[a] = np.zeros((1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = np.zeros((1, 2 * self.p[a]), dtype=float) + if self.bc[a]: + self.coeff_i[a] = xp.zeros((1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = xp.zeros((1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = np.array([1.0]) - self.coeff_h[a][0, :] = np.array([1.0, 1.0]) + self.coeff_i[a][0, :] = xp.array([1.0]) + self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) - self.coeff_h[a][0, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_i[a][0, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) + self.coeff_h[a][0, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 6 * np.array([1.0, -8.0, 20.0, -8.0, 1.0]) - self.coeff_h[a][0, :] = 1 / 6 * np.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) + self.coeff_i[a][0, :] = 1 / 6 * xp.array([1.0, -8.0, 20.0, -8.0, 1.0]) + self.coeff_h[a][0, :] = 1 / 6 * xp.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 2 / 45 * np.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) + self.coeff_i[a][0, :] = 2 / 45 * xp.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) self.coeff_h[a][0, :] = ( - 2 / 45 * np.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) + 2 / 45 * xp.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) ) else: print("degree > 4 not implemented!") else: - self.coeff_i[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) + self.coeff_i[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = np.array([1.0]) - self.coeff_h[a][0, :] = np.array([1.0, 1.0]) + self.coeff_i[a][0, :] = xp.array([1.0]) + self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * np.array([2.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 2 * np.array([0.0, 0.0, 2.0]) + self.coeff_i[a][0, :] = 1 / 2 * xp.array([2.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, 2.0]) - self.coeff_h[a][0, :] = 1 / 2 * np.array([3.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) - self.coeff_h[a][2, :] = 1 / 2 * np.array([0.0, 0.0, -1.0, 3.0]) + self.coeff_h[a][0, :] = 1 / 2 * xp.array([3.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_h[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, -1.0, 3.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 18 * np.array([18.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 18 * np.array([-5.0, 40.0, -24.0, 8.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 18 * np.array([3.0, -24.0, 60.0, -24.0, 3.0]) - self.coeff_i[a][3, :] = 1 / 18 * np.array([-1.0, 8.0, -24.0, 40.0, -5.0]) - self.coeff_i[a][4, :] = 1 / 18 * np.array([0.0, 0.0, 0.0, 0.0, 18.0]) - - self.coeff_h[a][0, :] = 1 / 18 * np.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 18 * np.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 18 * np.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) - self.coeff_h[a][3, :] = 1 / 18 * np.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) - self.coeff_h[a][4, :] = 1 / 18 * np.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) + self.coeff_i[a][0, :] = 1 / 18 * xp.array([18.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 18 * xp.array([-5.0, 40.0, -24.0, 8.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 18 * xp.array([3.0, -24.0, 60.0, -24.0, 3.0]) + self.coeff_i[a][3, :] = 1 / 18 * xp.array([-1.0, 8.0, -24.0, 40.0, -5.0]) + self.coeff_i[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, 0.0, 0.0, 18.0]) + + self.coeff_h[a][0, :] = 1 / 18 * xp.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 18 * xp.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 18 * xp.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) + self.coeff_h[a][3, :] = 1 / 18 * xp.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) + self.coeff_h[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 1 / 360 * np.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 360 * np.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) - self.coeff_i[a][2, :] = 1 / 360 * np.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) - self.coeff_i[a][3, :] = 1 / 360 * np.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) - self.coeff_i[a][4, :] = 1 / 360 * np.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) - self.coeff_i[a][5, :] = 1 / 360 * np.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) - self.coeff_i[a][6, :] = 1 / 360 * np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) - - self.coeff_h[a][0, :] = 1 / 360 * np.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 360 * np.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 360 * np.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) + self.coeff_i[a][0, :] = 1 / 360 * xp.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 360 * xp.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) + self.coeff_i[a][2, :] = 1 / 360 * xp.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) + self.coeff_i[a][3, :] = 1 / 360 * xp.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) + self.coeff_i[a][4, :] = 1 / 360 * xp.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) + self.coeff_i[a][5, :] = 1 / 360 * xp.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) + self.coeff_i[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) + + self.coeff_h[a][0, :] = 1 / 360 * xp.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 360 * xp.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 360 * xp.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) self.coeff_h[a][3, :] = ( - 1 / 360 * np.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) + 1 / 360 * xp.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) ) - self.coeff_h[a][4, :] = 1 / 360 * np.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) - self.coeff_h[a][5, :] = 1 / 360 * np.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) - self.coeff_h[a][6, :] = 1 / 360 * np.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) + self.coeff_h[a][4, :] = 1 / 360 * xp.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) + self.coeff_h[a][5, :] = 1 / 360 * xp.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) + self.coeff_h[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) else: print("degree > 4 not implemented!") @@ -150,31 +150,31 @@ def __init__(self, tensor_space, n_quad): ) # number of non-vanishing D bf in interpolation interval (1, 2, 4, 6) self.x_int = [ - np.zeros((n_lambda_int, n_int), dtype=float) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) + xp.zeros((n_lambda_int, n_int), dtype=float) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) ] self.int_global_N = [ - np.zeros((n_lambda_int, n_int_locbf_N), dtype=int) + xp.zeros((n_lambda_int, n_int_locbf_N), dtype=int) for n_lambda_int, n_int_locbf_N in zip(n_lambda_int, self.n_int_locbf_N) ] self.int_global_D = [ - np.zeros((n_lambda_int, n_int_locbf_D), dtype=int) + xp.zeros((n_lambda_int, n_int_locbf_D), dtype=int) for n_lambda_int, n_int_locbf_D in zip(n_lambda_int, self.n_int_locbf_D) ] self.int_loccof_N = [ - np.zeros((n_lambda_int, n_int_locbf_N), dtype=int) + xp.zeros((n_lambda_int, n_int_locbf_N), dtype=int) for n_lambda_int, n_int_locbf_N in zip(n_lambda_int, self.n_int_locbf_N) ] self.int_loccof_D = [ - np.zeros((n_lambda_int, n_int_locbf_D), dtype=int) + xp.zeros((n_lambda_int, n_int_locbf_D), dtype=int) for n_lambda_int, n_int_locbf_D in zip(n_lambda_int, self.n_int_locbf_D) ] self.x_int_indices = [ - np.zeros((n_lambda_int, n_int), dtype=int) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) + xp.zeros((n_lambda_int, n_int), dtype=int) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) ] - self.coeffi_indices = [np.zeros(n_lambda_int, dtype=int) for n_lambda_int in n_lambda_int] + self.coeffi_indices = [xp.zeros(n_lambda_int, dtype=int) for n_lambda_int in n_lambda_int] self.n_int_nvcof_D = [None, None, None] self.n_int_nvcof_N = [None, None, None] @@ -186,7 +186,7 @@ def __init__(self, tensor_space, n_quad): self.int_shift_N = [0, 0, 0] for a in range(3): - if self.bc[a] == False: + if not self.bc[a]: # maximum number of non-vanishing coefficients if self.p[a] == 1: self.n_int_nvcof_D[a] = 2 @@ -197,39 +197,39 @@ def __init__(self, tensor_space, n_quad): self.n_int_nvcof_N[a] = 3 * self.p[a] - 2 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.int_add_D[a] = np.arange(self.n_int[a] - 2) + 1 - self.int_add_N[a] = np.arange(self.n_int[a] - 1) + 1 + self.int_add_D[a] = xp.arange(self.n_int[a] - 2) + 1 + self.int_add_N[a] = xp.arange(self.n_int[a] - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) if self.p[a] == 1: - self.int_shift_D[a] = np.arange(self.NbaseD[a]) + self.int_shift_D[a] = xp.arange(self.NbaseD[a]) else: - self.int_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 2) + self.int_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 2) self.int_shift_D[a][: 2 * self.p[a] - 2] = 0 self.int_shift_D[a][-(2 * self.p[a] - 2) :] = self.int_shift_D[a][-(2 * self.p[a] - 2)] # shift local coefficients --> global coefficients (N) if self.p[a] == 1: - self.int_shift_N[a] = np.arange(self.NbaseN[a]) + self.int_shift_N[a] = xp.arange(self.NbaseN[a]) self.int_shift_N[a][-1] = self.int_shift_N[a][-2] else: - self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) self.int_shift_N[a][: 2 * self.p[a] - 1] = 0 self.int_shift_N[a][-(2 * self.p[a] - 1) :] = self.int_shift_N[a][-(2 * self.p[a] - 1)] - counter_coeffi = np.copy(self.p[a]) + counter_coeffi = xp.copy(self.p[a]) for i in range(n_lambda_int[a]): # left boundary region if i < self.p[a] - 1: - self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) - self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) - self.x_int_indices[a][i] = np.arange(self.n_int[a]) + self.x_int_indices[a][i] = xp.arange(self.n_int[a]) self.coeffi_indices[a][i] = i for j in range(2 * (self.p[a] - 1) + 1): xi = self.p[a] - 1 @@ -240,13 +240,13 @@ def __init__(self, tensor_space, n_quad): # right boundary region elif i > n_lambda_int[a] - self.p[a]: self.int_global_N[a][i] = ( - np.arange(self.n_int_locbf_N[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) + xp.arange(self.n_int_locbf_N[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) self.int_global_D[a][i] = ( - np.arange(self.n_int_locbf_D[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) + xp.arange(self.n_int_locbf_D[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) - self.x_int_indices[a][i] = np.arange(self.n_int[a]) + 2 * ( + self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + 2 * ( n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) self.coeffi_indices[a][i] = counter_coeffi @@ -260,20 +260,20 @@ def __init__(self, tensor_space, n_quad): # interior else: if self.p[a] == 1: - self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + i - self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + i + self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + i + self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + i self.int_global_N[a][-1] = self.int_global_N[a][-2] self.int_global_D[a][-1] = self.int_global_D[a][-2] else: - self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1) - self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1) + self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1) + self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1) if self.p[a] == 1: self.x_int_indices[a][i] = i else: - self.x_int_indices[a][i] = np.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1)) + self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1)) self.coeffi_indices[a][i] = self.p[a] - 1 @@ -284,8 +284,8 @@ def __init__(self, tensor_space, n_quad): # local coefficient index if self.p[a] == 1: - self.int_loccof_N[a][i] = np.array([0, 1]) - self.int_loccof_D[a][-1] = np.array([1]) + self.int_loccof_N[a][i] = xp.array([0, 1]) + self.int_loccof_D[a][-1] = xp.array([1]) else: if i > 0: @@ -293,8 +293,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.int_global_D[a][i, il] bol = k_glob_new == self.int_global_D[a][i - 1] - if np.any(bol): - self.int_loccof_D[a][i, il] = self.int_loccof_D[a][i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.int_loccof_D[a][i, il] = self.int_loccof_D[a][i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int[a] - self.p[a] - (self.p[a] - 2)) and ( self.int_loccof_D[a][i, il] == 0 @@ -306,8 +306,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.int_global_N[a][i, il] bol = k_glob_new == self.int_global_N[a][i - 1] - if np.any(bol): - self.int_loccof_N[a][i, il] = self.int_loccof_N[a][i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.int_loccof_N[a][i, il] = self.int_loccof_N[a][i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int[a] - self.p[a] - (self.p[a] - 2)) and ( self.int_loccof_N[a][i, il] == 0 @@ -327,24 +327,24 @@ def __init__(self, tensor_space, n_quad): # shift local coefficients --> global coefficients if self.p[a] == 1: - self.int_shift_D[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) - self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a]) + self.int_shift_D[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a]) else: - self.int_shift_D[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 2) - self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_D[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 2) + self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) for i in range(n_lambda_int[a]): # global indices of non-vanishing basis functions and position of coefficients in final matrix - self.int_global_N[a][i] = (np.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] - self.int_global_D[a][i] = (np.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] + self.int_global_N[a][i] = (xp.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] + self.int_global_D[a][i] = (xp.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] - self.int_loccof_N[a][i] = np.arange(self.n_int_locbf_N[a] - 1, -1, -1) - self.int_loccof_D[a][i] = np.arange(self.n_int_locbf_D[a] - 1, -1, -1) + self.int_loccof_N[a][i] = xp.arange(self.n_int_locbf_N[a] - 1, -1, -1) + self.int_loccof_D[a][i] = xp.arange(self.n_int_locbf_D[a] - 1, -1, -1) if self.p[a] == 1: self.x_int_indices[a][i] = i else: - self.x_int_indices[a][i] = (np.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1))) % ( + self.x_int_indices[a][i] = (xp.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1))) % ( 2 * self.Nel[a] ) @@ -356,41 +356,41 @@ def __init__(self, tensor_space, n_quad): ) % 1.0 # identify unique interpolation points to save memory - self.x_int[a] = np.unique(self.x_int[a].flatten()) + self.x_int[a] = xp.unique(self.x_int[a].flatten()) # set histopolation points, quadrature points and weights - n_lambda_his = [np.copy(NbaseD) for NbaseD in self.NbaseD] # number of coefficients in space V1 + n_lambda_his = [xp.copy(NbaseD) for NbaseD in self.NbaseD] # number of coefficients in space V1 self.n_his = [2 * p for p in self.p] # number of histopolation intervals self.n_his_locbf_N = [2 * p for p in self.p] # number of non-vanishing N bf in histopolation interval self.n_his_locbf_D = [2 * p - 1 for p in self.p] # number of non-vanishing D bf in histopolation interval self.x_his = [ - np.zeros((n_lambda_his, n_his + 1), dtype=float) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) + xp.zeros((n_lambda_his, n_his + 1), dtype=float) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) ] self.his_global_N = [ - np.zeros((n_lambda_his, n_his_locbf_N), dtype=int) + xp.zeros((n_lambda_his, n_his_locbf_N), dtype=int) for n_lambda_his, n_his_locbf_N in zip(n_lambda_his, self.n_his_locbf_N) ] self.his_global_D = [ - np.zeros((n_lambda_his, n_his_locbf_D), dtype=int) + xp.zeros((n_lambda_his, n_his_locbf_D), dtype=int) for n_lambda_his, n_his_locbf_D in zip(n_lambda_his, self.n_his_locbf_D) ] self.his_loccof_N = [ - np.zeros((n_lambda_his, n_his_locbf_N), dtype=int) + xp.zeros((n_lambda_his, n_his_locbf_N), dtype=int) for n_lambda_his, n_his_locbf_N in zip(n_lambda_his, self.n_his_locbf_N) ] self.his_loccof_D = [ - np.zeros((n_lambda_his, n_his_locbf_D), dtype=int) + xp.zeros((n_lambda_his, n_his_locbf_D), dtype=int) for n_lambda_his, n_his_locbf_D in zip(n_lambda_his, self.n_his_locbf_D) ] self.x_his_indices = [ - np.zeros((n_lambda_his, n_his), dtype=int) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) + xp.zeros((n_lambda_his, n_his), dtype=int) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) ] - self.coeffh_indices = [np.zeros(n_lambda_his, dtype=int) for n_lambda_his in n_lambda_his] + self.coeffh_indices = [xp.zeros(n_lambda_his, dtype=int) for n_lambda_his in n_lambda_his] self.pts = [0, 0, 0] self.wts = [0, 0, 0] @@ -405,37 +405,37 @@ def __init__(self, tensor_space, n_quad): self.his_shift_N = [0, 0, 0] for a in range(3): - if self.bc[a] == False: + if not self.bc[a]: # maximum number of non-vanishing coefficients self.n_his_nvcof_D[a] = 3 * self.p[a] - 2 self.n_his_nvcof_N[a] = 3 * self.p[a] - 1 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.his_add_D[a] = np.arange(self.n_his[a] - 2) + 1 - self.his_add_N[a] = np.arange(self.n_his[a] - 1) + 1 + self.his_add_D[a] = xp.arange(self.n_his[a] - 2) + 1 + self.his_add_N[a] = xp.arange(self.n_his[a] - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) - self.his_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 1) + self.his_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 1) self.his_shift_D[a][: 2 * self.p[a] - 1] = 0 self.his_shift_D[a][-(2 * self.p[a] - 1) :] = self.his_shift_D[a][-(2 * self.p[a] - 1)] # shift local coefficients --> global coefficients (N) - self.his_shift_N[a] = np.arange(self.NbaseN[a]) - self.p[a] + self.his_shift_N[a] = xp.arange(self.NbaseN[a]) - self.p[a] self.his_shift_N[a][: 2 * self.p[a]] = 0 self.his_shift_N[a][-2 * self.p[a] :] = self.his_shift_N[a][-2 * self.p[a]] - counter_coeffh = np.copy(self.p[a]) + counter_coeffh = xp.copy(self.p[a]) for i in range(n_lambda_his[a]): # left boundary region if i < self.p[a] - 1: - self.his_global_N[a][i] = np.arange(self.n_his_locbf_N[a]) - self.his_global_D[a][i] = np.arange(self.n_his_locbf_D[a]) + self.his_global_N[a][i] = xp.arange(self.n_his_locbf_N[a]) + self.his_global_D[a][i] = xp.arange(self.n_his_locbf_D[a]) - self.x_his_indices[a][i] = np.arange(self.n_his[a]) + self.x_his_indices[a][i] = xp.arange(self.n_his[a]) self.coeffh_indices[a][i] = i for j in range(2 * self.p[a] + 1): xi = self.p[a] - 1 @@ -446,13 +446,13 @@ def __init__(self, tensor_space, n_quad): # right boundary region elif i > n_lambda_his[a] - self.p[a]: self.his_global_N[a][i] = ( - np.arange(self.n_his_locbf_N[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) + xp.arange(self.n_his_locbf_N[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) self.his_global_D[a][i] = ( - np.arange(self.n_his_locbf_D[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) + xp.arange(self.n_his_locbf_D[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) - self.x_his_indices[a][i] = np.arange(self.n_his[a]) + 2 * ( + self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + 2 * ( n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) self.coeffh_indices[a][i] = counter_coeffh @@ -465,10 +465,10 @@ def __init__(self, tensor_space, n_quad): # interior else: - self.his_global_N[a][i] = np.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1) - self.his_global_D[a][i] = np.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1) + self.his_global_N[a][i] = xp.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1) + self.his_global_D[a][i] = xp.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1) - self.x_his_indices[a][i] = np.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1)) + self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1)) self.coeffh_indices[a][i] = self.p[a] - 1 for j in range(2 * self.p[a] + 1): self.x_his[a][i, j] = ( @@ -481,8 +481,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.his_global_D[a][i, il] bol = k_glob_new == self.his_global_D[a][i - 1] - if np.any(bol): - self.his_loccof_D[a][i, il] = self.his_loccof_D[a][i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.his_loccof_D[a][i, il] = self.his_loccof_D[a][i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his[a] - self.p[a] - (self.p[a] - 2)) and ( self.his_loccof_D[a][i, il] == 0 @@ -494,8 +494,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.his_global_N[a][i, il] bol = k_glob_new == self.his_global_N[a][i - 1] - if np.any(bol): - self.his_loccof_N[a][i, il] = self.his_loccof_N[a][i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.his_loccof_N[a][i, il] = self.his_loccof_N[a][i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his[a] - self.p[a] - (self.p[a] - 2)) and ( self.his_loccof_N[a][i, il] == 0 @@ -505,7 +505,9 @@ def __init__(self, tensor_space, n_quad): # quadrature points and weights self.pts[a], self.wts[a] = bsp.quadrature_grid( - np.unique(self.x_his[a].flatten()), self.pts_loc[a], self.wts_loc[a] + xp.unique(self.x_his[a].flatten()), + self.pts_loc[a], + self.wts_loc[a], ) else: @@ -514,18 +516,18 @@ def __init__(self, tensor_space, n_quad): self.n_his_nvcof_N[a] = 2 * self.p[a] # shift local coefficients --> global coefficients (D) - self.his_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 1) + self.his_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 1) # shift local coefficients --> global coefficients (N) - self.his_shift_N[a] = np.arange(self.NbaseD[a]) - self.p[a] + self.his_shift_N[a] = xp.arange(self.NbaseD[a]) - self.p[a] for i in range(n_lambda_his[a]): - self.his_global_N[a][i] = (np.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] - self.his_global_D[a][i] = (np.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] - self.his_loccof_N[a][i] = np.arange(self.n_his_locbf_N[a] - 1, -1, -1) - self.his_loccof_D[a][i] = np.arange(self.n_his_locbf_D[a] - 1, -1, -1) + self.his_global_N[a][i] = (xp.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] + self.his_global_D[a][i] = (xp.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] + self.his_loccof_N[a][i] = xp.arange(self.n_his_locbf_N[a] - 1, -1, -1) + self.his_loccof_D[a][i] = xp.arange(self.n_his_locbf_D[a] - 1, -1, -1) - self.x_his_indices[a][i] = (np.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1))) % ( + self.x_his_indices[a][i] = (xp.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1))) % ( 2 * self.Nel[a] ) self.coeffh_indices[a][i] = 0 @@ -535,7 +537,9 @@ def __init__(self, tensor_space, n_quad): # quadrature points and weights self.pts[a], self.wts[a] = bsp.quadrature_grid( - np.append(np.unique(self.x_his[a].flatten() % 1.0), 1.0), self.pts_loc[a], self.wts_loc[a] + xp.append(xp.unique(self.x_his[a].flatten() % 1.0), 1.0), + self.pts_loc[a], + self.wts_loc[a], ) # evaluate N basis functions at interpolation and quadrature points @@ -556,7 +560,9 @@ def __init__(self, tensor_space, n_quad): self.basisD_his = [ bsp.collocation_matrix(T[1:-1], p - 1, pts.flatten(), bc, normalize=True).reshape( - pts[:, 0].size, pts[0, :].size, NbaseD + pts[:, 0].size, + pts[0, :].size, + NbaseD, ) for T, p, pts, bc, NbaseD in zip(self.T, self.p, self.pts, self.bc, self.NbaseD) ] @@ -586,7 +592,7 @@ def projection_Q_0form(self, domain): """ # non-vanishing coefficients - Q11 = np.empty( + Q11 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -597,7 +603,7 @@ def projection_Q_0form(self, domain): ), dtype=float, ) - Q22 = np.empty( + Q22 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -608,7 +614,7 @@ def projection_Q_0form(self, domain): ), dtype=float, ) - Q33 = np.empty( + Q33 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -626,7 +632,7 @@ def projection_Q_0form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -682,7 +688,7 @@ def projection_Q_0form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -738,7 +744,7 @@ def projection_Q_0form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -794,7 +800,7 @@ def projection_Q_0form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -802,7 +808,7 @@ def projection_Q_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -819,7 +825,7 @@ def projection_Q_0form(self, domain): Q11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -827,7 +833,7 @@ def projection_Q_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -844,7 +850,7 @@ def projection_Q_0form(self, domain): Q22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -852,7 +858,7 @@ def projection_Q_0form(self, domain): self.n_his_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -895,7 +901,7 @@ def projection_Q_2form(self, domain): """ # non-vanishing coefficients - Q11 = np.empty( + Q11 = xp.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -906,7 +912,7 @@ def projection_Q_2form(self, domain): ), dtype=float, ) - Q22 = np.empty( + Q22 = xp.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -917,7 +923,7 @@ def projection_Q_2form(self, domain): ), dtype=float, ) - Q33 = np.empty( + Q33 = xp.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -935,7 +941,7 @@ def projection_Q_2form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -991,7 +997,7 @@ def projection_Q_2form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -1047,7 +1053,7 @@ def projection_Q_2form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -1103,7 +1109,7 @@ def projection_Q_2form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -1111,7 +1117,7 @@ def projection_Q_2form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_D[1], self.n_his_nvcof_D[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -1128,7 +1134,7 @@ def projection_Q_2form(self, domain): Q11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -1136,7 +1142,7 @@ def projection_Q_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_D[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -1153,7 +1159,7 @@ def projection_Q_2form(self, domain): Q22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -1161,7 +1167,7 @@ def projection_Q_2form(self, domain): self.n_his_nvcof_D[0], self.n_his_nvcof_D[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1204,7 +1210,7 @@ def projection_W_0form(self, domain): """ # non-vanishing coefficients - W1 = np.empty( + W1 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1215,14 +1221,14 @@ def projection_W_0form(self, domain): ), dtype=float, ) - # W2 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2]), dtype=float) - # W3 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2]), dtype=float) + # W2 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2]), dtype=float) + # W3 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2]), dtype=float) # size of interpolation/quadrature points of the 3 components n_unique = [self.x_int[0].size, self.x_int[1].size, self.x_int[2].size] # assembly - mat_eq = np.empty((n_unique[0], n_unique[1], n_unique[2]), dtype=float) + mat_eq = xp.empty((n_unique[0], n_unique[1], n_unique[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -1284,7 +1290,7 @@ def projection_W_0form(self, domain): """ # conversion to sparse matrix - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1292,7 +1298,7 @@ def projection_W_0form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) # row indices @@ -1345,7 +1351,7 @@ def projection_T_0form(self, domain): """ # non-vanishing coefficients - T12 = np.empty( + T12 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1356,7 +1362,7 @@ def projection_T_0form(self, domain): ), dtype=float, ) - T13 = np.empty( + T13 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1368,7 +1374,7 @@ def projection_T_0form(self, domain): dtype=float, ) - T21 = np.empty( + T21 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1379,7 +1385,7 @@ def projection_T_0form(self, domain): ), dtype=float, ) - T23 = np.empty( + T23 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1391,7 +1397,7 @@ def projection_T_0form(self, domain): dtype=float, ) - T31 = np.empty( + T31 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1402,7 +1408,7 @@ def projection_T_0form(self, domain): ), dtype=float, ) - T32 = np.empty( + T32 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1420,7 +1426,7 @@ def projection_T_0form(self, domain): n_unique3 = [self.x_int[0].size, self.x_int[1].size, self.pts[2].flatten().size] # ================= assembly of 1 - component (pi1_1 : his, int, int) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -1515,7 +1521,7 @@ def projection_T_0form(self, domain): ) # ================= assembly of 2 - component (PI_1_2 : int, his, int) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -1621,7 +1627,7 @@ def projection_T_0form(self, domain): ) # ================= assembly of 3 - component (PI_1_3 : int, int, his) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -1727,7 +1733,7 @@ def projection_T_0form(self, domain): ) # conversion to sparse matrices (1 - component) - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1735,7 +1741,7 @@ def projection_T_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1751,7 +1757,7 @@ def projection_T_0form(self, domain): ) T12.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1759,7 +1765,7 @@ def projection_T_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1776,7 +1782,7 @@ def projection_T_0form(self, domain): T13.eliminate_zeros() # conversion to sparse matrices (2 - component) - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1784,7 +1790,7 @@ def projection_T_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1800,7 +1806,7 @@ def projection_T_0form(self, domain): ) T21.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1808,7 +1814,7 @@ def projection_T_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1825,7 +1831,7 @@ def projection_T_0form(self, domain): T23.eliminate_zeros() # conversion to sparse matrices (3 - component) - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1833,7 +1839,7 @@ def projection_T_0form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1849,7 +1855,7 @@ def projection_T_0form(self, domain): ) T31.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -1857,7 +1863,7 @@ def projection_T_0form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -1900,7 +1906,7 @@ def projection_T_1form(self, domain): """ # non-vanishing coefficients - T12 = np.empty( + T12 = xp.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -1911,7 +1917,7 @@ def projection_T_1form(self, domain): ), dtype=float, ) - T13 = np.empty( + T13 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1923,7 +1929,7 @@ def projection_T_1form(self, domain): dtype=float, ) - T21 = np.empty( + T21 = xp.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -1934,7 +1940,7 @@ def projection_T_1form(self, domain): ), dtype=float, ) - T23 = np.empty( + T23 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -1946,7 +1952,7 @@ def projection_T_1form(self, domain): dtype=float, ) - T31 = np.empty( + T31 = xp.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -1957,7 +1963,7 @@ def projection_T_1form(self, domain): ), dtype=float, ) - T32 = np.empty( + T32 = xp.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -1975,7 +1981,7 @@ def projection_T_1form(self, domain): n_unique3 = [self.x_int[0].size, self.x_int[1].size, self.pts[2].flatten().size] # ================= assembly of 1 - component (pi1_1 : his, int, int) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -2070,7 +2076,7 @@ def projection_T_1form(self, domain): ) # ================= assembly of 2 - component (PI_1_2 : int, his, int) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -2165,7 +2171,7 @@ def projection_T_1form(self, domain): ) # ================= assembly of 3 - component (PI_1_3 : int, int, his) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -2260,7 +2266,7 @@ def projection_T_1form(self, domain): ) # conversion to sparse matrices (1 - component) - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -2268,7 +2274,7 @@ def projection_T_1form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_D[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2284,7 +2290,7 @@ def projection_T_1form(self, domain): ) T12.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -2292,7 +2298,7 @@ def projection_T_1form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_int_nvcof_D[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2309,7 +2315,7 @@ def projection_T_1form(self, domain): T13.eliminate_zeros() # conversion to sparse matrices (2 - component) - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -2317,7 +2323,7 @@ def projection_T_1form(self, domain): self.n_int_nvcof_D[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2333,7 +2339,7 @@ def projection_T_1form(self, domain): ) T21.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -2341,7 +2347,7 @@ def projection_T_1form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_D[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2358,7 +2364,7 @@ def projection_T_1form(self, domain): T23.eliminate_zeros() # conversion to sparse matrices (3 - component) - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -2366,7 +2372,7 @@ def projection_T_1form(self, domain): self.n_int_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2382,7 +2388,7 @@ def projection_T_1form(self, domain): ) T31.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -2390,7 +2396,7 @@ def projection_T_1form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_D[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2433,7 +2439,7 @@ def projection_T_2form(self, domain): """ # non-vanishing coefficients - T12 = np.empty( + T12 = xp.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -2444,7 +2450,7 @@ def projection_T_2form(self, domain): ), dtype=float, ) - T13 = np.empty( + T13 = xp.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -2456,7 +2462,7 @@ def projection_T_2form(self, domain): dtype=float, ) - T21 = np.empty( + T21 = xp.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -2467,7 +2473,7 @@ def projection_T_2form(self, domain): ), dtype=float, ) - T23 = np.empty( + T23 = xp.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -2479,7 +2485,7 @@ def projection_T_2form(self, domain): dtype=float, ) - T31 = np.empty( + T31 = xp.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -2490,7 +2496,7 @@ def projection_T_2form(self, domain): ), dtype=float, ) - T32 = np.empty( + T32 = xp.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -2508,7 +2514,7 @@ def projection_T_2form(self, domain): n_unique3 = [self.x_int[0].size, self.x_int[1].size, self.pts[2].flatten().size] # ================= assembly of 1 - component (pi1_1 : his, int, int) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -2603,7 +2609,7 @@ def projection_T_2form(self, domain): ) # ================= assembly of 2 - component (PI_1_2 : int, his, int) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -2698,7 +2704,7 @@ def projection_T_2form(self, domain): ) # ================= assembly of 3 - component (PI_1_3 : int, int, his) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -2793,7 +2799,7 @@ def projection_T_2form(self, domain): ) # ============== conversion to sparse matrices (1 - component) ============== - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -2801,7 +2807,7 @@ def projection_T_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_N[1], self.n_int_nvcof_D[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2817,7 +2823,7 @@ def projection_T_2form(self, domain): ) T12.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -2825,7 +2831,7 @@ def projection_T_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_D[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2842,7 +2848,7 @@ def projection_T_2form(self, domain): T13.eliminate_zeros() # ============== conversion to sparse matrices (2 - component) ============== - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -2850,7 +2856,7 @@ def projection_T_2form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_D[1], self.n_int_nvcof_D[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2866,7 +2872,7 @@ def projection_T_2form(self, domain): ) T21.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -2874,7 +2880,7 @@ def projection_T_2form(self, domain): self.n_int_nvcof_D[0], self.n_his_nvcof_D[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -2891,7 +2897,7 @@ def projection_T_2form(self, domain): T23.eliminate_zeros() # ============== conversion to sparse matrices (3 - component) ============== - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -2899,7 +2905,7 @@ def projection_T_2form(self, domain): self.n_int_nvcof_N[0], self.n_int_nvcof_D[1], self.n_his_nvcof_D[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2915,7 +2921,7 @@ def projection_T_2form(self, domain): ) T31.eliminate_zeros() - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -2923,7 +2929,7 @@ def projection_T_2form(self, domain): self.n_int_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_D[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -2966,7 +2972,7 @@ def projection_S_0form(self, domain): """ # non-vanishing coefficients - S11 = np.empty( + S11 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -2977,7 +2983,7 @@ def projection_S_0form(self, domain): ), dtype=float, ) - S22 = np.empty( + S22 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -2988,7 +2994,7 @@ def projection_S_0form(self, domain): ), dtype=float, ) - S33 = np.empty( + S33 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -3006,7 +3012,7 @@ def projection_S_0form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -3062,7 +3068,7 @@ def projection_S_0form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3118,7 +3124,7 @@ def projection_S_0form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3174,7 +3180,7 @@ def projection_S_0form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3182,7 +3188,7 @@ def projection_S_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3199,7 +3205,7 @@ def projection_S_0form(self, domain): S11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3207,7 +3213,7 @@ def projection_S_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3224,7 +3230,7 @@ def projection_S_0form(self, domain): S22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3232,7 +3238,7 @@ def projection_S_0form(self, domain): self.n_his_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3275,7 +3281,7 @@ def projection_S_2form(self, domain): """ # non-vanishing coefficients - S11 = np.empty( + S11 = xp.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -3286,7 +3292,7 @@ def projection_S_2form(self, domain): ), dtype=float, ) - S22 = np.empty( + S22 = xp.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -3297,7 +3303,7 @@ def projection_S_2form(self, domain): ), dtype=float, ) - S33 = np.empty( + S33 = xp.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -3315,7 +3321,7 @@ def projection_S_2form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -3371,7 +3377,7 @@ def projection_S_2form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3427,7 +3433,7 @@ def projection_S_2form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3483,7 +3489,7 @@ def projection_S_2form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -3491,7 +3497,7 @@ def projection_S_2form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_D[1], self.n_his_nvcof_D[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -3508,7 +3514,7 @@ def projection_S_2form(self, domain): S11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -3516,7 +3522,7 @@ def projection_S_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_D[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -3533,7 +3539,7 @@ def projection_S_2form(self, domain): S22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -3541,7 +3547,7 @@ def projection_S_2form(self, domain): self.n_his_nvcof_D[0], self.n_his_nvcof_D[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3582,7 +3588,7 @@ def projection_K_3form(self, domain): """ # non-vanishing coefficients - K = np.zeros( + K = xp.zeros( ( self.NbaseD[0], self.NbaseD[1], @@ -3597,7 +3603,7 @@ def projection_K_3form(self, domain): # evaluation of equilibrium pressure at interpolation points n_unique = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.pts[2].flatten().size] - mat_eq = np.zeros((n_unique[0], n_unique[1], n_unique[2]), dtype=float) + mat_eq = xp.zeros((n_unique[0], n_unique[1], n_unique[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3656,7 +3662,7 @@ def projection_K_3form(self, domain): ) # conversion to sparse matrix - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -3664,7 +3670,7 @@ def projection_K_3form(self, domain): self.n_his_nvcof_D[0], self.n_his_nvcof_D[1], self.n_his_nvcof_D[2], - ) + ), ) # row indices @@ -3711,7 +3717,7 @@ def projection_N_0form(self, domain): """ # non-vanishing coefficients - N11 = np.empty( + N11 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -3722,7 +3728,7 @@ def projection_N_0form(self, domain): ), dtype=float, ) - N22 = np.empty( + N22 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -3733,7 +3739,7 @@ def projection_N_0form(self, domain): ), dtype=float, ) - N33 = np.empty( + N33 = xp.empty( ( self.NbaseN[0], self.NbaseN[1], @@ -3751,7 +3757,7 @@ def projection_N_0form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -3807,7 +3813,7 @@ def projection_N_0form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3863,7 +3869,7 @@ def projection_N_0form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -3919,7 +3925,7 @@ def projection_N_0form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3927,7 +3933,7 @@ def projection_N_0form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3944,7 +3950,7 @@ def projection_N_0form(self, domain): N11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3952,7 +3958,7 @@ def projection_N_0form(self, domain): self.n_his_nvcof_N[0], self.n_int_nvcof_N[1], self.n_his_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -3969,7 +3975,7 @@ def projection_N_0form(self, domain): N22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseN[1], @@ -3977,7 +3983,7 @@ def projection_N_0form(self, domain): self.n_his_nvcof_N[0], self.n_his_nvcof_N[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -4020,7 +4026,7 @@ def projection_N_2form(self, domain): """ # non-vanishing coefficients - N11 = np.empty( + N11 = xp.empty( ( self.NbaseN[0], self.NbaseD[1], @@ -4031,7 +4037,7 @@ def projection_N_2form(self, domain): ), dtype=float, ) - N22 = np.empty( + N22 = xp.empty( ( self.NbaseD[0], self.NbaseN[1], @@ -4042,7 +4048,7 @@ def projection_N_2form(self, domain): ), dtype=float, ) - N33 = np.empty( + N33 = xp.empty( ( self.NbaseD[0], self.NbaseD[1], @@ -4060,7 +4066,7 @@ def projection_N_2form(self, domain): n_unique3 = [self.pts[0].flatten().size, self.pts[1].flatten().size, self.x_int[2].size] # ========= assembly of 1 - component (pi2_1 : int, his, his) ============ - mat_eq = np.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) + mat_eq = xp.empty((n_unique1[0], n_unique1[1], n_unique1[2]), dtype=float) ker_eva.kernel_eva( self.x_int[0], @@ -4108,7 +4114,7 @@ def projection_N_2form(self, domain): ) # ========= assembly of 2 - component (pi2_2 : his, int, his) ============ - mat_eq = np.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) + mat_eq = xp.empty((n_unique2[0], n_unique2[1], n_unique2[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -4156,7 +4162,7 @@ def projection_N_2form(self, domain): ) # ========= assembly of 3 - component (pi2_3 : his, his, int) ============ - mat_eq = np.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) + mat_eq = xp.empty((n_unique3[0], n_unique3[1], n_unique3[2]), dtype=float) ker_eva.kernel_eva( self.pts[0].flatten(), @@ -4204,7 +4210,7 @@ def projection_N_2form(self, domain): ) # ========= conversion to sparse matrices (1 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseN[0], self.NbaseD[1], @@ -4212,7 +4218,7 @@ def projection_N_2form(self, domain): self.n_int_nvcof_N[0], self.n_his_nvcof_D[1], self.n_his_nvcof_D[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -4229,7 +4235,7 @@ def projection_N_2form(self, domain): N11.eliminate_zeros() # ========= conversion to sparse matrices (2 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseN[1], @@ -4237,7 +4243,7 @@ def projection_N_2form(self, domain): self.n_his_nvcof_D[0], self.n_int_nvcof_N[1], self.n_his_nvcof_D[2], - ) + ), ) row = self.NbaseN[1] * self.NbaseD[2] * indices[0] + self.NbaseD[2] * indices[1] + indices[2] @@ -4254,7 +4260,7 @@ def projection_N_2form(self, domain): N22.eliminate_zeros() # ========= conversion to sparse matrices (3 - component) ================= - indices = np.indices( + indices = xp.indices( ( self.NbaseD[0], self.NbaseD[1], @@ -4262,7 +4268,7 @@ def projection_N_2form(self, domain): self.n_his_nvcof_D[0], self.n_his_nvcof_D[1], self.n_int_nvcof_N[2], - ) + ), ) row = self.NbaseD[1] * self.NbaseN[2] * indices[0] + self.NbaseN[2] * indices[1] + indices[2] @@ -4323,7 +4329,15 @@ class term_curl_beq: """ def __init__( - self, tensor_space, mapping, kind_map=None, params_map=None, tensor_space_F=None, cx=None, cy=None, cz=None + self, + tensor_space, + mapping, + kind_map=None, + params_map=None, + tensor_space_F=None, + cx=None, + cy=None, + cz=None, ): self.p = tensor_space.p # spline degrees self.Nel = tensor_space.Nel # number of elements @@ -4356,14 +4370,17 @@ def __init__( self.cz = cz # ============= evaluation of background magnetic field at quadrature points ========= - self.mat_curl_beq_1 = np.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float + self.mat_curl_beq_1 = xp.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), + dtype=float, ) - self.mat_curl_beq_2 = np.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float + self.mat_curl_beq_2 = xp.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), + dtype=float, ) - self.mat_curl_beq_3 = np.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float + self.mat_curl_beq_3 = xp.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), + dtype=float, ) if mapping == 0: @@ -4454,20 +4471,23 @@ def __init__( ) # ====================== perturbed magnetic field at quadrature points ========== - self.B1 = np.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float + self.B1 = xp.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), + dtype=float, ) - self.B2 = np.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float + self.B2 = xp.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), + dtype=float, ) - self.B3 = np.empty( - (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), dtype=float + self.B3 = xp.empty( + (self.Nel[0], self.Nel[1], self.Nel[2], self.n_quad[0], self.n_quad[1], self.n_quad[2]), + dtype=float, ) # ========================== inner products ===================================== - self.F1 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) - self.F2 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) - self.F3 = np.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + self.F1 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + self.F2 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + self.F3 = xp.empty((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) # ============================================================ def inner_curl_beq(self, b1, b2, b3): @@ -4598,7 +4618,7 @@ def inner_curl_beq(self, b1, b2, b3): # ker_loc_3d.kernel_inner_2(self.Nel[0], self.Nel[1], self.Nel[2], self.p[0], self.p[1], self.p[2], self.n_quad[0], self.n_quad[1], self.n_quad[2], 0, 0, 0, self.wts[0], self.wts[1], self.wts[2], self.basisN[0], self.basisN[1], self.basisN[2], self.NbaseN[0], self.NbaseN[1], self.NbaseN[2], self.F3, self.mat_curl_beq_3) # convert to 1d array and return - return np.concatenate((self.F1.flatten(), self.F2.flatten(), self.F3.flatten())) + return xp.concatenate((self.F1.flatten(), self.F2.flatten(), self.F3.flatten())) # ================ mass matrix in V1 =========================== @@ -4641,9 +4661,9 @@ def mass_curl(tensor_space, kind_map, params_map): Nbj3 = [NbaseD[2], NbaseN[2], NbaseD[2], NbaseN[2], NbaseD[2], NbaseD[2]] # ============= evaluation of background magnetic field at quadrature points ========= - mat_curl_beq_1 = np.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) - mat_curl_beq_2 = np.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) - mat_curl_beq_3 = np.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) + mat_curl_beq_1 = xp.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) + mat_curl_beq_2 = xp.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) + mat_curl_beq_3 = xp.empty((Nel[0], Nel[1], Nel[2], n_quad[0], n_quad[1], n_quad[2]), dtype=float) ker_eva.kernel_eva_quad(Nel, n_quad, pts[0], pts[1], pts[2], mat_curl_beq_1, 61, kind_map, params_map) ker_eva.kernel_eva_quad(Nel, n_quad, pts[0], pts[1], pts[2], mat_curl_beq_2, 62, kind_map, params_map) @@ -4652,7 +4672,7 @@ def mass_curl(tensor_space, kind_map, params_map): # blocks of global mass matrix M = [ - np.zeros((Nbi1, Nbi2, Nbi3, 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + xp.zeros((Nbi1, Nbi2, Nbi3, 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) for Nbi1, Nbi2, Nbi3 in zip(Nbi1, Nbi2, Nbi3) ] @@ -4858,11 +4878,11 @@ def mass_curl(tensor_space, kind_map, params_map): counter = 0 for i in range(6): - indices = np.indices((Nbi1[counter], Nbi2[counter], Nbi3[counter], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = xp.indices((Nbi1[counter], Nbi2[counter], Nbi3[counter], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift1 = np.arange(Nbi1[counter]) - p[0] - shift2 = np.arange(Nbi2[counter]) - p[1] - shift3 = np.arange(Nbi3[counter]) - p[2] + shift1 = xp.arange(Nbi1[counter]) - p[0] + shift2 = xp.arange(Nbi2[counter]) - p[1] + shift3 = xp.arange(Nbi3[counter]) - p[2] row = (Nbi2[counter] * Nbi3[counter] * indices[0] + Nbi3[counter] * indices[1] + indices[2]).flatten() diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/projectors_local.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/projectors_local.py index f7f880f2b..9ede3f608 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/projectors_local.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/pro_local/projectors_local.py @@ -6,7 +6,7 @@ Classes for local projectors in 1D and 3D based on quasi-spline interpolation and histopolation. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.feec.bsplines as bsp @@ -41,85 +41,85 @@ def __init__(self, spline_space, n_quad): self.n_quad = n_quad # number of quadrature point per integration interval # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = np.polynomial.legendre.leggauss(self.n_quad)[0] - self.wts_loc = np.polynomial.legendre.leggauss(self.n_quad)[1] + self.pts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[0] + self.wts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[1] # set interpolation and histopolation coefficients - if self.bc == True: - self.coeff_i = np.zeros((1, 2 * self.p - 1), dtype=float) - self.coeff_h = np.zeros((1, 2 * self.p), dtype=float) + if self.bc: + self.coeff_i = xp.zeros((1, 2 * self.p - 1), dtype=float) + self.coeff_h = xp.zeros((1, 2 * self.p), dtype=float) if self.p == 1: - self.coeff_i[0, :] = np.array([1.0]) - self.coeff_h[0, :] = np.array([1.0, 1.0]) + self.coeff_i[0, :] = xp.array([1.0]) + self.coeff_h[0, :] = xp.array([1.0, 1.0]) elif self.p == 2: - self.coeff_i[0, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) - self.coeff_h[0, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_i[0, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) + self.coeff_h[0, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) elif self.p == 3: - self.coeff_i[0, :] = 1 / 6 * np.array([1.0, -8.0, 20.0, -8.0, 1.0]) - self.coeff_h[0, :] = 1 / 6 * np.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) + self.coeff_i[0, :] = 1 / 6 * xp.array([1.0, -8.0, 20.0, -8.0, 1.0]) + self.coeff_h[0, :] = 1 / 6 * xp.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) elif self.p == 4: - self.coeff_i[0, :] = 2 / 45 * np.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) - self.coeff_h[0, :] = 2 / 45 * np.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) + self.coeff_i[0, :] = 2 / 45 * xp.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) + self.coeff_h[0, :] = 2 / 45 * xp.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) else: print("degree > 4 not implemented!") else: - self.coeff_i = np.zeros((2 * self.p - 1, 2 * self.p - 1), dtype=float) - self.coeff_h = np.zeros((2 * self.p - 1, 2 * self.p), dtype=float) + self.coeff_i = xp.zeros((2 * self.p - 1, 2 * self.p - 1), dtype=float) + self.coeff_h = xp.zeros((2 * self.p - 1, 2 * self.p), dtype=float) if self.p == 1: - self.coeff_i[0, :] = np.array([1.0]) - self.coeff_h[0, :] = np.array([1.0, 1.0]) + self.coeff_i[0, :] = xp.array([1.0]) + self.coeff_h[0, :] = xp.array([1.0, 1.0]) elif self.p == 2: - self.coeff_i[0, :] = 1 / 2 * np.array([2.0, 0.0, 0.0]) - self.coeff_i[1, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) - self.coeff_i[2, :] = 1 / 2 * np.array([0.0, 0.0, 2.0]) + self.coeff_i[0, :] = 1 / 2 * xp.array([2.0, 0.0, 0.0]) + self.coeff_i[1, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) + self.coeff_i[2, :] = 1 / 2 * xp.array([0.0, 0.0, 2.0]) - self.coeff_h[0, :] = 1 / 2 * np.array([3.0, -1.0, 0.0, 0.0]) - self.coeff_h[1, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) - self.coeff_h[2, :] = 1 / 2 * np.array([0.0, 0.0, -1.0, 3.0]) + self.coeff_h[0, :] = 1 / 2 * xp.array([3.0, -1.0, 0.0, 0.0]) + self.coeff_h[1, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_h[2, :] = 1 / 2 * xp.array([0.0, 0.0, -1.0, 3.0]) elif self.p == 3: - self.coeff_i[0, :] = 1 / 18 * np.array([18.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[1, :] = 1 / 18 * np.array([-5.0, 40.0, -24.0, 8.0, -1.0]) - self.coeff_i[2, :] = 1 / 18 * np.array([3.0, -24.0, 60.0, -24.0, 3.0]) - self.coeff_i[3, :] = 1 / 18 * np.array([-1.0, 8.0, -24.0, 40.0, -5.0]) - self.coeff_i[4, :] = 1 / 18 * np.array([0.0, 0.0, 0.0, 0.0, 18.0]) - - self.coeff_h[0, :] = 1 / 18 * np.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) - self.coeff_h[1, :] = 1 / 18 * np.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) - self.coeff_h[2, :] = 1 / 18 * np.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) - self.coeff_h[3, :] = 1 / 18 * np.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) - self.coeff_h[4, :] = 1 / 18 * np.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) + self.coeff_i[0, :] = 1 / 18 * xp.array([18.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[1, :] = 1 / 18 * xp.array([-5.0, 40.0, -24.0, 8.0, -1.0]) + self.coeff_i[2, :] = 1 / 18 * xp.array([3.0, -24.0, 60.0, -24.0, 3.0]) + self.coeff_i[3, :] = 1 / 18 * xp.array([-1.0, 8.0, -24.0, 40.0, -5.0]) + self.coeff_i[4, :] = 1 / 18 * xp.array([0.0, 0.0, 0.0, 0.0, 18.0]) + + self.coeff_h[0, :] = 1 / 18 * xp.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) + self.coeff_h[1, :] = 1 / 18 * xp.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) + self.coeff_h[2, :] = 1 / 18 * xp.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) + self.coeff_h[3, :] = 1 / 18 * xp.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) + self.coeff_h[4, :] = 1 / 18 * xp.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) elif self.p == 4: - self.coeff_i[0, :] = 1 / 360 * np.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[1, :] = 1 / 360 * np.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) - self.coeff_i[2, :] = 1 / 360 * np.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) - self.coeff_i[3, :] = 1 / 360 * np.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) - self.coeff_i[4, :] = 1 / 360 * np.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) - self.coeff_i[5, :] = 1 / 360 * np.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) - self.coeff_i[6, :] = 1 / 360 * np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) - - self.coeff_h[0, :] = 1 / 360 * np.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) - self.coeff_h[1, :] = 1 / 360 * np.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) - self.coeff_h[2, :] = 1 / 360 * np.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) - self.coeff_h[3, :] = 1 / 360 * np.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) - self.coeff_h[4, :] = 1 / 360 * np.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) - self.coeff_h[5, :] = 1 / 360 * np.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) - self.coeff_h[6, :] = 1 / 360 * np.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) + self.coeff_i[0, :] = 1 / 360 * xp.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[1, :] = 1 / 360 * xp.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) + self.coeff_i[2, :] = 1 / 360 * xp.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) + self.coeff_i[3, :] = 1 / 360 * xp.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) + self.coeff_i[4, :] = 1 / 360 * xp.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) + self.coeff_i[5, :] = 1 / 360 * xp.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) + self.coeff_i[6, :] = 1 / 360 * xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) + + self.coeff_h[0, :] = 1 / 360 * xp.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) + self.coeff_h[1, :] = 1 / 360 * xp.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) + self.coeff_h[2, :] = 1 / 360 * xp.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) + self.coeff_h[3, :] = 1 / 360 * xp.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) + self.coeff_h[4, :] = 1 / 360 * xp.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) + self.coeff_h[5, :] = 1 / 360 * xp.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) + self.coeff_h[6, :] = 1 / 360 * xp.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) else: print("degree > 4 not implemented!") # set interpolation points - n_lambda_int = np.copy(self.NbaseN) # number of coefficients in space V0 + n_lambda_int = xp.copy(self.NbaseN) # number of coefficients in space V0 self.n_int = 2 * self.p - 1 # number of local interpolation points (1, 3, 5, 7, ...) if self.p == 1: @@ -134,23 +134,25 @@ def __init__(self, spline_space, n_quad): 2 * self.p - 2 ) # number of non-vanishing D bf in interpolation interval (1, 2, 4, 6, ...) - self.x_int = np.zeros((n_lambda_int, self.n_int), dtype=float) # interpolation points for each coeff. + self.x_int = xp.zeros((n_lambda_int, self.n_int), dtype=float) # interpolation points for each coeff. - self.int_global_N = np.zeros( - (n_lambda_int, self.n_int_locbf_N), dtype=int + self.int_global_N = xp.zeros( + (n_lambda_int, self.n_int_locbf_N), + dtype=int, ) # global indices of non-vanishing N bf - self.int_global_D = np.zeros( - (n_lambda_int, self.n_int_locbf_D), dtype=int + self.int_global_D = xp.zeros( + (n_lambda_int, self.n_int_locbf_D), + dtype=int, ) # global indices of non-vanishing D bf - self.int_loccof_N = np.zeros((n_lambda_int, self.n_int_locbf_N), dtype=int) # index of non-vanishing coeff. (N) - self.int_loccof_D = np.zeros((n_lambda_int, self.n_int_locbf_D), dtype=int) # index of non-vanishing coeff. (D) + self.int_loccof_N = xp.zeros((n_lambda_int, self.n_int_locbf_N), dtype=int) # index of non-vanishing coeff. (N) + self.int_loccof_D = xp.zeros((n_lambda_int, self.n_int_locbf_D), dtype=int) # index of non-vanishing coeff. (D) - self.x_int_indices = np.zeros((n_lambda_int, self.n_int), dtype=int) + self.x_int_indices = xp.zeros((n_lambda_int, self.n_int), dtype=int) - self.coeffi_indices = np.zeros(n_lambda_int, dtype=int) + self.coeffi_indices = xp.zeros(n_lambda_int, dtype=int) - if self.bc == False: + if not self.bc: # maximum number of non-vanishing coefficients if self.p == 1: self.n_int_nvcof_D = 2 @@ -160,39 +162,39 @@ def __init__(self, spline_space, n_quad): self.n_int_nvcof_N = 3 * self.p - 2 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.int_add_D = np.arange(self.n_int - 2) + 1 - self.int_add_N = np.arange(self.n_int - 1) + 1 + self.int_add_D = xp.arange(self.n_int - 2) + 1 + self.int_add_N = xp.arange(self.n_int - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) if self.p == 1: - self.int_shift_D = np.arange(self.NbaseD) + self.int_shift_D = xp.arange(self.NbaseD) else: - self.int_shift_D = np.arange(self.NbaseD) - (self.p - 2) + self.int_shift_D = xp.arange(self.NbaseD) - (self.p - 2) self.int_shift_D[: 2 * self.p - 2] = 0 self.int_shift_D[-(2 * self.p - 2) :] = self.int_shift_D[-(2 * self.p - 2)] # shift local coefficients --> global coefficients (N) if self.p == 1: - self.int_shift_N = np.arange(self.NbaseN) + self.int_shift_N = xp.arange(self.NbaseN) self.int_shift_N[-1] = self.int_shift_N[-2] else: - self.int_shift_N = np.arange(self.NbaseN) - (self.p - 1) + self.int_shift_N = xp.arange(self.NbaseN) - (self.p - 1) self.int_shift_N[: 2 * self.p - 1] = 0 self.int_shift_N[-(2 * self.p - 1) :] = self.int_shift_N[-(2 * self.p - 1)] - counter_coeffi = np.copy(self.p) + counter_coeffi = xp.copy(self.p) for i in range(n_lambda_int): # left boundary region if i < self.p - 1: - self.int_global_N[i] = np.arange(self.n_int_locbf_N) - self.int_global_D[i] = np.arange(self.n_int_locbf_D) + self.int_global_N[i] = xp.arange(self.n_int_locbf_N) + self.int_global_D[i] = xp.arange(self.n_int_locbf_D) - self.x_int_indices[i] = np.arange(self.n_int) + self.x_int_indices[i] = xp.arange(self.n_int) self.coeffi_indices[i] = i for j in range(2 * (self.p - 1) + 1): xi = self.p - 1 @@ -200,10 +202,10 @@ def __init__(self, spline_space, n_quad): # right boundary region elif i > n_lambda_int - self.p: - self.int_global_N[i] = np.arange(self.n_int_locbf_N) + n_lambda_int - self.p - (self.p - 1) - self.int_global_D[i] = np.arange(self.n_int_locbf_D) + n_lambda_int - self.p - (self.p - 1) + self.int_global_N[i] = xp.arange(self.n_int_locbf_N) + n_lambda_int - self.p - (self.p - 1) + self.int_global_D[i] = xp.arange(self.n_int_locbf_D) + n_lambda_int - self.p - (self.p - 1) - self.x_int_indices[i] = np.arange(self.n_int) + 2 * (n_lambda_int - self.p - (self.p - 1)) + self.x_int_indices[i] = xp.arange(self.n_int) + 2 * (n_lambda_int - self.p - (self.p - 1)) self.coeffi_indices[i] = counter_coeffi counter_coeffi += 1 for j in range(2 * (self.p - 1) + 1): @@ -213,20 +215,20 @@ def __init__(self, spline_space, n_quad): # interior else: if self.p == 1: - self.int_global_N[i] = np.arange(self.n_int_locbf_N) + i - self.int_global_D[i] = np.arange(self.n_int_locbf_D) + i + self.int_global_N[i] = xp.arange(self.n_int_locbf_N) + i + self.int_global_D[i] = xp.arange(self.n_int_locbf_D) + i self.int_global_N[-1] = self.int_global_N[-2] self.int_global_D[-1] = self.int_global_D[-2] else: - self.int_global_N[i] = np.arange(self.n_int_locbf_N) + i - (self.p - 1) - self.int_global_D[i] = np.arange(self.n_int_locbf_D) + i - (self.p - 1) + self.int_global_N[i] = xp.arange(self.n_int_locbf_N) + i - (self.p - 1) + self.int_global_D[i] = xp.arange(self.n_int_locbf_D) + i - (self.p - 1) if self.p == 1: self.x_int_indices[i] = i else: - self.x_int_indices[i] = np.arange(self.n_int) + 2 * (i - (self.p - 1)) + self.x_int_indices[i] = xp.arange(self.n_int) + 2 * (i - (self.p - 1)) self.coeffi_indices[i] = self.p - 1 for j in range(2 * (self.p - 1) + 1): @@ -234,8 +236,8 @@ def __init__(self, spline_space, n_quad): # local coefficient index if self.p == 1: - self.int_loccof_N[i] = np.array([0, 1]) - self.int_loccof_D[-1] = np.array([1]) + self.int_loccof_N[i] = xp.array([0, 1]) + self.int_loccof_D[-1] = xp.array([1]) else: if i > 0: @@ -243,8 +245,8 @@ def __init__(self, spline_space, n_quad): k_glob_new = self.int_global_D[i, il] bol = k_glob_new == self.int_global_D[i - 1] - if np.any(bol): - self.int_loccof_D[i, il] = self.int_loccof_D[i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.int_loccof_D[i, il] = self.int_loccof_D[i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int - self.p - (self.p - 2)) and (self.int_loccof_D[i, il] == 0): self.int_loccof_D[i, il] = self.int_add_D[counter_D] @@ -254,8 +256,8 @@ def __init__(self, spline_space, n_quad): k_glob_new = self.int_global_N[i, il] bol = k_glob_new == self.int_global_N[i - 1] - if np.any(bol): - self.int_loccof_N[i, il] = self.int_loccof_N[i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.int_loccof_N[i, il] = self.int_loccof_N[i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int - self.p - (self.p - 2)) and (self.int_loccof_N[i, il] == 0): self.int_loccof_N[i, il] = self.int_add_N[counter_N] @@ -273,24 +275,24 @@ def __init__(self, spline_space, n_quad): # shift local coefficients --> global coefficients if self.p == 1: - self.int_shift_D = np.arange(self.NbaseN) - (self.p - 1) - self.int_shift_N = np.arange(self.NbaseN) - (self.p) + self.int_shift_D = xp.arange(self.NbaseN) - (self.p - 1) + self.int_shift_N = xp.arange(self.NbaseN) - (self.p) else: - self.int_shift_D = np.arange(self.NbaseN) - (self.p - 2) - self.int_shift_N = np.arange(self.NbaseN) - (self.p - 1) + self.int_shift_D = xp.arange(self.NbaseN) - (self.p - 2) + self.int_shift_N = xp.arange(self.NbaseN) - (self.p - 1) for i in range(n_lambda_int): # global indices of non-vanishing basis functions and position of coefficients in final matrix - self.int_global_D[i] = (np.arange(self.n_int_locbf_D) + i - (self.p - 1)) % self.NbaseD - self.int_loccof_D[i] = np.arange(self.n_int_locbf_D - 1, -1, -1) + self.int_global_D[i] = (xp.arange(self.n_int_locbf_D) + i - (self.p - 1)) % self.NbaseD + self.int_loccof_D[i] = xp.arange(self.n_int_locbf_D - 1, -1, -1) - self.int_global_N[i] = (np.arange(self.n_int_locbf_N) + i - (self.p - 1)) % self.NbaseN - self.int_loccof_N[i] = np.arange(self.n_int_locbf_N - 1, -1, -1) + self.int_global_N[i] = (xp.arange(self.n_int_locbf_N) + i - (self.p - 1)) % self.NbaseN + self.int_loccof_N[i] = xp.arange(self.n_int_locbf_N - 1, -1, -1) if self.p == 1: self.x_int_indices[i] = i else: - self.x_int_indices[i] = np.arange(self.n_int) + 2 * (i - (self.p - 1)) + self.x_int_indices[i] = xp.arange(self.n_int) + 2 * (i - (self.p - 1)) self.coeffi_indices[i] = 0 @@ -298,55 +300,55 @@ def __init__(self, spline_space, n_quad): self.x_int[i, j] = ((self.T[i + 1 + int(j / 2)] + self.T[i + 1 + int((j + 1) / 2)]) / 2) % 1.0 # set histopolation points, quadrature points and weights - n_lambda_his = np.copy(self.NbaseD) # number of coefficients in space V1 + n_lambda_his = xp.copy(self.NbaseD) # number of coefficients in space V1 self.n_his = 2 * self.p # number of histopolation intervals (2, 4, 6, 8, ...) self.n_his_locbf_N = 2 * self.p # number of non-vanishing N bf in histopolation interval (2, 4, 6, 8, ...) self.n_his_locbf_D = 2 * self.p - 1 # number of non-vanishing D bf in histopolation interval (2, 4, 6, 8, ...) - self.x_his = np.zeros((n_lambda_his, self.n_his + 1), dtype=float) # histopolation boundaries + self.x_his = xp.zeros((n_lambda_his, self.n_his + 1), dtype=float) # histopolation boundaries - self.his_global_N = np.zeros((n_lambda_his, self.n_his_locbf_N), dtype=int) - self.his_global_D = np.zeros((n_lambda_his, self.n_his_locbf_D), dtype=int) + self.his_global_N = xp.zeros((n_lambda_his, self.n_his_locbf_N), dtype=int) + self.his_global_D = xp.zeros((n_lambda_his, self.n_his_locbf_D), dtype=int) - self.his_loccof_N = np.zeros((n_lambda_his, self.n_his_locbf_N), dtype=int) - self.his_loccof_D = np.zeros((n_lambda_his, self.n_his_locbf_D), dtype=int) + self.his_loccof_N = xp.zeros((n_lambda_his, self.n_his_locbf_N), dtype=int) + self.his_loccof_D = xp.zeros((n_lambda_his, self.n_his_locbf_D), dtype=int) - self.x_his_indices = np.zeros((n_lambda_his, self.n_his), dtype=int) + self.x_his_indices = xp.zeros((n_lambda_his, self.n_his), dtype=int) - self.coeffh_indices = np.zeros(n_lambda_his, dtype=int) + self.coeffh_indices = xp.zeros(n_lambda_his, dtype=int) - if self.bc == False: + if not self.bc: # maximum number of non-vanishing coefficients self.n_his_nvcof_D = 3 * self.p - 2 self.n_his_nvcof_N = 3 * self.p - 1 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.his_add_D = np.arange(self.n_his - 2) + 1 - self.his_add_N = np.arange(self.n_his - 1) + 1 + self.his_add_D = xp.arange(self.n_his - 2) + 1 + self.his_add_N = xp.arange(self.n_his - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) - self.his_shift_D = np.arange(self.NbaseD) - (self.p - 1) + self.his_shift_D = xp.arange(self.NbaseD) - (self.p - 1) self.his_shift_D[: 2 * self.p - 1] = 0 self.his_shift_D[-(2 * self.p - 1) :] = self.his_shift_D[-(2 * self.p - 1)] # shift local coefficients --> global coefficients (N) - self.his_shift_N = np.arange(self.NbaseN) - self.p + self.his_shift_N = xp.arange(self.NbaseN) - self.p self.his_shift_N[: 2 * self.p] = 0 self.his_shift_N[-2 * self.p :] = self.his_shift_N[-2 * self.p] - counter_coeffh = np.copy(self.p) + counter_coeffh = xp.copy(self.p) for i in range(n_lambda_his): # left boundary region if i < self.p - 1: - self.his_global_N[i] = np.arange(self.n_his_locbf_N) - self.his_global_D[i] = np.arange(self.n_his_locbf_D) + self.his_global_N[i] = xp.arange(self.n_his_locbf_N) + self.his_global_D[i] = xp.arange(self.n_his_locbf_D) - self.x_his_indices[i] = np.arange(self.n_his) + self.x_his_indices[i] = xp.arange(self.n_his) self.coeffh_indices[i] = i for j in range(2 * self.p + 1): xi = self.p - 1 @@ -354,10 +356,10 @@ def __init__(self, spline_space, n_quad): # right boundary region elif i > n_lambda_his - self.p: - self.his_global_N[i] = np.arange(self.n_his_locbf_N) + n_lambda_his - self.p - (self.p - 1) - self.his_global_D[i] = np.arange(self.n_his_locbf_D) + n_lambda_his - self.p - (self.p - 1) + self.his_global_N[i] = xp.arange(self.n_his_locbf_N) + n_lambda_his - self.p - (self.p - 1) + self.his_global_D[i] = xp.arange(self.n_his_locbf_D) + n_lambda_his - self.p - (self.p - 1) - self.x_his_indices[i] = np.arange(self.n_his) + 2 * (n_lambda_his - self.p - (self.p - 1)) + self.x_his_indices[i] = xp.arange(self.n_his) + 2 * (n_lambda_his - self.p - (self.p - 1)) self.coeffh_indices[i] = counter_coeffh counter_coeffh += 1 for j in range(2 * self.p + 1): @@ -366,10 +368,10 @@ def __init__(self, spline_space, n_quad): # interior else: - self.his_global_N[i] = np.arange(self.n_his_locbf_N) + i - (self.p - 1) - self.his_global_D[i] = np.arange(self.n_his_locbf_D) + i - (self.p - 1) + self.his_global_N[i] = xp.arange(self.n_his_locbf_N) + i - (self.p - 1) + self.his_global_D[i] = xp.arange(self.n_his_locbf_D) + i - (self.p - 1) - self.x_his_indices[i] = np.arange(self.n_his) + 2 * (i - (self.p - 1)) + self.x_his_indices[i] = xp.arange(self.n_his) + 2 * (i - (self.p - 1)) self.coeffh_indices[i] = self.p - 1 for j in range(2 * self.p + 1): self.x_his[i, j] = (self.T[i + 1 + int(j / 2)] + self.T[i + 1 + int((j + 1) / 2)]) / 2 @@ -380,8 +382,8 @@ def __init__(self, spline_space, n_quad): k_glob_new = self.his_global_D[i, il] bol = k_glob_new == self.his_global_D[i - 1] - if np.any(bol): - self.his_loccof_D[i, il] = self.his_loccof_D[i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.his_loccof_D[i, il] = self.his_loccof_D[i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his - self.p - (self.p - 2)) and (self.his_loccof_D[i, il] == 0): self.his_loccof_D[i, il] = self.his_add_D[counter_D] @@ -391,15 +393,15 @@ def __init__(self, spline_space, n_quad): k_glob_new = self.his_global_N[i, il] bol = k_glob_new == self.his_global_N[i - 1] - if np.any(bol): - self.his_loccof_N[i, il] = self.his_loccof_N[i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.his_loccof_N[i, il] = self.his_loccof_N[i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his - self.p - (self.p - 2)) and (self.his_loccof_N[i, il] == 0): self.his_loccof_N[i, il] = self.his_add_N[counter_N] counter_N += 1 # quadrature points and weights - self.pts, self.wts = bsp.quadrature_grid(np.unique(self.x_his.flatten()), self.pts_loc, self.wts_loc) + self.pts, self.wts = bsp.quadrature_grid(xp.unique(self.x_his.flatten()), self.pts_loc, self.wts_loc) else: # maximum number of non-vanishing coefficients @@ -407,31 +409,33 @@ def __init__(self, spline_space, n_quad): self.n_his_nvcof_N = 2 * self.p # shift local coefficients --> global coefficients - self.his_shift_D = np.arange(self.NbaseD) - (self.p - 1) - self.his_shift_N = np.arange(self.NbaseD) - self.p + self.his_shift_D = xp.arange(self.NbaseD) - (self.p - 1) + self.his_shift_N = xp.arange(self.NbaseD) - self.p for i in range(n_lambda_his): - self.his_global_N[i] = (np.arange(self.n_his_locbf_N) + i - (self.p - 1)) % self.NbaseN - self.his_global_D[i] = (np.arange(self.n_his_locbf_D) + i - (self.p - 1)) % self.NbaseD - self.his_loccof_N[i] = np.arange(self.n_his_locbf_N - 1, -1, -1) - self.his_loccof_D[i] = np.arange(self.n_his_locbf_D - 1, -1, -1) + self.his_global_N[i] = (xp.arange(self.n_his_locbf_N) + i - (self.p - 1)) % self.NbaseN + self.his_global_D[i] = (xp.arange(self.n_his_locbf_D) + i - (self.p - 1)) % self.NbaseD + self.his_loccof_N[i] = xp.arange(self.n_his_locbf_N - 1, -1, -1) + self.his_loccof_D[i] = xp.arange(self.n_his_locbf_D - 1, -1, -1) - self.x_his_indices[i] = np.arange(self.n_his) + 2 * (i - (self.p - 1)) + self.x_his_indices[i] = xp.arange(self.n_his) + 2 * (i - (self.p - 1)) self.coeffh_indices[i] = 0 for j in range(2 * self.p + 1): self.x_his[i, j] = (self.T[i + 1 + int(j / 2)] + self.T[i + 1 + int((j + 1) / 2)]) / 2 # quadrature points and weights self.pts, self.wts = bsp.quadrature_grid( - np.append(np.unique(self.x_his.flatten() % 1.0), 1.0), self.pts_loc, self.wts_loc + xp.append(xp.unique(self.x_his.flatten() % 1.0), 1.0), + self.pts_loc, + self.wts_loc, ) # quasi interpolation def pi_0(self, fun): - lambdas = np.zeros(self.NbaseN, dtype=float) + lambdas = xp.zeros(self.NbaseN, dtype=float) # evaluate function at interpolation points - mat_f = fun(np.unique(self.x_int.flatten())) + mat_f = fun(xp.unique(self.x_int.flatten())) for i in range(self.NbaseN): for j in range(self.n_int): @@ -441,7 +445,7 @@ def pi_0(self, fun): # quasi histopolation def pi_1(self, fun): - lambdas = np.zeros(self.NbaseD, dtype=float) + lambdas = xp.zeros(self.NbaseD, dtype=float) # evaluate function at quadrature points mat_f = fun(self.pts) @@ -459,17 +463,17 @@ def pi_1(self, fun): # projection matrices of products of basis functions: pi0_i(A_j*B_k) and pi1_i(A_j*B_k) def projection_matrices_1d(self, bc_kind=["free", "free"]): - PI0_NN = np.empty((self.NbaseN, self.NbaseN, self.NbaseN), dtype=float) - PI0_DN = np.empty((self.NbaseN, self.NbaseD, self.NbaseN), dtype=float) - PI0_DD = np.empty((self.NbaseN, self.NbaseD, self.NbaseD), dtype=float) + PI0_NN = xp.empty((self.NbaseN, self.NbaseN, self.NbaseN), dtype=float) + PI0_DN = xp.empty((self.NbaseN, self.NbaseD, self.NbaseN), dtype=float) + PI0_DD = xp.empty((self.NbaseN, self.NbaseD, self.NbaseD), dtype=float) - PI1_NN = np.empty((self.NbaseD, self.NbaseN, self.NbaseN), dtype=float) - PI1_DN = np.empty((self.NbaseD, self.NbaseD, self.NbaseN), dtype=float) - PI1_DD = np.empty((self.NbaseD, self.NbaseD, self.NbaseD), dtype=float) + PI1_NN = xp.empty((self.NbaseD, self.NbaseN, self.NbaseN), dtype=float) + PI1_DN = xp.empty((self.NbaseD, self.NbaseD, self.NbaseN), dtype=float) + PI1_DD = xp.empty((self.NbaseD, self.NbaseD, self.NbaseD), dtype=float) # ========= PI0__NN and PI1_NN ============= - ci = np.zeros(self.NbaseN, dtype=float) - cj = np.zeros(self.NbaseN, dtype=float) + ci = xp.zeros(self.NbaseN, dtype=float) + cj = xp.zeros(self.NbaseN, dtype=float) for i in range(self.NbaseN): for j in range(self.NbaseN): @@ -485,8 +489,8 @@ def projection_matrices_1d(self, bc_kind=["free", "free"]): PI1_NN[:, i, j] = self.pi_1(fun) # ========= PI0__DN and PI1_DN ============= - ci = np.zeros(self.NbaseD, dtype=float) - cj = np.zeros(self.NbaseN, dtype=float) + ci = xp.zeros(self.NbaseD, dtype=float) + cj = xp.zeros(self.NbaseN, dtype=float) for i in range(self.NbaseD): for j in range(self.NbaseN): @@ -502,8 +506,8 @@ def projection_matrices_1d(self, bc_kind=["free", "free"]): PI1_DN[:, i, j] = self.pi_1(fun) # ========= PI0__DD and PI1_DD ============= - ci = np.zeros(self.NbaseD, dtype=float) - cj = np.zeros(self.NbaseD, dtype=float) + ci = xp.zeros(self.NbaseD, dtype=float) + cj = xp.zeros(self.NbaseD, dtype=float) for i in range(self.NbaseD): for j in range(self.NbaseD): @@ -518,8 +522,8 @@ def projection_matrices_1d(self, bc_kind=["free", "free"]): PI0_DD[:, i, j] = self.pi_0(fun) PI1_DD[:, i, j] = self.pi_1(fun) - PI0_ND = np.transpose(PI0_DN, (0, 2, 1)) - PI1_ND = np.transpose(PI1_DN, (0, 2, 1)) + PI0_ND = xp.transpose(PI0_DN, (0, 2, 1)) + PI1_ND = xp.transpose(PI1_DN, (0, 2, 1)) # remove contributions from first and last N-splines if bc_kind[0] == "dirichlet": @@ -544,25 +548,25 @@ def projection_matrices_1d(self, bc_kind=["free", "free"]): PI1_DN[:, :, -1] = 0.0 PI1_ND[:, -1, :] = 0.0 - PI0_NN_indices = np.nonzero(PI0_NN) - PI0_DN_indices = np.nonzero(PI0_DN) - PI0_ND_indices = np.nonzero(PI0_ND) - PI0_DD_indices = np.nonzero(PI0_DD) + PI0_NN_indices = xp.nonzero(PI0_NN) + PI0_DN_indices = xp.nonzero(PI0_DN) + PI0_ND_indices = xp.nonzero(PI0_ND) + PI0_DD_indices = xp.nonzero(PI0_DD) - PI1_NN_indices = np.nonzero(PI1_NN) - PI1_DN_indices = np.nonzero(PI1_DN) - PI1_ND_indices = np.nonzero(PI1_ND) - PI1_DD_indices = np.nonzero(PI1_DD) + PI1_NN_indices = xp.nonzero(PI1_NN) + PI1_DN_indices = xp.nonzero(PI1_DN) + PI1_ND_indices = xp.nonzero(PI1_ND) + PI1_DD_indices = xp.nonzero(PI1_DD) - PI0_NN_indices = np.vstack((PI0_NN_indices[0], PI0_NN_indices[1], PI0_NN_indices[2])) - PI0_DN_indices = np.vstack((PI0_DN_indices[0], PI0_DN_indices[1], PI0_DN_indices[2])) - PI0_ND_indices = np.vstack((PI0_ND_indices[0], PI0_ND_indices[1], PI0_ND_indices[2])) - PI0_DD_indices = np.vstack((PI0_DD_indices[0], PI0_DD_indices[1], PI0_DD_indices[2])) + PI0_NN_indices = xp.vstack((PI0_NN_indices[0], PI0_NN_indices[1], PI0_NN_indices[2])) + PI0_DN_indices = xp.vstack((PI0_DN_indices[0], PI0_DN_indices[1], PI0_DN_indices[2])) + PI0_ND_indices = xp.vstack((PI0_ND_indices[0], PI0_ND_indices[1], PI0_ND_indices[2])) + PI0_DD_indices = xp.vstack((PI0_DD_indices[0], PI0_DD_indices[1], PI0_DD_indices[2])) - PI1_NN_indices = np.vstack((PI1_NN_indices[0], PI1_NN_indices[1], PI1_NN_indices[2])) - PI1_DN_indices = np.vstack((PI1_DN_indices[0], PI1_DN_indices[1], PI1_DN_indices[2])) - PI1_ND_indices = np.vstack((PI1_ND_indices[0], PI1_ND_indices[1], PI1_ND_indices[2])) - PI1_DD_indices = np.vstack((PI1_DD_indices[0], PI1_DD_indices[1], PI1_DD_indices[2])) + PI1_NN_indices = xp.vstack((PI1_NN_indices[0], PI1_NN_indices[1], PI1_NN_indices[2])) + PI1_DN_indices = xp.vstack((PI1_DN_indices[0], PI1_DN_indices[1], PI1_DN_indices[2])) + PI1_ND_indices = xp.vstack((PI1_ND_indices[0], PI1_ND_indices[1], PI1_ND_indices[2])) + PI1_DD_indices = xp.vstack((PI1_DD_indices[0], PI1_DD_indices[1], PI1_DD_indices[2])) return ( PI0_NN, @@ -617,87 +621,87 @@ def __init__(self, tensor_space, n_quad): self.polar = False # local projectors for polar splines are not implemented yet # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = [np.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] - self.wts_loc = [np.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] + self.pts_loc = [xp.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] + self.wts_loc = [xp.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] # set interpolation and histopolation coefficients self.coeff_i = [0, 0, 0] self.coeff_h = [0, 0, 0] for a in range(3): - if self.bc[a] == True: - self.coeff_i[a] = np.zeros((1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = np.zeros((1, 2 * self.p[a]), dtype=float) + if self.bc[a]: + self.coeff_i[a] = xp.zeros((1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = xp.zeros((1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = np.array([1.0]) - self.coeff_h[a][0, :] = np.array([1.0, 1.0]) + self.coeff_i[a][0, :] = xp.array([1.0]) + self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) - self.coeff_h[a][0, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_i[a][0, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) + self.coeff_h[a][0, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 6 * np.array([1.0, -8.0, 20.0, -8.0, 1.0]) - self.coeff_h[a][0, :] = 1 / 6 * np.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) + self.coeff_i[a][0, :] = 1 / 6 * xp.array([1.0, -8.0, 20.0, -8.0, 1.0]) + self.coeff_h[a][0, :] = 1 / 6 * xp.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 2 / 45 * np.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) + self.coeff_i[a][0, :] = 2 / 45 * xp.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0]) self.coeff_h[a][0, :] = ( - 2 / 45 * np.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) + 2 / 45 * xp.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) ) else: print("degree > 4 not implemented!") else: - self.coeff_i[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) + self.coeff_i[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = np.array([1.0]) - self.coeff_h[a][0, :] = np.array([1.0, 1.0]) + self.coeff_i[a][0, :] = xp.array([1.0]) + self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * np.array([2.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 2 * np.array([0.0, 0.0, 2.0]) + self.coeff_i[a][0, :] = 1 / 2 * xp.array([2.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, 2.0]) - self.coeff_h[a][0, :] = 1 / 2 * np.array([3.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) - self.coeff_h[a][2, :] = 1 / 2 * np.array([0.0, 0.0, -1.0, 3.0]) + self.coeff_h[a][0, :] = 1 / 2 * xp.array([3.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_h[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, -1.0, 3.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 18 * np.array([18.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 18 * np.array([-5.0, 40.0, -24.0, 8.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 18 * np.array([3.0, -24.0, 60.0, -24.0, 3.0]) - self.coeff_i[a][3, :] = 1 / 18 * np.array([-1.0, 8.0, -24.0, 40.0, -5.0]) - self.coeff_i[a][4, :] = 1 / 18 * np.array([0.0, 0.0, 0.0, 0.0, 18.0]) - - self.coeff_h[a][0, :] = 1 / 18 * np.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 18 * np.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 18 * np.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) - self.coeff_h[a][3, :] = 1 / 18 * np.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) - self.coeff_h[a][4, :] = 1 / 18 * np.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) + self.coeff_i[a][0, :] = 1 / 18 * xp.array([18.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 18 * xp.array([-5.0, 40.0, -24.0, 8.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 18 * xp.array([3.0, -24.0, 60.0, -24.0, 3.0]) + self.coeff_i[a][3, :] = 1 / 18 * xp.array([-1.0, 8.0, -24.0, 40.0, -5.0]) + self.coeff_i[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, 0.0, 0.0, 18.0]) + + self.coeff_h[a][0, :] = 1 / 18 * xp.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 18 * xp.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 18 * xp.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) + self.coeff_h[a][3, :] = 1 / 18 * xp.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) + self.coeff_h[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 1 / 360 * np.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 360 * np.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) - self.coeff_i[a][2, :] = 1 / 360 * np.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) - self.coeff_i[a][3, :] = 1 / 360 * np.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) - self.coeff_i[a][4, :] = 1 / 360 * np.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) - self.coeff_i[a][5, :] = 1 / 360 * np.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) - self.coeff_i[a][6, :] = 1 / 360 * np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) - - self.coeff_h[a][0, :] = 1 / 360 * np.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 360 * np.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 360 * np.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) + self.coeff_i[a][0, :] = 1 / 360 * xp.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 360 * xp.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) + self.coeff_i[a][2, :] = 1 / 360 * xp.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) + self.coeff_i[a][3, :] = 1 / 360 * xp.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) + self.coeff_i[a][4, :] = 1 / 360 * xp.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) + self.coeff_i[a][5, :] = 1 / 360 * xp.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) + self.coeff_i[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) + + self.coeff_h[a][0, :] = 1 / 360 * xp.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 360 * xp.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 360 * xp.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) self.coeff_h[a][3, :] = ( - 1 / 360 * np.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) + 1 / 360 * xp.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) ) - self.coeff_h[a][4, :] = 1 / 360 * np.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) - self.coeff_h[a][5, :] = 1 / 360 * np.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) - self.coeff_h[a][6, :] = 1 / 360 * np.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) + self.coeff_h[a][4, :] = 1 / 360 * xp.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) + self.coeff_h[a][5, :] = 1 / 360 * xp.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) + self.coeff_h[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) else: print("degree > 4 not implemented!") @@ -723,31 +727,31 @@ def __init__(self, tensor_space, n_quad): ) # number of non-vanishing D bf in interpolation interval (1, 2, 4, 6) self.x_int = [ - np.zeros((n_lambda_int, n_int), dtype=float) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) + xp.zeros((n_lambda_int, n_int), dtype=float) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) ] self.int_global_N = [ - np.zeros((n_lambda_int, n_int_locbf_N), dtype=int) + xp.zeros((n_lambda_int, n_int_locbf_N), dtype=int) for n_lambda_int, n_int_locbf_N in zip(n_lambda_int, self.n_int_locbf_N) ] self.int_global_D = [ - np.zeros((n_lambda_int, n_int_locbf_D), dtype=int) + xp.zeros((n_lambda_int, n_int_locbf_D), dtype=int) for n_lambda_int, n_int_locbf_D in zip(n_lambda_int, self.n_int_locbf_D) ] self.int_loccof_N = [ - np.zeros((n_lambda_int, n_int_locbf_N), dtype=int) + xp.zeros((n_lambda_int, n_int_locbf_N), dtype=int) for n_lambda_int, n_int_locbf_N in zip(n_lambda_int, self.n_int_locbf_N) ] self.int_loccof_D = [ - np.zeros((n_lambda_int, n_int_locbf_D), dtype=int) + xp.zeros((n_lambda_int, n_int_locbf_D), dtype=int) for n_lambda_int, n_int_locbf_D in zip(n_lambda_int, self.n_int_locbf_D) ] self.x_int_indices = [ - np.zeros((n_lambda_int, n_int), dtype=int) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) + xp.zeros((n_lambda_int, n_int), dtype=int) for n_lambda_int, n_int in zip(n_lambda_int, self.n_int) ] - self.coeffi_indices = [np.zeros(n_lambda_int, dtype=int) for n_lambda_int in n_lambda_int] + self.coeffi_indices = [xp.zeros(n_lambda_int, dtype=int) for n_lambda_int in n_lambda_int] self.n_int_nvcof_D = [None, None, None] self.n_int_nvcof_N = [None, None, None] @@ -759,7 +763,7 @@ def __init__(self, tensor_space, n_quad): self.int_shift_N = [0, 0, 0] for a in range(3): - if self.bc[a] == False: + if not self.bc[a]: # maximum number of non-vanishing coefficients if self.p[a] == 1: self.n_int_nvcof_D[a] = 2 @@ -770,39 +774,39 @@ def __init__(self, tensor_space, n_quad): self.n_int_nvcof_N[a] = 3 * self.p[a] - 2 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.int_add_D[a] = np.arange(self.n_int[a] - 2) + 1 - self.int_add_N[a] = np.arange(self.n_int[a] - 1) + 1 + self.int_add_D[a] = xp.arange(self.n_int[a] - 2) + 1 + self.int_add_N[a] = xp.arange(self.n_int[a] - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) if self.p[a] == 1: - self.int_shift_D[a] = np.arange(self.NbaseD[a]) + self.int_shift_D[a] = xp.arange(self.NbaseD[a]) else: - self.int_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 2) + self.int_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 2) self.int_shift_D[a][: 2 * self.p[a] - 2] = 0 self.int_shift_D[a][-(2 * self.p[a] - 2) :] = self.int_shift_D[a][-(2 * self.p[a] - 2)] # shift local coefficients --> global coefficients (N) if self.p[a] == 1: - self.int_shift_N[a] = np.arange(self.NbaseN[a]) + self.int_shift_N[a] = xp.arange(self.NbaseN[a]) self.int_shift_N[a][-1] = self.int_shift_N[a][-2] else: - self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) self.int_shift_N[a][: 2 * self.p[a] - 1] = 0 self.int_shift_N[a][-(2 * self.p[a] - 1) :] = self.int_shift_N[a][-(2 * self.p[a] - 1)] - counter_coeffi = np.copy(self.p[a]) + counter_coeffi = xp.copy(self.p[a]) for i in range(n_lambda_int[a]): # left boundary region if i < self.p[a] - 1: - self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) - self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) - self.x_int_indices[a][i] = np.arange(self.n_int[a]) + self.x_int_indices[a][i] = xp.arange(self.n_int[a]) self.coeffi_indices[a][i] = i for j in range(2 * (self.p[a] - 1) + 1): xi = self.p[a] - 1 @@ -813,13 +817,13 @@ def __init__(self, tensor_space, n_quad): # right boundary region elif i > n_lambda_int[a] - self.p[a]: self.int_global_N[a][i] = ( - np.arange(self.n_int_locbf_N[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) + xp.arange(self.n_int_locbf_N[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) self.int_global_D[a][i] = ( - np.arange(self.n_int_locbf_D[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) + xp.arange(self.n_int_locbf_D[a]) + n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) - self.x_int_indices[a][i] = np.arange(self.n_int[a]) + 2 * ( + self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + 2 * ( n_lambda_int[a] - self.p[a] - (self.p[a] - 1) ) self.coeffi_indices[a][i] = counter_coeffi @@ -833,20 +837,20 @@ def __init__(self, tensor_space, n_quad): # interior else: if self.p[a] == 1: - self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + i - self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + i + self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + i + self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + i self.int_global_N[a][-1] = self.int_global_N[a][-2] self.int_global_D[a][-1] = self.int_global_D[a][-2] else: - self.int_global_N[a][i] = np.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1) - self.int_global_D[a][i] = np.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1) + self.int_global_N[a][i] = xp.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1) + self.int_global_D[a][i] = xp.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1) if self.p[a] == 1: self.x_int_indices[a][i] = i else: - self.x_int_indices[a][i] = np.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1)) + self.x_int_indices[a][i] = xp.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1)) self.coeffi_indices[a][i] = self.p[a] - 1 @@ -857,8 +861,8 @@ def __init__(self, tensor_space, n_quad): # local coefficient index if self.p[a] == 1: - self.int_loccof_N[a][i] = np.array([0, 1]) - self.int_loccof_D[a][-1] = np.array([1]) + self.int_loccof_N[a][i] = xp.array([0, 1]) + self.int_loccof_D[a][-1] = xp.array([1]) else: if i > 0: @@ -866,8 +870,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.int_global_D[a][i, il] bol = k_glob_new == self.int_global_D[a][i - 1] - if np.any(bol): - self.int_loccof_D[a][i, il] = self.int_loccof_D[a][i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.int_loccof_D[a][i, il] = self.int_loccof_D[a][i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int[a] - self.p[a] - (self.p[a] - 2)) and ( self.int_loccof_D[a][i, il] == 0 @@ -879,8 +883,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.int_global_N[a][i, il] bol = k_glob_new == self.int_global_N[a][i - 1] - if np.any(bol): - self.int_loccof_N[a][i, il] = self.int_loccof_N[a][i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.int_loccof_N[a][i, il] = self.int_loccof_N[a][i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_int[a] - self.p[a] - (self.p[a] - 2)) and ( self.int_loccof_N[a][i, il] == 0 @@ -900,24 +904,24 @@ def __init__(self, tensor_space, n_quad): # shift local coefficients --> global coefficients if self.p[a] == 1: - self.int_shift_D[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) - self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a]) + self.int_shift_D[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a]) else: - self.int_shift_D[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 2) - self.int_shift_N[a] = np.arange(self.NbaseN[a]) - (self.p[a] - 1) + self.int_shift_D[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 2) + self.int_shift_N[a] = xp.arange(self.NbaseN[a]) - (self.p[a] - 1) for i in range(n_lambda_int[a]): # global indices of non-vanishing basis functions and position of coefficients in final matrix - self.int_global_N[a][i] = (np.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] - self.int_global_D[a][i] = (np.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] + self.int_global_N[a][i] = (xp.arange(self.n_int_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] + self.int_global_D[a][i] = (xp.arange(self.n_int_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] - self.int_loccof_N[a][i] = np.arange(self.n_int_locbf_N[a] - 1, -1, -1) - self.int_loccof_D[a][i] = np.arange(self.n_int_locbf_D[a] - 1, -1, -1) + self.int_loccof_N[a][i] = xp.arange(self.n_int_locbf_N[a] - 1, -1, -1) + self.int_loccof_D[a][i] = xp.arange(self.n_int_locbf_D[a] - 1, -1, -1) if self.p[a] == 1: self.x_int_indices[a][i] = i else: - self.x_int_indices[a][i] = (np.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1))) % ( + self.x_int_indices[a][i] = (xp.arange(self.n_int[a]) + 2 * (i - (self.p[a] - 1))) % ( 2 * self.Nel[a] ) @@ -929,38 +933,38 @@ def __init__(self, tensor_space, n_quad): ) % 1.0 # set histopolation points, quadrature points and weights - n_lambda_his = [np.copy(NbaseD) for NbaseD in self.NbaseD] # number of coefficients in space V1 + n_lambda_his = [xp.copy(NbaseD) for NbaseD in self.NbaseD] # number of coefficients in space V1 self.n_his = [2 * p for p in self.p] # number of histopolation intervals self.n_his_locbf_N = [2 * p for p in self.p] # number of non-vanishing N bf in histopolation interval self.n_his_locbf_D = [2 * p - 1 for p in self.p] # number of non-vanishing D bf in histopolation interval self.x_his = [ - np.zeros((n_lambda_his, n_his + 1), dtype=float) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) + xp.zeros((n_lambda_his, n_his + 1), dtype=float) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) ] self.his_global_N = [ - np.zeros((n_lambda_his, n_his_locbf_N), dtype=int) + xp.zeros((n_lambda_his, n_his_locbf_N), dtype=int) for n_lambda_his, n_his_locbf_N in zip(n_lambda_his, self.n_his_locbf_N) ] self.his_global_D = [ - np.zeros((n_lambda_his, n_his_locbf_D), dtype=int) + xp.zeros((n_lambda_his, n_his_locbf_D), dtype=int) for n_lambda_his, n_his_locbf_D in zip(n_lambda_his, self.n_his_locbf_D) ] self.his_loccof_N = [ - np.zeros((n_lambda_his, n_his_locbf_N), dtype=int) + xp.zeros((n_lambda_his, n_his_locbf_N), dtype=int) for n_lambda_his, n_his_locbf_N in zip(n_lambda_his, self.n_his_locbf_N) ] self.his_loccof_D = [ - np.zeros((n_lambda_his, n_his_locbf_D), dtype=int) + xp.zeros((n_lambda_his, n_his_locbf_D), dtype=int) for n_lambda_his, n_his_locbf_D in zip(n_lambda_his, self.n_his_locbf_D) ] self.x_his_indices = [ - np.zeros((n_lambda_his, n_his), dtype=int) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) + xp.zeros((n_lambda_his, n_his), dtype=int) for n_lambda_his, n_his in zip(n_lambda_his, self.n_his) ] - self.coeffh_indices = [np.zeros(n_lambda_his, dtype=int) for n_lambda_his in n_lambda_his] + self.coeffh_indices = [xp.zeros(n_lambda_his, dtype=int) for n_lambda_his in n_lambda_his] self.pts = [0, 0, 0] self.wts = [0, 0, 0] @@ -975,37 +979,37 @@ def __init__(self, tensor_space, n_quad): self.his_shift_N = [0, 0, 0] for a in range(3): - if self.bc[a] == False: + if not self.bc[a]: # maximum number of non-vanishing coefficients self.n_his_nvcof_D[a] = 3 * self.p[a] - 2 self.n_his_nvcof_N[a] = 3 * self.p[a] - 1 # shift in local coefficient indices at right boundary (only for non-periodic boundary conditions) - self.his_add_D[a] = np.arange(self.n_his[a] - 2) + 1 - self.his_add_N[a] = np.arange(self.n_his[a] - 1) + 1 + self.his_add_D[a] = xp.arange(self.n_his[a] - 2) + 1 + self.his_add_N[a] = xp.arange(self.n_his[a] - 1) + 1 counter_D = 0 counter_N = 0 # shift local coefficients --> global coefficients (D) - self.his_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 1) + self.his_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 1) self.his_shift_D[a][: 2 * self.p[a] - 1] = 0 self.his_shift_D[a][-(2 * self.p[a] - 1) :] = self.his_shift_D[a][-(2 * self.p[a] - 1)] # shift local coefficients --> global coefficients (N) - self.his_shift_N[a] = np.arange(self.NbaseN[a]) - self.p[a] + self.his_shift_N[a] = xp.arange(self.NbaseN[a]) - self.p[a] self.his_shift_N[a][: 2 * self.p[a]] = 0 self.his_shift_N[a][-2 * self.p[a] :] = self.his_shift_N[a][-2 * self.p[a]] - counter_coeffh = np.copy(self.p[a]) + counter_coeffh = xp.copy(self.p[a]) for i in range(n_lambda_his[a]): # left boundary region if i < self.p[a] - 1: - self.his_global_N[a][i] = np.arange(self.n_his_locbf_N[a]) - self.his_global_D[a][i] = np.arange(self.n_his_locbf_D[a]) + self.his_global_N[a][i] = xp.arange(self.n_his_locbf_N[a]) + self.his_global_D[a][i] = xp.arange(self.n_his_locbf_D[a]) - self.x_his_indices[a][i] = np.arange(self.n_his[a]) + self.x_his_indices[a][i] = xp.arange(self.n_his[a]) self.coeffh_indices[a][i] = i for j in range(2 * self.p[a] + 1): xi = self.p[a] - 1 @@ -1016,13 +1020,13 @@ def __init__(self, tensor_space, n_quad): # right boundary region elif i > n_lambda_his[a] - self.p[a]: self.his_global_N[a][i] = ( - np.arange(self.n_his_locbf_N[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) + xp.arange(self.n_his_locbf_N[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) self.his_global_D[a][i] = ( - np.arange(self.n_his_locbf_D[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) + xp.arange(self.n_his_locbf_D[a]) + n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) - self.x_his_indices[a][i] = np.arange(self.n_his[a]) + 2 * ( + self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + 2 * ( n_lambda_his[a] - self.p[a] - (self.p[a] - 1) ) self.coeffh_indices[a][i] = counter_coeffh @@ -1035,10 +1039,10 @@ def __init__(self, tensor_space, n_quad): # interior else: - self.his_global_N[a][i] = np.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1) - self.his_global_D[a][i] = np.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1) + self.his_global_N[a][i] = xp.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1) + self.his_global_D[a][i] = xp.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1) - self.x_his_indices[a][i] = np.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1)) + self.x_his_indices[a][i] = xp.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1)) self.coeffh_indices[a][i] = self.p[a] - 1 for j in range(2 * self.p[a] + 1): self.x_his[a][i, j] = ( @@ -1051,8 +1055,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.his_global_D[a][i, il] bol = k_glob_new == self.his_global_D[a][i - 1] - if np.any(bol): - self.his_loccof_D[a][i, il] = self.his_loccof_D[a][i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.his_loccof_D[a][i, il] = self.his_loccof_D[a][i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his[a] - self.p[a] - (self.p[a] - 2)) and ( self.his_loccof_D[a][i, il] == 0 @@ -1064,8 +1068,8 @@ def __init__(self, tensor_space, n_quad): k_glob_new = self.his_global_N[a][i, il] bol = k_glob_new == self.his_global_N[a][i - 1] - if np.any(bol): - self.his_loccof_N[a][i, il] = self.his_loccof_N[a][i - 1, np.where(bol)[0][0]] + 1 + if xp.any(bol): + self.his_loccof_N[a][i, il] = self.his_loccof_N[a][i - 1, xp.where(bol)[0][0]] + 1 if (k_glob_new >= n_lambda_his[a] - self.p[a] - (self.p[a] - 2)) and ( self.his_loccof_N[a][i, il] == 0 @@ -1075,7 +1079,9 @@ def __init__(self, tensor_space, n_quad): # quadrature points and weights self.pts[a], self.wts[a] = bsp.quadrature_grid( - np.unique(self.x_his[a].flatten()), self.pts_loc[a], self.wts_loc[a] + xp.unique(self.x_his[a].flatten()), + self.pts_loc[a], + self.wts_loc[a], ) else: @@ -1084,18 +1090,18 @@ def __init__(self, tensor_space, n_quad): self.n_his_nvcof_N[a] = 2 * self.p[a] # shift local coefficients --> global coefficients (D) - self.his_shift_D[a] = np.arange(self.NbaseD[a]) - (self.p[a] - 1) + self.his_shift_D[a] = xp.arange(self.NbaseD[a]) - (self.p[a] - 1) # shift local coefficients --> global coefficients (N) - self.his_shift_N[a] = np.arange(self.NbaseD[a]) - self.p[a] + self.his_shift_N[a] = xp.arange(self.NbaseD[a]) - self.p[a] for i in range(n_lambda_his[a]): - self.his_global_N[a][i] = (np.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] - self.his_global_D[a][i] = (np.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] - self.his_loccof_N[a][i] = np.arange(self.n_his_locbf_N[a] - 1, -1, -1) - self.his_loccof_D[a][i] = np.arange(self.n_his_locbf_D[a] - 1, -1, -1) + self.his_global_N[a][i] = (xp.arange(self.n_his_locbf_N[a]) + i - (self.p[a] - 1)) % self.NbaseN[a] + self.his_global_D[a][i] = (xp.arange(self.n_his_locbf_D[a]) + i - (self.p[a] - 1)) % self.NbaseD[a] + self.his_loccof_N[a][i] = xp.arange(self.n_his_locbf_N[a] - 1, -1, -1) + self.his_loccof_D[a][i] = xp.arange(self.n_his_locbf_D[a] - 1, -1, -1) - self.x_his_indices[a][i] = (np.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1))) % ( + self.x_his_indices[a][i] = (xp.arange(self.n_his[a]) + 2 * (i - (self.p[a] - 1))) % ( 2 * self.Nel[a] ) self.coeffh_indices[a][i] = 0 @@ -1105,7 +1111,9 @@ def __init__(self, tensor_space, n_quad): # quadrature points and weights self.pts[a], self.wts[a] = bsp.quadrature_grid( - np.append(np.unique(self.x_his[a].flatten() % 1.0), 1.0), self.pts_loc[a], self.wts_loc[a] + xp.append(xp.unique(self.x_his[a].flatten() % 1.0), 1.0), + self.pts_loc[a], + self.wts_loc[a], ) # projector on space V0 (interpolation) @@ -1131,18 +1139,18 @@ def pi_0(self, fun, include_bc=True, eval_kind="meshgrid"): """ # interpolation points - x_int1 = np.unique(self.x_int[0].flatten()) - x_int2 = np.unique(self.x_int[1].flatten()) - x_int3 = np.unique(self.x_int[2].flatten()) + x_int1 = xp.unique(self.x_int[0].flatten()) + x_int2 = xp.unique(self.x_int[1].flatten()) + x_int3 = xp.unique(self.x_int[2].flatten()) # evaluation of function at interpolation points - mat_f = np.empty((x_int1.size, x_int2.size, x_int3.size), dtype=float) + mat_f = xp.empty((x_int1.size, x_int2.size, x_int3.size), dtype=float) # external function call if a callable is passed if callable(fun): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid(x_int1, x_int2, x_int3, indexing="ij") + pts1, pts2, pts3 = xp.meshgrid(x_int1, x_int2, x_int3, indexing="ij") mat_f[:, :, :] = fun(pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1161,7 +1169,7 @@ def pi_0(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # coefficients - lambdas = np.zeros((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + lambdas = xp.zeros((self.NbaseN[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) ker_loc.kernel_pi0_3d( self.NbaseN, @@ -1204,20 +1212,20 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): """ # interpolation points - x_int1 = np.unique(self.x_int[0].flatten()) - x_int2 = np.unique(self.x_int[1].flatten()) - x_int3 = np.unique(self.x_int[2].flatten()) + x_int1 = xp.unique(self.x_int[0].flatten()) + x_int2 = xp.unique(self.x_int[1].flatten()) + x_int3 = xp.unique(self.x_int[2].flatten()) # ======== 1-component ======== # evaluation of function at interpolation/quadrature points - mat_f = np.empty((self.pts[0].flatten().size, x_int2.size, x_int3.size), dtype=float) + mat_f = xp.empty((self.pts[0].flatten().size, x_int2.size, x_int3.size), dtype=float) # external function call if a callable is passed if callable(fun[0]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid(self.pts[0].flatten(), x_int2, x_int3, indexing="ij") + pts1, pts2, pts3 = xp.meshgrid(self.pts[0].flatten(), x_int2, x_int3, indexing="ij") mat_f[:, :, :] = fun[0](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1236,7 +1244,7 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas1 = np.zeros((self.NbaseD[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) + lambdas1 = xp.zeros((self.NbaseD[0], self.NbaseN[1], self.NbaseN[2]), dtype=float) ker_loc.kernel_pi11_3d( [self.NbaseD[0], self.NbaseN[1], self.NbaseN[2]], @@ -1259,13 +1267,13 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): # ======== 2-component ======== # evaluation of function at interpolation/quadrature points - mat_f = np.empty((x_int1.size, self.pts[1].flatten().size, x_int3.size), dtype=float) + mat_f = xp.empty((x_int1.size, self.pts[1].flatten().size, x_int3.size), dtype=float) # external function call if a callable is passed if callable(fun[1]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid(x_int1, self.pts[1].flatten(), x_int3, indexing="ij") + pts1, pts2, pts3 = xp.meshgrid(x_int1, self.pts[1].flatten(), x_int3, indexing="ij") mat_f[:, :, :] = fun[1](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1284,7 +1292,7 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas2 = np.zeros((self.NbaseN[0], self.NbaseD[1], self.NbaseN[2]), dtype=float) + lambdas2 = xp.zeros((self.NbaseN[0], self.NbaseD[1], self.NbaseN[2]), dtype=float) ker_loc.kernel_pi12_3d( [self.NbaseN[0], self.NbaseD[1], self.NbaseN[2]], @@ -1307,13 +1315,13 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): # ======== 3-component ======== # evaluation of function at interpolation/quadrature points - mat_f = np.empty((x_int1.size, x_int1.size, self.pts[2].flatten().size), dtype=float) + mat_f = xp.empty((x_int1.size, x_int1.size, self.pts[2].flatten().size), dtype=float) # external function call if a callable is passed if callable(fun[2]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid(x_int1, x_int2, self.pts[2].flatten(), indexing="ij") + pts1, pts2, pts3 = xp.meshgrid(x_int1, x_int2, self.pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun[2](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1332,7 +1340,7 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas3 = np.zeros((self.NbaseN[0], self.NbaseN[1], self.NbaseD[2]), dtype=float) + lambdas3 = xp.zeros((self.NbaseN[0], self.NbaseN[1], self.NbaseD[2]), dtype=float) ker_loc.kernel_pi13_3d( [self.NbaseN[0], self.NbaseN[1], self.NbaseD[2]], @@ -1352,7 +1360,7 @@ def pi_1(self, fun, include_bc=True, eval_kind="meshgrid"): lambdas3, ) - return np.concatenate((lambdas1.flatten(), lambdas2.flatten(), lambdas3.flatten())) + return xp.concatenate((lambdas1.flatten(), lambdas2.flatten(), lambdas3.flatten())) # projector on space V1 ([inter, histo, histo], [histo, inter, histo], [histo, histo, inter]) def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): @@ -1377,20 +1385,20 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): """ # interpolation points - x_int1 = np.unique(self.x_int[0].flatten()) - x_int2 = np.unique(self.x_int[1].flatten()) - x_int3 = np.unique(self.x_int[2].flatten()) + x_int1 = xp.unique(self.x_int[0].flatten()) + x_int2 = xp.unique(self.x_int[1].flatten()) + x_int3 = xp.unique(self.x_int[2].flatten()) # ======== 1-component ======== # evaluation of function at interpolation/quadrature points - mat_f = np.empty((x_int1.size, self.pts[1].flatten().size, self.pts[2].flatten().size), dtype=float) + mat_f = xp.empty((x_int1.size, self.pts[1].flatten().size, self.pts[2].flatten().size), dtype=float) # external function call if a callable is passed if callable(fun[0]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid(x_int1, self.pts[1].flatten(), self.pts[2].flatten(), indexing="ij") + pts1, pts2, pts3 = xp.meshgrid(x_int1, self.pts[1].flatten(), self.pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun[0](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1409,7 +1417,7 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas1 = np.zeros((self.NbaseN[0], self.NbaseD[1], self.NbaseD[2]), dtype=float) + lambdas1 = xp.zeros((self.NbaseN[0], self.NbaseD[1], self.NbaseD[2]), dtype=float) ker_loc.kernel_pi21_3d( [self.NbaseN[0], self.NbaseD[1], self.NbaseD[2]], @@ -1427,7 +1435,11 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): self.wts[1], self.wts[2], mat_f.reshape( - x_int1.size, self.pts[1].shape[0], self.pts[1].shape[1], self.pts[2].shape[0], self.pts[2].shape[1] + x_int1.size, + self.pts[1].shape[0], + self.pts[1].shape[1], + self.pts[2].shape[0], + self.pts[2].shape[1], ), lambdas1, ) @@ -1435,13 +1447,13 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): # ======== 2-component ======== # evaluation of function at interpolation/quadrature points - mat_f = np.empty((self.pts[0].flatten().size, x_int2.size, self.pts[2].flatten().size), dtype=float) + mat_f = xp.empty((self.pts[0].flatten().size, x_int2.size, self.pts[2].flatten().size), dtype=float) # external function call if a callable is passed if callable(fun[1]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid(self.pts[0].flatten(), x_int2, self.pts[2].flatten(), indexing="ij") + pts1, pts2, pts3 = xp.meshgrid(self.pts[0].flatten(), x_int2, self.pts[2].flatten(), indexing="ij") mat_f[:, :, :] = fun[1](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1460,7 +1472,7 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas2 = np.zeros((self.NbaseD[0], self.NbaseN[1], self.NbaseD[2]), dtype=float) + lambdas2 = xp.zeros((self.NbaseD[0], self.NbaseN[1], self.NbaseD[2]), dtype=float) ker_loc.kernel_pi22_3d( [self.NbaseD[0], self.NbaseN[1], self.NbaseD[2]], @@ -1478,7 +1490,11 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): self.wts[0], self.wts[2], mat_f.reshape( - self.pts[0].shape[0], self.pts[0].shape[1], x_int2.size, self.pts[2].shape[0], self.pts[2].shape[1] + self.pts[0].shape[0], + self.pts[0].shape[1], + x_int2.size, + self.pts[2].shape[0], + self.pts[2].shape[1], ), lambdas2, ) @@ -1486,13 +1502,13 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): # ======== 3-component ======== # evaluation of function at interpolation/quadrature points - mat_f = np.empty((self.pts[0].flatten().size, self.pts[1].flatten().size, x_int3.size), dtype=float) + mat_f = xp.empty((self.pts[0].flatten().size, self.pts[1].flatten().size, x_int3.size), dtype=float) # external function call if a callable is passed if callable(fun[2]): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid(self.pts[0].flatten(), self.pts[1].flatten(), x_int3, indexing="ij") + pts1, pts2, pts3 = xp.meshgrid(self.pts[0].flatten(), self.pts[1].flatten(), x_int3, indexing="ij") mat_f[:, :, :] = fun[2](pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1511,7 +1527,7 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas3 = np.zeros((self.NbaseD[0], self.NbaseD[1], self.NbaseN[2]), dtype=float) + lambdas3 = xp.zeros((self.NbaseD[0], self.NbaseD[1], self.NbaseN[2]), dtype=float) ker_loc.kernel_pi23_3d( [self.NbaseD[0], self.NbaseD[1], self.NbaseN[2]], @@ -1529,12 +1545,16 @@ def pi_2(self, fun, include_bc=True, eval_kind="meshgrid"): self.wts[0], self.wts[1], mat_f.reshape( - self.pts[0].shape[0], self.pts[0].shape[1], self.pts[1].shape[0], self.pts[1].shape[1], x_int3.size + self.pts[0].shape[0], + self.pts[0].shape[1], + self.pts[1].shape[0], + self.pts[1].shape[1], + x_int3.size, ), lambdas3, ) - return np.concatenate((lambdas1.flatten(), lambdas2.flatten(), lambdas3.flatten())) + return xp.concatenate((lambdas1.flatten(), lambdas2.flatten(), lambdas3.flatten())) # projector on space V3 (histopolation) def pi_3(self, fun, include_bc=True, eval_kind="meshgrid"): @@ -1559,16 +1579,20 @@ def pi_3(self, fun, include_bc=True, eval_kind="meshgrid"): """ # evaluation of function at quadrature points - mat_f = np.empty( - (self.pts[0].flatten().size, self.pts[1].flatten().size, self.pts[2].flatten().size), dtype=float + mat_f = xp.empty( + (self.pts[0].flatten().size, self.pts[1].flatten().size, self.pts[2].flatten().size), + dtype=float, ) # external function call if a callable is passed if callable(fun): # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid( - self.pts[0].flatten(), self.pts[1].flatten(), self.pts[2].flatten(), indexing="ij" + pts1, pts2, pts3 = xp.meshgrid( + self.pts[0].flatten(), + self.pts[1].flatten(), + self.pts[2].flatten(), + indexing="ij", ) mat_f[:, :, :] = fun(pts1, pts2, pts3) @@ -1582,7 +1606,9 @@ def pi_3(self, fun, include_bc=True, eval_kind="meshgrid"): for i2 in range(self.pts[1].size): for i3 in range(self.pts[2].size): mat_f[i1, i2, i3] = fun( - self.pts[0].flatten()[i1], self.pts[1].flatten()[i2], self.pts[2].flatten()[i3] + self.pts[0].flatten()[i1], + self.pts[1].flatten()[i2], + self.pts[2].flatten()[i3], ) # internal function call @@ -1590,7 +1616,7 @@ def pi_3(self, fun, include_bc=True, eval_kind="meshgrid"): print("no internal 3D function implemented!") # compute coefficients - lambdas = np.zeros((self.NbaseD[0], self.NbaseD[1], self.NbaseD[2]), dtype=float) + lambdas = xp.zeros((self.NbaseD[0], self.NbaseD[1], self.NbaseD[2]), dtype=float) ker_loc.kernel_pi3_3d( self.NbaseD, diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_L2_projector_kernel.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_L2_projector_kernel.py index 9aa26b243..0e711dbcf 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_L2_projector_kernel.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_L2_projector_kernel.py @@ -1390,33 +1390,42 @@ def vv_1_form( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) for i1 in range(cell_number[0]): @@ -1922,33 +1931,42 @@ def vv_push( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) for i1 in range(cell_number[0]): diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_L2.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_L2.py index c1ae9e9f6..137df7f09 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_L2.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_L2.py @@ -5,9 +5,9 @@ Classes for local projectors in 1D and 3D based on quasi-spline interpolation and histopolation. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI import struphy.feec.bsplines as bsp import struphy.feec.projectors.shape_pro_local.shape_L2_projector_kernel as ker_loc @@ -50,48 +50,48 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): self.indD = tensor_space.indD self.polar = False # local projectors for polar splines are not implemented yet - self.lambdas_0 = np.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) - self.potential_lambdas_0 = np.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_0 = xp.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) + self.potential_lambdas_0 = xp.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_11 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_12 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_13 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_11 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_12 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_13 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_1_21 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_22 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_23 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_21 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_22 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_23 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_1_31 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_32 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_33 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_31 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_32 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_33 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_11 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_12 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_13 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_11 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_12 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_13 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_2_21 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_22 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_23 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_21 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_22 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_23 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_2_31 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_32 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_33 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_31 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_32 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_33 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_3 = np.zeros((NbaseD[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_3 = xp.zeros((NbaseD[0], NbaseD[1], NbaseD[2]), dtype=float) self.p_size = p_size self.p_shape = p_shape - self.related = np.zeros(3, dtype=int) + self.related = xp.zeros(3, dtype=int) for a in range(3): - # self.related[a] = int(np.floor(NbaseN[a]/2.0)) + # self.related[a] = int(xp.floor(NbaseN[a]/2.0)) self.related[a] = int( - np.floor((3 * int((self.p_size[a] * (self.p_shape[a] + 1)) * self.Nel[a] + 1) + 3 * self.p[a]) / 2.0) + xp.floor((3 * int((self.p_size[a] * (self.p_shape[a] + 1)) * self.Nel[a] + 1) + 3 * self.p[a]) / 2.0), ) if (2 * self.related[a] + 1) > NbaseN[a]: - self.related[a] = int(np.floor(NbaseN[a] / 2.0)) + self.related[a] = int(xp.floor(NbaseN[a] / 2.0)) - self.kernel_0_loc = np.zeros( + self.kernel_0_loc = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -103,7 +103,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_11_loc = np.zeros( + self.kernel_1_11_loc = xp.zeros( ( NbaseD[0], NbaseN[1], @@ -114,7 +114,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_12_loc = np.zeros( + self.kernel_1_12_loc = xp.zeros( ( NbaseD[0], NbaseN[1], @@ -125,7 +125,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_13_loc = np.zeros( + self.kernel_1_13_loc = xp.zeros( ( NbaseD[0], NbaseN[1], @@ -137,7 +137,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_22_loc = np.zeros( + self.kernel_1_22_loc = xp.zeros( ( NbaseN[0], NbaseD[1], @@ -148,7 +148,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_23_loc = np.zeros( + self.kernel_1_23_loc = xp.zeros( ( NbaseN[0], NbaseD[1], @@ -160,7 +160,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_33_loc = np.zeros( + self.kernel_1_33_loc = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -172,12 +172,12 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.right_loc_1 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.right_loc_2 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.right_loc_3 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.right_loc_1 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.right_loc_2 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.right_loc_3 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) if self.mpi_rank == 0: - self.kernel_0 = np.zeros( + self.kernel_0 = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -189,7 +189,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_11 = np.zeros( + self.kernel_1_11 = xp.zeros( ( NbaseD[0], NbaseN[1], @@ -200,7 +200,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_12 = np.zeros( + self.kernel_1_12 = xp.zeros( ( NbaseN[0], NbaseD[1], @@ -211,7 +211,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_13 = np.zeros( + self.kernel_1_13 = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -223,7 +223,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_22 = np.zeros( + self.kernel_1_22 = xp.zeros( ( NbaseN[0], NbaseD[1], @@ -234,7 +234,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): ), dtype=float, ) - self.kernel_1_23 = np.zeros( + self.kernel_1_23 = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -246,7 +246,7 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.kernel_1_33 = np.zeros( + self.kernel_1_33 = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -258,9 +258,9 @@ def __init__(self, tensor_space, p_shape, p_size, NbaseN, NbaseD, mpi_comm): dtype=float, ) - self.right_1 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.right_2 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.right_3 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.right_1 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.right_2 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.right_3 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) else: self.kernel_0 = None @@ -301,11 +301,11 @@ def assemble_0_form(self, tensor_space_FEM, mpi_comm): Nj = tensor_space_FEM.Nbase_0form # conversion to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -316,7 +316,8 @@ def assemble_0_form(self, tensor_space_FEM, mpi_comm): col = Nj[1] * Ni[2] * col1 + Ni[2] * col2 + col3 M = spa.csr_matrix( - (self.kernel_0.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_0.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M.eliminate_zeros() @@ -358,11 +359,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -373,7 +374,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M11 = spa.csr_matrix( - (self.kernel_1_11.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_11.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M11.eliminate_zeros() @@ -384,11 +386,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -399,7 +401,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M12 = spa.csr_matrix( - (self.kernel_1_12.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_12.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M12.eliminate_zeros() @@ -410,11 +413,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -425,7 +428,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M13 = spa.csr_matrix( - (self.kernel_1_13.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_13.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M13.eliminate_zeros() @@ -436,11 +440,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -451,7 +455,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M22 = spa.csr_matrix( - (self.kernel_1_22.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_22.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M22.eliminate_zeros() @@ -462,11 +467,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -477,7 +482,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M23 = spa.csr_matrix( - (self.kernel_1_23.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_23.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M23.eliminate_zeros() @@ -488,11 +494,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -503,14 +509,15 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M33 = spa.csr_matrix( - (self.kernel_1_33.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_33.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M33.eliminate_zeros() # final block matrix M = spa.bmat([[M11, M12, M13], [M12.T, M22, M23], [M13.T, M23.T, M33]], format="csr") # print('insider_check', self.kernel_1_33) - return (M, np.concatenate((self.right_1.flatten(), self.right_2.flatten(), self.right_3.flatten()))) + return (M, xp.concatenate((self.right_1.flatten(), self.right_2.flatten(), self.right_3.flatten()))) def heavy_test(self, test1, test2, test3, acc, particles_loc, Np, domain): ker_loc.kernel_1_heavy( @@ -583,7 +590,7 @@ def potential_pi_0(self, particles_loc, Np, domain, mpi_comm): ------- kernel_0 matrix """ - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: ker_loc.potential_kernel_0_form( Np, self.p, @@ -630,7 +637,7 @@ def S_pi_0(self, particles_loc, Np, domain): kernel_0 matrix """ self.kernel_0[:, :, :, :, :, :] = 0.0 - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: ker_loc.kernel_0_form( Np, self.p, @@ -692,7 +699,7 @@ def S_pi_1(self, particles_loc, Np, domain): self.right_loc_2[:, :, :] = 0.0 self.right_loc_3[:, :, :] = 0.0 - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: ker_loc.kernel_1_form( self.indN[0], self.indN[1], @@ -757,7 +764,7 @@ def S_pi_1(self, particles_loc, Np, domain): print("non-periodic case not implemented!!!") def vv_S1(self, particles_loc, Np, domain, index_label, accvv, dt, mpi_comm): - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: if index_label == 1: ker_loc.vv_1_form( self.wts[0][0], diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_local.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_local.py index d361075e3..2ebb497a3 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_local.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_function_projectors_local.py @@ -5,9 +5,9 @@ Classes for local projectors in 1D and 3D based on quasi-spline interpolation and histopolation. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI import struphy.feec.bsplines as bsp import struphy.feec.projectors.shape_pro_local.shape_local_projector_kernel as ker_loc @@ -51,48 +51,48 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co self.polar = False # local projectors for polar splines are not implemented yet - self.lambdas_0 = np.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) - self.potential_lambdas_0 = np.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_0 = xp.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) + self.potential_lambdas_0 = xp.zeros((NbaseN[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_11 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_12 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_13 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_11 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_12 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_13 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_1_21 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_22 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_23 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_21 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_22 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_23 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_1_31 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.lambdas_1_32 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_1_33 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_1_31 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.lambdas_1_32 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_1_33 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_11 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_12 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_13 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_11 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_12 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_13 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_2_21 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_22 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_23 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_21 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_22 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_23 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_2_31 = np.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) - self.lambdas_2_32 = np.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) - self.lambdas_2_33 = np.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) + self.lambdas_2_31 = xp.zeros((NbaseN[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_2_32 = xp.zeros((NbaseD[0], NbaseN[1], NbaseD[2]), dtype=float) + self.lambdas_2_33 = xp.zeros((NbaseD[0], NbaseD[1], NbaseN[2]), dtype=float) - self.lambdas_3 = np.zeros((NbaseD[0], NbaseD[1], NbaseD[2]), dtype=float) + self.lambdas_3 = xp.zeros((NbaseD[0], NbaseD[1], NbaseD[2]), dtype=float) self.p_size = p_size self.p_shape = p_shape - self.related = np.zeros(3, dtype=int) + self.related = xp.zeros(3, dtype=int) for a in range(3): - # self.related[a] = int(np.floor(NbaseN[a]/2.0)) + # self.related[a] = int(xp.floor(NbaseN[a]/2.0)) self.related[a] = int( - np.floor((3 * int((self.p_size[a] * (self.p_shape[a] + 1)) * self.Nel[a] + 1) + 3 * self.p[a]) / 2.0) + xp.floor((3 * int((self.p_size[a] * (self.p_shape[a] + 1)) * self.Nel[a] + 1) + 3 * self.p[a]) / 2.0), ) if (2 * self.related[a] + 1) > NbaseN[a]: - self.related[a] = int(np.floor(NbaseN[a] / 2.0)) + self.related[a] = int(xp.floor(NbaseN[a] / 2.0)) - self.kernel_0_loc = np.zeros( + self.kernel_0_loc = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -104,7 +104,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_11_loc = np.zeros( + self.kernel_1_11_loc = xp.zeros( ( NbaseD[0], NbaseN[1], @@ -115,7 +115,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_12_loc = np.zeros( + self.kernel_1_12_loc = xp.zeros( ( NbaseD[0], NbaseN[1], @@ -126,7 +126,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_13_loc = np.zeros( + self.kernel_1_13_loc = xp.zeros( ( NbaseD[0], NbaseN[1], @@ -138,7 +138,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_22_loc = np.zeros( + self.kernel_1_22_loc = xp.zeros( ( NbaseN[0], NbaseD[1], @@ -149,7 +149,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_23_loc = np.zeros( + self.kernel_1_23_loc = xp.zeros( ( NbaseN[0], NbaseD[1], @@ -161,7 +161,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_33_loc = np.zeros( + self.kernel_1_33_loc = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -173,12 +173,12 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.right_loc_1 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.right_loc_2 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.right_loc_3 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.right_loc_1 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.right_loc_2 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.right_loc_3 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) if self.mpi_rank == 0: - self.kernel_0 = np.zeros( + self.kernel_0 = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -190,7 +190,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_11 = np.zeros( + self.kernel_1_11 = xp.zeros( ( NbaseD[0], NbaseN[1], @@ -201,7 +201,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_12 = np.zeros( + self.kernel_1_12 = xp.zeros( ( NbaseN[0], NbaseD[1], @@ -212,7 +212,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_13 = np.zeros( + self.kernel_1_13 = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -224,7 +224,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_22 = np.zeros( + self.kernel_1_22 = xp.zeros( ( NbaseN[0], NbaseD[1], @@ -235,7 +235,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co ), dtype=float, ) - self.kernel_1_23 = np.zeros( + self.kernel_1_23 = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -247,7 +247,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.kernel_1_33 = np.zeros( + self.kernel_1_33 = xp.zeros( ( NbaseN[0], NbaseN[1], @@ -259,9 +259,9 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co dtype=float, ) - self.right_1 = np.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) - self.right_2 = np.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) - self.right_3 = np.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) + self.right_1 = xp.zeros((NbaseD[0], NbaseN[1], NbaseN[2]), dtype=float) + self.right_2 = xp.zeros((NbaseN[0], NbaseD[1], NbaseN[2]), dtype=float) + self.right_3 = xp.zeros((NbaseN[0], NbaseN[1], NbaseD[2]), dtype=float) else: self.kernel_0 = None @@ -279,7 +279,7 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co self.right_2 = None self.right_3 = None - self.num_cell = np.empty(3, dtype=int) + self.num_cell = xp.empty(3, dtype=int) for i in range(3): if self.p[i] == 1: self.num_cell[i] = 1 @@ -287,14 +287,16 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co self.num_cell[i] = 2 # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = [np.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] - self.wts_loc = [np.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] + self.pts_loc = [xp.polynomial.legendre.leggauss(n_quad)[0] for n_quad in self.n_quad] + self.wts_loc = [xp.polynomial.legendre.leggauss(n_quad)[1] for n_quad in self.n_quad] self.pts = [0, 0, 0] self.wts = [0, 0, 0] for a in range(3): self.pts[a], self.wts[a] = bsp.quadrature_grid( - [0, 1.0 / 2.0 / self.Nel[a]], self.pts_loc[a], self.wts_loc[a] + [0, 1.0 / 2.0 / self.Nel[a]], + self.pts_loc[a], + self.wts_loc[a], ) # print('check_pts', self.pts[0].shape, self.pts[1].shape, self.pts[2].shape) # print('check_pts', self.wts) @@ -302,79 +304,79 @@ def __init__(self, tensor_space, n_quad, p_shape, p_size, NbaseN, NbaseD, mpi_co self.coeff_i = [0, 0, 0] self.coeff_h = [0, 0, 0] for a in range(3): - if self.bc[a] == True: - self.coeff_i[a] = np.zeros(2 * self.p[a], dtype=float) - self.coeff_h[a] = np.zeros(2 * self.p[a], dtype=float) + if self.bc[a]: + self.coeff_i[a] = xp.zeros(2 * self.p[a], dtype=float) + self.coeff_h[a] = xp.zeros(2 * self.p[a], dtype=float) if self.p[a] == 1: - self.coeff_i[a][:] = np.array([1.0, 0.0]) - self.coeff_h[a][:] = np.array([1.0, 1.0]) + self.coeff_i[a][:] = xp.array([1.0, 0.0]) + self.coeff_h[a][:] = xp.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][:] = 1 / 2 * np.array([-1.0, 4.0, -1.0, 0.0]) - self.coeff_h[a][:] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_i[a][:] = 1 / 2 * xp.array([-1.0, 4.0, -1.0, 0.0]) + self.coeff_h[a][:] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) elif self.p[a] == 3: - self.coeff_i[a][:] = 1 / 6 * np.array([1.0, -8.0, 20.0, -8.0, 1.0, 0.0]) - self.coeff_h[a][:] = 1 / 6 * np.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) + self.coeff_i[a][:] = 1 / 6 * xp.array([1.0, -8.0, 20.0, -8.0, 1.0, 0.0]) + self.coeff_h[a][:] = 1 / 6 * xp.array([1.0, -7.0, 12.0, 12.0, -7.0, 1.0]) elif self.p[a] == 4: - self.coeff_i[a][:] = 2 / 45 * np.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0, 0.0]) + self.coeff_i[a][:] = 2 / 45 * xp.array([-1.0, 16.0, -295 / 4, 140.0, -295 / 4, 16.0, -1.0, 0.0]) self.coeff_h[a][:] = ( - 2 / 45 * np.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) + 2 / 45 * xp.array([-1.0, 15.0, -231 / 4, 265 / 4, 265 / 4, -231 / 4, 15.0, -1.0]) ) else: print("degree > 4 not implemented!") else: - self.coeff_i[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) - self.coeff_h[a] = np.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) + self.coeff_i[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a] - 1), dtype=float) + self.coeff_h[a] = xp.zeros((2 * self.p[a] - 1, 2 * self.p[a]), dtype=float) if self.p[a] == 1: - self.coeff_i[a][0, :] = np.array([1.0]) - self.coeff_h[a][0, :] = np.array([1.0, 1.0]) + self.coeff_i[a][0, :] = xp.array([1.0]) + self.coeff_h[a][0, :] = xp.array([1.0, 1.0]) elif self.p[a] == 2: - self.coeff_i[a][0, :] = 1 / 2 * np.array([2.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 2 * np.array([-1.0, 4.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 2 * np.array([0.0, 0.0, 2.0]) + self.coeff_i[a][0, :] = 1 / 2 * xp.array([2.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 2 * xp.array([-1.0, 4.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, 2.0]) - self.coeff_h[a][0, :] = 1 / 2 * np.array([3.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 2 * np.array([-1.0, 3.0, 3.0, -1.0]) - self.coeff_h[a][2, :] = 1 / 2 * np.array([0.0, 0.0, -1.0, 3.0]) + self.coeff_h[a][0, :] = 1 / 2 * xp.array([3.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 2 * xp.array([-1.0, 3.0, 3.0, -1.0]) + self.coeff_h[a][2, :] = 1 / 2 * xp.array([0.0, 0.0, -1.0, 3.0]) elif self.p[a] == 3: - self.coeff_i[a][0, :] = 1 / 18 * np.array([18.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 18 * np.array([-5.0, 40.0, -24.0, 8.0, -1.0]) - self.coeff_i[a][2, :] = 1 / 18 * np.array([3.0, -24.0, 60.0, -24.0, 3.0]) - self.coeff_i[a][3, :] = 1 / 18 * np.array([-1.0, 8.0, -24.0, 40.0, -5.0]) - self.coeff_i[a][4, :] = 1 / 18 * np.array([0.0, 0.0, 0.0, 0.0, 18.0]) - - self.coeff_h[a][0, :] = 1 / 18 * np.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 18 * np.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 18 * np.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) - self.coeff_h[a][3, :] = 1 / 18 * np.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) - self.coeff_h[a][4, :] = 1 / 18 * np.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) + self.coeff_i[a][0, :] = 1 / 18 * xp.array([18.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 18 * xp.array([-5.0, 40.0, -24.0, 8.0, -1.0]) + self.coeff_i[a][2, :] = 1 / 18 * xp.array([3.0, -24.0, 60.0, -24.0, 3.0]) + self.coeff_i[a][3, :] = 1 / 18 * xp.array([-1.0, 8.0, -24.0, 40.0, -5.0]) + self.coeff_i[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, 0.0, 0.0, 18.0]) + + self.coeff_h[a][0, :] = 1 / 18 * xp.array([23.0, -17.0, 7.0, -1.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 18 * xp.array([-8.0, 56.0, -28.0, 4.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 18 * xp.array([3.0, -21.0, 36.0, 36.0, -21.0, 3.0]) + self.coeff_h[a][3, :] = 1 / 18 * xp.array([0.0, 0.0, 4.0, -28.0, 56.0, -8.0]) + self.coeff_h[a][4, :] = 1 / 18 * xp.array([0.0, 0.0, -1.0, 7.0, -17.0, 23.0]) elif self.p[a] == 4: - self.coeff_i[a][0, :] = 1 / 360 * np.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - self.coeff_i[a][1, :] = 1 / 360 * np.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) - self.coeff_i[a][2, :] = 1 / 360 * np.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) - self.coeff_i[a][3, :] = 1 / 360 * np.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) - self.coeff_i[a][4, :] = 1 / 360 * np.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) - self.coeff_i[a][5, :] = 1 / 360 * np.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) - self.coeff_i[a][6, :] = 1 / 360 * np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) - - self.coeff_h[a][0, :] = 1 / 360 * np.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) - self.coeff_h[a][1, :] = 1 / 360 * np.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) - self.coeff_h[a][2, :] = 1 / 360 * np.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) + self.coeff_i[a][0, :] = 1 / 360 * xp.array([360.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + self.coeff_i[a][1, :] = 1 / 360 * xp.array([-59.0, 944.0, -1000.0, 720.0, -305.0, 64.0, -4.0]) + self.coeff_i[a][2, :] = 1 / 360 * xp.array([23.0, -368.0, 1580.0, -1360.0, 605.0, -128.0, 8.0]) + self.coeff_i[a][3, :] = 1 / 360 * xp.array([-16.0, 256.0, -1180.0, 2240.0, -1180.0, 256.0, -16.0]) + self.coeff_i[a][4, :] = 1 / 360 * xp.array([8.0, -128.0, 605.0, -1360.0, 1580.0, -368.0, 23.0]) + self.coeff_i[a][5, :] = 1 / 360 * xp.array([-4.0, 64.0, -305.0, 720.0, -1000.0, 944.0, -59.0]) + self.coeff_i[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 360.0]) + + self.coeff_h[a][0, :] = 1 / 360 * xp.array([419.0, -525.0, 475.0, -245.0, 60.0, -4.0, 0.0, 0.0]) + self.coeff_h[a][1, :] = 1 / 360 * xp.array([-82.0, 1230.0, -1350.0, 730.0, -180.0, 12.0, 0.0, 0.0]) + self.coeff_h[a][2, :] = 1 / 360 * xp.array([39.0, -585.0, 2175.0, -1425.0, 360.0, -24.0, 0.0, 0.0]) self.coeff_h[a][3, :] = ( - 1 / 360 * np.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) + 1 / 360 * xp.array([-16.0, 240.0, -924.0, 1060.0, 1060.0, -924.0, 240.0, -16.0]) ) - self.coeff_h[a][4, :] = 1 / 360 * np.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) - self.coeff_h[a][5, :] = 1 / 360 * np.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) - self.coeff_h[a][6, :] = 1 / 360 * np.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) + self.coeff_h[a][4, :] = 1 / 360 * xp.array([0.0, 0.0, -24.0, 360.0, -1425.0, 2175.0, -585.0, 39.0]) + self.coeff_h[a][5, :] = 1 / 360 * xp.array([0.0, 0.0, 12.0, -180.0, 730.0, -1350.0, 1230.0, -82.0]) + self.coeff_h[a][6, :] = 1 / 360 * xp.array([0.0, 0.0, -4.0, 60.0, -245.0, 475.0, -525.0, 419.0]) else: print("degree > 4 not implemented!") @@ -402,11 +404,11 @@ def assemble_0_form(self, tensor_space_FEM, mpi_comm): Nj = tensor_space_FEM.Nbase_0form # conversion to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -417,7 +419,8 @@ def assemble_0_form(self, tensor_space_FEM, mpi_comm): col = Nj[1] * Ni[2] * col1 + Ni[2] * col2 + col3 M = spa.csr_matrix( - (self.kernel_0.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_0.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M.eliminate_zeros() @@ -459,11 +462,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -474,7 +477,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M11 = spa.csr_matrix( - (self.kernel_1_11.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_11.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M11.eliminate_zeros() @@ -485,11 +489,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -500,7 +504,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M12 = spa.csr_matrix( - (self.kernel_1_12.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_12.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M12.eliminate_zeros() @@ -511,11 +516,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -526,7 +531,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M13 = spa.csr_matrix( - (self.kernel_1_13.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_13.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M13.eliminate_zeros() @@ -537,11 +543,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -552,7 +558,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M22 = spa.csr_matrix( - (self.kernel_1_22.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_22.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M22.eliminate_zeros() @@ -563,11 +570,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -578,7 +585,8 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M23 = spa.csr_matrix( - (self.kernel_1_23.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_23.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M23.eliminate_zeros() @@ -589,11 +597,11 @@ def assemble_1_form(self, tensor_space_FEM): Nj = tensor_space_FEM.Nbase_1form[b] # convert to sparse matrix - indices = np.indices( - (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1) + indices = xp.indices( + (Ni[0], Ni[1], Ni[2], 2 * self.related[0] + 1, 2 * self.related[1] + 1, 2 * self.related[2] + 1), ) - shift = [np.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] + shift = [xp.arange(Ni) - offset for Ni, offset in zip(Ni, self.related)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -604,14 +612,15 @@ def assemble_1_form(self, tensor_space_FEM): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M33 = spa.csr_matrix( - (self.kernel_1_33.flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (self.kernel_1_33.flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M33.eliminate_zeros() # final block matrix M = spa.bmat([[M11, M12, M13], [M12.T, M22, M23], [M13.T, M23.T, M33]], format="csr") # print('insider_check', self.kernel_1_33) - return (M, np.concatenate((self.right_1.flatten(), self.right_2.flatten(), self.right_3.flatten()))) + return (M, xp.concatenate((self.right_1.flatten(), self.right_2.flatten(), self.right_3.flatten()))) def heavy_test(self, test1, test2, test3, acc, particles_loc, Np, domain): ker_loc.kernel_1_heavy( @@ -677,7 +686,7 @@ def potential_pi_0(self, particles_loc, Np, domain, mpi_comm): ------- kernel_0 matrix """ - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: ker_loc.potential_kernel_0_form( Np, self.p, @@ -724,7 +733,7 @@ def S_pi_0(self, particles_loc, Np, domain): kernel_0 matrix """ self.kernel_0[:, :, :, :, :, :] = 0.0 - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: ker_loc.kernel_0_form( Np, self.p, @@ -786,7 +795,7 @@ def S_pi_1(self, particles_loc, Np, domain): self.right_loc_2[:, :, :] = 0.0 self.right_loc_3[:, :, :] = 0.0 - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: ker_loc.kernel_1_form( self.right_loc_1, self.right_loc_2, @@ -873,7 +882,7 @@ def S_pi_01(self, particles_loc, Np, domain): self.right_loc_2[:, :, :] = 0.0 self.right_loc_3[:, :, :] = 0.0 - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: ker_loc.kernel_01_form( self.right_loc_1, self.right_loc_2, @@ -924,7 +933,7 @@ def S_pi_01(self, particles_loc, Np, domain): print("non-periodic case not implemented!!!") def vv_S1(self, particles_loc, Np, domain, index_label, accvv, dt, mpi_comm): - if self.bc[0] == True and self.bc[1] == True and self.bc[2] == True: + if self.bc[0] and self.bc[1] and self.bc[2]: if index_label == 1: ker_loc.vv_1_form( self.wts[0][0], diff --git a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_local_projector_kernel.py b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_local_projector_kernel.py index c0ebc624d..6db315daa 100644 --- a/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_local_projector_kernel.py +++ b/src/struphy/eigenvalue_solvers/legacy/projectors_local/shape_pro_local/shape_local_projector_kernel.py @@ -108,7 +108,8 @@ def kernel_0_form( width[il1] = p[il1] + cell_number[il1] - 1 mat_f = empty( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], num_cell[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], num_cell[2]), + dtype=float, ) mat_f[:, :, :, :, :, :] = 0.0 @@ -335,7 +336,8 @@ def potential_kernel_0_form( width[il1] = p[il1] + cell_number[il1] - 1 mat_f = empty( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], num_cell[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], num_cell[2]), + dtype=float, ) mat_f[:, :, :, :, :, :] = 0.0 @@ -539,33 +541,42 @@ def kernel_1_form( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) for i1 in range(cell_number[0]): @@ -1259,33 +1270,42 @@ def bv_localproj_push( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) for i1 in range(cell_number[0]): @@ -1805,33 +1825,42 @@ def kernel_1_heavy( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) for i1 in range(cell_number[0]): @@ -2373,33 +2402,42 @@ def vv_1_form( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) for i1 in range(cell_number[0]): @@ -2905,33 +2943,42 @@ def vv_push( # evaluation of function at interpolation/quadrature points mat_11 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_21 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_31 = zeros( - (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], 2, num_cell[1], num_cell[2], quad[0]), + dtype=float, ) mat_12 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_22 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_32 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], 2, num_cell[2], quad[1]), + dtype=float, ) mat_13 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_23 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) mat_33 = zeros( - (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), dtype=float + (cell_number[0], cell_number[1], cell_number[2], num_cell[0], num_cell[1], 2, quad[2]), + dtype=float, ) for i1 in range(cell_number[0]): diff --git a/src/struphy/eigenvalue_solvers/mass_matrices_1d.py b/src/struphy/eigenvalue_solvers/mass_matrices_1d.py index 4af163bc4..b5013c088 100644 --- a/src/struphy/eigenvalue_solvers/mass_matrices_1d.py +++ b/src/struphy/eigenvalue_solvers/mass_matrices_1d.py @@ -2,7 +2,7 @@ # # Copyright 2020 Florian Holderied -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.bsplines.bsplines as bsp @@ -47,7 +47,7 @@ def get_M(spline_space, phi_i=0, phi_j=0, fun=None): # evaluation of weight function at quadrature points (optional) if fun == None: - mat_fun = np.ones(pts.shape, dtype=float) + mat_fun = xp.ones(pts.shape, dtype=float) else: mat_fun = fun(pts.flatten()).reshape(Nel, n_quad) @@ -74,7 +74,7 @@ def get_M(spline_space, phi_i=0, phi_j=0, fun=None): bj = basisD[:, :, 0, :] # matrix assembly - M = np.zeros((Ni, 2 * p + 1), dtype=float) + M = xp.zeros((Ni, 2 * p + 1), dtype=float) for ie in range(Nel): for il in range(p + 1 - ni): @@ -86,8 +86,8 @@ def get_M(spline_space, phi_i=0, phi_j=0, fun=None): M[(ie + il) % Ni, p + jl - il] += value - indices = np.indices((Ni, 2 * p + 1)) - shift = np.arange(Ni) - p + indices = xp.indices((Ni, 2 * p + 1)) + shift = xp.arange(Ni) - p row = indices[0].flatten() col = (indices[1] + shift[:, None]) % Nj @@ -137,13 +137,13 @@ def get_M_gen(spline_space, phi_i=0, phi_j=0, fun=None, jac=None): # evaluation of weight function at quadrature points (optional) if fun == None: - mat_fun = np.ones(pts.shape, dtype=float) + mat_fun = xp.ones(pts.shape, dtype=float) else: mat_fun = fun(pts.flatten()).reshape(Nel, n_quad) # evaluation of jacobian at quadrature points if jac == None: - mat_jac = np.ones(pts.shape, dtype=float) + mat_jac = xp.ones(pts.shape, dtype=float) else: mat_jac = jac(pts.flatten()).reshape(Nel, n_quad) @@ -180,7 +180,7 @@ def get_M_gen(spline_space, phi_i=0, phi_j=0, fun=None, jac=None): bj = basis_t[:, :, 0, :] # matrix assembly - M = np.zeros((Ni, 2 * p + 1), dtype=float) + M = xp.zeros((Ni, 2 * p + 1), dtype=float) for ie in range(Nel): for il in range(p + 1 - ni): @@ -192,8 +192,8 @@ def get_M_gen(spline_space, phi_i=0, phi_j=0, fun=None, jac=None): M[(ie + il) % Ni, p + jl - il] += value - indices = np.indices((Ni, 2 * p + 1)) - shift = np.arange(Ni) - p + indices = xp.indices((Ni, 2 * p + 1)) + shift = xp.arange(Ni) - p row = indices[0].flatten() col = (indices[1] + shift[:, None]) % Nj @@ -235,11 +235,11 @@ def test_M(spline_space, phi_i=0, phi_j=0, fun=lambda eta: 1.0, jac=lambda eta: bj = lambda eta: spline_space.evaluate_D(eta, cj) / spline_space.Nel # coefficients - ci = np.zeros(Ni, dtype=float) - cj = np.zeros(Nj, dtype=float) + ci = xp.zeros(Ni, dtype=float) + cj = xp.zeros(Nj, dtype=float) # integration - M = np.zeros((Ni, Nj), dtype=float) + M = xp.zeros((Ni, Nj), dtype=float) for i in range(Ni): for j in range(Nj): diff --git a/src/struphy/eigenvalue_solvers/mass_matrices_2d.py b/src/struphy/eigenvalue_solvers/mass_matrices_2d.py index 4e62c6c72..c2c6c3ae9 100644 --- a/src/struphy/eigenvalue_solvers/mass_matrices_2d.py +++ b/src/struphy/eigenvalue_solvers/mass_matrices_2d.py @@ -2,7 +2,7 @@ # # Copyright 2020 Florian Holderied -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_2d as ker @@ -46,7 +46,7 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): # evaluation of weight function at quadrature points if weight == None: - mat_w = np.ones(det_df.shape, dtype=float) + mat_w = xp.ones(det_df.shape, dtype=float) else: mat_w = weight(pts[0].flatten(), pts[1].flatten(), 0.0) mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) @@ -55,14 +55,14 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_0form Nj = tensor_space_FEM.Nbase_0form - M = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array([0, 0]), - np.array([0, 0]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array([0, 0]), + xp.array([0, 0]), wts[0], wts[1], basisN[0], @@ -76,9 +76,9 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # conversion to sparse matrix - indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() @@ -156,7 +156,7 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_1form[a] Nj = tensor_space_FEM.Nbase_1form[b] - M[a][b] = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M[a][b] = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) # evaluate inverse metric tensor at quadrature points if weight == None: @@ -167,13 +167,13 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # assemble block if weight is not zero - if np.any(mat_w): + if xp.any(mat_w): ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array(ns[a]), - np.array(ns[b]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array(ns[a]), + xp.array(ns[b]), wts[0], wts[1], basis[a][0], @@ -187,9 +187,9 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() @@ -272,7 +272,7 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_2form[a] Nj = tensor_space_FEM.Nbase_2form[b] - M[a][b] = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M[a][b] = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -283,13 +283,13 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # assemble block if weight is not zero - if np.any(mat_w): + if xp.any(mat_w): ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array(ns[a]), - np.array(ns[b]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array(ns[a]), + xp.array(ns[b]), wts[0], wts[1], basis[a][0], @@ -303,9 +303,9 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() @@ -369,7 +369,7 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): # evaluation of weight function at quadrature points if weight == None: - mat_w = np.ones(det_df.shape, dtype=float) + mat_w = xp.ones(det_df.shape, dtype=float) else: mat_w = weight(pts[0].flatten(), pts[1].flatten(), 0.0) mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) @@ -378,14 +378,14 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_3form Nj = tensor_space_FEM.Nbase_3form - M = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array([1, 1]), - np.array([1, 1]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array([1, 1]), + xp.array([1, 1]), wts[0], wts[1], basisD[0], @@ -399,9 +399,9 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # conversion to sparse matrix - indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() @@ -475,7 +475,7 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_0form Nj = tensor_space_FEM.Nbase_0form - M[a][b] = np.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) + M[a][b] = xp.zeros((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -486,13 +486,13 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1]) # assemble block if weight is not zero - if np.any(mat_w): + if xp.any(mat_w): ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array(ns[a]), - np.array(ns[b]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array(ns[a]), + xp.array(ns[b]), wts[0], wts[1], basis[a][0], @@ -506,9 +506,9 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = np.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) + indices = xp.indices((Ni[0], Ni[1], 2 * p[0] + 1, 2 * p[1] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * indices[0] + indices[1]).flatten() diff --git a/src/struphy/eigenvalue_solvers/mass_matrices_3d.py b/src/struphy/eigenvalue_solvers/mass_matrices_3d.py index d77052727..d3dc4cad2 100644 --- a/src/struphy/eigenvalue_solvers/mass_matrices_3d.py +++ b/src/struphy/eigenvalue_solvers/mass_matrices_3d.py @@ -2,7 +2,7 @@ # # Copyright 2020 Florian Holderied (florian.holderied@ipp.mpg.de) -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_3d as ker @@ -46,7 +46,7 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): # evaluation of weight function at quadrature points if weight == None: - mat_w = np.ones(det_df.shape, dtype=float) + mat_w = xp.ones(det_df.shape, dtype=float) else: mat_w = weight(pts[0].flatten(), pts[1].flatten(), pts[2].flatten()) mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) @@ -55,14 +55,14 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_0form Nj = tensor_space_FEM.Nbase_0form - M = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array([0, 0, 0]), - np.array([0, 0, 0]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array([0, 0, 0]), + xp.array([0, 0, 0]), wts[0], wts[1], wts[2], @@ -80,9 +80,9 @@ def get_M0(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # conversion to sparse matrix - indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -161,7 +161,7 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_1form[a] Nj = tensor_space_FEM.Nbase_1form[b] - M[a][b] = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M[a][b] = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -172,13 +172,13 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # assemble block if weight is not zero - if np.any(mat_w): + if xp.any(mat_w): ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array(ns[a]), - np.array(ns[b]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array(ns[a]), + xp.array(ns[b]), wts[0], wts[1], wts[2], @@ -196,9 +196,9 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -209,7 +209,8 @@ def get_M1(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M[a][b] = spa.csr_matrix( - (M[a][b].flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (M[a][b].flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M[a][b].eliminate_zeros() @@ -280,7 +281,7 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_2form[a] Nj = tensor_space_FEM.Nbase_2form[b] - M[a][b] = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M[a][b] = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -291,13 +292,13 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # assemble block if weight is not zero - if np.any(mat_w): + if xp.any(mat_w): ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array(ns[a]), - np.array(ns[b]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array(ns[a]), + xp.array(ns[b]), wts[0], wts[1], wts[2], @@ -315,9 +316,9 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -328,7 +329,8 @@ def get_M2(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M[a][b] = spa.csr_matrix( - (M[a][b].flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (M[a][b].flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M[a][b].eliminate_zeros() @@ -381,7 +383,7 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): # evaluation of weight function at quadrature points if weight == None: - mat_w = np.ones(det_df.shape, dtype=float) + mat_w = xp.ones(det_df.shape, dtype=float) else: mat_w = weight(pts[0].flatten(), pts[1].flatten(), pts[2].flatten()) mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) @@ -390,14 +392,14 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_3form Nj = tensor_space_FEM.Nbase_3form - M = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array([1, 1, 1]), - np.array([1, 1, 1]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array([1, 1, 1]), + xp.array([1, 1, 1]), wts[0], wts[1], wts[2], @@ -415,9 +417,9 @@ def get_M3(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # conversion to sparse matrix - indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -515,7 +517,7 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): Ni = tensor_space_FEM.Nbase_2form[a] Nj = tensor_space_FEM.Nbase_2form[b] - M[a][b] = np.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) + M[a][b] = xp.zeros((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1), dtype=float) # evaluate metric tensor at quadrature points if weight == None: @@ -526,13 +528,13 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): mat_w = mat_w.reshape(Nel[0], n_quad[0], Nel[1], n_quad[1], Nel[2], n_quad[2]) # assemble block if weight is not zero - if np.any(mat_w): + if xp.any(mat_w): ker.kernel_mass( - np.array(Nel), - np.array(p), - np.array(n_quad), - np.array(ns[a]), - np.array(ns[b]), + xp.array(Nel), + xp.array(p), + xp.array(n_quad), + xp.array(ns[a]), + xp.array(ns[b]), wts[0], wts[1], wts[2], @@ -550,9 +552,9 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): ) # convert to sparse matrix - indices = np.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) + indices = xp.indices((Ni[0], Ni[1], Ni[2], 2 * p[0] + 1, 2 * p[1] + 1, 2 * p[2] + 1)) - shift = [np.arange(Ni) - p for Ni, p in zip(Ni, p)] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni, p)] row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() @@ -563,7 +565,8 @@ def get_Mv(tensor_space_FEM, domain, apply_boundary_ops=False, weight=None): col = Nj[1] * Nj[2] * col1 + Nj[2] * col2 + col3 M[a][b] = spa.csr_matrix( - (M[a][b].flatten(), (row, col.flatten())), shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]) + (M[a][b].flatten(), (row, col.flatten())), + shape=(Ni[0] * Ni[1] * Ni[2], Nj[0] * Nj[1] * Nj[2]), ) M[a][b].eliminate_zeros() diff --git a/src/struphy/eigenvalue_solvers/mhd_axisymmetric_main.py b/src/struphy/eigenvalue_solvers/mhd_axisymmetric_main.py index 3810f1d60..04a194c7f 100644 --- a/src/struphy/eigenvalue_solvers/mhd_axisymmetric_main.py +++ b/src/struphy/eigenvalue_solvers/mhd_axisymmetric_main.py @@ -32,7 +32,7 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N import os import time - import numpy as np + import cunumpy as xp import scipy.sparse as spa from struphy.eigenvalue_solvers.mhd_operators import MHDOperators @@ -45,13 +45,13 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N # print grid info print("\nGrid parameters:") - print(f"number of elements :", num_params["Nel"]) - print(f"spline degrees :", num_params["p"]) - print(f"periodic bcs :", num_params["spl_kind"]) - print(f"hom. Dirichlet bc :", num_params["bc"]) - print(f"GL quad pts (L2) :", num_params["nq_el"]) - print(f"GL quad pts (hist) :", num_params["nq_pr"]) - print(f"polar Ck :", num_params["polar_ck"]) + print("number of elements :", num_params["Nel"]) + print("spline degrees :", num_params["p"]) + print("periodic bcs :", num_params["spl_kind"]) + print("hom. Dirichlet bc :", num_params["bc"]) + print("GL quad pts (L2) :", num_params["nq_el"]) + print("GL quad pts (hist) :", num_params["nq_pr"]) + print("polar Ck :", num_params["polar_ck"]) print("") # extract numerical parameters @@ -72,7 +72,12 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N # set up 2d tensor-product space space_2d = Tensor_spline_space( - [space_1d_1, space_1d_2], polar_ck, eq_mhd.domain.cx[:, :, 0], eq_mhd.domain.cy[:, :, 0], n_tor, basis_tor + [space_1d_1, space_1d_2], + polar_ck, + eq_mhd.domain.cx[:, :, 0], + eq_mhd.domain.cy[:, :, 0], + n_tor, + basis_tor, ) # set up 2d projectors @@ -141,14 +146,14 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N .dot( EF.T.dot(space_2d.C0.conjugate().T.dot(M2_0.dot(space_2d.C0.dot(EF)))) + mhd_ops.MJ_mat.dot(space_2d.C0.dot(EF)) - - space_2d.D0.conjugate().T.dot(M3_0.dot(L)) + - space_2d.D0.conjugate().T.dot(M3_0.dot(L)), ) .toarray() ) print("Assembly of final system matrix done --> start of eigenvalue calculation") - omega2, U2_eig = np.linalg.eig(MAT) + omega2, U2_eig = xp.linalg.eig(MAT) print("Eigenstates calculated") @@ -161,8 +166,9 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N else: n_tor_str = "+" + str(n_tor) - np.save( - os.path.join(path_out, "spec_n_" + n_tor_str + ".npy"), np.vstack((omega2.reshape(1, omega2.size), U2_eig)) + xp.save( + os.path.join(path_out, "spec_n_" + n_tor_str + ".npy"), + xp.vstack((omega2.reshape(1, omega2.size), U2_eig)), ) # or return eigenfrequencies, eigenvectors and system matrix @@ -180,7 +186,7 @@ def solve_mhd_ev_problem_2d(num_params, eq_mhd, n_tor, basis_tor="i", path_out=N # parse arguments parser = argparse.ArgumentParser( - description="Computes the complete eigenspectrum for a given axisymmetric MHD equilibrium." + description="Computes the complete eigenspectrum for a given axisymmetric MHD equilibrium.", ) parser.add_argument("n_tor", type=int, help="the toroidal mode number") diff --git a/src/struphy/eigenvalue_solvers/mhd_axisymmetric_pproc.py b/src/struphy/eigenvalue_solvers/mhd_axisymmetric_pproc.py index 75a116c09..1685147e1 100644 --- a/src/struphy/eigenvalue_solvers/mhd_axisymmetric_pproc.py +++ b/src/struphy/eigenvalue_solvers/mhd_axisymmetric_pproc.py @@ -3,7 +3,7 @@ def main(): import argparse import os - import numpy as np + import cunumpy as xp import yaml # parse arguments @@ -21,7 +21,10 @@ def main(): ) parser.add_argument( - "--input-abs", type=str, metavar="DIR", help="directory with eigenspectrum (.npy) file, absolute path" + "--input-abs", + type=str, + metavar="DIR", + help="directory with eigenspectrum (.npy) file, absolute path", ) parser.add_argument("lower", type=float, help="lower range of squared eigenfrequency") @@ -51,18 +54,18 @@ def main(): spec_path = os.path.join(input_path, "spec_n_" + n_tor_str + ".npy") - omega2, U2_eig = np.split(np.load(spec_path), [1], axis=0) + omega2, U2_eig = xp.split(xp.load(spec_path), [1], axis=0) omega2 = omega2.flatten() - modes_ind = np.where((np.real(omega2) < args.upper) & (np.real(omega2) > args.lower))[0] + modes_ind = xp.where((xp.real(omega2) < args.upper) & (xp.real(omega2) > args.lower))[0] omega2 = omega2[modes_ind] U2_eig = U2_eig[:, modes_ind] # save restricted spectrum - np.save( + xp.save( os.path.join(input_path, "spec_" + str(args.lower) + "_" + str(args.upper) + "_n_" + n_tor_str + ".npy"), - np.vstack((omega2.reshape(1, omega2.size), U2_eig)), + xp.vstack((omega2.reshape(1, omega2.size), U2_eig)), ) diff --git a/src/struphy/eigenvalue_solvers/mhd_operators.py b/src/struphy/eigenvalue_solvers/mhd_operators.py index 22355c1c9..6f7325c6b 100644 --- a/src/struphy/eigenvalue_solvers/mhd_operators.py +++ b/src/struphy/eigenvalue_solvers/mhd_operators.py @@ -3,7 +3,7 @@ # Copyright 2021 Florian Holderied (florian.holderied@ipp.mpg.de) -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.legacy.mass_matrices_3d_pre as mass_3d_pre @@ -402,7 +402,7 @@ def __EF(self, u): out1 = self.int_N3.dot(self.dofs_EF[0].dot(u1).T).T + self.int_N3.dot(self.dofs_EF[1].dot(u3).T).T out3 = self.his_N3.dot(self.dofs_EF[2].dot(u1).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: u1, u3 = self.core.space.reshape_pol_2(u) @@ -410,7 +410,7 @@ def __EF(self, u): out1 = self.int_D3.dot(self.dofs_EF[0].dot(u1).T).T + self.int_N3.dot(self.dofs_EF[1].dot(u3).T).T out3 = self.his_D3.dot(self.dofs_EF[2].dot(u1).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_EF.dot(u) @@ -434,7 +434,7 @@ def __EF_transposed(self, e): ) out3 = self.int_N3.T.dot(self.dofs_EF[1].T.dot(e1).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: out1 = ( @@ -442,7 +442,7 @@ def __EF_transposed(self, e): ) out3 = self.int_N3.T.dot(self.dofs_EF[1].T.dot(e1).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_EF.T.dot(e) @@ -462,7 +462,7 @@ def __MF(self, u): out1 = self.his_N3.dot(self.dofs_MF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_MF[1].dot(u3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: u1, u3 = self.core.space.reshape_pol_2(u) @@ -470,7 +470,7 @@ def __MF(self, u): out1 = self.his_D3.dot(self.dofs_MF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_MF[1].dot(u3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_MF.dot(u) @@ -492,13 +492,13 @@ def __MF_transposed(self, f): out1 = self.his_N3.T.dot(self.dofs_MF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_MF[1].T.dot(f3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: out1 = self.his_D3.T.dot(self.dofs_MF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_MF[1].T.dot(f3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_MF.T.dot(f) @@ -518,7 +518,7 @@ def __PF(self, u): out1 = self.his_N3.dot(self.dofs_PF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_PF[1].dot(u3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: u1, u3 = self.core.space.reshape_pol_2(u) @@ -526,7 +526,7 @@ def __PF(self, u): out1 = self.his_D3.dot(self.dofs_PF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_PF[1].dot(u3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_PF.dot(u) @@ -548,13 +548,13 @@ def __PF_transposed(self, f): out1 = self.his_N3.T.dot(self.dofs_PF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_PF[1].T.dot(f3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: out1 = self.his_D3.T.dot(self.dofs_PF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_PF[1].T.dot(f3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_PF.T.dot(f) @@ -574,7 +574,7 @@ def __JF(self, u): out1 = self.his_N3.dot(self.dofs_JF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_JF[1].dot(u3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: u1, u3 = self.core.space.reshape_pol_2(u) @@ -582,7 +582,7 @@ def __JF(self, u): out1 = self.his_D3.dot(self.dofs_JF[0].dot(u1).T).T out3 = self.int_N3.dot(self.dofs_JF[1].dot(u3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_JF.dot(u) @@ -604,13 +604,13 @@ def __JF_transposed(self, f): out1 = self.his_N3.T.dot(self.dofs_JF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_JF[1].T.dot(f3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) elif self.core.basis_u == 2: out1 = self.his_D3.T.dot(self.dofs_JF[0].T.dot(f1).T).T out3 = self.int_N3.T.dot(self.dofs_JF[1].T.dot(f3).T).T - out = np.concatenate((out1.flatten(), out3.flatten())) + out = xp.concatenate((out1.flatten(), out3.flatten())) else: out = self.dofs_JF.T.dot(f) @@ -658,11 +658,13 @@ def __Mn(self, u): if self.Mn_as_tensor: if self.core.basis_u == 0: out = self.core.space.apply_Mv_ten( - u, [[self.Mn_mat[0], self.core.space.M0_tor], [self.Mn_mat[1], self.core.space.M0_tor]] + u, + [[self.Mn_mat[0], self.core.space.M0_tor], [self.Mn_mat[1], self.core.space.M0_tor]], ) elif self.core.basis_u == 2: out = self.core.space.apply_M2_ten( - u, [[self.Mn_mat[0], self.core.space.M1_tor], [self.Mn_mat[1], self.core.space.M0_tor]] + u, + [[self.Mn_mat[0], self.core.space.M1_tor], [self.Mn_mat[1], self.core.space.M0_tor]], ) else: @@ -678,15 +680,16 @@ def __MJ(self, b): if self.MJ_as_tensor: if self.core.basis_u == 0: - out = np.zeros(self.core.space.Ev_0.shape[0], dtype=float) + out = xp.zeros(self.core.space.Ev_0.shape[0], dtype=float) elif self.core.basis_u == 2: out = self.core.space.apply_M2_ten( - b, [[self.MJ_mat[0], self.core.space.M1_tor], [self.MJ_mat[1], self.core.space.M0_tor]] + b, + [[self.MJ_mat[0], self.core.space.M1_tor], [self.MJ_mat[1], self.core.space.M0_tor]], ) else: if self.core.basis_u == 0: - out = np.zeros(self.core.space.Ev_0.shape[0], dtype=float) + out = xp.zeros(self.core.space.Ev_0.shape[0], dtype=float) elif self.core.basis_u == 2: out = self.MJ_mat.dot(b) @@ -700,7 +703,7 @@ def __L(self, u): if self.core.basis_u == 0: out = -self.core.space.D0.dot(self.__PF(u)) - (self.gamma - 1) * self.__PR( - self.core.space.D0.dot(self.__JF(u)) + self.core.space.D0.dot(self.__JF(u)), ) elif self.core.basis_u == 2: out = -self.core.space.D0.dot(self.__PF(u)) - (self.gamma - 1) * self.__PR(self.core.space.D0.dot(u)) @@ -782,27 +785,32 @@ def set_operators(self, dt_2=1.0, dt_6=1.0): if hasattr(self, "dofs_Mn"): self.Mn = spa.linalg.LinearOperator( - (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), matvec=self.__Mn + (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), + matvec=self.__Mn, ) if hasattr(self, "dofs_MJ"): self.MJ = spa.linalg.LinearOperator( - (self.core.space.Ev_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__MJ + (self.core.space.Ev_0.shape[0], self.core.space.E2_0.shape[0]), + matvec=self.__MJ, ) if hasattr(self, "dofs_PF") and hasattr(self, "dofs_PR") and hasattr(self, "dofs_JF"): self.L = spa.linalg.LinearOperator( - (self.core.space.E3_0.shape[0], self.core.space.Ev_0.shape[0]), matvec=self.__L + (self.core.space.E3_0.shape[0], self.core.space.Ev_0.shape[0]), + matvec=self.__L, ) if hasattr(self, "Mn_mat") and hasattr(self, "dofs_EF"): self.S2 = spa.linalg.LinearOperator( - (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), matvec=self.__S2 + (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), + matvec=self.__S2, ) if hasattr(self, "Mn_mat") and hasattr(self, "L"): self.S6 = spa.linalg.LinearOperator( - (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), matvec=self.__S6 + (self.core.space.Ev_0.shape[0], self.core.space.Ev_0.shape[0]), + matvec=self.__S6, ) elif self.core.basis_u == 2: @@ -836,27 +844,32 @@ def set_operators(self, dt_2=1.0, dt_6=1.0): if hasattr(self, "Mn_mat"): self.Mn = spa.linalg.LinearOperator( - (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__Mn + (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), + matvec=self.__Mn, ) if hasattr(self, "MJ_mat"): self.MJ = spa.linalg.LinearOperator( - (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__MJ + (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), + matvec=self.__MJ, ) if hasattr(self, "dofs_PF") and hasattr(self, "dofs_PR"): self.L = spa.linalg.LinearOperator( - (self.core.space.E3_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__L + (self.core.space.E3_0.shape[0], self.core.space.E2_0.shape[0]), + matvec=self.__L, ) if hasattr(self, "Mn_mat") and hasattr(self, "dofs_EF"): self.S2 = spa.linalg.LinearOperator( - (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__S2 + (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), + matvec=self.__S2, ) if hasattr(self, "Mn_mat") and hasattr(self, "L"): self.S6 = spa.linalg.LinearOperator( - (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), matvec=self.__S6 + (self.core.space.E2_0.shape[0], self.core.space.E2_0.shape[0]), + matvec=self.__S6, ) # ====================================== @@ -929,7 +942,7 @@ def guess_S2(self, u, b, kind): u_guess = u + self.dt_2 / 6 * (k1_u + 2 * k2_u + 2 * k3_u + k4_u) else: - u_guess = np.copy(u) + u_guess = xp.copy(u) return u_guess @@ -1024,7 +1037,7 @@ def set_preconditioner_S2(self, which, tol_inv=1e-15, drop_tol=1e-4, fill_fac=10 # assemble approximate S2 matrix S2_approx = Mn + self.dt_2**2 / 4 * EF_approx.T.dot( - self.core.space.C0.T.dot(M2_0.dot(self.core.space.C0.dot(EF_approx))) + self.core.space.C0.T.dot(M2_0.dot(self.core.space.C0.dot(EF_approx))), ) del Mn, EF_approx, M2_0 @@ -1123,7 +1136,7 @@ def set_preconditioner_S6(self, which, tol_inv=1e-15, drop_tol=1e-4, fill_fac=10 # assemble approximate L matrix if self.core.basis_u == 0: L_approx = -self.core.space.D0.dot(PF_approx) - (self.gamma - 1) * PR_approx.dot( - self.core.space.D0.dot(JF_approx) + self.core.space.D0.dot(JF_approx), ) del PF_approx, PR_approx diff --git a/src/struphy/eigenvalue_solvers/mhd_operators_core.py b/src/struphy/eigenvalue_solvers/mhd_operators_core.py index a8c23e3c7..61d534148 100644 --- a/src/struphy/eigenvalue_solvers/mhd_operators_core.py +++ b/src/struphy/eigenvalue_solvers/mhd_operators_core.py @@ -3,7 +3,7 @@ # Copyright 2021 Florian Holderied (florian.holderied@ipp.mpg.de) -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.eigenvalue_solvers.kernels_projectors_global_mhd as ker @@ -58,11 +58,11 @@ def __init__(self, space, equilibrium, basis_u): self.subs_cum = [space.projectors.subs_cum for space in self.space.spaces] # get 1D indices of non-vanishing values of expressions dofs_0(N), dofs_0(D), dofs_1(N) and dofs_1(D) - self.dofs_0_N_i = [list(np.nonzero(space.projectors.I.toarray())) for space in self.space.spaces] - self.dofs_1_D_i = [list(np.nonzero(space.projectors.H.toarray())) for space in self.space.spaces] + self.dofs_0_N_i = [list(xp.nonzero(space.projectors.I.toarray())) for space in self.space.spaces] + self.dofs_1_D_i = [list(xp.nonzero(space.projectors.H.toarray())) for space in self.space.spaces] - self.dofs_0_D_i = [list(np.nonzero(space.projectors.ID.toarray())) for space in self.space.spaces] - self.dofs_1_N_i = [list(np.nonzero(space.projectors.HN.toarray())) for space in self.space.spaces] + self.dofs_0_D_i = [list(xp.nonzero(space.projectors.ID.toarray())) for space in self.space.spaces] + self.dofs_1_N_i = [list(xp.nonzero(space.projectors.HN.toarray())) for space in self.space.spaces] for i in range(self.space.dim): for j in range(2): @@ -116,9 +116,9 @@ def get_blocks_EF(self, pol=True): B2_3_pts = B2_3_pts.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_N_i[0][0], @@ -130,8 +130,8 @@ def get_blocks_EF(self, pol=True): self.wts[0], self.basis_his_N[0], self.basis_int_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_3_pts, val, row, @@ -139,7 +139,8 @@ def get_blocks_EF(self, pol=True): ) EF_12 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_0form // self.N3), ) EF_12.eliminate_zeros() # ---------------------------------------------------- @@ -150,9 +151,9 @@ def get_blocks_EF(self, pol=True): B2_2_pts = B2_2_pts.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_N_i[0][0], @@ -164,8 +165,8 @@ def get_blocks_EF(self, pol=True): self.wts[0], self.basis_his_N[0], self.basis_int_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_2_pts, val, row, @@ -173,7 +174,8 @@ def get_blocks_EF(self, pol=True): ) EF_13 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_0form // self.N3), ) EF_13.eliminate_zeros() # ---------------------------------------------------- @@ -184,9 +186,9 @@ def get_blocks_EF(self, pol=True): B2_3_pts = B2_3_pts.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -198,8 +200,8 @@ def get_blocks_EF(self, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_3_pts, val, row, @@ -207,7 +209,8 @@ def get_blocks_EF(self, pol=True): ) EF_21 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_0form // self.N3), ) EF_21.eliminate_zeros() # ---------------------------------------------------- @@ -218,9 +221,9 @@ def get_blocks_EF(self, pol=True): B2_1_pts = B2_1_pts.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -232,8 +235,8 @@ def get_blocks_EF(self, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_1_pts, val, row, @@ -241,7 +244,8 @@ def get_blocks_EF(self, pol=True): ) EF_23 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_0form // self.N3), ) EF_23.eliminate_zeros() # ---------------------------------------------------- @@ -251,9 +255,9 @@ def get_blocks_EF(self, pol=True): B2_2_pts = self.equilibrium.b2_2(self.eta_int[0], self.eta_int[1], 0.0) # assemble sparse matrix - val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs0_2d( self.dofs_0_N_i[0][0], @@ -269,7 +273,8 @@ def get_blocks_EF(self, pol=True): ) EF_31 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_0form // self.N3), ) EF_31.eliminate_zeros() # ---------------------------------------------------- @@ -279,9 +284,9 @@ def get_blocks_EF(self, pol=True): B2_1_pts = self.equilibrium.b2_1(self.eta_int[0], self.eta_int[1], 0.0) # assemble sparse matrix - val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs0_2d( self.dofs_0_N_i[0][0], @@ -297,7 +302,8 @@ def get_blocks_EF(self, pol=True): ) EF_32 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_0form // self.N3), ) EF_32.eliminate_zeros() # ---------------------------------------------------- @@ -309,14 +315,17 @@ def get_blocks_EF(self, pol=True): B2_3_pts = B2_3_pts.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) ker.rhs11( @@ -332,8 +341,8 @@ def get_blocks_EF(self, pol=True): self.basis_his_N[0], self.basis_int_N[1], self.basis_int_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_3_pts, val, row, @@ -350,14 +359,17 @@ def get_blocks_EF(self, pol=True): B2_2_pts = B2_2_pts.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) ker.rhs11( @@ -373,8 +385,8 @@ def get_blocks_EF(self, pol=True): self.basis_his_N[0], self.basis_int_N[1], self.basis_int_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_2_pts, val, row, @@ -391,14 +403,17 @@ def get_blocks_EF(self, pol=True): B2_3_pts = B2_3_pts.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) ker.rhs12( @@ -414,8 +429,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_his_N[1], self.basis_int_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_3_pts, val, row, @@ -432,14 +447,17 @@ def get_blocks_EF(self, pol=True): B2_1_pts = B2_1_pts.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) ker.rhs12( @@ -455,8 +473,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_his_N[1], self.basis_int_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_1_pts, val, row, @@ -473,14 +491,17 @@ def get_blocks_EF(self, pol=True): B2_2_pts = B2_2_pts.reshape(self.nint[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=int, ) ker.rhs13( @@ -496,8 +517,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_int_N[1], self.basis_his_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_2_pts, val, row, @@ -514,14 +535,17 @@ def get_blocks_EF(self, pol=True): B2_1_pts = B2_1_pts.reshape(self.nint[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=int, ) ker.rhs13( @@ -537,8 +561,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_int_N[1], self.basis_his_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_1_pts, val, row, @@ -561,9 +585,9 @@ def get_blocks_EF(self, pol=True): det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_D_i[0][0], @@ -575,8 +599,8 @@ def get_blocks_EF(self, pol=True): self.wts[0], self.basis_his_D[0], self.basis_int_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_3_pts / det_dF, val, row, @@ -584,7 +608,8 @@ def get_blocks_EF(self, pol=True): ) EF_12 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_2form[1] // self.D3) + (val, (row, col)), + shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_2form[1] // self.D3), ) EF_12.eliminate_zeros() # ---------------------------------------------------- @@ -599,9 +624,9 @@ def get_blocks_EF(self, pol=True): det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_D_i[0][0], @@ -613,8 +638,8 @@ def get_blocks_EF(self, pol=True): self.wts[0], self.basis_his_D[0], self.basis_int_D[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_2_pts / det_dF, val, row, @@ -622,7 +647,8 @@ def get_blocks_EF(self, pol=True): ) EF_13 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_2form[2] // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_1form[0] // self.N3, self.space.Ntot_2form[2] // self.N3), ) EF_13.eliminate_zeros() # ---------------------------------------------------- @@ -637,9 +663,9 @@ def get_blocks_EF(self, pol=True): det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -651,8 +677,8 @@ def get_blocks_EF(self, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_D[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_3_pts / det_dF, val, row, @@ -660,7 +686,8 @@ def get_blocks_EF(self, pol=True): ) EF_21 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_2form[0] // self.D3) + (val, (row, col)), + shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_2form[0] // self.D3), ) EF_21.eliminate_zeros() # ---------------------------------------------------- @@ -675,9 +702,9 @@ def get_blocks_EF(self, pol=True): det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_D_i[0][0], @@ -689,8 +716,8 @@ def get_blocks_EF(self, pol=True): self.wts[1], self.basis_int_D[0], self.basis_his_D[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_1_pts / det_dF, val, row, @@ -698,7 +725,8 @@ def get_blocks_EF(self, pol=True): ) EF_23 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_2form[2] // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_1form[1] // self.N3, self.space.Ntot_2form[2] // self.N3), ) EF_23.eliminate_zeros() # ---------------------------------------------------- @@ -711,9 +739,9 @@ def get_blocks_EF(self, pol=True): det_dF = abs(self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], 0.0)) # assemble sparse matrix - val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size, dtype=int) ker.rhs0_2d( self.dofs_0_N_i[0][0], @@ -729,7 +757,8 @@ def get_blocks_EF(self, pol=True): ) EF_31 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_2form[0] // self.D3) + (val, (row, col)), + shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_2form[0] // self.D3), ) EF_31.eliminate_zeros() # ---------------------------------------------------- @@ -742,9 +771,9 @@ def get_blocks_EF(self, pol=True): det_dF = abs(self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], 0.0)) # assemble sparse matrix - val = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs0_2d( self.dofs_0_D_i[0][0], @@ -760,7 +789,8 @@ def get_blocks_EF(self, pol=True): ) EF_32 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_2form[1] // self.D3) + (val, (row, col)), + shape=(self.space.Ntot_1form[2] // self.D3, self.space.Ntot_2form[1] // self.D3), ) EF_32.eliminate_zeros() # ---------------------------------------------------- @@ -773,19 +803,22 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_int[1], self.eta_int[2]) + self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_int[1], self.eta_int[2]), ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_0_D_i[2][0].size, + dtype=int, ) ker.rhs11( @@ -801,8 +834,8 @@ def get_blocks_EF(self, pol=True): self.basis_his_D[0], self.basis_int_N[1], self.basis_int_D[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_3_pts / det_dF, val, row, @@ -820,19 +853,22 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_int[1], self.eta_int[2]) + self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_int[1], self.eta_int[2]), ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) ker.rhs11( @@ -848,8 +884,8 @@ def get_blocks_EF(self, pol=True): self.basis_his_D[0], self.basis_int_D[1], self.basis_int_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_2_pts / det_dF, val, row, @@ -867,19 +903,22 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_his[1].flatten(), self.eta_int[2]) + self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_his[1].flatten(), self.eta_int[2]), ) det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_D_i[2][0].size, + dtype=int, ) ker.rhs12( @@ -895,8 +934,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_his_D[1], self.basis_int_D[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_3_pts / det_dF, val, row, @@ -914,19 +953,22 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_his[1].flatten(), self.eta_int[2]) + self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_his[1].flatten(), self.eta_int[2]), ) det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) ker.rhs12( @@ -942,8 +984,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_D[0], self.basis_his_D[1], self.basis_int_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_1_pts / det_dF, val, row, @@ -961,19 +1003,22 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], self.eta_his[2].flatten()) + self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], self.eta_his[2].flatten()), ) det_dF = det_dF.reshape(self.nint[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_0_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) ker.rhs13( @@ -989,8 +1034,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_N[0], self.basis_int_D[1], self.basis_his_D[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), -B2_2_pts / det_dF, val, row, @@ -1008,19 +1053,22 @@ def get_blocks_EF(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], self.eta_his[2].flatten()) + self.equilibrium.domain.jacobian_det(self.eta_int[0], self.eta_int[1], self.eta_his[2].flatten()), ) det_dF = det_dF.reshape(self.nint[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) ker.rhs13( @@ -1036,8 +1084,8 @@ def get_blocks_EF(self, pol=True): self.basis_int_D[0], self.basis_int_N[1], self.basis_his_D[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), B2_1_pts / det_dF, val, row, @@ -1093,9 +1141,9 @@ def get_blocks_FL(self, which, pol=True): EQ = EQ.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -1107,8 +1155,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ, val, row, @@ -1116,7 +1164,8 @@ def get_blocks_FL(self, which, pol=True): ) F_11 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_2form[0] // self.D3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_2form[0] // self.D3, self.space.Ntot_0form // self.N3), ) F_11.eliminate_zeros() # ------------------------------------------------------------ @@ -1133,9 +1182,9 @@ def get_blocks_FL(self, which, pol=True): EQ = EQ.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_N_i[0][0], @@ -1147,8 +1196,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[0], self.basis_his_N[0], self.basis_int_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ, val, row, @@ -1156,7 +1205,8 @@ def get_blocks_FL(self, which, pol=True): ) F_22 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_2form[1] // self.D3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_2form[1] // self.D3, self.space.Ntot_0form // self.N3), ) F_22.eliminate_zeros() # ------------------------------------------------------------ @@ -1173,9 +1223,9 @@ def get_blocks_FL(self, which, pol=True): EQ = EQ.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size, dtype=int) ker.rhs2_2d( self.dofs_1_N_i[0][0], @@ -1190,8 +1240,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[1], self.basis_his_N[0], self.basis_his_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ, val, row, @@ -1199,7 +1249,8 @@ def get_blocks_FL(self, which, pol=True): ) F_33 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_2form[2] // self.N3, self.space.Ntot_0form // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_2form[2] // self.N3, self.space.Ntot_0form // self.N3), ) F_33.eliminate_zeros() # ------------------------------------------------------------ @@ -1213,20 +1264,25 @@ def get_blocks_FL(self, which, pol=True): EQ = self.equilibrium.p3(self.eta_int[0], self.eta_his[1].flatten(), self.eta_his[2].flatten()) else: EQ = self.equilibrium.domain.jacobian_det( - self.eta_int[0], self.eta_his[1].flatten(), self.eta_his[2].flatten() + self.eta_int[0], + self.eta_his[1].flatten(), + self.eta_his[2].flatten(), ) EQ = EQ.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=int, ) ker.rhs21( @@ -1245,8 +1301,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_int_N[0], self.basis_his_N[1], self.basis_his_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ, val, row, @@ -1265,20 +1321,25 @@ def get_blocks_FL(self, which, pol=True): EQ = self.equilibrium.p3(self.eta_his[0].flatten(), self.eta_int[1], self.eta_his[2].flatten()) else: EQ = self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), self.eta_int[1], self.eta_his[2].flatten() + self.eta_his[0].flatten(), + self.eta_int[1], + self.eta_his[2].flatten(), ) EQ = EQ.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_N_i[2][0].size, + dtype=int, ) ker.rhs22( @@ -1297,8 +1358,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_his_N[0], self.basis_int_N[1], self.basis_his_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ, val, row, @@ -1317,20 +1378,25 @@ def get_blocks_FL(self, which, pol=True): EQ = self.equilibrium.p3(self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_int[2]) else: EQ = self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_int[2] + self.eta_his[0].flatten(), + self.eta_his[1].flatten(), + self.eta_int[2], ) EQ = EQ.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_N_i[0][0].size * self.dofs_1_N_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) ker.rhs23( @@ -1349,8 +1415,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_his_N[0], self.basis_his_N[1], self.basis_int_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ, val, row, @@ -1377,9 +1443,9 @@ def get_blocks_FL(self, which, pol=True): det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = np.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs12_2d( self.dofs_0_N_i[0][0], @@ -1391,8 +1457,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[1], self.basis_int_N[0], self.basis_his_D[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1400,7 +1466,8 @@ def get_blocks_FL(self, which, pol=True): ) F_11 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_2form[0] // self.D3, self.space.Ntot_2form[0] // self.D3) + (val, (row, col)), + shape=(self.space.Ntot_2form[0] // self.D3, self.space.Ntot_2form[0] // self.D3), ) F_11.eliminate_zeros() # ------------------------------------------------------------ @@ -1419,9 +1486,9 @@ def get_blocks_FL(self, which, pol=True): det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size, dtype=int) ker.rhs11_2d( self.dofs_1_D_i[0][0], @@ -1433,8 +1500,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[0], self.basis_his_D[0], self.basis_int_N[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1442,7 +1509,8 @@ def get_blocks_FL(self, which, pol=True): ) F_22 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_2form[1] // self.D3, self.space.Ntot_2form[1] // self.D3) + (val, (row, col)), + shape=(self.space.Ntot_2form[1] // self.D3, self.space.Ntot_2form[1] // self.D3), ) F_22.eliminate_zeros() # ------------------------------------------------------------ @@ -1458,14 +1526,14 @@ def get_blocks_FL(self, which, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_his[1].flatten(), 0.0) + self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_his[1].flatten(), 0.0), ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs2_2d( self.dofs_1_D_i[0][0], @@ -1480,8 +1548,8 @@ def get_blocks_FL(self, which, pol=True): self.wts[1], self.basis_his_D[0], self.basis_his_D[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1489,7 +1557,8 @@ def get_blocks_FL(self, which, pol=True): ) F_33 = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_2form[2] // self.N3, self.space.Ntot_2form[2] // self.N3) + (val, (row, col)), + shape=(self.space.Ntot_2form[2] // self.N3, self.space.Ntot_2form[2] // self.N3), ) F_33.eliminate_zeros() # ------------------------------------------------------------ @@ -1507,20 +1576,25 @@ def get_blocks_FL(self, which, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( self.equilibrium.domain.jacobian_det( - self.eta_int[0], self.eta_his[1].flatten(), self.eta_his[2].flatten() - ) + self.eta_int[0], + self.eta_his[1].flatten(), + self.eta_his[2].flatten(), + ), ) det_dF = det_dF.reshape(self.nint[0], self.nhis[1], self.nq[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_0_N_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) ker.rhs21( @@ -1539,8 +1613,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_int_N[0], self.basis_his_D[1], self.basis_his_D[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1563,20 +1637,25 @@ def get_blocks_FL(self, which, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), self.eta_int[1], self.eta_his[2].flatten() - ) + self.eta_his[0].flatten(), + self.eta_int[1], + self.eta_his[2].flatten(), + ), ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nint[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_0_N_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) ker.rhs22( @@ -1595,8 +1674,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_his_D[0], self.basis_int_N[1], self.basis_his_D[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1619,20 +1698,25 @@ def get_blocks_FL(self, which, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_int[2] - ) + self.eta_his[0].flatten(), + self.eta_his[1].flatten(), + self.eta_int[2], + ), ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1], self.nint[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_0_N_i[2][0].size, + dtype=int, ) ker.rhs23( @@ -1651,8 +1735,8 @@ def get_blocks_FL(self, which, pol=True): self.basis_his_D[0], self.basis_his_D[1], self.basis_int_N[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), EQ / det_dF, val, row, @@ -1691,14 +1775,14 @@ def get_blocks_PR(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( - self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_his[1].flatten(), 0.0) + self.equilibrium.domain.jacobian_det(self.eta_his[0].flatten(), self.eta_his[1].flatten(), 0.0), ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1]) # assemble sparse matrix - val = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) - row = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) - col = np.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + val = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=float) + row = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) + col = xp.empty(self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size, dtype=int) ker.rhs2_2d( self.dofs_1_D_i[0][0], @@ -1713,8 +1797,8 @@ def get_blocks_PR(self, pol=True): self.wts[1], self.basis_his_D[0], self.basis_his_D[1], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), P3_pts / det_dF, val, row, @@ -1722,7 +1806,8 @@ def get_blocks_PR(self, pol=True): ) PR = spa.csr_matrix( - (val, (row, col)), shape=(self.space.Ntot_3form // self.D3, self.space.Ntot_3form // self.D3) + (val, (row, col)), + shape=(self.space.Ntot_3form // self.D3, self.space.Ntot_3form // self.D3), ) PR.eliminate_zeros() # ----------------------------------------------------- @@ -1731,7 +1816,9 @@ def get_blocks_PR(self, pol=True): # --------------- ([his, his, his] of DDD) ------------ # evaluate equilibrium pressure at quadrature points P3_pts = self.equilibrium.p3( - self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_his[2].flatten() + self.eta_his[0].flatten(), + self.eta_his[1].flatten(), + self.eta_his[2].flatten(), ) P3_pts = P3_pts.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1], self.nhis[2], self.nq[2]) @@ -1739,20 +1826,25 @@ def get_blocks_PR(self, pol=True): # evaluate Jacobian determinant at at interpolation and quadrature points det_dF = abs( self.equilibrium.domain.jacobian_det( - self.eta_his[0].flatten(), self.eta_his[1].flatten(), self.eta_his[2].flatten() - ) + self.eta_his[0].flatten(), + self.eta_his[1].flatten(), + self.eta_his[2].flatten(), + ), ) det_dF = det_dF.reshape(self.nhis[0], self.nq[0], self.nhis[1], self.nq[1], self.nhis[2], self.nq[2]) # assemble sparse matrix - val = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=float + val = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=float, ) - row = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + row = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) - col = np.empty( - self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, dtype=int + col = xp.empty( + self.dofs_1_D_i[0][0].size * self.dofs_1_D_i[1][0].size * self.dofs_1_D_i[2][0].size, + dtype=int, ) ker.rhs3( @@ -1774,8 +1866,8 @@ def get_blocks_PR(self, pol=True): self.basis_his_D[0], self.basis_his_D[1], self.basis_his_D[2], - np.array(self.space.NbaseN), - np.array(self.space.NbaseD), + xp.array(self.space.NbaseN), + xp.array(self.space.NbaseD), P3_pts / det_dF, val, row, diff --git a/src/struphy/eigenvalue_solvers/projectors_global.py b/src/struphy/eigenvalue_solvers/projectors_global.py index df9080f01..9d246cdac 100644 --- a/src/struphy/eigenvalue_solvers/projectors_global.py +++ b/src/struphy/eigenvalue_solvers/projectors_global.py @@ -6,7 +6,7 @@ Classes for commuting projectors in 1D, 2D and 3D based on global spline interpolation and histopolation. """ -import numpy as np +import cunumpy as xp import scipy.sparse as spa import struphy.bsplines.bsplines as bsp @@ -156,20 +156,20 @@ def __init__(self, spline_space, n_quad=6): self.n_quad = n_quad # Gauss - Legendre quadrature points and weights in (-1, 1) - self.pts_loc = np.polynomial.legendre.leggauss(self.n_quad)[0] - self.wts_loc = np.polynomial.legendre.leggauss(self.n_quad)[1] + self.pts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[0] + self.wts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[1] # set interpolation points (Greville points) self.x_int = spline_space.greville.copy() # set number of sub-intervals per integration interval between Greville points and integration boundaries - self.subs = np.ones(spline_space.NbaseD, dtype=int) - self.x_his = np.array([self.x_int[0]]) + self.subs = xp.ones(spline_space.NbaseD, dtype=int) + self.x_his = xp.array([self.x_int[0]]) for i in range(spline_space.NbaseD): for br in spline_space.el_b: # left and right integration boundaries - if spline_space.spl_kind == False: + if not spline_space.spl_kind: xl = self.x_int[i] xr = self.x_int[i + 1] else: @@ -181,16 +181,16 @@ def __init__(self, spline_space, n_quad=6): # compute subs and x_his if (br > xl + 1e-10) and (br < xr - 1e-10): self.subs[i] += 1 - self.x_his = np.append(self.x_his, br) + self.x_his = xp.append(self.x_his, br) elif br >= xr - 1e-10: - self.x_his = np.append(self.x_his, xr) + self.x_his = xp.append(self.x_his, xr) break - if spline_space.spl_kind == True and spline_space.p % 2 == 0: - self.x_his = np.append(self.x_his, spline_space.el_b[-1] + self.x_his[0]) + if spline_space.spl_kind and spline_space.p % 2 == 0: + self.x_his = xp.append(self.x_his, spline_space.el_b[-1] + self.x_his[0]) # cumulative number of sub-intervals for conversion local interval --> global interval - self.subs_cum = np.append(0, np.cumsum(self.subs - 1)[:-1]) + self.subs_cum = xp.append(0, xp.cumsum(self.subs - 1)[:-1]) # quadrature points and weights self.pts, self.wts = bsp.quadrature_grid(self.x_his, self.pts_loc, self.wts_loc) @@ -198,33 +198,33 @@ def __init__(self, spline_space, n_quad=6): # quadrature points and weights, ignoring subs (less accurate integration for even degree) self.x_hisG = self.x_int - if spline_space.spl_kind == True: + if spline_space.spl_kind: if spline_space.p % 2 == 0: - self.x_hisG = np.append(self.x_hisG, spline_space.el_b[-1] + self.x_hisG[0]) + self.x_hisG = xp.append(self.x_hisG, spline_space.el_b[-1] + self.x_hisG[0]) else: - self.x_hisG = np.append(self.x_hisG, spline_space.el_b[-1]) + self.x_hisG = xp.append(self.x_hisG, spline_space.el_b[-1]) self.ptsG, self.wtsG = bsp.quadrature_grid(self.x_hisG, self.pts_loc, self.wts_loc) self.ptsG = self.ptsG % spline_space.el_b[-1] # Knot span indices at interpolation points in format (greville, 0) - self.span_x_int_N = np.zeros(self.x_int[:, None].shape, dtype=int) - self.span_x_int_D = np.zeros(self.x_int[:, None].shape, dtype=int) + self.span_x_int_N = xp.zeros(self.x_int[:, None].shape, dtype=int) + self.span_x_int_D = xp.zeros(self.x_int[:, None].shape, dtype=int) for i in range(self.x_int.shape[0]): self.span_x_int_N[i, 0] = bsp.find_span(self.space.T, self.space.p, self.x_int[i]) self.span_x_int_D[i, 0] = bsp.find_span(self.space.t, self.space.p - 1, self.x_int[i]) # Knot span indices at quadrature points between x_int in format (i, iq) - self.span_ptsG_N = np.zeros(self.ptsG.shape, dtype=int) - self.span_ptsG_D = np.zeros(self.ptsG.shape, dtype=int) + self.span_ptsG_N = xp.zeros(self.ptsG.shape, dtype=int) + self.span_ptsG_D = xp.zeros(self.ptsG.shape, dtype=int) for i in range(self.ptsG.shape[0]): for iq in range(self.ptsG.shape[1]): self.span_ptsG_N[i, iq] = bsp.find_span(self.space.T, self.space.p, self.ptsG[i, iq]) self.span_ptsG_D[i, iq] = bsp.find_span(self.space.t, self.space.p - 1, self.ptsG[i, iq]) # Values of p + 1 non-zero basis functions at Greville points in format (greville, 0, basis function) - self.basis_x_int_N = np.zeros((*self.x_int[:, None].shape, self.space.p + 1), dtype=float) - self.basis_x_int_D = np.zeros((*self.x_int[:, None].shape, self.space.p), dtype=float) + self.basis_x_int_N = xp.zeros((*self.x_int[:, None].shape, self.space.p + 1), dtype=float) + self.basis_x_int_D = xp.zeros((*self.x_int[:, None].shape, self.space.p), dtype=float) N_temp = bsp.basis_ders_on_quad_grid(self.space.T, self.space.p, self.x_int[:, None], 0, normalize=False) D_temp = bsp.basis_ders_on_quad_grid(self.space.t, self.space.p - 1, self.x_int[:, None], 0, normalize=True) @@ -236,8 +236,8 @@ def __init__(self, spline_space, n_quad=6): self.basis_x_int_D[i, 0, b] = D_temp[i, b, 0, 0] # Values of p + 1 non-zero basis functions at quadrature points points between x_int in format (i, iq, basis function) - self.basis_ptsG_N = np.zeros((*self.ptsG.shape, self.space.p + 1), dtype=float) - self.basis_ptsG_D = np.zeros((*self.ptsG.shape, self.space.p), dtype=float) + self.basis_ptsG_N = xp.zeros((*self.ptsG.shape, self.space.p + 1), dtype=float) + self.basis_ptsG_D = xp.zeros((*self.ptsG.shape, self.space.p), dtype=float) N_temp = bsp.basis_ders_on_quad_grid(self.space.T, self.space.p, self.ptsG, 0, normalize=False) D_temp = bsp.basis_ders_on_quad_grid(self.space.t, self.space.p - 1, self.ptsG, 0, normalize=True) @@ -250,7 +250,7 @@ def __init__(self, spline_space, n_quad=6): self.basis_ptsG_D[i, iq, b] = D_temp[i, b, 0, iq] # quadrature matrix for performing integrations as matrix-vector products - self.Q = np.zeros((spline_space.NbaseD, self.wts.shape[0] * self.n_quad), dtype=float) + self.Q = xp.zeros((spline_space.NbaseD, self.wts.shape[0] * self.n_quad), dtype=float) for i in range(spline_space.NbaseD): for j in range(self.subs[i]): @@ -260,7 +260,7 @@ def __init__(self, spline_space, n_quad=6): self.Q = spa.csr_matrix(self.Q) # quadrature matrix for performing integrations as matrix-vector products, ignoring subs (less accurate integration for even degree) - self.QG = np.zeros((spline_space.NbaseD, self.wtsG.shape[0] * self.n_quad), dtype=float) + self.QG = xp.zeros((spline_space.NbaseD, self.wtsG.shape[0] * self.n_quad), dtype=float) for i in range(spline_space.NbaseD): self.QG[i, self.n_quad * i : self.n_quad * (i + 1)] = self.wtsG[i] @@ -271,10 +271,18 @@ def __init__(self, spline_space, n_quad=6): BM_splines = [False, True] self.N_int = bsp.collocation_matrix( - spline_space.T, spline_space.p - 0, self.x_int, spline_space.spl_kind, BM_splines[0] + spline_space.T, + spline_space.p - 0, + self.x_int, + spline_space.spl_kind, + BM_splines[0], ) self.D_int = bsp.collocation_matrix( - spline_space.t, spline_space.p - 1, self.x_int, spline_space.spl_kind, BM_splines[1] + spline_space.t, + spline_space.p - 1, + self.x_int, + spline_space.spl_kind, + BM_splines[1], ) self.N_int[self.N_int < 1e-12] = 0.0 @@ -284,10 +292,18 @@ def __init__(self, spline_space, n_quad=6): self.D_int = spa.csr_matrix(self.D_int) self.N_pts = bsp.collocation_matrix( - spline_space.T, spline_space.p - 0, self.pts.flatten(), spline_space.spl_kind, BM_splines[0] + spline_space.T, + spline_space.p - 0, + self.pts.flatten(), + spline_space.spl_kind, + BM_splines[0], ) self.D_pts = bsp.collocation_matrix( - spline_space.t, spline_space.p - 1, self.pts.flatten(), spline_space.spl_kind, BM_splines[1] + spline_space.t, + spline_space.p - 1, + self.pts.flatten(), + spline_space.spl_kind, + BM_splines[1], ) self.N_pts = spa.csr_matrix(self.N_pts) @@ -399,17 +415,17 @@ def dofs_1d_bases_products(self, space): dofs_1_i(D_j*D_k). """ - dofs_0_NN = np.empty((space.NbaseN, space.NbaseN, space.NbaseN), dtype=float) - dofs_0_DN = np.empty((space.NbaseN, space.NbaseD, space.NbaseN), dtype=float) - dofs_0_DD = np.empty((space.NbaseN, space.NbaseD, space.NbaseD), dtype=float) + dofs_0_NN = xp.empty((space.NbaseN, space.NbaseN, space.NbaseN), dtype=float) + dofs_0_DN = xp.empty((space.NbaseN, space.NbaseD, space.NbaseN), dtype=float) + dofs_0_DD = xp.empty((space.NbaseN, space.NbaseD, space.NbaseD), dtype=float) - dofs_1_NN = np.empty((space.NbaseD, space.NbaseN, space.NbaseN), dtype=float) - dofs_1_DN = np.empty((space.NbaseD, space.NbaseD, space.NbaseN), dtype=float) - dofs_1_DD = np.empty((space.NbaseD, space.NbaseD, space.NbaseD), dtype=float) + dofs_1_NN = xp.empty((space.NbaseD, space.NbaseN, space.NbaseN), dtype=float) + dofs_1_DN = xp.empty((space.NbaseD, space.NbaseD, space.NbaseN), dtype=float) + dofs_1_DD = xp.empty((space.NbaseD, space.NbaseD, space.NbaseD), dtype=float) # ========= dofs_0_NN and dofs_1_NN ============== - cj = np.zeros(space.NbaseN, dtype=float) - ck = np.zeros(space.NbaseN, dtype=float) + cj = xp.zeros(space.NbaseN, dtype=float) + ck = xp.zeros(space.NbaseN, dtype=float) for j in range(space.NbaseN): for k in range(space.NbaseN): @@ -426,8 +442,8 @@ def N_jN_k(eta): dofs_1_NN[:, j, k] = self.dofs_1(N_jN_k) # ========= dofs_0_DN and dofs_1_DN ============== - cj = np.zeros(space.NbaseD, dtype=float) - ck = np.zeros(space.NbaseN, dtype=float) + cj = xp.zeros(space.NbaseD, dtype=float) + ck = xp.zeros(space.NbaseN, dtype=float) for j in range(space.NbaseD): for k in range(space.NbaseN): @@ -444,8 +460,8 @@ def D_jN_k(eta): dofs_1_DN[:, j, k] = self.dofs_1(D_jN_k) # ========= dofs_0_DD and dofs_1_DD ============= - cj = np.zeros(space.NbaseD, dtype=float) - ck = np.zeros(space.NbaseD, dtype=float) + cj = xp.zeros(space.NbaseD, dtype=float) + ck = xp.zeros(space.NbaseD, dtype=float) for j in range(space.NbaseD): for k in range(space.NbaseD): @@ -461,110 +477,110 @@ def D_jD_k(eta): dofs_0_DD[:, j, k] = self.dofs_0(D_jD_k) dofs_1_DD[:, j, k] = self.dofs_1(D_jD_k) - dofs_0_ND = np.transpose(dofs_0_DN, (0, 2, 1)) - dofs_1_ND = np.transpose(dofs_1_DN, (0, 2, 1)) + dofs_0_ND = xp.transpose(dofs_0_DN, (0, 2, 1)) + dofs_1_ND = xp.transpose(dofs_1_DN, (0, 2, 1)) # find non-zero entries - dofs_0_NN_indices = np.nonzero(dofs_0_NN) - dofs_0_DN_indices = np.nonzero(dofs_0_DN) - dofs_0_ND_indices = np.nonzero(dofs_0_ND) - dofs_0_DD_indices = np.nonzero(dofs_0_DD) - - dofs_1_NN_indices = np.nonzero(dofs_1_NN) - dofs_1_DN_indices = np.nonzero(dofs_1_DN) - dofs_1_ND_indices = np.nonzero(dofs_1_ND) - dofs_1_DD_indices = np.nonzero(dofs_1_DD) - - dofs_0_NN_i_red = np.empty(dofs_0_NN_indices[0].size, dtype=int) - dofs_0_DN_i_red = np.empty(dofs_0_DN_indices[0].size, dtype=int) - dofs_0_ND_i_red = np.empty(dofs_0_ND_indices[0].size, dtype=int) - dofs_0_DD_i_red = np.empty(dofs_0_DD_indices[0].size, dtype=int) - - dofs_1_NN_i_red = np.empty(dofs_1_NN_indices[0].size, dtype=int) - dofs_1_DN_i_red = np.empty(dofs_1_DN_indices[0].size, dtype=int) - dofs_1_ND_i_red = np.empty(dofs_1_ND_indices[0].size, dtype=int) - dofs_1_DD_i_red = np.empty(dofs_1_DD_indices[0].size, dtype=int) + dofs_0_NN_indices = xp.nonzero(dofs_0_NN) + dofs_0_DN_indices = xp.nonzero(dofs_0_DN) + dofs_0_ND_indices = xp.nonzero(dofs_0_ND) + dofs_0_DD_indices = xp.nonzero(dofs_0_DD) + + dofs_1_NN_indices = xp.nonzero(dofs_1_NN) + dofs_1_DN_indices = xp.nonzero(dofs_1_DN) + dofs_1_ND_indices = xp.nonzero(dofs_1_ND) + dofs_1_DD_indices = xp.nonzero(dofs_1_DD) + + dofs_0_NN_i_red = xp.empty(dofs_0_NN_indices[0].size, dtype=int) + dofs_0_DN_i_red = xp.empty(dofs_0_DN_indices[0].size, dtype=int) + dofs_0_ND_i_red = xp.empty(dofs_0_ND_indices[0].size, dtype=int) + dofs_0_DD_i_red = xp.empty(dofs_0_DD_indices[0].size, dtype=int) + + dofs_1_NN_i_red = xp.empty(dofs_1_NN_indices[0].size, dtype=int) + dofs_1_DN_i_red = xp.empty(dofs_1_DN_indices[0].size, dtype=int) + dofs_1_ND_i_red = xp.empty(dofs_1_ND_indices[0].size, dtype=int) + dofs_1_DD_i_red = xp.empty(dofs_1_DD_indices[0].size, dtype=int) # ================================ nv = space.NbaseN * dofs_0_NN_indices[1] + dofs_0_NN_indices[2] - un = np.unique(nv) + un = xp.unique(nv) for i in range(dofs_0_NN_indices[0].size): - dofs_0_NN_i_red[i] = np.nonzero(un == nv[i])[0] + dofs_0_NN_i_red[i] = xp.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseN * dofs_0_DN_indices[1] + dofs_0_DN_indices[2] - un = np.unique(nv) + un = xp.unique(nv) for i in range(dofs_0_DN_indices[0].size): - dofs_0_DN_i_red[i] = np.nonzero(un == nv[i])[0] + dofs_0_DN_i_red[i] = xp.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseD * dofs_0_ND_indices[1] + dofs_0_ND_indices[2] - un = np.unique(nv) + un = xp.unique(nv) for i in range(dofs_0_ND_indices[0].size): - dofs_0_ND_i_red[i] = np.nonzero(un == nv[i])[0] + dofs_0_ND_i_red[i] = xp.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseD * dofs_0_DD_indices[1] + dofs_0_DD_indices[2] - un = np.unique(nv) + un = xp.unique(nv) for i in range(dofs_0_DD_indices[0].size): - dofs_0_DD_i_red[i] = np.nonzero(un == nv[i])[0] + dofs_0_DD_i_red[i] = xp.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseN * dofs_1_NN_indices[1] + dofs_1_NN_indices[2] - un = np.unique(nv) + un = xp.unique(nv) for i in range(dofs_1_NN_indices[0].size): - dofs_1_NN_i_red[i] = np.nonzero(un == nv[i])[0] + dofs_1_NN_i_red[i] = xp.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseN * dofs_1_DN_indices[1] + dofs_1_DN_indices[2] - un = np.unique(nv) + un = xp.unique(nv) for i in range(dofs_1_DN_indices[0].size): - dofs_1_DN_i_red[i] = np.nonzero(un == nv[i])[0] + dofs_1_DN_i_red[i] = xp.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseD * dofs_1_ND_indices[1] + dofs_1_ND_indices[2] - un = np.unique(nv) + un = xp.unique(nv) for i in range(dofs_1_ND_indices[0].size): - dofs_1_ND_i_red[i] = np.nonzero(un == nv[i])[0] + dofs_1_ND_i_red[i] = xp.nonzero(un == nv[i])[0] # ================================ nv = space.NbaseD * dofs_1_DD_indices[1] + dofs_1_DD_indices[2] - un = np.unique(nv) + un = xp.unique(nv) for i in range(dofs_1_DD_indices[0].size): - dofs_1_DD_i_red[i] = np.nonzero(un == nv[i])[0] + dofs_1_DD_i_red[i] = xp.nonzero(un == nv[i])[0] - dofs_0_NN_indices = np.vstack( - (dofs_0_NN_indices[0], dofs_0_NN_indices[1], dofs_0_NN_indices[2], dofs_0_NN_i_red) + dofs_0_NN_indices = xp.vstack( + (dofs_0_NN_indices[0], dofs_0_NN_indices[1], dofs_0_NN_indices[2], dofs_0_NN_i_red), ) - dofs_0_DN_indices = np.vstack( - (dofs_0_DN_indices[0], dofs_0_DN_indices[1], dofs_0_DN_indices[2], dofs_0_DN_i_red) + dofs_0_DN_indices = xp.vstack( + (dofs_0_DN_indices[0], dofs_0_DN_indices[1], dofs_0_DN_indices[2], dofs_0_DN_i_red), ) - dofs_0_ND_indices = np.vstack( - (dofs_0_ND_indices[0], dofs_0_ND_indices[1], dofs_0_ND_indices[2], dofs_0_ND_i_red) + dofs_0_ND_indices = xp.vstack( + (dofs_0_ND_indices[0], dofs_0_ND_indices[1], dofs_0_ND_indices[2], dofs_0_ND_i_red), ) - dofs_0_DD_indices = np.vstack( - (dofs_0_DD_indices[0], dofs_0_DD_indices[1], dofs_0_DD_indices[2], dofs_0_DD_i_red) + dofs_0_DD_indices = xp.vstack( + (dofs_0_DD_indices[0], dofs_0_DD_indices[1], dofs_0_DD_indices[2], dofs_0_DD_i_red), ) - dofs_1_NN_indices = np.vstack( - (dofs_1_NN_indices[0], dofs_1_NN_indices[1], dofs_1_NN_indices[2], dofs_1_NN_i_red) + dofs_1_NN_indices = xp.vstack( + (dofs_1_NN_indices[0], dofs_1_NN_indices[1], dofs_1_NN_indices[2], dofs_1_NN_i_red), ) - dofs_1_DN_indices = np.vstack( - (dofs_1_DN_indices[0], dofs_1_DN_indices[1], dofs_1_DN_indices[2], dofs_1_DN_i_red) + dofs_1_DN_indices = xp.vstack( + (dofs_1_DN_indices[0], dofs_1_DN_indices[1], dofs_1_DN_indices[2], dofs_1_DN_i_red), ) - dofs_1_ND_indices = np.vstack( - (dofs_1_ND_indices[0], dofs_1_ND_indices[1], dofs_1_ND_indices[2], dofs_1_ND_i_red) + dofs_1_ND_indices = xp.vstack( + (dofs_1_ND_indices[0], dofs_1_ND_indices[1], dofs_1_ND_indices[2], dofs_1_ND_i_red), ) - dofs_1_DD_indices = np.vstack( - (dofs_1_DD_indices[0], dofs_1_DD_indices[1], dofs_1_DD_indices[2], dofs_1_DD_i_red) + dofs_1_DD_indices = xp.vstack( + (dofs_1_DD_indices[0], dofs_1_DD_indices[1], dofs_1_DD_indices[2], dofs_1_DD_i_red), ) return ( @@ -642,8 +658,8 @@ def eval_for_PI(self, comp, fun): pts_PI = self.pts_PI[comp] - pts1, pts2 = np.meshgrid(pts_PI[0], pts_PI[1], indexing="ij") - # pts1, pts2 = np.meshgrid(pts_PI[0], pts_PI[1], indexing='ij', sparse=True) # numpy >1.7 + pts1, pts2 = xp.meshgrid(pts_PI[0], pts_PI[1], indexing="ij") + # pts1, pts2 = xp.meshgrid(pts_PI[0], pts_PI[1], indexing='ij', sparse=True) # numpy >1.7 return fun(pts1, pts2) @@ -906,8 +922,8 @@ def eval_for_PI(self, comp, fun): pts_PI = self.pts_PI[comp] - pts1, pts2, pts3 = np.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing="ij") - # pts1, pts2, pts3 = np.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing='ij', sparse=True) # numpy >1.7 + pts1, pts2, pts3 = xp.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing="ij") + # pts1, pts2, pts3 = xp.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing='ij', sparse=True) # numpy >1.7 return fun(pts1, pts2, pts3) @@ -939,25 +955,25 @@ def eval_for_PI(self, comp, fun): # rhs = mat_f # # elif comp=='11': - # rhs = np.empty( (self.d1, self.n2, self.n3) ) + # rhs = xp.empty( (self.d1, self.n2, self.n3) ) # # ker_glob.kernel_int_3d_eta1(self.subs1, self.subs_cum1, self.wts1, # mat_f.reshape(self.ne1, self.nq1, self.n2, self.n3), rhs # ) # elif comp=='12': - # rhs = np.empty( (self.n1, self.d2, self.n3) ) + # rhs = xp.empty( (self.n1, self.d2, self.n3) ) # # ker_glob.kernel_int_3d_eta2(self.subs2, self.subs_cum2, self.wts2, # mat_f.reshape(self.n1, self.ne2, self.nq2, self.n3), rhs # ) # elif comp=='13': - # rhs = np.empty( (self.n1, self.n2, self.d3) ) + # rhs = xp.empty( (self.n1, self.n2, self.d3) ) # # ker_glob.kernel_int_3d_eta3(self.subs3, self.subs_cum3, self.wts3, # mat_f.reshape(self.n1, self.n2, self.ne3, self.nq3), rhs # ) # elif comp=='21': - # rhs = np.empty( (self.n1, self.d2, self.d3) ) + # rhs = xp.empty( (self.n1, self.d2, self.d3) ) # # ker_glob.kernel_int_3d_eta2_eta3(self.subs2, self.subs3, # self.subs_cum2, self.subs_cum3, @@ -965,7 +981,7 @@ def eval_for_PI(self, comp, fun): # mat_f.reshape(self.n1, self.ne2, self.nq2, self.ne3, self.nq3), rhs # ) # elif comp=='22': - # rhs = np.empty( (self.d1, self.n2, self.d3) ) + # rhs = xp.empty( (self.d1, self.n2, self.d3) ) # # ker_glob.kernel_int_3d_eta1_eta3(self.subs1, self.subs3, # self.subs_cum1, self.subs_cum3, @@ -973,7 +989,7 @@ def eval_for_PI(self, comp, fun): # mat_f.reshape(self.ne1, self.nq1, self.n2, self.ne3, self.nq3), rhs # ) # elif comp=='23': - # rhs = np.empty( (self.d1, self.d2, self.n3) ) + # rhs = xp.empty( (self.d1, self.d2, self.n3) ) # # ker_glob.kernel_int_3d_eta1_eta2(self.subs1, self.subs2, # self.subs_cum1, self.subs_cum2, @@ -981,7 +997,7 @@ def eval_for_PI(self, comp, fun): # mat_f.reshape(self.ne1, self.nq1, self.ne2, self.nq2, self.n3), rhs # ) # elif comp=='3': - # rhs = np.empty( (self.d1, self.d2, self.d3) ) + # rhs = xp.empty( (self.d1, self.d2, self.d3) ) # # ker_glob.kernel_int_3d_eta1_eta2_eta3(self.subs1, self.subs2, self.subs3, # self.subs_cum1, self.subs_cum2, self.subs_cum3, @@ -1025,7 +1041,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='11': # assert mat_dofs.shape == (self.d1, self.n2, self.n3) - # rhs = np.empty( (self.ne1, self.nq1, self.n2, self.n3) ) + # rhs = xp.empty( (self.ne1, self.nq1, self.n2, self.n3) ) # # ker_glob.kernel_int_3d_eta1_transpose(self.subs1, self.subs_cum1, self.wts1, # mat_dofs, rhs) @@ -1034,7 +1050,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='12': # assert mat_dofs.shape == (self.n1, self.d2, self.n3) - # rhs = np.empty( (self.n1, self.ne2, self.nq2, self.n3) ) + # rhs = xp.empty( (self.n1, self.ne2, self.nq2, self.n3) ) # # ker_glob.kernel_int_3d_eta2_transpose(self.subs2, self.subs_cum2, self.wts2, # mat_dofs, rhs) @@ -1043,7 +1059,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='13': # assert mat_dofs.shape == (self.n1, self.n2, self.d3) - # rhs = np.empty( (self.n1, self.n2, self.ne3, self.nq3) ) + # rhs = xp.empty( (self.n1, self.n2, self.ne3, self.nq3) ) # # ker_glob.kernel_int_3d_eta3_transpose(self.subs3, self.subs_cum3, self.wts3, # mat_dofs, rhs) @@ -1052,7 +1068,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='21': # assert mat_dofs.shape == (self.n1, self.d2, self.d3) - # rhs = np.empty( (self.n1, self.ne2, self.nq2, self.ne3, self.nq3) ) + # rhs = xp.empty( (self.n1, self.ne2, self.nq2, self.ne3, self.nq3) ) # # ker_glob.kernel_int_3d_eta2_eta3_transpose(self.subs2, self.subs3, # self.subs_cum2, self.subs_cum3, @@ -1062,7 +1078,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='22': # assert mat_dofs.shape == (self.d1, self.n2, self.d3) - # rhs = np.empty( (self.ne1, self.nq1, self.n2, self.ne3, self.nq3) ) + # rhs = xp.empty( (self.ne1, self.nq1, self.n2, self.ne3, self.nq3) ) # # ker_glob.kernel_int_3d_eta1_eta3_transpose(self.subs1, self.subs3, # self.subs_cum1, self.subs_cum3, @@ -1072,7 +1088,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='23': # assert mat_dofs.shape == (self.d1, self.d2, self.n3) - # rhs = np.empty( (self.ne1, self.nq1, self.ne2, self.nq2, self.n3) ) + # rhs = xp.empty( (self.ne1, self.nq1, self.ne2, self.nq2, self.n3) ) # # ker_glob.kernel_int_3d_eta1_eta2_transpose(self.subs1, self.subs2, # self.subs_cum1, self.subs_cum2, @@ -1082,7 +1098,7 @@ def eval_for_PI(self, comp, fun): # # elif comp=='3': # assert mat_dofs.shape == (self.d1, self.d2, self.d3) - # rhs = np.empty( (self.ne1, self.nq1, self.ne2, self.nq2, self.ne3, self.nq3) ) + # rhs = xp.empty( (self.ne1, self.nq1, self.ne2, self.nq2, self.ne3, self.nq3) ) # # ker_glob.kernel_int_3d_eta1_eta2_eta3_transpose(self.subs1, self.subs2, self.subs3, # self.subs_cum1, self.subs_cum2, self.subs_cum3, @@ -1119,7 +1135,8 @@ def dofs(self, comp, mat_f): if comp == "0": dofs = kron_matvec_3d( - [spa.identity(mat_f.shape[0]), spa.identity(mat_f.shape[1]), spa.identity(mat_f.shape[2])], mat_f + [spa.identity(mat_f.shape[0]), spa.identity(mat_f.shape[1]), spa.identity(mat_f.shape[2])], + mat_f, ) elif comp == "11": @@ -1172,15 +1189,18 @@ def dofs_T(self, comp, mat_dofs): elif comp == "11": rhs = kron_matvec_3d( - [self.Q1.T, spa.identity(mat_dofs.shape[1]), spa.identity(mat_dofs.shape[2])], mat_dofs + [self.Q1.T, spa.identity(mat_dofs.shape[1]), spa.identity(mat_dofs.shape[2])], + mat_dofs, ) elif comp == "12": rhs = kron_matvec_3d( - [spa.identity(mat_dofs.shape[0]), self.Q2.T, spa.identity(mat_dofs.shape[2])], mat_dofs + [spa.identity(mat_dofs.shape[0]), self.Q2.T, spa.identity(mat_dofs.shape[2])], + mat_dofs, ) elif comp == "13": rhs = kron_matvec_3d( - [spa.identity(mat_dofs.shape[0]), spa.identity(mat_dofs.shape[1]), self.Q3.T], mat_dofs + [spa.identity(mat_dofs.shape[0]), spa.identity(mat_dofs.shape[1]), self.Q3.T], + mat_dofs, ) elif comp == "21": @@ -1595,26 +1615,26 @@ def __init__(self, tensor_space): else: if tensor_space.n_tor == 0: - x_i3 = np.array([0.0]) - x_q3 = np.array([0.0]) - x_q3G = np.array([0.0]) + x_i3 = xp.array([0.0]) + x_q3 = xp.array([0.0]) + x_q3G = xp.array([0.0]) else: if tensor_space.basis_tor == "r": if tensor_space.n_tor > 0: - x_i3 = np.array([1.0, 0.25 / tensor_space.n_tor]) - x_q3 = np.array([1.0, 0.25 / tensor_space.n_tor]) - x_q3G = np.array([1.0, 0.25 / tensor_space.n_tor]) + x_i3 = xp.array([1.0, 0.25 / tensor_space.n_tor]) + x_q3 = xp.array([1.0, 0.25 / tensor_space.n_tor]) + x_q3G = xp.array([1.0, 0.25 / tensor_space.n_tor]) else: - x_i3 = np.array([1.0, 0.75 / (-tensor_space.n_tor)]) - x_q3 = np.array([1.0, 0.75 / (-tensor_space.n_tor)]) - x_q3G = np.array([1.0, 0.75 / (-tensor_space.n_tor)]) + x_i3 = xp.array([1.0, 0.75 / (-tensor_space.n_tor)]) + x_q3 = xp.array([1.0, 0.75 / (-tensor_space.n_tor)]) + x_q3G = xp.array([1.0, 0.75 / (-tensor_space.n_tor)]) else: - x_i3 = np.array([0.0]) - x_q3 = np.array([0.0]) - x_q3G = np.array([0.0]) + x_i3 = xp.array([0.0]) + x_q3 = xp.array([0.0]) + x_q3G = xp.array([0.0]) self.Q3 = spa.identity(tensor_space.NbaseN[2], format="csr") self.Q3G = spa.identity(tensor_space.NbaseN[2], format="csr") @@ -1756,11 +1776,11 @@ def eval_for_PI(self, comp, fun, eval_kind, with_subs=True): pts_PI = self.getpts_for_PI(comp, with_subs) # array of evaluated function - mat_f = np.empty((pts_PI[0].size, pts_PI[1].size, pts_PI[2].size), dtype=float) + mat_f = xp.empty((pts_PI[0].size, pts_PI[1].size, pts_PI[2].size), dtype=float) # create a meshgrid and evaluate function on point set if eval_kind == "meshgrid": - pts1, pts2, pts3 = np.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing="ij") + pts1, pts2, pts3 = xp.meshgrid(pts_PI[0], pts_PI[1], pts_PI[2], indexing="ij") mat_f[:, :, :] = fun(pts1, pts2, pts3) # tensor-product evaluation is done by input function @@ -1783,13 +1803,13 @@ def eval_for_PI(self, comp, fun, eval_kind, with_subs=True): # n2 = self.pts_PI_0[1].size # # # apply (I0_22) to each column - # self.S0 = np.zeros(((n1 - 2)*n2, 3), dtype=float) + # self.S0 = xp.zeros(((n1 - 2)*n2, 3), dtype=float) # # for i in range(3): # self.S0[:, i] = kron_lusolve_2d(self.I0_22_LUs, self.I0_21[:, i].toarray().reshape(n1 - 2, n2)).flatten() # # # 3 x 3 matrix - # self.S0 = np.linalg.inv(self.I0_11.toarray() - self.I0_12.toarray().dot(self.S0)) + # self.S0 = xp.linalg.inv(self.I0_11.toarray() - self.I0_12.toarray().dot(self.S0)) # # # ====================================== @@ -1814,7 +1834,7 @@ def eval_for_PI(self, comp, fun, eval_kind, with_subs=True): # # solve for tensor-product coefficients # out2 = out2 - kron_lusolve_2d(self.I0_22_LUs, self.I0_21.dot(self.S0.dot(rhs1)).reshape(n1 - 2, n2)) + kron_lusolve_2d(self.I0_22_LUs, self.I0_21.dot(self.S0.dot(self.I0_12.dot(out2.flatten()))).reshape(n1 - 2, n2)) # - # return np.concatenate((out1, out2.flatten())) + # return xp.concatenate((out1, out2.flatten())) # ====================================== @@ -1836,10 +1856,12 @@ def solve_V1(self, dofs_1, include_bc): # with boundary splines if include_bc: dofs_11 = dofs_1[: self.P1_pol.shape[0] * self.I_tor.shape[0]].reshape( - self.P1_pol.shape[0], self.I_tor.shape[0] + self.P1_pol.shape[0], + self.I_tor.shape[0], ) dofs_12 = dofs_1[self.P1_pol.shape[0] * self.I_tor.shape[0] :].reshape( - self.P0_pol.shape[0], self.H_tor.shape[0] + self.P0_pol.shape[0], + self.H_tor.shape[0], ) coeffs1 = self.I_tor_LU.solve(self.I1_pol_LU.solve(dofs_11).T).T @@ -1848,26 +1870,30 @@ def solve_V1(self, dofs_1, include_bc): # without boundary splines else: dofs_11 = dofs_1[: self.P1_pol_0.shape[0] * self.I0_tor.shape[0]].reshape( - self.P1_pol_0.shape[0], self.I0_tor.shape[0] + self.P1_pol_0.shape[0], + self.I0_tor.shape[0], ) dofs_12 = dofs_1[self.P1_pol_0.shape[0] * self.I0_tor.shape[0] :].reshape( - self.P0_pol_0.shape[0], self.H0_tor.shape[0] + self.P0_pol_0.shape[0], + self.H0_tor.shape[0], ) coeffs1 = self.I0_tor_LU.solve(self.I1_pol_0_LU.solve(dofs_11).T).T coeffs2 = self.H0_tor_LU.solve(self.I0_pol_0_LU.solve(dofs_12).T).T - return np.concatenate((coeffs1.flatten(), coeffs2.flatten())) + return xp.concatenate((coeffs1.flatten(), coeffs2.flatten())) # ====================================== def solve_V2(self, dofs_2, include_bc): # with boundary splines if include_bc: dofs_21 = dofs_2[: self.P2_pol.shape[0] * self.H_tor.shape[0]].reshape( - self.P2_pol.shape[0], self.H_tor.shape[0] + self.P2_pol.shape[0], + self.H_tor.shape[0], ) dofs_22 = dofs_2[self.P2_pol.shape[0] * self.H_tor.shape[0] :].reshape( - self.P3_pol.shape[0], self.I_tor.shape[0] + self.P3_pol.shape[0], + self.I_tor.shape[0], ) coeffs1 = self.H_tor_LU.solve(self.I2_pol_LU.solve(dofs_21).T).T @@ -1876,16 +1902,18 @@ def solve_V2(self, dofs_2, include_bc): # without boundary splines else: dofs_21 = dofs_2[: self.P2_pol_0.shape[0] * self.H0_tor.shape[0]].reshape( - self.P2_pol_0.shape[0], self.H0_tor.shape[0] + self.P2_pol_0.shape[0], + self.H0_tor.shape[0], ) dofs_22 = dofs_2[self.P2_pol_0.shape[0] * self.H0_tor.shape[0] :].reshape( - self.P3_pol_0.shape[0], self.I0_tor.shape[0] + self.P3_pol_0.shape[0], + self.I0_tor.shape[0], ) coeffs1 = self.H0_tor_LU.solve(self.I2_pol_0_LU.solve(dofs_21).T).T coeffs2 = self.I0_tor_LU.solve(self.I3_pol_0_LU.solve(dofs_22).T).T - return np.concatenate((coeffs1.flatten(), coeffs2.flatten())) + return xp.concatenate((coeffs1.flatten(), coeffs2.flatten())) # ====================================== def solve_V3(self, dofs_3, include_bc): @@ -1938,16 +1966,18 @@ def apply_IinvT_V1(self, rhs, include_bc=False): # without boundary splines else: rhs1 = rhs[: self.P1_pol_0.shape[0] * self.I0_tor.shape[0]].reshape( - self.P1_pol_0.shape[0], self.I0_tor.shape[0] + self.P1_pol_0.shape[0], + self.I0_tor.shape[0], ) rhs2 = rhs[self.P1_pol_0.shape[0] * self.I0_tor.shape[0] :].reshape( - self.P0_pol_0.shape[0], self.H0_tor.shape[0] + self.P0_pol_0.shape[0], + self.H0_tor.shape[0], ) rhs1 = self.I1_pol_0_T_LU.solve(self.I0_tor_T_LU.solve(rhs1.T).T) rhs2 = self.I0_pol_0_T_LU.solve(self.H0_tor_T_LU.solve(rhs2.T).T) - return np.concatenate((rhs1.flatten(), rhs2.flatten())) + return xp.concatenate((rhs1.flatten(), rhs2.flatten())) # ====================================== def apply_IinvT_V2(self, rhs, include_bc=False): @@ -1968,16 +1998,18 @@ def apply_IinvT_V2(self, rhs, include_bc=False): # without boundary splines else: rhs1 = rhs[: self.P2_pol_0.shape[0] * self.H0_tor.shape[0]].reshape( - self.P2_pol_0.shape[0], self.H0_tor.shape[0] + self.P2_pol_0.shape[0], + self.H0_tor.shape[0], ) rhs2 = rhs[self.P2_pol_0.shape[0] * self.H0_tor.shape[0] :].reshape( - self.P3_pol_0.shape[0], self.I0_tor.shape[0] + self.P3_pol_0.shape[0], + self.I0_tor.shape[0], ) rhs1 = self.I2_pol_0_T_LU.solve(self.H0_tor_T_LU.solve(rhs1.T).T) rhs2 = self.I3_pol_0_T_LU.solve(self.I0_tor_T_LU.solve(rhs2.T).T) - return np.concatenate((rhs1.flatten(), rhs2.flatten())) + return xp.concatenate((rhs1.flatten(), rhs2.flatten())) # ====================================== def apply_IinvT_V3(self, rhs, include_bc=False): @@ -2004,7 +2036,8 @@ def dofs_0(self, fun, include_bc=True, eval_kind="meshgrid"): # get dofs on tensor-product grid dofs = kron_matvec_3d( - [spa.identity(dofs.shape[0]), spa.identity(dofs.shape[1]), spa.identity(dofs.shape[2])], dofs + [spa.identity(dofs.shape[0]), spa.identity(dofs.shape[1]), spa.identity(dofs.shape[2])], + dofs, ) # apply extraction operator for dofs @@ -2042,9 +2075,9 @@ def dofs_1(self, fun, include_bc=True, eval_kind="meshgrid", with_subs=True): # apply extraction operator for dofs if include_bc: - dofs = self.P1.dot(np.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) + dofs = self.P1.dot(xp.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) else: - dofs = self.P1_0.dot(np.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) + dofs = self.P1_0.dot(xp.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) return dofs @@ -2075,9 +2108,9 @@ def dofs_2(self, fun, include_bc=True, eval_kind="meshgrid", with_subs=True): # apply extraction operator for dofs if include_bc: - dofs = self.P2.dot(np.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) + dofs = self.P2.dot(xp.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) else: - dofs = self.P2_0.dot(np.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) + dofs = self.P2_0.dot(xp.concatenate((dofs_1.flatten(), dofs_2.flatten(), dofs_3.flatten()))) return dofs @@ -2120,20 +2153,20 @@ def pi_3(self, fun, include_bc=True, eval_kind="meshgrid", with_subs=True): # ======================================== def assemble_approx_inv(self, tol): - if self.approx_Ik_0_inv == False or (self.approx_Ik_0_inv == True and self.approx_Ik_0_tol != tol): + if not self.approx_Ik_0_inv or (self.approx_Ik_0_inv and self.approx_Ik_0_tol != tol): # poloidal plane - I0_pol_0_inv_approx = np.linalg.inv(self.I0_pol_0.toarray()) - I1_pol_0_inv_approx = np.linalg.inv(self.I1_pol_0.toarray()) - I2_pol_0_inv_approx = np.linalg.inv(self.I2_pol_0.toarray()) - I3_pol_0_inv_approx = np.linalg.inv(self.I3_pol_0.toarray()) - I0_pol_inv_approx = np.linalg.inv(self.I0_pol.toarray()) + I0_pol_0_inv_approx = xp.linalg.inv(self.I0_pol_0.toarray()) + I1_pol_0_inv_approx = xp.linalg.inv(self.I1_pol_0.toarray()) + I2_pol_0_inv_approx = xp.linalg.inv(self.I2_pol_0.toarray()) + I3_pol_0_inv_approx = xp.linalg.inv(self.I3_pol_0.toarray()) + I0_pol_inv_approx = xp.linalg.inv(self.I0_pol.toarray()) if tol > 1e-14: - I0_pol_0_inv_approx[np.abs(I0_pol_0_inv_approx) < tol] = 0.0 - I1_pol_0_inv_approx[np.abs(I1_pol_0_inv_approx) < tol] = 0.0 - I2_pol_0_inv_approx[np.abs(I2_pol_0_inv_approx) < tol] = 0.0 - I3_pol_0_inv_approx[np.abs(I3_pol_0_inv_approx) < tol] = 0.0 - I0_pol_inv_approx[np.abs(I0_pol_inv_approx) < tol] = 0.0 + I0_pol_0_inv_approx[xp.abs(I0_pol_0_inv_approx) < tol] = 0.0 + I1_pol_0_inv_approx[xp.abs(I1_pol_0_inv_approx) < tol] = 0.0 + I2_pol_0_inv_approx[xp.abs(I2_pol_0_inv_approx) < tol] = 0.0 + I3_pol_0_inv_approx[xp.abs(I3_pol_0_inv_approx) < tol] = 0.0 + I0_pol_inv_approx[xp.abs(I0_pol_inv_approx) < tol] = 0.0 I0_pol_0_inv_approx = spa.csr_matrix(I0_pol_0_inv_approx) I1_pol_0_inv_approx = spa.csr_matrix(I1_pol_0_inv_approx) @@ -2142,12 +2175,12 @@ def assemble_approx_inv(self, tol): I0_pol_inv_approx = spa.csr_matrix(I0_pol_inv_approx) # toroidal direction - I_inv_tor_approx = np.linalg.inv(self.I_tor.toarray()) - H_inv_tor_approx = np.linalg.inv(self.H_tor.toarray()) + I_inv_tor_approx = xp.linalg.inv(self.I_tor.toarray()) + H_inv_tor_approx = xp.linalg.inv(self.H_tor.toarray()) if tol > 1e-14: - I_inv_tor_approx[np.abs(I_inv_tor_approx) < tol] = 0.0 - H_inv_tor_approx[np.abs(H_inv_tor_approx) < tol] = 0.0 + I_inv_tor_approx[xp.abs(I_inv_tor_approx) < tol] = 0.0 + H_inv_tor_approx[xp.abs(H_inv_tor_approx) < tol] = 0.0 I_inv_tor_approx = spa.csr_matrix(I_inv_tor_approx) H_inv_tor_approx = spa.csr_matrix(H_inv_tor_approx) diff --git a/src/struphy/eigenvalue_solvers/spline_space.py b/src/struphy/eigenvalue_solvers/spline_space.py index a4239af46..c10124e57 100644 --- a/src/struphy/eigenvalue_solvers/spline_space.py +++ b/src/struphy/eigenvalue_solvers/spline_space.py @@ -6,8 +6,8 @@ Basic modules to create tensor-product finite element spaces of univariate B-splines. """ +import cunumpy as xp import matplotlib -import numpy as np import scipy.sparse as spa matplotlib.rcParams.update({"font.size": 16}) @@ -49,19 +49,19 @@ class Spline_space_1d: Attributes ---------- - el_b : np.array + el_b : xp.array Element boundaries, equally spaced. delta : float Uniform grid spacing - T : np.array + T : xp.array Knot vector of 0-space. - t : np.arrray + t : xp.arrray Knot vector of 1-space. - greville : np.array + greville : xp.array Greville points. NbaseN : int @@ -70,22 +70,22 @@ class Spline_space_1d: NbaseD : int Dimension of 1-space. - indN : np.array + indN : xp.array Global indices of non-vanishing B-splines in each element in format (element, local basis function) - indD : np.array + indD : xp.array Global indices of non-vanishing M-splines in each element in format (element, local basis function) - pts : np.array + pts : xp.array Global GL quadrature points in format (element, local point). - wts : np.array + wts : xp.array Global GL quadrature weights in format (element, local point). - basisN : np.array + basisN : xp.array N-basis functions evaluated at quadrature points in format (element, local basis function, derivative, local point) - basisD : np.array + basisD : xp.array D-basis functions evaluated at quadrature points in format (element, local basis function, derivative, local point) E0 : csr_matrix @@ -139,7 +139,7 @@ def __init__(self, Nel, p, spl_kind, n_quad=6, bc=["f", "f"]): else: self.bc = bc - self.el_b = np.linspace(0.0, 1.0, Nel + 1) # element boundaries + self.el_b = xp.linspace(0.0, 1.0, Nel + 1) # element boundaries self.delta = 1 / self.Nel # element length self.T = bsp.make_knots(self.el_b, self.p, self.spl_kind) # spline knot vector for B-splines (N) @@ -151,13 +151,13 @@ def __init__(self, Nel, p, spl_kind, n_quad=6, bc=["f", "f"]): self.NbaseD = self.NbaseN - 1 + self.spl_kind # total number of M-splines (D) # global indices of non-vanishing splines in each element in format (Nel, p + 1) - self.indN = (np.indices((self.Nel, self.p + 1 - 0))[1] + np.arange(self.Nel)[:, None]) % self.NbaseN - self.indD = (np.indices((self.Nel, self.p + 1 - 1))[1] + np.arange(self.Nel)[:, None]) % self.NbaseD + self.indN = (xp.indices((self.Nel, self.p + 1 - 0))[1] + xp.arange(self.Nel)[:, None]) % self.NbaseN + self.indD = (xp.indices((self.Nel, self.p + 1 - 1))[1] + xp.arange(self.Nel)[:, None]) % self.NbaseD self.n_quad = n_quad # number of Gauss-Legendre points per grid cell (defined by break points) - self.pts_loc = np.polynomial.legendre.leggauss(self.n_quad)[0] # Gauss-Legendre points (GLQP) in (-1, 1) - self.wts_loc = np.polynomial.legendre.leggauss(self.n_quad)[1] # Gauss-Legendre weights (GLQW) in (-1, 1) + self.pts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[0] # Gauss-Legendre points (GLQP) in (-1, 1) + self.wts_loc = xp.polynomial.legendre.leggauss(self.n_quad)[1] # Gauss-Legendre weights (GLQW) in (-1, 1) # global GLQP in format (element, local point) and total number of GLQP self.pts = bsp.quadrature_grid(self.el_b, self.pts_loc, self.wts_loc)[0] @@ -177,8 +177,8 @@ def __init__(self, Nel, p, spl_kind, n_quad=6, bc=["f", "f"]): d1 = self.NbaseD # boundary operators - self.B0 = np.identity(n1, dtype=float) - self.B1 = np.identity(d1, dtype=float) + self.B0 = xp.identity(n1, dtype=float) + self.B1 = xp.identity(d1, dtype=float) # extraction operators without boundary conditions self.E0 = spa.csr_matrix(self.B0.copy()) @@ -267,16 +267,16 @@ def evaluate_N(self, eta, coeff, kind=0): coeff = self.E0_0.T.dot(coeff) if isinstance(eta, float): - pts = np.array([eta]) - elif isinstance(eta, np.ndarray): + pts = xp.array([eta]) + elif isinstance(eta, xp.ndarray): pts = eta.flatten() - values = np.empty(pts.size, dtype=float) + values = xp.empty(pts.size, dtype=float) eva_1d.evaluate_vector(self.T, self.p, self.indN, coeff, pts, values, kind) if isinstance(eta, float): values = values[0] - elif isinstance(eta, np.ndarray): + elif isinstance(eta, xp.ndarray): values = values.reshape(eta.shape) return values @@ -303,16 +303,16 @@ def evaluate_D(self, eta, coeff): assert coeff.size == self.E1.shape[0] if isinstance(eta, float): - pts = np.array([eta]) - elif isinstance(eta, np.ndarray): + pts = xp.array([eta]) + elif isinstance(eta, xp.ndarray): pts = eta.flatten() - values = np.empty(pts.size, dtype=float) + values = xp.empty(pts.size, dtype=float) eva_1d.evaluate_vector(self.t, self.p - 1, self.indD, coeff, pts, values, 1) if isinstance(eta, float): values = values[0] - elif isinstance(eta, np.ndarray): + elif isinstance(eta, xp.ndarray): values = values.reshape(eta.shape) return values @@ -331,12 +331,12 @@ def plot_splines(self, n_pts=500, which="N"): which basis to plot. 'N', 'D' or 'dN' (optional, default='N') """ - etaplot = np.linspace(0.0, 1.0, n_pts) + etaplot = xp.linspace(0.0, 1.0, n_pts) degree = self.p if which == "N": - coeff = np.zeros(self.NbaseN, dtype=float) + coeff = xp.zeros(self.NbaseN, dtype=float) for i in range(self.NbaseN): coeff[:] = 0.0 @@ -344,7 +344,7 @@ def plot_splines(self, n_pts=500, which="N"): plt.plot(etaplot, self.evaluate_N(etaplot, coeff), label=str(i)) elif which == "D": - coeff = np.zeros(self.NbaseD, dtype=float) + coeff = xp.zeros(self.NbaseD, dtype=float) for i in range(self.NbaseD): coeff[:] = 0.0 @@ -354,7 +354,7 @@ def plot_splines(self, n_pts=500, which="N"): degree = self.p - 1 elif which == "dN": - coeff = np.zeros(self.NbaseN, dtype=float) + coeff = xp.zeros(self.NbaseN, dtype=float) for i in range(self.NbaseN): coeff[:] = 0.0 @@ -369,8 +369,8 @@ def plot_splines(self, n_pts=500, which="N"): else: bcs = "clamped" - (greville,) = plt.plot(self.greville, np.zeros(self.greville.shape), "ro", label="greville") - (breaks,) = plt.plot(self.el_b, np.zeros(self.el_b.shape), "k+", label="breaks") + (greville,) = plt.plot(self.greville, xp.zeros(self.greville.shape), "ro", label="greville") + (breaks,) = plt.plot(self.el_b, xp.zeros(self.el_b.shape), "k+", label="breaks") plt.title(which + f"$^{degree}$-splines, " + bcs + f", Nel={self.Nel}") plt.legend(handles=[greville, breaks]) @@ -554,8 +554,8 @@ def __init__(self, spline_spaces, ck=-1, cx=None, cy=None, n_tor=0, basis_tor="r self.M1_tor = spa.identity(1, format="csr") else: - self.M0_tor = spa.csr_matrix(np.identity(2) / 2) - self.M1_tor = spa.csr_matrix(np.identity(2) / 2) + self.M0_tor = spa.csr_matrix(xp.identity(2) / 2) + self.M1_tor = spa.csr_matrix(xp.identity(2) / 2) else: self.M0_tor = mass_1d.get_M(self.spaces[2], 0, 0) @@ -712,27 +712,33 @@ def __init__(self, spline_spaces, ck=-1, cx=None, cy=None, n_tor=0, basis_tor="r # extraction operators for 3D diagram: without boundary conditions self.E0 = spa.kron(self.E0_pol, self.E0_tor, format="csr") self.E1 = spa.bmat( - [[spa.kron(self.E1_pol, self.E0_tor), None], [None, spa.kron(self.E0_pol, self.E1_tor)]], format="csr" + [[spa.kron(self.E1_pol, self.E0_tor), None], [None, spa.kron(self.E0_pol, self.E1_tor)]], + format="csr", ) self.E2 = spa.bmat( - [[spa.kron(self.E2_pol, self.E1_tor), None], [None, spa.kron(self.E3_pol, self.E0_tor)]], format="csr" + [[spa.kron(self.E2_pol, self.E1_tor), None], [None, spa.kron(self.E3_pol, self.E0_tor)]], + format="csr", ) self.E3 = spa.kron(self.E3_pol, self.E1_tor, format="csr") self.Ev = spa.bmat( - [[spa.kron(self.Ev_pol, self.E0_tor), None], [None, spa.kron(self.E0_pol, self.E0_tor)]], format="csr" + [[spa.kron(self.Ev_pol, self.E0_tor), None], [None, spa.kron(self.E0_pol, self.E0_tor)]], + format="csr", ) # boundary operators for 3D diagram self.B0 = spa.kron(self.B0_pol, self.B0_tor, format="csr") self.B1 = spa.bmat( - [[spa.kron(self.B1_pol, self.B0_tor), None], [None, spa.kron(self.B0_pol, self.B1_tor)]], format="csr" + [[spa.kron(self.B1_pol, self.B0_tor), None], [None, spa.kron(self.B0_pol, self.B1_tor)]], + format="csr", ) self.B2 = spa.bmat( - [[spa.kron(self.B2_pol, self.B1_tor), None], [None, spa.kron(self.B3_pol, self.B0_tor)]], format="csr" + [[spa.kron(self.B2_pol, self.B1_tor), None], [None, spa.kron(self.B3_pol, self.B0_tor)]], + format="csr", ) self.B3 = spa.kron(self.B3_pol, self.B1_tor, format="csr") self.Bv = spa.bmat( - [[spa.kron(self.Bv_pol, self.E0_tor), None], [None, spa.kron(Bv3, self.B0_tor)]], format="csr" + [[spa.kron(self.Bv_pol, self.E0_tor), None], [None, spa.kron(Bv3, self.B0_tor)]], + format="csr", ) # extraction operators for 3D diagram: with boundary conditions @@ -785,7 +791,7 @@ def apply_M1_ten(self, x, mats): out1 = mats[0][1].dot(mats[0][0].dot(x1).T).T out2 = mats[1][1].dot(mats[1][0].dot(x2).T).T - return np.concatenate((out1.flatten(), out2.flatten())) + return xp.concatenate((out1.flatten(), out2.flatten())) def apply_M2_ten(self, x, mats): """ @@ -797,7 +803,7 @@ def apply_M2_ten(self, x, mats): out1 = mats[0][1].dot(mats[0][0].dot(x1).T).T out2 = mats[1][1].dot(mats[1][0].dot(x2).T).T - return np.concatenate((out1.flatten(), out2.flatten())) + return xp.concatenate((out1.flatten(), out2.flatten())) def apply_M3_ten(self, x, mats): """ @@ -820,7 +826,7 @@ def apply_Mv_ten(self, x, mats): out1 = mats[0][1].dot(mats[0][0].dot(x1).T).T out2 = mats[1][1].dot(mats[1][0].dot(x2).T).T - return np.concatenate((out1.flatten(), out2.flatten())) + return xp.concatenate((out1.flatten(), out2.flatten())) def apply_M0_0_ten(self, x, mats): """ @@ -841,13 +847,13 @@ def apply_M1_0_ten(self, x, mats): x1, x2 = self.reshape_pol_1(x) out1 = self.B0_tor.dot( - mats[0][1].dot(self.B0_tor.T.dot(self.B1_pol.dot(mats[0][0].dot(self.B1_pol.T.dot(x1))).T)) + mats[0][1].dot(self.B0_tor.T.dot(self.B1_pol.dot(mats[0][0].dot(self.B1_pol.T.dot(x1))).T)), ).T out2 = self.B1_tor.dot( - mats[1][1].dot(self.B1_tor.T.dot(self.B0_pol.dot(mats[1][0].dot(self.B0_pol.T.dot(x2))).T)) + mats[1][1].dot(self.B1_tor.T.dot(self.B0_pol.dot(mats[1][0].dot(self.B0_pol.T.dot(x2))).T)), ).T - return np.concatenate((out1.flatten(), out2.flatten())) + return xp.concatenate((out1.flatten(), out2.flatten())) def apply_M2_0_ten(self, x, mats): """ @@ -857,13 +863,13 @@ def apply_M2_0_ten(self, x, mats): x1, x2 = self.reshape_pol_2(x) out1 = self.B1_tor.dot( - mats[0][1].dot(self.B1_tor.T.dot(self.B2_pol.dot(mats[0][0].dot(self.B2_pol.T.dot(x1))).T)) + mats[0][1].dot(self.B1_tor.T.dot(self.B2_pol.dot(mats[0][0].dot(self.B2_pol.T.dot(x1))).T)), ).T out2 = self.B0_tor.dot( - mats[1][1].dot(self.B0_tor.T.dot(self.B3_pol.dot(mats[1][0].dot(self.B3_pol.T.dot(x2))).T)) + mats[1][1].dot(self.B0_tor.T.dot(self.B3_pol.dot(mats[1][0].dot(self.B3_pol.T.dot(x2))).T)), ).T - return np.concatenate((out1.flatten(), out2.flatten())) + return xp.concatenate((out1.flatten(), out2.flatten())) def apply_M3_0_ten(self, x, mats): """ @@ -886,7 +892,7 @@ def apply_Mv_0_ten(self, x, mats): out1 = mats[0][1].dot(self.Bv_pol.dot(mats[0][0].dot(self.Bv_pol.T.dot(x1))).T).T out2 = self.B0_tor.dot(mats[1][1].dot(self.B0_tor.T.dot(mats[1][0].dot(x2).T))).T - return np.concatenate((out1.flatten(), out2.flatten())) + return xp.concatenate((out1.flatten(), out2.flatten())) def __assemble_M0(self, domain, as_tensor=False): """ @@ -928,10 +934,12 @@ def __assemble_M1(self, domain, as_tensor=False): self.M1_pol_mat = mass_2d.get_M1(self, domain) matvec = lambda x: self.apply_M1_ten( - x, [[self.M1_pol_mat[0], self.M0_tor], [self.M1_pol_mat[1], self.M1_tor]] + x, + [[self.M1_pol_mat[0], self.M0_tor], [self.M1_pol_mat[1], self.M1_tor]], ) matvec_0 = lambda x: self.apply_M1_0_ten( - x, [[self.M1_pol_mat[0], self.M0_tor], [self.M1_pol_mat[1], self.M1_tor]] + x, + [[self.M1_pol_mat[0], self.M0_tor], [self.M1_pol_mat[1], self.M1_tor]], ) # 3D @@ -939,7 +947,8 @@ def __assemble_M1(self, domain, as_tensor=False): if self.dim == 2: M11, M22 = mass_2d.get_M1(self, domain) self.M1_mat = spa.bmat( - [[spa.kron(M11, self.M0_tor), None], [None, spa.kron(M22, self.M1_tor)]], format="csr" + [[spa.kron(M11, self.M0_tor), None], [None, spa.kron(M22, self.M1_tor)]], + format="csr", ) else: self.M1_mat = mass_3d.get_M1(self, domain) @@ -963,10 +972,12 @@ def __assemble_M2(self, domain, as_tensor=False): self.M2_pol_mat = mass_2d.get_M2(self, domain) matvec = lambda x: self.apply_M2_ten( - x, [[self.M2_pol_mat[0], self.M1_tor], [self.M2_pol_mat[1], self.M0_tor]] + x, + [[self.M2_pol_mat[0], self.M1_tor], [self.M2_pol_mat[1], self.M0_tor]], ) matvec_0 = lambda x: self.apply_M2_0_ten( - x, [[self.M2_pol_mat[0], self.M1_tor], [self.M2_pol_mat[1], self.M0_tor]] + x, + [[self.M2_pol_mat[0], self.M1_tor], [self.M2_pol_mat[1], self.M0_tor]], ) # 3D @@ -974,7 +985,8 @@ def __assemble_M2(self, domain, as_tensor=False): if self.dim == 2: M11, M22 = mass_2d.get_M2(self, domain) self.M2_mat = spa.bmat( - [[spa.kron(M11, self.M1_tor), None], [None, spa.kron(M22, self.M0_tor)]], format="csr" + [[spa.kron(M11, self.M1_tor), None], [None, spa.kron(M22, self.M0_tor)]], + format="csr", ) else: self.M2_mat = mass_3d.get_M2(self, domain) @@ -1026,10 +1038,12 @@ def __assemble_Mv(self, domain, as_tensor=False): self.Mv_pol_mat = mass_2d.get_Mv(self, domain) matvec = lambda x: self.apply_Mv_ten( - x, [[self.Mv_pol_mat[0], self.M0_tor], [self.Mv_pol_mat[1], self.M0_tor]] + x, + [[self.Mv_pol_mat[0], self.M0_tor], [self.Mv_pol_mat[1], self.M0_tor]], ) matvec_0 = lambda x: self.apply_Mv_0_ten( - x, [[self.Mv_pol_mat[0], self.M0_tor], [self.Mv_pol_mat[1], self.M0_tor]] + x, + [[self.Mv_pol_mat[0], self.M0_tor], [self.Mv_pol_mat[1], self.M0_tor]], ) # 3D @@ -1037,7 +1051,8 @@ def __assemble_Mv(self, domain, as_tensor=False): if self.dim == 2: M11, M22 = mass_2d.get_Mv(self, domain) self.Mv_mat = spa.bmat( - [[spa.kron(M11, self.M0_tor), None], [None, spa.kron(M22, self.M0_tor)]], format="csr" + [[spa.kron(M11, self.M0_tor), None], [None, spa.kron(M22, self.M0_tor)]], + format="csr", ) else: self.Mv_mat = mass_3d.get_Mv(self, domain) @@ -1093,17 +1108,21 @@ def reshape_pol_1(self, coeff): if c_size == self.E1.shape[0]: coeff1_pol_1 = coeff[: self.E1_pol.shape[0] * self.E0_tor.shape[0]].reshape( - self.E1_pol.shape[0], self.E0_tor.shape[0] + self.E1_pol.shape[0], + self.E0_tor.shape[0], ) coeff1_pol_3 = coeff[self.E1_pol.shape[0] * self.E0_tor.shape[0] :].reshape( - self.E0_pol.shape[0], self.E1_tor.shape[0] + self.E0_pol.shape[0], + self.E1_tor.shape[0], ) else: coeff1_pol_1 = coeff[: self.E1_pol_0.shape[0] * self.E0_tor_0.shape[0]].reshape( - self.E1_pol_0.shape[0], self.E0_tor_0.shape[0] + self.E1_pol_0.shape[0], + self.E0_tor_0.shape[0], ) coeff1_pol_3 = coeff[self.E1_pol_0.shape[0] * self.E0_tor_0.shape[0] :].reshape( - self.E0_pol_0.shape[0], self.E1_tor_0.shape[0] + self.E0_pol_0.shape[0], + self.E1_tor_0.shape[0], ) return coeff1_pol_1, coeff1_pol_3 @@ -1119,17 +1138,21 @@ def reshape_pol_2(self, coeff): if c_size == self.E2.shape[0]: coeff2_pol_1 = coeff[: self.E2_pol.shape[0] * self.E1_tor.shape[0]].reshape( - self.E2_pol.shape[0], self.E1_tor.shape[0] + self.E2_pol.shape[0], + self.E1_tor.shape[0], ) coeff2_pol_3 = coeff[self.E2_pol.shape[0] * self.E1_tor.shape[0] :].reshape( - self.E3_pol.shape[0], self.E0_tor.shape[0] + self.E3_pol.shape[0], + self.E0_tor.shape[0], ) else: coeff2_pol_1 = coeff[: self.E2_pol_0.shape[0] * self.E1_tor_0.shape[0]].reshape( - self.E2_pol_0.shape[0], self.E1_tor_0.shape[0] + self.E2_pol_0.shape[0], + self.E1_tor_0.shape[0], ) coeff2_pol_3 = coeff[self.E2_pol_0.shape[0] * self.E1_tor_0.shape[0] :].reshape( - self.E3_pol_0.shape[0], self.E0_tor_0.shape[0] + self.E3_pol_0.shape[0], + self.E0_tor_0.shape[0], ) return coeff2_pol_1, coeff2_pol_3 @@ -1161,18 +1184,22 @@ def reshape_pol_v(self, coeff): if c_size == self.Ev.shape[0]: coeffv_pol_1 = coeff[: self.Ev_pol.shape[0] * self.E0_tor.shape[0]].reshape( - self.Ev_pol.shape[0], self.E0_tor.shape[0] + self.Ev_pol.shape[0], + self.E0_tor.shape[0], ) coeffv_pol_3 = coeff[self.Ev_pol.shape[0] * self.E0_tor.shape[0] :].reshape( - self.E0_pol.shape[0], self.E0_tor.shape[0] + self.E0_pol.shape[0], + self.E0_tor.shape[0], ) else: coeffv_pol_1 = coeff[: self.Ev_pol_0.shape[0] * self.E0_tor.shape[0]].reshape( - self.Ev_pol_0.shape[0], self.E0_tor.shape[0] + self.Ev_pol_0.shape[0], + self.E0_tor.shape[0], ) coeffv_pol_3 = coeff[self.Ev_pol_0.shape[0] * self.E0_tor.shape[0] :].reshape( - self.E0_pol.shape[0], self.E0_tor_0.shape[0] + self.E0_pol.shape[0], + self.E0_tor_0.shape[0], ) return coeffv_pol_1, coeffv_pol_3 @@ -1228,7 +1255,7 @@ def extract_1(self, coeff): else: coeff1 = self.E1_0.T.dot(coeff) - coeff1_1, coeff1_2, coeff1_3 = np.split(coeff1, [self.Ntot_1form_cum[0], self.Ntot_1form_cum[1]]) + coeff1_1, coeff1_2, coeff1_3 = xp.split(coeff1, [self.Ntot_1form_cum[0], self.Ntot_1form_cum[1]]) coeff1_1 = coeff1_1.reshape(self.Nbase_1form[0]) coeff1_2 = coeff1_2.reshape(self.Nbase_1form[1]) @@ -1259,7 +1286,7 @@ def extract_2(self, coeff): else: coeff2 = self.E2_0.T.dot(coeff) - coeff2_1, coeff2_2, coeff2_3 = np.split(coeff2, [self.Ntot_2form_cum[0], self.Ntot_2form_cum[1]]) + coeff2_1, coeff2_2, coeff2_3 = xp.split(coeff2, [self.Ntot_2form_cum[0], self.Ntot_2form_cum[1]]) coeff2_1 = coeff2_1.reshape(self.Nbase_2form[0]) coeff2_2 = coeff2_2.reshape(self.Nbase_2form[1]) @@ -1304,7 +1331,7 @@ def extract_v(self, coeff): else: coeffv = self.Ev_0.T.dot(coeff) - coeffv_1, coeffv_2, coeffv_3 = np.split(coeffv, [self.Ntot_0form, 2 * self.Ntot_0form]) + coeffv_1, coeffv_2, coeffv_3 = xp.split(coeffv, [self.Ntot_0form, 2 * self.Ntot_0form]) coeffv_1 = coeffv_1.reshape(self.Nbase_0form) coeffv_2 = coeffv_2.reshape(self.Nbase_0form) @@ -1357,15 +1384,15 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): assert coeff.shape[:2] == (self.NbaseN[0], self.NbaseN[1]) # get real and imaginary part - coeff_r = np.real(coeff) - coeff_i = np.imag(coeff) + coeff_r = xp.real(coeff) + coeff_i = xp.imag(coeff) # ------ evaluate FEM field at given points -------- - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values_r_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.T[0], @@ -1395,8 +1422,8 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.T[0], @@ -1427,8 +1454,8 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): # matrix evaluation else: - values_r_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.T[0], @@ -1458,8 +1485,8 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.T[0], @@ -1490,15 +1517,15 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): # multiply with Fourier basis in third direction if self.n_tor == 0: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.ones(eta3.shape, dtype=float) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.ones(eta3.shape, dtype=float) else: if self.basis_tor == "r": - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.cos(2 * np.pi * self.n_tor * eta3) - out += (values_r_2 + 1j * values_i_2)[:, :, None] * np.sin(2 * np.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.cos(2 * xp.pi * self.n_tor * eta3) + out += (values_r_2 + 1j * values_i_2)[:, :, None] * xp.sin(2 * xp.pi * self.n_tor * eta3) else: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.exp(1j * 2 * np.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) # --------- evaluate FEM field at given point ------- else: @@ -1527,10 +1554,26 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): if self.n_tor != 0 and self.basis_tor == "r": real_2 = eva_2d.evaluate_n_n( - self.T[0], self.T[1], self.p[0], self.p[1], self.indN[0], self.indN[1], coeff_r[:, :, 1], eta1, eta2 + self.T[0], + self.T[1], + self.p[0], + self.p[1], + self.indN[0], + self.indN[1], + coeff_r[:, :, 1], + eta1, + eta2, ) imag_2 = eva_2d.evaluate_n_n( - self.T[0], self.T[1], self.p[0], self.p[1], self.indN[0], self.indN[1], coeff_i[:, :, 1], eta1, eta2 + self.T[0], + self.T[1], + self.p[0], + self.p[1], + self.indN[0], + self.indN[1], + coeff_i[:, :, 1], + eta1, + eta2, ) # multiply with Fourier basis in third direction if |n_tor| > 0 @@ -1539,17 +1582,17 @@ def evaluate_NN(self, eta1, eta2, eta3, coeff, which="V0", part="r"): else: if self.basis_tor == "r": - out = (real_1 + 1j * imag_1) * np.cos(2 * np.pi * self.n_tor * eta3) - out += (real_2 + 1j * imag_2) * np.sin(2 * np.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * xp.cos(2 * xp.pi * self.n_tor * eta3) + out += (real_2 + 1j * imag_2) * xp.sin(2 * xp.pi * self.n_tor * eta3) else: - out = (real_1 + 1j * imag_1) * np.exp(1j * 2 * np.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) # return real or imaginary part if part == "r": - out = np.real(out) + out = xp.real(out) else: - out = np.imag(out) + out = xp.imag(out) return out @@ -1598,15 +1641,15 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): assert coeff.shape[:2] == (self.NbaseD[0], self.NbaseN[1]) # get real and imaginary part - coeff_r = np.real(coeff) - coeff_i = np.imag(coeff) + coeff_r = xp.real(coeff) + coeff_i = xp.imag(coeff) # ------ evaluate FEM field at given points -------- - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values_r_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.t[0], @@ -1636,8 +1679,8 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.t[0], @@ -1668,8 +1711,8 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): # matrix evaluation else: - values_r_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.t[0], @@ -1699,8 +1742,8 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.t[0], @@ -1731,15 +1774,15 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): # multiply with Fourier basis in third direction if self.n_tor == 0: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.ones(eta3.shape, dtype=float) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.ones(eta3.shape, dtype=float) else: if self.basis_tor == "r": - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.cos(2 * np.pi * self.n_tor * eta3) - out += (values_r_2 + 1j * values_i_2)[:, :, None] * np.sin(2 * np.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.cos(2 * xp.pi * self.n_tor * eta3) + out += (values_r_2 + 1j * values_i_2)[:, :, None] * xp.sin(2 * xp.pi * self.n_tor * eta3) else: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.exp(1j * 2 * np.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) # --------- evaluate FEM field at given point ------- else: @@ -1796,17 +1839,17 @@ def evaluate_DN(self, eta1, eta2, eta3, coeff, which="V1", part="r"): else: if self.basis_tor == "r": - out = (real_1 + 1j * imag_1) * np.cos(2 * np.pi * self.n_tor * eta3) - out += (real_2 + 1j * imag_2) * np.sin(2 * np.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * xp.cos(2 * xp.pi * self.n_tor * eta3) + out += (real_2 + 1j * imag_2) * xp.sin(2 * xp.pi * self.n_tor * eta3) else: - out = (real_1 + 1j * imag_1) * np.exp(1j * 2 * np.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) # return real or imaginary part if part == "r": - out = np.real(out) + out = xp.real(out) else: - out = np.imag(out) + out = xp.imag(out) return out @@ -1855,15 +1898,15 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): assert coeff.shape[:2] == (self.NbaseN[0], self.NbaseD[1]) # get real and imaginary part - coeff_r = np.real(coeff) - coeff_i = np.imag(coeff) + coeff_r = xp.real(coeff) + coeff_i = xp.imag(coeff) # ------ evaluate FEM field at given points -------- - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values_r_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.T[0], @@ -1893,8 +1936,8 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.T[0], @@ -1925,8 +1968,8 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): # matrix evaluation else: - values_r_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.T[0], @@ -1956,8 +1999,8 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.T[0], @@ -1988,15 +2031,15 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): # multiply with Fourier basis in third direction if self.n_tor == 0: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.ones(eta3.shape, dtype=float) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.ones(eta3.shape, dtype=float) else: if self.basis_tor == "r": - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.cos(2 * np.pi * self.n_tor * eta3) - out += (values_r_2 + 1j * values_i_2)[:, :, None] * np.sin(2 * np.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.cos(2 * xp.pi * self.n_tor * eta3) + out += (values_r_2 + 1j * values_i_2)[:, :, None] * xp.sin(2 * xp.pi * self.n_tor * eta3) else: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.exp(1j * 2 * np.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) # --------- evaluate FEM field at given point ------- else: @@ -2053,17 +2096,17 @@ def evaluate_ND(self, eta1, eta2, eta3, coeff, which="V2", part="r"): else: if self.basis_tor == "r": - out = (real_1 + 1j * imag_1) * np.cos(2 * np.pi * self.n_tor * eta3) - out += (real_2 + 1j * imag_2) * np.sin(2 * np.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * xp.cos(2 * xp.pi * self.n_tor * eta3) + out += (real_2 + 1j * imag_2) * xp.sin(2 * xp.pi * self.n_tor * eta3) else: - out = (real_1 + 1j * imag_1) * np.exp(1j * 2 * np.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) # return real or imaginary part if part == "r": - out = np.real(out) + out = xp.real(out) else: - out = np.imag(out) + out = xp.imag(out) return out @@ -2115,15 +2158,15 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): assert coeff.shape[:2] == (self.NbaseD[0], self.NbaseD[1]) # get real and imaginary part - coeff_r = np.real(coeff) - coeff_i = np.imag(coeff) + coeff_r = xp.real(coeff) + coeff_i = xp.imag(coeff) # ------ evaluate FEM field at given points -------- - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values_r_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_1 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_1 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.t[0], @@ -2153,8 +2196,8 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) - values_i_2 = np.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_r_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) + values_i_2 = xp.empty((eta1.shape[0], eta2.shape[0]), dtype=float) eva_2d.evaluate_tensor_product_2d( self.t[0], @@ -2185,8 +2228,8 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): # matrix evaluation else: - values_r_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_1 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_1 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.t[0], @@ -2216,8 +2259,8 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): ) if self.n_tor != 0 and self.basis_tor == "r": - values_r_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) - values_i_2 = np.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_r_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) + values_i_2 = xp.empty((eta1.shape[0], eta2.shape[1]), dtype=float) eva_2d.evaluate_matrix_2d( self.t[0], @@ -2248,15 +2291,15 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): # multiply with Fourier basis in third direction if self.n_tor == 0: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.ones(eta3.shape, dtype=float) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.ones(eta3.shape, dtype=float) else: if self.basis_tor == "r": - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.cos(2 * np.pi * self.n_tor * eta3) - out += (values_r_2 + 1j * values_i_2)[:, :, None] * np.sin(2 * np.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.cos(2 * xp.pi * self.n_tor * eta3) + out += (values_r_2 + 1j * values_i_2)[:, :, None] * xp.sin(2 * xp.pi * self.n_tor * eta3) else: - out = (values_r_1 + 1j * values_i_1)[:, :, None] * np.exp(1j * 2 * np.pi * self.n_tor * eta3) + out = (values_r_1 + 1j * values_i_1)[:, :, None] * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) # --------- evaluate FEM field at given point ------- else: @@ -2313,17 +2356,17 @@ def evaluate_DD(self, eta1, eta2, eta3, coeff, which="V3", part="r"): else: if self.basis_tor == "r": - out = (real_1 + 1j * imag_1) * np.cos(2 * np.pi * self.n_tor * eta3) - out += (real_2 + 1j * imag_2) * np.sin(2 * np.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * xp.cos(2 * xp.pi * self.n_tor * eta3) + out += (real_2 + 1j * imag_2) * xp.sin(2 * xp.pi * self.n_tor * eta3) else: - out = (real_1 + 1j * imag_1) * np.exp(1j * 2 * np.pi * self.n_tor * eta3) + out = (real_1 + 1j * imag_1) * xp.exp(1j * 2 * xp.pi * self.n_tor * eta3) # return real or imaginary part if part == "r": - out = np.real(out) + out = xp.real(out) else: - out = np.imag(out) + out = xp.imag(out) return out @@ -2334,13 +2377,13 @@ def evaluate_NNN(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or np.ndarray + eta1 : double or xp.ndarray 1st component of logical evaluation point - eta2 : double or np.ndarray + eta2 : double or xp.ndarray 2nd component of logical evaluation point - eta3 : double or np.ndarray + eta3 : double or xp.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2355,10 +2398,10 @@ def evaluate_NNN(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_0(coeff) - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor-product evaluation if eta1.ndim == 1: - values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.T[0], self.T[1], @@ -2379,7 +2422,7 @@ def evaluate_NNN(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2449,13 +2492,13 @@ def evaluate_DNN(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or np.ndarray + eta1 : double or xp.ndarray 1st component of logical evaluation point - eta2 : double or np.ndarray + eta2 : double or xp.ndarray 2nd component of logical evaluation point - eta3 : double or np.ndarray + eta3 : double or xp.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2470,10 +2513,10 @@ def evaluate_DNN(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_1(coeff)[0] - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.t[0], self.T[1], @@ -2494,7 +2537,7 @@ def evaluate_DNN(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2563,13 +2606,13 @@ def evaluate_NDN(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or np.ndarray + eta1 : double or xp.ndarray 1st component of logical evaluation point - eta2 : double or np.ndarray + eta2 : double or xp.ndarray 2nd component of logical evaluation point - eta3 : double or np.ndarray + eta3 : double or xp.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2584,10 +2627,10 @@ def evaluate_NDN(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_1(coeff)[1] - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.T[0], self.t[1], @@ -2608,7 +2651,7 @@ def evaluate_NDN(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2677,13 +2720,13 @@ def evaluate_NND(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or np.ndarray + eta1 : double or xp.ndarray 1st component of logical evaluation point - eta2 : double or np.ndarray + eta2 : double or xp.ndarray 2nd component of logical evaluation point - eta3 : double or np.ndarray + eta3 : double or xp.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2698,10 +2741,10 @@ def evaluate_NND(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_1(coeff)[2] - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.T[0], self.T[1], @@ -2722,7 +2765,7 @@ def evaluate_NND(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2791,13 +2834,13 @@ def evaluate_NDD(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or np.ndarray + eta1 : double or xp.ndarray 1st component of logical evaluation point - eta2 : double or np.ndarray + eta2 : double or xp.ndarray 2nd component of logical evaluation point - eta3 : double or np.ndarray + eta3 : double or xp.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2812,10 +2855,10 @@ def evaluate_NDD(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_2(coeff)[0] - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.T[0], self.t[1], @@ -2836,7 +2879,7 @@ def evaluate_NDD(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -2905,13 +2948,13 @@ def evaluate_DND(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or np.ndarray + eta1 : double or xp.ndarray 1st component of logical evaluation point - eta2 : double or np.ndarray + eta2 : double or xp.ndarray 2nd component of logical evaluation point - eta3 : double or np.ndarray + eta3 : double or xp.ndarray 3rd component of logical evaluation point coeff : array_like @@ -2926,10 +2969,10 @@ def evaluate_DND(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_2(coeff)[1] - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.t[0], self.T[1], @@ -2950,7 +2993,7 @@ def evaluate_DND(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -3019,13 +3062,13 @@ def evaluate_DDN(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or np.ndarray + eta1 : double or xp.ndarray 1st component of logical evaluation point - eta2 : double or np.ndarray + eta2 : double or xp.ndarray 2nd component of logical evaluation point - eta3 : double or np.ndarray + eta3 : double or xp.ndarray 3rd component of logical evaluation point coeff : array_like @@ -3040,10 +3083,10 @@ def evaluate_DDN(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_2(coeff)[2] - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.t[0], self.t[1], @@ -3064,7 +3107,7 @@ def evaluate_DDN(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( @@ -3133,13 +3176,13 @@ def evaluate_DDD(self, eta1, eta2, eta3, coeff): Parameters ---------- - eta1 : double or np.ndarray + eta1 : double or xp.ndarray 1st component of logical evaluation point - eta2 : double or np.ndarray + eta2 : double or xp.ndarray 2nd component of logical evaluation point - eta3 : double or np.ndarray + eta3 : double or xp.ndarray 3rd component of logical evaluation point coeff : array_like @@ -3154,10 +3197,10 @@ def evaluate_DDD(self, eta1, eta2, eta3, coeff): if coeff.ndim == 1: coeff = self.extract_3(coeff) - if isinstance(eta1, np.ndarray): + if isinstance(eta1, xp.ndarray): # tensor product evaluation if eta1.ndim == 1: - values = np.empty((eta1.size, eta2.size, eta3.size), dtype=float) + values = xp.empty((eta1.size, eta2.size, eta3.size), dtype=float) eva_3d.evaluate_tensor_product( self.t[0], self.t[1], @@ -3178,7 +3221,7 @@ def evaluate_DDD(self, eta1, eta2, eta3, coeff): # matrix evaluation else: - values = np.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) + values = xp.empty((eta1.shape[0], eta2.shape[1], eta3.shape[2]), dtype=float) # `eta1` is a sparse meshgrid. if max(eta1.shape) == eta1.size: eva_3d.evaluate_sparse( diff --git a/src/struphy/examples/_draw_parallel.py b/src/struphy/examples/_draw_parallel.py index 040e55c65..b31ba19c4 100644 --- a/src/struphy/examples/_draw_parallel.py +++ b/src/struphy/examples/_draw_parallel.py @@ -1,5 +1,5 @@ -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.geometry import domains @@ -69,19 +69,19 @@ def main(): ) # are all markers in the correct domain? - conds = np.logical_and( + conds = xp.logical_and( particles.markers[:, :3] > derham.domain_array[rank, 0::3], particles.markers[:, :3] < derham.domain_array[rank, 1::3], ) holes = particles.markers[:, 0] == -1.0 - stay = np.all(conds, axis=1) + stay = xp.all(conds, axis=1) - error_mks = particles.markers[np.logical_and(~stay, ~holes)] + error_mks = particles.markers[xp.logical_and(~stay, ~holes)] print( - f"rank {rank} | markers not on correct process: {np.nonzero(np.logical_and(~stay, ~holes))} \ - \n corresponding positions:\n {error_mks[:, :3]}" + f"rank {rank} | markers not on correct process: {xp.nonzero(xp.logical_and(~stay, ~holes))} \ + \n corresponding positions:\n {error_mks[:, :3]}", ) assert error_mks.size == 0 diff --git a/src/struphy/examples/restelli2018/callables.py b/src/struphy/examples/restelli2018/callables.py index 89ce4bfda..505a60f90 100644 --- a/src/struphy/examples/restelli2018/callables.py +++ b/src/struphy/examples/restelli2018/callables.py @@ -1,6 +1,6 @@ "Analytical callables needed for the simulation of the Two-Fluid Quasi-Neutral Model by Restelli." -import numpy as np +import cunumpy as xp class RestelliForcingTerm: @@ -74,9 +74,9 @@ def __init__(self, nu=1.0, R0=2.0, a=1.0, B0=10.0, Bp=12.5, alpha=0.1, beta=1.0, self._eps_norm = eps def __call__(self, x, y, z): - R = np.sqrt(x**2 + y**2) - R = np.where(R == 0.0, 1e-9, R) - phi = np.arctan2(-y, x) + R = xp.sqrt(x**2 + y**2) + R = xp.where(R == 0.0, 1e-9, R) + phi = xp.arctan2(-y, x) force_Z = self._nu * ( self._alpha * (self._R0 - 4 * R) / (self._a * self._R0 * R) - self._beta * self._Bp * self._R0**2 / (self._B0 * self._a * R**3) @@ -197,31 +197,31 @@ def __init__( def __call__(self, x, y, z): A = self._alpha / (self._a * self._R0) C = self._beta * self._Bp * self._R0 / (self._B0 * self._a) - R = np.sqrt(x**2 + y**2) - R = np.where(R == 0.0, 1e-9, R) - phi = np.arctan2(-y, x) + R = xp.sqrt(x**2 + y**2) + R = xp.where(R == 0.0, 1e-9, R) + phi = xp.arctan2(-y, x) if self._species == "Ions": """Forceterm for ions on the right hand side.""" if self._dimension == "2D": fx = ( - -2.0 * np.pi * np.sin(2 * np.pi * x) - + np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y) * self._B0 / self._eps_norm - - self._nu * 8.0 * np.pi**2 * np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y) + -2.0 * xp.pi * xp.sin(2 * xp.pi * x) + + xp.cos(2 * xp.pi * x) * xp.cos(2 * xp.pi * y) * self._B0 / self._eps_norm + - self._nu * 8.0 * xp.pi**2 * xp.sin(2 * xp.pi * x) * xp.sin(2 * xp.pi * y) ) fy = ( - 2.0 * np.pi * np.cos(2 * np.pi * y) - - np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y) * self._B0 / self._eps_norm - - self._nu * 8.0 * np.pi**2 * np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y) + 2.0 * xp.pi * xp.cos(2 * xp.pi * y) + - xp.sin(2 * xp.pi * x) * xp.sin(2 * xp.pi * y) * self._B0 / self._eps_norm + - self._nu * 8.0 * xp.pi**2 * xp.cos(2 * xp.pi * x) * xp.cos(2 * xp.pi * y) ) fz = 0.0 * x elif self._dimension == "1D": fx = ( - 2.0 * np.pi * np.cos(2 * np.pi * x) - + self._nu * 4.0 * np.pi**2 * np.sin(2 * np.pi * x) - + (np.sin(2 * np.pi * x) + 1.0) / self._dt + 2.0 * xp.pi * xp.cos(2 * xp.pi * x) + + self._nu * 4.0 * xp.pi**2 * xp.sin(2 * xp.pi * x) + + (xp.sin(2 * xp.pi * x) + 1.0) / self._dt ) - fy = (np.sin(2 * np.pi * x) + 1.0) * self._B0 / self._eps_norm + fy = (xp.sin(2 * xp.pi * x) + 1.0) * self._B0 / self._eps_norm fz = 0.0 * x elif self._dimension == "Tokamak": @@ -234,8 +234,8 @@ def __call__(self, x, y, z): fZ = self._alpha * self._B0 * z / self._a + A * self._R0 / R * ((R - self._R0) * self._B0) fphi = A * self._R0 * self._Bp / (self._a * R**2) * ((R - self._R0) ** 2 + z**2) - fx = np.cos(phi) * fR - R * np.sin(phi) * fphi - fy = -np.sin(phi) * fR - R * np.cos(phi) * fphi + fx = xp.cos(phi) * fR - R * xp.sin(phi) * fphi + fy = -xp.sin(phi) * fR - R * xp.cos(phi) * fphi fz = fZ if self._comp == "0": @@ -251,26 +251,26 @@ def __call__(self, x, y, z): """Forceterm for electrons on the right hand side.""" if self._dimension == "2D": fx = ( - 2.0 * np.pi * np.sin(2 * np.pi * x) - - np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y) * self._B0 / self._eps_norm - - self._nu_e * 32.0 * np.pi**2 * np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y) - - self._stab_sigma * (-np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y)) + 2.0 * xp.pi * xp.sin(2 * xp.pi * x) + - xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y) * self._B0 / self._eps_norm + - self._nu_e * 32.0 * xp.pi**2 * xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y) + - self._stab_sigma * (-xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y)) ) fy = ( - -2.0 * np.pi * np.cos(2 * np.pi * y) - + np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y) * self._B0 / self._eps_norm - - self._nu_e * 32.0 * np.pi**2 * np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y) - - self._stab_sigma * (-np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y)) + -2.0 * xp.pi * xp.cos(2 * xp.pi * y) + + xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y) * self._B0 / self._eps_norm + - self._nu_e * 32.0 * xp.pi**2 * xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y) + - self._stab_sigma * (-xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y)) ) fz = 0.0 * x elif self._dimension == "1D": fx = ( - -2.0 * np.pi * np.cos(2 * np.pi * x) - + self._nu_e * 4.0 * np.pi**2 * np.sin(2 * np.pi * x) - - self._stab_sigma * np.sin(2 * np.pi * x) + -2.0 * xp.pi * xp.cos(2 * xp.pi * x) + + self._nu_e * 4.0 * xp.pi**2 * xp.sin(2 * xp.pi * x) + - self._stab_sigma * xp.sin(2 * xp.pi * x) ) - fy = -np.sin(2 * np.pi * x) * self._B0 / self._eps_norm + fy = -xp.sin(2 * xp.pi * x) * self._B0 / self._eps_norm fz = 0.0 * x elif self._dimension == "Tokamak": @@ -283,8 +283,8 @@ def __call__(self, x, y, z): fZ = -self._alpha * self._B0 * z / self._a - A * self._R0 / R * ((R - self._R0) * self._B0) fphi = -A * self._R0 * self._Bp / (self._a * R**2) * ((R - self._R0) ** 2 + z**2) - fx = np.cos(phi) * fR - R * np.sin(phi) * fphi - fy = -np.sin(phi) * fR - R * np.cos(phi) * fphi + fx = xp.cos(phi) * fR - R * xp.sin(phi) * fphi + fy = -xp.sin(phi) * fR - R * xp.cos(phi) * fphi fz = fZ if self._comp == "0": diff --git a/src/struphy/feec/basis_projection_ops.py b/src/struphy/feec/basis_projection_ops.py index b335edea5..ab0925828 100644 --- a/src/struphy/feec/basis_projection_ops.py +++ b/src/struphy/feec/basis_projection_ops.py @@ -1,6 +1,6 @@ -import numpy as np -from mpi4py import MPI +import cunumpy as xp from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL +from psydac.ddm.mpi import mpi as MPI from psydac.fem.basic import FemSpace from psydac.fem.tensor import TensorFemSpace from psydac.linalg.basic import IdentityOperator, LinearOperator, Vector @@ -15,6 +15,7 @@ from struphy.feec.utilities import RotationMatrix from struphy.polar.basic import PolarDerhamSpace, PolarVector from struphy.polar.linear_operators import PolarExtractionOperator +from struphy.utils.pyccel import Pyccelkernel class BasisProjectionOperators: @@ -50,7 +51,7 @@ def __init__(self, derham, domain, verbose=True, **weights): self._rank = derham.comm.Get_rank() if derham.comm is not None else 0 - if np.any([p == 1 and Nel > 1 for p, Nel in zip(derham.p, derham.Nel)]): + if xp.any([p == 1 and Nel > 1 for p, Nel in zip(derham.p, derham.Nel)]): if MPI.COMM_WORLD.Get_rank() == 0: print( f'\nWARNING: Class "BasisProjectionOperators" called with p={derham.p} (interpolation of piece-wise constants should be avoided).', @@ -146,7 +147,7 @@ def K3(self): e3, ) / self.sqrt_g(e1, e2, e3), - ] + ], ] self._K3 = self.create_basis_op( fun, @@ -262,7 +263,7 @@ def Q3(self): e3, ) / self.sqrt_g(e1, e2, e3), - ] + ], ] self._Q3 = self.create_basis_op( fun, @@ -1050,11 +1051,11 @@ def __init__( if isinstance(V, TensorFemSpace): self._Vspaces = [V.coeff_space] self._V1ds = [V.spaces] - self._VNbasis = np.array([self._V1ds[0][0].nbasis, self._V1ds[0][1].nbasis, self._V1ds[0][2].nbasis]) + self._VNbasis = xp.array([self._V1ds[0][0].nbasis, self._V1ds[0][1].nbasis, self._V1ds[0][2].nbasis]) else: self._Vspaces = V.coeff_space self._V1ds = [comp.spaces for comp in V.spaces] - self._VNbasis = np.array( + self._VNbasis = xp.array( [ [self._V1ds[0][0].nbasis, self._V1ds[0][1].nbasis, self._V1ds[0][2].nbasis], [ @@ -1063,7 +1064,7 @@ def __init__( self._V1ds[1][2].nbasis, ], [self._V1ds[2][0].nbasis, self._V1ds[2][1].nbasis, self._V1ds[2][2].nbasis], - ] + ], ) # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component @@ -1282,7 +1283,7 @@ def assemble(self, verbose=False): self._pds, self._periodic, self._p, - np.array([col0, col1, col2]), + xp.array([col0, col1, col2]), self._VNbasis, self._mat._data, coeff, @@ -1357,12 +1358,12 @@ def assemble(self, verbose=False): self._pds, self._periodic, self._p, - np.array( + xp.array( [ col0, col1, col2, - ] + ], ), self._VNbasis[hh], Aux._data, @@ -1437,12 +1438,12 @@ def assemble(self, verbose=False): self._pds[h], self._periodic, self._p, - np.array( + xp.array( [ col0, col1, col2, - ] + ], ), self._VNbasis, Aux[h]._data, @@ -1538,12 +1539,12 @@ def assemble(self, verbose=False): self._pds[h], self._periodic, self._p, - np.array( + xp.array( [ col0, col1, col2, - ] + ], ), self._VNbasis[hh], Aux[h]._data, @@ -1612,7 +1613,7 @@ class BasisProjectionOperator(LinOpWithTransp): Finite element spline space (domain, input space). weights : list - Weight function(s) (callables or np.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. + Weight function(s) (callables or xp.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. V_extraction_op : PolarExtractionOperator | IdentityOperator Extraction operator to polar sub-space of V. @@ -1888,7 +1889,7 @@ def update_weights(self, weights): Parameters ---------- weights : list - Weight function(s) (callables or np.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. + Weight function(s) (callables or xp.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. """ self._weights = weights @@ -1944,13 +1945,13 @@ def assemble(self, weights=None, verbose=False): # input vector space (domain), column of block for j, (Vspace, V1d, loc_weight) in enumerate(zip(_Vspaces, _V1ds, weight_line)): - _starts_in = np.array(Vspace.starts) - _ends_in = np.array(Vspace.ends) - _pads_in = np.array(Vspace.pads) + _starts_in = xp.array(Vspace.starts) + _ends_in = xp.array(Vspace.ends) + _pads_in = xp.array(Vspace.pads) - _starts_out = np.array(Wspace.starts) - _ends_out = np.array(Wspace.ends) - _pads_out = np.array(Wspace.pads) + _starts_out = xp.array(Wspace.starts) + _ends_out = xp.array(Wspace.ends) + _pads_out = xp.array(Wspace.pads) # use cached information if asked if self._use_cache: @@ -1997,21 +1998,21 @@ def assemble(self, weights=None, verbose=False): # Evaluate weight function at quadrature points # evaluate weight at quadrature points if callable(loc_weight): - PTS = np.meshgrid(*_ptsG, indexing="ij") + PTS = xp.meshgrid(*_ptsG, indexing="ij") mat_w = loc_weight(*PTS).copy() - elif isinstance(loc_weight, np.ndarray): + elif isinstance(loc_weight, xp.ndarray): assert loc_weight.shape == (len(_ptsG[0]), len(_ptsG[1]), len(_ptsG[2])) mat_w = loc_weight elif loc_weight is not None: raise TypeError( - "weights must be np.ndarray, callable or None", + "weights must be xp.ndarray, callable or None", ) # Call the kernel if weight function is not zero or in the scalar case # to avoid calling _block of a StencilMatrix in the else - not_weight_zero = np.array( - int(loc_weight is not None and np.any(np.abs(mat_w) > 1e-14)), + not_weight_zero = xp.array( + int(loc_weight is not None and xp.any(xp.abs(mat_w) > 1e-14)), ) if self._mpi_comm is not None: @@ -2038,9 +2039,11 @@ def assemble(self, weights=None, verbose=False): ) dofs_mat = self._dof_mat[i, j] - kernel = getattr( - basis_projection_kernels, - "assemble_dofs_for_weighted_basisfuns_" + str(V.ldim) + "d", + kernel = Pyccelkernel( + getattr( + basis_projection_kernels, + "assemble_dofs_for_weighted_basisfuns_" + str(V.ldim) + "d", + ), ) if rank == 0 and verbose: @@ -2382,7 +2385,7 @@ def find_relative_col(col, row, Nbasis, periodic): The relative column position of col with respect to the the current row of the StencilMatrix. """ - if periodic == False: + if not periodic: relativecol = col - row # In the periodic case we must account for the possible looping of the basis functions when computing the relative row postion else: diff --git a/src/struphy/feec/linear_operators.py b/src/struphy/feec/linear_operators.py index 0fbcee763..28b4a0805 100644 --- a/src/struphy/feec/linear_operators.py +++ b/src/struphy/feec/linear_operators.py @@ -1,8 +1,9 @@ import itertools from abc import abstractmethod -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import MockComm +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.basic import LinearOperator, Vector, VectorSpace from psydac.linalg.block import BlockVectorSpace from psydac.linalg.stencil import StencilVectorSpace @@ -52,26 +53,27 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): if isinstance(self.domain, BlockVectorSpace): comm = self.domain.spaces[0].cart.comm - if comm is None: - comm = MPI.COMM_SELF elif isinstance(self.domain, StencilVectorSpace): comm = self.domain.cart.comm - if comm is None: - comm = MPI.COMM_SELF - rank = comm.Get_rank() - size = comm.Get_size() - if is_sparse == False: + if comm is None: + rank = 0 + size = 1 + else: + rank = comm.Get_rank() + size = comm.Get_size() + + if not is_sparse: if out is None: # We declare the matrix form of our linear operator - out = np.zeros([self.codomain.dimension, self.domain.dimension], dtype=self.dtype) + out = xp.zeros([self.codomain.dimension, self.domain.dimension], dtype=self.dtype) else: - assert isinstance(out, np.ndarray) + assert isinstance(out, xp.ndarray) assert out.shape[0] == self.codomain.dimension assert out.shape[1] == self.domain.dimension # We use this matrix to store the partial results that we shall combine into the final matrix with a reduction at the end - result = np.zeros((self.codomain.dimension, self.domain.dimension), dtype=self.dtype) + result = xp.zeros((self.codomain.dimension, self.domain.dimension), dtype=self.dtype) else: if out is not None: raise Exception("If is_sparse is True then out must be set to None.") @@ -95,23 +97,29 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): ndim = [sp.ndim for sp in self.domain.spaces] # First each rank is going to need to know the starts and ends of all other ranks - startsarr = np.array([starts[i][j] for i in range(nsp) for j in range(ndim[i])], dtype=int) + startsarr = xp.array([starts[i][j] for i in range(nsp) for j in range(ndim[i])], dtype=int) # Create an array to store gathered data from all ranks - allstarts = np.empty(size * len(startsarr), dtype=int) + allstarts = xp.empty(size * len(startsarr), dtype=int) # Use Allgather to gather 'starts' from all ranks into 'allstarts' - comm.Allgather(startsarr, allstarts) + if comm is None or isinstance(comm, MockComm): + allstarts = startsarr + else: + comm.Allgather(startsarr, allstarts) # Reshape 'allstarts' to have 9 columns and 'size' rows allstarts = allstarts.reshape((size, len(startsarr))) - endsarr = np.array([ends[i][j] for i in range(nsp) for j in range(ndim[i])], dtype=int) + endsarr = xp.array([ends[i][j] for i in range(nsp) for j in range(ndim[i])], dtype=int) # Create an array to store gathered data from all ranks - allends = np.empty(size * len(endsarr), dtype=int) + allends = xp.empty(size * len(endsarr), dtype=int) # Use Allgather to gather 'ends' from all ranks into 'allends' - comm.Allgather(endsarr, allends) + if comm is None or isinstance(comm, MockComm): + allends = endsarr + else: + comm.Allgather(endsarr, allends) # Reshape 'allends' to have 9 columns and 'size' rows allends = allends.reshape((size, len(endsarr))) @@ -128,7 +136,7 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): itterables = [] for i in range(ndim[h]): itterables.append( - range(allstarts[currentrank][i + npredim], allends[currentrank][i + npredim] + 1) + range(allstarts[currentrank][i + npredim], allends[currentrank][i + npredim] + 1), ) # We iterate over all the entries that belong to rank number currentrank for i in itertools.product(*itterables): @@ -140,13 +148,13 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): self.dot(v, out=tmp2) # Compute to which column this iteration belongs col = spoint - col += np.ravel_multi_index(i, npts[h]) - if is_sparse == False: + col += xp.ravel_multi_index(i, npts[h]) + if not is_sparse: result[:, col] = tmp2.toarray() else: aux = tmp2.toarray() # We now need to now which entries on tmp2 are non-zero and store then in our data list - for l in np.where(aux != 0)[0]: + for l in xp.where(aux != 0)[0]: data.append(aux[l]) colarr.append(col) row.append(l) @@ -171,22 +179,28 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): ndim = self.domain.ndim # First each rank is going to need to know the starts and ends of all other ranks - startsarr = np.array([starts[j] for j in range(ndim)], dtype=int) + startsarr = xp.array([starts[j] for j in range(ndim)], dtype=int) # Create an array to store gathered data from all ranks - allstarts = np.empty(size * len(startsarr), dtype=int) + allstarts = xp.empty(size * len(startsarr), dtype=int) # Use Allgather to gather 'starts' from all ranks into 'allstarts' - comm.Allgather(startsarr, allstarts) + if comm is None or isinstance(comm, MockComm): + allstarts = startsarr + else: + comm.Allgather(startsarr, allstarts) # Reshape 'allstarts' to have 3 columns and 'size' rows allstarts = allstarts.reshape((size, len(startsarr))) - endsarr = np.array([ends[j] for j in range(ndim)], dtype=int) + endsarr = xp.array([ends[j] for j in range(ndim)], dtype=int) # Create an array to store gathered data from all ranks - allends = np.empty(size * len(endsarr), dtype=int) + allends = xp.empty(size * len(endsarr), dtype=int) # Use Allgather to gather 'ends' from all ranks into 'allends' - comm.Allgather(endsarr, allends) + if comm is None or isinstance(comm, MockComm): + allends = endsarr + else: + comm.Allgather(endsarr, allends) # Reshape 'allends' to have 3 columns and 'size' rows allends = allends.reshape((size, len(endsarr))) @@ -205,13 +219,13 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): # Compute dot product with the linear operator. self.dot(v, out=tmp2) # Compute to which column this iteration belongs - col = np.ravel_multi_index(i, npts) - if is_sparse == False: + col = xp.ravel_multi_index(i, npts) + if not is_sparse: result[:, col] = tmp2.toarray() else: aux = tmp2.toarray() # We now need to now which entries on tmp2 are non-zero and store then in our data list - for l in np.where(aux != 0)[0]: + for l in xp.where(aux != 0)[0]: data.append(aux[l]) colarr.append(col) row.append(l) @@ -223,17 +237,22 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): # I cannot conceive any situation where this error should be thrown, but I put it here just in case something unexpected happens. raise Exception("Function toarray_struphy() only supports Stencil Vectors or Block Vectors.") - if is_sparse == False: + if not is_sparse: # Use Allreduce to perform addition reduction and give one copy of the result to all ranks. - comm.Allreduce(result, out, op=MPI.SUM) + if comm is None or isinstance(comm, MockComm): + out[:] = result + else: + comm.Allreduce(result, out, op=MPI.SUM) return out else: - # Gather all rows on rank 0 - gathered_rows = comm.gather(row, root=0) - # Gather all colarr on rank 0 - gathered_cols = comm.gather(colarr, root=0) - # Gather all data on rank 0 - gathered_data = comm.gather(data, root=0) + if comm is None or isinstance(comm, MockComm): + gathered_rows = [row] + gathered_cols = [colarr] + gathered_data = [data] + else: + gathered_rows = comm.gather(row, root=0) + gathered_cols = comm.gather(colarr, root=0) + gathered_data = comm.gather(data, root=0) if rank == 0: # Rank 0 collects all rows from other ranks @@ -243,12 +262,13 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): # Rank 0 collects all data from other ranks all_data = [item for sublist in gathered_data for item in sublist] - # Broadcast 'all_rows' to all other ranks - comm.bcast(all_rows, root=0) - # Broadcast 'all_cols' to all other ranks - comm.bcast(all_cols, root=0) - # Broadcast 'all_data' to all other ranks - comm.bcast(all_data, root=0) + if comm is not None: + # Broadcast 'all_rows' to all other ranks + comm.bcast(all_rows, root=0) + # Broadcast 'all_cols' to all other ranks + comm.bcast(all_cols, root=0) + # Broadcast 'all_data' to all other ranks + comm.bcast(all_data, root=0) else: # Other ranks receive the 'all_rows' list through broadcast all_rows = comm.bcast(None, root=0) @@ -273,7 +293,7 @@ def toarray_struphy(self, out=None, is_sparse=False, format="csr"): return sparse.csr_matrix((all_data, (all_rows, all_cols)), shape=(numrows, numcols)).todia() else: raise Exception( - "The selected sparse matrix format must be one of the following : csr, csc, bsr, lil, dok, coo or dia." + "The selected sparse matrix format must be one of the following : csr, csc, bsr, lil, dok, coo or dia.", ) diff --git a/src/struphy/feec/local_projectors_kernels.py b/src/struphy/feec/local_projectors_kernels.py index 09b4c182f..706b3f78e 100644 --- a/src/struphy/feec/local_projectors_kernels.py +++ b/src/struphy/feec/local_projectors_kernels.py @@ -63,7 +63,7 @@ def get_local_problem_size(periodic: "bool[:]", p: "int[:]", IoH: "bool[:]"): for h in range(3): # Interpolation - if IoH[h] == False: + if not IoH[h]: lenj[h] = 2 * p[h] - 1 # Histopolation else: @@ -168,7 +168,10 @@ def get_dofs_local_1_form_ec_component_weighted( @stack_array("shp") def get_dofs_local_1_form_ec_component( - args_solve: LocalProjectorsArguments, f3: "float[:,:,:]", f_eval_aux: "float[:,:,:]", c: int + args_solve: LocalProjectorsArguments, + f3: "float[:,:,:]", + f_eval_aux: "float[:,:,:]", + c: int, ): """Kernel for evaluating the degrees of freedom for the c-th component of 1-forms. This function is for local commuting projetors. @@ -220,7 +223,10 @@ def get_dofs_local_1_form_ec_component( @stack_array("shp") def get_dofs_local_2_form_ec_component( - args_solve: LocalProjectorsArguments, fc: "float[:,:,:]", f_eval_aux: "float[:,:,:]", c: int + args_solve: LocalProjectorsArguments, + fc: "float[:,:,:]", + f_eval_aux: "float[:,:,:]", + c: int, ): """Kernel for evaluating the degrees of freedom for the c-th component of 2-forms. This function is for local commuting projetors. @@ -728,7 +734,7 @@ def solve_local_main_loop_weighted( if counteri0 >= rows0[i00] and counteri0 <= rowe0[i00]: compute0 = True break - if compute0 == True: + if compute0: counteri1 = 0 for i1 in range(args_solve.starts[1], args_solve.ends[1] + 1): # This bool variable tell us if this row has a non-zero FE coefficient, based on the current basis function we are using on our projection @@ -738,7 +744,7 @@ def solve_local_main_loop_weighted( if counteri1 >= rows1[i11] and counteri1 <= rowe1[i11]: compute1 = True break - if compute1 == True: + if compute1: counteri2 = 0 for i2 in range(args_solve.starts[2], args_solve.ends[2] + 1): # This bool variable tell us if this row has a non-zero FE coefficient, based on the current basis function we are using on our projection @@ -748,7 +754,7 @@ def solve_local_main_loop_weighted( if counteri2 >= rows2[i22] and counteri2 <= rowe2[i22]: compute2 = True break - if compute2 == True: + if compute2: L123 = 0.0 startj1, endj1 = select_quasi_points( i0, @@ -844,7 +850,7 @@ def find_relative_col(col: int, row: int, Nbasis: int, periodic: bool): The relative column position of col with respect to the the current row of the StencilMatrix. """ - if periodic == False: + if not periodic: relativecol = col - row # In the periodic case we must account for the possible looping of the basis functions when computing the relative row postion else: @@ -938,7 +944,7 @@ def assemble_basis_projection_operator_local( compute0 = True break relativecol0 = find_relative_col(col[0], row0, VNbasis[0], periodic[0]) - if relativecol0 >= -p[0] and relativecol0 <= p[0] and compute0 == True: + if relativecol0 >= -p[0] and relativecol0 <= p[0] and compute0: count1 = 0 for row1 in range(starts[1], ends[1] + 1): # This bool variable tell us if this row has a non-zero FE coefficient, based on the current basis function we are using on our projection @@ -949,7 +955,7 @@ def assemble_basis_projection_operator_local( compute1 = True break relativecol1 = find_relative_col(col[1], row1, VNbasis[1], periodic[1]) - if relativecol1 >= -p[1] and relativecol1 <= p[1] and compute1 == True: + if relativecol1 >= -p[1] and relativecol1 <= p[1] and compute1: count2 = 0 for row2 in range(starts[2], ends[2] + 1): # This bool variable tell us if this row has a non-zero FE coefficient, based on the current basis function we are using on our projection @@ -960,7 +966,7 @@ def assemble_basis_projection_operator_local( compute2 = True break relativecol2 = find_relative_col(col[2], row2, VNbasis[2], periodic[2]) - if relativecol2 >= -p[2] and relativecol2 <= p[2] and compute2 == True: + if relativecol2 >= -p[2] and relativecol2 <= p[2] and compute2: mat[ count0 + pds[0], count1 + pds[1], @@ -996,7 +1002,7 @@ def are_quadrature_points_zero(aux: "int[:]", p: int, basis: "float[:]"): if basis[in_start + ii] != 0.0: all_zero = False break - if all_zero == True: + if all_zero: aux[i] = 0 @@ -1037,7 +1043,15 @@ def get_rows_periodic(starts: int, ends: int, modl: int, modr: int, Nbasis: int, def get_rows( - col: int, starts: int, ends: int, p: int, Nbasis: int, periodic: bool, IoH: bool, BoD: bool, aux: "int[:]" + col: int, + starts: int, + ends: int, + p: int, + Nbasis: int, + periodic: bool, + IoH: bool, + BoD: bool, + aux: "int[:]", ): """Kernel for getting the list of rows that are non-zero for the current BasisProjectionLocal column, within the start and end indices of the current MPI rank. @@ -1071,33 +1085,33 @@ def get_rows( Array where we put a one if the current row could have a non-zero FE coefficient for the column given by col. """ # Periodic boundary conditions - if periodic == True: + if periodic: # Histopolation - if IoH == True: + if IoH: # D-splines - if BoD == True: + if BoD: get_rows_periodic(starts, ends, -p + 1, p, Nbasis, col, aux) # B-splines - if BoD == False: + if not BoD: get_rows_periodic(starts, ends, -p + 1, p + 1, Nbasis, col, aux) # Interpolation - if IoH == False: + if not IoH: # D-splines - if BoD == True: + if BoD: # Special case p = 1 if p == 1: get_rows_periodic(starts, ends, -1, 1, Nbasis, col, aux) if p != 1: get_rows_periodic(starts, ends, -p + 1, p - 1, Nbasis, col, aux) # B-splines - if BoD == False: + if not BoD: get_rows_periodic(starts, ends, -p + 1, p, Nbasis, col, aux) # Clamped boundary conditions - if periodic == False: + if not periodic: # Histopolation - if IoH == True: + if IoH: # D-splines - if BoD == True: + if BoD: count = 0 for row in range(starts, ends + 1): if row >= 0 and row <= (p - 2) and col >= 0 and col <= row + p - 1: @@ -1110,7 +1124,7 @@ def get_rows( aux[count] = 1 count += 1 # B-splines - if BoD == False: + if not BoD: count = 0 for row in range(starts, ends + 1): if row >= 0 and row <= (p - 2) and col >= 0 and col <= (row + p): @@ -1121,9 +1135,9 @@ def get_rows( aux[count] = 1 count += 1 # Interpolation - if IoH == False: + if not IoH: # D-splines - if BoD == True: + if BoD: count = 0 for row in range(starts, ends + 1): if row == 0 and col <= (p - 1): @@ -1138,7 +1152,7 @@ def get_rows( aux[count] = 1 count += 1 # B-splines - if BoD == False: + if not BoD: count = 0 for row in range(starts, ends + 1): if row == 0 and col <= p: diff --git a/src/struphy/feec/mass.py b/src/struphy/feec/mass.py index 9d5531e98..5964f5f7c 100644 --- a/src/struphy/feec/mass.py +++ b/src/struphy/feec/mass.py @@ -1,12 +1,14 @@ import inspect +from copy import deepcopy -import numpy as np -from mpi4py import MPI +import cunumpy as xp from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL +from psydac.ddm.mpi import mpi as MPI from psydac.fem.tensor import TensorFemSpace from psydac.fem.vector import VectorFemSpace from psydac.linalg.basic import IdentityOperator, LinearOperator, Vector from psydac.linalg.block import BlockLinearOperator, BlockVector +from psydac.linalg.solvers import inverse from psydac.linalg.stencil import StencilDiagonalMatrix, StencilMatrix, StencilVector from struphy.feec import mass_kernels @@ -15,6 +17,7 @@ from struphy.feec.utilities import RotationMatrix from struphy.geometry.base import Domain from struphy.polar.linear_operators import PolarExtractionOperator +from struphy.utils.pyccel import Pyccelkernel class WeightedMassOperators: @@ -446,7 +449,7 @@ def M2B_div0(self): self.weights[self.selected_weight].a1_1, self.weights[self.selected_weight].a1_2, self.weights[self.selected_weight].a1_3, - ] + ], ) tmp_b2 = self.derham.curl.dot(a_eq) @@ -552,7 +555,7 @@ def M2Bn(self): self.weights[self.selected_weight].a1_1, self.weights[self.selected_weight].a1_2, self.weights[self.selected_weight].a1_3, - ] + ], ) tmp_b2 = self.derham.curl.dot(a_eq) @@ -728,6 +731,12 @@ def M1gyro(self): return self._M1gyro + @property + def WMM(self): + if not hasattr(self, "_WMM"): + self._WMM = self.H1vecMassMatrix_density(self.derham, self, self.domain) + return self._WMM + ####################################### # Wrapper around WeightedMassOperator # ####################################### @@ -768,7 +777,7 @@ def create_weighted_mass( 1. ``str`` : for square block matrices (V=W), a symmetry can be set in order to accelerate the assembly process. Possible strings are ``symm`` (symmetric), ``asym`` (anti-symmetric) and ``diag`` (diagonal). 2. ``None`` : all blocks are allocated, disregarding zero-blocks or any symmetry. 3. ``1D list`` : 1d list consisting of either a) strings or b) matrices (3x3 callables or 3x3 list) and can be mixed. Predefined names are ``G``, ``Ginv``, ``DFinv``, ``sqrt_g``. Access them using strings in the 1d list: ``weights=['']``. Possible choices for key-value pairs in **weights** are, at the moment: ``eq_mhd``: :class:`~struphy.fields_background.base.MHDequilibrium`. To access them, use for ```` the string ``eq_``, where ```` can be found in the just mentioned base classes for MHD equilibria. By default, all scalars are multiplied. For division of scalars use ``1/``. - 4. ``2D list`` : 2d list with the same number of rows/columns as the number of components of the domain/codomain spaces. The entries can be either a) callables or b) np.ndarrays representing the weights at the quadrature points. If an entry is zero or ``None``, the corresponding block is set to ``None`` to accelerate the dot product. + 4. ``2D list`` : 2d list with the same number of rows/columns as the number of components of the domain/codomain spaces. The entries can be either a) callables or b) xp.ndarrays representing the weights at the quadrature points. If an entry is zero or ``None``, the corresponding block is set to ``None`` to accelerate the dot product. assemble: bool Whether to assemble the weighted mass matrix, i.e. computes the integrals with @@ -896,7 +905,7 @@ def DFinvT(e1, e2, e3): if weights_rank2: # if matrix exits fun = [] - if listinput == True and len(weights_rank2) == 1: + if listinput and len(weights_rank2) == 1: for m in range(3): fun += [[]] for n in range(3): @@ -909,7 +918,11 @@ def DFinvT(e1, e2, e3): for n in range(3): fun[-1] += [ lambda e1, e2, e3, m=m, n=n: self._matrix_operate(e1, e2, e3, *weights_rank2)[ - :, :, :, m, n + :, + :, + :, + m, + n, ], ] # Scalar operations second @@ -936,14 +949,14 @@ def DFinvT(e1, e2, e3): fun = [ [ lambda e1, e2, e3: 1.0 / weights_rank0[0](e1, e2, e3), - ] + ], ] for f2, op in zip(weights_rank0[1:], operations[1:]): fun = [ [ lambda e1, e2, e3, f=fun[0][0], op=op, f2=f2: self._operate(f, f2, op, e1, e2, e3), - ] + ], ] V_id = self.derham.space_to_form[V_id] @@ -977,11 +990,11 @@ def _get_range_rank(self, func): else: dummy_eta = (0.0, 0.0, 0.0) val = func(*dummy_eta) - assert isinstance(val, np.ndarray) + assert isinstance(val, xp.ndarray) out = len(val.shape) - 3 else: if isinstance(func, list): - if isinstance(func[0], np.ndarray): + if isinstance(func[0], xp.ndarray): out = 2 else: out = len(func) - 1 @@ -1024,6 +1037,92 @@ def _operate(self, f1, f2, op, e1, e2, e3): return out + ####################################### + # Aux classes (to be removed in TODO) # + ####################################### + class H1vecMassMatrix_density: + """Wrapper around a Weighted mass operator from H1vec to H1vec whose weights are given by a 3 form""" + + def __init__(self, derham, mass_ops, domain): + self._massop = mass_ops.create_weighted_mass("H1vec", "H1vec") + self.field = derham.create_spline_function("field", "L2") + + integration_grid = [grid_1d.flatten() for grid_1d in derham.quad_grid_pts["0"]] + + self.integration_grid_spans, self.integration_grid_bn, self.integration_grid_bd = ( + derham.prepare_eval_tp_fixed( + integration_grid, + ) + ) + + grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) + self._f_values = xp.zeros(grid_shape, dtype=float) + + metric = domain.metric(*integration_grid) + self._mass_metric_term = deepcopy(metric) + self._full_term_mass = deepcopy(metric) + + @property + def massop( + self, + ): + """The WeightedMassOperator""" + return self._massop + + @property + def inv( + self, + ): + """The inverse WeightedMassOperator""" + if not hasattr(self, "_inv"): + self._create_inv() + return self._inv + + def update_weight(self, coeffs): + """Update the weighted mass matrix operator""" + + self.field.vector = coeffs + f_values = self.field.eval_tp_fixed_loc( + self.integration_grid_spans, + self.integration_grid_bd, + out=self._f_values, + ) + for i in range(3): + for j in range(3): + self._full_term_mass[i, j] = f_values * self._mass_metric_term[i, j] + + self._massop.assemble( + [ + [self._full_term_mass[0, 0], self._full_term_mass[0, 1], self._full_term_mass[0, 2]], + [ + self._full_term_mass[1, 0], + self._full_term_mass[ + 1, + 1, + ], + self._full_term_mass[1, 2], + ], + [self._full_term_mass[2, 0], self._full_term_mass[2, 1], self._full_term_mass[2, 2]], + ], + verbose=False, + ) + + if hasattr(self, "_inv") and self.inv._options["pc"] is not None: + self.inv._options["pc"].update_mass_operator(self.massop) + + def _create_inv(self, type="pcg", tol=1e-16, maxiter=500, verbose=False): + """Inverse the weighted mass matrix, preconditioner must be set outside + via self._inv._options['pc'] = ...""" + self._inv = inverse( + self.massop, + type, + pc=None, + tol=tol, + maxiter=maxiter, + verbose=verbose, + recycle=True, + ) + class WeightedMassOperatorsOldForTesting: r""" @@ -1531,7 +1630,7 @@ def M2B_div0(self): self.weights[self.selected_weight].a1_1, self.weights[self.selected_weight].a1_2, self.weights[self.selected_weight].a1_3, - ] + ], ) tmp_b2 = self.derham.curl.dot(a_eq) @@ -1645,7 +1744,7 @@ def M2Bn(self): self.weights[self.selected_weight].a1_1, self.weights[self.selected_weight].a1_2, self.weights[self.selected_weight].a1_3, - ] + ], ) tmp_b2 = self.derham.curl.dot(a_eq) @@ -1766,7 +1865,11 @@ def M1perp(self): for n in range(3): fun[-1] += [ lambda e1, e2, e3, m=m, n=n: (self.DFinv(e1, e2, e3) @ self.D @ self.DFinv(e1, e2, e3))[ - :, :, :, m, n + :, + :, + :, + m, + n, ] * self.sqrt_g( e1, @@ -1803,7 +1906,7 @@ def M0ad(self): e3, ) * self.sqrt_g(e1, e2, e3), - ] + ], ] self._M0ad = self._assemble_weighted_mass( @@ -1988,7 +2091,7 @@ class WeightedMassOperator(LinOpWithTransp): 1. ``None`` : all blocks are allocated, disregarding zero-blocks or any symmetry. 2. ``str`` : for square block matrices (V=W), a symmetry can be set in order to accelerate the assembly process. Possible strings are ``symm`` (symmetric), ``asym`` (anti-symmetric) and ``diag`` (diagonal). - 3. ``list`` : 2d list with the same number of rows/columns as the number of components of the domain/codomain spaces. The entries can be either a) callables or b) np.ndarrays representing the weights at the quadrature points. If an entry is zero or ``None``, the corresponding block is set to ``None`` to accelerate the dot product. + 3. ``list`` : 2d list with the same number of rows/columns as the number of components of the domain/codomain spaces. The entries can be either a) callables or b) xp.ndarrays representing the weights at the quadrature points. If an entry is zero or ``None``, the corresponding block is set to ``None`` to accelerate the dot product. transposed : bool Whether to assemble the transposed operator. @@ -2264,21 +2367,22 @@ def __init__( pts = [ quad_grid[nquad].points.flatten() for quad_grid, nquad in zip( - self.derham.get_quad_grids(wspace, nquads=self.nquads), self.nquads + self.derham.get_quad_grids(wspace, nquads=self.nquads), + self.nquads, ) ] if callable(weights_info[a][b]): - PTS = np.meshgrid(*pts, indexing="ij") + PTS = xp.meshgrid(*pts, indexing="ij") mat_w = weights_info[a][b](*PTS).copy() - elif isinstance(weights_info[a][b], np.ndarray): + elif isinstance(weights_info[a][b], xp.ndarray): mat_w = weights_info[a][b] assert mat_w.shape == tuple( [pt.size for pt in pts], ) - if np.any(np.abs(mat_w) > 1e-14): + if xp.any(xp.abs(mat_w) > 1e-14): if self._matrix_free: blocks[-1] += [ StencilMatrixFreeMassOperator( @@ -2374,9 +2478,11 @@ def __init__( # load assembly kernel if not self._matrix_free: - self._assembly_kernel = getattr( - mass_kernels, - "kernel_" + str(self._V.ldim) + "d_mat", + self._assembly_kernel = Pyccelkernel( + getattr( + mass_kernels, + "kernel_" + str(self._V.ldim) + "d_mat", + ), ) @property @@ -2412,10 +2518,10 @@ def tosparse(self): if all(op is None for op in (self._W_extraction_op, self._V_extraction_op)): for bl in self._V_boundary_op.bc: for bc in bl: - assert bc == False, print(".tosparse() only works without boundary conditions at the moment") + assert not bc, print(".tosparse() only works without boundary conditions at the moment") for bl in self._W_boundary_op.bc: for bc in bl: - assert bc == False, print(".tosparse() only works without boundary conditions at the moment") + assert not bc, print(".tosparse() only works without boundary conditions at the moment") return self._mat.tosparse() elif all(isinstance(op, IdentityOperator) for op in (self._W_extraction_op, self._V_extraction_op)): @@ -2428,10 +2534,10 @@ def toarray(self): if all(op is None for op in (self._W_extraction_op, self._V_extraction_op)): for bl in self._V_boundary_op.bc: for bc in bl: - assert bc == False, print(".toarray() only works without boundary conditions at the moment") + assert not bc, print(".toarray() only works without boundary conditions at the moment") for bl in self._W_boundary_op.bc: for bc in bl: - assert bc == False, print(".toarray() only works without boundary conditions at the moment") + assert not bc, print(".toarray() only works without boundary conditions at the moment") return self._mat.toarray() elif all(isinstance(op, IdentityOperator) for op in (self._W_extraction_op, self._V_extraction_op)): @@ -2592,7 +2698,7 @@ def assemble(self, weights=None, clear=True, verbose=True): Parameters ---------- weights : list | NoneType - Weight function(s) (callables or np.ndarrays) in a 2d list of shape corresponding to + Weight function(s) (callables or xp.ndarrays) in a 2d list of shape corresponding to number of components of domain/codomain. If ``weights=None``, the weight is taken from the given weights in the instanziation of the object, else it will be overriden. @@ -2615,7 +2721,7 @@ def assemble(self, weights=None, clear=True, verbose=True): if weight is not None: assert callable(weight) or isinstance( weight, - np.ndarray, + xp.ndarray, ) self._mat[a, b].weights = weight @@ -2682,7 +2788,8 @@ def assemble(self, weights=None, clear=True, verbose=True): codomain_spans = [ quad_grid[nquad].spans for quad_grid, nquad in zip( - self.derham.get_quad_grids(codomain_space, nquads=self.nquads), self.nquads + self.derham.get_quad_grids(codomain_space, nquads=self.nquads), + self.nquads, ) ] @@ -2695,7 +2802,8 @@ def assemble(self, weights=None, clear=True, verbose=True): pts = [ quad_grid[nquad].points.flatten() for quad_grid, nquad in zip( - self.derham.get_quad_grids(codomain_space, nquads=self.nquads), self.nquads + self.derham.get_quad_grids(codomain_space, nquads=self.nquads), + self.nquads, ) ] wts = [ @@ -2710,7 +2818,8 @@ def assemble(self, weights=None, clear=True, verbose=True): codomain_basis = [ quad_grid[nquad].basis for quad_grid, nquad in zip( - self.derham.get_quad_grids(codomain_space, nquads=self.nquads), self.nquads + self.derham.get_quad_grids(codomain_space, nquads=self.nquads), + self.nquads, ) ] @@ -2725,13 +2834,13 @@ def assemble(self, weights=None, clear=True, verbose=True): # evaluate weight at quadrature points if callable(loc_weight): - PTS = np.meshgrid(*pts, indexing="ij") + PTS = xp.meshgrid(*pts, indexing="ij") mat_w = loc_weight(*PTS).copy() - elif isinstance(loc_weight, np.ndarray): + elif isinstance(loc_weight, xp.ndarray): mat_w = loc_weight elif loc_weight is not None: raise TypeError( - "weights must be callable or np.ndarray or None but is {}".format( + "weights must be callable or xp.ndarray or None but is {}".format( type(self._weights[a][b]), ), ) @@ -2739,8 +2848,8 @@ def assemble(self, weights=None, clear=True, verbose=True): if loc_weight is not None: assert mat_w.shape == tuple([pt.size for pt in pts]) - not_weight_zero = np.array( - int(loc_weight is not None and np.any(np.abs(mat_w) > 1e-14)), + not_weight_zero = xp.array( + int(loc_weight is not None and xp.any(xp.abs(mat_w) > 1e-14)), ) if self._mpi_comm is not None: self._mpi_comm.Allreduce( @@ -2753,7 +2862,8 @@ def assemble(self, weights=None, clear=True, verbose=True): domain_basis = [ quad_grid[nquad].basis for quad_grid, nquad in zip( - self.derham.get_quad_grids(domain_space, nquads=self.nquads), self.nquads + self.derham.get_quad_grids(domain_space, nquads=self.nquads), + self.nquads, ) ] @@ -2764,7 +2874,7 @@ def assemble(self, weights=None, clear=True, verbose=True): mat = self._mat if loc_weight is None: # in case it's none we still need to have zeros weights to call the kernel - mat_w = np.zeros( + mat_w = xp.zeros( tuple([pt.size for pt in pts]), ) else: @@ -2900,12 +3010,12 @@ def eval_quad(W, coeffs, out=None): coeffs : StencilVector | BlockVector The coefficient vector corresponding to the FEM field. Ghost regions must be up-to-date! - out : np.ndarray | list/tuple of np.ndarrays, optional + out : xp.ndarray | list/tuple of xp.ndarrays, optional If given, the result will be written into these arrays in-place. Number of outs must be compatible with number of components of FEM field. Returns ------- - out : np.ndarray | list/tuple of np.ndarrays + out : xp.ndarray | list/tuple of xp.ndarrays The values of the FEM field at the quadrature points. """ @@ -2924,7 +3034,7 @@ def eval_quad(W, coeffs, out=None): out = () if isinstance(W, TensorFemSpace): out += ( - np.zeros( + xp.zeros( [ q_grid[nquad].points.size for q_grid, nquad in zip(self.derham.get_quad_grids(W, nquads=self.nquads), self.nquads) @@ -2935,11 +3045,12 @@ def eval_quad(W, coeffs, out=None): else: for space in W.spaces: out += ( - np.zeros( + xp.zeros( [ q_grid[nquad].points.size for q_grid, nquad in zip( - self.derham.get_quad_grids(space, nquads=self.nquads), self.nquads + self.derham.get_quad_grids(space, nquads=self.nquads), + self.nquads, ) ], dtype=float, @@ -2948,13 +3059,13 @@ def eval_quad(W, coeffs, out=None): else: if isinstance(W, TensorFemSpace): - assert isinstance(out, np.ndarray) + assert isinstance(out, xp.ndarray) out = (out,) else: assert isinstance(out, (list, tuple)) # load assembly kernel - kernel = getattr(mass_kernels, "kernel_" + str(W.ldim) + "d_eval") + kernel = Pyccelkernel(getattr(mass_kernels, "kernel_" + str(W.ldim) + "d_eval")) # loop over components for a, wspace in enumerate(Wspaces): @@ -3049,18 +3160,22 @@ def __init__(self, derham, V, W, weights=None, nquads=None): self._nquads = nquads self._dtype = V.coeff_space.dtype - self._dot_kernel = getattr( - mass_kernels, - "kernel_" + str(self._V.ldim) + "d_matrixfree", + self._dot_kernel = Pyccelkernel( + getattr( + mass_kernels, + "kernel_" + str(self._V.ldim) + "d_matrixfree", + ), ) - self._diag_kernel = getattr( - mass_kernels, - "kernel_" + str(self._V.ldim) + "d_diag", + self._diag_kernel = Pyccelkernel( + getattr( + mass_kernels, + "kernel_" + str(self._V.ldim) + "d_diag", + ), ) shape = tuple(e - s + 1 for s, e in zip(V.coeff_space.starts, V.coeff_space.ends)) - self._diag_tmp = np.zeros((shape)) + self._diag_tmp = xp.zeros((shape)) # knot span indices of elements of local domain self._codomain_spans = [ @@ -3144,7 +3259,11 @@ def toarray(self): def transpose(self, conjugate=False): return StencilMatrixFreeMassOperator( - self._derham, self._codomain, self._domain, self._weights, nquads=self._nquads + self._derham, + self._codomain, + self._domain, + self._weights, + nquads=self._nquads, ) @property @@ -3187,16 +3306,16 @@ def dot(self, v, out=None): # evaluate weight at quadrature points if callable(self._weights): - PTS = np.meshgrid(*self._pts, indexing="ij") + PTS = xp.meshgrid(*self._pts, indexing="ij") mat_w = self._weights(*PTS).copy() - elif isinstance(self._weights, np.ndarray): + elif isinstance(self._weights, xp.ndarray): mat_w = self._weights if self._weights is not None: assert mat_w.shape == tuple([pt.size for pt in self._pts]) # call kernel (if mat_w is not zero) by calling the appropriate kernel (1d, 2d or 3d) - if np.any(np.abs(mat_w) > 1e-14): + if xp.any(xp.abs(mat_w) > 1e-14): self._dot_kernel( *self._codomain_spans, *self._domain_spans, @@ -3256,9 +3375,9 @@ def diagonal(self, inverse=False, sqrt=False, out=None): # evaluate weight at quadrature points if callable(self._weights): - PTS = np.meshgrid(*self._pts, indexing="ij") + PTS = xp.meshgrid(*self._pts, indexing="ij") mat_w = self._weights(*PTS).copy() - elif isinstance(self._weights, np.ndarray): + elif isinstance(self._weights, xp.ndarray): mat_w = self._weights diag = self._diag_tmp @@ -3278,12 +3397,12 @@ def diagonal(self, inverse=False, sqrt=False, out=None): # Calculate entries of StencilDiagonalMatrix if sqrt: - diag = np.sqrt(diag) + diag = xp.sqrt(diag) if inverse: - data = np.divide(1, diag, out=data) + data = xp.divide(1, diag, out=data) elif out: - np.copyto(data, diag) + xp.copyto(data, diag) else: data = diag.copy() diff --git a/src/struphy/feec/mass_kernels.py b/src/struphy/feec/mass_kernels.py index f5fc6926b..7b4f09720 100644 --- a/src/struphy/feec/mass_kernels.py +++ b/src/struphy/feec/mass_kernels.py @@ -2,6 +2,7 @@ Integral kernels for mass matrices and L2-projections. """ +import numpy as np from numpy import shape # ================= 1d ================================= @@ -314,8 +315,6 @@ def kernel_3d_mat( The results are written into data (attention: data is NOT set to zero first, but the results are added to data). """ - import numpy as np - ne1 = spans1.size ne2 = spans2.size ne3 = spans3.size @@ -342,7 +341,9 @@ def kernel_3d_mat( for iel2 in range(ne2): for iel3 in range(ne3): tmp_mat_fun[:, :, :] = mat_fun[ - iel1 * nq1 : (iel1 + 1) * nq1, iel2 * nq2 : (iel2 + 1) * nq2, iel3 * nq3 : (iel3 + 1) * nq3 + iel1 * nq1 : (iel1 + 1) * nq1, + iel2 * nq2 : (iel2 + 1) * nq2, + iel3 * nq3 : (iel3 + 1) * nq3, ] tmp_w1[:] = w1[iel1, :] @@ -575,8 +576,6 @@ def kernel_3d_matrixfree( The results are written into data (attention: data is NOT set to zero first, but the results are added to data). """ - import numpy as np - ne1 = spansi1.size ne2 = spansi2.size ne3 = spansi3.size @@ -603,7 +602,9 @@ def kernel_3d_matrixfree( for iel2 in range(ne2): for iel3 in range(ne3): tmp_mat_fun[:, :, :] = mat_fun[ - iel1 * nq1 : (iel1 + 1) * nq1, iel2 * nq2 : (iel2 + 1) * nq2, iel3 * nq3 : (iel3 + 1) * nq3 + iel1 * nq1 : (iel1 + 1) * nq1, + iel2 * nq2 : (iel2 + 1) * nq2, + iel3 * nq3 : (iel3 + 1) * nq3, ] tmp_w1[:] = w1[iel1, :] @@ -692,8 +693,6 @@ def kernel_3d_diag( The results are written into data (attention: data is NOT set to zero first, but the results are added to data). """ - import numpy as np - ne1 = spans1.size ne2 = spans2.size ne3 = spans3.size @@ -718,7 +717,9 @@ def kernel_3d_diag( for iel2 in range(ne2): for iel3 in range(ne3): tmp_mat_fun[:, :, :] = mat_fun[ - iel1 * nq1 : (iel1 + 1) * nq1, iel2 * nq2 : (iel2 + 1) * nq2, iel3 * nq3 : (iel3 + 1) * nq3 + iel1 * nq1 : (iel1 + 1) * nq1, + iel2 * nq2 : (iel2 + 1) * nq2, + iel3 * nq3 : (iel3 + 1) * nq3, ] tmp_w1[:] = w1[iel1, :] diff --git a/src/struphy/feec/preconditioner.py b/src/struphy/feec/preconditioner.py index eab61c930..dfa00df4c 100644 --- a/src/struphy/feec/preconditioner.py +++ b/src/struphy/feec/preconditioner.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp from psydac.api.essential_bc import apply_essential_bc_stencil from psydac.ddm.cart import CartDecomposition, DomainDecomposition from psydac.fem.tensor import TensorFemSpace @@ -94,12 +94,12 @@ def fun(e): s = e.shape[0] newshape = tuple([1 if i != d else s for i in range(n_dims)]) f = e.reshape(newshape) - return np.atleast_1d( + return xp.atleast_1d( loc_weights( - *[np.array(np.full_like(f, 0.5)) if i != d else np.array(f) for i in range(n_dims)], + *[xp.array(xp.full_like(f, 0.5)) if i != d else xp.array(f) for i in range(n_dims)], ).squeeze(), ) - elif isinstance(loc_weights, np.ndarray): + elif isinstance(loc_weights, xp.ndarray): s = loc_weights.shape if d == 0: fun = loc_weights[:, s[1] // 2, s[2] // 2] @@ -108,14 +108,14 @@ def fun(e): elif d == 2: fun = loc_weights[s[0] // 2, s[1] // 2, :] elif loc_weights is None: - fun = lambda e: np.ones(e.size, dtype=float) + fun = lambda e: xp.ones(e.size, dtype=float) else: raise TypeError( - "weights needs to be callable, np.ndarray or None but is{}".format(type(loc_weights)), + "weights needs to be callable, xp.ndarray or None but is{}".format(type(loc_weights)), ) fun = [[fun]] else: - fun = [[lambda e: np.ones(e.size, dtype=float)]] + fun = [[lambda e: xp.ones(e.size, dtype=float)]] # get 1D FEM space (serial, not distributed) and quadrature order femspace_1d = femspaces[c].spaces[d] @@ -207,7 +207,7 @@ def fun(e): M_local = StencilMatrix(V_local, V_local) - row_indices, col_indices = np.nonzero(M_arr) + row_indices, col_indices = xp.nonzero(M_arr) for row_i, col_i in zip(row_indices, col_indices): # only consider row indices on process @@ -220,7 +220,7 @@ def fun(e): ] = M_arr[row_i, col_i] # check if stencil matrix was built correctly - assert np.allclose(M_local.toarray()[s : e + 1], M_arr[s : e + 1]) + assert xp.allclose(M_local.toarray()[s : e + 1], M_arr[s : e + 1]) matrixcells += [M_local.copy()] # ======================================================================================================= @@ -318,11 +318,6 @@ def solver(self): """KroneckerLinearSolver or BlockDiagonalSolver for exactly inverting the approximate mass matrix self.matrix.""" return self._solver - @property - def domain(self): - """The domain of the linear operator - an element of Vectorspace""" - return self._space - @property def codomain(self): """The codomain of the linear operator - an element of Vectorspace""" @@ -487,7 +482,7 @@ def __init__(self, mass_operator, apply_bc=True): # loop over spatial directions for d in range(n_dims): - fun = [[lambda e: np.ones(e.size, dtype=float)]] + fun = [[lambda e: xp.ones(e.size, dtype=float)]] # get 1D FEM space (serial, not distributed) and quadrature order femspace_1d = femspaces[c].spaces[d] @@ -579,7 +574,7 @@ def __init__(self, mass_operator, apply_bc=True): M_local = StencilMatrix(V_local, V_local) - row_indices, col_indices = np.nonzero(M_arr) + row_indices, col_indices = xp.nonzero(M_arr) for row_i, col_i in zip(row_indices, col_indices): # only consider row indices on process @@ -592,7 +587,7 @@ def __init__(self, mass_operator, apply_bc=True): ] = M_arr[row_i, col_i] # check if stencil matrix was built correctly - assert np.allclose(M_local.toarray()[s : e + 1], M_arr[s : e + 1]) + assert xp.allclose(M_local.toarray()[s : e + 1], M_arr[s : e + 1]) matrixcells += [M_local.copy()] # ======================================================================================================= @@ -676,7 +671,7 @@ def __init__(self, mass_operator, apply_bc=True): # Need to assemble the logical mass matrix to extract the coefficients fun = [ - [lambda e1, e2, e3: np.ones_like(e1, dtype=float) if i == j else None for j in range(3)] for i in range(3) + [lambda e1, e2, e3: xp.ones_like(e1, dtype=float) if i == j else None for j in range(3)] for i in range(3) ] log_M = WeightedMassOperator( self._mass_operator.derham, @@ -704,9 +699,6 @@ def matrix(self): def solver(self): """KroneckerLinearSolver or BlockDiagonalSolver for exactly inverting the approximate mass matrix self.matrix.""" return self._solver - - @property - def domain(self): """The domain of the linear operator - an element of Vectorspace""" return self._space @@ -864,15 +856,15 @@ class FFTSolver(BandedSolver): Parameters ---------- - circmat : np.ndarray + circmat : xp.ndarray Generic circulant matrix. """ def __init__(self, circmat): - assert isinstance(circmat, np.ndarray) + assert isinstance(circmat, xp.ndarray) assert is_circulant(circmat) - self._space = np.ndarray + self._space = xp.ndarray self._column = circmat[:, 0] # -------------------------------------- @@ -889,13 +881,13 @@ def solve(self, rhs, out=None, transposed=False): Parameters ---------- - rhs : np.ndarray + rhs : xp.ndarray The right-hand sides to solve for. The vectors are assumed to be given in C-contiguous order, i.e. if multiple right-hand sides are given, then rhs is a two-dimensional array with the 0-th index denoting the number of the right-hand side, and the 1-st index denoting the element inside a right-hand side. - out : np.ndarray, optional + out : xp.ndarray, optional Output vector. If given, it has to have the same shape and datatype as rhs. transposed : bool @@ -913,9 +905,9 @@ def solve(self, rhs, out=None, transposed=False): try: out[:] = solve_circulant(self._column, rhs.T).T - except np.linalg.LinAlgError: + except xp.linalg.LinAlgError: eps = 1e-4 - print(f"Stabilizing singular preconditioning FFTSolver with {eps = }:") + print(f"Stabilizing singular preconditioning FFTSolver with {eps =}:") self._column[0] *= 1.0 + eps out[:] = solve_circulant(self._column, rhs.T).T @@ -937,13 +929,13 @@ def is_circulant(mat): Whether the matrix is circulant (=True) or not (=False). """ - assert isinstance(mat, np.ndarray) + assert isinstance(mat, xp.ndarray) assert len(mat.shape) == 2 assert mat.shape[0] == mat.shape[1] if mat.shape[0] > 1: for i in range(mat.shape[0] - 1): - circulant = np.allclose(mat[i, :], np.roll(mat[i + 1, :], -1)) + circulant = xp.allclose(mat[i, :], xp.roll(mat[i + 1, :], -1)) if not circulant: return circulant else: diff --git a/src/struphy/feec/projectors.py b/src/struphy/feec/projectors.py index cbed00aa7..21b8f77b4 100644 --- a/src/struphy/feec/projectors.py +++ b/src/struphy/feec/projectors.py @@ -1,5 +1,4 @@ -import numpy as np -from mpi4py import MPI +import cunumpy as xp from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL from psydac.ddm.mpi import mpi as MPI from psydac.feec.global_geometric_projectors import GlobalGeometricProjector @@ -581,24 +580,24 @@ class CommutingProjectorLocal: fem_space : FemSpace FEEC space into which the functions shall be projected. - pts : list of np.array + pts : list of xp.array 3-list (or nested 3-list[3-list] for BlockVectors) of 2D arrays with the quasi-interpolation points (or Gauss-Legendre quadrature points for histopolation). In format [spatial direction](B-spline index, point) for StencilVector spaces or [vector component][spatial direction](B-spline index, point) for BlockVector spaces. - wts : list of np.array + wts : list of xp.array 3D (4D for BlockVectors) list of 2D array with the Gauss-Legendre quadrature weights (full of ones for interpolation). In format [spatial direction](B-spline index, point) for StencilVector spaces or [vector component][spatial direction](B-spline index, point) for BlockVector spaces. - wij : list of np.array + wij : list of xp.array List of 2D arrays for the coefficients :math:`\omega_j^i` obtained by inverting the local collocation matrix. Use for obtaining the FE coefficients of a function via interpolation. In format [spatial direction](B-spline index, point). - whij : list of np.array + whij : list of xp.array List of 2D arrays for the coefficients :math:`\hat{\omega}_j^i` obtained from the :math:`\omega_j^i`. Use for obtaining the FE coefficients of a function via histopolation. In format [spatial direction](D-spline index, point). @@ -644,64 +643,72 @@ def __init__( # FE space of zero forms. That means that we have B-splines in all three spatial directions. Bspaces_1d = [fem_space_B.spaces] - self._B_nbasis = np.array([space.nbasis for space in Bspaces_1d[0]]) + self._B_nbasis = xp.array([space.nbasis for space in Bspaces_1d[0]]) # Degree of the B-spline space, not to be confused with the degrees given by fem_space.spaces.degree since depending on the situation it will give the D-spline degree instead - self._p = np.zeros(3, dtype=int) + self._p = xp.zeros(3, dtype=int) for i, space in enumerate(fem_space_B.spaces): self._p[i] = space.degree # FE space of three forms. That means that we have D-splines in all three spatial directions. Dspaces_1d = [fem_space_D.spaces] - D_nbasis = np.array([space.nbasis for space in Dspaces_1d[0]]) + D_nbasis = xp.array([space.nbasis for space in Dspaces_1d[0]]) self._periodic = [] for space in fem_space.spaces: self._periodic.append(space.periodic) - self._periodic = np.array(self._periodic) + self._periodic = xp.array(self._periodic) if isinstance(fem_space, TensorFemSpace): # The comm, rank and size are only necessary for debugging. In particular, for printing stuff self._comm = self._coeff_space.cart.comm - self._rank = self._comm.Get_rank() - self._size = self._comm.Get_size() + if self._comm is None: + self._rank = 0 + self._size = 1 + else: + self._rank = self._comm.Get_rank() + self._size = self._comm.Get_size() # We get the start and endpoint for each sublist in out - self._starts = np.array(self.coeff_space.starts) - self._ends = np.array(self.coeff_space.ends) + self._starts = xp.array(self.coeff_space.starts) + self._ends = xp.array(self.coeff_space.ends) # We compute the number of FE coefficients the current MPI rank is responsible for - self._loc_num_coeff = np.array([self._ends[i] + 1 - self._starts[i] for i in range(3)], dtype=int) + self._loc_num_coeff = xp.array([self._ends[i] + 1 - self._starts[i] for i in range(3)], dtype=int) # We get the pads - self._pds = np.array(self.coeff_space.pads) + self._pds = xp.array(self.coeff_space.pads) # We get the number of spaces we have self._nsp = 1 self._localpts = [] self._index_translation = [] self._inv_index_translation = [] - self._original_pts_size = np.zeros((3), dtype=int) + self._original_pts_size = xp.zeros((3), dtype=int) elif isinstance(fem_space, VectorFemSpace): # The comm, rank and size are only necessary for debugging. In particular, for printing stuff self._comm = self._coeff_space.spaces[0].cart.comm - self._rank = self._comm.Get_rank() - self._size = self._comm.Get_size() + if self._comm is None: + self._rank = 0 + self._size = 1 + else: + self._rank = self._comm.Get_rank() + self._size = self._comm.Get_size() # we collect all starts and ends in two big lists - self._starts = np.array([vi.starts for vi in self.coeff_space.spaces]) - self._ends = np.array([vi.ends for vi in self.coeff_space.spaces]) + self._starts = xp.array([vi.starts for vi in self.coeff_space.spaces]) + self._ends = xp.array([vi.ends for vi in self.coeff_space.spaces]) # We compute the number of FE coefficients the current MPI rank is responsible for - self._loc_num_coeff = np.array( + self._loc_num_coeff = xp.array( [[self._ends[h][i] + 1 - self._starts[h][i] for i in range(3)] for h in range(3)], dtype=int, ) # We collect the pads - self._pds = np.array([vi.pads for vi in self.coeff_space.spaces]) + self._pds = xp.array([vi.pads for vi in self.coeff_space.spaces]) # We get the number of space we have self._nsp = len(self.coeff_space.spaces) @@ -717,7 +724,7 @@ def __init__( self._localpts = [[], [], []] # Here we will store the global number of points for each block entry and for each spatial direction. - self._original_pts_size = [np.zeros((3), dtype=int), np.zeros((3), dtype=int), np.zeros((3), dtype=int)] + self._original_pts_size = [xp.zeros((3), dtype=int), xp.zeros((3), dtype=int), xp.zeros((3), dtype=int)] # This will be a list of three elements (the first one for the first block element, the second one for the second block element, ...), each one being a list with three arrays, # each array will contain the B-spline indices of the corresponding spatial direction for which this MPI rank has to store at least one non-zero FE coefficient for the storage of the @@ -737,33 +744,33 @@ def __init__( self._are_zero_block_B_or_D_splines = [[], [], []] # self._Basis_function_indices_agreggated_B[i][j] = -1 if the jth B-spline is not necessary for any of the three block entries in the ith spatial direction, otherwise it is 0 - self._Basis_function_indices_agreggated_B = [-1 * np.ones(nbasis, dtype=int) for nbasis in self._B_nbasis] - self._Basis_function_indices_agreggated_D = [-1 * np.ones(nbasis, dtype=int) for nbasis in D_nbasis] + self._Basis_function_indices_agreggated_B = [-1 * xp.ones(nbasis, dtype=int) for nbasis in self._B_nbasis] + self._Basis_function_indices_agreggated_D = [-1 * xp.ones(nbasis, dtype=int) for nbasis in D_nbasis] # List that will contain the LocalProjectorsArguments for each value of h = 0,1,2. self._solve_args = [] else: - raise TypeError(f"{fem_space = } is not of type FemSpace.") + raise TypeError(f"{fem_space =} is not of type FemSpace.") if isinstance(fem_space, TensorFemSpace): if space_id == "H1": # List of list that tell us for each spatial direction whether we have Interpolation or Histopolation. IoH_for_indices = ["I", "I", "I"] # Same list as before but with bools instead of chars - self._IoH = np.array([False, False, False], dtype=bool) + self._IoH = xp.array([False, False, False], dtype=bool) # We make a list with the interpolation/histopolation weights we need for each block and each direction. self._geo_weights = [self._wij[0], self._wij[1], self._wij[2]] elif space_id == "L2": IoH_for_indices = ["H", "H", "H"] - self._IoH = np.array([True, True, True], dtype=bool) + self._IoH = xp.array([True, True, True], dtype=bool) self._geo_weights = [self._whij[0], self._whij[1], self._whij[2]] lenj1, lenj2, lenj3 = get_local_problem_size(self._periodic, self._p, self._IoH) lenj = [lenj1, lenj2, lenj3] - self._shift = np.array([0, 0, 0], dtype=int) + self._shift = xp.array([0, 0, 0], dtype=int) compute_shifts(self._IoH, self._p, self._B_nbasis, self._shift) split_points( @@ -785,7 +792,7 @@ def __init__( ) # We want to build the meshgrid for the evaluation of the degrees of freedom so it only contains the evaluation points that each specific MPI rank is actually going to use. - self._meshgrid = np.meshgrid( + self._meshgrid = xp.meshgrid( *[pt for pt in self._localpts], indexing="ij", ) @@ -924,18 +931,18 @@ def __init__( ) elif isinstance(fem_space, VectorFemSpace): - self._shift = [np.array([0, 0, 0], dtype=int) for _ in range(3)] + self._shift = [xp.array([0, 0, 0], dtype=int) for _ in range(3)] if space_id == "H1vec": # List of list that tell us for each block entry and for each spatial direction whether we have Interpolation or Histopolation. IoH_for_indices = [["I", "I", "I"], ["I", "I", "I"], ["I", "I", "I"]] # Same list as before but with bools instead of chars self._IoH = [ - np.array([False, False, False], dtype=bool), - np.array( + xp.array([False, False, False], dtype=bool), + xp.array( [False, False, False], dtype=bool, ), - np.array([False, False, False], dtype=bool), + xp.array([False, False, False], dtype=bool), ] # We make a list with the interpolation/histopolation weights we need for each block and each direction. self._geo_weights = [[self._wij[0], self._wij[1], self._wij[2]] for _ in range(3)] @@ -943,12 +950,12 @@ def __init__( elif space_id == "Hcurl": IoH_for_indices = [["H", "I", "I"], ["I", "H", "I"], ["I", "I", "H"]] self._IoH = [ - np.array([True, False, False], dtype=bool), - np.array( + xp.array([True, False, False], dtype=bool), + xp.array( [False, True, False], dtype=bool, ), - np.array([False, False, True], dtype=bool), + xp.array([False, False, True], dtype=bool), ] self._geo_weights = [ [self._whij[0], self._wij[1], self._wij[2]], @@ -963,12 +970,12 @@ def __init__( elif space_id == "Hdiv": IoH_for_indices = [["I", "H", "H"], ["H", "I", "H"], ["H", "H", "I"]] self._IoH = [ - np.array([False, True, True], dtype=bool), - np.array( + xp.array([False, True, True], dtype=bool), + xp.array( [True, False, True], dtype=bool, ), - np.array([True, True, False], dtype=bool), + xp.array([True, True, False], dtype=bool), ] self._geo_weights = [ [self._wij[0], self._whij[1], self._whij[2]], @@ -1007,7 +1014,7 @@ def __init__( # meshgrid for h component self._meshgrid.append( - np.meshgrid( + xp.meshgrid( *[pt for pt in self._localpts[h]], indexing="ij", ), @@ -1325,9 +1332,9 @@ def solve_weighted(self, rhs, out=None): if isinstance(self._fem_space, TensorFemSpace): if out is None: - out = np.zeros((self._loc_num_coeff[0], self._loc_num_coeff[1], self._loc_num_coeff[2]), dtype=float) + out = xp.zeros((self._loc_num_coeff[0], self._loc_num_coeff[1], self._loc_num_coeff[2]), dtype=float) else: - assert np.shape(out) == (self._loc_num_coeff[0], self._loc_num_coeff[1], self._loc_num_coeff[2]) + assert xp.shape(out) == (self._loc_num_coeff[0], self._loc_num_coeff[1], self._loc_num_coeff[2]) solve_local_main_loop_weighted( self._solve_args, @@ -1349,7 +1356,7 @@ def solve_weighted(self, rhs, out=None): out = [] for h in range(3): out.append( - np.zeros( + xp.zeros( ( self._loc_num_coeff[h][0], self._loc_num_coeff[h][1], @@ -1362,7 +1369,7 @@ def solve_weighted(self, rhs, out=None): else: assert len(out) == 3 for h in range(3): - assert np.shape(out[h]) == ( + assert xp.shape(out[h]) == ( self._loc_num_coeff[h][0], self._loc_num_coeff[h][1], self._loc_num_coeff[h][2], @@ -1372,7 +1379,7 @@ def solve_weighted(self, rhs, out=None): # the out block for which do_nothing tell us before hand they shall be zero. for h in range(3): if self._do_nothing[h] == 1: - out[h] = np.zeros( + out[h] = xp.zeros( ( self._loc_num_coeff[h][0], self._loc_num_coeff[h][1], @@ -1422,12 +1429,12 @@ def get_dofs(self, fun, dofs=None): fh = fun(*self._meshgrid[h])[h] # Case in which fun is a list of three functions, each one with one output. else: - assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) = }." + assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) =}." # Evaluation of the function to compute the h component fh = fun[h](*self._meshgrid[h]) # Array into which we will write the Dofs. - f_eval_aux = np.zeros(tuple(np.shape(dim)[0] for dim in self._localpts[h])) + f_eval_aux = xp.zeros(tuple(xp.shape(dim)[0] for dim in self._localpts[h])) # For 1-forms if self._space_key == "1": @@ -1439,7 +1446,7 @@ def get_dofs(self, fun, dofs=None): f_eval.append(f_eval_aux) elif self._space_key == "3": - f_eval = np.zeros(tuple(np.shape(dim)[0] for dim in self._localpts)) + f_eval = xp.zeros(tuple(xp.shape(dim)[0] for dim in self._localpts)) # Evaluation of the function at all Gauss-Legendre quadrature points faux = fun(*self._meshgrid) get_dofs_local_3_form(self._solve_args, faux, f_eval) @@ -1458,7 +1465,7 @@ def get_dofs(self, fun, dofs=None): fun, ) == 3 - ), f"List input only for vector-valued spaces of size 3, but {len(fun) = }." + ), f"List input only for vector-valued spaces of size 3, but {len(fun) =}." for h in range(3): f_eval.append(fun[h](*self._meshgrid[h])) @@ -1474,26 +1481,26 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non Builds 3D numpy array with the evaluation of the right-hand-side. """ if self._space_key == "0": - if first_go == True: + if first_go: pre_computed_dofs = [fun(*self._meshgrid)] elif self._space_key == "1" or self._space_key == "2": - assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) = }." + assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) =}." - self._do_nothing = np.zeros(3, dtype=int) + self._do_nothing = xp.zeros(3, dtype=int) f_eval = [] # If this is the first time this rank has to evaluate the weights degrees of freedom we declare the list where to store them. - if first_go == True: + if first_go: pre_computed_dofs = [] for h in range(3): # Evaluation of the function to compute the h component - if first_go == True: + if first_go: pre_computed_dofs.append(fun[h](*self._meshgrid[h])) # Array into which we will write the Dofs. - f_eval_aux = np.zeros(tuple(np.shape(dim)[0] for dim in self._localpts[h])) + f_eval_aux = xp.zeros(tuple(xp.shape(dim)[0] for dim in self._localpts[h])) # We check if the current set of basis functions is not one of those we have to compute in the current MPI rank. if ( @@ -1538,9 +1545,9 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non f_eval.append(f_eval_aux) elif self._space_key == "3": - f_eval = np.zeros(tuple(np.shape(dim)[0] for dim in self._localpts)) + f_eval = xp.zeros(tuple(xp.shape(dim)[0] for dim in self._localpts)) # Evaluation of the function at all Gauss-Legendre quadrature points - if first_go == True: + if first_go: pre_computed_dofs = [fun(*self._meshgrid)] get_dofs_local_3_form_weighted( @@ -1558,9 +1565,9 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non ) elif self._space_key == "v": - assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) = }." + assert len(fun) == 3, f"List input only for vector-valued spaces of size 3, but {len(fun) =}." - self._do_nothing = np.zeros(3, dtype=int) + self._do_nothing = xp.zeros(3, dtype=int) for h in range(3): # We check if the current set of basis functions is not one of those we have to compute in the current MPI rank. if ( @@ -1571,7 +1578,7 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non # We should do nothing here self._do_nothing[h] = 1 - if first_go == True: + if first_go: f_eval = [] for h in range(3): f_eval.append(fun[h](*self._meshgrid[h])) @@ -1581,7 +1588,7 @@ def get_dofs_weighted(self, fun, dofs=None, first_go=True, pre_computed_dofs=Non "Uknown space. It must be either H1, Hcurl, Hdiv, L2 or H1vec.", ) - if first_go == True: + if first_go: if self._space_key == "0": return pre_computed_dofs[0], pre_computed_dofs elif self._space_key == "v": @@ -1638,23 +1645,23 @@ def __call__( set to false it means we computed it once already and we can reuse the dofs evaluation of the weights instead of recomputing them. - pre_computed_dofs : list of np.arrays + pre_computed_dofs : list of xp.arrays If we have already computed the evaluation of the weights at the dofs we can pass the arrays with their values here, so we do not have to compute them again. Returns ------- - coeffs : psydac.linalg.basic.vector | np.array 3D + coeffs : psydac.linalg.basic.vector | xp.array 3D The FEM spline coefficients after projection. """ - if weighted == False: + if not weighted: return self.solve(self.get_dofs(fun, dofs=dofs), out=out) else: # We set B_or_D and basis_indices as attributes of the projectors so we can easily access them in the get_rowstarts, get_rowends and get_values functions, where they are needed. self._B_or_D = B_or_D self._basis_indices = basis_indices - if first_go == True: + if first_go: # rhs contains the evaluation over the degrees of freedom of the weights multiplied by the basis function # rhs_weights contains the evaluation over the degrees of freedom of only the weights rhs, rhs_weights = self.get_dofs_weighted( @@ -1665,7 +1672,8 @@ def __call__( return self.solve_weighted(rhs, out=out), rhs_weights else: return self.solve_weighted( - self.get_dofs_weighted(fun, dofs=dofs, first_go=False, pre_computed_dofs=pre_computed_dofs), out=out + self.get_dofs_weighted(fun, dofs=dofs, first_go=False, pre_computed_dofs=pre_computed_dofs), + out=out, ) def get_translation_b(self, i, h): @@ -1855,7 +1863,7 @@ def __init__(self, space_id, mass_ops, **params): self._quad_grid_pts = self.mass_ops.derham.quad_grid_pts[self.space_key] if space_id in ("H1", "L2"): - self._quad_grid_mesh = np.meshgrid( + self._quad_grid_mesh = xp.meshgrid( *[pt.flatten() for pt in self.quad_grid_pts], indexing="ij", ) @@ -1865,12 +1873,12 @@ def __init__(self, space_id, mass_ops, **params): self._tmp = [] # tmp for matrix-vector product of geom_weights with fun for pts in self.quad_grid_pts: self._quad_grid_mesh += [ - np.meshgrid( + xp.meshgrid( *[pt.flatten() for pt in pts], indexing="ij", ), ] - self._tmp += [np.zeros_like(self.quad_grid_mesh[-1][0])] + self._tmp += [xp.zeros_like(self.quad_grid_mesh[-1][0])] # geometric weights evaluated at quadrature grid self._geom_weights = [] # loop over rows (different meshes) @@ -1881,7 +1889,7 @@ def __init__(self, space_id, mass_ops, **params): if weight is not None: self._geom_weights[-1] += [weight(*mesh)] else: - self._geom_weights[-1] += [np.zeros_like(mesh[0])] + self._geom_weights[-1] += [xp.zeros_like(mesh[0])] # other quad grid info if isinstance(self.space, TensorFemSpace): @@ -2007,7 +2015,7 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): Parameters ---------- fun : callable | list - Weight function(s) (callables or np.ndarrays) in a 1d list of shape corresponding to number of components. + Weight function(s) (callables or xp.ndarrays) in a 1d list of shape corresponding to number of components. dofs : StencilVector | BlockVector, optional The vector for the output. @@ -2022,9 +2030,9 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): # evaluate fun at quad_grid or check array size if callable(fun): fun_weights = fun(*self._quad_grid_mesh) - elif isinstance(fun, np.ndarray): + elif isinstance(fun, xp.ndarray): assert fun.shape == self._quad_grid_mesh[0].shape, ( - f"Expected shape {self._quad_grid_mesh[0].shape}, got {fun.shape = } instead." + f"Expected shape {self._quad_grid_mesh[0].shape}, got {fun.shape =} instead." ) fun_weights = fun else: @@ -2033,7 +2041,7 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): fun, ) == 3 - ), f"List input only for vector-valued spaces of size 3, but {len(fun) = }." + ), f"List input only for vector-valued spaces of size 3, but {len(fun) =}." fun_weights = [] # loop over rows (different meshes) for mesh in self._quad_grid_mesh: @@ -2042,12 +2050,12 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): for f in fun: if callable(f): fun_weights[-1] += [f(*mesh)] - elif isinstance(f, np.ndarray): - assert f.shape == mesh[0].shape, f"Expected shape {mesh[0].shape}, got {f.shape = } instead." + elif isinstance(f, xp.ndarray): + assert f.shape == mesh[0].shape, f"Expected shape {mesh[0].shape}, got {f.shape =} instead." fun_weights[-1] += [f] else: raise ValueError( - f"Expected callable or numpy array, got {type(f) = } instead.", + f"Expected callable or numpy array, got {type(f) =} instead.", ) # check output vector @@ -2059,7 +2067,7 @@ def get_dofs(self, fun, dofs=None, apply_bc=False, clear=True): # compute matrix data for kernel, i.e. fun * geom_weight tot_weights = [] - if isinstance(fun_weights, np.ndarray): + if isinstance(fun_weights, xp.ndarray): tot_weights += [fun_weights * self.geom_weights] else: # loop over rows (differnt meshes) diff --git a/src/struphy/feec/psydac_derham.py b/src/struphy/feec/psydac_derham.py index a0589a362..e0e261340 100644 --- a/src/struphy/feec/psydac_derham.py +++ b/src/struphy/feec/psydac_derham.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import importlib.metadata -import numpy as np +import cunumpy as xp import psydac.core.bsplines as bsp -from mpi4py import MPI -from mpi4py.MPI import Intracomm from psydac.ddm.cart import DomainDecomposition from psydac.ddm.mpi import MockComm, MockMPI from psydac.ddm.mpi import mpi as MPI @@ -102,7 +100,7 @@ def __init__( dirichlet_bc: list | tuple = None, nquads: list | tuple = None, nq_pr: list | tuple = None, - comm: Intracomm = None, + comm=None, mpi_dims_mask: list = None, with_projectors: bool = True, polar_ck: int = -1, @@ -123,7 +121,7 @@ def __init__( if dirichlet_bc is not None: assert len(dirichlet_bc) == 3 # make sure that boundary conditions are compatible with spline space - assert np.all([bc == (False, False) for i, bc in enumerate(dirichlet_bc) if spl_kind[i]]) + assert xp.all([bc == (False, False) for i, bc in enumerate(dirichlet_bc) if spl_kind[i]]) self._dirichlet_bc = dirichlet_bc @@ -306,7 +304,7 @@ def __init__( fag.basis, ] - self._spline_types_pyccel[sp_form][-1] = np.array( + self._spline_types_pyccel[sp_form][-1] = xp.array( self._spline_types_pyccel[sp_form][-1], ) # In this case we are working with a scalar valued space @@ -358,11 +356,11 @@ def __init__( self._quad_grid_spans[sp_form] += [fag.spans] self._quad_grid_bases[sp_form] += [fag.basis] - self._spline_types_pyccel[sp_form] = np.array( + self._spline_types_pyccel[sp_form] = xp.array( self._spline_types_pyccel[sp_form], ) else: - raise TypeError(f"{fem_space = } is not a valid type.") + raise TypeError(f"{fem_space =} is not a valid type.") # break points self._breaks = [space.breaks for space in _derham.spaces[0].spaces] @@ -370,8 +368,8 @@ def __init__( # index arrays self._indN = [ ( - np.indices((space.ncells, space.degree + 1))[1] - + np.arange( + xp.indices((space.ncells, space.degree + 1))[1] + + xp.arange( space.ncells, )[:, None] ) @@ -380,8 +378,8 @@ def __init__( ] self._indD = [ ( - np.indices((space.ncells, space.degree + 1))[1] - + np.arange( + xp.indices((space.ncells, space.degree + 1))[1] + + xp.arange( space.ncells, )[:, None] ) @@ -390,8 +388,6 @@ def __init__( ] # distribute info on domain decomposition - self._domain_decomposition = self._Vh["0"].cart.domain_decomposition - self._domain_array = self._get_domain_array() self._breaks_loc = [ self.breaks[k][self.domain_decomposition.starts[k] : self.domain_decomposition.ends[k] + 2] @@ -399,7 +395,7 @@ def __init__( ] self._index_array = self._get_index_array( - self._domain_decomposition, + self.domain_decomposition, ) self._index_array_N = self._get_index_array(self._Vh["0"].cart) self._index_array_D = self._get_index_array(self._Vh["3"].cart) @@ -533,11 +529,11 @@ def __init__( # collect arguments for kernels self._args_derham = DerhamArguments( - np.array(self.p), + xp.array(self.p), self.Vh_fem["0"].knots[0], self.Vh_fem["0"].knots[1], self.Vh_fem["0"].knots[2], - np.array(self.Vh["0"].starts), + xp.array(self.Vh["0"].starts), ) @property @@ -806,7 +802,7 @@ def init_derham( Nel: tuple | list, p: tuple | list, spl_kind: tuple | list, - comm: Intracomm = None, + comm=None, mpi_dims_mask: tuple | list = None, ): """Discretize the Derahm complex. Allows for the use of tiny-psydac. @@ -834,12 +830,13 @@ def init_derham( if "dev" in psydac_ver: # use tiny-psydac version - ddm = DomainDecomposition(Nel, spl_kind, comm=comm, mpi_dims_mask=mpi_dims_mask) + self._domain_decomposition = DomainDecomposition(Nel, spl_kind, comm=comm, mpi_dims_mask=mpi_dims_mask) + _derham = self._discretize_derham( Nel=Nel, p=p, spl_kind=spl_kind, - ddm=ddm, + ddm=self.domain_decomposition, ) else: from psydac.api.discretization import discretize @@ -1069,7 +1066,7 @@ def _discretize_space( ) # Create uniform grid - grids = [np.linspace(xmin, xmax, num=ne + 1) for xmin, xmax, ne in zip(min_coords, max_coords, ncells)] + grids = [xp.linspace(xmin, xmax, num=ne + 1) for xmin, xmax, ne in zip(min_coords, max_coords, ncells)] # Create 1D finite element spaces and precompute quadrature data spaces_1d = [ @@ -1100,7 +1097,7 @@ def _discretize_space( elif V == "L2": Wh = Vh.reduce_degree(axes=[0, 1, 2], multiplicity=Vh.multiplicity, basis=basis) else: - raise ValueError(f"V must be one of H1, Hcurl, Hdiv or L2, but is {V = }.") + raise ValueError(f"V must be one of H1, Hcurl, Hdiv or L2, but is {V =}.") Wh.symbolic_space = V for key in Wh._refined_space: @@ -1114,7 +1111,7 @@ def _get_domain_array(self): Returns ------- - dom_arr : np.ndarray + dom_arr : xp.ndarray A 2d array of shape (#MPI processes, 9). The row index denotes the process rank. The columns are for n=0,1,2: - arr[i, 3*n + 0] holds the LEFT domain boundary of process i in direction eta_(n+1). - arr[i, 3*n + 1] holds the RIGHT domain boundary of process i in direction eta_(n+1). @@ -1128,10 +1125,10 @@ def _get_domain_array(self): nproc = 1 # send buffer - dom_arr_loc = np.zeros(9, dtype=float) + dom_arr_loc = xp.zeros(9, dtype=float) # main array (receive buffers) - dom_arr = np.zeros(nproc * 9, dtype=float) + dom_arr = xp.zeros(nproc * 9, dtype=float) # Get global starts and ends of domain decomposition gl_s = self.domain_decomposition.starts @@ -1144,7 +1141,7 @@ def _get_domain_array(self): dom_arr_loc[3 * n + 2] = el_end - el_sta + 1 # distribute - if self.comm is not None: + if not isinstance(self.comm, (MockComm, type(None))): self.comm.Allgather(dom_arr_loc, dom_arr) else: dom_arr[:] = dom_arr_loc @@ -1162,23 +1159,23 @@ def _get_index_array(self, decomposition): Returns ------- - ind_arr : np.ndarray + ind_arr : xp.ndarray A 2d array of shape (#MPI processes, 6). The row index denotes the process rank. The columns are for n=0,1,2: - arr[i, 2*n + 0] holds the global start index process i in direction eta_(n+1). - arr[i, 2*n + 1] holds the global end index of process i in direction eta_(n+1). """ # MPI info - if self.comm is not None: + if not isinstance(self.comm, (MockComm, type(None))): nproc = self.comm.Get_size() else: nproc = 1 # send buffer - ind_arr_loc = np.zeros(6, dtype=int) + ind_arr_loc = xp.zeros(6, dtype=int) # main array (receive buffers) - ind_arr = np.zeros(nproc * 6, dtype=int) + ind_arr = xp.zeros(nproc * 6, dtype=int) # Get global starts and ends of cart OR domain decomposition gl_s = decomposition.starts @@ -1190,7 +1187,7 @@ def _get_index_array(self, decomposition): ind_arr_loc[2 * n + 1] = end # distribute - if self.comm is not None: + if not isinstance(self.comm, (MockComm, type(None))): self.comm.Allgather(ind_arr_loc, ind_arr) else: ind_arr[:] = ind_arr_loc @@ -1221,13 +1218,13 @@ def _get_neighbours(self): Returns ------- - neighbours : np.ndarray + neighbours : xp.ndarray A 3d array of shape (3,3,3). The i-th axis is the direction eta_(i+1). Neighbours along the faces have index with two 1s, neighbours along the edges only have one 1, neighbours along the edges have no 1 in the index. """ - neighs = np.empty((3, 3, 3), dtype=int) + neighs = xp.empty((3, 3, 3), dtype=int) for i in range(3): for j in range(3): @@ -1272,12 +1269,12 @@ def _get_neighbour_one_component(self, comp): if comp == [1, 1, 1]: return neigh_id - comp = np.array(comp) - kinds = np.array(kinds) + comp = xp.array(comp) + kinds = xp.array(kinds) # if only one process: check if comp is neighbour in non-peridic directions, if this is not the case then return the rank as neighbour id if size == 1: - if (comp[kinds == False] == 1).all(): + if (comp[~kinds] == 1).all(): return rank # multiple processes @@ -1308,15 +1305,15 @@ def _get_neighbour_one_component(self, comp): "Wrong value for component; must be 0 or 1 or 2 !", ) - neigh_inds = np.array(neigh_inds) + neigh_inds = xp.array(neigh_inds) # only use indices where information is present to find the neighbours rank - inds = np.where(neigh_inds != None) + inds = xp.where(neigh_inds != None) # find ranks (row index of domain_array) which agree in start/end indices - index_temp = np.squeeze(self.index_array[:, inds]) - unique_ranks = np.where( - np.equal(index_temp, neigh_inds[inds]).all(1), + index_temp = xp.squeeze(self.index_array[:, inds]) + unique_ranks = xp.where( + xp.equal(index_temp, neigh_inds[inds]).all(1), )[0] # if any row satisfies condition, return its index (=rank of neighbour) @@ -1336,7 +1333,7 @@ def _get_span_and_basis_for_eval_mpi(self, etas, Nspace, end): Parameters ---------- - etas : np.array + etas : xp.array 1d array of evaluation points (ascending). Nspace : SplineSpace @@ -1347,13 +1344,13 @@ def _get_span_and_basis_for_eval_mpi(self, etas, Nspace, end): Returns ------- - spans : np.array + spans : xp.array 1d array of knot span indices. - bn : np.array + bn : xp.array 2d array of pn + 1 values of N-splines indexed by (eta, spline value). - bd : np.array + bd : xp.array 2d array of pn values of D-splines indexed by (eta, spline value). """ @@ -1363,11 +1360,11 @@ def _get_span_and_basis_for_eval_mpi(self, etas, Nspace, end): Tn = Nspace.knots pn = Nspace.degree - spans = np.zeros(etas.size, dtype=int) - bns = np.zeros((etas.size, pn + 1), dtype=float) - bds = np.zeros((etas.size, pn), dtype=float) - bn = np.zeros(pn + 1, dtype=float) - bd = np.zeros(pn, dtype=float) + spans = xp.zeros(etas.size, dtype=int) + bns = xp.zeros((etas.size, pn + 1), dtype=float) + bds = xp.zeros((etas.size, pn), dtype=float) + bn = xp.zeros(pn + 1, dtype=float) + bd = xp.zeros(pn, dtype=float) for n in range(etas.size): # avoid 1. --> 0. for clamped interpolation @@ -1545,7 +1542,7 @@ def vector(self, value): """In-place setter for Stencil-/Block-/PolarVector.""" if isinstance(self._vector, StencilVector): - assert isinstance(value, (StencilVector, np.ndarray)) + assert isinstance(value, (StencilVector, xp.ndarray)) s1, s2, s3 = self.starts e1, e2, e3 = self.ends @@ -1568,10 +1565,10 @@ def vector(self, value): self._vector.set_vector(value) else: if isinstance(self._vector.tp, StencilVector): - assert isinstance(value[0], np.ndarray) + assert isinstance(value[0], xp.ndarray) assert isinstance( value[1], - (StencilVector, np.ndarray), + (StencilVector, xp.ndarray), ) self._vector.pol[0][:] = value[0][:] @@ -1580,14 +1577,16 @@ def vector(self, value): e1, e2, e3 = self.ends self._vector.tp[s1 : e1 + 1, s2 : e2 + 1, s3 : e3 + 1] = value[1][ - s1 : e1 + 1, s2 : e2 + 1, s3 : e3 + 1 + s1 : e1 + 1, + s2 : e2 + 1, + s3 : e3 + 1, ] else: for n in range(3): - assert isinstance(value[n][0], np.ndarray) + assert isinstance(value[n][0], xp.ndarray) assert isinstance( value[n][1], - (StencilVector, np.ndarray), + (StencilVector, xp.ndarray), ) self._vector.pol[n][:] = value[n][0][:] @@ -1596,7 +1595,9 @@ def vector(self, value): e1, e2, e3 = self.ends[n] self._vector.tp[n][s1 : e1 + 1, s2 : e2 + 1, s3 : e3 + 1] = value[n][1][ - s1 : e1 + 1, s2 : e2 + 1, s3 : e3 + 1 + s1 : e1 + 1, + s2 : e2 + 1, + s3 : e3 + 1, ] self._vector.update_ghost_regions() @@ -1739,13 +1740,13 @@ def f_tmp(e1, e2, e3): else: assert equil is not None var = fb.variable - assert var in dir(MHDequilibrium), f"{var = } is not an attribute of any fields background." + assert var in dir(MHDequilibrium), f"{var =} is not an attribute of any fields background." if self.space_id in {"H1", "L2"}: fun = getattr(equil, var) else: assert (var + "_1") in dir(MHDequilibrium), ( - f"{(var + '_1') = } is not an attribute of any fields background." + f"{(var + '_1') =} is not an attribute of any fields background." ) fun = [ getattr(equil, var + "_1"), @@ -1891,7 +1892,7 @@ def eval_tp_fixed_loc(self, spans, bases, out=None): assert [span.size for span in spans] == [base.shape[0] for base in bases] if out is None: - out = np.empty([span.size for span in spans], dtype=float) + out = xp.empty([span.size for span in spans], dtype=float) else: assert out.shape == tuple([span.size for span in spans]) @@ -1900,8 +1901,8 @@ def eval_tp_fixed_loc(self, spans, bases, out=None): *bases, vec._data, self.derham.spline_types_pyccel[self.space_key], - np.array(self.derham.p), - np.array(self.starts), + xp.array(self.derham.p), + xp.array(self.starts), out, ) @@ -1915,7 +1916,7 @@ def eval_tp_fixed_loc(self, spans, bases, out=None): assert [span.size for span in spans] == [base.shape[0] for base in bases[i]] if out_is_none: - out += np.empty( + out += xp.empty( [span.size for span in spans], dtype=float, ) @@ -1929,10 +1930,10 @@ def eval_tp_fixed_loc(self, spans, bases, out=None): *bases[i], vec[i]._data, self.derham.spline_types_pyccel[self.space_key][i], - np.array( + xp.array( self.derham.p, ), - np.array( + xp.array( self.starts[i], ), out[i], @@ -1999,14 +2000,14 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): # prepare arrays for AllReduce if tmp is None: - tmp = np.zeros( + tmp = xp.zeros( tmp_shape, dtype=float, ) else: - assert isinstance(tmp, np.ndarray) + assert isinstance(tmp, xp.ndarray) assert tmp.shape == tmp_shape - assert tmp.dtype.type is np.float64 + assert tmp.dtype.type is xp.float64 tmp[:] = 0.0 # scalar-valued field @@ -2021,11 +2022,11 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): E3, self._vector_stencil._data, kind, - np.array(self.derham.p), + xp.array(self.derham.p), T1, T2, T3, - np.array(self.starts), + xp.array(self.starts), tmp, ) elif marker_evaluation: @@ -2034,11 +2035,11 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): markers, self._vector_stencil._data, kind, - np.array(self.derham.p), + xp.array(self.derham.p), T1, T2, T3, - np.array(self.starts), + xp.array(self.starts), tmp, ) else: @@ -2049,16 +2050,16 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): E3, self._vector_stencil._data, kind, - np.array(self.derham.p), + xp.array(self.derham.p), T1, T2, T3, - np.array(self.starts), + xp.array(self.starts), tmp, ) if self.derham.comm is not None: - if local == False: + if not local: self.derham.comm.Allreduce( MPI.IN_PLACE, tmp, @@ -2073,7 +2074,7 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): out += tmp if squeeze_out: - out = np.squeeze(out) + out = xp.squeeze(out) if out.ndim == 0: out = out.item() @@ -2092,11 +2093,11 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): E3, self._vector_stencil[n]._data, kind, - np.array(self.derham.p), + xp.array(self.derham.p), T1, T2, T3, - np.array(self.starts[n]), + xp.array(self.starts[n]), tmp, ) elif marker_evaluation: @@ -2105,11 +2106,11 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): markers, self._vector_stencil[n]._data, kind, - np.array(self.derham.p), + xp.array(self.derham.p), T1, T2, T3, - np.array(self.starts[n]), + xp.array(self.starts[n]), tmp, ) else: @@ -2120,16 +2121,16 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): E3, self._vector_stencil[n]._data, kind, - np.array(self.derham.p), + xp.array(self.derham.p), T1, T2, T3, - np.array(self.starts[n]), + xp.array(self.starts[n]), tmp, ) if self.derham.comm is not None: - if local == False: + if not local: self.derham.comm.Allreduce( MPI.IN_PLACE, tmp, @@ -2146,7 +2147,7 @@ def __call__(self, *etas, out=None, tmp=None, squeeze_out=False, local=False): tmp[:] = 0.0 if squeeze_out: - out[-1] = np.squeeze(out[-1]) + out[-1] = xp.squeeze(out[-1]) if out[-1].ndim == 0: out[-1] = out[-1].item() @@ -2179,11 +2180,11 @@ def _flag_pts_not_on_proc(self, *etas): markers = etas[0] # check which particles are on the current process domain - is_on_proc_domain = np.logical_and( + is_on_proc_domain = xp.logical_and( markers[:, :3] >= dom_arr[rank, 0::3], markers[:, :3] <= dom_arr[rank, 1::3], ) - on_proc = np.all(is_on_proc_domain, axis=1) + on_proc = xp.all(is_on_proc_domain, axis=1) markers[~on_proc, :] = -1.0 @@ -2209,15 +2210,15 @@ def _flag_pts_not_on_proc(self, *etas): E3[E3 == dom_arr[rank, 7]] += 1e-8 # True for eval points on current process - E1_on_proc = np.logical_and( + E1_on_proc = xp.logical_and( E1 >= dom_arr[rank, 0], E1 <= dom_arr[rank, 1], ) - E2_on_proc = np.logical_and( + E2_on_proc = xp.logical_and( E2 >= dom_arr[rank, 3], E2 <= dom_arr[rank, 4], ) - E3_on_proc = np.logical_and( + E3_on_proc = xp.logical_and( E3 >= dom_arr[rank, 6], E3 <= dom_arr[rank, 7], ) @@ -2378,7 +2379,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): Returns ------- - _amps : np.array + _amps : xp.array The noisy FE coefficients in the desired direction (1d, 2d or 3d array).""" if self.derham.comm is not None: @@ -2393,40 +2394,40 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): domain_array = self.derham.domain_array if seed is not None: - np.random.seed(seed) + xp.random.seed(seed) # temporary - _amps = np.zeros(shapes) + _amps = xp.zeros(shapes) # no process has been drawn for yet - already_drawn = np.zeros(nprocs) == 1.0 + already_drawn = xp.zeros(nprocs) == 1.0 # 1d mid point arrays in each direction mid_points = [] for npr in nprocs: delta = 1.0 / npr - mid_points_i = np.zeros(npr) + mid_points_i = xp.zeros(npr) for n in range(npr): mid_points_i[n] = delta * (n + 1 / 2) mid_points += [mid_points_i] if direction == "e1": - tmp_arrays = np.zeros(nprocs[0]).tolist() + tmp_arrays = xp.zeros(nprocs[0]).tolist() elif direction == "e2": - tmp_arrays = np.zeros(nprocs[1]).tolist() + tmp_arrays = xp.zeros(nprocs[1]).tolist() elif direction == "e3": - tmp_arrays = np.zeros(nprocs[2]).tolist() + tmp_arrays = xp.zeros(nprocs[2]).tolist() elif direction == "e1e2": - tmp_arrays = np.zeros((nprocs[0], nprocs[1])).tolist() + tmp_arrays = xp.zeros((nprocs[0], nprocs[1])).tolist() Warning, f"2d noise in the directions {direction} is not correctly initilaized for MPI !!" elif direction == "e1e3": - tmp_arrays = np.zeros((nprocs[0], nprocs[2])).tolist() + tmp_arrays = xp.zeros((nprocs[0], nprocs[2])).tolist() Warning, f"2d noise in the directions {direction} is not correctly initilaized for MPI !!" elif direction == "e2e3": - tmp_arrays = np.zeros((nprocs[1], nprocs[2])).tolist() + tmp_arrays = xp.zeros((nprocs[1], nprocs[2])).tolist() Warning, f"2d noise in the directions {direction} is not correctly initilaized for MPI !!" elif direction == "e1e2e3": - tmp_arrays = np.zeros((nprocs[0], nprocs[1], nprocs[2])).tolist() + tmp_arrays = xp.zeros((nprocs[0], nprocs[1], nprocs[2])).tolist() Warning, f"3d noise in the directions {direction} is not correctly initilaized for MPI !!" else: raise ValueError("Invalid direction for tmp_arrays.") @@ -2435,7 +2436,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): inds_current = [] for n in range(3): mid_pt_current = (domain_array[rank, 3 * n] + domain_array[rank, 3 * n + 1]) / 2.0 - inds_current += [np.argmin(np.abs(mid_points[n] - mid_pt_current))] + inds_current += [xp.argmin(xp.abs(mid_points[n] - mid_pt_current))] # loop over processes for i in range(comm_size): @@ -2443,7 +2444,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): inds = [] for n in range(3): mid_pt = (domain_array[i, 3 * n] + domain_array[i, 3 * n + 1]) / 2.0 - inds += [np.argmin(np.abs(mid_points[n] - mid_pt))] + inds += [xp.argmin(xp.abs(mid_points[n] - mid_pt))] if already_drawn[inds[0], inds[1], inds[2]]: if direction == "e1": @@ -2465,7 +2466,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): if direction == "e1": tmp_arrays[inds[0]] = ( ( - np.random.rand( + xp.random.rand( *shapes, ) - 0.5 @@ -2478,7 +2479,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): elif direction == "e2": tmp_arrays[inds[1]] = ( ( - np.random.rand( + xp.random.rand( *shapes, ) - 0.5 @@ -2491,7 +2492,7 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): elif direction == "e3": tmp_arrays[inds[2]] = ( ( - np.random.rand( + xp.random.rand( *shapes, ) - 0.5 @@ -2502,23 +2503,23 @@ def _tmp_noise_for_mpi(self, *shapes, direction="e3", amp=0.0001, seed=None): already_drawn[:, :, inds[2]] = True _amps[:] = tmp_arrays[inds[2]] elif direction == "e1e2": - tmp_arrays[inds[0]][inds[1]] = (np.random.rand(*shapes) - 0.5) * 2.0 * amp + tmp_arrays[inds[0]][inds[1]] = (xp.random.rand(*shapes) - 0.5) * 2.0 * amp already_drawn[inds[0], inds[1], :] = True _amps[:] = tmp_arrays[inds[0]][inds[1]] elif direction == "e1e3": - tmp_arrays[inds[0]][inds[2]] = (np.random.rand(*shapes) - 0.5) * 2.0 * amp + tmp_arrays[inds[0]][inds[2]] = (xp.random.rand(*shapes) - 0.5) * 2.0 * amp already_drawn[inds[0], :, inds[2]] = True _amps[:] = tmp_arrays[inds[0]][inds[2]] elif direction == "e2e3": - tmp_arrays[inds[1]][inds[2]] = (np.random.rand(*shapes) - 0.5) * 2.0 * amp + tmp_arrays[inds[1]][inds[2]] = (xp.random.rand(*shapes) - 0.5) * 2.0 * amp already_drawn[:, inds[1], inds[2]] = True _amps[:] = tmp_arrays[inds[1]][inds[2]] elif direction == "e1e2e3": - tmp_arrays[inds[0]][inds[1]][inds[2]] = (np.random.rand(*shapes) - 0.5) * 2.0 * amp + tmp_arrays[inds[0]][inds[1]][inds[2]] = (xp.random.rand(*shapes) - 0.5) * 2.0 * amp already_drawn[inds[0], inds[1], inds[2]] = True _amps[:] = tmp_arrays[inds[0]][inds[1]][inds[2]] - if np.all(np.array([ind_c == ind for ind_c, ind in zip(inds_current, inds)])): + if xp.all(xp.array([ind_c == ind for ind_c, ind in zip(inds_current, inds)])): return _amps @@ -2769,16 +2770,16 @@ def get_pts_and_wts(space_1d, start, end, n_quad=None, polar_shift=False): histopol_loc = space_1d.histopolation_grid[start : end + 2].copy() # make sure that greville points used for interpolation are in [0, 1] - assert np.all(np.logical_and(greville_loc >= 0.0, greville_loc <= 1.0)) + assert xp.all(xp.logical_and(greville_loc >= 0.0, greville_loc <= 1.0)) # interpolation if space_1d.basis == "B": x_grid = greville_loc pts = greville_loc[:, None] - wts = np.ones(pts.shape, dtype=float) + wts = xp.ones(pts.shape, dtype=float) # sub-interval index is always 0 for interpolation. - subs = np.zeros(pts.shape[0], dtype=int) + subs = xp.zeros(pts.shape[0], dtype=int) # !! shift away first interpolation point in eta_1 direction for polar domains !! if pts[0] == 0.0 and polar_shift: @@ -2792,27 +2793,27 @@ def get_pts_and_wts(space_1d, start, end, n_quad=None, polar_shift=False): union_breaks = space_1d.breaks[:-1] # Make union of Greville and break points - tmp = set(np.round(space_1d.histopolation_grid, decimals=14)).union( - np.round(union_breaks, decimals=14), + tmp = set(xp.round(space_1d.histopolation_grid, decimals=14)).union( + xp.round(union_breaks, decimals=14), ) tmp = list(tmp) tmp.sort() - tmp_a = np.array(tmp) + tmp_a = xp.array(tmp) x_grid = tmp_a[ - np.logical_and( + xp.logical_and( tmp_a - >= np.min( + >= xp.min( histopol_loc, ) - 1e-14, - tmp_a <= np.max(histopol_loc) + 1e-14, + tmp_a <= xp.max(histopol_loc) + 1e-14, ) ] # determine subinterval index (= 0 or 1): - subs = np.zeros(x_grid[:-1].size, dtype=int) + subs = xp.zeros(x_grid[:-1].size, dtype=int) for n, x_h in enumerate(x_grid[:-1]): add = 1 for x_g in histopol_loc: @@ -2825,7 +2826,7 @@ def get_pts_and_wts(space_1d, start, end, n_quad=None, polar_shift=False): # products of basis functions are integrated exactly n_quad = space_1d.degree + 1 - pts_loc, wts_loc = np.polynomial.legendre.leggauss(n_quad) + pts_loc, wts_loc = xp.polynomial.legendre.leggauss(n_quad) x, wts = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) @@ -2888,12 +2889,12 @@ def get_pts_and_wts_quasi( # interpolation if space_1d.basis == "B": if p == 1 and h != 1.0: - x_grid = np.linspace(-(p - 1) * h, 1.0 - h + (h / 2.0), (N + p - 1) * 2) + x_grid = xp.linspace(-(p - 1) * h, 1.0 - h + (h / 2.0), (N + p - 1) * 2) else: - x_grid = np.linspace(-(p - 1) * h, 1.0 - h, (N + p - 1) * 2 - 1) + x_grid = xp.linspace(-(p - 1) * h, 1.0 - h, (N + p - 1) * 2 - 1) pts = x_grid[:, None] % 1.0 - wts = np.ones(pts.shape, dtype=float) + wts = xp.ones(pts.shape, dtype=float) # !! shift away first interpolation point in eta_1 direction for polar domains !! if pts[0] == 0.0 and polar_shift: @@ -2904,16 +2905,16 @@ def get_pts_and_wts_quasi( # The computation of histopolation points breaks in case we have Nel=1 and periodic boundary conditions since we end up with only one x_grid point. # We need to build the histopolation points by hand in this scenario. if p == 0 and h == 1.0: - x_grid = np.array([0.0, 0.5, 1.0]) + x_grid = xp.array([0.0, 0.5, 1.0]) elif p == 0 and h != 1.0: - x_grid = np.linspace(-p * h, 1.0 - h + (h / 2.0), (N + p) * 2) + x_grid = xp.linspace(-p * h, 1.0 - h + (h / 2.0), (N + p) * 2) else: - x_grid = np.linspace(-p * h, 1.0 - h, (N + p) * 2 - 1) + x_grid = xp.linspace(-p * h, 1.0 - h, (N + p) * 2 - 1) n_quad = p + 1 # Gauss - Legendre quadrature points and weights # products of basis functions are integrated exactly - pts_loc, wts_loc = np.polynomial.legendre.leggauss(n_quad) + pts_loc, wts_loc = xp.polynomial.legendre.leggauss(n_quad) x, wts = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) pts = x % 1.0 @@ -2927,26 +2928,26 @@ def get_pts_and_wts_quasi( N_b = N + p # Filling the quasi-interpolation points for i=0 and i=1 (since they are equal) - x_grid = np.linspace(0.0, knots[p + 1], p + 1) - x_aux = np.linspace(0.0, knots[p + 1], p + 1) - x_grid = np.append(x_grid, x_aux) + x_grid = xp.linspace(0.0, knots[p + 1], p + 1) + x_aux = xp.linspace(0.0, knots[p + 1], p + 1) + x_grid = xp.append(x_grid, x_aux) # Now we append those for 1 V2):") res_PSY = OPS_PSY.Q1.dot(x1_st) - res_STR = OPS_STR.Q1_dot(np.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) + res_STR = OPS_STR.Q1_dot(xp.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_2(res_STR) MPI_COMM.Barrier() @@ -284,7 +284,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): Q1T = OPS_PSY.Q1.transpose() res_PSY = Q1T.dot(x2_st) - res_STR = OPS_STR.transpose_Q1_dot(np.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) + res_STR = OPS_STR.transpose_Q1_dot(xp.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_1(res_STR) MPI_COMM.Barrier() @@ -310,7 +310,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): print("\nW1 (V1 --> V1, Identity operator in this case):") res_PSY = OPS_PSY.W1.dot(x1_st) - res_STR = OPS_STR.W1_dot(np.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) + res_STR = OPS_STR.W1_dot(xp.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_1(res_STR) MPI_COMM.barrier() @@ -333,7 +333,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): W1T = OPS_PSY.W1.transpose() res_PSY = W1T.dot(x1_st) - res_STR = OPS_STR.transpose_W1_dot(np.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) + res_STR = OPS_STR.transpose_W1_dot(xp.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_1(res_STR) MPI_COMM.barrier() @@ -359,7 +359,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): print("\nQ2 (V2 --> V2, Identity operator in this case):") res_PSY = OPS_PSY.Q2.dot(x2_st) - res_STR = OPS_STR.Q2_dot(np.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) + res_STR = OPS_STR.Q2_dot(xp.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_2(res_STR) MPI_COMM.Barrier() @@ -382,7 +382,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): Q2T = OPS_PSY.Q2.transpose() res_PSY = Q2T.dot(x2_st) - res_STR = OPS_STR.transpose_Q2_dot(np.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) + res_STR = OPS_STR.transpose_Q2_dot(xp.concatenate((x2[0].flatten(), x2[1].flatten(), x2[2].flatten()))) res_STR_0, res_STR_1, res_STR_2 = SPACES.extract_2(res_STR) MPI_COMM.Barrier() @@ -408,7 +408,7 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): print("\nX1 (V1 --> V0 x V0 x V0):") res_PSY = OPS_PSY.X1.dot(x1_st) - res_STR = OPS_STR.X1_dot(np.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) + res_STR = OPS_STR.X1_dot(xp.concatenate((x1[0].flatten(), x1[1].flatten(), x1[2].flatten()))) res_STR_0 = SPACES.extract_0(res_STR[0]) res_STR_1 = SPACES.extract_0(res_STR[1]) res_STR_2 = SPACES.extract_0(res_STR[2]) @@ -455,7 +455,6 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): print(f"Rank {mpi_rank} | Assertion passed.") -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[6, 9, 7]]) @pytest.mark.parametrize("p", [[2, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -465,8 +464,8 @@ def test_some_basis_ops(Nel, p, spl_kind, mapping): ) @pytest.mark.parametrize("mapping", [["IGAPolarCylinder", {"a": 1.0, "Lz": 3.0}]]) def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.mhd_operators import MHDOperators from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space @@ -504,7 +503,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) if show_plots: @@ -585,11 +584,11 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal x2_pol_psy.tp = x2_psy x3_pol_psy.tp = x3_psy - np.random.seed(1607) - x0_pol_psy.pol = [np.random.rand(x0_pol_psy.pol[0].shape[0], x0_pol_psy.pol[0].shape[1])] - x1_pol_psy.pol = [np.random.rand(x1_pol_psy.pol[n].shape[0], x1_pol_psy.pol[n].shape[1]) for n in range(3)] - x2_pol_psy.pol = [np.random.rand(x2_pol_psy.pol[n].shape[0], x2_pol_psy.pol[n].shape[1]) for n in range(3)] - x3_pol_psy.pol = [np.random.rand(x3_pol_psy.pol[0].shape[0], x3_pol_psy.pol[0].shape[1])] + xp.random.seed(1607) + x0_pol_psy.pol = [xp.random.rand(x0_pol_psy.pol[0].shape[0], x0_pol_psy.pol[0].shape[1])] + x1_pol_psy.pol = [xp.random.rand(x1_pol_psy.pol[n].shape[0], x1_pol_psy.pol[n].shape[1]) for n in range(3)] + x2_pol_psy.pol = [xp.random.rand(x2_pol_psy.pol[n].shape[0], x2_pol_psy.pol[n].shape[1]) for n in range(3)] + x3_pol_psy.pol = [xp.random.rand(x3_pol_psy.pol[0].shape[0], x3_pol_psy.pol[0].shape[1])] # apply boundary conditions to legacy vectors for right shape x0_pol_str = space.B0.dot(x0_pol_psy.toarray(True)) @@ -615,7 +614,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.PR(x3_pol_str) print(f"Rank {mpi_rank} | Asserting MHD operator K3.") - np.allclose(space.B3.T.dot(r_str), r_psy.toarray(True)) + xp.allclose(space.B3.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") mpi_comm.Barrier() @@ -628,7 +627,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.PR.T(x3_pol_str) print(f"Rank {mpi_rank} | Asserting transpose MHD operator K3.T.") - np.allclose(space.B3.T.dot(r_str), r_psy.toarray(True)) + xp.allclose(space.B3.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") # ===== operator Q2 (V2 --> V2) ============ @@ -645,7 +644,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.MF(x2_pol_str) print(f"Rank {mpi_rank} | Asserting MHD operator Q2.") - np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") mpi_comm.Barrier() @@ -658,7 +657,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.MF.T(x2_pol_str) print(f"Rank {mpi_rank} | Asserting transposed MHD operator Q2.T.") - np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") # ===== operator T2 (V2 --> V1) ============ @@ -675,7 +674,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.EF(x2_pol_str) print(f"Rank {mpi_rank} | Asserting MHD operator T2.") - np.allclose(space.B1.T.dot(r_str), r_psy.toarray(True)) + xp.allclose(space.B1.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") mpi_comm.Barrier() @@ -688,7 +687,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.EF.T(x1_pol_str) print(f"Rank {mpi_rank} | Asserting transposed MHD operator T2.T.") - np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") # ===== operator S2 (V2 --> V2) ============ @@ -705,7 +704,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.PF(x2_pol_str) print(f"Rank {mpi_rank} | Asserting MHD operator S2.") - np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") mpi_comm.Barrier() @@ -718,7 +717,7 @@ def test_basis_ops_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=Fal r_str = mhd_ops_str.PF.T(x2_pol_str) print(f"Rank {mpi_rank} | Asserting transposed MHD operator S2.T.") - np.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) + xp.allclose(space.B2.T.dot(r_str), r_psy.toarray(True)) print(f"Rank {mpi_rank} | Assertion passed.") @@ -727,7 +726,7 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): TODO """ - import numpy as np + import cunumpy as xp if verbose: if MPI_COMM is not None: @@ -790,8 +789,8 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): print( f"Rank {mpi_rank} | Maximum absolute diference (result):\n", - np.max( - np.abs( + xp.max( + xp.abs( res_PSY[ res_PSY.starts[0] : res_PSY.ends[0] + 1, res_PSY.starts[1] : res_PSY.ends[1] + 1, @@ -801,8 +800,8 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): res_PSY.starts[0] : res_PSY.ends[0] + 1, res_PSY.starts[1] : res_PSY.ends[1] + 1, res_PSY.starts[2] : res_PSY.ends[2] + 1, - ] - ) + ], + ), ), ) @@ -810,7 +809,7 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): MPI_COMM.Barrier() # Compare results. (Works only for Nel=[N, N, N] so far! TODO: Find this bug!) - assert np.allclose( + assert xp.allclose( res_PSY[ res_PSY.starts[0] : res_PSY.ends[0] + 1, res_PSY.starts[1] : res_PSY.ends[1] + 1, @@ -835,5 +834,10 @@ def assert_ops(mpi_rank, res_PSY, res_STR, verbose=False, MPI_COMM=None): # mapping=["Cuboid", {"l1": 0.0, "r1": 1.0, "l2": 0.0, "r2": 1.0, "l3": 0.0, "r3": 1.0}], # ) test_basis_ops_polar( - [6, 9, 7], [2, 2, 3], [False, True, True], None, ["IGAPolarCylinder", {"a": 1.0, "Lz": 3.0}], False + [6, 9, 7], + [2, 2, 3], + [False, True, True], + None, + ["IGAPolarCylinder", {"a": 1.0, "Lz": 3.0}], + False, ) diff --git a/src/struphy/feec/tests/test_derham.py b/src/struphy/feec/tests/test_derham.py index e89d1a1a6..1e857b5a2 100644 --- a/src/struphy/feec/tests/test_derham.py +++ b/src/struphy/feec/tests/test_derham.py @@ -1,15 +1,14 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 8, 12]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True]]) def test_psydac_derham(Nel, p, spl_kind): """Remark: p=even projectors yield slightly different results, pass with atol=1e-3.""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -18,7 +17,6 @@ def test_psydac_derham(Nel, p, spl_kind): from struphy.feec.utilities import compare_arrays comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() print("Nel=", Nel) @@ -49,11 +47,11 @@ def test_psydac_derham(Nel, p, spl_kind): N3_tot = DR_STR.Ntot_3form # Random vectors for testing - np.random.seed(1981) - x0 = np.random.rand(N0_tot) - x1 = np.random.rand(np.sum(N1_tot)) - x2 = np.random.rand(np.sum(N2_tot)) - x3 = np.random.rand(N3_tot) + xp.random.seed(1981) + x0 = xp.random.rand(N0_tot) + x1 = xp.random.rand(xp.sum(N1_tot)) + x2 = xp.random.rand(xp.sum(N2_tot)) + x3 = xp.random.rand(N3_tot) ############################ ### TEST STENCIL VECTORS ### @@ -72,7 +70,9 @@ def test_psydac_derham(Nel, p, spl_kind): # Assign from start to end index + 1 x0_PSY[s0[0] : e0[0] + 1, s0[1] : e0[1] + 1, s0[2] : e0[2] + 1] = DR_STR.extract_0(x0)[ - s0[0] : e0[0] + 1, s0[1] : e0[1] + 1, s0[2] : e0[2] + 1 + s0[0] : e0[0] + 1, + s0[1] : e0[1] + 1, + s0[2] : e0[2] + 1, ] # Block of StencilVecttors @@ -89,13 +89,19 @@ def test_psydac_derham(Nel, p, spl_kind): x11, x12, x13 = DR_STR.extract_1(x1) x1_PSY[0][s11[0] : e11[0] + 1, s11[1] : e11[1] + 1, s11[2] : e11[2] + 1] = x11[ - s11[0] : e11[0] + 1, s11[1] : e11[1] + 1, s11[2] : e11[2] + 1 + s11[0] : e11[0] + 1, + s11[1] : e11[1] + 1, + s11[2] : e11[2] + 1, ] x1_PSY[1][s12[0] : e12[0] + 1, s12[1] : e12[1] + 1, s12[2] : e12[2] + 1] = x12[ - s12[0] : e12[0] + 1, s12[1] : e12[1] + 1, s12[2] : e12[2] + 1 + s12[0] : e12[0] + 1, + s12[1] : e12[1] + 1, + s12[2] : e12[2] + 1, ] x1_PSY[2][s13[0] : e13[0] + 1, s13[1] : e13[1] + 1, s13[2] : e13[2] + 1] = x13[ - s13[0] : e13[0] + 1, s13[1] : e13[1] + 1, s13[2] : e13[2] + 1 + s13[0] : e13[0] + 1, + s13[1] : e13[1] + 1, + s13[2] : e13[2] + 1, ] x2_PSY = BlockVector(derham.Vh["2"]) @@ -111,13 +117,19 @@ def test_psydac_derham(Nel, p, spl_kind): x21, x22, x23 = DR_STR.extract_2(x2) x2_PSY[0][s21[0] : e21[0] + 1, s21[1] : e21[1] + 1, s21[2] : e21[2] + 1] = x21[ - s21[0] : e21[0] + 1, s21[1] : e21[1] + 1, s21[2] : e21[2] + 1 + s21[0] : e21[0] + 1, + s21[1] : e21[1] + 1, + s21[2] : e21[2] + 1, ] x2_PSY[1][s22[0] : e22[0] + 1, s22[1] : e22[1] + 1, s22[2] : e22[2] + 1] = x22[ - s22[0] : e22[0] + 1, s22[1] : e22[1] + 1, s22[2] : e22[2] + 1 + s22[0] : e22[0] + 1, + s22[1] : e22[1] + 1, + s22[2] : e22[2] + 1, ] x2_PSY[2][s23[0] : e23[0] + 1, s23[1] : e23[1] + 1, s23[2] : e23[2] + 1] = x23[ - s23[0] : e23[0] + 1, s23[1] : e23[1] + 1, s23[2] : e23[2] + 1 + s23[0] : e23[0] + 1, + s23[1] : e23[1] + 1, + s23[2] : e23[2] + 1, ] x3_PSY = StencilVector(derham.Vh["3"]) @@ -132,7 +144,9 @@ def test_psydac_derham(Nel, p, spl_kind): e3 = x3_PSY.ends x3_PSY[s3[0] : e3[0] + 1, s3[1] : e3[1] + 1, s3[2] : e3[2] + 1] = DR_STR.extract_3(x3)[ - s3[0] : e3[0] + 1, s3[1] : e3[1] + 1, s3[2] : e3[2] + 1 + s3[0] : e3[0] + 1, + s3[1] : e3[1] + 1, + s3[2] : e3[2] + 1, ] ######################## @@ -176,7 +190,7 @@ def test_psydac_derham(Nel, p, spl_kind): zero2_STR = curl_STR.dot(d1_STR) zero2_PSY = derham.curl.dot(d1_PSY) - assert np.allclose(zero2_STR, np.zeros_like(zero2_STR)) + assert xp.allclose(zero2_STR, xp.zeros_like(zero2_STR)) if rank == 0: print("\nCompare curl of grad:") compare_arrays(zero2_PSY, DR_STR.extract_2(zero2_STR), rank) @@ -185,7 +199,7 @@ def test_psydac_derham(Nel, p, spl_kind): zero3_STR = div_STR.dot(d2_STR) zero3_PSY = derham.div.dot(d2_PSY) - assert np.allclose(zero3_STR, np.zeros_like(zero3_STR)) + assert xp.allclose(zero3_STR, xp.zeros_like(zero3_STR)) if rank == 0: print("\nCompare div of curl:") compare_arrays(zero3_PSY, DR_STR.extract_3(zero3_STR), rank) @@ -203,7 +217,7 @@ def test_psydac_derham(Nel, p, spl_kind): # compare projectors def f(eta1, eta2, eta3): - return np.sin(4 * np.pi * eta1) * np.cos(2 * np.pi * eta2) + np.exp(np.cos(2 * np.pi * eta3)) + return xp.sin(4 * xp.pi * eta1) * xp.cos(2 * xp.pi * eta2) + xp.exp(xp.cos(2 * xp.pi * eta3)) fh0_STR = PI("0", f) fh0_PSY = derham.P["0"](f) diff --git a/src/struphy/feec/tests/test_eval_field.py b/src/struphy/feec/tests/test_eval_field.py index 9fb829942..f9a00c18d 100644 --- a/src/struphy/feec/tests/test_eval_field.py +++ b/src/struphy/feec/tests/test_eval_field.py @@ -1,9 +1,9 @@ -import numpy as np +import cunumpy as xp import pytest -from mpi4py import MPI +from psydac.ddm.mpi import MockComm +from psydac.ddm.mpi import mpi as MPI -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[3, 2, 4]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -53,31 +53,34 @@ def test_eval_field(Nel, p, spl_kind): uv.initialize_coeffs(perturbations=[pert_uv_1, pert_uv_2, pert_uv_3]) # evaluation points for meshgrid - eta1 = np.linspace(0, 1, 11) - eta2 = np.linspace(0, 1, 14) - eta3 = np.linspace(0, 1, 18) + eta1 = xp.linspace(0, 1, 11) + eta2 = xp.linspace(0, 1, 14) + eta3 = xp.linspace(0, 1, 18) # evaluation points for markers Np = 33 - markers = np.random.rand(Np, 3) - markers_1 = np.zeros((eta1.size, 3)) + markers = xp.random.rand(Np, 3) + markers_1 = xp.zeros((eta1.size, 3)) markers_1[:, 0] = eta1 - markers_2 = np.zeros((eta2.size, 3)) + markers_2 = xp.zeros((eta2.size, 3)) markers_2[:, 1] = eta2 - markers_3 = np.zeros((eta3.size, 3)) + markers_3 = xp.zeros((eta3.size, 3)) markers_3[:, 2] = eta3 # arrays for legacy evaluation arr1, arr2, arr3, is_sparse_meshgrid = Domain.prepare_eval_pts(eta1, eta2, eta3) - tmp = np.zeros_like(arr1) + tmp = xp.zeros_like(arr1) ###### # V0 # ###### # create legacy arrays with same coeffs - coeffs_loc = np.reshape(p0.vector.toarray(), p0.nbasis) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(p0.vector.toarray(), p0.nbasis) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(p0.vector, coeffs, rank) # legacy evaluation @@ -98,12 +101,12 @@ def test_eval_field(Nel, p, spl_kind): tmp, 0, ) - val_legacy = np.squeeze(tmp.copy()) + val_legacy = xp.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val = p0(eta1, eta2, eta3, squeeze_out=True) - assert np.allclose(val, val_legacy) + assert xp.allclose(val, val_legacy) # marker evaluation m_vals = p0(markers) @@ -116,17 +119,20 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = p0(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = p0(0.0, 0.0, eta3, squeeze_out=True) - assert np.allclose(m_vals_1, m_vals_ref_1) - assert np.allclose(m_vals_2, m_vals_ref_2) - assert np.allclose(m_vals_3, m_vals_ref_3) + assert xp.allclose(m_vals_1, m_vals_ref_1) + assert xp.allclose(m_vals_2, m_vals_ref_2) + assert xp.allclose(m_vals_3, m_vals_ref_3) ###### # V1 # ###### # create legacy arrays with same coeffs - coeffs_loc = np.reshape(E1.vector[0].toarray(), E1.nbasis[0]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(E1.vector[0].toarray(), E1.nbasis[0]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(E1.vector[0], coeffs, rank) # legacy evaluation @@ -147,13 +153,16 @@ def test_eval_field(Nel, p, spl_kind): tmp, 11, ) - val_legacy_1 = np.squeeze(tmp.copy()) + val_legacy_1 = xp.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = np.reshape(E1.vector[1].toarray(), E1.nbasis[1]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(E1.vector[1].toarray(), E1.nbasis[1]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(E1.vector[1], coeffs, rank) # legacy evaluation @@ -174,13 +183,16 @@ def test_eval_field(Nel, p, spl_kind): tmp, 12, ) - val_legacy_2 = np.squeeze(tmp.copy()) + val_legacy_2 = xp.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = np.reshape(E1.vector[2].toarray(), E1.nbasis[2]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(E1.vector[2].toarray(), E1.nbasis[2]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(E1.vector[2], coeffs, rank) # legacy evaluation @@ -201,14 +213,14 @@ def test_eval_field(Nel, p, spl_kind): tmp, 13, ) - val_legacy_3 = np.squeeze(tmp.copy()) + val_legacy_3 = xp.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val1, val2, val3 = E1(eta1, eta2, eta3, squeeze_out=True) - assert np.allclose(val1, val_legacy_1) - assert np.allclose(val2, val_legacy_2) - assert np.allclose(val3, val_legacy_3) + assert xp.allclose(val1, val_legacy_1) + assert xp.allclose(val2, val_legacy_2) + assert xp.allclose(val3, val_legacy_3) # marker evaluation m_vals = E1(markers) @@ -221,23 +233,26 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = E1(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = E1(0.0, 0.0, eta3, squeeze_out=True) - assert np.all( - [np.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)] + assert xp.all( + [xp.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)], ) - assert np.all( - [np.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)] + assert xp.all( + [xp.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)], ) - assert np.all( - [np.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)] + assert xp.all( + [xp.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)], ) ###### # V2 # ###### # create legacy arrays with same coeffs - coeffs_loc = np.reshape(B2.vector[0].toarray(), B2.nbasis[0]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(B2.vector[0].toarray(), B2.nbasis[0]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(B2.vector[0], coeffs, rank) # legacy evaluation @@ -258,13 +273,16 @@ def test_eval_field(Nel, p, spl_kind): tmp, 21, ) - val_legacy_1 = np.squeeze(tmp.copy()) + val_legacy_1 = xp.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = np.reshape(B2.vector[1].toarray(), B2.nbasis[1]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(B2.vector[1].toarray(), B2.nbasis[1]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(B2.vector[1], coeffs, rank) # legacy evaluation @@ -285,13 +303,16 @@ def test_eval_field(Nel, p, spl_kind): tmp, 22, ) - val_legacy_2 = np.squeeze(tmp.copy()) + val_legacy_2 = xp.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = np.reshape(B2.vector[2].toarray(), B2.nbasis[2]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(B2.vector[2].toarray(), B2.nbasis[2]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(B2.vector[2], coeffs, rank) # legacy evaluation @@ -312,14 +333,14 @@ def test_eval_field(Nel, p, spl_kind): tmp, 23, ) - val_legacy_3 = np.squeeze(tmp.copy()) + val_legacy_3 = xp.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val1, val2, val3 = B2(eta1, eta2, eta3, squeeze_out=True) - assert np.allclose(val1, val_legacy_1) - assert np.allclose(val2, val_legacy_2) - assert np.allclose(val3, val_legacy_3) + assert xp.allclose(val1, val_legacy_1) + assert xp.allclose(val2, val_legacy_2) + assert xp.allclose(val3, val_legacy_3) # marker evaluation m_vals = B2(markers) @@ -332,23 +353,26 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = B2(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = B2(0.0, 0.0, eta3, squeeze_out=True) - assert np.all( - [np.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)] + assert xp.all( + [xp.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)], ) - assert np.all( - [np.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)] + assert xp.all( + [xp.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)], ) - assert np.all( - [np.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)] + assert xp.all( + [xp.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)], ) ###### # V3 # ###### # create legacy arrays with same coeffs - coeffs_loc = np.reshape(n3.vector.toarray(), n3.nbasis) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(n3.vector.toarray(), n3.nbasis) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(n3.vector, coeffs, rank) # legacy evaluation @@ -369,12 +393,12 @@ def test_eval_field(Nel, p, spl_kind): tmp, 3, ) - val_legacy = np.squeeze(tmp.copy()) + val_legacy = xp.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val = n3(eta1, eta2, eta3, squeeze_out=True) - assert np.allclose(val, val_legacy) + assert xp.allclose(val, val_legacy) # marker evaluation m_vals = n3(markers) @@ -387,17 +411,20 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = n3(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = n3(0.0, 0.0, eta3, squeeze_out=True) - assert np.allclose(m_vals_1, m_vals_ref_1) - assert np.allclose(m_vals_2, m_vals_ref_2) - assert np.allclose(m_vals_3, m_vals_ref_3) + assert xp.allclose(m_vals_1, m_vals_ref_1) + assert xp.allclose(m_vals_2, m_vals_ref_2) + assert xp.allclose(m_vals_3, m_vals_ref_3) ######### # V0vec # ######### # create legacy arrays with same coeffs - coeffs_loc = np.reshape(uv.vector[0].toarray(), uv.nbasis[0]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(uv.vector[0].toarray(), uv.nbasis[0]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(uv.vector[0], coeffs, rank) # legacy evaluation @@ -418,13 +445,16 @@ def test_eval_field(Nel, p, spl_kind): tmp, 0, ) - val_legacy_1 = np.squeeze(tmp.copy()) + val_legacy_1 = xp.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = np.reshape(uv.vector[1].toarray(), uv.nbasis[1]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(uv.vector[1].toarray(), uv.nbasis[1]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(uv.vector[1], coeffs, rank) # legacy evaluation @@ -445,13 +475,16 @@ def test_eval_field(Nel, p, spl_kind): tmp, 0, ) - val_legacy_2 = np.squeeze(tmp.copy()) + val_legacy_2 = xp.squeeze(tmp.copy()) tmp[:] = 0 # create legacy arrays with same coeffs - coeffs_loc = np.reshape(uv.vector[2].toarray(), uv.nbasis[2]) - coeffs = np.zeros_like(coeffs_loc) - comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) + coeffs_loc = xp.reshape(uv.vector[2].toarray(), uv.nbasis[2]) + if isinstance(comm, MockComm): + coeffs = coeffs_loc + else: + coeffs = xp.zeros_like(coeffs_loc) + comm.Allreduce(coeffs_loc, coeffs, op=MPI.SUM) compare_arrays(uv.vector[2], coeffs, rank) # legacy evaluation @@ -472,14 +505,14 @@ def test_eval_field(Nel, p, spl_kind): tmp, 0, ) - val_legacy_3 = np.squeeze(tmp.copy()) + val_legacy_3 = xp.squeeze(tmp.copy()) tmp[:] = 0 # distributed evaluation and comparison val1, val2, val3 = uv(eta1, eta2, eta3, squeeze_out=True) - assert np.allclose(val1, val_legacy_1) - assert np.allclose(val2, val_legacy_2) - assert np.allclose(val3, val_legacy_3) + assert xp.allclose(val1, val_legacy_1) + assert xp.allclose(val2, val_legacy_2) + assert xp.allclose(val3, val_legacy_3) # marker evaluation m_vals = uv(markers) @@ -492,14 +525,14 @@ def test_eval_field(Nel, p, spl_kind): m_vals_ref_2 = uv(0.0, eta2, 0.0, squeeze_out=True) m_vals_ref_3 = uv(0.0, 0.0, eta3, squeeze_out=True) - assert np.all( - [np.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)] + assert xp.all( + [xp.allclose(m_vals_1_i, m_vals_ref_1_i) for m_vals_1_i, m_vals_ref_1_i in zip(m_vals_1, m_vals_ref_1)], ) - assert np.all( - [np.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)] + assert xp.all( + [xp.allclose(m_vals_2_i, m_vals_ref_2_i) for m_vals_2_i, m_vals_ref_2_i in zip(m_vals_2, m_vals_ref_2)], ) - assert np.all( - [np.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)] + assert xp.all( + [xp.allclose(m_vals_3_i, m_vals_ref_3_i) for m_vals_3_i, m_vals_ref_3_i in zip(m_vals_3, m_vals_ref_3)], ) print("\nAll assertions passed.") diff --git a/src/struphy/feec/tests/test_field_init.py b/src/struphy/feec/tests/test_field_init.py index a6d5ca815..2f0da1611 100644 --- a/src/struphy/feec/tests/test_field_init.py +++ b/src/struphy/feec/tests/test_field_init.py @@ -1,7 +1,6 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 10, 12]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [True, True, False]]) @@ -10,8 +9,8 @@ def test_bckgr_init_const(Nel, p, spl_kind, spaces, vec_comps): """Test field background initialization of "LogicalConst" with multiple fields in params.""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.io.options import FieldsBackground @@ -23,14 +22,14 @@ def test_bckgr_init_const(Nel, p, spl_kind, spaces, vec_comps): derham = Derham(Nel, p, spl_kind, comm=comm) # evaluation grids for comparisons - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) - meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) + meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") # test values - np.random.seed(1234) - val = np.random.rand() + xp.random.seed(1234) + val = xp.random.rand() if val > 0.5: val = int(val * 10) @@ -41,23 +40,22 @@ def test_bckgr_init_const(Nel, p, spl_kind, spaces, vec_comps): background = FieldsBackground(type="LogicalConst", values=(val,)) field.initialize_coeffs(backgrounds=background) print( - f"\n{rank = }, {space = }, after init:\n {np.max(np.abs(field(*meshgrids) - val)) = }", + f"\n{rank =}, {space =}, after init:\n {xp.max(xp.abs(field(*meshgrids) - val)) =}", ) # print(f'{field(*meshgrids) = }') - assert np.allclose(field(*meshgrids), val) + assert xp.allclose(field(*meshgrids), val) else: background = FieldsBackground(type="LogicalConst", values=(val, None, val)) field.initialize_coeffs(backgrounds=background) for j, val in enumerate(background.values): if val is not None: print( - f"\n{rank = }, {space = }, after init:\n {j = }, {np.max(np.abs(field(*meshgrids)[j] - val)) = }", + f"\n{rank =}, {space =}, after init:\n {j =}, {xp.max(xp.abs(field(*meshgrids)[j] - val)) =}", ) # print(f'{field(*meshgrids)[i] = }') - assert np.allclose(field(*meshgrids)[j], val) + assert xp.allclose(field(*meshgrids)[j], val) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[18, 24, 12]]) @pytest.mark.parametrize("p", [[1, 2, 1]]) @pytest.mark.parametrize("spl_kind", [[False, True, True]]) @@ -66,9 +64,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show import inspect - import numpy as np + import cunumpy as xp from matplotlib import pyplot as plt - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.fields_background import equils @@ -90,28 +88,28 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show bckgr_4 = FieldsBackground(type="FluidEquilibrium", variable="uv") # evaluation grids for comparisons - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) - meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) + meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") # test for key, val in inspect.getmembers(equils): if inspect.isclass(val) and val.__module__ == equils.__name__: - print(f"{key = }") + print(f"{key =}") if "DESC" in key and not with_desc: - print(f"Attention: {with_desc = }, DESC not tested here !!") + print(f"Attention: {with_desc =}, DESC not tested here !!") continue if "GVEC" in key and not with_gvec: - print(f"Attention: {with_gvec = }, GVEC not tested here !!") + print(f"Attention: {with_gvec =}, GVEC not tested here !!") continue mhd_equil = val() if not isinstance(mhd_equil, FluidEquilibriumWithB): continue - print(f"{mhd_equil.params = }") + print(f"{mhd_equil.params =}") if "AdhocTorus" in key: mhd_equil.domain = domains.HollowTorus( @@ -134,8 +132,8 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show elif "ShearedSlab" in key: mhd_equil.domain = domains.Cuboid( r1=mhd_equil.params["a"], - r2=mhd_equil.params["a"] * 2 * np.pi, - r3=mhd_equil.params["R0"] * 2 * np.pi, + r2=mhd_equil.params["a"] * 2 * xp.pi, + r3=mhd_equil.params["R0"] * 2 * xp.pi, ) elif "ShearFluid" in key: mhd_equil.domain = domains.Cuboid( @@ -147,7 +145,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show mhd_equil.domain = domains.HollowCylinder( a1=1e-3, a2=mhd_equil.params["a"], - Lz=mhd_equil.params["R0"] * 2 * np.pi, + Lz=mhd_equil.params["R0"] * 2 * xp.pi, ) else: try: @@ -188,57 +186,57 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show # scalar spaces print( - f"{np.max(np.abs(field_3(*meshgrids) - mhd_equil.p3(*meshgrids))) / np.max(np.abs(mhd_equil.p3(*meshgrids)))}" + f"{xp.max(xp.abs(field_3(*meshgrids) - mhd_equil.p3(*meshgrids))) / xp.max(xp.abs(mhd_equil.p3(*meshgrids)))}", ) assert ( - np.max( - np.abs(field_3(*meshgrids) - mhd_equil.p3(*meshgrids)), + xp.max( + xp.abs(field_3(*meshgrids) - mhd_equil.p3(*meshgrids)), ) - / np.max(np.abs(mhd_equil.p3(*meshgrids))) + / xp.max(xp.abs(mhd_equil.p3(*meshgrids))) < 0.54 ) if isinstance(mhd_equil, FluidEquilibriumWithB): print( - f"{np.max(np.abs(field_0(*meshgrids) - mhd_equil.absB0(*meshgrids))) / np.max(np.abs(mhd_equil.absB0(*meshgrids)))}" + f"{xp.max(xp.abs(field_0(*meshgrids) - mhd_equil.absB0(*meshgrids))) / xp.max(xp.abs(mhd_equil.absB0(*meshgrids)))}", ) assert ( - np.max( - np.abs(field_0(*meshgrids) - mhd_equil.absB0(*meshgrids)), + xp.max( + xp.abs(field_0(*meshgrids) - mhd_equil.absB0(*meshgrids)), ) - / np.max(np.abs(mhd_equil.absB0(*meshgrids))) + / xp.max(xp.abs(mhd_equil.absB0(*meshgrids))) < 0.057 ) print("Scalar asserts passed.") # vector-valued spaces ref = mhd_equil.u1(*meshgrids) - if np.max(np.abs(ref[0])) < 1e-11: + if xp.max(xp.abs(ref[0])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[0])) + denom = xp.max(xp.abs(ref[0])) print( - f"{np.max(np.abs(field_1(*meshgrids)[0] - ref[0])) / denom = }", + f"{xp.max(xp.abs(field_1(*meshgrids)[0] - ref[0])) / denom =}", ) - assert np.max(np.abs(field_1(*meshgrids)[0] - ref[0])) / denom < 0.28 - if np.max(np.abs(ref[1])) < 1e-11: + assert xp.max(xp.abs(field_1(*meshgrids)[0] - ref[0])) / denom < 0.28 + if xp.max(xp.abs(ref[1])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[1])) + denom = xp.max(xp.abs(ref[1])) print( - f"{np.max(np.abs(field_1(*meshgrids)[1] - ref[1])) / denom = }", + f"{xp.max(xp.abs(field_1(*meshgrids)[1] - ref[1])) / denom =}", ) - assert np.max(np.abs(field_1(*meshgrids)[1] - ref[1])) / denom < 0.33 - if np.max(np.abs(ref[2])) < 1e-11: + assert xp.max(xp.abs(field_1(*meshgrids)[1] - ref[1])) / denom < 0.33 + if xp.max(xp.abs(ref[2])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[2])) + denom = xp.max(xp.abs(ref[2])) print( - f"{np.max(np.abs(field_1(*meshgrids)[2] - ref[2])) / denom = }", + f"{xp.max(xp.abs(field_1(*meshgrids)[2] - ref[2])) / denom =}", ) assert ( - np.max( - np.abs( + xp.max( + xp.abs( field_1(*meshgrids)[2] - ref[2], ), ) @@ -248,75 +246,75 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show print("u1 asserts passed.") ref = mhd_equil.u2(*meshgrids) - if np.max(np.abs(ref[0])) < 1e-11: + if xp.max(xp.abs(ref[0])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[0])) + denom = xp.max(xp.abs(ref[0])) print( - f"{np.max(np.abs(field_2(*meshgrids)[0] - ref[0])) / denom = }", + f"{xp.max(xp.abs(field_2(*meshgrids)[0] - ref[0])) / denom =}", ) - assert np.max(np.abs(field_2(*meshgrids)[0] - ref[0])) / denom < 0.86 - if np.max(np.abs(ref[1])) < 1e-11: + assert xp.max(xp.abs(field_2(*meshgrids)[0] - ref[0])) / denom < 0.86 + if xp.max(xp.abs(ref[1])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[1])) + denom = xp.max(xp.abs(ref[1])) print( - f"{np.max(np.abs(field_2(*meshgrids)[1] - ref[1])) / denom = }", + f"{xp.max(xp.abs(field_2(*meshgrids)[1] - ref[1])) / denom =}", ) assert ( - np.max( - np.abs( + xp.max( + xp.abs( field_2(*meshgrids)[1] - ref[1], ), ) / denom < 0.4 ) - if np.max(np.abs(ref[2])) < 1e-11: + if xp.max(xp.abs(ref[2])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[2])) + denom = xp.max(xp.abs(ref[2])) print( - f"{np.max(np.abs(field_2(*meshgrids)[2] - ref[2])) / denom = }", + f"{xp.max(xp.abs(field_2(*meshgrids)[2] - ref[2])) / denom =}", ) - assert np.max(np.abs(field_2(*meshgrids)[2] - ref[2])) / denom < 0.21 + assert xp.max(xp.abs(field_2(*meshgrids)[2] - ref[2])) / denom < 0.21 print("u2 asserts passed.") ref = mhd_equil.uv(*meshgrids) - if np.max(np.abs(ref[0])) < 1e-11: + if xp.max(xp.abs(ref[0])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[0])) + denom = xp.max(xp.abs(ref[0])) print( - f"{np.max(np.abs(field_4(*meshgrids)[0] - ref[0])) / denom = }", + f"{xp.max(xp.abs(field_4(*meshgrids)[0] - ref[0])) / denom =}", ) - assert np.max(np.abs(field_4(*meshgrids)[0] - ref[0])) / denom < 0.6 - if np.max(np.abs(ref[1])) < 1e-11: + assert xp.max(xp.abs(field_4(*meshgrids)[0] - ref[0])) / denom < 0.6 + if xp.max(xp.abs(ref[1])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[1])) + denom = xp.max(xp.abs(ref[1])) print( - f"{np.max(np.abs(field_4(*meshgrids)[1] - ref[1])) / denom = }", + f"{xp.max(xp.abs(field_4(*meshgrids)[1] - ref[1])) / denom =}", ) assert ( - np.max( - np.abs( + xp.max( + xp.abs( field_4(*meshgrids)[1] - ref[1], ), ) / denom < 0.2 ) - if np.max(np.abs(ref[2])) < 1e-11: + if xp.max(xp.abs(ref[2])) < 1e-11: denom = 1.0 else: - denom = np.max(np.abs(ref[2])) + denom = xp.max(xp.abs(ref[2])) print( - f"{np.max(np.abs(field_4(*meshgrids)[2] - ref[2])) / denom = }", + f"{xp.max(xp.abs(field_4(*meshgrids)[2] - ref[2])) / denom =}", ) assert ( - np.max( - np.abs( + xp.max( + xp.abs( field_4(*meshgrids)[2] - ref[2], ), ) @@ -327,27 +325,27 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show # plotting fields with equilibrium if show_plot and rank == 0: - plt.figure(f"0/3-forms top, {mhd_equil = }", figsize=(24, 16)) + plt.figure(f"0/3-forms top, {mhd_equil =}", figsize=(24, 16)) plt.figure( - f"0/3-forms poloidal, {mhd_equil = }", + f"0/3-forms poloidal, {mhd_equil =}", figsize=(24, 16), ) - plt.figure(f"1-forms top, {mhd_equil = }", figsize=(24, 16)) + plt.figure(f"1-forms top, {mhd_equil =}", figsize=(24, 16)) plt.figure( - f"1-forms poloidal, {mhd_equil = }", + f"1-forms poloidal, {mhd_equil =}", figsize=(24, 16), ) - plt.figure(f"2-forms top, {mhd_equil = }", figsize=(24, 16)) + plt.figure(f"2-forms top, {mhd_equil =}", figsize=(24, 16)) plt.figure( - f"2-forms poloidal, {mhd_equil = }", + f"2-forms poloidal, {mhd_equil =}", figsize=(24, 16), ) plt.figure( - f"vector-fields top, {mhd_equil = }", + f"vector-fields top, {mhd_equil =}", figsize=(24, 16), ) plt.figure( - f"vector-fields poloidal, {mhd_equil = }", + f"vector-fields poloidal, {mhd_equil =}", figsize=(24, 16), ) x, y, z = mhd_equil.domain(*meshgrids) @@ -357,9 +355,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show absB0_h = mhd_equil.domain.push(field_0, *meshgrids) absB0 = mhd_equil.domain.push(mhd_equil.absB0, *meshgrids) - levels = np.linspace(np.min(absB0) - 1e-10, np.max(absB0), 20) + levels = xp.linspace(xp.min(absB0) - 1e-10, xp.max(absB0), 20) - plt.figure(f"0/3-forms top, {mhd_equil = }") + plt.figure(f"0/3-forms top, {mhd_equil =}") plt.subplot(2, 3, 1) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -445,7 +443,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"0/3-forms poloidal, {mhd_equil = }") + plt.figure(f"0/3-forms poloidal, {mhd_equil =}") plt.subplot(2, 3, 1) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -495,9 +493,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show p3_h = mhd_equil.domain.push(field_3, *meshgrids) p3 = mhd_equil.domain.push(mhd_equil.p3, *meshgrids) - levels = np.linspace(np.min(p3) - 1e-10, np.max(p3), 20) + levels = xp.linspace(xp.min(p3) - 1e-10, xp.max(p3), 20) - plt.figure(f"0/3-forms top, {mhd_equil = }") + plt.figure(f"0/3-forms top, {mhd_equil =}") plt.subplot(2, 3, 2) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -583,7 +581,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"0/3-forms poloidal, {mhd_equil = }") + plt.figure(f"0/3-forms poloidal, {mhd_equil =}") plt.subplot(2, 3, 2) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -642,9 +640,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show ) for i, (bh, b) in enumerate(zip(b1h, b1)): - levels = np.linspace(np.min(b) - 1e-10, np.max(b), 20) + levels = xp.linspace(xp.min(b) - 1e-10, xp.max(b), 20) - plt.figure(f"1-forms top, {mhd_equil = }") + plt.figure(f"1-forms top, {mhd_equil =}") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -730,7 +728,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"1-forms poloidal, {mhd_equil = }") + plt.figure(f"1-forms poloidal, {mhd_equil =}") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -791,9 +789,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show ) for i, (bh, b) in enumerate(zip(b2h, b2)): - levels = np.linspace(np.min(b) - 1e-10, np.max(b), 20) + levels = xp.linspace(xp.min(b) - 1e-10, xp.max(b), 20) - plt.figure(f"2-forms top, {mhd_equil = }") + plt.figure(f"2-forms top, {mhd_equil =}") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -879,7 +877,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"2-forms poloidal, {mhd_equil = }") + plt.figure(f"2-forms poloidal, {mhd_equil =}") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -940,9 +938,9 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show ) for i, (bh, b) in enumerate(zip(bvh, bv)): - levels = np.linspace(np.min(b) - 1e-10, np.max(b), 20) + levels = xp.linspace(xp.min(b) - 1e-10, xp.max(b), 20) - plt.figure(f"vector-fields top, {mhd_equil = }") + plt.figure(f"vector-fields top, {mhd_equil =}") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -1028,7 +1026,7 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.colorbar() plt.title("reference, top view (e1-e3)") - plt.figure(f"vector-fields poloidal, {mhd_equil = }") + plt.figure(f"vector-fields poloidal, {mhd_equil =}") plt.subplot(2, 3, 1 + i) if "Slab" in key or "Pinch" in key: plt.contourf( @@ -1079,16 +1077,15 @@ def test_bckgr_init_mhd(Nel, p, spl_kind, with_desc=False, with_gvec=False, show plt.show() -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[1, 32, 32]]) @pytest.mark.parametrize("p", [[1, 3, 3]]) @pytest.mark.parametrize("spl_kind", [[True, True, True]]) def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): """Test field perturbation with ModesSin + ModesCos on top of of "LogicalConst" with multiple fields in params.""" - import numpy as np + import cunumpy as xp from matplotlib import pyplot as plt - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.initial.perturbations import ModesCos, ModesSin @@ -1165,15 +1162,18 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): field_0 = derham.create_spline_function("name_0", "H1", backgrounds=bckgr_0, perturbations=[f_sin_0, f_cos_0]) field_1 = derham.create_spline_function( - "name_1", "Hcurl", backgrounds=bckgr_1, perturbations=[f_sin_11, f_sin_13, f_cos_11, f_cos_12] + "name_1", + "Hcurl", + backgrounds=bckgr_1, + perturbations=[f_sin_11, f_sin_13, f_cos_11, f_cos_12], ) field_2 = derham.create_spline_function("name_2", "Hdiv", backgrounds=bckgr_2, perturbations=[f_cos_22]) # evaluation grids for comparisons - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) - meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) + meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") fun_0 = avg_0 + f_sin_0(*meshgrids) + f_cos_0(*meshgrids) @@ -1192,24 +1192,24 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): f1_h = field_1(*meshgrids) f2_h = field_2(*meshgrids) - print(f"{np.max(np.abs(fun_0 - f0_h)) = }") - print(f"{np.max(np.abs(fun_1[0] - f1_h[0])) = }") - print(f"{np.max(np.abs(fun_1[1] - f1_h[1])) = }") - print(f"{np.max(np.abs(fun_1[2] - f1_h[2])) = }") - print(f"{np.max(np.abs(fun_2[0] - f2_h[0])) = }") - print(f"{np.max(np.abs(fun_2[1] - f2_h[1])) = }") - print(f"{np.max(np.abs(fun_2[2] - f2_h[2])) = }") - - assert np.max(np.abs(fun_0 - f0_h)) < 3e-5 - assert np.max(np.abs(fun_1[0] - f1_h[0])) < 3e-5 - assert np.max(np.abs(fun_1[1] - f1_h[1])) < 3e-5 - assert np.max(np.abs(fun_1[2] - f1_h[2])) < 3e-5 - assert np.max(np.abs(fun_2[0] - f2_h[0])) < 3e-5 - assert np.max(np.abs(fun_2[1] - f2_h[1])) < 3e-5 - assert np.max(np.abs(fun_2[2] - f2_h[2])) < 3e-5 + print(f"{xp.max(xp.abs(fun_0 - f0_h)) =}") + print(f"{xp.max(xp.abs(fun_1[0] - f1_h[0])) =}") + print(f"{xp.max(xp.abs(fun_1[1] - f1_h[1])) =}") + print(f"{xp.max(xp.abs(fun_1[2] - f1_h[2])) =}") + print(f"{xp.max(xp.abs(fun_2[0] - f2_h[0])) =}") + print(f"{xp.max(xp.abs(fun_2[1] - f2_h[1])) =}") + print(f"{xp.max(xp.abs(fun_2[2] - f2_h[2])) =}") + + assert xp.max(xp.abs(fun_0 - f0_h)) < 3e-5 + assert xp.max(xp.abs(fun_1[0] - f1_h[0])) < 3e-5 + assert xp.max(xp.abs(fun_1[1] - f1_h[1])) < 3e-5 + assert xp.max(xp.abs(fun_1[2] - f1_h[2])) < 3e-5 + assert xp.max(xp.abs(fun_2[0] - f2_h[0])) < 3e-5 + assert xp.max(xp.abs(fun_2[1] - f2_h[1])) < 3e-5 + assert xp.max(xp.abs(fun_2[2] - f2_h[2])) < 3e-5 if show_plot and rank == 0: - levels = np.linspace(np.min(fun_0) - 1e-10, np.max(fun_0), 40) + levels = xp.linspace(xp.min(fun_0) - 1e-10, xp.max(fun_0), 40) plt.figure("0-form", figsize=(10, 16)) plt.subplot(2, 1, 1) @@ -1242,7 +1242,7 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): plt.figure("1-form", figsize=(30, 16)) for i, (f_h, fun) in enumerate(zip(f1_h, fun_1)): - levels = np.linspace(np.min(fun) - 1e-10, np.max(fun), 40) + levels = xp.linspace(xp.min(fun) - 1e-10, xp.max(fun), 40) plt.subplot(2, 3, 1 + i) plt.contourf( @@ -1274,7 +1274,7 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): plt.figure("2-form", figsize=(30, 16)) for i, (f_h, fun) in enumerate(zip(f2_h, fun_2)): - levels = np.linspace(np.min(fun) - 1e-10, np.max(fun), 40) + levels = xp.linspace(xp.min(fun) - 1e-10, xp.max(fun), 40) plt.subplot(2, 3, 1 + i) plt.contourf( @@ -1307,7 +1307,6 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): plt.show() -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 10, 12]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [True, False, True]]) @@ -1316,8 +1315,8 @@ def test_sincos_init_const(Nel, p, spl_kind, show_plot=False): def test_noise_init(Nel, p, spl_kind, space, direction): """Only tests 1d noise ('e1', 'e2', 'e3') !!""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.feec.utilities import compare_arrays diff --git a/src/struphy/feec/tests/test_l2_projectors.py b/src/struphy/feec/tests/test_l2_projectors.py index e376d2d84..2e9f611eb 100644 --- a/src/struphy/feec/tests/test_l2_projectors.py +++ b/src/struphy/feec/tests/test_l2_projectors.py @@ -1,9 +1,9 @@ import inspect +import cunumpy as xp import matplotlib.pyplot as plt -import numpy as np import pytest -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.projectors import L2Projector @@ -11,12 +11,11 @@ from struphy.geometry import domains -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[16, 32, 1]]) @pytest.mark.parametrize("p", [[2, 1, 1], [3, 2, 1]]) @pytest.mark.parametrize("spl_kind", [[False, True, True]]) @pytest.mark.parametrize("array_input", [False, True]) -def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_desc, do_plot=False): +def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_gvec=False, with_desc=False, do_plot=False): """Tests the L2-projectors for all available mappings. Both callable and array inputs to the projectors are tested. @@ -29,7 +28,7 @@ def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_desc, do_plo derham = Derham(Nel, p, spl_kind, comm=comm) # constant function - f = lambda e1, e2, e3: np.sin(np.pi * e1) * np.cos(2 * np.pi * e2) + f = lambda e1, e2, e3: xp.sin(xp.pi * e1) * xp.cos(2 * xp.pi * e2) # create domain object dom_types = [] @@ -40,19 +39,23 @@ def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_desc, do_plo dom_classes += [val] # evaluation points - e1 = np.linspace(0.0, 1.0, 30) - e2 = np.linspace(0.0, 1.0, 40) + e1 = xp.linspace(0.0, 1.0, 30) + e2 = xp.linspace(0.0, 1.0, 40) e3 = 0.0 - ee1, ee2, ee3 = np.meshgrid(e1, e2, e3, indexing="ij") + ee1, ee2, ee3 = xp.meshgrid(e1, e2, e3, indexing="ij") for dom_type, dom_class in zip(dom_types, dom_classes): print("#" * 80) - print(f"Testing {dom_class = }") + print(f"Testing {dom_class =}") print("#" * 80) + if "GVEC" in dom_type and not with_gvec: + print(f"Attention: {with_gvec =}, GVEC not tested here !!") + continue + if "DESC" in dom_type and not with_desc: - print(f"Attention: {with_desc = }, DESC not tested here !!") + print(f"Attention: {with_desc =}, DESC not tested here !!") continue domain = dom_class() @@ -77,12 +80,12 @@ def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_desc, do_plo if array_input: pts_q = derham.quad_grid_pts[sp_key] if sp_id in ("H1", "L2"): - ee = np.meshgrid(*[pt.flatten() for pt in pts_q], indexing="ij") + ee = xp.meshgrid(*[pt.flatten() for pt in pts_q], indexing="ij") f_array = f(*ee) else: f_array = [] for pts in pts_q: - ee = np.meshgrid(*[pt.flatten() for pt in pts], indexing="ij") + ee = xp.meshgrid(*[pt.flatten() for pt in pts], indexing="ij") f_array += [f(*ee)] f_args = f_array else: @@ -92,31 +95,30 @@ def test_l2_projectors_mappings(Nel, p, spl_kind, array_input, with_desc, do_plo veco = P_L2(f_args, out=out) assert veco is out - assert np.all(vec.toarray() == veco.toarray()) + assert xp.all(vec.toarray() == veco.toarray()) field.vector = vec field_vals = field(e1, e2, e3) if sp_id in ("H1", "L2"): - err = np.max(np.abs(f_analytic(ee1, ee2, ee3) - field_vals)) + err = xp.max(xp.abs(f_analytic(ee1, ee2, ee3) - field_vals)) f_plot = field_vals else: - err = [np.max(np.abs(exact(ee1, ee2, ee3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] + err = [xp.max(xp.abs(exact(ee1, ee2, ee3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] f_plot = field_vals[0] - print(f"{sp_id = }, {np.max(err) = }") + print(f"{sp_id =}, {xp.max(err) =}") if sp_id in ("H1", "H1vec"): - assert np.max(err) < 0.004 + assert xp.max(err) < 0.004 else: - assert np.max(err) < 0.12 + assert xp.max(err) < 0.12 if do_plot and rank == 0: plt.figure(f"{dom_type}, {sp_id}") - plt.contourf(e1, e2, np.squeeze(f_plot[:, :, 0].T)) + plt.contourf(e1, e2, xp.squeeze(f_plot[:, :, 0].T)) plt.show() -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("direction", [0, 1, 2]) @pytest.mark.parametrize("pi", [1, 2]) @pytest.mark.parametrize("spl_kindi", [True, False]) @@ -136,7 +138,7 @@ def test_l2_projectors_convergence(direction, pi, spl_kindi, do_plot=False): for n, Neli in enumerate(Nels): # test function def fun(eta): - return np.cos(4 * np.pi * eta) + return xp.cos(4 * xp.pi * eta) # create derham object, test functions and evaluation points e1 = 0.0 @@ -146,7 +148,7 @@ def fun(eta): Nel = [Neli, 1, 1] p = [pi, 1, 1] spl_kind = [spl_kindi, True, True] - e1 = np.linspace(0.0, 1.0, 100) + e1 = xp.linspace(0.0, 1.0, 100) e = e1 c = 0 @@ -156,7 +158,7 @@ def f(x, y, z): Nel = [1, Neli, 1] p = [1, pi, 1] spl_kind = [True, spl_kindi, True] - e2 = np.linspace(0.0, 1.0, 100) + e2 = xp.linspace(0.0, 1.0, 100) e = e2 c = 1 @@ -166,7 +168,7 @@ def f(x, y, z): Nel = [1, 1, Neli] p = [1, 1, pi] spl_kind = [True, True, spl_kindi] - e3 = np.linspace(0.0, 1.0, 100) + e3 = xp.linspace(0.0, 1.0, 100) e = e3 c = 2 @@ -201,19 +203,19 @@ def f(x, y, z): vec = P_L2(f_analytic) veco = P_L2(f_analytic, out=out) assert veco is out - assert np.all(vec.toarray() == veco.toarray()) + assert xp.all(vec.toarray() == veco.toarray()) field.vector = vec field_vals = field(e1, e2, e3, squeeze_out=True) if sp_id in ("H1", "L2"): - err = np.max(np.abs(f_analytic(e1, e2, e3) - field_vals)) + err = xp.max(xp.abs(f_analytic(e1, e2, e3) - field_vals)) f_plot = field_vals else: - err = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] + err = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] f_plot = field_vals[0] - errors[sp_id] += [np.max(err)] + errors[sp_id] += [xp.max(err)] if do_plot: plt.figure(sp_id + ", L2-proj. convergence") @@ -234,8 +236,8 @@ def f(x, y, z): line_for_rate_p1 = [Ne ** (-rate_p1) * errors[sp_id][0] / Nels[0] ** (-rate_p1) for Ne in Nels] line_for_rate_p0 = [Ne ** (-rate_p0) * errors[sp_id][0] / Nels[0] ** (-rate_p0) for Ne in Nels] - m, _ = np.polyfit(np.log(Nels), np.log(errors[sp_id]), deg=1) - print(f"{sp_id = }, fitted convergence rate = {-m}, degree = {pi}") + m, _ = xp.polyfit(xp.log(Nels), xp.log(errors[sp_id]), deg=1) + print(f"{sp_id =}, fitted convergence rate = {-m}, degree = {pi}") if sp_id in ("H1", "H1vec"): assert -m > (pi + 1 - 0.05) else: @@ -249,7 +251,7 @@ def f(x, y, z): plt.loglog(Nels, line_for_rate_p0, "k--") plt.text(Nels[-2], line_for_rate_p1[-2], f"1/Nel^{rate_p1}") plt.text(Nels[-2], line_for_rate_p0[-2], f"1/Nel^{rate_p0}") - plt.title(f"{sp_id = }, degree = {pi}") + plt.title(f"{sp_id =}, degree = {pi}") plt.xlabel("Nel") if do_plot and rank == 0: @@ -262,5 +264,5 @@ def f(x, y, z): spl_kind = [False, True, True] array_input = True test_l2_projectors_mappings(Nel, p, spl_kind, array_input, do_plot=False, with_desc=False) - # test_l2_projectors_convergence(0, 1, True, do_plot=True) + test_l2_projectors_convergence(0, 1, True, do_plot=False) # test_l2_projectors_convergence(1, 1, False, do_plot=True) diff --git a/src/struphy/feec/tests/test_local_projectors.py b/src/struphy/feec/tests/test_local_projectors.py index 3a6216c9f..f51177a6a 100644 --- a/src/struphy/feec/tests/test_local_projectors.py +++ b/src/struphy/feec/tests/test_local_projectors.py @@ -1,10 +1,11 @@ import inspect import time +import cunumpy as xp import matplotlib.pyplot as plt -import numpy as np import pytest -from mpi4py import MPI +from psydac.ddm.mpi import MockComm +from psydac.ddm.mpi import mpi as MPI from struphy.bsplines.bsplines import basis_funs, find_span from struphy.bsplines.evaluation_kernels_1d import evaluation_kernel_1d @@ -14,53 +15,6 @@ from struphy.feec.utilities_local_projectors import get_one_spline, get_span_and_basis, get_values_and_indices_splines -def get_span_and_basis(pts, space): - """Compute the knot span index and the values of p + 1 basis function at each point in pts. - - Parameters - ---------- - pts : np.array - 2d array of points (ii, iq) = (interval, quadrature point). - - space : SplineSpace - Psydac object, the 1d spline space to be projected. - - Returns - ------- - span : np.array - 2d array indexed by (n, nq), where n is the interval and nq is the quadrature point in the interval. - - basis : np.array - 3d array of values of basis functions indexed by (n, nq, basis function). - """ - - import psydac.core.bsplines as bsp - - # Extract knot vectors, degree and kind of basis - T = space.knots - p = space.degree - - span = np.zeros(pts.shape, dtype=int) - basis = np.zeros((*pts.shape, p + 1), dtype=float) - - for n in range(pts.shape[0]): - for nq in range(pts.shape[1]): - # avoid 1. --> 0. for clamped interpolation - x = pts[n, nq] % (1.0 + 1e-14) - span_tmp = bsp.find_span(T, p, x) - basis[n, nq, :] = bsp.basis_funs_all_ders( - T, - p, - x, - span_tmp, - 0, - normalization=space.basis, - ) - span[n, nq] = span_tmp # % space.nbasis - - return span, basis - - @pytest.mark.parametrize("Nel", [[14, 16, 18]]) @pytest.mark.parametrize("p", [[5, 4, 3]]) @pytest.mark.parametrize("spl_kind", [[True, False, False], [False, True, False], [False, False, True]]) @@ -78,15 +32,15 @@ def test_local_projectors_compare_global(Nel, p, spl_kind): # constant function def f(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) * np.cos(4.0 * np.pi * e2) * np.sin(6.0 * np.pi * e3) + return xp.sin(2.0 * xp.pi * e1) * xp.cos(4.0 * xp.pi * e2) * xp.sin(6.0 * xp.pi * e3) - # f = lambda e1, e2, e3: np.sin(2.0*np.pi*e1) * np.cos(4.0*np.pi*e2) + # f = lambda e1, e2, e3: xp.sin(2.0*xp.pi*e1) * xp.cos(4.0*xp.pi*e2) # evaluation points - e1 = np.linspace(0.0, 1.0, 10) - e2 = np.linspace(0.0, 1.0, 9) - e3 = np.linspace(0.0, 1.0, 8) + e1 = xp.linspace(0.0, 1.0, 10) + e2 = xp.linspace(0.0, 1.0, 9) + e3 = xp.linspace(0.0, 1.0, 8) - ee1, ee2, ee3 = np.meshgrid(e1, e2, e3, indexing="ij") + ee1, ee2, ee3 = xp.meshgrid(e1, e2, e3, indexing="ij") # loop over spaces for sp_id, sp_key in derham.space_to_form.items(): @@ -125,32 +79,31 @@ def f(e1, e2, e3): fieldg_vals = fieldg(e1, e2, e3) if sp_id in ("H1", "L2"): - err = np.max(np.abs(f_analytic(ee1, ee2, ee3) - field_vals)) + err = xp.max(xp.abs(f_analytic(ee1, ee2, ee3) - field_vals)) # Error comparing the global and local projectors - errg = np.max(np.abs(fieldg_vals - field_vals)) + errg = xp.max(xp.abs(fieldg_vals - field_vals)) else: - err = np.zeros(3) - err[0] = np.max(np.abs(f(ee1, ee2, ee3) - field_vals[0])) - err[1] = np.max(np.abs(f(ee1, ee2, ee3) - field_vals[1])) - err[2] = np.max(np.abs(f(ee1, ee2, ee3) - field_vals[2])) + err = xp.zeros(3) + err[0] = xp.max(xp.abs(f(ee1, ee2, ee3) - field_vals[0])) + err[1] = xp.max(xp.abs(f(ee1, ee2, ee3) - field_vals[1])) + err[2] = xp.max(xp.abs(f(ee1, ee2, ee3) - field_vals[2])) # Error comparing the global and local projectors - errg = np.zeros(3) - errg[0] = np.max(np.abs(fieldg_vals[0] - field_vals[0])) - errg[1] = np.max(np.abs(fieldg_vals[1] - field_vals[1])) - errg[2] = np.max(np.abs(fieldg_vals[2] - field_vals[2])) + errg = xp.zeros(3) + errg[0] = xp.max(xp.abs(fieldg_vals[0] - field_vals[0])) + errg[1] = xp.max(xp.abs(fieldg_vals[1] - field_vals[1])) + errg[2] = xp.max(xp.abs(fieldg_vals[2] - field_vals[2])) - print(f"{sp_id = }, {np.max(err) = }, {np.max(errg) = },{exectime = }") + print(f"{sp_id =}, {xp.max(err) =}, {xp.max(errg) =},{exectime =}") if sp_id in ("H1", "H1vec"): - assert np.max(err) < 0.011 - assert np.max(errg) < 0.011 + assert xp.max(err) < 0.011 + assert xp.max(errg) < 0.011 else: - assert np.max(err) < 0.1 - assert np.max(errg) < 0.1 + assert xp.max(err) < 0.1 + assert xp.max(errg) < 0.1 -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("direction", [0, 1, 2]) @pytest.mark.parametrize("pi", [3, 4]) @pytest.mark.parametrize("spl_kindi", [True, False]) @@ -173,7 +126,7 @@ def test_local_projectors_convergence(direction, pi, spl_kindi, do_plot=False): for n, Neli in enumerate(Nels): # test function def fun(eta): - return np.cos(4 * np.pi * eta) + return xp.cos(4 * xp.pi * eta) # create derham object, test functions and evaluation points e1 = 0.0 @@ -183,7 +136,7 @@ def fun(eta): Nel = [Neli, 1, 1] p = [pi, 1, 1] spl_kind = [spl_kindi, True, True] - e1 = np.linspace(0.0, 1.0, 100) + e1 = xp.linspace(0.0, 1.0, 100) e = e1 c = 0 @@ -193,7 +146,7 @@ def f(x, y, z): Nel = [1, Neli, 1] p = [1, pi, 1] spl_kind = [True, spl_kindi, True] - e2 = np.linspace(0.0, 1.0, 100) + e2 = xp.linspace(0.0, 1.0, 100) e = e2 c = 1 @@ -203,7 +156,7 @@ def f(x, y, z): Nel = [1, 1, Neli] p = [1, 1, pi] spl_kind = [True, True, spl_kindi] - e3 = np.linspace(0.0, 1.0, 100) + e3 = xp.linspace(0.0, 1.0, 100) e = e3 c = 2 @@ -232,13 +185,13 @@ def f(x, y, z): field_vals = field(e1, e2, e3, squeeze_out=True) if sp_id in ("H1", "L2"): - err = np.max(np.abs(f_analytic(e1, e2, e3) - field_vals)) + err = xp.max(xp.abs(f_analytic(e1, e2, e3) - field_vals)) f_plot = field_vals else: - err = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] + err = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(f_analytic, field_vals)] f_plot = field_vals[0] - errors[sp_id] += [np.max(err)] + errors[sp_id] += [xp.max(err)] if do_plot: plt.figure(sp_id + ", Local-proj. convergence") @@ -257,21 +210,21 @@ def f(x, y, z): line_for_rate_p1 = [Ne ** (-rate_p1) * errors[sp_id][0] / Nels[0] ** (-rate_p1) for Ne in Nels] line_for_rate_p0 = [Ne ** (-rate_p0) * errors[sp_id][0] / Nels[0] ** (-rate_p0) for Ne in Nels] - m, _ = np.polyfit(np.log(Nels), np.log(errors[sp_id]), deg=1) + m, _ = xp.polyfit(xp.log(Nels), xp.log(errors[sp_id]), deg=1) if sp_id in ("H1", "H1vec"): # Sometimes for very large number of elements the convergance rate falls of a bit since the error is already so small floating point impressions become relevant # for those cases is better to compute the convergance rate using only the information of Nel with smaller number if -m <= (pi + 1 - 0.1): - m = -np.log2(errors[sp_id][1] / errors[sp_id][2]) - print(f"{sp_id = }, fitted convergence rate = {-m}, degree = {pi}") + m = -xp.log2(errors[sp_id][1] / errors[sp_id][2]) + print(f"{sp_id =}, fitted convergence rate = {-m}, degree = {pi}") assert -m > (pi + 1 - 0.1) else: # Sometimes for very large number of elements the convergance rate falls of a bit since the error is already so small floating point impressions become relevant # for those cases is better to compute the convergance rate using only the information of Nel with smaller number if -m <= (pi - 0.1): - m = -np.log2(errors[sp_id][1] / errors[sp_id][2]) - print(f"{sp_id = }, fitted convergence rate = {-m}, degree = {pi}") + m = -xp.log2(errors[sp_id][1] / errors[sp_id][2]) + print(f"{sp_id =}, fitted convergence rate = {-m}, degree = {pi}") assert -m > (pi - 0.1) if do_plot: @@ -282,7 +235,7 @@ def f(x, y, z): plt.loglog(Nels, line_for_rate_p0, "k--") plt.text(Nels[-2], line_for_rate_p1[-2], f"1/Nel^{rate_p1}") plt.text(Nels[-2], line_for_rate_p0[-2], f"1/Nel^{rate_p0}") - plt.title(f"{sp_id = }, degree = {pi}") + plt.title(f"{sp_id =}, degree = {pi}") plt.xlabel("Nel") if do_plot and rank == 0: @@ -315,12 +268,12 @@ def aux_test_replication_of_basis(Nel, plist, spl_kind): def make_basis_fun(i): def fun(etas, eta2, eta3): if isinstance(etas, float) or isinstance(etas, int): - etas = np.array([etas]) - out = np.zeros_like(etas) + etas = xp.array([etas]) + out = xp.zeros_like(etas) for j, eta in enumerate(etas): span = find_span(T, p, eta) - inds = np.arange(span - p, span + 1) % N - pos = np.argwhere(inds == i) + inds = xp.arange(span - p, span + 1) % N + pos = xp.argwhere(inds == i) # print(f'{pos = }') if pos.size > 0: pos = pos[0, 0] @@ -335,18 +288,18 @@ def fun(etas, eta2, eta3): fun = make_basis_fun(j) lambdas = P_Loc(fun).toarray() - etas = np.linspace(0.0, 1.0, 100) - fun_h = np.zeros(100) + etas = xp.linspace(0.0, 1.0, 100) + fun_h = xp.zeros(100) for k, eta in enumerate(etas): span = find_span(T, p, eta) - ind1 = np.arange(span - p, span + 1) % N + ind1 = xp.arange(span - p, span + 1) % N basis = basis_funs(T, p, eta, span, normalize=normalize) fun_h[k] = evaluation_kernel_1d(p, basis, ind1, lambdas) - if np.max(np.abs(fun(etas, 0.0, 0.0) - fun_h)) >= 10.0**-10: - print(np.max(np.abs(fun(etas, 0.0, 0.0) - fun_h))) - assert np.max(np.abs(fun(etas, 0.0, 0.0) - fun_h)) < 10.0**-10 - # print(f'{j = }, max error: {np.max(np.abs(fun(etas,0.0,0.0) - fun_h))}') + if xp.max(xp.abs(fun(etas, 0.0, 0.0) - fun_h)) >= 10.0**-10: + print(xp.max(xp.abs(fun(etas, 0.0, 0.0) - fun_h))) + assert xp.max(xp.abs(fun(etas, 0.0, 0.0) - fun_h)) < 10.0**-10 + # print(f'{j = }, max error: {xp.max(xp.abs(fun(etas,0.0,0.0) - fun_h))}') # For D-splines @@ -421,7 +374,7 @@ def test_basis_projection_operator_local(Nel, plist, spl_kind, out_sp_key, in_sp # Helper function to handle reshaping and getting spans and basis def process_eta(eta, w1d): if isinstance(eta, (float, int)): - eta = np.array([eta]) + eta = xp.array([eta]) if len(eta.shape) == 1: eta = eta.reshape((eta.shape[0], 1)) spans, values = get_span_and_basis(eta, w1d) @@ -434,7 +387,7 @@ def fun(eta1, eta2, eta3): eta = eta_map[dim_idx] w1d = W1ds[0][dim_idx] if is_B else V1ds[0][dim_idx] - out = np.zeros_like(eta) + out = xp.zeros_like(eta) for j1 in range(eta.shape[0]): for j2 in range(eta.shape[1]): for j3 in range(eta.shape[2]): @@ -477,21 +430,21 @@ def fun(eta1, eta2, eta3): if out_sp_key == "0" or out_sp_key == "3": npts_out = derham.Vh[out_sp_key].npts - starts = np.array(out.starts, dtype=int) - ends = np.array(out.ends, dtype=int) - pds = np.array(out.pads, dtype=int) + starts = xp.array(out.starts, dtype=int) + ends = xp.array(out.ends, dtype=int) + pds = xp.array(out.pads, dtype=int) VFEM1ds = [VFEM.spaces] - nbasis_out = np.array([VFEM1ds[0][0].nbasis, VFEM1ds[0][1].nbasis, VFEM1ds[0][2].nbasis]) + nbasis_out = xp.array([VFEM1ds[0][0].nbasis, VFEM1ds[0][1].nbasis, VFEM1ds[0][2].nbasis]) else: - npts_out = np.array([sp.npts for sp in P_Loc.coeff_space.spaces]) - pds = np.array([vi.pads for vi in P_Loc.coeff_space.spaces]) - starts = np.array([vi.starts for vi in P_Loc.coeff_space.spaces]) - ends = np.array([vi.ends for vi in P_Loc.coeff_space.spaces]) - starts = np.array(starts, dtype=int) - ends = np.array(ends, dtype=int) - pds = np.array(pds, dtype=int) + npts_out = xp.array([sp.npts for sp in P_Loc.coeff_space.spaces]) + pds = xp.array([vi.pads for vi in P_Loc.coeff_space.spaces]) + starts = xp.array([vi.starts for vi in P_Loc.coeff_space.spaces]) + ends = xp.array([vi.ends for vi in P_Loc.coeff_space.spaces]) + starts = xp.array(starts, dtype=int) + ends = xp.array(ends, dtype=int) + pds = xp.array(pds, dtype=int) VFEM1ds = [comp.spaces for comp in VFEM.spaces] - nbasis_out = np.array( + nbasis_out = xp.array( [ [VFEM1ds[0][0].nbasis, VFEM1ds[0][1].nbasis, VFEM1ds[0][2].nbasis], [ @@ -500,13 +453,13 @@ def fun(eta1, eta2, eta3): VFEM1ds[1][2].nbasis, ], [VFEM1ds[2][0].nbasis, VFEM1ds[2][1].nbasis, VFEM1ds[2][2].nbasis], - ] + ], ) if in_sp_key == "0" or in_sp_key == "3": npts_in = derham.Vh[in_sp_key].npts else: - npts_in = np.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + npts_in = xp.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) def define_basis(in_sp_key): def wrapper(dim, index, h=None): @@ -556,13 +509,13 @@ def basis3(i3, h=None): input[random_i0, random_i1, random_i2] = 1.0 input.update_ghost_regions() else: - npts_in = np.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + npts_in = xp.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) random_h = random.randrange(0, 3) random_i0 = random.randrange(0, npts_in[random_h][0]) random_i1 = random.randrange(0, npts_in[random_h][1]) random_i2 = random.randrange(0, npts_in[random_h][2]) - starts_in = np.array([sp.starts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) - ends_in = np.array([sp.ends for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + starts_in = xp.array([sp.starts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + ends_in = xp.array([sp.ends for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) if starts_in[random_h][0] <= random_i0 and random_i0 <= ends_in[random_h][0]: input[random_h][random_i0, random_i1, random_i2] = 1.0 input.update_ghost_regions() @@ -570,84 +523,84 @@ def basis3(i3, h=None): # We define the matrix if out_sp_key == "0" or out_sp_key == "3": if in_sp_key == "0" or in_sp_key == "3": - matrix = np.zeros((npts_out[0] * npts_out[1] * npts_out[2], npts_in[0] * npts_in[1] * npts_in[2])) + matrix = xp.zeros((npts_out[0] * npts_out[1] * npts_out[2], npts_in[0] * npts_in[1] * npts_in[2])) else: - matrix = np.zeros( + matrix = xp.zeros( ( npts_out[0] * npts_out[1] * npts_out[2], npts_in[0][0] * npts_in[0][1] * npts_in[0][2] + npts_in[1][0] * npts_in[1][1] * npts_in[1][2] + npts_in[2][0] * npts_in[2][1] * npts_in[2][2], - ) + ), ) else: if in_sp_key == "0" or in_sp_key == "3": - matrix0 = np.zeros((npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[0] * npts_in[1] * npts_in[2])) - matrix1 = np.zeros((npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[0] * npts_in[1] * npts_in[2])) - matrix2 = np.zeros((npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[0] * npts_in[1] * npts_in[2])) + matrix0 = xp.zeros((npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[0] * npts_in[1] * npts_in[2])) + matrix1 = xp.zeros((npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[0] * npts_in[1] * npts_in[2])) + matrix2 = xp.zeros((npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[0] * npts_in[1] * npts_in[2])) else: - matrix00 = np.zeros( + matrix00 = xp.zeros( ( npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[0][0] * npts_in[0][1] * npts_in[0][2], - ) + ), ) - matrix10 = np.zeros( + matrix10 = xp.zeros( ( npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[0][0] * npts_in[0][1] * npts_in[0][2], - ) + ), ) - matrix20 = np.zeros( + matrix20 = xp.zeros( ( npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[0][0] * npts_in[0][1] * npts_in[0][2], - ) + ), ) - matrix01 = np.zeros( + matrix01 = xp.zeros( ( npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[1][0] * npts_in[1][1] * npts_in[1][2], - ) + ), ) - matrix11 = np.zeros( + matrix11 = xp.zeros( ( npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[1][0] * npts_in[1][1] * npts_in[1][2], - ) + ), ) - matrix21 = np.zeros( + matrix21 = xp.zeros( ( npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[1][0] * npts_in[1][1] * npts_in[1][2], - ) + ), ) - matrix02 = np.zeros( + matrix02 = xp.zeros( ( npts_out[0][0] * npts_out[0][1] * npts_out[0][2], npts_in[2][0] * npts_in[2][1] * npts_in[2][2], - ) + ), ) - matrix12 = np.zeros( + matrix12 = xp.zeros( ( npts_out[1][0] * npts_out[1][1] * npts_out[1][2], npts_in[2][0] * npts_in[2][1] * npts_in[2][2], - ) + ), ) - matrix22 = np.zeros( + matrix22 = xp.zeros( ( npts_out[2][0] * npts_out[2][1] * npts_out[2][2], npts_in[2][0] * npts_in[2][1] * npts_in[2][2], - ) + ), ) # We build the BasisProjectionOperator by hand if out_sp_key == "0" or out_sp_key == "3": if in_sp_key == "0" or in_sp_key == "3": - # def f_analytic(e1,e2,e3): return (np.sin(2.0*np.pi*e1)+np.cos(4.0*np.pi*e2))*basis1(random_i0)(e1,e2,e3)*basis2(random_i1)(e1,e2,e3)*basis3(random_i2)(e1,e2,e3) + # def f_analytic(e1,e2,e3): return (xp.sin(2.0*xp.pi*e1)+xp.cos(4.0*xp.pi*e2))*basis1(random_i0)(e1,e2,e3)*basis2(random_i1)(e1,e2,e3)*basis3(random_i2)(e1,e2,e3) # out = P_Loc(f_analytic) counter = 0 @@ -657,7 +610,7 @@ def basis3(i3, h=None): def f_analytic(e1, e2, e3): return ( - (np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2)) + (xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2)) * basis1(col0)(e1, e2, e3) * basis2(col1)(e1, e2, e3) * basis3(col2)(e1, e2, e3) @@ -677,7 +630,7 @@ def f_analytic(e1, e2, e3): def f_analytic(e1, e2, e3): return ( - (np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2)) + (xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -696,7 +649,7 @@ def f_analytic(e1, e2, e3): def f_analytic1(e1, e2, e3): return ( - (np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2)) + (xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2)) * basis1(col0)(e1, e2, e3) * basis2(col1)(e1, e2, e3) * basis3(col2)(e1, e2, e3) @@ -704,7 +657,7 @@ def f_analytic1(e1, e2, e3): def f_analytic2(e1, e2, e3): return ( - (np.cos(2.0 * np.pi * e2) + np.cos(6.0 * np.pi * e3)) + (xp.cos(2.0 * xp.pi * e2) + xp.cos(6.0 * xp.pi * e3)) * basis1(col0)(e1, e2, e3) * basis2(col1)(e1, e2, e3) * basis3(col2)(e1, e2, e3) @@ -712,7 +665,7 @@ def f_analytic2(e1, e2, e3): def f_analytic3(e1, e2, e3): return ( - (np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e3)) + (xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e3)) * basis1(col0)(e1, e2, e3) * basis2(col1)(e1, e2, e3) * basis3(col2)(e1, e2, e3) @@ -724,7 +677,7 @@ def f_analytic3(e1, e2, e3): fill_matrix_column(starts[2], ends[2], pds[2], counter, nbasis_out[2], matrix2, out[2]._data) counter += 1 - matrix = np.vstack((matrix0, matrix1, matrix2)) + matrix = xp.vstack((matrix0, matrix1, matrix2)) else: for h in range(3): @@ -736,7 +689,7 @@ def f_analytic3(e1, e2, e3): def f_analytic0(e1, e2, e3): return ( - (np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2)) + (xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -744,7 +697,7 @@ def f_analytic0(e1, e2, e3): def f_analytic1(e1, e2, e3): return ( - (np.sin(10.0 * np.pi * e1) + np.cos(41.0 * np.pi * e2)) + (xp.sin(10.0 * xp.pi * e1) + xp.cos(41.0 * xp.pi * e2)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -752,7 +705,7 @@ def f_analytic1(e1, e2, e3): def f_analytic2(e1, e2, e3): return ( - (np.sin(25.0 * np.pi * e1) + np.cos(49.0 * np.pi * e2)) + (xp.sin(25.0 * xp.pi * e1) + xp.cos(49.0 * xp.pi * e2)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -762,7 +715,7 @@ def f_analytic2(e1, e2, e3): def f_analytic0(e1, e2, e3): return ( - (np.cos(2.0 * np.pi * e2) + np.cos(6.0 * np.pi * e3)) + (xp.cos(2.0 * xp.pi * e2) + xp.cos(6.0 * xp.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -770,7 +723,7 @@ def f_analytic0(e1, e2, e3): def f_analytic1(e1, e2, e3): return ( - (np.cos(12.0 * np.pi * e2) + np.cos(62.0 * np.pi * e3)) + (xp.cos(12.0 * xp.pi * e2) + xp.cos(62.0 * xp.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -778,7 +731,7 @@ def f_analytic1(e1, e2, e3): def f_analytic2(e1, e2, e3): return ( - (np.cos(25.0 * np.pi * e2) + np.cos(68.0 * np.pi * e3)) + (xp.cos(25.0 * xp.pi * e2) + xp.cos(68.0 * xp.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -787,7 +740,7 @@ def f_analytic2(e1, e2, e3): def f_analytic0(e1, e2, e3): return ( - (np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e3)) + (xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -795,7 +748,7 @@ def f_analytic0(e1, e2, e3): def f_analytic1(e1, e2, e3): return ( - (np.sin(16.0 * np.pi * e1) + np.sin(43.0 * np.pi * e3)) + (xp.sin(16.0 * xp.pi * e1) + xp.sin(43.0 * xp.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -803,7 +756,7 @@ def f_analytic1(e1, e2, e3): def f_analytic2(e1, e2, e3): return ( - (np.sin(65.0 * np.pi * e1) + np.sin(47.0 * np.pi * e3)) + (xp.sin(65.0 * xp.pi * e1) + xp.sin(47.0 * xp.pi * e3)) * basis1(col0, h)(e1, e2, e3) * basis2(col1, h)(e1, e2, e3) * basis3(col2, h)(e1, e2, e3) @@ -898,23 +851,23 @@ def f_analytic2(e1, e2, e3): ) counter += 1 - matrix0 = np.hstack((matrix00, matrix01, matrix02)) - matrix1 = np.hstack((matrix10, matrix11, matrix12)) - matrix2 = np.hstack((matrix20, matrix21, matrix22)) - matrix = np.vstack((matrix0, matrix1, matrix2)) + matrix0 = xp.hstack((matrix00, matrix01, matrix02)) + matrix1 = xp.hstack((matrix10, matrix11, matrix12)) + matrix2 = xp.hstack((matrix20, matrix21, matrix22)) + matrix = xp.vstack((matrix0, matrix1, matrix2)) # Now we build the same matrix using the BasisProjectionOperatorLocal if out_sp_key == "0" or out_sp_key == "3": if in_sp_key == "0" or in_sp_key == "3": def f_analytic(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2) + return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2) matrix_new = BasisProjectionOperatorLocal(P_Loc, derham.Vh_fem[in_sp_key], [[f_analytic]], transposed=False) else: def f_analytic(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2) + return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -929,13 +882,13 @@ def f_analytic(e1, e2, e3): if in_sp_key == "0" or in_sp_key == "3": def f_analytic1(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2) + return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2) def f_analytic2(e1, e2, e3): - return np.cos(2.0 * np.pi * e2) + np.cos(6.0 * np.pi * e3) + return xp.cos(2.0 * xp.pi * e2) + xp.cos(6.0 * xp.pi * e3) def f_analytic3(e1, e2, e3): - return np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e3) + return xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e3) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -952,31 +905,31 @@ def f_analytic3(e1, e2, e3): else: def f_analytic00(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e2) + return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e2) def f_analytic01(e1, e2, e3): - return np.cos(2.0 * np.pi * e2) + np.cos(6.0 * np.pi * e3) + return xp.cos(2.0 * xp.pi * e2) + xp.cos(6.0 * xp.pi * e3) def f_analytic02(e1, e2, e3): - return np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e3) + return xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e3) def f_analytic10(e1, e2, e3): - return np.sin(10.0 * np.pi * e1) + np.cos(41.0 * np.pi * e2) + return xp.sin(10.0 * xp.pi * e1) + xp.cos(41.0 * xp.pi * e2) def f_analytic11(e1, e2, e3): - return np.cos(12.0 * np.pi * e2) + np.cos(62.0 * np.pi * e3) + return xp.cos(12.0 * xp.pi * e2) + xp.cos(62.0 * xp.pi * e3) def f_analytic12(e1, e2, e3): - return np.sin(16.0 * np.pi * e1) + np.sin(43.0 * np.pi * e3) + return xp.sin(16.0 * xp.pi * e1) + xp.sin(43.0 * xp.pi * e3) def f_analytic20(e1, e2, e3): - return np.sin(25.0 * np.pi * e1) + np.cos(49.0 * np.pi * e2) + return xp.sin(25.0 * xp.pi * e1) + xp.cos(49.0 * xp.pi * e2) def f_analytic21(e1, e2, e3): - return np.cos(25.0 * np.pi * e2) + np.cos(68.0 * np.pi * e3) + return xp.cos(25.0 * xp.pi * e2) + xp.cos(68.0 * xp.pi * e3) def f_analytic22(e1, e2, e3): - return np.sin(65.0 * np.pi * e1) + np.sin(47.0 * np.pi * e3) + return xp.sin(65.0 * xp.pi * e1) + xp.sin(47.0 * xp.pi * e3) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -993,7 +946,7 @@ def f_analytic22(e1, e2, e3): transposed=False, ) - compare_arrays(matrix_new.dot(v), np.matmul(matrix, varr), rank) + compare_arrays(matrix_new.dot(v), xp.matmul(matrix, varr), rank) print("BasisProjectionOperatorLocal test passed.") @@ -1029,7 +982,7 @@ def test_basis_projection_operator_local_new(Nel, plist, spl_kind, out_sp_key, i # Helper function to handle reshaping and getting spans and basis def process_eta(eta, w1d): if isinstance(eta, (float, int)): - eta = np.array([eta]) + eta = xp.array([eta]) if len(eta.shape) == 1: eta = eta.reshape((eta.shape[0], 1)) spans, values = get_span_and_basis(eta, w1d) @@ -1042,7 +995,7 @@ def fun(eta1, eta2, eta3): eta = eta_map[dim_idx] w1d = W1ds[0][dim_idx] if is_B else V1ds[0][dim_idx] - out = np.zeros_like(eta) + out = xp.zeros_like(eta) for j1 in range(eta.shape[0]): for j2 in range(eta.shape[1]): for j3 in range(eta.shape[2]): @@ -1119,22 +1072,22 @@ def basis3(i3, h=None): input[random_i0, random_i1, random_i2] = 1.0 input.update_ghost_regions() else: - npts_in = np.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + npts_in = xp.array([sp.npts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) random_h = random.randrange(0, 3) random_i0 = random.randrange(0, npts_in[random_h][0]) random_i1 = random.randrange(0, npts_in[random_h][1]) random_i2 = random.randrange(0, npts_in[random_h][2]) - starts = np.array([sp.starts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) - ends = np.array([sp.ends for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + starts = xp.array([sp.starts for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) + ends = xp.array([sp.ends for sp in derham.Vh_fem[in_sp_key].coeff_space.spaces]) if starts[random_h][0] <= random_i0 and random_i0 <= ends[random_h][0]: input[random_h][random_i0, random_i1, random_i2] = 1.0 input.update_ghost_regions() - etas1 = np.linspace(0.0, 1.0, 1000) - etas2 = np.array([0.5]) + etas1 = xp.linspace(0.0, 1.0, 1000) + etas2 = xp.array([0.5]) - etas3 = np.array([0.5]) - meshgrid = np.meshgrid(*[etas1, etas2, etas3], indexing="ij") + etas3 = xp.array([0.5]) + meshgrid = xp.meshgrid(*[etas1, etas2, etas3], indexing="ij") # Now we build the same matrix using the BasisProjectionOperatorLocal and BasisProjectionOperator @@ -1142,7 +1095,7 @@ def basis3(i3, h=None): if in_sp_key == "0" or in_sp_key == "3": def f_analytic(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) + np.sin(4.0 * np.pi * e1) + return xp.sin(2.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e1) matrix_new = BasisProjectionOperatorLocal(P_Loc, derham.Vh_fem[in_sp_key], [[f_analytic]], transposed=False) matrix_global = BasisProjectionOperator(P, derham.Vh_fem[in_sp_key], [[f_analytic]], transposed=False) @@ -1156,7 +1109,7 @@ def f_analytic(e1, e2, e3): else: def f_analytic(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) + return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -1186,13 +1139,13 @@ def f_analytic(e1, e2, e3): if in_sp_key == "0" or in_sp_key == "3": def f_analytic1(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) + return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) def f_analytic2(e1, e2, e3): - return np.cos(2.0 * np.pi * e1) + np.cos(6.0 * np.pi * e1) + return xp.cos(2.0 * xp.pi * e1) + xp.cos(6.0 * xp.pi * e1) def f_analytic3(e1, e2, e3): - return np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e1) + return xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e1) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -1219,7 +1172,7 @@ def f_analytic3(e1, e2, e3): transposed=False, ) - analytic_vals = np.array( + analytic_vals = xp.array( [ f_analytic1(*meshgrid) * basis1(random_i0)(*meshgrid) @@ -1233,36 +1186,36 @@ def f_analytic3(e1, e2, e3): * basis1(random_i0)(*meshgrid) * basis2(random_i1)(*meshgrid) * basis3(random_i2)(*meshgrid), - ] + ], ) else: def f_analytic00(e1, e2, e3): - return np.sin(2.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) + return xp.sin(2.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) def f_analytic01(e1, e2, e3): - return np.cos(2.0 * np.pi * e1) + np.cos(6.0 * np.pi * e1) + return xp.cos(2.0 * xp.pi * e1) + xp.cos(6.0 * xp.pi * e1) def f_analytic02(e1, e2, e3): - return np.sin(6.0 * np.pi * e1) + np.sin(4.0 * np.pi * e1) + return xp.sin(6.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e1) def f_analytic10(e1, e2, e3): - return np.sin(3.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) + return xp.sin(3.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) def f_analytic11(e1, e2, e3): - return np.cos(2.0 * np.pi * e1) + np.cos(3.0 * np.pi * e1) + return xp.cos(2.0 * xp.pi * e1) + xp.cos(3.0 * xp.pi * e1) def f_analytic12(e1, e2, e3): - return np.sin(5.0 * np.pi * e1) + np.sin(3.0 * np.pi * e1) + return xp.sin(5.0 * xp.pi * e1) + xp.sin(3.0 * xp.pi * e1) def f_analytic20(e1, e2, e3): - return np.sin(5.0 * np.pi * e1) + np.cos(4.0 * np.pi * e1) + return xp.sin(5.0 * xp.pi * e1) + xp.cos(4.0 * xp.pi * e1) def f_analytic21(e1, e2, e3): - return np.cos(5.0 * np.pi * e1) + np.cos(6.0 * np.pi * e1) + return xp.cos(5.0 * xp.pi * e1) + xp.cos(6.0 * xp.pi * e1) def f_analytic22(e1, e2, e3): - return np.sin(5.0 * np.pi * e1) + np.sin(4.0 * np.pi * e1) + return xp.sin(5.0 * xp.pi * e1) + xp.sin(4.0 * xp.pi * e1) matrix_new = BasisProjectionOperatorLocal( P_Loc, @@ -1300,14 +1253,14 @@ def f_analytic22(e1, e2, e3): } # Use the map to get analytic values - analytic_vals = np.array( + analytic_vals = xp.array( [ f_analytic_map[dim][random_h](*meshgrid) * basis1(random_i0, random_h)(*meshgrid) * basis2(random_i1, random_h)(*meshgrid) * basis3(random_i2, random_h)(*meshgrid) for dim in range(3) - ] + ], ) FE_loc = matrix_new.dot(input) @@ -1330,31 +1283,47 @@ def f_analytic22(e1, e2, e3): fieldglo = derham.create_spline_function("fh", out_sp_id) fieldglo.vector = FE_glo - errorloc = np.abs(fieldloc(*meshgrid) - analytic_vals) - errorglo = np.abs(fieldglo(*meshgrid) - analytic_vals) + errorloc = xp.abs(fieldloc(*meshgrid) - analytic_vals) + errorglo = xp.abs(fieldglo(*meshgrid) - analytic_vals) - meanlocal = np.mean(errorloc) - maxlocal = np.max(errorloc) + meanlocal = xp.mean(errorloc) + maxlocal = xp.max(errorloc) - meanglobal = np.mean(errorglo) - maxglobal = np.max(errorglo) + meanglobal = xp.mean(errorglo) + maxglobal = xp.max(errorglo) + + if isinstance(comm, MockComm): + reducemeanlocal = meanlocal + else: + reducemeanlocal = comm.reduce(meanlocal, op=MPI.SUM, root=0) - reducemeanlocal = comm.reduce(meanlocal, op=MPI.SUM, root=0) if rank == 0: reducemeanlocal = reducemeanlocal / world_size - reducemaxlocal = comm.reduce(maxlocal, op=MPI.MAX, root=0) - reducemeanglobal = comm.reduce(meanglobal, op=MPI.SUM, root=0) + if isinstance(comm, MockComm): + reducemaxlocal = maxlocal + else: + reducemaxlocal = comm.reduce(maxlocal, op=MPI.MAX, root=0) + + if isinstance(comm, MockComm): + reducemeanglobal = meanglobal + else: + reducemeanglobal = comm.reduce(meanglobal, op=MPI.SUM, root=0) + if rank == 0: reducemeanglobal = reducemeanglobal / world_size - reducemaxglobal = comm.reduce(maxglobal, op=MPI.MAX, root=0) + + if isinstance(comm, MockComm): + reducemaxglobal = maxglobal + else: + reducemaxglobal = comm.reduce(maxglobal, op=MPI.MAX, root=0) if rank == 0: assert reducemeanlocal < 10.0 * reducemeanglobal or reducemeanlocal < 10.0**-5 - print(f"{reducemeanlocal = }") - print(f"{reducemaxlocal = }") - print(f"{reducemeanglobal = }") - print(f"{reducemaxglobal = }") + print(f"{reducemeanlocal =}") + print(f"{reducemaxlocal =}") + print(f"{reducemeanglobal =}") + print(f"{reducemaxglobal =}") if do_plot: if out_sp_key == "0" or out_sp_key == "3": @@ -1408,7 +1377,7 @@ def aux_test_spline_evaluation(Nel, plist, spl_kind): # Helper function to handle reshaping and getting spans and basis def process_eta(eta, w1d): if isinstance(eta, (float, int)): - eta = np.array([eta]) + eta = xp.array([eta]) if len(eta.shape) == 1: eta = eta.reshape((eta.shape[0], 1)) spans, values = get_span_and_basis(eta, w1d) @@ -1421,7 +1390,7 @@ def fun(eta1, eta2, eta3): eta = eta_map[dim_idx] w1d = W1ds[0][dim_idx] if is_B else V1ds[0][dim_idx] - out = np.zeros_like(eta) + out = xp.zeros_like(eta) for j1 in range(eta.shape[0]): for j2 in range(eta.shape[1]): for j3 in range(eta.shape[2]): @@ -1455,10 +1424,10 @@ def fun(eta1, eta2, eta3): fieldD = derham.create_spline_function("fh", "L2") npts_in_D = derham.Vh["3"].npts - etas1 = np.linspace(0.0, 1.0, 20) - etas2 = np.linspace(0.0, 1.0, 20) - etas3 = np.linspace(0.0, 1.0, 20) - meshgrid = np.meshgrid(*[etas1, etas2, etas3], indexing="ij") + etas1 = xp.linspace(0.0, 1.0, 20) + etas2 = xp.linspace(0.0, 1.0, 20) + etas3 = xp.linspace(0.0, 1.0, 20) + meshgrid = xp.meshgrid(*[etas1, etas2, etas3], indexing="ij") maxerrorB = 0.0 @@ -1471,7 +1440,7 @@ def fun(eta1, eta2, eta3): fieldB.vector = inputB def error(e1, e2, e3): - return np.abs( + return xp.abs( fieldB(e1, e2, e3) - ( make_basis_fun(True, 0, col0)(e1, e2, e3) @@ -1480,13 +1449,13 @@ def error(e1, e2, e3): ), ) - auxerror = np.max(error(*meshgrid)) + auxerror = xp.max(error(*meshgrid)) if auxerror > maxerrorB: maxerrorB = auxerror inputB[col0, col1, col2] = 0.0 - print(f"{maxerrorB = }") + print(f"{maxerrorB =}") assert maxerrorB < 10.0**-13 maxerrorD = 0.0 @@ -1499,7 +1468,7 @@ def error(e1, e2, e3): fieldD.vector = inputD def error(e1, e2, e3): - return np.abs( + return xp.abs( fieldD(e1, e2, e3) - ( make_basis_fun(False, 0, col0)(e1, e2, e3) @@ -1508,13 +1477,13 @@ def error(e1, e2, e3): ), ) - auxerror = np.max(error(*meshgrid)) + auxerror = xp.max(error(*meshgrid)) if auxerror > maxerrorD: maxerrorD = auxerror inputD[col0, col1, col2] = 0.0 - print(f"{maxerrorD = }") + print(f"{maxerrorD =}") assert maxerrorD < 10.0**-13 print("Test spline evaluation passed.") diff --git a/src/struphy/feec/tests/test_lowdim_nel_is_1.py b/src/struphy/feec/tests/test_lowdim_nel_is_1.py index fe8c11727..325da31ea 100644 --- a/src/struphy/feec/tests/test_lowdim_nel_is_1.py +++ b/src/struphy/feec/tests/test_lowdim_nel_is_1.py @@ -1,23 +1,21 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[32, 1, 1], [1, 32, 1], [1, 1, 32], [31, 32, 1], [32, 1, 31], [1, 31, 32]]) @pytest.mark.parametrize("p", [[1, 1, 1]]) @pytest.mark.parametrize("spl_kind", [[True, True, True]]) def test_lowdim_derham(Nel, p, spl_kind, do_plot=False): """Test Nel=1 in various directions.""" - import numpy as np + import cunumpy as xp from matplotlib import pyplot as plt - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector from struphy.feec.psydac_derham import Derham comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() print("Nel=", Nel) @@ -76,17 +74,17 @@ def test_lowdim_derham(Nel, p, spl_kind, do_plot=False): ### TEST COMMUTING PROJECTORS ### ################################# def fun(eta): - return np.cos(2 * np.pi * eta) + return xp.cos(2 * xp.pi * eta) def dfun(eta): - return -2 * np.pi * np.sin(2 * np.pi * eta) + return -2 * xp.pi * xp.sin(2 * xp.pi * eta) # evaluation points and gradient e1 = 0.0 e2 = 0.0 e3 = 0.0 if Nel[0] > 1: - e1 = np.linspace(0.0, 1.0, 100) + e1 = xp.linspace(0.0, 1.0, 100) e = e1 c = 0 @@ -97,12 +95,12 @@ def dfx(x, y, z): return dfun(x) def dfy(x, y, z): - return np.zeros_like(x) + return xp.zeros_like(x) def dfz(x, y, z): - return np.zeros_like(x) + return xp.zeros_like(x) elif Nel[1] > 1: - e2 = np.linspace(0.0, 1.0, 100) + e2 = xp.linspace(0.0, 1.0, 100) e = e2 c = 1 @@ -110,15 +108,15 @@ def f(x, y, z): return fun(y) def dfx(x, y, z): - return np.zeros_like(y) + return xp.zeros_like(y) def dfy(x, y, z): return dfun(y) def dfz(x, y, z): - return np.zeros_like(y) + return xp.zeros_like(y) elif Nel[2] > 1: - e3 = np.linspace(0.0, 1.0, 100) + e3 = xp.linspace(0.0, 1.0, 100) e = e3 c = 2 @@ -126,10 +124,10 @@ def f(x, y, z): return fun(z) def dfx(x, y, z): - return np.zeros_like(z) + return xp.zeros_like(z) def dfy(x, y, z): - return np.zeros_like(z) + return xp.zeros_like(z) def dfz(x, y, z): return dfun(z) @@ -162,22 +160,22 @@ def div_f(x, y, z): field_f0_vals = field_f0(e1, e2, e3, squeeze_out=True) # a) projection error - err_f0 = np.max(np.abs(f(e1, e2, e3) - field_f0_vals)) - print(f"\n{err_f0 = }") + err_f0 = xp.max(xp.abs(f(e1, e2, e3) - field_f0_vals)) + print(f"\n{err_f0 =}") assert err_f0 < 1e-2 # b) commuting property df0_h = derham.grad.dot(f0_h) - assert np.allclose(df0_h.toarray(), proj_of_grad_f.toarray()) + assert xp.allclose(df0_h.toarray(), proj_of_grad_f.toarray()) # c) derivative error field_df0 = derham.create_spline_function("df0", "Hcurl") field_df0.vector = df0_h field_df0_vals = field_df0(e1, e2, e3, squeeze_out=True) - err_df0 = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(grad_f, field_df0_vals)] - print(f"{err_df0 = }") - assert np.max(err_df0) < 0.64 + err_df0 = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(grad_f, field_df0_vals)] + print(f"{err_df0 =}") + assert xp.max(err_df0) < 0.64 # d) plotting plt.figure(figsize=(8, 12)) @@ -204,22 +202,22 @@ def div_f(x, y, z): field_f1_vals = field_f1(e1, e2, e3, squeeze_out=True) # a) projection error - err_f1 = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip([f, f, f], field_f1_vals)] - print(f"{err_f1 = }") - assert np.max(err_f1) < 0.09 + err_f1 = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip([f, f, f], field_f1_vals)] + print(f"{err_f1 =}") + assert xp.max(err_f1) < 0.09 # b) commuting property df1_h = derham.curl.dot(f1_h) - assert np.allclose(df1_h.toarray(), proj_of_curl_fff.toarray()) + assert xp.allclose(df1_h.toarray(), proj_of_curl_fff.toarray()) # c) derivative error field_df1 = derham.create_spline_function("df1", "Hdiv") field_df1.vector = df1_h field_df1_vals = field_df1(e1, e2, e3, squeeze_out=True) - err_df1 = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(curl_f, field_df1_vals)] - print(f"{err_df1 = }") - assert np.max(err_df1) < 0.64 + err_df1 = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip(curl_f, field_df1_vals)] + print(f"{err_df1 =}") + assert xp.max(err_df1) < 0.64 # d) plotting plt.figure(figsize=(8, 12)) @@ -251,22 +249,22 @@ def div_f(x, y, z): field_f2_vals = field_f2(e1, e2, e3, squeeze_out=True) # a) projection error - err_f2 = [np.max(np.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip([f, f, f], field_f2_vals)] - print(f"{err_f2 = }") - assert np.max(err_f2) < 0.09 + err_f2 = [xp.max(xp.abs(exact(e1, e2, e3) - field_v)) for exact, field_v in zip([f, f, f], field_f2_vals)] + print(f"{err_f2 =}") + assert xp.max(err_f2) < 0.09 # b) commuting property df2_h = derham.div.dot(f2_h) - assert np.allclose(df2_h.toarray(), proj_of_div_fff.toarray()) + assert xp.allclose(df2_h.toarray(), proj_of_div_fff.toarray()) # c) derivative error field_df2 = derham.create_spline_function("df2", "L2") field_df2.vector = df2_h field_df2_vals = field_df2(e1, e2, e3, squeeze_out=True) - err_df2 = np.max(np.abs(div_f(e1, e2, e3) - field_df2_vals)) - print(f"{err_df2 = }") - assert np.max(err_df2) < 0.64 + err_df2 = xp.max(xp.abs(div_f(e1, e2, e3) - field_df2_vals)) + print(f"{err_df2 =}") + assert xp.max(err_df2) < 0.64 # d) plotting plt.figure(figsize=(8, 12)) @@ -279,7 +277,7 @@ def div_f(x, y, z): plt.subplot(2, 1, 2) plt.plot(e, div_f(e1, e2, e3), "o") plt.plot(e, field_df2_vals) - plt.title(f"div") + plt.title("div") plt.subplots_adjust(wspace=1.0, hspace=0.4) @@ -293,8 +291,8 @@ def div_f(x, y, z): field_f3_vals = field_f3(e1, e2, e3, squeeze_out=True) # a) projection error - err_f3 = np.max(np.abs(f(e1, e2, e3) - field_f3_vals)) - print(f"{err_f3 = }") + err_f3 = xp.max(xp.abs(f(e1, e2, e3) - field_f3_vals)) + print(f"{err_f3 =}") assert err_f3 < 0.09 # d) plotting diff --git a/src/struphy/feec/tests/test_mass_matrices.py b/src/struphy/feec/tests/test_mass_matrices.py index 5f05c3228..e1d629c2e 100644 --- a/src/struphy/feec/tests/test_mass_matrices.py +++ b/src/struphy/feec/tests/test_mass_matrices.py @@ -1,7 +1,6 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[5, 6, 7]]) @pytest.mark.parametrize("p", [[2, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [True, False, True]]) @@ -13,8 +12,8 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): """Compare Struphy mass matrices to Struphy-legacy mass matrices.""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.mhd_operators import MHDOperators from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space @@ -49,7 +48,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): eq_mhd = ShearedSlab( **{ "a": (mapping[1]["r1"] - mapping[1]["l1"]), - "R0": (mapping[1]["r3"] - mapping[1]["l3"]) / (2 * np.pi), + "R0": (mapping[1]["r3"] - mapping[1]["l3"]) / (2 * xp.pi), "B0": 1.0, "q0": 1.05, "q1": 1.8, @@ -57,14 +56,14 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) elif mapping[0] == "Colella": eq_mhd = ShearedSlab( **{ "a": mapping[1]["Lx"], - "R0": mapping[1]["Lz"] / (2 * np.pi), + "R0": mapping[1]["Lz"] / (2 * xp.pi), "B0": 1.0, "q0": 1.05, "q1": 1.8, @@ -72,7 +71,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) if show_plots: @@ -90,7 +89,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) if show_plots: @@ -107,7 +106,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): dirichlet_bc = [(False, False)] * 3 dirichlet_bc = tuple(dirichlet_bc) - print(f"{dirichlet_bc = }") + print(f"{dirichlet_bc =}") # derham object derham = Derham(Nel, p, spl_kind, comm=mpi_comm, dirichlet_bc=dirichlet_bc) @@ -125,7 +124,7 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): # test calling the diagonal method aaa = mass_mats.M0.matrix.diagonal() bbb = mass_mats.M1.matrix.diagonal() - print(f"{aaa = }, {bbb[0, 0] = }, {bbb[0, 1] = }") + print(f"{aaa =}, {bbb[0, 0] =}, {bbb[0, 1] =}") # compare to old STRUPHY bc_old = [[None, None], [None, None], [None, None]] @@ -221,7 +220,11 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): # Change order of input in callable rM1ninvswitch_psy = mass_mats.create_weighted_mass( - "Hcurl", "Hcurl", weights=["sqrt_g", "1/eq_n0", "Ginv"], name="M1ninv", assemble=True + "Hcurl", + "Hcurl", + weights=["sqrt_g", "1/eq_n0", "Ginv"], + name="M1ninv", + assemble=True, ).dot(x1_psy, apply_bc=True) rot_B = RotationMatrix( @@ -230,7 +233,11 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): mass_mats.weights[mass_mats.selected_weight].b2_3, ) rM1Bninvswitch_psy = mass_mats.create_weighted_mass( - "Hcurl", "Hcurl", weights=["1/eq_n0", "sqrt_g", "Ginv", rot_B, "Ginv"], name="M1Bninv", assemble=True + "Hcurl", + "Hcurl", + weights=["1/eq_n0", "sqrt_g", "Ginv", rot_B, "Ginv"], + name="M1Bninv", + assemble=True, ).dot(x1_psy, apply_bc=True) # Test matrix free operators @@ -256,7 +263,11 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): # Change order of input in callable rM1ninvswitch_fre = mass_mats_free.create_weighted_mass( - "Hcurl", "Hcurl", weights=["sqrt_g", "1/eq_n0", "Ginv"], name="M1ninvswitch", assemble=True + "Hcurl", + "Hcurl", + weights=["sqrt_g", "1/eq_n0", "Ginv"], + name="M1ninvswitch", + assemble=True, ).dot(x1_psy, apply_bc=True) rot_B = RotationMatrix( mass_mats_free.weights[mass_mats_free.selected_weight].b2_1, @@ -265,7 +276,11 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): ) rM1Bninvswitch_fre = mass_mats_free.create_weighted_mass( - "Hcurl", "Hcurl", weights=["1/eq_n0", "sqrt_g", "Ginv", rot_B, "Ginv"], name="M1Bninvswitch", assemble=True + "Hcurl", + "Hcurl", + weights=["1/eq_n0", "sqrt_g", "Ginv", rot_B, "Ginv"], + name="M1Bninvswitch", + assemble=True, ).dot(x1_psy, apply_bc=True) # compare output arrays @@ -366,7 +381,6 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): print(f"Rank {mpi_rank} | All tests passed!") -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 12, 6]]) @pytest.mark.parametrize("p", [[2, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -378,8 +392,8 @@ def test_mass(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): """Compare Struphy polar mass matrices to Struphy-legacy polar mass matrices.""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.mhd_operators import MHDOperators from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space @@ -422,7 +436,7 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) if show_plots: @@ -442,7 +456,14 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): # derham object derham = Derham( - Nel, p, spl_kind, comm=mpi_comm, dirichlet_bc=dirichlet_bc, with_projectors=False, polar_ck=1, domain=domain + Nel, + p, + spl_kind, + comm=mpi_comm, + dirichlet_bc=dirichlet_bc, + with_projectors=False, + polar_ck=1, + domain=domain, ) print(f"Rank {mpi_rank} | Local domain : " + str(derham.domain_array[mpi_rank])) @@ -501,11 +522,11 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): x2_pol_psy.tp = x2_psy x3_pol_psy.tp = x3_psy - np.random.seed(1607) - x0_pol_psy.pol = [np.random.rand(x0_pol_psy.pol[0].shape[0], x0_pol_psy.pol[0].shape[1])] - x1_pol_psy.pol = [np.random.rand(x1_pol_psy.pol[n].shape[0], x1_pol_psy.pol[n].shape[1]) for n in range(3)] - x2_pol_psy.pol = [np.random.rand(x2_pol_psy.pol[n].shape[0], x2_pol_psy.pol[n].shape[1]) for n in range(3)] - x3_pol_psy.pol = [np.random.rand(x3_pol_psy.pol[0].shape[0], x3_pol_psy.pol[0].shape[1])] + xp.random.seed(1607) + x0_pol_psy.pol = [xp.random.rand(x0_pol_psy.pol[0].shape[0], x0_pol_psy.pol[0].shape[1])] + x1_pol_psy.pol = [xp.random.rand(x1_pol_psy.pol[n].shape[0], x1_pol_psy.pol[n].shape[1]) for n in range(3)] + x2_pol_psy.pol = [xp.random.rand(x2_pol_psy.pol[n].shape[0], x2_pol_psy.pol[n].shape[1]) for n in range(3)] + x3_pol_psy.pol = [xp.random.rand(x3_pol_psy.pol[0].shape[0], x3_pol_psy.pol[0].shape[1])] # apply boundary conditions to old STRUPHY x0_pol_str = x0_pol_psy.toarray(True) @@ -535,12 +556,12 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): rn_pol_psy = mass_mats.M2n.dot(x2_pol_psy, apply_bc=True) rJ_pol_psy = mass_mats.M2J.dot(x2_pol_psy, apply_bc=True) - assert np.allclose(r0_pol_str, r0_pol_psy.toarray(True)) - assert np.allclose(r1_pol_str, r1_pol_psy.toarray(True)) - assert np.allclose(r2_pol_str, r2_pol_psy.toarray(True)) - assert np.allclose(r3_pol_str, r3_pol_psy.toarray(True)) - assert np.allclose(rn_pol_str, rn_pol_psy.toarray(True)) - assert np.allclose(rJ_pol_str, rJ_pol_psy.toarray(True)) + assert xp.allclose(r0_pol_str, r0_pol_psy.toarray(True)) + assert xp.allclose(r1_pol_str, r1_pol_psy.toarray(True)) + assert xp.allclose(r2_pol_str, r2_pol_psy.toarray(True)) + assert xp.allclose(r3_pol_str, r3_pol_psy.toarray(True)) + assert xp.allclose(rn_pol_str, rn_pol_psy.toarray(True)) + assert xp.allclose(rJ_pol_str, rJ_pol_psy.toarray(True)) # perfrom matrix-vector products (without boundary conditions) r0_pol_str = space.M0(x0_pol_str) @@ -553,17 +574,16 @@ def test_mass_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots=False): r2_pol_psy = mass_mats.M2.dot(x2_pol_psy, apply_bc=False) r3_pol_psy = mass_mats.M3.dot(x3_pol_psy, apply_bc=False) - assert np.allclose(r0_pol_str, r0_pol_psy.toarray(True)) - assert np.allclose(r1_pol_str, r1_pol_psy.toarray(True)) - assert np.allclose(r2_pol_str, r2_pol_psy.toarray(True)) - assert np.allclose(r3_pol_str, r3_pol_psy.toarray(True)) - assert np.allclose(rn_pol_str, rn_pol_psy.toarray(True)) - assert np.allclose(rJ_pol_str, rJ_pol_psy.toarray(True)) + assert xp.allclose(r0_pol_str, r0_pol_psy.toarray(True)) + assert xp.allclose(r1_pol_str, r1_pol_psy.toarray(True)) + assert xp.allclose(r2_pol_str, r2_pol_psy.toarray(True)) + assert xp.allclose(r3_pol_str, r3_pol_psy.toarray(True)) + assert xp.allclose(rn_pol_str, rn_pol_psy.toarray(True)) + assert xp.allclose(rJ_pol_str, rJ_pol_psy.toarray(True)) print(f"Rank {mpi_rank} | All tests passed!") -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 12, 6]]) @pytest.mark.parametrize("p", [[2, 3, 2]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -578,8 +598,8 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots import time - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.solvers import inverse from struphy.feec.mass import WeightedMassOperators, WeightedMassOperatorsOldForTesting @@ -614,7 +634,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots eq_mhd = ShearedSlab( **{ "a": (mapping[1]["r1"] - mapping[1]["l1"]), - "R0": (mapping[1]["r3"] - mapping[1]["l3"]) / (2 * np.pi), + "R0": (mapping[1]["r3"] - mapping[1]["l3"]) / (2 * xp.pi), "B0": 1.0, "q0": 1.05, "q1": 1.8, @@ -622,14 +642,14 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) elif mapping[0] == "Colella": eq_mhd = ShearedSlab( **{ "a": mapping[1]["Lx"], - "R0": mapping[1]["Lz"] / (2 * np.pi), + "R0": mapping[1]["Lz"] / (2 * xp.pi), "B0": 1.0, "q0": 1.05, "q1": 1.8, @@ -637,7 +657,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) if show_plots: @@ -655,7 +675,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) if show_plots: @@ -754,27 +774,27 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots print("Done") # compare output arrays - assert np.allclose(r0.toarray(), r0_pre.toarray()) - assert np.allclose(r1.toarray(), r1_pre.toarray()) - assert np.allclose(r2.toarray(), r2_pre.toarray()) - assert np.allclose(r3.toarray(), r3_pre.toarray()) - assert np.allclose(rv.toarray(), rv_pre.toarray()) + assert xp.allclose(r0.toarray(), r0_pre.toarray()) + assert xp.allclose(r1.toarray(), r1_pre.toarray()) + assert xp.allclose(r2.toarray(), r2_pre.toarray()) + assert xp.allclose(r3.toarray(), r3_pre.toarray()) + assert xp.allclose(rv.toarray(), rv_pre.toarray()) - assert np.allclose(r1n.toarray(), r1n_pre.toarray()) - assert np.allclose(r2n.toarray(), r2n_pre.toarray()) - assert np.allclose(rvn.toarray(), rvn_pre.toarray()) + assert xp.allclose(r1n.toarray(), r1n_pre.toarray()) + assert xp.allclose(r2n.toarray(), r2n_pre.toarray()) + assert xp.allclose(rvn.toarray(), rvn_pre.toarray()) - assert np.allclose(r1Bninv.toarray(), r1Bninv_pre.toarray()) - assert np.allclose(r1Bninv.toarray(), r1Bninvold_pre.toarray()) - assert np.allclose(r1Bninvold.toarray(), r1Bninv_pre.toarray()) + assert xp.allclose(r1Bninv.toarray(), r1Bninv_pre.toarray()) + assert xp.allclose(r1Bninv.toarray(), r1Bninvold_pre.toarray()) + assert xp.allclose(r1Bninvold.toarray(), r1Bninv_pre.toarray()) # test if preconditioner satisfies PC * M = Identity if mapping[0] == "Cuboid" or mapping[0] == "HollowCylinder": - assert np.allclose(mass_mats.M0.dot(M0pre.solve(x0)).toarray(), derham.boundary_ops["0"].dot(x0).toarray()) - assert np.allclose(mass_mats.M1.dot(M1pre.solve(x1)).toarray(), derham.boundary_ops["1"].dot(x1).toarray()) - assert np.allclose(mass_mats.M2.dot(M2pre.solve(x2)).toarray(), derham.boundary_ops["2"].dot(x2).toarray()) - assert np.allclose(mass_mats.M3.dot(M3pre.solve(x3)).toarray(), derham.boundary_ops["3"].dot(x3).toarray()) - assert np.allclose(mass_mats.Mv.dot(Mvpre.solve(xv)).toarray(), derham.boundary_ops["v"].dot(xv).toarray()) + assert xp.allclose(mass_mats.M0.dot(M0pre.solve(x0)).toarray(), derham.boundary_ops["0"].dot(x0).toarray()) + assert xp.allclose(mass_mats.M1.dot(M1pre.solve(x1)).toarray(), derham.boundary_ops["1"].dot(x1).toarray()) + assert xp.allclose(mass_mats.M2.dot(M2pre.solve(x2)).toarray(), derham.boundary_ops["2"].dot(x2).toarray()) + assert xp.allclose(mass_mats.M3.dot(M3pre.solve(x3)).toarray(), derham.boundary_ops["3"].dot(x3).toarray()) + assert xp.allclose(mass_mats.Mv.dot(Mvpre.solve(xv)).toarray(), derham.boundary_ops["v"].dot(xv).toarray()) # test preconditioner in iterative solver M0inv = inverse(mass_mats.M0, "pcg", pc=M0pre, tol=1e-8, maxiter=1000) @@ -871,7 +891,6 @@ def test_mass_preconditioner(Nel, p, spl_kind, dirichlet_bc, mapping, show_plots print(f"Rank {mpi_rank} | All tests passed!") -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 6]]) @pytest.mark.parametrize("p", [[2, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -886,8 +905,8 @@ def test_mass_preconditioner_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show import time - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.solvers import inverse from struphy.feec.mass import WeightedMassOperators @@ -930,7 +949,7 @@ def test_mass_preconditioner_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show "n2": 4.0, "na": 0.0, "beta": 0.1, - } + }, ) if show_plots: @@ -950,7 +969,14 @@ def test_mass_preconditioner_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show # derham object derham = Derham( - Nel, p, spl_kind, comm=mpi_comm, dirichlet_bc=dirichlet_bc, with_projectors=False, polar_ck=1, domain=domain + Nel, + p, + spl_kind, + comm=mpi_comm, + dirichlet_bc=dirichlet_bc, + with_projectors=False, + polar_ck=1, + domain=domain, ) print(f"Rank {mpi_rank} | Local domain : " + str(derham.domain_array[mpi_rank])) @@ -990,11 +1016,11 @@ def test_mass_preconditioner_polar(Nel, p, spl_kind, dirichlet_bc, mapping, show x2_pol.tp = x2 x3_pol.tp = x3 - np.random.seed(1607) - x0_pol.pol = [np.random.rand(x0_pol.pol[0].shape[0], x0_pol.pol[0].shape[1])] - x1_pol.pol = [np.random.rand(x1_pol.pol[n].shape[0], x1_pol.pol[n].shape[1]) for n in range(3)] - x2_pol.pol = [np.random.rand(x2_pol.pol[n].shape[0], x2_pol.pol[n].shape[1]) for n in range(3)] - x3_pol.pol = [np.random.rand(x3_pol.pol[0].shape[0], x3_pol.pol[0].shape[1])] + xp.random.seed(1607) + x0_pol.pol = [xp.random.rand(x0_pol.pol[0].shape[0], x0_pol.pol[0].shape[1])] + x1_pol.pol = [xp.random.rand(x1_pol.pol[n].shape[0], x1_pol.pol[n].shape[1]) for n in range(3)] + x2_pol.pol = [xp.random.rand(x2_pol.pol[n].shape[0], x2_pol.pol[n].shape[1]) for n in range(3)] + x3_pol.pol = [xp.random.rand(x3_pol.pol[0].shape[0], x3_pol.pol[0].shape[1])] # test preconditioner in iterative solver and compare to case without preconditioner M0inv = inverse(mass_mats.M0, "pcg", pc=M0pre, tol=1e-8, maxiter=500) diff --git a/src/struphy/feec/tests/test_toarray_struphy.py b/src/struphy/feec/tests/test_toarray_struphy.py index 3b7386fca..90427d8e4 100644 --- a/src/struphy/feec/tests/test_toarray_struphy.py +++ b/src/struphy/feec/tests/test_toarray_struphy.py @@ -1,20 +1,20 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[12, 5, 2], [8, 12, 4], [5, 4, 12]]) @pytest.mark.parametrize("p", [[3, 2, 1]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [True, False, False]]) @pytest.mark.parametrize( - "mapping", [["Cuboid", {"l1": 1.0, "r1": 2.0, "l2": 10.0, "r2": 20.0, "l3": 100.0, "r3": 200.0}]] + "mapping", + [["Cuboid", {"l1": 1.0, "r1": 2.0, "l2": 10.0, "r2": 20.0, "l3": 100.0, "r3": 200.0}]], ) def test_toarray_struphy(Nel, p, spl_kind, mapping): """ TODO """ - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.psydac_derham import Derham @@ -33,7 +33,7 @@ def test_toarray_struphy(Nel, p, spl_kind, mapping): domain = domain_class(**dom_params) # create derham object - derham = Derham(Nel, p, spl_kind, comm=MPI.COMM_WORLD) + derham = Derham(Nel, p, spl_kind, comm=comm) # assemble mass matrices in V0 and V1 mass = WeightedMassOperators(derham, domain) @@ -71,30 +71,30 @@ def test_toarray_struphy(Nel, p, spl_kind, mapping): v3arr = v3arr[0].flatten() # not in-place - compare_arrays(M0.dot(v0), np.matmul(M0arr, v0arr), rank) - compare_arrays(M1.dot(v1), np.matmul(M1arr, v1arr), rank) - compare_arrays(M2.dot(v2), np.matmul(M2arr, v2arr), rank) - compare_arrays(M3.dot(v3), np.matmul(M3arr, v3arr), rank) + compare_arrays(M0.dot(v0), xp.matmul(M0arr, v0arr), rank) + compare_arrays(M1.dot(v1), xp.matmul(M1arr, v1arr), rank) + compare_arrays(M2.dot(v2), xp.matmul(M2arr, v2arr), rank) + compare_arrays(M3.dot(v3), xp.matmul(M3arr, v3arr), rank) # Now we test the in-place version - IM0 = np.zeros([M0.codomain.dimension, M0.domain.dimension], dtype=M0.dtype) - IM1 = np.zeros([M1.codomain.dimension, M1.domain.dimension], dtype=M1.dtype) - IM2 = np.zeros([M2.codomain.dimension, M2.domain.dimension], dtype=M2.dtype) - IM3 = np.zeros([M3.codomain.dimension, M3.domain.dimension], dtype=M3.dtype) + IM0 = xp.zeros([M0.codomain.dimension, M0.domain.dimension], dtype=M0.dtype) + IM1 = xp.zeros([M1.codomain.dimension, M1.domain.dimension], dtype=M1.dtype) + IM2 = xp.zeros([M2.codomain.dimension, M2.domain.dimension], dtype=M2.dtype) + IM3 = xp.zeros([M3.codomain.dimension, M3.domain.dimension], dtype=M3.dtype) M0.toarray_struphy(out=IM0) M1.toarray_struphy(out=IM1) M2.toarray_struphy(out=IM2) M3.toarray_struphy(out=IM3) - compare_arrays(M0.dot(v0), np.matmul(IM0, v0arr), rank) - compare_arrays(M1.dot(v1), np.matmul(IM1, v1arr), rank) - compare_arrays(M2.dot(v2), np.matmul(IM2, v2arr), rank) - compare_arrays(M3.dot(v3), np.matmul(IM3, v3arr), rank) + compare_arrays(M0.dot(v0), xp.matmul(IM0, v0arr), rank) + compare_arrays(M1.dot(v1), xp.matmul(IM1, v1arr), rank) + compare_arrays(M2.dot(v2), xp.matmul(IM2, v2arr), rank) + compare_arrays(M3.dot(v3), xp.matmul(IM3, v3arr), rank) print("test_toarray_struphy passed!") - # assert np.allclose(out1.toarray(), v1.toarray(), atol=1e-5) + # assert xp.allclose(out1.toarray(), v1.toarray(), atol=1e-5) if __name__ == "__main__": diff --git a/src/struphy/feec/tests/test_tosparse_struphy.py b/src/struphy/feec/tests/test_tosparse_struphy.py index 2e3850890..48cbfd7a2 100644 --- a/src/struphy/feec/tests/test_tosparse_struphy.py +++ b/src/struphy/feec/tests/test_tosparse_struphy.py @@ -3,20 +3,21 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[12, 5, 2], [8, 12, 4], [5, 4, 12]]) @pytest.mark.parametrize("p", [[3, 2, 1]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [True, False, False]]) @pytest.mark.parametrize( - "mapping", [["Cuboid", {"l1": 1.0, "r1": 2.0, "l2": 10.0, "r2": 20.0, "l3": 100.0, "r3": 200.0}]] + "mapping", + [["Cuboid", {"l1": 1.0, "r1": 2.0, "l2": 10.0, "r2": 20.0, "l3": 100.0, "r3": 200.0}]], ) def test_tosparse_struphy(Nel, p, spl_kind, mapping): """ TODO """ - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import MockComm + from psydac.ddm.mpi import mpi as MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.psydac_derham import Derham @@ -74,40 +75,67 @@ def test_tosparse_struphy(Nel, p, spl_kind, mapping): M2arrad = M2.toarray_struphy(is_sparse=True, format="dia") v0_local = M0.dot(v0).toarray() - v0_global = M0.domain.zeros().toarray() - comm.Allreduce(v0_local, v0_global, op=MPI.SUM) + if isinstance(comm, MockComm): + v0_global = v0_local + else: + v0_global = M0.domain.zeros().toarray() + comm.Allreduce(v0_local, v0_global, op=MPI.SUM) + v1_local = M1.dot(v1).toarray() - v1_global = M1.domain.zeros().toarray() - comm.Allreduce(v1_local, v1_global, op=MPI.SUM) + if isinstance(comm, MockComm): + v1_global = v1_local + else: + v1_global = M1.domain.zeros().toarray() + comm.Allreduce(v1_local, v1_global, op=MPI.SUM) + v2_local = M2.dot(v2).toarray() - v2_global = M2.domain.zeros().toarray() - comm.Allreduce(v2_local, v2_global, op=MPI.SUM) + if isinstance(comm, MockComm): + v2_global = v2_local + else: + v2_global = M2.domain.zeros().toarray() + comm.Allreduce(v2_local, v2_global, op=MPI.SUM) + v3_local = M3.dot(v3).toarray() - v3_global = M3.domain.zeros().toarray() - comm.Allreduce(v3_local, v3_global, op=MPI.SUM) + if isinstance(comm, MockComm): + v3_global = v3_local + else: + v3_global = M3.domain.zeros().toarray() + comm.Allreduce(v3_local, v3_global, op=MPI.SUM) # not in-place - assert np.allclose(v0_global, M0arr.dot(v0arr)) - assert np.allclose(v1_global, M1arr.dot(v1arr)) - assert np.allclose(v2_global, M2arr.dot(v2arr)) - assert np.allclose(v3_global, M3arr.dot(v3arr)) - assert np.allclose(v0_global, M0arrad.dot(v0arr)) - assert np.allclose(v1_global, M1arrad.dot(v1arr)) - assert np.allclose(v2_global, M2arrad.dot(v2arr)) + assert xp.allclose(v0_global, M0arr.dot(v0arr)) + assert xp.allclose(v1_global, M1arr.dot(v1arr)) + assert xp.allclose(v2_global, M2arr.dot(v2arr)) + assert xp.allclose(v3_global, M3arr.dot(v3arr)) + assert xp.allclose(v0_global, M0arrad.dot(v0arr)) + assert xp.allclose(v1_global, M1arrad.dot(v1arr)) + assert xp.allclose(v2_global, M2arrad.dot(v2arr)) print("test_tosparse_struphy passed!") if __name__ == "__main__": test_tosparse_struphy( - [32, 2, 2], [2, 1, 1], [True, True, True], ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}] + [32, 2, 2], + [2, 1, 1], + [True, True, True], + ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}], ) test_tosparse_struphy( - [2, 32, 2], [1, 2, 1], [True, True, True], ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}] + [2, 32, 2], + [1, 2, 1], + [True, True, True], + ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}], ) test_tosparse_struphy( - [2, 2, 32], [1, 1, 2], [True, True, True], ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}] + [2, 2, 32], + [1, 1, 2], + [True, True, True], + ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}], ) test_tosparse_struphy( - [2, 2, 32], [1, 1, 2], [False, False, False], ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}] + [2, 2, 32], + [1, 1, 2], + [False, False, False], + ["Colella", {"Lx": 1.0, "Ly": 2.0, "alpha": 0.5, "Lz": 3.0}], ) diff --git a/src/struphy/feec/tests/xx_test_preconds.py b/src/struphy/feec/tests/xx_test_preconds.py index c00a8fdaf..267e0279a 100644 --- a/src/struphy/feec/tests/xx_test_preconds.py +++ b/src/struphy/feec/tests/xx_test_preconds.py @@ -12,8 +12,8 @@ ], ) def test_mass_preconditioner(Nel, p, spl_kind, mapping): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -40,22 +40,22 @@ def test_mass_preconditioner(Nel, p, spl_kind, mapping): v = [] v += [StencilVector(derham.V0.coeff_space)] - v[-1]._data = np.random.rand(*v[-1]._data.shape) + v[-1]._data = xp.random.rand(*v[-1]._data.shape) v += [BlockVector(derham.V1.coeff_space)] for v1i in v[-1]: - v1i._data = np.random.rand(*v1i._data.shape) + v1i._data = xp.random.rand(*v1i._data.shape) v += [BlockVector(derham.V2.coeff_space)] for v1i in v[-1]: - v1i._data = np.random.rand(*v1i._data.shape) + v1i._data = xp.random.rand(*v1i._data.shape) v += [StencilVector(derham.V3.coeff_space)] - v[-1]._data = np.random.rand(*v[-1]._data.shape) + v[-1]._data = xp.random.rand(*v[-1]._data.shape) v += [BlockVector(derham.V0vec.coeff_space)] for v1i in v[-1]: - v1i._data = np.random.rand(*v1i._data.shape) + v1i._data = xp.random.rand(*v1i._data.shape) # assemble preconditioners M_pre = [] @@ -68,7 +68,7 @@ def test_mass_preconditioner(Nel, p, spl_kind, mapping): n = "v" if domain.kind_map == 10 or domain.kind_map == 11: - assert np.allclose(M._mat.toarray(), M_p.matrix.toarray()) + assert xp.allclose(M._mat.toarray(), M_p.matrix.toarray()) print(f'Matrix assertion for space {n} case "Cuboid/HollowCylinder" passed.') inv_A = InverseLinearOperator(M, pc=M_p, tol=1e-8, maxiter=5000) diff --git a/src/struphy/feec/utilities.py b/src/struphy/feec/utilities.py index 2541f9a63..2fffe38e3 100644 --- a/src/struphy/feec/utilities.py +++ b/src/struphy/feec/utilities.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp from psydac.api.essential_bc import apply_essential_bc_stencil from psydac.fem.tensor import TensorFemSpace from psydac.fem.vector import VectorFemSpace @@ -41,15 +41,15 @@ def __init__(self, *vec_fun): def __call__(self, e1, e2, e3): # array from 2d list gives 3x3 array is in the first two indices - tmp = np.array( + tmp = xp.array( [ [self._cross_mask[m][n] * fun(e1, e2, e3) for n, fun in enumerate(row)] for m, row in enumerate(self._funs) - ] + ], ) # numpy operates on the last two indices with @ - return np.transpose(tmp, axes=(2, 3, 4, 0, 1)) + return xp.transpose(tmp, axes=(2, 3, 4, 0, 1)) def create_equal_random_arrays(V, seed=123, flattened=False): @@ -77,7 +77,7 @@ def create_equal_random_arrays(V, seed=123, flattened=False): assert isinstance(V, (TensorFemSpace, VectorFemSpace)) - np.random.seed(seed) + xp.random.seed(seed) arr = [] @@ -93,13 +93,15 @@ def create_equal_random_arrays(V, seed=123, flattened=False): dims = V.coeff_space.npts - arr += [np.random.rand(*dims)] + arr += [xp.random.rand(*dims)] s = arr_psy.starts e = arr_psy.ends arr_psy[s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1] = arr[-1][ - s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1 + s[0] : e[0] + 1, + s[1] : e[1] + 1, + s[2] : e[2] + 1, ] if flattened: @@ -111,22 +113,24 @@ def create_equal_random_arrays(V, seed=123, flattened=False): for d, block in enumerate(arr_psy.blocks): dims = V.spaces[d].coeff_space.npts - arr += [np.random.rand(*dims)] + arr += [xp.random.rand(*dims)] s = block.starts e = block.ends arr_psy[d][s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1] = arr[-1][ - s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1 + s[0] : e[0] + 1, + s[1] : e[1] + 1, + s[2] : e[2] + 1, ] if flattened: - arr = np.concatenate( + arr = xp.concatenate( ( arr[0].flatten(), arr[1].flatten(), arr[2].flatten(), - ) + ), ) arr_psy.update_ghost_regions() @@ -168,11 +172,11 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): arr_psy.space.npts[2], )[s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1] - assert np.allclose(tmp1, tmp2, atol=atol) + assert xp.allclose(tmp1, tmp2, atol=atol) elif isinstance(arr_psy, BlockVector): if not (isinstance(arr, tuple) or isinstance(arr, list)): - arrs = np.split( + arrs = xp.split( arr, [ arr_psy.blocks[0].shape[0], @@ -197,7 +201,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): s[2] : e[2] + 1, ] - assert np.allclose(tmp1, tmp2, atol=atol) + assert xp.allclose(tmp1, tmp2, atol=atol) elif isinstance(arr_psy, StencilMatrix): s = arr_psy.codomain.starts @@ -216,7 +220,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): if tmp_arr.shape == tmp1.shape: tmp2 = tmp_arr else: - tmp2 = np.zeros( + tmp2 = xp.zeros( ( e[0] + 1 - s[0], e[1] + 1 - s[1], @@ -229,7 +233,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): ) bts.band_to_stencil_3d(tmp_arr, tmp2) - assert np.allclose(tmp1, tmp2, atol=atol) + assert xp.allclose(tmp1, tmp2, atol=atol) elif isinstance(arr_psy, BlockLinearOperator): for row_psy, row in zip(arr_psy.blocks, arr): @@ -260,7 +264,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): if tmp_mat.shape == tmp1.shape: tmp2 = tmp_mat else: - tmp2 = np.zeros( + tmp2 = xp.zeros( ( e[0] + 1 - s[0], e[1] + 1 - s[1], @@ -273,7 +277,7 @@ def compare_arrays(arr_psy, arr, rank, atol=1e-14, verbose=False): ) bts.band_to_stencil_3d(tmp_mat, tmp2) - assert np.allclose(tmp1, tmp2, atol=atol) + assert xp.allclose(tmp1, tmp2, atol=atol) else: raise AssertionError("Wrong input type.") diff --git a/src/struphy/feec/utilities_local_projectors.py b/src/struphy/feec/utilities_local_projectors.py index 1831cb31e..3ce6590f2 100644 --- a/src/struphy/feec/utilities_local_projectors.py +++ b/src/struphy/feec/utilities_local_projectors.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp from struphy.feec.local_projectors_kernels import are_quadrature_points_zero, get_rows, select_quasi_points @@ -33,7 +33,7 @@ def split_points( shifts : 1d int array For each one of the three spatial directions it determines by which amount to shift the position index (pos) in case we have to loop over the evaluation points. - pts : list of np.array + pts : list of xp.array 3D list of 2D array with the quasi-interpolation points (or Gauss-Legendre quadrature points for histopolation). In format (ns, nb, np) = (spatial direction, B-spline index, point) for StencilVector spaces . @@ -50,7 +50,7 @@ def split_points( npts : list of ints Contains the number of B-splines for each one of the three spatial directions. - periodic : 1D bool np.array + periodic : 1D bool xp.array For each one of the three spatial directions contains the information of whether the B-splines are periodic or not. wij: 3d float array @@ -75,18 +75,18 @@ def split_points( """ # We iterate over the three spatial directions for n, pt in enumerate(pts): - original_pts_size[n] = np.shape(pt)[0] + original_pts_size[n] = xp.shape(pt)[0] # We initialize localpts with as many entries as the global pt, but with all entries being -1 # This function will change the values of the needed entries from -1 to the value of the point. if IoH[n] == "I": - localpts = np.full((np.shape(pt)[0]), fill_value=-1, dtype=float) + localpts = xp.full((xp.shape(pt)[0]), fill_value=-1, dtype=float) elif IoH[n] == "H": - localpts = np.full((np.shape(pt)), fill_value=-1, dtype=float) + localpts = xp.full((xp.shape(pt)), fill_value=-1, dtype=float) for i in range(starts[n], ends[n] + 1): startj1, endj1 = select_quasi_points(int(i), int(p[n]), int(npts[n]), bool(periodic[n])) for j1 in range(lenj[n]): - if startj1 + j1 < np.shape(pt)[0]: + if startj1 + j1 < xp.shape(pt)[0]: pos = startj1 + j1 else: pos = int(startj1 + j1 + shift[n]) @@ -98,42 +98,42 @@ def split_points( localpts[pos] = pt[pos] # We get the local points by grabing only the values different from -1. if IoH[n] == "I": - localpos = np.where(localpts != -1)[0] + localpos = xp.where(localpts != -1)[0] elif IoH[n] == "H": - localpos = np.where(localpts[:, 0] != -1)[0] + localpos = xp.where(localpts[:, 0] != -1)[0] localpts = localpts[localpos] - localptsout.append(np.array(localpts)) + localptsout.append(xp.array(localpts)) ## # We build the index_translation array that shall turn global indices into local indices ## - mini_indextranslation = np.full( - (np.shape(pt)[0]), + mini_indextranslation = xp.full( + (xp.shape(pt)[0]), fill_value=-1, dtype=int, ) for i, j in enumerate(localpos): mini_indextranslation[j] = i - index_translation.append(np.array(mini_indextranslation)) + index_translation.append(xp.array(mini_indextranslation)) ## # We build the inv_index_translation that shall turn local indices into global indices ## - inv_mini_indextranslation = np.full( - (np.shape(localptsout[-1])[0]), + inv_mini_indextranslation = xp.full( + (xp.shape(localptsout[-1])[0]), fill_value=-1, dtype=int, ) for i, j in enumerate(localpos): inv_mini_indextranslation[i] = j - inv_index_translation.append(np.array(inv_mini_indextranslation)) + inv_index_translation.append(xp.array(inv_mini_indextranslation)) def get_values_and_indices_splines(Nbasis, degree, periodic, spans, values): - """Given an array with the values of the splines evaluated at certain points this function returns a np.array that tell us the index of each spline. So we can know to which spline each + """Given an array with the values of the splines evaluated at certain points this function returns a xp.array that tell us the index of each spline. So we can know to which spline each value corresponds. It also modifies the evaluation values in the case we have one spline of degree one with periodic boundary conditions, so it is artificially equal to the identity. Parameters @@ -147,31 +147,31 @@ def get_values_and_indices_splines(Nbasis, degree, periodic, spans, values): periodic : bool Whether we have periodic boundary conditions or nor. - span : np.array + span : xp.array 2d array indexed by (n, nq), where n is the interval and nq is the quadrature point in the interval. - values : np.array + values : xp.array 3d array of values of basis functions indexed by (n, nq, basis function). Returns ------- - eval_indeces : np.array + eval_indeces : xp.array 3d array of basis functions indices, indexed by (n, nq, basis function). - values : np.array + values : xp.array 3d array of values of basis functions indexed by (n, nq, basis function). """ # In this case we want this spatial direction to be "neglected", that means we artificially set the values of the B-spline to 1 at all points. So it becomes the multiplicative identity. if Nbasis == 1 and degree == 1 and periodic: # Set all values to 1 for the identity case - values = np.ones((values.shape[0], values.shape[1], 1)) - eval_indeces = np.zeros_like(values, dtype=int) + values = xp.ones((values.shape[0], values.shape[1], 1)) + eval_indeces = xp.zeros_like(values, dtype=int) else: - eval_indeces = np.zeros_like(values, dtype=int) - for i in range(np.shape(spans)[0]): - for k in range(np.shape(spans)[1]): + eval_indeces = xp.zeros_like(values, dtype=int) + for i in range(xp.shape(spans)[0]): + for k in range(xp.shape(spans)[1]): for j in range(degree + 1): eval_indeces[i, k, j] = (spans[i][k] - degree + j) % Nbasis @@ -180,31 +180,31 @@ def get_values_and_indices_splines(Nbasis, degree, periodic, spans, values): def get_one_spline(a, values, eval_indeces): """Given the spline index, an array with the splines evaluated at the evaluation points and another array with the indices indicating to which spline each value corresponds, this function returns - a 1d np.array with the desired spline evaluated at all evaluation points. + a 1d xp.array with the desired spline evaluated at all evaluation points. Parameters ---------- a : int Spline index - values : np.array + values : xp.array 3d array of values of basis functions indexed by (n, nq, basis function). - eval_indeces : np.array + eval_indeces : xp.array 3d array of basis functions indices, indexed by (n, nq, basis function). Returns ------- - my_values : np.array + my_values : xp.array 1d array of values for the spline evaluated at all evaluation points. """ - my_values = np.zeros(np.shape(values)[0] * np.shape(values)[1]) - for i in range(np.shape(values)[0]): - for j in range(np.shape(values)[1]): - for k in range(np.shape(values)[2]): + my_values = xp.zeros(xp.shape(values)[0] * xp.shape(values)[1]) + for i in range(xp.shape(values)[0]): + for j in range(xp.shape(values)[1]): + for k in range(xp.shape(values)[2]): if eval_indeces[i, j, k] == a: - my_values[i * np.shape(values)[1] + j] = values[i, j, k] + my_values[i * xp.shape(values)[1] + j] = values[i, j, k] break return my_values @@ -214,7 +214,7 @@ def get_span_and_basis(pts, space): Parameters ---------- - pts : np.array + pts : xp.array 2d array of points (ii, iq) = (interval, quadrature point). space : SplineSpace @@ -222,10 +222,10 @@ def get_span_and_basis(pts, space): Returns ------- - span : np.array + span : xp.array 2d array indexed by (n, nq), where n is the interval and nq is the quadrature point in the interval. - basis : np.array + basis : xp.array 3d array of values of basis functions indexed by (n, nq, basis function). """ @@ -235,8 +235,8 @@ def get_span_and_basis(pts, space): T = space.knots p = space.degree - span = np.zeros(pts.shape, dtype=int) - basis = np.zeros((*pts.shape, p + 1), dtype=float) + span = xp.zeros(pts.shape, dtype=int) + basis = xp.zeros((*pts.shape, p + 1), dtype=float) for n in range(pts.shape[0]): for nq in range(pts.shape[1]): @@ -464,7 +464,7 @@ def get_non_zero_B_spline_indices(periodic, IoH, p, B_nbasis, starts, ends, Basi stuck, ) - Basis_functions_indices_B.append(np.array(aux_indices)) + Basis_functions_indices_B.append(xp.array(aux_indices)) def get_non_zero_D_spline_indices(periodic, IoH, p, D_nbasis, starts, ends, Basis_functions_indices_D): @@ -522,7 +522,7 @@ def get_non_zero_D_spline_indices(periodic, IoH, p, D_nbasis, starts, ends, Basi stuck, ) - Basis_functions_indices_D.append(np.array(aux_indices)) + Basis_functions_indices_D.append(xp.array(aux_indices)) def build_translation_list_for_non_zero_spline_indices( @@ -584,18 +584,18 @@ def build_translation_list_for_non_zero_spline_indices( """ translation_indices_B_or_D_splines = [ { - "B": np.full((B_nbasis[h]), fill_value=-1, dtype=int), - "D": np.full((D_nbasis[h]), fill_value=-1, dtype=int), + "B": xp.full((B_nbasis[h]), fill_value=-1, dtype=int), + "D": xp.full((D_nbasis[h]), fill_value=-1, dtype=int), } for h in range(3) ] for h in range(3): - translation_indices_B_or_D_splines[h]["B"][Basis_functions_indices_B[h]] = np.arange( - len(Basis_functions_indices_B[h]) + translation_indices_B_or_D_splines[h]["B"][Basis_functions_indices_B[h]] = xp.arange( + len(Basis_functions_indices_B[h]), ) - translation_indices_B_or_D_splines[h]["D"][Basis_functions_indices_D[h]] = np.arange( - len(Basis_functions_indices_D[h]) + translation_indices_B_or_D_splines[h]["D"][Basis_functions_indices_D[h]] = xp.arange( + len(Basis_functions_indices_D[h]), ) if sp_id in {"Hcurl", "Hdiv", "H1vec"}: @@ -610,7 +610,11 @@ def build_translation_list_for_non_zero_spline_indices( def evaluate_relevant_splines_at_relevant_points( - localpts, Bspaces_1d, Dspaces_1d, Basis_functions_indices_B, Basis_functions_indices_D + localpts, + Bspaces_1d, + Dspaces_1d, + Basis_functions_indices_B, + Basis_functions_indices_D, ): """This function evaluates all the B and D-splines that produce non-zeros in the BasisProjectionOperatorLocal's rows that belong to the current MPI rank over all the local evaluation points. They are store as float arrays in a dictionary of lists. @@ -654,7 +658,7 @@ def evaluate_relevant_splines_at_relevant_points( for h in range(3): # Reshape localpts[h] if necessary localpts_reshaped = ( - localpts[h].reshape((np.shape(localpts[h])[0], 1)) if len(np.shape(localpts[h])) == 1 else localpts[h] + localpts[h].reshape((xp.shape(localpts[h])[0], 1)) if len(xp.shape(localpts[h])) == 1 else localpts[h] ) # Get spans and evaluation values for B-splines and D-splines @@ -693,7 +697,15 @@ def evaluate_relevant_splines_at_relevant_points( def determine_non_zero_rows_for_each_spline( - Basis_functions_indices_B, Basis_functions_indices_D, starts, ends, p, B_nbasis, D_nbasis, periodic, IoH + Basis_functions_indices_B, + Basis_functions_indices_D, + starts, + ends, + p, + B_nbasis, + D_nbasis, + periodic, + IoH, ): """This function determines for which rows (amongst those belonging to the current MPI rank) of the BasisProjectionOperatorLocal each B and D spline, of relevance for the current MPI rank, produces non-zero entries and annotates this regions of non-zeros by saving the rows at which each region starts and ends. @@ -760,7 +772,7 @@ def determine_non_zero_rows_for_each_spline( def process_splines(indices, nbasis, is_D, h): for i in indices[h]: - aux = np.zeros((ends[h] + 1 - starts[h]), dtype=int) + aux = xp.zeros((ends[h] + 1 - starts[h]), dtype=int) get_rows( int(i), int(starts[h]), @@ -774,8 +786,8 @@ def process_splines(indices, nbasis, is_D, h): ) rangestart, rangeend = transform_into_ranges(aux) key = "D" if is_D else "B" - rows_B_or_D_splines[h][key].append(np.array(rangestart, dtype=int)) - rowe_B_or_D_splines[h][key].append(np.array(rangeend, dtype=int)) + rows_B_or_D_splines[h][key].append(xp.array(rangestart, dtype=int)) + rowe_B_or_D_splines[h][key].append(xp.array(rangeend, dtype=int)) for h in range(3): process_splines(Basis_functions_indices_B, B_nbasis, False, h) @@ -792,7 +804,8 @@ def process_splines(indices, nbasis, is_D, h): def get_splines_that_are_relevant_for_at_least_one_block( - Basis_function_indices_agreggated_B, Basis_function_indices_agreggated_D + Basis_function_indices_agreggated_B, + Basis_function_indices_agreggated_D, ): """This function builds one list with all the B-spline indices (and another one for the D-splines) that are required for at least one block of the FE coefficients the current MPI rank needs to build its share of the BasisProjectionOperatorLocal. @@ -891,7 +904,7 @@ def is_spline_zero_at_quadrature_points( for h in range(3): if necessary_direction[h]: for i in Basis_functions_indices_B[h]: - Auxiliar = np.ones((np.shape(localpts[h])[0]), dtype=int) + Auxiliar = xp.ones((xp.shape(localpts[h])[0]), dtype=int) are_quadrature_points_zero( Auxiliar, int( @@ -902,7 +915,7 @@ def is_spline_zero_at_quadrature_points( are_zero_B_or_D_splines[h]["B"].append(Auxiliar) for i in Basis_functions_indices_D[h]: - Auxiliar = np.ones((np.shape(localpts[h])[0]), dtype=int) + Auxiliar = xp.ones((xp.shape(localpts[h])[0]), dtype=int) are_quadrature_points_zero( Auxiliar, int( diff --git a/src/struphy/feec/variational_utilities.py b/src/struphy/feec/variational_utilities.py index 8a9dabe9c..8174a1a5b 100644 --- a/src/struphy/feec/variational_utilities.py +++ b/src/struphy/feec/variational_utilities.py @@ -1,6 +1,6 @@ from copy import deepcopy -import numpy as np +import cunumpy as xp from psydac.linalg.basic import IdentityOperator, Vector from psydac.linalg.block import BlockVector from psydac.linalg.solvers import inverse @@ -94,7 +94,7 @@ def __init__( self.Pcoord3 = CoordinateProjector(2, derham.Vh_pol["v"], derham.Vh_pol["0"]) @ derham.boundary_ops["v"] # Initialize the BasisProjectionOperators - if derham._with_local_projectors == True: + if derham._with_local_projectors: self.PiuT = BasisProjectionOperatorLocal( P0, V1h, @@ -192,10 +192,10 @@ def __init__( # Create tmps for later use in evaluating on the grid grid_shape = tuple([len(loc_grid) for loc_grid in interpolation_grid]) - self._vf_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._gvf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._gvf2_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._gvf3_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._vf_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._gvf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._gvf2_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._gvf3_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] # gradient of the component of the vector field grad = derham.grad_bcfree @@ -264,19 +264,27 @@ def dot(self, v, out=None): self.gv3f.vector = grad_3_v vf_values = self.vf.eval_tp_fixed_loc( - self.interpolation_grid_spans, [self.interpolation_grid_bn] * 3, out=self._vf_values + self.interpolation_grid_spans, + [self.interpolation_grid_bn] * 3, + out=self._vf_values, ) gvf1_values = self.gv1f.eval_tp_fixed_loc( - self.interpolation_grid_spans, self.interpolation_grid_gradient, out=self._gvf1_values + self.interpolation_grid_spans, + self.interpolation_grid_gradient, + out=self._gvf1_values, ) gvf2_values = self.gv2f.eval_tp_fixed_loc( - self.interpolation_grid_spans, self.interpolation_grid_gradient, out=self._gvf2_values + self.interpolation_grid_spans, + self.interpolation_grid_gradient, + out=self._gvf2_values, ) gvf3_values = self.gv3f.eval_tp_fixed_loc( - self.interpolation_grid_spans, self.interpolation_grid_gradient, out=self._gvf3_values + self.interpolation_grid_spans, + self.interpolation_grid_gradient, + out=self._gvf3_values, ) self.PiuT.update_weights([[vf_values[0], vf_values[1], vf_values[2]]]) @@ -378,13 +386,13 @@ def __init__(self, derham, transposed=False, weights=None): ) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_0]) - self._f_0_values = np.zeros(grid_shape, dtype=float) + self._f_0_values = xp.zeros(grid_shape, dtype=float) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_1]) - self._f_1_values = np.zeros(grid_shape, dtype=float) + self._f_1_values = xp.zeros(grid_shape, dtype=float) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_2]) - self._f_2_values = np.zeros(grid_shape, dtype=float) + self._f_2_values = xp.zeros(grid_shape, dtype=float) @property def domain(self): @@ -533,7 +541,7 @@ def __init__(self, derham, transposed=False, weights=None): ) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_0]) - self._bf0_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._bf0_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] self.hist_grid_0_b = [ [self.hist_grid_0_bn[0], self.hist_grid_0_bd[1], self.hist_grid_0_bd[2]], [ @@ -544,7 +552,7 @@ def __init__(self, derham, transposed=False, weights=None): [self.hist_grid_0_bd[0], self.hist_grid_0_bd[1], self.hist_grid_0_bn[2]], ] grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_1]) - self._bf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._bf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] self.hist_grid_1_b = [ [self.hist_grid_1_bn[0], self.hist_grid_1_bd[1], self.hist_grid_1_bd[2]], [ @@ -556,7 +564,7 @@ def __init__(self, derham, transposed=False, weights=None): ] grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_2]) - self._bf2_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._bf2_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] self.hist_grid_2_b = [ [self.hist_grid_2_bn[0], self.hist_grid_2_bd[1], self.hist_grid_2_bd[2]], [ @@ -727,8 +735,8 @@ def __init__(self, derham, phys_domain, Uv, gamma, transposed=False, weights1=No self._proj_p_metric = deepcopy(metric) grid_shape = tuple([len(loc_grid) for loc_grid in int_grid]) - self._pf_values = np.zeros(grid_shape, dtype=float) - self._mapped_pf_values = np.zeros(grid_shape, dtype=float) + self._pf_values = xp.zeros(grid_shape, dtype=float) + self._mapped_pf_values = xp.zeros(grid_shape, dtype=float) # gradient of the component of the vector field @@ -749,13 +757,13 @@ def __init__(self, derham, phys_domain, Uv, gamma, transposed=False, weights1=No ) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_20]) - self._pf_0_values = np.zeros(grid_shape, dtype=float) + self._pf_0_values = xp.zeros(grid_shape, dtype=float) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_21]) - self._pf_1_values = np.zeros(grid_shape, dtype=float) + self._pf_1_values = xp.zeros(grid_shape, dtype=float) grid_shape = tuple([len(loc_grid) for loc_grid in hist_grid_22]) - self._pf_2_values = np.zeros(grid_shape, dtype=float) + self._pf_2_values = xp.zeros(grid_shape, dtype=float) @property def domain(self): @@ -877,21 +885,21 @@ def __init__(self, derham, gamma): self.rhof1 = self._derham.create_spline_function("rhof1", "L2") grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._rhof_values = np.zeros(grid_shape, dtype=float) - self._rhof1_values = np.zeros(grid_shape, dtype=float) - self._sf_values = np.zeros(grid_shape, dtype=float) - self._sf1_values = np.zeros(grid_shape, dtype=float) - self._delta_values = np.zeros(grid_shape, dtype=float) - self._rhof_mid_values = np.zeros(grid_shape, dtype=float) - self._sf_mid_values = np.zeros(grid_shape, dtype=float) - self._eta_values = np.zeros(grid_shape, dtype=float) - self._en_values = np.zeros(grid_shape, dtype=float) - self._en1_values = np.zeros(grid_shape, dtype=float) - self._de_values = np.zeros(grid_shape, dtype=float) - self._d2e_values = np.zeros(grid_shape, dtype=float) - self._tmp_int_grid = np.zeros(grid_shape, dtype=float) - self._tmp_int_grid2 = np.zeros(grid_shape, dtype=float) - self._DG_values = np.zeros(grid_shape, dtype=float) + self._rhof_values = xp.zeros(grid_shape, dtype=float) + self._rhof1_values = xp.zeros(grid_shape, dtype=float) + self._sf_values = xp.zeros(grid_shape, dtype=float) + self._sf1_values = xp.zeros(grid_shape, dtype=float) + self._delta_values = xp.zeros(grid_shape, dtype=float) + self._rhof_mid_values = xp.zeros(grid_shape, dtype=float) + self._sf_mid_values = xp.zeros(grid_shape, dtype=float) + self._eta_values = xp.zeros(grid_shape, dtype=float) + self._en_values = xp.zeros(grid_shape, dtype=float) + self._en1_values = xp.zeros(grid_shape, dtype=float) + self._de_values = xp.zeros(grid_shape, dtype=float) + self._d2e_values = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid2 = xp.zeros(grid_shape, dtype=float) + self._DG_values = xp.zeros(grid_shape, dtype=float) def ener(self, rho, s, out=None): r"""Themodynamical energy as a function of rho and s, usign the perfect gaz hypothesis. @@ -901,13 +909,13 @@ def ener(self, rho, s, out=None): """ gam = self._gamma if out is None: - out = np.power(rho, gam) * np.exp(s / rho) + out = xp.power(rho, gam) * xp.exp(s / rho) else: out *= 0.0 out += s out /= rho - np.exp(out, out=out) - np.power(rho, gam, out=self._tmp_int_grid) + xp.exp(out, out=out) + xp.power(rho, gam, out=self._tmp_int_grid) out *= self._tmp_int_grid return out @@ -919,17 +927,17 @@ def dener_drho(self, rho, s, out=None): """ gam = self._gamma if out is None: - out = (gam * np.power(rho, gam - 1) - s * np.power(rho, gam - 2)) * np.exp(s / rho) + out = (gam * xp.power(rho, gam - 1) - s * xp.power(rho, gam - 2)) * xp.exp(s / rho) else: out *= 0.0 out += s out /= rho - np.exp(out, out=out) + xp.exp(out, out=out) - np.power(rho, gam - 1, out=self._tmp_int_grid) + xp.power(rho, gam - 1, out=self._tmp_int_grid) self._tmp_int_grid *= gam - np.power(rho, gam - 2, out=self._tmp_int_grid2) + xp.power(rho, gam - 2, out=self._tmp_int_grid2) self._tmp_int_grid2 *= s self._tmp_int_grid -= self._tmp_int_grid2 @@ -944,13 +952,13 @@ def dener_ds(self, rho, s, out=None): """ gam = self._gamma if out is None: - out = np.power(rho, gam - 1) * np.exp(s / rho) + out = xp.power(rho, gam - 1) * xp.exp(s / rho) else: out *= 0.0 out += s out /= rho - np.exp(out, out=out) - np.power(rho, gam - 1, out=self._tmp_int_grid) + xp.exp(out, out=out) + xp.power(rho, gam - 1, out=self._tmp_int_grid) out *= self._tmp_int_grid return out @@ -963,25 +971,25 @@ def d2ener_drho2(self, rho, s, out=None): gam = self._gamma if out is None: out = ( - gam * (gam - 1) * np.power(rho, gam - 2) - - s * 2 * (gam - 1) * np.power(rho, gam - 3) - + s**2 * np.power(rho, gam - 4) - ) * np.exp(s / rho) + gam * (gam - 1) * xp.power(rho, gam - 2) + - s * 2 * (gam - 1) * xp.power(rho, gam - 3) + + s**2 * xp.power(rho, gam - 4) + ) * xp.exp(s / rho) else: out *= 0.0 out += s out /= rho - np.exp(out, out=out) + xp.exp(out, out=out) - np.power(rho, gam - 2, out=self._tmp_int_grid) + xp.power(rho, gam - 2, out=self._tmp_int_grid) self._tmp_int_grid *= gam * (gam - 1) - np.power(rho, gam - 3, out=self._tmp_int_grid2) + xp.power(rho, gam - 3, out=self._tmp_int_grid2) self._tmp_int_grid2 *= s self._tmp_int_grid2 *= 2 * (gam - 1) self._tmp_int_grid -= self._tmp_int_grid2 - np.power(rho, gam - 4, out=self._tmp_int_grid2) + xp.power(rho, gam - 4, out=self._tmp_int_grid2) self._tmp_int_grid2 *= s self._tmp_int_grid2 *= s self._tmp_int_grid += self._tmp_int_grid2 @@ -996,27 +1004,27 @@ def d2ener_ds2(self, rho, s, out=None): """ gam = self._gamma if out is None: - out = np.power(rho, gam - 2) * np.exp(s / rho) + out = xp.power(rho, gam - 2) * xp.exp(s / rho) else: out *= 0.0 out += s out /= rho - np.exp(out, out=out) - np.power(rho, gam - 2, out=self._tmp_int_grid) + xp.exp(out, out=out) + xp.power(rho, gam - 2, out=self._tmp_int_grid) out *= self._tmp_int_grid return out def eta(self, delta_x, out=None): r"""Switch function :math:`\eta(\delta) = 1- \text{exp}((-\delta/10^{-5})^2)`.""" if out is None: - out = 1.0 - np.exp(-((delta_x / 1e-5) ** 2)) + out = 1.0 - xp.exp(-((delta_x / 1e-5) ** 2)) else: out *= 0.0 out += delta_x out /= 1e-5 out **= 2 out *= -1 - np.exp(out, out=out) + xp.exp(out, out=out) out *= -1 out += 1.0 return out @@ -1328,7 +1336,7 @@ def __init__(self, derham, mass_ops, domain): ) grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._f_values = np.zeros(grid_shape, dtype=float) + self._f_values = xp.zeros(grid_shape, dtype=float) metric = domain.metric(*integration_grid) self._mass_metric_term = deepcopy(metric) @@ -1383,7 +1391,12 @@ def update_weight(self, coeffs): self._pc.update_mass_operator(self._massop) def _create_inv( - self, type="pcg", pc_type="MassMatrixDiagonalPreconditioner", tol=1e-16, maxiter=500, verbose=False + self, + type="pcg", + pc_type="MassMatrixDiagonalPreconditioner", + tol=1e-16, + maxiter=500, + verbose=False, ): """Inverse the weighted mass matrix""" if pc_type is None: @@ -1437,10 +1450,10 @@ def __init__(self, derham, domain, mass_ops): self.uf = derham.create_spline_function("uf", "H1vec") self.uf1 = derham.create_spline_function("uf1", "H1vec") - self._uf_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._uf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._Guf_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._tmp_int_grid = np.zeros(grid_shape, dtype=float) + self._uf_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._Guf_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) metric = domain.metric( *integration_grid, diff --git a/src/struphy/fields_background/base.py b/src/struphy/fields_background/base.py index ef6b3a769..7ad6e3887 100644 --- a/src/struphy/fields_background/base.py +++ b/src/struphy/fields_background/base.py @@ -2,7 +2,7 @@ from abc import ABCMeta, abstractmethod -import numpy as np +import cunumpy as xp from matplotlib import pyplot as plt from pyevtk.hl import gridToVTK @@ -140,7 +140,7 @@ def t3(self, *etas, squeeze_out=False): def vth0(self, *etas, squeeze_out=False): """0-form thermal velocity on logical cube [0, 1]^3.""" - return np.sqrt(self.t0(*etas, squeeze_out=squeeze_out)) + return xp.sqrt(self.t0(*etas, squeeze_out=squeeze_out)) def vth3(self, *etas, squeeze_out=False): """3-form thermal velocity on logical cube [0, 1]^3.""" @@ -156,7 +156,7 @@ def q0(self, *etas, squeeze_out=False): """0-form square root of the pressure on logical cube [0, 1]^3.""" # xyz = self.domain(*etas, squeeze_out=False) p = self.p0(*etas) - q = np.sqrt(p) + q = xp.sqrt(p) return self.domain.pull(q, *etas, kind="0", squeeze_out=squeeze_out) def q3(self, *etas, squeeze_out=False): @@ -176,7 +176,7 @@ def s0_monoatomic(self, *etas, squeeze_out=False): # xyz = self.domain(*etas, squeeze_out=False) p = self.p0(*etas) n = self.n0(*etas) - s = n * np.log(p / (2 / 3 * np.power(n, 5 / 3))) + s = n * xp.log(p / (2 / 3 * xp.power(n, 5 / 3))) return self.domain.pull(s, *etas, kind="0", squeeze_out=squeeze_out) def s3_monoatomic(self, *etas, squeeze_out=False): @@ -198,7 +198,7 @@ def s0_diatomic(self, *etas, squeeze_out=False): # xyz = self.domain(*etas, squeeze_out=False) p = self.p0(*etas) n = self.n0(*etas) - s = n * np.log(p / (2 / 5 * np.power(n, 7 / 5))) + s = n * xp.log(p / (2 / 5 * xp.power(n, 7 / 5))) return self.domain.pull(s, *etas, kind="0", squeeze_out=squeeze_out) def s3_diatomic(self, *etas, squeeze_out=False): @@ -395,7 +395,7 @@ def unit_b_cart(self, *etas, squeeze_out=False): """Unit vector Cartesian components of magnetic field evaluated on logical cube [0, 1]^3. Returns also (x,y,z).""" b, xyz = self.b_cart(*etas, squeeze_out=squeeze_out) absB = self.absB0(*etas, squeeze_out=squeeze_out) - out = np.array([b[0] / absB, b[1] / absB, b[2] / absB], dtype=float) + out = xp.array([b[0] / absB, b[1] / absB, b[2] / absB], dtype=float) return out, xyz def gradB1(self, *etas, squeeze_out=False): @@ -481,7 +481,7 @@ def av(self, *etas, squeeze_out=False): def absB0(self, *etas, squeeze_out=False): """0-form absolute value of magnetic field on logical cube [0, 1]^3.""" b, xyz = self.b_cart(*etas, squeeze_out=squeeze_out) - return np.sqrt(b[0] ** 2 + b[1] ** 2 + b[2] ** 2) + return xp.sqrt(b[0] ** 2 + b[1] ** 2 + b[2] ** 2) def absB3(self, *etas, squeeze_out=False): """3-form absolute value of magnetic field on logical cube [0, 1]^3.""" @@ -783,19 +783,28 @@ def u_cart(self, *etas, squeeze_out=False): def curl_unit_b1(self, *etas, squeeze_out=False): """1-form components of curl of unit magnetic field evaluated on logical cube [0, 1]^3. Returns also (x,y,z).""" return self.domain.pull( - self.curl_unit_b_cart(*etas, squeeze_out=False)[0], *etas, kind="1", squeeze_out=squeeze_out + self.curl_unit_b_cart(*etas, squeeze_out=False)[0], + *etas, + kind="1", + squeeze_out=squeeze_out, ) def curl_unit_b2(self, *etas, squeeze_out=False): """2-form components of curl of unit magnetic field evaluated on logical cube [0, 1]^3. Returns also (x,y,z).""" return self.domain.pull( - self.curl_unit_b_cart(*etas, squeeze_out=False)[0], *etas, kind="2", squeeze_out=squeeze_out + self.curl_unit_b_cart(*etas, squeeze_out=False)[0], + *etas, + kind="2", + squeeze_out=squeeze_out, ) def curl_unit_bv(self, *etas, squeeze_out=False): """Contra-variant components of curl of unit magnetic field evaluated on logical cube [0, 1]^3. Returns also (x,y,z).""" return self.domain.pull( - self.curl_unit_b_cart(*etas, squeeze_out=False)[0], *etas, kind="v", squeeze_out=squeeze_out + self.curl_unit_b_cart(*etas, squeeze_out=False)[0], + *etas, + kind="v", + squeeze_out=squeeze_out, ) def curl_unit_b_cart(self, *etas, squeeze_out=False): @@ -804,7 +813,7 @@ def curl_unit_b_cart(self, *etas, squeeze_out=False): j, xyz = self.j_cart(*etas, squeeze_out=squeeze_out) gradB, xyz = self.gradB_cart(*etas, squeeze_out=squeeze_out) absB = self.absB0(*etas, squeeze_out=squeeze_out) - out = np.array( + out = xp.array( [ j[0] / absB + (b[1] * gradB[2] - b[2] * gradB[1]) / absB**2, j[1] / absB + (b[2] * gradB[0] - b[0] * gradB[2]) / absB**2, @@ -908,9 +917,9 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): "HollowTorus", ) - e1 = np.linspace(0.0001, 1, n1) - e2 = np.linspace(0, 1, n2) - e3 = np.linspace(0, 1, n3) + e1 = xp.linspace(0.0001, 1, n1) + e2 = xp.linspace(0, 1, n2) + e3 = xp.linspace(0, 1, n3) if self.domain.__class__.__name__ in ("GVECunit", "DESCunit"): if n_planes > 1: @@ -935,7 +944,7 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): print("Computation of abs(B) done.") j_cart, xyz = self.j_cart(e1, e2, e3) print("Computation of current density done.") - absJ = np.sqrt(j_cart[0] ** 2 + j_cart[1] ** 2 + j_cart[2] ** 2) + absJ = xp.sqrt(j_cart[0] ** 2 + j_cart[1] ** 2 + j_cart[2] ** 2) _path = struphy.__path__[0] + "/fields_background/mhd_equil/gvec/output/" gridToVTK( @@ -958,24 +967,24 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): print(key, ": ", val) # poloidal plane grid - fig = plt.figure(figsize=(13, np.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(13, xp.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xp = x[:, :, int(n * jump)].squeeze() + xpp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = np.sqrt(xp**2 + yp**2) + pc1 = xp.sqrt(xpp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xp + pc1 = xpp pc2 = yp l1 = "x" l2 = "y" - ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) for i in range(pc1.shape[0]): for j in range(pc1.shape[1] - 1): if i < pc1.shape[0] - 1: @@ -1004,26 +1013,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): ) # top view - e1 = np.linspace(0, 1, n1) # radial coordinate in [0, 1] - e2 = np.linspace(0, 1, 3) # poloidal angle in [0, 1] - e3 = np.linspace(0, 1, n3) # toroidal angle in [0, 1] + e1 = xp.linspace(0, 1, n1) # radial coordinate in [0, 1] + e2 = xp.linspace(0, 1, 3) # poloidal angle in [0, 1] + e3 = xp.linspace(0, 1, n3) # toroidal angle in [0, 1] xt, yt, zt = self.domain(e1, e2, e3) fig = plt.figure(figsize=(13, 2 * 6.5)) ax = fig.add_subplot() for m in range(2): - xp = xt[:, m, :].squeeze() + xpp = xt[:, m, :].squeeze() yp = yt[:, m, :].squeeze() zp = zt[:, m, :].squeeze() if self.domain.__class__.__name__ in torus_mappings: - tc1 = xp + tc1 = xpp tc2 = yp l1 = "x" l2 = "y" else: - tc1 = xp + tc1 = xpp tc2 = zp l1 = "x" l2 = "z" @@ -1058,26 +1067,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): ax.set_title("Device top view") # Jacobian determinant - fig = plt.figure(figsize=(13, np.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(13, xp.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xp = x[:, :, int(n * jump)].squeeze() + xpp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = np.sqrt(xp**2 + yp**2) + pc1 = xp.sqrt(xpp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xp + pc1 = xpp pc2 = yp l1 = "x" l2 = "y" detp = det_df[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, detp, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1088,26 +1097,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): fig.colorbar(map, ax=ax, location="right") # pressure - fig = plt.figure(figsize=(15, np.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(15, xp.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xp = x[:, :, int(n * jump)].squeeze() + xpp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = np.sqrt(xp**2 + yp**2) + pc1 = xp.sqrt(xpp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xp + pc1 = xpp pc2 = yp l1 = "x" l2 = "y" pp = p[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, pp, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1118,26 +1127,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): fig.colorbar(map, ax=ax, location="right") # density - fig = plt.figure(figsize=(15, np.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(15, xp.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xp = x[:, :, int(n * jump)].squeeze() + xpp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = np.sqrt(xp**2 + yp**2) + pc1 = xp.sqrt(xpp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xp + pc1 = xpp pc2 = yp l1 = "x" l2 = "y" nn = n_dens[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, nn, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1148,26 +1157,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): fig.colorbar(map, ax=ax, location="right") # magnetic field strength - fig = plt.figure(figsize=(15, np.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(15, xp.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xp = x[:, :, int(n * jump)].squeeze() + xpp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = np.sqrt(xp**2 + yp**2) + pc1 = xp.sqrt(xpp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xp + pc1 = xpp pc2 = yp l1 = "x" l2 = "y" ab = absB[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, ab, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1178,26 +1187,26 @@ def show(self, n1=16, n2=33, n3=21, n_planes=5): fig.colorbar(map, ax=ax, location="right") # current density - fig = plt.figure(figsize=(15, np.ceil(n_planes / 2) * 6.5)) + fig = plt.figure(figsize=(15, xp.ceil(n_planes / 2) * 6.5)) for n in range(n_planes): - xp = x[:, :, int(n * jump)].squeeze() + xpp = x[:, :, int(n * jump)].squeeze() yp = y[:, :, int(n * jump)].squeeze() zp = z[:, :, int(n * jump)].squeeze() if self.domain.__class__.__name__ in torus_mappings: - pc1 = np.sqrt(xp**2 + yp**2) + pc1 = xp.sqrt(xpp**2 + yp**2) pc2 = zp l1 = "R" l2 = "Z" else: - pc1 = xp + pc1 = xpp pc2 = yp l1 = "x" l2 = "y" ab = absJ[:, :, int(n * jump)].squeeze() - ax = fig.add_subplot(int(np.ceil(n_planes / 2)), 2, n + 1) + ax = fig.add_subplot(int(xp.ceil(n_planes / 2)), 2, n + 1) map = ax.contourf(pc1, pc2, ab, 30) ax.set_xlabel(l1) ax.set_ylabel(l2) @@ -1307,8 +1316,8 @@ def b_xyz(self, x, y, z): BZ = self.psi(R, Z, dR=1) / R # push-forward to Cartesian components - Bx = BR * np.cos(Phi) - BP * np.sin(Phi) - By = BR * np.sin(Phi) + BP * np.cos(Phi) + Bx = BR * xp.cos(Phi) - BP * xp.sin(Phi) + By = BR * xp.sin(Phi) + BP * xp.cos(Phi) Bz = 1 * BZ return Bx, By, Bz @@ -1324,8 +1333,8 @@ def j_xyz(self, x, y, z): jZ = self.g_tor(R, Z, dR=1) / R # push-forward to Cartesian components - jx = jR * np.cos(Phi) - jP * np.sin(Phi) - jy = jR * np.sin(Phi) + jP * np.cos(Phi) + jx = jR * xp.cos(Phi) - jP * xp.sin(Phi) + jy = jR * xp.sin(Phi) + jP * xp.cos(Phi) jz = 1 * jZ return jx, jy, jz @@ -1335,7 +1344,7 @@ def gradB_xyz(self, x, y, z): R, Phi, Z = self.inverse_map(x, y, z) - RabsB = np.sqrt( + RabsB = xp.sqrt( self.psi(R, Z, dZ=1) ** 2 + self.g_tor(R, Z) ** 2 + self.psi(R, Z, dR=1) ** 2, ) @@ -1363,8 +1372,8 @@ def gradB_xyz(self, x, y, z): ) # push-forward to Cartesian components - gradBx = gradBR * np.cos(Phi) - gradBP * np.sin(Phi) - gradBy = gradBR * np.sin(Phi) + gradBP * np.cos(Phi) + gradBx = gradBR * xp.cos(Phi) - gradBP * xp.sin(Phi) + gradBy = gradBR * xp.sin(Phi) + gradBP * xp.cos(Phi) gradBz = 1 * gradBZ return gradBx, gradBy, gradBz @@ -1373,8 +1382,8 @@ def gradB_xyz(self, x, y, z): def inverse_map(x, y, z): """Inverse cylindrical mapping.""" - R = np.sqrt(x**2 + y**2) - P = np.arctan2(y, x) + R = xp.sqrt(x**2 + y**2) + P = xp.arctan2(y, x) Z = 1 * z return R, P, Z diff --git a/src/struphy/fields_background/equils.py b/src/struphy/fields_background/equils.py index 96febc74b..f1afacd35 100644 --- a/src/struphy/fields_background/equils.py +++ b/src/struphy/fields_background/equils.py @@ -7,7 +7,7 @@ import warnings from time import time -import numpy as np +import cunumpy as xp from scipy.integrate import odeint, quad from scipy.interpolate import RectBivariateSpline, UnivariateSpline from scipy.optimize import fsolve, minimize @@ -178,7 +178,8 @@ class ShearedSlab(CartesianMHDequilibrium): Ion number density at x=a (default: 1.). beta : float Plasma beta (ratio of kinematic pressure to B^2/2, default: 0.1). - + q_kind : int + Kind of safety factor profile, (0 or 1, default: 0). Note ---- In the parameter .yml, use the following in the section ``fluid_background``:: @@ -193,6 +194,7 @@ class ShearedSlab(CartesianMHDequilibrium): n2 : 0. # 2nd shape factor for ion number density profile na : 1. # number density at r=a beta : .1 # plasma beta = p*2/B^2 + q_kind : 0. # kind of safety factor profile """ def __init__( @@ -206,6 +208,7 @@ def __init__( n2: float = 0.0, na: float = 1.0, beta: float = 0.1, + q_kind: int = 0, ): # use params setter self.params = copy.deepcopy(locals()) @@ -226,10 +229,19 @@ def q_x(self, x, der=0): qout = 0 * x else: - if der == 0: - qout = self.params["q0"] + (self.params["q1"] - self.params["q0"]) * (x / self.params["a"]) ** 2 + if self.params["q_kind"] == 0: + if der == 0: + qout = self.params["q0"] + (self.params["q1"] - self.params["q0"]) * (x / self.params["a"]) ** 2 + else: + qout = 2 * (self.params["q1"] - self.params["q0"]) * x / self.params["a"] ** 2 + else: - qout = 2 * (self.params["q1"] - self.params["q0"]) * x / self.params["a"] ** 2 + if der == 0: + qout = self.params["q0"] + self.params["q1"] * xp.sin(2.0 * xp.pi * x / self.params["a"]) + else: + qout = ( + 2.0 * xp.pi / self.params["a"] * self.params["q1"] * xp.cos(2.0 * xp.pi * x / self.params["a"]) + ) return qout @@ -239,7 +251,7 @@ def p_x(self, x): eps = self.params["a"] / self.params["R0"] - if np.all(q >= 100.0): + if xp.all(q >= 100.0): pout = self.params["B0"] ** 2 * self.params["beta"] / 2.0 - 0 * x else: pout = self.params["B0"] ** 2 * self.params["beta"] / 2.0 * (1 + eps**2 / q**2) + self.params[ @@ -261,7 +273,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - x = np.linspace(0.0, self.params["a"], n_pts) + x = xp.linspace(0.0, self.params["a"], n_pts) fig, ax = plt.subplots(1, 3) @@ -295,7 +307,7 @@ def b_xyz(self, x, y, z): q = self.q_x(x) eps = self.params["a"] / self.params["R0"] - if np.all(q >= 100.0): + if xp.all(q >= 100.0): by = 0 * x bz = self.params["B0"] - 0 * x else: @@ -312,7 +324,7 @@ def j_xyz(self, x, y, z): q = self.q_x(x) eps = self.params["a"] / self.params["R0"] - if np.all(q >= 100.0): + if xp.all(q >= 100.0): jz = 0 * x else: jz = -self.params["B0"] * eps * self.q_x(x, der=1) / q**2 @@ -341,13 +353,13 @@ def gradB_xyz(self, x, y, z): q = self.q_x(x) eps = self.params["a"] / self.params["R0"] - if np.all(q >= 100.0): + if xp.all(q >= 100.0): gradBx = 0 * x else: gradBx = ( -self.params["B0"] * eps**2 - / np.sqrt(1 + eps**2 / self.q_x(x) ** 2) + / xp.sqrt(1 + eps**2 / self.q_x(x) ** 2) * self.q_x(x, der=1) / self.q_x(x) ** 3 ) @@ -445,8 +457,8 @@ def __init__( def T_z(self, z): r"""Swap function T(z) = \tanh(z - z_1)/\delta) - \tanh(z - z_2)/\delta)""" Tout = ( - np.tanh((z - self.params["z1"]) / self.params["delta"]) - - np.tanh((z - self.params["z2"]) / self.params["delta"]) + xp.tanh((z - self.params["z1"]) / self.params["delta"]) + - xp.tanh((z - self.params["z2"]) / self.params["delta"]) ) / 2.0 return Tout @@ -468,7 +480,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - z = np.linspace(0.0, self.params["c"], n_pts) + z = xp.linspace(0.0, self.params["c"], n_pts) fig, ax = plt.subplots(1, 3) @@ -635,8 +647,8 @@ def __init__( self.params = copy.deepcopy(locals()) # inverse cylindrical coordinate transformation (x, y, z) --> (r, theta, phi) - self.r = lambda x, y, z: np.sqrt(x**2 + y**2) - self.theta = lambda x, y, z: np.arctan2(y, x) + self.r = lambda x, y, z: xp.sqrt(x**2 + y**2) + self.theta = lambda x, y, z: xp.arctan2(y, x) self.z = lambda x, y, z: 1 * z # =============================================================== @@ -695,7 +707,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - r = np.linspace(0.0, self.params["a"], n_pts) + r = xp.linspace(0.0, self.params["a"], n_pts) fig, ax = plt.subplots(1, 3) @@ -706,7 +718,7 @@ def plot_profiles(self, n_pts=501): ax[0].set_xlabel("r") ax[0].set_ylabel("q") - ax[0].plot(r, np.ones(r.size), "k--") + ax[0].plot(r, xp.ones(r.size), "k--") ax[1].plot(r, self.p_r(r)) ax[1].set_xlabel("r") @@ -731,13 +743,13 @@ def b_xyz(self, x, y, z): theta = self.theta(x, y, z) q = self.q_r(r) # azimuthal component - if np.all(q >= 100.0): + if xp.all(q >= 100.0): b_theta = 0 * r else: b_theta = self.params["B0"] * r / (self.params["R0"] * q) # cartesian x-component - bx = -b_theta * np.sin(theta) - by = b_theta * np.cos(theta) + bx = -b_theta * xp.sin(theta) + by = b_theta * xp.cos(theta) bz = self.params["B0"] - 0 * x return bx, by, bz @@ -751,7 +763,7 @@ def j_xyz(self, x, y, z): r = self.r(x, y, z) q = self.q_r(r) q_p = self.q_r(r, der=1) - if np.all(q >= 100.0): + if xp.all(q >= 100.0): jz = 0 * x else: jz = self.params["B0"] / (self.params["R0"] * q**2) * (2 * q - r * q_p) @@ -778,13 +790,13 @@ def gradB_xyz(self, x, y, z): r = self.r(x, y, z) theta = self.theta(x, y, z) q = self.q_r(r) - if np.all(q >= 100.0): + if xp.all(q >= 100.0): gradBr = 0 * x else: gradBr = ( self.params["B0"] / self.params["R0"] ** 2 - / np.sqrt( + / xp.sqrt( 1 + r**2 / self.q_r( @@ -795,8 +807,8 @@ def gradB_xyz(self, x, y, z): ) * (r / self.q_r(r) ** 2 - r**2 / self.q_r(r) ** 3 * self.q_r(r, der=1)) ) - gradBx = gradBr * np.cos(theta) - gradBy = gradBr * np.sin(theta) + gradBx = gradBr * xp.cos(theta) + gradBy = gradBr * xp.sin(theta) gradBz = 0 * x return gradBx, gradBy, gradBz @@ -935,10 +947,10 @@ def __init__( self.params = copy.deepcopy(locals()) # plasma boundary contour - ths = np.linspace(0.0, 2 * np.pi, 201) + ths = xp.linspace(0.0, 2 * xp.pi, 201) - self._rbs = self.params["R0"] * (1 + self.params["a"] / self.params["R0"] * np.cos(ths)) - self._zbs = self.params["a"] * np.sin(ths) + self._rbs = self.params["R0"] * (1 + self.params["a"] / self.params["R0"] * xp.cos(ths)) + self._zbs = self.params["a"] * xp.sin(ths) # set on-axis and boundary fluxes if self.params["q_kind"] == 0: @@ -949,12 +961,12 @@ def __init__( self._p_i = None else: - r_i = np.linspace(0.0, self.params["a"], self.params["psi_nel"] + 1) + r_i = xp.linspace(0.0, self.params["a"], self.params["psi_nel"] + 1) def dpsi_dr(r): - return self.params["B0"] * r / (self.q_r(r) * np.sqrt(1 - r**2 / self.params["R0"] ** 2)) + return self.params["B0"] * r / (self.q_r(r) * xp.sqrt(1 - r**2 / self.params["R0"] ** 2)) - psis = np.zeros_like(r_i) + psis = xp.zeros_like(r_i) for i, rr in enumerate(r_i): psis[i] = quad(dpsi_dr, 0.0, rr)[0] @@ -977,7 +989,7 @@ def dp_dr(r): * (2 * self.q_r(r) - r * self.q_r(r, der=1)) ) - ps = np.zeros_like(r_i) + ps = xp.zeros_like(r_i) for i, rr in enumerate(r_i): ps[i] = quad(dp_dr, 0.0, rr)[0] @@ -1032,7 +1044,7 @@ def psi_r(self, r, der=0): dq = q1 - q0 # geometric correction factor and its first derivative - gf_0 = np.sqrt(1 - (r / self.params["R0"]) ** 2) + gf_0 = xp.sqrt(1 - (r / self.params["R0"]) ** 2) gf_1 = -r / (self.params["R0"] ** 2 * gf_0) # safety factors @@ -1043,9 +1055,9 @@ def psi_r(self, r, der=0): q_bar_1 = q_1 * gf_0 + q_0 * gf_1 if der == 0: - out = -self.params["B0"] * self.params["a"] ** 2 / np.sqrt(dq * q0 * eps**2 + dq**2) - out *= np.arctanh( - np.sqrt((dq - dq * (r / self.params["R0"]) ** 2) / (q0 * eps**2 + dq)), + out = -self.params["B0"] * self.params["a"] ** 2 / xp.sqrt(dq * q0 * eps**2 + dq**2) + out *= xp.arctanh( + xp.sqrt((dq - dq * (r / self.params["R0"]) ** 2) / (q0 * eps**2 + dq)), ) elif der == 1: out = self.params["B0"] * r / q_bar_0 @@ -1115,10 +1127,10 @@ def q_r(self, r, der=0): r_flat = r.flatten() - r_zeros = np.where(r_flat == 0.0)[0] - r_nzero = np.where(r_flat != 0.0)[0] + r_zeros = xp.where(r_flat == 0.0)[0] + r_nzero = xp.where(r_flat != 0.0)[0] - qout = np.zeros(r_flat.size, dtype=float) + qout = xp.zeros(r_flat.size, dtype=float) if der == 0: if self.params["q0"] == self.params["q1"]: @@ -1211,7 +1223,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - r = np.linspace(0.0, self.params["a"], n_pts) + r = xp.linspace(0.0, self.params["a"], n_pts) fig, ax = plt.subplots(2, 2) @@ -1245,7 +1257,7 @@ def plot_profiles(self, n_pts=501): def psi(self, R, Z, dR=0, dZ=0): """Poloidal flux function psi = psi(R, Z).""" - r = np.sqrt(Z**2 + (R - self.params["R0"]) ** 2) + r = xp.sqrt(Z**2 + (R - self.params["R0"]) ** 2) if dR == 0 and dZ == 0: out = self.psi_r(r, der=0) @@ -1293,7 +1305,7 @@ def g_tor(self, R, Z, dR=0, dZ=0): def p_xyz(self, x, y, z): """Pressure p = p(x, y, z).""" - r = np.sqrt((np.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) + r = xp.sqrt((xp.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) pp = self.p_r(r) @@ -1301,7 +1313,7 @@ def p_xyz(self, x, y, z): def n_xyz(self, x, y, z): """Number density n = n(x, y, z).""" - r = np.sqrt((np.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) + r = xp.sqrt((xp.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) nn = self.n_r(r) @@ -1429,10 +1441,10 @@ def __init__( self.params = copy.deepcopy(locals()) # plasma boundary contour - ths = np.linspace(0.0, 2 * np.pi, 201) + ths = xp.linspace(0.0, 2 * xp.pi, 201) - self._rbs = self.params["R0"] * (1 + self.params["a"] / self.params["R0"] * np.cos(ths)) - self._zbs = self.params["a"] * np.sin(ths) + self._rbs = self.params["R0"] * (1 + self.params["a"] / self.params["R0"] * xp.cos(ths)) + self._zbs = self.params["a"] * xp.sin(ths) # on-axis flux (arbitrary value) self._psi0 = -10.0 @@ -1453,12 +1465,12 @@ def dpsi_dr(psi, r, psi1): q = q0 + psi_norm * (q1 - q0 + (q1p - q1 + q0) * (1 - psi_s) * (psi_norm - 1) / (psi_norm - psi_s)) - out = B0 * r / (q * np.sqrt(1 - r**2 / R0**2)) + out = B0 * r / (q * xp.sqrt(1 - r**2 / R0**2)) return out # solve differential equation and fix boundary flux - r_i = np.linspace(0.0, self.params["a"], self.params["psi_nel"] + 1) + r_i = xp.linspace(0.0, self.params["a"], self.params["psi_nel"] + 1) def fun(psi1): out = odeint(dpsi_dr, self._psi0, r_i, args=(psi1,)).flatten() @@ -1545,13 +1557,13 @@ def p_psi(self, psi, der=0): psi_norm = (psi - self._psi0) / (self._psi1 - self._psi0) if der == 0: - out = self.params["beta"] * self.params["B0"] ** 2 / 2.0 * np.exp(-psi_norm / p1) + out = self.params["beta"] * self.params["B0"] ** 2 / 2.0 * xp.exp(-psi_norm / p1) else: out = ( -self.params["beta"] * self.params["B0"] ** 2 / 2.0 - * np.exp(-psi_norm / p1) + * xp.exp(-psi_norm / p1) / (p1 * (self._psi1 - self._psi0)) ) @@ -1580,8 +1592,8 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - r = np.linspace(0.0, self.params["a"], n_pts) - psi = np.linspace(self._psi0, self._psi1, n_pts) + r = xp.linspace(0.0, self.params["a"], n_pts) + psi = xp.linspace(self._psi0, self._psi1, n_pts) fig, ax = plt.subplots(2, 2) @@ -1615,7 +1627,7 @@ def plot_profiles(self, n_pts=501): def psi(self, R, Z, dR=0, dZ=0): """Poloidal flux function psi = psi(R, Z).""" - r = np.sqrt(Z**2 + (R - self.params["R0"]) ** 2) + r = xp.sqrt(Z**2 + (R - self.params["R0"]) ** 2) if dR == 0 and dZ == 0: out = self.psi_r(r, der=0) @@ -1659,13 +1671,13 @@ def g_tor(self, R, Z, dR=0, dZ=0): def p_xyz(self, x, y, z): """Pressure p = p(x, y, z).""" - r = np.sqrt((np.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) + r = xp.sqrt((xp.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) return self.p_psi(self.psi_r(r)) def n_xyz(self, x, y, z): """Number density n = n(x, y, z).""" - r = np.sqrt((np.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) + r = xp.sqrt((xp.sqrt(x**2 + y**2) - self._params["R0"]) ** 2 + z**2) return self.n_psi(self.psi_r(r)) @@ -1747,7 +1759,7 @@ def __init__( units["p"] = 1.0 units["n"] = 1e20 warnings.warn( - f"{units = }, no rescaling performed in EQDSK output.", + f"{units =}, no rescaling performed in EQDSK output.", ) self._units = units @@ -1803,8 +1815,8 @@ def __init__( self._r_range = [rleft, rleft + rdim] self._z_range = [zmid - zdim / 2, zmid + zdim / 2] - R = np.linspace(self._r_range[0], self._r_range[1], nR) - Z = np.linspace(self._z_range[0], self._z_range[1], nZ) + R = xp.linspace(self._r_range[0], self._r_range[1], nR) + Z = xp.linspace(self._z_range[0], self._z_range[1], nZ) smooth_steps = [ int(1 / (self.params["psi_resolution"][0] * 0.01)), @@ -1834,7 +1846,7 @@ def __init__( self._psi1 = psi_edge # interpolate toroidal field function, pressure profile and q-profile on unifrom flux grid from axis to boundary - flux_grid = np.linspace(self._psi0, self._psi1, g_profile.size) + flux_grid = xp.linspace(self._psi0, self._psi1, g_profile.size) smooth_step = int(1 / (self.params["flux_resolution"] * 0.01)) @@ -2006,7 +2018,7 @@ def g_tor(self, R, Z, dR=0, dZ=0): def p_xyz(self, x, y, z): """Pressure p = p(x, y, z) in units 1 Tesla^2/mu_0.""" - R = np.sqrt(x**2 + y**2) + R = xp.sqrt(x**2 + y**2) Z = 1 * z out = self.p_psi(self.psi(R, Z)) @@ -2019,7 +2031,7 @@ def p_xyz(self, x, y, z): def n_xyz(self, x, y, z): """Number density in physical space. Units from parameter file.""" - R = np.sqrt(x**2 + y**2) + R = xp.sqrt(x**2 + y**2) Z = 1 * z out = self.n_psi(self.psi(R, Z)) @@ -2121,7 +2133,7 @@ def __init__( with pytest.raises(SystemExit) as exc: print("Simulation aborted, gvec must be installed (pip install gvec)!") sys.exit(1) - print(f"{exc.value.code = }") + print(f"{exc.value.code =}") import gvec @@ -2136,7 +2148,7 @@ def __init__( units["p"] = 1.0 units["n"] = 1e20 warnings.warn( - f"{units = }, no rescaling performed in GVEC output.", + f"{units =}, no rescaling performed in GVEC output.", ) self._units = units @@ -2199,9 +2211,9 @@ def bv(self, *etas, squeeze_out=False): bt += "_B" bz += "_B" self.state.compute(ev, bt, bz) - bv_2 = getattr(ev, bt).data / (2 * np.pi) - bv_3 = getattr(ev, bz).data / (2 * np.pi) * self._nfp - out = (np.zeros_like(bv_2), bv_2, bv_3) + bv_2 = getattr(ev, bt).data / (2 * xp.pi) + bv_3 = getattr(ev, bz).data / (2 * xp.pi) * self._nfp + out = (xp.zeros_like(bv_2), bv_2, bv_3) # apply struphy units for o in out: @@ -2219,8 +2231,8 @@ def jv(self, *etas, squeeze_out=False): self.state.compute(ev, jr, jt, jz) rmin = self._params["rmin"] jv_1 = ev.J_contra_r.data / (1.0 - rmin) - jv_2 = ev.J_contra_t.data / (2 * np.pi) - jv_3 = ev.J_contra_z.data / (2 * np.pi) * self._nfp + jv_2 = ev.J_contra_t.data / (2 * xp.pi) + jv_3 = ev.J_contra_z.data / (2 * xp.pi) * self._nfp if self.params["use_boozer"]: warnings.warn("GVEC current density in Boozer coords not yet implemented, set to zero.") # jr += "_B" @@ -2245,11 +2257,11 @@ def p0(self, *etas, squeeze_out=False): if not flat_eval: eta2 = etas[1] eta3 = etas[2] - if isinstance(eta2, np.ndarray): + if isinstance(eta2, xp.ndarray): if eta2.ndim == 3: eta2 = eta2[0, :, 0] eta3 = eta3[0, 0, :] - tmp, _1, _2 = np.meshgrid(ev.p.data, eta2, eta3, indexing="ij") + tmp, _1, _2 = xp.meshgrid(ev.p.data, eta2, eta3, indexing="ij") else: tmp = ev.p.data @@ -2309,7 +2321,7 @@ def _gvec_evaluations(self, *etas): etas = list(etas) for i, eta in enumerate(etas): if isinstance(eta, (float, int)): - etas[i] = np.array((eta,)) + etas[i] = xp.array((eta,)) assert etas[0].ndim == etas[1].ndim == etas[2].ndim if etas[0].ndim == 1: eta1 = etas[0] @@ -2326,8 +2338,8 @@ def _gvec_evaluations(self, *etas): # gvec coordinates rho = rmin + eta1 * (1.0 - rmin) - theta = 2 * np.pi * eta2 - zeta = 2 * np.pi * eta3 + theta = 2 * xp.pi * eta2 + zeta = 2 * xp.pi * eta3 # evaluate if self.params["use_boozer"]: @@ -2416,7 +2428,7 @@ def __init__( units["p"] = 1.0 units["n"] = 1e20 warnings.warn( - f"{units = }, no rescaling performed in DESC output.", + f"{units =}, no rescaling performed in DESC output.", ) self._units = units @@ -2497,7 +2509,7 @@ def bv(self, *etas, squeeze_out=False): li = [] for gi, ei in zip(grid, etas): if gi.shape == ei.shape: - li += [np.allclose(gi, ei)] + li += [xp.allclose(gi, ei)] else: li += [False] if all(li): @@ -2549,9 +2561,9 @@ def _eval_bv(self, *etas, squeeze_out=False): if var == "B^rho": tmp /= 1.0 - self.rmin elif var == "B^theta": - tmp /= 2.0 * np.pi + tmp /= 2.0 * xp.pi elif var == "B^zeta": - tmp /= 2.0 * np.pi / nfp + tmp /= 2.0 * xp.pi / nfp # adjust for Struphy units tmp /= self.units["B"] / self.units["x"] out += [tmp] @@ -2570,7 +2582,7 @@ def jv(self, *etas, squeeze_out=False): li = [] for gi, ei in zip(grid, etas): if gi.shape == ei.shape: - li += [np.allclose(gi, ei)] + li += [xp.allclose(gi, ei)] else: li += [False] if all(li): @@ -2622,9 +2634,9 @@ def _eval_jv(self, *etas, squeeze_out=False): if var == "J^rho": tmp /= 1.0 - self.rmin elif var == "J^theta": - tmp /= 2.0 * np.pi + tmp /= 2.0 * xp.pi elif var == "J^zeta": - tmp /= 2.0 * np.pi / nfp + tmp /= 2.0 * xp.pi / nfp # adjust for Struphy units tmp /= self.units["j"] / self.units["x"] out += [tmp] @@ -2694,7 +2706,7 @@ def gradB1(self, *etas, squeeze_out=False): li = [] for gi, ei in zip(grid, etas): if gi.shape == ei.shape: - li += [np.allclose(gi, ei)] + li += [xp.allclose(gi, ei)] else: li += [False] if all(li): @@ -2745,9 +2757,9 @@ def _eval_gradB1(self, *etas, squeeze_out=False): if var == "|B|_r": tmp *= 1.0 - self.rmin elif var == "|B|_t": - tmp *= 2.0 * np.pi + tmp *= 2.0 * xp.pi elif var == "|B|_z": - tmp *= 2.0 * np.pi / nfp + tmp *= 2.0 * xp.pi / nfp # adjust for Struphy units tmp /= self.units["B"] out += [tmp] @@ -2757,9 +2769,9 @@ def _eval_gradB1(self, *etas, squeeze_out=False): def desc_eval( self, var: str, - e1: np.ndarray, - e2: np.ndarray, - e3: np.ndarray, + e1: xp.ndarray, + e2: xp.ndarray, + e3: xp.ndarray, flat_eval: bool = False, nfp: int = 1, verbose: bool = False, @@ -2773,7 +2785,7 @@ def desc_eval( Desc equilibrium quantitiy to evaluate, from `https://desc-docs.readthedocs.io/en/latest/variables.html#list-of-variables`_. - e1, e2, e3 : np.ndarray + e1, e2, e3 : xp.ndarray Input grids, either 1d or 3d. flat_eval : bool @@ -2792,21 +2804,21 @@ def desc_eval( warnings.filterwarnings("ignore") ttime = time() # Fix issue 353 with float dummy etas - e1 = np.array([e1]) if isinstance(e1, float) else e1 - e2 = np.array([e2]) if isinstance(e2, float) else e2 - e3 = np.array([e3]) if isinstance(e3, float) else e3 + e1 = xp.array([e1]) if isinstance(e1, float) else e1 + e2 = xp.array([e2]) if isinstance(e2, float) else e2 + e3 = xp.array([e3]) if isinstance(e3, float) else e3 # transform input grids if e1.ndim == 3: assert e1.shape == e2.shape == e3.shape rho = self.rmin + e1[:, 0, 0] * (1.0 - self.rmin) - theta = 2 * np.pi * e2[0, :, 0] - zeta = 2 * np.pi * e3[0, 0, :] / nfp + theta = 2 * xp.pi * e2[0, :, 0] + zeta = 2 * xp.pi * e3[0, 0, :] / nfp else: assert e1.ndim == e2.ndim == e3.ndim == 1 rho = self.rmin + e1 * (1.0 - self.rmin) - theta = 2 * np.pi * e2 - zeta = 2 * np.pi * e3 / nfp + theta = 2 * xp.pi * e2 + zeta = 2 * xp.pi * e3 / nfp # eval type if flat_eval: @@ -2815,13 +2827,13 @@ def desc_eval( t = theta z = zeta else: - r, t, z = np.meshgrid(rho, theta, zeta, indexing="ij") + r, t, z = xp.meshgrid(rho, theta, zeta, indexing="ij") r = r.flatten() t = t.flatten() z = z.flatten() - nodes = np.stack((r, t, z)).T - grid_3d = Grid(nodes, spacing=np.ones_like(nodes), jitable=False) + nodes = xp.stack((r, t, z)).T + grid_3d = Grid(nodes, spacing=xp.ones_like(nodes), jitable=False) # compute output corresponding to the generated desc grid node_values = self.eq.compute( @@ -2862,32 +2874,32 @@ def desc_eval( )[0, 0, :] # make sure the desc grid is correct - assert np.all(rho == rho1) - assert np.all(theta == theta1) - assert np.all(zeta == zeta1) + assert xp.all(rho == rho1) + assert xp.all(theta == theta1) + assert xp.all(zeta == zeta1) if verbose: # import sys - print(f"\n{nfp = }") - print(f"{self.eq.axis = }") - print(f"{rho.size = }") - print(f"{theta.size = }") - print(f"{zeta.size = }") - print(f"{grid_3d.num_rho = }") - print(f"{grid_3d.num_theta = }") - print(f"{grid_3d.num_zeta = }") + print(f"\n{nfp =}") + print(f"{self.eq.axis =}") + print(f"{rho.size =}") + print(f"{theta.size =}") + print(f"{zeta.size =}") + print(f"{grid_3d.num_rho =}") + print(f"{grid_3d.num_theta =}") + print(f"{grid_3d.num_zeta =}") # print(f'\n{grid_3d.nodes[:, 0] = }') # print(f'\n{grid_3d.nodes[:, 1] = }') # print(f'\n{grid_3d.nodes[:, 2] = }') - print(f"\n{rho = }") - print(f"{rho1 = }") - print(f"\n{theta = }") - print(f"{theta1 = }") - print(f"\n{zeta = }") - print(f"{zeta1 = }") + print(f"\n{rho =}") + print(f"{rho1 =}") + print(f"\n{theta =}") + print(f"{theta1 =}") + print(f"\n{zeta =}") + print(f"{zeta1 =}") # make c-contiguous - out = np.ascontiguousarray(out) + out = xp.ascontiguousarray(out) print(f"desc_eval for {var}: {time() - ttime} seconds") return out @@ -2935,12 +2947,12 @@ def n_xyz(self, x, y, z): elif self.params["density_profile"] == "affine": return self.params["n"] + self.params["n1"] * x elif self.params["density_profile"] == "gaussian_xy": - return self.params["n"] * np.exp(-(x**2 + y**2) / self.params["p0"]) + return self.params["n"] * xp.exp(-(x**2 + y**2) / self.params["p0"]) elif self.params["density_profile"] == "step_function_x": out = 1e-8 + 0 * x - # mask_x = np.logical_and(x < .6, x > .4) - # mask_y = np.logical_and(y < .6, y > .4) - # mask = np.logical_and(mask_x, mask_y) + # mask_x = xp.logical_and(x < .6, x > .4) + # mask_y = xp.logical_and(y < .6, y > .4) + # mask = xp.logical_and(mask_x, mask_y) mask = x < -2.0 out[mask] = self.params["n"] return out @@ -3266,7 +3278,7 @@ def plot_profiles(self, n_pts=501): import matplotlib.pyplot as plt - r = np.linspace(0.0, self.params["a"], n_pts) + r = xp.linspace(0.0, self.params["a"], n_pts) fig, ax = plt.subplots(1, 3) @@ -3277,7 +3289,7 @@ def plot_profiles(self, n_pts=501): ax[0].set_xlabel("r") ax[0].set_ylabel("q") - ax[0].plot(r, np.ones(r.size), "k--") + ax[0].plot(r, xp.ones(r.size), "k--") ax[1].plot(r, self.p_r(r)) ax[1].set_xlabel("r") @@ -3300,8 +3312,8 @@ def b_xyz(self, x, y, z): """Magnetic field.""" bz = 0 * x - by = np.tanh(z / self._params["delta"]) - bx = np.sqrt(1 - by**2) + by = xp.tanh(z / self._params["delta"]) + bx = xp.sqrt(1 - by**2) bxs = self._params["amp"] * bx bys = self._params["amp"] * by diff --git a/src/struphy/fields_background/mhd_equil/eqdsk/readeqdsk.py b/src/struphy/fields_background/mhd_equil/eqdsk/readeqdsk.py index 67578ce17..812381e87 100644 --- a/src/struphy/fields_background/mhd_equil/eqdsk/readeqdsk.py +++ b/src/struphy/fields_background/mhd_equil/eqdsk/readeqdsk.py @@ -173,7 +173,11 @@ def main(): action="store_true", ) parser.add_option( - "-v", "--vars", dest="vars", help="comma separated list of variables (use '-v \"*\"' for all)", default="*" + "-v", + "--vars", + dest="vars", + help="comma separated list of variables (use '-v \"*\"' for all)", + default="*", ) parser.add_option( "-p", diff --git a/src/struphy/fields_background/tests/test_desc_equil.py b/src/struphy/fields_background/tests/test_desc_equil.py index be5a3d2db..c7130f0a3 100644 --- a/src/struphy/fields_background/tests/test_desc_equil.py +++ b/src/struphy/fields_background/tests/test_desc_equil.py @@ -1,6 +1,6 @@ import importlib.util -import numpy as np +import cunumpy as xp import pytest from matplotlib import pyplot as plt @@ -32,9 +32,9 @@ def test_desc_equil(do_plot=False): n2 = 9 n3 = 11 - e1 = np.linspace(0.0001, 1, n1) - e2 = np.linspace(0, 1, n2) - e3 = np.linspace(0, 1 - 1e-6, n3) + e1 = xp.linspace(0.0001, 1, n1) + e2 = xp.linspace(0, 1, n2) + e3 = xp.linspace(0, 1 - 1e-6, n3) # desc grid and evaluation vars = [ @@ -69,43 +69,43 @@ def test_desc_equil(do_plot=False): outs[nfp] = {} rho = rmin + e1 * (1.0 - rmin) - theta = 2 * np.pi * e2 - zeta = 2 * np.pi * e3 / nfp + theta = 2 * xp.pi * e2 + zeta = 2 * xp.pi * e3 / nfp - r, t, ze = np.meshgrid(rho, theta, zeta, indexing="ij") + r, t, ze = xp.meshgrid(rho, theta, zeta, indexing="ij") r = r.flatten() t = t.flatten() ze = ze.flatten() - nodes = np.stack((r, t, ze)).T - grid_3d = Grid(nodes, spacing=np.ones_like(nodes), jitable=False) + nodes = xp.stack((r, t, ze)).T + grid_3d = Grid(nodes, spacing=xp.ones_like(nodes), jitable=False) for var in vars: node_values = desc_eq.compute(var, grid=grid_3d, override_grid=False) if node_values[var].ndim == 1: out = node_values[var].reshape((rho.size, theta.size, zeta.size), order="C") - outs[nfp][var] = np.ascontiguousarray(out) + outs[nfp][var] = xp.ascontiguousarray(out) else: B = [] for i in range(3): Bcomp = node_values[var][:, i].reshape((rho.size, theta.size, zeta.size), order="C") - Bcomp = np.ascontiguousarray(Bcomp) + Bcomp = xp.ascontiguousarray(Bcomp) B += [Bcomp] outs[nfp][var + str(i + 1)] = Bcomp - outs[nfp][var] = np.sqrt(B[0] ** 2 + B[1] ** 2 + B[2] ** 2) + outs[nfp][var] = xp.sqrt(B[0] ** 2 + B[1] ** 2 + B[2] ** 2) - assert np.allclose(outs[nfp]["B1"], outs[nfp]["B_R"]) - assert np.allclose(outs[nfp]["B2"], outs[nfp]["B_phi"]) - assert np.allclose(outs[nfp]["B3"], outs[nfp]["B_Z"]) + assert xp.allclose(outs[nfp]["B1"], outs[nfp]["B_R"]) + assert xp.allclose(outs[nfp]["B2"], outs[nfp]["B_phi"]) + assert xp.allclose(outs[nfp]["B3"], outs[nfp]["B_Z"]) - assert np.allclose(outs[nfp]["J1"], outs[nfp]["J_R"]) - assert np.allclose(outs[nfp]["J2"], outs[nfp]["J_phi"]) - assert np.allclose(outs[nfp]["J3"], outs[nfp]["J_Z"]) + assert xp.allclose(outs[nfp]["J1"], outs[nfp]["J_R"]) + assert xp.allclose(outs[nfp]["J2"], outs[nfp]["J_phi"]) + assert xp.allclose(outs[nfp]["J3"], outs[nfp]["J_Z"]) - outs[nfp]["Bx"] = np.cos(outs[nfp]["phi"]) * outs[nfp]["B_R"] - np.sin(outs[nfp]["phi"]) * outs[nfp]["B_phi"] + outs[nfp]["Bx"] = xp.cos(outs[nfp]["phi"]) * outs[nfp]["B_R"] - xp.sin(outs[nfp]["phi"]) * outs[nfp]["B_phi"] - outs[nfp]["By"] = np.sin(outs[nfp]["phi"]) * outs[nfp]["B_R"] + np.cos(outs[nfp]["phi"]) * outs[nfp]["B_phi"] + outs[nfp]["By"] = xp.sin(outs[nfp]["phi"]) * outs[nfp]["B_R"] + xp.cos(outs[nfp]["phi"]) * outs[nfp]["B_phi"] outs[nfp]["Bz"] = outs[nfp]["B_Z"] @@ -122,32 +122,32 @@ def test_desc_equil(do_plot=False): outs_struphy[nfp]["Y"] = y outs_struphy[nfp]["Z"] = z - outs_struphy[nfp]["R"] = np.sqrt(x**2 + y**2) - tmp = np.arctan2(y, x) - tmp[tmp < -1e-6] += 2 * np.pi + outs_struphy[nfp]["R"] = xp.sqrt(x**2 + y**2) + tmp = xp.arctan2(y, x) + tmp[tmp < -1e-6] += 2 * xp.pi outs_struphy[nfp]["phi"] = tmp - outs_struphy[nfp]["sqrt(g)"] = s_eq.domain.jacobian_det(e1, e2, e3) / (4 * np.pi**2 / nfp) + outs_struphy[nfp]["sqrt(g)"] = s_eq.domain.jacobian_det(e1, e2, e3) / (4 * xp.pi**2 / nfp) outs_struphy[nfp]["p"] = s_eq.p0(e1, e2, e3) # include push forward to DESC logical coordinates bv = s_eq.bv(e1, e2, e3) outs_struphy[nfp]["B^rho"] = bv[0] * (1 - rmin) - outs_struphy[nfp]["B^theta"] = bv[1] * 2 * np.pi - outs_struphy[nfp]["B^zeta"] = bv[2] * 2 * np.pi / nfp + outs_struphy[nfp]["B^theta"] = bv[1] * 2 * xp.pi + outs_struphy[nfp]["B^zeta"] = bv[2] * 2 * xp.pi / nfp outs_struphy[nfp]["B"] = s_eq.absB0(e1, e2, e3) # include push forward to DESC logical coordinates jv = s_eq.jv(e1, e2, e3) outs_struphy[nfp]["J^rho"] = jv[0] * (1 - rmin) - outs_struphy[nfp]["J^theta"] = jv[1] * 2 * np.pi - outs_struphy[nfp]["J^zeta"] = jv[2] * 2 * np.pi / nfp + outs_struphy[nfp]["J^theta"] = jv[1] * 2 * xp.pi + outs_struphy[nfp]["J^zeta"] = jv[2] * 2 * xp.pi / nfp j1 = s_eq.j1(e1, e2, e3) - outs_struphy[nfp]["J"] = np.sqrt(jv[0] * j1[0] + jv[1] * j1[1] + jv[2] * j1[2]) + outs_struphy[nfp]["J"] = xp.sqrt(jv[0] * j1[0] + jv[1] * j1[1] + jv[2] * j1[2]) b_cart, xyz = s_eq.b_cart(e1, e2, e3) outs_struphy[nfp]["Bx"] = b_cart[0] @@ -157,8 +157,8 @@ def test_desc_equil(do_plot=False): # include push forward to DESC logical coordinates gradB1 = s_eq.gradB1(e1, e2, e3) outs_struphy[nfp]["|B|_r"] = gradB1[0] / (1 - rmin) - outs_struphy[nfp]["|B|_t"] = gradB1[1] / (2 * np.pi) - outs_struphy[nfp]["|B|_z"] = gradB1[2] / (2 * np.pi / nfp) + outs_struphy[nfp]["|B|_t"] = gradB1[1] / (2 * xp.pi) + outs_struphy[nfp]["|B|_z"] = gradB1[2] / (2 * xp.pi / nfp) # comparisons vars += ["Bx", "By", "Bz"] @@ -167,25 +167,25 @@ def test_desc_equil(do_plot=False): err_lim = 0.09 for nfp in nfps: - print(f"\n{nfp = }") + print(f"\n{nfp =}") for var in vars: if var in ("B_R", "B_phi", "B_Z", "J_R", "J_phi", "J_Z"): continue else: - max_norm = np.max(np.abs(outs[nfp][var])) + max_norm = xp.max(xp.abs(outs[nfp][var])) if max_norm < 1e-16: max_norm = 1.0 - err = np.max(np.abs(outs[nfp][var] - outs_struphy[nfp][var])) / max_norm + err = xp.max(xp.abs(outs[nfp][var] - outs_struphy[nfp][var])) / max_norm assert err < err_lim print( - f"compare {var}: {err = }", + f"compare {var}: {err =}", ) if do_plot: fig = plt.figure(figsize=(12, 13)) - levels = np.linspace(np.min(outs[nfp][var]) - 1e-10, np.max(outs[nfp][var]), 20) + levels = xp.linspace(xp.min(outs[nfp][var]) - 1e-10, xp.max(outs[nfp][var]), 20) # poloidal plot R = outs[nfp]["R"][:, :, 0].squeeze() @@ -193,7 +193,7 @@ def test_desc_equil(do_plot=False): plt.subplot(2, 2, 1) map1 = plt.contourf(R, Z, outs[nfp][var][:, :, 0], levels=levels) - plt.title(f"DESC, {var = }, {nfp = }") + plt.title(f"DESC, {var =}, {nfp =}") plt.xlabel("$R$") plt.ylabel("$Z$") plt.axis("equal") @@ -201,7 +201,7 @@ def test_desc_equil(do_plot=False): plt.subplot(2, 2, 2) map2 = plt.contourf(R, Z, outs_struphy[nfp][var][:, :, 0], levels=levels) - plt.title(f"Struphy, {err = }") + plt.title(f"Struphy, {err =}") plt.xlabel("$R$") plt.ylabel("$Z$") plt.axis("equal") @@ -217,7 +217,7 @@ def test_desc_equil(do_plot=False): plt.subplot(2, 2, 3) map3 = plt.contourf(x1, y1, outs[nfp][var][:, 0, :], levels=levels) map3b = plt.contourf(x2, y2, outs[nfp][var][:, n2 // 2, :], levels=levels) - plt.title(f"DESC, {var = }, {nfp = }") + plt.title(f"DESC, {var =}, {nfp =}") plt.xlabel("$x$") plt.ylabel("$y$") plt.axis("equal") @@ -226,7 +226,7 @@ def test_desc_equil(do_plot=False): plt.subplot(2, 2, 4) map4 = plt.contourf(x1, y1, outs_struphy[nfp][var][:, 0, :], levels=levels) map4b = plt.contourf(x2, y2, outs_struphy[nfp][var][:, n2 // 2, :], levels=levels) - plt.title(f"Struphy, {err = }") + plt.title(f"Struphy, {err =}") plt.xlabel("$x$") plt.ylabel("$y$") plt.axis("equal") diff --git a/src/struphy/fields_background/tests/test_generic_equils.py b/src/struphy/fields_background/tests/test_generic_equils.py index d4d7fc25b..77ca8baaa 100644 --- a/src/struphy/fields_background/tests/test_generic_equils.py +++ b/src/struphy/fields_background/tests/test_generic_equils.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp import pytest from matplotlib import pyplot as plt @@ -9,8 +9,8 @@ def test_generic_equils(show=False): - fun_vec = lambda x, y, z: (np.cos(2 * np.pi * x), np.cos(2 * np.pi * y), z) - fun_n = lambda x, y, z: np.exp(-((x - 1) ** 2) - (y) ** 2) + fun_vec = lambda x, y, z: (xp.cos(2 * xp.pi * x), xp.cos(2 * xp.pi * y), z) + fun_n = lambda x, y, z: xp.exp(-((x - 1) ** 2) - (y) ** 2) fun_p = lambda x, y, z: x**2 gen_eq = GenericCartesianFluidEquilibrium( u_xyz=fun_vec, @@ -25,22 +25,22 @@ def test_generic_equils(show=False): gradB_xyz=fun_vec, ) - x = np.linspace(-3, 3, 32) - y = np.linspace(-4, 4, 32) + x = xp.linspace(-3, 3, 32) + y = xp.linspace(-4, 4, 32) z = 1.0 - xx, yy, zz = np.meshgrid(x, y, z) + xx, yy, zz = xp.meshgrid(x, y, z) # gen_eq - assert all([np.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq.u_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) - assert np.all(gen_eq.p_xyz(xx, yy, zz) == fun_p(xx, yy, zz)) - assert np.all(gen_eq.n_xyz(xx, yy, zz) == fun_n(xx, yy, zz)) + assert all([xp.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq.u_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) + assert xp.all(gen_eq.p_xyz(xx, yy, zz) == fun_p(xx, yy, zz)) + assert xp.all(gen_eq.n_xyz(xx, yy, zz) == fun_n(xx, yy, zz)) # gen_eq_B - assert all([np.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.u_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) - assert np.all(gen_eq_B.p_xyz(xx, yy, zz) == fun_p(xx, yy, zz)) - assert np.all(gen_eq_B.n_xyz(xx, yy, zz) == fun_n(xx, yy, zz)) - assert all([np.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.b_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) - assert all([np.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.gradB_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) + assert all([xp.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.u_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) + assert xp.all(gen_eq_B.p_xyz(xx, yy, zz) == fun_p(xx, yy, zz)) + assert xp.all(gen_eq_B.n_xyz(xx, yy, zz) == fun_n(xx, yy, zz)) + assert all([xp.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.b_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) + assert all([xp.all(tmp == fun_i) for tmp, fun_i in zip(gen_eq_B.gradB_xyz(xx, yy, zz), fun_vec(xx, yy, zz))]) if show: plt.figure(figsize=(12, 12)) diff --git a/src/struphy/fields_background/tests/test_mhd_equils.py b/src/struphy/fields_background/tests/test_mhd_equils.py index 4ab6f7e19..f363ddbe3 100644 --- a/src/struphy/fields_background/tests/test_mhd_equils.py +++ b/src/struphy/fields_background/tests/test_mhd_equils.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp import pytest from struphy.fields_background import equils @@ -9,44 +9,44 @@ [ ("HomogenSlab", {}, "Cuboid", {}), ("HomogenSlab", {}, "Colella", {"alpha": 0.06}), - ("ShearedSlab", {"a": 0.75, "R0": 3.5}, "Cuboid", {"r1": 0.75, "r2": 2 * np.pi * 0.75, "r3": 2 * np.pi * 3.5}), + ("ShearedSlab", {"a": 0.75, "R0": 3.5}, "Cuboid", {"r1": 0.75, "r2": 2 * xp.pi * 0.75, "r3": 2 * xp.pi * 3.5}), ( "ShearedSlab", {"a": 0.75, "R0": 3.5, "q0": "inf", "q1": "inf"}, "Cuboid", - {"r1": 0.75, "r2": 2 * np.pi * 0.75, "r3": 2 * np.pi * 3.5}, + {"r1": 0.75, "r2": 2 * xp.pi * 0.75, "r3": 2 * xp.pi * 3.5}, ), ( "ShearedSlab", {"a": 0.55, "R0": 4.5}, "Orthogonal", - {"Lx": 0.55, "Ly": 2 * np.pi * 0.55, "Lz": 2 * np.pi * 4.5}, + {"Lx": 0.55, "Ly": 2 * xp.pi * 0.55, "Lz": 2 * xp.pi * 4.5}, ), - ("ScrewPinch", {"a": 0.45, "R0": 2.5}, "HollowCylinder", {"a1": 0.05, "a2": 0.45, "Lz": 2 * np.pi * 2.5}), - ("ScrewPinch", {"a": 1.45, "R0": 6.5}, "IGAPolarCylinder", {"a": 1.45, "Lz": 2 * np.pi * 6.5}), + ("ScrewPinch", {"a": 0.45, "R0": 2.5}, "HollowCylinder", {"a1": 0.05, "a2": 0.45, "Lz": 2 * xp.pi * 2.5}), + ("ScrewPinch", {"a": 1.45, "R0": 6.5}, "IGAPolarCylinder", {"a": 1.45, "Lz": 2 * xp.pi * 6.5}), ( "ScrewPinch", {"a": 0.45, "R0": 2.5, "q0": 1.5, "q1": 1.5}, "HollowCylinder", - {"a1": 0.05, "a2": 0.45, "Lz": 2 * np.pi * 2.5}, + {"a1": 0.05, "a2": 0.45, "Lz": 2 * xp.pi * 2.5}, ), ( "ScrewPinch", {"a": 1.45, "R0": 6.5, "q0": 1.5, "q1": 1.5}, "IGAPolarCylinder", - {"a": 1.45, "Lz": 2 * np.pi * 6.5}, + {"a": 1.45, "Lz": 2 * xp.pi * 6.5}, ), ( "ScrewPinch", {"a": 0.45, "R0": 2.5, "q0": "inf", "q1": "inf"}, "HollowCylinder", - {"a1": 0.05, "a2": 0.45, "Lz": 2 * np.pi * 2.5}, + {"a1": 0.05, "a2": 0.45, "Lz": 2 * xp.pi * 2.5}, ), ( "ScrewPinch", {"a": 1.45, "R0": 6.5, "q0": "inf", "q1": "inf"}, "IGAPolarCylinder", - {"a": 1.45, "Lz": 2 * np.pi * 6.5}, + {"a": 1.45, "Lz": 2 * xp.pi * 6.5}, ), ( "AdhocTorus", @@ -136,29 +136,28 @@ def test_equils(equil_domain_pair): Test field evaluations of all implemented MHD equilbria with default parameters. """ - from struphy.fields_background import equils from struphy.fields_background.base import CartesianMHDequilibrium, NumericalMHDequilibrium from struphy.geometry import domains # logical evalution point - pt = (np.random.rand(), np.random.rand(), np.random.rand()) + pt = (xp.random.rand(), xp.random.rand(), xp.random.rand()) # logical arrays: - e1 = np.random.rand(4) - e2 = np.random.rand(5) - e3 = np.random.rand(6) + e1 = xp.random.rand(4) + e2 = xp.random.rand(5) + e3 = xp.random.rand(6) # 2d slices - mat_12_1, mat_12_2 = np.meshgrid(e1, e2, indexing="ij") - mat_13_1, mat_13_3 = np.meshgrid(e1, e3, indexing="ij") - mat_23_2, mat_23_3 = np.meshgrid(e2, e3, indexing="ij") + mat_12_1, mat_12_2 = xp.meshgrid(e1, e2, indexing="ij") + mat_13_1, mat_13_3 = xp.meshgrid(e1, e3, indexing="ij") + mat_23_2, mat_23_3 = xp.meshgrid(e2, e3, indexing="ij") # 3d - mat_123_1, mat_123_2, mat_123_3 = np.meshgrid(e1, e2, e3, indexing="ij") - mat_123_1_sp, mat_123_2_sp, mat_123_3_sp = np.meshgrid(e1, e2, e3, indexing="ij", sparse=True) + mat_123_1, mat_123_2, mat_123_3 = xp.meshgrid(e1, e2, e3, indexing="ij") + mat_123_1_sp, mat_123_2_sp, mat_123_3_sp = xp.meshgrid(e1, e2, e3, indexing="ij", sparse=True) # markers - markers = np.random.rand(33, 10) + markers = xp.random.rand(33, 10) # create MHD equilibrium eq_mhd = getattr(equils, equil_domain_pair[0])(**equil_domain_pair[1]) @@ -274,8 +273,8 @@ def test_equils(equil_domain_pair): # --------- eta1 evaluation --------- results = [] - e2_pt = np.random.rand() - e3_pt = np.random.rand() + e2_pt = xp.random.rand() + e3_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(e1, e2_pt, e3_pt, squeeze_out=True)) @@ -321,8 +320,8 @@ def test_equils(equil_domain_pair): # --------- eta2 evaluation --------- results = [] - e1_pt = np.random.rand() - e3_pt = np.random.rand() + e1_pt = xp.random.rand() + e3_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(e1_pt, e2, e3_pt, squeeze_out=True)) @@ -370,8 +369,8 @@ def test_equils(equil_domain_pair): # --------- eta3 evaluation --------- results = [] - e1_pt = np.random.rand() - e2_pt = np.random.rand() + e1_pt = xp.random.rand() + e2_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(e1_pt, e2_pt, e3, squeeze_out=True)) @@ -419,7 +418,7 @@ def test_equils(equil_domain_pair): # --------- eta1-eta2 evaluation --------- results = [] - e3_pt = np.random.rand() + e3_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(e1, e2, e3_pt, squeeze_out=True)) @@ -467,7 +466,7 @@ def test_equils(equil_domain_pair): # --------- eta1-eta3 evaluation --------- results = [] - e2_pt = np.random.rand() + e2_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(e1, e2_pt, e3, squeeze_out=True)) @@ -515,7 +514,7 @@ def test_equils(equil_domain_pair): # --------- eta2-eta3 evaluation --------- results = [] - e1_pt = np.random.rand() + e1_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(e1_pt, e2, e3, squeeze_out=True)) @@ -609,7 +608,7 @@ def test_equils(equil_domain_pair): # --------- 12 matrix evaluation --------- results = [] - e3_pt = np.random.rand() + e3_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(mat_12_1, mat_12_2, e3_pt, squeeze_out=True)) @@ -657,7 +656,7 @@ def test_equils(equil_domain_pair): # --------- 13 matrix evaluation --------- results = [] - e2_pt = np.random.rand() + e2_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(mat_13_1, e2_pt, mat_13_3, squeeze_out=True)) @@ -705,7 +704,7 @@ def test_equils(equil_domain_pair): # --------- 23 matrix evaluation --------- results = [] - e1_pt = np.random.rand() + e1_pt = xp.random.rand() # scalar functions results.append(eq_mhd.absB0(e1_pt, mat_23_2, mat_23_3, squeeze_out=True)) @@ -848,22 +847,22 @@ def assert_scalar(result, kind, *etas): markers = etas[0] n_p = markers.shape[0] - assert isinstance(result, np.ndarray) + assert isinstance(result, xp.ndarray) assert result.shape == (n_p,) for ip in range(n_p): assert isinstance(result[ip], float) - assert not np.isnan(result[ip]) + assert not xp.isnan(result[ip]) else: # point-wise if kind == "point": assert isinstance(result, float) - assert not np.isnan(result) + assert not xp.isnan(result) # slices else: - assert isinstance(result, np.ndarray) + assert isinstance(result, xp.ndarray) # eta1-array if kind == "e1": @@ -915,27 +914,27 @@ def assert_vector(result, kind, *etas): markers = etas[0] n_p = markers.shape[0] - assert isinstance(result, np.ndarray) + assert isinstance(result, xp.ndarray) assert result.shape == (3, n_p) for c in range(3): for ip in range(n_p): assert isinstance(result[c, ip], float) - assert not np.isnan(result[c, ip]) + assert not xp.isnan(result[c, ip]) else: # point-wise if kind == "point": - assert isinstance(result, np.ndarray) + assert isinstance(result, xp.ndarray) assert result.shape == (3,) for c in range(3): assert isinstance(result[c], float) - assert not np.isnan(result[c]) + assert not xp.isnan(result[c]) # slices else: - assert isinstance(result, np.ndarray) + assert isinstance(result, xp.ndarray) # eta1-array if kind == "e1": diff --git a/src/struphy/fields_background/tests/test_numerical_mhd_equil.py b/src/struphy/fields_background/tests/test_numerical_mhd_equil.py index 619bad2d9..aa1278d5d 100644 --- a/src/struphy/fields_background/tests/test_numerical_mhd_equil.py +++ b/src/struphy/fields_background/tests/test_numerical_mhd_equil.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp import pytest from struphy.fields_background.base import FluidEquilibrium, LogicalMHDequilibrium @@ -50,53 +50,53 @@ def test_transformations(mapping, mhd_equil): num_equil = NumEqTest(domain, proxy) # compare values: - eta1 = np.random.rand(4) - eta2 = np.random.rand(5) - eta3 = np.random.rand(6) + eta1 = xp.random.rand(4) + eta2 = xp.random.rand(5) + eta3 = xp.random.rand(6) - assert np.allclose(ana_equil.absB0(eta1, eta2, eta3), num_equil.absB0(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.absB0(eta1, eta2, eta3), num_equil.absB0(eta1, eta2, eta3)) - assert np.allclose(ana_equil.bv(eta1, eta2, eta3)[0], num_equil.bv(eta1, eta2, eta3)[0]) - assert np.allclose(ana_equil.bv(eta1, eta2, eta3)[1], num_equil.bv(eta1, eta2, eta3)[1]) - assert np.allclose(ana_equil.bv(eta1, eta2, eta3)[2], num_equil.bv(eta1, eta2, eta3)[2]) + assert xp.allclose(ana_equil.bv(eta1, eta2, eta3)[0], num_equil.bv(eta1, eta2, eta3)[0]) + assert xp.allclose(ana_equil.bv(eta1, eta2, eta3)[1], num_equil.bv(eta1, eta2, eta3)[1]) + assert xp.allclose(ana_equil.bv(eta1, eta2, eta3)[2], num_equil.bv(eta1, eta2, eta3)[2]) - assert np.allclose(ana_equil.b1_1(eta1, eta2, eta3), num_equil.b1_1(eta1, eta2, eta3)) - assert np.allclose(ana_equil.b1_2(eta1, eta2, eta3), num_equil.b1_2(eta1, eta2, eta3)) - assert np.allclose(ana_equil.b1_3(eta1, eta2, eta3), num_equil.b1_3(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.b1_1(eta1, eta2, eta3), num_equil.b1_1(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.b1_2(eta1, eta2, eta3), num_equil.b1_2(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.b1_3(eta1, eta2, eta3), num_equil.b1_3(eta1, eta2, eta3)) - assert np.allclose(ana_equil.b2_1(eta1, eta2, eta3), num_equil.b2_1(eta1, eta2, eta3)) - assert np.allclose(ana_equil.b2_2(eta1, eta2, eta3), num_equil.b2_2(eta1, eta2, eta3)) - assert np.allclose(ana_equil.b2_3(eta1, eta2, eta3), num_equil.b2_3(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.b2_1(eta1, eta2, eta3), num_equil.b2_1(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.b2_2(eta1, eta2, eta3), num_equil.b2_2(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.b2_3(eta1, eta2, eta3), num_equil.b2_3(eta1, eta2, eta3)) - assert np.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[0], num_equil.unit_bv(eta1, eta2, eta3)[0]) - assert np.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[1], num_equil.unit_bv(eta1, eta2, eta3)[1]) - assert np.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[2], num_equil.unit_bv(eta1, eta2, eta3)[2]) + assert xp.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[0], num_equil.unit_bv(eta1, eta2, eta3)[0]) + assert xp.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[1], num_equil.unit_bv(eta1, eta2, eta3)[1]) + assert xp.allclose(ana_equil.unit_bv(eta1, eta2, eta3)[2], num_equil.unit_bv(eta1, eta2, eta3)[2]) - assert np.allclose(ana_equil.unit_b1_1(eta1, eta2, eta3), num_equil.unit_b1_1(eta1, eta2, eta3)) - assert np.allclose(ana_equil.unit_b1_2(eta1, eta2, eta3), num_equil.unit_b1_2(eta1, eta2, eta3)) - assert np.allclose(ana_equil.unit_b1_3(eta1, eta2, eta3), num_equil.unit_b1_3(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.unit_b1_1(eta1, eta2, eta3), num_equil.unit_b1_1(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.unit_b1_2(eta1, eta2, eta3), num_equil.unit_b1_2(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.unit_b1_3(eta1, eta2, eta3), num_equil.unit_b1_3(eta1, eta2, eta3)) - assert np.allclose(ana_equil.unit_b2_1(eta1, eta2, eta3), num_equil.unit_b2_1(eta1, eta2, eta3)) - assert np.allclose(ana_equil.unit_b2_2(eta1, eta2, eta3), num_equil.unit_b2_2(eta1, eta2, eta3)) - assert np.allclose(ana_equil.unit_b2_3(eta1, eta2, eta3), num_equil.unit_b2_3(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.unit_b2_1(eta1, eta2, eta3), num_equil.unit_b2_1(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.unit_b2_2(eta1, eta2, eta3), num_equil.unit_b2_2(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.unit_b2_3(eta1, eta2, eta3), num_equil.unit_b2_3(eta1, eta2, eta3)) - assert np.allclose(ana_equil.jv(eta1, eta2, eta3)[0], num_equil.jv(eta1, eta2, eta3)[0]) - assert np.allclose(ana_equil.jv(eta1, eta2, eta3)[1], num_equil.jv(eta1, eta2, eta3)[1]) - assert np.allclose(ana_equil.jv(eta1, eta2, eta3)[2], num_equil.jv(eta1, eta2, eta3)[2]) + assert xp.allclose(ana_equil.jv(eta1, eta2, eta3)[0], num_equil.jv(eta1, eta2, eta3)[0]) + assert xp.allclose(ana_equil.jv(eta1, eta2, eta3)[1], num_equil.jv(eta1, eta2, eta3)[1]) + assert xp.allclose(ana_equil.jv(eta1, eta2, eta3)[2], num_equil.jv(eta1, eta2, eta3)[2]) - assert np.allclose(ana_equil.j1_1(eta1, eta2, eta3), num_equil.j1_1(eta1, eta2, eta3)) - assert np.allclose(ana_equil.j1_2(eta1, eta2, eta3), num_equil.j1_2(eta1, eta2, eta3)) - assert np.allclose(ana_equil.j1_3(eta1, eta2, eta3), num_equil.j1_3(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.j1_1(eta1, eta2, eta3), num_equil.j1_1(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.j1_2(eta1, eta2, eta3), num_equil.j1_2(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.j1_3(eta1, eta2, eta3), num_equil.j1_3(eta1, eta2, eta3)) - assert np.allclose(ana_equil.j2_1(eta1, eta2, eta3), num_equil.j2_1(eta1, eta2, eta3)) - assert np.allclose(ana_equil.j2_2(eta1, eta2, eta3), num_equil.j2_2(eta1, eta2, eta3)) - assert np.allclose(ana_equil.j2_3(eta1, eta2, eta3), num_equil.j2_3(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.j2_1(eta1, eta2, eta3), num_equil.j2_1(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.j2_2(eta1, eta2, eta3), num_equil.j2_2(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.j2_3(eta1, eta2, eta3), num_equil.j2_3(eta1, eta2, eta3)) - assert np.allclose(ana_equil.p0(eta1, eta2, eta3), num_equil.p0(eta1, eta2, eta3)) - assert np.allclose(ana_equil.p3(eta1, eta2, eta3), num_equil.p3(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.p0(eta1, eta2, eta3), num_equil.p0(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.p3(eta1, eta2, eta3), num_equil.p3(eta1, eta2, eta3)) - assert np.allclose(ana_equil.n0(eta1, eta2, eta3), num_equil.n0(eta1, eta2, eta3)) - assert np.allclose(ana_equil.n3(eta1, eta2, eta3), num_equil.n3(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.n0(eta1, eta2, eta3), num_equil.n0(eta1, eta2, eta3)) + assert xp.allclose(ana_equil.n3(eta1, eta2, eta3), num_equil.n3(eta1, eta2, eta3)) class NumEqTest(LogicalMHDequilibrium): diff --git a/src/struphy/geometry/base.py b/src/struphy/geometry/base.py index 528a813ad..d2b21688e 100644 --- a/src/struphy/geometry/base.py +++ b/src/struphy/geometry/base.py @@ -3,10 +3,8 @@ from abc import ABCMeta, abstractmethod +import cunumpy as xp import h5py -import matplotlib.pyplot as plt -import numpy as np -from mpl_toolkits.mplot3d import Axes3D from scipy.sparse import csc_matrix, kron from scipy.sparse.linalg import splu, spsolve @@ -58,12 +56,12 @@ def __init__( self._NbaseN = [Nel + p - kind * p for Nel, p, kind in zip(Nel, p, spl_kind)] - el_b = [np.linspace(0.0, 1.0, Nel + 1) for Nel in Nel] + el_b = [xp.linspace(0.0, 1.0, Nel + 1) for Nel in Nel] self._T = [bsp.make_knots(el_b, p, kind) for el_b, p, kind in zip(el_b, p, spl_kind)] self._indN = [ - (np.indices((Nel, p + 1))[1] + np.arange(Nel)[:, None]) % NbaseN + (xp.indices((Nel, p + 1))[1] + xp.arange(Nel)[:, None]) % NbaseN for Nel, p, NbaseN in zip(Nel, p, self._NbaseN) ] @@ -73,15 +71,15 @@ def __init__( self._p = (*self._p, 0) self._NbaseN = self._NbaseN + [0] - self._T = self._T + [np.zeros((1,), dtype=float)] + self._T = self._T + [xp.zeros((1,), dtype=float)] - self._indN = self._indN + [np.zeros((1, 1), dtype=int)] + self._indN = self._indN + [xp.zeros((1, 1), dtype=int)] # create dummy attributes for analytical mappings if self.kind_map >= 10: - self._cx = np.zeros((1, 1, 1), dtype=float) - self._cy = np.zeros((1, 1, 1), dtype=float) - self._cz = np.zeros((1, 1, 1), dtype=float) + self._cx = xp.zeros((1, 1, 1), dtype=float) + self._cy = xp.zeros((1, 1, 1), dtype=float) + self._cz = xp.zeros((1, 1, 1), dtype=float) self._transformation_ids = { "pull": 0, @@ -122,7 +120,7 @@ def __init__( self._args_domain = DomainArguments( self.kind_map, self.params_numpy, - np.array(self.p), + xp.array(self.p), self.T[0], self.T[1], self.T[2], @@ -167,15 +165,15 @@ def params(self, new): self._params = new @property - def params_numpy(self) -> np.ndarray: + def params_numpy(self) -> xp.ndarray: """Mapping parameters as numpy array (can be empty).""" if not hasattr(self, "_params_numpy"): - self._params_numpy = np.array([0], dtype=float) + self._params_numpy = xp.array([0], dtype=float) return self._params_numpy @params_numpy.setter def params_numpy(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) assert new.ndim == 1 self._params_numpy = new @@ -770,7 +768,7 @@ def _evaluate_metric_coefficient(self, *etas, which=0, **kwargs): markers = etas[0] # to keep C-ordering the (3, 3)-part is in the last indices - out = np.empty((markers.shape[0], 3, 3), dtype=float) + out = xp.empty((markers.shape[0], 3, 3), dtype=float) n_inside = evaluation_kernels.kernel_evaluate_pic( markers, @@ -782,24 +780,24 @@ def _evaluate_metric_coefficient(self, *etas, which=0, **kwargs): ) # move the (3, 3)-part to front - out = np.transpose(out, axes=(1, 2, 0)) + out = xp.transpose(out, axes=(1, 2, 0)) # remove holes out = out[:, :, :n_inside] if transposed: - out = np.transpose(out, axes=(1, 0, 2)) + out = xp.transpose(out, axes=(1, 0, 2)) # change size of "out" depending on which metric coeff has been evaluated if which == 0 or which == -1: out = out[:, 0, :] if change_out_order: - out = np.transpose(out, axes=(1, 0)) + out = xp.transpose(out, axes=(1, 0)) elif which == 2: out = out[0, 0, :] else: if change_out_order: - out = np.transpose(out, axes=(2, 0, 1)) + out = xp.transpose(out, axes=(2, 0, 1)) # tensor-product/slice evaluation else: @@ -811,7 +809,7 @@ def _evaluate_metric_coefficient(self, *etas, which=0, **kwargs): ) # to keep C-ordering the (3, 3)-part is in the last indices - out = np.empty( + out = xp.empty( (E1.shape[0], E2.shape[1], E3.shape[2], 3, 3), dtype=float, ) @@ -827,20 +825,20 @@ def _evaluate_metric_coefficient(self, *etas, which=0, **kwargs): ) # move the (3, 3)-part to front - out = np.transpose(out, axes=(3, 4, 0, 1, 2)) + out = xp.transpose(out, axes=(3, 4, 0, 1, 2)) if transposed: - out = np.transpose(out, axes=(1, 0, 2, 3, 4)) + out = xp.transpose(out, axes=(1, 0, 2, 3, 4)) if which == 0: out = out[:, 0, :, :, :] if change_out_order: - out = np.transpose(out, axes=(1, 2, 3, 0)) + out = xp.transpose(out, axes=(1, 2, 3, 0)) elif which == 2: out = out[0, 0, :, :, :] else: if change_out_order: - out = np.transpose(out, axes=(2, 3, 4, 0, 1)) + out = xp.transpose(out, axes=(2, 3, 4, 0, 1)) # remove singleton dimensions for slice evaluation if squeeze_out: @@ -905,7 +903,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa assert len(etas) == 3 assert etas[0].shape == etas[1].shape == etas[2].shape assert etas[0].ndim == 1 - markers = np.stack(etas, axis=1) + markers = xp.stack(etas, axis=1) else: markers = etas[0] @@ -957,7 +955,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa A_has_holes = False # call evaluation kernel - out = np.empty((markers.shape[0], 3), dtype=float) + out = xp.empty((markers.shape[0], 3), dtype=float) # make sure we don't have stride = 0 A = A.copy() @@ -973,7 +971,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa ) # move the (3, 3)-part to front - out = np.transpose(out, axes=(1, 0)) + out = xp.transpose(out, axes=(1, 0)) # remove holes out = out[:, :n_inside] @@ -987,7 +985,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa out = out[0, :] else: if change_out_order: - out = np.transpose(out, axes=(1, 0)) + out = xp.transpose(out, axes=(1, 0)) # tensor-product/slice evaluation else: @@ -1014,7 +1012,7 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa A = Domain.prepare_arg(a, X[0], X[1], X[2], a_kwargs=a_kwargs) # call evaluation kernel - out = np.empty( + out = xp.empty( (E1.shape[0], E2.shape[1], E3.shape[2], 3), dtype=float, ) @@ -1031,14 +1029,14 @@ def _pull_push_transform(self, which, a, kind_fun, *etas, flat_eval=False, **kwa ) # move the (3, 3)-part to front - out = np.transpose(out, axes=(3, 0, 1, 2)) + out = xp.transpose(out, axes=(3, 0, 1, 2)) # change output order if kind_int < 10: out = out[0, :, :, :] else: if change_out_order: - out = np.transpose(out, axes=(1, 2, 3, 0)) + out = xp.transpose(out, axes=(1, 2, 3, 0)) # remove singleton dimensions for slice evaluation if squeeze_out: @@ -1085,22 +1083,22 @@ def prepare_eval_pts(x, y, z, flat_eval=False): if flat_eval: # convert list type data to numpy array: if isinstance(x, list): - arg_x = np.array(x) - elif isinstance(x, np.ndarray): + arg_x = xp.array(x) + elif isinstance(x, xp.ndarray): arg_x = x else: raise ValueError("Input x must be a 1d list or numpy array") if isinstance(y, list): - arg_y = np.array(y) - elif isinstance(y, np.ndarray): + arg_y = xp.array(y) + elif isinstance(y, xp.ndarray): arg_y = y else: raise ValueError("Input y must be a 1d list or numpy array") if isinstance(z, list): - arg_z = np.array(z) - elif isinstance(z, np.ndarray): + arg_z = xp.array(z) + elif isinstance(z, xp.ndarray): arg_z = z else: raise ValueError("Input z must be a 1d list or numpy array") @@ -1119,56 +1117,56 @@ def prepare_eval_pts(x, y, z, flat_eval=False): else: # convert list type data to numpy array: if isinstance(x, float): - arg_x = np.array([x]) + arg_x = xp.array([x]) elif isinstance(x, int): - arg_x = np.array([float(x)]) + arg_x = xp.array([float(x)]) elif isinstance(x, list): - arg_x = np.array(x) - elif isinstance(x, np.ndarray): + arg_x = xp.array(x) + elif isinstance(x, xp.ndarray): arg_x = x.copy() else: raise ValueError(f"data type {type(x)} not supported") if isinstance(y, float): - arg_y = np.array([y]) + arg_y = xp.array([y]) elif isinstance(y, int): - arg_y = np.array([float(y)]) + arg_y = xp.array([float(y)]) elif isinstance(y, list): - arg_y = np.array(y) - elif isinstance(y, np.ndarray): + arg_y = xp.array(y) + elif isinstance(y, xp.ndarray): arg_y = y.copy() else: raise ValueError(f"data type {type(y)} not supported") if isinstance(z, float): - arg_z = np.array([z]) + arg_z = xp.array([z]) elif isinstance(z, int): - arg_z = np.array([float(z)]) + arg_z = xp.array([float(z)]) elif isinstance(z, list): - arg_z = np.array(z) - elif isinstance(z, np.ndarray): + arg_z = xp.array(z) + elif isinstance(z, xp.ndarray): arg_z = z.copy() else: raise ValueError(f"data type {type(z)} not supported") # tensor-product for given three 1D arrays if arg_x.ndim == 1 and arg_y.ndim == 1 and arg_z.ndim == 1: - E1, E2, E3 = np.meshgrid(arg_x, arg_y, arg_z, indexing="ij") + E1, E2, E3 = xp.meshgrid(arg_x, arg_y, arg_z, indexing="ij") # given xy-plane at point z: elif arg_x.ndim == 2 and arg_y.ndim == 2 and arg_z.size == 1: E1 = arg_x[:, :, None] E2 = arg_y[:, :, None] - E3 = arg_z * np.ones(E1.shape) + E3 = arg_z * xp.ones(E1.shape) # given xz-plane at point y: elif arg_x.ndim == 2 and arg_y.size == 1 and arg_z.ndim == 2: E1 = arg_x[:, None, :] - E2 = arg_y * np.ones(E1.shape) + E2 = arg_y * xp.ones(E1.shape) E3 = arg_z[:, None, :] # given yz-plane at point x: elif arg_x.size == 1 and arg_y.ndim == 2 and arg_z.ndim == 2: E2 = arg_y[None, :, :] E3 = arg_z[None, :, :] - E1 = arg_x * np.ones(E2.shape) + E1 = arg_x * xp.ones(E2.shape) # given three 3D arrays elif arg_x.ndim == 3 and arg_y.ndim == 3 and arg_z.ndim == 3: # Distinguish if input coordinates are from sparse or dense meshgrid. @@ -1226,7 +1224,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): # float (point-wise, scalar function) if isinstance(a_in, float): - a_out = np.array([[[[a_in]]]]) + a_out = xp.array([[[[a_in]]]]) # single callable: # scalar function -> must return a 3d array for 3d evaluation points @@ -1239,7 +1237,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): else: if is_sparse_meshgrid: a_out = a_in( - *np.meshgrid(Xs[0][:, 0, 0], Xs[1][0, :, 0], Xs[2][0, 0, :], indexing="ij"), + *xp.meshgrid(Xs[0][:, 0, 0], Xs[1][0, :, 0], Xs[2][0, 0, :], indexing="ij"), **a_kwargs, ) else: @@ -1247,7 +1245,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): # case of Field.__call__ if isinstance(a_out, list): - a_out = np.array(a_out) + a_out = xp.array(a_out) if a_out.ndim == 3: a_out = a_out[None, :, :, :] @@ -1275,7 +1273,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): if is_sparse_meshgrid: a_out += [ component( - *np.meshgrid( + *xp.meshgrid( Xs[0][:, 0, 0], Xs[1][0, :, 0], Xs[2][0, 0, :], @@ -1287,25 +1285,25 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): else: a_out += [component(*Xs, **a_kwargs)] - elif isinstance(component, np.ndarray): + elif isinstance(component, xp.ndarray): if flat_eval: - assert component.ndim == 1, print(f"{component.ndim = }") + assert component.ndim == 1, print(f"{component.ndim =}") else: - assert component.ndim == 3, print(f"{component.ndim = }") + assert component.ndim == 3, print(f"{component.ndim =}") a_out += [component] elif isinstance(component, float): - a_out += [np.array([component])[:, None, None]] + a_out += [xp.array([component])[:, None, None]] - a_out = np.array(a_out, dtype=float) + a_out = xp.array(a_out, dtype=float) # numpy array: # 1d array (flat_eval=True and scalar input or flat_eval=False and length 1 (scalar) or length 3 (vector)) # 2d array (flat_eval=True and vector-valued input of shape (3,:)) # 3d array (flat_eval=False and scalar input) # 4d array (flat_eval=False and vector-valued input of shape (3,:,:,:)) - elif isinstance(a_in, np.ndarray): + elif isinstance(a_in, xp.ndarray): if flat_eval: if a_in.ndim == 1: a_out = a_in[None, :] @@ -1314,7 +1312,7 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): else: raise ValueError( "Input array a_in must be either 1d (scalar) or \ - 2d (vector-valued, shape (3,:)) for flat evaluation!" + 2d (vector-valued, shape (3,:)) for flat evaluation!", ) else: @@ -1333,415 +1331,39 @@ def prepare_arg(a_in, *Xs, is_sparse_meshgrid=False, a_kwargs={}): else: raise ValueError( "Input array a_in must be either 3d (scalar) or \ - 4d (vector-valued, shape (3,:,:,:)) for non-flat evaluation!" + 4d (vector-valued, shape (3,:,:,:)) for non-flat evaluation!", ) else: raise TypeError( "Argument a must be either a float OR a list/tuple of 1 or 3 callable(s)/numpy array(s)/float(s) \ - OR a single numpy array OR a single callable!" + OR a single numpy array OR a single callable!", ) # make sure that output array is 2d and of shape (:, 1) or (:, 3) for flat evaluation if flat_eval: assert a_out.ndim == 2 assert a_out.shape[0] == 1 or a_out.shape[0] == 3 - a_out = np.ascontiguousarray(np.transpose(a_out, axes=(1, 0))).copy() # Make sure we don't have stride 0 + a_out = xp.ascontiguousarray(xp.transpose(a_out, axes=(1, 0))).copy() # Make sure we don't have stride 0 # make sure that output array is 4d and of shape (:,:,:, 1) or (:,:,:, 3) for tensor-product/slice evaluation else: assert a_out.ndim == 4 assert a_out.shape[0] == 1 or a_out.shape[0] == 3 - a_out = np.ascontiguousarray( - np.transpose(a_out, axes=(1, 2, 3, 0)), + a_out = xp.ascontiguousarray( + xp.transpose(a_out, axes=(1, 2, 3, 0)), ).copy() # Make sure we don't have stride 0 return a_out # ================================ - def get_params_numpy(self) -> np.ndarray: + def get_params_numpy(self) -> xp.ndarray: """Convert parameter dict into numpy array.""" params_numpy = [] for k, v in self.params.items(): params_numpy.append(v) - return np.array(params_numpy) - - def show3D_interactive( - self, - fig=None, - logical=False, - grid_info=None, - markers=None, - marker_coords="logical", - show_control_pts=False, - save_dir=None, - ): - import numpy as np - import plotly.graph_objects as go - - torus_mappings = ( - "Tokamak", - "GVECunit", - "DESCunit", - "IGAPolarTorus", - "HollowTorus", - ) - if fig is None: - fig = go.Figure() - - # --- default grid - e1 = np.linspace(0.0, 1.0, 20) - e2 = np.linspace(0.0, 1.0, 20) - e3 = np.linspace(0.0, 1.0, 20) - - if logical: - E1, E2, E3 = np.meshgrid(e1, e2, e3, indexing="ij") - X, Y, Z = E1, E2, E3 - else: - XYZ = self(e1, e2, e3, squeeze_out=True) - X, Y, Z = XYZ[0], XYZ[1], XYZ[2] - - # add wireframes along e1, e2, e3 directions - axis_colors = ["black", "red", "blue"] # colors for each direction - - # e1 isolines - for j in range(len(e2)): - for k in range(len(e3)): - fig.add_trace( - go.Scatter3d( - x=X[:, j, k], - y=Y[:, j, k], - z=Z[:, j, k], - mode="lines", - line=dict(color=axis_colors[0], width=1), - opacity=0.3, - ) - ) - - # e2 isolines - for k in range(len(e3)): - for i in range(len(e1)): - fig.add_trace( - go.Scatter3d( - x=X[i, :, k], - y=Y[i, :, k], - z=Z[i, :, k], - mode="lines", - line=dict(color=axis_colors[1], width=1), - opacity=0.3, - ) - ) - - # e3 isolines - for i in range(len(e1)): - for j in range(len(e2)): - fig.add_trace( - go.Scatter3d( - x=X[i, j, :], - y=Y[i, j, :], - z=Z[i, j, :], - mode="lines", - line=dict(color=axis_colors[2], width=1), - opacity=0.3, - ) - ) - - # Layout - fig.update_layout( - scene=dict(xaxis_title="x", yaxis_title="y", zaxis_title="z", aspectmode="data"), - title=self.__class__.__name__ + " 3D Domain (Interactive)", - showlegend=False, - autosize=True, - margin=dict(l=0, r=0, t=30, b=0), # remove internal margins - width=None, - height=None, - ) - - # if save_dir: - # fig.write_html(save_dir) - # else: - # fig.show() - - return fig - - def show_plotly( - self, - logical=False, - grid_info=None, - markers=None, - marker_coords="logical", - show_control_pts=False, - save_dir=None, - fig=None, - ): - import numpy as np - import plotly.graph_objects as go - from plotly.subplots import make_subplots - - torus_mappings = ( - "Tokamak", - "GVECunit", - "DESCunit", - "IGAPolarTorus", - "HollowTorus", - ) - is_not_cube = self.kind_map < 10 or self.kind_map > 19 - - # --- figure with 2 subplots - if fig is None: - fig = make_subplots( - rows=1, - cols=2, - subplot_titles=[f"{self.__class__.__name__} at η₃=0", "Top view"], - ) - - # --- default grid - if grid_info is None: - e1 = np.linspace(0.0, 1.0, 16) - e2 = np.linspace(0.0, 1.0, 65) - e3 = np.linspace(0.0, 1.0, 65) - else: - e1 = np.linspace(0.0, 1.0, grid_info[0] + 1) - e2 = np.linspace(0.0, 1.0, grid_info[1] + 1) - e3 = np.linspace(0.0, 1.0, grid_info[2] + 1 if len(grid_info) > 2 else 2) - - # --- compute coordinates - if logical: - E1, E2 = np.meshgrid(e1, e2, indexing="ij") - X, Y = E1, E2 - else: - XYZ = self(e1, e2, 0.0, squeeze_out=True) - X = XYZ[0] - Y = XYZ[2] if self.__class__.__name__ in torus_mappings else XYZ[1] - - # --- subplot 1: side view - for i in range(len(e1)): - fig.add_trace( - go.Scatter( - x=X[i, :], - y=Y[i, :], - mode="lines", - line=dict(color="blue", width=1), - opacity=0.5, - ), - row=1, - col=1, - ) - for j in range(len(e2) - int(is_not_cube)): - fig.add_trace( - go.Scatter( - x=X[:, j], - y=Y[:, j], - mode="lines", - line=dict(color="blue", width=1), - opacity=0.5, - ), - row=1, - col=1, - ) - - # --- subplot 2: top view - if not logical: - theta_0 = self(e1, 0.0, e3, squeeze_out=True) - theta_pi = self(e1, 0.5, e3, squeeze_out=True) - X0, Z0 = theta_0[0], theta_0[2] if self.__class__.__name__ in torus_mappings else theta_0[2] - Xpi, Zpi = theta_pi[0], theta_pi[2] if self.__class__.__name__ in torus_mappings else theta_pi[2] - - for i in range(len(e1)): - fig.add_trace( - go.Scatter(x=X0[i, :], y=Z0[i, :], mode="lines", line=dict(color="blue", width=1), opacity=0.5), - row=1, - col=2, - ) - for j in range(len(e2)): - fig.add_trace( - go.Scatter(x=X0[:, j], y=Z0[:, j], mode="lines", line=dict(color="blue", width=1), opacity=0.5), - row=1, - col=2, - ) - - if is_not_cube: - for i in range(len(e1)): - fig.add_trace( - go.Scatter( - x=Xpi[i, :], y=Zpi[i, :], mode="lines", line=dict(color="blue", width=1), opacity=0.5 - ), - row=1, - col=2, - ) - for j in range(len(e2)): - fig.add_trace( - go.Scatter( - x=Xpi[:, j], y=Zpi[:, j], mode="lines", line=dict(color="blue", width=1), opacity=0.5 - ), - row=1, - col=2, - ) - - # --- layout - fig.update_layout( - title_text=f"{self.__class__.__name__} Domain", - showlegend=False, - autosize=True, - margin=dict(l=10, r=10, t=40, b=10), - ) - - return fig - - def show_combined_plotly( - self, - logical=False, - grid_info=None, - markers=None, - marker_coords="logical", - show_control_pts=False, - save_dir=None, - fig=None, - ): - """ - Combined interactive Plotly figure: - 1) 3D domain wireframe - 2) Side view (eta1/eta2) - 3) Top view (eta1/eta3) - """ - import numpy as np - import plotly.graph_objects as go - from plotly.subplots import make_subplots - - torus_mappings = ( - "Tokamak", - "GVECunit", - "DESCunit", - "IGAPolarTorus", - "HollowTorus", - ) - is_not_cube = self.kind_map < 10 or self.kind_map > 19 - - # --- default figure with 3 subplots - if fig is None: - fig = make_subplots( - rows=1, - cols=3, - specs=[[{"type": "scene"}, {}, {}]], - subplot_titles=["3D Domain", f"{self.__class__.__name__} at η₃=0", "Top view"], - ) - - # --- grid setup - if grid_info is None: - e1 = np.linspace(0.0, 1.0, 20) - e2 = np.linspace(0.0, 1.0, 20) - e3 = np.linspace(0.0, 1.0, 20) - else: - e1 = np.linspace(0.0, 1.0, grid_info[0] + 1) - e2 = np.linspace(0.0, 1.0, grid_info[1] + 1) - e3 = np.linspace(0.0, 1.0, grid_info[2] + 1 if len(grid_info) > 2 else 2) - - # --- compute coordinates - if logical: - E1, E2, E3 = np.meshgrid(e1, e2, e3, indexing="ij") - X3d, Y3d, Z3d = E1, E2, E3 - X_side, Y_side = E1[:, :, 0], E2[:, :, 0] - X_top, Z_top = E1[:, 0, :], E3[:, 0, :] - else: - XYZ = self(e1, e2, e3, squeeze_out=True) - X3d, Y3d, Z3d = XYZ[0], XYZ[1], XYZ[2] - - # side view at eta3=0 - XYZ_side = self(e1, e2, 0.0, squeeze_out=True) - X_side = XYZ_side[0] - Y_side = XYZ_side[2] if self.__class__.__name__ in torus_mappings else XYZ_side[1] - - # top view at eta2=0 - XYZ_top = self(e1, 0.0, e3, squeeze_out=True) - X_top = XYZ_top[0] - Z_top = XYZ_top[2] if self.__class__.__name__ in torus_mappings else XYZ_top[2] - - # --- 3D wireframe (subplot 1) - axis_colors = ["black", "red", "blue"] - # e1 lines - for j in range(Y3d.shape[1]): - for k in range(Z3d.shape[2]): - fig.add_trace( - go.Scatter3d( - x=X3d[:, j, k], - y=Y3d[:, j, k], - z=Z3d[:, j, k], - mode="lines", - line=dict(color=axis_colors[0], width=1), - opacity=0.3, - ), - row=1, - col=1, - ) - # e2 lines - for k in range(Z3d.shape[2]): - for i in range(X3d.shape[0]): - fig.add_trace( - go.Scatter3d( - x=X3d[i, :, k], - y=Y3d[i, :, k], - z=Z3d[i, :, k], - mode="lines", - line=dict(color=axis_colors[1], width=1), - opacity=0.3, - ), - row=1, - col=1, - ) - # e3 lines - for i in range(X3d.shape[0]): - for j in range(Y3d.shape[1]): - fig.add_trace( - go.Scatter3d( - x=X3d[i, j, :], - y=Y3d[i, j, :], - z=Z3d[i, j, :], - mode="lines", - line=dict(color=axis_colors[2], width=1), - opacity=0.3, - ), - row=1, - col=1, - ) - - # --- Side view (subplot 2) - for i in range(X_side.shape[0]): - fig.add_trace( - go.Scatter(x=X_side[i, :], y=Y_side[i, :], mode="lines", line=dict(color="blue", width=1), opacity=0.5), - row=1, - col=2, - ) - for j in range(X_side.shape[1]): - fig.add_trace( - go.Scatter(x=X_side[:, j], y=Y_side[:, j], mode="lines", line=dict(color="blue", width=1), opacity=0.5), - row=1, - col=2, - ) - - # --- Top view (subplot 3) - for i in range(X_top.shape[0]): - fig.add_trace( - go.Scatter(x=X_top[i, :], y=Z_top[i, :], mode="lines", line=dict(color="blue", width=1), opacity=0.5), - row=1, - col=3, - ) - for j in range(X_top.shape[1]): - fig.add_trace( - go.Scatter(x=X_top[:, j], y=Z_top[:, j], mode="lines", line=dict(color="blue", width=1), opacity=0.5), - row=1, - col=3, - ) - - fig.update_layout( - title_text=f"{self.__class__.__name__} Combined Domain", - showlegend=False, - autosize=True, - margin=dict(l=10, r=10, t=40, b=10), - height=500, - ) - - return fig + return xp.array(params_numpy) def show( self, @@ -1752,7 +1374,6 @@ def show( show_control_pts=False, figsize=(12, 5), save_dir=None, - fig=None, ): """Plots isolines (and control point in case on spline mappings) of the 2D physical domain for eta3 = 0. Markers can be plotted as well (optional). @@ -1793,12 +1414,12 @@ def show( # plot domain without MPI decomposition and high resolution if grid_info is None: - e1 = np.linspace(0.0, 1.0, 16) - e2 = np.linspace(0.0, 1.0, 65) + e1 = xp.linspace(0.0, 1.0, 16) + e2 = xp.linspace(0.0, 1.0, 65) if logical: - E1, E2 = np.meshgrid(e1, e2, indexing="ij") - X = np.stack((E1, E2), axis=0) + E1, E2 = xp.meshgrid(e1, e2, indexing="ij") + X = xp.stack((E1, E2), axis=0) else: XYZ = self(e1, e2, 0.0, squeeze_out=True) @@ -1808,8 +1429,7 @@ def show( else: Y = XYZ[1] - if fig is None: - fig = plt.figure(figsize=figsize) + fig = plt.figure(figsize=figsize) ax = fig.add_subplot(1, 2, 1) # eta1-isolines @@ -1839,11 +1459,11 @@ def show( ) # top view - e3 = np.linspace(0.0, 1.0, 65) + e3 = xp.linspace(0.0, 1.0, 65) if logical: - E1, E2 = np.meshgrid(e1, e2, indexing="ij") - X = np.stack((E1, E2), axis=0) + E1, E2 = xp.meshgrid(e1, e2, indexing="ij") + X = xp.stack((E1, E2), axis=0) else: theta_0 = self(e1, 0.0, e3, squeeze_out=True) theta_pi = self(e1, 0.5, e3, squeeze_out=True) @@ -1904,7 +1524,7 @@ def show( # coordinates # e3 = [0., .25, .5, .75] # x, y, z = self(e1, e2, e3) - # R = np.sqrt(x**2 + y**2) + # R = xp.sqrt(x**2 + y**2) # fig = plt.figure(figsize=(13, 13)) # for n in range(4): @@ -1931,14 +1551,14 @@ def show( elif isinstance(grid_info, list): assert len(grid_info) > 1 - e1 = np.linspace(0.0, 1.0, grid_info[0] + 1) - e2 = np.linspace(0.0, 1.0, grid_info[1] + 1) + e1 = xp.linspace(0.0, 1.0, grid_info[0] + 1) + e2 = xp.linspace(0.0, 1.0, grid_info[1] + 1) fig = plt.figure(figsize=figsize) ax = fig.add_subplot(1, 1, 1) if logical: - E1, E2 = np.meshgrid(e1, e2, indexing="ij") + E1, E2 = xp.meshgrid(e1, e2, indexing="ij") # eta1-isolines for i in range(e1.size): @@ -1966,7 +1586,7 @@ def show( ax.plot(X[co1, :, j], X[co2, :, j], "tab:blue", alpha=0.5) # plot domain with MPI decomposition - elif isinstance(grid_info, np.ndarray): + elif isinstance(grid_info, xp.ndarray): assert grid_info.ndim == 2 assert grid_info.shape[1] > 5 @@ -1974,7 +1594,7 @@ def show( ax = fig.add_subplot(1, 1, 1) for i in range(grid_info.shape[0]): - e1 = np.linspace( + e1 = xp.linspace( grid_info[i, 0], grid_info[i, 1], int( @@ -1982,7 +1602,7 @@ def show( ) + 1, ) - e2 = np.linspace( + e2 = xp.linspace( grid_info[i, 3], grid_info[i, 4], int( @@ -1992,7 +1612,7 @@ def show( ) if logical: - E1, E2 = np.meshgrid(e1, e2, indexing="ij") + E1, E2 = xp.meshgrid(e1, e2, indexing="ij") # eta1-isolines first_line = ax.plot( @@ -2117,7 +1737,7 @@ def show( ax.axis("equal") - if isinstance(grid_info, np.ndarray): + if isinstance(grid_info, xp.ndarray): plt.legend() if self.__class__.__name__ in torus_mappings: @@ -2127,12 +1747,10 @@ def show( ax.set_xlabel("x") ax.set_ylabel(ylab) - # if save_dir is not None: - # plt.savefig(save_dir, bbox_inches="tight") - # else: - # plt.show() - - return fig, ax + if save_dir is not None: + plt.savefig(save_dir, bbox_inches="tight") + else: + plt.show() class Spline(Domain): @@ -2154,9 +1772,9 @@ def __init__( Nel: tuple[int] = (8, 24, 6), p: tuple[int] = (2, 3, 1), spl_kind: tuple[bool] = (False, True, True), - cx: np.ndarray = None, - cy: np.ndarray = None, - cz: np.ndarray = None, + cx: xp.ndarray = None, + cy: xp.ndarray = None, + cz: xp.ndarray = None, ): self.kind_map = 0 @@ -2187,7 +1805,7 @@ def __init__( assert self.cz.shape == expected_shape # identify polar singularity at eta1=0 - if np.all(self.cx[0, :, 0] == self.cx[0, 0, 0]): + if xp.all(self.cx[0, :, 0] == self.cx[0, 0, 0]): self.pole = True else: self.pole = False @@ -2218,17 +1836,17 @@ def __init__( Nel: tuple[int] = (8, 24), p: tuple[int] = (2, 3), spl_kind: tuple[bool] = (False, True), - cx: np.ndarray = None, - cy: np.ndarray = None, + cx: xp.ndarray = None, + cy: xp.ndarray = None, ): # get default control points if cx is None or cy is None: def X(eta1, eta2): - return eta1 * np.cos(2 * np.pi * eta2) + 3.0 + return eta1 * xp.cos(2 * xp.pi * eta2) + 3.0 def Y(eta1, eta2): - return eta1 * np.sin(2 * np.pi * eta2) + return eta1 * xp.sin(2 * xp.pi * eta2) cx, cy = interp_mapping(Nel, p, spl_kind, X, Y) @@ -2251,7 +1869,7 @@ def Y(eta1, eta2): assert self.cy.shape == expected_shape # identify polar singularity at eta1=0 - if np.all(self.cx[0, :] == self.cx[0, 0]): + if xp.all(self.cx[0, :] == self.cx[0, 0]): self.pole = True else: self.pole = False @@ -2259,7 +1877,7 @@ def Y(eta1, eta2): # reshape control points to 3D self._cx = self.cx[:, :, None] self._cy = self.cy[:, :, None] - self._cz = np.zeros((1, 1, 1), dtype=float) + self._cz = xp.zeros((1, 1, 1), dtype=float) # init base class super().__init__(Nel=Nel, p=p, spl_kind=spl_kind) @@ -2284,8 +1902,8 @@ def __init__( Nel: tuple[int] = (8, 24), p: tuple[int] = (2, 3), spl_kind: tuple[bool] = (False, True), - cx: np.ndarray = None, - cy: np.ndarray = None, + cx: xp.ndarray = None, + cy: xp.ndarray = None, Lz: float = 4.0, ): self.kind_map = 1 @@ -2294,10 +1912,10 @@ def __init__( if cx is None or cy is None: def X(eta1, eta2): - return eta1 * np.cos(2 * np.pi * eta2) + return eta1 * xp.cos(2 * xp.pi * eta2) def Y(eta1, eta2): - return eta1 * np.sin(2 * np.pi * eta2) + return eta1 * xp.sin(2 * xp.pi * eta2) cx, cy = interp_mapping(Nel, p, spl_kind, X, Y) @@ -2305,7 +1923,7 @@ def Y(eta1, eta2): cx[0] = 0.0 cy[0] = 0.0 - self.params_numpy = np.array([Lz]) + self.params_numpy = xp.array([Lz]) self.periodic_eta3 = False # init base class @@ -2336,7 +1954,7 @@ class PoloidalSplineTorus(PoloidalSpline): spl_kind : tuple[bool] Kind of spline in each poloidal direction (True=periodic, False=clamped). - cx, cy : np.ndarray + cx, cy : xp.ndarray Control points (spline coefficients) of the poloidal mapping. If None, a default square-to-disc mapping of radius 1 centered around (x, y) = (3, 0) is interpolated. @@ -2349,23 +1967,23 @@ def __init__( Nel: tuple[int] = (8, 24), p: tuple[int] = (2, 3), spl_kind: tuple[bool] = (False, True), - cx: np.ndarray = None, - cy: np.ndarray = None, + cx: xp.ndarray = None, + cy: xp.ndarray = None, tor_period: int = 3, ): # use setters for mapping attributes self.kind_map = 2 - self.params_numpy = np.array([float(tor_period)]) + self.params_numpy = xp.array([float(tor_period)]) self.periodic_eta3 = True # get default control points if cx is None or cy is None: def X(eta1, eta2): - return eta1 * np.cos(2 * np.pi * eta2) + 3.0 + return eta1 * xp.cos(2 * xp.pi * eta2) + 3.0 def Y(eta1, eta2): - return eta1 * np.sin(2 * np.pi * eta2) + return eta1 * xp.sin(2 * xp.pi * eta2) cx, cy = interp_mapping(Nel, p, spl_kind, X, Y) @@ -2407,7 +2025,7 @@ def interp_mapping(Nel, p, spl_kind, X, Y, Z=None): NbaseN = [Nel + p - kind * p for Nel, p, kind in zip(Nel, p, spl_kind)] # element boundaries - el_b = [np.linspace(0.0, 1.0, Nel + 1) for Nel in Nel] + el_b = [xp.linspace(0.0, 1.0, Nel + 1) for Nel in Nel] # spline knot vectors T = [bsp.make_knots(el_b, p, kind) for el_b, p, kind in zip(el_b, p, spl_kind)] @@ -2422,7 +2040,7 @@ def interp_mapping(Nel, p, spl_kind, X, Y, Z=None): if len(Nel) == 2: I = kron(I_mat[0], I_mat[1], format="csc") - I_pts = np.meshgrid(I_pts[0], I_pts[1], indexing="ij") + I_pts = xp.meshgrid(I_pts[0], I_pts[1], indexing="ij") cx = spsolve(I, X(I_pts[0], I_pts[1]).flatten()).reshape( NbaseN[0], @@ -2455,7 +2073,7 @@ def interp_mapping(Nel, p, spl_kind, X, Y, Z=None): return 0.0 -def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: np.ndarray): +def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: xp.ndarray): """n-dimensional tensor-product spline interpolation with discrete input. The interpolation points are passed as a list of 1d arrays, each array with increasing entries g[0]=0 < g[1] < ... @@ -2477,7 +2095,7 @@ def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: np. Returns -------- - coeffs : np.array + coeffs : xp.array spline coefficients as nd array. T : list[array] @@ -2492,11 +2110,11 @@ def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: np. I_mat = [] I_LU = [] for sh, x_grid, p_i, kind_i in zip(values.shape, grids_1d, p, spl_kind): - assert isinstance(x_grid, np.ndarray) + assert isinstance(x_grid, xp.ndarray) assert sh == x_grid.size assert ( - np.all( - np.roll(x_grid, 1)[1:] < x_grid[1:], + xp.all( + xp.roll(x_grid, 1)[1:] < x_grid[1:], ) and x_grid[-1] > x_grid[-2] ) @@ -2504,17 +2122,17 @@ def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: np. if kind_i: assert x_grid[-1] < 1.0, "Interpolation points must be <1 for periodic interpolation." - breaks = np.ones(x_grid.size + 1) + breaks = xp.ones(x_grid.size + 1) if p_i % 2 == 0: - breaks[1:-1] = (x_grid[1:] + np.roll(x_grid, 1)[1:]) / 2.0 + breaks[1:-1] = (x_grid[1:] + xp.roll(x_grid, 1)[1:]) / 2.0 breaks[0] = 0.0 else: breaks[:-1] = x_grid else: assert ( - np.abs( + xp.abs( x_grid[-1] - 1.0, ) < 1e-14 @@ -2531,12 +2149,12 @@ def spline_interpolation_nd(p: list, spl_kind: list, grids_1d: list, values: np. breaks[0] = 0.0 breaks[-1] = 1.0 - # breaks = np.linspace(0., 1., x_grid.size - (not kind_i)*p_i + 1) + # breaks = xp.linspace(0., 1., x_grid.size - (not kind_i)*p_i + 1) T += [bsp.make_knots(breaks, p_i, periodic=kind_i)] indN += [ - (np.indices((breaks.size - 1, p_i + 1))[1] + np.arange(breaks.size - 1)[:, None]) % x_grid.size, + (xp.indices((breaks.size - 1, p_i + 1))[1] + xp.arange(breaks.size - 1)[:, None]) % x_grid.size, ] I_mat += [bsp.collocation_matrix(T[-1], p_i, x_grid, periodic=kind_i)] diff --git a/src/struphy/geometry/domains.py b/src/struphy/geometry/domains.py index e33b6a2e8..20f995779 100644 --- a/src/struphy/geometry/domains.py +++ b/src/struphy/geometry/domains.py @@ -2,7 +2,7 @@ import copy -import numpy as np +import cunumpy as xp from struphy.fields_background.base import AxisymmMHDequilibrium from struphy.fields_background.equils import EQDSKequilibrium @@ -157,8 +157,8 @@ def __init__(self, gvec_equil=None): def XYZ(e1, e2, e3): rho = _rmin + e1 * (1.0 - _rmin) - theta = 2 * np.pi * e2 - zeta = 2 * np.pi * e3 / gvec_equil._nfp + theta = 2 * xp.pi * e2 + zeta = 2 * xp.pi * e3 / gvec_equil._nfp if gvec_equil.params["use_boozer"]: ev = gvec.EvaluationsBoozer(rho=rho, theta_B=theta, zeta_B=zeta, state=gvec_equil.state) else: @@ -210,9 +210,6 @@ def __init__(self, desc_equil=None): else: assert isinstance(desc_equil, DESCequilibrium) - # use params setter - self.params = copy.deepcopy(locals()) - Nel = desc_equil.params["Nel"] p = desc_equil.params["p"] @@ -290,10 +287,10 @@ def __init__( # get control points def X(eta1, eta2): - return a * eta1 * np.cos(2 * np.pi * eta2) + return a * eta1 * xp.cos(2 * xp.pi * eta2) def Y(eta1, eta2): - return a * eta1 * np.sin(2 * np.pi * eta2) + return a * eta1 * xp.sin(2 * xp.pi * eta2) spl_kind = (False, True) @@ -367,17 +364,17 @@ def __init__( if sfl: def theta(eta1, eta2): - return 2 * np.arctan(np.sqrt((1 + a * eta1 / R0) / (1 - a * eta1 / R0)) * np.tan(np.pi * eta2)) + return 2 * xp.arctan(xp.sqrt((1 + a * eta1 / R0) / (1 - a * eta1 / R0)) * xp.tan(xp.pi * eta2)) else: def theta(eta1, eta2): - return 2 * np.pi * eta2 + return 2 * xp.pi * eta2 def R(eta1, eta2): - return a * eta1 * np.cos(theta(eta1, eta2)) + R0 + return a * eta1 * xp.cos(theta(eta1, eta2)) + R0 def Z(eta1, eta2): - return a * eta1 * np.sin(theta(eta1, eta2)) + return a * eta1 * xp.sin(theta(eta1, eta2)) spl_kind = (False, True) @@ -746,11 +743,11 @@ def __init__( self.params = copy.deepcopy(locals()) self.params_numpy = self.get_params_numpy() - assert a2 <= R0, f"The minor radius must be smaller or equal than the major radius! {a2 = }, {R0 = }" + assert a2 <= R0, f"The minor radius must be smaller or equal than the major radius! {a2 =}, {R0 =}" if sfl: assert pol_period == 1, ( - f"Piece-of-cake is only implemented for torus coordinates, not for straight field line coordinates!" + "Piece-of-cake is only implemented for torus coordinates, not for straight field line coordinates!" ) # periodicity in eta3-direction and pole at eta1=0 @@ -766,24 +763,24 @@ def __init__( def inverse_map(self, x, y, z, bounded=True, change_out_order=False): """Analytical inverse map of HollowTorus""" - mr = np.sqrt(x**2 + y**2) - self.params["R0"] + mr = xp.sqrt(x**2 + y**2) - self.params["R0"] - eta3 = np.arctan2(-y, x) % (2 * np.pi / self.params["tor_period"]) / (2 * np.pi) * self.params["tor_period"] - eta2 = np.arctan2(z, mr) % (2 * np.pi / self.params["pol_period"]) / (2 * np.pi / self.params["pol_period"]) - eta1 = (z / np.sin(2 * np.pi * eta2 / self.params["pol_period"]) - self.params["a1"]) / ( + eta3 = xp.arctan2(-y, x) % (2 * xp.pi / self.params["tor_period"]) / (2 * xp.pi) * self.params["tor_period"] + eta2 = xp.arctan2(z, mr) % (2 * xp.pi / self.params["pol_period"]) / (2 * xp.pi / self.params["pol_period"]) + eta1 = (z / xp.sin(2 * xp.pi * eta2 / self.params["pol_period"]) - self.params["a1"]) / ( self.params["a2"] - self.params["a1"] ) if bounded: eta1[eta1 > 1] = 1.0 eta1[eta1 < 0] = 0.0 - assert np.all(np.logical_and(eta1 >= 0, eta1 <= 1)) + assert xp.all(xp.logical_and(eta1 >= 0, eta1 <= 1)) - assert np.all(np.logical_and(eta2 >= 0, eta2 <= 1)) - assert np.all(np.logical_and(eta3 >= 0, eta3 <= 1)) + assert xp.all(xp.logical_and(eta2 >= 0, eta2 <= 1)) + assert xp.all(xp.logical_and(eta3 >= 0, eta3 <= 1)) if change_out_order: - return np.transpose((eta1, eta2, eta3)) + return xp.transpose((eta1, eta2, eta3)) else: return eta1, eta2, eta3 diff --git a/src/struphy/geometry/evaluation_kernels.py b/src/struphy/geometry/evaluation_kernels.py index a357bbea1..4f97b9ce9 100644 --- a/src/struphy/geometry/evaluation_kernels.py +++ b/src/struphy/geometry/evaluation_kernels.py @@ -31,7 +31,7 @@ def f( args: DomainArguments Arguments for the mapping. - f_out : np.array + f_out : xp.array Output array of shape (3,). """ @@ -196,7 +196,7 @@ def df( args: DomainArguments Arguments for the mapping. - df_out : np.array + df_out : xp.array Output array of shape (3, 3). """ @@ -354,7 +354,7 @@ def det_df( args: DomainArguments Arguments for the mapping. - tmp1 : np.array + tmp1 : xp.array Temporary array of shape (3, 3). """ @@ -388,13 +388,13 @@ def df_inv( args: DomainArguments Arguments for the mapping. - tmp1: np.array + tmp1: xp.array Temporary array of shape (3, 3). avoid_round_off: bool Whether to manually set exact zeros in arrays. - dfinv_out: np.array + dfinv_out: xp.array Output array of shape (3, 3). """ @@ -484,13 +484,13 @@ def g( args: DomainArguments Arguments for the mapping. - tmp1, tmp2: np.array + tmp1, tmp2: xp.array Temporary arrays of shape (3, 3). avoid_round_off: bool Whether to manually set exact zeros in arrays. - g_out: np.array + g_out: xp.array Output array of shape (3, 3). """ df( @@ -601,13 +601,13 @@ def g_inv( args: DomainArguments Arguments for the mapping. - tmp1, tmp2, tmp3: np.array + tmp1, tmp2, tmp3: xp.array Temporary arrays of shape (3, 3). avoid_round_off: bool Whether to manually set exact zeros in arrays. - ginv_out: np.array + ginv_out: xp.array Output array of shape (3, 3). """ g( @@ -732,16 +732,16 @@ def select_metric_coeff( args: DomainArguments Arguments for the mapping. - tmp0: np.array + tmp0: xp.array Temporary array of shape (3,). - tmp1, tmp2, tmp3: np.array + tmp1, tmp2, tmp3: xp.array Temporary arrays of shape (3, 3). avoid_round_off: bool Whether to manually set exact zeros in arrays. - out: np.array + out: xp.array Output array of shape (3, 3). """ # identity map diff --git a/src/struphy/geometry/mappings_kernels.py b/src/struphy/geometry/mappings_kernels.py index 8b643855d..83e6275fd 100644 --- a/src/struphy/geometry/mappings_kernels.py +++ b/src/struphy/geometry/mappings_kernels.py @@ -49,13 +49,40 @@ def spline_3d( tmp3 = ind3[span3 - int(p[2]), :] f_out[0] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, b2, b3, tmp1, tmp2, tmp3, args.cx + int(p[0]), + int(p[1]), + int(p[2]), + b1, + b2, + b3, + tmp1, + tmp2, + tmp3, + args.cx, ) f_out[1] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, b2, b3, tmp1, tmp2, tmp3, args.cy + int(p[0]), + int(p[1]), + int(p[2]), + b1, + b2, + b3, + tmp1, + tmp2, + tmp3, + args.cy, ) f_out[2] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, b2, b3, tmp1, tmp2, tmp3, args.cz + int(p[0]), + int(p[1]), + int(p[2]), + b1, + b2, + b3, + tmp1, + tmp2, + tmp3, + args.cz, ) @@ -97,31 +124,112 @@ def spline_3d_df( tmp3 = ind3[span3 - int(p[2]), :] df_out[0, 0] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), der1, b2, b3, tmp1, tmp2, tmp3, args.cx + int(p[0]), + int(p[1]), + int(p[2]), + der1, + b2, + b3, + tmp1, + tmp2, + tmp3, + args.cx, ) df_out[0, 1] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, der2, b3, tmp1, tmp2, tmp3, args.cx + int(p[0]), + int(p[1]), + int(p[2]), + b1, + der2, + b3, + tmp1, + tmp2, + tmp3, + args.cx, ) df_out[0, 2] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, b2, der3, tmp1, tmp2, tmp3, args.cx + int(p[0]), + int(p[1]), + int(p[2]), + b1, + b2, + der3, + tmp1, + tmp2, + tmp3, + args.cx, ) df_out[1, 0] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), der1, b2, b3, tmp1, tmp2, tmp3, args.cy + int(p[0]), + int(p[1]), + int(p[2]), + der1, + b2, + b3, + tmp1, + tmp2, + tmp3, + args.cy, ) df_out[1, 1] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, der2, b3, tmp1, tmp2, tmp3, args.cy + int(p[0]), + int(p[1]), + int(p[2]), + b1, + der2, + b3, + tmp1, + tmp2, + tmp3, + args.cy, ) df_out[1, 2] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, b2, der3, tmp1, tmp2, tmp3, args.cy + int(p[0]), + int(p[1]), + int(p[2]), + b1, + b2, + der3, + tmp1, + tmp2, + tmp3, + args.cy, ) df_out[2, 0] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), der1, b2, b3, tmp1, tmp2, tmp3, args.cz + int(p[0]), + int(p[1]), + int(p[2]), + der1, + b2, + b3, + tmp1, + tmp2, + tmp3, + args.cz, ) df_out[2, 1] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, der2, b3, tmp1, tmp2, tmp3, args.cz + int(p[0]), + int(p[1]), + int(p[2]), + b1, + der2, + b3, + tmp1, + tmp2, + tmp3, + args.cz, ) df_out[2, 2] = evaluation_kernels_3d.evaluation_kernel_3d( - int(p[0]), int(p[1]), int(p[2]), b1, b2, der3, tmp1, tmp2, tmp3, args.cz + int(p[0]), + int(p[1]), + int(p[2]), + b1, + b2, + der3, + tmp1, + tmp2, + tmp3, + args.cz, ) @@ -276,7 +384,7 @@ def spline_2d_torus( tmp2 = ind2[span2 - int(p[1]), :] f_out[0] = evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), b1, b2, tmp1, tmp2, cx) * cos( - 2 * pi * eta3 / tor_period + 2 * pi * eta3 / tor_period, ) f_out[1] = ( evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), b1, b2, tmp1, tmp2, cx) @@ -329,10 +437,10 @@ def spline_2d_torus_df( tmp2 = ind2[span2 - int(p[1]), :] df_out[0, 0] = evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), der1, b2, tmp1, tmp2, cx) * cos( - 2 * pi * eta3 / tor_period + 2 * pi * eta3 / tor_period, ) df_out[0, 1] = evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), b1, der2, tmp1, tmp2, cx) * cos( - 2 * pi * eta3 / tor_period + 2 * pi * eta3 / tor_period, ) df_out[0, 2] = ( evaluation_kernels_2d.evaluation_kernel_2d(int(p[0]), int(p[1]), b1, b2, tmp1, tmp2, cx) @@ -621,7 +729,14 @@ def hollow_cyl_df(eta1: float, eta2: float, a1: float, a2: float, lz: float, poc @pure def powered_ellipse( - eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, s: float, f_out: "float[:]" + eta1: float, + eta2: float, + eta3: float, + rx: float, + ry: float, + lz: float, + s: float, + f_out: "float[:]", ): r""" Point-wise evaluation of @@ -664,7 +779,14 @@ def powered_ellipse( @pure def powered_ellipse_df( - eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, s: float, df_out: "float[:,:]" + eta1: float, + eta2: float, + eta3: float, + rx: float, + ry: float, + lz: float, + s: float, + df_out: "float[:,:]", ): """Jacobian matrix for :meth:`struphy.geometry.mappings_kernels.powered_ellipse`.""" @@ -843,7 +965,14 @@ def hollow_torus_df( @pure def shafranov_shift( - eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, de: float, f_out: "float[:]" + eta1: float, + eta2: float, + eta3: float, + rx: float, + ry: float, + lz: float, + de: float, + f_out: "float[:]", ): r""" Point-wise evaluation of @@ -887,7 +1016,14 @@ def shafranov_shift( @pure def shafranov_shift_df( - eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, de: float, df_out: "float[:,:]" + eta1: float, + eta2: float, + eta3: float, + rx: float, + ry: float, + lz: float, + de: float, + df_out: "float[:,:]", ): """Jacobian matrix for :meth:`struphy.geometry.mappings_kernels.shafranov_shift`.""" @@ -904,7 +1040,14 @@ def shafranov_shift_df( @pure def shafranov_sqrt( - eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, de: float, f_out: "float[:]" + eta1: float, + eta2: float, + eta3: float, + rx: float, + ry: float, + lz: float, + de: float, + f_out: "float[:]", ): r""" Point-wise evaluation of @@ -946,7 +1089,14 @@ def shafranov_sqrt( @pure def shafranov_sqrt_df( - eta1: float, eta2: float, eta3: float, rx: float, ry: float, lz: float, de: float, df_out: "float[:,:]" + eta1: float, + eta2: float, + eta3: float, + rx: float, + ry: float, + lz: float, + de: float, + df_out: "float[:,:]", ): """Jacobian matrix for :meth:`struphy.geometry.mappings_kernels.shafranov_sqrt`.""" diff --git a/src/struphy/geometry/tests/test_domain.py b/src/struphy/geometry/tests/test_domain.py index 1b02e4718..79de1402f 100644 --- a/src/struphy/geometry/tests/test_domain.py +++ b/src/struphy/geometry/tests/test_domain.py @@ -4,7 +4,7 @@ def test_prepare_arg(): """Tests prepare_arg static method in domain base class.""" - import numpy as np + import cunumpy as xp from struphy.geometry.base import Domain @@ -22,12 +22,12 @@ def a_vec(e1, e2, e3): a_2 = e2 * e3 a_3 = e3 * e1 - return np.stack((a_1, a_2, a_3), axis=0) + return xp.stack((a_1, a_2, a_3), axis=0) # ========== tensor-product/slice evaluation =============== - e1 = np.random.rand(4) - e2 = np.random.rand(5) - e3 = np.random.rand(6) + e1 = xp.random.rand(4) + e2 = xp.random.rand(5) + e3 = xp.random.rand(6) E1, E2, E3, is_sparse_meshgrid = Domain.prepare_eval_pts(e1, e2, e3, flat_eval=False) @@ -85,7 +85,7 @@ def a_vec(e1, e2, e3): assert Domain.prepare_arg([A1, A2, A3], E1, E2, E3).shape == shape_vector # ============== markers evaluation ========================== - markers = np.random.rand(10, 6) + markers = xp.random.rand(10, 6) shape_scalar = (markers.shape[0], 1) shape_vector = (markers.shape[0], 3) @@ -159,16 +159,16 @@ def a_vec(e1, e2, e3): def test_evaluation_mappings(mapping): """Tests domain object creation with default parameters and evaluation of metric coefficients.""" - import numpy as np + import cunumpy as xp from struphy.geometry import domains from struphy.geometry.base import Domain # arrays: - arr1 = np.linspace(0.0, 1.0, 4) - arr2 = np.linspace(0.0, 1.0, 5) - arr3 = np.linspace(0.0, 1.0, 6) - arrm = np.random.rand(10, 8) + arr1 = xp.linspace(0.0, 1.0, 4) + arr2 = xp.linspace(0.0, 1.0, 5) + arr3 = xp.linspace(0.0, 1.0, 6) + arrm = xp.random.rand(10, 8) print() print('Testing "evaluate"...') print("array shapes:", arr1.shape, arr2.shape, arr3.shape, arrm.shape) @@ -264,9 +264,9 @@ def test_evaluation_mappings(mapping): assert domain.metric_inv(arr1, arr2, arr3).shape == (3, 3) + arr1.shape + arr2.shape + arr3.shape # matrix evaluations at one point in third direction - mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing="ij") - mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing="ij") - mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing="ij") + mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing="ij") + mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing="ij") + mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing="ij") # eta1-eta2 matrix evaluation: print("eta1-eta2 matrix evaluation, shape:", domain(mat12_x, mat12_y, 0.5, squeeze_out=True).shape) @@ -296,7 +296,7 @@ def test_evaluation_mappings(mapping): assert domain.metric_inv(0.5, mat23_y, mat23_z, squeeze_out=True).shape == (3, 3) + mat23_y.shape # matrix evaluations for sparse meshgrid - mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) + mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) print("sparse meshgrid matrix evaluation, shape:", domain(mat_x, mat_y, mat_z).shape) assert domain(mat_x, mat_y, mat_z).shape == (3,) + (mat_x.shape[0], mat_y.shape[1], mat_z.shape[2]) assert domain.jacobian(mat_x, mat_y, mat_z).shape == (3, 3) + (mat_x.shape[0], mat_y.shape[1], mat_z.shape[2]) @@ -306,7 +306,7 @@ def test_evaluation_mappings(mapping): assert domain.metric_inv(mat_x, mat_y, mat_z).shape == (3, 3) + (mat_x.shape[0], mat_y.shape[1], mat_z.shape[2]) # matrix evaluations - mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij") + mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij") print("matrix evaluation, shape:", domain(mat_x, mat_y, mat_z).shape) assert domain(mat_x, mat_y, mat_z).shape == (3,) + mat_x.shape assert domain.jacobian(mat_x, mat_y, mat_z).shape == (3, 3) + mat_x.shape @@ -319,24 +319,24 @@ def test_evaluation_mappings(mapping): def test_pullback(): """Tests pullbacks to p-forms.""" - import numpy as np + import cunumpy as xp from struphy.geometry import domains from struphy.geometry.base import Domain # arrays: - arr1 = np.linspace(0.0, 1.0, 4) - arr2 = np.linspace(0.0, 1.0, 5) - arr3 = np.linspace(0.0, 1.0, 6) + arr1 = xp.linspace(0.0, 1.0, 4) + arr2 = xp.linspace(0.0, 1.0, 5) + arr3 = xp.linspace(0.0, 1.0, 6) print() print('Testing "pull"...') print("array shapes:", arr1.shape, arr2.shape, arr3.shape) - markers = np.random.rand(13, 6) + markers = xp.random.rand(13, 6) # physical function to pull back (used as components of forms too): def fun(x, y, z): - return np.exp(x) * np.sin(y) * np.cos(z) + return xp.exp(x) * xp.sin(y) * xp.cos(z) domain_class = getattr(domains, "Colella") domain = domain_class() @@ -424,9 +424,9 @@ def fun(x, y, z): ) # matrix pullbacks at one point in third direction - mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing="ij") - mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing="ij") - mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing="ij") + mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing="ij") + mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing="ij") + mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing="ij") # eta1-eta2 matrix pullback: if p_str == "0" or p_str == "3": @@ -453,7 +453,7 @@ def fun(x, y, z): ) # matrix pullbacks for sparse meshgrid - mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) + mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) if p_str == "0" or p_str == "3": assert domain.pull(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == ( mat_x.shape[0], @@ -469,7 +469,7 @@ def fun(x, y, z): ) # matrix pullbacks - mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij") + mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij") if p_str == "0" or p_str == "3": assert domain.pull(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == mat_x.shape else: @@ -479,24 +479,24 @@ def fun(x, y, z): def test_pushforward(): """Tests pushforward of p-forms.""" - import numpy as np + import cunumpy as xp from struphy.geometry import domains from struphy.geometry.base import Domain # arrays: - arr1 = np.linspace(0.0, 1.0, 4) - arr2 = np.linspace(0.0, 1.0, 5) - arr3 = np.linspace(0.0, 1.0, 6) + arr1 = xp.linspace(0.0, 1.0, 4) + arr2 = xp.linspace(0.0, 1.0, 5) + arr3 = xp.linspace(0.0, 1.0, 6) print() print('Testing "push"...') print("array shapes:", arr1.shape, arr2.shape, arr3.shape) - markers = np.random.rand(13, 6) + markers = xp.random.rand(13, 6) # logical function to push (used as components of forms too): def fun(e1, e2, e3): - return np.exp(e1) * np.sin(e2) * np.cos(e3) + return xp.exp(e1) * xp.sin(e2) * xp.cos(e3) domain_class = getattr(domains, "Colella") domain = domain_class() @@ -584,9 +584,9 @@ def fun(e1, e2, e3): ) # matrix pushs at one point in third direction - mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing="ij") - mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing="ij") - mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing="ij") + mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing="ij") + mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing="ij") + mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing="ij") # eta1-eta2 matrix push: if p_str == "0" or p_str == "3": @@ -613,7 +613,7 @@ def fun(e1, e2, e3): ) # matrix pushs for sparse meshgrid - mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) + mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) if p_str == "0" or p_str == "3": assert domain.push(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == ( mat_x.shape[0], @@ -629,7 +629,7 @@ def fun(e1, e2, e3): ) # matrix pushs - mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij") + mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij") if p_str == "0" or p_str == "3": assert domain.push(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == mat_x.shape else: @@ -639,24 +639,24 @@ def fun(e1, e2, e3): def test_transform(): """Tests transformation of p-forms.""" - import numpy as np + import cunumpy as xp from struphy.geometry import domains from struphy.geometry.base import Domain # arrays: - arr1 = np.linspace(0.0, 1.0, 4) - arr2 = np.linspace(0.0, 1.0, 5) - arr3 = np.linspace(0.0, 1.0, 6) + arr1 = xp.linspace(0.0, 1.0, 4) + arr2 = xp.linspace(0.0, 1.0, 5) + arr3 = xp.linspace(0.0, 1.0, 6) print() print('Testing "transform"...') print("array shapes:", arr1.shape, arr2.shape, arr3.shape) - markers = np.random.rand(13, 6) + markers = xp.random.rand(13, 6) # logical function to push (used as components of forms too): def fun(e1, e2, e3): - return np.exp(e1) * np.sin(e2) * np.cos(e3) + return xp.exp(e1) * xp.sin(e2) * xp.cos(e3) domain_class = getattr(domains, "Colella") domain = domain_class() @@ -756,9 +756,9 @@ def fun(e1, e2, e3): ) # matrix transforms at one point in third direction - mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing="ij") - mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing="ij") - mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing="ij") + mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing="ij") + mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing="ij") + mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing="ij") # eta1-eta2 matrix transform: if p_str == "0_to_3" or p_str == "3_to_0": @@ -794,7 +794,7 @@ def fun(e1, e2, e3): ) # matrix transforms for sparse meshgrid - mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) + mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij", sparse=True) if p_str == "0_to_3" or p_str == "3_to_0": assert domain.transform(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == ( mat_x.shape[0], @@ -810,7 +810,7 @@ def fun(e1, e2, e3): ) # matrix transforms - mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing="ij") + mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing="ij") if p_str == "0_to_3" or p_str == "3_to_0": assert domain.transform(fun_form, mat_x, mat_y, mat_z, kind=p_str).shape == mat_x.shape else: @@ -822,18 +822,18 @@ def fun(e1, e2, e3): # """ # # from struphy.geometry import domains -# import numpy as np +# import cunumpy as xp # # # arrays: -# arr1 = np.linspace(0., 1., 4) -# arr2 = np.linspace(0., 1., 5) -# arr3 = np.linspace(0., 1., 6) +# arr1 = xp.linspace(0., 1., 4) +# arr2 = xp.linspace(0., 1., 5) +# arr3 = xp.linspace(0., 1., 6) # print() # print('Testing "transform"...') # print('array shapes:', arr1.shape, arr2.shape, arr3.shape) # # # logical function to tranform (used as components of forms too): -# fun = lambda eta1, eta2, eta3: np.exp(eta1)*np.sin(eta2)*np.cos(eta3) +# fun = lambda eta1, eta2, eta3: xp.exp(eta1)*xp.sin(eta2)*xp.cos(eta3) # # domain_class = getattr(domains, 'Colella') # domain = domain_class() @@ -890,9 +890,9 @@ def fun(e1, e2, e3): # assert a.shape[0] == arr1.size and a.shape[1] == arr2.size and a.shape[2] == arr3.size # # # matrix transformation at one point in third direction -# mat12_x, mat12_y = np.meshgrid(arr1, arr2, indexing='ij') -# mat13_x, mat13_z = np.meshgrid(arr1, arr3, indexing='ij') -# mat23_y, mat23_z = np.meshgrid(arr2, arr3, indexing='ij') +# mat12_x, mat12_y = xp.meshgrid(arr1, arr2, indexing='ij') +# mat13_x, mat13_z = xp.meshgrid(arr1, arr3, indexing='ij') +# mat23_y, mat23_z = xp.meshgrid(arr2, arr3, indexing='ij') # # # eta1-eta2 matrix transformation: # a = domain.transform(fun_form, mat12_x, mat12_y, .5, p_str) @@ -908,21 +908,21 @@ def fun(e1, e2, e3): # assert a.shape == mat23_y.shape # # # matrix transformation for sparse meshgrid -# mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing='ij', sparse=True) +# mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing='ij', sparse=True) # a = domain.transform(fun_form, mat_x, mat_y, mat_z, p_str) # #print('sparse meshgrid matrix transformation, shape:', a.shape) # assert a.shape[0] == mat_x.shape[0] and a.shape[1] == mat_y.shape[1] and a.shape[2] == mat_z.shape[2] # # # matrix transformation -# mat_x, mat_y, mat_z = np.meshgrid(arr1, arr2, arr3, indexing='ij') +# mat_x, mat_y, mat_z = xp.meshgrid(arr1, arr2, arr3, indexing='ij') # a = domain.transform(fun_form, mat_x, mat_y, mat_z, p_str) # #print('matrix transformation, shape:', a.shape) # assert a.shape == mat_x.shape if __name__ == "__main__": - test_prepare_arg() - test_evaluation_mappings("GVECunit") - test_pullback() - test_pushforward() - test_transform() + # test_prepare_arg() + test_evaluation_mappings("DESCunit") + # test_pullback() + # test_pushforward() + # test_transform() diff --git a/src/struphy/geometry/transform_kernels.py b/src/struphy/geometry/transform_kernels.py index c95156b96..f9e6d8077 100644 --- a/src/struphy/geometry/transform_kernels.py +++ b/src/struphy/geometry/transform_kernels.py @@ -54,7 +54,13 @@ @stack_array("dfmat1", "dfmat2") def pull( - a: "float[:]", eta1: float, eta2: float, eta3: float, kind_fun: int, args_domain: "DomainArguments", out: "float[:]" + a: "float[:]", + eta1: float, + eta2: float, + eta3: float, + kind_fun: int, + args_domain: "DomainArguments", + out: "float[:]", ): """ Pull-back of a Cartesian scalar/vector field to a differential p-form. @@ -114,7 +120,13 @@ def pull( @stack_array("dfmat1", "dfmat2", "dfmat3") def push( - a: "float[:]", eta1: float, eta2: float, eta3: float, kind_fun: int, args_domain: "DomainArguments", out: "float[:]" + a: "float[:]", + eta1: float, + eta2: float, + eta3: float, + kind_fun: int, + args_domain: "DomainArguments", + out: "float[:]", ): """ Pushforward of a differential p-forms to a Cartesian scalar/vector field. @@ -172,7 +184,13 @@ def push( @stack_array("dfmat1", "dfmat2", "dfmat3", "vec1", "vec2") def tran( - a: "float[:]", eta1: float, eta2: float, eta3: float, kind_fun: int, args_domain: "DomainArguments", out: "float[:]" + a: "float[:]", + eta1: float, + eta2: float, + eta3: float, + kind_fun: int, + args_domain: "DomainArguments", + out: "float[:]", ): """ Transformations between differential p-forms and/or vector fields. diff --git a/src/struphy/geometry/utilities.py b/src/struphy/geometry/utilities.py index 6449174df..ccc159692 100644 --- a/src/struphy/geometry/utilities.py +++ b/src/struphy/geometry/utilities.py @@ -3,6 +3,7 @@ from typing import Callable +import cunumpy as xp import numpy as np # from typing import TYPE_CHECKING @@ -128,10 +129,10 @@ def field_line_tracing( Returns ------- - cR : np.ndarray + cR : xp.ndarray Control points (2d) of flux aligned spline mapping (R-component). - cZ : np.ndarray + cZ : xp.ndarray Control points (2d) of flux aligned spline mapping (Z-component). """ @@ -144,8 +145,8 @@ def field_line_tracing( ps, px = p_pre # spline knots - Ts = bsp.make_knots(np.linspace(0.0, 1.0, ns + 1), ps, False) - Tx = bsp.make_knots(np.linspace(0.0, 1.0, nx + 1), px, True) + Ts = bsp.make_knots(xp.linspace(0.0, 1.0, ns + 1), ps, False) + Tx = bsp.make_knots(xp.linspace(0.0, 1.0, nx + 1), px, True) # interpolation (Greville) points s_gr = bsp.greville(Ts, ps, False) @@ -164,13 +165,13 @@ def field_line_tracing( ] # check if pole is included - if np.abs(psi(psi_axis_R, psi_axis_Z) - psi0) < 1e-14: + if xp.abs(psi(psi_axis_R, psi_axis_Z) - psi0) < 1e-14: pole = True else: pole = False - R = np.zeros((s_gr.size, x_gr.size), dtype=float) - Z = np.zeros((s_gr.size, x_gr.size), dtype=float) + R = xp.zeros((s_gr.size, x_gr.size), dtype=float) + Z = xp.zeros((s_gr.size, x_gr.size), dtype=float) # function whose root must be found for j, x in enumerate(x_gr): @@ -187,8 +188,8 @@ def field_line_tracing( # function whose root must be found def f(r): - _R = psi_axis_R + r * np.cos(2 * np.pi * x) - _Z = psi_axis_Z + r * np.sin(2 * np.pi * x) + _R = psi_axis_R + r * xp.cos(2 * xp.pi * x) + _Z = psi_axis_Z + r * xp.sin(2 * xp.pi * x) psi_norm = (psi(_R, _Z) - psi0) / (psi1 - psi0) @@ -199,8 +200,8 @@ def f(r): r_flux_surface = newton(f, x0=r_guess) - R[i, j] = psi_axis_R + r_flux_surface * np.cos(2 * np.pi * x) - Z[i, j] = psi_axis_Z + r_flux_surface * np.sin(2 * np.pi * x) + R[i, j] = psi_axis_R + r_flux_surface * xp.cos(2 * xp.pi * x) + Z[i, j] = psi_axis_Z + r_flux_surface * xp.sin(2 * xp.pi * x) # get control points cR_equal_angle = kron_lusolve_2d(ILUs, R) @@ -226,8 +227,8 @@ def f(r): ps, px = p # spline knots - Ts = bsp.make_knots(np.linspace(0.0, 1.0, ns + 1), ps, False) - Tx = bsp.make_knots(np.linspace(0.0, 1.0, nx + 1), px, True) + Ts = bsp.make_knots(xp.linspace(0.0, 1.0, ns + 1), ps, False) + Tx = bsp.make_knots(xp.linspace(0.0, 1.0, nx + 1), px, True) # interpolation (Greville) points s_gr = bsp.greville(Ts, ps, False) @@ -254,10 +255,10 @@ def f(r): # target function for xi parametrization def f_angles(xis, s_val): - assert np.all(np.logical_and(xis > 0.0, xis < 1.0)) + assert xp.all(xp.logical_and(xis > 0.0, xis < 1.0)) # add 0 and 1 to angles array - xis_extended = np.array([0.0] + list(xis) + [1.0]) + xis_extended = xp.array([0.0] + list(xis) + [1.0]) # compute (R, Z) coordinates for given xis on fixed flux surface corresponding to s_val _RZ = domain_eq_angle(s_val, xis_extended, 0.0) @@ -266,17 +267,17 @@ def f_angles(xis, s_val): _Z = _RZ[2] # |grad(psi)| at xis - gp = np.sqrt(psi(_R, _Z, dR=1) ** 2 + psi(_R, _Z, dZ=1) ** 2) + gp = xp.sqrt(psi(_R, _Z, dR=1) ** 2 + psi(_R, _Z, dZ=1) ** 2) # compute weighted arc_lengths between two successive points in xis_extended array - dl = np.zeros(xis_extended.size - 1, dtype=float) + dl = xp.zeros(xis_extended.size - 1, dtype=float) weighted_arc_lengths_flux_surface(_R, _Z, gp, dl, xi_param_dict[xi_param]) # total length of the flux surface - l = np.sum(dl) + l = xp.sum(dl) # cumulative sum of arc lengths, start with 0! - l_cum = np.cumsum(dl) + l_cum = xp.cumsum(dl) # odd spline degree if px % 2 == 1: @@ -288,8 +289,8 @@ def f_angles(xis, s_val): return xi_diff # loop over flux surfaces and find xi parametrization - R = np.zeros((s_gr.size, x_gr.size), dtype=float) - Z = np.zeros((s_gr.size, x_gr.size), dtype=float) + R = xp.zeros((s_gr.size, x_gr.size), dtype=float) + Z = xp.zeros((s_gr.size, x_gr.size), dtype=float) if px % 2 == 1: xis0 = x_gr[1:].copy() diff --git a/src/struphy/geometry/utilities_kernels.py b/src/struphy/geometry/utilities_kernels.py index 85ccdbcf9..1c26b25c5 100644 --- a/src/struphy/geometry/utilities_kernels.py +++ b/src/struphy/geometry/utilities_kernels.py @@ -18,16 +18,16 @@ def weighted_arc_lengths_flux_surface(r: "float[:]", z: "float[:]", grad_psi: "f Parameters ---------- - r : np.ndarray + r : xp.ndarray R coordinates of the flux surface. - z : np.ndarray + z : xp.ndarray Z coordinates of the flux surface. - grad_psi : np.ndarray + grad_psi : xp.ndarray Absolute values of the flux function gradient on the flux surface: |grad(psi)| = sqrt[ (d_R psi)**2 + (d_Z psi)**2 ]. - dwls : np.ndarray + dwls : xp.ndarray The weighted arc lengths will be written into this array. Length must be one smaller than lengths of r, z and grad_psi. kind : int diff --git a/src/struphy/initial/eigenfunctions.py b/src/struphy/initial/eigenfunctions.py index c13397a1f..239ae24a9 100644 --- a/src/struphy/initial/eigenfunctions.py +++ b/src/struphy/initial/eigenfunctions.py @@ -1,6 +1,6 @@ import os -import numpy as np +import cunumpy as xp import yaml from psydac.api.discretization import discretize from sympde.topology import Derham, Line @@ -54,11 +54,11 @@ def __init__(self, derham, **params): spec_path = params["spec_abs"] # load eigenvector for velocity field - omega2, U2_eig = np.split(np.load(spec_path), [1], axis=0) + omega2, U2_eig = xp.split(xp.load(spec_path), [1], axis=0) omega2 = omega2.flatten() # find eigenvector corresponding to given squared eigenfrequency range - mode = np.where((np.real(omega2) < params["eig_freq_upper"]) & (np.real(omega2) > params["eig_freq_lower"]))[0] + mode = xp.where((xp.real(omega2) < params["eig_freq_upper"]) & (xp.real(omega2) > params["eig_freq_lower"]))[0] assert mode.size == 1 mode = mode[0] @@ -67,13 +67,16 @@ def __init__(self, derham, **params): nnz_tor = derham.boundary_ops["2"].dim_nz_tor eig_vec_1 = U2_eig[ - 0 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2], mode + 0 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2], + mode, ] eig_vec_2 = U2_eig[ - 1 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 1 * nnz_pol[1] + 0 * nnz_pol[2], mode + 1 * nnz_pol[0] + 0 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 1 * nnz_pol[1] + 0 * nnz_pol[2], + mode, ] eig_vec_3 = U2_eig[ - 1 * nnz_pol[0] + 1 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 1 * nnz_pol[1] + 1 * nnz_pol[2], mode + 1 * nnz_pol[0] + 1 * nnz_pol[1] + 0 * nnz_pol[2] : 1 * nnz_pol[0] + 1 * nnz_pol[1] + 1 * nnz_pol[2], + mode, ] del omega2, U2_eig @@ -89,28 +92,28 @@ def __init__(self, derham, **params): n_tor = int(os.path.split(spec_path)[-1][-6:-4]) - N_cos = p0(lambda phi: np.cos(2 * np.pi * n_tor * phi)).coeffs.toarray() - N_sin = p0(lambda phi: np.sin(2 * np.pi * n_tor * phi)).coeffs.toarray() + N_cos = p0(lambda phi: xp.cos(2 * xp.pi * n_tor * phi)).coeffs.toarray() + N_sin = p0(lambda phi: xp.sin(2 * xp.pi * n_tor * phi)).coeffs.toarray() - D_cos = p1(lambda phi: np.cos(2 * np.pi * n_tor * phi)).coeffs.toarray() - D_sin = p1(lambda phi: np.sin(2 * np.pi * n_tor * phi)).coeffs.toarray() + D_cos = p1(lambda phi: xp.cos(2 * xp.pi * n_tor * phi)).coeffs.toarray() + D_sin = p1(lambda phi: xp.sin(2 * xp.pi * n_tor * phi)).coeffs.toarray() # select real part or imaginary part assert params["kind"] == "r" or params["kind"] == "i" if params["kind"] == "r": - eig_vec_1 = (np.outer(np.real(eig_vec_1), D_cos) - np.outer(np.imag(eig_vec_1), D_sin)).flatten() - eig_vec_2 = (np.outer(np.real(eig_vec_2), D_cos) - np.outer(np.imag(eig_vec_2), D_sin)).flatten() - eig_vec_3 = (np.outer(np.real(eig_vec_3), N_cos) - np.outer(np.imag(eig_vec_3), N_sin)).flatten() + eig_vec_1 = (xp.outer(xp.real(eig_vec_1), D_cos) - xp.outer(xp.imag(eig_vec_1), D_sin)).flatten() + eig_vec_2 = (xp.outer(xp.real(eig_vec_2), D_cos) - xp.outer(xp.imag(eig_vec_2), D_sin)).flatten() + eig_vec_3 = (xp.outer(xp.real(eig_vec_3), N_cos) - xp.outer(xp.imag(eig_vec_3), N_sin)).flatten() else: - eig_vec_1 = (np.outer(np.imag(eig_vec_1), D_cos) + np.outer(np.real(eig_vec_1), D_sin)).flatten() - eig_vec_2 = (np.outer(np.imag(eig_vec_2), D_cos) + np.outer(np.real(eig_vec_2), D_sin)).flatten() - eig_vec_3 = (np.outer(np.imag(eig_vec_3), N_cos) + np.outer(np.real(eig_vec_3), N_sin)).flatten() + eig_vec_1 = (xp.outer(xp.imag(eig_vec_1), D_cos) + xp.outer(xp.real(eig_vec_1), D_sin)).flatten() + eig_vec_2 = (xp.outer(xp.imag(eig_vec_2), D_cos) + xp.outer(xp.real(eig_vec_2), D_sin)).flatten() + eig_vec_3 = (xp.outer(xp.imag(eig_vec_3), N_cos) + xp.outer(xp.real(eig_vec_3), N_sin)).flatten() # set coefficients in full space - eigvec_1_ten = np.zeros(derham.nbasis["2"][0], dtype=float) - eigvec_2_ten = np.zeros(derham.nbasis["2"][1], dtype=float) - eigvec_3_ten = np.zeros(derham.nbasis["2"][2], dtype=float) + eigvec_1_ten = xp.zeros(derham.nbasis["2"][0], dtype=float) + eigvec_2_ten = xp.zeros(derham.nbasis["2"][1], dtype=float) + eigvec_3_ten = xp.zeros(derham.nbasis["2"][2], dtype=float) bc1_1 = derham.dirichlet_bc[0][0] bc1_2 = derham.dirichlet_bc[0][1] @@ -138,19 +141,19 @@ def __init__(self, derham, **params): else: # split into polar/tensor product parts - eig_vec_1 = np.split( + eig_vec_1 = xp.split( eig_vec_1, [ derham.Vh_pol["2"].n_polar[0] * nnz_tor[0], ], ) - eig_vec_2 = np.split( + eig_vec_2 = xp.split( eig_vec_2, [ derham.Vh_pol["2"].n_polar[1] * nnz_tor[1], ], ) - eig_vec_3 = np.split( + eig_vec_3 = xp.split( eig_vec_3, [ derham.Vh_pol["2"].n_polar[2] * nnz_tor[2], @@ -182,7 +185,7 @@ def __init__(self, derham, **params): ] eigvec_1_ten[derham.Vh_pol["2"].n_rings[0] : derham.nbasis["2"][0][0] - bc1_2, :, :] = eig_vec_1[1].reshape( - n_v2_0[0] + n_v2_0[0], ) eigvec_2_ten[derham.Vh_pol["2"].n_rings[1] :, :, :] = eig_vec_2[1].reshape(n_v2_0[1]) eigvec_3_ten[derham.Vh_pol["2"].n_rings[2] :, :, :] = eig_vec_3[1].reshape(n_v2_0[2]) diff --git a/src/struphy/initial/perturbations.py b/src/struphy/initial/perturbations.py index f37e38584..4c113d02d 100644 --- a/src/struphy/initial/perturbations.py +++ b/src/struphy/initial/perturbations.py @@ -3,7 +3,7 @@ from dataclasses import dataclass -import numpy as np +import cunumpy as xp import scipy import scipy.special @@ -168,7 +168,7 @@ def __init__( self._pfuns += [lambda eta3: 1.0] elif pfun == "localize": self._pfuns += [ - lambda eta3: np.tanh((eta3 - 0.5) / params) / np.cosh((eta3 - 0.5) / params), + lambda eta3: xp.tanh((eta3 - 0.5) / params) / xp.cosh((eta3 - 0.5) / params), ] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -193,10 +193,10 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * np.sin( - l * 2.0 * np.pi / self._Lx * x - + m * 2.0 * np.pi / self._Ly * y - + n * 2.0 * np.pi / self._Lz * z + * xp.sin( + l * 2.0 * xp.pi / self._Lx * x + + m * 2.0 * xp.pi / self._Ly * y + + n * 2.0 * xp.pi / self._Lz * z + t, ) ) @@ -296,8 +296,8 @@ def __call__(self, x, y, z): val = 0.0 for amp, l, m, n in zip(self._amps, self._ls, self._ms, self._ns): - val += amp * np.cos( - l * 2.0 * np.pi / self._Lx * x + m * 2.0 * np.pi / self._Ly * y + n * 2.0 * np.pi / self._Lz * z, + val += amp * xp.cos( + l * 2.0 * xp.pi / self._Lx * x + m * 2.0 * xp.pi / self._Ly * y + n * 2.0 * xp.pi / self._Lz * z, ) # print( "Cos max value", val.max()) return val @@ -333,12 +333,12 @@ def __init__(self, m=1, a1=1.0, a2=2.0, a=1, b=-0.28): def __call__(self, eta1, eta2, eta3): val = 0.0 r = eta1 * (self._r2 - self._r1) + self._r1 - theta = eta2 * 2.0 * np.pi + theta = eta2 * 2.0 * xp.pi val += ( -self._m / r - * np.cos(self._m * theta) + * xp.cos(self._m * theta) * (self._a * scipy.special.jv(self._m, r) + self._b * scipy.special.yn(self._m, r)) ) return val @@ -375,12 +375,12 @@ def __init__(self, m=1, a1=1.0, a2=2.0, a=1, b=-0.28): def __call__(self, eta1, eta2, eta3): val = 0.0 r = eta1 * (self._r2 - self._r1) + self._r1 - theta = eta2 * 2.0 * np.pi + theta = eta2 * 2.0 * xp.pi val += ( self._a * ((self._m / r) * scipy.special.jv(self._m, r) - scipy.special.jv(self._m + 1, r)) + (self._b * ((self._m / r) * scipy.special.yn(self._m, r) - scipy.special.yn(self._m + 1, r))) - ) * np.sin(self._m * theta) + ) * xp.sin(self._m * theta) return val @@ -414,11 +414,11 @@ def __init__(self, m=1, a1=1.0, a2=2.0, a=1, b=-0.28): def __call__(self, eta1, eta2, eta3): val = 0.0 r = eta1 * (self._r2 - self._r1) + self._r1 - theta = eta2 * 2.0 * np.pi + theta = eta2 * 2.0 * xp.pi z = eta3 - val += (self._a * scipy.special.jv(self._m, r) + self._b * scipy.special.yn(self._m, r)) * np.cos( - self._m * theta + val += (self._a * scipy.special.jv(self._m, r) + self._b * scipy.special.yn(self._m, r)) * xp.cos( + self._m * theta, ) return val @@ -509,7 +509,7 @@ def __init__( if pfun == "Id": self._pfuns += [lambda z: 1.0] elif pfun == "localize": - self._pfuns += [lambda z, p=params: np.tanh((z - 0.5) / p) / np.cosh((z - 0.5) / p)] + self._pfuns += [lambda z, p=params: xp.tanh((z - 0.5) / p) / xp.cosh((z - 0.5) / p)] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -523,8 +523,8 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * np.cos(l * 2.0 * np.pi / self._Lx * x + thx) - * np.cos(m * 2.0 * np.pi / self._Ly * y + thy) + * xp.cos(l * 2.0 * xp.pi / self._Lx * x + thx) + * xp.cos(m * 2.0 * xp.pi / self._Ly * y + thy) ) return val @@ -614,7 +614,7 @@ def __init__( if pfun == "Id": self._pfuns += [lambda z: 1.0] elif pfun == "localize": - self._pfuns += [lambda z, p=params: np.tanh((z - 0.5) / p) / np.cosh((z - 0.5) / p)] + self._pfuns += [lambda z, p=params: xp.tanh((z - 0.5) / p) / xp.cosh((z - 0.5) / p)] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -628,8 +628,8 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * np.sin(l * 2.0 * np.pi / self._Lx * x + thx) - * np.sin(m * 2.0 * np.pi / self._Ly * y + thy) + * xp.sin(l * 2.0 * xp.pi / self._Lx * x + thx) + * xp.sin(m * 2.0 * xp.pi / self._Ly * y + thy) ) return val @@ -721,7 +721,7 @@ def __init__( if pfun == "Id": self._pfuns += [lambda z: 1.0] elif pfun == "localize": - self._pfuns += [lambda z, p=params: np.tanh((z - 0.5) / p) / np.cosh((z - 0.5) / p)] + self._pfuns += [lambda z, p=params: xp.tanh((z - 0.5) / p) / xp.cosh((z - 0.5) / p)] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -735,8 +735,8 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * np.sin(l * 2.0 * np.pi / self._Lx * x + thx) - * np.cos(m * 2.0 * np.pi / self._Ly * y + thy) + * xp.sin(l * 2.0 * xp.pi / self._Lx * x + thx) + * xp.cos(m * 2.0 * xp.pi / self._Ly * y + thy) ) return val @@ -828,7 +828,7 @@ def __init__( if pfun == "Id": self._pfuns += [lambda z: 1.0] elif pfun == "localize": - self._pfuns += [lambda z, p=params: np.tanh((z - 0.5) / p) / np.cosh((z - 0.5) / p)] + self._pfuns += [lambda z, p=params: xp.tanh((z - 0.5) / p) / xp.cosh((z - 0.5) / p)] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -842,8 +842,8 @@ def __call__(self, x, y, z): val += ( amp * pfun(z) - * np.cos(l * 2.0 * np.pi / self._Lx * x + thx) - * np.sin(m * 2.0 * np.pi / self._Ly * y + thy) + * xp.cos(l * 2.0 * xp.pi / self._Lx * x + thx) + * xp.sin(m * 2.0 * xp.pi / self._Ly * y + thy) ) return val @@ -946,18 +946,18 @@ def __init__( ls = 1 else: ls = params - self._pfuns += [lambda eta1: np.sin(ls * np.pi * eta1)] + self._pfuns += [lambda eta1: xp.sin(ls * xp.pi * eta1)] elif pfun == "exp": self._pfuns += [ - lambda eta1: np.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) - / np.sqrt(2 * np.pi * params[1] ** 2), + lambda eta1: xp.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) + / xp.sqrt(2 * xp.pi * params[1] ** 2), ] elif pfun == "d_exp": self._pfuns += [ lambda eta1: -(eta1 - params[0]) / params[1] ** 2 - * np.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) - / np.sqrt(2 * np.pi * params[1] ** 2), + * xp.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) + / xp.sqrt(2 * xp.pi * params[1] ** 2), ] else: raise ValueError(f"Profile function {pfun} is not defined..") @@ -972,8 +972,8 @@ def __call__(self, eta1, eta2, eta3): val += ( amp * pfun(eta1) - * np.sin( - mi * 2.0 * np.pi * eta2 + ni * 2.0 * np.pi * eta3, + * xp.sin( + mi * 2.0 * xp.pi * eta2 + ni * 2.0 * xp.pi * eta3, ) ) @@ -1078,20 +1078,20 @@ def __init__( ls = 1 else: ls = params - self._pfuns += [lambda eta1: np.sin(ls * np.pi * eta1)] + self._pfuns += [lambda eta1: xp.sin(ls * xp.pi * eta1)] elif pfun == "cos": - self._pfuns += [lambda eta1: np.cos(np.pi * eta1)] + self._pfuns += [lambda eta1: xp.cos(xp.pi * eta1)] elif pfun == "exp": self._pfuns += [ - lambda eta1: np.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) - / np.sqrt(2 * np.pi * params[1] ** 2), + lambda eta1: xp.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) + / xp.sqrt(2 * xp.pi * params[1] ** 2), ] elif pfun == "d_exp": self._pfuns += [ lambda eta1: -(eta1 - params[0]) / params[1] ** 2 - * np.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) - / np.sqrt(2 * np.pi * params[1] ** 2), + * xp.exp(-((eta1 - params[0]) ** 2) / (2 * params[1] ** 2)) + / xp.sqrt(2 * xp.pi * params[1] ** 2), ] else: raise ValueError( @@ -1108,8 +1108,8 @@ def __call__(self, eta1, eta2, eta3): val += ( amp * pfun(eta1) - * np.cos( - mi * 2.0 * np.pi * eta2 + ni * 2.0 * np.pi * eta3, + * xp.cos( + mi * 2.0 * xp.pi * eta2 + ni * 2.0 * xp.pi * eta3, ) ) @@ -1157,7 +1157,7 @@ def __init__( self.comp = comp def __call__(self, e1, e2, e3): - val = self._amp * (-np.tanh((e1 - 0.75) / self._delta) + np.tanh((e1 - 0.25) / self._delta) - 1) + val = self._amp * (-xp.tanh((e1 - 0.75) / self._delta) + xp.tanh((e1 - 0.25) / self._delta) - 1) return val @@ -1203,7 +1203,7 @@ def __init__( self.comp = comp def __call__(self, e1, e2, e3): - val = self._amp * (-np.tanh((e2 - 0.75) / self._delta) + np.tanh((e2 - 0.25) / self._delta) - 1) + val = self._amp * (-xp.tanh((e2 - 0.75) / self._delta) + xp.tanh((e2 - 0.25) / self._delta) - 1) return val @@ -1249,7 +1249,7 @@ def __init__( self.comp = comp def __call__(self, e1, e2, e3): - val = self._amp * (-np.tanh((e3 - 0.75) / self._delta) + np.tanh((e3 - 0.25) / self._delta) - 1) + val = self._amp * (-xp.tanh((e3 - 0.75) / self._delta) + xp.tanh((e3 - 0.25) / self._delta) - 1) return val @@ -1376,9 +1376,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = np.sqrt(x**2 + y**2) - R = np.where(R == 0.0, 1e-9, R) - phi = np.arctan2(-y, x) + R = xp.sqrt(x**2 + y**2) + R = xp.where(R == 0.0, 1e-9, R) + phi = xp.arctan2(-y, x) ustarR = ( self._alpha * R / (self._a * self._R0) * (-z) + self._beta * self._Bp * self._R0 / (self._B0 * self._a * R) * z @@ -1396,10 +1396,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = np.cos(phi) * uR - R * np.sin(phi) * uphi + ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi return ux elif self.comp == 1: - uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi + uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -1482,9 +1482,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = np.sqrt(x**2 + y**2) - R = np.where(R == 0.0, 1e-9, R) - phi = np.arctan2(-y, x) + R = xp.sqrt(x**2 + y**2) + R = xp.where(R == 0.0, 1e-9, R) + phi = xp.arctan2(-y, x) ustarR = ( self._alpha * R / (self._a * self._R0) * (-z) + self._beta * self._Bp * self._R0 / (self._B0 * self._a * R) * z @@ -1502,10 +1502,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = np.cos(phi) * uR - R * np.sin(phi) * uphi + ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi return ux elif self.comp == 1: - uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi + uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -1588,9 +1588,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = np.sqrt(x**2 + y**2) - R = np.where(R == 0.0, 1e-9, R) - phi = np.arctan2(-y, x) + R = xp.sqrt(x**2 + y**2) + R = xp.where(R == 0.0, 1e-9, R) + phi = xp.arctan2(-y, x) ustarR = ( self._alpha * R / (self._a * self._R0) * (-z) + self._beta * self._Bp * self._R0 / (self._B0 * self._a * R) * z @@ -1608,10 +1608,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = np.cos(phi) * uR - R * np.sin(phi) * uphi + ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi return ux elif self.comp == 1: - uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi + uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -1682,7 +1682,7 @@ def __init__(self, a=1.0, R0=2.0, B0=10.0, Bp=12.5, alpha=0.1, beta=1.0): # equilibrium potential def __call__(self, x, y, z): """Equilibrium potential.""" - R = np.sqrt(x**2 + y**2) + R = xp.sqrt(x**2 + y**2) pp = 0.5 * self._a * self._B0 * self._alpha * (((R - self._R0) ** 2 + z**2) / self._a**2 - 2.0 / 3.0) return pp @@ -1745,15 +1745,15 @@ def __call__(self, x, y, z): """Velocity of ions.""" """x component""" if self._dimension == "2D": - ux = -np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y) + ux = -xp.sin(2 * xp.pi * x) * xp.sin(2 * xp.pi * y) elif self._dimension == "1D": - ux = np.sin(2 * np.pi * x) + 1.0 + ux = xp.sin(2 * xp.pi * x) + 1.0 """y component""" if self._dimension == "2D": - uy = -np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y) + uy = -xp.cos(2 * xp.pi * x) * xp.cos(2 * xp.pi * y) elif self._dimension == "1D": - uy = np.cos(2 * np.pi * x) + uy = xp.cos(2 * xp.pi * x) """z component""" uz = 0.0 * x @@ -1771,15 +1771,15 @@ def __call__(self, x, y, z): """Velocity of electrons.""" """x component""" if self._dimension == "2D": - ux = -np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y) + ux = -xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y) elif self._dimension == "1D": - ux = np.sin(2.0 * np.pi * x) + ux = xp.sin(2.0 * xp.pi * x) """y component""" if self._dimension == "2D": - uy = -np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y) + uy = -xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y) elif self._dimension == "1D": - uy = np.cos(2 * np.pi * x) + uy = xp.cos(2 * xp.pi * x) """z component""" uz = 0.0 * x @@ -1844,9 +1844,9 @@ def __init__(self, dimension="1D", b0=1.0): def __call__(self, x, y, z): """Potential.""" if self._dimension == "2D": - phi = np.cos(2 * np.pi * x) + np.sin(2 * np.pi * y) + phi = xp.cos(2 * xp.pi * x) + xp.sin(2 * xp.pi * y) elif self._dimension == "1D": - phi = np.sin(2.0 * np.pi * x) + phi = xp.sin(2.0 * xp.pi * x) return phi @@ -1906,15 +1906,15 @@ def __call__(self, x, y, z): """Velocity of ions.""" """x component""" if self._dimension == "2D": - ux = -np.sin(2 * np.pi * x) * np.sin(2 * np.pi * y) + ux = -xp.sin(2 * xp.pi * x) * xp.sin(2 * xp.pi * y) elif self._dimension == "1D": - ux = np.sin(2 * np.pi * x) + 1.0 + ux = xp.sin(2 * xp.pi * x) + 1.0 """y component""" if self._dimension == "2D": - uy = -np.cos(2 * np.pi * x) * np.cos(2 * np.pi * y) + uy = -xp.cos(2 * xp.pi * x) * xp.cos(2 * xp.pi * y) elif self._dimension == "1D": - uy = np.cos(2 * np.pi * x) + uy = xp.cos(2 * xp.pi * x) """z component""" uz = 0.0 * x @@ -1932,15 +1932,15 @@ def __call__(self, x, y, z): """Velocity of electrons.""" """x component""" if self._dimension == "2D": - ux = -np.sin(4 * np.pi * x) * np.sin(4 * np.pi * y) + ux = -xp.sin(4 * xp.pi * x) * xp.sin(4 * xp.pi * y) elif self._dimension == "1D": - ux = np.sin(2.0 * np.pi * x) + ux = xp.sin(2.0 * xp.pi * x) """y component""" if self._dimension == "2D": - uy = -np.cos(4 * np.pi * x) * np.cos(4 * np.pi * y) + uy = -xp.cos(4 * xp.pi * x) * xp.cos(4 * xp.pi * y) elif self._dimension == "1D": - uy = np.cos(2 * np.pi * x) + uy = xp.cos(2 * xp.pi * x) """z component""" uz = 0.0 * x @@ -2001,8 +2001,8 @@ def __call__(self, eta1, eta2=None, eta3=None): val = ( self._n0 * self._c[3] - * np.exp( - -self._c[2] / self._c[1] * np.tanh((eta1 - self._c[0]) / self._c[2]), + * xp.exp( + -self._c[2] / self._c[1] * xp.tanh((eta1 - self._c[0]) / self._c[2]), ) ) @@ -2088,9 +2088,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = np.sqrt(x**2 + y**2) - R = np.where(R == 0.0, 1e-9, R) - phi = np.arctan2(-y, x) + R = xp.sqrt(x**2 + y**2) + R = xp.where(R == 0.0, 1e-9, R) + phi = xp.arctan2(-y, x) A = self._alpha / (self._a * self._R0) C = self._beta * self._Bp * self._R0 / (self._B0 * self._a) @@ -2101,10 +2101,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = np.cos(phi) * uR - R * np.sin(phi) * uphi + ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi return ux elif self.comp == 1: - uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi + uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -2192,9 +2192,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = np.sqrt(x**2 + y**2) - R = np.where(R == 0.0, 1e-9, R) - phi = np.arctan2(-y, x) + R = xp.sqrt(x**2 + y**2) + R = xp.where(R == 0.0, 1e-9, R) + phi = xp.arctan2(-y, x) A = self._alpha / (self._a * self._R0) C = self._beta * self._Bp * self._R0 / (self._B0 * self._a) @@ -2205,10 +2205,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = np.cos(phi) * uR - R * np.sin(phi) * uphi + ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi return ux elif self.comp == 1: - uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi + uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ @@ -2296,9 +2296,9 @@ def __init__( # equilibrium ion velocity def __call__(self, x, y, z): """Velocity of ions and electrons.""" - R = np.sqrt(x**2 + y**2) - R = np.where(R == 0.0, 1e-9, R) - phi = np.arctan2(-y, x) + R = xp.sqrt(x**2 + y**2) + R = xp.where(R == 0.0, 1e-9, R) + phi = xp.arctan2(-y, x) A = self._alpha / (self._a * self._R0) C = self._beta * self._Bp * self._R0 / (self._B0 * self._a) @@ -2309,10 +2309,10 @@ def __call__(self, x, y, z): # from cylindrical to cartesian: if self.comp == 0: - ux = np.cos(phi) * uR - R * np.sin(phi) * uphi + ux = xp.cos(phi) * uR - R * xp.sin(phi) * uphi return ux elif self.comp == 1: - uy = -np.sin(phi) * uR - R * np.cos(phi) * uphi + uy = -xp.sin(phi) * uR - R * xp.cos(phi) * uphi return uy elif self.comp == 2: uz = uZ diff --git a/src/struphy/initial/tests/test_init_perturbations.py b/src/struphy/initial/tests/test_init_perturbations.py index 4a012f08b..5d52e9291 100644 --- a/src/struphy/initial/tests/test_init_perturbations.py +++ b/src/struphy/initial/tests/test_init_perturbations.py @@ -4,7 +4,6 @@ import pytest -# @pytest.mark.mpi(min_size=2) # @pytest.mark.parametrize('combine_comps', [('f0', 'f1'), ('f0', 'f3'), ('f1', 'f2'), ('fvec', 'f3'), ('f1', 'fvec', 'f0')]) @pytest.mark.parametrize("Nel", [[16, 16, 16]]) @pytest.mark.parametrize("p", [[2, 3, 4]]) @@ -21,9 +20,9 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False): """Test the initialization Field.initialize_coeffs with all "Modes" classes in perturbations.py.""" - import numpy as np + import cunumpy as xp from matplotlib import pyplot as plt - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.geometry import domains @@ -51,10 +50,10 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False form_vector = ["1", "2", "v", "norm", "physical_at_eta"] # evaluation points - e1 = np.linspace(0.0, 1.0, 30) - e2 = np.linspace(0.0, 1.0, 40) - e3 = np.linspace(0.0, 1.0, 50) - eee1, eee2, eee3 = np.meshgrid(e1, e2, e3, indexing="ij") + e1 = xp.linspace(0.0, 1.0, 30) + e2 = xp.linspace(0.0, 1.0, 40) + e3 = xp.linspace(0.0, 1.0, 50) + eee1, eee2, eee3 = xp.meshgrid(e1, e2, e3, indexing="ij") # mode paramters kwargs = {} @@ -131,7 +130,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False field_vals_xyz = domain.push(field, e1, e2, e3, kind=form) x, y, z = domain(e1, e2, e3) - r = np.sqrt(x**2 + y**2) + r = xp.sqrt(x**2 + y**2) if fun_form == "physical": fun_vals_xyz = perturbation_xyz(x, y, z) @@ -140,7 +139,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False else: fun_vals_xyz = domain.push(perturbation, eee1, eee2, eee3, kind=fun_form) - error = np.max(np.abs(field_vals_xyz - fun_vals_xyz)) / np.max(np.abs(fun_vals_xyz)) + error = xp.max(xp.abs(field_vals_xyz - fun_vals_xyz)) / xp.max(xp.abs(fun_vals_xyz)) print(f"{rank=}, {key=}, {form=}, {fun_form=}, {error=}") assert error < 0.02 @@ -170,7 +169,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False plt.xlabel("x") plt.ylabel("y") plt.colorbar() - plt.title(f"exact function") + plt.title("exact function") ax = plt.gca() ax.set_aspect("equal", adjustable="box") @@ -199,7 +198,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False plt.xlabel("x") plt.ylabel("z") plt.colorbar() - plt.title(f"exact function") + plt.title("exact function") ax = plt.gca() ax.set_aspect("equal", adjustable="box") @@ -223,7 +222,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False params = { key: { "given_in_basis": [fun_form] * 3, - } + }, } if "Modes" in key: @@ -255,7 +254,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False f_xyz = [f1_xyz, f2_xyz, f3_xyz] x, y, z = domain(e1, e2, e3) - r = np.sqrt(x**2 + y**2) + r = xp.sqrt(x**2 + y**2) # exact values if fun_form == "physical": @@ -268,19 +267,27 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False fun3_xyz = perturbation(eee1, eee2, eee3) elif fun_form == "norm": tmp1, tmp2, tmp3 = domain.transform( - [perturbation, perturbation, perturbation], eee1, eee2, eee3, kind=fun_form + "_to_v" + [perturbation, perturbation, perturbation], + eee1, + eee2, + eee3, + kind=fun_form + "_to_v", ) fun1_xyz, fun2_xyz, fun3_xyz = domain.push([tmp1, tmp2, tmp3], eee1, eee2, eee3, kind="v") else: fun1_xyz, fun2_xyz, fun3_xyz = domain.push( - [perturbation, perturbation, perturbation], eee1, eee2, eee3, kind=fun_form + [perturbation, perturbation, perturbation], + eee1, + eee2, + eee3, + kind=fun_form, ) fun_xyz_vec = [fun1_xyz, fun2_xyz, fun3_xyz] error = 0.0 for fi, funi in zip(f_xyz, fun_xyz_vec): - error += np.max(np.abs(fi - funi)) / np.max(np.abs(funi)) + error += xp.max(xp.abs(fi - funi)) / xp.max(xp.abs(funi)) error /= 3.0 print(f"{rank=}, {key=}, {form=}, {fun_form=}, {error=}") assert error < 0.02 @@ -300,7 +307,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False plt.ylabel("y") plt.colorbar() plt.title( - f"component {c + 1}, init was {fun_form}, (m,n)=({kwargs['ms'][0]},{kwargs['ns'][0]})" + f"component {c + 1}, init was {fun_form}, (m,n)=({kwargs['ms'][0]},{kwargs['ns'][0]})", ) ax = plt.gca() ax.set_aspect("equal", adjustable="box") @@ -317,7 +324,7 @@ def test_init_modes(Nel, p, spl_kind, mapping, combine_comps=None, do_plot=False plt.ylabel("z") plt.colorbar() plt.title( - f"component {c + 1}, init was {fun_form}, (m,n)=({kwargs['ms'][0]},{kwargs['ns'][0]})" + f"component {c + 1}, init was {fun_form}, (m,n)=({kwargs['ms'][0]},{kwargs['ns'][0]})", ) ax = plt.gca() ax.set_aspect("equal", adjustable="box") diff --git a/src/struphy/initial/utilities.py b/src/struphy/initial/utilities.py index 3f9f2dfec..7af042249 100644 --- a/src/struphy/initial/utilities.py +++ b/src/struphy/initial/utilities.py @@ -1,7 +1,7 @@ import os +import cunumpy as xp import h5py -import numpy as np from struphy.fields_background.equils import set_defaults from struphy.io.output_handling import DataContainer @@ -98,6 +98,6 @@ def __init__( self._amp = amp def __call__(self, x, y, z): - val = self._amp * np.random.rand(*x.shape).squeeze() + val = self._amp * xp.random.rand(*x.shape).squeeze() return val diff --git a/src/struphy/io/inp/parameters.yml b/src/struphy/io/inp/parameters.yml index 73fbf48ae..020aeffdf 100644 --- a/src/struphy/io/inp/parameters.yml +++ b/src/struphy/io/inp/parameters.yml @@ -109,7 +109,7 @@ kinetic : plot_pts : [[32, 1, 1], [1, 16, 1]] # number of plot points in each direction background : # background is mandatory for kinetic species Maxwellian3D : - n : 0.05 + n : 1.0 u2 : 2.5 perturbation : n: diff --git a/src/struphy/io/inp/params_Maxwell.py b/src/struphy/io/inp/params_Maxwell.py index a60b57588..984b81620 100644 --- a/src/struphy/io/inp/params_Maxwell.py +++ b/src/struphy/io/inp/params_Maxwell.py @@ -21,11 +21,7 @@ time_opts = Time() # geometry -domain = domains.GVECunit() # Tokamak(tor_period=3) - -domain.show3D_interactive(save_dir="3d.html") - -exit() +domain = domains.Cuboid() # fluid equilibrium (can be used as part of initial conditions) equil = equils.HomogenSlab() @@ -40,7 +36,7 @@ model = Model() # propagator options -# model.propagators.maxwell.set_options() +model.propagators.maxwell.set_options() # initial conditions (background + perturbation) model.em_fields.b_field.add_background(FieldsBackground()) diff --git a/src/struphy/io/options.py b/src/struphy/io/options.py index d9fe53557..9be8b3249 100644 --- a/src/struphy/io/options.py +++ b/src/struphy/io/options.py @@ -2,7 +2,8 @@ from dataclasses import dataclass from typing import Literal, get_args -import numpy as np +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from struphy.physics.physics import ConstantsOfNature @@ -25,8 +26,11 @@ # solvers OptsSymmSolver = Literal["pcg", "cg"] -OptsGenSolver = Literal["pbicgstab", "bicgstab"] -OptsMassPrecond = Literal["MassMatrixPreconditioner", None] +OptsGenSolver = Literal["pbicgstab", "bicgstab", "GMRES"] +OptsMassPrecond = Literal["MassMatrixPreconditioner", "MassMatrixDiagonalPreconditioner", None] +OptsSaddlePointSolver = Literal["Uzawa", "GMRES"] +OptsDirectSolver = Literal["SparseSolver", "ScipySparse", "InexactNPInverse", "DirectNPInverse"] +OptsNonlinearSolver = Literal["Picard", "Newton"] # markers OptsPICSpace = Literal["Particles6D", "DeltaFParticles6D", "Particles5D", "Particles3D"] @@ -43,6 +47,9 @@ OptsSpatialLoading = Literal["uniform", "disc"] OptsMPIsort = Literal["each", "last", None] +# filters +OptsFilter = Literal["fourier_in_tor", "hybrid", "three_point", None] + # sph OptsKernel = Literal[ "trigonometric_1d", @@ -171,8 +178,6 @@ def j(self): def derive_units(self, velocity_scale: str = "light", A_bulk: int = None, Z_bulk: int = None, verbose=False): """Derive the remaining units from the base units, velocity scale and bulk species' A and Z.""" - from mpi4py import MPI - con = ConstantsOfNature() # velocity (m/s) @@ -184,7 +189,7 @@ def derive_units(self, velocity_scale: str = "light", A_bulk: int = None, Z_bulk elif velocity_scale == "alfvén": assert A_bulk is not None, 'Need bulk species to choose velocity scale "alfvén".' - self._v = self.B / np.sqrt(self.n * A_bulk * con.mH * con.mu0) + self._v = self.B / xp.sqrt(self.n * A_bulk * con.mH * con.mu0) elif velocity_scale == "cyclotron": assert Z_bulk is not None, 'Need bulk species to choose velocity scale "cyclotron".' @@ -194,7 +199,7 @@ def derive_units(self, velocity_scale: str = "light", A_bulk: int = None, Z_bulk elif velocity_scale == "thermal": assert A_bulk is not None, 'Need bulk species to choose velocity scale "thermal".' assert self.kBT is not None - self._v = np.sqrt(self.kBT * 1000 * con.e / (con.mH * A_bulk)) + self._v = xp.sqrt(self.kBT * 1000 * con.e / (con.mH * A_bulk)) # time (s) self._t = self.x / self.v diff --git a/src/struphy/io/output_handling.py b/src/struphy/io/output_handling.py index 35c8fbfc0..808c2bcf3 100644 --- a/src/struphy/io/output_handling.py +++ b/src/struphy/io/output_handling.py @@ -1,8 +1,8 @@ import ctypes import os +import cunumpy as xp import h5py -import numpy as np class DataContainer: @@ -24,7 +24,7 @@ def __init__(self, path_out, file_name=None, comm=None): # set name of hdf5 file if comm is None: self._rank = None - _affix = "" + _affix = "_proc0" else: self._rank = comm.Get_rank() _affix = "_proc" + str(self._rank) @@ -54,7 +54,7 @@ def __init__(self, path_out, file_name=None, comm=None): dataset_keys = [] self._file.visit( - lambda key: dataset_keys.append(key) if isinstance(self._file[key], h5py.Dataset) else None + lambda key: dataset_keys.append(key) if isinstance(self._file[key], h5py.Dataset) else None, ) for key in dataset_keys: @@ -82,11 +82,11 @@ def add_data(self, data_dict): Parameters ---------- data_dict : dict - Name-object pairs to save during time stepping, e.g. {key : val}. key must be a string and val must be a np.array of fixed shape. Scalar values (floats) must therefore be passed as 1d arrays of size 1. + Name-object pairs to save during time stepping, e.g. {key : val}. key must be a string and val must be a xp.array of fixed shape. Scalar values (floats) must therefore be passed as 1d arrays of size 1. """ for key, val in data_dict.items(): - assert isinstance(val, np.ndarray) + assert isinstance(val, xp.ndarray) # if dataset already exists, check for compatibility with given array if key in self._dset_dict: @@ -110,7 +110,11 @@ def add_data(self, data_dict): self._file[key][0] = val[0] else: self._file.create_dataset( - key, (1,) + val.shape, maxshape=(None,) + val.shape, dtype=val.dtype, chunks=True + key, + (1,) + val.shape, + maxshape=(None,) + val.shape, + dtype=val.dtype, + chunks=True, ) self._file[key][0] = val diff --git a/src/struphy/io/setup.py b/src/struphy/io/setup.py index ed23926df..4ecd96f47 100644 --- a/src/struphy/io/setup.py +++ b/src/struphy/io/setup.py @@ -5,12 +5,11 @@ import sys from types import ModuleType -import yaml -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from struphy.geometry.base import Domain -from struphy.geometry.domains import Cuboid -from struphy.io.options import DerhamOptions, Time, Units +from struphy.io.options import DerhamOptions from struphy.topology.grids import TensorProductGrid @@ -153,12 +152,12 @@ def setup_derham( if MPI.COMM_WORLD.Get_rank() == 0 and verbose: print("\nDERHAM:") - print(f"number of elements:".ljust(25), Nel) - print(f"spline degrees:".ljust(25), p) - print(f"periodic bcs:".ljust(25), spl_kind) - print(f"hom. Dirichlet bc:".ljust(25), dirichlet_bc) - print(f"GL quad pts (L2):".ljust(25), nquads) - print(f"GL quad pts (hist):".ljust(25), nq_pr) + print("number of elements:".ljust(25), Nel) + print("spline degrees:".ljust(25), p) + print("periodic bcs:".ljust(25), spl_kind) + print("hom. Dirichlet bc:".ljust(25), dirichlet_bc) + print("GL quad pts (L2):".ljust(25), nquads) + print("GL quad pts (hist):".ljust(25), nq_pr) print( "MPI proc. per dir.:".ljust(25), derham.domain_decomposition.nprocs, @@ -234,35 +233,35 @@ def descend_options_dict( out = copy.deepcopy(d) if verbose: - print(f"{d = }") - print(f"{out = }") - print(f"{d_default = }") - print(f"{d_opts = }") - print(f"{keys = }") - print(f"{depth = }") - print(f"{pop_again = }") + print(f"{d =}") + print(f"{out =}") + print(f"{d_default =}") + print(f"{d_opts =}") + print(f"{keys =}") + print(f"{depth =}") + print(f"{pop_again =}") if verbose: - print(f"{d = }") - print(f"{out = }") - print(f"{d_default = }") - print(f"{d_opts = }") - print(f"{keys = }") - print(f"{depth = }") - print(f"{pop_again = }") + print(f"{d =}") + print(f"{out =}") + print(f"{d_default =}") + print(f"{d_opts =}") + print(f"{keys =}") + print(f"{depth =}") + print(f"{pop_again =}") count = 0 for key, val in d.items(): count += 1 if verbose: - print(f"\n{keys = } | {key = }, {type(val) = }, {count = }\n") + print(f"\n{keys =} | {key =}, {type(val) =}, {count =}\n") if isinstance(val, list): # create default parameter dict "out" if verbose: - print(f"{val = }") + print(f"{val =}") if d_default is None: if len(keys) == 0: @@ -300,10 +299,10 @@ def descend_options_dict( out += [out_sublist] if verbose: - print(f"{out = }") + print(f"{out =}") if verbose: - print(f"{out = }") + print(f"{out =}") # recurse if necessary elif isinstance(val, dict): diff --git a/src/struphy/kinetic_background/base.py b/src/struphy/kinetic_background/base.py index ca84a8ec5..765ee1508 100644 --- a/src/struphy/kinetic_background/base.py +++ b/src/struphy/kinetic_background/base.py @@ -3,12 +3,10 @@ from abc import ABCMeta, abstractmethod from typing import Callable -import numpy as np +import cunumpy as xp from struphy.fields_background.base import FluidEquilibriumWithB -from struphy.fields_background.equils import set_defaults from struphy.initial.base import Perturbation -from struphy.initial.utilities import Noise class KineticBackground(metaclass=ABCMeta): @@ -106,7 +104,7 @@ def __call__(self, *args): Returns ------- - f0 : np.ndarray + f0 : xp.ndarray The evaluated background. """ pass @@ -121,12 +119,12 @@ def __rmul__(self, a): return ScalarMultiplyKineticBackground(self, a) def __div__(self, a): - assert isinstance(a, float) or isinstance(a, int) or isinstance(a, np.int64) + assert isinstance(a, float) or isinstance(a, int) or isinstance(a, xp.int64) assert a != 0, "Cannot divide by zero!" return ScalarMultiplyKineticBackground(self, 1 / a) def __rdiv__(self, a): - assert isinstance(a, float) or isinstance(a, int) or isinstance(a, np.int64) + assert isinstance(a, float) or isinstance(a, int) or isinstance(a, xp.int64) assert a != 0, "Cannot divide by zero!" return ScalarMultiplyKineticBackground(self, 1 / a) @@ -234,7 +232,7 @@ def __call__(self, *args): Returns ------- - f0 : np.ndarray + f0 : xp.ndarray The evaluated background. """ return self._f1(*args) + self._f2(*args) @@ -243,7 +241,7 @@ def __call__(self, *args): class ScalarMultiplyKineticBackground(KineticBackground): def __init__(self, f0, a): assert isinstance(f0, KineticBackground) - assert isinstance(a, float) or isinstance(a, int) or isinstance(a, np.int64) + assert isinstance(a, float) or isinstance(a, int) or isinstance(a, xp.int64) self._f = f0 self._a = a @@ -320,7 +318,7 @@ def __call__(self, *args): Returns ------- - f0 : np.ndarray + f0 : xp.ndarray The evaluated background. """ return self._a * self._f(*args) @@ -396,14 +394,14 @@ def gaussian(self, v, u=0.0, vth=1.0, polar=False, volume_form=False): An array of size(v). """ - if isinstance(v, np.ndarray) and isinstance(u, np.ndarray): - assert v.shape == u.shape, f"{v.shape = } but {u.shape = }" + if isinstance(v, xp.ndarray) and isinstance(u, xp.ndarray): + assert v.shape == u.shape, f"{v.shape =} but {u.shape =}" if not polar: - out = 1.0 / vth * 1.0 / np.sqrt(2.0 * np.pi) * np.exp(-((v - u) ** 2) / (2.0 * vth**2)) + out = 1.0 / vth * 1.0 / xp.sqrt(2.0 * xp.pi) * xp.exp(-((v - u) ** 2) / (2.0 * vth**2)) else: - assert np.all(v >= 0.0) - out = 1.0 / vth**2 * np.exp(-((v - u) ** 2) / (2.0 * vth**2)) + assert xp.all(v >= 0.0) + out = 1.0 / vth**2 * xp.exp(-((v - u) ** 2) / (2.0 * vth**2)) if volume_form: out *= v @@ -429,16 +427,16 @@ def __call__(self, *args): Returns ------- - f : np.ndarray + f : xp.ndarray The evaluated Maxwellian. """ # Check that all args have the same shape - shape0 = np.shape(args[0]) + shape0 = xp.shape(args[0]) for i, arg in enumerate(args): - assert np.shape(arg) == shape0, f"Argument {i} has {np.shape(arg) = }, but must be {shape0 = }." - assert np.ndim(arg) == 1 or np.ndim(arg) == 3 + self.vdim, ( - f"{np.ndim(arg) = } not allowed for Maxwellian evaluation." + assert xp.shape(arg) == shape0, f"Argument {i} has {xp.shape(arg) =}, but must be {shape0 =}." + assert xp.ndim(arg) == 1 or xp.ndim(arg) == 3 + self.vdim, ( + f"{xp.ndim(arg) =} not allowed for Maxwellian evaluation." ) # flat or meshgrid evaluation # Get result evaluated at eta's @@ -447,33 +445,33 @@ def __call__(self, *args): vths = self.vth(*args[: -self.vdim]) # take care of correct broadcasting, assuming args come from phase space meshgrid - if np.ndim(args[0]) > 3: + if xp.ndim(args[0]) > 3: # move eta axes to the back - arg_t = np.moveaxis(args[0], 0, -1) - arg_t = np.moveaxis(arg_t, 0, -1) - arg_t = np.moveaxis(arg_t, 0, -1) + arg_t = xp.moveaxis(args[0], 0, -1) + arg_t = xp.moveaxis(arg_t, 0, -1) + arg_t = xp.moveaxis(arg_t, 0, -1) # broadcast res_broad = res + 0.0 * arg_t # move eta axes to the front - res = np.moveaxis(res_broad, -1, 0) - res = np.moveaxis(res, -1, 0) - res = np.moveaxis(res, -1, 0) + res = xp.moveaxis(res_broad, -1, 0) + res = xp.moveaxis(res, -1, 0) + res = xp.moveaxis(res, -1, 0) # Multiply result with gaussian in v's for i, v in enumerate(args[-self.vdim :]): # correct broadcasting - if np.ndim(args[0]) > 3: + if xp.ndim(args[0]) > 3: u_broad = us[i] + 0.0 * arg_t - u = np.moveaxis(u_broad, -1, 0) - u = np.moveaxis(u, -1, 0) - u = np.moveaxis(u, -1, 0) + u = xp.moveaxis(u_broad, -1, 0) + u = xp.moveaxis(u, -1, 0) + u = xp.moveaxis(u, -1, 0) vth_broad = vths[i] + 0.0 * arg_t - vth = np.moveaxis(vth_broad, -1, 0) - vth = np.moveaxis(vth, -1, 0) - vth = np.moveaxis(vth, -1, 0) + vth = xp.moveaxis(vth_broad, -1, 0) + vth = xp.moveaxis(vth, -1, 0) + vth = xp.moveaxis(vth, -1, 0) else: u = us[i] vth = vths[i] @@ -502,9 +500,9 @@ def _evaluate_moment(self, eta1, eta2, eta3, *, name: str = "n", add_perturbatio """ # collect arguments - assert isinstance(eta1, np.ndarray) - assert isinstance(eta2, np.ndarray) - assert isinstance(eta3, np.ndarray) + assert isinstance(eta1, xp.ndarray) + assert isinstance(eta2, xp.ndarray) + assert isinstance(eta3, xp.ndarray) assert eta1.shape == eta2.shape == eta3.shape params = self.maxw_params[name] @@ -514,7 +512,7 @@ def _evaluate_moment(self, eta1, eta2, eta3, *, name: str = "n", add_perturbatio # flat evaluation for markers if eta1.ndim == 1: etas = [ - np.concatenate( + xp.concatenate( (eta1[:, None], eta2[:, None], eta3[:, None]), axis=1, ), diff --git a/src/struphy/kinetic_background/maxwellians.py b/src/struphy/kinetic_background/maxwellians.py index 5e240a1b7..d9e34dcab 100644 --- a/src/struphy/kinetic_background/maxwellians.py +++ b/src/struphy/kinetic_background/maxwellians.py @@ -2,7 +2,7 @@ from typing import Callable -import numpy as np +import cunumpy as xp from struphy.fields_background.base import FluidEquilibriumWithB from struphy.fields_background.equils import set_defaults @@ -251,7 +251,7 @@ def velocity_jacobian_det(self, eta1, eta2, eta3, *v): assert len(v) == 2 # call equilibrium - etas = (np.vstack((eta1, eta2, eta3)).T).copy() + etas = (xp.vstack((eta1, eta2, eta3)).T).copy() absB0 = self.equil.absB0(etas) # J = v_perp/B @@ -405,14 +405,14 @@ def velocity_jacobian_det(self, eta1, eta2, eta3, energy): assert eta3.ndim == 1 if self.maxw_params["type"] == "Particles6D": - return np.sqrt(2.0 * energy) * 4.0 * np.pi + return xp.sqrt(2.0 * energy) * 4.0 * xp.pi else: # call equilibrium - etas = (np.vstack((eta1, eta2, eta3)).T).copy() + etas = (xp.vstack((eta1, eta2, eta3)).T).copy() absB0 = self.equil.absB0(etas) - return np.sqrt(energy) * 2.0 * np.sqrt(2.0) / absB0 + return xp.sqrt(energy) * 2.0 * xp.sqrt(2.0) / absB0 def gaussian(self, e, vth=1.0): """3-dim. normal distribution, to which array-valued thermal velocities can be passed. @@ -430,10 +430,10 @@ def gaussian(self, e, vth=1.0): An array of size(e). """ - if isinstance(vth, np.ndarray): - assert e.shape == vth.shape, f"{e.shape = } but {vth.shape = }" + if isinstance(vth, xp.ndarray): + assert e.shape == vth.shape, f"{e.shape =} but {vth.shape =}" - return 2.0 * np.sqrt(e / np.pi) / vth**3 * np.exp(-e / vth**2) + return 2.0 * xp.sqrt(e / xp.pi) / vth**3 * xp.exp(-e / vth**2) def __call__(self, *args): """Evaluates the canonical Maxwellian distribution function. @@ -455,16 +455,16 @@ def __call__(self, *args): Returns ------- - f : np.ndarray + f : xp.ndarray The evaluated Maxwellian. """ # Check that all args have the same shape - shape0 = np.shape(args[0]) + shape0 = xp.shape(args[0]) for i, arg in enumerate(args): - assert np.shape(arg) == shape0, f"Argument {i} has {np.shape(arg) = }, but must be {shape0 = }." - assert np.ndim(arg) == 1 or np.ndim(arg) == 3, ( - f"{np.ndim(arg) = } not allowed for canonical Maxwellian evaluation." + assert xp.shape(arg) == shape0, f"Argument {i} has {xp.shape(arg) =}, but must be {shape0 =}." + assert xp.ndim(arg) == 1 or xp.ndim(arg) == 3, ( + f"{xp.ndim(arg) =} not allowed for canonical Maxwellian evaluation." ) # flat or meshgrid evaluation # Get result evaluated with each particles' psic @@ -472,26 +472,26 @@ def __call__(self, *args): vths = self.vth(args[2]) # take care of correct broadcasting, assuming args come from phase space meshgrid - if np.ndim(args[0]) == 3: + if xp.ndim(args[0]) == 3: # move eta axes to the back - arg_t = np.moveaxis(args[0], 0, -1) - arg_t = np.moveaxis(arg_t, 0, -1) - arg_t = np.moveaxis(arg_t, 0, -1) + arg_t = xp.moveaxis(args[0], 0, -1) + arg_t = xp.moveaxis(arg_t, 0, -1) + arg_t = xp.moveaxis(arg_t, 0, -1) # broadcast res_broad = res + 0.0 * arg_t # move eta axes to the front - res = np.moveaxis(res_broad, -1, 0) - res = np.moveaxis(res, -1, 0) - res = np.moveaxis(res, -1, 0) + res = xp.moveaxis(res_broad, -1, 0) + res = xp.moveaxis(res, -1, 0) + res = xp.moveaxis(res, -1, 0) # Multiply result with gaussian in energy - if np.ndim(args[0]) == 3: + if xp.ndim(args[0]) == 3: vth_broad = vths + 0.0 * arg_t - vth = np.moveaxis(vth_broad, -1, 0) - vth = np.moveaxis(vth, -1, 0) - vth = np.moveaxis(vth, -1, 0) + vth = xp.moveaxis(vth_broad, -1, 0) + vth = xp.moveaxis(vth, -1, 0) + vth = xp.moveaxis(vth, -1, 0) else: vth = vths @@ -544,13 +544,13 @@ def rc(self, psic): rc_squared = (psic - self.equil.psi_range[0]) / (self.equil.psi_range[1] - self.equil.psi_range[0]) # sorting out indices of negative rc² - neg_index = np.logical_not(rc_squared >= 0) + neg_index = xp.logical_not(rc_squared >= 0) # make them positive rc_squared[neg_index] *= -1 # calculate rc - rc = np.sqrt(rc_squared) + rc = xp.sqrt(rc_squared) rc[neg_index] *= -1 return rc @@ -568,7 +568,7 @@ def n(self, psic, add_perturbation: bool = None): A float (background value) or a numpy.array of the evaluated density. """ # collect arguments - assert isinstance(psic, np.ndarray) + assert isinstance(psic, xp.ndarray) # assuming that input comes from meshgrid. if psic.ndim == 3: @@ -612,7 +612,7 @@ def vth(self, psic): """ # collect arguments - assert isinstance(psic, np.ndarray) + assert isinstance(psic, xp.ndarray) # assuming that input comes from meshgrid. if psic.ndim == 3: @@ -655,20 +655,28 @@ def default_maxw_params(cls): def __init__( self, - maxw_params: dict = None, - pert_params: dict = None, + n: tuple[float | Callable, Perturbation] = (1.0, None), + u1: tuple[float | Callable, Perturbation] = (0.0, None), + u2: tuple[float | Callable, Perturbation] = (0.0, None), + u3: tuple[float | Callable, Perturbation] = (0.0, None), equil: FluidEquilibriumWithB = None, ): - super().__init__( - maxw_params=maxw_params, - pert_params=pert_params, - equil=equil, - ) + self._maxw_params = {} + self._maxw_params["n"] = n + self._maxw_params["u1"] = u1 + self._maxw_params["u2"] = u2 + self._maxw_params["u3"] = u3 + self._maxw_params["vth1"] = (0.0, None) + self._maxw_params["vth2"] = (0.0, None) + self._maxw_params["vth3"] = (0.0, None) + + self.check_maxw_params() + + self._equil = equil - # make sure temperatures are zero - self._maxw_params["vth1"] = 0.0 - self._maxw_params["vth2"] = 0.0 - self._maxw_params["vth3"] = 0.0 + @property + def maxw_params(self): + return self._maxw_params @property def coords(self): @@ -691,6 +699,10 @@ def volume_form(self): return False @property + def equil(self) -> FluidEquilibriumWithB: + """Fluid background with B-field.""" + return self._equil + def velocity_jacobian_det(self, eta1, eta2, eta3, *v): """Jacobian determinant of the velocity coordinate transformation.""" return 1.0 @@ -718,14 +730,3 @@ def vth(self, eta1, eta2, eta3): def __call__(self, eta1, eta2, eta3): return self.n(eta1, eta2, eta3) - - @property - def add_perturbation(self) -> bool: - if not hasattr(self, "_add_perturbation"): - self._add_perturbation = True - return self._add_perturbation - - @add_perturbation.setter - def add_perturbation(self, new): - assert isinstance(new, bool) - self._add_perturbation = new diff --git a/src/struphy/kinetic_background/tests/test_base.py b/src/struphy/kinetic_background/tests/test_base.py index 09172c866..8a2e89d28 100644 --- a/src/struphy/kinetic_background/tests/test_base.py +++ b/src/struphy/kinetic_background/tests/test_base.py @@ -1,16 +1,16 @@ def test_kinetic_background_magics(show_plot=False): """Test the magic commands __sum__, __mul__ and __sub__ of the Maxwellian base class.""" + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from struphy.kinetic_background.maxwellians import Maxwellian3D Nel = [32, 1, 1] - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) - v1 = np.linspace(-7.0, 7.0, 128) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) + v1 = xp.linspace(-7.0, 7.0, 128) m1_params = {"n": 0.5, "u1": 3.0} m2_params = {"n": 0.5, "u1": -3.0} @@ -22,11 +22,11 @@ def test_kinetic_background_magics(show_plot=False): m_rmul_int = 2 * m1 m_mul_int = m1 * 2 m_mul_float = 2.0 * m1 - m_mul_npint = np.ones(1, dtype=int)[0] * m1 + m_mul_npint = xp.ones(1, dtype=int)[0] * m1 m_sub = m1 - m2 # compare distribution function - meshgrids = np.meshgrid(e1, e2, e3, v1, [0.0], [0.0]) + meshgrids = xp.meshgrid(e1, e2, e3, v1, [0.0], [0.0]) m1_vals = m1(*meshgrids) m2_vals = m2(*meshgrids) @@ -38,15 +38,15 @@ def test_kinetic_background_magics(show_plot=False): m_mul_npint_vals = m_mul_npint(*meshgrids) m_sub_vals = m_sub(*meshgrids) - assert np.allclose(m1_vals + m2_vals, m_add_vals) - assert np.allclose(2 * m1_vals, m_rmul_int_vals) - assert np.allclose(2 * m1_vals, m_mul_int_vals) - assert np.allclose(2.0 * m1_vals, m_mul_float_vals) - assert np.allclose(np.ones(1, dtype=int)[0] * m1_vals, m_mul_npint_vals) - assert np.allclose(m1_vals - m2_vals, m_sub_vals) + assert xp.allclose(m1_vals + m2_vals, m_add_vals) + assert xp.allclose(2 * m1_vals, m_rmul_int_vals) + assert xp.allclose(2 * m1_vals, m_mul_int_vals) + assert xp.allclose(2.0 * m1_vals, m_mul_float_vals) + assert xp.allclose(xp.ones(1, dtype=int)[0] * m1_vals, m_mul_npint_vals) + assert xp.allclose(m1_vals - m2_vals, m_sub_vals) # compare first two moments - meshgrids = np.meshgrid(e1, e2, e3) + meshgrids = xp.meshgrid(e1, e2, e3) n1_vals = m1.n(*meshgrids) n2_vals = m2.n(*meshgrids) @@ -57,11 +57,11 @@ def test_kinetic_background_magics(show_plot=False): u_add1, u_add2, u_add3 = m_add.u(*meshgrids) n_sub_vals = m_sub.n(*meshgrids) - assert np.allclose(n1_vals + n2_vals, n_add_vals) - assert np.allclose(u11 + u21, u_add1) - assert np.allclose(u12 + u22, u_add2) - assert np.allclose(u13 + u23, u_add3) - assert np.allclose(n1_vals - n2_vals, n_sub_vals) + assert xp.allclose(n1_vals + n2_vals, n_add_vals) + assert xp.allclose(u11 + u21, u_add1) + assert xp.allclose(u12 + u22, u_add2) + assert xp.allclose(u13 + u23, u_add3) + assert xp.allclose(n1_vals - n2_vals, n_sub_vals) if show_plot: plt.figure(figsize=(12, 8)) diff --git a/src/struphy/kinetic_background/tests/test_maxwellians.py b/src/struphy/kinetic_background/tests/test_maxwellians.py index deeb43272..9de7fd28f 100644 --- a/src/struphy/kinetic_background/tests/test_maxwellians.py +++ b/src/struphy/kinetic_background/tests/test_maxwellians.py @@ -8,31 +8,31 @@ def test_maxwellian_3d_uniform(Nel, show_plot=False): Asserts that the results over the domain and velocity space correspond to the analytical computation. """ + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from struphy.kinetic_background.maxwellians import Maxwellian3D - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) # ========================================================== # ==== Test uniform non-shifted, isothermal Maxwellian ===== # ========================================================== maxwellian = Maxwellian3D(n=(2.0, None)) - meshgrids = np.meshgrid(e1, e2, e3, [0.0], [0.0], [0.0]) + meshgrids = xp.meshgrid(e1, e2, e3, [0.0], [0.0], [0.0]) # Test constant value at v=0 res = maxwellian(*meshgrids).squeeze() - assert np.allclose(res, 2.0 / (2 * np.pi) ** (3 / 2) + 0 * e1, atol=10e-10), ( - f"{res=},\n {2.0 / (2 * np.pi) ** (3 / 2)}" + assert xp.allclose(res, 2.0 / (2 * xp.pi) ** (3 / 2) + 0 * e1, atol=10e-10), ( + f"{res=},\n {2.0 / (2 * xp.pi) ** (3 / 2)}" ) # test Maxwellian profile in v - v1 = np.linspace(-5, 5, 128) - meshgrids = np.meshgrid( + v1 = xp.linspace(-5, 5, 128) + meshgrids = xp.meshgrid( [0.0], [0.0], [0.0], @@ -41,8 +41,8 @@ def test_maxwellian_3d_uniform(Nel, show_plot=False): [0.0], ) res = maxwellian(*meshgrids).squeeze() - res_ana = 2.0 * np.exp(-(v1**2) / 2.0) / (2 * np.pi) ** (3 / 2) - assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + res_ana = 2.0 * xp.exp(-(v1**2) / 2.0) / (2 * xp.pi) ** (3 / 2) + assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # ======================================================= # ===== Test non-zero shifts and thermal velocities ===== @@ -68,14 +68,14 @@ def test_maxwellian_3d_uniform(Nel, show_plot=False): # test Maxwellian profile in v for i in range(3): vs = [0, 0, 0] - vs[i] = np.linspace(-5, 5, 128) - meshgrids = np.meshgrid([0.0], [0.0], [0.0], *vs) + vs[i] = xp.linspace(-5, 5, 128) + meshgrids = xp.meshgrid([0.0], [0.0], [0.0], *vs) res = maxwellian(*meshgrids).squeeze() - res_ana = np.exp(-((vs[0] - u1) ** 2) / (2 * vth1**2)) - res_ana *= np.exp(-((vs[1] - u2) ** 2) / (2 * vth2**2)) - res_ana *= np.exp(-((vs[2] - u3) ** 2) / (2 * vth3**2)) - res_ana *= n / ((2 * np.pi) ** (3 / 2) * vth1 * vth2 * vth3) + res_ana = xp.exp(-((vs[0] - u1) ** 2) / (2 * vth1**2)) + res_ana *= xp.exp(-((vs[1] - u2) ** 2) / (2 * vth2**2)) + res_ana *= xp.exp(-((vs[2] - u3) ** 2) / (2 * vth3**2)) + res_ana *= n / ((2 * xp.pi) ** (3 / 2) * vth1 * vth2 * vth3) if show_plot: plt.plot(vs[i], res_ana, label="analytical") @@ -86,21 +86,21 @@ def test_maxwellian_3d_uniform(Nel, show_plot=False): plt.xlabel("v_" + str(i + 1)) plt.show() - assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana =}" + assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana =}" @pytest.mark.parametrize("Nel", [[64, 1, 1]]) def test_maxwellian_3d_perturbed(Nel, show_plot=False): """Tests the Maxwellian3D class for perturbations.""" + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from struphy.initial import perturbations from struphy.kinetic_background.maxwellians import Maxwellian3D - e1 = np.linspace(0.0, 1.0, Nel[0]) - v1 = np.linspace(-5.0, 5.0, 128) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + v1 = xp.linspace(-5.0, 5.0, 128) # =============================================== # ===== Test cosine perturbation in density ===== @@ -112,10 +112,10 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): maxwellian = Maxwellian3D(n=(2.0, pert)) - meshgrids = np.meshgrid(e1, [0.0], [0.0], [0.0], [0.0], [0.0]) + meshgrids = xp.meshgrid(e1, [0.0], [0.0], [0.0], [0.0], [0.0]) res = maxwellian(*meshgrids).squeeze() - ana_res = (2.0 + amp * np.cos(2 * np.pi * mode * e1)) / (2 * np.pi) ** (3 / 2) + ana_res = (2.0 + amp * xp.cos(2 * xp.pi * mode * e1)) / (2 * xp.pi) ** (3 / 2) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -126,7 +126,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ============================================= # ===== Test cosine perturbation in shift ===== @@ -140,7 +140,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): maxwellian = Maxwellian3D(n=(n, None), u1=(u1, pert)) - meshgrids = np.meshgrid( + meshgrids = xp.meshgrid( e1, [0.0], [0.0], @@ -150,9 +150,9 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - shift = u1 + amp * np.cos(2 * np.pi * mode * e1) - ana_res = np.exp(-((v1 - shift[:, None]) ** 2) / 2) - ana_res *= n / (2 * np.pi) ** (3 / 2) + shift = u1 + amp * xp.cos(2 * xp.pi * mode * e1) + ana_res = xp.exp(-((v1 - shift[:, None]) ** 2) / 2) + ana_res *= n / (2 * xp.pi) ** (3 / 2) if show_plot: plt.figure(1) @@ -173,7 +173,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # =========================================== # ===== Test cosine perturbation in vth ===== @@ -187,7 +187,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): maxwellian = Maxwellian3D(n=(n, None), vth1=(vth1, pert)) - meshgrids = np.meshgrid( + meshgrids = xp.meshgrid( e1, [0.0], [0.0], @@ -197,9 +197,9 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - thermal = vth1 + amp * np.cos(2 * np.pi * mode * e1) - ana_res = np.exp(-(v1**2) / (2.0 * thermal[:, None] ** 2)) - ana_res *= n / ((2 * np.pi) ** (3 / 2) * thermal[:, None]) + thermal = vth1 + amp * xp.cos(2 * xp.pi * mode * e1) + ana_res = xp.exp(-(v1**2) / (2.0 * thermal[:, None] ** 2)) + ana_res *= n / ((2 * xp.pi) ** (3 / 2) * thermal[:, None]) if show_plot: plt.figure(1) @@ -220,7 +220,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ============================================= # ===== Test ITPA perturbation in density ===== @@ -232,10 +232,10 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): maxwellian = Maxwellian3D(n=(0.0, pert)) - meshgrids = np.meshgrid(e1, [0.0], [0.0], [0.0], [0.0], [0.0]) + meshgrids = xp.meshgrid(e1, [0.0], [0.0], [0.0], [0.0], [0.0]) res = maxwellian(*meshgrids).squeeze() - ana_res = n0 * c[3] * np.exp(-c[2] / c[1] * np.tanh((e1 - c[0]) / c[2])) / (2 * np.pi) ** (3 / 2) + ana_res = n0 * c[3] * xp.exp(-c[2] / c[1] * xp.tanh((e1 - c[0]) / c[2])) / (2 * xp.pi) ** (3 / 2) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -246,7 +246,7 @@ def test_maxwellian_3d_perturbed(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" @pytest.mark.parametrize("Nel", [[8, 11, 12]]) @@ -255,8 +255,8 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): import inspect + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from struphy.fields_background import equils from struphy.fields_background.base import FluidEquilibrium @@ -265,32 +265,32 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): from struphy.initial.base import Perturbation from struphy.kinetic_background.maxwellians import Maxwellian3D - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) v1 = [0.0] v2 = [0.0, -1.0] v3 = [0.0, -1.0, -1.3] - meshgrids = np.meshgrid(e1, e2, e3, v1, v2, v3, indexing="ij") - e_meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") + meshgrids = xp.meshgrid(e1, e2, e3, v1, v2, v3, indexing="ij") + e_meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") n_mks = 17 - e1_fl = np.random.rand(n_mks) - e2_fl = np.random.rand(n_mks) - e3_fl = np.random.rand(n_mks) - v1_fl = np.random.randn(n_mks) - v2_fl = np.random.randn(n_mks) - v3_fl = np.random.randn(n_mks) + e1_fl = xp.random.rand(n_mks) + e2_fl = xp.random.rand(n_mks) + e3_fl = xp.random.rand(n_mks) + v1_fl = xp.random.randn(n_mks) + v2_fl = xp.random.randn(n_mks) + v3_fl = xp.random.randn(n_mks) args_fl = [e1_fl, e2_fl, e3_fl, v1_fl, v2_fl, v3_fl] - e_args_fl = np.concatenate((e1_fl[:, None], e2_fl[:, None], e3_fl[:, None]), axis=1) + e_args_fl = xp.concatenate((e1_fl[:, None], e2_fl[:, None], e3_fl[:, None]), axis=1) for key, val in inspect.getmembers(equils): if inspect.isclass(val) and val.__module__ == equils.__name__: - print(f"{key = }") + print(f"{key =}") if "DESCequilibrium" in key and not with_desc: - print(f"Attention: {with_desc = }, DESC not tested here !!") + print(f"Attention: {with_desc =}, DESC not tested here !!") continue if "GVECequilibrium" in key: @@ -300,32 +300,42 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): mhd_equil = val() assert isinstance(mhd_equil, FluidEquilibrium) - print(f"{mhd_equil.params = }") + print(f"{mhd_equil.params =}") if "AdhocTorus" in key: mhd_equil.domain = domains.HollowTorus( - a1=1e-3, a2=mhd_equil.params["a"], R0=mhd_equil.params["R0"], tor_period=1 + a1=1e-3, + a2=mhd_equil.params["a"], + R0=mhd_equil.params["R0"], + tor_period=1, ) elif "EQDSKequilibrium" in key: mhd_equil.domain = domains.Tokamak(equilibrium=mhd_equil) elif "CircularTokamak" in key: mhd_equil.domain = domains.HollowTorus( - a1=1e-3, a2=mhd_equil.params["a"], R0=mhd_equil.params["R0"], tor_period=1 + a1=1e-3, + a2=mhd_equil.params["a"], + R0=mhd_equil.params["R0"], + tor_period=1, ) elif "HomogenSlab" in key: mhd_equil.domain = domains.Cuboid() elif "ShearedSlab" in key: mhd_equil.domain = domains.Cuboid( r1=mhd_equil.params["a"], - r2=mhd_equil.params["a"] * 2 * np.pi, - r3=mhd_equil.params["R0"] * 2 * np.pi, + r2=mhd_equil.params["a"] * 2 * xp.pi, + r3=mhd_equil.params["R0"] * 2 * xp.pi, ) elif "ShearFluid" in key: mhd_equil.domain = domains.Cuboid( - r1=mhd_equil.params["a"], r2=mhd_equil.params["b"], r3=mhd_equil.params["c"] + r1=mhd_equil.params["a"], + r2=mhd_equil.params["b"], + r3=mhd_equil.params["c"], ) elif "ScrewPinch" in key: mhd_equil.domain = domains.HollowCylinder( - a1=1e-3, a2=mhd_equil.params["a"], Lz=mhd_equil.params["R0"] * 2 * np.pi + a1=1e-3, + a2=mhd_equil.params["a"], + Lz=mhd_equil.params["R0"] * 2 * xp.pi, ) else: try: @@ -355,12 +365,14 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): # test meshgrid evaluation n0 = mhd_equil.n0(*e_meshgrids) - assert np.allclose( - maxwellian(*meshgrids)[:, :, :, 0, 0, 0], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 0, 0] + assert xp.allclose( + maxwellian(*meshgrids)[:, :, :, 0, 0, 0], + n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 0, 0], ) - assert np.allclose( - maxwellian(*meshgrids)[:, :, :, 0, 1, 2], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 1, 2] + assert xp.allclose( + maxwellian(*meshgrids)[:, :, :, 0, 1, 2], + n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 1, 2], ) # test flat evaluation @@ -370,39 +382,45 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): continue pass else: - assert np.allclose(maxwellian(*args_fl), mhd_equil.n0(e_args_fl) * maxwellian_1(*args_fl)) - assert np.allclose(maxwellian.n(e1_fl, e2_fl, e3_fl), mhd_equil.n0(e_args_fl)) + assert xp.allclose(maxwellian(*args_fl), mhd_equil.n0(e_args_fl) * maxwellian_1(*args_fl)) + assert xp.allclose(maxwellian.n(e1_fl, e2_fl, e3_fl), mhd_equil.n0(e_args_fl)) u_maxw = maxwellian.u(e1_fl, e2_fl, e3_fl) u_eq = mhd_equil.u_cart(e_args_fl)[0] - assert all([np.allclose(m, e) for m, e in zip(u_maxw, u_eq)]) + assert all([xp.allclose(m, e) for m, e in zip(u_maxw, u_eq)]) vth_maxw = maxwellian.vth(e1_fl, e2_fl, e3_fl) - vth_eq = np.sqrt(mhd_equil.p0(e_args_fl) / mhd_equil.n0(e_args_fl)) - assert all([np.allclose(v, vth_eq) for v in vth_maxw]) + vth_eq = xp.sqrt(mhd_equil.p0(e_args_fl) / mhd_equil.n0(e_args_fl)) + assert all([xp.allclose(v, vth_eq) for v in vth_maxw]) # plotting moments if show_plot: - plt.figure(f"{mhd_equil = }", figsize=(24, 16)) + plt.figure(f"{mhd_equil =}", figsize=(24, 16)) x, y, z = mhd_equil.domain(*e_meshgrids) # density plots n_cart = mhd_equil.domain.push(maxwellian.n, *e_meshgrids) - levels = np.linspace(np.min(n_cart) - 1e-10, np.max(n_cart), 20) + levels = xp.linspace(xp.min(n_cart) - 1e-10, xp.max(n_cart), 20) plt.subplot(2, 5, 1) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], z[:, Nel[1] // 2 - 1, :], n_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + z[:, Nel[1] // 2 - 1, :], + n_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], y[:, Nel[1] // 2 - 1, :], n_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + y[:, Nel[1] // 2 - 1, :], + n_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("y") @@ -425,7 +443,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): # velocity plots us = maxwellian.u(*e_meshgrids) for i, u in enumerate(us): - levels = np.linspace(np.min(u) - 1e-10, np.max(u), 20) + levels = xp.linspace(xp.min(u) - 1e-10, xp.max(u), 20) plt.subplot(2, 5, 2 + i) if "Slab" in key or "Pinch" in key: @@ -458,26 +476,32 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): vth = maxwellian.vth(*e_meshgrids)[0] vth_cart = mhd_equil.domain.push(vth, *e_meshgrids) - levels = np.linspace(np.min(vth_cart) - 1e-10, np.max(vth_cart), 20) + levels = xp.linspace(xp.min(vth_cart) - 1e-10, xp.max(vth_cart), 20) plt.subplot(2, 5, 5) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], vth_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], z[:, Nel[1] // 2 - 1, :], vth_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + z[:, Nel[1] // 2 - 1, :], + vth_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], vth_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], y[:, Nel[1] // 2 - 1, :], vth_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + y[:, Nel[1] // 2 - 1, :], + vth_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("y") plt.axis("equal") plt.colorbar() - plt.title(f"Maxwellian thermal velocity $v_t$, top view (e1-e3)") + plt.title("Maxwellian thermal velocity $v_t$, top view (e1-e3)") plt.subplot(2, 5, 10) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, :, 0], y[:, :, 0], vth_cart[:, :, 0], levels=levels) @@ -489,7 +513,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("z") plt.axis("equal") plt.colorbar() - plt.title(f"Maxwellian thermal velocity $v_t$, poloidal view (e1-e2)") + plt.title("Maxwellian thermal velocity $v_t$, poloidal view (e1-e2)") plt.show() @@ -501,7 +525,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): if inspect.isclass(val_2) and val_2.__module__ == perturbations.__name__: pert = val_2() assert isinstance(pert, Perturbation) - print(f"{pert = }") + print(f"{pert =}") if isinstance(pert, perturbations.Noise): continue @@ -533,13 +557,13 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): vth3=(0.0, pert), ) - assert np.allclose(maxwellian_zero_bckgr.n(*e_meshgrids), pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[0], pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[1], pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[2], pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[0], pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[1], pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[2], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.n(*e_meshgrids), pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[0], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[1], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[2], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[0], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[1], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[2], pert(*e_meshgrids)) # plotting perturbations if show_plot: # and 'Torus' in key_2: @@ -549,20 +573,26 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): # density plots n_cart = mhd_equil.domain.push(maxwellian_zero_bckgr.n, *e_meshgrids) - levels = np.linspace(np.min(n_cart) - 1e-10, np.max(n_cart), 20) + levels = xp.linspace(xp.min(n_cart) - 1e-10, xp.max(n_cart), 20) plt.subplot(2, 5, 1) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], z[:, Nel[1] // 2, :], n_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + z[:, Nel[1] // 2, :], + n_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], y[:, Nel[1] // 2, :], n_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + y[:, Nel[1] // 2, :], + n_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("y") @@ -585,20 +615,26 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): # velocity plots us = maxwellian_zero_bckgr.u(*e_meshgrids) for i, u in enumerate(us): - levels = np.linspace(np.min(u) - 1e-10, np.max(u), 20) + levels = xp.linspace(xp.min(u) - 1e-10, xp.max(u), 20) plt.subplot(2, 5, 2 + i) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], u[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], z[:, Nel[1] // 2, :], u[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + z[:, Nel[1] // 2, :], + u[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], u[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], y[:, Nel[1] // 2, :], u[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + y[:, Nel[1] // 2, :], + u[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("y") @@ -622,7 +658,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): vth = maxwellian_zero_bckgr.vth(*e_meshgrids)[0] vth_cart = mhd_equil.domain.push(vth, *e_meshgrids) - levels = np.linspace(np.min(vth_cart) - 1e-10, np.max(vth_cart), 20) + levels = xp.linspace(xp.min(vth_cart) - 1e-10, xp.max(vth_cart), 20) plt.subplot(2, 5, 5) if "Slab" in key or "Pinch" in key: @@ -647,7 +683,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("y") plt.axis("equal") plt.colorbar() - plt.title(f"Maxwellian perturbed thermal velocity $v_t$, top view (e1-e3)") + plt.title("Maxwellian perturbed thermal velocity $v_t$, top view (e1-e3)") plt.subplot(2, 5, 10) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, :, 0], y[:, :, 0], vth_cart[:, :, 0], levels=levels) @@ -659,7 +695,7 @@ def test_maxwellian_3d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("z") plt.axis("equal") plt.colorbar() - plt.title(f"Maxwellian perturbed thermal velocity $v_t$, poloidal view (e1-e2)") + plt.title("Maxwellian perturbed thermal velocity $v_t$, poloidal view (e1-e2)") plt.show() @@ -671,34 +707,34 @@ def test_maxwellian_2d_uniform(Nel, show_plot=False): Asserts that the results over the domain and velocity space correspond to the analytical computation. """ + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from struphy.kinetic_background.maxwellians import GyroMaxwellian2D - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) # =========================================================== # ===== Test uniform non-shifted, isothermal Maxwellian ===== # =========================================================== maxwellian = GyroMaxwellian2D(n=(2.0, None), volume_form=False) - meshgrids = np.meshgrid(e1, e2, e3, [0.01], [0.01]) + meshgrids = xp.meshgrid(e1, e2, e3, [0.01], [0.01]) # Test constant value at v_para = v_perp = 0.01 res = maxwellian(*meshgrids).squeeze() - assert np.allclose(res, 2.0 / (2 * np.pi) ** (1 / 2) * np.exp(-(0.01**2)) + 0 * e1, atol=10e-10), ( - f"{res=},\n {2.0 / (2 * np.pi) ** (3 / 2)}" + assert xp.allclose(res, 2.0 / (2 * xp.pi) ** (1 / 2) * xp.exp(-(0.01**2)) + 0 * e1, atol=10e-10), ( + f"{res=},\n {2.0 / (2 * xp.pi) ** (3 / 2)}" ) # test Maxwellian profile in v - v_para = np.linspace(-5, 5, 64) - v_perp = np.linspace(0, 2.5, 64) - vpara, vperp = np.meshgrid(v_para, v_perp) + v_para = xp.linspace(-5, 5, 64) + v_perp = xp.linspace(0, 2.5, 64) + vpara, vperp = xp.meshgrid(v_para, v_perp) - meshgrids = np.meshgrid( + meshgrids = xp.meshgrid( [0.0], [0.0], [0.0], @@ -707,8 +743,8 @@ def test_maxwellian_2d_uniform(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - res_ana = 2.0 / (2 * np.pi) ** (1 / 2) * np.exp(-(vpara.T**2) / 2.0 - vperp.T**2 / 2.0) - assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + res_ana = 2.0 / (2 * xp.pi) ** (1 / 2) * xp.exp(-(vpara.T**2) / 2.0 - vperp.T**2 / 2.0) + assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # ======================================================= # ===== Test non-zero shifts and thermal velocities ===== @@ -729,16 +765,16 @@ def test_maxwellian_2d_uniform(Nel, show_plot=False): ) # test Maxwellian profile in v - v_para = np.linspace(-5, 5, 64) - v_perp = np.linspace(0, 2.5, 64) - vpara, vperp = np.meshgrid(v_para, v_perp) + v_para = xp.linspace(-5, 5, 64) + v_perp = xp.linspace(0, 2.5, 64) + vpara, vperp = xp.meshgrid(v_para, v_perp) - meshgrids = np.meshgrid([0.0], [0.0], [0.0], v_para, v_perp) + meshgrids = xp.meshgrid([0.0], [0.0], [0.0], v_para, v_perp) res = maxwellian(*meshgrids).squeeze() - res_ana = np.exp(-((vpara.T - u_para) ** 2) / (2 * vth_para**2)) - res_ana *= np.exp(-((vperp.T - u_perp) ** 2) / (2 * vth_perp**2)) - res_ana *= n / ((2 * np.pi) ** (1 / 2) * vth_para * vth_perp**2) + res_ana = xp.exp(-((vpara.T - u_para) ** 2) / (2 * vth_para**2)) + res_ana *= xp.exp(-((vperp.T - u_perp) ** 2) / (2 * vth_perp**2)) + res_ana *= n / ((2 * xp.pi) ** (1 / 2) * vth_para * vth_perp**2) if show_plot: plt.plot(v_para, res_ana[:, 32], label="analytical") @@ -757,22 +793,22 @@ def test_maxwellian_2d_uniform(Nel, show_plot=False): plt.xlabel("v_" + "perp") plt.show() - assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana =}" + assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana =}" @pytest.mark.parametrize("Nel", [[6, 1, 1]]) def test_maxwellian_2d_perturbed(Nel, show_plot=False): """Tests the GyroMaxwellian2D class for perturbations.""" + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from struphy.initial import perturbations from struphy.kinetic_background.maxwellians import GyroMaxwellian2D - e1 = np.linspace(0.0, 1.0, Nel[0]) - v1 = np.linspace(-5.0, 5.0, 128) - v2 = np.linspace(0, 2.5, 128) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + v1 = xp.linspace(-5.0, 5.0, 128) + v2 = xp.linspace(0, 2.5, 128) # =============================================== # ===== Test cosine perturbation in density ===== @@ -784,11 +820,11 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): maxwellian = GyroMaxwellian2D(n=(2.0, pert), volume_form=False) v_perp = 0.1 - meshgrids = np.meshgrid(e1, [0.0], [0.0], [0.0], v_perp) + meshgrids = xp.meshgrid(e1, [0.0], [0.0], [0.0], v_perp) res = maxwellian(*meshgrids).squeeze() - ana_res = (2.0 + amp * np.cos(2 * np.pi * mode * e1)) / (2 * np.pi) ** (1 / 2) - ana_res *= np.exp(-(v_perp**2) / 2) + ana_res = (2.0 + amp * xp.cos(2 * xp.pi * mode * e1)) / (2 * xp.pi) ** (1 / 2) + ana_res *= xp.exp(-(v_perp**2) / 2) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -799,7 +835,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ==================================================== # ===== Test cosine perturbation in shift (para) ===== @@ -817,12 +853,12 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): ) v_perp = 0.1 - meshgrids = np.meshgrid(e1, [0.0], [0.0], v1, v_perp) + meshgrids = xp.meshgrid(e1, [0.0], [0.0], v1, v_perp) res = maxwellian(*meshgrids).squeeze() - shift = u_para + amp * np.cos(2 * np.pi * mode * e1) - ana_res = np.exp(-((v1 - shift[:, None]) ** 2) / 2.0) - ana_res *= n / (2 * np.pi) ** (1 / 2) * np.exp(-(v_perp**2) / 2.0) + shift = u_para + amp * xp.cos(2 * xp.pi * mode * e1) + ana_res = xp.exp(-((v1 - shift[:, None]) ** 2) / 2.0) + ana_res *= n / (2 * xp.pi) ** (1 / 2) * xp.exp(-(v_perp**2) / 2.0) if show_plot: plt.figure(1) @@ -843,7 +879,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ==================================================== # ===== Test cosine perturbation in shift (perp) ===== @@ -860,12 +896,12 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): volume_form=False, ) - meshgrids = np.meshgrid(e1, [0.0], [0.0], 0.0, v2) + meshgrids = xp.meshgrid(e1, [0.0], [0.0], 0.0, v2) res = maxwellian(*meshgrids).squeeze() - shift = u_perp + amp * np.cos(2 * np.pi * mode * e1) - ana_res = np.exp(-((v2 - shift[:, None]) ** 2) / 2.0) - ana_res *= n / (2 * np.pi) ** (1 / 2) + shift = u_perp + amp * xp.cos(2 * xp.pi * mode * e1) + ana_res = xp.exp(-((v2 - shift[:, None]) ** 2) / 2.0) + ana_res *= n / (2 * xp.pi) ** (1 / 2) if show_plot: plt.figure(1) @@ -886,7 +922,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ================================================== # ===== Test cosine perturbation in vth (para) ===== @@ -904,7 +940,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): ) v_perp = 0.1 - meshgrids = np.meshgrid( + meshgrids = xp.meshgrid( e1, [0.0], [0.0], @@ -913,10 +949,10 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - thermal = vth_para + amp * np.cos(2 * np.pi * mode * e1) - ana_res = np.exp(-(v1**2) / (2.0 * thermal[:, None] ** 2)) - ana_res *= n / ((2 * np.pi) ** (1 / 2) * thermal[:, None]) - ana_res *= np.exp(-(v_perp**2) / 2.0) + thermal = vth_para + amp * xp.cos(2 * xp.pi * mode * e1) + ana_res = xp.exp(-(v1**2) / (2.0 * thermal[:, None] ** 2)) + ana_res *= n / ((2 * xp.pi) ** (1 / 2) * thermal[:, None]) + ana_res *= xp.exp(-(v_perp**2) / 2.0) if show_plot: plt.figure(1) @@ -937,7 +973,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ================================================== # ===== Test cosine perturbation in vth (perp) ===== @@ -954,7 +990,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): volume_form=False, ) - meshgrids = np.meshgrid( + meshgrids = xp.meshgrid( e1, [0.0], [0.0], @@ -963,9 +999,9 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): ) res = maxwellian(*meshgrids).squeeze() - thermal = vth_perp + amp * np.cos(2 * np.pi * mode * e1) - ana_res = np.exp(-(v2**2) / (2.0 * thermal[:, None] ** 2)) - ana_res *= n / ((2 * np.pi) ** (1 / 2) * thermal[:, None] ** 2) + thermal = vth_perp + amp * xp.cos(2 * xp.pi * mode * e1) + ana_res = xp.exp(-(v2**2) / (2.0 * thermal[:, None] ** 2)) + ana_res *= n / ((2 * xp.pi) ** (1 / 2) * thermal[:, None] ** 2) if show_plot: plt.figure(1) @@ -986,7 +1022,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" # ============================================= # ===== Test ITPA perturbation in density ===== @@ -998,11 +1034,11 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): maxwellian = GyroMaxwellian2D(n=(0.0, pert), volume_form=False) v_perp = 0.1 - meshgrids = np.meshgrid(e1, [0.0], [0.0], [0.0], v_perp) + meshgrids = xp.meshgrid(e1, [0.0], [0.0], [0.0], v_perp) res = maxwellian(*meshgrids).squeeze() - ana_res = n0 * c[3] * np.exp(-c[2] / c[1] * np.tanh((e1 - c[0]) / c[2])) / (2 * np.pi) ** (1 / 2) - ana_res *= np.exp(-(v_perp**2) / 2.0) + ana_res = n0 * c[3] * xp.exp(-c[2] / c[1] * xp.tanh((e1 - c[0]) / c[2])) / (2 * xp.pi) ** (1 / 2) + ana_res *= xp.exp(-(v_perp**2) / 2.0) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -1013,7 +1049,7 @@ def test_maxwellian_2d_perturbed(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" @pytest.mark.parametrize("Nel", [[8, 12, 12]]) @@ -1022,8 +1058,8 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): import inspect + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from struphy.fields_background import equils from struphy.fields_background.base import FluidEquilibriumWithB @@ -1032,30 +1068,30 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): from struphy.initial.base import Perturbation from struphy.kinetic_background.maxwellians import GyroMaxwellian2D - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) v1 = [0.0] v2 = [0.0, 2.0] - meshgrids = np.meshgrid(e1, e2, e3, v1, v2, indexing="ij") - e_meshgrids = np.meshgrid(e1, e2, e3, indexing="ij") + meshgrids = xp.meshgrid(e1, e2, e3, v1, v2, indexing="ij") + e_meshgrids = xp.meshgrid(e1, e2, e3, indexing="ij") n_mks = 17 - e1_fl = np.random.rand(n_mks) - e2_fl = np.random.rand(n_mks) - e3_fl = np.random.rand(n_mks) - v1_fl = np.random.randn(n_mks) - v2_fl = np.random.rand(n_mks) + e1_fl = xp.random.rand(n_mks) + e2_fl = xp.random.rand(n_mks) + e3_fl = xp.random.rand(n_mks) + v1_fl = xp.random.randn(n_mks) + v2_fl = xp.random.rand(n_mks) args_fl = [e1_fl, e2_fl, e3_fl, v1_fl, v2_fl] - e_args_fl = np.concatenate((e1_fl[:, None], e2_fl[:, None], e3_fl[:, None]), axis=1) + e_args_fl = xp.concatenate((e1_fl[:, None], e2_fl[:, None], e3_fl[:, None]), axis=1) for key, val in inspect.getmembers(equils): if inspect.isclass(val) and val.__module__ == equils.__name__: - print(f"{key = }") + print(f"{key =}") if "DESCequilibrium" in key and not with_desc: - print(f"Attention: {with_desc = }, DESC not tested here !!") + print(f"Attention: {with_desc =}, DESC not tested here !!") continue if "GVECequilibrium" in key: @@ -1067,32 +1103,42 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): if not isinstance(mhd_equil, FluidEquilibriumWithB): continue - print(f"{mhd_equil.params = }") + print(f"{mhd_equil.params =}") if "AdhocTorus" in key: mhd_equil.domain = domains.HollowTorus( - a1=1e-3, a2=mhd_equil.params["a"], R0=mhd_equil.params["R0"], tor_period=1 + a1=1e-3, + a2=mhd_equil.params["a"], + R0=mhd_equil.params["R0"], + tor_period=1, ) elif "EQDSKequilibrium" in key: mhd_equil.domain = domains.Tokamak(equilibrium=mhd_equil) elif "CircularTokamak" in key: mhd_equil.domain = domains.HollowTorus( - a1=1e-3, a2=mhd_equil.params["a"], R0=mhd_equil.params["R0"], tor_period=1 + a1=1e-3, + a2=mhd_equil.params["a"], + R0=mhd_equil.params["R0"], + tor_period=1, ) elif "HomogenSlab" in key: mhd_equil.domain = domains.Cuboid() elif "ShearedSlab" in key: mhd_equil.domain = domains.Cuboid( r1=mhd_equil.params["a"], - r2=mhd_equil.params["a"] * 2 * np.pi, - r3=mhd_equil.params["R0"] * 2 * np.pi, + r2=mhd_equil.params["a"] * 2 * xp.pi, + r3=mhd_equil.params["R0"] * 2 * xp.pi, ) elif "ShearFluid" in key: mhd_equil.domain = domains.Cuboid( - r1=mhd_equil.params["a"], r2=mhd_equil.params["b"], r3=mhd_equil.params["c"] + r1=mhd_equil.params["a"], + r2=mhd_equil.params["b"], + r3=mhd_equil.params["c"], ) elif "ScrewPinch" in key: mhd_equil.domain = domains.HollowCylinder( - a1=1e-3, a2=mhd_equil.params["a"], Lz=mhd_equil.params["R0"] * 2 * np.pi + a1=1e-3, + a2=mhd_equil.params["a"], + Lz=mhd_equil.params["R0"] * 2 * xp.pi, ) else: try: @@ -1118,9 +1164,9 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): # test meshgrid evaluation n0 = mhd_equil.n0(*e_meshgrids) - assert np.allclose(maxwellian(*meshgrids)[:, :, :, 0, 0], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 0]) + assert xp.allclose(maxwellian(*meshgrids)[:, :, :, 0, 0], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 0]) - assert np.allclose(maxwellian(*meshgrids)[:, :, :, 0, 1], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 1]) + assert xp.allclose(maxwellian(*meshgrids)[:, :, :, 0, 1], n0 * maxwellian_1(*meshgrids)[:, :, :, 0, 1]) # test flat evaluation if "GVECequilibrium" in key: @@ -1129,42 +1175,48 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): continue pass else: - assert np.allclose(maxwellian(*args_fl), mhd_equil.n0(e_args_fl) * maxwellian_1(*args_fl)) - assert np.allclose(maxwellian.n(e1_fl, e2_fl, e3_fl), mhd_equil.n0(e_args_fl)) + assert xp.allclose(maxwellian(*args_fl), mhd_equil.n0(e_args_fl) * maxwellian_1(*args_fl)) + assert xp.allclose(maxwellian.n(e1_fl, e2_fl, e3_fl), mhd_equil.n0(e_args_fl)) u_maxw = maxwellian.u(e1_fl, e2_fl, e3_fl) tmp_jv = mhd_equil.jv(e_args_fl) / mhd_equil.n0(e_args_fl) tmp_unit_b1 = mhd_equil.unit_b1(e_args_fl) # j_parallel = jv.b1 j_para = sum([ji * bi for ji, bi in zip(tmp_jv, tmp_unit_b1)]) - assert np.allclose(u_maxw[0], j_para) + assert xp.allclose(u_maxw[0], j_para) vth_maxw = maxwellian.vth(e1_fl, e2_fl, e3_fl) - vth_eq = np.sqrt(mhd_equil.p0(e_args_fl) / mhd_equil.n0(e_args_fl)) - assert all([np.allclose(v, vth_eq) for v in vth_maxw]) + vth_eq = xp.sqrt(mhd_equil.p0(e_args_fl) / mhd_equil.n0(e_args_fl)) + assert all([xp.allclose(v, vth_eq) for v in vth_maxw]) # plotting moments if show_plot: - plt.figure(f"{mhd_equil = }", figsize=(24, 16)) + plt.figure(f"{mhd_equil =}", figsize=(24, 16)) x, y, z = mhd_equil.domain(*e_meshgrids) # density plots n_cart = mhd_equil.domain.push(maxwellian.n, *e_meshgrids) - levels = np.linspace(np.min(n_cart) - 1e-10, np.max(n_cart), 20) + levels = xp.linspace(xp.min(n_cart) - 1e-10, xp.max(n_cart), 20) plt.subplot(2, 4, 1) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], z[:, Nel[1] // 2 - 1, :], n_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + z[:, Nel[1] // 2 - 1, :], + n_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], y[:, Nel[1] // 2 - 1, :], n_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + y[:, Nel[1] // 2 - 1, :], + n_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("y") @@ -1187,7 +1239,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): # velocity plots us = maxwellian.u(*e_meshgrids) for i, u in enumerate(us[:1]): - levels = np.linspace(np.min(u) - 1e-10, np.max(u), 20) + levels = xp.linspace(xp.min(u) - 1e-10, xp.max(u), 20) plt.subplot(2, 4, 2 + i) if "Slab" in key or "Pinch" in key: @@ -1220,26 +1272,32 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): vth = maxwellian.vth(*e_meshgrids)[0] vth_cart = mhd_equil.domain.push(vth, *e_meshgrids) - levels = np.linspace(np.min(vth_cart) - 1e-10, np.max(vth_cart), 20) + levels = xp.linspace(xp.min(vth_cart) - 1e-10, xp.max(vth_cart), 20) plt.subplot(2, 4, 4) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], vth_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], z[:, Nel[1] // 2 - 1, :], vth_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + z[:, Nel[1] // 2 - 1, :], + vth_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], vth_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], y[:, Nel[1] // 2 - 1, :], vth_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + y[:, Nel[1] // 2 - 1, :], + vth_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("y") plt.axis("equal") plt.colorbar() - plt.title(f"Maxwellian thermal velocity $v_t$, top view (e1-e3)") + plt.title("Maxwellian thermal velocity $v_t$, top view (e1-e3)") plt.subplot(2, 4, 8) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, :, 0], y[:, :, 0], vth_cart[:, :, 0], levels=levels) @@ -1251,7 +1309,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("z") plt.axis("equal") plt.colorbar() - plt.title(f"Maxwellian density $v_t$, poloidal view (e1-e2)") + plt.title("Maxwellian density $v_t$, poloidal view (e1-e2)") plt.show() @@ -1260,7 +1318,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): for key_2, val_2 in inspect.getmembers(perturbations): if inspect.isclass(val_2) and val_2.__module__ == perturbations.__name__: pert = val_2() - print(f"{pert = }") + print(f"{pert =}") assert isinstance(pert, Perturbation) if isinstance(pert, perturbations.Noise): @@ -1291,11 +1349,11 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): volume_form=False, ) - assert np.allclose(maxwellian_zero_bckgr.n(*e_meshgrids), pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[0], pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[1], pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[0], pert(*e_meshgrids)) - assert np.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[1], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.n(*e_meshgrids), pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[0], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.u(*e_meshgrids)[1], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[0], pert(*e_meshgrids)) + assert xp.allclose(maxwellian_zero_bckgr.vth(*e_meshgrids)[1], pert(*e_meshgrids)) # plotting perturbations if show_plot and "EQDSKequilibrium" in key: # and 'Torus' in key_2: @@ -1305,20 +1363,26 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): # density plots n_cart = mhd_equil.domain.push(maxwellian_zero_bckgr.n, *e_meshgrids) - levels = np.linspace(np.min(n_cart) - 1e-10, np.max(n_cart), 20) + levels = xp.linspace(xp.min(n_cart) - 1e-10, xp.max(n_cart), 20) plt.subplot(2, 4, 1) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], z[:, Nel[1] // 2, :], n_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + z[:, Nel[1] // 2, :], + n_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], n_cart[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], y[:, Nel[1] // 2, :], n_cart[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + y[:, Nel[1] // 2, :], + n_cart[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("y") @@ -1341,20 +1405,26 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): # velocity plots us = maxwellian_zero_bckgr.u(*e_meshgrids) for i, u in enumerate(us): - levels = np.linspace(np.min(u) - 1e-10, np.max(u), 20) + levels = xp.linspace(xp.min(u) - 1e-10, xp.max(u), 20) plt.subplot(2, 4, 2 + i) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, 0, :], z[:, 0, :], u[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], z[:, Nel[1] // 2, :], u[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + z[:, Nel[1] // 2, :], + u[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("z") else: plt.contourf(x[:, 0, :], y[:, 0, :], u[:, 0, :], levels=levels) plt.contourf( - x[:, Nel[1] // 2, :], y[:, Nel[1] // 2, :], u[:, Nel[1] // 2, :], levels=levels + x[:, Nel[1] // 2, :], + y[:, Nel[1] // 2, :], + u[:, Nel[1] // 2, :], + levels=levels, ) plt.xlabel("x") plt.ylabel("y") @@ -1378,7 +1448,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): vth = maxwellian_zero_bckgr.vth(*e_meshgrids)[0] vth_cart = mhd_equil.domain.push(vth, *e_meshgrids) - levels = np.linspace(np.min(vth_cart) - 1e-10, np.max(vth_cart), 20) + levels = xp.linspace(xp.min(vth_cart) - 1e-10, xp.max(vth_cart), 20) plt.subplot(2, 4, 4) if "Slab" in key or "Pinch" in key: @@ -1403,7 +1473,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("y") plt.axis("equal") plt.colorbar() - plt.title(f"Maxwellian perturbed thermal velocity $v_t$, top view (e1-e3)") + plt.title("Maxwellian perturbed thermal velocity $v_t$, top view (e1-e3)") plt.subplot(2, 4, 8) if "Slab" in key or "Pinch" in key: plt.contourf(x[:, :, 0], y[:, :, 0], vth_cart[:, :, 0], levels=levels) @@ -1415,7 +1485,7 @@ def test_maxwellian_2d_mhd(Nel, with_desc, show_plot=False): plt.ylabel("z") plt.axis("equal") plt.colorbar() - plt.title(f"Maxwellian perturbed density $v_t$, poloidal view (e1-e2)") + plt.title("Maxwellian perturbed density $v_t$, poloidal view (e1-e2)") plt.show() @@ -1427,19 +1497,19 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): Asserts that the results over the domain and velocity space correspond to the analytical computation. """ + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from struphy.fields_background import equils from struphy.geometry import domains from struphy.initial import perturbations from struphy.kinetic_background.maxwellians import CanonicalMaxwellian - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) - eta_meshgrid = np.meshgrid(e1, e2, e3) + eta_meshgrid = xp.meshgrid(e1, e2, e3) v_para = 0.01 v_perp = 0.01 @@ -1486,7 +1556,7 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): psi = mhd_equil.psi_r(r) psic = psi - epsilon * B0 * R0 / absB * v_para - psic += epsilon * np.sign(v_para) * np.sqrt(2 * (energy - mu * B0)) * R0 * np.heaviside(energy - mu * B0, 0) + psic += epsilon * xp.sign(v_para) * xp.sqrt(2 * (energy - mu * B0)) * R0 * xp.heaviside(energy - mu * B0, 0) # =========================================================== # ===== Test uniform, isothermal canonical Maxwellian ===== @@ -1500,14 +1570,14 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): res_ana = ( maxw_params["n"] * 2 - * np.sqrt(energy / np.pi) + * xp.sqrt(energy / xp.pi) / maxw_params["vth"] ** 3 - * np.exp(-energy / maxw_params["vth"] ** 2) + * xp.exp(-energy / maxw_params["vth"] ** 2) ) - assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # test canonical Maxwellian profile in v_para - v_para = np.linspace(-5, 5, 64) + v_para = xp.linspace(-5, 5, 64) v_perp = 0.1 absB = mhd_equil.absB0(0.0, 0.0, 0.0)[0, 0, 0] @@ -1524,18 +1594,18 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): psi = mhd_equil.psi_r(r) psic = psi - epsilon * B0 * R0 / absB * v_para - psic += epsilon * np.sign(v_para) * np.sqrt(2 * (energy - mu * B0)) * R0 * np.heaviside(energy - mu * B0, 0) + psic += epsilon * xp.sign(v_para) * xp.sqrt(2 * (energy - mu * B0)) * R0 * xp.heaviside(energy - mu * B0, 0) - com_meshgrids = np.meshgrid(energy, mu, psic) + com_meshgrids = xp.meshgrid(energy, mu, psic) res = maxwellian(*com_meshgrids).squeeze() res_ana = ( maxw_params["n"] * 2 - * np.sqrt(com_meshgrids[0] / np.pi) + * xp.sqrt(com_meshgrids[0] / xp.pi) / maxw_params["vth"] ** 3 - * np.exp(-com_meshgrids[0] / maxw_params["vth"] ** 2) + * xp.exp(-com_meshgrids[0] / maxw_params["vth"] ** 2) ) if show_plot: @@ -1547,11 +1617,11 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): plt.xlabel("v_para") plt.show() - assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # test canonical Maxwellian profile in v_perp v_para = 0.1 - v_perp = np.linspace(0, 2.5, 64) + v_perp = xp.linspace(0, 2.5, 64) absB = mhd_equil.absB0(0.5, 0.5, 0.5)[0, 0, 0] @@ -1567,18 +1637,18 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): psi = mhd_equil.psi_r(r) psic = psi - epsilon * B0 * R0 / absB * v_para - psic += epsilon * np.sign(v_para) * np.sqrt(2 * (energy - mu * B0)) * R0 * np.heaviside(energy - mu * B0, 0) + psic += epsilon * xp.sign(v_para) * xp.sqrt(2 * (energy - mu * B0)) * R0 * xp.heaviside(energy - mu * B0, 0) - com_meshgrids = np.meshgrid(energy, mu, psic) + com_meshgrids = xp.meshgrid(energy, mu, psic) res = maxwellian(*com_meshgrids).squeeze() res_ana = ( maxw_params["n"] * 2 - * np.sqrt(com_meshgrids[0] / np.pi) + * xp.sqrt(com_meshgrids[0] / xp.pi) / maxw_params["vth"] ** 3 - * np.exp(-com_meshgrids[0] / maxw_params["vth"] ** 2) + * xp.exp(-com_meshgrids[0] / maxw_params["vth"] ** 2) ) if show_plot: @@ -1590,7 +1660,7 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): plt.xlabel("v_perp") plt.show() - assert np.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" + assert xp.allclose(res, res_ana, atol=10e-10), f"{res=},\n {res_ana}" # ============================================= # ===== Test ITPA perturbation in density ===== @@ -1605,11 +1675,11 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): maxwellian = CanonicalMaxwellian(n=(0.0, pert), equil=mhd_equil, volume_form=False) - e1 = np.linspace(0.0, 1.0, Nel[0]) - e2 = np.linspace(0.0, 1.0, Nel[1]) - e3 = np.linspace(0.0, 1.0, Nel[2]) + e1 = xp.linspace(0.0, 1.0, Nel[0]) + e2 = xp.linspace(0.0, 1.0, Nel[1]) + e3 = xp.linspace(0.0, 1.0, Nel[2]) - eta_meshgrid = np.meshgrid(e1, e2, e3) + eta_meshgrid = xp.meshgrid(e1, e2, e3) v_para = 0.01 v_perp = 0.01 @@ -1628,16 +1698,16 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): psi = mhd_equil.psi_r(r[0, :, 0]) psic = psi - epsilon * B0 * R0 / absB * v_para - psic += epsilon * np.sign(v_para) * np.sqrt(2 * (energy - mu * B0)) * R0 * np.heaviside(energy - mu * B0, 0) + psic += epsilon * xp.sign(v_para) * xp.sqrt(2 * (energy - mu * B0)) * R0 * xp.heaviside(energy - mu * B0, 0) - com_meshgrids = np.meshgrid(energy, mu, psic) + com_meshgrids = xp.meshgrid(energy, mu, psic) res = maxwellian(energy, mu, psic).squeeze() # calculate rc rc = maxwellian.rc(psic) - ana_res = n0 * c[3] * np.exp(-c[2] / c[1] * np.tanh((rc - c[0]) / c[2])) - ana_res *= 2 * np.sqrt(energy / np.pi) / maxw_params["vth"] ** 3 * np.exp(-energy / maxw_params["vth"] ** 2) + ana_res = n0 * c[3] * xp.exp(-c[2] / c[1] * xp.tanh((rc - c[0]) / c[2])) + ana_res *= 2 * xp.sqrt(energy / xp.pi) / maxw_params["vth"] ** 3 * xp.exp(-energy / maxw_params["vth"] ** 2) if show_plot: plt.plot(e1, ana_res, label="analytical") @@ -1648,7 +1718,7 @@ def test_canonical_maxwellian_uniform(Nel, show_plot=False): plt.ylabel("f(eta_1)") plt.show() - assert np.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" + assert xp.allclose(res, ana_res, atol=10e-10), f"{res=},\n {ana_res}" if __name__ == "__main__": diff --git a/src/struphy/linear_algebra/linalg_kron.py b/src/struphy/linear_algebra/linalg_kron.py index 712b47e43..fedd05979 100644 --- a/src/struphy/linear_algebra/linalg_kron.py +++ b/src/struphy/linear_algebra/linalg_kron.py @@ -13,7 +13,7 @@ [r_M11, rM12, ... , r_MNO]] """ -import numpy as np +import cunumpy as xp from scipy.linalg import solve_circulant from scipy.sparse.linalg import splu @@ -82,8 +82,9 @@ def kron_matvec_3d(kmat, vec3d): ( kmat[2].dot( ((kmat[1].dot(((kmat[0].dot(vec3d.reshape(v0, v1 * v2))).T).reshape(v1, v2 * k0))).T).reshape( - v2, k0 * k1 - ) + v2, + k0 * k1, + ), ) ).T ).reshape(k0, k1, k2) @@ -196,9 +197,9 @@ def kron_matmat_fft_3d(a_vec, b_vec): c_vec = [0, 0, 0] - c_vec[0] = np.fft.ifft(np.fft.fft(a_vec[0]) * np.fft.fft(b_vec[0])) - c_vec[1] = np.fft.ifft(np.fft.fft(a_vec[1]) * np.fft.fft(b_vec[1])) - c_vec[2] = np.fft.ifft(np.fft.fft(a_vec[2]) * np.fft.fft(b_vec[2])) + c_vec[0] = xp.fft.ifft(xp.fft.fft(a_vec[0]) * xp.fft.fft(b_vec[0])) + c_vec[1] = xp.fft.ifft(xp.fft.fft(a_vec[1]) * xp.fft.fft(b_vec[1])) + c_vec[2] = xp.fft.ifft(xp.fft.fft(a_vec[2]) * xp.fft.fft(b_vec[2])) return c_vec @@ -278,8 +279,9 @@ def kron_lusolve_3d(kmatlu, rhs): ( kmatlu[2].solve( ((kmatlu[1].solve(((kmatlu[0].solve(rhs.reshape(r0, r1 * r2))).T).reshape(r1, r2 * r0))).T).reshape( - r2, r0 * r1 - ) + r2, + r0 * r1, + ), ) ).T ).reshape(r0, r1, r2) @@ -320,7 +322,7 @@ def kron_solve_3d(kmat, rhs): splu(kmat[2]).solve( ( (splu(kmat[1]).solve(((splu(kmat[0]).solve(rhs.reshape(r0, r1 * r2))).T).reshape(r1, r2 * r0))).T - ).reshape(r2, r0 * r1) + ).reshape(r2, r0 * r1), ) ).T ).reshape(r0, r1, r2) @@ -361,7 +363,8 @@ def kron_fftsolve_3d(cvec, rhs): ( ( solve_circulant( - cvec[1], ((solve_circulant(cvec[0], rhs.reshape(r0, r1 * r2))).T).reshape(r1, r2 * r0) + cvec[1], + ((solve_circulant(cvec[0], rhs.reshape(r0, r1 * r2))).T).reshape(r1, r2 * r0), ) ).T ).reshape(r2, r0 * r1), diff --git a/src/struphy/linear_algebra/saddle_point.py b/src/struphy/linear_algebra/saddle_point.py index 15ca4aac4..337664754 100644 --- a/src/struphy/linear_algebra/saddle_point.py +++ b/src/struphy/linear_algebra/saddle_point.py @@ -1,6 +1,6 @@ from typing import Union -import numpy as np +import cunumpy as xp import scipy as sc from psydac.linalg.basic import LinearOperator, Vector from psydac.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace @@ -28,7 +28,7 @@ class SaddlePointSolver: } \right) using either the Uzawa iteration :math:`BA^{-1}B^{\top} y = BA^{-1} f` or using on of the solvers given in :mod:`psydac.linalg.solvers`. The prefered solver is GMRES. - The decission which variant to use is given by the type of A. If A is of type list of np.ndarrays or sc.sparse.csr_matrices, then this class uses the Uzawa algorithm. + The decission which variant to use is given by the type of A. If A is of type list of xp.ndarrays or sc.sparse.csr_matrices, then this class uses the Uzawa algorithm. If A is of type LinearOperator or BlockLinearOperator, a solver is used for the inverse. Using the Uzawa algorithm, solution is given by: @@ -41,7 +41,7 @@ class SaddlePointSolver: ---------- A : list, LinearOperator or BlockLinearOperator Upper left block. - Either the entries on the diagonals of block A are given as list of np.ndarray or sc.sparse.csr_matrix. + Either the entries on the diagonals of block A are given as list of xp.ndarray or sc.sparse.csr_matrix. Alternative: Give whole matrice A as LinearOperator or BlockLinearOperator. list: Uzawa algorithm is used. LinearOperator: A solver given in :mod:`psydac.linalg.solvers` is used. Specified by solver_name. @@ -49,16 +49,16 @@ class SaddlePointSolver: B : list, LinearOperator or BlockLinearOperator Lower left block. - Uzwaw Algorithm: All entries of block B are given either as list of np.ndarray or sc.sparse.csr_matrix. + Uzwaw Algorithm: All entries of block B are given either as list of xp.ndarray or sc.sparse.csr_matrix. Solver: Give whole B as LinearOperator or BlocklinearOperator F : list Right hand side of the upper block. - Uzawa: Given as list of np.ndarray or sc.sparse.csr_matrix. + Uzawa: Given as list of xp.ndarray or sc.sparse.csr_matrix. Solver: Given as LinearOperator or BlockLinearOperator Apre : list - The non-inverted preconditioner for entries on the diagonals of block A are given as list of np.ndarray or sc.sparse.csr_matrix. Only required for the Uzawa algorithm. + The non-inverted preconditioner for entries on the diagonals of block A are given as list of xp.ndarray or sc.sparse.csr_matrix. Only required for the Uzawa algorithm. method_to_solve : str Method for the inverses. Choose from 'DirectNPInverse', 'ScipySparse', 'InexactNPInverse' ,'SparseSolver'. Only required for the Uzawa algorithm. @@ -98,14 +98,14 @@ def __init__( if isinstance(A, list): self._variant = "Uzawa" for i in A: - assert isinstance(i, np.ndarray) or isinstance(i, sc.sparse.csr_matrix) + assert isinstance(i, xp.ndarray) or isinstance(i, sc.sparse.csr_matrix) for i in B: - assert isinstance(i, np.ndarray) or isinstance(i, sc.sparse.csr_matrix) + assert isinstance(i, xp.ndarray) or isinstance(i, sc.sparse.csr_matrix) for i in F: - assert isinstance(i, np.ndarray) or isinstance(i, sc.sparse.csr_matrix) + assert isinstance(i, xp.ndarray) or isinstance(i, sc.sparse.csr_matrix) for i in Apre: assert ( - isinstance(i, np.ndarray) + isinstance(i, xp.ndarray) or isinstance(i, sc.sparse.csr_matrix) or isinstance(i, sc.sparse.csr_array) ) @@ -169,9 +169,9 @@ def __init__( self._setup_inverses() # Solution vectors numpy - self._Pnp = np.zeros(self._B1np.shape[0]) - self._Unp = np.zeros(self._A[0].shape[1]) - self._Uenp = np.zeros(self._A[1].shape[1]) + self._Pnp = xp.zeros(self._B1np.shape[0]) + self._Unp = xp.zeros(self._A[0].shape[1]) + self._Uenp = xp.zeros(self._A[1].shape[1]) # Allocate memory for matrices used in solving the system self._rhs0np = self._F[0].copy() self._rhs1np = self._F[1].copy() @@ -195,8 +195,8 @@ def A(self, a): same_A0 = (A0_old != A0_new).nnz == 0 same_A1 = (A1_old != A1_new).nnz == 0 else: - same_A0 = np.allclose(A0_old, A0_new, atol=1e-10) - same_A1 = np.allclose(A1_old, A1_new, atol=1e-10) + same_A0 = xp.allclose(A0_old, A0_new, atol=1e-10) + same_A1 = xp.allclose(A1_old, A1_new, atol=1e-10) if same_A0 and same_A1: need_update = False self._A = a @@ -240,8 +240,8 @@ def Apre(self, a): same_A0 = (A0_old != A0_new).nnz == 0 same_A1 = (A1_old != A1_new).nnz == 0 else: - same_A0 = np.allclose(A0_old, A0_new, atol=1e-10) - same_A1 = np.allclose(A1_old, A1_new, atol=1e-10) + same_A0 = xp.allclose(A0_old, A0_new, atol=1e-10) + same_A1 = xp.allclose(A1_old, A1_new, atol=1e-10) if same_A0 and same_A1: need_update = False self._Apre = a @@ -256,11 +256,11 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): Parameters ---------- - U_init : Vector, np.ndarray or sc.sparse.csr.csr_matrix, optional - Initial guess for the velocity of the ions. If None, initializes to zero. Types np.ndarray and sc.sparse.csr.csr_matrix can only be given if system should be solved with Uzawa algorithm. + U_init : Vector, xp.ndarray or sc.sparse.csr.csr_matrix, optional + Initial guess for the velocity of the ions. If None, initializes to zero. Types xp.ndarray and sc.sparse.csr.csr_matrix can only be given if system should be solved with Uzawa algorithm. - Ue_init : Vector, np.ndarray or sc.sparse.csr.csr_matrix, optional - Initial guess for the velocity of the electrons. If None, initializes to zero. Types np.ndarray and sc.sparse.csr.csr_matrix can only be given if system should be solved with Uzawa algorithm. + Ue_init : Vector, xp.ndarray or sc.sparse.csr.csr_matrix, optional + Initial guess for the velocity of the electrons. If None, initializes to zero. Types xp.ndarray and sc.sparse.csr.csr_matrix can only be given if system should be solved with Uzawa algorithm. P_init : Vector, optional Initial guess for the potential. If None, initializes to zero. @@ -304,13 +304,13 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): elif self._variant == "Uzawa": info = {} - if self._spectralanalysis == True: + if self._spectralanalysis: self._spectralresult = self._spectral_analysis() else: self._spectralresult = [] # Initialize P to zero or given initial guess - if isinstance(U_init, np.ndarray) or isinstance(U_init, sc.sparse.csr.csr_matrix): + if isinstance(U_init, xp.ndarray) or isinstance(U_init, sc.sparse.csr.csr_matrix): self._Pnp = P_init if P_init is not None else self._P self._Unp = U_init if U_init is not None else self._U self._Uenp = Ue_init if U_init is not None else self._Ue @@ -333,9 +333,9 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): self._rhs0np -= self._B1np.transpose().dot(self._Pnp) self._rhs0np -= self._Anp.dot(self._Unp) self._rhs0np += self._F[0] - if self._preconditioner == False: + if not self._preconditioner: self._Unp += self._Anpinv.dot(self._rhs0np) - elif self._preconditioner == True: + elif self._preconditioner: self._Unp += self._Anpinv.dot(self._A11npinv @ self._rhs0np) R1 = self._B1np.dot(self._Unp) @@ -344,17 +344,17 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): self._rhs1np -= self._B2np.transpose().dot(self._Pnp) self._rhs1np -= self._Aenp.dot(self._Uenp) self._rhs1np += self._F[1] - if self._preconditioner == False: + if not self._preconditioner: self._Uenp += self._Aenpinv.dot(self._rhs1np) - elif self._preconditioner == True: + elif self._preconditioner: self._Uenp += self._Aenpinv.dot(self._A22npinv @ self._rhs1np) R2 = self._B2np.dot(self._Uenp) # Step 2: Compute residual R = BU (divergence of U) R = R1 + R2 # self._B1np.dot(self._Unp) + self._B2np.dot(self._Uenp) - residual_norm = np.linalg.norm(R) - residual_normR1 = np.linalg.norm(R) + residual_norm = xp.linalg.norm(R) + residual_normR1 = xp.linalg.norm(R) self._residual_norms.append(residual_normR1) # Store residual norm # Check for convergence based on residual norm if residual_norm < self._tol: @@ -382,7 +382,7 @@ def __call__(self, U_init=None, Ue_init=None, P_init=None, out=None): # Return with info if maximum iterations reached info["success"] = False info["niter"] = iteration + 1 - if self._verbose == True: + if self._verbose: _plot_residual_norms(self._residual_norms) return self._Unp, self._Uenp, self._Pnp, info, self._residual_norms, self._spectralresult @@ -413,7 +413,10 @@ def _setup_inverses(self): # === Inverse for A[1] if hasattr(self, "_Aenpinv") and self._is_inverse_still_valid( - self._Aenpinv, A1, "A[1]", pre=self._A22npinv + self._Aenpinv, + A1, + "A[1]", + pre=self._A22npinv, ): pass else: @@ -444,10 +447,10 @@ def _is_inverse_still_valid(self, inv, mat, name="", pre=None): I_approx = inv @ test_mat if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - I_exact = np.eye(test_mat.shape[0]) - if not np.allclose(I_approx, I_exact, atol=1e-6): + I_exact = xp.eye(test_mat.shape[0]) + if not xp.allclose(I_approx, I_exact, atol=1e-6): diff = I_approx - I_exact - max_abs = np.abs(diff).max() + max_abs = xp.abs(diff).max() print(f"{name} inverse is NOT valid anymore. Max diff: {max_abs:.2e}") return False print(f"{name} inverse is still valid.") @@ -455,7 +458,7 @@ def _is_inverse_still_valid(self, inv, mat, name="", pre=None): elif self._method_to_solve == "ScipySparse": I_exact = sc.sparse.identity(I_approx.shape[0], format=I_approx.format) diff = (I_approx - I_exact).tocoo() - max_abs = np.abs(diff.data).max() if diff.nnz > 0 else 0.0 + max_abs = xp.abs(diff.data).max() if diff.nnz > 0 else 0.0 if max_abs > 1e-6: print(f"{name} inverse is NOT valid anymore.") @@ -468,12 +471,12 @@ def _is_inverse_still_valid(self, inv, mat, name="", pre=None): def _compute_inverse(self, mat, which="matrix"): print(f"Computing inverse for {which} using method {self._method_to_solve}") if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - return np.linalg.inv(mat) + return xp.linalg.inv(mat) elif self._method_to_solve == "ScipySparse": return sc.sparse.linalg.inv(mat) elif self._method_to_solve == "SparseSolver": solver = SparseSolver(mat) - return solver.solve(np.eye(mat.shape[0])) + return solver.solve(xp.eye(mat.shape[0])) else: raise ValueError(f"Unknown solver method {self._method_to_solve}") @@ -481,14 +484,14 @@ def _spectral_analysis(self): # Spectral analysis # A11 before if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - eigvalsA11_before, eigvecs_before = np.linalg.eig(self._A[0]) - condA11_before = np.linalg.cond(self._A[0]) + eigvalsA11_before, eigvecs_before = xp.linalg.eig(self._A[0]) + condA11_before = xp.linalg.cond(self._A[0]) elif self._method_to_solve in ("SparseSolver", "ScipySparse"): - eigvalsA11_before, eigvecs_before = np.linalg.eig(self._A[0].toarray()) - condA11_before = np.linalg.cond(self._A[0].toarray()) + eigvalsA11_before, eigvecs_before = xp.linalg.eig(self._A[0].toarray()) + condA11_before = xp.linalg.cond(self._A[0].toarray()) maxbeforeA11 = max(eigvalsA11_before) - maxbeforeA11_abs = np.max(np.abs(eigvalsA11_before)) - minbeforeA11_abs = np.min(np.abs(eigvalsA11_before)) + maxbeforeA11_abs = xp.max(xp.abs(eigvalsA11_before)) + minbeforeA11_abs = xp.min(xp.abs(eigvalsA11_before)) minbeforeA11 = min(eigvalsA11_before) specA11_bef = maxbeforeA11 / minbeforeA11 specA11_bef_abs = maxbeforeA11_abs / minbeforeA11_abs @@ -497,18 +500,18 @@ def _spectral_analysis(self): # print(f'{minbeforeA11_abs = }') # print(f'{minbeforeA11 = }') # print(f'{specA11_bef = }') - print(f"{specA11_bef_abs = }") + print(f"{specA11_bef_abs =}") # A22 before if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - eigvalsA22_before, eigvecs_before = np.linalg.eig(self._A[1]) - condA22_before = np.linalg.cond(self._A[1]) + eigvalsA22_before, eigvecs_before = xp.linalg.eig(self._A[1]) + condA22_before = xp.linalg.cond(self._A[1]) elif self._method_to_solve in ("SparseSolver", "ScipySparse"): - eigvalsA22_before, eigvecs_before = np.linalg.eig(self._A[1].toarray()) - condA22_before = np.linalg.cond(self._A[1].toarray()) + eigvalsA22_before, eigvecs_before = xp.linalg.eig(self._A[1].toarray()) + condA22_before = xp.linalg.cond(self._A[1].toarray()) maxbeforeA22 = max(eigvalsA22_before) - maxbeforeA22_abs = np.max(np.abs(eigvalsA22_before)) - minbeforeA22_abs = np.min(np.abs(eigvalsA22_before)) + maxbeforeA22_abs = xp.max(xp.abs(eigvalsA22_before)) + minbeforeA22_abs = xp.min(xp.abs(eigvalsA22_before)) minbeforeA22 = min(eigvalsA22_before) specA22_bef = maxbeforeA22 / minbeforeA22 specA22_bef_abs = maxbeforeA22_abs / minbeforeA22_abs @@ -517,19 +520,19 @@ def _spectral_analysis(self): # print(f'{minbeforeA22_abs = }') # print(f'{minbeforeA22 = }') # print(f'{specA22_bef = }') - print(f"{specA22_bef_abs = }") - print(f"{condA22_before = }") + print(f"{specA22_bef_abs =}") + print(f"{condA22_before =}") - if self._preconditioner == True: + if self._preconditioner: # A11 after preconditioning with its inverse if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - eigvalsA11_after_prec, eigvecs_after = np.linalg.eig(self._A11npinv @ self._A[0]) # Implement this + eigvalsA11_after_prec, eigvecs_after = xp.linalg.eig(self._A11npinv @ self._A[0]) # Implement this elif self._method_to_solve in ("SparseSolver", "ScipySparse"): - eigvalsA11_after_prec, eigvecs_after = np.linalg.eig((self._A11npinv @ self._A[0]).toarray()) + eigvalsA11_after_prec, eigvecs_after = xp.linalg.eig((self._A11npinv @ self._A[0]).toarray()) maxafterA11_prec = max(eigvalsA11_after_prec) minafterA11_prec = min(eigvalsA11_after_prec) - maxafterA11_abs_prec = np.max(np.abs(eigvalsA11_after_prec)) - minafterA11_abs_prec = np.min(np.abs(eigvalsA11_after_prec)) + maxafterA11_abs_prec = xp.max(xp.abs(eigvalsA11_after_prec)) + minafterA11_abs_prec = xp.min(xp.abs(eigvalsA11_after_prec)) specA11_aft_prec = maxafterA11_prec / minafterA11_prec specA11_aft_abs_prec = maxafterA11_abs_prec / minafterA11_abs_prec # print(f'{maxafterA11_prec = }') @@ -537,19 +540,19 @@ def _spectral_analysis(self): # print(f'{minafterA11_abs_prec = }') # print(f'{minafterA11_prec = }') # print(f'{specA11_aft_prec = }') - print(f"{specA11_aft_abs_prec = }") + print(f"{specA11_aft_abs_prec =}") # A22 after preconditioning with its inverse if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - eigvalsA22_after_prec, eigvecs_after = np.linalg.eig(self._A22npinv @ self._A[1]) # Implement this - condA22_after = np.linalg.cond(self._A22npinv @ self._A[1]) + eigvalsA22_after_prec, eigvecs_after = xp.linalg.eig(self._A22npinv @ self._A[1]) # Implement this + condA22_after = xp.linalg.cond(self._A22npinv @ self._A[1]) elif self._method_to_solve in ("SparseSolver", "ScipySparse"): - eigvalsA22_after_prec, eigvecs_after = np.linalg.eig((self._A22npinv @ self._A[1]).toarray()) - condA22_after = np.linalg.cond((self._A22npinv @ self._A[1]).toarray()) + eigvalsA22_after_prec, eigvecs_after = xp.linalg.eig((self._A22npinv @ self._A[1]).toarray()) + condA22_after = xp.linalg.cond((self._A22npinv @ self._A[1]).toarray()) maxafterA22_prec = max(eigvalsA22_after_prec) minafterA22_prec = min(eigvalsA22_after_prec) - maxafterA22_abs_prec = np.max(np.abs(eigvalsA22_after_prec)) - minafterA22_abs_prec = np.min(np.abs(eigvalsA22_after_prec)) + maxafterA22_abs_prec = xp.max(xp.abs(eigvalsA22_after_prec)) + minafterA22_abs_prec = xp.min(xp.abs(eigvalsA22_after_prec)) specA22_aft_prec = maxafterA22_prec / minafterA22_prec specA22_aft_abs_prec = maxafterA22_abs_prec / minafterA22_abs_prec # print(f'{maxafterA22_prec = }') @@ -557,7 +560,7 @@ def _spectral_analysis(self): # print(f'{minafterA22_abs_prec = }') # print(f'{minafterA22_prec = }') # print(f'{specA22_aft_prec = }') - print(f"{specA22_aft_abs_prec = }") + print(f"{specA22_aft_abs_prec =}") return condA22_before, specA22_bef_abs, condA11_before, condA22_after, specA22_aft_abs_prec diff --git a/src/struphy/linear_algebra/solver.py b/src/struphy/linear_algebra/solver.py index 0e36cfcaf..217326309 100644 --- a/src/struphy/linear_algebra/solver.py +++ b/src/struphy/linear_algebra/solver.py @@ -1,5 +1,7 @@ from dataclasses import dataclass +from struphy.io.options import OptsNonlinearSolver + @dataclass class SolverParameters: @@ -10,3 +12,26 @@ class SolverParameters: info: bool = False verbose: bool = False recycle: bool = True + + +@dataclass +class DiscreteGradientSolverParameters: + """Parameters for discrete gradient solvers.""" + + relaxation_factor: float = 0.5 + tol: float = 1e-12 + maxiter: int = 20 + verbose: bool = False + info: bool = False + + +@dataclass +class NonlinearSolverParameters: + """Parameters for psydac solvers.""" + + tol: float = 1e-8 + maxiter: int = 100 + info: bool = False + verbose: bool = False + type: OptsNonlinearSolver = "Picard" + linearize: bool = False diff --git a/src/struphy/linear_algebra/tests/test_saddle_point_propagator.py b/src/struphy/linear_algebra/tests/test_saddle_point_propagator.py index 74ee6293b..3aa3f4ab0 100644 --- a/src/struphy/linear_algebra/tests/test_saddle_point_propagator.py +++ b/src/struphy/linear_algebra/tests/test_saddle_point_propagator.py @@ -13,7 +13,7 @@ def test_propagator1D(Nel, p, spl_kind, dirichlet_bc, mapping, epsilon, dt): """Test saddle-point-solver by propagator TwoFluidQuasiNeutralFull. Use manufactured solutions from perturbations to verify h- and p-convergence when model TwoFluidQuasiNeutralToy calculates solution with SaddlePointSolver.""" - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from struphy.feec.basis_projection_ops import BasisProjectionOperators from struphy.feec.mass import WeightedMassOperators @@ -224,7 +224,7 @@ def test_propagator1D(Nel, p, spl_kind, dirichlet_bc, mapping, epsilon, dt): def test_propagator2D(Nel, p, spl_kind, dirichlet_bc, mapping, epsilon, dt): """Test saddle-point-solver by propagator TwoFluidQuasiNeutralFull. Use manufactured solutions from perturbations to verify h- and p-convergence when model TwoFluidQuasiNeutralToy calculates solution with SaddlePointSolver. Allow a certain error after one time step, save this solution and compare the follwing timesteps with this solution but with less tolerance. Shows that the solver can stay in a steady state solution.""" - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from struphy.feec.basis_projection_ops import BasisProjectionOperators from struphy.feec.mass import WeightedMassOperators @@ -306,7 +306,7 @@ def test_propagator2D(Nel, p, spl_kind, dirichlet_bc, mapping, epsilon, dt): "ManufacturedSolutionPotential": { "given_in_basis": "physical", "dimension": "2D", - } + }, } uvec.initialize_coeffs(domain=domain, pert_params=pp_u) diff --git a/src/struphy/linear_algebra/tests/test_saddlepoint_massmatrices.py b/src/struphy/linear_algebra/tests/test_saddlepoint_massmatrices.py index 67de947e0..823584bc9 100644 --- a/src/struphy/linear_algebra/tests/test_saddlepoint_massmatrices.py +++ b/src/struphy/linear_algebra/tests/test_saddlepoint_massmatrices.py @@ -13,9 +13,9 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m import time - import numpy as np + import cunumpy as xp import scipy as sc - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.basic import IdentityOperator from psydac.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace @@ -107,7 +107,7 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m Cnp = derhamnumpy.curl.toarray() # Dnp = D.toarray() # Cnp = C.toarray() - if derham.with_local_projectors == True: + if derham.with_local_projectors: S21np = S21.toarray else: S21np = S21.toarray_struphy() @@ -121,7 +121,7 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m Cnp = derhamnumpy.curl.tosparse() # Dnp = D.tosparse() # Cnp = C.tosparse() - if derham.with_local_projectors == True: + if derham.with_local_projectors: S21np = S21.tosparse else: S21np = S21.toarray_struphy(is_sparse=True) @@ -132,12 +132,12 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m A11np = M2np / dt + nu * (Dnp.T @ M3np @ Dnp + S21np.T @ Cnp.T @ M2np @ Cnp @ S21np) - M2Bnp if method_to_solve in ("DirectNPInverse", "InexactNPInverse"): A22np = ( - stab_sigma * np.identity(A11np.shape[0]) + stab_sigma * xp.identity(A11np.shape[0]) + nue * (Dnp.T @ M3np @ Dnp + S21np.T @ Cnp.T @ M2np @ Cnp @ S21np) + M2Bnp ) # Preconditioner - _A22np_pre = stab_sigma * np.identity(A22np.shape[0]) # + nue*(Dnp.T @ M3np @ Dnp) + _A22np_pre = stab_sigma * xp.identity(A22np.shape[0]) # + nue*(Dnp.T @ M3np @ Dnp) _A11np_pre = M2np / dt # + nu * (Dnp.T @ M3np @ Dnp) elif method_to_solve in ("SparseSolver", "ScipySparse"): A22np = ( @@ -201,9 +201,9 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m - (B[0, 1].T).dot(y1_rdm) ) TestDiv = -B1.dot(x1) + B2.dot(x2) - RestDiv = np.linalg.norm(TestDiv.toarray()) - RestA = np.linalg.norm(TestA.toarray()) - RestAe = np.linalg.norm(TestAe.toarray()) + RestDiv = xp.linalg.norm(TestDiv.toarray()) + RestA = xp.linalg.norm(TestA.toarray()) + RestAe = xp.linalg.norm(TestAe.toarray()) print(f"{RestA =}") print(f"{RestAe =}") print(f"{RestDiv =}") @@ -218,10 +218,10 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m - (nue * (Dnp.T @ M3np @ Dnp + S21np.T @ Cnp.T @ M2np @ Cnp @ S21np) + M2Bnp).dot(x2np) - B2np.T.dot(ynp) ) - RestAnp = np.linalg.norm(TestAnp) - RestAenp = np.linalg.norm(TestAenp) + RestAnp = xp.linalg.norm(TestAnp) + RestAenp = xp.linalg.norm(TestAenp) TestDivnp = -B1np.dot(x1np) + B2np.dot(x2np) - RestDivnp = np.linalg.norm(TestDivnp) + RestDivnp = xp.linalg.norm(TestDivnp) print(f"{RestAnp =}") print(f"{RestAenp =}") print(f"{RestDivnp =}") @@ -242,7 +242,7 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m TestA11dot = TestA11.dot(x1) compare_arrays(TestA11dot, TestA11composeddot, mpi_rank, atol=1e-5) # compare_arrays(TestA11dot, TestA11npdot, mpi_rank, atol=1e-5) - print(f"Comparison numpy to psydac succesfull.") + print("Comparison numpy to psydac succesfull.") M2pre = MassMatrixPreconditioner(mass_mats.M2) @@ -270,7 +270,7 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m x_uzawa = {} x_uzawa[0] = x_u x_uzawa[1] = x_ue - if show_plots == True: + if show_plots: _plot_residual_norms(residual_norms) elif method_for_solving == "SaddlePointSolverGMRES": # Wrong initialization to check if changed @@ -296,22 +296,22 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m elapsed_time = end_time - start_time print(f"Method execution time: {elapsed_time:.6f} seconds") - if isinstance(x_uzawa[0], np.ndarray): - # Output as np.ndarray + if isinstance(x_uzawa[0], xp.ndarray): + # Output as xp.ndarray Rx1 = x1np - x_uzawa[0] Rx2 = x2np - x_uzawa[1] Ry = ynp - y_uzawa - residualx_normx1 = np.linalg.norm(Rx1) - residualx_normx2 = np.linalg.norm(Rx2) - residualy_norm = np.linalg.norm(Ry) + residualx_normx1 = xp.linalg.norm(Rx1) + residualx_normx2 = xp.linalg.norm(Rx2) + residualy_norm = xp.linalg.norm(Ry) TestRest1 = F1np - A11np.dot(x_uzawa[0]) - B1np.T.dot(y_uzawa) - TestRest1val = np.max(abs(TestRest1)) + TestRest1val = xp.max(abs(TestRest1)) Testoldy1 = F1np - A11np.dot(x_uzawa[0]) - B1np.T.dot(ynp) - Testoldy1val = np.max(abs(Testoldy1)) + Testoldy1val = xp.max(abs(Testoldy1)) TestRest2 = F2np - A22np.dot(x_uzawa[1]) - B2np.T.dot(y_uzawa) - TestRest2val = np.max(abs(TestRest2)) + TestRest2val = xp.max(abs(TestRest2)) Testoldy2 = F2np - A22np.dot(x_uzawa[1]) - B2np.T.dot(ynp) - Testoldy2val = np.max(abs(Testoldy2)) + Testoldy2val = xp.max(abs(Testoldy2)) print(f"{TestRest1val =}") print(f"{TestRest2val =}") print(f"{Testoldy1val =}") @@ -323,24 +323,24 @@ def test_saddlepointsolver(method_for_solving, Nel, p, spl_kind, dirichlet_bc, m compare_arrays(y1_rdm, y_uzawa, mpi_rank, atol=1e-5) compare_arrays(x1, x_uzawa[0], mpi_rank, atol=1e-5) compare_arrays(x2, x_uzawa[1], mpi_rank, atol=1e-5) - print(f"{info = }") + print(f"{info =}") elif isinstance(x_uzawa[0], BlockVector): # Output as Blockvector Rx1 = x1 - x_uzawa[0] Rx2 = x2 - x_uzawa[1] Ry = y1_rdm - y_uzawa - residualx_normx1 = np.linalg.norm(Rx1.toarray()) - residualx_normx2 = np.linalg.norm(Rx2.toarray()) - residualy_norm = np.linalg.norm(Ry.toarray()) + residualx_normx1 = xp.linalg.norm(Rx1.toarray()) + residualx_normx2 = xp.linalg.norm(Rx2.toarray()) + residualy_norm = xp.linalg.norm(Ry.toarray()) TestRest1 = F1 - A11.dot(x_uzawa[0]) - B1T.dot(y_uzawa) - TestRest1val = np.max(abs(TestRest1.toarray())) + TestRest1val = xp.max(abs(TestRest1.toarray())) Testoldy1 = F1 - A11.dot(x_uzawa[0]) - B1T.dot(y1_rdm) - Testoldy1val = np.max(abs(Testoldy1.toarray())) + Testoldy1val = xp.max(abs(Testoldy1.toarray())) TestRest2 = F2 - A22.dot(x_uzawa[1]) - B2T.dot(y_uzawa) - TestRest2val = np.max(abs(TestRest2.toarray())) + TestRest2val = xp.max(abs(TestRest2.toarray())) Testoldy2 = F2 - A22.dot(x_uzawa[1]) - B2T.dot(y1_rdm) - Testoldy2val = np.max(abs(Testoldy2.toarray())) + Testoldy2val = xp.max(abs(Testoldy2.toarray())) # print(f"{TestRest1val =}") # print(f"{TestRest2val =}") # print(f"{Testoldy1val =}") @@ -372,15 +372,15 @@ def _plot_residual_norms(residual_norms): def _plot_velocity(data_reshaped): + import cunumpy as xp import matplotlib import matplotlib.pyplot as plt - import numpy as np matplotlib.use("Agg") - x = np.linspace(0, 1, 30) - y = np.linspace(0, 1, 30) - X, Y = np.meshgrid(x, y) + x = xp.linspace(0, 1, 30) + y = xp.linspace(0, 1, 30) + X, Y = xp.meshgrid(x, y) plt.figure(figsize=(6, 5)) plt.imshow(data_reshaped.T, cmap="viridis", origin="lower", extent=[0, 1, 0, 1]) diff --git a/src/struphy/linear_algebra/tests/test_stencil_dot_kernels.py b/src/struphy/linear_algebra/tests/test_stencil_dot_kernels.py index c66a67aa0..d2c2238ff 100644 --- a/src/struphy/linear_algebra/tests/test_stencil_dot_kernels.py +++ b/src/struphy/linear_algebra/tests/test_stencil_dot_kernels.py @@ -1,7 +1,6 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [12]) @pytest.mark.parametrize("p", [1, 2, 3]) @pytest.mark.parametrize("spl_kind", [False, True]) @@ -14,9 +13,9 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): a) the result from kernel in struphy.linear_algebra.stencil_dot_kernels.matvec_1d_kernel b) the result from Stencil .dot with precompiled=True""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix, StencilVector from struphy.feec.psydac_derham import Derham @@ -26,7 +25,6 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): PSYDAC_BACKEND_GPYCCEL["flags"] = "-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none" comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() if rank == 0: @@ -80,8 +78,8 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): mat_pre._data[p_out + i_loc, d1] = m - i # random vector - # np.random.seed(123) - x[s_in : e_in + 1] = np.random.rand(domain.coeff_space.npts[0]) + # xp.random.seed(123) + x[s_in : e_in + 1] = xp.random.rand(domain.coeff_space.npts[0]) if rank == 0: print(f"spl_kind={spl_kind}") @@ -120,11 +118,10 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): print("\nout_ker=", out_ker._data) print("\nout_pre=", out_pre._data) - assert np.allclose(out_ker._data, out._data) - assert np.allclose(out_pre._data, out._data) + assert xp.allclose(out_ker._data, out._data) + assert xp.allclose(out_pre._data, out._data) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[12, 16, 20]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[True, False, False]]) @@ -137,9 +134,9 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): a) the result from kernel in struphy.linear_algebra.stencil_dot_kernels.matvec_1d_kernel b) the result from Stencil .dot with precompiled=True""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix, StencilVector from struphy.feec.psydac_derham import Derham @@ -149,7 +146,6 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): PSYDAC_BACKEND_GPYCCEL["flags"] = "-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none" comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() if rank == 0: @@ -181,16 +177,16 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): x = StencilVector(domain.coeff_space) out_ker = StencilVector(codomain.coeff_space) - s_out = np.array(mat.codomain.starts) - e_out = np.array(mat.codomain.ends) - p_out = np.array(mat.codomain.pads) - s_in = np.array(mat.domain.starts) - e_in = np.array(mat.domain.ends) - p_in = np.array(mat.domain.pads) + s_out = xp.array(mat.codomain.starts) + e_out = xp.array(mat.codomain.ends) + p_out = xp.array(mat.codomain.pads) + s_in = xp.array(mat.domain.starts) + e_in = xp.array(mat.domain.ends) + p_in = xp.array(mat.domain.pads) # random matrix - np.random.seed(123) - tmp1 = np.random.rand(*codomain.coeff_space.npts, *[2 * q + 1 for q in p]) + xp.random.seed(123) + tmp1 = xp.random.rand(*codomain.coeff_space.npts, *[2 * q + 1 for q in p]) mat[ s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, @@ -211,7 +207,7 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): ] # random vector - tmp2 = np.random.rand(*domain.coeff_space.npts) + tmp2 = xp.random.rand(*domain.coeff_space.npts) x[ s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, @@ -230,7 +226,7 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): # kernel matvec add = [int(end_in >= end_out) for end_in, end_out in zip(mat.domain.ends, mat.codomain.ends)] - add = np.array(add) + add = xp.array(add) matvec_3d_kernel(mat._data, x._data, out_ker._data, s_in, p_in, add, s_out, e_out, p_out) # precompiled .dot @@ -257,12 +253,12 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): print("\nout_ker[2]=", out_ker._data[p_out[0], p_out[1], :]) print("\nout_pre[2]=", out_pre._data[p_out[0], p_out[1], :]) - assert np.allclose( + assert xp.allclose( out_ker[s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, s_out[2] : e_out[2] + 1], out[s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, s_out[2] : e_out[2] + 1], ) - assert np.allclose( + assert xp.allclose( out_pre[s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, s_out[2] : e_out[2] + 1], out[s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, s_out[2] : e_out[2] + 1], ) diff --git a/src/struphy/linear_algebra/tests/test_stencil_transpose_kernels.py b/src/struphy/linear_algebra/tests/test_stencil_transpose_kernels.py index 92f65410d..1125a980c 100644 --- a/src/struphy/linear_algebra/tests/test_stencil_transpose_kernels.py +++ b/src/struphy/linear_algebra/tests/test_stencil_transpose_kernels.py @@ -1,7 +1,6 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [12]) @pytest.mark.parametrize("p", [1, 2, 3]) @pytest.mark.parametrize("spl_kind", [False, True]) @@ -14,9 +13,9 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): a) the result from kernel in struphy.linear_algebra.stencil_transpose_kernels.transpose_1d_kernel b) the result from Stencil .transpose with precompiled=True""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix from struphy.feec.psydac_derham import Derham @@ -26,7 +25,6 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): PSYDAC_BACKEND_GPYCCEL["flags"] = "-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none" comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() if rank == 0: @@ -114,11 +112,10 @@ def test_1d(Nel, p, spl_kind, domain_ind, codomain_ind): print("\nmatT_pre=", matT_pre._data) print("\nmatT_pre.toarray=\n", matT_pre.toarray()) - assert np.allclose(matT_ker[s_in : e_in + 1, :], matT[s_in : e_in + 1, :]) - assert np.allclose(matT_pre[s_in : e_in + 1, :], matT[s_in : e_in + 1, :]) + assert xp.allclose(matT_ker[s_in : e_in + 1, :], matT[s_in : e_in + 1, :]) + assert xp.allclose(matT_pre[s_in : e_in + 1, :], matT[s_in : e_in + 1, :]) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[12, 16, 20]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[True, False, False]]) @@ -131,9 +128,9 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): a) the result from kernel in struphy.linear_algebra.stencil_transpose_kernels.transpose_3d_kernel b) the result from Stencil .transpose with precompiled=True""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix from struphy.feec.psydac_derham import Derham @@ -143,7 +140,6 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): PSYDAC_BACKEND_GPYCCEL["flags"] = "-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none" comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() if rank == 0: @@ -174,16 +170,16 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): mat_pre = StencilMatrix(domain.coeff_space, codomain.coeff_space, backend=PSYDAC_BACKEND_GPYCCEL, precompiled=True) matT_ker = StencilMatrix(codomain.coeff_space, domain.coeff_space) - s_out = np.array(mat.codomain.starts) - e_out = np.array(mat.codomain.ends) - p_out = np.array(mat.codomain.pads) - s_in = np.array(mat.domain.starts) - e_in = np.array(mat.domain.ends) - p_in = np.array(mat.domain.pads) + s_out = xp.array(mat.codomain.starts) + e_out = xp.array(mat.codomain.ends) + p_out = xp.array(mat.codomain.pads) + s_in = xp.array(mat.domain.starts) + e_in = xp.array(mat.domain.ends) + p_in = xp.array(mat.domain.pads) # random matrix - np.random.seed(123) - tmp1 = np.random.rand(*codomain.coeff_space.npts, *[2 * q + 1 for q in p]) + xp.random.seed(123) + tmp1 = xp.random.rand(*codomain.coeff_space.npts, *[2 * q + 1 for q in p]) mat[ s_out[0] : e_out[0] + 1, s_out[1] : e_out[1] + 1, @@ -212,7 +208,7 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): # kernel transpose add = [int(end_out >= end_in) for end_in, end_out in zip(mat.domain.ends, mat.codomain.ends)] - add = np.array(add) + add = xp.array(add) transpose_3d_kernel(mat._data, matT_ker._data, s_out, p_out, add, s_in, e_in, p_in) # precompiled transpose @@ -241,12 +237,12 @@ def test_3d(Nel, p, spl_kind, domain_ind, codomain_ind): print("\nmatT_ker[2]=", matT_ker._data[p_in[0], p_in[1], :, 1, 1, :]) print("\nmatT_pre[2]=", matT_pre._data[p_in[0], p_in[1], :, 1, 1, :]) - assert np.allclose( + assert xp.allclose( matT_ker[s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, s_in[2] : e_in[2] + 1], matT[s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, s_in[2] : e_in[2] + 1], ) - assert np.allclose( + assert xp.allclose( matT_pre[s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, s_in[2] : e_in[2] + 1], matT[s_in[0] : e_in[0] + 1, s_in[1] : e_in[1] + 1, s_in[2] : e_in[2] + 1], ) diff --git a/src/struphy/main.py b/src/struphy/main.py index ecf1f2968..ecdbcd986 100644 --- a/src/struphy/main.py +++ b/src/struphy/main.py @@ -8,10 +8,11 @@ import time from typing import Optional, TypedDict +import cunumpy as xp import h5py -import numpy as np from line_profiler import profile -from mpi4py import MPI +from psydac.ddm.mpi import MockMPI +from psydac.ddm.mpi import mpi as MPI from pyevtk.hl import gridToVTK from struphy.fields_background.base import FluidEquilibrium, FluidEquilibriumWithB @@ -68,9 +69,16 @@ def run( Absolute path to .py parameter file. """ - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - size = comm.Get_size() + if isinstance(MPI, MockMPI): + comm = None + rank = 0 + size = 1 + Barrier = lambda: None + else: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + size = comm.Get_size() + Barrier = comm.Barrier if rank == 0: print("") @@ -94,6 +102,7 @@ def run( save_step = env.save_step sort_step = env.sort_step num_clones = env.num_clones + use_mpi = (not comm is None,) meta = {} meta["platform"] = sysconfig.get_platform() @@ -102,6 +111,7 @@ def run( meta["parameter file"] = params_path meta["output folder"] = path_out meta["MPI processes"] = size + meta["use MPI.COMM_WORLD"] = use_mpi meta["number of domain clones"] = num_clones meta["restart"] = restart meta["max wall-clock [min]"] = max_runtime @@ -174,7 +184,7 @@ def run( clone_config.print_particle_config() model.clone_config = clone_config - comm.Barrier() + Barrier() ## configure model instance @@ -196,22 +206,7 @@ def run( # domain and fluid background model.setup_domain_and_equil(domain, equil) - # default grid - if grid is None: - Nel = (16, 16, 16) - if rank == 0: - print(f"\nNo grid specified - using TensorProductGrid with {Nel = }.") - grid = grids.TensorProductGrid(Nel=Nel) - - # allocate derham-related objects - if derham_opts is None: - p = (3, 3, 3) - spl_kind = (False, False, False) - if rank == 0: - print( - f"\nNo Derham options specified - creating Derham with {p = } and {spl_kind = } for projecting equilibrium." - ) - derham_opts = DerhamOptions(p=p, spl_kind=spl_kind) + # feec model.allocate_feec(grid, derham_opts) # equation paramters @@ -238,9 +233,9 @@ def run( # store geometry vtk if rank == 0: grids_log = [ - np.linspace(1e-6, 1.0, 32), - np.linspace(0.0, 1.0, 32), - np.linspace(0.0, 1.0, 32), + xp.linspace(1e-6, 1.0, 32), + xp.linspace(0.0, 1.0, 32), + xp.linspace(0.0, 1.0, 32), ] tmp = model.domain(*grids_log) @@ -265,9 +260,9 @@ def run( # time quantities (current time value, value in seconds and index) time_state = {} - time_state["value"] = np.zeros(1, dtype=float) - time_state["value_sec"] = np.zeros(1, dtype=float) - time_state["index"] = np.zeros(1, dtype=int) + time_state["value"] = xp.zeros(1, dtype=float) + time_state["value_sec"] = xp.zeros(1, dtype=float) + time_state["index"] = xp.zeros(1, dtype=int) # add time quantities to data object for saving for key, val in time_state.items(): @@ -313,7 +308,7 @@ def run( # time loop run_time_now = 0.0 while True: - comm.Barrier() + Barrier() # stop time loop? break_cond_1 = time_state["value"][0] >= Tend @@ -341,22 +336,23 @@ def run( t1 = time.time() if rank == 0 and verbose: message = "Particles sorted | wall clock [s]: {0:8.4f} | sorting duration [s]: {1:8.4f}".format( - run_time_now * 60, t1 - t0 + run_time_now * 60, + t1 - t0, ) print(message, end="\n") print() + # update time and index (round time to 10 decimals for a clean time grid!) + time_state["value"][0] = round(time_state["value"][0] + dt, 10) + time_state["value_sec"][0] = round(time_state["value_sec"][0] + dt * model.units.t, 10) + time_state["index"][0] += 1 + # perform one time step dt t0 = time.time() with ProfileManager.profile_region("model.integrate"): model.integrate(dt, split_algo) t1 = time.time() - # update time and index (round time to 10 decimals for a clean time grid!) - time_state["value"][0] = round(time_state["value"][0] + dt, 10) - time_state["value_sec"][0] = round(time_state["value_sec"][0] + dt * model.units.t, 10) - time_state["index"][0] += 1 - run_time_now = (time.time() - start_simulation) / 60 # update diagnostics data and save data @@ -386,10 +382,12 @@ def run( message = "time step: " + step + "/" + str(total_steps) message += " | " + "time: {0:10.5f}/{1:10.5f}".format(time_state["value"][0], Tend) message += " | " + "phys. time [s]: {0:12.10f}/{1:12.10f}".format( - time_state["value_sec"][0], Tend * model.units.t + time_state["value_sec"][0], + Tend * model.units.t, ) message += " | " + "wall clock [s]: {0:8.4f} | last step duration [s]: {1:8.4f}".format( - run_time_now * 60, t1 - t0 + run_time_now * 60, + t1 - t0, ) print(message, end="\n") @@ -399,7 +397,7 @@ def run( # =================================================================== meta["wall-clock time[min]"] = (end_simulation - start_simulation) / 60 - comm.Barrier() + Barrier() if rank == 0: # save meta-data @@ -479,7 +477,7 @@ def pproc( file = h5py.File(os.path.join(path, "data/", "data_proc0.hdf5"), "r") # save time grid at which post-processing data is created - np.save(os.path.join(path_pproc, "t_grid.npy"), file["time/value"][::step].copy()) + xp.save(os.path.join(path_pproc, "t_grid.npy"), file["time/value"][::step].copy()) if "feec" in file.keys(): exist_fields = True @@ -516,7 +514,10 @@ def pproc( if physical: point_data_phy, grids_log, grids_phy = eval_femfields( - params_in, fields, celldivide=[celldivide] * 3, physical=True + params_in, + fields, + celldivide=[celldivide] * 3, + physical=True, ) # directory for field data @@ -638,28 +639,28 @@ def __init__(self, path: str): self._f = {} self._spline_values = {} self._n_sph = {} - self.grids_log: list[np.ndarray] = None - self.grids_phy: list[np.ndarray] = None - self.t_grid: np.ndarray = None + self.grids_log: list[xp.ndarray] = None + self.grids_phy: list[xp.ndarray] = None + self.t_grid: xp.ndarray = None @property - def orbits(self) -> dict[str, np.ndarray]: + def orbits(self) -> dict[str, xp.ndarray]: """Keys: species name. Values: 3d arrays indexed by (n, p, a), where 'n' is the time index, 'p' the particle index and 'a' the attribute index.""" return self._orbits @property - def f(self) -> dict[str, dict[str, dict[str, np.ndarray]]]: - """Keys: species name. Values: dicts of slice names ('e1_v1' etc.) holding dicts of corresponding np.arrays for plotting.""" + def f(self) -> dict[str, dict[str, dict[str, xp.ndarray]]]: + """Keys: species name. Values: dicts of slice names ('e1_v1' etc.) holding dicts of corresponding xp.arrays for plotting.""" return self._f @property - def spline_values(self) -> dict[str, dict[str, np.ndarray]]: + def spline_values(self) -> dict[str, dict[str, xp.ndarray]]: """Keys: species name. Values: dicts of variable names with values being 3d arrays on the grid.""" return self._spline_values @property - def n_sph(self) -> dict[str, dict[str, dict[str, np.ndarray]]]: - """Keys: species name. Values: dicts of view names ('view_0' etc.) holding dicts of corresponding np.arrays for plotting.""" + def n_sph(self) -> dict[str, dict[str, dict[str, xp.ndarray]]]: + """Keys: species name. Values: dicts of view names ('view_0' etc.) holding dicts of corresponding xp.arrays for plotting.""" return self._n_sph @property @@ -689,18 +690,6 @@ def Nattr(self) -> dict[str, int]: self._Nattr[spec] = orbs.shape[2] return self._Nattr - @property - def spline_grid_resolution(self): - if self.grids_log is not None: - res = [x.size for x in self.grids_log] - else: - res = None - return res - - @property - def time_grid_size(self): - return self.t_grid.size - def load_data(path: str) -> SimData: """Load data generated during post-processing. @@ -714,12 +703,12 @@ def load_data(path: str) -> SimData: path_pproc = os.path.join(path, "post_processing") assert os.path.exists(path_pproc), f"Path {path_pproc} does not exist, run 'pproc' first?" print("\n*** Loading post-processed simulation data:") - print(f"{path = }") + print(f"{path =}") simdata = SimData(path) # load time grid - simdata.t_grid = np.load(os.path.join(path_pproc, "t_grid.npy")) + simdata.t_grid = xp.load(os.path.join(path_pproc, "t_grid.npy")) # data paths path_fields = os.path.join(path_pproc, "fields_data") @@ -753,7 +742,7 @@ def load_data(path: str) -> SimData: if os.path.exists(path_kinetic): # species folders species = next(os.walk(path_kinetic))[1] - print(f"{species = }") + print(f"{species =}") for spec in species: path_spec = os.path.join(path_kinetic, spec) wlk = os.walk(path_spec) @@ -770,9 +759,9 @@ def load_data(path: str) -> SimData: # print(f"{file = }") if ".npy" in file: step = int(file.split(".")[0].split("_")[-1]) - tmp = np.load(os.path.join(path_dat, file)) + tmp = xp.load(os.path.join(path_dat, file)) if n == 0: - simdata._orbits[spec] = np.zeros((Nt, *tmp.shape), dtype=float) + simdata._orbits[spec] = xp.zeros((Nt, *tmp.shape), dtype=float) simdata._orbits[spec][step] = tmp n += 1 @@ -787,7 +776,7 @@ def load_data(path: str) -> SimData: # print(f"{files = }") for file in files: name = file.split(".")[0] - tmp = np.load(os.path.join(path_dat, sli, file)) + tmp = xp.load(os.path.join(path_dat, sli, file)) # print(f"{name = }") simdata._f[spec][sli][name] = tmp @@ -802,33 +791,41 @@ def load_data(path: str) -> SimData: # print(f"{files = }") for file in files: name = file.split(".")[0] - tmp = np.load(os.path.join(path_dat, sli, file)) + tmp = xp.load(os.path.join(path_dat, sli, file)) # print(f"{name = }") simdata._n_sph[spec][sli][name] = tmp else: - print(f"{folder = }") + print(f"{folder =}") raise NotImplementedError print("\nThe following data has been loaded:") - print(f"{simdata.time_grid_size = }") - print(f"{simdata.spline_grid_resolution = }") - print(f"\nsimdata.spline_values:") + print("\ngrids:") + print(f"{simdata.t_grid.shape =}") + if simdata.grids_log is not None: + print(f"{simdata.grids_log[0].shape =}") + print(f"{simdata.grids_log[1].shape =}") + print(f"{simdata.grids_log[2].shape =}") + if simdata.grids_phy is not None: + print(f"{simdata.grids_phy[0].shape =}") + print(f"{simdata.grids_phy[1].shape =}") + print(f"{simdata.grids_phy[2].shape =}") + print("\nsimdata.spline_values:") for k, v in simdata.spline_values.items(): print(f" {k}") for kk, vv in v.items(): print(f" {kk}") - print(f"\nsimdata.orbits:") + print("\nsimdata.orbits:") for k, v in simdata.orbits.items(): print(f" {k}") - print(f"\nsimdata.f:") + print("\nsimdata.f:") for k, v in simdata.f.items(): print(f" {k}") for kk, vv in v.items(): print(f" {kk}") for kkk, vvv in vv.items(): print(f" {kkk}") - print(f"\nsimdata.n_sph:") + print("\nsimdata.n_sph:") for k, v in simdata.n_sph.items(): print(f" {k}") for kk, vv in v.items(): diff --git a/src/struphy/models/__init__.py b/src/struphy/models/__init__.py index 3878287f4..0153467f6 100644 --- a/src/struphy/models/__init__.py +++ b/src/struphy/models/__init__.py @@ -1,74 +1,74 @@ -from struphy.models.fluid import ( - ColdPlasma, - EulerSPH, - HasegawaWakatani, - LinearExtendedMHDuniform, - LinearMHD, - ViscoresistiveDeltafMHD, - ViscoresistiveDeltafMHD_with_q, - ViscoresistiveLinearMHD, - ViscoresistiveLinearMHD_with_q, - ViscoresistiveMHD, - ViscoresistiveMHD_with_p, - ViscoresistiveMHD_with_q, - ViscousFluid, -) -from struphy.models.hybrid import ColdPlasmaVlasov, LinearMHDDriftkineticCC, LinearMHDVlasovCC, LinearMHDVlasovPC -from struphy.models.kinetic import ( - DriftKineticElectrostaticAdiabatic, - LinearVlasovAmpereOneSpecies, - LinearVlasovMaxwellOneSpecies, - VlasovAmpereOneSpecies, - VlasovMaxwellOneSpecies, -) -from struphy.models.toy import ( - DeterministicParticleDiffusion, - GuidingCenter, - Maxwell, - Poisson, - PressureLessSPH, - RandomParticleDiffusion, - ShearAlfven, - TwoFluidQuasiNeutralToy, - VariationalBarotropicFluid, - VariationalCompressibleFluid, - VariationalPressurelessFluid, - Vlasov, -) +# from struphy.models.fluid import ( +# ColdPlasma, +# EulerSPH, +# HasegawaWakatani, +# LinearExtendedMHDuniform, +# LinearMHD, +# ViscoresistiveDeltafMHD, +# ViscoresistiveDeltafMHD_with_q, +# ViscoresistiveLinearMHD, +# ViscoresistiveLinearMHD_with_q, +# ViscoresistiveMHD, +# ViscoresistiveMHD_with_p, +# ViscoresistiveMHD_with_q, +# ViscousFluid, +# ) +# from struphy.models.hybrid import ColdPlasmaVlasov, LinearMHDDriftkineticCC, LinearMHDVlasovCC, LinearMHDVlasovPC +# from struphy.models.kinetic import ( +# DriftKineticElectrostaticAdiabatic, +# LinearVlasovAmpereOneSpecies, +# LinearVlasovMaxwellOneSpecies, +# VlasovAmpereOneSpecies, +# VlasovMaxwellOneSpecies, +# ) +# from struphy.models.toy import ( +# DeterministicParticleDiffusion, +# GuidingCenter, +# Maxwell, +# Poisson, +# PressureLessSPH, +# RandomParticleDiffusion, +# ShearAlfven, +# TwoFluidQuasiNeutralToy, +# VariationalBarotropicFluid, +# VariationalCompressibleFluid, +# VariationalPressurelessFluid, +# Vlasov, +# ) -__all__ = [ - "Maxwell", - "Vlasov", - "GuidingCenter", - "ShearAlfven", - "VariationalPressurelessFluid", - "VariationalBarotropicFluid", - "VariationalCompressibleFluid", - "Poisson", - "DeterministicParticleDiffusion", - "RandomParticleDiffusion", - "PressureLessSPH", - "TwoFluidQuasiNeutralToy", - "LinearMHD", - "LinearExtendedMHDuniform", - "ColdPlasma", - "ViscoresistiveMHD", - "ViscousFluid", - "ViscoresistiveMHD_with_p", - "ViscoresistiveLinearMHD", - "ViscoresistiveDeltafMHD", - "ViscoresistiveMHD_with_q", - "ViscoresistiveLinearMHD_with_q", - "ViscoresistiveDeltafMHD_with_q", - "EulerSPH", - "HasegawaWakatani", - "LinearMHDVlasovCC", - "LinearMHDVlasovPC", - "LinearMHDDriftkineticCC", - "ColdPlasmaVlasov", - "VlasovAmpereOneSpecies", - "VlasovMaxwellOneSpecies", - "LinearVlasovAmpereOneSpecies", - "LinearVlasovMaxwellOneSpecies", - "DriftKineticElectrostaticAdiabatic", -] +# __all__ = [ +# "Maxwell", +# "Vlasov", +# "GuidingCenter", +# "ShearAlfven", +# "VariationalPressurelessFluid", +# "VariationalBarotropicFluid", +# "VariationalCompressibleFluid", +# "Poisson", +# "DeterministicParticleDiffusion", +# "RandomParticleDiffusion", +# "PressureLessSPH", +# "TwoFluidQuasiNeutralToy", +# "LinearMHD", +# "LinearExtendedMHDuniform", +# "ColdPlasma", +# "ViscoresistiveMHD", +# "ViscousFluid", +# "ViscoresistiveMHD_with_p", +# "ViscoresistiveLinearMHD", +# "ViscoresistiveDeltafMHD", +# "ViscoresistiveMHD_with_q", +# "ViscoresistiveLinearMHD_with_q", +# "ViscoresistiveDeltafMHD_with_q", +# "EulerSPH", +# "HasegawaWakatani", +# "LinearMHDVlasovCC", +# "LinearMHDVlasovPC", +# "LinearMHDDriftkineticCC", +# "ColdPlasmaVlasov", +# "VlasovAmpereOneSpecies", +# "VlasovMaxwellOneSpecies", +# "LinearVlasovAmpereOneSpecies", +# "LinearVlasovMaxwellOneSpecies", +# "DriftKineticElectrostaticAdiabatic", +# ] diff --git a/src/struphy/models/base.py b/src/struphy/models/base.py index 780c3efff..b484397a0 100644 --- a/src/struphy/models/base.py +++ b/src/struphy/models/base.py @@ -5,10 +5,11 @@ from functools import reduce from textwrap import indent -import numpy as np +import cunumpy as xp import yaml from line_profiler import profile -from mpi4py import MPI +from psydac.ddm.mpi import MockMPI +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilVector import struphy @@ -105,7 +106,7 @@ def setup_domain_and_equil(self, domain: Domain, equil: FluidEquilibrium): if MPI.COMM_WORLD.Get_rank() == 0 and self.verbose: print("\nDOMAIN:") - print(f"type:".ljust(25), self.domain.__class__.__name__) + print("type:".ljust(25), self.domain.__class__.__name__) for key, val in self.domain.params.items(): if key not in {"cx", "cy", "cz"}: print((key + ":").ljust(25), val) @@ -171,40 +172,59 @@ def allocate_feec(self, grid: TensorProductGrid, derham_opts: DerhamOptions): else: derham_comm = self.clone_config.sub_comm - self._derham = setup_derham( - grid, - derham_opts, - comm=derham_comm, - domain=self.domain, - verbose=self.verbose, - ) - - # create weighted mass operators - self._mass_ops = WeightedMassOperators( - self.derham, - self.domain, - verbose=self.verbose, - eq_mhd=self.equil, - ) - - # create projected equilibrium - if isinstance(self.equil, MHDequilibrium): - self._projected_equil = ProjectedMHDequilibrium( - self.equil, - self.derham, + if grid is None or derham_opts is None: + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\n{grid =}, {derham_opts =}: no Derham object set up.") + self._derham = None + else: + self._derham = setup_derham( + grid, + derham_opts, + comm=derham_comm, + domain=self.domain, + verbose=self.verbose, ) - elif isinstance(self.equil, FluidEquilibriumWithB): - self._projected_equil = ProjectedFluidEquilibriumWithB( - self.equil, + + # create weighted mass and basis operators + if self.derham is None: + self._mass_ops = None + self._basis_ops = None + else: + self._mass_ops = WeightedMassOperators( self.derham, + self.domain, + verbose=self.verbose, + eq_mhd=self.equil, ) - elif isinstance(self.equil, FluidEquilibrium): - self._projected_equil = ProjectedFluidEquilibrium( - self.equil, + + self._basis_ops = BasisProjectionOperators( self.derham, + self.domain, + verbose=self.verbose, + eq_mhd=self.equil, ) - else: + + # create projected equilibrium + if self.derham is None: self._projected_equil = None + else: + if isinstance(self.equil, MHDequilibrium): + self._projected_equil = ProjectedMHDequilibrium( + self.equil, + self.derham, + ) + elif isinstance(self.equil, FluidEquilibriumWithB): + self._projected_equil = ProjectedFluidEquilibriumWithB( + self.equil, + self.derham, + ) + elif isinstance(self.equil, FluidEquilibrium): + self._projected_equil = ProjectedFluidEquilibrium( + self.equil, + self.derham, + ) + else: + self._projected_equil = None def allocate_propagators(self): # set propagators base class attributes (then available to all propagators) @@ -212,12 +232,7 @@ def allocate_propagators(self): Propagator.domain = self.domain if self.derham is not None: Propagator.mass_ops = self.mass_ops - Propagator.basis_ops = BasisProjectionOperators( - self.derham, - self.domain, - verbose=self.verbose, - eq_mhd=self.equil, - ) + Propagator.basis_ops = self.basis_ops Propagator.projected_equil = self.projected_equil assert len(self.prop_list) > 0, "No propagators in this model, check the model class." @@ -260,11 +275,6 @@ def clone_config(self, new): assert isinstance(new, CloneConfig) or new is None self._clone_config = new - @property - def diagnostics(self): - """Dictionary of diagnostics.""" - return self._diagnostics - @property def domain(self): """Domain object, see :ref:`avail_mappings`.""" @@ -300,6 +310,11 @@ def mass_ops(self): """WeighteMassOperators object, see :ref:`mass_ops`.""" return self._mass_ops + @property + def basis_ops(self): + """Basis projection operators.""" + return self._basis_ops + @property def prop_list(self): """List of Propagator objects.""" @@ -331,6 +346,8 @@ def kwargs(self): @property def scalar_quantities(self): """A dictionary of scalar quantities to be saved during the simulation.""" + if not hasattr(self, "_scalar_quantities"): + self._scalar_quantities = {} return self._scalar_quantities @property @@ -411,13 +428,13 @@ def getFromDict(dataDict, mapList): def setInDict(dataDict, mapList, value): # Loop over dicitionary and creaty empty dicts where the path does not exist for k in range(len(mapList)): - if not mapList[k] in getFromDict(dataDict, mapList[:k]).keys(): + if mapList[k] not in getFromDict(dataDict, mapList[:k]).keys(): getFromDict(dataDict, mapList[:k])[mapList[k]] = {} getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value # make sure that the base keys are top-level keys for base_key in ["em_fields", "fluid", "kinetic"]: - if not base_key in dct.keys(): + if base_key not in dct.keys(): dct[base_key] = {} if isinstance(species, str): @@ -455,13 +472,13 @@ def add_scalar(self, name: str, variable: PICVariable | SPHVariable = None, comp assert isinstance(name, str), "name must be a string" if compute == "from_particles": - assert isinstance(variable, (PICVariable, SPHVariable)), f"Variable is needed when {compute = }" + assert isinstance(variable, (PICVariable, SPHVariable)), f"Variable is needed when {compute =}" if not hasattr(self, "_scalar_quantities"): self._scalar_quantities = {} self._scalar_quantities[name] = { - "value": np.empty(1, dtype=float), + "value": xp.empty(1, dtype=float), "variable": variable, "compute": compute, "summands": summands, @@ -507,17 +524,17 @@ def update_scalar(self, name, value=None): assert isinstance(value, float) # Create a numpy array to hold the scalar value - value_array = np.array([value], dtype=np.float64) + value_array = xp.array([value], dtype=xp.float64) # Perform MPI operations based on the compute flags - if "sum_world" in compute_operations: + if "sum_world" in compute_operations and not isinstance(MPI, MockMPI): MPI.COMM_WORLD.Allreduce( MPI.IN_PLACE, value_array, op=MPI.SUM, ) - if "sum_within_clone" in compute_operations: + if "sum_within_clone" in compute_operations and self.derham.comm is not None: self.derham.comm.Allreduce( MPI.IN_PLACE, value_array, @@ -545,7 +562,7 @@ def update_scalar(self, name, value=None): if "divide_n_mks" in compute_operations: # Initialize the total number of markers - n_mks_tot = np.array([variable.particles.Np]) + n_mks_tot = xp.array([variable.particles.Np]) value_array /= n_mks_tot # Update the scalar value @@ -623,6 +640,18 @@ def allocate_variables(self, verbose: bool = False): verbose=verbose, ) + # allocate memory for FE coeffs of fluid variables + if self.diagnostic_species: + for species, spec in self.diagnostic_species.items(): + assert isinstance(spec, DiagnosticSpecies) + for k, v in spec.variables.items(): + assert isinstance(v, FEECVariable) + v.allocate( + derham=self.derham, + domain=self.domain, + equil=self.equil, + ) + # TODO: allocate memory for FE coeffs of diagnostics # if self.params.diagnostic_fields is not None: # for key, val in self.diagnostics.items(): @@ -692,18 +721,18 @@ def update_markers_to_be_saved(self): for name, species in self.particle_species.items(): assert isinstance(species, ParticleSpecies) - assert len(species.variables) == 1, f"More than 1 variable per kinetic species is not allowed." + assert len(species.variables) == 1, "More than 1 variable per kinetic species is not allowed." for _, var in species.variables.items(): assert isinstance(var, PICVariable | SPHVariable) obj = var.particles assert isinstance(obj, Particles) if var.n_to_save > 0: - markers_on_proc = np.logical_and( + markers_on_proc = xp.logical_and( obj.markers[:, -1] >= 0.0, obj.markers[:, -1] < var.n_to_save, ) - n_markers_on_proc = np.count_nonzero(markers_on_proc) + n_markers_on_proc = xp.count_nonzero(markers_on_proc) var.saved_markers[:] = -1.0 var.saved_markers[:n_markers_on_proc] = obj.markers[markers_on_proc] @@ -717,7 +746,7 @@ def update_distr_functions(self): for name, species in self.particle_species.items(): assert isinstance(species, ParticleSpecies) - assert len(species.variables) == 1, f"More than 1 variable per kinetic species is not allowed." + assert len(species.variables) == 1, "More than 1 variable per kinetic species is not allowed." for _, var in species.variables.items(): assert isinstance(var, PICVariable | SPHVariable) obj = var.particles @@ -747,7 +776,7 @@ def update_distr_functions(self): h2 = 1 / obj.boxes_per_dim[1] h3 = 1 / obj.boxes_per_dim[2] - ndim = np.count_nonzero([d > 1 for d in obj.boxes_per_dim]) + ndim = xp.count_nonzero([d > 1 for d in obj.boxes_per_dim]) if ndim == 0: kernel_type = "gaussian_3d" else: @@ -771,7 +800,7 @@ def print_scalar_quantities(self): sq_str = "" for key, scalar_dict in self._scalar_quantities.items(): val = scalar_dict["value"] - assert not np.isnan(val[0]), f"Scalar {key} is {val[0]}." + assert not xp.isnan(val[0]), f"Scalar {key} is {val[0]}." sq_str += key + ": {:14.11f}".format(val[0]) + " " print(sq_str) @@ -933,8 +962,9 @@ def print_scalar_quantities(self): # if obj.coords == "vpara_mu": # obj.save_magnetic_moment() - # if val["space"] != "ParticlesSPH" and obj.f0.coords == "constants_of_motion": - # obj.save_constants_of_motion() + # obj.draw_markers(sort=True, verbose=self.verbose) + # if self.comm_world is not None: + # obj.mpi_sort_markers(do_test=True) # obj.initialize_weights( # reject_weights=obj.weights_params["reject_weights"], @@ -984,7 +1014,8 @@ def initialize_from_restart(self, data): obj._markers[:, :] = data.file["restart/" + key][-1, :, :] # important: sets holes attribute of markers! - obj.mpi_sort_markers(do_test=True) + if self.comm_world is not None: + obj.mpi_sort_markers(do_test=True) def initialize_data_output(self, data: DataContainer, size): """ @@ -1076,7 +1107,7 @@ def initialize_data_output(self, data: DataContainer, size): # save kinetic data in group 'kinetic/' for name, species in self.particle_species.items(): assert isinstance(species, ParticleSpecies) - assert len(species.variables) == 1, f"More than 1 variable per kinetic species is not allowed." + assert len(species.variables) == 1, "More than 1 variable per kinetic species is not allowed." for varname, var in species.variables.items(): assert isinstance(var, PICVariable | SPHVariable) obj = var.particles @@ -1141,7 +1172,7 @@ def show_options(cls): print( 'Options are given under the keyword "options" for each species dict. \ -Available options stand in lists as dict values.\nThe first entry of a list denotes the default value.' +Available options stand in lists as dict values.\nThe first entry of a list denotes the default value.', ) tab = " " @@ -1202,7 +1233,6 @@ def write_parameters_to_file(cls, parameters=None, file=None, save=True, prompt= import yaml - import struphy import struphy.utils.utils as utils # Read struphy state file @@ -1270,7 +1300,7 @@ def generate_default_parameter_file( file = open(path, "w") else: print("exiting ...") - return + exit() except FileNotFoundError: folder = os.path.join("/", *path.split("/")[:-1]) if not prompt: @@ -1282,7 +1312,7 @@ def generate_default_parameter_file( file = open(path, "x") else: print("exiting ...") - return + exit() file.write("from struphy.io.options import EnvironmentOptions, BaseUnits, Time\n") file.write("from struphy.geometry import domains\n") @@ -1301,15 +1331,15 @@ def generate_default_parameter_file( has_plasma = True species_params += f"model.{sn}.set_phys_params()\n" if isinstance(species, ParticleSpecies): - particle_params += f"\nloading_params = LoadingParameters()\n" - particle_params += f"weights_params = WeightsParameters()\n" - particle_params += f"boundary_params = BoundaryParameters()\n" + particle_params += "\nloading_params = LoadingParameters()\n" + particle_params += "weights_params = WeightsParameters()\n" + particle_params += "boundary_params = BoundaryParameters()\n" particle_params += f"model.{sn}.set_markers(loading_params=loading_params,\n" - txt = f"weights_params=weights_params,\n" + txt = "weights_params=weights_params,\n" particle_params += indent(txt, " " * len(f"model.{sn}.set_markers(")) - txt = f"boundary_params=boundary_params,\n" + txt = "boundary_params=boundary_params,\n" particle_params += indent(txt, " " * len(f"model.{sn}.set_markers(")) - txt = f")\n" + txt = ")\n" particle_params += indent(txt, " " * len(f"model.{sn}.set_markers(")) particle_params += f"model.{sn}.set_sorting_boxes()\n" particle_params += f"model.{sn}.set_save_data()\n" @@ -1330,32 +1360,40 @@ def generate_default_parameter_file( elif isinstance(var, PICVariable): has_pic = True - init_pert_pic = f"\n# if .add_initial_condition is not called, the background is the kinetic initial condition\n" - init_pert_pic += f"perturbation = perturbations.TorusModesCos()\n" + init_pert_pic = ( + "\n# if .add_initial_condition is not called, the background is the kinetic initial condition\n" + ) + init_pert_pic += "perturbation = perturbations.TorusModesCos()\n" if "6D" in var.space: - init_bckgr_pic = f"maxwellian_1 = maxwellians.Maxwellian3D(n=(1.0, None))\n" - init_bckgr_pic += f"maxwellian_2 = maxwellians.Maxwellian3D(n=(0.1, None))\n" - init_pert_pic += f"maxwellian_1pt = maxwellians.Maxwellian3D(n=(1.0, perturbation))\n" - init_pert_pic += f"init = maxwellian_1pt + maxwellian_2\n" - init_pert_pic += f"model.kinetic_ions.var.add_initial_condition(init)\n" + init_bckgr_pic = "maxwellian_1 = maxwellians.Maxwellian3D(n=(1.0, None))\n" + init_bckgr_pic += "maxwellian_2 = maxwellians.Maxwellian3D(n=(0.1, None))\n" + init_pert_pic += "maxwellian_1pt = maxwellians.Maxwellian3D(n=(1.0, perturbation))\n" + init_pert_pic += "init = maxwellian_1pt + maxwellian_2\n" + init_pert_pic += f"model.{sn}.{vn}.add_initial_condition(init)\n" elif "5D" in var.space: - init_bckgr_pic = f"maxwellian_1 = maxwellians.GyroMaxwellian2D(n=(1.0, None), equil=equil)\n" - init_bckgr_pic += f"maxwellian_2 = maxwellians.GyroMaxwellian2D(n=(0.1, None), equil=equil)\n" + init_bckgr_pic = "maxwellian_1 = maxwellians.GyroMaxwellian2D(n=(1.0, None), equil=equil)\n" + init_bckgr_pic += "maxwellian_2 = maxwellians.GyroMaxwellian2D(n=(0.1, None), equil=equil)\n" init_pert_pic += ( - f"maxwellian_1pt = maxwellians.GyroMaxwellian2D(n=(1.0, perturbation), equil=equil)\n" + "maxwellian_1pt = maxwellians.GyroMaxwellian2D(n=(1.0, perturbation), equil=equil)\n" ) - init_pert_pic += f"init = maxwellian_1pt + maxwellian_2\n" - init_pert_pic += f"model.kinetic_ions.var.add_initial_condition(init)\n" - init_bckgr_pic += f"background = maxwellian_1 + maxwellian_2\n" + init_pert_pic += "init = maxwellian_1pt + maxwellian_2\n" + init_pert_pic += f"model.{sn}.{vn}.add_initial_condition(init)\n" + if "3D" in var.space: + init_bckgr_pic = "maxwellian_1 = maxwellians.ColdPlasma(n=(1.0, None))\n" + init_bckgr_pic += "maxwellian_2 = maxwellians.ColdPlasma(n=(0.1, None))\n" + init_pert_pic += "maxwellian_1pt = maxwellians.ColdPlasma(n=(1.0, perturbation))\n" + init_pert_pic += "init = maxwellian_1pt + maxwellian_2\n" + init_pert_pic += f"model.{sn}.{vn}.add_initial_condition(init)\n" + init_bckgr_pic += "background = maxwellian_1 + maxwellian_2\n" init_bckgr_pic += f"model.{sn}.{vn}.add_background(background)\n" - exclude = f"# model.....save_data = False\n" + exclude = "# model.....save_data = False\n" elif isinstance(var, SPHVariable): has_sph = True - init_bckgr_sph = f"background = equils.ConstantVelocity()\n" + init_bckgr_sph = "background = equils.ConstantVelocity()\n" init_bckgr_sph += f"model.{sn}.{vn}.add_background(background)\n" - init_pert_sph = f"perturbation = perturbations.TorusModesCos()\n" + init_pert_sph = "perturbation = perturbations.TorusModesCos()\n" init_pert_sph += f"model.{sn}.{vn}.add_perturbation(del_n=perturbation)\n" exclude = f"# model.{sn}.{vn}.save_data = False\n" @@ -1371,13 +1409,12 @@ def generate_default_parameter_file( BoundaryParameters,\n\ BinningPlot,\n\ KernelDensityPlot,\n\ - )\n" + )\n", ) file.write("from struphy import main\n") file.write("\n# import model, set verbosity\n") file.write(f"from {self.__module__} import {self.__class__.__name__}\n") - file.write("verbose = True\n") file.write("\n# environment options\n") file.write("env = EnvironmentOptions()\n") @@ -1394,12 +1431,12 @@ def generate_default_parameter_file( file.write("\n# fluid equilibrium (can be used as part of initial conditions)\n") file.write("equil = equils.HomogenSlab()\n") - if has_feec: - grid = "grid = grids.TensorProductGrid()\n" - derham = "derham_opts = DerhamOptions()\n" - else: - grid = "grid = None\n" - derham = "derham_opts = None\n" + # if has_feec: + grid = "grid = grids.TensorProductGrid()\n" + derham = "derham_opts = DerhamOptions()\n" + # else: + # grid = "grid = None\n" + # derham = "derham_opts = None\n" file.write("\n# grid\n") file.write(grid) @@ -1436,6 +1473,7 @@ def generate_default_parameter_file( file.write('\nif __name__ == "__main__":\n') file.write(" # start run\n") + file.write(" verbose = True\n\n") file.write( " main.run(model,\n\ params_path=__file__,\n\ @@ -1447,14 +1485,14 @@ def generate_default_parameter_file( grid=grid,\n\ derham_opts=derham_opts,\n\ verbose=verbose,\n\ - )" + )", ) file.close() print( f"\nDefault parameter file for '{self.__class__.__name__}' has been created in the cwd ({path}).\n\ -You can now launch a simulation with 'python params_{self.__class__.__name__}.py'" +You can now launch a simulation with 'python params_{self.__class__.__name__}.py'", ) return path @@ -1463,131 +1501,6 @@ def generate_default_parameter_file( # Private methods : ################### - def _init_variable_dicts(self): - """ - Initialize em-fields, fluid and kinetic dictionaries for information on the model variables. - """ - - # electromagnetic fields, fluid and/or kinetic species - self._em_fields = {} - self._fluid = {} - self._kinetic = {} - self._diagnostics = {} - - if self.rank_world == 0 and self.verbose: - print("\nMODEL SPECIES:") - - # create dictionaries for each em-field/species and fill in space/class name and parameters - for var_name, space in self.species()["em_fields"].items(): - assert space in {"H1", "Hcurl", "Hdiv", "L2", "H1vec"} - assert self.params.em_fields is not None, '"em_fields" is missing in parameter file.' - - if self.rank_world == 0 and self.verbose: - print("em_field:".ljust(25), f'"{var_name}" ({space})') - - self._em_fields[var_name] = {} - - # space - self._em_fields[var_name]["space"] = space - - # initial conditions - if "background" in self.params.em_fields: - # background= self.params.em_fields["background"].get(var_name) - self._em_fields[var_name]["background"] = self.params.em_fields["background"].get(var_name) - # else: - # background = None - - if "perturbation" in self.params.em_fields: - # perturbation = self.params.em_fields["perturbation"].get(var_name) - self._em_fields[var_name]["perturbation"] = self.params.em_fields["perturbation"].get(var_name) - # else: - # perturbation = None - - # which components to save - if "save_data" in self.params.em_fields: - # save_data = self.params.em_fields["save_data"]["comps"][var_name] - self._em_fields[var_name]["save_data"] = self.params.em_fields["save_data"]["comps"][var_name] - else: - self._em_fields[var_name]["save_data"] = True - # save_data = True - - # self._em_fields[var_name] = Variable(name=var_name, - # space=space, - # background=background, - # perturbation=perturbation, - # save_data=save_data,) - - # overall parameters - # print(f'{self._em_fields = }') - self._em_fields["params"] = self.params.em_fields - - for var_name, space in self.species()["fluid"].items(): - assert isinstance(space, dict) - assert "fluid" in self.params, 'Top-level key "fluid" is missing in parameter file.' - assert var_name in self.params["fluid"], f"Fluid species {var_name} is missing in parameter file." - - if self.rank_world == 0 and self.verbose: - print("fluid:".ljust(25), f'"{var_name}" ({space})') - - self._fluid[var_name] = {} - for sub_var_name, sub_space in space.items(): - self._fluid[var_name][sub_var_name] = {} - - # space - self._fluid[var_name][sub_var_name]["space"] = sub_space - - # initial conditions - if "background" in self.params["fluid"][var_name]: - self._fluid[var_name][sub_var_name]["background"] = self.params["fluid"][var_name][ - "background" - ].get(sub_var_name) - if "perturbation" in self.params["fluid"][var_name]: - self._fluid[var_name][sub_var_name]["perturbation"] = self.params["fluid"][var_name][ - "perturbation" - ].get(sub_var_name) - - # which components to save - if "save_data" in self.params["fluid"][var_name]: - self._fluid[var_name][sub_var_name]["save_data"] = self.params["fluid"][var_name]["save_data"][ - "comps" - ][sub_var_name] - - else: - self._fluid[var_name][sub_var_name]["save_data"] = True - - # overall parameters - self._fluid[var_name]["params"] = self.params["fluid"][var_name] - - for var_name, space in self.species()["kinetic"].items(): - assert "Particles" in space - assert "kinetic" in self.params, 'Top-level key "kinetic" is missing in parameter file.' - assert var_name in self.params["kinetic"], f"Kinetic species {var_name} is missing in parameter file." - - if self.rank_world == 0 and self.verbose: - print("kinetic:".ljust(25), f'"{var_name}" ({space})') - - self._kinetic[var_name] = {} - self._kinetic[var_name]["space"] = space - self._kinetic[var_name]["params"] = self.params["kinetic"][var_name] - - if self.diagnostics_dct() is not None: - for var_name, space in self.diagnostics_dct().items(): - assert space in {"H1", "Hcurl", "Hdiv", "L2", "H1vec"} - - if self.rank_world == 0 and self.verbose: - print("diagnostics:".ljust(25), f'"{var_name}" ({space})') - - self._diagnostics[var_name] = {} - self._diagnostics[var_name]["space"] = space - self._diagnostics["params"] = self.params["diagnostics"][var_name] - - # which components to save - if "save_data" in self.params["diagnostics"][var_name]: - self._diagnostics[var_name]["save_data"] = self.params["diagnostics"][var_name]["save_data"] - - else: - self._diagnostics[var_name]["save_data"] = True - def compute_plasma_params(self, verbose=True): """ Compute and print volume averaged plasma parameters for each species of the model. @@ -1642,15 +1555,15 @@ def compute_plasma_params(self, verbose=True): units_affix["epsilon"] = "" h = 1 / 20 - eta1 = np.linspace(h / 2.0, 1.0 - h / 2.0, 20) - eta2 = np.linspace(h / 2.0, 1.0 - h / 2.0, 20) - eta3 = np.linspace(h / 2.0, 1.0 - h / 2.0, 20) + eta1 = xp.linspace(h / 2.0, 1.0 - h / 2.0, 20) + eta2 = xp.linspace(h / 2.0, 1.0 - h / 2.0, 20) + eta3 = xp.linspace(h / 2.0, 1.0 - h / 2.0, 20) ## global parameters # plasma volume (hat x^3) det_tmp = self.domain.jacobian_det(eta1, eta2, eta3) - vol1 = np.mean(np.abs(det_tmp)) + vol1 = xp.mean(xp.abs(det_tmp)) # plasma volume (m⁻³) plasma_volume = vol1 * self.units.x**3 # transit length (m) @@ -1659,35 +1572,35 @@ def compute_plasma_params(self, verbose=True): if isinstance(self.equil, FluidEquilibriumWithB): B_tmp = self.equil.absB0(eta1, eta2, eta3) else: - B_tmp = np.zeros((eta1.size, eta2.size, eta3.size)) - magnetic_field = np.mean(B_tmp * np.abs(det_tmp)) / vol1 * self.units.B - B_max = np.max(B_tmp) * self.units.B - B_min = np.min(B_tmp) * self.units.B + B_tmp = xp.zeros((eta1.size, eta2.size, eta3.size)) + magnetic_field = xp.mean(B_tmp * xp.abs(det_tmp)) / vol1 * self.units.B + B_max = xp.max(B_tmp) * self.units.B + B_min = xp.min(B_tmp) * self.units.B if magnetic_field < 1e-14: - magnetic_field = np.nan + magnetic_field = xp.nan # print("\n+++++++ WARNING +++++++ magnetic field is zero - set to nan !!") if verbose and MPI.COMM_WORLD.Get_rank() == 0: print("\nPLASMA PARAMETERS:") print( - f"Plasma volume:".ljust(25), + "Plasma volume:".ljust(25), "{:4.3e}".format(plasma_volume) + units_affix["plasma volume"], ) print( - f"Transit length:".ljust(25), + "Transit length:".ljust(25), "{:4.3e}".format(transit_length) + units_affix["transit length"], ) print( - f"Avg. magnetic field:".ljust(25), + "Avg. magnetic field:".ljust(25), "{:4.3e}".format(magnetic_field) + units_affix["magnetic field"], ) print( - f"Max magnetic field:".ljust(25), + "Max magnetic field:".ljust(25), "{:4.3e}".format(B_max) + units_affix["magnetic field"], ) print( - f"Min magnetic field:".ljust(25), + "Min magnetic field:".ljust(25), "{:4.3e}".format(B_min) + units_affix["magnetic field"], ) @@ -1705,13 +1618,13 @@ def compute_plasma_params(self, verbose=True): # self._pparams[species]["charge"] = val["params"]["phys_params"]["Z"] * e # # density (m⁻³) # self._pparams[species]["density"] = ( - # np.mean( + # xp.mean( # self.equil.n0( # eta1, # eta2, # eta3, # ) - # * np.abs(det_tmp), + # * xp.abs(det_tmp), # ) # * self.units.x ** 3 # / plasma_volume @@ -1719,13 +1632,13 @@ def compute_plasma_params(self, verbose=True): # ) # # pressure (bar) # self._pparams[species]["pressure"] = ( - # np.mean( + # xp.mean( # self.equil.p0( # eta1, # eta2, # eta3, # ) - # * np.abs(det_tmp), + # * xp.abs(det_tmp), # ) # * self.units.x ** 3 # / plasma_volume @@ -1736,7 +1649,7 @@ def compute_plasma_params(self, verbose=True): # self._pparams[species]["kBT"] = self._pparams[species]["pressure"] * 1e5 / self._pparams[species]["density"] / e * 1e-3 # if len(self.kinetic) > 0: - # eta1mg, eta2mg, eta3mg = np.meshgrid( + # eta1mg, eta2mg, eta3mg = xp.meshgrid( # eta1, # eta2, # eta3, @@ -1782,11 +1695,11 @@ def compute_plasma_params(self, verbose=True): # # density (m⁻³) # self._pparams[species]["density"] = ( - # np.mean(tmp.n(psi) * np.abs(det_tmp)) * self.units.x ** 3 / plasma_volume * self.units.n + # xp.mean(tmp.n(psi) * xp.abs(det_tmp)) * self.units.x ** 3 / plasma_volume * self.units.n # ) # # thermal speed (m/s) # self._pparams[species]["v_th"] = ( - # np.mean(tmp.vth(psi) * np.abs(det_tmp)) * self.units.x ** 3 / plasma_volume * self.units.v + # xp.mean(tmp.vth(psi) * xp.abs(det_tmp)) * self.units.x ** 3 / plasma_volume * self.units.v # ) # # thermal energy (keV) # self._pparams[species]["kBT"] = self._pparams[species]["mass"] * self._pparams[species]["v_th"] ** 2 / e * 1e-3 @@ -1797,8 +1710,8 @@ def compute_plasma_params(self, verbose=True): # else: # # density (m⁻³) - # # self._pparams[species]['density'] = np.mean(tmp.n( - # # eta1mg, eta2mg, eta3mg) * np.abs(det_tmp)) * units['x']**3 / plasma_volume * units['n'] + # # self._pparams[species]['density'] = xp.mean(tmp.n( + # # eta1mg, eta2mg, eta3mg) * xp.abs(det_tmp)) * units['x']**3 / plasma_volume * units['n'] # self._pparams[species]["density"] = 99.0 # # thermal speeds (m/s) # vth = [] @@ -1806,11 +1719,11 @@ def compute_plasma_params(self, verbose=True): # vths = [99.0] # for k in range(len(vths)): # vth += [ - # vths[k] * np.abs(det_tmp) * self.units.x ** 3 / plasma_volume * self.units.v, + # vths[k] * xp.abs(det_tmp) * self.units.x ** 3 / plasma_volume * self.units.v, # ] # thermal_speed = 0.0 # for dir in range(val["obj"].vdim): - # # self._pparams[species]['vth' + str(dir + 1)] = np.mean(vth[dir]) + # # self._pparams[species]['vth' + str(dir + 1)] = xp.mean(vth[dir]) # self._pparams[species]["vth" + str(dir + 1)] = 99.0 # thermal_speed += self._pparams[species]["vth" + str(dir + 1)] # # TODO: here it is assumed that background density parameter is called "n", @@ -1829,11 +1742,11 @@ def compute_plasma_params(self, verbose=True): # for species in self._pparams: # # alfvén speed (m/s) - # self._pparams[species]["v_A"] = magnetic_field / np.sqrt( + # self._pparams[species]["v_A"] = magnetic_field / xp.sqrt( # mu0 * self._pparams[species]["mass"] * self._pparams[species]["density"], # ) # # thermal speed (m/s) - # self._pparams[species]["v_th"] = np.sqrt( + # self._pparams[species]["v_th"] = xp.sqrt( # self._pparams[species]["kBT"] * 1e3 * e / self._pparams[species]["mass"], # ) # # thermal frequency (Mrad/s) @@ -1842,7 +1755,7 @@ def compute_plasma_params(self, verbose=True): # self._pparams[species]["Omega_c"] = self._pparams[species]["charge"] * magnetic_field / self._pparams[species]["mass"] * 1e-6 # # plasma frequency (Mrad/s) # self._pparams[species]["Omega_p"] = ( - # np.sqrt( + # xp.sqrt( # self._pparams[species]["density"] * (self._pparams[species]["charge"]) ** 2 / eps0 / self._pparams[species]["mass"], # ) # * 1e-6 @@ -1852,7 +1765,7 @@ def compute_plasma_params(self, verbose=True): # # Larmor radius (m) # self._pparams[species]["rho_th"] = self._pparams[species]["v_th"] / (self._pparams[species]["Omega_c"] * 1e6) # # MHD length scale (m) - # self._pparams[species]["v_A/Omega_c"] = self._pparams[species]["v_A"] / (np.abs(self._pparams[species]["Omega_c"]) * 1e6) + # self._pparams[species]["v_A/Omega_c"] = self._pparams[species]["v_A"] / (xp.abs(self._pparams[species]["Omega_c"]) * 1e6) # # dim-less ratios # self._pparams[species]["rho_th/L"] = self._pparams[species]["rho_th"] / transit_length diff --git a/src/struphy/models/fluid.py b/src/struphy/models/fluid.py index 26e6314ee..405610b7b 100644 --- a/src/struphy/models/fluid.py +++ b/src/struphy/models/fluid.py @@ -1,9 +1,12 @@ -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.block import BlockVector +from psydac.linalg.stencil import StencilVector +from struphy.feec.projectors import L2Projector +from struphy.feec.variational_utilities import H1vecMassMatrix_density, InternalEnergyEvaluator from struphy.models.base import StruphyModel -from struphy.models.species import FieldSpecies, FluidSpecies, ParticleSpecies +from struphy.models.species import DiagnosticSpecies, FieldSpecies, FluidSpecies, ParticleSpecies from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.polar.basic import PolarVector from struphy.propagators import propagators_coupling, propagators_fields, propagators_markers @@ -154,7 +157,7 @@ def generate_default_parameter_file(self, path=None, prompt=True): for line in f: if "mag_sonic.Options" in line: new_file += [ - "model.propagators.mag_sonic.options = model.propagators.mag_sonic.Options(b_field=model.em_fields.b_field)\n" + "model.propagators.mag_sonic.options = model.propagators.mag_sonic.Options(b_field=model.em_fields.b_field)\n", ] else: new_file += [line] @@ -203,87 +206,52 @@ class LinearExtendedMHDuniform(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species - dct["em_fields"]["b_field"] = "Hcurl" - dct["fluid"]["mhd"] = { - "rho": "L2", - "u": "Hdiv", - "p": "L2", - } - return dct + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hcurl") + self.init_variables() - @staticmethod - def bulk_species(): - return "mhd" + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="Hdiv") + self.pressure = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + ## propagators - @staticmethod - def propagators_dct(): - return { - propagators_fields.ShearAlfvenB1: ["mhd_u", "b_field"], - propagators_fields.Hall: ["b_field"], - propagators_fields.MagnetosonicUniform: ["mhd_rho", "mhd_u", "mhd_p"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - from struphy.polar.basic import PolarVector - - # extract necessary parameters - alfven_solver = params["fluid"]["mhd"]["options"]["ShearAlfvenB1"]["solver"] - M1_inv = params["fluid"]["mhd"]["options"]["ShearAlfvenB1"]["solver_M1"] - hall_solver = params["em_fields"]["options"]["Hall"]["solver"] - sonic_solver = params["fluid"]["mhd"]["options"]["MagnetosonicUniform"]["solver"] - - # project background magnetic field (1-form) and pressure (3-form) - self._b_eq = self.projected_equil.b1 - self._a_eq = self.projected_equil.a1 - self._p_eq = self.projected_equil.p3 - self._ones = self.pointer["mhd_p"].space.zeros() + class Propagators: + def __init__(self): + self.shear_alf = propagators_fields.ShearAlfvenB1() + self.hall = propagators_fields.Hall() + self.mag_sonic = propagators_fields.MagnetosonicUniform() - if isinstance(self._ones, PolarVector): - self._ones.tp[:] = 1.0 - else: - self._ones[:] = 1.0 + ## abstract methods - # compute coupling parameters - epsilon = self.equation_params["mhd"]["epsilon"] + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - if abs(epsilon - 1) < 1e-6: - epsilon = 1.0 + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() - # set keyword arguments for propagators - self._kwargs[propagators_fields.ShearAlfvenB1] = { - "solver": alfven_solver, - "solver_M1": M1_inv, - } + # 2. instantiate all propagators + self.propagators = self.Propagators() - self._kwargs[propagators_fields.Hall] = { - "solver": hall_solver, - "epsilon": epsilon, - } + # 3. assign variables to propagators + self.propagators.shear_alf.variables.u = self.mhd.velocity + self.propagators.shear_alf.variables.b = self.em_fields.b_field - self._kwargs[propagators_fields.MagnetosonicUniform] = {"solver": sonic_solver} + self.propagators.hall.variables.b = self.em_fields.b_field - # Initialize propagators used in splitting substeps - self.init_propagators() + self.propagators.mag_sonic.variables.n = self.mhd.density + self.propagators.mag_sonic.variables.u = self.mhd.velocity + self.propagators.mag_sonic.variables.p = self.mhd.pressure - # Scalar variables to be saved during simulation + # define scalars for update_scalar_quantities self.add_scalar("en_U") self.add_scalar("en_p") self.add_scalar("en_B") @@ -293,17 +261,45 @@ def __init__(self, params, comm, clone_config=None): self.add_scalar("en_tot") self.add_scalar("helicity") - # temporary vectors for scalar quantities - self._tmp_b1 = self.derham.Vh["1"].zeros() - self._tmp_b2 = self.derham.Vh["1"].zeros() + @property + def bulk_species(self): + return self.mhd + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + self._b_eq = self.projected_equil.b1 + self._a_eq = self.projected_equil.a1 + self._p_eq = self.projected_equil.p3 + + self._ones = self.projected_equil.p3.space.zeros() + if isinstance(self._ones, PolarVector): + self._ones.tp[:] = 1.0 + else: + self._ones[:] = 1.0 + + self._tmp_b1: BlockVector = self.derham.Vh["1"].zeros() # TODO: replace derham.Vh dict by class + self._tmp_b2: BlockVector = self.derham.Vh["1"].zeros() + + # adjust coupling parameters + epsilon = self.mhd.equation_params.epsilon + + if abs(epsilon - 1) < 1e-6: + self.mhd.equation_params.epsilon = 1.0 def update_scalar_quantities(self): # perturbed fields - en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_u"], self.pointer["mhd_u"]) - b1 = self.mass_ops.M1.dot(self.pointer["b_field"], out=self._tmp_b1) - en_B = 0.5 * self.pointer["b_field"].inner(b1) + u = self.mhd.velocity.spline.vector + p = self.mhd.pressure.spline.vector + b = self.em_fields.b_field.spline.vector + + en_U = 0.5 * self.mass_ops.M2n.dot_inner(u, u) + b1 = self.mass_ops.M1.dot(b, out=self._tmp_b1) + en_B = 0.5 * b.inner(b1) helicity = 2.0 * self._a_eq.inner(b1) - en_p_i = self.pointer["mhd_p"].inner(self._ones) / (5.0 / 3.0 - 1.0) + en_p_i = p.inner(self._ones) / (5.0 / 3.0 - 1.0) self.update_scalar("en_U", en_U) self.update_scalar("en_B", en_B) @@ -321,13 +317,30 @@ def update_scalar_quantities(self): # total magnetic field b1 = self._b_eq.copy(out=self._tmp_b1) - self._tmp_b1 += self.pointer["b_field"] + self._tmp_b1 += b b2 = self.mass_ops.M1.dot(b1, apply_bc=False, out=self._tmp_b2) en_Btot = b1.inner(b2) / 2.0 self.update_scalar("en_B_tot", en_Btot) + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "hall.Options" in line: + new_file += [ + "model.propagators.hall.options = model.propagators.hall.Options(epsilon_from=model.mhd)\n", + ] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + class ColdPlasma(StruphyModel): r"""Cold plasma model. @@ -364,82 +377,74 @@ class ColdPlasma(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species + + class EMFields(FieldSpecies): + def __init__(self): + self.e_field = FEECVariable(space="Hcurl") + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() + + class Electrons(FluidSpecies): + def __init__(self): + self.current = FEECVariable(space="Hcurl") + self.init_variables() - dct["em_fields"]["e_field"] = "Hcurl" - dct["em_fields"]["b_field"] = "Hdiv" - dct["fluid"]["electrons"] = {"j": "Hcurl"} - return dct + ## propagators - @staticmethod - def bulk_species(): - return "electrons" + class Propagators: + def __init__(self): + self.maxwell = propagators_fields.Maxwell() + self.ohm = propagators_fields.OhmCold() + self.jxb = propagators_fields.JxBCold() - @staticmethod - def velocity_scale(): - return "light" + ## abstract methods + + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.electrons = self.Electrons() - @staticmethod - def propagators_dct(): - return { - propagators_fields.Maxwell: ["e_field", "b_field"], - propagators_fields.OhmCold: ["electrons_j", "e_field"], - propagators_fields.JxBCold: ["electrons_j"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - # model parameters - self._alpha = self.equation_params["electrons"]["alpha"] - self._epsilon = self.equation_params["electrons"]["epsilon"] - - # solver parameters - params_maxwell = params["em_fields"]["options"]["Maxwell"]["solver"] - params_ohmcold = params["fluid"]["electrons"]["options"]["OhmCold"]["solver"] - params_jxbcold = params["fluid"]["electrons"]["options"]["JxBCold"]["solver"] - - # set keyword arguments for propagators - self._kwargs[propagators_fields.Maxwell] = {"solver": params_maxwell} - - self._kwargs[propagators_fields.OhmCold] = { - "alpha": self._alpha, - "epsilon": self._epsilon, - "solver": params_ohmcold, - } - - self._kwargs[propagators_fields.JxBCold] = { - "epsilon": self._epsilon, - "solver": params_jxbcold, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.maxwell.variables.e = self.em_fields.e_field + self.propagators.maxwell.variables.b = self.em_fields.b_field + + self.propagators.ohm.variables.j = self.electrons.current + self.propagators.ohm.variables.e = self.em_fields.e_field + + self.propagators.jxb.variables.j = self.electrons.current + + # define scalars for update_scalar_quantities self.add_scalar("electric energy") self.add_scalar("magnetic energy") self.add_scalar("kinetic energy") self.add_scalar("total energy") + @property + def bulk_species(self): + return self.electrons + + @property + def velocity_scale(self): + return "light" + + def allocate_helpers(self): + self._alpha = self.electrons.equation_params.alpha + def update_scalar_quantities(self): - en_E = 0.5 * self.mass_ops.M1.dot_inner(self.pointer["e_field"], self.pointer["e_field"]) - en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) - en_J = ( - 0.5 - * self._alpha**2 - * self.mass_ops.M1ninv.dot_inner(self.pointer["electrons_j"], self.pointer["electrons_j"]) - ) + e = self.em_fields.e_field.spline.vector + b = self.em_fields.b_field.spline.vector + j = self.electrons.current.spline.vector + + en_E = 0.5 * self.mass_ops.M1.dot_inner(e, e) + en_B = 0.5 * self.mass_ops.M2.dot_inner(b, b) + en_J = 0.5 * self._alpha**2 * self.mass_ops.M1ninv.dot_inner(j, j) self.update_scalar("electric energy", en_E) self.update_scalar("magnetic energy", en_B) @@ -447,7 +452,7 @@ def update_scalar_quantities(self): self.update_scalar("total energy", en_E + en_B + en_J) -class ViscoresistiveMHD(StruphyModel): +class ViscoResistiveMHD(StruphyModel): r"""Full (non-linear) visco-resistive MHD equations discretized with a variational method. :ref:`normalization`: @@ -483,139 +488,73 @@ class ViscoresistiveMHD(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["em_fields"]["b2"] = "Hdiv" - dct["fluid"]["mhd"] = {"rho3": "L2", "s3": "L2", "uv": "H1vec"} - return dct + ## species + + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - @staticmethod - def bulk_species(): - return "mhd" + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.entropy = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + ## propagators - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], - propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], - propagators_fields.VariationalEntropyEvolve: ["mhd_s3", "mhd_uv"], - propagators_fields.VariationalMagFieldEvolve: ["b2", "mhd_uv"], - propagators_fields.VariationalViscosity: ["mhd_s3", "mhd_uv"], - propagators_fields.VariationalResistivity: ["mhd_s3", "b2"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import H1vecMassMatrix_density, InternalEnergyEvaluator - from struphy.polar.basic import PolarVector - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_entropy = params["fluid"]["mhd"]["options"]["VariationalEntropyEvolve"]["lin_solver"] - nonlin_solver_entropy = params["fluid"]["mhd"]["options"]["VariationalEntropyEvolve"]["nonlin_solver"] - lin_solver_magfield = params["em_fields"]["options"]["VariationalMagFieldEvolve"]["lin_solver"] - nonlin_solver_magfield = params["em_fields"]["options"]["VariationalMagFieldEvolve"]["nonlin_solver"] - lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] - nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] - lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] - nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] - if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): - self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] - else: - self._linearize_current = False - self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] - self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] - self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] - self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] - self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] - model = "full" - - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "s": self.pointer["mhd_s3"], - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - "energy_evaluator": self._energy_evaluator, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - self._kwargs[propagators_fields.VariationalEntropyEvolve] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_entropy, - "nonlin_solver": nonlin_solver_entropy, - "energy_evaluator": self._energy_evaluator, - } - - self._kwargs[propagators_fields.VariationalMagFieldEvolve] = { - "model": model, - "mass_ops": self.WMM, - "lin_solver": lin_solver_magfield, - "nonlin_solver": nonlin_solver_magfield, - } - - self._kwargs[propagators_fields.VariationalViscosity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "mu": self._mu, - "mu_a": self._mu_a, - "alpha": self._alpha, - "mass_ops": self.WMM, - "lin_solver": lin_solver_viscosity, - "nonlin_solver": nonlin_solver_viscosity, - "energy_evaluator": self._energy_evaluator, - } - - self._kwargs[propagators_fields.VariationalResistivity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "eta": self._eta, - "eta_a": self._eta_a, - "lin_solver": lin_solver_resistivity, - "nonlin_solver": nonlin_solver_resistivity, - "linearize_current": self._linearize_current, - "energy_evaluator": self._energy_evaluator, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + class Propagators: + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() + self.variat_ent = propagators_fields.VariationalEntropyEvolve() + self.variat_mag = propagators_fields.VariationalMagFieldEvolve() + if with_viscosity: + self.variat_viscous = propagators_fields.VariationalViscosity() + if with_resistivity: + self.variat_resist = propagators_fields.VariationalResistivity() + + ## abstract methods + + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + + # 2. instantiate all propagators + self.propagators = self.Propagators( + with_viscosity=with_viscosity, + with_resistivity=with_resistivity, + ) + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.mhd.density + self.propagators.variat_dens.variables.u = self.mhd.velocity + self.propagators.variat_mom.variables.u = self.mhd.velocity + self.propagators.variat_ent.variables.s = self.mhd.entropy + self.propagators.variat_ent.variables.u = self.mhd.velocity + self.propagators.variat_mag.variables.u = self.mhd.velocity + self.propagators.variat_mag.variables.b = self.em_fields.b_field + if with_viscosity: + self.propagators.variat_viscous.variables.s = self.mhd.entropy + self.propagators.variat_viscous.variables.u = self.mhd.velocity + if with_resistivity: + self.propagators.variat_resist.variables.s = self.mhd.entropy + self.propagators.variat_resist.variables.b = self.em_fields.b_field + + # define scalars for update_scalar_quantities self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag") @@ -624,16 +563,24 @@ def __init__(self, params, comm, clone_config=None): self.add_scalar("entr_tot") self.add_scalar("tot_div_B") - # temporary vectors for scalar quantities - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - tmp_dof = self.derham.Vh_pol["3"].zeros() - projV3 = L2Projector("L2", self.mass_ops) + @property + def bulk_species(self): + return self.mhd + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + projV3 = L2Projector("L2", self._mass_ops) def f(e1, e2, e3): return 1 - f = np.vectorize(f) - self._integrator = projV3(f, dofs=tmp_dof) + f = xp.vectorize(f) + self._integrator = projV3(f) + + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self.propagators.variat_ent.options.gamma) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -641,12 +588,18 @@ def f(e1, e2, e3): else: self._ones[:] = 1.0 + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + def update_scalar_quantities(self): - # Update mass matrix - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) + rho = self.mhd.density.spline.vector + u = self.mhd.velocity.spline.vector + s = self.mhd.entropy.spline.vector + b = self.em_fields.b_field.spline.vector + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) - en_mag = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) + en_mag = 0.5 * self.mass_ops.M2.dot_inner(b, b) self.update_scalar("en_mag", en_mag) en_thermo = self.update_thermo_energy() @@ -654,12 +607,12 @@ def update_scalar_quantities(self): en_tot = en_U + en_thermo + en_mag self.update_scalar("en_tot", en_tot) - dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) + dens_tot = self._ones.inner(rho) self.update_scalar("dens_tot", dens_tot) - entr_tot = self._ones.inner(self.pointer["mhd_s3"]) + entr_tot = self._ones.inner(s) self.update_scalar("entr_tot", entr_tot) - div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) + div_B = self.derham.div.dot(b, out=self._tmp_div_B) L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) self.update_scalar("tot_div_B", L2_div_B) @@ -668,9 +621,12 @@ def update_thermo_energy(self): :meta private: """ - en_prop = self._propagators[0] - self._energy_evaluator.sf.vector = self.pointer["mhd_s3"] - self._energy_evaluator.rhof.vector = self.pointer["mhd_rho3"] + rho = self.mhd.density.spline.vector + s = self.mhd.entropy.spline.vector + en_prop = self.propagators.variat_dens + + self._energy_evaluator.sf.vector = s + self._energy_evaluator.rhof.vector = rho sf_values = self._energy_evaluator.sf.eval_tp_fixed_loc( self._energy_evaluator.integration_grid_spans, self._energy_evaluator.integration_grid_bd, @@ -688,6 +644,44 @@ def update_thermo_energy(self): self.update_scalar("en_thermo", en_thermo) return en_thermo + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='full',\n", + ] + new_file += [ + " s=model.mhd.entropy)\n", + ] + elif "variat_ent.Options" in line: + new_file += [ + "model.propagators.variat_ent.options = model.propagators.variat_ent.Options(model='full',\n", + ] + new_file += [ + " rho=model.mhd.density)\n", + ] + elif "variat_viscous.Options" in line: + new_file += [ + "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(rho=model.mhd.density)\n", + ] + elif "variat_resist.Options" in line: + new_file += [ + "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(rho=model.mhd.density)\n", + ] + elif "entropy.add_background" in line: + new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + class ViscousFluid(StruphyModel): r"""Full (non-linear) viscous Navier-Stokes equations discretized with a variational method. @@ -721,122 +715,72 @@ class ViscousFluid(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["fluid"]["fluid"] = {"rho3": "L2", "s3": "L2", "uv": "H1vec"} - return dct + ## species + + class Fluid(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.entropy = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def bulk_species(): - return "fluid" + ## propagators - @staticmethod - def velocity_scale(): - return "alfvén" + class Propagators: + def __init__(self, with_viscosity: bool = True): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() + self.variat_ent = propagators_fields.VariationalEntropyEvolve() + if with_viscosity: + self.variat_viscous = propagators_fields.VariationalViscosity() + + ## abstract methods + + def __init__(self, with_viscosity: bool = True): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.fluid = self.Fluid() - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["fluid_rho3", "fluid_uv"], - propagators_fields.VariationalMomentumAdvection: ["fluid_uv"], - propagators_fields.VariationalEntropyEvolve: ["fluid_s3", "fluid_uv"], - propagators_fields.VariationalViscosity: ["fluid_s3", "fluid_uv"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.polar.basic import PolarVector - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - from struphy.feec.variational_utilities import H1vecMassMatrix_density, InternalEnergyEvaluator - - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_entropy = params["fluid"]["fluid"]["options"]["VariationalEntropyEvolve"]["lin_solver"] - nonlin_solver_entropy = params["fluid"]["fluid"]["options"]["VariationalEntropyEvolve"]["nonlin_solver"] - lin_solver_viscosity = params["fluid"]["fluid"]["options"]["VariationalViscosity"]["lin_solver"] - nonlin_solver_viscosity = params["fluid"]["fluid"]["options"]["VariationalViscosity"]["nonlin_solver"] - - self._gamma = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - self._mu = params["fluid"]["fluid"]["options"]["VariationalViscosity"]["physics"]["mu"] - self._mu_a = params["fluid"]["fluid"]["options"]["VariationalViscosity"]["physics"]["mu_a"] - model = "full" - - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "s": self.pointer["fluid_s3"], - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - "energy_evaluator": self._energy_evaluator, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - self._kwargs[propagators_fields.VariationalEntropyEvolve] = { - "model": model, - "rho": self.pointer["fluid_rho3"], - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_entropy, - "nonlin_solver": nonlin_solver_entropy, - "energy_evaluator": self._energy_evaluator, - } - - self._kwargs[propagators_fields.VariationalViscosity] = { - "model": model, - "gamma": self._gamma, - "rho": self.pointer["fluid_rho3"], - "mu": self._mu, - "mu_a": self._mu_a, - "mass_ops": self.WMM, - "lin_solver": lin_solver_viscosity, - "nonlin_solver": nonlin_solver_viscosity, - "energy_evaluator": self._energy_evaluator, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + # 2. instantiate all propagators + self.propagators = self.Propagators(with_viscosity=with_viscosity) + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.fluid.density + self.propagators.variat_dens.variables.u = self.fluid.velocity + self.propagators.variat_mom.variables.u = self.fluid.velocity + self.propagators.variat_ent.variables.s = self.fluid.entropy + self.propagators.variat_ent.variables.u = self.fluid.velocity + if with_viscosity: + self.propagators.variat_viscous.variables.s = self.fluid.entropy + self.propagators.variat_viscous.variables.u = self.fluid.velocity + + # define scalars for update_scalar_quantities self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_tot") self.add_scalar("dens_tot") self.add_scalar("entr_tot") - # temporary vectors for scalar quantities - tmp_dof = self.derham.Vh_pol["3"].zeros() - projV3 = L2Projector("L2", self.mass_ops) + @property + def bulk_species(self): + return self.fluid + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + projV3 = L2Projector("L2", self._mass_ops) def f(e1, e2, e3): return 1 - f = np.vectorize(f) - self._integrator = projV3(f, dofs=tmp_dof) + f = xp.vectorize(f) + self._integrator = projV3(f) + + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self.propagators.variat_ent.options.gamma) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -845,8 +789,11 @@ def f(e1, e2, e3): self._ones[:] = 1.0 def update_scalar_quantities(self): - # Update mass matrix - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["fluid_uv"], self.pointer["fluid_uv"]) + rho = self.fluid.density.spline.vector + u = self.fluid.velocity.spline.vector + s = self.fluid.entropy.spline.vector + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) en_thermo = self.update_thermo_energy() @@ -854,9 +801,9 @@ def update_scalar_quantities(self): en_tot = en_U + en_thermo self.update_scalar("en_tot", en_tot) - dens_tot = self._ones.inner(self.pointer["fluid_rho3"]) + dens_tot = self._ones.inner(rho) self.update_scalar("dens_tot", dens_tot) - entr_tot = self._ones.inner(self.pointer["fluid_s3"]) + entr_tot = self._ones.inner(s) self.update_scalar("entr_tot", entr_tot) def update_thermo_energy(self): @@ -864,9 +811,12 @@ def update_thermo_energy(self): :meta private: """ - en_prop = self._propagators[0] - self._energy_evaluator.sf.vector = self.pointer["fluid_s3"] - self._energy_evaluator.rhof.vector = self.pointer["fluid_rho3"] + rho = self.fluid.density.spline.vector + s = self.fluid.entropy.spline.vector + en_prop = self.propagators.variat_dens + + self._energy_evaluator.sf.vector = s + self._energy_evaluator.rhof.vector = rho sf_values = self._energy_evaluator.sf.eval_tp_fixed_loc( self._energy_evaluator.integration_grid_spans, self._energy_evaluator.integration_grid_bd, @@ -884,8 +834,42 @@ def update_thermo_energy(self): self.update_scalar("en_thermo", en_thermo) return en_thermo + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='full',\n", + ] + new_file += [ + " s=model.fluid.entropy)\n", + ] + elif "variat_ent.Options" in line: + new_file += [ + "model.propagators.variat_ent.options = model.propagators.variat_ent.Options(model='full',\n", + ] + new_file += [ + " rho=model.fluid.density)\n", + ] + elif "variat_viscous.Options" in line: + new_file += [ + "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(rho=model.fluid.density)\n", + ] + elif "entropy.add_background" in line: + new_file += ["model.fluid.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + -class ViscoresistiveMHD_with_p(StruphyModel): +class ViscoResistiveMHD_with_p(StruphyModel): r"""Full (non-linear) visco-resistive MHD equations, with the pressure variable discretized with a variational method. :ref:`normalization`: @@ -919,134 +903,101 @@ class ViscoresistiveMHD_with_p(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["em_fields"]["b2"] = "Hdiv" - dct["fluid"]["mhd"] = {"rho3": "L2", "p3": "L2", "uv": "H1vec"} - return dct + ## species - @staticmethod - def bulk_species(): - return "mhd" + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.pressure = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], - propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], - propagators_fields.VariationalPBEvolve: ["mhd_p3", "b2", "mhd_uv"], - propagators_fields.VariationalViscosity: ["mhd_p3", "mhd_uv"], - propagators_fields.VariationalResistivity: ["mhd_p3", "b2"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import H1vecMassMatrix_density - from struphy.polar.basic import PolarVector - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["lin_solver"] - nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["nonlin_solver"] - lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] - nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] - lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] - nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] - if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): - self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] - else: - self._linearize_current = False - self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] - self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] - self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] - self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] - self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] - model = "full_p" - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - self._kwargs[propagators_fields.VariationalPBEvolve] = { - "model": model, - "mass_ops": self.WMM, - "lin_solver": lin_solver_magfield, - "nonlin_solver": nonlin_solver_magfield, - "gamma": self._gamma, - } - - self._kwargs[propagators_fields.VariationalViscosity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "mu": self._mu, - "mu_a": self._mu_a, - "alpha": self._alpha, - "mass_ops": self.WMM, - "lin_solver": lin_solver_viscosity, - "nonlin_solver": nonlin_solver_viscosity, - } - - self._kwargs[propagators_fields.VariationalResistivity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "eta": self._eta, - "eta_a": self._eta_a, - "lin_solver": lin_solver_resistivity, - "nonlin_solver": nonlin_solver_resistivity, - "linearize_current": self._linearize_current, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation - self.add_scalar("en_U") - self.add_scalar("en_thermo") - self.add_scalar("en_mag") - self.add_scalar("en_tot") - self.add_scalar("dens_tot") + class Diagnostics(DiagnosticSpecies): + def __init__(self): + self.div_u = FEECVariable(space="L2") + self.u2 = FEECVariable(space="Hdiv") + self.init_variables() + + ## propagators + + class Propagators: + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() + self.variat_pb = propagators_fields.VariationalPBEvolve() + if with_viscosity: + self.variat_viscous = propagators_fields.VariationalViscosity() + if with_resistivity: + self.variat_resist = propagators_fields.VariationalResistivity() + + ## abstract methods + + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.diagnostics = self.Diagnostics() + + # 2. instantiate all propagators + self.propagators = self.Propagators( + with_viscosity=with_viscosity, + with_resistivity=with_resistivity, + ) + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.mhd.density + self.propagators.variat_dens.variables.u = self.mhd.velocity + self.propagators.variat_mom.variables.u = self.mhd.velocity + self.propagators.variat_pb.variables.u = self.mhd.velocity + self.propagators.variat_pb.variables.p = self.mhd.pressure + self.propagators.variat_pb.variables.b = self.em_fields.b_field + if with_viscosity: + self.propagators.variat_viscous.variables.s = self.mhd.pressure + self.propagators.variat_viscous.variables.u = self.mhd.velocity + if with_resistivity: + self.propagators.variat_resist.variables.s = self.mhd.pressure + self.propagators.variat_resist.variables.b = self.em_fields.b_field + + # define scalars for update_scalar_quantities + self.add_scalar("en_U") + self.add_scalar("en_thermo") + self.add_scalar("en_mag") + self.add_scalar("en_tot") + self.add_scalar("dens_tot") self.add_scalar("tot_div_B") - # temporary vectors for scalar quantities - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - tmp_dof = self.derham.Vh_pol["3"].zeros() - projV3 = L2Projector("L2", self.mass_ops) + @property + def bulk_species(self): + return self.mhd + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + projV3 = L2Projector("L2", self._mass_ops) - self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) + def f(e1, e2, e3): + return 1 + + f = xp.vectorize(f) + self._integrator = projV3(f) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1054,39 +1005,68 @@ def __init__(self, params, comm, clone_config=None): else: self._ones[:] = 1.0 + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + def update_scalar_quantities(self): - # Update mass matrix - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) + rho = self.mhd.density.spline.vector + u = self.mhd.velocity.spline.vector + p = self.mhd.pressure.spline.vector + b = self.em_fields.b_field.spline.vector + + gamma = self.propagators.variat_pb.options.gamma + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) - en_mag = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) + en_mag = 0.5 * self.mass_ops.M2.dot_inner(b, b) self.update_scalar("en_mag", en_mag) - en_thermo = self.mass_ops.M3.dot_inner(self.pointer["mhd_p3"], self._integrator) / (self._gamma - 1.0) + en_thermo = self.mass_ops.M3.dot_inner(p, self._integrator) / (gamma - 1.0) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo + en_mag self.update_scalar("en_tot", en_tot) - dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) + dens_tot = self._ones.inner(rho) self.update_scalar("dens_tot", dens_tot) - div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) + div_B = self.derham.div.dot(b, out=self._tmp_div_B) L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) self.update_scalar("tot_div_B", L2_div_B) - @staticmethod - def diagnostics_dct(): - dct = {} - - dct["div_u"] = "L2" - dct["u2"] = "Hdiv" - return dct + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_pb.Options" in line: + new_file += [ + "model.propagators.variat_pb.options = model.propagators.variat_pb.Options(div_u=model.diagnostics.div_u,\n", + ] + new_file += [ + " u2=model.diagnostics.u2)\n", + ] + elif "variat_viscous.Options" in line: + new_file += [ + "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(rho=model.mhd.density)\n", + ] + elif "variat_resist.Options" in line: + new_file += [ + "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(rho=model.mhd.density)\n", + ] + elif "pressure.add_background" in line: + new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] - __diagnostics__ = diagnostics_dct() + with open(params_path, "w") as f: + for line in new_file: + f.write(line) -class ViscoresistiveLinearMHD(StruphyModel): +class ViscoResistiveLinearMHD(StruphyModel): r"""Linear visco-resistive MHD equations discretized with a variational method. :ref:`normalization`: @@ -1119,136 +1099,104 @@ class ViscoresistiveLinearMHD(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["em_fields"]["b2"] = "Hdiv" - dct["fluid"]["mhd"] = {"rho3": "L2", "p3": "L2", "uv": "H1vec"} - return dct + ## species - @staticmethod - def bulk_species(): - return "mhd" + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.pressure = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], - propagators_fields.VariationalPBEvolve: ["mhd_p3", "b2", "mhd_uv"], - propagators_fields.VariationalViscosity: ["mhd_p3", "mhd_uv"], - propagators_fields.VariationalResistivity: ["mhd_p3", "b2"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import H1vecMassMatrix_density - from struphy.polar.basic import PolarVector - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["lin_solver"] - nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["nonlin_solver"] - lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] - nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] - lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] - nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] - if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): - self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] - else: - self._linearize_current = False - self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] - self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] - self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] - self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] - self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] - model = "linear" - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - } - - self._kwargs[propagators_fields.VariationalPBEvolve] = { - "model": model, - "mass_ops": self.WMM, - "lin_solver": lin_solver_magfield, - "nonlin_solver": nonlin_solver_magfield, - "gamma": self._gamma, - "div_u": self.pointer["div_u"], - "u2": self.pointer["u2"], - "bt2": self.pointer["bt2"], - "pt3": self.pointer["pt3"], - } - - self._kwargs[propagators_fields.VariationalViscosity] = { - "model": "linear_p", - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "mu": self._mu, - "mu_a": self._mu_a, - "alpha": self._alpha, - "mass_ops": self.WMM, - "lin_solver": lin_solver_viscosity, - "nonlin_solver": nonlin_solver_viscosity, - } - - self._kwargs[propagators_fields.VariationalResistivity] = { - "model": "linear_p", - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "eta": self._eta, - "eta_a": self._eta_a, - "lin_solver": lin_solver_resistivity, - "nonlin_solver": nonlin_solver_resistivity, - "linearize_current": self._linearize_current, - "pt3": self.pointer["pt3"], - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + class Diagnostics(DiagnosticSpecies): + def __init__(self): + self.div_u = FEECVariable(space="L2") + self.u2 = FEECVariable(space="Hdiv") + self.pt3 = FEECVariable(space="L2") + self.bt2 = FEECVariable(space="Hdiv") + self.init_variables() + + ## propagators + + class Propagators: + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_pb = propagators_fields.VariationalPBEvolve() + if with_viscosity: + self.variat_viscous = propagators_fields.VariationalViscosity() + if with_resistivity: + self.variat_resist = propagators_fields.VariationalResistivity() + + ## abstract methods + + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.diagnostics = self.Diagnostics() + + # 2. instantiate all propagators + self.propagators = self.Propagators( + with_viscosity=with_viscosity, + with_resistivity=with_resistivity, + ) + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.mhd.density + self.propagators.variat_dens.variables.u = self.mhd.velocity + self.propagators.variat_pb.variables.u = self.mhd.velocity + self.propagators.variat_pb.variables.p = self.mhd.pressure + self.propagators.variat_pb.variables.b = self.em_fields.b_field + if with_viscosity: + self.propagators.variat_viscous.variables.s = self.mhd.pressure + self.propagators.variat_viscous.variables.u = self.mhd.velocity + if with_resistivity: + self.propagators.variat_resist.variables.s = self.mhd.pressure + self.propagators.variat_resist.variables.b = self.em_fields.b_field + + # define scalars for update_scalar_quantities self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag_1") self.add_scalar("en_mag_2") self.add_scalar("en_tot") - # self.add_scalar("dens_tot") - # self.add_scalar("tot_div_B") - self.add_scalar("en_tot_l1") self.add_scalar("en_thermo_l1") self.add_scalar("en_mag_l1") - # temporary vectors for scalar quantities - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - tmp_dof = self.derham.Vh_pol["3"].zeros() - projV3 = L2Projector("L2", self.mass_ops) + @property + def bulk_species(self): + return self.mhd + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + projV3 = L2Projector("L2", self._mass_ops) + + def f(e1, e2, e3): + return 1 - self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) + f = xp.vectorize(f) + self._integrator = projV3(f) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1256,52 +1204,104 @@ def __init__(self, params, comm, clone_config=None): else: self._ones[:] = 1.0 + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + def update_scalar_quantities(self): - # Update mass matrix - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) + rho = self.mhd.density.spline.vector + u = self.mhd.velocity.spline.vector + p = self.mhd.pressure.spline.vector + b = self.em_fields.b_field.spline.vector + bt2 = self.propagators.variat_pb.options.bt2.spline.vector + pt3 = self.propagators.variat_pb.options.pt3.spline.vector + + gamma = self.propagators.variat_pb.options.gamma + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) - en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) + en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(b, b) self.update_scalar("en_mag_1", en_mag1) - en_mag2 = self.mass_ops.M2.dot_inner(self.pointer["bt2"], self.projected_equil.b2) + en_mag2 = self.mass_ops.M2.dot_inner(bt2, self.projected_equil.b2) self.update_scalar("en_mag_2", en_mag2) - en_thermo = self.mass_ops.M3.dot_inner(self.pointer["pt3"], self._integrator) / (self._gamma - 1.0) + en_thermo = self.mass_ops.M3.dot_inner(pt3, self._integrator) / (gamma - 1.0) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo + en_mag1 + en_mag2 self.update_scalar("en_tot", en_tot) - # dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) + # dens_tot = self._ones.inner(rho) # self.update_scalar("dens_tot", dens_tot) - # div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) + # div_B = self.derham.div.dot(b, out=self._tmp_div_B) # L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) # self.update_scalar("tot_div_B", L2_div_B) - en_thermo_l1 = self.mass_ops.M3.dot_inner(self.pointer["mhd_p3"], self._integrator) / (self._gamma - 1.0) + en_thermo_l1 = self.mass_ops.M3.dot_inner(p, self._integrator) / (gamma - 1.0) self.update_scalar("en_thermo_l1", en_thermo_l1) - en_mag_l1 = self.mass_ops.M2.dot_inner(self.pointer["b2"], self.projected_equil.b2) + en_mag_l1 = self.mass_ops.M2.dot_inner(b, self.projected_equil.b2) self.update_scalar("en_mag_l1", en_mag_l1) en_tot_l1 = en_thermo_l1 + en_mag_l1 self.update_scalar("en_tot_l1", en_tot_l1) - @staticmethod - def diagnostics_dct(): - dct = {} - dct["bt2"] = "Hdiv" - dct["pt3"] = "L2" - dct["div_u"] = "L2" - dct["u2"] = "Hdiv" - return dct + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='linear')\n", + ] + elif "variat_pb.Options" in line: + new_file += [ + "model.propagators.variat_pb.options = model.propagators.variat_pb.Options(model='linear',\n", + ] + new_file += [ + " div_u=model.diagnostics.div_u,\n", + ] + new_file += [ + " u2=model.diagnostics.u2,\n", + ] + new_file += [ + " pt3=model.diagnostics.pt3,\n", + ] + new_file += [ + " bt2=model.diagnostics.bt2)\n", + ] + elif "variat_viscous.Options" in line: + new_file += [ + "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='linear_p',\n", + ] + new_file += [ + " rho=model.mhd.density)\n", + ] + elif "variat_resist.Options" in line: + new_file += [ + "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='linear_p',\n", + ] + new_file += [ + " rho=model.mhd.density,\n", + ] + new_file += [ + " pt3=model.diagnostics.pt3)\n", + ] + elif "pressure.add_background" in line: + new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] - __diagnostics__ = diagnostics_dct() + with open(params_path, "w") as f: + for line in new_file: + f.write(line) -class ViscoresistiveDeltafMHD(StruphyModel): +class ViscoResistiveDeltafMHD(StruphyModel): r""":math:`\delta f` visco-resistive MHD equations discretized with a variational method. :ref:`normalization`: @@ -1335,141 +1335,106 @@ class ViscoresistiveDeltafMHD(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["em_fields"]["b2"] = "Hdiv" - dct["fluid"]["mhd"] = {"rho3": "L2", "p3": "L2", "uv": "H1vec"} - return dct + ## species - @staticmethod - def bulk_species(): - return "mhd" + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.pressure = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], - propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], - propagators_fields.VariationalPBEvolve: ["mhd_p3", "b2", "mhd_uv"], - propagators_fields.VariationalViscosity: ["mhd_p3", "mhd_uv"], - propagators_fields.VariationalResistivity: ["mhd_p3", "b2"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import H1vecMassMatrix_density - from struphy.polar.basic import PolarVector - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["lin_solver"] - nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalPBEvolve"]["nonlin_solver"] - lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] - nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] - lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] - nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] - if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): - self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] - else: - self._linearize_current = False - self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] - self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] - self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] - self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] - self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] - model = "deltaf" - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - self._kwargs[propagators_fields.VariationalPBEvolve] = { - "model": model, - "mass_ops": self.WMM, - "lin_solver": lin_solver_magfield, - "nonlin_solver": nonlin_solver_magfield, - "gamma": self._gamma, - "bt2": self.pointer["bt2"], - "pt3": self.pointer["pt3"], - } - - self._kwargs[propagators_fields.VariationalViscosity] = { - "model": "full_p", - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "mu": self._mu, - "mu_a": self._mu_a, - "alpha": self._alpha, - "mass_ops": self.WMM, - "lin_solver": lin_solver_viscosity, - "nonlin_solver": nonlin_solver_viscosity, - } - - self._kwargs[propagators_fields.VariationalResistivity] = { - "model": "delta_p", - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "eta": self._eta, - "eta_a": self._eta_a, - "lin_solver": lin_solver_resistivity, - "nonlin_solver": nonlin_solver_resistivity, - "linearize_current": self._linearize_current, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + class Diagnostics(DiagnosticSpecies): + def __init__(self): + self.div_u = FEECVariable(space="L2") + self.u2 = FEECVariable(space="Hdiv") + self.pt3 = FEECVariable(space="L2") + self.bt2 = FEECVariable(space="Hdiv") + self.init_variables() + + ## propagators + + class Propagators: + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() + self.variat_pb = propagators_fields.VariationalPBEvolve() + if with_viscosity: + self.variat_viscous = propagators_fields.VariationalViscosity() + if with_resistivity: + self.variat_resist = propagators_fields.VariationalResistivity() + + ## abstract methods + + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.diagnostics = self.Diagnostics() + + # 2. instantiate all propagators + self.propagators = self.Propagators( + with_viscosity=with_viscosity, + with_resistivity=with_resistivity, + ) + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.mhd.density + self.propagators.variat_dens.variables.u = self.mhd.velocity + self.propagators.variat_mom.variables.u = self.mhd.velocity + self.propagators.variat_pb.variables.u = self.mhd.velocity + self.propagators.variat_pb.variables.p = self.mhd.pressure + self.propagators.variat_pb.variables.b = self.em_fields.b_field + if with_viscosity: + self.propagators.variat_viscous.variables.s = self.mhd.pressure + self.propagators.variat_viscous.variables.u = self.mhd.velocity + if with_resistivity: + self.propagators.variat_resist.variables.s = self.mhd.pressure + self.propagators.variat_resist.variables.b = self.em_fields.b_field + + # define scalars for update_scalar_quantities self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag_1") self.add_scalar("en_mag_2") self.add_scalar("en_tot") - # self.add_scalar("dens_tot") - # self.add_scalar("tot_div_B") - self.add_scalar("en_tot_l1") self.add_scalar("en_thermo_l1") self.add_scalar("en_mag_l1") - # temporary vectors for scalar quantities - tmp_dof = self.derham.Vh_pol["3"].zeros() - projV3 = L2Projector("L2", self.mass_ops) + @property + def bulk_species(self): + return self.mhd + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + projV3 = L2Projector("L2", self._mass_ops) - self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) + def f(e1, e2, e3): + return 1 + + f = xp.vectorize(f) + self._integrator = projV3(f) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1477,52 +1442,95 @@ def __init__(self, params, comm, clone_config=None): else: self._ones[:] = 1.0 + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + def update_scalar_quantities(self): - # Update mass matrix - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) + rho = self.mhd.density.spline.vector + u = self.mhd.velocity.spline.vector + p = self.mhd.pressure.spline.vector + b = self.em_fields.b_field.spline.vector + bt2 = self.propagators.variat_pb.options.bt2.spline.vector + pt3 = self.propagators.variat_pb.options.pt3.spline.vector + + gamma = self.propagators.variat_pb.options.gamma + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) - en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) + en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(b, b) self.update_scalar("en_mag_1", en_mag1) - en_mag2 = self.mass_ops.M2.dot_inner(self.pointer["bt2"], self.projected_equil.b2) + en_mag2 = self.mass_ops.M2.dot_inner(bt2, self.projected_equil.b2) self.update_scalar("en_mag_2", en_mag2) - en_thermo = self.mass_ops.M3.dot_inner(self.pointer["pt3"], self._integrator) / (self._gamma - 1.0) + en_thermo = self.mass_ops.M3.dot_inner(pt3, self._integrator) / (gamma - 1.0) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo + en_mag1 + en_mag2 self.update_scalar("en_tot", en_tot) - # dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) + # dens_tot = self._ones.inner(rho) # self.update_scalar("dens_tot", dens_tot) - # div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) + # div_B = self.derham.div.dot(b, out=self._tmp_div_B) # L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) # self.update_scalar("tot_div_B", L2_div_B) - en_thermo_l1 = self.mass_ops.M3.dot_inner(self.pointer["mhd_p3"], self._integrator) / (self._gamma - 1.0) + en_thermo_l1 = self.mass_ops.M3.dot_inner(p, self._integrator) / (gamma - 1.0) self.update_scalar("en_thermo_l1", en_thermo_l1) - en_mag_l1 = self.mass_ops.M2.dot_inner(self.pointer["b2"], self.projected_equil.b2) + en_mag_l1 = self.mass_ops.M2.dot_inner(b, self.projected_equil.b2) self.update_scalar("en_mag_l1", en_mag_l1) en_tot_l1 = en_thermo_l1 + en_mag_l1 self.update_scalar("en_tot_l1", en_tot_l1) - @staticmethod - def diagnostics_dct(): - dct = {} - dct["bt2"] = "Hdiv" - dct["pt3"] = "L2" - dct["div_u"] = "L2" - dct["u2"] = "Hdiv" - return dct + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='deltaf')\n", + ] + elif "variat_pb.Options" in line: + new_file += [ + "model.propagators.variat_pb.options = model.propagators.variat_pb.Options(model='deltaf',\n", + ] + new_file += [ + " pt3=model.diagnostics.pt3,\n", + ] + new_file += [ + " bt2=model.diagnostics.bt2)\n", + ] + elif "variat_viscous.Options" in line: + new_file += [ + "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='full_p',\n", + ] + new_file += [ + " rho=model.mhd.density)\n", + ] + elif "variat_resist.Options" in line: + new_file += [ + "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='full_p',\n", + ] + new_file += [ + " rho=model.mhd.density)\n", + ] + elif "pressure.add_background" in line: + new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] - __diagnostics__ = diagnostics_dct() + with open(params_path, "w") as f: + for line in new_file: + f.write(line) -class ViscoresistiveMHD_with_q(StruphyModel): +class ViscoResistiveMHD_with_q(StruphyModel): r"""Full (non-linear) visco-resistive MHD equations, with the q variable (square root of the pressure) discretized with a variational method. :ref:`normalization`: @@ -1558,121 +1566,78 @@ class ViscoresistiveMHD_with_q(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["em_fields"]["b2"] = "Hdiv" - dct["fluid"]["mhd"] = {"rho3": "L2", "q3": "L2", "uv": "H1vec"} - return dct + ## species - @staticmethod - def bulk_species(): - return "mhd" + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.sqrt_p = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], - propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], - propagators_fields.VariationalQBEvolve: ["mhd_q3", "b2", "mhd_uv"], - propagators_fields.VariationalViscosity: ["mhd_q3", "mhd_uv"], - propagators_fields.VariationalResistivity: ["mhd_q3", "b2"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import H1vecMassMatrix_density - from struphy.polar.basic import PolarVector - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["lin_solver"] - nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["nonlin_solver"] - lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] - nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] - lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] - nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] - if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): - self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] - else: - self._linearize_current = False - self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] - self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] - self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] - self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] - self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] - model = "full_q" - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - self._kwargs[propagators_fields.VariationalQBEvolve] = { - "model": model, - "mass_ops": self.WMM, - "lin_solver": lin_solver_magfield, - "nonlin_solver": nonlin_solver_magfield, - "gamma": self._gamma, - } - - self._kwargs[propagators_fields.VariationalViscosity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "mu": self._mu, - "mu_a": self._mu_a, - "alpha": self._alpha, - "mass_ops": self.WMM, - "lin_solver": lin_solver_viscosity, - "nonlin_solver": nonlin_solver_viscosity, - } - - self._kwargs[propagators_fields.VariationalResistivity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "eta": self._eta, - "eta_a": self._eta_a, - "lin_solver": lin_solver_resistivity, - "nonlin_solver": nonlin_solver_resistivity, - "linearize_current": self._linearize_current, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + class Diagnostics(DiagnosticSpecies): + def __init__(self): + self.div_u = FEECVariable(space="L2") + self.u2 = FEECVariable(space="Hdiv") + self.init_variables() + + ## propagators + + class Propagators: + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() + self.variat_qb = propagators_fields.VariationalQBEvolve() + if with_viscosity: + self.variat_viscous = propagators_fields.VariationalViscosity() + if with_resistivity: + self.variat_resist = propagators_fields.VariationalResistivity() + + ## abstract methods + + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.diagnostics = self.Diagnostics() + + # 2. instantiate all propagators + self.propagators = self.Propagators( + with_viscosity=with_viscosity, + with_resistivity=with_resistivity, + ) + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.mhd.density + self.propagators.variat_dens.variables.u = self.mhd.velocity + self.propagators.variat_mom.variables.u = self.mhd.velocity + self.propagators.variat_qb.variables.u = self.mhd.velocity + self.propagators.variat_qb.variables.q = self.mhd.sqrt_p + self.propagators.variat_qb.variables.b = self.em_fields.b_field + if with_viscosity: + self.propagators.variat_viscous.variables.s = self.mhd.sqrt_p + self.propagators.variat_viscous.variables.u = self.mhd.velocity + if with_resistivity: + self.propagators.variat_resist.variables.s = self.mhd.sqrt_p + self.propagators.variat_resist.variables.b = self.em_fields.b_field + + # define scalars for update_scalar_quantities self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_mag") @@ -1680,12 +1645,22 @@ def __init__(self, params, comm, clone_config=None): self.add_scalar("dens_tot") self.add_scalar("tot_div_B") - # temporary vectors for scalar quantities - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - tmp_dof = self.derham.Vh_pol["3"].zeros() - projV3 = L2Projector("L2", self.mass_ops) + @property + def bulk_species(self): + return self.mhd - self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + projV3 = L2Projector("L2", self._mass_ops) + + def f(e1, e2, e3): + return 1 + + f = xp.vectorize(f) + self._integrator = projV3(f) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1693,39 +1668,75 @@ def __init__(self, params, comm, clone_config=None): else: self._ones[:] = 1.0 + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + def update_scalar_quantities(self): - # Update mass matrix - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) + rho = self.mhd.density.spline.vector + u = self.mhd.velocity.spline.vector + q = self.mhd.sqrt_p.spline.vector + b = self.em_fields.b_field.spline.vector + + gamma = self.propagators.variat_qb.options.gamma + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) - en_mag = 0.5 * self._mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) + en_mag = 0.5 * self.mass_ops.M2.dot_inner(b, b) self.update_scalar("en_mag", en_mag) - en_thermo = 1 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["mhd_q3"], self.pointer["mhd_q3"]) + en_thermo = 1.0 / (gamma - 1.0) * self._mass_ops.M3.dot_inner(q, q) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo + en_mag self.update_scalar("en_tot", en_tot) - dens_tot = self._ones.inner(self.pointer["mhd_rho3"]) + dens_tot = self._ones.inner(rho) self.update_scalar("dens_tot", dens_tot) - div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) + div_B = self.derham.div.dot(b, out=self._tmp_div_B) L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) self.update_scalar("tot_div_B", L2_div_B) - @staticmethod - def diagnostics_dct(): - dct = {} - - dct["div_u"] = "L2" - dct["u2"] = "Hdiv" - return dct + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='full_q')\n", + ] + elif "variat_qb.Options" in line: + new_file += [ + "model.propagators.variat_qb.options = model.propagators.variat_qb.Options(model='full_q')\n", + ] + elif "variat_viscous.Options" in line: + new_file += [ + "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='full_q',\n", + ] + new_file += [ + " rho=model.mhd.density)\n", + ] + elif "variat_resist.Options" in line: + new_file += [ + "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='full_q',\n", + ] + new_file += [ + " rho=model.mhd.density)\n", + ] + elif "sqrt_p.add_background" in line: + new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] - __diagnostics__ = diagnostics_dct() + with open(params_path, "w") as f: + for line in new_file: + f.write(line) -class ViscoresistiveLinearMHD_with_q(StruphyModel): +class ViscoResistiveLinearMHD_with_q(StruphyModel): r"""Linear visco-resistive MHD equations, with the q variable (square root of the pressure), discretized with a variational method. :ref:`normalization`: @@ -1758,138 +1769,101 @@ class ViscoresistiveLinearMHD_with_q(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["em_fields"]["b2"] = "Hdiv" - dct["fluid"]["mhd"] = {"rho3": "L2", "q3": "L2", "uv": "H1vec"} - return dct + ## species - @staticmethod - def bulk_species(): - return "mhd" + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.sqrt_p = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], - propagators_fields.VariationalQBEvolve: ["mhd_q3", "b2", "mhd_uv"], - propagators_fields.VariationalViscosity: ["mhd_q3", "mhd_uv"], - propagators_fields.VariationalResistivity: ["mhd_q3", "b2"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import H1vecMassMatrix_density - from struphy.polar.basic import PolarVector - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["lin_solver"] - nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["nonlin_solver"] - lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] - nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] - lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] - nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] - if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): - self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] - else: - self._linearize_current = False - self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] - self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] - self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] - self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] - self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] - model = "linear_q" - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - } - - self._kwargs[propagators_fields.VariationalQBEvolve] = { - "model": model, - "mass_ops": self.WMM, - "lin_solver": lin_solver_magfield, - "nonlin_solver": nonlin_solver_magfield, - "gamma": self._gamma, - "div_u": self.pointer["div_u"], - "u2": self.pointer["u2"], - "bt2": self.pointer["bt2"], - "qt3": self.pointer["qt3"], - } - - self._kwargs[propagators_fields.VariationalViscosity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "mu": self._mu, - "mu_a": self._mu_a, - "alpha": self._alpha, - "mass_ops": self.WMM, - "lin_solver": lin_solver_viscosity, - "nonlin_solver": nonlin_solver_viscosity, - "pt3": self.pointer["qt3"], - } - - self._kwargs[propagators_fields.VariationalResistivity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "eta": self._eta, - "eta_a": self._eta_a, - "lin_solver": lin_solver_resistivity, - "nonlin_solver": nonlin_solver_resistivity, - "linearize_current": self._linearize_current, - "pt3": self.pointer["qt3"], - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + class Diagnostics(DiagnosticSpecies): + def __init__(self): + self.div_u = FEECVariable(space="L2") + self.u2 = FEECVariable(space="Hdiv") + self.qt3 = FEECVariable(space="L2") + self.bt2 = FEECVariable(space="Hdiv") + self.init_variables() + + ## propagators + + class Propagators: + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_qb = propagators_fields.VariationalQBEvolve() + if with_viscosity: + self.variat_viscous = propagators_fields.VariationalViscosity() + if with_resistivity: + self.variat_resist = propagators_fields.VariationalResistivity() + + ## abstract methods + + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.diagnostics = self.Diagnostics() + + # 2. instantiate all propagators + self.propagators = self.Propagators( + with_viscosity=with_viscosity, + with_resistivity=with_resistivity, + ) + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.mhd.density + self.propagators.variat_dens.variables.u = self.mhd.velocity + self.propagators.variat_qb.variables.u = self.mhd.velocity + self.propagators.variat_qb.variables.q = self.mhd.sqrt_p + self.propagators.variat_qb.variables.b = self.em_fields.b_field + if with_viscosity: + self.propagators.variat_viscous.variables.s = self.mhd.sqrt_p + self.propagators.variat_viscous.variables.u = self.mhd.velocity + if with_resistivity: + self.propagators.variat_resist.variables.s = self.mhd.sqrt_p + self.propagators.variat_resist.variables.b = self.em_fields.b_field + + # define scalars for update_scalar_quantities self.add_scalar("en_U") - # self.add_scalar("en_thermo_1") - # self.add_scalar("en_thermo_2") - # self.add_scalar("en_mag_1") - # self.add_scalar("en_mag_2") + self.add_scalar("en_mag_1") + self.add_scalar("en_mag_2") + self.add_scalar("en_thermo_1") + self.add_scalar("en_thermo_2") self.add_scalar("en_tot") - # self.add_scalar("dens_tot") - # self.add_scalar("tot_div_B") + @property + def bulk_species(self): + return self.mhd + + @property + def velocity_scale(self): + return "alfvén" - # self.add_scalar("en_tot_l1") - # self.add_scalar("en_thermo_l1") - # self.add_scalar("en_mag_l1") + def allocate_helpers(self): + projV3 = L2Projector("L2", self._mass_ops) - # temporary vectors for scalar quantities - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - tmp_dof = self.derham.Vh_pol["3"].zeros() - projV3 = L2Projector("L2", self.mass_ops) + def f(e1, e2, e3): + return 1 - self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) + f = xp.vectorize(f) + self._integrator = projV3(f) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -1897,56 +1871,94 @@ def __init__(self, params, comm, clone_config=None): else: self._ones[:] = 1.0 + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + def update_scalar_quantities(self): - # Update mass matrix - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) + rho = self.mhd.density.spline.vector + u = self.mhd.velocity.spline.vector + q = self.mhd.sqrt_p.spline.vector + b = self.em_fields.b_field.spline.vector + bt2 = self.propagators.variat_qb.options.bt2.spline.vector + qt3 = self.propagators.variat_qb.options.qt3.spline.vector + + gamma = self.propagators.variat_qb.options.gamma + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) - en_mag1 = self._mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) - # self.update_scalar("en_mag_1", en_mag1) + en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(b, b) + self.update_scalar("en_mag_1", en_mag1) - en_mag2 = self._mass_ops.M2.dot_inner(self.pointer["bt2"], self.projected_equil.b2) - # self.update_scalar("en_mag_2", en_mag2) + en_mag2 = self.mass_ops.M2.dot_inner(bt2, self.projected_equil.b2) + self.update_scalar("en_mag_2", en_mag2) - en_th_1 = 1 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["mhd_q3"], self.pointer["mhd_q3"]) - # self.update_scalar("en_thermo_1", en_th_1) + en_th_1 = 1.0 / (gamma - 1.0) * self.mass_ops.M3.dot_inner(q, q) + self.update_scalar("en_thermo_1", en_th_1) - en_th_2 = 2 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["qt3"], self.projected_equil.q3) - # self.update_scalar("en_thermo_2", en_th_2) + en_th_2 = 2.0 / (gamma - 1.0) * self.mass_ops.M3.dot_inner(qt3, self.projected_equil.q3) + self.update_scalar("en_thermo_2", en_th_2) en_tot = en_U + en_th_1 + en_th_2 + en_mag1 + en_mag2 self.update_scalar("en_tot", en_tot) - # dens_tot = self._ones.dot(self.pointer["mhd_rho3"]) - # self.update_scalar("dens_tot", dens_tot) - - # div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) - # L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) - # self.update_scalar("tot_div_B", L2_div_B) - - # en_thermo_l1 = self._integrator.dot(self.mass_ops.M3.dot(self.pointer["mhd_p3"])) / (self._gamma - 1.0) - # self.update_scalar("en_thermo_l1", en_thermo_l1) - - # wb2 = self._mass_ops.M2.dot(self.pointer["b2"], out=self._tmp_wb2) - # en_mag_l1 = wb2.dot(self.projected_equil.b2) - # self.update_scalar("en_mag_l1", en_mag_l1) - - # en_tot_l1 = en_thermo_l1 + en_mag_l1 - # self.update_scalar("en_tot_l1", en_tot_l1) - - @staticmethod - def diagnostics_dct(): - dct = {} - dct["bt2"] = "Hdiv" - dct["qt3"] = "L2" - dct["div_u"] = "L2" - dct["u2"] = "Hdiv" - return dct + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='linear_q')\n", + ] + elif "variat_qb.Options" in line: + new_file += [ + "model.propagators.variat_qb.options = model.propagators.variat_qb.Options(model='linear_q',\n", + ] + new_file += [ + " div_u=model.diagnostics.div_u,\n", + ] + new_file += [ + " u2=model.diagnostics.u2,\n", + ] + new_file += [ + " qt3=model.diagnostics.qt3,\n", + ] + new_file += [ + " bt2=model.diagnostics.bt2)\n", + ] + elif "variat_viscous.Options" in line: + new_file += [ + "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='linear_q',\n", + ] + new_file += [ + " rho=model.mhd.density,\n", + ] + new_file += [ + " pt3=model.diagnostics.qt3)\n", + ] + elif "variat_resist.Options" in line: + new_file += [ + "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='linear_q',\n", + ] + new_file += [ + " rho=model.mhd.density,\n", + ] + new_file += [ + " pt3=model.diagnostics.qt3)\n", + ] + elif "sqrt_p.add_background" in line: + new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] - __diagnostics__ = diagnostics_dct() + with open(params_path, "w") as f: + for line in new_file: + f.write(line) -class ViscoresistiveDeltafMHD_with_q(StruphyModel): +class ViscoResistiveDeltafMHD_with_q(StruphyModel): r"""Linear visco-resistive MHD equations discretized with a variational method. :ref:`normalization`: @@ -1980,147 +1992,103 @@ class ViscoresistiveDeltafMHD_with_q(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["em_fields"]["b2"] = "Hdiv" - dct["fluid"]["mhd"] = {"rho3": "L2", "q3": "L2", "uv": "H1vec"} - return dct + ## species - @staticmethod - def bulk_species(): - return "mhd" + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.sqrt_p = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["mhd_rho3", "mhd_uv"], - propagators_fields.VariationalMomentumAdvection: ["mhd_uv"], - propagators_fields.VariationalQBEvolve: ["mhd_q3", "b2", "mhd_uv"], - propagators_fields.VariationalViscosity: ["mhd_q3", "mhd_uv"], - propagators_fields.VariationalResistivity: ["mhd_q3", "b2"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import H1vecMassMatrix_density - from struphy.polar.basic import PolarVector - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["mhd"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["lin_solver"] - nonlin_solver_magfield = params["fluid"]["mhd"]["options"]["VariationalQBEvolve"]["nonlin_solver"] - lin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["lin_solver"] - nonlin_solver_viscosity = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["nonlin_solver"] - lin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["lin_solver"] - nonlin_solver_resistivity = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["nonlin_solver"] - if "linearize_current" in params["fluid"]["mhd"]["options"]["VariationalResistivity"].keys(): - self._linearize_current = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["linearize_current"] - else: - self._linearize_current = False - self._gamma = params["fluid"]["mhd"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - self._mu = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu"] - self._mu_a = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["mu_a"] - self._alpha = params["fluid"]["mhd"]["options"]["VariationalViscosity"]["physics"]["alpha"] - self._eta = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta"] - self._eta_a = params["fluid"]["mhd"]["options"]["VariationalResistivity"]["physics"]["eta_a"] - model = "deltaf_q" - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - self._kwargs[propagators_fields.VariationalQBEvolve] = { - "model": model, - "mass_ops": self.WMM, - "lin_solver": lin_solver_magfield, - "nonlin_solver": nonlin_solver_magfield, - "gamma": self._gamma, - "div_u": self.pointer["div_u"], - "u2": self.pointer["u2"], - "bt2": self.pointer["bt2"], - "qt3": self.pointer["qt3"], - } - - self._kwargs[propagators_fields.VariationalViscosity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "mu": self._mu, - "mu_a": self._mu_a, - "alpha": self._alpha, - "mass_ops": self.WMM, - "lin_solver": lin_solver_viscosity, - "nonlin_solver": nonlin_solver_viscosity, - "pt3": self.pointer["qt3"], - } - - self._kwargs[propagators_fields.VariationalResistivity] = { - "model": model, - "rho": self.pointer["mhd_rho3"], - "gamma": self._gamma, - "eta": self._eta, - "eta_a": self._eta_a, - "lin_solver": lin_solver_resistivity, - "nonlin_solver": nonlin_solver_resistivity, - "linearize_current": self._linearize_current, - "pt3": self.pointer["qt3"], - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + class Diagnostics(DiagnosticSpecies): + def __init__(self): + self.div_u = FEECVariable(space="L2") + self.u2 = FEECVariable(space="Hdiv") + self.qt3 = FEECVariable(space="L2") + self.bt2 = FEECVariable(space="Hdiv") + self.init_variables() + + ## propagators + + class Propagators: + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() + self.variat_qb = propagators_fields.VariationalQBEvolve() + if with_viscosity: + self.variat_viscous = propagators_fields.VariationalViscosity() + if with_resistivity: + self.variat_resist = propagators_fields.VariationalResistivity() + + ## abstract methods + + def __init__( + self, + with_viscosity: bool = True, + with_resistivity: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.diagnostics = self.Diagnostics() + + # 2. instantiate all propagators + self.propagators = self.Propagators( + with_viscosity=with_viscosity, + with_resistivity=with_resistivity, + ) + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.mhd.density + self.propagators.variat_dens.variables.u = self.mhd.velocity + self.propagators.variat_mom.variables.u = self.mhd.velocity + self.propagators.variat_qb.variables.u = self.mhd.velocity + self.propagators.variat_qb.variables.q = self.mhd.sqrt_p + self.propagators.variat_qb.variables.b = self.em_fields.b_field + if with_viscosity: + self.propagators.variat_viscous.variables.s = self.mhd.sqrt_p + self.propagators.variat_viscous.variables.u = self.mhd.velocity + if with_resistivity: + self.propagators.variat_resist.variables.s = self.mhd.sqrt_p + self.propagators.variat_resist.variables.b = self.em_fields.b_field + + # define scalars for update_scalar_quantities self.add_scalar("en_U") - self.add_scalar("en_thermo_1") - self.add_scalar("en_thermo_2") self.add_scalar("en_mag_1") self.add_scalar("en_mag_2") + self.add_scalar("en_thermo_1") + self.add_scalar("en_thermo_2") self.add_scalar("en_tot") - # self.add_scalar("dens_tot") - # self.add_scalar("tot_div_B") + @property + def bulk_species(self): + return self.mhd + + @property + def velocity_scale(self): + return "alfvén" - # self.add_scalar("en_tot_l1") - # self.add_scalar("en_thermo_l1") - # self.add_scalar("en_mag_l1") + def allocate_helpers(self): + projV3 = L2Projector("L2", self._mass_ops) - # temporary vectors for scalar quantities - self._tmp_div_B = self.derham.Vh_pol["3"].zeros() - tmp_dof = self.derham.Vh_pol["3"].zeros() - projV3 = L2Projector("L2", self.mass_ops) + def f(e1, e2, e3): + return 1 - self._integrator = projV3(self.domain.jacobian_det, dofs=tmp_dof) + f = xp.vectorize(f) + self._integrator = projV3(f) self._ones = self.derham.Vh_pol["3"].zeros() if isinstance(self._ones, PolarVector): @@ -2128,53 +2096,91 @@ def __init__(self, params, comm, clone_config=None): else: self._ones[:] = 1.0 + self._tmp_div_B = self.derham.Vh_pol["3"].zeros() + def update_scalar_quantities(self): - # Update mass matrix - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["mhd_uv"], self.pointer["mhd_uv"]) + rho = self.mhd.density.spline.vector + u = self.mhd.velocity.spline.vector + q = self.mhd.sqrt_p.spline.vector + b = self.em_fields.b_field.spline.vector + bt2 = self.propagators.variat_qb.options.bt2.spline.vector + qt3 = self.propagators.variat_qb.options.qt3.spline.vector + + gamma = self.propagators.variat_qb.options.gamma + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) - en_mag1 = 0.5 * self._mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) + en_mag1 = 0.5 * self.mass_ops.M2.dot_inner(b, b) self.update_scalar("en_mag_1", en_mag1) - en_mag2 = 0.5 * self._mass_ops.M2.dot_inner(self.pointer["bt2"], self.projected_equil.b2) + en_mag2 = self.mass_ops.M2.dot_inner(bt2, self.projected_equil.b2) self.update_scalar("en_mag_2", en_mag2) - en_th_1 = 1 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["mhd_q3"], self.pointer["mhd_q3"]) + en_th_1 = 1.0 / (gamma - 1.0) * self.mass_ops.M3.dot_inner(q, q) self.update_scalar("en_thermo_1", en_th_1) - en_th_2 = 2 / (self._gamma - 1) * self._mass_ops.M3.dot_inner(self.pointer["qt3"], self.projected_equil.q3) + en_th_2 = 2.0 / (gamma - 1.0) * self.mass_ops.M3.dot_inner(qt3, self.projected_equil.q3) self.update_scalar("en_thermo_2", en_th_2) en_tot = en_U + en_th_1 + en_th_2 + en_mag1 + en_mag2 self.update_scalar("en_tot", en_tot) - # dens_tot = self._ones.dot(self.pointer["mhd_rho3"]) - # self.update_scalar("dens_tot", dens_tot) - - # div_B = self.derham.div.dot(self.pointer["b2"], out=self._tmp_div_B) - # L2_div_B = self._mass_ops.M3.dot_inner(div_B, div_B) - # self.update_scalar("tot_div_B", L2_div_B) - - # en_thermo_l1 = self._integrator.dot(self.mass_ops.M3.dot(self.pointer["mhd_p3"])) / (self._gamma - 1.0) - # self.update_scalar("en_thermo_l1", en_thermo_l1) - - # wb2 = self._mass_ops.M2.dot(self.pointer["b2"], out=self._tmp_wb2) - # en_mag_l1 = wb2.dot(self.projected_equil.b2) - # self.update_scalar("en_mag_l1", en_mag_l1) - - # en_tot_l1 = en_thermo_l1 + en_mag_l1 - # self.update_scalar("en_tot_l1", en_tot_l1) - - @staticmethod - def diagnostics_dct(): - dct = {} - dct["bt2"] = "Hdiv" - dct["qt3"] = "L2" - dct["div_u"] = "L2" - dct["u2"] = "Hdiv" - return dct + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='deltaf_q')\n", + ] + elif "variat_qb.Options" in line: + new_file += [ + "model.propagators.variat_qb.options = model.propagators.variat_qb.Options(model='deltaf_q',\n", + ] + new_file += [ + " div_u=model.diagnostics.div_u,\n", + ] + new_file += [ + " u2=model.diagnostics.u2,\n", + ] + new_file += [ + " qt3=model.diagnostics.qt3,\n", + ] + new_file += [ + " bt2=model.diagnostics.bt2)\n", + ] + elif "variat_viscous.Options" in line: + new_file += [ + "model.propagators.variat_viscous.options = model.propagators.variat_viscous.Options(model='deltaf_q',\n", + ] + new_file += [ + " rho=model.mhd.density,\n", + ] + new_file += [ + " pt3=model.diagnostics.qt3)\n", + ] + elif "variat_resist.Options" in line: + new_file += [ + "model.propagators.variat_resist.options = model.propagators.variat_resist.Options(model='deltaf_q',\n", + ] + new_file += [ + " rho=model.mhd.density,\n", + ] + new_file += [ + " pt3=model.diagnostics.qt3)\n", + ] + elif "sqrt_p.add_background" in line: + new_file += ["model.mhd.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] - __diagnostics__ = diagnostics_dct() + with open(params_path, "w") as f: + for line in new_file: + f.write(line) class EulerSPH(StruphyModel): @@ -2274,7 +2280,7 @@ def update_scalar_quantities(self): particles = self.euler_fluid.var.particles valid_markers = particles.markers_wo_holes_and_ghost en_kin = valid_markers[:, 6].dot( - valid_markers[:, 3] ** 2 + valid_markers[:, 4] ** 2 + valid_markers[:, 5] ** 2 + valid_markers[:, 3] ** 2 + valid_markers[:, 4] ** 2 + valid_markers[:, 5] ** 2, ) / (2.0 * particles.Np) self.update_scalar("en_kin", en_kin) @@ -2330,119 +2336,99 @@ class HasegawaWakatani(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species - dct["em_fields"] = {"phi0": "H1"} - dct["fluid"]["hw"] = { - "n0": "H1", - "omega0": "H1", - } - return dct + class EMFields(FieldSpecies): + def __init__(self): + self.phi = FEECVariable(space="H1") + self.init_variables() - @staticmethod - def bulk_species(): - return "hw" + class Plasma(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="H1") + self.vorticity = FEECVariable(space="H1") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + ## propagators - # @staticmethod - # def diagnostics_dct(): - # dct = {} - # dct["projected_density"] = "L2" - # return dct + class Propagators: + def __init__(self): + self.poisson = propagators_fields.Poisson() + self.hw = propagators_fields.HasegawaWakatani() - @staticmethod - def propagators_dct(): - return { - propagators_fields.Poisson: ["phi0"], - propagators_fields.HasegawaWakatani: ["hw_n0", "hw_omega0"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - from struphy.polar.basic import PolarVector - - # extract necessary parameters - self._stab_eps = params["em_fields"]["options"]["Poisson"]["stabilization"]["stab_eps"] - self._stab_mat = params["em_fields"]["options"]["Poisson"]["stabilization"]["stab_mat"] - self._solver = params["em_fields"]["options"]["Poisson"]["solver"] - c_fun = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["c_fun"] - kappa = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["kappa"] - nu = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["nu"] - algo = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["algo"] - M0_solver = params["fluid"]["hw"]["options"]["HasegawaWakatani"]["M0_solver"] - - # rhs of Poisson - self._rho = self.derham.Vh["0"].zeros() - self.update_rho() + ## abstract methods + + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.plasma = self.Plasma() + + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.poisson.variables.phi = self.em_fields.phi + self.propagators.hw.variables.n = self.plasma.density + self.propagators.hw.variables.omega = self.plasma.vorticity + + # define scalars for update_scalar_quantities + + @property + def bulk_species(self): + return self.plasma + + @property + def velocity_scale(self): + return "alfvén" - # set keyword arguments for propagators - self._kwargs[propagators_fields.Poisson] = { - "stab_eps": self._stab_eps, - "stab_mat": self._stab_mat, - "rho": self.update_rho, - "solver": self._solver, - } - - self._kwargs[propagators_fields.HasegawaWakatani] = { - "phi": self.em_fields["phi0"]["obj"], - "c_fun": c_fun, - "kappa": kappa, - "nu": nu, - "algo": algo, - "M0_solver": M0_solver, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() + def allocate_helpers(self): + self._rho: StencilVector = self.derham.Vh["0"].zeros() + self.update_rho() def update_rho(self): - self._rho = self.mass_ops.M0.dot(self.pointer["hw_omega0"], out=self._rho) + omega = self.plasma.vorticity.spline.vector + self._rho = self.mass_ops.M0.dot(omega, out=self._rho) self._rho.update_ghost_regions() return self._rho - def initialize_from_params(self): + def allocate_propagators(self): """Solve initial Poisson equation. :meta private: """ # initialize fields and particles - super().initialize_from_params() + super().allocate_propagators() - if self.rank_world == 0: + if MPI.COMM_WORLD.Get_rank() == 0: print("\nINITIAL POISSON SOLVE:") - # Instantiate Poisson solver - poisson_solver = propagators_fields.Poisson( - self.pointer["phi0"], - stab_eps=self._stab_eps, - stab_mat=self._stab_mat, - rho=self._rho, - solver=self._solver, - ) - - # Solve with dt=1. and compute electric field - if self.rank_world == 0: - print("\nSolving initial Poisson problem...") - self.update_rho() - poisson_solver(1.0) + self.propagators.poisson(1.0) - if self.rank_world == 0: + if MPI.COMM_WORLD.Get_rank() == 0: print("Done.") def update_scalar_quantities(self): pass + + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "hw.Options" in line: + new_file += [ + "model.propagators.hw.options = model.propagators.hw.Options(phi=model.em_fields.phi)\n", + ] + elif "vorticity.add_background" in line: + new_file += ["model.plasma.density.add_background(FieldsBackground())\n"] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) diff --git a/src/struphy/models/hybrid.py b/src/struphy/models/hybrid.py index ee93662ca..c1952f59c 100644 --- a/src/struphy/models/hybrid.py +++ b/src/struphy/models/hybrid.py @@ -1,8 +1,16 @@ -import numpy as np +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from struphy.models.base import StruphyModel +from struphy.models.species import FieldSpecies, FluidSpecies, ParticleSpecies +from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.pic.accumulation import accum_kernels, accum_kernels_gc +from struphy.pic.accumulation.particles_to_grid import AccumulatorVector +from struphy.polar.basic import PolarVector from struphy.propagators import propagators_coupling, propagators_fields, propagators_markers +from struphy.utils.pyccel import Pyccelkernel + +rank = MPI.COMM_WORLD.Get_rank() class LinearMHDVlasovCC(StruphyModel): @@ -61,187 +69,109 @@ class LinearMHDVlasovCC(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - - dct["em_fields"]["b_field"] = "Hdiv" - dct["fluid"]["mhd"] = {"density": "L2", "velocity": "Hdiv", "pressure": "L2"} - dct["kinetic"]["energetic_ions"] = "Particles6D" - return dct - - @staticmethod - def bulk_species(): - return "mhd" + ## species - @staticmethod - def velocity_scale(): - return "alfvén" + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - @staticmethod - def propagators_dct(): - return { - propagators_fields.CurrentCoupling6DDensity: ["mhd_velocity"], - propagators_fields.ShearAlfven: ["mhd_velocity", "b_field"], - propagators_coupling.CurrentCoupling6DCurrent: ["energetic_ions", "mhd_velocity"], - propagators_markers.PushEta: ["energetic_ions"], - propagators_markers.PushVxB: ["energetic_ions"], - propagators_fields.Magnetosonic: ["mhd_density", "mhd_velocity", "mhd_pressure"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - # add special options - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["fluid", "mhd"], - key="u_space", - option="Hdiv", - dct=dct, - ) - return dct + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="Hdiv") + self.pressure = FEECVariable(space="L2") + self.init_variables() - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) + class EnergeticIons(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="Particles6D") + self.init_variables() - from mpi4py.MPI import IN_PLACE, SUM + ## propagators - from struphy.polar.basic import PolarVector + class Propagators: + def __init__(self): + self.couple_dens = propagators_fields.CurrentCoupling6DDensity() + self.shear_alf = propagators_fields.ShearAlfven() + self.couple_curr = propagators_coupling.CurrentCoupling6DCurrent() + self.push_eta = propagators_markers.PushEta() + self.push_vxb = propagators_markers.PushVxB() + self.mag_sonic = propagators_fields.Magnetosonic() - # prelim - e_ions_params = self.kinetic["energetic_ions"]["params"] + ## abstract methods - # extract necessary parameters - u_space = params["fluid"]["mhd"]["options"]["u_space"] - params_alfven = params["fluid"]["mhd"]["options"]["ShearAlfven"] - params_sonic = params["fluid"]["mhd"]["options"]["Magnetosonic"] - params_eta = params["kinetic"]["energetic_ions"]["options"]["PushEta"] - params_vxb = params["kinetic"]["energetic_ions"]["options"]["PushVxB"] - params_density = params["fluid"]["mhd"]["options"]["CurrentCoupling6DDensity"] - params_current = params["kinetic"]["energetic_ions"]["options"]["CurrentCoupling6DCurrent"] + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - # compute coupling parameters - Ab = params["fluid"]["mhd"]["phys_params"]["A"] - Ah = params["kinetic"]["energetic_ions"]["phys_params"]["A"] - epsilon = self.equation_params["energetic_ions"]["epsilon"] + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.energetic_ions = self.EnergeticIons() - if abs(epsilon - 1) < 1e-6: - epsilon = 1.0 + # 2. instantiate all propagators + self.propagators = self.Propagators() - self._Ab = Ab - self._Ah = Ah + # 3. assign variables to propagators + self.propagators.couple_dens.variables.u = self.mhd.velocity - # add control variate to mass_ops object - if self.pointer["energetic_ions"].control_variate: - self.mass_ops.weights["f0"] = self.pointer["energetic_ions"].f0 - - # project background magnetic field (2-form) and background pressure (3-form) - self._b_eq = self.derham.P["2"]( - [ - self.equil.b2_1, - self.equil.b2_2, - self.equil.b2_3, - ] - ) - self._p_eq = self.derham.P["3"](self.equil.p3) - self._ones = self._p_eq.space.zeros() - - if isinstance(self._ones, PolarVector): - self._ones.tp[:] = 1.0 - else: - self._ones[:] = 1.0 + self.propagators.shear_alf.variables.u = self.mhd.velocity + self.propagators.shear_alf.variables.b = self.em_fields.b_field - # set keyword arguments for propagators - if params_density["turn_off"]: - self._kwargs[propagators_fields.CurrentCoupling6DDensity] = None - else: - self._kwargs[propagators_fields.CurrentCoupling6DDensity] = { - "particles": self.pointer["energetic_ions"], - "u_space": u_space, - "b_eq": self._b_eq, - "b_tilde": self.pointer["b_field"], - "Ab": Ab, - "Ah": Ah, - "epsilon": epsilon, - "solver": params_density["solver"], - "filter": params_density["filter"], - "boundary_cut": params_density["boundary_cut"], - } - - if params_alfven["turn_off"]: - self._kwargs[propagators_fields.ShearAlfven] = None - else: - self._kwargs[propagators_fields.ShearAlfven] = { - "u_space": u_space, - "solver": params_alfven["solver"], - } + self.propagators.couple_curr.variables.ions = self.energetic_ions.var + self.propagators.couple_curr.variables.u = self.mhd.velocity - if params_current["turn_off"]: - self._kwargs[propagators_coupling.CurrentCoupling6DCurrent] = None - else: - self._kwargs[propagators_coupling.CurrentCoupling6DCurrent] = { - "u_space": u_space, - "b_eq": self._b_eq, - "b_tilde": self.pointer["b_field"], - "Ab": Ab, - "Ah": Ah, - "epsilon": epsilon, - "solver": params_current["solver"], - "filter": params_current["filter"], - "boundary_cut": params_current["boundary_cut"], - } - - self._kwargs[propagators_markers.PushEta] = { - "algo": params_eta["algo"], - } - - self._kwargs[propagators_markers.PushVxB] = { - "algo": params_vxb["algo"], - "kappa": 1.0 / epsilon, - "b2": self.pointer["b_field"], - "b2_add": self._b_eq, - } - - if params_sonic["turn_off"]: - self._kwargs[propagators_fields.Magnetosonic] = None - else: - self._kwargs[propagators_fields.Magnetosonic] = { - "u_space": u_space, - "b": self.pointer["b_field"], - "solver": params_sonic["solver"], - } + self.propagators.push_eta.variables.var = self.energetic_ions.var + self.propagators.push_vxb.variables.ions = self.energetic_ions.var - # Initialize propagators used in splitting substeps - self.init_propagators() + self.propagators.mag_sonic.variables.n = self.mhd.density + self.propagators.mag_sonic.variables.u = self.mhd.velocity + self.propagators.mag_sonic.variables.p = self.mhd.pressure - # Scalar variables to be saved during simulation: + # define scalars for update_scalar_quantities self.add_scalar("en_U", compute="from_field") self.add_scalar("en_p", compute="from_field") self.add_scalar("en_B", compute="from_field") - self.add_scalar("en_f", compute="from_particles", species="energetic_ions") + self.add_scalar("en_f", compute="from_particles", variable=self.energetic_ions.var) self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_f"]) + self.add_scalar("n_lost_particles", compute="from_particles", variable=self.energetic_ions.var) + + @property + def bulk_species(self): + return self.mhd + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + self._ones = self.projected_equil.p3.space.zeros() + if isinstance(self._ones, PolarVector): + self._ones.tp[:] = 1.0 + else: + self._ones[:] = 1.0 + + self._tmp = xp.empty(1, dtype=float) + self._n_lost_particles = xp.empty(1, dtype=float) - # temporary vectors for scalar quantities: - self._tmp = np.empty(1, dtype=float) - self._n_lost_particles = np.empty(1, dtype=float) + # add control variate to mass_ops object + if self.energetic_ions.var.particles.control_variate: + self.mass_ops.weights["f0"] = self.energetic_ions.var.particles.f0 - # MPI operations needed for scalar variables - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE + self._Ah = self.energetic_ions.mass_number + self._Ab = self.mhd.mass_number def update_scalar_quantities(self): # perturbed fields - en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_velocity"], self.pointer["mhd_velocity"]) - en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) - en_p = self.pointer["mhd_pressure"].inner(self._ones) / (5 / 3 - 1) + u = self.mhd.velocity.spline.vector + p = self.mhd.pressure.spline.vector + b = self.em_fields.b_field.spline.vector + particles = self.energetic_ions.var.particles + + en_U = 0.5 * self.mass_ops.M2n.dot_inner(u, u) + en_B = 0.5 * self.mass_ops.M2.dot_inner(b, b) + en_p = p.inner(self._ones) / (5 / 3 - 1) self.update_scalar("en_U", en_U) self.update_scalar("en_B", en_B) @@ -251,12 +181,10 @@ def update_scalar_quantities(self): self._tmp[0] = ( self._Ah / self._Ab - * self.pointer["energetic_ions"] - .markers_wo_holes[:, 6] - .dot( - self.pointer["energetic_ions"].markers_wo_holes[:, 3] ** 2 - + self.pointer["energetic_ions"].markers_wo_holes[:, 4] ** 2 - + self.pointer["energetic_ions"].markers_wo_holes[:, 5] ** 2, + * particles.markers_wo_holes[:, 6].dot( + particles.markers_wo_holes[:, 3] ** 2 + + particles.markers_wo_holes[:, 4] ** 2 + + particles.markers_wo_holes[:, 5] ** 2, ) / (2) ) @@ -265,15 +193,47 @@ def update_scalar_quantities(self): self.update_scalar("en_tot", en_U + en_B + en_p + self._tmp[0]) # Print number of lost ions - self._n_lost_particles[0] = self.pointer["energetic_ions"].n_lost_markers - self.derham.comm.Allreduce(self._mpi_in_place, self._n_lost_particles, op=self._mpi_sum) - if self.derham.comm.Get_rank() == 0: + self._n_lost_particles[0] = particles.n_lost_markers + self.update_scalar("n_lost_particles", self._n_lost_particles[0]) + + if rank == 0: print( "ratio of lost particles: ", - self._n_lost_particles[0] / self.pointer["energetic_ions"].Np * 100, + self._n_lost_particles[0] / particles.Np * 100, "%", ) + ## default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "mag_sonic.Options" in line: + new_file += [ + "model.propagators.mag_sonic.options = model.propagators.mag_sonic.Options(b_field=model.em_fields.b_field)\n", + ] + elif "couple_dens.Options" in line: + new_file += [ + "model.propagators.couple_dens.options = model.propagators.couple_dens.Options(energetic_ions=model.energetic_ions.var,\n", + ] + new_file += [ + " b_tilde=model.em_fields.b_field)\n", + ] + elif "couple_curr.Options" in line: + new_file += [ + "model.propagators.couple_curr.options = model.propagators.couple_curr.Options(b_tilde=model.em_fields.b_field)\n", + ] + elif "set_save_data" in line: + new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] + new_file += ["model.energetic_ions.set_save_data(binning_plots=(binplot,))\n"] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + class LinearMHDVlasovPC(StruphyModel): r""" @@ -337,214 +297,192 @@ class LinearMHDVlasovPC(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - - dct["em_fields"]["b_field"] = "Hdiv" - dct["fluid"]["mhd"] = { - "density": "L2", - "velocity": "Hdiv", - "pressure": "L2", - } - dct["kinetic"]["energetic_ions"] = "Particles6D" - return dct - - @staticmethod - def bulk_species(): - return "mhd" - - @staticmethod - def velocity_scale(): - return "alfvén" - - @staticmethod - def propagators_dct(): - return { - propagators_markers.PushEtaPC: ["energetic_ions"], - propagators_markers.PushVxB: ["energetic_ions"], - propagators_coupling.PressureCoupling6D: ["energetic_ions", "mhd_velocity"], - propagators_fields.ShearAlfven: ["mhd_velocity", "b_field"], - propagators_fields.Magnetosonic: ["mhd_density", "mhd_velocity", "mhd_pressure"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - # add special options - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["fluid", "mhd"], - key="u_space", - option="Hdiv", - dct=dct, - ) - return dct - - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - from mpi4py.MPI import IN_PLACE, SUM - - from struphy.polar.basic import PolarVector - - # extract necessary parameters - u_space = params["fluid"]["mhd"]["options"]["u_space"] - params_alfven = params["fluid"]["mhd"]["options"]["ShearAlfven"] - params_sonic = params["fluid"]["mhd"]["options"]["Magnetosonic"] - params_vxb = params["kinetic"]["energetic_ions"]["options"]["PushVxB"] - params_pressure = params["kinetic"]["energetic_ions"]["options"]["PressureCoupling6D"] - - # use perp model - assert ( - params["kinetic"]["energetic_ions"]["options"]["PressureCoupling6D"]["use_perp_model"] - == params["kinetic"]["energetic_ions"]["options"]["PressureCoupling6D"]["use_perp_model"] + ## species + class EnergeticIons(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="Particles6D") + self.init_variables() + + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() + + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.pressure = FEECVariable(space="L2") + self.velocity = FEECVariable(space="Hdiv") + self.init_variables() + + ## propagators + + class Propagators: + def __init__(self, turn_off: tuple[str, ...] = (None,)): + if "PushEtaPC" not in turn_off: + self.push_eta_pc = propagators_markers.PushEtaPC() + if "PushVxB" not in turn_off: + self.push_vxb = propagators_markers.PushVxB() + if "PressureCoupling6D" not in turn_off: + self.pc6d = propagators_coupling.PressureCoupling6D() + if "ShearAlfven" not in turn_off: + self.shearalfven = propagators_fields.ShearAlfven() + if "Magnetosonic" not in turn_off: + self.magnetosonic = propagators_fields.Magnetosonic() + + def __init__(self, turn_off: tuple[str, ...] = (None,)): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.energetic_ions = self.EnergeticIons() + + # 2. instantiate all propagators + self.propagators = self.Propagators(turn_off) + + # 3. assign variables to propagators + if "ShearAlfven" not in turn_off: + self.propagators.shearalfven.variables.u = self.mhd.velocity + self.propagators.shearalfven.variables.b = self.em_fields.b_field + if "Magnetosonic" not in turn_off: + self.propagators.magnetosonic.variables.n = self.mhd.density + self.propagators.magnetosonic.variables.u = self.mhd.velocity + self.propagators.magnetosonic.variables.p = self.mhd.pressure + if "PressureCoupling6D" not in turn_off: + self.propagators.pc6d.variables.u = self.mhd.velocity + self.propagators.pc6d.variables.energetic_ions = self.energetic_ions.var + if "PushEtaPC" not in turn_off: + self.propagators.push_eta_pc.variables.var = self.energetic_ions.var + if "PushVxB" not in turn_off: + self.propagators.push_vxb.variables.ions = self.energetic_ions.var + + # define scalars for update_scalar_quantities + self.add_scalar("en_U") + self.add_scalar("en_p") + self.add_scalar("en_B") + self.add_scalar("en_f", compute="from_particles", variable=self.energetic_ions.var) + self.add_scalar( + "en_tot", + summands=[ + "en_U", + "en_p", + "en_B", + "en_f", + ], ) - use_perp_model = params["kinetic"]["energetic_ions"]["options"]["PressureCoupling6D"]["use_perp_model"] - - # compute coupling parameters - Ab = params["fluid"]["mhd"]["phys_params"]["A"] - Ah = params["kinetic"]["energetic_ions"]["phys_params"]["A"] - epsilon = self.equation_params["energetic_ions"]["epsilon"] - if abs(epsilon - 1) < 1e-6: - epsilon = 1.0 + @property + def bulk_species(self): + return self.mhd - self._coupling_params = {} - self._coupling_params["Ab"] = Ab - self._coupling_params["Ah"] = Ah - self._coupling_params["epsilon"] = epsilon - - # add control variate to mass_ops object - if self.pointer["energetic_ions"].control_variate: - self.mass_ops.weights["f0"] = self.pointer["energetic_ions"].f0 - - # Project magnetic field - self._b_eq = self.derham.P["2"]( - [ - self.equil.b2_1, - self.equil.b2_2, - self.equil.b2_3, - ] - ) - self._p_eq = self.derham.P["3"](self.equil.p3) - self._ones = self._p_eq.space.zeros() + @property + def velocity_scale(self): + return "alfvén" + def allocate_helpers(self): + self._ones = self.projected_equil.p3.space.zeros() if isinstance(self._ones, PolarVector): self._ones.tp[:] = 1.0 else: self._ones[:] = 1.0 - # set keyword arguments for propagators - self._kwargs[propagators_markers.PushEtaPC] = { - "u": self.pointer["mhd_velocity"], - "use_perp_model": use_perp_model, - "u_space": u_space, - } - - self._kwargs[propagators_markers.PushVxB] = { - "algo": params_vxb["algo"], - "kappa": epsilon, - "b2": self.pointer["b_field"], - "b2_add": self._b_eq, - } - - if params_pressure["turn_off"]: - self._kwargs[propagators_coupling.PressureCoupling6D] = None - else: - self._kwargs[propagators_coupling.PressureCoupling6D] = { - "use_perp_model": use_perp_model, - "u_space": u_space, - "solver": params_pressure["solver"], - "coupling_params": self._coupling_params, - "filter": params_pressure["filter"], - "boundary_cut": params_pressure["boundary_cut"], - } - - if params_alfven["turn_off"]: - self._kwargs[propagators_fields.ShearAlfven] = None - else: - self._kwargs[propagators_fields.ShearAlfven] = { - "u_space": u_space, - "solver": params_alfven["solver"], - } - - if params_sonic["turn_off"]: - self._kwargs[propagators_fields.Magnetosonic] = None - else: - self._kwargs[propagators_fields.Magnetosonic] = { - "b": self.pointer["b_field"], - "u_space": u_space, - "solver": params_sonic["solver"], - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation: - self.add_scalar("en_U", compute="from_field") - self.add_scalar("en_p", compute="from_field") - self.add_scalar("en_B", compute="from_field") - self.add_scalar("en_f", compute="from_particles", species="energetic_ions") - self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_f"]) - - # temporary vectors for scalar quantities - self._tmp_u = self.derham.Vh["2"].zeros() - self._tmp_b1 = self.derham.Vh["2"].zeros() - self._tmp = np.empty(1, dtype=float) - self._n_lost_particles = np.empty(1, dtype=float) - - # MPI operations needed for scalar variables - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE + self._en_f = xp.empty(1, dtype=float) + self._n_lost_particles = xp.empty(1, dtype=float) def update_scalar_quantities(self): + # scaling factor + Ab = self.mhd.mass_number + Ah = self.energetic_ions.var.species.mass_number + # perturbed fields - if "Hdiv" == "Hdiv": - en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_velocity"], self.pointer["mhd_velocity"]) - else: - en_U = 0.5 * self.mass_ops.Mvn.dot_inner(self.pointer["mhd_velocity"], self.pointer["mhd_velocity"]) - en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) - en_p = self.pointer["mhd_pressure"].inner(self._ones) / (5 / 3 - 1) + en_U = 0.5 * self.mass_ops.M2n.dot_inner( + self.mhd.velocity.spline.vector, + self.mhd.velocity.spline.vector, + ) + en_B = 0.5 * self.mass_ops.M2.dot_inner( + self.em_fields.b_field.spline.vector, + self.em_fields.b_field.spline.vector, + ) + en_p = self.mhd.pressure.spline.vector.inner(self._ones) / (5 / 3 - 1) self.update_scalar("en_U", en_U) self.update_scalar("en_B", en_B) self.update_scalar("en_p", en_p) - # particles - self._tmp[0] = ( - self._coupling_params["Ah"] - / self._coupling_params["Ab"] - * self.pointer["energetic_ions"] - .markers_wo_holes[:, 6] - .dot( - self.pointer["energetic_ions"].markers_wo_holes[:, 3] ** 2 - + self.pointer["energetic_ions"].markers_wo_holes[:, 4] ** 2 - + self.pointer["energetic_ions"].markers_wo_holes[:, 5] ** 2, + # particles' energy + particles = self.energetic_ions.var.particles + + self._en_f[0] = ( + particles.markers[~particles.holes, 6].dot( + particles.markers[~particles.holes, 3] ** 2 + + particles.markers[~particles.holes, 4] ** 2 + + particles.markers[~particles.holes, 5] ** 2, ) - / (2.0) + / 2.0 + * Ah + / Ab ) - self.update_scalar("en_f", self._tmp[0]) - self.update_scalar("en_tot", en_U + en_B + en_p + self._tmp[0]) + self.update_scalar("en_f", self._en_f[0]) + self.update_scalar("en_tot") - # Print number of lost ions - self._n_lost_particles[0] = self.pointer["energetic_ions"].n_lost_markers - self.derham.comm.Allreduce(self._mpi_in_place, self._n_lost_particles, op=self._mpi_sum) - if self.derham.comm.Get_rank() == 0: + # print number of lost particles + n_lost_markers = xp.array(particles.n_lost_markers) + + if self.derham.comm is not None: + self.derham.comm.Allreduce( + MPI.IN_PLACE, + n_lost_markers, + op=MPI.SUM, + ) + + if self.clone_config is not None: + self.clone_config.inter_comm.Allreduce( + MPI.IN_PLACE, + n_lost_markers, + op=MPI.SUM, + ) + + if rank == 0: print( - "ratio of lost particles: ", - self._n_lost_particles[0] / self.pointer["energetic_ions"].Np * 100, - "%", + "Lost particle ratio: ", + n_lost_markers / particles.Np * 100, + "% \n", ) + ## default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "magnetosonic.Options" in line: + new_file += [ + """model.propagators.magnetosonic.options = model.propagators.magnetosonic.Options( + b_field=model.em_fields.b_field,)\n""", + ] + + elif "push_eta_pc.Options" in line: + new_file += [ + """model.propagators.push_eta_pc.options = model.propagators.push_eta_pc.Options( + u_tilde = model.mhd.velocity,)\n""", + ] + + elif "push_vxb.Options" in line: + new_file += [ + """model.propagators.push_vxb.options = model.propagators.push_vxb.Options( + b2_var = model.em_fields.b_field,)\n""", + ] + + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + class LinearMHDDriftkineticCC(StruphyModel): r"""Hybrid linear ideal MHD + energetic ions (5D Driftkinetic) with **current coupling scheme**. @@ -567,13 +505,13 @@ class LinearMHDDriftkineticCC(StruphyModel): &\frac{\partial \tilde{\rho}}{\partial t}+\nabla\cdot(\rho_{0} \tilde{\mathbf{U}})=0\,, \\ \rho_{0} &\frac{\partial \tilde{\mathbf{U}}}{\partial t} - \tilde p\, \nabla - = (\nabla \times \tilde{\mathbf{B}}) \times (\mathbf{B}_0 + (\nabla \times \mathbf B_0) \times \tilde{\mathbf{B}} + = (\nabla \times \tilde{\mathbf{B}}) \times \mathbf{B} + (\nabla \times \mathbf B_0) \times \tilde{\mathbf{B}} + \frac{A_\textnormal{h}}{A_\textnormal{b}} \left[ \frac{1}{\epsilon} n_\textnormal{gc} \tilde{\mathbf{U}} - \frac{1}{\epsilon} \mathbf{J}_\textnormal{gc} - \nabla \times \mathbf{M}_\textnormal{gc} \right] \times \mathbf{B} \,, \\ &\frac{\partial \tilde p}{\partial t} + \nabla\cdot(p_0 \tilde{\mathbf{U}}) + \frac{2}{3}\,p_0\nabla\cdot \tilde{\mathbf{U}}=0\,, \\ - &\frac{\partial \tilde{\mathbf{B}}}{\partial t} - \nabla\times(\tilde{\mathbf{U}} \times \mathbf{B}_0) + &\frac{\partial \tilde{\mathbf{B}}}{\partial t} - \nabla\times(\tilde{\mathbf{U}} \times \mathbf{B}) = 0\,, \end{aligned} \right. @@ -586,7 +524,7 @@ class LinearMHDDriftkineticCC(StruphyModel): \\ & n_\textnormal{gc} = \int f_\textnormal{h} B_\parallel^* \,\textnormal dv_\parallel \textnormal d\mu \,, \\ - & \mathbf{J}_\textnormal{gc} = \int f_\textnormal{h}(v_\parallel \mathbf{B}^* - \mathbf{b}_0 \times \mathbf{E}^*) \,\textnormal dv_\parallel \textnormal d\mu \,, + & \mathbf{J}_\textnormal{gc} = \int \frac{f_\textnormal{h}}{B_\parallel^*}(v_\parallel \mathbf{B}^* - \mathbf{b}_0 \times \mathbf{E}^*) \,\textnormal dv_\parallel \textnormal d\mu \,, \\ & \mathbf{M}_\textnormal{gc} = - \int f_\textnormal{h} B_\parallel^* \mu \mathbf{b}_0 \,\textnormal dv_\parallel \textnormal d\mu \,, \end{aligned} @@ -598,9 +536,11 @@ class LinearMHDDriftkineticCC(StruphyModel): .. math:: \begin{align} - \mathbf{B}^* &= \mathbf{B} + \epsilon v_\parallel \nabla \times \mathbf{b}_0 \,,\qquad B^*_\parallel = \mathbf{b}_0 \cdot \mathbf{B}^*\,, + B^*_\parallel = \mathbf{b}_0 \cdot \mathbf{B}^*\,, + \\[2mm] + \mathbf{B}^* &= \mathbf{B} + \epsilon v_\parallel \nabla \times \mathbf{b}_0 \,, \\[2mm] - \mathbf{E}^* &= - \tilde{\mathbf{U}} \times \mathbf{B} - \epsilon \mu \nabla B_\parallel \,, + \mathbf{E}^* &= - \tilde{\mathbf{U}} \times \mathbf{B} - \epsilon \mu \nabla (\mathbf{b}_0 \cdot \mathbf{B}) \,, \end{align} with the normalization parameter @@ -617,340 +557,238 @@ class LinearMHDDriftkineticCC(StruphyModel): 4. :class:`~struphy.propagators.propagators_coupling.CurrentCoupling5DCurlb` 5. :class:`~struphy.propagators.propagators_fields.CurrentCoupling5DDensity` 6. :class:`~struphy.propagators.propagators_fields.ShearAlfvenCurrentCoupling5D` - 7. :class:`~struphy.propagators.propagators_fields.MagnetosonicCurrentCoupling5D` + 7. :class:`~struphy.propagators.propagators_fields.Magnetosonic` :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - - dct["em_fields"]["b_field"] = "Hdiv" - dct["fluid"]["mhd"] = { - "density": "L2", - "velocity": "Hdiv", - "pressure": "L2", - } - dct["kinetic"]["energetic_ions"] = "Particles5D" - return dct - - @staticmethod - def bulk_species(): - return "mhd" - - @staticmethod - def velocity_scale(): - return "alfvén" - - @staticmethod - def propagators_dct(): - return { - propagators_markers.PushGuidingCenterBxEstar: ["energetic_ions"], - propagators_markers.PushGuidingCenterParallel: ["energetic_ions"], - propagators_coupling.CurrentCoupling5DGradB: ["energetic_ions", "mhd_velocity"], - propagators_coupling.CurrentCoupling5DCurlb: ["energetic_ions", "mhd_velocity"], - propagators_fields.CurrentCoupling5DDensity: ["mhd_velocity"], - propagators_fields.ShearAlfvenCurrentCoupling5D: ["mhd_velocity", "b_field"], - propagators_fields.MagnetosonicCurrentCoupling5D: ["mhd_density", "mhd_velocity", "mhd_pressure"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - # add special options - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["fluid", "mhd"], - key="u_space", - option="Hdiv", - dct=dct, - ) - return dct - - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - from mpi4py.MPI import IN_PLACE, SUM - - from struphy.polar.basic import PolarVector - - # extract necessary parameters - u_space = params["fluid"]["mhd"]["options"]["u_space"] - params_alfven = params["fluid"]["mhd"]["options"]["ShearAlfvenCurrentCoupling5D"] - params_sonic = params["fluid"]["mhd"]["options"]["MagnetosonicCurrentCoupling5D"] - params_density = params["fluid"]["mhd"]["options"]["CurrentCoupling5DDensity"] - - params_bxE = params["kinetic"]["energetic_ions"]["options"]["PushGuidingCenterBxEstar"] - params_parallel = params["kinetic"]["energetic_ions"]["options"]["PushGuidingCenterParallel"] - params_cc_gradB = params["kinetic"]["energetic_ions"]["options"]["CurrentCoupling5DGradB"] - params_cc_curlb = params["kinetic"]["energetic_ions"]["options"]["CurrentCoupling5DCurlb"] - params_cc_gradB = params["kinetic"]["energetic_ions"]["options"]["CurrentCoupling5DGradB"] - - # compute coupling parameters - Ab = params["fluid"]["mhd"]["phys_params"]["A"] - Ah = params["kinetic"]["energetic_ions"]["phys_params"]["A"] - epsilon = self.equation_params["energetic_ions"]["epsilon"] - - self._coupling_params = {} - self._coupling_params["Ab"] = Ab - self._coupling_params["Ah"] = Ah - - # add control variate to mass_ops object - if self.pointer["energetic_ions"].control_variate: - self.mass_ops.weights["f0"] = self.pointer["energetic_ions"].f0 - - # Project magnetic field - self._b_eq = self.derham.P["2"]( - [ - self.equil.b2_1, - self.equil.b2_2, - self.equil.b2_3, - ] - ) - - self._absB0 = self.derham.P["0"](self.equil.absB0) - - self._unit_b1 = self.derham.P["1"]( - [ - self.equil.unit_b1_1, - self.equil.unit_b1_2, - self.equil.unit_b1_3, - ] - ) - - self._unit_b2 = self.derham.P["2"]( - [ - self.equil.unit_b2_1, - self.equil.unit_b2_2, - self.equil.unit_b2_3, - ] - ) - - self._gradB1 = self.derham.P["1"]( - [ - self.equil.gradB1_1, - self.equil.gradB1_2, - self.equil.gradB1_3, - ] - ) + ## species + class EnergeticIons(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="Particles5D") + self.init_variables() + + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() + + class MHD(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.pressure = FEECVariable(space="L2") + self.velocity = FEECVariable(space="Hdiv") + self.init_variables() + + ## propagators + + class Propagators: + def __init__(self, turn_off: tuple[str, ...] = (None,)): + if "PushGuidingCenterBxEstar" not in turn_off: + self.push_bxe = propagators_markers.PushGuidingCenterBxEstar() + if "PushGuidingCenterParallel" not in turn_off: + self.push_parallel = propagators_markers.PushGuidingCenterParallel() + if "ShearAlfvenCurrentCoupling5D" not in turn_off: + self.shearalfen_cc5d = propagators_fields.ShearAlfvenCurrentCoupling5D() + if "Magnetosonic" not in turn_off: + self.magnetosonic = propagators_fields.Magnetosonic() + if "CurrentCoupling5DDensity" not in turn_off: + self.cc5d_density = propagators_fields.CurrentCoupling5DDensity() + if "CurrentCoupling5DGradB" not in turn_off: + self.cc5d_gradb = propagators_coupling.CurrentCoupling5DGradB() + if "CurrentCoupling5DCurlb" not in turn_off: + self.cc5d_curlb = propagators_coupling.CurrentCoupling5DCurlb() + + def __init__(self, turn_off: tuple[str, ...] = (None,)): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() + self.energetic_ions = self.EnergeticIons() + + # 2. instantiate all propagators + self.propagators = self.Propagators(turn_off) + + # 3. assign variables to propagators + if "ShearAlfvenCurrentCoupling5D" not in turn_off: + self.propagators.shearalfen_cc5d.variables.u = self.mhd.velocity + self.propagators.shearalfen_cc5d.variables.b = self.em_fields.b_field + if "Magnetosonic" not in turn_off: + self.propagators.magnetosonic.variables.n = self.mhd.density + self.propagators.magnetosonic.variables.u = self.mhd.velocity + self.propagators.magnetosonic.variables.p = self.mhd.pressure + if "CurrentCoupling5DDensity" not in turn_off: + self.propagators.cc5d_density.variables.u = self.mhd.velocity + if "CurrentCoupling5DGradB" not in turn_off: + self.propagators.cc5d_gradb.variables.u = self.mhd.velocity + self.propagators.cc5d_gradb.variables.energetic_ions = self.energetic_ions.var + if "CurrentCoupling5DCurlb" not in turn_off: + self.propagators.cc5d_curlb.variables.u = self.mhd.velocity + self.propagators.cc5d_curlb.variables.energetic_ions = self.energetic_ions.var + if "PushGuidingCenterBxEstar" not in turn_off: + self.propagators.push_bxe.variables.ions = self.energetic_ions.var + if "PushGuidingCenterParallel" not in turn_off: + self.propagators.push_parallel.variables.ions = self.energetic_ions.var + + # define scalars for update_scalar_quantities + self.add_scalar("en_U") + self.add_scalar("en_p") + self.add_scalar("en_B") + self.add_scalar("en_fv", compute="from_particles", variable=self.energetic_ions.var) + self.add_scalar("en_fB", compute="from_particles", variable=self.energetic_ions.var) + self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_fv", "en_fB"]) - self._curl_unit_b2 = self.derham.P["2"]( - [ - self.equil.curl_unit_b2_1, - self.equil.curl_unit_b2_2, - self.equil.curl_unit_b2_3, - ] - ) + @property + def bulk_species(self): + return self.mhd - self._p_eq = self.derham.P["3"](self.equil.p3) - self._ones = self._p_eq.space.zeros() + @property + def velocity_scale(self): + return "alfvén" + def allocate_helpers(self): + self._ones = self.projected_equil.p3.space.zeros() if isinstance(self._ones, PolarVector): self._ones.tp[:] = 1.0 else: self._ones[:] = 1.0 - # set keyword arguments for propagators - self._kwargs[propagators_markers.PushGuidingCenterBxEstar] = { - "b_tilde": self.pointer["b_field"], - "algo": params_bxE["algo"], - "epsilon": epsilon, - } - - self._kwargs[propagators_markers.PushGuidingCenterParallel] = { - "b_tilde": self.pointer["b_field"], - "algo": params_parallel["algo"], - "epsilon": epsilon, - } - - if params_cc_gradB["turn_off"]: - self._kwargs[propagators_coupling.CurrentCoupling5DGradB] = None - else: - self._kwargs[propagators_coupling.CurrentCoupling5DGradB] = { - "b": self.pointer["b_field"], - "b_eq": self._b_eq, - "unit_b1": self._unit_b1, - "unit_b2": self._unit_b2, - "absB0": self._absB0, - "gradB1": self._gradB1, - "curl_unit_b2": self._curl_unit_b2, - "u_space": u_space, - "solver": params_cc_gradB["solver"], - "algo": params_cc_gradB["algo"], - "filter": params_cc_gradB["filter"], - "coupling_params": self._coupling_params, - "epsilon": epsilon, - "boundary_cut": params_cc_gradB["boundary_cut"], - } - - if params_cc_curlb["turn_off"]: - self._kwargs[propagators_coupling.CurrentCoupling5DCurlb] = None - else: - self._kwargs[propagators_coupling.CurrentCoupling5DCurlb] = { - "b": self.pointer["b_field"], - "b_eq": self._b_eq, - "unit_b1": self._unit_b1, - "absB0": self._absB0, - "gradB1": self._gradB1, - "curl_unit_b2": self._curl_unit_b2, - "u_space": u_space, - "solver": params_cc_curlb["solver"], - "filter": params_cc_curlb["filter"], - "coupling_params": self._coupling_params, - "epsilon": epsilon, - "boundary_cut": params_cc_curlb["boundary_cut"], - } - - if params_density["turn_off"]: - self._kwargs[propagators_fields.CurrentCoupling5DDensity] = None - else: - self._kwargs[propagators_fields.CurrentCoupling5DDensity] = { - "particles": self.pointer["energetic_ions"], - "b": self.pointer["b_field"], - "b_eq": self._b_eq, - "unit_b1": self._unit_b1, - "curl_unit_b2": self._curl_unit_b2, - "u_space": u_space, - "solver": params_density["solver"], - "coupling_params": self._coupling_params, - "epsilon": epsilon, - "boundary_cut": params_density["boundary_cut"], - } - - if params_alfven["turn_off"]: - self._kwargs[propagators_fields.ShearAlfvenCurrentCoupling5D] = None - else: - self._kwargs[propagators_fields.ShearAlfvenCurrentCoupling5D] = { - "particles": self.pointer["energetic_ions"], - "unit_b1": self._unit_b1, - "absB0": self._absB0, - "u_space": u_space, - "solver": params_alfven["solver"], - "filter": params_alfven["filter"], - "coupling_params": self._coupling_params, - "accumulated_magnetization": self.pointer["accumulated_magnetization"], - "boundary_cut": params_alfven["boundary_cut"], - } - - if params_sonic["turn_off"]: - self._kwargs[propagators_fields.MagnetosonicCurrentCoupling5D] = None - else: - self._kwargs[propagators_fields.MagnetosonicCurrentCoupling5D] = { - "particles": self.pointer["energetic_ions"], - "b": self.pointer["b_field"], - "unit_b1": self._unit_b1, - "absB0": self._absB0, - "u_space": u_space, - "solver": params_sonic["solver"], - "filter": params_sonic["filter"], - "coupling_params": self._coupling_params, - "boundary_cut": params_sonic["boundary_cut"], - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - # Scalar variables to be saved during simulation - self.add_scalar("en_U", compute="from_field") - self.add_scalar("en_p", compute="from_field") - self.add_scalar("en_B", compute="from_field") - self.add_scalar("en_fv", compute="from_particles", species="energetic_ions") - self.add_scalar("en_fB", compute="from_particles", species="energetic_ions") - # self.add_scalar('en_fv_lost', compute = 'from_particles', species='energetic_ions') - # self.add_scalar('en_fB_lost', compute = 'from_particles', species='energetic_ions') - # self.add_scalar('en_tot',summands = ['en_U','en_p','en_B','en_fv','en_fB','en_fv_lost','en_fB_lost']) - self.add_scalar("en_tot", summands=["en_U", "en_p", "en_B", "en_fv", "en_fB"]) - - # things needed in update_scalar_quantities - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE - - # temporaries - self._b_full1 = self._b_eq.space.zeros() - self._PBb = self._absB0.space.zeros() + self._en_fv = xp.empty(1, dtype=float) + self._en_fB = xp.empty(1, dtype=float) + self._en_tot = xp.empty(1, dtype=float) + self._n_lost_particles = xp.empty(1, dtype=float) - self._en_fv = np.empty(1, dtype=float) - self._en_fB = np.empty(1, dtype=float) - # self._en_fv_lost = np.empty(1, dtype=float) - # self._en_fB_lost = np.empty(1, dtype=float) - self._n_lost_particles = np.empty(1, dtype=float) + self._PB = getattr(self.basis_ops, "PB") + self._PBb = self._PB.codomain.zeros() def update_scalar_quantities(self): - en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_velocity"], self.pointer["mhd_velocity"]) - en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) - en_p = self.pointer["mhd_pressure"].inner(self._ones) / (5 / 3 - 1) + # scaling factor + Ab = self.mhd.mass_number + Ah = self.energetic_ions.var.species.mass_number + + # perturbed fields + en_U = 0.5 * self.mass_ops.M2n.dot_inner( + self.mhd.velocity.spline.vector, + self.mhd.velocity.spline.vector, + ) + en_B = 0.5 * self.mass_ops.M2.dot_inner( + self.em_fields.b_field.spline.vector, + self.em_fields.b_field.spline.vector, + ) + en_p = self.mhd.pressure.spline.vector.inner(self._ones) / (5 / 3 - 1) self.update_scalar("en_U", en_U) - self.update_scalar("en_p", en_p) self.update_scalar("en_B", en_B) + self.update_scalar("en_p", en_p) + + # particles' energy + particles = self.energetic_ions.var.particles self._en_fv[0] = ( - self.pointer["energetic_ions"] - .markers[~self.pointer["energetic_ions"].holes, 5] - .dot( - self.pointer["energetic_ions"].markers[~self.pointer["energetic_ions"].holes, 3] ** 2, + particles.markers[~particles.holes, 5].dot( + particles.markers[~particles.holes, 3] ** 2, ) / (2.0) - * self._coupling_params["Ah"] - / self._coupling_params["Ab"] + * Ah + / Ab ) - self.update_scalar("en_fv", self._en_fv[0]) - - # self._en_fv_lost[0] = self.pointer['energetic_ions'].lost_markers[:self.pointer['energetic_ions'].n_lost_markers, 5].dot( - # self.pointer['energetic_ions'].lost_markers[:self.pointer['energetic_ions'].n_lost_markers, 3]**2) / (2.0) * self._coupling_params['Ah']/self._coupling_params['Ab'] - - # self.update_scalar('en_fv_lost', self._en_fv_lost[0]) - - # calculate particle magnetic energy - self.pointer["energetic_ions"].save_magnetic_energy( - self.pointer["b_field"], - ) + self._PBb = self._PB.dot(self.em_fields.b_field.spline.vector) + particles.save_magnetic_energy(self._PBb) self._en_fB[0] = ( - self.pointer["energetic_ions"] - .markers[~self.pointer["energetic_ions"].holes, 5] - .dot( - self.pointer["energetic_ions"].markers[~self.pointer["energetic_ions"].holes, 8], + particles.markers[~particles.holes, 5].dot( + particles.markers[~particles.holes, 8], ) - * self._coupling_params["Ah"] - / self._coupling_params["Ab"] + * Ah + / Ab ) + self.update_scalar("en_fv", self._en_fv[0]) self.update_scalar("en_fB", self._en_fB[0]) + self.update_scalar("en_tot") - # self._en_fB_lost[0] = self.pointer['energetic_ions'].lost_markers[:self.pointer['energetic_ions'].n_lost_markers, 5].dot( - # self.pointer['energetic_ions'] .lost_markers[:self.pointer['energetic_ions'].n_lost_markers, 8]) * self._coupling_params['Ah']/self._coupling_params['Ab'] + # print number of lost particles + n_lost_markers = xp.array(particles.n_lost_markers) - # self.update_scalar('en_fB_lost', self._en_fB_lost[0]) + if self.derham.comm is not None: + self.derham.comm.Allreduce( + MPI.IN_PLACE, + n_lost_markers, + op=MPI.SUM, + ) - self.update_scalar("en_tot") + if self.clone_config is not None: + self.clone_config.inter_comm.Allreduce( + MPI.IN_PLACE, + n_lost_markers, + op=MPI.SUM, + ) - # Print number of lost ions - self._n_lost_particles[0] = self.pointer["energetic_ions"].n_lost_markers - self.derham.comm.Allreduce(self._mpi_in_place, self._n_lost_particles, op=self._mpi_sum) - if self.derham.comm.Get_rank() == 0: + if rank == 0: print( - "ratio of lost particles: ", - self._n_lost_particles[0] / self.pointer["energetic_ions"].Np * 100, - "%", + "Lost particle ratio: ", + n_lost_markers / particles.Np * 100, + "% \n", ) - @staticmethod - def diagnostics_dct(): - dct = {} - - dct["accumulated_magnetization"] = "Hdiv" - return dct - - __diagnostics__ = diagnostics_dct() + ## default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "shearalfen_cc5d.Options" in line: + new_file += [ + """model.propagators.shearalfen_cc5d.options = model.propagators.shearalfen_cc5d.Options( + energetic_ions = model.energetic_ions.var,)\n""", + ] + + elif "magnetosonic.Options" in line: + new_file += [ + """model.propagators.magnetosonic.options = model.propagators.magnetosonic.Options( + b_field=model.em_fields.b_field,)\n""", + ] + + elif "cc5d_density.Options" in line: + new_file += [ + """model.propagators.cc5d_density.options = model.propagators.cc5d_density.Options( + energetic_ions = model.energetic_ions.var, + b_tilde = model.em_fields.b_field,)\n""", + ] + + elif "cc5d_curlb.Options" in line: + new_file += [ + """model.propagators.cc5d_curlb.options = model.propagators.cc5d_curlb.Options( + b_tilde = model.em_fields.b_field,)\n""", + ] + + elif "cc5d_gradb.Options" in line: + new_file += [ + """model.propagators.cc5d_gradb.options = model.propagators.cc5d_gradb.Options( + b_tilde = model.em_fields.b_field,)\n""", + ] + + elif "push_bxe.Options" in line: + new_file += [ + """model.propagators.push_bxe.options = model.propagators.push_bxe.Options( + b_tilde = model.em_fields.b_field,)\n""", + ] + + elif "push_parallel.Options" in line: + new_file += [ + """model.propagators.push_parallel.options = model.propagators.push_parallel.Options( + b_tilde = model.em_fields.b_field,)\n""", + ] + + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) class ColdPlasmaVlasov(StruphyModel): @@ -1000,209 +838,175 @@ class ColdPlasmaVlasov(StruphyModel): 6. :class:`~struphy.propagators.propagators_coupling.VlasovAmpere` """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species - dct["em_fields"]["e_field"] = "Hcurl" - dct["em_fields"]["b_field"] = "Hdiv" - dct["fluid"]["cold_electrons"] = {"j": "Hcurl"} - dct["kinetic"]["hot_electrons"] = "Particles6D" - return dct + class EMFields(FieldSpecies): + def __init__(self): + self.e_field = FEECVariable(space="Hcurl") + self.b_field = FEECVariable(space="Hdiv") + self.phi = FEECVariable(space="H1") + self.init_variables() - @staticmethod - def bulk_species(): - return "cold_electrons" + class ThermalElectrons(FluidSpecies): + def __init__(self): + self.current = FEECVariable(space="Hcurl") + self.init_variables() - @staticmethod - def velocity_scale(): - return "light" + class HotElectrons(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="Particles6D") + self.init_variables() - @staticmethod - def propagators_dct(): - return { - propagators_fields.Maxwell: ["e_field", "b_field"], - propagators_fields.OhmCold: ["cold_electrons_j", "e_field"], - propagators_fields.JxBCold: ["cold_electrons_j"], - propagators_markers.PushEta: ["hot_electrons"], - propagators_markers.PushVxB: ["hot_electrons"], - propagators_coupling.VlasovAmpere: ["e_field", "hot_electrons"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - # add special options - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["em_fields"], - option=propagators_fields.ImplicitDiffusion, - dct=dct, - ) - return dct + ## propagators - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) + class Propagators: + def __init__(self): + self.maxwell = propagators_fields.Maxwell() + self.ohm = propagators_fields.OhmCold() + self.jxb = propagators_fields.JxBCold() + self.push_eta = propagators_markers.PushEta() + self.push_vxb = propagators_markers.PushVxB() + self.coupling_va = propagators_coupling.VlasovAmpere() - from mpi4py.MPI import IN_PLACE, SUM + ## abstract methods - # Get rank and size - self._rank = comm.Get_rank() + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - # prelim - hot_params = params["kinetic"]["hot_electrons"] + # 1. instantiate all species + self.em_fields = self.EMFields() + self.thermal_elec = self.ThermalElectrons() + self.hot_elec = self.HotElectrons() - # model parameters - self._alpha = np.abs( - self.equation_params["cold_electrons"]["alpha"], - ) - self._epsilon_cold = self.equation_params["cold_electrons"]["epsilon"] - self._epsilon_hot = self.equation_params["hot_electrons"]["epsilon"] - - self._nu = hot_params["phys_params"]["Z"] / params["fluid"]["cold_electrons"]["phys_params"]["Z"] - - # Initialize background magnetic field from MHD equilibrium - self._b_background = self.derham.P["2"]( - [ - self.equil.b2_1, - self.equil.b2_2, - self.equil.b2_3, - ] - ) + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.maxwell.variables.e = self.em_fields.e_field + self.propagators.maxwell.variables.b = self.em_fields.b_field + + self.propagators.ohm.variables.j = self.thermal_elec.current + self.propagators.ohm.variables.e = self.em_fields.e_field + + self.propagators.jxb.variables.j = self.thermal_elec.current - # propagator parameters - params_maxwell = params["em_fields"]["options"]["Maxwell"]["solver"] - params_ohmcold = params["fluid"]["cold_electrons"]["options"]["OhmCold"]["solver"] - params_jxbcold = params["fluid"]["cold_electrons"]["options"]["JxBCold"]["solver"] - algo_eta = params["kinetic"]["hot_electrons"]["options"]["PushEta"]["algo"] - algo_vxb = params["kinetic"]["hot_electrons"]["options"]["PushVxB"]["algo"] - params_coupling = params["em_fields"]["options"]["VlasovAmpere"]["solver"] - self._poisson_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] - - # set keyword arguments for propagators - self._kwargs[propagators_fields.Maxwell] = {"solver": params_maxwell} - - self._kwargs[propagators_fields.OhmCold] = { - "alpha": self._alpha, - "epsilon": self._epsilon_cold, - "solver": params_ohmcold, - } - - self._kwargs[propagators_fields.JxBCold] = { - "epsilon": self._epsilon_cold, - "solver": params_jxbcold, - } - - self._kwargs[propagators_markers.PushEta] = {"algo": algo_eta} - - self._kwargs[propagators_markers.PushVxB] = { - "algo": algo_vxb, - "kappa": 1.0 / self._epsilon_hot, - "b2": self.pointer["b_field"], - "b2_add": self._b_background, - } - - self._kwargs[propagators_coupling.VlasovAmpere] = { - "c1": self._alpha**2 / self._epsilon_hot, - "c2": 1.0 / self._epsilon_hot, - "solver": params_coupling, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during simulation + self.propagators.push_eta.variables.var = self.hot_elec.var + self.propagators.push_vxb.variables.ions = self.hot_elec.var + + self.propagators.coupling_va.variables.e = self.em_fields.e_field + self.propagators.coupling_va.variables.ions = self.hot_elec.var + + # define scalars for update_scalar_quantities self.add_scalar("en_E") self.add_scalar("en_B") self.add_scalar("en_J") - self.add_scalar("en_f") + self.add_scalar("en_f", compute="from_particles", variable=self.hot_elec.var) self.add_scalar("en_tot") - # MPI operations needed for scalar variables - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE + # initial Poisson (not a propagator used in time stepping) + self.initial_poisson = propagators_fields.Poisson() + self.initial_poisson.variables.phi = self.em_fields.phi + + @property + def bulk_species(self): + return self.thermal_elec - # temporaries - self._tmp = np.empty(1, dtype=float) + @property + def velocity_scale(self): + return "light" - def initialize_from_params(self): - """:meta private:""" - from psydac.linalg.stencil import StencilVector + def allocate_helpers(self): + self._tmp = xp.empty(1, dtype=float) - from struphy.pic.accumulation.particles_to_grid import AccumulatorVector + def update_scalar_quantities(self): + # e*M1*e/2 + e = self.em_fields.e_field.spline.vector + en_E = 0.5 * self.mass_ops.M1.dot_inner(e, e) + self.update_scalar("en_E", en_E) + + # alpha^2 / 2 / N * sum_p w_p v_p^2 + particles = self.hot_elec.var.particles + alpha = self.hot_elec.equation_params.alpha + self._tmp[0] = ( + alpha**2 + / (2 * particles.Np) + * xp.dot( + particles.markers_wo_holes[:, 3] ** 2 + + particles.markers_wo_holes[:, 4] ** 2 + + particles.markers_wo_holes[:, 5] ** 2, + particles.markers_wo_holes[:, 6], + ) + ) + self.update_scalar("en_f", self._tmp[0]) - # Initialize fields and particles - super().initialize_from_params() + # en_tot = en_w + en_e + self.update_scalar("en_tot", en_E + self._tmp[0]) - # Accumulate charge density + def allocate_propagators(self): + """Solve initial Poisson equation. + + :meta private: + """ + + # initialize fields and particles + super().allocate_propagators() + + if MPI.COMM_WORLD.Get_rank() == 0: + print("\nINITIAL POISSON SOLVE:") + + # use control variate method + particles = self.hot_elec.var.particles + particles.update_weights() + + # sanity check + # self.pointer['species1'].show_distribution_function( + # [True] + [False]*5, [xp.linspace(0, 1, 32)]) + + # accumulate charge density charge_accum = AccumulatorVector( - self.pointer["hot_electrons"], + particles, "H1", - accum_kernels.vlasov_maxwell_poisson, + Pyccelkernel(accum_kernels.charge_density_0form), self.mass_ops, self.domain.args_domain, ) - charge_accum() - # Locally subtract mean charge for solvability with periodic bc - if np.all(charge_accum.vectors[0].space.periods): - charge_accum._vectors[0][:] -= np.mean( - charge_accum.vectors[0].toarray()[charge_accum.vectors[0].toarray() != 0], - ) + # another sanity check: compute FE coeffs of density + # charge_accum.show_accumulated_spline_field(self.mass_ops) - # Instantiate Poisson solver - _phi = StencilVector(self.derham.Vh["0"]) - poisson_solver = propagators_fields.ImplicitDiffusion( - _phi, - sigma_1=0, - rho=self._nu * self._alpha**2 / self._epsilon_cold * charge_accum.vectors[0], - x0=self._nu * self._alpha**2 / self._epsilon_cold * charge_accum.vectors[0], - solver=self._poisson_params, - ) + alpha = self.hot_elec.equation_params.alpha + epsilon = self.hot_elec.equation_params.epsilon - # Solve with dt=1. and compute electric field - poisson_solver(1.0) - self.derham.grad.dot(-_phi, out=self.pointer["e_field"]) + self.initial_poisson.options.rho = charge_accum + self.initial_poisson.options.rho_coeffs = alpha**2 / epsilon + self.initial_poisson.allocate() - def update_scalar_quantities(self): - en_E = 0.5 * self.mass_ops.M1.dot_inner(self.pointer["e_field"], self.pointer["e_field"]) - en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) - en_J = ( - 0.5 - * self._alpha**2 - * self.mass_ops.M1ninv.dot_inner(self.pointer["cold_electrons_j"], self.pointer["cold_electrons_j"]) - ) - self.update_scalar("en_E", en_E) - self.update_scalar("en_B", en_B) - self.update_scalar("en_J", en_J) - - # nu alpha^2 eps_h / eps_c / 2 / N * sum_p w_p v_p^2 - self._tmp[0] = ( - self._nu - * self._alpha**2 - * self._epsilon_hot - / self._epsilon_cold - / (2 * self.pointer["hot_electrons"].Np) - * np.dot( - self.pointer["hot_electrons"].markers_wo_holes[:, 3] ** 2 - + self.pointer["hot_electrons"].markers_wo_holes[:, 4] ** 2 - + self.pointer["hot_electrons"].markers_wo_holes[:, 5] ** 2, - self.pointer["hot_electrons"].markers_wo_holes[:, 6], - ) - ) - self.derham.comm.Allreduce( - self._mpi_in_place, - self._tmp, - op=self._mpi_sum, - ) - self.update_scalar("en_f", self._tmp[0]) - - # en_tot = en_E + en_B + en_J + en_w - self.update_scalar("en_tot", en_E + en_B + en_J + self._tmp[0]) + # Solve with dt=1. and compute electric field + if MPI.COMM_WORLD.Get_rank() == 0: + print("\nSolving initial Poisson problem...") + self.initial_poisson(1.0) + + phi = self.initial_poisson.variables.phi.spline.vector + self.derham.grad.dot(-phi, out=self.em_fields.e_field.spline.vector) + if MPI.COMM_WORLD.Get_rank() == 0: + print("Done.") + + ## default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "coupling_va.Options" in line: + new_file += [line] + new_file += ["model.initial_poisson.options = model.initial_poisson.Options()\n"] + elif "set_save_data" in line: + new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] + new_file += ["model.hot_elec.set_save_data(binning_plots=(binplot,))\n"] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) diff --git a/src/struphy/models/kinetic.py b/src/struphy/models/kinetic.py index 4f5dd89bd..3f327e9b2 100644 --- a/src/struphy/models/kinetic.py +++ b/src/struphy/models/kinetic.py @@ -1,14 +1,16 @@ -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from struphy.feec.projectors import L2Projector from struphy.kinetic_background.base import KineticBackground +from struphy.kinetic_background.maxwellians import Maxwellian3D from struphy.models.base import StruphyModel from struphy.models.species import FieldSpecies, FluidSpecies, ParticleSpecies from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.pic.accumulation import accum_kernels, accum_kernels_gc from struphy.pic.accumulation.particles_to_grid import AccumulatorVector from struphy.propagators import propagators_coupling, propagators_fields, propagators_markers +from struphy.utils.pyccel import Pyccelkernel rank = MPI.COMM_WORLD.Get_rank() @@ -144,7 +146,7 @@ def velocity_scale(self): return "light" def allocate_helpers(self): - self._tmp = np.empty(1, dtype=float) + self._tmp = xp.empty(1, dtype=float) def update_scalar_quantities(self): # e*M1*e/2 @@ -158,7 +160,7 @@ def update_scalar_quantities(self): self._tmp[0] = ( alpha**2 / (2 * particles.Np) - * np.dot( + * xp.dot( particles.markers_wo_holes[:, 3] ** 2 + particles.markers_wo_holes[:, 4] ** 2 + particles.markers_wo_holes[:, 5] ** 2, @@ -188,29 +190,25 @@ def allocate_propagators(self): # sanity check # self.pointer['species1'].show_distribution_function( - # [True] + [False]*5, [np.linspace(0, 1, 32)]) + # [True] + [False]*5, [xp.linspace(0, 1, 32)]) # accumulate charge density charge_accum = AccumulatorVector( particles, "H1", - accum_kernels.charge_density_0form, + Pyccelkernel(accum_kernels.charge_density_0form), self.mass_ops, self.domain.args_domain, ) - charge_accum(particles.vdim) - # another sanity check: compute FE coeffs of density # charge_accum.show_accumulated_spline_field(self.mass_ops) alpha = self.kinetic_ions.equation_params.alpha epsilon = self.kinetic_ions.equation_params.epsilon - l2_proj = L2Projector(space_id="H1", mass_ops=self.mass_ops) - rho_coeffs = l2_proj.solve(charge_accum.vectors[0]) - - self.initial_poisson.options.rho = alpha**2 / epsilon * rho_coeffs + self.initial_poisson.options.rho = charge_accum + self.initial_poisson.options.rho_coeffs = alpha**2 / epsilon self.initial_poisson.allocate() # Solve with dt=1. and compute electric field @@ -325,208 +323,171 @@ class VlasovMaxwellOneSpecies(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species - dct["em_fields"]["e_field"] = "Hcurl" - dct["em_fields"]["b_field"] = "Hdiv" - dct["kinetic"]["species1"] = "Particles6D" - return dct + class EMFields(FieldSpecies): + def __init__(self): + self.e_field = FEECVariable(space="Hcurl") + self.b_field = FEECVariable(space="Hdiv") + self.phi = FEECVariable(space="H1") + self.init_variables() - @staticmethod - def bulk_species(): - return "species1" + class KineticIons(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="Particles6D") + self.init_variables() - @staticmethod - def velocity_scale(): - return "light" + ## propagators - @staticmethod - def propagators_dct(): - return { - propagators_fields.Maxwell: ["e_field", "b_field"], - propagators_markers.PushEta: ["species1"], - propagators_markers.PushVxB: ["species1"], - propagators_coupling.VlasovAmpere: ["e_field", "species1"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - # add special options - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["em_fields"], - option=propagators_fields.ImplicitDiffusion, - dct=dct, - ) - cls.add_option( - species=["kinetic", "species1"], - key="override_eq_params", - option=[False, {"alpha": 1.0, "epsilon": -1.0}], - dct=dct, - ) - return dct + class Propagators: + def __init__(self): + self.maxwell = propagators_fields.Maxwell() + self.push_eta = propagators_markers.PushEta() + self.push_vxb = propagators_markers.PushVxB() + self.coupling_va = propagators_coupling.VlasovAmpere() - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) + ## abstract methods - from mpi4py.MPI import IN_PLACE, SUM + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - # get species paramaters - species1_params = params["kinetic"]["species1"] + # 1. instantiate all species + self.em_fields = self.EMFields() + self.kinetic_ions = self.KineticIons() - # equation parameters - if species1_params["options"]["override_eq_params"]: - self._alpha = species1_params["options"]["override_eq_params"]["alpha"] - self._epsilon = species1_params["options"]["override_eq_params"]["epsilon"] - print( - f"\n!!! Override equation parameters: {self._alpha = } and {self._epsilon = }.", - ) - else: - self._alpha = self.equation_params["species1"]["alpha"] - self._epsilon = self.equation_params["species1"]["epsilon"] - - # set background density and mean velocity factors - self.pointer["species1"].f0.moment_factors["u"] = [ - self._epsilon / self._alpha**2, - ] * 3 - - # Initialize background magnetic field from MHD equilibrium - if self.projected_equil: - self._b_background = self.projected_equil.b2 - else: - self._b_background = None - - # propagator parameters - params_maxwell = params["em_fields"]["options"]["Maxwell"]["solver"] - algo_eta = params["kinetic"]["species1"]["options"]["PushEta"]["algo"] - algo_vxb = params["kinetic"]["species1"]["options"]["PushVxB"]["algo"] - params_coupling = params["em_fields"]["options"]["VlasovAmpere"]["solver"] - self._poisson_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] - - # set keyword arguments for propagators - self._kwargs[propagators_fields.Maxwell] = {"solver": params_maxwell} - - self._kwargs[propagators_markers.PushEta] = {"algo": algo_eta} - - self._kwargs[propagators_markers.PushVxB] = { - "algo": algo_vxb, - "kappa": 1.0 / self._epsilon, - "b2": self.pointer["b_field"], - "b2_add": self._b_background, - } - - self._kwargs[propagators_coupling.VlasovAmpere] = { - "c1": self._alpha**2 / self._epsilon, - "c2": 1.0 / self._epsilon, - "solver": params_coupling, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # Scalar variables to be saved during the simulation + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.maxwell.variables.e = self.em_fields.e_field + self.propagators.maxwell.variables.b = self.em_fields.b_field + self.propagators.push_eta.variables.var = self.kinetic_ions.var + self.propagators.push_vxb.variables.ions = self.kinetic_ions.var + self.propagators.coupling_va.variables.e = self.em_fields.e_field + self.propagators.coupling_va.variables.ions = self.kinetic_ions.var + + # define scalars for update_scalar_quantities self.add_scalar("en_E") self.add_scalar("en_B") - self.add_scalar("en_f") + self.add_scalar("en_f", compute="from_particles", variable=self.kinetic_ions.var) self.add_scalar("en_tot") - # MPI operations needed for scalar variables - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE + # initial Poisson (not a propagator used in time stepping) + self.initial_poisson = propagators_fields.Poisson() + self.initial_poisson.variables.phi = self.em_fields.phi + + @property + def bulk_species(self): + return self.kinetic_ions + + @property + def velocity_scale(self): + return "light" + + def allocate_helpers(self): + self._tmp = xp.empty(1, dtype=float) - # temporaries - self._tmp = np.empty(1, dtype=float) + def update_scalar_quantities(self): + # e*M1*e/2 + e = self.em_fields.e_field.spline.vector + b = self.em_fields.b_field.spline.vector - def initialize_from_params(self): - """:meta private:""" + en_E = 0.5 * self.mass_ops.M1.dot_inner(e, e) + self.update_scalar("en_E", en_E) - from struphy.pic.accumulation.particles_to_grid import AccumulatorVector + en_B = 0.5 * self.mass_ops.M2.dot_inner(b, b) + self.update_scalar("en_B", en_B) + + # alpha^2 / 2 / N * sum_p w_p v_p^2 + particles = self.kinetic_ions.var.particles + alpha = self.kinetic_ions.equation_params.alpha + self._tmp[0] = ( + alpha**2 + / (2 * particles.Np) + * xp.dot( + particles.markers_wo_holes[:, 3] ** 2 + + particles.markers_wo_holes[:, 4] ** 2 + + particles.markers_wo_holes[:, 5] ** 2, + particles.markers_wo_holes[:, 6], + ) + ) + self.update_scalar("en_f", self._tmp[0]) + + # en_tot = en_w + en_e + self.update_scalar("en_tot", en_E + self._tmp[0]) + + def allocate_propagators(self): + """Solve initial Poisson equation. + + :meta private: + """ # initialize fields and particles - super().initialize_from_params() + super().allocate_propagators() - if self.rank_world == 0: + if MPI.COMM_WORLD.Get_rank() == 0: print("\nINITIAL POISSON SOLVE:") # use control variate method - self.pointer["species1"].update_weights() + particles = self.kinetic_ions.var.particles + particles.update_weights() # sanity check # self.pointer['species1'].show_distribution_function( - # [True] + [False]*5, [np.linspace(0, 1, 32)]) + # [True] + [False]*5, [xp.linspace(0, 1, 32)]) # accumulate charge density charge_accum = AccumulatorVector( - self.pointer["species1"], + particles, "H1", - accum_kernels.charge_density_0form, + Pyccelkernel(accum_kernels.charge_density_0form), self.mass_ops, self.domain.args_domain, ) - charge_accum(self.pointer["species1"].vdim) - # another sanity check: compute FE coeffs of density # charge_accum.show_accumulated_spline_field(self.mass_ops) - # Instantiate Poisson solver - _phi = self.derham.Vh["0"].zeros() - poisson_solver = propagators_fields.ImplicitDiffusion( - _phi, - sigma_1=0.0, - sigma_2=0.0, - sigma_3=1.0, - rho=self._alpha**2 / self._epsilon * charge_accum.vectors[0], - solver=self._poisson_params, - ) + alpha = self.kinetic_ions.equation_params.alpha + epsilon = self.kinetic_ions.equation_params.epsilon + + self.initial_poisson.options.rho = charge_accum + self.initial_poisson.options.rho_coeffs = alpha**2 / epsilon + self.initial_poisson.allocate() # Solve with dt=1. and compute electric field - if self.rank_world == 0: + if MPI.COMM_WORLD.Get_rank() == 0: print("\nSolving initial Poisson problem...") - poisson_solver(1.0) + self.initial_poisson(1.0) - self.derham.grad.dot(-_phi, out=self.pointer["e_field"]) - if self.rank_world == 0: + phi = self.initial_poisson.variables.phi.spline.vector + self.derham.grad.dot(-phi, out=self.em_fields.e_field.spline.vector) + if MPI.COMM_WORLD.Get_rank() == 0: print("Done.") - def update_scalar_quantities(self): - # e*M1*e and b*M2*b - en_E = 0.5 * self.mass_ops.M1.dot_inner(self.pointer["e_field"], self.pointer["e_field"]) - en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) - self.update_scalar("en_E", en_E) - self.update_scalar("en_B", en_B) - - # alpha^2 / 2 / N * sum_p w_p v_p^2 - self._tmp[0] = ( - self._alpha**2 - / (2 * self.pointer["species1"].Np) - * np.dot( - self.pointer["species1"].markers_wo_holes[:, 3] ** 2 - + self.pointer["species1"].markers_wo_holes[:, 4] ** 2 - + self.pointer["species1"].markers_wo_holes[:, 5] ** 2, - self.pointer["species1"].markers_wo_holes[:, 6], - ) - ) - if self.comm_world is not None: - self.comm_world.Allreduce( - self._mpi_in_place, - self._tmp, - op=self._mpi_sum, - ) - self.update_scalar("en_f", self._tmp[0]) + ## default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "coupling_va.Options" in line: + new_file += [line] + new_file += ["model.initial_poisson.options = model.initial_poisson.Options()\n"] + elif "push_vxb.Options" in line: + new_file += [ + "model.propagators.push_vxb.options = model.propagators.push_vxb.Options(b2_var=model.em_fields.b_field)\n", + ] + elif "set_save_data" in line: + new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] + new_file += ["model.kinetic_ions.set_save_data(binning_plots=(binplot,))\n"] + else: + new_file += [line] - # en_tot = en_w + en_e + en_b - self.update_scalar("en_tot", en_E + en_B + self._tmp[0]) + with open(params_path, "w") as f: + for line in new_file: + f.write(line) class LinearVlasovAmpereOneSpecies(StruphyModel): @@ -596,257 +557,188 @@ class LinearVlasovAmpereOneSpecies(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species - dct["em_fields"]["e_field"] = "Hcurl" - dct["kinetic"]["species1"] = "DeltaFParticles6D" - return dct + class EMFields(FieldSpecies): + def __init__(self): + self.e_field = FEECVariable(space="Hcurl") + self.phi = FEECVariable(space="H1") + self.init_variables() - @staticmethod - def bulk_species(): - return "species1" + class KineticIons(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="DeltaFParticles6D") + self.init_variables() - @staticmethod - def velocity_scale(): - return "light" + ## propagators - @staticmethod - def propagators_dct(): - return { - propagators_markers.PushEta: ["species1"], - propagators_markers.PushVinEfield: ["species1"], - propagators_coupling.EfieldWeights: ["e_field", "species1"], - propagators_markers.PushVxB: ["species1"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["em_fields"], - option=propagators_fields.ImplicitDiffusion, - dct=dct, - ) - cls.add_option( - species=["kinetic", "species1"], - key="override_eq_params", - option=[False, {"epsilon": -1.0, "alpha": 1.0}], - dct=dct, - ) - return dct + class Propagators: + def __init__( + self, + with_B0: bool = True, + with_E0: bool = True, + ): + self.push_eta = propagators_markers.PushEta() + if with_E0: + self.push_vinE = propagators_markers.PushVinEfield() + self.coupling_Eweights = propagators_coupling.EfieldWeights() + if with_B0: + self.push_vxb = propagators_markers.PushVxB() - def __init__(self, params, comm, clone_config=None, baseclass=False): - """Initializes the model either as the full model or as a baseclass to inherit from. - In case of being a baseclass, the propagators will not be initialized in the __init__ which allows other propagators to be added. + ## abstract methods - Parameters - ---------- - baseclass : Boolean [optional] - If this model should be used as a baseclass. Default value is False. - """ + def __init__( + self, + with_B0: bool = True, + with_E0: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.kinetic_ions = self.KineticIons() + + # 2. instantiate all propagators + self.propagators = self.Propagators(with_B0=with_B0, with_E0=with_E0) + + # 3. assign variables to propagators + self.propagators.push_eta.variables.var = self.kinetic_ions.var + if with_E0: + self.propagators.push_vinE.variables.var = self.kinetic_ions.var + self.propagators.coupling_Eweights.variables.e = self.em_fields.e_field + self.propagators.coupling_Eweights.variables.ions = self.kinetic_ions.var + if with_B0: + self.propagators.push_vxb.variables.ions = self.kinetic_ions.var + + # define scalars for update_scalar_quantities + self.add_scalar("en_E") + self.add_scalar("en_w", compute="from_particles", variable=self.kinetic_ions.var) + self.add_scalar("en_tot") - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) + # initial Poisson (not a propagator used in time stepping) + self.initial_poisson = propagators_fields.Poisson() + self.initial_poisson.variables.phi = self.em_fields.phi - from mpi4py.MPI import IN_PLACE, SUM + @property + def bulk_species(self): + return self.kinetic_ions + + @property + def velocity_scale(self): + return "light" - from struphy.kinetic_background import maxwellians + def allocate_helpers(self): + self._tmp = xp.empty(1, dtype=float) - # if model is used as a baseclass - self._baseclass = baseclass + def update_scalar_quantities(self): + # e*M1*e/2 + e = self.em_fields.e_field.spline.vector + particles = self.kinetic_ions.var.particles - # kinetic parameters - self._species_params = params["kinetic"]["species1"] + en_E = 0.5 * self.mass_ops.M1.dot_inner(e, e) + self.update_scalar("en_E", en_E) - # Assert Maxwellian background (if list, the first entry is taken) - bckgr_params = self._species_params["background"] - li_bp = list(bckgr_params) - assert li_bp[0] == "Maxwellian3D", "The background distribution function must be a uniform Maxwellian!" - if len(li_bp) > 1: - # overwrite f0 with single Maxwellian - self._f0 = getattr(maxwellians, li_bp[0][:-2])( - maxw_params=bckgr_params[li_bp[0]], + # evaluate f0 + if not hasattr(self, "_f0"): + backgrounds = self.kinetic_ions.var.backgrounds + if isinstance(backgrounds, list): + self._f0 = backgrounds[0] + else: + self._f0 = backgrounds + self._f0_values = xp.zeros( + self.kinetic_ions.var.particles.markers.shape[0], + dtype=float, ) - else: - # keep allocated background - self._f0 = self.pointer["species1"].f0 - - # Assert uniformity of the Maxwellian background - assert self._f0.maxw_params["u1"] == 0.0, "The background Maxwellian cannot have shifts in velocity space!" - assert self._f0.maxw_params["u2"] == 0.0, "The background Maxwellian cannot have shifts in velocity space!" - assert self._f0.maxw_params["u3"] == 0.0, "The background Maxwellian cannot have shifts in velocity space!" - assert self._f0.maxw_params["vth1"] == self._f0.maxw_params["vth2"] == self._f0.maxw_params["vth3"], ( - "The background Maxwellian must be isotropic in velocity space!" - ) - self.vth = self._f0.maxw_params["vth1"] - - # Get coupling strength - if self._species_params["options"]["override_eq_params"]: - self.epsilon = self._species_params["options"]["override_eq_params"]["epsilon"] - self.alpha = self._species_params["options"]["override_eq_params"]["alpha"] - if self.rank_world == 0: - print( - f"\n!!! Override equation parameters: {self.epsilon = }, {self.alpha = }.\n", - ) - else: - self.epsilon = self.equation_params["species1"]["epsilon"] - self.alpha = self.equation_params["species1"]["alpha"] - - # allocate memory for evaluating f0 in energy computation - self._f0_values = np.zeros( - self.pointer["species1"].markers.shape[0], - dtype=float, - ) + assert isinstance(self._f0, Maxwellian3D) - # ==================================================================================== - # Create pointers to background electric potential and field - self._has_background_e = False - if "external_E0" in self.params["em_fields"]["options"].keys(): - e0 = self.params["em_fields"]["options"]["external_E0"] - if e0 != 0.0: - self._has_background_e = True - self._e_background = self.derham.Vh["1"].zeros() - for block in self._e_background._blocks: - block._data[:, :, :] += e0 - - # Get parameters of the background magnetic field - if self.projected_equil: - self._b_background = self.projected_equil.b2 - else: - self._b_background = None - # ==================================================================================== - - # propagator parameters - self._poisson_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] - algo_eta = params["kinetic"]["species1"]["options"]["PushEta"]["algo"] - params_coupling = params["em_fields"]["options"]["EfieldWeights"]["solver"] - - # Initialize propagators/integrators used in splitting substeps - self._kwargs[propagators_markers.PushEta] = { - "algo": algo_eta, - } - - # Only add PushVinEfield if e-field is non-zero, otherwise it is more expensive - if self._has_background_e: - self._kwargs[propagators_markers.PushVinEfield] = { - "e_field": self._e_background, - "kappa": 1.0 / self.epsilon, - } - else: - self._kwargs[propagators_markers.PushVinEfield] = None - - self._kwargs[propagators_coupling.EfieldWeights] = { - "alpha": self.alpha, - "kappa": 1.0 / self.epsilon, - "f0": self._f0, - "solver": params_coupling, - } - - # Only add PushVxB if magnetic field is not zero - self._kwargs[propagators_markers.PushVxB] = None - if self._b_background: - self._kwargs[propagators_markers.PushVxB] = { - "kappa": 1.0 / self.epsilon, - "b2": self._b_background, - } - - # Initialize propagators used in splitting substeps - if not self._baseclass: - self.init_propagators() - - # Scalar variables to be saved during the simulation - self.add_scalar("en_E") - self.add_scalar("en_w") - self.add_scalar("en_tot") + self._f0_values[particles.valid_mks] = self._f0(*particles.phasespace_coords.T) + + # alpha^2 * v_th^2 / (2*N) * sum_p s_0 * w_p^2 / f_{0,p} + alpha = self.kinetic_ions.equation_params.alpha + vth = self._f0.maxw_params["vth1"][0] - # MPI operations needed for scalar variables - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE + self._tmp[0] = ( + alpha**2 + * vth**2 + / (2 * particles.Np) + * xp.dot( + particles.weights**2, # w_p^2 + particles.sampling_density / self._f0_values[particles.valid_mks], # s_{0,p} / f_{0,p} + ) + ) - # temporaries - self._tmp = np.empty(1, dtype=float) - self.en_E = 0.0 + self.update_scalar("en_w", self._tmp[0]) + self.update_scalar("en_tot", self._tmp[0] + en_E) - def initialize_from_params(self): + def allocate_propagators(self): """Solve initial Poisson equation. :meta private: """ - from struphy.pic.accumulation.particles_to_grid import AccumulatorVector - # Initialize fields and particles - super().initialize_from_params() + # initialize fields and particles + super().allocate_propagators() + + if MPI.COMM_WORLD.Get_rank() == 0: + print("\nINITIAL POISSON SOLVE:") + + # use control variate method + particles = self.kinetic_ions.var.particles + particles.update_weights() + + # sanity check + # self.pointer['species1'].show_distribution_function( + # [True] + [False]*5, [xp.linspace(0, 1, 32)]) - # Accumulate charge density + # accumulate charge density charge_accum = AccumulatorVector( - self.pointer["species1"], + particles, "H1", - accum_kernels.charge_density_0form, + Pyccelkernel(accum_kernels.charge_density_0form), self.mass_ops, self.domain.args_domain, ) - charge_accum(self.pointer["species1"].vdim) - - # Instantiate Poisson solver - _phi = self.derham.Vh["0"].zeros() - poisson_solver = propagators_fields.ImplicitDiffusion( - _phi, - sigma_1=0.0, - sigma_2=0.0, - sigma_3=1.0, - rho=self.alpha**2 / self.epsilon * charge_accum.vectors[0], - solver=self._poisson_params, - ) - - # Solve with dt=1. and compute electric field - if self.rank_world == 0: - print("\nSolving initial Poisson problem...") - poisson_solver(1.0) - self.derham.grad.dot(-_phi, out=self.pointer["e_field"]) - if self.rank_world == 0: - print("Done.") + # another sanity check: compute FE coeffs of density + # charge_accum.show_accumulated_spline_field(self.mass_ops) - def update_scalar_quantities(self): - # 0.5 * e^T * M_1 * e - self.en_E = 0.5 * self.mass_ops.M1.dot_inner(self.pointer["e_field"], self.pointer["e_field"]) - self.update_scalar("en_E", self.en_E) + alpha = self.kinetic_ions.equation_params.alpha + epsilon = self.kinetic_ions.equation_params.epsilon - # evaluate f0 - self._f0_values[self.pointer["species1"].valid_mks] = self._f0(*self.pointer["species1"].phasespace_coords.T) + self.initial_poisson.options.rho = charge_accum + self.initial_poisson.options.rho_coeffs = alpha**2 / epsilon + self.initial_poisson.allocate() - # alpha^2 * v_th^2 / (2*N) * sum_p s_0 * w_p^2 / f_{0,p} - self._tmp[0] = ( - self.alpha**2 - * self.vth**2 - / (2 * self.pointer["species1"].Np) - * np.dot( - self.pointer["species1"].weights ** 2, # w_p^2 - self.pointer["species1"].sampling_density - / self._f0_values[self.pointer["species1"].valid_mks], # s_{0,p} / f_{0,p} - ) - ) + # Solve with dt=1. and compute electric field + if MPI.COMM_WORLD.Get_rank() == 0: + print("\nSolving initial Poisson problem...") + self.initial_poisson(1.0) - self.derham.comm.Allreduce( - self._mpi_in_place, - self._tmp, - op=self._mpi_sum, - ) + phi = self.initial_poisson.variables.phi.spline.vector + self.derham.grad.dot(-phi, out=self.em_fields.e_field.spline.vector) + if MPI.COMM_WORLD.Get_rank() == 0: + print("Done.") - self.update_scalar("en_w", self._tmp[0]) + ## default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "maxwellian_1 + maxwellian_2" in line: + new_file += ["background = maxwellian_1\n"] + elif "maxwellian_1pt =" in line: + new_file += ["maxwellian_1pt = maxwellians.Maxwellian3D(n=(0.0, perturbation))\n"] + elif "set_save_data" in line: + new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] + new_file += ["model.kinetic_ions.set_save_data(binning_plots=(binplot,))\n"] + else: + new_file += [line] - # en_tot = en_w + en_e - if not self._baseclass: - self.update_scalar("en_tot", self._tmp[0] + self.en_E) + with open(params_path, "w") as f: + for line in new_file: + f.write(line) class LinearVlasovMaxwellOneSpecies(LinearVlasovAmpereOneSpecies): @@ -919,80 +811,82 @@ class LinearVlasovMaxwellOneSpecies(LinearVlasovAmpereOneSpecies): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species - dct["em_fields"]["e_field"] = "Hcurl" - dct["em_fields"]["b_field"] = "Hdiv" - dct["kinetic"]["species1"] = "DeltaFParticles6D" - return dct + class EMFields(FieldSpecies): + def __init__(self): + self.e_field = FEECVariable(space="Hcurl") + self.b_field = FEECVariable(space="Hdiv") + self.phi = FEECVariable(space="H1") + self.init_variables() - @staticmethod - def bulk_species(): - return "species1" + class KineticIons(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="DeltaFParticles6D") + self.init_variables() - @staticmethod - def velocity_scale(): - return "light" + ## propagators - @staticmethod - def propagators_dct(): - return { - propagators_markers.PushEta: ["species1"], - propagators_markers.PushVinEfield: ["species1"], - propagators_coupling.EfieldWeights: ["e_field", "species1"], - propagators_markers.PushVxB: ["species1"], - propagators_fields.Maxwell: ["e_field", "b_field"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["em_fields"], - option=propagators_fields.ImplicitDiffusion, - dct=dct, - ) - cls.add_option( - species=["kinetic", "species1"], - key="override_eq_params", - option=[False, {"epsilon": -1.0, "alpha": 1.0}], - dct=dct, - ) - return dct + class Propagators: + def __init__( + self, + with_B0: bool = True, + with_E0: bool = True, + ): + self.push_eta = propagators_markers.PushEta() + if with_E0: + self.push_vinE = propagators_markers.PushVinEfield() + self.coupling_Eweights = propagators_coupling.EfieldWeights() + if with_B0: + self.push_vxb = propagators_markers.PushVxB() + self.maxwell = propagators_fields.Maxwell() - def __init__(self, params, comm, clone_config=None): - super().__init__(params=params, comm=comm, clone_config=clone_config, baseclass=True) + ## abstract methods - # propagator parameters - params_maxwell = params["em_fields"]["options"]["Maxwell"]["solver"] + def __init__( + self, + with_B0: bool = True, + with_E0: bool = True, + ): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - # set keyword arguments for propagators - self._kwargs[propagators_fields.Maxwell] = {"solver": params_maxwell} + # 1. instantiate all species + self.em_fields = self.EMFields() + self.kinetic_ions = self.KineticIons() - # Initialize propagators used in splitting substeps - self.init_propagators() + # 2. instantiate all propagators + self.propagators = self.Propagators(with_B0=with_B0, with_E0=with_E0) - # magnetic energy - self.add_scalar("en_b") + # 3. assign variables to propagators + self.propagators.push_eta.variables.var = self.kinetic_ions.var + if with_E0: + self.propagators.push_vinE.variables.var = self.kinetic_ions.var + self.propagators.coupling_Eweights.variables.e = self.em_fields.e_field + self.propagators.coupling_Eweights.variables.ions = self.kinetic_ions.var + if with_B0: + self.propagators.push_vxb.variables.ions = self.kinetic_ions.var + self.propagators.maxwell.variables.e = self.em_fields.e_field + self.propagators.maxwell.variables.b = self.em_fields.b_field + + # define scalars for update_scalar_quantities + self.add_scalar("en_E") + self.add_scalar("en_B") + self.add_scalar("en_w", compute="from_particles", variable=self.kinetic_ions.var) + self.add_scalar("en_tot") - def initialize_from_params(self): - super().initialize_from_params() + # initial Poisson (not a propagator used in time stepping) + self.initial_poisson = propagators_fields.Poisson() + self.initial_poisson.variables.phi = self.em_fields.phi def update_scalar_quantities(self): super().update_scalar_quantities() # 0.5 * b^T * M_2 * b - en_B = 0.5 * self._mass_ops.M2.dot_inner(self.pointer["b_field"], self.pointer["b_field"]) - self.update_scalar("en_tot", self._tmp[0] + self.en_E + en_B) + b = self.em_fields.b_field.spline.vector + + en_B = 0.5 * self._mass_ops.M2.dot_inner(b, b) + self.update_scalar("en_tot", self.scalar_quantities["en_tot"]["value"][0] + en_B) class DriftKineticElectrostaticAdiabatic(StruphyModel): @@ -1044,164 +938,159 @@ class DriftKineticElectrostaticAdiabatic(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species - dct["em_fields"]["phi"] = "H1" - dct["kinetic"]["ions"] = "Particles5D" - return dct + class EMFields(FieldSpecies): + def __init__(self): + self.phi = FEECVariable(space="H1") + self.init_variables() - @staticmethod - def bulk_species(): - return "ions" + class KineticIons(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="Particles5D") + self.init_variables() - @staticmethod - def velocity_scale(): - return "thermal" + ## propagators - @staticmethod - def propagators_dct(): - return { - propagators_fields.ImplicitDiffusion: ["phi"], - propagators_markers.PushGuidingCenterBxEstar: ["ions"], - propagators_markers.PushGuidingCenterParallel: ["ions"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - # add special options - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["kinetic", "ions"], - key="override_eq_params", - option=[False, {"epsilon": 1.0}], - dct=dct, - ) - return dct + class Propagators: + def __init__(self): + self.gc_poisson = propagators_fields.ImplicitDiffusion() + self.push_gc_bxe = propagators_markers.PushGuidingCenterBxEstar() + self.push_gc_para = propagators_markers.PushGuidingCenterParallel() - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) + ## abstract methods + + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.kinetic_ions = self.KineticIons() - from mpi4py.MPI import IN_PLACE, SUM + # 2. instantiate all propagators + self.propagators = self.Propagators() - from struphy.feec.projectors import L2Projector - from struphy.pic.accumulation.particles_to_grid import AccumulatorVector + # 3. assign variables to propagators + self.propagators.gc_poisson.variables.phi = self.em_fields.phi + self.propagators.push_gc_bxe.variables.ions = self.kinetic_ions.var + self.propagators.push_gc_para.variables.ions = self.kinetic_ions.var - # prelim - solver_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] - ions_params = params["kinetic"]["ions"] + # define scalars for update_scalar_quantities + self.add_scalar("en_phi") + self.add_scalar("en_particles", compute="from_particles", variable=self.kinetic_ions.var) + self.add_scalar("en_tot") - Z = ions_params["phys_params"]["Z"] - assert Z > 0 # must be positive ions + @property + def bulk_species(self): + return self.kinetic_ions + + @property + def velocity_scale(self): + return "thermal" + + def allocate_helpers(self): + self._tmp3 = xp.empty(1, dtype=float) + self._e_field = self.derham.Vh["1"].zeros() + + assert self.kinetic_ions.charge_number > 0, "Model written only for positive ions." + + def allocate_propagators(self): + """Solve initial Poisson equation. + + :meta private: + """ + + # initialize fields and particles + super().allocate_propagators() # Poisson right-hand side + particles = self.kinetic_ions.var.particles + Z = self.kinetic_ions.charge_number + epsilon = self.kinetic_ions.equation_params.epsilon + charge_accum = AccumulatorVector( - self.pointer["ions"], + particles, "H1", - accum_kernels_gc.gc_density_0form, + Pyccelkernel(accum_kernels_gc.gc_density_0form), self.mass_ops, self.domain.args_domain, ) - rho = (charge_accum, self.pointer["ions"]) + rho = charge_accum # get neutralizing background density - if not self.pointer["ions"].control_variate: + if not particles.control_variate: l2_proj = L2Projector("H1", self.mass_ops) - f0e = Z * self.pointer["ions"].f0 + f0e = Z * particles.f0 assert isinstance(f0e, KineticBackground) - rho_eh = l2_proj.get_dofs(f0e.n) + rho_eh = FEECVariable(space="H1") + rho_eh.allocate(derham=self.derham, domain=self.domain) + rho_eh.spline.vector = l2_proj.get_dofs(f0e.n) rho = [rho] rho += [rho_eh] - # Get coupling strength - if ions_params["options"]["override_eq_params"]: - self.epsilon = ions_params["options"]["override_eq_params"]["epsilon"] - print( - f"\n!!! Override equation parameters: {self.epsilon = }.", - ) - else: - self.epsilon = self.equation_params["ions"]["epsilon"] - - # set keyword arguments for propagators - self._kwargs[propagators_fields.ImplicitDiffusion] = { - "sigma_1": 1.0 / self.epsilon**2 / Z, # set to zero for Landau damping test - "sigma_2": 0.0, - "sigma_3": 1.0 / self.epsilon, - "stab_mat": "M0ad", - "diffusion_mat": "M1gyro", - "rho": rho, - "solver": solver_params, - } - - self._kwargs[propagators_markers.PushGuidingCenterBxEstar] = { - "phi": self.pointer["phi"], - "evaluate_e_field": True, - "epsilon": self.epsilon / Z, - "algo": ions_params["options"]["PushGuidingCenterBxEstar"]["algo"], - } - - self._kwargs[propagators_markers.PushGuidingCenterParallel] = { - "phi": self.pointer["phi"], - "evaluate_e_field": True, - "epsilon": self.epsilon / Z, - "algo": ions_params["options"]["PushGuidingCenterParallel"]["algo"], - } - - # Initialize propagators used in splitting substeps - self.init_propagators() - - # scalar quantities - self.add_scalar("en_phi") - self.add_scalar("en_particles") - self.add_scalar("en_tot") - - # MPI operations needed for scalar variables - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE - self._tmp3 = np.empty(1, dtype=float) - self._e_field = self.derham.Vh["1"].zeros() + self.propagators.gc_poisson.options.sigma_1 = 1.0 / epsilon**2 / Z + self.propagators.gc_poisson.options.sigma_2 = 0.0 + self.propagators.gc_poisson.options.sigma_3 = 1.0 / epsilon + self.propagators.gc_poisson.options.stab_mat = "M0ad" + self.propagators.gc_poisson.options.diffusion_mat = "M1perp" + self.propagators.gc_poisson.options.rho = rho + self.propagators.gc_poisson.allocate() def update_scalar_quantities(self): + phi = self.em_fields.phi.spline.vector + particles = self.kinetic_ions.var.particles + epsilon = self.kinetic_ions.equation_params.epsilon + # energy from polarization - e1 = self.derham.grad.dot(-self.pointer["phi"], out=self._e_field) + e1 = self.derham.grad.dot(-phi, out=self._e_field) en_phi1 = 0.5 * self.mass_ops.M1gyro.dot_inner(e1, e1) # energy from adiabatic electrons - en_phi = 0.5 / self.epsilon**2 * self.mass_ops.M0ad.dot_inner(self.pointer["phi"], self.pointer["phi"]) + en_phi = 0.5 / epsilon**2 * self.mass_ops.M0ad.dot_inner(phi, phi) # for Landau damping test # en_phi = 0. # mu_p * |B0(eta_p)| - self.pointer["ions"].save_magnetic_background_energy() + particles.save_magnetic_background_energy() # 1/N sum_p (w_p v_p^2/2 + mu_p |B0|_p) self._tmp3[0] = ( 1 - / self.pointer["ions"].Np - * np.sum( - self.pointer["ions"].weights * self.pointer["ions"].velocities[:, 0] ** 2 / 2.0 - + self.pointer["ions"].markers_wo_holes_and_ghost[:, 8], + / particles.Np + * xp.sum( + particles.weights * particles.velocities[:, 0] ** 2 / 2.0 + particles.markers_wo_holes_and_ghost[:, 8], ) ) - if self.comm_world is not None: - self.comm_world.Allreduce( - self._mpi_in_place, - self._tmp3, - op=self._mpi_sum, - ) - self.update_scalar("en_phi", en_phi + en_phi1) self.update_scalar("en_particles", self._tmp3[0]) self.update_scalar("en_tot", en_phi + en_phi1 + self._tmp3[0]) + + ## default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "BaseUnits(" in line: + new_file += ["base_units = BaseUnits(kBT=1.0)\n"] + elif "push_gc_bxe.Options" in line: + new_file += [ + "model.propagators.push_gc_bxe.options = model.propagators.push_gc_bxe.Options(phi=model.em_fields.phi)\n", + ] + elif "push_gc_para.Options" in line: + new_file += [ + "model.propagators.push_gc_para.options = model.propagators.push_gc_para.Options(phi=model.em_fields.phi)\n", + ] + elif "set_save_data" in line: + new_file += ["\nbinplot = BinningPlot(slice='e1', n_bins=128, ranges=(0.0, 1.0))\n"] + new_file += ["model.kinetic_ions.set_save_data(binning_plots=(binplot,))\n"] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) diff --git a/src/struphy/models/species.py b/src/struphy/models/species.py index 72e765a16..122d40486 100644 --- a/src/struphy/models/species.py +++ b/src/struphy/models/species.py @@ -1,15 +1,10 @@ import warnings from abc import ABCMeta, abstractmethod -from copy import deepcopy -from dataclasses import dataclass -from typing import Callable -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI -from struphy.fields_background.base import FluidEquilibrium from struphy.io.options import Units -from struphy.kinetic_background.base import KineticBackground from struphy.models.variables import Variable from struphy.physics.physics import ConstantsOfNature from struphy.pic.utilities import ( @@ -88,7 +83,7 @@ def __init__( con = ConstantsOfNature() # relevant frequencies - om_p = np.sqrt(units.n * (Z * con.e) ** 2 / (con.eps0 * A * con.mH)) + om_p = xp.sqrt(units.n * (Z * con.e) ** 2 / (con.eps0 * A * con.mH)) om_c = Z * con.e * units.B / (A * con.mH) # compute equation parameters @@ -96,19 +91,22 @@ def __init__( self.alpha = om_p / om_c else: self.alpha = alpha - warnings.warn(f"Override equation parameter {self.alpha = }") + if MPI.COMM_WORLD.Get_rank() == 0: + warnings.warn(f"Override equation parameter {self.alpha =}") if epsilon is None: self.epsilon = 1.0 / (om_c * units.t) else: self.epsilon = epsilon - warnings.warn(f"Override equation parameter {self.epsilon = }") + if MPI.COMM_WORLD.Get_rank() == 0: + warnings.warn(f"Override equation parameter {self.epsilon =}") if kappa is None: self.kappa = om_p * units.t else: self.kappa = kappa - warnings.warn(f"Override equation parameter {self.kappa = }") + if MPI.COMM_WORLD.Get_rank() == 0: + warnings.warn(f"Override equation parameter {self.kappa =}") if verbose and MPI.COMM_WORLD.Get_rank() == 0: print(f"\nSet normalization parameters for species {species.__class__.__name__}:") @@ -143,8 +141,6 @@ class FieldSpecies(Species): class FluidSpecies(Species): """Single fluid species in 3d configuration space.""" - pass - class ParticleSpecies(Species): """Single kinetic species in 3d + vdim phase space.""" @@ -214,5 +210,3 @@ def set_save_data( class DiagnosticSpecies(Species): """Diagnostic species (fields) without mass and charge.""" - - pass diff --git a/src/struphy/models/tests/test_models.py b/src/struphy/models/tests/test_models.py index 24aa086f1..b9802abdc 100644 --- a/src/struphy/models/tests/test_models.py +++ b/src/struphy/models/tests/test_models.py @@ -1,8 +1,9 @@ +import inspect import os from types import ModuleType import pytest -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from struphy import main from struphy.io.options import EnvironmentOptions @@ -13,43 +14,33 @@ rank = MPI.COMM_WORLD.Get_rank() # available models -toy_models = [ - "Maxwell", - "Vlasov", - "GuidingCenter", - "PressureLessSPH", -] -# for name, obj in inspect.getmembers(toy): -# if inspect.isclass(obj) and "models.toy" in obj.__module__: -# toy_models += [name] +toy_models = [] +for name, obj in inspect.getmembers(toy): + if inspect.isclass(obj) and "models.toy" in obj.__module__: + toy_models += [name] if rank == 0: - print(f"\n{toy_models = }") - -fluid_models = [ - "LinearMHD", - "EulerSPH", -] -# for name, obj in inspect.getmembers(fluid): -# if inspect.isclass(obj) and "models.fluid" in obj.__module__: -# fluid_models += [name] + print(f"\n{toy_models =}") + +fluid_models = [] +for name, obj in inspect.getmembers(fluid): + if inspect.isclass(obj) and "models.fluid" in obj.__module__: + fluid_models += [name] if rank == 0: - print(f"\n{fluid_models = }") - -kinetic_models = [ - "VlasovAmpereOneSpecies", -] -# for name, obj in inspect.getmembers(kinetic): -# if inspect.isclass(obj) and "models.kinetic" in obj.__module__: -# kinetic_models += [name] + print(f"\n{fluid_models =}") + +kinetic_models = [] +for name, obj in inspect.getmembers(kinetic): + if inspect.isclass(obj) and "models.kinetic" in obj.__module__: + kinetic_models += [name] if rank == 0: - print(f"\n{kinetic_models = }") + print(f"\n{kinetic_models =}") hybrid_models = [] -# for name, obj in inspect.getmembers(hybrid): -# if inspect.isclass(obj) and "models.hybrid" in obj.__module__: -# hybrid_models += [name] +for name, obj in inspect.getmembers(hybrid): + if inspect.isclass(obj) and "models.hybrid" in obj.__module__: + hybrid_models += [name] if rank == 0: - print(f"\n{hybrid_models = }") + print(f"\n{hybrid_models =}") # folder for test simulations @@ -61,12 +52,17 @@ def call_test(model_name: str, module: ModuleType = None, verbose=True): if rank == 0: print(f"\n*** Testing '{model_name}':") + # exceptions + if model_name == "TwoFluidQuasiNeutralToy" and MPI.COMM_WORLD.Get_size() > 1: + print(f"WARNING: Model {model_name} cannot be tested for {MPI.COMM_WORLD.Get_size() =}") + return + if module is None: submods = [toy, fluid, kinetic, hybrid] for submod in submods: try: model = getattr(submod, model_name)() - except: + except AttributeError: continue else: diff --git a/src/struphy/models/tests/test_verif_LinearMHD.py b/src/struphy/models/tests/test_verif_LinearMHD.py index 8ff0d28b8..475b11aef 100644 --- a/src/struphy/models/tests/test_verif_LinearMHD.py +++ b/src/struphy/models/tests/test_verif_LinearMHD.py @@ -1,8 +1,8 @@ import os -import numpy as np +import cunumpy as xp import pytest -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from struphy import main from struphy.diagnostics.diagn_tools import power_spectrum_2d @@ -113,10 +113,10 @@ def test_slab_waves_1d(algo: str, do_plot: bool = False): ) # assert - vA = np.sqrt(Bsquare / n0) - v_alfven = vA * B0z / np.sqrt(Bsquare) - print(f"{v_alfven = }") - assert np.abs(coeffs[0][0] - v_alfven) < 0.07 + vA = xp.sqrt(Bsquare / n0) + v_alfven = vA * B0z / xp.sqrt(Bsquare) + print(f"{v_alfven =}") + assert xp.abs(coeffs[0][0] - v_alfven) < 0.07 # second fft p_of_t = simdata.spline_values["mhd"]["pressure_log"] @@ -139,15 +139,15 @@ def test_slab_waves_1d(algo: str, do_plot: bool = False): # assert gamma = 5 / 3 - cS = np.sqrt(gamma * p0 / n0) + cS = xp.sqrt(gamma * p0 / n0) delta = (4 * B0z**2 * cS**2 * vA**2) / ((cS**2 + vA**2) ** 2 * Bsquare) - v_slow = np.sqrt(1 / 2 * (cS**2 + vA**2) * (1 - np.sqrt(1 - delta))) - v_fast = np.sqrt(1 / 2 * (cS**2 + vA**2) * (1 + np.sqrt(1 - delta))) - print(f"{v_slow = }") - print(f"{v_fast = }") - assert np.abs(coeffs[0][0] - v_slow) < 0.05 - assert np.abs(coeffs[1][0] - v_fast) < 0.19 + v_slow = xp.sqrt(1 / 2 * (cS**2 + vA**2) * (1 - xp.sqrt(1 - delta))) + v_fast = xp.sqrt(1 / 2 * (cS**2 + vA**2) * (1 + xp.sqrt(1 - delta))) + print(f"{v_slow =}") + print(f"{v_fast =}") + assert xp.abs(coeffs[0][0] - v_slow) < 0.05 + assert xp.abs(coeffs[1][0] - v_fast) < 0.19 if __name__ == "__main__": diff --git a/src/struphy/models/tests/test_verif_Maxwell.py b/src/struphy/models/tests/test_verif_Maxwell.py index f191a93c4..ccea67c18 100644 --- a/src/struphy/models/tests/test_verif_Maxwell.py +++ b/src/struphy/models/tests/test_verif_Maxwell.py @@ -1,9 +1,9 @@ import os -import numpy as np +import cunumpy as xp import pytest from matplotlib import pyplot as plt -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from scipy.special import jv, yn from struphy import main @@ -13,6 +13,7 @@ from struphy.initial import perturbations from struphy.io.options import BaseUnits, DerhamOptions, EnvironmentOptions, FieldsBackground, Time from struphy.kinetic_background import maxwellians +from struphy.models.toy import Maxwell from struphy.topology import grids test_folder = os.path.join(os.getcwd(), "struphy_verification_tests") @@ -21,11 +22,6 @@ @pytest.mark.mpi(min_size=3) @pytest.mark.parametrize("algo", ["implicit", "explicit"]) def test_light_wave_1d(algo: str, do_plot: bool = False): - # import model, set verbosity - from struphy.models.toy import Maxwell - - verbose = True - # environment options out_folders = os.path.join(test_folder, "Maxwell") env = EnvironmentOptions(out_folders=out_folders, sim_folder="light_wave_1d") @@ -58,7 +54,9 @@ def test_light_wave_1d(algo: str, do_plot: bool = False): model.em_fields.e_field.add_perturbation(perturbations.Noise(amp=0.1, comp=0, seed=123)) model.em_fields.e_field.add_perturbation(perturbations.Noise(amp=0.1, comp=1, seed=123)) - # # start run + # start run + verbose = True + main.run( model, params_path=None, @@ -99,7 +97,7 @@ def test_light_wave_1d(algo: str, do_plot: bool = False): # assert c_light_speed = 1.0 - assert np.abs(coeffs[0][0] - c_light_speed) < 0.02 + assert xp.abs(coeffs[0][0] - c_light_speed) < 0.02 @pytest.mark.mpi(min_size=4) @@ -191,32 +189,32 @@ def test_coaxial(do_plot: bool = False): def B_z(X, Y, Z, m, t): """Magnetic field in z direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return (jv(m, r) - 0.28 * yn(m, r)) * np.cos(m * theta - t) + theta = xp.arctan2(Y, X) + return (jv(m, r) - 0.28 * yn(m, r)) * xp.cos(m * theta - t) def E_r(X, Y, Z, m, t): """Electrical field in radial direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return -m / r * (jv(m, r) - 0.28 * yn(m, r)) * np.cos(m * theta - t) + theta = xp.arctan2(Y, X) + return -m / r * (jv(m, r) - 0.28 * yn(m, r)) * xp.cos(m * theta - t) def E_theta(X, Y, Z, m, t): """Electrical field in azimuthal direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return ((m / r * jv(m, r) - jv(m + 1, r)) - 0.28 * (m / r * yn(m, r) - yn(m + 1, r))) * np.sin( - m * theta - t + theta = xp.arctan2(Y, X) + return ((m / r * jv(m, r) - jv(m + 1, r)) - 0.28 * (m / r * yn(m, r) - yn(m + 1, r))) * xp.sin( + m * theta - t, ) def to_E_r(X, Y, E_x, E_y): r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return np.cos(theta) * E_x + np.sin(theta) * E_y + theta = xp.arctan2(Y, X) + return xp.cos(theta) * E_x + xp.sin(theta) * E_y def to_E_theta(X, Y, E_x, E_y): r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return -np.sin(theta) * E_x + np.cos(theta) * E_y + theta = xp.arctan2(Y, X) + return -xp.sin(theta) * E_x + xp.cos(theta) * E_y # plot if do_plot: @@ -224,7 +222,13 @@ def to_E_theta(X, Y, E_x, E_y): vmax = E_theta(X, Y, grids_phy[0], modes, 0).max() fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) plot_exac = ax1.contourf( - X, Y, E_theta(X, Y, grids_phy[0], modes, t_grid[-1]), cmap="plasma", levels=100, vmin=vmin, vmax=vmax + X, + Y, + E_theta(X, Y, grids_phy[0], modes, t_grid[-1]), + cmap="plasma", + levels=100, + vmin=vmin, + vmax=vmax, ) ax2.contourf( X, @@ -249,21 +253,21 @@ def to_E_theta(X, Y, E_x, E_y): Bz_tend = b_field_phy[t_grid[-1]][2][:, :, 0] Bz_exact = B_z(X, Y, grids_phy[0], modes, t_grid[-1]) - error_Er = np.max(np.abs((to_E_r(X, Y, Ex_tend, Ey_tend) - Er_exact))) - error_Etheta = np.max(np.abs((to_E_theta(X, Y, Ex_tend, Ey_tend) - Etheta_exact))) - error_Bz = np.max(np.abs((Bz_tend - Bz_exact))) + error_Er = xp.max(xp.abs((to_E_r(X, Y, Ex_tend, Ey_tend) - Er_exact))) + error_Etheta = xp.max(xp.abs((to_E_theta(X, Y, Ex_tend, Ey_tend) - Etheta_exact))) + error_Bz = xp.max(xp.abs((Bz_tend - Bz_exact))) - rel_err_Er = error_Er / np.max(np.abs(Er_exact)) - rel_err_Etheta = error_Etheta / np.max(np.abs(Etheta_exact)) - rel_err_Bz = error_Bz / np.max(np.abs(Bz_exact)) + rel_err_Er = error_Er / xp.max(xp.abs(Er_exact)) + rel_err_Etheta = error_Etheta / xp.max(xp.abs(Etheta_exact)) + rel_err_Bz = error_Bz / xp.max(xp.abs(Bz_exact)) print("") - assert rel_err_Bz < 0.0021, f"Assertion for magnetic field Maxwell failed: {rel_err_Bz = }" - print(f"Assertion for magnetic field Maxwell passed ({rel_err_Bz = }).") - assert rel_err_Etheta < 0.0021, f"Assertion for electric (E_theta) field Maxwell failed: {rel_err_Etheta = }" - print(f"Assertion for electric field Maxwell passed ({rel_err_Etheta = }).") - assert rel_err_Er < 0.0021, f"Assertion for electric (E_r) field Maxwell failed: {rel_err_Er = }" - print(f"Assertion for electric field Maxwell passed ({rel_err_Er = }).") + assert rel_err_Bz < 0.0021, f"Assertion for magnetic field Maxwell failed: {rel_err_Bz =}" + print(f"Assertion for magnetic field Maxwell passed ({rel_err_Bz =}).") + assert rel_err_Etheta < 0.0021, f"Assertion for electric (E_theta) field Maxwell failed: {rel_err_Etheta =}" + print(f"Assertion for electric field Maxwell passed ({rel_err_Etheta =}).") + assert rel_err_Er < 0.0021, f"Assertion for electric (E_r) field Maxwell failed: {rel_err_Er =}" + print(f"Assertion for electric field Maxwell passed ({rel_err_Er =}).") if __name__ == "__main__": diff --git a/src/struphy/models/tests/verification.py b/src/struphy/models/tests/verification.py index c88e2ff32..d28fc3d6e 100644 --- a/src/struphy/models/tests/verification.py +++ b/src/struphy/models/tests/verification.py @@ -2,12 +2,12 @@ import pickle from pathlib import Path +import cunumpy as xp import h5py -import numpy as np import yaml from matplotlib import pyplot as plt from matplotlib.ticker import FormatStrFormatter -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from scipy.special import jv, yn import struphy @@ -40,7 +40,7 @@ def E_exact(t): r = 0.3677 omega = 1.4156 phi = 0.5362 - return 2 * eps**2 * np.pi / k**2 * r**2 * np.exp(2 * gamma * t) * np.cos(omega * t - phi) ** 2 + return 2 * eps**2 * xp.pi / k**2 * r**2 * xp.exp(2 * gamma * t) * xp.cos(omega * t - phi) ** 2 # get parameters with open(os.path.join(path_out, "parameters.yml")) as f: @@ -56,24 +56,24 @@ def E_exact(t): with h5py.File(os.path.join(pa_data, "data_proc0.hdf5"), "r") as f: time = f["time"]["value"][()] E = f["scalar"]["en_E"][()] - logE = np.log10(E) + logE = xp.log10(E) # find where time derivative of E is zero - dEdt = (np.roll(logE, -1) - np.roll(logE, 1))[1:-1] / (2.0 * dt) - zeros = dEdt * np.roll(dEdt, -1) < 0.0 - maxima_inds = np.logical_and(zeros, dEdt > 0.0) + dEdt = (xp.roll(logE, -1) - xp.roll(logE, 1))[1:-1] / (2.0 * dt) + zeros = dEdt * xp.roll(dEdt, -1) < 0.0 + maxima_inds = xp.logical_and(zeros, dEdt > 0.0) maxima = logE[1:-1][maxima_inds] t_maxima = time[1:-1][maxima_inds] # linear fit - linfit = np.polyfit(t_maxima[:5], maxima[:5], 1) + linfit = xp.polyfit(t_maxima[:5], maxima[:5], 1) gamma_num = linfit[0] # plot if show_plots and rank == 0: plt.figure(figsize=(18, 12)) plt.plot(time, logE, label="numerical") - plt.plot(time, np.log10(E_exact(time)), label="exact") + plt.plot(time, xp.log10(E_exact(time)), label="exact") plt.legend() plt.title(f"{dt=}, {algo=}, {Nel=}, {p=}, {ppc=}") plt.xlabel("time [m/c]") @@ -84,9 +84,9 @@ def E_exact(t): plt.show() # assert - rel_error = np.abs(gamma_num - gamma) / np.abs(gamma) - assert rel_error < 0.25, f"{rank = }: Assertion for weak Landau damping failed: {gamma_num = } vs. {gamma = }." - print(f"{rank = }: Assertion for weak Landau damping passed ({rel_error = }).") + rel_error = xp.abs(gamma_num - gamma) / xp.abs(gamma) + assert rel_error < 0.25, f"{rank =}: Assertion for weak Landau damping failed: {gamma_num =} vs. {gamma =}." + print(f"{rank =}: Assertion for weak Landau damping passed ({rel_error =}).") def LinearVlasovAmpereOneSpecies_weakLandau( @@ -115,7 +115,7 @@ def E_exact(t): r = 0.3677 omega = 1.4156 phi = 0.5362 - return 2 * eps**2 * np.pi / k**2 * r**2 * np.exp(2 * gamma * t) * np.cos(omega * t - phi) ** 2 + return 2 * eps**2 * xp.pi / k**2 * r**2 * xp.exp(2 * gamma * t) * xp.cos(omega * t - phi) ** 2 # get parameters with open(os.path.join(path_out, "parameters.yml")) as f: @@ -131,24 +131,24 @@ def E_exact(t): with h5py.File(os.path.join(pa_data, "data_proc0.hdf5"), "r") as f: time = f["time"]["value"][()] E = f["scalar"]["en_E"][()] - logE = np.log10(E) + logE = xp.log10(E) # find where time derivative of E is zero - dEdt = (np.roll(logE, -1) - np.roll(logE, 1))[1:-1] / (2.0 * dt) - zeros = dEdt * np.roll(dEdt, -1) < 0.0 - maxima_inds = np.logical_and(zeros, dEdt > 0.0) + dEdt = (xp.roll(logE, -1) - xp.roll(logE, 1))[1:-1] / (2.0 * dt) + zeros = dEdt * xp.roll(dEdt, -1) < 0.0 + maxima_inds = xp.logical_and(zeros, dEdt > 0.0) maxima = logE[1:-1][maxima_inds] t_maxima = time[1:-1][maxima_inds] # linear fit - linfit = np.polyfit(t_maxima[:5], maxima[:5], 1) + linfit = xp.polyfit(t_maxima[:5], maxima[:5], 1) gamma_num = linfit[0] # plot if show_plots and rank == 0: plt.figure(figsize=(18, 12)) plt.plot(time, logE, label="numerical") - plt.plot(time, np.log10(E_exact(time)), label="exact") + plt.plot(time, xp.log10(E_exact(time)), label="exact") plt.legend() plt.title(f"{dt=}, {algo=}, {Nel=}, {p=}, {ppc=}") plt.xlabel("time [m/c]") @@ -160,9 +160,9 @@ def E_exact(t): # plt.show() # assert - rel_error = np.abs(gamma_num - gamma) / np.abs(gamma) - assert rel_error < 0.25, f"{rank = }: Assertion for weak Landau damping failed: {gamma_num = } vs. {gamma = }." - print(f"{rank = }: Assertion for weak Landau damping passed ({rel_error = }).") + rel_error = xp.abs(gamma_num - gamma) / xp.abs(gamma) + assert rel_error < 0.25, f"{rank =}: Assertion for weak Landau damping failed: {gamma_num =} vs. {gamma =}." + print(f"{rank =}: Assertion for weak Landau damping passed ({rel_error =}).") def IsothermalEulerSPH_soundwave( @@ -190,8 +190,8 @@ def IsothermalEulerSPH_soundwave( MPI.COMM_WORLD.Barrier() path_n_sph = os.path.join(path_pp, "kinetic_data/euler_fluid/n_sph/view_0/") - ee1, ee2, ee3 = np.load(os.path.join(path_n_sph, "grid_n_sph.npy")) - n_sph = np.load(os.path.join(path_n_sph, "n_sph.npy")) + ee1, ee2, ee3 = xp.load(os.path.join(path_n_sph, "grid_n_sph.npy")) + n_sph = xp.load(os.path.join(path_n_sph, "n_sph.npy")) # print(f'{ee1.shape = }, {n_sph.shape = }') if show_plots and rank == 0: @@ -207,7 +207,7 @@ def IsothermalEulerSPH_soundwave( plot_ct = 0 for i in range(0, Nt + 1): if i % interval == 0: - print(f"{i = }") + print(f"{i =}") plot_ct += 1 ax = plt.gca() @@ -218,21 +218,21 @@ def IsothermalEulerSPH_soundwave( plt.plot(x.squeeze(), n_sph[i, :, 0, 0], style, label=f"time={i * dt:4.2f}") plt.xlim(0, 2.5) plt.legend() - ax.set_xticks(np.linspace(0, 2.5, nx + 1)) + ax.set_xticks(xp.linspace(0, 2.5, nx + 1)) ax.xaxis.set_major_formatter(FormatStrFormatter("%.2f")) plt.grid(c="k") plt.xlabel("x") plt.ylabel(r"$\rho$") - plt.title(f"standing sound wave ($c_s = 1$) for {nx = } and {ppb = }") + plt.title(f"standing sound wave ($c_s = 1$) for {nx =} and {ppb =}") if plot_ct == 11: break plt.show() # assert - error = np.max(np.abs(n_sph[0] - n_sph[-1])) - print(f"{rank = }: Assertion for SPH sound wave passed ({error = }).") + error = xp.max(xp.abs(n_sph[0] - n_sph[-1])) + print(f"{rank =}: Assertion for SPH sound wave passed ({error =}).") assert error < 1.3e-3 @@ -262,30 +262,30 @@ def Maxwell_coaxial( def B_z(X, Y, Z, m, t): """Magnetic field in z direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return (jv(m, r) - 0.28 * yn(m, r)) * np.cos(m * theta - t) + theta = xp.arctan2(Y, X) + return (jv(m, r) - 0.28 * yn(m, r)) * xp.cos(m * theta - t) def E_r(X, Y, Z, m, t): """Electrical field in radial direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return -m / r * (jv(m, r) - 0.28 * yn(m, r)) * np.cos(m * theta - t) + theta = xp.arctan2(Y, X) + return -m / r * (jv(m, r) - 0.28 * yn(m, r)) * xp.cos(m * theta - t) def E_theta(X, Y, Z, m, t): """Electrical field in azimuthal direction of coaxial cabel""" r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return ((m / r * jv(m, r) - jv(m + 1, r)) - 0.28 * (m / r * yn(m, r) - yn(m + 1, r))) * np.sin(m * theta - t) + theta = xp.arctan2(Y, X) + return ((m / r * jv(m, r) - jv(m + 1, r)) - 0.28 * (m / r * yn(m, r) - yn(m + 1, r))) * xp.sin(m * theta - t) def to_E_r(X, Y, E_x, E_y): r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return np.cos(theta) * E_x + np.sin(theta) * E_y + theta = xp.arctan2(Y, X) + return xp.cos(theta) * E_x + xp.sin(theta) * E_y def to_E_theta(X, Y, E_x, E_y): r = (X**2 + Y**2) ** 0.5 - theta = np.arctan2(Y, X) - return -np.sin(theta) * E_x + np.cos(theta) * E_y + theta = xp.arctan2(Y, X) + return -xp.sin(theta) * E_x + xp.cos(theta) * E_y # get parameters with open(os.path.join(path_out, "parameters.yml")) as f: @@ -297,7 +297,7 @@ def to_E_theta(X, Y, E_x, E_y): pproc_path = os.path.join(path_out, "post_processing/") em_fields_path = os.path.join(pproc_path, "fields_data/em_fields/") - t_grid = np.load(os.path.join(pproc_path, "t_grid.npy")) + t_grid = xp.load(os.path.join(pproc_path, "t_grid.npy")) grids_phy = pickle.loads(Path(os.path.join(pproc_path, "fields_data/grids_phy.bin")).read_bytes()) b_field_phy = pickle.loads(Path(os.path.join(em_fields_path, "b_field_phy.bin")).read_bytes()) e_field_phy = pickle.loads(Path(os.path.join(em_fields_path, "e_field_phy.bin")).read_bytes()) @@ -311,7 +311,13 @@ def to_E_theta(X, Y, E_x, E_y): vmax = E_theta(X, Y, grids_phy[0], modes, 0).max() fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) plot_exac = ax1.contourf( - X, Y, E_theta(X, Y, grids_phy[0], modes, t_grid[-1]), cmap="plasma", levels=100, vmin=vmin, vmax=vmax + X, + Y, + E_theta(X, Y, grids_phy[0], modes, t_grid[-1]), + cmap="plasma", + levels=100, + vmin=vmin, + vmax=vmax, ) ax2.contourf( X, @@ -336,26 +342,26 @@ def to_E_theta(X, Y, E_x, E_y): Bz_tend = b_field_phy[t_grid[-1]][2][:, :, 0] Bz_exact = B_z(X, Y, grids_phy[0], modes, t_grid[-1]) - error_Er = np.max(np.abs((to_E_r(X, Y, Ex_tend, Ey_tend) - Er_exact))) - error_Etheta = np.max(np.abs((to_E_theta(X, Y, Ex_tend, Ey_tend) - Etheta_exact))) - error_Bz = np.max(np.abs((Bz_tend - Bz_exact))) + error_Er = xp.max(xp.abs((to_E_r(X, Y, Ex_tend, Ey_tend) - Er_exact))) + error_Etheta = xp.max(xp.abs((to_E_theta(X, Y, Ex_tend, Ey_tend) - Etheta_exact))) + error_Bz = xp.max(xp.abs((Bz_tend - Bz_exact))) - rel_err_Er = error_Er / np.max(np.abs(Er_exact)) - rel_err_Etheta = error_Etheta / np.max(np.abs(Etheta_exact)) - rel_err_Bz = error_Bz / np.max(np.abs(Bz_exact)) + rel_err_Er = error_Er / xp.max(xp.abs(Er_exact)) + rel_err_Etheta = error_Etheta / xp.max(xp.abs(Etheta_exact)) + rel_err_Bz = error_Bz / xp.max(xp.abs(Bz_exact)) - print(f"{rel_err_Er = }") - print(f"{rel_err_Etheta = }") - print(f"{rel_err_Bz = }") + print(f"{rel_err_Er =}") + print(f"{rel_err_Etheta =}") + print(f"{rel_err_Bz =}") - assert rel_err_Bz < 0.0021, f"{rank = }: Assertion for magnetic field Maxwell failed: {rel_err_Bz = }" - print(f"{rank = }: Assertion for magnetic field Maxwell passed ({rel_err_Bz = }).") + assert rel_err_Bz < 0.0021, f"{rank =}: Assertion for magnetic field Maxwell failed: {rel_err_Bz =}" + print(f"{rank =}: Assertion for magnetic field Maxwell passed ({rel_err_Bz =}).") assert rel_err_Etheta < 0.0021, ( - f"{rank = }: Assertion for electric (E_theta) field Maxwell failed: {rel_err_Etheta = }" + f"{rank =}: Assertion for electric (E_theta) field Maxwell failed: {rel_err_Etheta =}" ) - print(f"{rank = }: Assertion for electric field Maxwell passed ({rel_err_Etheta = }).") - assert rel_err_Er < 0.0021, f"{rank = }: Assertion for electric (E_r) field Maxwell failed: {rel_err_Er = }" - print(f"{rank = }: Assertion for electric field Maxwell passed ({rel_err_Er = }).") + print(f"{rank =}: Assertion for electric field Maxwell passed ({rel_err_Etheta =}).") + assert rel_err_Er < 0.0021, f"{rank =}: Assertion for electric (E_r) field Maxwell failed: {rel_err_Er =}" + print(f"{rank =}: Assertion for electric field Maxwell passed ({rel_err_Er =}).") if __name__ == "__main__": diff --git a/src/struphy/models/toy.py b/src/struphy/models/toy.py index 6e0ad8879..fd36b5d5f 100644 --- a/src/struphy/models/toy.py +++ b/src/struphy/models/toy.py @@ -1,13 +1,12 @@ -from dataclasses import dataclass - -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI +from struphy.feec.projectors import L2Projector +from struphy.feec.variational_utilities import InternalEnergyEvaluator from struphy.models.base import StruphyModel from struphy.models.species import FieldSpecies, FluidSpecies, ParticleSpecies from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.propagators import propagators_coupling, propagators_fields, propagators_markers -from struphy.propagators.base import Propagator rank = MPI.COMM_WORLD.Get_rank() @@ -15,33 +14,19 @@ class Maxwell(StruphyModel): r"""Maxwell's equations in vacuum. - :ref:`Equations `: - - .. math:: - - &\frac{\partial \mathbf E}{\partial t} - \nabla\times\mathbf B = 0\,, - - &\frac{\partial \mathbf B}{\partial t} + \nabla\times\mathbf E = 0\,. - - Unknowns: + :ref:`normalization`: .. math:: - & \mathbf E(t, \mathbf{x}) \ \ldots \ \textrm{electric field} - - & \mathbf B(t, \mathbf{x}) \ \ldots \ \textrm{magnetic field} + \hat E = c \hat B\,. - Parameters: + :ref:`Equations `: .. math:: - & \textrm{None} - - :ref:`normalization`: - - .. math:: + &\frac{\partial \mathbf E}{\partial t} - \nabla\times\mathbf B = 0\,, - \hat E = c \hat B\,. + &\frac{\partial \mathbf B}{\partial t} + \nabla\times\mathbf E = 0\,. :ref:`propagators` (called in sequence): @@ -96,10 +81,12 @@ def allocate_helpers(self): def update_scalar_quantities(self): en_E = 0.5 * self.mass_ops.M1.dot_inner( - self.em_fields.e_field.spline.vector, self.em_fields.e_field.spline.vector + self.em_fields.e_field.spline.vector, + self.em_fields.e_field.spline.vector, ) en_B = 0.5 * self.mass_ops.M2.dot_inner( - self.em_fields.b_field.spline.vector, self.em_fields.b_field.spline.vector + self.em_fields.b_field.spline.vector, + self.em_fields.b_field.spline.vector, ) self.update_scalar("electric energy", en_E) @@ -110,30 +97,17 @@ def update_scalar_quantities(self): class Vlasov(StruphyModel): r"""Vlasov equation in static background magnetic field. - - :ref:`Equations `: - - .. math:: - - \frac{\partial f}{\partial t} + \mathbf{v} \cdot \nabla f + \left(\mathbf{v}\times\mathbf{B}_0 \right) \cdot \frac{\partial f}{\partial \mathbf{v}} = 0\,. - - Unknowns: - - .. math:: - - & f(t, \mathbf{x}, \mathbf{v}) \ \ldots \ \textrm{phase space distribution (6D)} - - Parameters: + :ref:`normalization`: .. math:: - \mathbf{B}_0 \ \ldots \ \textrm{background magnetic field} + \hat v = \hat \Omega_\textnormal{c} \hat x\,. - :ref:`normalization`: + :ref:`Equations `: .. math:: - \hat v = \hat \Omega_\textnormal{c} \hat x\,. + \frac{\partial f}{\partial t} + \mathbf{v} \cdot \nabla f + \left(\mathbf{v}\times\mathbf{B}_0 \right) \cdot \frac{\partial f}{\partial \mathbf{v}} = 0\,. :ref:`propagators` (called in sequence): @@ -183,7 +157,7 @@ def velocity_scale(self): return "cyclotron" def allocate_helpers(self): - self._tmp = np.empty(1, dtype=float) + self._tmp = xp.empty(1, dtype=float) def update_scalar_quantities(self): particles = self.kinetic_ions.var.particles @@ -199,38 +173,29 @@ def update_scalar_quantities(self): class GuidingCenter(StruphyModel): r"""Guiding-center equation in static background magnetic field. - - :ref:`Equations `: - - .. math:: - - \frac{\partial f}{\partial t} + \left[ v_\parallel \frac{\mathbf{B}^*}{B^*_\parallel} + \frac{\mathbf{E}^* \times \mathbf{b}_0}{B^*_\parallel}\right] \cdot \frac{\partial f}{\partial \mathbf{X}} + \left[\frac{1}{\epsilon} \frac{\mathbf{B}^*}{B^*_\parallel} \cdot \mathbf{E}^*\right] \cdot \frac{\partial f}{\partial v_\parallel} = 0\,. - - where + :ref:`normalization`: .. math:: - \mathbf{E}^* = -\epsilon \mu \nabla |B_0| \,, \qquad \mathbf{B}^* = \mathbf{B}_0 + \epsilon v_\parallel \nabla \times \mathbf{b}_0 \,,\qquad B^*_\parallel = \mathbf B^* \cdot \mathbf b_0 \,. + \hat v = \hat v_\textnormal{A} \,. - Unknowns: + :ref:`Equations `: .. math:: - & f(\mathbf{X}, v_\parallel, \mu) \ \ldots \ \textrm{guiding center distribution (5D)} + \frac{\partial f}{\partial t} + \left[ v_\parallel \frac{\mathbf{B}^*}{B^*_\parallel} + \frac{\mathbf{E}^* \times \mathbf{b}_0}{B^*_\parallel}\right] \cdot \frac{\partial f}{\partial \mathbf{X}} + \left[\frac{1}{\epsilon} \frac{\mathbf{B}^*}{B^*_\parallel} \cdot \mathbf{E}^*\right] \cdot \frac{\partial f}{\partial v_\parallel} = 0\,. - Parameters: + where :math:`f(\mathbf{X}, v_\parallel, \mu, t)` is the guiding center distribution and .. math:: - & \epsilon = \frac{1 }{ \hat \Omega_{\textnormal{c}} \hat t} \ \ldots \ \textrm{inverse of cyclotron frequency times time unit} - - & \mathbf{B}_0 \ \ldots \ \textrm{background magnetic field} + \mathbf{E}^* = -\epsilon \mu \nabla |B_0| \,, \qquad \mathbf{B}^* = \mathbf{B}_0 + \epsilon v_\parallel \nabla \times \mathbf{b}_0 \,,\qquad B^*_\parallel = \mathbf B^* \cdot \mathbf b_0 \,. - :ref:`normalization`: + Moreover, .. math:: - \hat v = \hat v_\textnormal{A} \,. + \epsilon = \frac{1 }{ \hat \Omega_{\textnormal{c}} \hat t}\,,\qquad \textnormal{with} \qquad\hat \Omega_{\textnormal{c}} = \frac{Ze \hat B}{A m_\textnormal{H}}\,. :ref:`propagators` (called in sequence): @@ -282,10 +247,10 @@ def velocity_scale(self): return "alfvén" def allocate_helpers(self): - self._en_fv = np.empty(1, dtype=float) - self._en_fB = np.empty(1, dtype=float) - self._en_tot = np.empty(1, dtype=float) - self._n_lost_particles = np.empty(1, dtype=float) + self._en_fv = xp.empty(1, dtype=float) + self._en_fB = xp.empty(1, dtype=float) + self._en_tot = xp.empty(1, dtype=float) + self._n_lost_particles = xp.empty(1, dtype=float) def update_scalar_quantities(self): particles = self.kinetic_ions.var.particles @@ -343,67 +308,59 @@ class ShearAlfven(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - - dct["em_fields"]["b2"] = "Hdiv" - dct["fluid"]["mhd"] = {"u2": "Hdiv"} - return dct - - @staticmethod - def bulk_species(): - return "mhd" - - @staticmethod - def velocity_scale(): - return "alfvén" - - @staticmethod - def propagators_dct(): - return {propagators_fields.ShearAlfven: ["mhd_u2", "b2"]} + ## species + class EMFields(FieldSpecies): + def __init__(self): + self.b_field = FEECVariable(space="Hdiv") + self.init_variables() - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] + class MHD(FluidSpecies): + def __init__(self): + self.velocity = FEECVariable(space="Hdiv") + self.init_variables() - def __init__(self, params, comm, clone_config=None): - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) + class Propagators: + def __init__(self) -> None: + self.shear_alf = propagators_fields.ShearAlfven() - from struphy.polar.basic import PolarVector + @property + def bulk_species(self): + return self.mhd - # extract necessary parameters - alfven_solver = params["fluid"]["mhd"]["options"]["ShearAlfven"]["solver"] - alfven_algo = params["fluid"]["mhd"]["options"]["ShearAlfven"]["algo"] + @property + def velocity_scale(self): + return "alfvén" + def allocate_helpers(self): # project background magnetic field (2-form) and pressure (3-form) self._b_eq = self.derham.P["2"]( [ self.equil.b2_1, self.equil.b2_2, self.equil.b2_3, - ] + ], ) - # set keyword arguments for propagators - self._kwargs[propagators_fields.ShearAlfven] = { - "u_space": "Hdiv", - "solver": alfven_solver, - "algo": alfven_algo, - } + # temporary vectors for scalar quantities + self._tmp_b1 = self.derham.Vh["2"].zeros() + self._tmp_b2 = self.derham.Vh["2"].zeros() + + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + self.mhd = self.MHD() - # Initialize propagators used in splitting substeps - self.init_propagators() + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.shear_alf.variables.u = self.mhd.velocity + self.propagators.shear_alf.variables.b = self.em_fields.b_field # Scalar variables to be saved during simulation - # self.add_scalar('en_U') - # self.add_scalar('en_B') - # self.add_scalar('en_B_eq') - # self.add_scalar('en_B_tot') self.add_scalar("en_tot") self.add_scalar("en_U", compute="from_field") @@ -412,14 +369,13 @@ def __init__(self, params, comm, clone_config=None): self.add_scalar("en_B_tot", compute="from_field") self.add_scalar("en_tot2", summands=["en_U", "en_B", "en_B_eq"]) - # temporary vectors for scalar quantities - self._tmp_b1 = self.derham.Vh["2"].zeros() - self._tmp_b2 = self.derham.Vh["2"].zeros() - def update_scalar_quantities(self): # perturbed fields - en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.pointer["mhd_u2"], self.pointer["mhd_u2"]) - en_B = 0.5 * self.mass_ops.M2.dot_inner(self.pointer["b2"], self.pointer["b2"]) + en_U = 0.5 * self.mass_ops.M2n.dot_inner(self.mhd.velocity.spline.vector, self.mhd.velocity.spline.vector) + en_B = 0.5 * self.mass_ops.M2.dot_inner( + self.em_fields.b_field.spline.vector, + self.em_fields.b_field.spline.vector, + ) self.update_scalar("en_U", en_U) self.update_scalar("en_B", en_B) @@ -432,7 +388,7 @@ def update_scalar_quantities(self): # total magnetic field self._b_eq.copy(out=self._tmp_b1) - self._tmp_b1 += self.pointer["b2"] + self._tmp_b1 += self.em_fields.b_field.spline.vector self.mass_ops.M2.dot(self._tmp_b1, apply_bc=False, out=self._tmp_b2) en_Btot = self._tmp_b1.inner(self._tmp_b2) / 2 @@ -465,77 +421,74 @@ class VariationalPressurelessFluid(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["fluid"]["fluid"] = {"rho3": "L2", "uv": "H1vec"} - return dct + ## species + + class Fluid(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.init_variables() + + ## propagators - @staticmethod - def bulk_species(): - return "fluid" + class Propagators: + def __init__(self): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() - @staticmethod - def velocity_scale(): - return "alfvén" + ## abstract methods - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["fluid_rho3", "fluid_uv"], - propagators_fields.VariationalMomentumAdvection: ["fluid_uv"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.mass import WeightedMassOperator - from struphy.feec.variational_utilities import H1vecMassMatrix_density - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - # Initialize mass matrix - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - - gamma = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": "pressureless", - "gamma": gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - # Scalar variables to be saved during simulation + # 1. instantiate all species + self.fluid = self.Fluid() + + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.fluid.density + self.propagators.variat_dens.variables.u = self.fluid.velocity + self.propagators.variat_mom.variables.u = self.fluid.velocity + + # define scalars for update_scalar_quantities self.add_scalar("en_U") + @property + def bulk_species(self): + return self.fluid + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + pass + def update_scalar_quantities(self): - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["fluid_uv"], self.pointer["fluid_uv"]) + u = self.fluid.velocity.spline.vector + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='pressureless')\n", + ] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + class VariationalBarotropicFluid(StruphyModel): r"""Barotropic fluid equations discretized with a variational method. @@ -564,84 +517,84 @@ class VariationalBarotropicFluid(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["fluid"]["fluid"] = {"rho3": "L2", "uv": "H1vec"} - return dct + ## species - @staticmethod - def bulk_species(): - return "fluid" + class Fluid(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + ## propagators - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["fluid_rho3", "fluid_uv"], - propagators_fields.VariationalMomentumAdvection: ["fluid_uv"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.variational_utilities import H1vecMassMatrix_density - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - # Initialize mass matrix - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - - gamma = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": "barotropic", - "gamma": gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() + class Propagators: + def __init__(self): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() - # Scalar variables to be saved during simulation + ## abstract methods + + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.fluid = self.Fluid() + + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.fluid.density + self.propagators.variat_dens.variables.u = self.fluid.velocity + self.propagators.variat_mom.variables.u = self.fluid.velocity + + # define scalars for update_scalar_quantities self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_tot") + @property + def bulk_species(self): + return self.fluid + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): + pass + def update_scalar_quantities(self): - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["fluid_uv"], self.pointer["fluid_uv"]) + rho = self.fluid.density.spline.vector + u = self.fluid.velocity.spline.vector + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) - en_thermo = 0.5 * self.mass_ops.M3.dot_inner(self.pointer["fluid_rho3"], self.pointer["fluid_rho3"]) + en_thermo = 0.5 * self.mass_ops.M3.dot_inner(rho, rho) self.update_scalar("en_thermo", en_thermo) en_tot = en_U + en_thermo self.update_scalar("en_tot", en_tot) + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='barotropic')\n", + ] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + class VariationalCompressibleFluid(StruphyModel): r"""Fully compressible fluid equations discretized with a variational method. @@ -673,106 +626,71 @@ class VariationalCompressibleFluid(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - dct["fluid"]["fluid"] = {"rho3": "L2", "s3": "L2", "uv": "H1vec"} - return dct + ## species - @staticmethod - def bulk_species(): - return "fluid" + class Fluid(FluidSpecies): + def __init__(self): + self.density = FEECVariable(space="L2") + self.velocity = FEECVariable(space="H1vec") + self.entropy = FEECVariable(space="L2") + self.init_variables() - @staticmethod - def velocity_scale(): - return "alfvén" + ## propagators - @staticmethod - def propagators_dct(): - return { - propagators_fields.VariationalDensityEvolve: ["fluid_rho3", "fluid_uv"], - propagators_fields.VariationalMomentumAdvection: ["fluid_uv"], - propagators_fields.VariationalEntropyEvolve: ["fluid_s3", "fluid_uv"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import H1vecMassMatrix_density - - # initialize base class - super().__init__(params, comm=comm, clone_config=clone_config) - - # Initialize mass matrix - self.WMM = H1vecMassMatrix_density(self.derham, self.mass_ops, self.domain) - - # Initialize propagators/integrators used in splitting substeps - lin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["lin_solver"] - nonlin_solver_momentum = params["fluid"]["fluid"]["options"]["VariationalMomentumAdvection"]["nonlin_solver"] - lin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["lin_solver"] - nonlin_solver_density = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["nonlin_solver"] - lin_solver_entropy = params["fluid"]["fluid"]["options"]["VariationalEntropyEvolve"]["lin_solver"] - nonlin_solver_entropy = params["fluid"]["fluid"]["options"]["VariationalEntropyEvolve"]["nonlin_solver"] - - self._gamma = params["fluid"]["fluid"]["options"]["VariationalDensityEvolve"]["physics"]["gamma"] - model = "full" - - from struphy.feec.variational_utilities import InternalEnergyEvaluator - - self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) - - # set keyword arguments for propagators - self._kwargs[propagators_fields.VariationalDensityEvolve] = { - "model": model, - "s": self.pointer["fluid_s3"], - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_density, - "nonlin_solver": nonlin_solver_density, - "energy_evaluator": self._energy_evaluator, - } - - self._kwargs[propagators_fields.VariationalMomentumAdvection] = { - "mass_ops": self.WMM, - "lin_solver": lin_solver_momentum, - "nonlin_solver": nonlin_solver_momentum, - } - - self._kwargs[propagators_fields.VariationalEntropyEvolve] = { - "model": model, - "rho": self.pointer["fluid_rho3"], - "gamma": self._gamma, - "mass_ops": self.WMM, - "lin_solver": lin_solver_entropy, - "nonlin_solver": nonlin_solver_entropy, - "energy_evaluator": self._energy_evaluator, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() + class Propagators: + def __init__(self): + self.variat_dens = propagators_fields.VariationalDensityEvolve() + self.variat_mom = propagators_fields.VariationalMomentumAdvection() + self.variat_ent = propagators_fields.VariationalEntropyEvolve() - # Scalar variables to be saved during simulation + ## abstract methods + + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.fluid = self.Fluid() + + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.variat_dens.variables.rho = self.fluid.density + self.propagators.variat_dens.variables.u = self.fluid.velocity + self.propagators.variat_mom.variables.u = self.fluid.velocity + self.propagators.variat_ent.variables.s = self.fluid.entropy + self.propagators.variat_ent.variables.u = self.fluid.velocity + + # define scalars for update_scalar_quantities self.add_scalar("en_U") self.add_scalar("en_thermo") self.add_scalar("en_tot") - # temporary vectors for scalar quantities + @property + def bulk_species(self): + return self.fluid + + @property + def velocity_scale(self): + return "alfvén" + + def allocate_helpers(self): projV3 = L2Projector("L2", self._mass_ops) def f(e1, e2, e3): return 1 - f = np.vectorize(f) + f = xp.vectorize(f) self._integrator = projV3(f) + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self.propagators.variat_ent.options.gamma) + def update_scalar_quantities(self): - en_U = 0.5 * self.WMM.massop.dot_inner(self.pointer["fluid_uv"], self.pointer["fluid_uv"]) + rho = self.fluid.density.spline.vector + u = self.fluid.velocity.spline.vector + + en_U = 0.5 * self.mass_ops.WMM.massop.dot_inner(u, u) self.update_scalar("en_U", en_U) en_thermo = self.update_thermo_energy() @@ -780,15 +698,45 @@ def update_scalar_quantities(self): en_tot = en_U + en_thermo self.update_scalar("en_tot", en_tot) + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "variat_dens.Options" in line: + new_file += [ + "model.propagators.variat_dens.options = model.propagators.variat_dens.Options(model='full',\n", + ] + new_file += [ + " s=model.fluid.entropy)\n", + ] + elif "variat_ent.Options" in line: + new_file += [ + "model.propagators.variat_ent.options = model.propagators.variat_ent.Options(model='full',\n", + ] + new_file += [ + " rho=model.fluid.density)\n", + ] + elif "entropy.add_background" in line: + new_file += ["model.fluid.density.add_background(FieldsBackground())\n"] + new_file += [line] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + def update_thermo_energy(self): """Reuse tmp used in VariationalEntropyEvolve to compute the thermodynamical energy. :meta private: """ - en_prop = self._propagators[2] + en_prop = self.propagators.variat_ent - self._energy_evaluator.sf.vector = self.pointer["fluid_s3"] - self._energy_evaluator.rhof.vector = self.pointer["fluid_rho3"] + self._energy_evaluator.sf.vector = self.fluid.entropy.spline.vector + self._energy_evaluator.rhof.vector = self.fluid.density.spline.vector sf_values = self._energy_evaluator.sf.eval_tp_fixed_loc( self._energy_evaluator.integration_grid_spans, self._energy_evaluator.integration_grid_bd, @@ -809,7 +757,7 @@ def update_thermo_energy(self): def __ener(self, rho, s): """Themodynamical energy as a function of rho and s, usign the perfect gaz hypothesis E(rho, s) = rho^gamma*exp(s/rho)""" - return np.power(rho, self._gamma) * np.exp(s / rho) + return xp.power(rho, self.propagators.variat_ent.options.gamma) * xp.exp(s / rho) class Poisson(StruphyModel): @@ -840,65 +788,89 @@ class Poisson(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} + ## species + + class EMFields(FieldSpecies): + def __init__(self): + self.phi = FEECVariable(space="H1") + self.source = FEECVariable(space="H1") + self.init_variables() + + ## propagators + + class Propagators: + def __init__(self): + self.source = propagators_fields.TimeDependentSource() + self.poisson = propagators_fields.Poisson() - dct["em_fields"]["phi"] = "H1" - dct["em_fields"]["source"] = "H1" - return dct + ## abstract methods - @staticmethod - def bulk_species(): + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMFields() + + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.source.variables.source = self.em_fields.source + self.propagators.poisson.variables.phi = self.em_fields.phi + + @property + def bulk_species(self): return None - @staticmethod - def velocity_scale(): + @property + def velocity_scale(self): return None - @staticmethod - def propagators_dct(): - return { - propagators_fields.TimeDependentSource: ["source"], - propagators_fields.ImplicitDiffusion: ["phi"], - } - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - def __init__(self, params, comm, clone_config=None): - super().__init__(params, comm=comm, clone_config=clone_config) - - # extract necessary parameters - model_params = params["em_fields"]["options"]["ImplicitDiffusion"]["model"] - solver_params = params["em_fields"]["options"]["ImplicitDiffusion"]["solver"] - omega = params["em_fields"]["options"]["TimeDependentSource"]["omega"] - hfun = params["em_fields"]["options"]["TimeDependentSource"]["hfun"] - - # set keyword arguments for propagators - self._kwargs[propagators_fields.TimeDependentSource] = { - "omega": omega, - "hfun": hfun, - } - - self._kwargs[propagators_fields.ImplicitDiffusion] = { - "sigma_1": model_params["sigma_1"], - "stab_mat": model_params["stab_mat"], - "diffusion_mat": model_params["diffusion_mat"], - "rho": self.pointer["source"], - "solver": solver_params, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() + def allocate_helpers(self): + pass def update_scalar_quantities(self): pass + def allocate_propagators(self): + """Solve initial Poisson equation. + + :meta private: + """ + + # initialize fields and particles + super().allocate_propagators() + + # # use setter to assign source + # self.propagators.poisson.rho = self.mass_ops.M0.dot(self.em_fields.source.spline.vector) + + # Solve with dt=1. and compute electric field + if MPI.COMM_WORLD.Get_rank() == 0: + print("\nSolving initial Poisson problem...") + + self.propagators.poisson(1.0) + + if MPI.COMM_WORLD.Get_rank() == 0: + print("Done.") + + # default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "poisson.Options" in line: + new_file += [ + "model.propagators.poisson.options = model.propagators.poisson.Options(rho=model.em_fields.source)\n", + ] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) + class DeterministicParticleDiffusion(StruphyModel): r"""Diffusion equation discretized with a deterministic particle method; @@ -926,64 +898,49 @@ class DeterministicParticleDiffusion(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - - dct["kinetic"]["species1"] = "Particles3D" - return dct + ## species - @staticmethod - def bulk_species(): - return "species1" + class Hydrogen(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="Particles3D") + self.init_variables() - @staticmethod - def velocity_scale(): - return None + ## propagators - @staticmethod - def propagators_dct(): - return {propagators_markers.PushDeterministicDiffusion: ["species1"]} + class Propagators: + def __init__(self): + self.det_diff = propagators_markers.PushDeterministicDiffusion() - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] + ## abstract methods - def __init__(self, params, comm, clone_config=None): - super().__init__(params, comm=comm, clone_config=clone_config) + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - from mpi4py.MPI import IN_PLACE, SUM + # 1. instantiate all species + self.hydrogen = self.Hydrogen() - # prelim - params = self.kinetic["species1"]["params"] - algo = params["options"]["PushDeterministicDiffusion"]["algo"] - diffusion_coefficient = params["options"]["PushDeterministicDiffusion"]["diffusion_coefficient"] + # 2. instantiate all propagators + self.propagators = self.Propagators() - # # project magnetic background - # self._b_eq = self.derham.P['2']([self.equil.b2_1, - # self.equil.b2_2, - # self.equil.b2_3]) + # 3. assign variables to propagators + self.propagators.det_diff.variables.var = self.hydrogen.var - # set keyword arguments for propagators - self._kwargs[propagators_markers.PushDeterministicDiffusion] = { - "algo": algo, - "bc_type": params["markers"]["bc"], - "diffusion_coefficient": diffusion_coefficient, - } + # define scalars for update_scalar_quantities + # self.add_scalar("electric energy") + # self.add_scalar("magnetic energy") + # self.add_scalar("total energy") - # Initialize propagators used in splitting substeps - self.init_propagators() + @property + def bulk_species(self): + return self.hydrogen - # Scalar variables to be saved during simulation - self.add_scalar("en_f") + @property + def velocity_scale(self): + return None - # MPI operations needed for scalar variables - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE - self._tmp = np.empty(1, dtype=float) + def allocate_helpers(self): + pass def update_scalar_quantities(self): pass @@ -1014,64 +971,49 @@ class RandomParticleDiffusion(StruphyModel): :ref:`Model info `: """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - - dct["kinetic"]["species1"] = "Particles3D" - return dct + ## species - @staticmethod - def bulk_species(): - return "species1" + class Hydrogen(ParticleSpecies): + def __init__(self): + self.var = PICVariable(space="Particles3D") + self.init_variables() - @staticmethod - def velocity_scale(): - return None + ## propagators - @staticmethod - def propagators_dct(): - return {propagators_markers.PushRandomDiffusion: ["species1"]} + class Propagators: + def __init__(self): + self.rand_diff = propagators_markers.PushRandomDiffusion() - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] + ## abstract methods - def __init__(self, params, comm, clone_config=None): - super().__init__(params, comm=comm, clone_config=clone_config) + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") - from mpi4py.MPI import IN_PLACE, SUM + # 1. instantiate all species + self.hydrogen = self.Hydrogen() - # prelim - species1_params = self.kinetic["species1"]["params"] - algo = species1_params["options"]["PushRandomDiffusion"]["algo"] - diffusion_coefficient = species1_params["options"]["PushRandomDiffusion"]["diffusion_coefficient"] + # 2. instantiate all propagators + self.propagators = self.Propagators() - # # project magnetic background - # self._b_eq = self.derham.P['2']([self.equil.b2_1, - # self.equil.b2_2, - # self.equil.b2_3]) + # 3. assign variables to propagators + self.propagators.rand_diff.variables.var = self.hydrogen.var - # set keyword arguments for propagators - self._kwargs[propagators_markers.PushRandomDiffusion] = { - "algo": algo, - "bc_type": species1_params["markers"]["bc"], - "diffusion_coefficient": diffusion_coefficient, - } + # define scalars for update_scalar_quantities + # self.add_scalar("electric energy") + # self.add_scalar("magnetic energy") + # self.add_scalar("total energy") - # Initialize propagators used in splitting substeps - self.init_propagators() + @property + def bulk_species(self): + return self.hydrogen - # Scalar variables to be saved during simulation - self.add_scalar("en_f") + @property + def velocity_scale(self): + return None - # MPI operations needed for scalar variables - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE - self._tmp = np.empty(1, dtype=float) + def allocate_helpers(self): + pass def update_scalar_quantities(self): pass @@ -1211,116 +1153,75 @@ class TwoFluidQuasiNeutralToy(StruphyModel): in plasma physics, Journal of Computational Physics 2018. """ - @staticmethod - def species(): - dct = {"em_fields": {}, "fluid": {}, "kinetic": {}} - - dct["em_fields"]["potential"] = "L2" - dct["fluid"]["ions"] = { - "u": "Hdiv", - } - dct["fluid"]["electrons"] = { - "u": "Hdiv", - } - return dct - - @staticmethod - def bulk_species(): - return "ions" - - @staticmethod - def velocity_scale(): - return "thermal" + ## species - @staticmethod - def propagators_dct(): - return {propagators_fields.TwoFluidQuasiNeutralFull: ["ions_u", "electrons_u", "potential"]} - - __em_fields__ = species()["em_fields"] - __fluid_species__ = species()["fluid"] - __kinetic_species__ = species()["kinetic"] - __bulk_species__ = bulk_species() - __velocity_scale__ = velocity_scale() - __propagators__ = [prop.__name__ for prop in propagators_dct()] - - # add special options - @classmethod - def options(cls): - dct = super().options() - cls.add_option( - species=["fluid", "electrons"], - option=propagators_fields.TwoFluidQuasiNeutralFull, - dct=dct, - ) - return dct + class EMfields(FieldSpecies): + def __init__(self): + self.phi = FEECVariable(space="L2") + self.init_variables() - def __init__(self, params, comm, clone_config=None): - super().__init__(params, comm=comm, clone_config=clone_config) + class Ions(FluidSpecies): + def __init__(self): + self.u = FEECVariable(space="Hdiv") + self.init_variables() - # get species paramaters - electrons_params = params["fluid"]["electrons"] + class Electrons(FluidSpecies): + def __init__(self): + self.u = FEECVariable(space="Hdiv") + self.init_variables() - # Get coupling strength - if electrons_params["options"]["TwoFluidQuasiNeutralFull"]["override_eq_params"]: - self._epsilon = electrons_params["options"]["TwoFluidQuasiNeutralFull"]["eps_norm"] - print( - f"\n!!! Override equation parameters: {self._epsilon = }.", - ) - else: - self._epsilon = self.equation_params["electrons"]["epsilon"] - - # extract necessary parameters - stokes_solver = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["solver"] - stokes_nu = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["nu"] - stokes_nu_e = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["nu_e"] - stokes_a = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["a"] - stokes_R0 = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["R0"] - stokes_B0 = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["B0"] - stokes_Bp = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["Bp"] - stokes_alpha = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["alpha"] - stokes_beta = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["beta"] - stokes_sigma = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["stab_sigma"] - stokes_variant = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["variant"] - stokes_method_to_solve = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["method_to_solve"] - stokes_preconditioner = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["preconditioner"] - stokes_spectralanalysis = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"][ - "spectralanalysis" - ] - stokes_lifting = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["lifting"] - stokes_dimension = params["fluid"]["electrons"]["options"]["TwoFluidQuasiNeutralFull"]["dimension"] - stokes_1D_dt = params["time"]["dt"] - - # Check MPI size to ensure only one MPI process - size = comm.Get_size() - if size != 1 and stokes_variant == "Uzawa": - if comm.Get_rank() == 0: - print(f"Error: TwoFluidQuasiNeutralToy only runs with one MPI process.") - return # Early return to stop execution for multiple MPI processes - - # set keyword arguments for propagators - self._kwargs[propagators_fields.TwoFluidQuasiNeutralFull] = { - "solver": stokes_solver, - "nu": stokes_nu, - "nu_e": stokes_nu_e, - "eps_norm": self._epsilon, - "a": stokes_a, - "R0": stokes_R0, - "B0": stokes_B0, - "Bp": stokes_Bp, - "alpha": stokes_alpha, - "beta": stokes_beta, - "stab_sigma": stokes_sigma, - "variant": stokes_variant, - "method_to_solve": stokes_method_to_solve, - "preconditioner": stokes_preconditioner, - "spectralanalysis": stokes_spectralanalysis, - "dimension": stokes_dimension, - "D1_dt": stokes_1D_dt, - "lifting": stokes_lifting, - } - - # Initialize propagators used in splitting substeps - self.init_propagators() + ## propagators + + class Propagators: + def __init__(self): + self.qn_full = propagators_fields.TwoFluidQuasiNeutralFull() + + ## abstract methods + + def __init__(self): + if rank == 0: + print(f"\n*** Creating light-weight instance of model '{self.__class__.__name__}':") + + # 1. instantiate all species + self.em_fields = self.EMfields() + self.ions = self.Ions() + self.electrons = self.Electrons() + + # 2. instantiate all propagators + self.propagators = self.Propagators() + + # 3. assign variables to propagators + self.propagators.qn_full.variables.u = self.ions.u + self.propagators.qn_full.variables.ue = self.electrons.u + self.propagators.qn_full.variables.phi = self.em_fields.phi + + # define scalars for update_scalar_quantities + + @property + def bulk_species(self): + return self.ions + + @property + def velocity_scale(self): + return "thermal" + + def allocate_helpers(self): + pass def update_scalar_quantities(self): pass + + ## default parameters + def generate_default_parameter_file(self, path=None, prompt=True): + params_path = super().generate_default_parameter_file(path=path, prompt=prompt) + new_file = [] + with open(params_path, "r") as f: + for line in f: + if "BaseUnits()" in line: + new_file += ["base_units = BaseUnits(kBT=1.0)\n"] + else: + new_file += [line] + + with open(params_path, "w") as f: + for line in new_file: + f.write(line) diff --git a/src/struphy/models/variables.py b/src/struphy/models/variables.py index edac5f6a6..3f47f1efe 100644 --- a/src/struphy/models/variables.py +++ b/src/struphy/models/variables.py @@ -4,8 +4,8 @@ from abc import ABCMeta, abstractmethod from typing import TYPE_CHECKING -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham, SplineFunction from struphy.fields_background.base import FluidEquilibrium @@ -83,7 +83,7 @@ def add_background(self, background, verbose=True): if verbose and MPI.COMM_WORLD.Get_rank() == 0: print( - f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added background '{background.__class__.__name__}' with:" + f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added background '{background.__class__.__name__}' with:", ) for k, v in background.__dict__.items(): print(f" {k}: {v}") @@ -125,7 +125,7 @@ def add_perturbation(self, perturbation: Perturbation, verbose=True): if verbose and MPI.COMM_WORLD.Get_rank() == 0: print( - f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added perturbation '{perturbation.__class__.__name__}' with:" + f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added perturbation '{perturbation.__class__.__name__}' with:", ) for k, v in perturbation.__dict__.items(): print(f" {k}: {v}") @@ -182,7 +182,7 @@ def add_initial_condition(self, init: KineticBackground, verbose=True): self._initial_condition = init if verbose and MPI.COMM_WORLD.Get_rank() == 0: print( - f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added initial condition '{init.__class__.__name__}' with:" + f"\nVariable '{self.__name__}' of species '{self.species.__class__.__name__}' - added initial condition '{init.__class__.__name__}' with:", ) for k, v in init.__dict__.items(): print(f" {k}: {v}") @@ -204,7 +204,7 @@ def allocate( ): # assert isinstance(self.species, KineticSpecies) assert isinstance(self.backgrounds, KineticBackground), ( - f"List input not allowed, you can sum Kineticbackgrounds before passing them to add_background." + "List input not allowed, you can sum Kineticbackgrounds before passing them to add_background." ) if derham is None: @@ -264,7 +264,7 @@ def allocate( f"The number of markers for which data should be stored (={self._n_to_save}) murst be <= than the total number of markers (={obj.Np})" ) if self._n_to_save > 0: - self._saved_markers = np.zeros( + self._saved_markers = xp.zeros( (self._n_to_save, self.particles.markers.shape[1]), dtype=float, ) @@ -277,7 +277,7 @@ def n_to_save(self) -> int: return self._n_to_save @property - def saved_markers(self) -> np.ndarray: + def saved_markers(self) -> xp.ndarray: return self._saved_markers @@ -350,7 +350,7 @@ def allocate( verbose: bool = False, ): assert isinstance(self.backgrounds, FluidEquilibrium), ( - f"List input not allowed, you can sum Kineticbackgrounds before passing them to add_background." + "List input not allowed, you can sum Kineticbackgrounds before passing them to add_background." ) self.backgrounds.domain = domain @@ -408,7 +408,7 @@ def allocate( f"The number of markers for which data should be stored (={self._n_to_save}) murst be <= than the total number of markers (={obj.Np})" ) if self._n_to_save > 0: - self._saved_markers = np.zeros( + self._saved_markers = xp.zeros( (self._n_to_save, self.particles.markers.shape[1]), dtype=float, ) @@ -421,5 +421,5 @@ def n_to_save(self) -> int: return self._n_to_save @property - def saved_markers(self) -> np.ndarray: + def saved_markers(self) -> xp.ndarray: return self._saved_markers diff --git a/src/struphy/ode/solvers.py b/src/struphy/ode/solvers.py index 12900a878..c6d6366b9 100644 --- a/src/struphy/ode/solvers.py +++ b/src/struphy/ode/solvers.py @@ -1,6 +1,6 @@ from inspect import signature -import numpy as np +import cunumpy as xp from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector diff --git a/src/struphy/ode/tests/test_ode_feec.py b/src/struphy/ode/tests/test_ode_feec.py index d03af5a95..7dfa87a46 100644 --- a/src/struphy/ode/tests/test_ode_feec.py +++ b/src/struphy/ode/tests/test_ode_feec.py @@ -5,7 +5,6 @@ from struphy.ode.utils import OptsButcher -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize( "spaces", [ @@ -21,9 +20,9 @@ def test_exp_growth(spaces, algo, show_plots=False): """Solve dy/dt = omega*y for different feec variables y and with all available solvers from the ButcherTableau.""" - import numpy as np + import cunumpy as xp from matplotlib import pyplot as plt - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -41,7 +40,7 @@ def test_exp_growth(spaces, algo, show_plots=False): c0 = 1.2 omega = 2.3 - y_exact = lambda t: c0 * np.exp(omega * t) + y_exact = lambda t: c0 * xp.exp(omega * t) vector_field = {} for i, space in enumerate(spaces): @@ -101,9 +100,9 @@ def f(t, y1, y2, y3, out=out): vector_field[var] = f - print(f"{vector_field = }") + print(f"{vector_field =}") butcher = ButcherTableau(algo=algo) - print(f"{butcher = }") + print(f"{butcher =}") solver = ODEsolverFEEC(vector_field, butcher=butcher) @@ -118,8 +117,8 @@ def f(t, y1, y2, y3, out=out): errors = {} for i, h in enumerate(hs): errors[h] = {} - time = np.linspace(0, Tend, int(Tend / h) + 1) - print(f"{h = }, {time.size = }") + time = xp.linspace(0, Tend, int(Tend / h) + 1) + print(f"{h =}, {time.size =}") yvec = y_exact(time) ymax = {} for var in vector_field: @@ -130,17 +129,17 @@ def f(t, y1, y2, y3, out=out): for b in var.blocks: b[:] = c0 var.update_ghost_regions() - ymax[var] = c0 * np.ones_like(time) + ymax[var] = c0 * xp.ones_like(time) for n in range(time.size - 1): tn = h * n solver(tn, h) for var in vector_field: - ymax[var][n + 1] = np.max(var.toarray()) + ymax[var][n + 1] = xp.max(var.toarray()) # checks for var in vector_field: - errors[h][var] = h * np.sum(np.abs(yvec - ymax[var])) / (h * np.sum(np.abs(yvec))) - print(f"{errors[h][var] = }") + errors[h][var] = h * xp.sum(xp.abs(yvec - ymax[var])) / (h * xp.sum(xp.abs(yvec))) + print(f"{errors[h][var] =}") assert errors[h][var] < 0.31 if rank == 0: @@ -162,16 +161,16 @@ def f(t, y1, y2, y3, out=out): h_vec += [h] err_vec += [dct[var]] - m, _ = np.polyfit(np.log(h_vec), np.log(err_vec), deg=1) - print(f"{spaces[j]}-space, fitted convergence rate = {m} for {algo = } with {solver.butcher.conv_rate = }") - assert np.abs(m - solver.butcher.conv_rate) < 0.1 - print(f"Convergence check passed on {rank = }.") + m, _ = xp.polyfit(xp.log(h_vec), xp.log(err_vec), deg=1) + print(f"{spaces[j]}-space, fitted convergence rate = {m} for {algo =} with {solver.butcher.conv_rate =}") + assert xp.abs(m - solver.butcher.conv_rate) < 0.1 + print(f"Convergence check passed on {rank =}.") if rank == 0: - plt.loglog(h_vec, h_vec, "--", label=f"h") - plt.loglog(h_vec, [h**2 for h in h_vec], "--", label=f"h^2") - plt.loglog(h_vec, [h**3 for h in h_vec], "--", label=f"h^3") - plt.loglog(h_vec, [h**4 for h in h_vec], "--", label=f"h^4") + plt.loglog(h_vec, h_vec, "--", label="h") + plt.loglog(h_vec, [h**2 for h in h_vec], "--", label="h^2") + plt.loglog(h_vec, [h**3 for h in h_vec], "--", label="h^3") + plt.loglog(h_vec, [h**4 for h in h_vec], "--", label="h^4") plt.loglog(h_vec, err_vec, "o-k", label=f"{spaces[j]}-space, {algo}") if rank == 0: plt.xlabel("log(h)") diff --git a/src/struphy/ode/utils.py b/src/struphy/ode/utils.py index 83300ae58..6748d07f1 100644 --- a/src/struphy/ode/utils.py +++ b/src/struphy/ode/utils.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Literal, get_args -import numpy as np +import cunumpy as xp OptsButcher = Literal[ "rk4", @@ -67,14 +67,14 @@ def __post_init__(self): else: raise NotImplementedError(f"Chosen algorithm {self.algo} is not implemented.") - self._b = np.array(b) - self._c = np.array(c) + self._b = xp.array(b) + self._c = xp.array(c) assert self._b.size == self._c.size self._n_stages = self._b.size assert len(a) == self.n_stages - 1 - self._a = np.tri(self.n_stages, k=-1) + self._a = xp.tri(self.n_stages, k=-1) for l, st in enumerate(a): assert len(st) == l + 1 self._a[l + 1, : l + 1] = st diff --git a/src/struphy/pic/accumulation/accum_kernels.py b/src/struphy/pic/accumulation/accum_kernels.py index 8d3c2923b..2a82a9bcf 100644 --- a/src/struphy/pic/accumulation/accum_kernels.py +++ b/src/struphy/pic/accumulation/accum_kernels.py @@ -33,7 +33,6 @@ def charge_density_0form( args_derham: "DerhamArguments", args_domain: "DomainArguments", vec: "float[:,:,:]", - vdim: "int", ): r""" Kernel for :class:`~struphy.pic.accumulation.particles_to_grid.AccumulatorVector` into V0 with the filling @@ -45,6 +44,7 @@ def charge_density_0form( markers = args_markers.markers Np = args_markers.Np + weight_idx = args_markers.weight_idx # -- removed omp: #$ omp parallel private (ip, eta1, eta2, eta3, filling) # -- removed omp: #$ omp for reduction ( + :vec) @@ -59,7 +59,7 @@ def charge_density_0form( eta3 = markers[ip, 2] # filling = w_p/N - filling = markers[ip, 3 + vdim] / Np + filling = markers[ip, weight_idx] / Np particle_to_mat_kernels.vec_fill_b_v0( args_derham, @@ -487,57 +487,6 @@ def linear_vlasov_ampere( # -- removed omp: #$ omp end parallel -def vlasov_maxwell_poisson( - args_markers: "MarkerArguments", - args_derham: "DerhamArguments", - args_domain: "DomainArguments", - vec: "float[:,:,:]", -): - r""" - Accumulates the charge density in V0 - - .. math:: - - \rho_p^\mu = w_p \,. - - Parameters - ---------- - - Note - ---- - The above parameter list contains only the model specific input arguments. - """ - - markers = args_markers.markers - Np = args_markers.Np - - # -- removed omp: #$ omp parallel private (ip, eta1, eta2, eta3, filling) - # -- removed omp: #$ omp for reduction ( + :vec) - for ip in range(shape(markers)[0]): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - # marker positions - eta1 = markers[ip, 0] - eta2 = markers[ip, 1] - eta3 = markers[ip, 2] - - # filling = w_p - filling = markers[ip, 6] / Np - - particle_to_mat_kernels.vec_fill_b_v0( - args_derham, - eta1, - eta2, - eta3, - vec, - filling, - ) - - # -- removed omp: #$ omp end parallel - - @stack_array("dfm", "df_inv", "df_inv_t", "g_inv", "v", "df_inv_times_v", "filling_m", "filling_v") def vlasov_maxwell( args_markers: "MarkerArguments", @@ -1163,9 +1112,7 @@ def pc_lin_mhd_6d_full( vec1_3: "float[:,:,:]", vec2_3: "float[:,:,:]", vec3_3: "float[:,:,:]", - scale_mat: "float", - scale_vec: "float", - boundary_cut: "float", + ep_scale: "float", ): r"""Accumulates into V1 with the filling functions @@ -1209,10 +1156,6 @@ def pc_lin_mhd_6d_full( if markers[ip, 0] == -1.0: continue - # boundary cut - if markers[ip, 0] < boundary_cut or markers[ip, 0] > 1.0 - boundary_cut: - continue - # marker positions eta1 = markers[ip, 0] eta2 = markers[ip, 1] @@ -1243,8 +1186,8 @@ def pc_lin_mhd_6d_full( weight = markers[ip, 8] - filling_m[:, :] = weight * tmp1 / Np * scale_mat - filling_v[:] = weight * tmp_v / Np * scale_vec + filling_m[:, :] = weight * tmp1 / Np * ep_scale + filling_v[:] = weight * tmp_v / Np * ep_scale # call the appropriate matvec filler particle_to_mat_kernels.m_v_fill_v1_pressure_full( @@ -1362,9 +1305,7 @@ def pc_lin_mhd_6d( vec1_3: "float[:,:,:]", vec2_3: "float[:,:,:]", vec3_3: "float[:,:,:]", - scale_mat: "float", - scale_vec: "float", - boundary_cut: "float", + ep_scale: "float", ): r"""Accumulates into V1 with the filling functions @@ -1407,10 +1348,6 @@ def pc_lin_mhd_6d( if markers[ip, 0] == -1.0: continue - # boundary cut - if markers[ip, 0] < boundary_cut or markers[ip, 0] > 1.0 - boundary_cut: - continue - # marker positions eta1 = markers[ip, 0] eta2 = markers[ip, 1] @@ -1441,8 +1378,8 @@ def pc_lin_mhd_6d( linalg_kernels.matrix_matrix(df_inv, df_inv_t, tmp1) linalg_kernels.matrix_vector(df_inv, v, tmp_v) - filling_m[:, :] = weight * tmp1 * scale_mat - filling_v[:] = weight * tmp_v * scale_vec + filling_m[:, :] = weight * tmp1 * ep_scale + filling_v[:] = weight * tmp_v * ep_scale # call the appropriate matvec filler particle_to_mat_kernels.m_v_fill_v1_pressure( diff --git a/src/struphy/pic/accumulation/accum_kernels_gc.py b/src/struphy/pic/accumulation/accum_kernels_gc.py index 628eeeab7..fecf6a255 100644 --- a/src/struphy/pic/accumulation/accum_kernels_gc.py +++ b/src/struphy/pic/accumulation/accum_kernels_gc.py @@ -8,7 +8,7 @@ These kernels are passed to :class:`struphy.pic.accumulation.particles_to_grid.Accumulator`. """ -from numpy import empty, shape, zeros +from numpy import empty, mod, shape, zeros from pyccel.decorators import stack_array import struphy.bsplines.bsplines_kernels as bsplines_kernels @@ -67,6 +67,46 @@ def gc_density_0form( # -- removed omp: #$ omp end parallel +def gc_mag_density_0form( + args_markers: "MarkerArguments", + args_derham: "DerhamArguments", + args_domain: "DomainArguments", + vec: "float[:,:,:]", + scale: "float", # model specific argument +): + r""" + Kernel for :class:`~struphy.pic.accumulation.particles_to_grid.AccumulatorVector` into V0 with the filling + + .. math:: + + B_p^\mu = \mu \frac{w_p}{N} \,. + """ + + markers = args_markers.markers + Np = args_markers.Np + + # -- removed omp: #$ omp parallel private (ip, eta1, eta2, eta3, filling) + # -- removed omp: #$ omp for reduction ( + :vec) + for ip in range(shape(markers)[0]): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + # marker positions + eta1 = markers[ip, 0] + eta2 = markers[ip, 1] + eta3 = markers[ip, 2] + + # marker weight and magnetic moment + weight = markers[ip, 5] + mu = markers[ip, 9] + + # filling =mu*w_p/N + filling = mu * weight / Np * scale + + particle_to_mat_kernels.vec_fill_b_v0(args_derham, eta1, eta2, eta3, vec, filling) + + @stack_array("dfm", "df_inv", "df_inv_t", "g_inv", "tmp1", "tmp2", "b", "b_prod", "bstar", "norm_b1", "curl_norm_b") def cc_lin_mhd_5d_D( args_markers: "MarkerArguments", @@ -75,22 +115,19 @@ def cc_lin_mhd_5d_D( mat12: "float[:,:,:,:,:,:]", mat13: "float[:,:,:,:,:,:]", mat23: "float[:,:,:,:,:,:]", - epsilon: float, # model specific argument - b2_1: "float[:,:,:]", # model specific argument - b2_2: "float[:,:,:]", # model specific argument - b2_3: "float[:,:,:]", # model specific argument - # model specific argument + epsilon: float, + ep_scale: "float", + b2_1: "float[:,:,:]", + b2_2: "float[:,:,:]", + b2_3: "float[:,:,:]", norm_b11: "float[:,:,:]", norm_b12: "float[:,:,:]", norm_b13: "float[:,:,:]", - # model specific argument curl_norm_b1: "float[:,:,:]", curl_norm_b2: "float[:,:,:]", curl_norm_b3: "float[:,:,:]", basis_u: "int", - scale_mat: "float", - boundary_cut: float, -): # model specific argument +): r"""Accumulation kernel for the propagator :class:`~struphy.propagators.propagators_fields.CurrentCoupling5DDensity`. Accumulates :math:`\alpha`-form matrix with the filling functions (:math:`\alpha = 2`) @@ -157,9 +194,6 @@ def cc_lin_mhd_5d_D( v = markers[ip, 3] - if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: - continue - # b-field evaluation span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) @@ -186,11 +220,9 @@ def cc_lin_mhd_5d_D( # calculate Bstar and transform to H1vec b_star[:] = b + epsilon * v * curl_norm_b - b_star /= det_df # calculate b_para and b_star_para b_para = linalg_kernels.scalar_dot(norm_b1, b) - b_para /= det_df b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) @@ -202,13 +234,22 @@ def cc_lin_mhd_5d_D( if basis_u == 0: # filling functions - filling_m12 = -weight * density_const * b_prod[0, 1] * scale_mat - filling_m13 = -weight * density_const * b_prod[0, 2] * scale_mat - filling_m23 = -weight * density_const * b_prod[1, 2] * scale_mat + filling_m12 = -weight * density_const * b_prod[0, 1] * ep_scale / epsilon + filling_m13 = -weight * density_const * b_prod[0, 2] * ep_scale / epsilon + filling_m23 = -weight * density_const * b_prod[1, 2] * ep_scale / epsilon # call the appropriate matvec filler particle_to_mat_kernels.mat_fill_v0vec_asym( - args_derham, span1, span2, span3, mat12, mat13, mat23, filling_m12, filling_m13, filling_m23 + args_derham, + span1, + span2, + span3, + mat12, + mat13, + mat23, + filling_m12, + filling_m13, + filling_m23, ) elif basis_u == 1: @@ -219,24 +260,42 @@ def cc_lin_mhd_5d_D( linalg_kernels.matrix_matrix(g_inv, b_prod, tmp1) linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) - filling_m12 = -weight * density_const * tmp2[0, 1] * scale_mat - filling_m13 = -weight * density_const * tmp2[0, 2] * scale_mat - filling_m23 = -weight * density_const * tmp2[1, 2] * scale_mat + filling_m12 = -weight * density_const * tmp2[0, 1] * ep_scale / epsilon + filling_m13 = -weight * density_const * tmp2[0, 2] * ep_scale / epsilon + filling_m23 = -weight * density_const * tmp2[1, 2] * ep_scale / epsilon # call the appropriate matvec filler particle_to_mat_kernels.mat_fill_v1_asym( - args_derham, span1, span2, span3, mat12, mat13, mat23, filling_m12, filling_m13, filling_m23 + args_derham, + span1, + span2, + span3, + mat12, + mat13, + mat23, + filling_m12, + filling_m13, + filling_m23, ) elif basis_u == 2: # filling functions - filling_m12 = -weight * density_const * b_prod[0, 1] * scale_mat / det_df**2 - filling_m13 = -weight * density_const * b_prod[0, 2] * scale_mat / det_df**2 - filling_m23 = -weight * density_const * b_prod[1, 2] * scale_mat / det_df**2 + filling_m12 = -weight * density_const * b_prod[0, 1] * ep_scale / epsilon / det_df**2 + filling_m13 = -weight * density_const * b_prod[0, 2] * ep_scale / epsilon / det_df**2 + filling_m23 = -weight * density_const * b_prod[1, 2] * ep_scale / epsilon / det_df**2 # call the appropriate matvec filler particle_to_mat_kernels.mat_fill_v2_asym( - args_derham, span1, span2, span3, mat12, mat13, mat23, filling_m12, filling_m13, filling_m23 + args_derham, + span1, + span2, + span3, + mat12, + mat13, + mat23, + filling_m12, + filling_m13, + filling_m23, ) # -- removed omp: #$ omp end parallel @@ -248,23 +307,23 @@ def cc_lin_mhd_5d_D( @stack_array( "dfm", - "df_inv_t", "df_inv", + "df_inv_t", "g_inv", "filling_m", "filling_v", "tmp", "tmp1", - "tmp2", "tmp_m", "tmp_v", "b", + "bfull_star", "b_prod", - "b_prod_negb_star", + "b_prod_neg", "norm_b1", "curl_norm_b", ) -def cc_lin_mhd_5d_J1( +def cc_lin_mhd_5d_curlb( args_markers: "MarkerArguments", args_derham: "DerhamArguments", args_domain: "DomainArguments", @@ -277,21 +336,19 @@ def cc_lin_mhd_5d_J1( vec1: "float[:,:,:]", vec2: "float[:,:,:]", vec3: "float[:,:,:]", - epsilon: float, # model specific argument - b1: "float[:,:,:]", # model specific argument - b2: "float[:,:,:]", # model specific argument - b3: "float[:,:,:]", # model specific argument - norm_b11: "float[:,:,:]", # model specific argument - norm_b12: "float[:,:,:]", # model specific argument - norm_b13: "float[:,:,:]", # model specific argument - curl_norm_b1: "float[:,:,:]", # model specific argument - curl_norm_b2: "float[:,:,:]", # model specific argument - curl_norm_b3: "float[:,:,:]", # model specific argument - basis_u: "int", # model specific argument - scale_mat: "float", # model specific argument - scale_vec: "float", # model specific argument - boundary_cut: "float", -): # model specific argument + epsilon: float, + ep_scale: float, + b1: "float[:,:,:]", + b2: "float[:,:,:]", + b3: "float[:,:,:]", + norm_b11: "float[:,:,:]", + norm_b12: "float[:,:,:]", + norm_b13: "float[:,:,:]", + curl_norm_b1: "float[:,:,:]", + curl_norm_b2: "float[:,:,:]", + curl_norm_b3: "float[:,:,:]", + basis_u: "int", +): r"""Accumulation kernel for the propagator :class:`~struphy.propagators.propagators_coupling.CurrentCoupling5DCurlb`. Accumulates :math:`\alpha`-form matrix and vector with the filling functions (:math:`\alpha = 2`) @@ -303,21 +360,6 @@ def cc_lin_mhd_5d_J1( B_p^\mu &= w_p \left( \frac{v^2_{\parallel,p}}{g\hat B^*_\parallel} \mathbf B^2_{\times} \right)_\mu \,, where :math:`\mathbf B^2_{\times} \mathbf a := \hat{\mathbf B}^2 \times \mathbf a` for :math:`a \in \mathbb R^3`. - - Parameters - ---------- - b1, b2, b3 : array[float] - FE coefficients c_ijk of the magnetic field as a 2-form. - - norm_b11, norm_b12, norm_b13 : array[float] - FE coefficients c_ijk of the normalized magnetic field as a 1-form. - - curl_norm_b1, curl_norm_b2, curl_norm_b3 : array[float] - FE coefficients c_ijk of the curl of normalized magnetic field as a 2-form. - - Note - ---- - The above parameter list contains only the model specific input arguments. """ markers = args_markers.markers @@ -325,7 +367,7 @@ def cc_lin_mhd_5d_J1( # allocate for magnetic field evaluation b = empty(3, dtype=float) - b_star = empty(3, dtype=float) + bfull_star = empty(3, dtype=float) b_prod = zeros((3, 3), dtype=float) b_prod_neg = zeros((3, 3), dtype=float) norm_b1 = empty(3, dtype=float) @@ -338,12 +380,11 @@ def cc_lin_mhd_5d_J1( g_inv = empty((3, 3), dtype=float) # allocate for filling - filling_m = empty((3, 3), dtype=float) - filling_v = empty(3, dtype=float) + filling_m = zeros((3, 3), dtype=float) + filling_v = zeros(3, dtype=float) tmp = empty((3, 3), dtype=float) tmp1 = empty((3, 3), dtype=float) - tmp2 = empty((3, 3), dtype=float) tmp_m = empty((3, 3), dtype=float) tmp_v = empty(3, dtype=float) @@ -351,8 +392,6 @@ def cc_lin_mhd_5d_J1( # get number of markers n_markers_loc = shape(markers)[0] - # -- removed omp: #$ omp parallel firstprivate(b_prod) private(ip, boundary_cut, eta1, eta2, eta3, v, weight, span1, span2, span3, b1, b2, b3, b, b_star, b_prod_neg, norm_b1, curl_norm_b, abs_b_star_para, dfm, df_inv, df_inv_t, g_inv, det_df, tmp, tmp1, tmp2, tmp_m, tmp_v, filling_m, filling_v) - # -- removed omp: #$ omp for reduction ( + : mat11, mat12, mat13, mat22, mat23, mat33, vec1, vec2, vec3) for ip in range(n_markers_loc): # only do something if particle is a "true" particle (i.e. not a hole) if markers[ip, 0] == -1.0: @@ -367,9 +406,6 @@ def cc_lin_mhd_5d_J1( weight = markers[ip, 5] v = markers[ip, 3] - if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: - continue - # b-field evaluation span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) @@ -387,11 +423,11 @@ def cc_lin_mhd_5d_J1( # curl_norm_b; 2form eval_2form_spline_mpi(span1, span2, span3, args_derham, curl_norm_b1, curl_norm_b2, curl_norm_b3, curl_norm_b) - # b_star; 2form in H1vec - b_star[:] = (b + curl_norm_b * v * epsilon) / det_df + # b_star; 2form + bfull_star[:] = b + curl_norm_b * v * epsilon # calculate abs_b_star_para - abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) + abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, bfull_star) # calculate tensor product of two curl_norm_b linalg_kernels.outer(curl_norm_b, curl_norm_b, tmp) @@ -411,8 +447,8 @@ def cc_lin_mhd_5d_J1( linalg_kernels.matrix_matrix(tmp1, b_prod_neg, tmp_m) linalg_kernels.matrix_vector(b_prod, curl_norm_b, tmp_v) - filling_m[:, :] = weight * tmp_m * v**2 / abs_b_star_para**2 / det_df**2 * scale_mat - filling_v[:] = weight * tmp_v * v**2 / abs_b_star_para / det_df * scale_vec + filling_m[:, :] += weight * tmp_m * v**2 / abs_b_star_para**2 * ep_scale + filling_v[:] += weight * tmp_v * v**2 / abs_b_star_para * ep_scale # call the appropriate matvec filler particle_to_mat_kernels.m_v_fill_v0vec_symm( @@ -440,54 +476,13 @@ def cc_lin_mhd_5d_J1( filling_v[2], ) - elif basis_u == 1: - # needed metric coefficients - linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) - linalg_kernels.transpose(df_inv, df_inv_t) - linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) - linalg_kernels.matrix_matrix(g_inv, b_prod, tmp1) - linalg_kernels.matrix_vector(tmp1, curl_norm_b, tmp_v) - - linalg_kernels.matrix_matrix(tmp1, tmp, tmp2) - linalg_kernels.matrix_matrix(tmp2, b_prod_neg, tmp1) - linalg_kernels.matrix_matrix(tmp1, g_inv, tmp_m) - - filling_m[:, :] = weight * tmp_m * v**2 / abs_b_star_para**2 / det_df**2 * scale_mat - filling_v[:] = weight * tmp_v * v**2 / abs_b_star_para / det_df * scale_vec - - # call the appropriate matvec filler - particle_to_mat_kernels.m_v_fill_v1_symm( - args_derham, - span1, - span2, - span3, - mat11, - mat12, - mat13, - mat22, - mat23, - mat33, - filling_m[0, 0], - filling_m[0, 1], - filling_m[0, 2], - filling_m[1, 1], - filling_m[1, 2], - filling_m[2, 2], - vec1, - vec2, - vec3, - filling_v[0], - filling_v[1], - filling_v[2], - ) - elif basis_u == 2: linalg_kernels.matrix_matrix(b_prod, tmp, tmp1) linalg_kernels.matrix_matrix(tmp1, b_prod_neg, tmp_m) linalg_kernels.matrix_vector(b_prod, curl_norm_b, tmp_v) - filling_m[:, :] = weight * tmp_m * v**2 / abs_b_star_para**2 / det_df**4 * scale_mat - filling_v[:] = weight * tmp_v * v**2 / abs_b_star_para / det_df**2 * scale_vec + filling_m[:, :] = weight * tmp_m * v**2 / abs_b_star_para**2 / det_df**2 * ep_scale + filling_v[:] = weight * tmp_v * v**2 / abs_b_star_para / det_df * ep_scale # call the appropriate matvec filler particle_to_mat_kernels.m_v_fill_v2_symm( @@ -526,8 +521,6 @@ def cc_lin_mhd_5d_J1( vec2 /= Np vec3 /= Np - # -- removed omp: #$ omp end parallel - @stack_array("dfm", "norm_b1", "filling_v") def cc_lin_mhd_5d_M( @@ -547,8 +540,7 @@ def cc_lin_mhd_5d_M( norm_b12: "float[:,:,:]", # model specific argument norm_b13: "float[:,:,:]", # model specific argument scale_vec: "float", # model specific argument - boundary_cut: "float", -): # model specific argument +): r"""Accumulation kernel for the propagator :class:`~struphy.propagators.propagators_fields.ShearAlfvenCurrentCoupling5D` and :class:`~struphy.propagators.propagators_fields.MagnetosonicCurrentCoupling5D`. Accumulates 2-form vector with the filling functions: @@ -600,9 +592,6 @@ def cc_lin_mhd_5d_M( weight = markers[ip, 5] mu = markers[ip, 9] - if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: - continue - # b-field evaluation span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) @@ -617,7 +606,16 @@ def cc_lin_mhd_5d_M( filling_v[:] = weight * mu / det_df * scale_vec * norm_b1 particle_to_mat_kernels.vec_fill_v2( - args_derham, span1, span2, span3, vec1, vec2, vec3, filling_v[0], filling_v[1], filling_v[2] + args_derham, + span1, + span2, + span3, + vec1, + vec2, + vec3, + filling_v[0], + filling_v[1], + filling_v[2], ) vec1 /= Np @@ -633,19 +631,17 @@ def cc_lin_mhd_5d_M( "df_inv", "g_inv", "filling_v", - "tmp1", - "tmp2", + "tmp", "tmp_v", "b", "b_prod", - "norm_b2_prod", + "norm_b_prod", "b_star", "curl_norm_b", "norm_b1", - "norm_b2", "grad_PB", ) -def cc_lin_mhd_5d_J2( +def cc_lin_mhd_5d_gradB( args_markers: "MarkerArguments", args_derham: "DerhamArguments", args_domain: "DomainArguments", @@ -658,27 +654,22 @@ def cc_lin_mhd_5d_J2( vec1: "float[:,:,:]", vec2: "float[:,:,:]", vec3: "float[:,:,:]", - epsilon: float, # model specific argument - b1: "float[:,:,:]", # model specific argument - b2: "float[:,:,:]", # model specific argument - b3: "float[:,:,:]", # model specific argument - norm_b11: "float[:,:,:]", # model specific argument - norm_b12: "float[:,:,:]", # model specific argument - norm_b13: "float[:,:,:]", # model specific argument - norm_b21: "float[:,:,:]", # model specific argument - norm_b22: "float[:,:,:]", # model specific argument - norm_b23: "float[:,:,:]", # model specific argument - curl_norm_b1: "float[:,:,:]", # model specific argument - curl_norm_b2: "float[:,:,:]", # model specific argument - curl_norm_b3: "float[:,:,:]", # model specific argument - grad_PB1: "float[:,:,:]", # model specific argument - grad_PB2: "float[:,:,:]", # model specific argument - grad_PB3: "float[:,:,:]", # model specific argument + epsilon: float, + ep_scale: float, + b1: "float[:,:,:]", + b2: "float[:,:,:]", + b3: "float[:,:,:]", + norm_b11: "float[:,:,:]", + norm_b12: "float[:,:,:]", + norm_b13: "float[:,:,:]", + curl_norm_b1: "float[:,:,:]", + curl_norm_b2: "float[:,:,:]", + curl_norm_b3: "float[:,:,:]", + grad_PB1: "float[:,:,:]", + grad_PB2: "float[:,:,:]", + grad_PB3: "float[:,:,:]", basis_u: "int", - scale_mat: "float", - scale_vec: "float", - boundary_cut: float, -): # model specific argument +): r"""Accumulation kernel for the propagator :class:`~struphy.propagators.propagators_coupling.CurrentCoupling5DGradB`. Accumulates math:`\alpha` -form vector with the filling functions @@ -697,9 +688,6 @@ def cc_lin_mhd_5d_J2( norm_b11, norm_b12, norm_b13 : array[float] FE coefficients c_ijk of the normalized magnetic field as a 1-form. - norm_b21, norm_b22, norm_b23 : array[float] - FE coefficients c_ijk of the normalized magnetic field as a 2-form. - curl_norm_b1, curl_norm_b2, curl_norm_b3 : array[float] FE coefficients c_ijk of the curl of normalized magnetic field as a 2-form. @@ -718,10 +706,9 @@ def cc_lin_mhd_5d_J2( b = empty(3, dtype=float) b_star = empty(3, dtype=float) b_prod = zeros((3, 3), dtype=float) - norm_b2_prod = zeros((3, 3), dtype=float) + norm_b_prod = zeros((3, 3), dtype=float) curl_norm_b = empty(3, dtype=float) norm_b1 = empty(3, dtype=float) - norm_b2 = empty(3, dtype=float) grad_PB = empty(3, dtype=float) # allocate for metric coeffs @@ -732,17 +719,13 @@ def cc_lin_mhd_5d_J2( # allocate for filling filling_v = empty(3, dtype=float) - - tmp1 = empty((3, 3), dtype=float) - tmp2 = empty((3, 3), dtype=float) + tmp = empty((3, 3), dtype=float) tmp_v = empty(3, dtype=float) # get number of markers n_markers_loc = shape(markers)[0] - # -- removed omp: #$ omp parallel firstprivate(b_prod) private(ip, boundary_cut, eta1, eta2, eta3, v, mu, weight, span1, span2, span3, b1, b2, b3, b, b_star, norm_b1, norm_b2, norm_b2_prod, curl_norm_b, grad_PB, abs_b_star_para, dfm, df_inv, df_inv_t, g_inv, det_df, tmp1, tmp2, tmp_v, filling_v) - # -- removed omp: #$ omp for reduction ( + : mat11, mat12, mat13, mat22, mat23, mat33, vec1, vec2, vec3) for ip in range(n_markers_loc): # only do something if particle is a "true" particle (i.e. not a hole) if markers[ip, 0] == -1.0: @@ -753,9 +736,191 @@ def cc_lin_mhd_5d_J2( eta2 = markers[ip, 1] eta3 = markers[ip, 2] - if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: + # marker weight and velocity + weight = markers[ip, 5] + v = markers[ip, 3] + mu = markers[ip, 9] + + # b-field evaluation + span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) + + # evaluate Jacobian, result in dfm + evaluation_kernels.df(eta1, eta2, eta3, args_domain, dfm) + + det_df = linalg_kernels.det(dfm) + + # needed metric coefficients + linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) + linalg_kernels.transpose(df_inv, df_inv_t) + linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) + + # b; 2form + eval_2form_spline_mpi(span1, span2, span3, args_derham, b1, b2, b3, b) + + # norm_b1; 1form + eval_1form_spline_mpi(span1, span2, span3, args_derham, norm_b11, norm_b12, norm_b13, norm_b1) + + # curl_norm_b; 2form + eval_2form_spline_mpi(span1, span2, span3, args_derham, curl_norm_b1, curl_norm_b2, curl_norm_b3, curl_norm_b) + + # grad_PB; 1form + eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PB1, grad_PB2, grad_PB3, grad_PB) + + # b_star; 2form transformed into H1vec + b_star[:] = b + curl_norm_b * v * epsilon + + # calculate abs_b_star_para + abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) + + # operator bx() as matrix + b_prod[0, 1] = -b[2] + b_prod[0, 2] = +b[1] + b_prod[1, 0] = +b[2] + b_prod[1, 2] = -b[0] + b_prod[2, 0] = -b[1] + b_prod[2, 1] = +b[0] + + norm_b_prod[0, 1] = -norm_b1[2] + norm_b_prod[0, 2] = +norm_b1[1] + norm_b_prod[1, 0] = +norm_b1[2] + norm_b_prod[1, 2] = -norm_b1[0] + norm_b_prod[2, 0] = -norm_b1[1] + norm_b_prod[2, 1] = +norm_b1[0] + + if basis_u == 0: + linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + + filling_v[:] = weight * tmp_v * mu / abs_b_star_para * ep_scale + + # call the appropriate matvec filler + particle_to_mat_kernels.vec_fill_v0vec( + args_derham, + span1, + span2, + span3, + vec1, + vec2, + vec3, + filling_v[0], + filling_v[1], + filling_v[2], + ) + + elif basis_u == 2: + linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + + filling_v[:] = weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale + + # call the appropriate matvec filler + particle_to_mat_kernels.vec_fill_v2( + args_derham, + span1, + span2, + span3, + vec1, + vec2, + vec3, + filling_v[0], + filling_v[1], + filling_v[2], + ) + vec1 /= Np + vec2 /= Np + vec3 /= Np + + +@stack_array( + "dfm", + "df_inv_t", + "df_inv", + "g_inv", + "filling_v", + "tmp", + "tmp_v", + "b", + "b_prod", + "beq", + "beq_prod", + "norm_b_prod", + "bfull_star", + "curl_norm_b", + "norm_b1", + "grad_PB", + "grad_PBeq", +) +def cc_lin_mhd_5d_gradB_dg_init( + args_markers: "MarkerArguments", + args_derham: "DerhamArguments", + args_domain: "DomainArguments", + vec1: "float[:,:,:]", + vec2: "float[:,:,:]", + vec3: "float[:,:,:]", + epsilon: float, + ep_scale: float, + b1: "float[:,:,:]", + b2: "float[:,:,:]", + b3: "float[:,:,:]", + beq1: "float[:,:,:]", + beq2: "float[:,:,:]", + beq3: "float[:,:,:]", + norm_b11: "float[:,:,:]", + norm_b12: "float[:,:,:]", + norm_b13: "float[:,:,:]", + curl_norm_b1: "float[:,:,:]", + curl_norm_b2: "float[:,:,:]", + curl_norm_b3: "float[:,:,:]", + grad_PB1: "float[:,:,:]", + grad_PB2: "float[:,:,:]", + grad_PB3: "float[:,:,:]", + grad_PBeq1: "float[:,:,:]", + grad_PBeq2: "float[:,:,:]", + grad_PBeq3: "float[:,:,:]", + basis_u: "int", +): + r"""TODO""" + + markers = args_markers.markers + Np = args_markers.Np + + # allocate for magnetic field evaluation + b = empty(3, dtype=float) + beq = empty(3, dtype=float) + bfull_star = empty(3, dtype=float) + b_prod = zeros((3, 3), dtype=float) + beq_prod = zeros((3, 3), dtype=float) + norm_b_prod = zeros((3, 3), dtype=float) + curl_norm_b = empty(3, dtype=float) + norm_b1 = empty(3, dtype=float) + grad_PB = empty(3, dtype=float) + grad_PBeq = empty(3, dtype=float) + + # allocate for metric coeffs + dfm = empty((3, 3), dtype=float) + df_inv = empty((3, 3), dtype=float) + df_inv_t = empty((3, 3), dtype=float) + g_inv = empty((3, 3), dtype=float) + + # allocate for filling + filling_v = empty(3, dtype=float) + tmp = empty((3, 3), dtype=float) + + tmp_v = empty(3, dtype=float) + + # get number of markers + n_markers_loc = shape(markers)[0] + + for ip in range(n_markers_loc): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: continue + # marker positions + eta1 = markers[ip, 0] + eta2 = markers[ip, 1] + eta3 = markers[ip, 2] + # marker weight and velocity weight = markers[ip, 5] v = markers[ip, 3] @@ -777,23 +942,26 @@ def cc_lin_mhd_5d_J2( # b; 2form eval_2form_spline_mpi(span1, span2, span3, args_derham, b1, b2, b3, b) + # beq; 2form + eval_2form_spline_mpi(span1, span2, span3, args_derham, beq1, beq2, beq3, beq) + # norm_b1; 1form eval_1form_spline_mpi(span1, span2, span3, args_derham, norm_b11, norm_b12, norm_b13, norm_b1) - # norm_b2; 2form - eval_2form_spline_mpi(span1, span2, span3, args_derham, norm_b21, norm_b22, norm_b23, norm_b2) - # curl_norm_b; 2form eval_2form_spline_mpi(span1, span2, span3, args_derham, curl_norm_b1, curl_norm_b2, curl_norm_b3, curl_norm_b) # grad_PB; 1form eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PB1, grad_PB2, grad_PB3, grad_PB) + # grad_PBeq; 1form + eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PBeq1, grad_PBeq2, grad_PBeq3, grad_PBeq) + # b_star; 2form transformed into H1vec - b_star[:] = (b + curl_norm_b * v * epsilon) / det_df + bfull_star[:] = b + beq + curl_norm_b * v * epsilon # calculate abs_b_star_para - abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) + abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, bfull_star) # operator bx() as matrix b_prod[0, 1] = -b[2] @@ -803,58 +971,346 @@ def cc_lin_mhd_5d_J2( b_prod[2, 0] = -b[1] b_prod[2, 1] = +b[0] - norm_b2_prod[0, 1] = -norm_b2[2] - norm_b2_prod[0, 2] = +norm_b2[1] - norm_b2_prod[1, 0] = +norm_b2[2] - norm_b2_prod[1, 2] = -norm_b2[0] - norm_b2_prod[2, 0] = -norm_b2[1] - norm_b2_prod[2, 1] = +norm_b2[0] + beq_prod[0, 1] = -beq[2] + beq_prod[0, 2] = +beq[1] + beq_prod[1, 0] = +beq[2] + beq_prod[1, 2] = -beq[0] + beq_prod[2, 0] = -beq[1] + beq_prod[2, 1] = +beq[0] + + norm_b_prod[0, 1] = -norm_b1[2] + norm_b_prod[0, 2] = +norm_b1[1] + norm_b_prod[1, 0] = +norm_b1[2] + norm_b_prod[1, 2] = -norm_b1[0] + norm_b_prod[2, 0] = -norm_b1[1] + norm_b_prod[2, 1] = +norm_b1[0] if basis_u == 0: - linalg_kernels.matrix_matrix(b_prod, g_inv, tmp1) - linalg_kernels.matrix_matrix(tmp1, norm_b2_prod, tmp2) - linalg_kernels.matrix_matrix(tmp2, g_inv, tmp1) + # beq contribution + linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - linalg_kernels.matrix_vector(tmp1, grad_PB, tmp_v) + filling_v[:] = weight * tmp_v * mu / abs_b_star_para * ep_scale - filling_v[:] = weight * tmp_v * mu / abs_b_star_para * scale_vec + # b contribution + linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + + filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale + + linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) + + filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale + + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + + filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale # call the appropriate matvec filler particle_to_mat_kernels.vec_fill_v0vec( - args_derham, span1, span2, span3, vec1, vec2, vec3, filling_v[0], filling_v[1], filling_v[2] + args_derham, + span1, + span2, + span3, + vec1, + vec2, + vec3, + filling_v[0], + filling_v[1], + filling_v[2], ) - elif basis_u == 1: - linalg_kernels.matrix_matrix(g_inv, b_prod, tmp1) - linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) - linalg_kernels.matrix_matrix(tmp2, norm_b2_prod, tmp1) - linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) + elif basis_u == 2: + # beq contribution + linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) + + filling_v[:] = weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale + + # b contribution + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + + filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - linalg_kernels.matrix_vector(tmp2, grad_PB, tmp_v) + linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - filling_v[:] = weight * tmp_v * mu / abs_b_star_para * scale_vec + filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale + + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + + filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale # call the appropriate matvec filler - particle_to_mat_kernels.vec_fill_v1( - args_derham, span1, span2, span3, vec1, vec2, vec3, filling_v[0], filling_v[1], filling_v[2] + particle_to_mat_kernels.vec_fill_v2( + args_derham, + span1, + span2, + span3, + vec1, + vec2, + vec3, + filling_v[0], + filling_v[1], + filling_v[2], + ) + + vec1 /= Np + vec2 /= Np + vec3 /= Np + + +@stack_array( + "dfm", + "df_inv_t", + "df_inv", + "g_inv", + "filling_v", + "tmp", + "tmp_v", + "b", + "b_prod", + "eta_diff", + "beq", + "beq_prod", + "norm_b_prod", + "bfull_star", + "curl_norm_b", + "norm_b1", + "grad_PB", + "grad_PBeq", + "eta_mid", + "eta_diff", +) +def cc_lin_mhd_5d_gradB_dg( + args_markers: "MarkerArguments", + args_derham: "DerhamArguments", + args_domain: "DomainArguments", + vec1: "float[:,:,:]", + vec2: "float[:,:,:]", + vec3: "float[:,:,:]", + epsilon: float, + ep_scale: float, + b1: "float[:,:,:]", + b2: "float[:,:,:]", + b3: "float[:,:,:]", + beq1: "float[:,:,:]", + beq2: "float[:,:,:]", + beq3: "float[:,:,:]", + norm_b11: "float[:,:,:]", + norm_b12: "float[:,:,:]", + norm_b13: "float[:,:,:]", + curl_norm_b1: "float[:,:,:]", + curl_norm_b2: "float[:,:,:]", + curl_norm_b3: "float[:,:,:]", + grad_PB1: "float[:,:,:]", + grad_PB2: "float[:,:,:]", + grad_PB3: "float[:,:,:]", + grad_PBeq1: "float[:,:,:]", + grad_PBeq2: "float[:,:,:]", + grad_PBeq3: "float[:,:,:]", + basis_u: "int", + const: "float", +): + r"""TODO""" + + markers = args_markers.markers + Np = args_markers.Np + + # allocate for magnetic field evaluation + eta_diff = empty(3, dtype=float) + eta_mid = empty(3, dtype=float) + b = empty(3, dtype=float) + beq = empty(3, dtype=float) + bfull_star = empty(3, dtype=float) + b_prod = zeros((3, 3), dtype=float) + beq_prod = zeros((3, 3), dtype=float) + norm_b_prod = zeros((3, 3), dtype=float) + curl_norm_b = empty(3, dtype=float) + norm_b1 = empty(3, dtype=float) + grad_PB = empty(3, dtype=float) + grad_PBeq = empty(3, dtype=float) + + # allocate for metric coeffs + dfm = empty((3, 3), dtype=float) + df_inv = empty((3, 3), dtype=float) + df_inv_t = empty((3, 3), dtype=float) + g_inv = empty((3, 3), dtype=float) + + # allocate for filling + filling_v = empty(3, dtype=float) + tmp = empty((3, 3), dtype=float) + + tmp_v = empty(3, dtype=float) + + # get number of markers + n_markers_loc = shape(markers)[0] + + for ip in range(n_markers_loc): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + # marker positions, mid point + eta_mid[:] = (markers[ip, 0:3] + markers[ip, 11:14]) / 2.0 + eta_mid[:] = mod(eta_mid[:], 1.0) + + eta_diff[:] = markers[ip, 0:3] - markers[ip, 11:14] + + # marker weight and velocity + weight = markers[ip, 5] + v = markers[ip, 3] + mu = markers[ip, 9] + + # b-field evaluation + span1, span2, span3 = get_spans(eta_mid[0], eta_mid[1], eta_mid[2], args_derham) + + # evaluate Jacobian, result in dfm + evaluation_kernels.df(eta_mid[0], eta_mid[1], eta_mid[2], args_domain, dfm) + + det_df = linalg_kernels.det(dfm) + + # needed metric coefficients + linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) + linalg_kernels.transpose(df_inv, df_inv_t) + linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) + + # b; 2form + eval_2form_spline_mpi(span1, span2, span3, args_derham, b1, b2, b3, b) + + # beq; 2form + eval_2form_spline_mpi(span1, span2, span3, args_derham, beq1, beq2, beq3, beq) + + # norm_b1; 1form + eval_1form_spline_mpi(span1, span2, span3, args_derham, norm_b11, norm_b12, norm_b13, norm_b1) + + # curl_norm_b; 2form + eval_2form_spline_mpi(span1, span2, span3, args_derham, curl_norm_b1, curl_norm_b2, curl_norm_b3, curl_norm_b) + + # grad_PB; 1form + eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PB1, grad_PB2, grad_PB3, grad_PB) + + # grad_PBeq; 1form + eval_1form_spline_mpi(span1, span2, span3, args_derham, grad_PBeq1, grad_PBeq2, grad_PBeq3, grad_PBeq) + + # b_star; 2form transformed into H1vec + bfull_star[:] = b + beq + curl_norm_b * v * epsilon + + # calculate abs_b_star_para + abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, bfull_star) + + # operator bx() as matrix + b_prod[0, 1] = -b[2] + b_prod[0, 2] = +b[1] + b_prod[1, 0] = +b[2] + b_prod[1, 2] = -b[0] + b_prod[2, 0] = -b[1] + b_prod[2, 1] = +b[0] + + beq_prod[0, 1] = -beq[2] + beq_prod[0, 2] = +beq[1] + beq_prod[1, 0] = +beq[2] + beq_prod[1, 2] = -beq[0] + beq_prod[2, 0] = -beq[1] + beq_prod[2, 1] = +beq[0] + + norm_b_prod[0, 1] = -norm_b1[2] + norm_b_prod[0, 2] = +norm_b1[1] + norm_b_prod[1, 0] = +norm_b1[2] + norm_b_prod[1, 2] = -norm_b1[0] + norm_b_prod[2, 0] = -norm_b1[1] + norm_b_prod[2, 1] = +norm_b1[0] + + if basis_u == 0: + # beq * gradPBeq contribution + linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) + + filling_v[:] = weight * tmp_v * mu / abs_b_star_para * ep_scale + + # beq * gradPB contribution + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale + + # beq * dg term contribution + linalg_kernels.matrix_vector(tmp, eta_diff, tmp_v) + filling_v[:] += tmp_v / abs_b_star_para * const + + # b * gradPBeq contribution + linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) + filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale + + # b * gradPB contribution + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + filling_v[:] += weight * tmp_v * mu / abs_b_star_para * ep_scale + + # b * dg term contribution + linalg_kernels.matrix_vector(tmp, eta_diff, tmp_v) + filling_v[:] += tmp_v / abs_b_star_para * const + + # call the appropriate matvec filler + particle_to_mat_kernels.vec_fill_v0vec( + args_derham, + span1, + span2, + span3, + vec1, + vec2, + vec3, + filling_v[0], + filling_v[1], + filling_v[2], ) elif basis_u == 2: - linalg_kernels.matrix_matrix(b_prod, g_inv, tmp1) - linalg_kernels.matrix_matrix(tmp1, norm_b2_prod, tmp2) - linalg_kernels.matrix_matrix(tmp2, g_inv, tmp1) + # beq * gradPBeq contribution + linalg_kernels.matrix_matrix(beq_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) + + filling_v[:] = weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale + + # beq * gradPB contribution + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + + filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale + + # beq * dg term contribution + linalg_kernels.matrix_vector(tmp, eta_diff, tmp_v) + + filling_v[:] += tmp_v / abs_b_star_para / det_df * const + + # b * gradPBeq contribtuion + linalg_kernels.matrix_matrix(b_prod, norm_b_prod, tmp) + linalg_kernels.matrix_vector(tmp, grad_PBeq, tmp_v) - linalg_kernels.matrix_vector(tmp1, grad_PB, tmp_v) + filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale - filling_v[:] = weight * tmp_v * mu / abs_b_star_para / det_df * scale_vec + # b * gradPB contribution + linalg_kernels.matrix_vector(tmp, grad_PB, tmp_v) + + filling_v[:] += weight * tmp_v * mu / abs_b_star_para / det_df * ep_scale + + # b * dg term contribution + linalg_kernels.matrix_vector(tmp, eta_diff, tmp_v) + + filling_v[:] += tmp_v / abs_b_star_para / det_df * const # call the appropriate matvec filler particle_to_mat_kernels.vec_fill_v2( - args_derham, span1, span2, span3, vec1, vec2, vec3, filling_v[0], filling_v[1], filling_v[2] + args_derham, + span1, + span2, + span3, + vec1, + vec2, + vec3, + filling_v[0], + filling_v[1], + filling_v[2], ) vec1 /= Np vec2 /= Np vec3 /= Np - - # -- removed omp: #$ omp end parallel diff --git a/src/struphy/pic/accumulation/filter_kernels.py b/src/struphy/pic/accumulation/filter_kernels.py index e24c7ad5d..a6c498ca8 100644 --- a/src/struphy/pic/accumulation/filter_kernels.py +++ b/src/struphy/pic/accumulation/filter_kernels.py @@ -5,8 +5,10 @@ @stack_array("vec_copy", "mask1d", "mask", "top", "i_bottom", "i_top", "fi", "ir") -def apply_three_point_filter( +def apply_three_point_filter_3d( vec: "float[:,:,:]", + dir: "int", + form: "int", Nel: "int[:]", spl_kind: "bool[:]", pn: "int[:]", @@ -47,6 +49,7 @@ def apply_three_point_filter( i_top = zeros(3, dtype=int) fi = empty(3, dtype=int) ir = empty(3, dtype=int) + isDspline = zeros(3, dtype=int) # copy vectors vec_copy[:, :, :] = vec[:, :, :] @@ -62,22 +65,33 @@ def apply_three_point_filter( mask[i, j, k] *= mask1d[i] * mask1d[j] * mask1d[k] # consider left and right boundary + if form == 1: + isDspline[dir] = 1 + elif form == 2: + isDspline[:] = 1 + isDspline[dir] = 0 + elif form == 3: + isDspline[:] = 1 + for i in range(3): if spl_kind[i]: top[i] = Nel[i] - 1 else: - top[i] = Nel[i] + pn[i] - 1 + if isDspline[i] == 1: + top[i] = Nel[i] + pn[i] - 2 + else: + top[i] = Nel[i] + pn[i] - 1 for i in range(3): if starts[i] == 0: if spl_kind[i]: - i_bottom[i] = -1 + i_bottom[i] = 0 else: i_bottom[i] = +1 if ends[i] == top[i]: if spl_kind[i]: - i_top[i] = +1 + i_top[i] = 0 else: i_top[i] = -1 diff --git a/src/struphy/pic/accumulation/particle_to_mat_kernels.py b/src/struphy/pic/accumulation/particle_to_mat_kernels.py index 576d4571c..bc9364f6a 100644 --- a/src/struphy/pic/accumulation/particle_to_mat_kernels.py +++ b/src/struphy/pic/accumulation/particle_to_mat_kernels.py @@ -5834,7 +5834,12 @@ def m_v_fill_v2_full( def mat_fill_b_v0( - args_derham: "DerhamArguments", eta1: float, eta2: float, eta3: float, mat: "float[:,:,:,:,:,:]", fill: float + args_derham: "DerhamArguments", + eta1: float, + eta2: float, + eta3: float, + mat: "float[:,:,:,:,:,:]", + fill: float, ): """ Adds the contribution of one particle to the elements of an accumulation matrix V0 -> V0. The result is returned in mat. @@ -5969,7 +5974,12 @@ def m_v_fill_b_v0( def mat_fill_b_v3( - args_derham: "DerhamArguments", eta1: float, eta2: float, eta3: float, mat: "float[:,:,:,:,:,:]", fill: float + args_derham: "DerhamArguments", + eta1: float, + eta2: float, + eta3: float, + mat: "float[:,:,:,:,:,:]", + fill: float, ): """ Adds the contribution of one particle to the elements of an accumulation matrix V3 -> V3. The result is returned in mat. @@ -6112,7 +6122,12 @@ def m_v_fill_b_v3( def mat_fill_v0( - args_derham: "DerhamArguments", span1: int, span2: int, span3: int, mat: "float[:,:,:,:,:,:]", fill: float + args_derham: "DerhamArguments", + span1: int, + span2: int, + span3: int, + mat: "float[:,:,:,:,:,:]", + fill: float, ): """ Adds the contribution of one particle to the elements of an accumulation matrix V0 -> V0. The result is returned in mat. @@ -6239,7 +6254,12 @@ def m_v_fill_v0( def mat_fill_v3( - args_derham: "DerhamArguments", span1: int, span2: int, span3: int, mat: "float[:,:,:,:,:,:]", fill: float + args_derham: "DerhamArguments", + span1: int, + span2: int, + span3: int, + mat: "float[:,:,:,:,:,:]", + fill: float, ): """ Adds the contribution of one particle to the elements of an accumulation block matrix V3 -> V3. The result is returned in mat. @@ -12949,7 +12969,12 @@ def vec_fill_v0vec( def vec_fill_b_v0( - args_derham: "DerhamArguments", eta1: float, eta2: float, eta3: float, vec: "float[:,:,:]", fill: float + args_derham: "DerhamArguments", + eta1: float, + eta2: float, + eta3: float, + vec: "float[:,:,:]", + fill: float, ): """TODO""" @@ -13127,7 +13152,12 @@ def vec_fill_b_v2( def vec_fill_b_v3( - args_derham: "DerhamArguments", eta1: float, eta2: float, eta3: float, vec: "float[:,:,:]", fill: float + args_derham: "DerhamArguments", + eta1: float, + eta2: float, + eta3: float, + vec: "float[:,:,:]", + fill: float, ): """TODO""" diff --git a/src/struphy/pic/accumulation/particles_to_grid.py b/src/struphy/pic/accumulation/particles_to_grid.py index 23345df23..06d67a6df 100644 --- a/src/struphy/pic/accumulation/particles_to_grid.py +++ b/src/struphy/pic/accumulation/particles_to_grid.py @@ -1,18 +1,19 @@ "Base classes for particle deposition (accumulation) on the grid." -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilMatrix, StencilVector import struphy.pic.accumulation.accum_kernels as accums import struphy.pic.accumulation.accum_kernels_gc as accums_gc -import struphy.pic.accumulation.filter_kernels as filters from struphy.feec.mass import WeightedMassOperators from struphy.feec.psydac_derham import Derham from struphy.kernel_arguments.pusher_args_kernels import DerhamArguments, DomainArguments +from struphy.pic.accumulation.filter import AccumFilter, FilterParameters from struphy.pic.base import Particles from struphy.profiling.profiling import ProfileManager +from struphy.utils.pyccel import Pyccelkernel class Accumulator: @@ -66,6 +67,7 @@ class Accumulator: filter_params : dict Params for the accumulation filter: use_filter(string, either `three_point or `fourier), repeat(int), alpha(float) and modes(list with int). + Note ---- Struphy accumulation kernels called by ``Accumulator`` objects must be added to ``struphy/pic/accumulation/accum_kernels.py`` @@ -77,29 +79,23 @@ def __init__( self, particles: Particles, space_id: str, - kernel, + kernel: Pyccelkernel, mass_ops: WeightedMassOperators, args_domain: DomainArguments, *, add_vector: bool = False, symmetry: str = None, - filter_params: dict = { - "use_filter": None, - "modes": None, - "repeat": None, - "alpha": None, - }, + filter_params: FilterParameters = None, ): self._particles = particles self._space_id = space_id + assert isinstance(kernel, Pyccelkernel), f"{kernel} is not of type Pyccelkernel" self._kernel = kernel self._derham = mass_ops.derham self._args_domain = args_domain self._symmetry = symmetry - self._filter_params = filter_params - self._form = self.derham.space_to_form[space_id] # initialize matrices (instances of WeightedMassOperator) @@ -176,6 +172,9 @@ def __init__( for bl in vec.blocks: self._args_data += (bl._data,) + # initialize filter + self._accfilter = AccumFilter(filter_params, self._derham, self._space_id) + def __call__(self, *optional_args, **args_control): """ Performs the accumulation into the matrix/vector by calling the chosen accumulation kernel and additional analytical contributions (control variate, optional). @@ -192,7 +191,7 @@ def __call__(self, *optional_args, **args_control): Entries must be pyccel-conform types. args_control : any - Keyword arguments for an analytical control variate correction in the accumulation step. Possible keywords are 'control_vec' for a vector correction or 'control_mat' for a matrix correction. Values are a 1d (vector) or 2d (matrix) list with callables or np.ndarrays used for the correction. + Keyword arguments for an analytical control variate correction in the accumulation step. Possible keywords are 'control_vec' for a vector correction or 'control_mat' for a matrix correction. Values are a 1d (vector) or 2d (matrix) list with callables or xp.ndarrays used for the correction. """ # flags for break @@ -204,7 +203,7 @@ def __call__(self, *optional_args, **args_control): dat[:] = 0.0 # accumulate into matrix (and vector) with markers - with ProfileManager.profile_region("kernel: " + self.kernel.__name__): + with ProfileManager.profile_region("kernel: " + self.kernel.name): self.kernel( self.particles.args_markers, self.derham.args_derham, @@ -214,52 +213,13 @@ def __call__(self, *optional_args, **args_control): ) # apply filter - if self.filter_params["use_filter"] is not None: + if self.accfilter.params.use_filter is not None: for vec in self._vectors: vec.exchange_assembly_data() vec.update_ghost_regions() - if self.filter_params["use_filter"] == "fourier_in_tor": - self.apply_toroidal_fourier_filter(vec, self.filter_params["modes"]) - - elif self.filter_params["use_filter"] == "three_point": - for _ in range(self.filter_params["repeat"]): - for i in range(3): - filters.apply_three_point_filter( - vec[i]._data, - np.array(self.derham.Nel), - np.array(self.derham.spl_kind), - np.array(self.derham.p), - np.array(self.derham.Vh[self.form][i].starts), - np.array(self.derham.Vh[self.form][i].ends), - alpha=self.filter_params["alpha"], - ) - - vec.update_ghost_regions() - - elif self.filter_params["use_filter"] == "hybrid": - self.apply_toroidal_fourier_filter(vec, self.filter_params["modes"]) - - for _ in range(self.filter_params["repeat"]): - for i in range(2): - filters.apply_three_point_filter( - vec[i]._data, - np.array(self.derham.Nel), - np.array(self.derham.spl_kind), - np.array(self.derham.p), - np.array(self.derham.Vh[self.form][i].starts), - np.array(self.derham.Vh[self.form][i].ends), - alpha=self.filter_params["alpha"], - ) - - vec.update_ghost_regions() - - else: - raise NotImplemented( - "The type of filter must be fourier or three_point.", - ) - - vec_finished = True + self.accfilter(vec) + vec_finished = True if self.particles.clone_config is None: num_clones = 1 @@ -347,7 +307,7 @@ def particles(self): return self._particles @property - def kernel(self): + def kernel(self) -> Pyccelkernel: """The accumulation kernel.""" return self._kernel @@ -393,14 +353,9 @@ def vectors(self): return out @property - def filter_params(self): - """Dict of three components for the accumulation filter parameters: use_filter(string), repeat(int) and alpha(float).""" - return self._filter_params - - @property - def filter_params(self): - """Dict of three components for the accumulation filter parameters: use_filter(string), repeat(int) and alpha(float).""" - return self._filter_params + def accfilter(self): + """Callable filters""" + return self._accfilter def init_control_variate(self, mass_ops): """Set up the use of noise reduction by control variate.""" @@ -410,55 +365,6 @@ def init_control_variate(self, mass_ops): # L2 projector for dofs self._get_L2dofs = L2Projector(self.space_id, mass_ops).get_dofs - def apply_toroidal_fourier_filter(self, vec, modes): - """ - Applying fourier filter to the spline coefficients of the accumulated vector (toroidal direction). - - Parameters - ---------- - vec : BlockVector - - modes : list - Mode numbers which are not filtered out. - """ - - from scipy.fft import irfft, rfft - - tor_Nel = self.derham.Nel[2] - - # Nel along the toroidal direction must be equal or bigger than 2*maximum mode - assert tor_Nel >= 2 * max(modes) - - pn = self.derham.p - ir = np.empty(3, dtype=int) - - if (tor_Nel % 2) == 0: - vec_temp = np.zeros(int(tor_Nel / 2) + 1, dtype=complex) - else: - vec_temp = np.zeros(int((tor_Nel - 1) / 2) + 1, dtype=complex) - - # no domain decomposition along the toroidal direction - assert self.derham.domain_decomposition.nprocs[2] == 1 - - for axis in range(3): - starts = self.derham.Vh[ſelf.form][axis].starts - ends = self.derham.Vh[self.form][axis].ends - - # index range - for i in range(3): - ir[i] = ends[i] + 1 - starts[i] - - # filtering - for i in range(ir[0]): - for j in range(ir[1]): - vec_temp[:] = 0 - vec_temp[modes] = rfft( - vec[axis]._data[pn[0] + i, pn[1] + j, pn[2] : pn[2] + ir[2]], - )[modes] - vec[axis]._data[pn[0] + i, pn[1] + j, pn[2] : pn[2] + ir[2]] = irfft(vec_temp, n=tor_Nel) - - vec.update_ghost_regions() - def show_accumulated_spline_field(self, mass_ops: WeightedMassOperators, eta_direction=0, component=0): r"""1D plot of the spline field corresponding to the accumulated vector. The latter can be viewed as the rhs of an L2-projection: @@ -482,7 +388,7 @@ def show_accumulated_spline_field(self, mass_ops: WeightedMassOperators, eta_dir field.vector = a # plot field - eta = np.linspace(0, 1, 100) + eta = xp.linspace(0, 1, 100) if eta_direction == 0: args = (eta, 0.5, 0.5) elif eta_direction == 1: @@ -521,18 +427,21 @@ class AccumulatorVector: args_domain : DomainArguments Mapping infos. + """ def __init__( self, particles: Particles, space_id: str, - kernel, + kernel: Pyccelkernel, mass_ops: WeightedMassOperators, args_domain: DomainArguments, + filter_params: FilterParameters = None, ): self._particles = particles self._space_id = space_id + assert isinstance(kernel, Pyccelkernel), f"{kernel} is not of type Pyccelkernel" self._kernel = kernel self._derham = mass_ops.derham self._args_domain = args_domain @@ -582,6 +491,9 @@ def __init__( for bl in vec.blocks: self._args_data += (bl._data,) + # initialize filter + self._accfilter = AccumFilter(filter_params, self._derham, self._space_id) + def __call__(self, *optional_args, **args_control): """ Performs the accumulation into the vector by calling the chosen accumulation kernel @@ -598,7 +510,7 @@ def __call__(self, *optional_args, **args_control): args_control : any Keyword arguments for an analytical control variate correction in the accumulation step. Possible keywords are 'control_vec' for a vector correction or 'control_mat' for a matrix correction. - Values are a 1d (vector) or 2d (matrix) list with callables or np.ndarrays used for the correction. + Values are a 1d (vector) or 2d (matrix) list with callables or xp.ndarrays used for the correction. """ # flags for break @@ -609,15 +521,24 @@ def __call__(self, *optional_args, **args_control): dat[:] = 0.0 # accumulate into matrix (and vector) with markers - with ProfileManager.profile_region("kernel: " + self.kernel.__name__): + with ProfileManager.profile_region("kernel: " + self.kernel.name): self.kernel( self.particles.args_markers, - self.derham._args_derham, + self.derham.args_derham, self.args_domain, *self._args_data, *optional_args, ) + # apply filter + if self.accfilter.params.use_filter is not None: + for vec in self._vectors: + vec.exchange_assembly_data() + vec.update_ghost_regions() + + self.accfilter(vec) + vec_finished = True + if self.particles.clone_config is None: num_clones = 1 else: @@ -652,7 +573,7 @@ def particles(self): return self._particles @property - def kernel(self): + def kernel(self) -> Pyccelkernel: """The accumulation kernel.""" return self._kernel @@ -687,6 +608,11 @@ def vectors(self): return out + @property + def accfilter(self): + """Callable filters""" + return self._accfilter + def init_control_variate(self, mass_ops): """Set up the use of noise reduction by control variate.""" @@ -718,7 +644,7 @@ def show_accumulated_spline_field(self, mass_ops, eta_direction=0): field.vector = a # plot field - eta = np.linspace(0, 1, 100) + eta = xp.linspace(0, 1, 100) if eta_direction == 0: args = (eta, 0.5, 0.5) elif eta_direction == 1: diff --git a/src/struphy/pic/base.py b/src/struphy/pic/base.py index a3100c2da..1dc148cdb 100644 --- a/src/struphy/pic/base.py +++ b/src/struphy/pic/base.py @@ -4,11 +4,20 @@ from abc import ABCMeta, abstractmethod import h5py -import numpy as np import scipy.special as sp + +try: + from mpi4py.MPI import Intracomm +except ModuleNotFoundError: + + class Intracomm: + x = None + + +import cunumpy as xp from line_profiler import profile -from mpi4py import MPI -from mpi4py.MPI import Intracomm +from psydac.ddm.mpi import MockComm +from psydac.ddm.mpi import mpi as MPI from sympy.ntheory import factorint from struphy.bsplines.bsplines import quadrature_grid @@ -46,6 +55,7 @@ ) from struphy.utils import utils from struphy.utils.clone_config import CloneConfig +from struphy.utils.pyccel import Pyccelkernel class Particles(metaclass=ABCMeta): @@ -141,7 +151,7 @@ def __init__( domain_decomp: tuple = None, mpi_dims_mask: tuple | list = None, boxes_per_dim: tuple | list = None, - box_bufsize: float = 2.0, + box_bufsize: float = 5.0, type: str = "full_f", name: str = "some_name", loading_params: LoadingParameters = None, @@ -192,9 +202,11 @@ def __init__( if self.mpi_comm is None: self._mpi_size = 1 self._mpi_rank = 0 + self._Barrier = lambda: None else: self._mpi_size = self.mpi_comm.Get_size() self._mpi_rank = self.mpi_comm.Get_rank() + self._Barrier = self.mpi_comm.Barrier # domain decomposition (MPI) and cell information self._boxes_per_dim = boxes_per_dim @@ -207,7 +219,7 @@ def __init__( self._nprocs = domain_decomp[1] # total number of cells (equal to mpi_size if no grid) - n_cells = np.sum(np.prod(self.domain_array[:, 2::3], axis=1, dtype=int)) * self.num_clones + n_cells = xp.sum(xp.prod(self.domain_array[:, 2::3], axis=1, dtype=int)) * self.num_clones # if verbose: # print(f"\n{self.mpi_rank = }, {n_cells = }") @@ -216,12 +228,12 @@ def __init__( n_boxes = self.mpi_size * self.num_clones else: assert all([nboxes >= nproc for nboxes, nproc in zip(self.boxes_per_dim, self.nprocs)]), ( - f"There must be at least one box {self.boxes_per_dim = } on each process {self.nprocs = } in each direction." + f"There must be at least one box {self.boxes_per_dim =} on each process {self.nprocs =} in each direction." ) assert all([nboxes % nproc == 0 for nboxes, nproc in zip(self.boxes_per_dim, self.nprocs)]), ( - f"Number of boxes {self.boxes_per_dim = } must be divisible by number of processes {self.nprocs = } in each direction." + f"Number of boxes {self.boxes_per_dim =} must be divisible by number of processes {self.nprocs =} in each direction." ) - n_boxes = np.prod(self.boxes_per_dim, dtype=int) * self.num_clones + n_boxes = xp.prod(self.boxes_per_dim, dtype=int) * self.num_clones # if verbose: # print(f"\n{self.mpi_rank = }, {n_boxes = }") @@ -328,14 +340,13 @@ def __init__( self._generate_sampling_moments() # create buffers for mpi_sort_markers - if self.mpi_comm is not None: - self._sorting_etas = np.zeros(self.markers.shape, dtype=float) - self._is_on_proc_domain = np.zeros((self.markers.shape[0], 3), dtype=bool) - self._can_stay = np.zeros(self.markers.shape[0], dtype=bool) - self._reqs = [None] * self.mpi_size - self._recvbufs = [None] * self.mpi_size - self._send_to_i = [None] * self.mpi_size - self._send_list = [None] * self.mpi_size + self._sorting_etas = xp.zeros(self.markers.shape, dtype=float) + self._is_on_proc_domain = xp.zeros((self.markers.shape[0], 3), dtype=bool) + self._can_stay = xp.zeros(self.markers.shape[0], dtype=bool) + self._reqs = [None] * self.mpi_size + self._recvbufs = [None] * self.mpi_size + self._send_to_i = [None] * self.mpi_size + self._send_list = [None] * self.mpi_size @classmethod @abstractmethod @@ -713,16 +724,16 @@ def index(self): def valid_mks(self): """Array of booleans stating if an entry in the markers array is a true local particle (not a hole or ghost).""" if not hasattr(self, "_valid_mks"): - self._valid_mks = ~np.logical_or(self.holes, self.ghost_particles) + self._valid_mks = ~xp.logical_or(self.holes, self.ghost_particles) return self._valid_mks def update_valid_mks(self): - self._valid_mks[:] = ~np.logical_or(self.holes, self.ghost_particles) + self._valid_mks[:] = ~xp.logical_or(self.holes, self.ghost_particles) @property def n_mks_loc(self): """Number of valid markers on process (without holes and ghosts).""" - return np.count_nonzero(self.valid_mks) + return xp.count_nonzero(self.valid_mks) @property def n_mks_on_each_proc(self): @@ -732,7 +743,7 @@ def n_mks_on_each_proc(self): @property def n_mks_on_clone(self): """Number of valid markers on current clone (without holes and ghosts).""" - return np.sum(self.n_mks_on_each_proc) + return xp.sum(self.n_mks_on_each_proc) @property def n_mks_on_each_clone(self): @@ -742,7 +753,7 @@ def n_mks_on_each_clone(self): @property def n_mks_global(self): """Number of valid markers on current clone (without holes and ghosts).""" - return np.sum(self.n_mks_on_each_clone) + return xp.sum(self.n_mks_on_each_clone) @property def positions(self): @@ -751,7 +762,7 @@ def positions(self): @positions.setter def positions(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) assert new.shape == (self.n_mks_loc, 3) self._markers[self.valid_mks, self.index["pos"]] = new @@ -762,8 +773,8 @@ def velocities(self): @velocities.setter def velocities(self, new): - assert isinstance(new, np.ndarray) - assert new.shape == (self.n_mks_loc, self.vdim), f"{self.n_mks_loc = } and {self.vdim = } but {new.shape = }" + assert isinstance(new, xp.ndarray) + assert new.shape == (self.n_mks_loc, self.vdim), f"{self.n_mks_loc =} and {self.vdim =} but {new.shape =}" self._markers[self.valid_mks, self.index["vel"]] = new @property @@ -773,7 +784,7 @@ def phasespace_coords(self): @phasespace_coords.setter def phasespace_coords(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) assert new.shape == (self.n_mks_loc, 3 + self.vdim) self._markers[self.valid_mks, self.index["coords"]] = new @@ -784,7 +795,7 @@ def weights(self): @weights.setter def weights(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) assert new.shape == (self.n_mks_loc,) self._markers[self.valid_mks, self.index["weights"]] = new @@ -795,7 +806,7 @@ def sampling_density(self): @sampling_density.setter def sampling_density(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) assert new.shape == (self.n_mks_loc,) self._markers[self.valid_mks, self.index["s0"]] = new @@ -806,7 +817,7 @@ def weights0(self): @weights0.setter def weights0(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) assert new.shape == (self.n_mks_loc,) self._markers[self.valid_mks, self.index["w0"]] = new @@ -817,7 +828,7 @@ def marker_ids(self): @marker_ids.setter def marker_ids(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) assert new.shape == (self.n_mks_loc,) self._markers[self.valid_mks, self.index["ids"]] = new @@ -848,7 +859,7 @@ def f_coords(self): @f_coords.setter def f_coords(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) self.markers[self.valid_mks, self.f_coords_index] = new @property @@ -860,16 +871,16 @@ def args_markers(self): def f_jacobian_coords(self): """Coordinates of the velocity jacobian determinant of the distribution fuction.""" if isinstance(self.f_jacobian_coords_index, list): - return self.markers[np.ix_(~self.holes, self.f_jacobian_coords_index)] + return self.markers[xp.ix_(~self.holes, self.f_jacobian_coords_index)] else: return self.markers[~self.holes, self.f_jacobian_coords_index] @f_jacobian_coords.setter def f_jacobian_coords(self, new): - assert isinstance(new, np.ndarray) + assert isinstance(new, xp.ndarray) if isinstance(self.f_jacobian_coords_index, list): self.markers[ - np.ix_( + xp.ix_( ~self.holes, self.f_jacobian_coords_index, ) @@ -916,7 +927,7 @@ def _get_domain_decomp(self, mpi_dims_mask: tuple | list = None): Returns ------- - dom_arr : np.ndarray + dom_arr : xp.ndarray A 2d array of shape (#MPI processes, 9). The row index denotes the process rank. The columns are for n=0,1,2: - arr[i, 3*n + 0] holds the LEFT domain boundary of process i in direction eta_(n+1). - arr[i, 3*n + 1] holds the RIGHT domain boundary of process i in direction eta_(n+1). @@ -928,7 +939,7 @@ def _get_domain_decomp(self, mpi_dims_mask: tuple | list = None): if mpi_dims_mask is None: mpi_dims_mask = [True, True, True] - dom_arr = np.zeros((self.mpi_size, 9), dtype=float) + dom_arr = xp.zeros((self.mpi_size, 9), dtype=float) # factorize mpi size factors = factorint(self.mpi_size) @@ -952,10 +963,10 @@ def _get_domain_decomp(self, mpi_dims_mask: tuple | list = None): mm = (mm + 1) % 3 nprocs[mm] *= fac - assert np.prod(nprocs) == self.mpi_size + assert xp.prod(nprocs) == self.mpi_size # domain decomposition - breaks = [np.linspace(0.0, 1.0, nproc + 1) for nproc in nprocs] + breaks = [xp.linspace(0.0, 1.0, nproc + 1) for nproc in nprocs] # fill domain array for n in range(self.mpi_size): @@ -1044,14 +1055,14 @@ def _n_mks_load_and_Np_per_clone(self): """Return two arrays: 1) an array of sub_comm.size where the i-th entry corresponds to the number of markers drawn on process i, and 2) an array of size num_clones where the i-th entry corresponds to the number of markers on clone i.""" # number of cells on current process - n_cells_loc = np.prod( + n_cells_loc = xp.prod( self.domain_array[self.mpi_rank, 2::3], dtype=int, ) # array of number of markers on each process at loading stage if self.clone_config is not None: - _n_cells_clone = np.sum(np.prod(self.domain_array[:, 2::3], axis=1, dtype=int)) + _n_cells_clone = xp.sum(xp.prod(self.domain_array[:, 2::3], axis=1, dtype=int)) _n_mks_load_tot = self.clone_config.get_Np_clone(self.Np) _ppc = _n_mks_load_tot / _n_cells_clone else: @@ -1061,14 +1072,14 @@ def _n_mks_load_and_Np_per_clone(self): n_mks_load = self._gather_scalar_in_subcomm_array(int(_ppc * n_cells_loc)) # add deviation from Np to rank 0 - n_mks_load[0] += _n_mks_load_tot - np.sum(n_mks_load) + n_mks_load[0] += _n_mks_load_tot - xp.sum(n_mks_load) # check if all markers are there - assert np.sum(n_mks_load) == _n_mks_load_tot + assert xp.sum(n_mks_load) == _n_mks_load_tot # Np on each clone Np_per_clone = self._gather_scalar_in_intercomm_array(_n_mks_load_tot) - assert np.sum(Np_per_clone) == self.Np + assert xp.sum(Np_per_clone) == self.Np return n_mks_load, Np_per_clone @@ -1079,23 +1090,23 @@ def _allocate_marker_array(self): # number of markers on the local process at loading stage n_mks_load_loc = self.n_mks_load[self._mpi_rank] - bufsize = self.bufsize + 1.0 / np.sqrt(n_mks_load_loc) + bufsize = self.bufsize + 1.0 / xp.sqrt(n_mks_load_loc) # allocate markers array (3 x positions, vdim x velocities, weight, s0, w0, ..., ID) with buffer self._n_rows = round(n_mks_load_loc * (1 + bufsize)) - self._markers = np.zeros((self.n_rows, self.n_cols), dtype=float) + self._markers = xp.zeros((self.n_rows, self.n_cols), dtype=float) # allocate auxiliary arrays - self._holes = np.zeros(self.n_rows, dtype=bool) - self._ghost_particles = np.zeros(self.n_rows, dtype=bool) - self._valid_mks = np.zeros(self.n_rows, dtype=bool) - self._is_outside_right = np.zeros(self.n_rows, dtype=bool) - self._is_outside_left = np.zeros(self.n_rows, dtype=bool) - self._is_outside = np.zeros(self.n_rows, dtype=bool) + self._holes = xp.zeros(self.n_rows, dtype=bool) + self._ghost_particles = xp.zeros(self.n_rows, dtype=bool) + self._valid_mks = xp.zeros(self.n_rows, dtype=bool) + self._is_outside_right = xp.zeros(self.n_rows, dtype=bool) + self._is_outside_left = xp.zeros(self.n_rows, dtype=bool) + self._is_outside = xp.zeros(self.n_rows, dtype=bool) # create array container (3 x positions, vdim x velocities, weight, s0, w0, ID) for removed markers self._n_lost_markers = 0 - self._lost_markers = np.zeros((int(self.n_rows * 0.5), 10), dtype=float) + self._lost_markers = xp.zeros((int(self.n_rows * 0.5), 10), dtype=float) # arguments for kernels self._args_markers = MarkerArguments( @@ -1113,7 +1124,7 @@ def _allocate_marker_array(self): # Have at least 3 spare places in markers array assert self.args_markers.first_free_idx + 2 < self.n_cols - 1, ( - f"{self.args_markers.first_free_idx + 2} is not smaller than {self.n_cols - 1 = }; not enough columns in marker array !!" + f"{self.args_markers.first_free_idx + 2} is not smaller than {self.n_cols - 1 =}; not enough columns in marker array !!" ) def _initialize_sorting_boxes(self): @@ -1210,16 +1221,16 @@ def _generate_sampling_moments(self): # assert len(ns) == len(us) == len(vths) - # ns = np.array(ns) - # us = np.array(us) - # vths = np.array(vths) + # ns = xp.array(ns) + # us = xp.array(us) + # vths = xp.array(vths) # Use the mean of shifts and thermal velocity such that outermost shift+thermal is # new shift + new thermal - # mean_us = np.mean(us, axis=0) - # us_ext = us + vths * np.where(us >= 0, 1, -1) + # mean_us = xp.mean(us, axis=0) + # us_ext = us + vths * xp.where(us >= 0, 1, -1) # us_ext_dist = us_ext - mean_us[None, :] - # new_vths = np.max(np.abs(us_ext_dist), axis=0) + # new_vths = xp.max(xp.abs(us_ext_dist), axis=0) # new_moments = [] @@ -1294,7 +1305,7 @@ def _f_init(*etas, flat_eval=False): out = out0 + out1 if flat_eval: - out = np.squeeze(out) + out = xp.squeeze(out) return out @@ -1303,7 +1314,7 @@ def _f_init(*etas, flat_eval=False): def _load_external( self, n_mks_load_loc: int, - n_mks_load_cum_sum: np.ndarray, + n_mks_load_cum_sum: xp.ndarray, ): """Load markers from external .hdf5 file. @@ -1312,7 +1323,7 @@ def _load_external( n_mks_load_loc: int Number of markers on the local process at loading stage. - n_mks_load_cum_sum: np.ndarray + n_mks_load_cum_sum: xp.ndarray Cumulative sum of number of markers on each process at loading stage. """ if self.mpi_rank == 0: @@ -1336,7 +1347,7 @@ def _load_external( file.close() else: - recvbuf = np.zeros( + recvbuf = xp.zeros( (n_mks_load_loc, self.markers.shape[1]), dtype=float, ) @@ -1478,8 +1489,8 @@ def draw_markers( self.update_ghost_particles() # cumulative sum of number of markers on each process at loading stage. - n_mks_load_cum_sum = np.cumsum(self.n_mks_load) - Np_per_clone_cum_sum = np.cumsum(self.Np_per_clone) + n_mks_load_cum_sum = xp.cumsum(self.n_mks_load) + Np_per_clone_cum_sum = xp.cumsum(self.Np_per_clone) _first_marker_id = (Np_per_clone_cum_sum - self.Np_per_clone)[self.clone_id] + ( n_mks_load_cum_sum - self.n_mks_load )[self._mpi_rank] @@ -1507,9 +1518,9 @@ def draw_markers( self._load_tesselation() if self.type == "sph": self._set_initial_condition() - self.velocities = np.array(self.u_init(self.positions)[0]).T + self.velocities = xp.array(self.u_init(self.positions)[0]).T # set markers ID in last column - self.marker_ids = _first_marker_id + np.arange(n_mks_load_loc, dtype=float) + self.marker_ids = _first_marker_id + xp.arange(n_mks_load_loc, dtype=float) else: if self.mpi_rank == 0 and verbose: print("\nLoading fresh markers:") @@ -1521,7 +1532,7 @@ def draw_markers( # set seed _seed = self.loading_params.seed if _seed is not None: - np.random.seed(_seed) + xp.random.seed(_seed) # counting integers num_loaded_particles_loc = 0 # number of particles alreday loaded (local) @@ -1532,15 +1543,15 @@ def draw_markers( while num_loaded_particles_glob < int(self.Np): # Generate a chunk of random particles num_to_add_glob = min(chunk_size, int(self.Np) - num_loaded_particles_glob) - temp = np.random.rand(num_to_add_glob, 3 + self.vdim) + temp = xp.random.rand(num_to_add_glob, 3 + self.vdim) # check which particles are on the current process domain - is_on_proc_domain = np.logical_and( + is_on_proc_domain = xp.logical_and( temp[:, :3] > self.domain_array[self.mpi_rank, 0::3], temp[:, :3] < self.domain_array[self.mpi_rank, 1::3], ) - valid_idx = np.nonzero(np.all(is_on_proc_domain, axis=1))[0] + valid_idx = xp.nonzero(xp.all(is_on_proc_domain, axis=1))[0] valid_particles = temp[valid_idx] - valid_particles = np.array_split(valid_particles, self.num_clones)[self.clone_id] + valid_particles = xp.array_split(valid_particles, self.num_clones)[self.clone_id] num_valid = valid_particles.shape[0] # Add the valid particles to the phasespace_coords array @@ -1552,12 +1563,12 @@ def draw_markers( num_loaded_particles_loc += num_valid # make sure all particles are loaded - assert self.Np == int(num_loaded_particles_glob), f"{self.Np = }, {int(num_loaded_particles_glob) = }" + assert self.Np == int(num_loaded_particles_glob), f"{self.Np =}, {int(num_loaded_particles_glob) =}" # set new n_mks_load self._gather_scalar_in_subcomm_array(num_loaded_particles_loc, out=self.n_mks_load) n_mks_load_loc = self.n_mks_load[self.mpi_rank] - n_mks_load_cum_sum = np.cumsum(self.n_mks_load) + n_mks_load_cum_sum = xp.cumsum(self.n_mks_load) # set new holes in markers array to -1 self._markers[num_loaded_particles_loc:] = -1.0 @@ -1597,11 +1608,11 @@ def draw_markers( # initial velocities - SPH case: v(0) = u(x(0)) for given velocity u(x) if self.type == "sph": self._set_initial_condition() - self.velocities = np.array(self.u_init(self.positions)[0]).T + self.velocities = xp.array(self.u_init(self.positions)[0]).T else: # inverse transform sampling in velocity space - u_mean = np.array(self.loading_params.moments[: self.vdim]) - v_th = np.array(self.loading_params.moments[self.vdim :]) + u_mean = xp.array(self.loading_params.moments[: self.vdim]) + v_th = xp.array(self.loading_params.moments[self.vdim :]) # Particles6D: (1d Maxwellian, 1d Maxwellian, 1d Maxwellian) if self.vdim == 3: @@ -1609,7 +1620,7 @@ def draw_markers( sp.erfinv( 2 * self.velocities - 1, ) - * np.sqrt(2) + * xp.sqrt(2) * v_th + u_mean ) @@ -1619,16 +1630,16 @@ def draw_markers( sp.erfinv( 2 * self.velocities[:, 0] - 1, ) - * np.sqrt(2) + * xp.sqrt(2) * v_th[0] + u_mean[0] ) self._markers[:n_mks_load_loc, 4] = ( - np.sqrt( - -1 * np.log(1 - self.velocities[:, 1]), + xp.sqrt( + -1 * xp.log(1 - self.velocities[:, 1]), ) - * np.sqrt(2) + * xp.sqrt(2) * v_th[1] + u_mean[1] ) @@ -1641,13 +1652,13 @@ def draw_markers( # inversion method for drawing uniformly on the disc if self.spatial == "disc": - self._markers[:n_mks_load_loc, 0] = np.sqrt( + self._markers[:n_mks_load_loc, 0] = xp.sqrt( self._markers[:n_mks_load_loc, 0], ) else: assert self.spatial == "uniform", f'Spatial drawing must be "uniform" or "disc", is {self.spatial}.' - self.marker_ids = _first_marker_id + np.arange(n_mks_load_loc, dtype=float) + self.marker_ids = _first_marker_id + xp.arange(n_mks_load_loc, dtype=float) # set specific initial condition for some particles if self.loading_params.specific_markers is not None: @@ -1668,13 +1679,14 @@ def draw_markers( # check if all particle positions are inside the unit cube [0, 1]^3 n_mks_load_loc = self.n_mks_load[self._mpi_rank] - assert np.all(~self.holes[:n_mks_load_loc]) - assert np.all(self.holes[n_mks_load_loc:]) + assert xp.all(~self.holes[:n_mks_load_loc]) + assert xp.all(self.holes[n_mks_load_loc:]) if self._initialized_sorting and sort: if self.mpi_rank == 0 and verbose: print("Sorting the markers after initial draw") - self.mpi_sort_markers() + if self.mpi_comm is not None: + self.mpi_sort_markers() self.do_sort() @profile @@ -1712,7 +1724,7 @@ def mpi_sort_markers( if remove_ghost: self.remove_ghost_particles() - self.mpi_comm.Barrier() + self._Barrier() # before sorting, apply kinetic bc if apply_bc: @@ -1741,8 +1753,8 @@ def mpi_sort_markers( # check if all markers are on the right process after sorting if do_test: - all_on_right_proc = np.all( - np.logical_and( + all_on_right_proc = xp.all( + xp.logical_and( self.positions > self.domain_array[self.mpi_rank, 0::3], self.positions < self.domain_array[self.mpi_rank, 1::3], ), @@ -1751,7 +1763,7 @@ def mpi_sort_markers( assert all_on_right_proc # assert self.phasespace_coords.size > 0, f'No particles on process {self.mpi_rank}, please rebalance, aborting ...' - self.mpi_comm.Barrier() + self._Barrier() def initialize_weights( self, @@ -1829,7 +1841,7 @@ def initialize_weights( self.update_holes() self.reset_marker_ids() print( - f"\nWeights < {self.threshold} have been rejected, number of valid markers on process {self.mpi_rank} is {self.n_mks_loc}." + f"\nWeights < {self.threshold} have been rejected, number of valid markers on process {self.mpi_rank} is {self.n_mks_loc}.", ) # compute (time-dependent) weights at vdim + 3 @@ -1866,18 +1878,18 @@ def update_weights(self): def reset_marker_ids(self): """Reset the marker ids (last column in marker array) according to the current distribution of particles. The first marker on rank 0 gets the id '0', the last marker on the last rank gets the id 'n_mks_global - 1'.""" - n_mks_proc_cumsum = np.cumsum(self.n_mks_on_each_proc) - n_mks_clone_cumsum = np.cumsum(self.n_mks_on_each_clone) + n_mks_proc_cumsum = xp.cumsum(self.n_mks_on_each_proc) + n_mks_clone_cumsum = xp.cumsum(self.n_mks_on_each_clone) first_marker_id = (n_mks_clone_cumsum - self.n_mks_on_each_clone)[self.clone_id] + ( n_mks_proc_cumsum - self.n_mks_on_each_proc )[self.mpi_rank] - self.marker_ids = first_marker_id + np.arange(self.n_mks_loc, dtype=int) + self.marker_ids = first_marker_id + xp.arange(self.n_mks_loc, dtype=int) @profile def binning( self, components: tuple[bool], - bin_edges: tuple[np.ndarray], + bin_edges: tuple[xp.ndarray], divide_by_jac: bool = True, ): r"""Computes full-f and delta-f distribution functions via marker binning in logical space. @@ -1903,7 +1915,7 @@ def binning( The reconstructed delta-f distribution function. """ - assert np.count_nonzero(components) == len(bin_edges) + assert xp.count_nonzero(components) == len(bin_edges) # volume of a bin bin_vol = 1.0 @@ -1925,13 +1937,13 @@ def binning( _weights0 /= self.domain.jacobian_det(self.positions, remove_outside=False) # _weights0 /= self.velocity_jacobian_det(*self.phasespace_coords.T) - f_slice = np.histogramdd( + f_slice = xp.histogramdd( self.markers_wo_holes_and_ghost[:, slicing], bins=bin_edges, weights=_weights0, )[0] - df_slice = np.histogramdd( + df_slice = xp.histogramdd( self.markers_wo_holes_and_ghost[:, slicing], bins=bin_edges, weights=_weights, @@ -1958,7 +1970,7 @@ def show_distribution_function(self, components, bin_edges): import matplotlib.pyplot as plt - n_dim = np.count_nonzero(components) + n_dim = xp.count_nonzero(components) assert n_dim == 1 or n_dim == 2, f"Distribution function can only be shown in 1D or 2D slices, not {n_dim}." @@ -1974,7 +1986,7 @@ def show_distribution_function(self, components, bin_edges): 4: "$v_2$", 5: "$v_3$", } - indices = np.nonzero(components)[0] + indices = xp.nonzero(components)[0] if n_dim == 1: plt.plot(bin_centers[0], f_slice) @@ -1998,13 +2010,13 @@ def _find_outside_particles(self, axis): self._is_outside_left[self.holes] = False self._is_outside_left[self.ghost_particles] = False - self._is_outside[:] = np.logical_or( + self._is_outside[:] = xp.logical_or( self._is_outside_right, self._is_outside_left, ) # indices or particles that are outside of the logical unit cube - outside_inds = np.nonzero(self._is_outside)[0] + outside_inds = xp.nonzero(self._is_outside)[0] return outside_inds @@ -2031,7 +2043,7 @@ def apply_kinetic_bc(self, newton=False): self.particle_refilling() self._markers[self._is_outside, :-1] = -1.0 - self._n_lost_markers += len(np.nonzero(self._is_outside)[0]) + self._n_lost_markers += len(xp.nonzero(self._is_outside)[0]) for axis in self._periodic_axes: outside_inds = self._find_outside_particles(axis) @@ -2042,8 +2054,8 @@ def apply_kinetic_bc(self, newton=False): self.markers[outside_inds, axis] = self.markers[outside_inds, axis] % 1.0 # set shift for alpha-weighted mid-point computation - outside_right_inds = np.nonzero(self._is_outside_right)[0] - outside_left_inds = np.nonzero(self._is_outside_left)[0] + outside_right_inds = xp.nonzero(self._is_outside_right)[0] + outside_left_inds = xp.nonzero(self._is_outside_left)[0] if newton: self.markers[ outside_right_inds, @@ -2111,12 +2123,12 @@ def particle_refilling(self): for kind in self.bc_refill: # sorting out particles which are out of the domain if kind == "inner": - outside_inds = np.nonzero(self._is_outside_left)[0] + outside_inds = xp.nonzero(self._is_outside_left)[0] self.markers[outside_inds, 0] = 1e-4 r_loss = self.domain.params["a1"] else: - outside_inds = np.nonzero(self._is_outside_right)[0] + outside_inds = xp.nonzero(self._is_outside_right)[0] self.markers[outside_inds, 0] = 1 - 1e-4 r_loss = 1.0 @@ -2165,12 +2177,12 @@ def gyro_transfer(self, outside_inds): Parameters ---------- - outside_inds : np.array (int) + outside_inds : xp.array (int) An array of indices of particles which are outside of the domain. Returns ------- - out : np.array (bool) + out : xp.array (bool) An array of indices of particles where its guiding centers are outside of the domain. """ @@ -2187,18 +2199,18 @@ def gyro_transfer(self, outside_inds): b_cart, xyz = self.equil.b_cart(self.markers[outside_inds, :]) # calculate magnetic field amplitude and normalized magnetic field - absB0 = np.sqrt(b_cart[0] ** 2 + b_cart[1] ** 2 + b_cart[2] ** 2) + absB0 = xp.sqrt(b_cart[0] ** 2 + b_cart[1] ** 2 + b_cart[2] ** 2) norm_b_cart = b_cart / absB0 # calculate parallel and perpendicular velocities - v_parallel = np.einsum("ij,ij->j", v, norm_b_cart) - v_perp = np.cross(norm_b_cart, np.cross(v, norm_b_cart, axis=0), axis=0) - v_perp_square = np.sqrt(v_perp[0] ** 2 + v_perp[1] ** 2 + v_perp[2] ** 2) + v_parallel = xp.einsum("ij,ij->j", v, norm_b_cart) + v_perp = xp.cross(norm_b_cart, xp.cross(v, norm_b_cart, axis=0), axis=0) + v_perp_square = xp.sqrt(v_perp[0] ** 2 + v_perp[1] ** 2 + v_perp[2] ** 2) - assert np.all(np.isclose(v_perp, v - norm_b_cart * v_parallel)) + assert xp.all(xp.isclose(v_perp, v - norm_b_cart * v_parallel)) # calculate Larmor radius - Larmor_r = np.cross(norm_b_cart, v_perp, axis=0) / absB0 * self._epsilon + Larmor_r = xp.cross(norm_b_cart, v_perp, axis=0) / absB0 * self._epsilon # transform cartesian coordinates to logical coordinates # TODO: currently only possible with the geomoetry where its inverse map is defined. @@ -2217,17 +2229,17 @@ def gyro_transfer(self, outside_inds): b_cart = self.equil.b_cart(self.markers[outside_inds, :])[0] # calculate magnetic field amplitude and normalized magnetic field - absB0 = np.sqrt(b_cart[0] ** 2 + b_cart[1] ** 2 + b_cart[2] ** 2) + absB0 = xp.sqrt(b_cart[0] ** 2 + b_cart[1] ** 2 + b_cart[2] ** 2) norm_b_cart = b_cart / absB0 Larmor_r = new_xyz - xyz - Larmor_r /= np.sqrt(Larmor_r[0] ** 2 + Larmor_r[1] ** 2 + Larmor_r[2] ** 2) + Larmor_r /= xp.sqrt(Larmor_r[0] ** 2 + Larmor_r[1] ** 2 + Larmor_r[2] ** 2) - new_v_perp = np.cross(Larmor_r, norm_b_cart, axis=0) * v_perp_square + new_v_perp = xp.cross(Larmor_r, norm_b_cart, axis=0) * v_perp_square self.markers[outside_inds, 3:6] = (norm_b_cart * v_parallel).T + new_v_perp.T - return np.logical_and(1.0 > gc_etas[0], gc_etas[0] > 0.0) + return xp.logical_and(1.0 > gc_etas[0], gc_etas[0] > 0.0) class SortingBoxes: """Boxes used for the sorting of the particles. @@ -2410,26 +2422,26 @@ def _set_boxes(self): n_particles = self._markers_shape[0] n_mkr = int(n_particles / n_box_in) + 1 n_cols = round( - n_mkr * (1 + 1 / np.sqrt(n_mkr) + self._box_bufsize), + n_mkr * (1 + 1 / xp.sqrt(n_mkr) + self._box_bufsize), ) # cartesian boxes - self._boxes = np.zeros((self._n_boxes + 1, n_cols), dtype=int) + self._boxes = xp.zeros((self._n_boxes + 1, n_cols), dtype=int) # TODO: there is still a bug here # the row number in self._boxes should not be n_boxes + 1; this is just a temporary fix to avoid an error that I dont understand. # Must be fixed soon! - self._next_index = np.zeros((self._n_boxes + 1), dtype=int) - self._cumul_next_index = np.zeros((self._n_boxes + 2), dtype=int) - self._neighbours = np.zeros((self._n_boxes, 27), dtype=int) + self._next_index = xp.zeros((self._n_boxes + 1), dtype=int) + self._cumul_next_index = xp.zeros((self._n_boxes + 2), dtype=int) + self._neighbours = xp.zeros((self._n_boxes, 27), dtype=int) # A particle on box i only sees particles in boxes that belong to neighbours[i] initialize_neighbours(self._neighbours, self.nx, self.ny, self.nz) # print(f"{self._rank = }\n{self._neighbours = }") - self._swap_line_1 = np.zeros(self._markers_shape[1]) - self._swap_line_2 = np.zeros(self._markers_shape[1]) + self._swap_line_1 = xp.zeros(self._markers_shape[1]) + self._swap_line_2 = xp.zeros(self._markers_shape[1]) def _set_boundary_boxes(self): """Gather all the boxes that are part of a boundary""" @@ -2450,7 +2462,7 @@ def _set_boundary_boxes(self): self._bnd_boxes_x_p.append(flatten_index(self.nx, j, k, self.nx, self.ny, self.nz)) if self._verbose: - print(f"eta1 boundary on {self._rank = }:\n{self._bnd_boxes_x_m = }\n{self._bnd_boxes_x_p = }") + print(f"eta1 boundary on {self._rank =}:\n{self._bnd_boxes_x_m =}\n{self._bnd_boxes_x_p =}") # y boundary # negative direction @@ -2465,7 +2477,7 @@ def _set_boundary_boxes(self): self._bnd_boxes_y_p.append(flatten_index(i, self.ny, k, self.nx, self.ny, self.nz)) if self._verbose: - print(f"eta2 boundary on {self._rank = }:\n{self._bnd_boxes_y_m = }\n{self._bnd_boxes_y_p = }") + print(f"eta2 boundary on {self._rank =}:\n{self._bnd_boxes_y_m =}\n{self._bnd_boxes_y_p =}") # z boundary # negative direction @@ -2480,7 +2492,7 @@ def _set_boundary_boxes(self): self._bnd_boxes_z_p.append(flatten_index(i, j, self.nz, self.nx, self.ny, self.nz)) if self._verbose: - print(f"eta3 boundary on {self._rank = }:\n{self._bnd_boxes_z_m = }\n{self._bnd_boxes_z_p = }") + print(f"eta3 boundary on {self._rank =}:\n{self._bnd_boxes_z_m =}\n{self._bnd_boxes_z_p =}") # x-y edges self._bnd_boxes_x_m_y_m = [] @@ -2498,11 +2510,11 @@ def _set_boundary_boxes(self): if self._verbose: print( ( - f"eta1-eta2 edge on {self._rank = }:\n{self._bnd_boxes_x_m_y_m = }" - f"\n{self._bnd_boxes_x_m_y_p = }" - f"\n{self._bnd_boxes_x_p_y_m = }" - f"\n{self._bnd_boxes_x_p_y_p = }" - ) + f"eta1-eta2 edge on {self._rank =}:\n{self._bnd_boxes_x_m_y_m =}" + f"\n{self._bnd_boxes_x_m_y_p =}" + f"\n{self._bnd_boxes_x_p_y_m =}" + f"\n{self._bnd_boxes_x_p_y_p =}" + ), ) # x-z edges @@ -2521,11 +2533,11 @@ def _set_boundary_boxes(self): if self._verbose: print( ( - f"eta1-eta3 edge on {self._rank = }:\n{self._bnd_boxes_x_m_z_m = }" - f"\n{self._bnd_boxes_x_m_z_p = }" - f"\n{self._bnd_boxes_x_p_z_m = }" - f"\n{self._bnd_boxes_x_p_z_p = }" - ) + f"eta1-eta3 edge on {self._rank =}:\n{self._bnd_boxes_x_m_z_m =}" + f"\n{self._bnd_boxes_x_m_z_p =}" + f"\n{self._bnd_boxes_x_p_z_m =}" + f"\n{self._bnd_boxes_x_p_z_p =}" + ), ) # y-z edges @@ -2544,11 +2556,11 @@ def _set_boundary_boxes(self): if self._verbose: print( ( - f"eta2-eta3 edge on {self._rank = }:\n{self._bnd_boxes_y_m_z_m = }" - f"\n{self._bnd_boxes_y_m_z_p = }" - f"\n{self._bnd_boxes_y_p_z_m = }" - f"\n{self._bnd_boxes_y_p_z_p = }" - ) + f"eta2-eta3 edge on {self._rank =}:\n{self._bnd_boxes_y_m_z_m =}" + f"\n{self._bnd_boxes_y_m_z_p =}" + f"\n{self._bnd_boxes_y_p_z_m =}" + f"\n{self._bnd_boxes_y_p_z_p =}" + ), ) # corners @@ -2574,15 +2586,15 @@ def _set_boundary_boxes(self): if self._verbose: print( ( - f"corners on {self._rank = }:\n{self._bnd_boxes_x_m_y_m_z_m = }" - f"\n{self._bnd_boxes_x_m_y_m_z_p = }" - f"\n{self._bnd_boxes_x_m_y_p_z_m = }" - f"\n{self._bnd_boxes_x_p_y_m_z_m = }" - f"\n{self._bnd_boxes_x_m_y_p_z_p = }" - f"\n{self._bnd_boxes_x_p_y_m_z_p = }" - f"\n{self._bnd_boxes_x_p_y_p_z_m = }" - f"\n{self._bnd_boxes_x_p_y_p_z_p = }" - ) + f"corners on {self._rank =}:\n{self._bnd_boxes_x_m_y_m_z_m =}" + f"\n{self._bnd_boxes_x_m_y_m_z_p =}" + f"\n{self._bnd_boxes_x_m_y_p_z_m =}" + f"\n{self._bnd_boxes_x_p_y_m_z_m =}" + f"\n{self._bnd_boxes_x_m_y_p_z_p =}" + f"\n{self._bnd_boxes_x_p_y_m_z_p =}" + f"\n{self._bnd_boxes_x_p_y_p_z_m =}" + f"\n{self._bnd_boxes_x_p_y_p_z_p =}" + ), ) def _sort_boxed_particles_numpy(self): @@ -2590,7 +2602,7 @@ def _sort_boxed_particles_numpy(self): sorting_axis = self._sorting_boxes.box_index if not hasattr(self, "_argsort_array"): - self._argsort_array = np.zeros(self.markers.shape[0], dtype=int) + self._argsort_array = xp.zeros(self.markers.shape[0], dtype=int) self._argsort_array[:] = self._markers[:, sorting_axis].argsort() self._markers[:, :] = self._markers[self._argsort_array] @@ -2619,24 +2631,24 @@ def put_particles_in_boxes(self): self.update_ghost_particles() # if self.verbose: - # valid_box_ids = np.nonzero(self._sorting_boxes._boxes[:, 0] != -1)[0] + # valid_box_ids = xp.nonzero(self._sorting_boxes._boxes[:, 0] != -1)[0] # print(f"Boxes holding at least one particle: {valid_box_ids}") # for i in valid_box_ids: - # n_mks_box = np.count_nonzero(self._sorting_boxes._boxes[i] != -1) + # n_mks_box = xp.count_nonzero(self._sorting_boxes._boxes[i] != -1) # print(f"Number of markers in box {i} is {n_mks_box}") def check_and_assign_particles_to_boxes(self): """Check whether the box array has enough columns (detect load imbalance wrt to sorting boxes), and then assigne the particles to boxes.""" - bcount = np.bincount(np.int64(self.markers_wo_holes[:, -2])) - max_in_box = np.max(bcount) + bcount = xp.bincount(xp.int64(self.markers_wo_holes[:, -2])) + max_in_box = xp.max(bcount) if max_in_box > self._sorting_boxes.boxes.shape[1]: warnings.warn( f'Strong load imbalance detected in sorting boxes: \ max number of markers in a box ({max_in_box}) on rank {self.mpi_rank} \ exceeds the column-size of the box array ({self._sorting_boxes.boxes.shape[1]}). \ -Increasing the value of "box_bufsize" in the markers parameters for the next run.' +Increasing the value of "box_bufsize" in the markers parameters for the next run.', ) self.mpi_comm.Abort() @@ -2674,7 +2686,7 @@ def do_sort(self, use_numpy_argsort=False): def remove_ghost_particles(self): self.update_ghost_particles() - new_holes = np.nonzero(self.ghost_particles) + new_holes = xp.nonzero(self.ghost_particles) self._markers[new_holes] = -1.0 self.update_holes() @@ -2720,17 +2732,23 @@ def prepare_ghost_particles(self): # Mirror position for boundary condition if self.bc_sph[0] in ("mirror", "fixed"): self._mirror_particles( - "_markers_x_m", "_markers_x_p", is_domain_boundary=self.sorting_boxes.is_domain_boundary + "_markers_x_m", + "_markers_x_p", + is_domain_boundary=self.sorting_boxes.is_domain_boundary, ) if self.bc_sph[1] in ("mirror", "fixed"): self._mirror_particles( - "_markers_y_m", "_markers_y_p", is_domain_boundary=self.sorting_boxes.is_domain_boundary + "_markers_y_m", + "_markers_y_p", + is_domain_boundary=self.sorting_boxes.is_domain_boundary, ) if self.bc_sph[2] in ("mirror", "fixed"): self._mirror_particles( - "_markers_z_m", "_markers_z_p", is_domain_boundary=self.sorting_boxes.is_domain_boundary + "_markers_z_m", + "_markers_z_p", + is_domain_boundary=self.sorting_boxes.is_domain_boundary, ) ## Edges x-y @@ -2885,7 +2903,8 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 0] *= -1.0 if self.bc_sph[0] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, flat_eval=True + *arr[:, :3].T, + flat_eval=True, ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2897,7 +2916,8 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 0] = 2.0 - arr[:, 0] if self.bc_sph[0] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, flat_eval=True + *arr[:, :3].T, + flat_eval=True, ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2912,7 +2932,8 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 1] *= -1.0 if self.bc_sph[1] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, flat_eval=True + *arr[:, :3].T, + flat_eval=True, ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2924,7 +2945,8 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 1] = 2.0 - arr[:, 1] if self.bc_sph[1] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, flat_eval=True + *arr[:, :3].T, + flat_eval=True, ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2939,7 +2961,8 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 2] *= -1.0 if self.bc_sph[2] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, flat_eval=True + *arr[:, :3].T, + flat_eval=True, ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2951,7 +2974,8 @@ def _mirror_particles(self, *marker_array_names, is_domain_boundary=None): arr[:, 2] = 2.0 - arr[:, 2] if self.bc_sph[2] == "fixed" and arr_name not in self._fixed_markers_set: boundary_values = self.f_init( - *arr[:, :3].T, flat_eval=True + *arr[:, :3].T, + flat_eval=True, ) # evaluation outside of the unit cube - maybe not working for all f_init! arr[:, self.index["weights"]] = -boundary_values / self.s0( *arr[:, :3].T, @@ -2966,162 +2990,162 @@ def determine_markers_in_box(self, list_boxes): for i in list_boxes: indices += list(self._sorting_boxes._boxes[i][self._sorting_boxes._boxes[i] != -1]) - indices = np.array(indices, dtype=int) + indices = xp.array(indices, dtype=int) markers_in_box = self.markers[indices] return markers_in_box def get_destinations_box(self): """Find the destination proc for the particles to communicate for the box structure.""" - self._send_info_box = np.zeros(self.mpi_size, dtype=int) - self._send_list_box = [np.zeros((0, self.n_cols))] * self.mpi_size + self._send_info_box = xp.zeros(self.mpi_size, dtype=int) + self._send_list_box = [xp.zeros((0, self.n_cols))] * self.mpi_size # Faces # if self._x_m_proc is not None: self._send_info_box[self._x_m_proc] += len(self._markers_x_m) - self._send_list_box[self._x_m_proc] = np.concatenate((self._send_list_box[self._x_m_proc], self._markers_x_m)) + self._send_list_box[self._x_m_proc] = xp.concatenate((self._send_list_box[self._x_m_proc], self._markers_x_m)) # if self._x_p_proc is not None: self._send_info_box[self._x_p_proc] += len(self._markers_x_p) - self._send_list_box[self._x_p_proc] = np.concatenate((self._send_list_box[self._x_p_proc], self._markers_x_p)) + self._send_list_box[self._x_p_proc] = xp.concatenate((self._send_list_box[self._x_p_proc], self._markers_x_p)) # if self._y_m_proc is not None: self._send_info_box[self._y_m_proc] += len(self._markers_y_m) - self._send_list_box[self._y_m_proc] = np.concatenate((self._send_list_box[self._y_m_proc], self._markers_y_m)) + self._send_list_box[self._y_m_proc] = xp.concatenate((self._send_list_box[self._y_m_proc], self._markers_y_m)) # if self._y_p_proc is not None: self._send_info_box[self._y_p_proc] += len(self._markers_y_p) - self._send_list_box[self._y_p_proc] = np.concatenate((self._send_list_box[self._y_p_proc], self._markers_y_p)) + self._send_list_box[self._y_p_proc] = xp.concatenate((self._send_list_box[self._y_p_proc], self._markers_y_p)) # if self._z_m_proc is not None: self._send_info_box[self._z_m_proc] += len(self._markers_z_m) - self._send_list_box[self._z_m_proc] = np.concatenate((self._send_list_box[self._z_m_proc], self._markers_z_m)) + self._send_list_box[self._z_m_proc] = xp.concatenate((self._send_list_box[self._z_m_proc], self._markers_z_m)) # if self._z_p_proc is not None: self._send_info_box[self._z_p_proc] += len(self._markers_z_p) - self._send_list_box[self._z_p_proc] = np.concatenate((self._send_list_box[self._z_p_proc], self._markers_z_p)) + self._send_list_box[self._z_p_proc] = xp.concatenate((self._send_list_box[self._z_p_proc], self._markers_z_p)) # x-y edges # if self._x_m_y_m_proc is not None: self._send_info_box[self._x_m_y_m_proc] += len(self._markers_x_m_y_m) - self._send_list_box[self._x_m_y_m_proc] = np.concatenate( - (self._send_list_box[self._x_m_y_m_proc], self._markers_x_m_y_m) + self._send_list_box[self._x_m_y_m_proc] = xp.concatenate( + (self._send_list_box[self._x_m_y_m_proc], self._markers_x_m_y_m), ) # if self._x_m_y_p_proc is not None: self._send_info_box[self._x_m_y_p_proc] += len(self._markers_x_m_y_p) - self._send_list_box[self._x_m_y_p_proc] = np.concatenate( - (self._send_list_box[self._x_m_y_p_proc], self._markers_x_m_y_p) + self._send_list_box[self._x_m_y_p_proc] = xp.concatenate( + (self._send_list_box[self._x_m_y_p_proc], self._markers_x_m_y_p), ) # if self._x_p_y_m_proc is not None: self._send_info_box[self._x_p_y_m_proc] += len(self._markers_x_p_y_m) - self._send_list_box[self._x_p_y_m_proc] = np.concatenate( - (self._send_list_box[self._x_p_y_m_proc], self._markers_x_p_y_m) + self._send_list_box[self._x_p_y_m_proc] = xp.concatenate( + (self._send_list_box[self._x_p_y_m_proc], self._markers_x_p_y_m), ) # if self._x_p_y_p_proc is not None: self._send_info_box[self._x_p_y_p_proc] += len(self._markers_x_p_y_p) - self._send_list_box[self._x_p_y_p_proc] = np.concatenate( - (self._send_list_box[self._x_p_y_p_proc], self._markers_x_p_y_p) + self._send_list_box[self._x_p_y_p_proc] = xp.concatenate( + (self._send_list_box[self._x_p_y_p_proc], self._markers_x_p_y_p), ) # x-z edges # if self._x_m_z_m_proc is not None: self._send_info_box[self._x_m_z_m_proc] += len(self._markers_x_m_z_m) - self._send_list_box[self._x_m_z_m_proc] = np.concatenate( - (self._send_list_box[self._x_m_z_m_proc], self._markers_x_m_z_m) + self._send_list_box[self._x_m_z_m_proc] = xp.concatenate( + (self._send_list_box[self._x_m_z_m_proc], self._markers_x_m_z_m), ) # if self._x_m_z_p_proc is not None: self._send_info_box[self._x_m_z_p_proc] += len(self._markers_x_m_z_p) - self._send_list_box[self._x_m_z_p_proc] = np.concatenate( - (self._send_list_box[self._x_m_z_p_proc], self._markers_x_m_z_p) + self._send_list_box[self._x_m_z_p_proc] = xp.concatenate( + (self._send_list_box[self._x_m_z_p_proc], self._markers_x_m_z_p), ) # if self._x_p_z_m_proc is not None: self._send_info_box[self._x_p_z_m_proc] += len(self._markers_x_p_z_m) - self._send_list_box[self._x_p_z_m_proc] = np.concatenate( - (self._send_list_box[self._x_p_z_m_proc], self._markers_x_p_z_m) + self._send_list_box[self._x_p_z_m_proc] = xp.concatenate( + (self._send_list_box[self._x_p_z_m_proc], self._markers_x_p_z_m), ) # if self._x_p_z_p_proc is not None: self._send_info_box[self._x_p_z_p_proc] += len(self._markers_x_p_z_p) - self._send_list_box[self._x_p_z_p_proc] = np.concatenate( - (self._send_list_box[self._x_p_z_p_proc], self._markers_x_p_z_p) + self._send_list_box[self._x_p_z_p_proc] = xp.concatenate( + (self._send_list_box[self._x_p_z_p_proc], self._markers_x_p_z_p), ) # y-z edges # if self._y_m_z_m_proc is not None: self._send_info_box[self._y_m_z_m_proc] += len(self._markers_y_m_z_m) - self._send_list_box[self._y_m_z_m_proc] = np.concatenate( - (self._send_list_box[self._y_m_z_m_proc], self._markers_y_m_z_m) + self._send_list_box[self._y_m_z_m_proc] = xp.concatenate( + (self._send_list_box[self._y_m_z_m_proc], self._markers_y_m_z_m), ) # if self._y_m_z_p_proc is not None: self._send_info_box[self._y_m_z_p_proc] += len(self._markers_y_m_z_p) - self._send_list_box[self._y_m_z_p_proc] = np.concatenate( - (self._send_list_box[self._y_m_z_p_proc], self._markers_y_m_z_p) + self._send_list_box[self._y_m_z_p_proc] = xp.concatenate( + (self._send_list_box[self._y_m_z_p_proc], self._markers_y_m_z_p), ) # if self._y_p_z_m_proc is not None: self._send_info_box[self._y_p_z_m_proc] += len(self._markers_y_p_z_m) - self._send_list_box[self._y_p_z_m_proc] = np.concatenate( - (self._send_list_box[self._y_p_z_m_proc], self._markers_y_p_z_m) + self._send_list_box[self._y_p_z_m_proc] = xp.concatenate( + (self._send_list_box[self._y_p_z_m_proc], self._markers_y_p_z_m), ) # if self._y_p_z_p_proc is not None: self._send_info_box[self._y_p_z_p_proc] += len(self._markers_y_p_z_p) - self._send_list_box[self._y_p_z_p_proc] = np.concatenate( - (self._send_list_box[self._y_p_z_p_proc], self._markers_y_p_z_p) + self._send_list_box[self._y_p_z_p_proc] = xp.concatenate( + (self._send_list_box[self._y_p_z_p_proc], self._markers_y_p_z_p), ) # corners # if self._x_m_y_m_z_m_proc is not None: self._send_info_box[self._x_m_y_m_z_m_proc] += len(self._markers_x_m_y_m_z_m) - self._send_list_box[self._x_m_y_m_z_m_proc] = np.concatenate( - (self._send_list_box[self._x_m_y_m_z_m_proc], self._markers_x_m_y_m_z_m) + self._send_list_box[self._x_m_y_m_z_m_proc] = xp.concatenate( + (self._send_list_box[self._x_m_y_m_z_m_proc], self._markers_x_m_y_m_z_m), ) # if self._x_m_y_m_z_p_proc is not None: self._send_info_box[self._x_m_y_m_z_p_proc] += len(self._markers_x_m_y_m_z_p) - self._send_list_box[self._x_m_y_m_z_p_proc] = np.concatenate( - (self._send_list_box[self._x_m_y_m_z_p_proc], self._markers_x_m_y_m_z_p) + self._send_list_box[self._x_m_y_m_z_p_proc] = xp.concatenate( + (self._send_list_box[self._x_m_y_m_z_p_proc], self._markers_x_m_y_m_z_p), ) # if self._x_m_y_p_z_m_proc is not None: self._send_info_box[self._x_m_y_p_z_m_proc] += len(self._markers_x_m_y_p_z_m) - self._send_list_box[self._x_m_y_p_z_m_proc] = np.concatenate( - (self._send_list_box[self._x_m_y_p_z_m_proc], self._markers_x_m_y_p_z_m) + self._send_list_box[self._x_m_y_p_z_m_proc] = xp.concatenate( + (self._send_list_box[self._x_m_y_p_z_m_proc], self._markers_x_m_y_p_z_m), ) # if self._x_m_y_p_z_p_proc is not None: self._send_info_box[self._x_m_y_p_z_p_proc] += len(self._markers_x_m_y_p_z_p) - self._send_list_box[self._x_m_y_p_z_p_proc] = np.concatenate( - (self._send_list_box[self._x_m_y_p_z_p_proc], self._markers_x_m_y_p_z_p) + self._send_list_box[self._x_m_y_p_z_p_proc] = xp.concatenate( + (self._send_list_box[self._x_m_y_p_z_p_proc], self._markers_x_m_y_p_z_p), ) # if self._x_p_y_m_z_m_proc is not None: self._send_info_box[self._x_p_y_m_z_m_proc] += len(self._markers_x_p_y_m_z_m) - self._send_list_box[self._x_p_y_m_z_m_proc] = np.concatenate( - (self._send_list_box[self._x_p_y_m_z_m_proc], self._markers_x_p_y_m_z_m) + self._send_list_box[self._x_p_y_m_z_m_proc] = xp.concatenate( + (self._send_list_box[self._x_p_y_m_z_m_proc], self._markers_x_p_y_m_z_m), ) # if self._x_p_y_m_z_p_proc is not None: self._send_info_box[self._x_p_y_m_z_p_proc] += len(self._markers_x_p_y_m_z_p) - self._send_list_box[self._x_p_y_m_z_p_proc] = np.concatenate( - (self._send_list_box[self._x_p_y_m_z_p_proc], self._markers_x_p_y_m_z_p) + self._send_list_box[self._x_p_y_m_z_p_proc] = xp.concatenate( + (self._send_list_box[self._x_p_y_m_z_p_proc], self._markers_x_p_y_m_z_p), ) # if self._x_p_y_p_z_m_proc is not None: self._send_info_box[self._x_p_y_p_z_m_proc] += len(self._markers_x_p_y_p_z_m) - self._send_list_box[self._x_p_y_p_z_m_proc] = np.concatenate( - (self._send_list_box[self._x_p_y_p_z_m_proc], self._markers_x_p_y_p_z_m) + self._send_list_box[self._x_p_y_p_z_m_proc] = xp.concatenate( + (self._send_list_box[self._x_p_y_p_z_m_proc], self._markers_x_p_y_p_z_m), ) # if self._x_p_y_p_z_p_proc is not None: self._send_info_box[self._x_p_y_p_z_p_proc] += len(self._markers_x_p_y_p_z_p) - self._send_list_box[self._x_p_y_p_z_p_proc] = np.concatenate( - (self._send_list_box[self._x_p_y_p_z_p_proc], self._markers_x_p_y_p_z_p) + self._send_list_box[self._x_p_y_p_z_p_proc] = xp.concatenate( + (self._send_list_box[self._x_p_y_p_z_p_proc], self._markers_x_p_y_p_z_p), ) def self_communication_boxes(self): @@ -3130,14 +3154,14 @@ def self_communication_boxes(self): if self._send_info_box[self.mpi_rank] > 0: self.update_holes() - holes_inds = np.nonzero(self.holes)[0] + holes_inds = xp.nonzero(self.holes)[0] if holes_inds.size < self._send_info_box[self.mpi_rank]: warnings.warn( f'Strong load imbalance detected: \ number of holes ({holes_inds.size}) on rank {self.mpi_rank} \ is smaller than number of incoming particles ({self._send_info_box[self.mpi_rank]}). \ -Increasing the value of "bufsize" in the markers parameters for the next run.' +Increasing the value of "bufsize" in the markers parameters for the next run.', ) self.mpi_comm.Abort() @@ -3152,16 +3176,16 @@ def self_communication_boxes(self): # self.update_holes() # self.update_ghost_particles() # self.update_valid_mks() - # holes_inds = np.nonzero(self.holes)[0] + # holes_inds = xp.nonzero(self.holes)[0] - self.markers[holes_inds[np.arange(self._send_info_box[self.mpi_rank])]] = self._send_list_box[self.mpi_rank] + self.markers[holes_inds[xp.arange(self._send_info_box[self.mpi_rank])]] = self._send_list_box[self.mpi_rank] @profile def communicate_boxes(self, verbose=False): # if verbose: - # n_valid = np.count_nonzero(self.valid_mks) - # n_holes = np.count_nonzero(self.holes) - # n_ghosts = np.count_nonzero(self.ghost_particles) + # n_valid = xp.count_nonzero(self.valid_mks) + # n_holes = xp.count_nonzero(self.holes) + # n_ghosts = xp.count_nonzero(self.ghost_particles) # print(f"before communicate_boxes: {self.mpi_rank = }, {n_valid = } {n_holes = }, {n_ghosts = }") self.prepare_ghost_particles() @@ -3169,16 +3193,16 @@ def communicate_boxes(self, verbose=False): self.self_communication_boxes() self.update_holes() if self.mpi_comm is not None: - self.mpi_comm.Barrier() + self._Barrier() self.sendrecv_all_to_all_boxes() self.sendrecv_markers_boxes() self.update_holes() self.update_ghost_particles() # if verbose: - # n_valid = np.count_nonzero(self.valid_mks) - # n_holes = np.count_nonzero(self.holes) - # n_ghosts = np.count_nonzero(self.ghost_particles) + # n_valid = xp.count_nonzero(self.valid_mks) + # n_holes = xp.count_nonzero(self.holes) + # n_ghosts = xp.count_nonzero(self.ghost_particles) # print(f"after communicate_boxes: {self.mpi_rank = }, {n_valid = }, {n_holes = }, {n_ghosts = }") def sendrecv_all_to_all_boxes(self): @@ -3187,7 +3211,7 @@ def sendrecv_all_to_all_boxes(self): for the communication of particles in boundary boxes. """ - self._recv_info_box = np.zeros(self.mpi_comm.Get_size(), dtype=int) + self._recv_info_box = xp.zeros(self.mpi_comm.Get_size(), dtype=int) self.mpi_comm.Alltoall(self._send_info_box, self._recv_info_box) @@ -3198,8 +3222,8 @@ def sendrecv_markers_boxes(self): """ # i-th entry holds the number (not the index) of the first hole to be filled by data from process i - first_hole = np.cumsum(self._recv_info_box) - self._recv_info_box - hole_inds = np.nonzero(self._holes)[0] + first_hole = xp.cumsum(self._recv_info_box) - self._recv_info_box + hole_inds = xp.nonzero(self._holes)[0] # Initialize send and receive commands reqs = [] recvbufs = [] @@ -3210,7 +3234,7 @@ def sendrecv_markers_boxes(self): else: self.mpi_comm.Isend(data, dest=i, tag=self.mpi_comm.Get_rank()) - recvbufs += [np.zeros((N_recv, self._markers.shape[1]), dtype=float)] + recvbufs += [xp.zeros((N_recv, self._markers.shape[1]), dtype=float)] reqs += [self.mpi_comm.Irecv(recvbufs[-1], source=i, tag=i)] # Wait for buffer, then put markers into holes @@ -3228,17 +3252,17 @@ def sendrecv_markers_boxes(self): f'Strong load imbalance detected: \ number of holes ({hole_inds.size}) on rank {self.mpi_rank} \ is smaller than number of incoming particles ({first_hole[i] + self._recv_info_box[i]}). \ -Increasing the value of "bufsize" in the markers parameters for the next run.' +Increasing the value of "bufsize" in the markers parameters for the next run.', ) self.mpi_comm.Abort() # exit() - self._markers[hole_inds[first_hole[i] + np.arange(self._recv_info_box[i])]] = recvbufs[i] + self._markers[hole_inds[first_hole[i] + xp.arange(self._recv_info_box[i])]] = recvbufs[i] test_reqs.pop() reqs[i] = None - self.mpi_comm.Barrier() + self._Barrier() def _get_neighbouring_proc(self): """Find the neighbouring processes for the sending of boxes. @@ -3708,11 +3732,11 @@ def eval_density( def eval_sph( self, - eta1: np.ndarray, - eta2: np.ndarray, - eta3: np.ndarray, + eta1: xp.ndarray, + eta2: xp.ndarray, + eta3: xp.ndarray, index: int, - out: np.ndarray = None, + out: xp.ndarray = None, fast: bool = True, kernel_type: str = "gaussian_1d", derivative: int = "0", @@ -3758,12 +3782,12 @@ def eval_sph( h1, h2, h3 : float Radius of the smoothing kernel in each dimension. """ - _shp = np.shape(eta1) - assert _shp == np.shape(eta2) == np.shape(eta3) + _shp = xp.shape(eta1) + assert _shp == xp.shape(eta2) == xp.shape(eta3) if out is not None: - assert _shp == np.shape(out) + assert _shp == xp.shape(out) else: - out = np.zeros_like(eta1) + out = xp.zeros_like(eta1) assert derivative in {0, 1, 2, 3}, f"derivative must be 0, 1, 2 or 3, but is {derivative}." @@ -3777,7 +3801,7 @@ def eval_sph( self.put_particles_in_boxes() if len(_shp) == 1: - func = box_based_evaluation_flat + func = Pyccelkernel(box_based_evaluation_flat) elif len(_shp) == 3: if _shp[0] > 1: assert eta1[0, 0, 0] != eta1[1, 0, 0], "Meshgrids must be obtained with indexing='ij'!" @@ -3785,7 +3809,7 @@ def eval_sph( assert eta2[0, 0, 0] != eta2[0, 1, 0], "Meshgrids must be obtained with indexing='ij'!" if _shp[2] > 1: assert eta3[0, 0, 0] != eta3[0, 0, 1], "Meshgrids must be obtained with indexing='ij'!" - func = box_based_evaluation_meshgrid + func = Pyccelkernel(box_based_evaluation_meshgrid) func( self.args_markers, @@ -3811,9 +3835,9 @@ def eval_sph( ) else: if len(_shp) == 1: - func = naive_evaluation_flat + func = Pyccelkernel(naive_evaluation_flat) elif len(_shp) == 3: - func = naive_evaluation_meshgrid + func = Pyccelkernel(naive_evaluation_meshgrid) func( self.args_markers, eta1, @@ -3846,7 +3870,7 @@ def update_ghost_particles(self): def sendrecv_determine_mtbs( self, - alpha: list | tuple | np.ndarray = (1.0, 1.0, 1.0), + alpha: list | tuple | xp.ndarray = (1.0, 1.0, 1.0), ): """ Determine which markers have to be sent from current process and put them in a new array. @@ -3868,34 +3892,34 @@ def sendrecv_determine_mtbs( Eta-values of shape (n_send, :) according to which the sorting is performed. """ # position that determines the sorting (including periodic shift of boundary conditions) - if not isinstance(alpha, np.ndarray): - alpha = np.array(alpha, dtype=float) + if not isinstance(alpha, xp.ndarray): + alpha = xp.array(alpha, dtype=float) assert alpha.size == 3 - assert np.all(alpha >= 0.0) and np.all(alpha <= 1.0) + assert xp.all(alpha >= 0.0) and xp.all(alpha <= 1.0) bi = self.first_pusher_idx - self._sorting_etas = np.mod( + self._sorting_etas = xp.mod( alpha * (self.markers[:, :3] + self.markers[:, bi + 3 + self.vdim : bi + 3 + self.vdim + 3]) + (1.0 - alpha) * self.markers[:, bi : bi + 3], 1.0, ) # check which particles are on the current process domain - self._is_on_proc_domain = np.logical_and( + self._is_on_proc_domain = xp.logical_and( self._sorting_etas > self.domain_array[self.mpi_rank, 0::3], self._sorting_etas < self.domain_array[self.mpi_rank, 1::3], ) # to stay on the current process, all three columns must be True - self._can_stay = np.all(self._is_on_proc_domain, axis=1) + self._can_stay = xp.all(self._is_on_proc_domain, axis=1) # holes and ghosts can stay, too self._can_stay[self.holes] = True self._can_stay[self.ghost_particles] = True # True values can stay on the process, False must be sent, already empty rows (-1) cannot be sent - send_inds = np.nonzero(~self._can_stay)[0] + send_inds = xp.nonzero(~self._can_stay)[0] - hole_inds_after_send = np.nonzero(np.logical_or(~self._can_stay, self.holes))[0] + hole_inds_after_send = xp.nonzero(xp.logical_or(~self._can_stay, self.holes))[0] return hole_inds_after_send, send_inds @@ -3914,16 +3938,16 @@ def sendrecv_get_destinations(self, send_inds): """ # One entry for each process - send_info = np.zeros(self.mpi_size, dtype=int) + send_info = xp.zeros(self.mpi_size, dtype=int) # TODO: do not loop over all processes, start with neighbours and work outwards (using while) for i in range(self.mpi_size): - conds = np.logical_and( + conds = xp.logical_and( self._sorting_etas[send_inds] > self.domain_array[i, 0::3], self._sorting_etas[send_inds] < self.domain_array[i, 1::3], ) - self._send_to_i[i] = np.nonzero(np.all(conds, axis=1))[0] + self._send_to_i[i] = xp.nonzero(xp.all(conds, axis=1))[0] send_info[i] = self._send_to_i[i].size self._send_list[i] = self.markers[send_inds][self._send_to_i[i]] @@ -3945,7 +3969,7 @@ def sendrecv_all_to_all(self, send_info): Amount of marticles to be received from i-th process. """ - recv_info = np.zeros(self.mpi_size, dtype=int) + recv_info = xp.zeros(self.mpi_size, dtype=int) self.mpi_comm.Alltoall(send_info, recv_info) @@ -3965,7 +3989,7 @@ def sendrecv_markers(self, recv_info, hole_inds_after_send): """ # i-th entry holds the number (not the index) of the first hole to be filled by data from process i - first_hole = np.cumsum(recv_info) - recv_info + first_hole = xp.cumsum(recv_info) - recv_info # Initialize send and receive commands for i, (data, N_recv) in enumerate(zip(self._send_list, list(recv_info))): @@ -3975,7 +3999,7 @@ def sendrecv_markers(self, recv_info, hole_inds_after_send): else: self.mpi_comm.Isend(data, dest=i, tag=self.mpi_rank) - self._recvbufs[i] = np.zeros((N_recv, self.markers.shape[1]), dtype=float) + self._recvbufs[i] = xp.zeros((N_recv, self.markers.shape[1]), dtype=float) self._reqs[i] = self.mpi_comm.Irecv(self._recvbufs[i], source=i, tag=i) # Wait for buffer, then put markers into holes @@ -3993,16 +4017,16 @@ def sendrecv_markers(self, recv_info, hole_inds_after_send): f'Strong load imbalance detected: \ number of holes ({hole_inds_after_send.size}) on rank {self.mpi_rank} \ is smaller than number of incoming particles ({first_hole[i] + recv_info[i]}). \ -Increasing the value of "bufsize" in the markers parameters for the next run.' +Increasing the value of "bufsize" in the markers parameters for the next run.', ) self.mpi_comm.Abort() - self.markers[hole_inds_after_send[first_hole[i] + np.arange(recv_info[i])]] = self._recvbufs[i] + self.markers[hole_inds_after_send[first_hole[i] + xp.arange(recv_info[i])]] = self._recvbufs[i] test_reqs.pop() self._reqs[i] = None - def _gather_scalar_in_subcomm_array(self, scalar: int, out: np.ndarray = None): + def _gather_scalar_in_subcomm_array(self, scalar: int, out: xp.ndarray = None): """Return an array of length sub_comm.size, where the i-th entry corresponds to the value of the scalar on process i. @@ -4011,11 +4035,11 @@ def _gather_scalar_in_subcomm_array(self, scalar: int, out: np.ndarray = None): scalar : int The scalar value on each process. - out : np.ndarray + out : xp.ndarray The returned array (optional). """ if out is None: - _tmp = np.zeros(self.mpi_size, dtype=int) + _tmp = xp.zeros(self.mpi_size, dtype=int) else: assert out.size == self.mpi_size _tmp = out @@ -4030,7 +4054,7 @@ def _gather_scalar_in_subcomm_array(self, scalar: int, out: np.ndarray = None): return _tmp - def _gather_scalar_in_intercomm_array(self, scalar: int, out: np.ndarray = None): + def _gather_scalar_in_intercomm_array(self, scalar: int, out: xp.ndarray = None): """Return an array of length inter_comm.size, where the i-th entry corresponds to the value of the scalar on clone i. @@ -4039,11 +4063,11 @@ def _gather_scalar_in_intercomm_array(self, scalar: int, out: np.ndarray = None) scalar : int The scalar value on each clone. - out : np.ndarray + out : xp.ndarray The returned array (optional). """ if out is None: - _tmp = np.zeros(self.num_clones, dtype=int) + _tmp = xp.zeros(self.num_clones, dtype=int) else: assert out.size == self.num_clones _tmp = out @@ -4072,7 +4096,7 @@ class Tesselation: comm : Intracomm MPI communicator. - domain_array : np.ndarray + domain_array : xp.ndarray A 2d array[float] of shape (comm.Get_size(), 9) holding info on the domain decomposition. sorting_boxes : Particles.SortingBoxes @@ -4084,7 +4108,7 @@ def __init__( tiles_pb: int | float, *, comm: Intracomm = None, - domain_array: np.ndarray = None, + domain_array: xp.ndarray = None, sorting_boxes: Particles.SortingBoxes = None, ): if isinstance(tiles_pb, int): @@ -4102,8 +4126,8 @@ def __init__( assert domain_array is not None if domain_array is None: - self._starts = np.zeros(3) - self._ends = np.ones(3) + self._starts = xp.zeros(3) + self._ends = xp.ones(3) else: self._starts = domain_array[self.rank, 0::3] self._ends = domain_array[self.rank, 1::3] @@ -4126,9 +4150,9 @@ def __init__( if n_boxes == 1: self._dims_mask = [True] * 3 else: - self._dims_mask = np.array(self.boxes_per_dim) > 1 + self._dims_mask = xp.array(self.boxes_per_dim) > 1 - min_tiles = 2 ** np.count_nonzero(self.dims_mask) + min_tiles = 2 ** xp.count_nonzero(self.dims_mask) assert self.tiles_pb >= min_tiles, ( f"At least {min_tiles} tiles per sorting box is enforced, but you have {self.tiles_pb}!" ) @@ -4151,19 +4175,19 @@ def get_tiles(self): # print(f'{self.dims_mask = }') # tiles in one sorting box - self._nt_per_dim = np.array([1, 1, 1]) - _ids = np.nonzero(self._dims_mask)[0] + self._nt_per_dim = xp.array([1, 1, 1]) + _ids = xp.nonzero(self._dims_mask)[0] for fac in factors_vec: _nt = self.nt_per_dim[self._dims_mask] - d = _ids[np.argmin(_nt)] + d = _ids[xp.argmin(_nt)] self._nt_per_dim[d] *= fac # print(f'{_nt = }, {d = }, {self.nt_per_dim = }') - assert np.prod(self.nt_per_dim) == self.tiles_pb + assert xp.prod(self.nt_per_dim) == self.tiles_pb # tiles between [0, box_width] in each direction - self._tile_breaks = [np.linspace(0.0, bw, nt + 1) for bw, nt in zip(self.box_widths, self.nt_per_dim)] - self._tile_midpoints = [(np.roll(tbs, -1)[:-1] + tbs[:-1]) / 2 for tbs in self.tile_breaks] + self._tile_breaks = [xp.linspace(0.0, bw, nt + 1) for bw, nt in zip(self.box_widths, self.nt_per_dim)] + self._tile_midpoints = [(xp.roll(tbs, -1)[:-1] + tbs[:-1]) / 2 for tbs in self.tile_breaks] self._tile_volume = 1.0 for tb in self.tile_breaks: self._tile_volume *= tb[1] @@ -4171,8 +4195,8 @@ def get_tiles(self): def draw_markers(self): """Draw markers on the tile midpoints.""" _, eta1 = self._tile_output_arrays() - eta2 = np.zeros_like(eta1) - eta3 = np.zeros_like(eta1) + eta2 = xp.zeros_like(eta1) + eta3 = xp.zeros_like(eta1) nt_x, nt_y, nt_z = self.nt_per_dim @@ -4183,7 +4207,7 @@ def draw_markers(self): for k in range(self.boxes_per_dim[2]): z_midpoints = self._get_midpoints(k, 2) - xx, yy, zz = np.meshgrid( + xx, yy, zz = xp.meshgrid( x_midpoints, y_midpoints, z_midpoints, @@ -4220,7 +4244,7 @@ def _get_quad_pts(self, n_quad=None): self._tile_quad_pts = [] self._tile_quad_wts = [] for nq, tb in zip(n_quad, self.tile_breaks): - pts_loc, wts_loc = np.polynomial.legendre.leggauss(nq) + pts_loc, wts_loc = xp.polynomial.legendre.leggauss(nq) pts, wts = quadrature_grid(tb[:2], pts_loc, wts_loc) self._tile_quad_pts += [pts[0]] self._tile_quad_wts += [wts[0]] @@ -4247,7 +4271,7 @@ def cell_averages(self, fun, n_quad=None): for k in range(self.boxes_per_dim[2]): z_pts = self._get_box_quad_pts(k, 2) - xx, yy, zz = np.meshgrid( + xx, yy, zz = xp.meshgrid( x_pts.flatten(), y_pts.flatten(), z_pts.flatten(), @@ -4276,9 +4300,9 @@ def _tile_output_arrays(self): * the first with one entry for each tile on one sorting box * the second with one entry for each tile on current process """ - # self._quad_pts = [np.zeros((nt, nq)).flatten() for nt, nq in zip(self.nt_per_dim, self.tile_quad_pts)] - single_box_out = np.zeros(self.nt_per_dim) - out = np.tile(single_box_out, self.boxes_per_dim) + # self._quad_pts = [xp.zeros((nt, nq)).flatten() for nt, nq in zip(self.nt_per_dim, self.tile_quad_pts)] + single_box_out = xp.zeros(self.nt_per_dim) + out = xp.tile(single_box_out, self.boxes_per_dim) return single_box_out, out def _get_midpoints(self, i: int, dim: int): @@ -4299,13 +4323,13 @@ def _get_box_quad_pts(self, i: int, dim: int): Returns ------- - x_pts : np.array + x_pts : xp.array 2d array of shape (n_tiles_pb, n_tile_quad_pts) """ xl = self.starts[dim] + i * self.box_widths[dim] x_tile_breaks = xl + self.tile_breaks[dim][:-1] x_tile_pts = self.tile_quad_pts[dim] - x_pts = np.tile(x_tile_breaks, (x_tile_pts.size, 1)).T + x_tile_pts + x_pts = xp.tile(x_tile_breaks, (x_tile_pts.size, 1)).T + x_tile_pts return x_pts @property diff --git a/src/struphy/pic/particles.py b/src/struphy/pic/particles.py index 35df68d04..6c818b3ee 100644 --- a/src/struphy/pic/particles.py +++ b/src/struphy/pic/particles.py @@ -1,6 +1,6 @@ import copy -import numpy as np +import cunumpy as xp from struphy.fields_background import equils from struphy.fields_background.base import FluidEquilibrium, FluidEquilibriumWithB @@ -142,7 +142,7 @@ def s0(self, eta1, eta2, eta3, *v, flat_eval=False, remove_holes=True): The 0-form sampling density. ------- """ - assert self.domain, f"self.domain must be set to call the sampling density 0-form." + assert self.domain, "self.domain must be set to call the sampling density 0-form." return self.domain.transform( self.svol(eta1, eta2, eta3, *v), @@ -219,9 +219,11 @@ def save_constants_of_motion(self): # send particles to the guiding center positions self.markers[~self.holes, self.first_pusher_idx : self.first_pusher_idx + 3] = self.markers[ - ~self.holes, slice_gc + ~self.holes, + slice_gc, ] - self.mpi_sort_markers(alpha=1) + if self.mpi_comm is not None: + self.mpi_sort_markers(alpha=1) utilities_kernels.eval_canonical_toroidal_moment_6d( self.markers, @@ -234,7 +236,8 @@ def save_constants_of_motion(self): ) # send back and clear buffer - self.mpi_sort_markers() + if self.mpi_comm is not None: + self.mpi_sort_markers() self.markers[~self.holes, self.first_pusher_idx : self.first_pusher_idx + 3] = 0 @@ -350,6 +353,7 @@ def __init__( self._unit_b1_h = self.projected_equil.unit_b1 self._derham = self.projected_equil.derham + self._tmp0 = self.derham.Vh["0"].zeros() self._tmp2 = self.derham.Vh["2"].zeros() @property @@ -559,7 +563,7 @@ def save_constants_of_motion(self): self.absB0_h._data, ) - def save_magnetic_energy(self, b2): + def save_magnetic_energy(self, PBb): r""" Calculate magnetic field energy at each particles' position and assign it into markers[:,self.first_diagnostics_idx]. @@ -570,22 +574,17 @@ def save_magnetic_energy(self, b2): Finite element coefficients of the time-dependent magnetic field. """ - E2T = self.derham.extraction_ops["2"].transpose() - b2t = E2T.dot(b2, out=self._tmp2) - b2t.update_ghost_regions() + E0T = self.derham.extraction_ops["0"].transpose() + PBbt = E0T.dot(PBb, out=self._tmp0) + PBbt.update_ghost_regions() - utilities_kernels.eval_magnetic_energy( + utilities_kernels.eval_magnetic_energy_PBb( self.markers, self.derham.args_derham, self.domain.args_domain, self.first_diagnostics_idx, self.absB0_h._data, - self.unit_b1_h[0]._data, - self.unit_b1_h[1]._data, - self.unit_b1_h[2]._data, - b2t[0]._data, - b2t[1]._data, - b2t[2]._data, + PBbt._data, ) def save_magnetic_background_energy(self): @@ -647,8 +646,8 @@ class Particles3D(Particles): """ @classmethod - def default_bckgr_params(cls): - return {"ColdPlasma": {}} + def default_background(cls): + return maxwellians.ColdPlasma() def __init__( self, @@ -656,8 +655,10 @@ def __init__( ): kwargs["type"] = "full_f" - if "bckgr_params" not in kwargs: - kwargs["bckgr_params"] = self.default_bckgr_params() + if "background" not in kwargs: + kwargs["background"] = self.default_background() + elif kwargs["background"] is None: + kwargs["background"] = self.default_background() # default number of diagnostics and auxiliary columns self._n_cols_diagnostics = kwargs.pop("n_cols_diagn", 0) diff --git a/src/struphy/pic/pushing/pusher.py b/src/struphy/pic/pushing/pusher.py index acaae39c2..14c756b31 100644 --- a/src/struphy/pic/pushing/pusher.py +++ b/src/struphy/pic/pushing/pusher.py @@ -1,12 +1,13 @@ "Accelerated particle pushing." -import numpy as np +import cunumpy as xp from line_profiler import profile -from mpi4py.MPI import IN_PLACE, SUM +from psydac.ddm.mpi import mpi as MPI from struphy.kernel_arguments.pusher_args_kernels import DerhamArguments, DomainArguments from struphy.pic.base import Particles from struphy.profiling.profiling import ProfileManager +from struphy.utils.pyccel import Pyccelkernel class Pusher: @@ -98,7 +99,7 @@ class Pusher: def __init__( self, particles: Particles, - kernel, + kernel: Pyccelkernel, args_kernel: tuple, args_domain: DomainArguments, *, @@ -112,8 +113,9 @@ def __init__( verbose: bool = False, ): self._particles = particles + assert isinstance(kernel, Pyccelkernel), f"{kernel} is not of type Pyccelkernel" self._kernel = kernel - self._newton = "newton" in kernel.__name__ + self._newton = "newton" in kernel.name self._args_kernel = args_kernel self._args_domain = args_domain @@ -132,9 +134,9 @@ def __init__( comps = ker_args[2] # check marker array column number - assert isinstance(comps, np.ndarray) + assert isinstance(comps, xp.ndarray) assert column_nr + comps.size < particles.n_cols, ( - f"{column_nr + comps.size} not smaller than {particles.n_cols = }; not enough columns in marker array !!" + f"{column_nr + comps.size} not smaller than {particles.n_cols =}; not enough columns in marker array !!" ) # prepare and check eval_kernels @@ -144,18 +146,15 @@ def __init__( comps = ker_args[3] # check marker array column number - assert isinstance(comps, np.ndarray) + assert isinstance(comps, xp.ndarray) assert column_nr + comps.size < particles.n_cols, ( - f"{column_nr + comps.size} not smaller than {particles.n_cols = }; not enough columns in marker array !!" + f"{column_nr + comps.size} not smaller than {particles.n_cols =}; not enough columns in marker array !!" ) self._init_kernels = init_kernels self._eval_kernels = eval_kernels - self._mpi_sum = SUM - self._mpi_in_place = IN_PLACE - - self._residuals = np.zeros(self.particles.markers.shape[0]) + self._residuals = xp.zeros(self.particles.markers.shape[0]) self._converged_loc = self._residuals == 1.0 self._not_converged_loc = self._residuals == 0.0 @@ -179,10 +178,10 @@ def __call__(self, dt: float): residual_idx = self.particles.residual_idx if self.verbose: - print(f"{first_pusher_idx = }") - print(f"{first_shift_idx = }") - print(f"{residual_idx = }") - print(f"{self.particles.n_cols = }") + print(f"{first_pusher_idx =}") + print(f"{first_shift_idx =}") + print(f"{residual_idx =}") + print(f"{self.particles.n_cols =}") init_slice = slice(first_pusher_idx, first_shift_idx) shift_slice = slice(first_shift_idx, residual_idx) @@ -210,7 +209,7 @@ def __call__(self, dt: float): add_args = ker_args[3] ker( - np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + xp.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), column_nr, comps, self.particles.args_markers, @@ -225,14 +224,14 @@ def __call__(self, dt: float): # start stages (e.g. n_stages=4 for RK4) for stage in range(self.n_stages): # start iteration (maxiter=1 for explicit schemes) - n_not_converged = np.empty(1, dtype=int) + n_not_converged = xp.empty(1, dtype=int) n_not_converged[0] = self.particles.n_mks_loc k = 0 if self.verbose and self.maxiter > 1: max_res = 1.0 print( - f"rank {rank}: {k = }, tol: {self._tol}, {n_not_converged[0] = }, {max_res = }", + f"rank {rank}: {k =}, tol: {self._tol}, {n_not_converged[0] =}, {max_res =}", ) if self.particles.mpi_comm is not None: self.particles.mpi_comm.Barrier() @@ -280,7 +279,7 @@ def __call__(self, dt: float): ) # push markers - with ProfileManager.profile_region("kernel: " + self.kernel.__name__): + with ProfileManager.profile_region("kernel: " + self.kernel.name): self.kernel( dt, stage, @@ -299,27 +298,27 @@ def __call__(self, dt: float): # compute number of non-converged particles (maxiter=1 for explicit schemes) if self.maxiter > 1: self._residuals[:] = markers[:, residual_idx] - max_res = np.max(self._residuals) + max_res = xp.max(self._residuals) if max_res < 0.0: max_res = None self._converged_loc[:] = self._residuals < self._tol self._not_converged_loc[:] = ~self._converged_loc - n_not_converged[0] = np.count_nonzero( + n_not_converged[0] = xp.count_nonzero( self._not_converged_loc, ) if self.verbose: print( - f"rank {rank}: {k = }, tol: {self._tol}, {n_not_converged[0] = }, {max_res = }", + f"rank {rank}: {k =}, tol: {self._tol}, {n_not_converged[0] =}, {max_res =}", ) if self.particles.mpi_comm is not None: self.particles.mpi_comm.Barrier() if self.particles.mpi_comm is not None: self.particles.mpi_comm.Allreduce( - self._mpi_in_place, + MPI.IN_PLACE, n_not_converged, - op=self._mpi_sum, + op=MPI.SUM, ) # take converged markers out of the loop @@ -330,7 +329,7 @@ def __call__(self, dt: float): if self.maxiter > 1: rank = self.particles.mpi_rank print( - f"rank {rank}: {k = }, maxiter={self.maxiter} reached! tol: {self._tol}, {n_not_converged[0] = }, {max_res = }", + f"rank {rank}: {k =}, maxiter={self.maxiter} reached! tol: {self._tol}, {n_not_converged[0] =}, {max_res =}", ) # sort markers according to domain decomposition if self.mpi_sort == "each": diff --git a/src/struphy/pic/pushing/pusher_kernels.py b/src/struphy/pic/pushing/pusher_kernels.py index 429e1c722..47b6a71ba 100644 --- a/src/struphy/pic/pushing/pusher_kernels.py +++ b/src/struphy/pic/pushing/pusher_kernels.py @@ -1615,6 +1615,15 @@ def push_bxu_Hdiv_pauli( # -- removed omp: #$ omp end parallel +@stack_array( + "dfm", + "dfinv", + "dfinv_t", + "e", + "e_cart", + "GXu", + "v", +) def push_pc_GXu_full( dt: float, stage: int, @@ -1630,7 +1639,6 @@ def push_pc_GXu_full( GXu_31: "float[:,:,:]", GXu_32: "float[:,:,:]", GXu_33: "float[:,:,:]", - boundary_cut: "float", ): r"""Updates @@ -1671,10 +1679,6 @@ def push_pc_GXu_full( if markers[ip, 0] == -1.0: continue - # boundary cut - if markers[ip, 0] < boundary_cut or markers[ip, 0] > 1.0 - boundary_cut: - continue - eta1 = markers[ip, 0] eta2 = markers[ip, 1] eta3 = markers[ip, 2] @@ -1740,6 +1744,15 @@ def push_pc_GXu_full( markers[ip, 3:6] -= dt * e_cart / 2.0 +@stack_array( + "dfm", + "dfinv", + "dfinv_t", + "e", + "e_cart", + "GXu", + "v", +) def push_pc_GXu( dt: float, stage: int, @@ -1755,7 +1768,6 @@ def push_pc_GXu( GXu_31: "float[:,:,:]", GXu_32: "float[:,:,:]", GXu_33: "float[:,:,:]", - boundary_cut: "float", ): r"""Updates @@ -1783,7 +1795,6 @@ def push_pc_GXu( e = empty(3, dtype=float) e_cart = empty(3, dtype=float) GXu = empty((3, 3), dtype=float) - GXu_t = empty((3, 3), dtype=float) # particle velocity v = empty(3, dtype=float) @@ -1797,10 +1808,6 @@ def push_pc_GXu( if markers[ip, 0] == -1.0: continue - # boundary cut - if markers[ip, 0] < boundary_cut or markers[ip, 0] > 1.0 - boundary_cut: - continue - eta1 = markers[ip, 0] eta2 = markers[ip, 1] eta3 = markers[ip, 2] @@ -1939,7 +1946,7 @@ def push_eta_stage( @stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v", "k_u") -def push_pc_eta_rk4_Hcurl_full( +def push_pc_eta_stage_Hcurl( dt: float, stage: int, args_markers: "MarkerArguments", @@ -1948,6 +1955,10 @@ def push_pc_eta_rk4_Hcurl_full( u_1: "float[:,:,:]", u_2: "float[:,:,:]", u_3: "float[:,:,:]", + use_perp_model: "bool", + a: "float[:]", + b: "float[:]", + c: "float[:]", ): r"""Fourth order Runge-Kutta solve of @@ -1960,14 +1971,6 @@ def push_pc_eta_rk4_Hcurl_full( .. math:: \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. - - Parameters - ---------- - u_1, u_2, u_3: array[float] - 3d array of FE coeffs of U-field, either as 1-form or as 2-form. - - u_basis : int - U is 1-form (u_basis=1) or a 2-form (u_basis=2). """ # allocate metric coeffs @@ -1993,22 +1996,13 @@ def push_pc_eta_rk4_Hcurl_full( first_init_idx = args_markers.first_init_idx first_free_idx = args_markers.first_free_idx - # assign factor of k for each stage - if stage == 0 or stage == 3: - nk = 1.0 - else: - nk = 2.0 + # get number of stages + n_stages = shape(b)[0] - # which stage - if stage == 3: + if stage == n_stages - 1: last = 1.0 - cont = 0.0 - elif stage == 2: - last = 0.0 - cont = 2.0 else: last = 0.0 - cont = 1.0 for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) @@ -2053,396 +2047,8 @@ def push_pc_eta_rk4_Hcurl_full( u, ) - # transform to vector field - linalg_kernels.matrix_vector(ginv, u, k_u) - - # sum contribs - k[:] = k_v + k_u - - # accum k - markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 - - # update markers for the next stage - markers[ip, 0:3] = ( - markers[ip, first_init_idx : first_init_idx + 3] - + dt * k / 2 * cont - + dt * markers[ip, first_free_idx : first_free_idx + 3] * last - ) - - -@stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v", "k_u") -def push_pc_eta_rk4_Hdiv_full( - dt: float, - stage: int, - args_markers: "MarkerArguments", - args_domain: "DomainArguments", - args_derham: "DerhamArguments", - u_1: "float[:,:,:]", - u_2: "float[:,:,:]", - u_3: "float[:,:,:]", -): - r"""Fourth order Runge-Kutta solve of - - .. math:: - - \frac{\textnormal d \boldsymbol \eta_p(t)}{\textnormal d t} = DF^{-1}(\boldsymbol \eta_p(t)) \mathbf v + \textnormal{vec}( \hat{\mathbf U}^{1(2)}) - - for each marker :math:`p` in markers array, where :math:`\mathbf v` is constant and - - .. math:: - - \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. - - Parameters - ---------- - u_1, u_2, u_3: array[float] - 3d array of FE coeffs of U-field, either as 1-form or as 2-form. - - u_basis : int - U is 1-form (u_basis=1) or a 2-form (u_basis=2). - """ - - # allocate metric coeffs - dfm = empty((3, 3), dtype=float) - dfinv = empty((3, 3), dtype=float) - dfinv_t = empty((3, 3), dtype=float) - ginv = empty((3, 3), dtype=float) - - # marker velocity - v = empty(3, dtype=float) - - # U-fiels - u = empty(3, dtype=float) - - # intermediate stages in RK4 - k = empty(3, dtype=float) - k_v = empty(3, dtype=float) - k_u = empty(3, dtype=float) - - # get marker arguments - markers = args_markers.markers - n_markers = args_markers.n_markers - first_init_idx = args_markers.first_init_idx - first_free_idx = args_markers.first_free_idx - - # assign factor of k for each stage - if stage == 0 or stage == 3: - nk = 1.0 - else: - nk = 2.0 - - # is it the last stage? - if stage == 3: - last = 1.0 - cont = 0.0 - else: - last = 0.0 - cont = 1.0 - - for ip in range(n_markers): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - e1 = markers[ip, 0] - e2 = markers[ip, 1] - e3 = markers[ip, 2] - v[:] = markers[ip, 3:6] - - # ----------------- stage n in Runge-Kutta method ------------------- - # evaluate Jacobian, result in dfm - evaluation_kernels.df( - e1, - e2, - e3, - args_domain, - dfm, - ) - - # metric coeffs - det_df = linalg_kernels.det(dfm) - linalg_kernels.matrix_inv(dfm, dfinv) - linalg_kernels.transpose(dfinv, dfinv_t) - linalg_kernels.matrix_matrix(dfinv, dfinv_t, ginv) - - # pull-back of velocity - linalg_kernels.matrix_vector(dfinv, v, k_v) - - # spline evaluation - span1, span2, span3 = get_spans(e1, e2, e3, args_derham) - - # U-field - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - u_1, - u_2, - u_3, - u, - ) - - # transform to vector field - k_u[:] = u / det_df - - # sum contribs - k[:] = k_v + k_u - - # accum k - markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 - - # update markers for the next stage - markers[ip, 0:3] = ( - markers[ip, first_init_idx : first_init_idx + 3] - + dt * k / 2 * cont - + dt * markers[ip, first_free_idx : first_free_idx + 3] * last - ) - - -@stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v") -def push_pc_eta_rk4_H1vec_full( - dt: float, - stage: int, - args_markers: "MarkerArguments", - args_domain: "DomainArguments", - args_derham: "DerhamArguments", - u_1: "float[:,:,:]", - u_2: "float[:,:,:]", - u_3: "float[:,:,:]", -): - r"""Fourth order Runge-Kutta solve of - - .. math:: - - \frac{\textnormal d \boldsymbol \eta_p(t)}{\textnormal d t} = DF^{-1}(\boldsymbol \eta_p(t)) \mathbf v + \textnormal{vec}( \hat{\mathbf U}^{1(2)}) - - for each marker :math:`p` in markers array, where :math:`\mathbf v` is constant and - - .. math:: - - \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. - - Parameters - ---------- - u_1, u_2, u_3 : array[float] - 3d array of FE coeffs of U-field, either as 1-form or as 2-form. - - u_basis : int - U is 1-form (u_basis=1) or a 2-form (u_basis=2). - """ - - # allocate metric coeffs - dfm = empty((3, 3), dtype=float) - dfinv = empty((3, 3), dtype=float) - dfinv_t = empty((3, 3), dtype=float) - ginv = empty((3, 3), dtype=float) - - # marker and velocity - v = empty(3, dtype=float) - - # U-fiels - u = empty(3, dtype=float) - - # intermediate stages in RK4 - k = empty(3, dtype=float) - k_v = empty(3, dtype=float) - - # get marker arguments - markers = args_markers.markers - n_markers = args_markers.n_markers - first_init_idx = args_markers.first_init_idx - first_free_idx = args_markers.first_free_idx - - # assign factor of k for each stage - if stage == 0 or stage == 3: - nk = 1.0 - else: - nk = 2.0 - - # which stage - if stage == 3: - last = 1.0 - cont = 0.0 - elif stage == 2: - last = 0.0 - cont = 2.0 - else: - last = 0.0 - cont = 1.0 - - for ip in range(n_markers): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - e1 = markers[ip, 0] - e2 = markers[ip, 1] - e3 = markers[ip, 2] - v[:] = markers[ip, 3:6] - - # ----------------- stage n in Runge-Kutta method ------------------- - # evaluate Jacobian, result in dfm - evaluation_kernels.df( - e1, - e2, - e3, - args_domain, - dfm, - ) - - # metric coeffs - linalg_kernels.matrix_inv(dfm, dfinv) - linalg_kernels.transpose(dfinv, dfinv_t) - linalg_kernels.matrix_matrix(dfinv, dfinv_t, ginv) - - # pull-back of velocity - linalg_kernels.matrix_vector(dfinv, v, k_v) - - # spline evaluation - span1, span2, span3 = get_spans(e1, e2, e3, args_derham) - - # U-field - eval_vectorfield_spline_mpi( - span1, - span2, - span3, - args_derham, - u_1, - u_2, - u_3, - u, - ) - - # sum contribs - k[:] = k_v + u - - # accum k - markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 - - # update markers for the next stage - markers[ip, 0:3] = ( - markers[ip, first_init_idx : first_init_idx + 3] - + dt * k / 2 * cont - + dt * markers[ip, first_free_idx : first_free_idx + 3] * last - ) - - -@stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v", "k_u") -def push_pc_eta_rk4_Hcurl( - dt: float, - stage: int, - args_markers: "MarkerArguments", - args_domain: "DomainArguments", - args_derham: "DerhamArguments", - u_1: "float[:,:,:]", - u_2: "float[:,:,:]", - u_3: "float[:,:,:]", -): - r"""Fourth order Runge-Kutta solve of - - .. math:: - - \frac{\textnormal d \boldsymbol \eta_p(t)}{\textnormal d t} = DF^{-1}(\boldsymbol \eta_p(t)) \mathbf v + \textnormal{vec}( \hat{\mathbf U}^{1(2)}) - - for each marker :math:`p` in markers array, where :math:`\mathbf v` is constant and - - .. math:: - - \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. - - Parameters - ---------- - u_1, u_2, u_3 : array[float] - 3d array of FE coeffs of U-field, either as 1-form or as 2-form. - - u_basis : int - U is 1-form (u_basis=1) or a 2-form (u_basis=2). - """ - - # allocate metric coeffs - dfm = empty((3, 3), dtype=float) - dfinv = empty((3, 3), dtype=float) - dfinv_t = empty((3, 3), dtype=float) - ginv = empty((3, 3), dtype=float) - - # marker velocity - v = empty(3, dtype=float) - - # U-fiels - u = empty(3, dtype=float) - - # intermediate stages in RK4 - k = empty(3, dtype=float) - k_v = empty(3, dtype=float) - k_u = empty(3, dtype=float) - - # get marker arguments - markers = args_markers.markers - n_markers = args_markers.n_markers - first_init_idx = args_markers.first_init_idx - first_free_idx = args_markers.first_free_idx - - # assign factor of k for each stage - if stage == 0 or stage == 3: - nk = 1.0 - else: - nk = 2.0 - - # which stage - if stage == 3: - last = 1.0 - cont = 0.0 - elif stage == 2: - last = 0.0 - cont = 2.0 - else: - last = 0.0 - cont = 1.0 - - for ip in range(n_markers): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - e1 = markers[ip, 0] - e2 = markers[ip, 1] - e3 = markers[ip, 2] - v[:] = markers[ip, 3:6] - - # ----------------- stage n in Runge-Kutta method ------------------- - # evaluate Jacobian, result in dfm - evaluation_kernels.df( - e1, - e2, - e3, - args_domain, - dfm, - ) - - # metric coeffs - linalg_kernels.matrix_inv(dfm, dfinv) - linalg_kernels.transpose(dfinv, dfinv_t) - linalg_kernels.matrix_matrix(dfinv, dfinv_t, ginv) - - # pull-back of velocity - linalg_kernels.matrix_vector(dfinv, v, k_v) - - # spline evaluation - span1, span2, span3 = get_spans(e1, e2, e3, args_derham) - - # U-field - eval_1form_spline_mpi( - span1, - span2, - span3, - args_derham, - u_1, - u_2, - u_3, - u, - ) - u[2] = 0.0 + if use_perp_model: + u[2] = 0.0 # transform to vector field linalg_kernels.matrix_vector(ginv, u, k_u) @@ -2451,18 +2057,18 @@ def push_pc_eta_rk4_Hcurl( k[:] = k_v + k_u # accum k - markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 + markers[ip, first_free_idx : first_free_idx + 3] += dt * b[stage] * k # update markers for the next stage markers[ip, 0:3] = ( markers[ip, first_init_idx : first_init_idx + 3] - + dt * k / 2 * cont - + dt * markers[ip, first_free_idx : first_free_idx + 3] * last + + dt * k * a[stage] + + last * markers[ip, first_free_idx : first_free_idx + 3] ) @stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v", "k_u") -def push_pc_eta_rk4_Hdiv( +def push_pc_eta_stage_Hdiv( dt: float, stage: int, args_markers: "MarkerArguments", @@ -2471,6 +2077,10 @@ def push_pc_eta_rk4_Hdiv( u_1: "float[:,:,:]", u_2: "float[:,:,:]", u_3: "float[:,:,:]", + use_perp_model: "bool", + a: "float[:]", + b: "float[:]", + c: "float[:]", ): r"""Fourth order Runge-Kutta solve of @@ -2483,14 +2093,6 @@ def push_pc_eta_rk4_Hdiv( .. math:: \textnormal{vec}( \hat{\mathbf U}^{1}) = G^{-1}\hat{\mathbf U}^{1}\,,\qquad \textnormal{vec}( \hat{\mathbf U}^{2}) = \frac{\hat{\mathbf U}^{2}}{\sqrt g}\,. - - Parameters - ---------- - u_1, u_2, u_3 : array[float] - 3d array of FE coeffs of U-field, either as 1-form or as 2-form. - - u_basis : int - U is 1-form (u_basis=1) or a 2-form (u_basis=2). """ # allocate metric coeffs @@ -2516,19 +2118,13 @@ def push_pc_eta_rk4_Hdiv( first_init_idx = args_markers.first_init_idx first_free_idx = args_markers.first_free_idx - # assign factor of k for each stage - if stage == 0 or stage == 3: - nk = 1.0 - else: - nk = 2.0 + # get number of stages + n_stages = shape(b)[0] - # is it the last stage? - if stage == 3: + if stage == n_stages - 1: last = 1.0 - cont = 0.0 else: last = 0.0 - cont = 1.0 for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) @@ -2573,7 +2169,9 @@ def push_pc_eta_rk4_Hdiv( u_3, u, ) - u[2] = 0.0 + + if use_perp_model: + u[2] = 0.0 # transform to vector field k_u[:] = u / det_df @@ -2582,18 +2180,18 @@ def push_pc_eta_rk4_Hdiv( k[:] = k_v + k_u # accum k - markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 + markers[ip, first_free_idx : first_free_idx + 3] += dt * b[stage] * k # update markers for the next stage markers[ip, 0:3] = ( markers[ip, first_init_idx : first_init_idx + 3] - + dt * k / 2 * cont - + dt * markers[ip, first_free_idx : first_free_idx + 3] * last + + dt * k * a[stage] + + last * markers[ip, first_free_idx : first_free_idx + 3] ) @stack_array("dfm", "dfinv", "dfinv_t", "ginv", "v", "u", "k", "k_v") -def push_pc_eta_rk4_H1vec( +def push_pc_eta_stage_H1vec( dt: float, stage: int, args_markers: "MarkerArguments", @@ -2602,6 +2200,10 @@ def push_pc_eta_rk4_H1vec( u_1: "float[:,:,:]", u_2: "float[:,:,:]", u_3: "float[:,:,:]", + use_perp_model: "bool", + a: "float[:]", + b: "float[:]", + c: "float[:]", ): r"""Fourth order Runge-Kutta solve of @@ -2646,22 +2248,13 @@ def push_pc_eta_rk4_H1vec( first_init_idx = args_markers.first_init_idx first_free_idx = args_markers.first_free_idx - # assign factor of k for each stage - if stage == 0 or stage == 3: - nk = 1.0 - else: - nk = 2.0 + # get number of stages + n_stages = shape(b)[0] - # which stage - if stage == 3: + if stage == n_stages - 1: last = 1.0 - cont = 0.0 - elif stage == 2: - last = 0.0 - cont = 2.0 else: last = 0.0 - cont = 1.0 for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) @@ -2705,19 +2298,21 @@ def push_pc_eta_rk4_H1vec( u_3, u, ) - u[2] = 0.0 + + if use_perp_model: + u[2] = 0.0 # sum contribs k[:] = k_v + u # accum k - markers[ip, first_free_idx : first_free_idx + 3] += k * nk / 6.0 + markers[ip, first_free_idx : first_free_idx + 3] += dt * b[stage] * k # update markers for the next stage markers[ip, 0:3] = ( markers[ip, first_init_idx : first_init_idx + 3] - + dt * k / 2 * cont - + dt * markers[ip, first_free_idx : first_free_idx + 3] * last + + dt * k * a[stage] + + last * markers[ip, first_free_idx : first_free_idx + 3] ) @@ -3035,7 +2630,7 @@ def push_v_sph_pressure( h1, h2, h3 : float Kernel width in respective dimension. - gravity: np.ndarray + gravity: xp.ndarray Constant gravitational force as 3-vector. """ # allocate arrays @@ -3266,7 +2861,7 @@ def push_v_sph_pressure_ideal_gas( h1, h2, h3 : float Kernel width in respective dimension. - gravity: np.ndarray + gravity: xp.ndarray Constant gravitational force as 3-vector. """ # allocate arrays @@ -3498,7 +3093,7 @@ def push_v_viscosity( h1, h2, h3 : float Kernel width in respective dimension. - gravity: np.ndarray + gravity: xp.ndarray Constant gravitational force as 3-vector. """ # allocate arrays diff --git a/src/struphy/pic/pushing/pusher_kernels_gc.py b/src/struphy/pic/pushing/pusher_kernels_gc.py index 485f288b5..6e5df8034 100644 --- a/src/struphy/pic/pushing/pusher_kernels_gc.py +++ b/src/struphy/pic/pushing/pusher_kernels_gc.py @@ -1896,7 +1896,7 @@ def push_gc_cc_J1_H1vec( ) # b_star; in H1vec - b_star[:] = (b + curl_norm_b * v * epsilon) / det_df + b_star[:] = b + curl_norm_b * v * epsilon # calculate abs_b_star_para abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) @@ -1905,7 +1905,7 @@ def push_gc_cc_J1_H1vec( linalg_kernels.cross(b, u, e) # curl_norm_b dot electric field - temp = linalg_kernels.scalar_dot(e, curl_norm_b) / det_df + temp = linalg_kernels.scalar_dot(e, curl_norm_b) markers[ip, 3] += temp / abs_b_star_para * v * dt @@ -2077,7 +2077,6 @@ def push_gc_cc_J1_Hdiv( u1: "float[:,:,:]", u2: "float[:,:,:]", u3: "float[:,:,:]", - boundary_cut: float, ): r"""Velocity update step for the `CurrentCoupling5DCurlb `_ @@ -2105,8 +2104,6 @@ def push_gc_cc_J1_Hdiv( markers = args_markers.markers n_markers = args_markers.n_markers - # -- removed omp: #$ omp parallel private(ip, boundary_cut, eta1, eta2, eta3, v, det_df, dfm, span1, span2, span3, b, u, e, curl_norm_b, norm_b1, b_star, temp, abs_b_star_para) - # -- removed omp: #$ omp for for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) if markers[ip, 0] == -1.0: @@ -2117,9 +2114,6 @@ def push_gc_cc_J1_Hdiv( eta3 = markers[ip, 2] v = markers[ip, 3] - if eta1 < boundary_cut or eta1 > 1.0 - boundary_cut: - continue - # evaluate Jacobian, result in dfm evaluation_kernels.df( eta1, @@ -2183,10 +2177,10 @@ def push_gc_cc_J1_Hdiv( curl_norm_b, ) - # b_star; 2form in H1vec - b_star[:] = (b + curl_norm_b * v * epsilon) / det_df + # b_star; 2form + b_star[:] = b + curl_norm_b * v * epsilon - # calculate abs_b_star_para + # calculate 3form abs_b_star_para abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) # transform u into H1vec @@ -2196,12 +2190,10 @@ def push_gc_cc_J1_Hdiv( linalg_kernels.cross(b, u, e) # curl_norm_b dot electric field - temp = linalg_kernels.scalar_dot(e, curl_norm_b) / det_df + temp = linalg_kernels.scalar_dot(e, curl_norm_b) markers[ip, 3] += temp / abs_b_star_para * v * dt - # -- removed omp: #$ omp end parallel - @stack_array( "dfm", @@ -2212,13 +2204,11 @@ def push_gc_cc_J1_Hdiv( "u", "bb", "b_star", - "norm_b1", - "norm_b2", + "norm_b", "curl_norm_b", - "tmp1", - "tmp2", + "tmp", "b_prod", - "norm_b2_prod", + "norm_b_prod", ) def push_gc_cc_J2_stage_H1vec( dt: float, @@ -2233,9 +2223,6 @@ def push_gc_cc_J2_stage_H1vec( norm_b11: "float[:,:,:]", norm_b12: "float[:,:,:]", norm_b13: "float[:,:,:]", - norm_b21: "float[:,:,:]", - norm_b22: "float[:,:,:]", - norm_b23: "float[:,:,:]", curl_norm_b1: "float[:,:,:]", curl_norm_b2: "float[:,:,:]", curl_norm_b3: "float[:,:,:]", @@ -2264,16 +2251,14 @@ def push_gc_cc_J2_stage_H1vec( g_inv = empty((3, 3), dtype=float) # containers for fields - tmp1 = empty((3, 3), dtype=float) - tmp2 = empty((3, 3), dtype=float) + tmp = empty((3, 3), dtype=float) b_prod = zeros((3, 3), dtype=float) - norm_b2_prod = empty((3, 3), dtype=float) + norm_b_prod = empty((3, 3), dtype=float) e = empty(3, dtype=float) u = empty(3, dtype=float) bb = empty(3, dtype=float) b_star = empty(3, dtype=float) norm_b1 = empty(3, dtype=float) - norm_b2 = empty(3, dtype=float) curl_norm_b = empty(3, dtype=float) # get marker arguments @@ -2354,18 +2339,6 @@ def push_gc_cc_J2_stage_H1vec( norm_b1, ) - # norm_b; 2form - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - norm_b21, - norm_b22, - norm_b23, - norm_b2, - ) - # curl_norm_b; 2form eval_2form_spline_mpi( span1, @@ -2386,24 +2359,21 @@ def push_gc_cc_J2_stage_H1vec( b_prod[2, 0] = -bb[1] b_prod[2, 1] = +bb[0] - norm_b2_prod[0, 1] = -norm_b2[2] - norm_b2_prod[0, 2] = +norm_b2[1] - norm_b2_prod[1, 0] = +norm_b2[2] - norm_b2_prod[1, 2] = -norm_b2[0] - norm_b2_prod[2, 0] = -norm_b2[1] - norm_b2_prod[2, 1] = +norm_b2[0] + norm_b_prod[0, 1] = -norm_b1[2] + norm_b_prod[0, 2] = +norm_b1[1] + norm_b_prod[1, 0] = +norm_b1[2] + norm_b_prod[1, 2] = -norm_b1[0] + norm_b_prod[2, 0] = -norm_b1[1] + norm_b_prod[2, 1] = +norm_b1[0] # b_star; 2form in H1vec - b_star[:] = (bb + curl_norm_b * v * epsilon) / det_df + b_star[:] = bb + curl_norm_b * v * epsilon - # calculate abs_b_star_para + # calculate 3form abs_b_star_para abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) - linalg_kernels.matrix_matrix(g_inv, norm_b2_prod, tmp1) - linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) - linalg_kernels.matrix_matrix(tmp2, b_prod, tmp1) - - linalg_kernels.matrix_vector(tmp1, u, e) + linalg_kernels.matrix_matrix(norm_b_prod, b_prod, tmp) + linalg_kernels.matrix_vector(tmp, u, e) e /= abs_b_star_para @@ -2428,12 +2398,10 @@ def push_gc_cc_J2_stage_H1vec( "bb", "b_star", "norm_b1", - "norm_b2", "curl_norm_b", - "tmp1", - "tmp2", + "tmp", "b_prod", - "norm_b2_prod", + "norm_b_prod", ) def push_gc_cc_J2_stage_Hdiv( dt: float, @@ -2448,9 +2416,6 @@ def push_gc_cc_J2_stage_Hdiv( norm_b11: "float[:,:,:]", norm_b12: "float[:,:,:]", norm_b13: "float[:,:,:]", - norm_b21: "float[:,:,:]", - norm_b22: "float[:,:,:]", - norm_b23: "float[:,:,:]", curl_norm_b1: "float[:,:,:]", curl_norm_b2: "float[:,:,:]", curl_norm_b3: "float[:,:,:]", @@ -2460,7 +2425,6 @@ def push_gc_cc_J2_stage_Hdiv( a: "float[:]", b: "float[:]", c: "float[:]", - boundary_cut: float, ): r"""Single stage of a s-stage explicit pushing step for the `CurrentCoupling5DGradB `_ @@ -2480,16 +2444,14 @@ def push_gc_cc_J2_stage_Hdiv( g_inv = empty((3, 3), dtype=float) # containers for fields - tmp1 = zeros((3, 3), dtype=float) - tmp2 = zeros((3, 3), dtype=float) + tmp = zeros((3, 3), dtype=float) b_prod = zeros((3, 3), dtype=float) - norm_b2_prod = zeros((3, 3), dtype=float) + norm_b_prod = zeros((3, 3), dtype=float) e = empty(3, dtype=float) u = empty(3, dtype=float) bb = empty(3, dtype=float) b_star = empty(3, dtype=float) norm_b1 = empty(3, dtype=float) - norm_b2 = empty(3, dtype=float) curl_norm_b = empty(3, dtype=float) # get marker arguments @@ -2507,8 +2469,6 @@ def push_gc_cc_J2_stage_Hdiv( else: last = 0.0 - # -- removed omp: #$ omp parallel firstprivate(b_prod, norm_b2_prod) private(ip, boundary_cut, eta1, eta2, eta3, v, det_df, dfm, df_inv, df_inv_t, g_inv, span1, span2, span3, bb, u, e, curl_norm_b, norm_b1, norm_b2, b_star, tmp1, tmp2, abs_b_star_para) - # -- removed omp: #$ omp for for ip in range(n_markers): # check if marker is a hole if markers[ip, first_init_idx] == -1.0: @@ -2519,9 +2479,6 @@ def push_gc_cc_J2_stage_Hdiv( eta3 = markers[ip, 2] v = markers[ip, 3] - if eta1 < boundary_cut or eta2 > 1.0 - boundary_cut: - continue - # evaluate Jacobian, result in dfm evaluation_kernels.df( eta1, @@ -2576,18 +2533,6 @@ def push_gc_cc_J2_stage_Hdiv( norm_b1, ) - # norm_b; 2form - eval_2form_spline_mpi( - span1, - span2, - span3, - args_derham, - norm_b21, - norm_b22, - norm_b23, - norm_b2, - ) - # curl_norm_b; 2form eval_2form_spline_mpi( span1, @@ -2608,24 +2553,21 @@ def push_gc_cc_J2_stage_Hdiv( b_prod[2, 0] = -bb[1] b_prod[2, 1] = +bb[0] - norm_b2_prod[0, 1] = -norm_b2[2] - norm_b2_prod[0, 2] = +norm_b2[1] - norm_b2_prod[1, 0] = +norm_b2[2] - norm_b2_prod[1, 2] = -norm_b2[0] - norm_b2_prod[2, 0] = -norm_b2[1] - norm_b2_prod[2, 1] = +norm_b2[0] + norm_b_prod[0, 1] = -norm_b1[2] + norm_b_prod[0, 2] = +norm_b1[1] + norm_b_prod[1, 0] = +norm_b1[2] + norm_b_prod[1, 2] = -norm_b1[0] + norm_b_prod[2, 0] = -norm_b1[1] + norm_b_prod[2, 1] = +norm_b1[0] - # b_star; 2form in H1vec - b_star[:] = (bb + curl_norm_b * v * epsilon) / det_df + # b_star; 2form + b_star[:] = bb + curl_norm_b * v * epsilon # calculate abs_b_star_para abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) - linalg_kernels.matrix_matrix(g_inv, norm_b2_prod, tmp1) - linalg_kernels.matrix_matrix(tmp1, g_inv, tmp2) - linalg_kernels.matrix_matrix(tmp2, b_prod, tmp1) - - linalg_kernels.matrix_vector(tmp1, u, e) + linalg_kernels.matrix_matrix(norm_b_prod, b_prod, tmp) + linalg_kernels.matrix_vector(tmp, u, e) e /= abs_b_star_para e /= det_df @@ -2640,4 +2582,367 @@ def push_gc_cc_J2_stage_Hdiv( + last * markers[ip, first_free_idx : first_free_idx + 3] ) - # -- removed omp: #$ omp end parallel + +@stack_array( + "dfm", + "df_inv", + "df_inv_t", + "g_inv", + "e", + "u", + "bb", + "b_star", + "norm_b1", + "curl_norm_b", + "tmp1", + "b_prod", + "norm_b_prod", +) +def push_gc_cc_J2_dg_init_Hdiv( + dt: float, + args_markers: "MarkerArguments", + args_domain: "DomainArguments", + args_derham: "DerhamArguments", + epsilon: float, + b1: "float[:,:,:]", + b2: "float[:,:,:]", + b3: "float[:,:,:]", + norm_b11: "float[:,:,:]", + norm_b12: "float[:,:,:]", + norm_b13: "float[:,:,:]", + curl_norm_b1: "float[:,:,:]", + curl_norm_b2: "float[:,:,:]", + curl_norm_b3: "float[:,:,:]", + u1: "float[:,:,:]", + u2: "float[:,:,:]", + u3: "float[:,:,:]", +): + r"""TODO""" + + # allocate metric coeffs + dfm = empty((3, 3), dtype=float) + df_inv = empty((3, 3), dtype=float) + df_inv_t = empty((3, 3), dtype=float) + g_inv = empty((3, 3), dtype=float) + + # containers for fields + tmp1 = zeros((3, 3), dtype=float) + b_prod = zeros((3, 3), dtype=float) + norm_b_prod = zeros((3, 3), dtype=float) + e = empty(3, dtype=float) + u = empty(3, dtype=float) + bb = empty(3, dtype=float) + b_star = empty(3, dtype=float) + norm_b1 = empty(3, dtype=float) + curl_norm_b = empty(3, dtype=float) + + # get marker arguments + markers = args_markers.markers + n_markers = args_markers.n_markers + mu_idx = args_markers.mu_idx + first_init_idx = args_markers.first_init_idx + first_free_idx = args_markers.first_free_idx + + for ip in range(n_markers): + # check if marker is a hole + if markers[ip, first_init_idx] == -1.0: + continue + + eta1 = markers[ip, 0] + eta2 = markers[ip, 1] + eta3 = markers[ip, 2] + v = markers[ip, 3] + + # evaluate Jacobian, result in dfm + evaluation_kernels.df( + eta1, + eta2, + eta3, + args_domain, + dfm, + ) + + # metric coeffs + det_df = linalg_kernels.det(dfm) + linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) + linalg_kernels.transpose(df_inv, df_inv_t) + linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) + + # spline evaluation + span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) + + # b; 2form + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + b1, + b2, + b3, + bb, + ) + + # u; 2form + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + u1, + u2, + u3, + u, + ) + + # norm_b1; 1form + eval_1form_spline_mpi( + span1, + span2, + span3, + args_derham, + norm_b11, + norm_b12, + norm_b13, + norm_b1, + ) + + # curl_norm_b; 2form + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + curl_norm_b1, + curl_norm_b2, + curl_norm_b3, + curl_norm_b, + ) + + # operator bx() as matrix + b_prod[0, 1] = -bb[2] + b_prod[0, 2] = +bb[1] + b_prod[1, 0] = +bb[2] + b_prod[1, 2] = -bb[0] + b_prod[2, 0] = -bb[1] + b_prod[2, 1] = +bb[0] + + norm_b_prod[0, 1] = -norm_b1[2] + norm_b_prod[0, 2] = +norm_b1[1] + norm_b_prod[1, 0] = +norm_b1[2] + norm_b_prod[1, 2] = -norm_b1[0] + norm_b_prod[2, 0] = -norm_b1[1] + norm_b_prod[2, 1] = +norm_b1[0] + + # b_star; 2form + b_star[:] = bb + curl_norm_b * v * epsilon + + # calculate 3form abs_b_star_para + abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) + + linalg_kernels.matrix_matrix(norm_b_prod, b_prod, tmp1) + linalg_kernels.matrix_vector(tmp1, u, e) + + e /= abs_b_star_para + e /= det_df + + markers[ip, 0:3] -= dt * e + + +@stack_array( + "dfm", + "df_inv", + "df_inv_t", + "g_inv", + "e", + "u", + "ud", + "bb", + "b_star", + "norm_b1", + "curl_norm_b", + "tmp1", + "tmp2", + "b_prod", + "norm_b_prod", + "eta_old", + "eta_mid", +) +def push_gc_cc_J2_dg_Hdiv( + dt: float, + args_markers: "MarkerArguments", + args_domain: "DomainArguments", + args_derham: "DerhamArguments", + epsilon: float, + b1: "float[:,:,:]", + b2: "float[:,:,:]", + b3: "float[:,:,:]", + norm_b11: "float[:,:,:]", + norm_b12: "float[:,:,:]", + norm_b13: "float[:,:,:]", + curl_norm_b1: "float[:,:,:]", + curl_norm_b2: "float[:,:,:]", + curl_norm_b3: "float[:,:,:]", + u1: "float[:,:,:]", + u2: "float[:,:,:]", + u3: "float[:,:,:]", + ud1: "float[:,:,:]", + ud2: "float[:,:,:]", + ud3: "float[:,:,:]", + const: float, + alpha: float, +): + r"""TODO""" + + # allocate metric coeffs + dfm = empty((3, 3), dtype=float) + df_inv = empty((3, 3), dtype=float) + df_inv_t = empty((3, 3), dtype=float) + g_inv = empty((3, 3), dtype=float) + + # containers for fields + tmp1 = zeros((3, 3), dtype=float) + tmp2 = zeros(3, dtype=float) + b_prod = zeros((3, 3), dtype=float) + norm_b_prod = zeros((3, 3), dtype=float) + e = empty(3, dtype=float) + u = empty(3, dtype=float) + ud = empty(3, dtype=float) + bb = empty(3, dtype=float) + b_star = empty(3, dtype=float) + norm_b1 = empty(3, dtype=float) + curl_norm_b = empty(3, dtype=float) + eta_old = empty(3, dtype=float) + eta_mid = empty(3, dtype=float) + + # get marker arguments + markers = args_markers.markers + n_markers = args_markers.n_markers + mu_idx = args_markers.mu_idx + first_init_idx = args_markers.first_init_idx + first_free_idx = args_markers.first_free_idx + + for ip in range(n_markers): + # check if marker is a hole + if markers[ip, 0] == -1.0: + continue + + # marker positions, mid point + eta_old[:] = markers[ip, 0:3] + eta_mid[:] = (markers[ip, 0:3] + markers[ip, first_init_idx : first_init_idx + 3]) / 2.0 + eta_mid[:] = mod(eta_mid[:], 1.0) + + v = markers[ip, 3] + + # evaluate Jacobian, result in dfm + evaluation_kernels.df( + eta_mid[0], + eta_mid[1], + eta_mid[2], + args_domain, + dfm, + ) + + # metric coeffs + det_df = linalg_kernels.det(dfm) + linalg_kernels.matrix_inv_with_det(dfm, det_df, df_inv) + linalg_kernels.transpose(df_inv, df_inv_t) + linalg_kernels.matrix_matrix(df_inv, df_inv_t, g_inv) + + # spline evaluation + span1, span2, span3 = get_spans(eta_mid[0], eta_mid[1], eta_mid[2], args_derham) + + # b; 2form + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + b1, + b2, + b3, + bb, + ) + + # u; 2form + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + u1, + u2, + u3, + u, + ) + + # ud; 2form + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + ud1, + ud2, + ud3, + ud, + ) + + # norm_b1; 1form + eval_1form_spline_mpi( + span1, + span2, + span3, + args_derham, + norm_b11, + norm_b12, + norm_b13, + norm_b1, + ) + + # curl_norm_b; 2form + eval_2form_spline_mpi( + span1, + span2, + span3, + args_derham, + curl_norm_b1, + curl_norm_b2, + curl_norm_b3, + curl_norm_b, + ) + + # operator bx() as matrix + b_prod[0, 1] = -bb[2] + b_prod[0, 2] = +bb[1] + b_prod[1, 0] = +bb[2] + b_prod[1, 2] = -bb[0] + b_prod[2, 0] = -bb[1] + b_prod[2, 1] = +bb[0] + + norm_b_prod[0, 1] = -norm_b1[2] + norm_b_prod[0, 2] = +norm_b1[1] + norm_b_prod[1, 0] = +norm_b1[2] + norm_b_prod[1, 2] = -norm_b1[0] + norm_b_prod[2, 0] = -norm_b1[1] + norm_b_prod[2, 1] = +norm_b1[0] + + # b_star; 2form + b_star[:] = bb + curl_norm_b * v * epsilon + + # calculate 3form abs_b_star_para + abs_b_star_para = linalg_kernels.scalar_dot(norm_b1, b_star) + + linalg_kernels.matrix_matrix(norm_b_prod, b_prod, tmp1) + linalg_kernels.matrix_vector(tmp1, u, e) + linalg_kernels.matrix_vector(tmp1, ud, tmp2) + tmp2 *= const + + e += tmp2 + + e /= abs_b_star_para + e /= det_df + + markers[ip, 0:3] = markers[ip, first_init_idx : first_init_idx + 3] - dt * e + markers[ip, 0:3] *= alpha + markers[ip, 0:3] += eta_old * (1.0 - alpha) diff --git a/src/struphy/pic/pushing/pusher_utilities_kernels.py b/src/struphy/pic/pushing/pusher_utilities_kernels.py index a5a70ace4..0d3749660 100644 --- a/src/struphy/pic/pushing/pusher_utilities_kernels.py +++ b/src/struphy/pic/pushing/pusher_utilities_kernels.py @@ -77,466 +77,3 @@ def reflect( # update the particle velocities markers[ip, 3:6] = v[:] - - -@pure -def quicksort(a: "float[:]", lo: "int", hi: "int"): - """ - Implementation of the quicksort sorting algorithm. Ref? - - Parameters - ---------- - a : array - list that is to be sorted - - lo : integer - lower index from which the sort to start - - hi : integer - upper index until which the sort is to be done - """ - i = lo - j = hi - while i < hi: - pivot = a[(lo + hi) // 2] - while i <= j: - while a[i] < pivot: - i += 1 - while a[j] > pivot: - j -= 1 - if i <= j: - tmp = a[i] - a[i] = a[j] - a[j] = tmp - i += 1 - j -= 1 - if lo < j: - quicksort(a, lo, j) - lo = i - j = hi - - -def find_taus(eta: "float", eta_next: "float", Nel: "int", breaks: "float[:]", uniform: "int", tau_list: "float[:]"): - """ - Find the values of tau for which the particle crosses the cell boundaries while going from eta to eta_next - - Parameters - ---------- - eta : float - old position - - eta_next : float - new position - - Nel : integer - contains the number of elements in this direction - - breaks : array - break points in this direction - - uniform : integer - 0 if the grid is non-uniform, 1 if the grid is uniform - """ - - if uniform == 1: - index = int(floor(eta * Nel)) - index_next = int(floor(eta_next * Nel)) - length = int(abs(index_next - index)) - - # break = eta / dx = eta * Nel - - for i in range(length): - if index_next > index: - tau_list[i] = (1.0 / Nel * (index + i + 1) - eta) / (eta_next - eta) - elif index > index_next: - tau_list[i] = (eta - 1.0 / Nel * (index - i)) / (eta - eta_next) - - elif uniform == 0: - # TODO - print("Not implemented yet") - - else: - print("ValueError, uniform must be 1 or 0 !") - - -@stack_array("Nel") -def aux_fun_x_v_stat_e( - particle: "float[:]", - args_derham: "DerhamArguments", - args_domain: "DomainArguments", - n_quad1: "int", - n_quad2: "int", - n_quad3: "int", - dfm: "float[:,:]", - df_inv: "float[:,:]", - taus: "float[:]", - dt: "float", - loc1: "float[:]", - loc2: "float[:]", - loc3: "float[:]", - weight1: "float[:]", - weight2: "float[:]", - weight3: "float[:]", - e1_1: "float[:,:,:]", - e1_2: "float[:,:,:]", - e1_3: "float[:,:,:]", - kappa: "float", - eps: "float[:]", - maxiter: "int", -) -> "int": - """ - Auxiliary function for the pusher_x_v_static_efield, introduced to enable time-step splitting if scheme does not converge for the standard dt - - Parameters - ---------- - particle : array - shape(7), contains the values for the positions [0:3], velocities [3:6], and weights [8] - - dt2 : double - time stepping of substep - - loc1, loc2, loc3 : array - contain the positions of the Legendre-Gauss quadrature points of necessary order to integrate basis splines exactly in each direction - - weight1, weight2, weight3 : array - contain the values of the weights for the Legendre-Gauss quadrature in each direction - - e1_1, e1_2, e1_3: array[float] - 3d array of FE coeffs of the background E-field as 1-form. - - eps: array - determines the accuracy for the position (0th element) and velocity (1st element) with which the implicit scheme is executed - - maxiter : integer - sets the maximum number of iterations for the iterative scheme - """ - - # Find number of elements in each direction - Nel = empty(3, dtype=int) - Nel[0] = len(args_derham.tn1) - Nel[1] = len(args_derham.tn2) - Nel[2] = len(args_derham.tn3) - - # total number of basis functions : B-splines (pn) and D-splines (pd) - pn1 = int(args_derham.pn[0]) - pn2 = int(args_derham.pn[1]) - pn3 = int(args_derham.pn[2]) - - pd1 = pn1 - 1 - pd2 = pn2 - 1 - pd3 = pn3 - 1 - - eps_pos = eps[0] - eps_vel = eps[1] - - # position - eta1 = particle[0] - eta2 = particle[1] - eta3 = particle[2] - - # velocities - v1 = particle[3] - v2 = particle[4] - v3 = particle[5] - - # set initial value for x_k^{n+1} - eta1_curr = eta1 - eta2_curr = eta2 - eta3_curr = eta3 - - # set initial value for v_k^{n+1} - v1_curr = v1 - v2_curr = v2 - v3_curr = v3 - - # Use Euler method as a predictor for positions - evaluation_kernels.df( - eta1, - eta2, - eta3, - args_domain, - dfm, - ) - - linalg_kernels.matrix_inv(dfm, df_inv) - - v1_curv = kappa * (df_inv[0, 0] * (v1_curr + v1) + df_inv[0, 1] * (v2_curr + v2) + df_inv[0, 2] * (v3_curr + v3)) - v2_curv = kappa * (df_inv[1, 0] * (v1_curr + v1) + df_inv[1, 1] * (v2_curr + v2) + df_inv[1, 2] * (v3_curr + v3)) - v3_curv = kappa * (df_inv[2, 0] * (v1_curr + v1) + df_inv[2, 1] * (v2_curr + v2) + df_inv[2, 2] * (v3_curr + v3)) - - eta1_next = (eta1 + dt * v1_curv / 2.0) % 1 - eta2_next = (eta2 + dt * v2_curv / 2.0) % 1 - eta3_next = (eta3 + dt * v3_curv / 2.0) % 1 - - # set some initial value for v_next - v1_next = v1_curr - v2_next = v2_curr - v3_next = v3_curr - - runs = 0 - - while ( - abs(eta1_next - eta1_curr) > eps_pos - or abs(eta2_next - eta2_curr) > eps_pos - or abs(eta3_next - eta3_curr) > eps_pos - or abs(v1_next - v1_curr) > eps_vel - or abs(v2_next - v2_curr) > eps_vel - or abs(v3_next - v3_curr) > eps_vel - ): - taus[:] = 0.0 - - # update the positions and velocities - eta1_curr = eta1_next - eta2_curr = eta2_next - eta3_curr = eta3_next - - v1_curr = v1_next - v2_curr = v2_next - v3_curr = v3_next - - # find Jacobian matrix - evaluation_kernels.df( - (eta1_curr + eta1) / 2, - (eta2_curr + eta2) / 2, - (eta3_curr + eta3) / 2, - args_domain, - dfm, - ) - - # evaluate inverse Jacobian matrix - linalg_kernels.matrix_inv(dfm, df_inv) - - # ====================================================================================== - # update the positions and place them back into the computational domain - v1_curv = kappa * ( - df_inv[0, 0] * (v1_curr + v1) + df_inv[0, 1] * (v2_curr + v2) + df_inv[0, 2] * (v3_curr + v3) - ) - v2_curv = kappa * ( - df_inv[1, 0] * (v1_curr + v1) + df_inv[1, 1] * (v2_curr + v2) + df_inv[1, 2] * (v3_curr + v3) - ) - v3_curv = kappa * ( - df_inv[2, 0] * (v1_curr + v1) + df_inv[2, 1] * (v2_curr + v2) + df_inv[2, 2] * (v3_curr + v3) - ) - - # x_{n+1} = x_n + dt/2 * DF^{-1}(x_{n+1}/2 + x_n/2) * (v_{n+1} + v_n) - eta1_next = (eta1 + dt * v1_curv / 2.0) % 1 - eta2_next = (eta2 + dt * v2_curv / 2.0) % 1 - eta3_next = (eta3 + dt * v3_curv / 2.0) % 1 - - # ====================================================================================== - # Compute tau-values in [0,1] for crossings of cell-boundaries - - index1 = int(floor(eta1_curr * Nel[0])) - index1_next = int(floor(eta1_next * Nel[0])) - length1 = int(abs(index1_next - index1)) - - index2 = int(floor(eta2_curr * Nel[1])) - index2_next = int(floor(eta2_next * Nel[1])) - length2 = int(abs(index2_next - index2)) - - index3 = int(floor(eta3_curr * Nel[2])) - index3_next = int(floor(eta3_next * Nel[2])) - length3 = int(abs(index3_next - index3)) - - length = length1 + length2 + length3 - - taus[0] = 0.0 - taus[length + 1] = 1.0 - - tmp1 = taus[1 : length1 + 1] - find_taus(eta1_curr, eta1_next, Nel[0], args_derham.tn1, 1, tmp1) - taus[1 : length1 + 1] = tmp1 - - tmp2 = taus[length1 + 1 : length1 + length2 + 1] - find_taus(eta2_curr, eta2_next, Nel[1], args_derham.tn2, 1, tmp2) - taus[length1 + 1 : length1 + length2 + 1] = tmp2 - - tmp3 = taus[length1 + length2 + 1 : length + 1] - find_taus(eta3_curr, eta3_next, Nel[2], args_derham.tn3, 1, tmp3) - taus[length1 + length2 + 1 : length + 1] = tmp3 - - del tmp1, tmp2, tmp3 - - if length != 0: - tmp4 = taus[0 : length + 1] - quicksort(tmp4, 1, length) - taus[0 : length + 1] = tmp4 - del tmp4 - - # ====================================================================================== - # update velocity in direction 1 - - temp1 = 0.0 - - # loop over the cells - for k in range(length + 1): - a = eta1 + taus[k] * (eta1_curr - eta1) - b = eta1 + taus[k + 1] * (eta1_curr - eta1) - factor = (b - a) / 2 - adding = (a + b) / 2 - - for n in range(n_quad1): - quad_pos1 = factor * loc1[n] + adding - quad_pos2 = factor * loc1[n] + adding - quad_pos3 = factor * loc1[n] + adding - - # spline evaluation - span1, span2, span3 = get_spans( - quad_pos1, - quad_pos2, - quad_pos3, - args_derham, - ) - - # find global index where non-zero basis functions begin - ie1 = span1 - args_derham.pn[0] - ie2 = span2 - args_derham.pn[1] - ie3 = span3 - args_derham.pn[2] - - # (DNN) - for il1 in range(pd1 + 1): - i1 = ie1 + il1 - bi1 = args_derham.bd1[il1] - for il2 in range(pn2 + 1): - i2 = ie2 + il2 - bi2 = bi1 * args_derham.bn2[il2] - for il3 in range(pn3 + 1): - i3 = ie3 + il3 - bi3 = ( - bi2 - * args_derham.bn3[il3] - * e1_1[ - i1 - args_derham.starts[0] + pn1, - i2 - args_derham.starts[1] + pn2, - i3 - args_derham.starts[2] + pn3, - ] - ) - - temp1 += bi3 * weight1[n] - - # ====================================================================================== - # update velocity in direction 2 - - temp2 = 0.0 - - # loop over the cells - for k in range(length + 1): - a = eta2 + taus[k] * (eta2_curr - eta2) - b = eta2 + taus[k + 1] * (eta2_curr - eta2) - factor = (b - a) / 2 - adding = (a + b) / 2 - - for n in range(n_quad2): - quad_pos1 = factor * loc2[n] + adding - quad_pos2 = factor * loc2[n] + adding - quad_pos3 = factor * loc2[n] + adding - - # spline evaluation - span1, span2, span3 = get_spans( - quad_pos1, - quad_pos2, - quad_pos3, - args_derham, - ) - - # find global index where non-zero basis functions begin - ie1 = span1 - args_derham.pn[0] - ie2 = span2 - args_derham.pn[1] - ie3 = span3 - args_derham.pn[2] - - # (NDN) - for il1 in range(pn1 + 1): - i1 = ie1 + il1 - bi1 = args_derham.bn1[il1] - for il2 in range(pd2 + 1): - i2 = ie2 + il2 - bi2 = bi1 * args_derham.bd2[il2] - for il3 in range(pn3 + 1): - i3 = ie3 + il3 - bi3 = ( - bi2 - * args_derham.bn3[il3] - * e1_2[ - i1 - args_derham.starts[0] + pn1, - i2 - args_derham.starts[1] + pn2, - i3 - args_derham.starts[2] + pn3, - ] - ) - - temp2 += bi3 * weight2[n] - - # ====================================================================================== - # update velocity in direction 3 - - temp3 = 0.0 - - # loop over the cells - for k in range(length + 1): - a = eta3 + taus[k] * (eta3_curr - eta3) - b = eta3 + taus[k + 1] * (eta3_curr - eta3) - factor = (b - a) / 2 - adding = (a + b) / 2 - - for n in range(n_quad3): - quad_pos1 = factor * loc3[n] + adding - quad_pos2 = factor * loc3[n] + adding - quad_pos3 = factor * loc3[n] + adding - - # spline evaluation - span1, span2, span3 = get_spans( - quad_pos1, - quad_pos2, - quad_pos3, - args_derham, - ) - - # find global index where non-zero basis functions begin - ie1 = span1 - args_derham.pn[0] - ie2 = span2 - args_derham.pn[1] - ie3 = span3 - args_derham.pn[2] - - # (NND) - for il1 in range(pn1 + 1): - i1 = ie1 + il1 - bi1 = args_derham.bn1[il1] - for il2 in range(pn2 + 1): - i2 = ie2 + il2 - bi2 = bi1 * args_derham.bn2[il2] - for il3 in range(pd3 + 1): - i3 = ie3 + il3 - bi3 = ( - bi2 - * args_derham.bd3[il3] - * e1_3[ - i1 - args_derham.starts[0] + pn1, - i2 - args_derham.starts[1] + pn2, - i3 - args_derham.starts[2] + pn3, - ] - ) - - temp3 += bi3 * weight3[n] - - # v_{n+1} = v_n + dt * DF^{-T}(x_n) * int_0^1 d tau ( E(x_n + tau*(x_{n+1} - x_n) ) ) - v1_next = v1 + dt * kappa * (df_inv[0, 0] * temp1 + df_inv[1, 0] * temp2 + df_inv[2, 0] * temp3) - v2_next = v2 + dt * kappa * (df_inv[0, 1] * temp1 + df_inv[1, 1] * temp2 + df_inv[2, 1] * temp3) - v3_next = v3 + dt * kappa * (df_inv[0, 2] * temp1 + df_inv[1, 2] * temp2 + df_inv[2, 2] * temp3) - - runs += 1 - - if runs == maxiter: - break - - if runs < maxiter: - # print('For convergence this took runs:', runs) - # print() - runs = 0 - - # write the results in the particle array and impose periodic boundary conditions on the particles by taking modulo 1 - particle[0] = eta1_next % 1 - particle[1] = eta2_next % 1 - particle[2] = eta3_next % 1 - particle[3] = v1_next - particle[4] = v2_next - particle[5] = v3_next - - return runs diff --git a/src/struphy/pic/sampling_kernels.py b/src/struphy/pic/sampling_kernels.py index 821363a97..ce68d5aff 100644 --- a/src/struphy/pic/sampling_kernels.py +++ b/src/struphy/pic/sampling_kernels.py @@ -93,13 +93,13 @@ def tile_int_kernel( Parameters ---------- - fun: np.ndarray + fun: xp.ndarray The integrand evaluated at the quadrature points (meshgrid). - x_wts, y_wts, z_wts: np.ndarray + x_wts, y_wts, z_wts: xp.ndarray Quadrature weights for tile integral. - out: np.ndarray + out: xp.ndarray The result holding all tile integrals in one sorting box.""" _shp = shape(out) diff --git a/src/struphy/pic/sobol_seq.py b/src/struphy/pic/sobol_seq.py index 430b5442b..f4c01347a 100644 --- a/src/struphy/pic/sobol_seq.py +++ b/src/struphy/pic/sobol_seq.py @@ -17,7 +17,7 @@ from __future__ import division -import numpy as np +import cunumpy as xp from scipy.stats import norm __all__ = ["i4_bit_hi1", "i4_bit_lo0", "i4_sobol_generate", "i4_sobol", "i4_uniform", "prime_ge", "is_prime"] @@ -59,7 +59,7 @@ def i4_bit_hi1(n): Output, integer BIT, the number of bits base 2. """ - i = np.floor(n) + i = xp.floor(n) bit = 0 while i > 0: bit += 1 @@ -104,7 +104,7 @@ def i4_bit_lo0(n): Output, integer BIT, the position of the low 1 bit. """ bit = 1 - i = np.floor(n) + i = xp.floor(n) while i != 2 * (i // 2): bit += 1 i //= 2 @@ -122,7 +122,7 @@ def i4_sobol_generate(dim_num, n, skip=1): Output, real R(M,N), the points. """ - r = np.full((n, dim_num), np.nan) + r = xp.full((n, dim_num), xp.nan) for j in range(n): seed = j + skip r[j, 0:dim_num], next_seed = i4_sobol(dim_num, seed) @@ -221,8 +221,8 @@ def i4_sobol(dim_num, seed): seed_save = -1 # Initialize (part of) V. - v = np.zeros((dim_max, log_max)) - v[0:40, 0] = np.transpose( + v = xp.zeros((dim_max, log_max)) + v[0:40, 0] = xp.transpose( [ 1, 1, @@ -264,10 +264,10 @@ def i4_sobol(dim_num, seed): 1, 1, 1, - ] + ], ) - v[2:40, 1] = np.transpose( + v[2:40, 1] = xp.transpose( [ 1, 3, @@ -307,10 +307,10 @@ def i4_sobol(dim_num, seed): 3, 1, 3, - ] + ], ) - v[3:40, 2] = np.transpose( + v[3:40, 2] = xp.transpose( [ 7, 5, @@ -349,10 +349,10 @@ def i4_sobol(dim_num, seed): 1, 3, 3, - ] + ], ) - v[5:40, 3] = np.transpose( + v[5:40, 3] = xp.transpose( [ 1, 7, @@ -389,10 +389,10 @@ def i4_sobol(dim_num, seed): 1, 7, 9, - ] + ], ) - v[7:40, 4] = np.transpose( + v[7:40, 4] = xp.transpose( [ 9, 3, @@ -427,18 +427,18 @@ def i4_sobol(dim_num, seed): 9, 31, 9, - ] + ], ) - v[13:40, 5] = np.transpose( - [37, 33, 7, 5, 11, 39, 63, 27, 17, 15, 23, 29, 3, 21, 13, 31, 25, 9, 49, 33, 19, 29, 11, 19, 27, 15, 25] + v[13:40, 5] = xp.transpose( + [37, 33, 7, 5, 11, 39, 63, 27, 17, 15, 23, 29, 3, 21, 13, 31, 25, 9, 49, 33, 19, 29, 11, 19, 27, 15, 25], ) - v[19:40, 6] = np.transpose( - [13, 33, 115, 41, 79, 17, 29, 119, 75, 73, 105, 7, 59, 65, 21, 3, 113, 61, 89, 45, 107] + v[19:40, 6] = xp.transpose( + [13, 33, 115, 41, 79, 17, 29, 119, 75, 73, 105, 7, 59, 65, 21, 3, 113, 61, 89, 45, 107], ) - v[37:40, 7] = np.transpose([7, 23, 39]) + v[37:40, 7] = xp.transpose([7, 23, 39]) # Set POLY. poly = [ @@ -517,7 +517,7 @@ def i4_sobol(dim_num, seed): # Expand this bit pattern to separate components of the logical array INCLUD. j = poly[i - 1] - includ = np.zeros(m) + includ = xp.zeros(m) for k in range(m, 0, -1): j2 = j // 2 includ[k - 1] = j != 2 * j2 @@ -531,7 +531,7 @@ def i4_sobol(dim_num, seed): for k in range(1, m + 1): l *= 2 if includ[k - 1]: - newv = np.bitwise_xor(int(newv), int(l * v[i - 1, j - k - 1])) + newv = xp.bitwise_xor(int(newv), int(l * v[i - 1, j - k - 1])) v[i - 1, j - 1] = newv # Multiply columns of V by appropriate power of 2. @@ -542,16 +542,16 @@ def i4_sobol(dim_num, seed): # RECIPD is 1/(common denominator of the elements in V). recipd = 1.0 / (2 * l) - lastq = np.zeros(dim_num) + lastq = xp.zeros(dim_num) - seed = int(np.floor(seed)) + seed = int(xp.floor(seed)) if seed < 0: seed = 0 l = 1 if seed == 0: - lastq = np.zeros(dim_num) + lastq = xp.zeros(dim_num) elif seed == seed_save + 1: # Find the position of the right-hand zero in SEED. @@ -559,12 +559,12 @@ def i4_sobol(dim_num, seed): elif seed <= seed_save: seed_save = 0 - lastq = np.zeros(dim_num) + lastq = xp.zeros(dim_num) for seed_temp in range(int(seed_save), int(seed)): l = i4_bit_lo0(seed_temp) for i in range(1, dim_num + 1): - lastq[i - 1] = np.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) + lastq[i - 1] = xp.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) l = i4_bit_lo0(seed) @@ -572,7 +572,7 @@ def i4_sobol(dim_num, seed): for seed_temp in range(int(seed_save + 1), int(seed)): l = i4_bit_lo0(seed_temp) for i in range(1, dim_num + 1): - lastq[i - 1] = np.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) + lastq[i - 1] = xp.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) l = i4_bit_lo0(seed) @@ -585,10 +585,10 @@ def i4_sobol(dim_num, seed): return # Calculate the new components of QUASI. - quasi = np.zeros(dim_num) + quasi = xp.zeros(dim_num) for i in range(1, dim_num + 1): quasi[i - 1] = lastq[i - 1] * recipd - lastq[i - 1] = np.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) + lastq[i - 1] = xp.bitwise_xor(int(lastq[i - 1]), int(v[i - 1, l - 1])) seed_save = seed seed += 1 @@ -638,11 +638,11 @@ def i4_uniform(a, b, seed): print("I4_UNIFORM - Fatal error!") print(" Input SEED = 0!") - seed = np.floor(seed) + seed = xp.floor(seed) a = round(a) b = round(b) - seed = np.mod(seed, 2147483647) + seed = xp.mod(seed, 2147483647) if seed < 0: seed += 2147483647 @@ -696,7 +696,7 @@ def prime_ge(n): Output, integer P, the smallest prime number that is greater than or equal to N. """ - p = max(np.ceil(n), 2) + p = max(xp.ceil(n), 2) while not is_prime(p): p += 1 @@ -720,7 +720,7 @@ def is_prime(n): return False # All primes >3 are of the form 6n+1 or 6n+5 (6n, 6n+2, 6n+4 are 2-divisible, 6n+3 is 3-divisible) p = 5 - root = int(np.ceil(np.sqrt(n))) + root = int(xp.ceil(xp.sqrt(n))) while p <= root: if n % p == 0 or n % (p + 2) == 0: return False diff --git a/src/struphy/pic/sph_eval_kernels.py b/src/struphy/pic/sph_eval_kernels.py index 37414f447..4c63e0156 100644 --- a/src/struphy/pic/sph_eval_kernels.py +++ b/src/struphy/pic/sph_eval_kernels.py @@ -297,7 +297,19 @@ def naive_evaluation_meshgrid( e2 = eta2[i, j, k] e3 = eta3[i, j, k] out[i, j, k] = naive_evaluation_kernel( - args_markers, e1, e2, e3, holes, periodic1, periodic2, periodic3, index, kernel_type, h1, h2, h3 + args_markers, + e1, + e2, + e3, + holes, + periodic1, + periodic2, + periodic3, + index, + kernel_type, + h1, + h2, + h3, ) diff --git a/src/struphy/pic/tests/test_accum_vec_H1.py b/src/struphy/pic/tests/test_accum_vec_H1.py index 3f2ce4923..cb5cbb17e 100644 --- a/src/struphy/pic/tests/test_accum_vec_H1.py +++ b/src/struphy/pic/tests/test_accum_vec_H1.py @@ -1,11 +1,13 @@ import pytest +from struphy.utils.pyccel import Pyccelkernel + -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[2, 3, 4]]) @pytest.mark.parametrize( - "spl_kind", [[False, False, True], [False, True, True], [True, False, True], [True, True, True]] + "spl_kind", + [[False, False, True], [False, True, True], [True, False, True], [True, True, True]], ) @pytest.mark.parametrize( "mapping", @@ -46,8 +48,9 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): import copy - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import MockComm + from psydac.ddm.mpi import mpi as MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.psydac_derham import Derham @@ -58,8 +61,12 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): from struphy.pic.utilities import BoundaryParameters, LoadingParameters, WeightsParameters from struphy.utils.clone_config import CloneConfig - mpi_comm = MPI.COMM_WORLD - mpi_rank = mpi_comm.Get_rank() + if isinstance(MPI.COMM_WORLD, MockComm): + mpi_comm = None + mpi_rank = 0 + else: + mpi_comm = MPI.COMM_WORLD + mpi_rank = mpi_comm.Get_rank() # domain object dom_type = mapping[0] @@ -70,17 +77,26 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): params = { "grid": {"Nel": Nel}, - "kinetic": {"test_particles": {"markers": {"Np": Np, "ppc": Np / np.prod(Nel)}}}, + "kinetic": {"test_particles": {"markers": {"Np": Np, "ppc": Np / xp.prod(Nel)}}}, } - clone_config = CloneConfig(comm=mpi_comm, params=params, num_clones=num_clones) - - # DeRham object - derham = Derham( - Nel, - p, - spl_kind, - comm=clone_config.sub_comm, - ) + if mpi_comm is None: + clone_config = None + + derham = Derham( + Nel, + p, + spl_kind, + comm=None, + ) + else: + clone_config = CloneConfig(comm=mpi_comm, params=params, num_clones=num_clones) + + derham = Derham( + Nel, + p, + spl_kind, + comm=clone_config.sub_comm, + ) domain_array = derham.domain_array nprocs = derham.domain_decomposition.nprocs @@ -106,19 +122,20 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): ) particles.draw_markers() - particles.mpi_sort_markers() + if mpi_comm is not None: + particles.mpi_sort_markers() particles.initialize_weights() _vdim = particles.vdim _w0 = particles.weights print("Test weights:") - print(f"rank {mpi_rank}:", _w0.shape, np.min(_w0), np.max(_w0)) + print(f"rank {mpi_rank}:", _w0.shape, xp.min(_w0), xp.max(_w0)) _sqrtg = domain.jacobian_det(0.5, 0.5, 0.5) - assert np.isclose(np.min(_w0), _sqrtg) - assert np.isclose(np.max(_w0), _sqrtg) + assert xp.isclose(xp.min(_w0), _sqrtg) + assert xp.isclose(xp.max(_w0), _sqrtg) # mass operators mass_ops = WeightedMassOperators(derham, domain) @@ -127,33 +144,36 @@ def test_accum_poisson(Nel, p, spl_kind, mapping, num_clones, Np=1000): acc = AccumulatorVector( particles, "H1", - accum_kernels.charge_density_0form, + Pyccelkernel(accum_kernels.charge_density_0form), mass_ops, domain.args_domain, ) - acc(particles.vdim) + acc() # sum all MC integrals - _sum_within_clone = np.empty(1, dtype=float) - _sum_within_clone[0] = np.sum(acc.vectors[0].toarray()) - clone_config.sub_comm.Allreduce(MPI.IN_PLACE, _sum_within_clone, op=MPI.SUM) + _sum_within_clone = xp.empty(1, dtype=float) + _sum_within_clone[0] = xp.sum(acc.vectors[0].toarray()) + if clone_config is not None: + clone_config.sub_comm.Allreduce(MPI.IN_PLACE, _sum_within_clone, op=MPI.SUM) - print(f"rank {mpi_rank}: {_sum_within_clone = }, {_sqrtg = }") + print(f"rank {mpi_rank}: {_sum_within_clone =}, {_sqrtg =}") # Check within clone - assert np.isclose(_sum_within_clone, _sqrtg) + assert xp.isclose(_sum_within_clone, _sqrtg) # Check for all clones - _sum_between_clones = np.empty(1, dtype=float) - _sum_between_clones[0] = np.sum(acc.vectors[0].toarray()) - mpi_comm.Allreduce(MPI.IN_PLACE, _sum_between_clones, op=MPI.SUM) - clone_config.inter_comm.Allreduce(MPI.IN_PLACE, _sqrtg, op=MPI.SUM) + _sum_between_clones = xp.empty(1, dtype=float) + _sum_between_clones[0] = xp.sum(acc.vectors[0].toarray()) + + if mpi_comm is not None: + mpi_comm.Allreduce(MPI.IN_PLACE, _sum_between_clones, op=MPI.SUM) + clone_config.inter_comm.Allreduce(MPI.IN_PLACE, _sqrtg, op=MPI.SUM) - print(f"rank {mpi_rank}: {_sum_between_clones = }, {_sqrtg = }") + print(f"rank {mpi_rank}: {_sum_between_clones =}, {_sqrtg =}") # Check within clone - assert np.isclose(_sum_between_clones, _sqrtg) + assert xp.isclose(_sum_between_clones, _sqrtg) if __name__ == "__main__": diff --git a/src/struphy/pic/tests/test_accumulation.py b/src/struphy/pic/tests/test_accumulation.py index 7fcd8f039..ed3a41ff4 100644 --- a/src/struphy/pic/tests/test_accumulation.py +++ b/src/struphy/pic/tests/test_accumulation.py @@ -1,11 +1,13 @@ import pytest +from struphy.utils.pyccel import Pyccelkernel + -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[2, 3, 4]]) @pytest.mark.parametrize( - "spl_kind", [[False, False, True], [False, True, False], [True, False, True], [True, True, False]] + "spl_kind", + [[False, False, True], [False, True, False], [True, False, True], [True, True, False]], ) @pytest.mark.parametrize( "mapping", @@ -35,7 +37,7 @@ def test_accumulation(Nel, p, spl_kind, mapping, Np=40, verbose=False): The times for both legacy and the new way are printed if verbose == True. This comparison only makes sense if the ..test_pic_legacy_files/ are also all compiled. """ - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI rank = MPI.COMM_WORLD.Get_rank() @@ -47,8 +49,9 @@ def test_accumulation(Nel, p, spl_kind, mapping, Np=40, verbose=False): def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): from time import time - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import MockComm + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.mass import WeightedMassOperators @@ -61,10 +64,15 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): from struphy.pic.tests.test_pic_legacy_files.accumulation_kernels_3d import kernel_step_ph_full from struphy.pic.utilities import BoundaryParameters, LoadingParameters, WeightsParameters - mpi_comm = MPI.COMM_WORLD - # assert mpi_comm.size >= 2 - rank = mpi_comm.Get_rank() - mpi_size = mpi_comm.Get_size() + if isinstance(MPI.COMM_WORLD, MockComm): + mpi_comm = None + rank = 0 + mpi_size = 1 + else: + mpi_comm = MPI.COMM_WORLD + # assert mpi_comm.size >= 2 + rank = mpi_comm.Get_rank() + mpi_size = mpi_comm.Get_size() # DOMAIN object dom_type = mapping[0] @@ -100,15 +108,17 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): particles.markers[ ~particles.holes, 6, - ] = np.random.rand(particles.n_mks_loc) + ] = xp.random.rand(particles.n_mks_loc) # gather all particles for legacy kernel - marker_shapes = np.zeros(mpi_size, dtype=int) - - mpi_comm.Allgather(np.array([particles.markers.shape[0]]), marker_shapes) + if mpi_comm is None: + marker_shapes = xp.array([particles.markers.shape[0]]) + else: + marker_shapes = xp.zeros(mpi_size, dtype=int) + mpi_comm.Allgather(xp.array([particles.markers.shape[0]]), marker_shapes) print(rank, marker_shapes) - particles_leg = np.zeros( + particles_leg = xp.zeros( (sum(marker_shapes), particles.markers.shape[1]), dtype=float, ) @@ -119,7 +129,7 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): cumulative_lengths = marker_shapes[0] for i in range(1, mpi_size): - arr_recv = np.zeros( + arr_recv = xp.zeros( (marker_shapes[i], particles.markers.shape[1]), dtype=float, ) @@ -130,7 +140,8 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): else: mpi_comm.Send(particles.markers, dest=0) - mpi_comm.Bcast(particles_leg, root=0) + if mpi_comm is not None: + mpi_comm.Bcast(particles_leg, root=0) # sort new particles if particles.mpi_comm: @@ -151,10 +162,10 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): for a in range(3): Ni = SPACES.Nbase_1form[a] - vec[a] = np.zeros((Ni[0], Ni[1], Ni[2], 3), dtype=float) + vec[a] = xp.zeros((Ni[0], Ni[1], Ni[2], 3), dtype=float) for b in range(3): - mat[a][b] = np.zeros( + mat[a][b] = xp.zeros( ( Ni[0], Ni[1], @@ -176,21 +187,21 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): SPACES.T[0], SPACES.T[1], SPACES.T[2], - np.array(SPACES.p), - np.array(Nel), - np.array(SPACES.NbaseN), - np.array(SPACES.NbaseD), + xp.array(SPACES.p), + xp.array(Nel), + xp.array(SPACES.NbaseN), + xp.array(SPACES.NbaseD), particles_leg.shape[0], domain.kind_map, domain.params_numpy, domain.T[0], domain.T[1], domain.T[2], - np.array(domain.p), - np.array( + xp.array(domain.p), + xp.array( domain.Nel, ), - np.array(domain.NbaseN), + xp.array(domain.NbaseN), domain.cx, domain.cy, domain.cz, @@ -207,7 +218,7 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): ) end_time = time() - tot_time = np.round(end_time - start_time, 3) + tot_time = xp.round(end_time - start_time, 3) mat[0][0] /= Np mat[0][1] /= Np @@ -229,7 +240,7 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): ACC = Accumulator( particles, "Hcurl", - accum_kernels.pc_lin_mhd_6d_full, + Pyccelkernel(accum_kernels.pc_lin_mhd_6d_full), mass_ops, domain.args_domain, add_vector=True, @@ -237,10 +248,12 @@ def pc_lin_mhd_6d_step_ph_full(Nel, p, spl_kind, mapping, Np, verbose=False): ) start_time = time() - ACC(1.0, 1.0, 0.0) + ACC( + 1.0, + ) end_time = time() - tot_time = np.round(end_time - start_time, 3) + tot_time = xp.round(end_time - start_time, 3) if rank == 0 and verbose: print(f"Step ph New took {tot_time} seconds.") diff --git a/src/struphy/pic/tests/test_binning.py b/src/struphy/pic/tests/test_binning.py index 2e7500e01..cda2524e7 100644 --- a/src/struphy/pic/tests/test_binning.py +++ b/src/struphy/pic/tests/test_binning.py @@ -35,9 +35,9 @@ def test_binning_6D_full_f(mapping, show_plot=False): name and specification of the mapping """ + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from struphy.geometry import domains from struphy.initial import perturbations @@ -79,7 +79,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): # test weights particles.initialize_weights() - v1_bins = np.linspace(-5.0, 5.0, 200, endpoint=True) + v1_bins = xp.linspace(-5.0, 5.0, 200, endpoint=True) dv = v1_bins[1] - v1_bins[0] binned_res, r2 = particles.binning( @@ -89,7 +89,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): v1_plot = v1_bins[:-1] + dv / 2 - ana_res = 1.0 / np.sqrt(2.0 * np.pi) * np.exp(-(v1_plot**2) / 2.0) + ana_res = 1.0 / xp.sqrt(2.0 * xp.pi) * xp.exp(-(v1_plot**2) / 2.0) if show_plot: plt.plot(v1_plot, ana_res, label="Analytical result") @@ -100,7 +100,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.02, f"Error between binned data and analytical result was {l2_error}" @@ -122,7 +122,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -132,7 +132,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): e1_plot = e1_bins[:-1] + de / 2 - ana_res = 1.0 + amp_n * np.cos(2 * np.pi * l_n * e1_plot) + ana_res = 1.0 + amp_n * xp.cos(2 * xp.pi * l_n * e1_plot) if show_plot: plt.plot(e1_plot, ana_res, label="Analytical result") @@ -143,7 +143,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.02, f"Error between binned data and analytical result was {l2_error}" @@ -182,7 +182,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -192,7 +192,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): e1_plot = e1_bins[:-1] + de / 2 - ana_res = n1 + amp_n1 * np.cos(2 * np.pi * l_n1 * e1_plot) + n2 + amp_n2 * np.cos(2 * np.pi * l_n2 * e1_plot) + ana_res = n1 + amp_n1 * xp.cos(2 * xp.pi * l_n1 * e1_plot) + n2 + amp_n2 * xp.cos(2 * xp.pi * l_n2 * e1_plot) # Compare s0 and the sum of two Maxwellians if show_plot: @@ -206,14 +206,14 @@ def test_binning_6D_full_f(mapping, show_plot=False): vth3=(particles.loading_params.moments[5], None), ) - v1 = np.linspace(-10.0, 10.0, 400) - phase_space = np.meshgrid( - np.array([0.0]), - np.array([0.0]), - np.array([0.0]), + v1 = xp.linspace(-10.0, 10.0, 400) + phase_space = xp.meshgrid( + xp.array([0.0]), + xp.array([0.0]), + xp.array([0.0]), v1, - np.array([0.0]), - np.array([0.0]), + xp.array([0.0]), + xp.array([0.0]), ) s0_vals = s0(*phase_space).squeeze() @@ -235,7 +235,7 @@ def test_binning_6D_full_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.04, f"Error between binned data and analytical result was {l2_error}" @@ -268,9 +268,9 @@ def test_binning_6D_delta_f(mapping, show_plot=False): name and specification of the mapping """ + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np - from mpi4py import MPI + from psydac.ddm.mpi import mpi as MPI from struphy.geometry import domains from struphy.initial import perturbations @@ -316,7 +316,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -326,7 +326,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): e1_plot = e1_bins[:-1] + de / 2 - ana_res = amp_n * np.cos(2 * np.pi * l_n * e1_plot) + ana_res = amp_n * xp.cos(2 * xp.pi * l_n * e1_plot) if show_plot: plt.plot(e1_plot, ana_res, label="Analytical result") @@ -337,7 +337,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.02, f"Error between binned data and analytical result was {l2_error}" @@ -376,7 +376,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -386,7 +386,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): e1_plot = e1_bins[:-1] + de / 2 - ana_res = amp_n1 * np.cos(2 * np.pi * l_n1 * e1_plot) + amp_n2 * np.cos(2 * np.pi * l_n2 * e1_plot) + ana_res = amp_n1 * xp.cos(2 * xp.pi * l_n1 * e1_plot) + amp_n2 * xp.cos(2 * xp.pi * l_n2 * e1_plot) # Compare s0 and the sum of two Maxwellians if show_plot: @@ -400,14 +400,14 @@ def test_binning_6D_delta_f(mapping, show_plot=False): vth3=(particles.loading_params.moments[5], None), ) - v1 = np.linspace(-10.0, 10.0, 400) - phase_space = np.meshgrid( - np.array([0.0]), - np.array([0.0]), - np.array([0.0]), + v1 = xp.linspace(-10.0, 10.0, 400) + phase_space = xp.meshgrid( + xp.array([0.0]), + xp.array([0.0]), + xp.array([0.0]), v1, - np.array([0.0]), - np.array([0.0]), + xp.array([0.0]), + xp.array([0.0]), ) s0_vals = s0(*phase_space).squeeze() @@ -429,7 +429,7 @@ def test_binning_6D_delta_f(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - binned_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - binned_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.04, f"Error between binned data and analytical result was {l2_error}" @@ -437,7 +437,6 @@ def test_binning_6D_delta_f(mapping, show_plot=False): # ========================================== # ========== multi-threaded tests ========== # ========================================== -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize( "mapping", [ @@ -465,9 +464,10 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): name and specification of the mapping """ + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np - from mpi4py import MPI + from psydac.ddm.mpi import MockComm + from psydac.ddm.mpi import mpi as MPI from struphy.geometry import domains from struphy.initial import perturbations @@ -490,10 +490,14 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): domain = domain_class(**mapping[1]) # Psydac discrete Derham sequence - comm = MPI.COMM_WORLD - size = comm.Get_size() - rank = comm.Get_rank() - assert size > 1 + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + size = 1 + rank = 0 + else: + comm = MPI.COMM_WORLD + size = comm.Get_size() + rank = comm.Get_rank() # create particles bc_params = ("periodic", "periodic", "periodic") @@ -515,7 +519,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): # test weights particles.initialize_weights() - v1_bins = np.linspace(-5.0, 5.0, 200, endpoint=True) + v1_bins = xp.linspace(-5.0, 5.0, 200, endpoint=True) dv = v1_bins[1] - v1_bins[0] binned_res, r2 = particles.binning( @@ -524,13 +528,16 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - mpi_res = np.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + if comm is None: + mpi_res = binned_res + else: + mpi_res = xp.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() v1_plot = v1_bins[:-1] + dv / 2 - ana_res = 1.0 / np.sqrt(2.0 * np.pi) * np.exp(-(v1_plot**2) / 2.0) + ana_res = 1.0 / xp.sqrt(2.0 * xp.pi) * xp.exp(-(v1_plot**2) / 2.0) if show_plot and rank == 0: plt.plot(v1_plot, ana_res, label="Analytical result") @@ -541,7 +548,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.03, f"Error between binned data and analytical result was {l2_error}" @@ -564,7 +571,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -573,13 +580,16 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - mpi_res = np.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + if comm is None: + mpi_res = binned_res + else: + mpi_res = xp.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() e1_plot = e1_bins[:-1] + de / 2 - ana_res = 1.0 + amp_n * np.cos(2 * np.pi * l_n * e1_plot) + ana_res = 1.0 + amp_n * xp.cos(2 * xp.pi * l_n * e1_plot) if show_plot and rank == 0: plt.plot(e1_plot, ana_res, label="Analytical result") @@ -590,7 +600,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.03, f"Error between binned data and analytical result was {l2_error}" @@ -621,8 +631,8 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n1], "amps": [amp_n1], - } - } + }, + }, }, "Maxwellian3D_2": { "n": { @@ -630,8 +640,8 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n2], "amps": [amp_n2], - } - } + }, + }, }, } pert_1 = perturbations.ModesCos(ls=(l_n1,), amps=(amp_n1,)) @@ -658,7 +668,7 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -667,13 +677,16 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - mpi_res = np.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + if comm is None: + mpi_res = binned_res + else: + mpi_res = xp.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() e1_plot = e1_bins[:-1] + de / 2 - ana_res = n1 + amp_n1 * np.cos(2 * np.pi * l_n1 * e1_plot) + n2 + amp_n2 * np.cos(2 * np.pi * l_n2 * e1_plot) + ana_res = n1 + amp_n1 * xp.cos(2 * xp.pi * l_n1 * e1_plot) + n2 + amp_n2 * xp.cos(2 * xp.pi * l_n2 * e1_plot) # Compare s0 and the sum of two Maxwellians if show_plot and rank == 0: @@ -687,14 +700,14 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): vth3=(particles.loading_params.moments[5], None), ) - v1 = np.linspace(-10.0, 10.0, 400) - phase_space = np.meshgrid( - np.array([0.0]), - np.array([0.0]), - np.array([0.0]), + v1 = xp.linspace(-10.0, 10.0, 400) + phase_space = xp.meshgrid( + xp.array([0.0]), + xp.array([0.0]), + xp.array([0.0]), v1, - np.array([0.0]), - np.array([0.0]), + xp.array([0.0]), + xp.array([0.0]), ) s0_vals = s0(*phase_space).squeeze() @@ -716,12 +729,11 @@ def test_binning_6D_full_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.04, f"Error between binned data and analytical result was {l2_error}" -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize( "mapping", [ @@ -749,9 +761,10 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): name and specification of the mapping """ + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np - from mpi4py import MPI + from psydac.ddm.mpi import MockComm + from psydac.ddm.mpi import mpi as MPI from struphy.geometry import domains from struphy.initial import perturbations @@ -774,10 +787,14 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): domain = domain_class(**mapping[1]) # Psydac discrete Derham sequence - comm = MPI.COMM_WORLD - size = comm.Get_size() - rank = comm.Get_rank() - assert size > 1 + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + size = 1 + rank = 0 + else: + comm = MPI.COMM_WORLD + size = comm.Get_size() + rank = comm.Get_rank() # create particles bc_params = ("periodic", "periodic", "periodic") @@ -797,8 +814,8 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n], "amps": [amp_n], - } - } + }, + }, } pert = perturbations.ModesCos(ls=(l_n,), amps=(amp_n,)) background = Maxwellian3D(n=(1.0, pert)) @@ -813,7 +830,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -822,13 +839,16 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - mpi_res = np.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + if comm is None: + mpi_res = binned_res + else: + mpi_res = xp.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() e1_plot = e1_bins[:-1] + de / 2 - ana_res = amp_n * np.cos(2 * np.pi * l_n * e1_plot) + ana_res = amp_n * xp.cos(2 * xp.pi * l_n * e1_plot) if show_plot and rank == 0: plt.plot(e1_plot, ana_res, label="Analytical result") @@ -839,7 +859,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.02, f"Error between binned data and analytical result was {l2_error}" @@ -871,7 +891,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n1], "amps": [amp_n1], - } + }, }, }, "Maxwellian3D_2": { @@ -881,7 +901,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): "given_in_basis": "0", "ls": [l_n2], "amps": [amp_n2], - } + }, }, }, } @@ -909,7 +929,7 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): particles.draw_markers() particles.initialize_weights() - e1_bins = np.linspace(0.0, 1.0, 200, endpoint=True) + e1_bins = xp.linspace(0.0, 1.0, 200, endpoint=True) de = e1_bins[1] - e1_bins[0] binned_res, r2 = particles.binning( @@ -918,13 +938,16 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): ) # Reduce all threads to get complete result - mpi_res = np.zeros_like(binned_res) - comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) - comm.Barrier() + if comm is None: + mpi_res = binned_res + else: + mpi_res = xp.zeros_like(binned_res) + comm.Allreduce(binned_res, mpi_res, op=MPI.SUM) + comm.Barrier() e1_plot = e1_bins[:-1] + de / 2 - ana_res = amp_n1 * np.cos(2 * np.pi * l_n1 * e1_plot) + amp_n2 * np.cos(2 * np.pi * l_n2 * e1_plot) + ana_res = amp_n1 * xp.cos(2 * xp.pi * l_n1 * e1_plot) + amp_n2 * xp.cos(2 * xp.pi * l_n2 * e1_plot) # Compare s0 and the sum of two Maxwellians if show_plot and rank == 0: @@ -938,14 +961,14 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): vth3=(particles.loading_params.moments[5], None), ) - v1 = np.linspace(-10.0, 10.0, 400) - phase_space = np.meshgrid( - np.array([0.0]), - np.array([0.0]), - np.array([0.0]), + v1 = xp.linspace(-10.0, 10.0, 400) + phase_space = xp.meshgrid( + xp.array([0.0]), + xp.array([0.0]), + xp.array([0.0]), v1, - np.array([0.0]), - np.array([0.0]), + xp.array([0.0]), + xp.array([0.0]), ) s0_vals = s0(*phase_space).squeeze() @@ -967,16 +990,23 @@ def test_binning_6D_delta_f_mpi(mapping, show_plot=False): plt.legend() plt.show() - l2_error = np.sqrt(np.sum((ana_res - mpi_res) ** 2)) / np.sqrt(np.sum((ana_res) ** 2)) + l2_error = xp.sqrt(xp.sum((ana_res - mpi_res) ** 2)) / xp.sqrt(xp.sum((ana_res) ** 2)) assert l2_error <= 0.04, f"Error between binned data and analytical result was {l2_error}" if __name__ == "__main__": - from mpi4py import MPI + from psydac.ddm.mpi import MockComm + from psydac.ddm.mpi import mpi as MPI - comm = MPI.COMM_WORLD - size = comm.Get_size() + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + size = 1 + rank = 0 + else: + comm = MPI.COMM_WORLD + size = comm.Get_size() + rank = comm.Get_rank() if comm is None or size == 1: test_binning_6D_full_f( diff --git a/src/struphy/pic/tests/test_draw_parallel.py b/src/struphy/pic/tests/test_draw_parallel.py index c4520eeab..cf95f4dc7 100644 --- a/src/struphy/pic/tests/test_draw_parallel.py +++ b/src/struphy/pic/tests/test_draw_parallel.py @@ -1,7 +1,6 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -36,8 +35,8 @@ def test_draw(Nel, p, spl_kind, mapping, ppc=10): """Asserts whether all particles are on the correct process after `particles.mpi_sort_markers()`.""" - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.geometry import domains @@ -45,7 +44,6 @@ def test_draw(Nel, p, spl_kind, mapping, ppc=10): from struphy.pic.utilities import BoundaryParameters, LoadingParameters, WeightsParameters comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() seed = 1234 @@ -87,7 +85,7 @@ def test_draw(Nel, p, spl_kind, mapping, ppc=10): particles.initialize_weights() _w0 = particles.weights print("Test weights:") - print(f"rank {rank}:", _w0.shape, np.min(_w0), np.max(_w0)) + print(f"rank {rank}:", _w0.shape, xp.min(_w0), xp.max(_w0)) comm.Barrier() print("Number of particles w/wo holes on each process before sorting : ") @@ -108,17 +106,17 @@ def test_draw(Nel, p, spl_kind, mapping, ppc=10): print("Rank", rank, ":", particles.n_mks_loc, particles.markers.shape[0]) # are all markers in the correct domain? - conds = np.logical_and( + conds = xp.logical_and( particles.markers[:, :3] > derham.domain_array[rank, 0::3], particles.markers[:, :3] < derham.domain_array[rank, 1::3], ) holes = particles.markers[:, 0] == -1.0 - stay = np.all(conds, axis=1) + stay = xp.all(conds, axis=1) - error_mks = particles.markers[np.logical_and(~stay, ~holes)] + error_mks = particles.markers[xp.logical_and(~stay, ~holes)] assert error_mks.size == 0, ( - f"rank {rank} | markers not on correct process: {np.nonzero(np.logical_and(~stay, ~holes))} \n corresponding positions:\n {error_mks[:, :3]}" + f"rank {rank} | markers not on correct process: {xp.nonzero(xp.logical_and(~stay, ~holes))} \n corresponding positions:\n {error_mks[:, :3]}" ) diff --git a/src/struphy/pic/tests/test_mat_vec_filler.py b/src/struphy/pic/tests/test_mat_vec_filler.py index 36f3924f0..073d52ae7 100644 --- a/src/struphy/pic/tests/test_mat_vec_filler.py +++ b/src/struphy/pic/tests/test_mat_vec_filler.py @@ -1,8 +1,7 @@ -import numpy as np +import cunumpy as xp import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[1, 2, 3]]) @pytest.mark.parametrize("spl_kind", [[False, False, True], [False, True, False], [True, False, False]]) @@ -15,8 +14,8 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): from time import sleep - from mpi4py import MPI from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL + from psydac.ddm.mpi import mpi as MPI from psydac.linalg.stencil import StencilMatrix, StencilVector from struphy.bsplines import bsplines_kernels as bsp @@ -24,7 +23,6 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): from struphy.pic.accumulation import particle_to_mat_kernels as ptomat comm = MPI.COMM_WORLD - assert comm.size >= 2 rank = comm.Get_rank() # Psydac discrete Derham sequence @@ -34,12 +32,12 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): print(f"\nNel={Nel}, p={p}, spl_kind={spl_kind}\n") # DR attributes - pn = np.array(DR.p) + pn = xp.array(DR.p) tn1, tn2, tn3 = DR.Vh_fem["0"].knots starts1 = {} - starts1["v0"] = np.array(DR.Vh["0"].starts) + starts1["v0"] = xp.array(DR.Vh["0"].starts) comm.Barrier() sleep(0.02 * (rank + 1)) @@ -72,8 +70,11 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): for j in range(3): mat["v1"][-1] += [ StencilMatrix( - DR.Vh["1"].spaces[i], DR.Vh["1"].spaces[j], backend=PSYDAC_BACKEND_GPYCCEL, precompiled=True - )._data + DR.Vh["1"].spaces[i], + DR.Vh["1"].spaces[j], + backend=PSYDAC_BACKEND_GPYCCEL, + precompiled=True, + )._data, ] vec["v1"] = [] @@ -86,8 +87,11 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): for j in range(3): mat["v2"][-1] += [ StencilMatrix( - DR.Vh["2"].spaces[i], DR.Vh["2"].spaces[j], backend=PSYDAC_BACKEND_GPYCCEL, precompiled=True - )._data + DR.Vh["2"].spaces[i], + DR.Vh["2"].spaces[j], + backend=PSYDAC_BACKEND_GPYCCEL, + precompiled=True, + )._data, ] vec["v2"] = [] @@ -95,14 +99,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): vec["v2"] += [StencilVector(DR.Vh["2"].spaces[i])._data] # Some filling for testing - fill_mat = np.reshape(np.arange(9, dtype=float), (3, 3)) + 1.0 - fill_vec = np.arange(3, dtype=float) + 1.0 + fill_mat = xp.reshape(xp.arange(9, dtype=float), (3, 3)) + 1.0 + fill_vec = xp.arange(3, dtype=float) + 1.0 # Random points in domain of process (VERY IMPORTANT to be in the right domain, otherwise NON-TRACKED errors occur in filler_kernels !!) dom = DR.domain_array[rank] - eta1s = np.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] - eta2s = np.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] - eta3s = np.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] + eta1s = xp.random.rand(n_markers) * (dom[1] - dom[0]) + dom[0] + eta2s = xp.random.rand(n_markers) * (dom[4] - dom[3]) + dom[3] + eta3s = xp.random.rand(n_markers) * (dom[7] - dom[6]) + dom[6] for eta1, eta2, eta3 in zip(eta1s, eta2s, eta3s): comm.Barrier() @@ -119,13 +123,13 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): span3 = bsp.find_span(tn3, DR.p[2], eta3) # non-zero spline values at eta - bn1 = np.empty(DR.p[0] + 1, dtype=float) - bn2 = np.empty(DR.p[1] + 1, dtype=float) - bn3 = np.empty(DR.p[2] + 1, dtype=float) + bn1 = xp.empty(DR.p[0] + 1, dtype=float) + bn2 = xp.empty(DR.p[1] + 1, dtype=float) + bn3 = xp.empty(DR.p[2] + 1, dtype=float) - bd1 = np.empty(DR.p[0], dtype=float) - bd2 = np.empty(DR.p[1], dtype=float) - bd3 = np.empty(DR.p[2], dtype=float) + bd1 = xp.empty(DR.p[0], dtype=float) + bd2 = xp.empty(DR.p[1], dtype=float) + bd3 = xp.empty(DR.p[2], dtype=float) bsp.b_d_splines_slim(tn1, DR.p[0], eta1, span1, bn1, bd1) bsp.b_d_splines_slim(tn2, DR.p[1], eta2, span2, bn2, bd2) @@ -137,9 +141,9 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): ie3 = span3 - pn[2] # global indices of non-vanishing B- and D-splines (no modulo) - glob_n1 = np.arange(ie1, ie1 + pn[0] + 1) - glob_n2 = np.arange(ie2, ie2 + pn[1] + 1) - glob_n3 = np.arange(ie3, ie3 + pn[2] + 1) + glob_n1 = xp.arange(ie1, ie1 + pn[0] + 1) + glob_n2 = xp.arange(ie2, ie2 + pn[1] + 1) + glob_n3 = xp.arange(ie3, ie3 + pn[2] + 1) glob_d1 = glob_n1[:-1] glob_d2 = glob_n2[:-1] @@ -165,10 +169,10 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): # local column indices in _data of non-vanishing B- and D-splines, as sets for comparison cols = [{}, {}, {}] for n in range(3): - cols[n]["NN"] = set(np.arange(2 * pn[n] + 1)) - cols[n]["ND"] = set(np.arange(2 * pn[n])) - cols[n]["DN"] = set(np.arange(1, 2 * pn[n] + 1)) - cols[n]["DD"] = set(np.arange(1, 2 * pn[n])) + cols[n]["NN"] = set(xp.arange(2 * pn[n] + 1)) + cols[n]["ND"] = set(xp.arange(2 * pn[n])) + cols[n]["DN"] = set(xp.arange(1, 2 * pn[n] + 1)) + cols[n]["DD"] = set(xp.arange(1, 2 * pn[n])) # testing vector-valued spaces spaces_vector = ["v1", "v2"] @@ -215,7 +219,13 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): for n, ij in enumerate(ind_pairs): assert_mat( - args[n], rows, cols, basis[space][ij[0]], basis[space][ij[1]], rank, verbose=False + args[n], + rows, + cols, + basis[space][ij[0]], + basis[space][ij[1]], + rank, + verbose=False, ) # assertion test of mat if mv == "m_v": for i in range(3): @@ -232,7 +242,13 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): for n, ij in enumerate(ind_pairs): assert_mat( - args[n], rows, cols, basis[space][ij[0]], basis[space][ij[1]], rank, verbose=False + args[n], + rows, + cols, + basis[space][ij[0]], + basis[space][ij[1]], + rank, + verbose=False, ) # assertion test of mat if mv == "m_v": for i in range(3): @@ -245,14 +261,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): # testing salar spaces if rank == 0: - print(f"\nTesting mat_fill_b_v0 ...") + print("\nTesting mat_fill_b_v0 ...") ptomat.mat_fill_b_v0(DR.args_derham, eta1, eta2, eta3, mat["v0"], fill_mat[0, 0]) assert_mat(mat["v0"], rows, cols, basis["v0"], basis["v0"], rank) # assertion test of mat count += 1 comm.Barrier() if rank == 0: - print(f"\nTesting m_v_fill_b_v0 ...") + print("\nTesting m_v_fill_b_v0 ...") ptomat.m_v_fill_b_v0(DR.args_derham, eta1, eta2, eta3, mat["v0"], fill_mat[0, 0], vec["v0"], fill_vec[0]) assert_mat(mat["v0"], rows, cols, basis["v0"], basis["v0"], rank) # assertion test of mat assert_vec(vec["v0"], rows, basis["v0"], rank) # assertion test of vec @@ -260,14 +276,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): comm.Barrier() if rank == 0: - print(f"\nTesting mat_fill_b_v3 ...") + print("\nTesting mat_fill_b_v3 ...") ptomat.mat_fill_b_v3(DR.args_derham, eta1, eta2, eta3, mat["v3"], fill_mat[0, 0]) assert_mat(mat["v3"], rows, cols, basis["v3"], basis["v3"], rank) # assertion test of mat count += 1 comm.Barrier() if rank == 0: - print(f"\nTesting m_v_fill_b_v3 ...") + print("\nTesting m_v_fill_b_v3 ...") ptomat.m_v_fill_b_v3(DR.args_derham, eta1, eta2, eta3, mat["v3"], fill_mat[0, 0], vec["v3"], fill_vec[0]) assert_mat(mat["v3"], rows, cols, basis["v3"], basis["v3"], rank) # assertion test of mat assert_vec(vec["v3"], rows, basis["v3"], rank) # assertion test of vec @@ -275,14 +291,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): comm.Barrier() if rank == 0: - print(f"\nTesting mat_fill_v0 ...") + print("\nTesting mat_fill_v0 ...") ptomat.mat_fill_v0(DR.args_derham, span1, span2, span3, mat["v0"], fill_mat[0, 0]) assert_mat(mat["v0"], rows, cols, basis["v0"], basis["v0"], rank) # assertion test of mat count += 1 comm.Barrier() if rank == 0: - print(f"\nTesting m_v_fill_v0 ...") + print("\nTesting m_v_fill_v0 ...") ptomat.m_v_fill_v0(DR.args_derham, span1, span2, span3, mat["v0"], fill_mat[0, 0], vec["v0"], fill_vec[0]) assert_mat(mat["v0"], rows, cols, basis["v0"], basis["v0"], rank) # assertion test of mat assert_vec(vec["v0"], rows, basis["v0"], rank) # assertion test of vec @@ -290,14 +306,14 @@ def test_particle_to_mat_kernels(Nel, p, spl_kind, n_markers=1): comm.Barrier() if rank == 0: - print(f"\nTesting mat_fill_v3 ...") + print("\nTesting mat_fill_v3 ...") ptomat.mat_fill_v3(DR.args_derham, span1, span2, span3, mat["v3"], fill_mat[0, 0]) assert_mat(mat["v3"], rows, cols, basis["v3"], basis["v3"], rank) # assertion test of mat count += 1 comm.Barrier() if rank == 0: - print(f"\nTesting m_v_fill_v3 ...") + print("\nTesting m_v_fill_v3 ...") ptomat.m_v_fill_v3(DR.args_derham, span1, span2, span3, mat["v3"], fill_mat[0, 0], vec["v3"], fill_vec[0]) assert_mat(mat["v3"], rows, cols, basis["v3"], basis["v3"], rank) # assertion test of mat assert_vec(vec["v3"], rows, basis["v3"], rank) # assertion test of vec @@ -338,23 +354,23 @@ def assert_mat(mat, rows, cols, row_str, col_str, rank, verbose=False): """ assert len(mat.shape) == 6 # assert non NaN - assert ~np.isnan(mat).any() + assert ~xp.isnan(mat).any() atol = 1e-14 if verbose: print(f"\n({row_str}) ({col_str})") - print(f"rank {rank} | ind_row1: {set(np.where(mat > atol)[0])}") - print(f"rank {rank} | ind_row2: {set(np.where(mat > atol)[1])}") - print(f"rank {rank} | ind_row3: {set(np.where(mat > atol)[2])}") - print(f"rank {rank} | ind_col1: {set(np.where(mat > atol)[3])}") - print(f"rank {rank} | ind_col2: {set(np.where(mat > atol)[4])}") - print(f"rank {rank} | ind_col3: {set(np.where(mat > atol)[5])}") + print(f"rank {rank} | ind_row1: {set(xp.where(mat > atol)[0])}") + print(f"rank {rank} | ind_row2: {set(xp.where(mat > atol)[1])}") + print(f"rank {rank} | ind_row3: {set(xp.where(mat > atol)[2])}") + print(f"rank {rank} | ind_col1: {set(xp.where(mat > atol)[3])}") + print(f"rank {rank} | ind_col2: {set(xp.where(mat > atol)[4])}") + print(f"rank {rank} | ind_col3: {set(xp.where(mat > atol)[5])}") # check if correct indices are non-zero for n, (r, c) in enumerate(zip(row_str, col_str)): - assert set(np.where(mat > atol)[n]) == rows[n][r] - assert set(np.where(mat > atol)[n + 3]) == cols[n][r + c] + assert set(xp.where(mat > atol)[n]) == rows[n][r] + assert set(xp.where(mat > atol)[n + 3]) == cols[n][r + c] # Set matrix back to zero mat[:, :] = 0.0 @@ -385,19 +401,19 @@ def assert_vec(vec, rows, row_str, rank, verbose=False): """ assert len(vec.shape) == 3 # assert non Nan - assert ~np.isnan(vec).any() + assert ~xp.isnan(vec).any() atol = 1e-14 if verbose: print(f"\n({row_str})") - print(f"rank {rank} | ind_row1: {set(np.where(vec > atol)[0])}") - print(f"rank {rank} | ind_row2: {set(np.where(vec > atol)[1])}") - print(f"rank {rank} | ind_row3: {set(np.where(vec > atol)[2])}") + print(f"rank {rank} | ind_row1: {set(xp.where(vec > atol)[0])}") + print(f"rank {rank} | ind_row2: {set(xp.where(vec > atol)[1])}") + print(f"rank {rank} | ind_row3: {set(xp.where(vec > atol)[2])}") # check if correct indices are non-zero for n, r in enumerate(row_str): - assert set(np.where(vec > atol)[n]) == rows[n][r] + assert set(xp.where(vec > atol)[n]) == rows[n][r] # Set vector back to zero vec[:] = 0.0 diff --git a/src/struphy/pic/tests/test_pic_legacy_files/accumulation.py b/src/struphy/pic/tests/test_pic_legacy_files/accumulation.py index 5687f6287..6bb225571 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/accumulation.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/accumulation.py @@ -8,9 +8,9 @@ import time -import numpy as np +import cunumpy as xp import scipy.sparse as spa -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI import struphy.pic.tests.test_pic_legacy_files.accumulation_kernels_3d as pic_ker_3d @@ -69,22 +69,22 @@ def __init__(self, tensor_space_FEM, domain, basis_u, mpi_comm, use_control, cv_ else: Ni = getattr(self.space, "Nbase_" + str(self.basis_u) + "form")[a] - self.vecs_loc[a] = np.empty((Ni[0], Ni[1], Ni[2]), dtype=float) - self.vecs_glo[a] = np.empty((Ni[0], Ni[1], Ni[2]), dtype=float) + self.vecs_loc[a] = xp.empty((Ni[0], Ni[1], Ni[2]), dtype=float) + self.vecs_glo[a] = xp.empty((Ni[0], Ni[1], Ni[2]), dtype=float) for b in range(3): if self.space.dim == 2: - self.blocks_loc[a][b] = np.empty( + self.blocks_loc[a][b] = xp.empty( (Ni[0], Ni[1], Ni[2], 2 * self.space.p[0] + 1, 2 * self.space.p[1] + 1, self.space.NbaseN[2]), dtype=float, ) - self.blocks_glo[a][b] = np.empty( + self.blocks_glo[a][b] = xp.empty( (Ni[0], Ni[1], Ni[2], 2 * self.space.p[0] + 1, 2 * self.space.p[1] + 1, self.space.NbaseN[2]), dtype=float, ) else: - self.blocks_loc[a][b] = np.empty( + self.blocks_loc[a][b] = xp.empty( ( Ni[0], Ni[1], @@ -95,7 +95,7 @@ def __init__(self, tensor_space_FEM, domain, basis_u, mpi_comm, use_control, cv_ ), dtype=float, ) - self.blocks_glo[a][b] = np.empty( + self.blocks_glo[a][b] = xp.empty( ( Ni[0], Ni[1], @@ -134,16 +134,16 @@ def to_sparse_step1(self): Ni = self.space.Nbase_2form[a] Nj = self.space.Nbase_2form[b] - indices = np.indices(self.blocks_glo[a][b].shape) + indices = xp.indices(self.blocks_glo[a][b].shape) row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() - shift = [np.arange(Ni) - p for Ni, p in zip(Ni[:2], self.space.p[:2])] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni[:2], self.space.p[:2])] if self.space.dim == 2: - shift += [np.zeros(self.space.NbaseN[2], dtype=int)] + shift += [xp.zeros(self.space.NbaseN[2], dtype=int)] else: - shift += [np.arange(Ni[2]) - self.space.p[2]] + shift += [xp.arange(Ni[2]) - self.space.p[2]] col1 = (indices[3] + shift[0][:, None, None, None, None, None]) % Nj[0] col2 = (indices[4] + shift[1][None, :, None, None, None, None]) % Nj[1] @@ -159,7 +159,8 @@ def to_sparse_step1(self): # final block matrix M = spa.bmat( - [[None, M[0][1], M[0][2]], [-M[0][1].T, None, M[1][2]], [-M[0][2].T, -M[1][2].T, None]], format="csr" + [[None, M[0][1], M[0][2]], [-M[0][1].T, None, M[1][2]], [-M[0][2].T, -M[1][2].T, None]], + format="csr", ) # apply extraction operator @@ -201,16 +202,16 @@ def to_sparse_step3(self): Ni = self.space.Nbase_2form[a] Nj = self.space.Nbase_2form[b] - indices = np.indices(self.blocks_glo[a][b].shape) + indices = xp.indices(self.blocks_glo[a][b].shape) row = (Ni[1] * Ni[2] * indices[0] + Ni[2] * indices[1] + indices[2]).flatten() - shift = [np.arange(Ni) - p for Ni, p in zip(Ni[:2], self.space.p[:2])] + shift = [xp.arange(Ni) - p for Ni, p in zip(Ni[:2], self.space.p[:2])] if self.space.dim == 2: - shift += [np.zeros(self.space.NbaseN[2], dtype=int)] + shift += [xp.zeros(self.space.NbaseN[2], dtype=int)] else: - shift += [np.arange(Ni[2]) - self.space.p[2]] + shift += [xp.arange(Ni[2]) - self.space.p[2]] col1 = (indices[3] + shift[0][:, None, None, None, None, None]) % Nj[0] col2 = (indices[4] + shift[1][None, :, None, None, None, None]) % Nj[1] @@ -226,7 +227,8 @@ def to_sparse_step3(self): # final block matrix M = spa.bmat( - [[M[0][0], M[0][1], M[0][2]], [M[0][1].T, M[1][1], M[1][2]], [M[0][2].T, M[1][2].T, M[2][2]]], format="csr" + [[M[0][0], M[0][1], M[0][2]], [M[0][1].T, M[1][1], M[1][2]], [M[0][2].T, M[1][2].T, M[2][2]]], + format="csr", ) # apply extraction operator @@ -528,15 +530,15 @@ def assemble_step3(self, b2_eq, b2): # build global sparse matrix and global vector if self.basis_u == 0: return self.to_sparse_step3(), self.space.Ev_0.dot( - np.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())) + xp.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())), ) elif self.basis_u == 1: return self.to_sparse_step3(), self.space.E1_0.dot( - np.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())) + xp.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())), ) elif self.basis_u == 2: return self.to_sparse_step3(), self.space.E2_0.dot( - np.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())) + xp.concatenate((self.vecs[0].flatten(), self.vecs[1].flatten(), self.vecs[2].flatten())), ) diff --git a/src/struphy/pic/tests/test_pic_legacy_files/accumulation_kernels_3d.py b/src/struphy/pic/tests/test_pic_legacy_files/accumulation_kernels_3d.py index c70261023..349cca379 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/accumulation_kernels_3d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/accumulation_kernels_3d.py @@ -185,13 +185,49 @@ def kernel_step1( bsp.b_d_splines_slim(t3, int(pn3), eta3, int(span3), bn3, bd3) b[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + b2_1, ) b[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + b2_2, ) b[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + b2_3, ) b_prod[0, 1] = -b[2] @@ -554,13 +590,49 @@ def kernel_step3( bsp.b_d_splines_slim(t3, int(pn3), eta3, int(span3), bn3, bd3) b[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + b2_1, ) b[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + b2_2, ) b[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + b2_3, ) b_prod[0, 1] = -b[2] @@ -1130,7 +1202,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat11[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] for jl1 in range(pn1 + 1): @@ -1142,7 +1221,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat12[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] for jl1 in range(pn1 + 1): @@ -1154,7 +1240,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat13[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] # add contribution to 22 component (NDN NDN) and 23 component (NDN NND) @@ -1179,7 +1272,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat22[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] for jl1 in range(pn1 + 1): @@ -1191,7 +1291,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat23[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] # add contribution to 33 component (NND NND) @@ -1216,7 +1323,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat33[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] elif basis_u == 2: @@ -1242,7 +1356,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat11[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] for jl1 in range(pd1 + 1): @@ -1254,7 +1375,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat12[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] for jl1 in range(pd1 + 1): @@ -1266,7 +1394,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat13[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] # add contribution to 22 component (DND DND) and 23 component (DND DDN) @@ -1291,7 +1426,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat22[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] for jl1 in range(pd1 + 1): @@ -1303,7 +1445,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat23[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] # add contribution to 33 component (DDN DDN) @@ -1328,7 +1477,14 @@ def kernel_step_ph_full( for vp in range(3): for vq in range(3): mat33[ - i1, i2, i3, pn1 + jl1 - il1, pn2 + jl2 - il2, pn3 + jl3 - il3, vp, vq + i1, + i2, + i3, + pn1 + jl1 - il1, + pn2 + jl2 - il2, + pn3 + jl3 - il3, + vp, + vq, ] += bj3 * v[vp] * v[vq] # -- removed omp: #$ omp end parallel diff --git a/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d.py b/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d.py index 587b8b15f..2e54d34dd 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d.py @@ -74,17 +74,53 @@ def f( if kind_map == 0: if component == 1: value = eva_3d.evaluate_n_n_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cx, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cx, + eta1, + eta2, + eta3, ) elif component == 2: value = eva_3d.evaluate_n_n_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cy, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cy, + eta1, + eta2, + eta3, ) elif component == 3: value = eva_3d.evaluate_n_n_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cz, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cz, + eta1, + eta2, + eta3, ) # ==== 2d spline (straight in 3rd direction) === @@ -110,7 +146,7 @@ def f( elif kind_map == 2: if component == 1: value = eva_2d.evaluate_n_n(tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2) * cos( - 2 * pi * eta3 + 2 * pi * eta3, ) if eta1 == 0.0 and cx[0, 0, 0] == cx[0, 1, 0]: @@ -124,7 +160,7 @@ def f( elif component == 3: value = eva_2d.evaluate_n_n(tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2) * sin( - 2 * pi * eta3 + 2 * pi * eta3, ) if eta1 == 0.0 and cx[0, 0, 0] == cx[0, 1, 0]: @@ -299,39 +335,147 @@ def df( if kind_map == 0: if component == 11: value = eva_3d.evaluate_diffn_n_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cx, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cx, + eta1, + eta2, + eta3, ) elif component == 12: value = eva_3d.evaluate_n_diffn_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cx, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cx, + eta1, + eta2, + eta3, ) elif component == 13: value = eva_3d.evaluate_n_n_diffn( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cx, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cx, + eta1, + eta2, + eta3, ) elif component == 21: value = eva_3d.evaluate_diffn_n_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cy, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cy, + eta1, + eta2, + eta3, ) elif component == 22: value = eva_3d.evaluate_n_diffn_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cy, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cy, + eta1, + eta2, + eta3, ) elif component == 23: value = eva_3d.evaluate_n_n_diffn( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cy, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cy, + eta1, + eta2, + eta3, ) elif component == 31: value = eva_3d.evaluate_diffn_n_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cz, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cz, + eta1, + eta2, + eta3, ) elif component == 32: value = eva_3d.evaluate_n_diffn_n( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cz, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cz, + eta1, + eta2, + eta3, ) elif component == 33: value = eva_3d.evaluate_n_n_diffn( - tn1, tn2, tn3, pn[0], pn[1], pn[2], nbase_n[0], nbase_n[1], nbase_n[2], cz, eta1, eta2, eta3 + tn1, + tn2, + tn3, + pn[0], + pn[1], + pn[2], + nbase_n[0], + nbase_n[1], + nbase_n[2], + cz, + eta1, + eta2, + eta3, ) # ==== 2d spline (straight in 3rd direction) === @@ -369,11 +513,27 @@ def df( elif kind_map == 2: if component == 11: value = eva_2d.evaluate_diffn_n( - tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2 + tn1, + tn2, + pn[0], + pn[1], + nbase_n[0], + nbase_n[1], + cx[:, :, 0], + eta1, + eta2, ) * cos(2 * pi * eta3) elif component == 12: value = eva_2d.evaluate_n_diffn( - tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2 + tn1, + tn2, + pn[0], + pn[1], + nbase_n[0], + nbase_n[1], + cx[:, :, 0], + eta1, + eta2, ) * cos(2 * pi * eta3) if eta1 == 0.0 and cx[0, 0, 0] == cx[0, 1, 0]: @@ -397,11 +557,27 @@ def df( value = 0.0 elif component == 31: value = eva_2d.evaluate_diffn_n( - tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2 + tn1, + tn2, + pn[0], + pn[1], + nbase_n[0], + nbase_n[1], + cx[:, :, 0], + eta1, + eta2, ) * sin(2 * pi * eta3) elif component == 32: value = eva_2d.evaluate_n_diffn( - tn1, tn2, pn[0], pn[1], nbase_n[0], nbase_n[1], cx[:, :, 0], eta1, eta2 + tn1, + tn2, + pn[0], + pn[1], + nbase_n[0], + nbase_n[1], + cx[:, :, 0], + eta1, + eta2, ) * sin(2 * pi * eta3) if eta1 == 0.0 and cx[0, 0, 0] == cx[0, 1, 0]: diff --git a/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d_fast.py b/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d_fast.py index fbd912b39..f87380685 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d_fast.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/mappings_3d_fast.py @@ -264,19 +264,51 @@ def df_all( if mat_or_vec == 0 or mat_or_vec == 2: # sum-up non-vanishing contributions (line 1: df_11, df_12 and df_13) mat_out[0, 0] = evaluation_kernel_2d( - pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + der1, + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) mat_out[0, 1] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + der2, + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) mat_out[0, 2] = 0.0 # sum-up non-vanishing contributions (line 2: df_21, df_22 and df_23) mat_out[1, 0] = evaluation_kernel_2d( - pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] + pn[0], + pn[1], + der1, + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cy[:, :, 0], ) mat_out[1, 1] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + der2, + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cy[:, :, 0], ) mat_out[1, 2] = 0.0 @@ -288,10 +320,26 @@ def df_all( # evaluate mapping if mat_or_vec == 1 or mat_or_vec == 2: vec_out[0] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) vec_out[1] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cy[:, :, 0], ) vec_out[2] = lz * eta3 @@ -305,14 +353,38 @@ def df_all( if mat_or_vec == 0 or mat_or_vec == 2: # sum-up non-vanishing contributions (line 1: df_11, df_12 and df_13) mat_out[0, 0] = evaluation_kernel_2d( - pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + der1, + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) * cos(2 * pi * eta3) mat_out[0, 1] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + der2, + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) * cos(2 * pi * eta3) mat_out[0, 2] = ( evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) * sin(2 * pi * eta3) * (-2 * pi) @@ -320,23 +392,63 @@ def df_all( # sum-up non-vanishing contributions (line 2: df_21, df_22 and df_23) mat_out[1, 0] = evaluation_kernel_2d( - pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] + pn[0], + pn[1], + der1, + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cy[:, :, 0], ) mat_out[1, 1] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + der2, + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cy[:, :, 0], ) mat_out[1, 2] = 0.0 # sum-up non-vanishing contributions (line 3: df_31, df_32 and df_33) mat_out[2, 0] = evaluation_kernel_2d( - pn[0], pn[1], der1, b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + der1, + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) * sin(2 * pi * eta3) mat_out[2, 1] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], der2, span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + der2, + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) * sin(2 * pi * eta3) mat_out[2, 2] = ( evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) * cos(2 * pi * eta3) * 2 @@ -346,13 +458,37 @@ def df_all( # evaluate mapping if mat_or_vec == 1 or mat_or_vec == 2: vec_out[0] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) * cos(2 * pi * eta3) vec_out[1] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cy[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cy[:, :, 0], ) vec_out[2] = evaluation_kernel_2d( - pn[0], pn[1], b1[pn[0]], b2[pn[1]], span_n1, span_n2, nbase_n[0], nbase_n[1], cx[:, :, 0] + pn[0], + pn[1], + b1[pn[0]], + b2[pn[1]], + span_n1, + span_n2, + nbase_n[0], + nbase_n[1], + cx[:, :, 0], ) * sin(2 * pi * eta3) # analytical mapping @@ -360,33 +496,150 @@ def df_all( # evaluate Jacobian matrix if mat_or_vec == 0 or mat_or_vec == 2: mat_out[0, 0] = mapping.df( - eta1, eta2, eta3, 11, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 11, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) mat_out[0, 1] = mapping.df( - eta1, eta2, eta3, 12, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 12, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) mat_out[0, 2] = mapping.df( - eta1, eta2, eta3, 13, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 13, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) mat_out[1, 0] = mapping.df( - eta1, eta2, eta3, 21, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 21, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) mat_out[1, 1] = mapping.df( - eta1, eta2, eta3, 22, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 22, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) mat_out[1, 2] = mapping.df( - eta1, eta2, eta3, 23, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 23, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) mat_out[2, 0] = mapping.df( - eta1, eta2, eta3, 31, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 31, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) mat_out[2, 1] = mapping.df( - eta1, eta2, eta3, 32, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 32, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) mat_out[2, 2] = mapping.df( - eta1, eta2, eta3, 33, kind_map, params_map, tn1, tn2, tn3, pn, nbase_n, cx, cy, cz + eta1, + eta2, + eta3, + 33, + kind_map, + params_map, + tn1, + tn2, + tn3, + pn, + nbase_n, + cx, + cy, + cz, ) # evaluate mapping diff --git a/src/struphy/pic/tests/test_pic_legacy_files/pusher.py b/src/struphy/pic/tests/test_pic_legacy_files/pusher.py index 1da52c793..518e19ee0 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/pusher.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/pusher.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp import struphy.pic.tests.test_pic_legacy_files.pusher_pos as push_pos import struphy.pic.tests.test_pic_legacy_files.pusher_vel_2d as push_vel_2d diff --git a/src/struphy/pic/tests/test_pic_legacy_files/pusher_pos.py b/src/struphy/pic/tests/test_pic_legacy_files/pusher_pos.py index 78631440e..81b5e1e53 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/pusher_pos.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/pusher_pos.py @@ -532,13 +532,52 @@ def pusher_step4_pcart( # compute old pseudo-cartesian coordinates fx_pseudo[0] = mapping.f( - eta[0], eta[1], eta[2], 1, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 1, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) fx_pseudo[1] = mapping.f( - eta[0], eta[1], eta[2], 2, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 2, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) fx_pseudo[2] = mapping.f( - eta[0], eta[1], eta[2], 3, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 3, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) # evaluate old Jacobian matrix of mapping F @@ -588,33 +627,150 @@ def pusher_step4_pcart( # evaluate old Jacobian matrix of mapping F_pseudo df_pseudo_old[0, 0] = mapping.df( - eta[0], eta[1], eta[2], 11, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 11, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo_old[0, 1] = mapping.df( - eta[0], eta[1], eta[2], 12, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 12, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo_old[0, 2] = mapping.df( - eta[0], eta[1], eta[2], 13, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 13, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo_old[1, 0] = mapping.df( - eta[0], eta[1], eta[2], 21, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 21, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo_old[1, 1] = mapping.df( - eta[0], eta[1], eta[2], 22, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 22, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo_old[1, 2] = mapping.df( - eta[0], eta[1], eta[2], 23, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 23, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo_old[2, 0] = mapping.df( - eta[0], eta[1], eta[2], 31, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 31, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo_old[2, 1] = mapping.df( - eta[0], eta[1], eta[2], 32, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 32, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo_old[2, 2] = mapping.df( - eta[0], eta[1], eta[2], 33, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 33, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) while True: @@ -687,33 +843,150 @@ def pusher_step4_pcart( # evaluate Jacobian matrix of mapping F_pseudo df_pseudo[0, 0] = mapping.df( - eta[0], eta[1], eta[2], 11, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 11, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[0, 1] = mapping.df( - eta[0], eta[1], eta[2], 12, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 12, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[0, 2] = mapping.df( - eta[0], eta[1], eta[2], 13, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 13, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 0] = mapping.df( - eta[0], eta[1], eta[2], 21, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 21, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 1] = mapping.df( - eta[0], eta[1], eta[2], 22, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 22, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 2] = mapping.df( - eta[0], eta[1], eta[2], 23, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 23, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 0] = mapping.df( - eta[0], eta[1], eta[2], 31, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 31, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 1] = mapping.df( - eta[0], eta[1], eta[2], 32, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 32, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 2] = mapping.df( - eta[0], eta[1], eta[2], 33, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 33, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) # compute df_pseudo*df_inv*v @@ -784,33 +1057,150 @@ def pusher_step4_pcart( # evaluate Jacobian matrix of mapping F_pseudo df_pseudo[0, 0] = mapping.df( - eta[0], eta[1], eta[2], 11, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 11, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[0, 1] = mapping.df( - eta[0], eta[1], eta[2], 12, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 12, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[0, 2] = mapping.df( - eta[0], eta[1], eta[2], 13, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 13, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 0] = mapping.df( - eta[0], eta[1], eta[2], 21, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 21, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 1] = mapping.df( - eta[0], eta[1], eta[2], 22, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 22, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 2] = mapping.df( - eta[0], eta[1], eta[2], 23, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 23, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 0] = mapping.df( - eta[0], eta[1], eta[2], 31, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 31, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 1] = mapping.df( - eta[0], eta[1], eta[2], 32, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 32, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 2] = mapping.df( - eta[0], eta[1], eta[2], 33, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 33, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) # compute df_pseudo*df_inv*v @@ -881,33 +1271,150 @@ def pusher_step4_pcart( # evaluate Jacobian matrix of mapping F_pseudo df_pseudo[0, 0] = mapping.df( - eta[0], eta[1], eta[2], 11, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 11, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[0, 1] = mapping.df( - eta[0], eta[1], eta[2], 12, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 12, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[0, 2] = mapping.df( - eta[0], eta[1], eta[2], 13, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 13, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 0] = mapping.df( - eta[0], eta[1], eta[2], 21, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 21, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 1] = mapping.df( - eta[0], eta[1], eta[2], 22, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 22, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[1, 2] = mapping.df( - eta[0], eta[1], eta[2], 23, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 23, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 0] = mapping.df( - eta[0], eta[1], eta[2], 31, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 31, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 1] = mapping.df( - eta[0], eta[1], eta[2], 32, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 32, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) df_pseudo[2, 2] = mapping.df( - eta[0], eta[1], eta[2], 33, map_pseudo, params_pseudo, tf1, tf2, tf3, pf, nbasef, cx, cy, cz + eta[0], + eta[1], + eta[2], + 33, + map_pseudo, + params_pseudo, + tf1, + tf2, + tf3, + pf, + nbasef, + cx, + cy, + cz, ) # compute df_pseudo*df_inv*v @@ -1374,26 +1881,98 @@ def pusher_rk4_pc_full( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(Ginv, u, k1_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) k1_u[:] = u / det_df @@ -1491,26 +2070,98 @@ def pusher_rk4_pc_full( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(Ginv, u, k2_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) k2_u[:] = u / det_df @@ -1608,26 +2259,98 @@ def pusher_rk4_pc_full( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(Ginv, u, k3_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) k3_u[:] = u / det_df @@ -1722,26 +2445,98 @@ def pusher_rk4_pc_full( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(Ginv, u, k4_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) k4_u[:] = u / det_df @@ -1992,26 +2787,98 @@ def pusher_rk4_pc_perp( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(Ginv, u, k1_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) k1_u[:] = u / det_df @@ -2108,26 +2975,98 @@ def pusher_rk4_pc_perp( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(Ginv, u, k2_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) k2_u[:] = u / det_df @@ -2223,26 +3162,98 @@ def pusher_rk4_pc_perp( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(Ginv, u, k3_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) k3_u[:] = u / det_df @@ -2338,26 +3349,98 @@ def pusher_rk4_pc_perp( # velocity field if basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(Ginv, u, k4_u) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) k4_u[:] = u / det_df diff --git a/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_2d.py b/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_2d.py index 43e320311..0fcc29751 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_2d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_2d.py @@ -248,19 +248,43 @@ def pusher_step3( for i in range(nbase_n[2]): u[0] += ( eva2.evaluation_kernel_2d( - pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], u1[:, :, i] + pd1, + pn2, + bd1, + bn2, + span1 - 1, + span2 - 0, + nbase_d[0], + nbase_n[1], + u1[:, :, i], ) * cs[i] ) u[1] += ( eva2.evaluation_kernel_2d( - pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], u2[:, :, i] + pn1, + pd2, + bn1, + bd2, + span1 - 0, + span2 - 1, + nbase_n[0], + nbase_d[1], + u2[:, :, i], ) * cs[i] ) u[2] += ( eva2.evaluation_kernel_2d( - pn1, pn2, bn1, bn2, span1 - 0, span2 - 0, nbase_n[0], nbase_n[1], u3[:, :, i] + pn1, + pn2, + bn1, + bn2, + span1 - 0, + span2 - 0, + nbase_n[0], + nbase_n[1], + u3[:, :, i], ) * cs[i] ) @@ -274,19 +298,43 @@ def pusher_step3( for i in range(nbase_n[2]): u[0] += ( eva2.evaluation_kernel_2d( - pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], u1[:, :, i] + pn1, + pd2, + bn1, + bd2, + span1 - 0, + span2 - 1, + nbase_n[0], + nbase_d[1], + u1[:, :, i], ) * cs[i] ) u[1] += ( eva2.evaluation_kernel_2d( - pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], u2[:, :, i] + pd1, + pn2, + bd1, + bn2, + span1 - 1, + span2 - 0, + nbase_d[0], + nbase_n[1], + u2[:, :, i], ) * cs[i] ) u[2] += ( eva2.evaluation_kernel_2d( - pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], u3[:, :, i] + pd1, + pd2, + bd1, + bd2, + span1 - 1, + span2 - 1, + nbase_d[0], + nbase_d[1], + u3[:, :, i], ) * cs[i] ) @@ -299,32 +347,80 @@ def pusher_step3( # equilibrium magnetic field (2-form) b[0] = eva2.evaluation_kernel_2d( - pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], b_eq_1[:, :, 0] + pn1, + pd2, + bn1, + bd2, + span1 - 0, + span2 - 1, + nbase_n[0], + nbase_d[1], + b_eq_1[:, :, 0], ) b[1] = eva2.evaluation_kernel_2d( - pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], b_eq_2[:, :, 0] + pd1, + pn2, + bd1, + bn2, + span1 - 1, + span2 - 0, + nbase_d[0], + nbase_n[1], + b_eq_2[:, :, 0], ) b[2] = eva2.evaluation_kernel_2d( - pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], b_eq_3[:, :, 0] + pd1, + pd2, + bd1, + bd2, + span1 - 1, + span2 - 1, + nbase_d[0], + nbase_d[1], + b_eq_3[:, :, 0], ) # perturbed magnetic field (2-form) for i in range(nbase_n[2]): b[0] += ( eva2.evaluation_kernel_2d( - pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], b_p_1[:, :, i] + pn1, + pd2, + bn1, + bd2, + span1 - 0, + span2 - 1, + nbase_n[0], + nbase_d[1], + b_p_1[:, :, i], ) * cs[i] ) b[1] += ( eva2.evaluation_kernel_2d( - pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], b_p_2[:, :, i] + pd1, + pn2, + bd1, + bn2, + span1 - 1, + span2 - 0, + nbase_d[0], + nbase_n[1], + b_p_2[:, :, i], ) * cs[i] ) b[2] += ( eva2.evaluation_kernel_2d( - pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], b_p_3[:, :, i] + pd1, + pd2, + bd1, + bd2, + span1 - 1, + span2 - 1, + nbase_d[0], + nbase_d[1], + b_p_3[:, :, i], ) * cs[i] ) @@ -338,10 +434,26 @@ def pusher_step3( # gradient of absolute value of magnetic field (1-form) b_grad[0] = eva2.evaluation_kernel_2d( - pn1, pn2, der1, bn2, span1, span2, nbase_n[0], nbase_n[1], b_norm[:, :, 0] + pn1, + pn2, + der1, + bn2, + span1, + span2, + nbase_n[0], + nbase_n[1], + b_norm[:, :, 0], ) b_grad[1] = eva2.evaluation_kernel_2d( - pn1, pn2, bn1, der2, span1, span2, nbase_n[0], nbase_n[1], b_norm[:, :, 0] + pn1, + pn2, + bn1, + der2, + span1, + span2, + nbase_n[0], + nbase_n[1], + b_norm[:, :, 0], ) b_grad[2] = 0.0 @@ -562,32 +674,80 @@ def pusher_step5( # equilibrium magnetic field (2-form) b[0] = eva2.evaluation_kernel_2d( - pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], b_eq_1[:, :, 0] + pn1, + pd2, + bn1, + bd2, + span1 - 0, + span2 - 1, + nbase_n[0], + nbase_d[1], + b_eq_1[:, :, 0], ) b[1] = eva2.evaluation_kernel_2d( - pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], b_eq_2[:, :, 0] + pd1, + pn2, + bd1, + bn2, + span1 - 1, + span2 - 0, + nbase_d[0], + nbase_n[1], + b_eq_2[:, :, 0], ) b[2] = eva2.evaluation_kernel_2d( - pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], b_eq_3[:, :, 0] + pd1, + pd2, + bd1, + bd2, + span1 - 1, + span2 - 1, + nbase_d[0], + nbase_d[1], + b_eq_3[:, :, 0], ) # perturbed magnetic field (2-form) for i in range(nbase_n[2]): b[0] += ( eva2.evaluation_kernel_2d( - pn1, pd2, bn1, bd2, span1 - 0, span2 - 1, nbase_n[0], nbase_d[1], b_p_1[:, :, i] + pn1, + pd2, + bn1, + bd2, + span1 - 0, + span2 - 1, + nbase_n[0], + nbase_d[1], + b_p_1[:, :, i], ) * cs[i] ) b[1] += ( eva2.evaluation_kernel_2d( - pd1, pn2, bd1, bn2, span1 - 1, span2 - 0, nbase_d[0], nbase_n[1], b_p_2[:, :, i] + pd1, + pn2, + bd1, + bn2, + span1 - 1, + span2 - 0, + nbase_d[0], + nbase_n[1], + b_p_2[:, :, i], ) * cs[i] ) b[2] += ( eva2.evaluation_kernel_2d( - pd1, pd2, bd1, bd2, span1 - 1, span2 - 1, nbase_d[0], nbase_d[1], b_p_3[:, :, i] + pd1, + pd2, + bd1, + bd2, + span1 - 1, + span2 - 1, + nbase_d[0], + nbase_d[1], + b_p_3[:, :, i], ) * cs[i] ) diff --git a/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_3d.py b/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_3d.py index 4eabb26dd..cd3884209 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_3d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/pusher_vel_3d.py @@ -227,13 +227,49 @@ def pusher_step3( # velocity field (0-form, push-forward with df) if basis_u == 0: u[0] = eva3.evaluation_kernel_3d( - pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], u1 + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span1, + span2, + span3, + nbase_n[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], u2 + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span1, + span2, + span3, + nbase_n[0], + nbase_n[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pn3, bn1, bn2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], u3 + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span1, + span2, + span3, + nbase_n[0], + nbase_n[1], + nbase_n[2], + u3, ) linalg.matrix_vector(df, u, u_cart) @@ -241,13 +277,49 @@ def pusher_step3( # velocity field (1-form, push forward with df^(-T)) elif basis_u == 1: u[0] = eva3.evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span1 - 1, span2, span3, nbase_d[0], nbase_n[1], nbase_n[2], u1 + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span1 - 1, + span2, + span3, + nbase_d[0], + nbase_n[1], + nbase_n[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span1, span2 - 1, span3, nbase_n[0], nbase_d[1], nbase_n[2], u2 + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span1, + span2 - 1, + span3, + nbase_n[0], + nbase_d[1], + nbase_n[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span1, span2, span3 - 1, nbase_n[0], nbase_n[1], nbase_d[2], u3 + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span1, + span2, + span3 - 1, + nbase_n[0], + nbase_n[1], + nbase_d[2], + u3, ) linalg.matrix_vector(dfinv_t, u, u_cart) @@ -255,13 +327,49 @@ def pusher_step3( # velocity field (2-form, push forward with df/|det df|) elif basis_u == 2: u[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], u1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + u1, ) u[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], u2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + u2, ) u[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], u3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + u3, ) linalg.matrix_vector(df, u, u_cart) @@ -272,13 +380,49 @@ def pusher_step3( # magnetic field (2-form) b[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + b2_1, ) b[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + b2_2, ) b[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + b2_3, ) # push-forward to physical domain @@ -290,13 +434,49 @@ def pusher_step3( # gradient of absolute value of magnetic field (1-form) b_grad[0] = eva3.evaluation_kernel_3d( - pn1, pn2, pn3, der1, bn2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], b0 + pn1, + pn2, + pn3, + der1, + bn2, + bn3, + span1, + span2, + span3, + nbase_n[0], + nbase_n[1], + nbase_n[2], + b0, ) b_grad[1] = eva3.evaluation_kernel_3d( - pn1, pn2, pn3, bn1, der2, bn3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], b0 + pn1, + pn2, + pn3, + bn1, + der2, + bn3, + span1, + span2, + span3, + nbase_n[0], + nbase_n[1], + nbase_n[2], + b0, ) b_grad[2] = eva3.evaluation_kernel_3d( - pn1, pn2, pn3, bn1, bn2, der3, span1, span2, span3, nbase_n[0], nbase_n[1], nbase_n[2], b0 + pn1, + pn2, + pn3, + bn1, + bn2, + der3, + span1, + span2, + span3, + nbase_n[0], + nbase_n[1], + nbase_n[2], + b0, ) # push-forward to physical domain @@ -537,13 +717,49 @@ def pusher_step5_old( # magnetic field (2-form) b[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + b2_1, ) b[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + b2_2, ) b[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + b2_3, ) b_prod[0, 1] = -b[2] @@ -794,13 +1010,49 @@ def pusher_step5( # magnetic field (2-form) b[0] = eva3.evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span1, span2 - 1, span3 - 1, nbase_n[0], nbase_d[1], nbase_d[2], b2_1 + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span1, + span2 - 1, + span3 - 1, + nbase_n[0], + nbase_d[1], + nbase_d[2], + b2_1, ) b[1] = eva3.evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span1 - 1, span2, span3 - 1, nbase_d[0], nbase_n[1], nbase_d[2], b2_2 + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span1 - 1, + span2, + span3 - 1, + nbase_d[0], + nbase_n[1], + nbase_d[2], + b2_2, ) b[2] = eva3.evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span1 - 1, span2 - 1, span3, nbase_d[0], nbase_d[1], nbase_n[2], b2_3 + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span1 - 1, + span2 - 1, + span3, + nbase_d[0], + nbase_d[1], + nbase_n[2], + b2_3, ) # push-forward to physical domain diff --git a/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_2d.py b/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_2d.py index ba32b93bf..fdd4485b5 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_2d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_2d.py @@ -400,7 +400,7 @@ def evaluate_tensor_product( Returns: -------- - values: double[:, :] values of spline at points from np.meshgrid(eta1, eta2, indexing='ij'). + values: double[:, :] values of spline at points from xp.meshgrid(eta1, eta2, indexing='ij'). """ for i1 in range(len(eta1)): diff --git a/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_3d.py b/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_3d.py index 28e2b5d9c..7923b3966 100644 --- a/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_3d.py +++ b/src/struphy/pic/tests/test_pic_legacy_files/spline_evaluation_3d.py @@ -127,7 +127,19 @@ def evaluate_n_n_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, pn2, pn3, bn1, bn2, bn3, span_n1, span_n2, span_n3, nbase_n1, nbase_n2, nbase_n3, coeff + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span_n1, + span_n2, + span_n3, + nbase_n1, + nbase_n2, + nbase_n3, + coeff, ) return value @@ -189,7 +201,19 @@ def evaluate_diffn_n_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, pn2, pn3, bn1, bn2, bn3, span_n1, span_n2, span_n3, nbase_n1, nbase_n2, nbase_n3, coeff + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span_n1, + span_n2, + span_n3, + nbase_n1, + nbase_n2, + nbase_n3, + coeff, ) return value @@ -251,7 +275,19 @@ def evaluate_n_diffn_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, pn2, pn3, bn1, bn2, bn3, span_n1, span_n2, span_n3, nbase_n1, nbase_n2, nbase_n3, coeff + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span_n1, + span_n2, + span_n3, + nbase_n1, + nbase_n2, + nbase_n3, + coeff, ) return value @@ -313,7 +349,19 @@ def evaluate_n_n_diffn( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, pn2, pn3, bn1, bn2, bn3, span_n1, span_n2, span_n3, nbase_n1, nbase_n2, nbase_n3, coeff + pn1, + pn2, + pn3, + bn1, + bn2, + bn3, + span_n1, + span_n2, + span_n3, + nbase_n1, + nbase_n2, + nbase_n3, + coeff, ) return value @@ -377,7 +425,19 @@ def evaluate_d_n_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pd1, pn2, pn3, bd1, bn2, bn3, span_d1, span_n2, span_n3, nbase_d1, nbase_n2, nbase_n3, coeff + pd1, + pn2, + pn3, + bd1, + bn2, + bn3, + span_d1, + span_n2, + span_n3, + nbase_d1, + nbase_n2, + nbase_n3, + coeff, ) return value @@ -441,7 +501,19 @@ def evaluate_n_d_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, pd2, pn3, bn1, bd2, bn3, span_n1, span_d2, span_n3, nbase_n1, nbase_d2, nbase_n3, coeff + pn1, + pd2, + pn3, + bn1, + bd2, + bn3, + span_n1, + span_d2, + span_n3, + nbase_n1, + nbase_d2, + nbase_n3, + coeff, ) return value @@ -505,7 +577,19 @@ def evaluate_n_n_d( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, pn2, pd3, bn1, bn2, bd3, span_n1, span_n2, span_d3, nbase_n1, nbase_n2, nbase_d3, coeff + pn1, + pn2, + pd3, + bn1, + bn2, + bd3, + span_n1, + span_n2, + span_d3, + nbase_n1, + nbase_n2, + nbase_d3, + coeff, ) return value @@ -570,7 +654,19 @@ def evaluate_n_d_d( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pn1, pd2, pd3, bn1, bd2, bd3, span_n1, span_d2, span_d3, nbase_n1, nbase_d2, nbase_d3, coeff + pn1, + pd2, + pd3, + bn1, + bd2, + bd3, + span_n1, + span_d2, + span_d3, + nbase_n1, + nbase_d2, + nbase_d3, + coeff, ) return value @@ -635,7 +731,19 @@ def evaluate_d_n_d( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pd1, pn2, pd3, bd1, bn2, bd3, span_d1, span_n2, span_d3, nbase_d1, nbase_n2, nbase_d3, coeff + pd1, + pn2, + pd3, + bd1, + bn2, + bd3, + span_d1, + span_n2, + span_d3, + nbase_d1, + nbase_n2, + nbase_d3, + coeff, ) return value @@ -700,7 +808,19 @@ def evaluate_d_d_n( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pd1, pd2, pn3, bd1, bd2, bn3, span_d1, span_d2, span_n3, nbase_d1, nbase_d2, nbase_n3, coeff + pd1, + pd2, + pn3, + bd1, + bd2, + bn3, + span_d1, + span_d2, + span_n3, + nbase_d1, + nbase_d2, + nbase_n3, + coeff, ) return value @@ -766,7 +886,19 @@ def evaluate_d_d_d( # sum up non-vanishing contributions value = evaluation_kernel_3d( - pd1, pd2, pd3, bd1, bd2, bd3, span_d1, span_d2, span_d3, nbase_d1, nbase_d2, nbase_d3, coeff + pd1, + pd2, + pd3, + bd1, + bd2, + bd3, + span_d1, + span_d2, + span_d3, + nbase_d1, + nbase_d2, + nbase_d3, + coeff, ) return value @@ -806,7 +938,7 @@ def evaluate_tensor_product( Returns: -------- values: double[:, :, :] values of spline at points from - np.meshgrid(eta1, eta2, eta3, indexing='ij'). + xp.meshgrid(eta1, eta2, eta3, indexing='ij'). """ for i1 in range(len(eta1)): @@ -815,41 +947,137 @@ def evaluate_tensor_product( # V0 - space if kind == 0: values[i1, i2, i3] = evaluate_n_n_n( - t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] + t1, + t2, + t3, + p1, + p2, + p3, + nbase_1, + nbase_2, + nbase_3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) # V1 - space elif kind == 11: values[i1, i2, i3] = evaluate_d_n_n( - t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] + t1, + t2, + t3, + p1, + p2, + p3, + nbase_1, + nbase_2, + nbase_3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 12: values[i1, i2, i3] = evaluate_n_d_n( - t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] + t1, + t2, + t3, + p1, + p2, + p3, + nbase_1, + nbase_2, + nbase_3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 13: values[i1, i2, i3] = evaluate_n_n_d( - t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] + t1, + t2, + t3, + p1, + p2, + p3, + nbase_1, + nbase_2, + nbase_3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) # V2 - space elif kind == 21: values[i1, i2, i3] = evaluate_n_d_d( - t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] + t1, + t2, + t3, + p1, + p2, + p3, + nbase_1, + nbase_2, + nbase_3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 22: values[i1, i2, i3] = evaluate_d_n_d( - t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] + t1, + t2, + t3, + p1, + p2, + p3, + nbase_1, + nbase_2, + nbase_3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) elif kind == 23: values[i1, i2, i3] = evaluate_d_d_n( - t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] + t1, + t2, + t3, + p1, + p2, + p3, + nbase_1, + nbase_2, + nbase_3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) # V3 - space elif kind == 3: values[i1, i2, i3] = evaluate_d_d_d( - t1, t2, t3, p1, p2, p3, nbase_1, nbase_2, nbase_3, coeff, eta1[i1], eta2[i2], eta3[i3] + t1, + t2, + t3, + p1, + p2, + p3, + nbase_1, + nbase_2, + nbase_3, + coeff, + eta1[i1], + eta2[i2], + eta3[i3], ) diff --git a/src/struphy/pic/tests/test_pushers.py b/src/struphy/pic/tests/test_pushers.py index acb89f2e9..321ab9aba 100644 --- a/src/struphy/pic/tests/test_pushers.py +++ b/src/struphy/pic/tests/test_pushers.py @@ -1,11 +1,13 @@ import pytest +from struphy.utils.pyccel import Pyccelkernel + -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] + "spl_kind", + [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], ) @pytest.mark.parametrize( "mapping", @@ -22,8 +24,8 @@ ], ) def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -112,7 +114,7 @@ def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy = Pusher_psy( particles, - pusher_kernels.push_vxb_analytic, + Pyccelkernel(pusher_kernels.push_vxb_analytic), ( derham.args_derham, b2_eq_psy[0]._data + b2_psy[0]._data, @@ -124,7 +126,7 @@ def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert np.allclose(particles.markers, markers_str.T) + assert xp.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -134,14 +136,14 @@ def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] + "spl_kind", + [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], ) @pytest.mark.parametrize( "mapping", @@ -158,8 +160,8 @@ def test_push_vxb_analytic(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -250,12 +252,12 @@ def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): basis_u=2, bc_pos=0, ) - mu0_str = np.zeros(markers_str.shape[1], dtype=float) - pow_str = np.zeros(markers_str.shape[1], dtype=float) + mu0_str = xp.zeros(markers_str.shape[1], dtype=float) + pow_str = xp.zeros(markers_str.shape[1], dtype=float) pusher_psy = Pusher_psy( particles, - pusher_kernels.push_bxu_Hdiv, + Pyccelkernel(pusher_kernels.push_bxu_Hdiv), ( derham.args_derham, b2_eq_psy[0]._data + b2_psy[0]._data, @@ -271,7 +273,7 @@ def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert np.allclose(particles.markers, markers_str.T) + assert xp.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -281,14 +283,14 @@ def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] + "spl_kind", + [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], ) @pytest.mark.parametrize( "mapping", @@ -305,8 +307,8 @@ def test_push_bxu_Hdiv(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -397,12 +399,12 @@ def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): basis_u=1, bc_pos=0, ) - mu0_str = np.zeros(markers_str.shape[1], dtype=float) - pow_str = np.zeros(markers_str.shape[1], dtype=float) + mu0_str = xp.zeros(markers_str.shape[1], dtype=float) + pow_str = xp.zeros(markers_str.shape[1], dtype=float) pusher_psy = Pusher_psy( particles, - pusher_kernels.push_bxu_Hcurl, + Pyccelkernel(pusher_kernels.push_bxu_Hcurl), ( derham.args_derham, b2_eq_psy[0]._data + b2_psy[0]._data, @@ -418,7 +420,7 @@ def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert np.allclose(particles.markers, markers_str.T) + assert xp.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -428,14 +430,14 @@ def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] + "spl_kind", + [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], ) @pytest.mark.parametrize( "mapping", @@ -452,8 +454,8 @@ def test_push_bxu_Hcurl(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -544,12 +546,12 @@ def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): basis_u=0, bc_pos=0, ) - mu0_str = np.zeros(markers_str.shape[1], dtype=float) - pow_str = np.zeros(markers_str.shape[1], dtype=float) + mu0_str = xp.zeros(markers_str.shape[1], dtype=float) + pow_str = xp.zeros(markers_str.shape[1], dtype=float) pusher_psy = Pusher_psy( particles, - pusher_kernels.push_bxu_H1vec, + Pyccelkernel(pusher_kernels.push_bxu_H1vec), ( derham.args_derham, b2_eq_psy[0]._data + b2_psy[0]._data, @@ -565,7 +567,7 @@ def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert np.allclose(particles.markers, markers_str.T) + assert xp.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -575,14 +577,14 @@ def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] + "spl_kind", + [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], ) @pytest.mark.parametrize( "mapping", @@ -599,8 +601,8 @@ def test_push_bxu_H1vec(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -691,12 +693,12 @@ def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): basis_u=2, bc_pos=0, ) - mu0_str = np.random.rand(markers_str.shape[1]) - pow_str = np.zeros(markers_str.shape[1], dtype=float) + mu0_str = xp.random.rand(markers_str.shape[1]) + pow_str = xp.zeros(markers_str.shape[1], dtype=float) pusher_psy = Pusher_psy( particles, - pusher_kernels.push_bxu_Hdiv_pauli, + Pyccelkernel(pusher_kernels.push_bxu_Hdiv_pauli), ( derham.args_derham, *derham.p, @@ -714,7 +716,7 @@ def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert np.allclose(particles.markers, markers_str.T) + assert xp.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -724,14 +726,14 @@ def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): pusher_psy(dt) # compare if markers are the same AFTER push - assert np.allclose(particles.markers[:, :6], markers_str.T[:, :6]) + assert xp.allclose(particles.markers[:, :6], markers_str.T[:, :6]) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("p", [[2, 3, 1], [1, 2, 3]]) @pytest.mark.parametrize( - "spl_kind", [[False, True, True], [True, False, True], [False, False, True], [True, True, True]] + "spl_kind", + [[False, True, True], [True, False, True], [False, False, True], [True, True, True]], ) @pytest.mark.parametrize( "mapping", @@ -748,8 +750,8 @@ def test_push_bxu_Hdiv_pauli(Nel, p, spl_kind, mapping, show_plots=False): ], ) def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -834,12 +836,12 @@ def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): butcher = ButcherTableau("rk4") # temp fix due to refactoring of ButcherTableau: - butcher._a = np.diag(butcher.a, k=-1) - butcher._a = np.array(list(butcher._a) + [0.0]) + butcher._a = xp.diag(butcher.a, k=-1) + butcher._a = xp.array(list(butcher._a) + [0.0]) pusher_psy = Pusher_psy( particles, - pusher_kernels.push_eta_stage, + Pyccelkernel(pusher_kernels.push_eta_stage), (butcher.a, butcher.b, butcher.c), domain.args_domain, alpha_in_kernel=1.0, @@ -847,7 +849,7 @@ def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): ) # compare if markers are the same BEFORE push - assert np.allclose(particles.markers, markers_str.T) + assert xp.allclose(particles.markers, markers_str.T) # push markers dt = 0.1 @@ -855,12 +857,12 @@ def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): pusher_str.push_step4(markers_str, dt) pusher_psy(dt) - n_mks_load = np.zeros(size, dtype=int) + n_mks_load = xp.zeros(size, dtype=int) - comm.Allgather(np.array(np.shape(particles.markers)[0]), n_mks_load) + comm.Allgather(xp.array(xp.shape(particles.markers)[0]), n_mks_load) - sendcounts = np.zeros(size, dtype=int) - displacements = np.zeros(size, dtype=int) + sendcounts = xp.zeros(size, dtype=int) + displacements = xp.zeros(size, dtype=int) accum_sendcounts = 0.0 for i in range(size): @@ -868,23 +870,27 @@ def test_push_eta_rk4(Nel, p, spl_kind, mapping, show_plots=False): displacements[i] = accum_sendcounts accum_sendcounts += sendcounts[i] - all_particles_psy = np.zeros((int(accum_sendcounts) * 3,), dtype=float) - all_particles_str = np.zeros((int(accum_sendcounts) * 3,), dtype=float) + all_particles_psy = xp.zeros((int(accum_sendcounts) * 3,), dtype=float) + all_particles_str = xp.zeros((int(accum_sendcounts) * 3,), dtype=float) comm.Barrier() - comm.Allgatherv(np.array(particles.markers[:, :3]), [all_particles_psy, sendcounts, displacements, MPI.DOUBLE]) - comm.Allgatherv(np.array(markers_str.T[:, :3]), [all_particles_str, sendcounts, displacements, MPI.DOUBLE]) + comm.Allgatherv(xp.array(particles.markers[:, :3]), [all_particles_psy, sendcounts, displacements, MPI.DOUBLE]) + comm.Allgatherv(xp.array(markers_str.T[:, :3]), [all_particles_str, sendcounts, displacements, MPI.DOUBLE]) comm.Barrier() - unique_psy = np.unique(all_particles_psy) - unique_str = np.unique(all_particles_str) + unique_psy = xp.unique(all_particles_psy) + unique_str = xp.unique(all_particles_str) - assert np.allclose(unique_psy, unique_str) + assert xp.allclose(unique_psy, unique_str) if __name__ == "__main__": test_push_vxb_analytic( - [8, 9, 5], [4, 2, 3], [False, True, True], ["Colella", {"Lx": 2.0, "Ly": 2.0, "alpha": 0.1, "Lz": 4.0}], False + [8, 9, 5], + [4, 2, 3], + [False, True, True], + ["Colella", {"Lx": 2.0, "Ly": 2.0, "alpha": 0.1, "Lz": 4.0}], + False, ) # test_push_bxu_Hdiv([8, 9, 5], [4, 2, 3], [False, True, True], ['Colella', { # 'Lx': 2., 'Ly': 2., 'alpha': 0.1, 'Lz': 4.}], False) diff --git a/src/struphy/pic/tests/test_sorting.py b/src/struphy/pic/tests/test_sorting.py index 85f8f2935..0daf8f4c9 100644 --- a/src/struphy/pic/tests/test_sorting.py +++ b/src/struphy/pic/tests/test_sorting.py @@ -1,8 +1,8 @@ from time import time -import numpy as np +import cunumpy as xp import pytest -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.geometry import domains @@ -14,12 +14,12 @@ @pytest.mark.parametrize("ny", [16, 80]) @pytest.mark.parametrize("nz", [32, 90]) @pytest.mark.parametrize("algo", ["fortran_ordering", "c_ordering"]) -def test_flattening(nx, ny, nz, algo): +def test_flattening_1(nx, ny, nz, algo): from struphy.pic.sorting_kernels import flatten_index, unflatten_index - n1s = np.array(np.random.rand(10) * (nx + 1), dtype=int) - n2s = np.array(np.random.rand(10) * (ny + 1), dtype=int) - n3s = np.array(np.random.rand(10) * (nz + 1), dtype=int) + n1s = xp.array(xp.random.rand(10) * (nx + 1), dtype=int) + n2s = xp.array(xp.random.rand(10) * (ny + 1), dtype=int) + n3s = xp.array(xp.random.rand(10) * (nz + 1), dtype=int) for n1 in n1s: for n2 in n2s: for n3 in n3s: @@ -34,12 +34,32 @@ def test_flattening(nx, ny, nz, algo): @pytest.mark.parametrize("ny", [16, 80]) @pytest.mark.parametrize("nz", [32, 90]) @pytest.mark.parametrize("algo", ["fortran_ordering", "c_ordering"]) -def test_flattening(nx, ny, nz, algo): +def test_flattening_2(nx, ny, nz, algo): from struphy.pic.sorting_kernels import flatten_index, unflatten_index - n1s = np.array(np.random.rand(10) * (nx + 1), dtype=int) - n2s = np.array(np.random.rand(10) * (ny + 1), dtype=int) - n3s = np.array(np.random.rand(10) * (nz + 1), dtype=int) + n1s = xp.array(xp.random.rand(10) * (nx + 1), dtype=int) + n2s = xp.array(xp.random.rand(10) * (ny + 1), dtype=int) + n3s = xp.array(xp.random.rand(10) * (nz + 1), dtype=int) + for n1 in n1s: + for n2 in n2s: + for n3 in n3s: + n_glob = flatten_index(int(n1), int(n2), int(n3), nx, ny, nz, algo) + n1n, n2n, n3n = unflatten_index(n_glob, nx, ny, nz, algo) + assert n1n == n1 + assert n2n == n2 + assert n3n == n3 + + +@pytest.mark.parametrize("nx", [8, 70]) +@pytest.mark.parametrize("ny", [16, 80]) +@pytest.mark.parametrize("nz", [32, 90]) +@pytest.mark.parametrize("algo", ["fortran_ordering", "c_ordering"]) +def test_flattening_3(nx, ny, nz, algo): + from struphy.pic.sorting_kernels import flatten_index, unflatten_index + + n1s = xp.array(xp.random.rand(10) * (nx + 1), dtype=int) + n2s = xp.array(xp.random.rand(10) * (ny + 1), dtype=int) + n3s = xp.array(xp.random.rand(10) * (nz + 1), dtype=int) for n1 in n1s: for n2 in n2s: for n3 in n3s: @@ -50,11 +70,11 @@ def test_flattening(nx, ny, nz, algo): assert n3n == n3 -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 10]]) @pytest.mark.parametrize("p", [[2, 3, 4]]) @pytest.mark.parametrize( - "spl_kind", [[False, False, True], [False, True, False], [True, False, True], [True, True, False]] + "spl_kind", + [[False, False, True], [False, True, False], [True, False, True], [True, True, False]], ) @pytest.mark.parametrize( "mapping", @@ -116,7 +136,7 @@ def test_sorting(Nel, p, spl_kind, mapping, Np, verbose=False): if __name__ == "__main__": - test_flattening(8, 8, 8, "c_orderwding") + test_flattening_1(8, 8, 8, "c_orderwding") # test_sorting( # [8, 9, 10], # [2, 3, 4], diff --git a/src/struphy/pic/tests/test_sph.py b/src/struphy/pic/tests/test_sph.py index abb38aff6..294e7f9dc 100644 --- a/src/struphy/pic/tests/test_sph.py +++ b/src/struphy/pic/tests/test_sph.py @@ -1,7 +1,8 @@ -import numpy as np +import cunumpy as xp import pytest from matplotlib import pyplot as plt -from mpi4py import MPI +from psydac.ddm.mpi import MockComm +from psydac.ddm.mpi import mpi as MPI from struphy.fields_background.equils import ConstantVelocity from struphy.geometry import domains @@ -25,7 +26,12 @@ def test_sph_evaluation_1d( tesselation, show_plot=False, ): - comm = MPI.COMM_WORLD + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + rank = 0 + else: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() # DOMAIN object dom_type = "Cuboid" @@ -53,9 +59,9 @@ def test_sph_evaluation_1d( pert = {"n": perturbations.ModesCos(ls=(1,), amps=(1e-0,))} if derivative == 0: - fun_exact = lambda e1, e2, e3: 1.5 + np.cos(2 * np.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 + xp.cos(2 * xp.pi * e1) else: - fun_exact = lambda e1, e2, e3: -2 * np.pi * np.sin(2 * np.pi * e1) + fun_exact = lambda e1, e2, e3: -2 * xp.pi * xp.sin(2 * xp.pi * e1) boundary_params = BoundaryParameters(bc_sph=(bc_x, "periodic", "periodic")) @@ -72,17 +78,18 @@ def test_sph_evaluation_1d( ) # eval points - eta1 = np.linspace(0, 1.0, eval_pts) - eta2 = np.array([0.0]) - eta3 = np.array([0.0]) + eta1 = xp.linspace(0, 1.0, eval_pts) + eta2 = xp.array([0.0]) + eta3 = xp.array([0.0]) particles.draw_markers(sort=False, verbose=False) - particles.mpi_sort_markers() + if comm is not None: + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] - ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") + ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") test_eval = particles.eval_density( ee1, ee2, @@ -93,17 +100,20 @@ def test_sph_evaluation_1d( kernel_type=kernel, derivative=derivative, ) - all_eval = np.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + if comm is None: + all_eval = test_eval + else: + all_eval = xp.zeros_like(test_eval) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) exact_eval = fun_exact(ee1, ee2, ee3) - err_max_norm = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) + err_max_norm = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) - if comm.Get_rank() == 0: - print(f"\n{boxes_per_dim = }") - print(f"{kernel = }, {derivative =}") - print(f"{bc_x = }, {eval_pts = }, {tesselation = }, {err_max_norm = }") + if rank == 0: + print(f"\n{boxes_per_dim =}") + print(f"{kernel =}, {derivative =}") + print(f"{bc_x =}, {eval_pts =}, {tesselation =}, {err_max_norm =}") if show_plot: plt.figure(figsize=(12, 8)) plt.plot(ee1.squeeze(), fun_exact(ee1, ee2, ee3).squeeze(), label="exact") @@ -139,7 +149,12 @@ def test_sph_evaluation_2d( eval_pts, show_plot=False, ): - comm = MPI.COMM_WORLD + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + rank = 0 + else: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() tesselation = True @@ -163,19 +178,19 @@ def test_sph_evaluation_2d( pert = {"n": perturbations.ModesCosCos(ls=(1,), ms=(1,), amps=(1e-0,))} if derivative == 0: - fun_exact = lambda e1, e2, e3: 1.5 + np.cos(2 * np.pi * e1) * np.cos(2 * np.pi * e2) + fun_exact = lambda e1, e2, e3: 1.5 + xp.cos(2 * xp.pi * e1) * xp.cos(2 * xp.pi * e2) elif derivative == 1: - fun_exact = lambda e1, e2, e3: -2 * np.pi * np.sin(2 * np.pi * e1) * np.cos(2 * np.pi * e2) + fun_exact = lambda e1, e2, e3: -2 * xp.pi * xp.sin(2 * xp.pi * e1) * xp.cos(2 * xp.pi * e2) else: - fun_exact = lambda e1, e2, e3: -2 * np.pi * np.cos(2 * np.pi * e1) * np.sin(2 * np.pi * e2) + fun_exact = lambda e1, e2, e3: -2 * xp.pi * xp.cos(2 * xp.pi * e1) * xp.sin(2 * xp.pi * e2) # boundary conditions boundary_params = BoundaryParameters(bc_sph=(bc_x, bc_y, "periodic")) # eval points - eta1 = np.linspace(0, 1.0, eval_pts) - eta2 = np.linspace(0, 1.0, eval_pts) - eta3 = np.array([0.0]) + eta1 = xp.linspace(0, 1.0, eval_pts) + eta2 = xp.linspace(0, 1.0, eval_pts) + eta3 = xp.array([0.0]) # particles object particles = ParticlesSPH( @@ -192,12 +207,13 @@ def test_sph_evaluation_2d( ) particles.draw_markers(sort=False, verbose=False) - particles.mpi_sort_markers() + if comm is not None: + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] - ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") + ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") test_eval = particles.eval_density( ee1, ee2, @@ -208,17 +224,20 @@ def test_sph_evaluation_2d( kernel_type=kernel, derivative=derivative, ) - all_eval = np.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + if comm is None: + all_eval = test_eval + else: + all_eval = xp.zeros_like(test_eval) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) exact_eval = fun_exact(ee1, ee2, ee3) - err_max_norm = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) + err_max_norm = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) - if comm.Get_rank() == 0: - print(f"\n{boxes_per_dim = }") - print(f"{kernel = }, {derivative =}") - print(f"{bc_x = }, {bc_y = }, {eval_pts = }, {tesselation = }, {err_max_norm = }") + if rank == 0: + print(f"\n{boxes_per_dim =}") + print(f"{kernel =}, {derivative =}") + print(f"{bc_x =}, {bc_y =}, {eval_pts =}, {tesselation =}, {err_max_norm =}") if show_plot: plt.figure(figsize=(12, 24)) plt.subplot(2, 1, 1) @@ -254,7 +273,12 @@ def test_sph_evaluation_3d( eval_pts, show_plot=False, ): - comm = MPI.COMM_WORLD + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + rank = 0 + else: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() tesselation = True @@ -284,9 +308,9 @@ def test_sph_evaluation_3d( boundary_params = BoundaryParameters(bc_sph=(bc_x, bc_y, bc_z)) # eval points - eta1 = np.linspace(0, 1.0, eval_pts) - eta2 = np.linspace(0, 1.0, eval_pts) - eta3 = np.linspace(0, 1.0, eval_pts) + eta1 = xp.linspace(0, 1.0, eval_pts) + eta2 = xp.linspace(0, 1.0, eval_pts) + eta3 = xp.linspace(0, 1.0, eval_pts) # particles object particles = ParticlesSPH( @@ -302,12 +326,13 @@ def test_sph_evaluation_3d( ) particles.draw_markers(sort=False, verbose=False) - particles.mpi_sort_markers() + if comm is not None: + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] - ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") + ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") test_eval = particles.eval_density( ee1, ee2, @@ -318,36 +343,39 @@ def test_sph_evaluation_3d( kernel_type=kernel, derivative=derivative, ) - all_eval = np.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + if comm is None: + all_eval = test_eval + else: + all_eval = xp.zeros_like(test_eval) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) exact_eval = fun_exact(ee1, ee2, ee3) - err_max_norm = np.max(np.abs(all_eval - exact_eval)) + err_max_norm = xp.max(xp.abs(all_eval - exact_eval)) - if comm.Get_rank() == 0: - print(f"\n{boxes_per_dim = }") - print(f"{kernel = }, {derivative =}") - print(f"{bc_x = }, {bc_y = }, {bc_z = }, {eval_pts = }, {tesselation = }, {err_max_norm = }") + if rank == 0: + print(f"\n{boxes_per_dim =}") + print(f"{kernel =}, {derivative =}") + print(f"{bc_x =}, {bc_y =}, {bc_z =}, {eval_pts =}, {tesselation =}, {err_max_norm =}") if show_plot: - print(f"\n{fun_exact(ee1, ee2, ee3)[5, 5, 5] = }") - print(f"{ee1[5, 5, 5] = }, {ee2[5, 5, 5] = }, {ee3[5, 5, 5] = }") - print(f"{all_eval[5, 5, 5] = }") + print(f"\n{fun_exact(ee1, ee2, ee3)[5, 5, 5] =}") + print(f"{ee1[5, 5, 5] =}, {ee2[5, 5, 5] =}, {ee3[5, 5, 5] =}") + print(f"{all_eval[5, 5, 5] =}") - print(f"\n{ee1[4, 4, 4] = }, {ee2[4, 4, 4] = }, {ee3[4, 4, 4] = }") - print(f"{all_eval[4, 4, 4] = }") + print(f"\n{ee1[4, 4, 4] =}, {ee2[4, 4, 4] =}, {ee3[4, 4, 4] =}") + print(f"{all_eval[4, 4, 4] =}") - print(f"\n{ee1[3, 3, 3] = }, {ee2[3, 3, 3] = }, {ee3[3, 3, 3] = }") - print(f"{all_eval[3, 3, 3] = }") + print(f"\n{ee1[3, 3, 3] =}, {ee2[3, 3, 3] =}, {ee3[3, 3, 3] =}") + print(f"{all_eval[3, 3, 3] =}") - print(f"\n{ee1[2, 2, 2] = }, {ee2[2, 2, 2] = }, {ee3[2, 2, 2] = }") - print(f"{all_eval[2, 2, 2] = }") + print(f"\n{ee1[2, 2, 2] =}, {ee2[2, 2, 2] =}, {ee3[2, 2, 2] =}") + print(f"{all_eval[2, 2, 2] =}") - print(f"\n{ee1[1, 1, 1] = }, {ee2[1, 1, 1] = }, {ee3[1, 1, 1] = }") - print(f"{all_eval[1, 1, 1] = }") + print(f"\n{ee1[1, 1, 1] =}, {ee2[1, 1, 1] =}, {ee3[1, 1, 1] =}") + print(f"{all_eval[1, 1, 1] =}") - print(f"\n{ee1[0, 0, 0] = }, {ee2[0, 0, 0] = }, {ee3[0, 0, 0] = }") - print(f"{all_eval[0, 0, 0] = }") + print(f"\n{ee1[0, 0, 0] =}, {ee2[0, 0, 0] =}, {ee3[0, 0, 0] =}") + print(f"{all_eval[0, 0, 0] =}") # plt.figure(figsize=(12, 24)) # plt.subplot(2, 1, 1) # plt.pcolor(ee1[0, :, :], ee2[0, :, :], fun_exact(ee1, ee2, ee3)[0, :, :]) @@ -367,7 +395,12 @@ def test_sph_evaluation_3d( @pytest.mark.parametrize("eval_pts", [11, 16]) @pytest.mark.parametrize("tesselation", [False, True]) def test_evaluation_SPH_Np_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselation, show_plot=False): - comm = MPI.COMM_WORLD + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + rank = 0 + else: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() # DOMAIN object dom_type = "Cuboid" @@ -388,17 +421,17 @@ def test_evaluation_SPH_Np_convergence_1d(boxes_per_dim, bc_x, eval_pts, tessela # perturbation]} if bc_x in ("periodic", "fixed"): - fun_exact = lambda e1, e2, e3: 1.5 - np.sin(2 * np.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - xp.sin(2 * xp.pi * e1) pert = {"n": perturbations.ModesSin(ls=(1,), amps=(-1e-0,))} elif bc_x == "mirror": - fun_exact = lambda e1, e2, e3: 1.5 - np.cos(2 * np.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - xp.cos(2 * xp.pi * e1) pert = {"n": perturbations.ModesCos(ls=(1,), amps=(-1e-0,))} # exact solution - eta1 = np.linspace(0, 1.0, eval_pts) # add offset for non-periodic boundary conditions, TODO: implement Neumann - eta2 = np.array([0.0]) - eta3 = np.array([0.0]) - ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") + eta1 = xp.linspace(0, 1.0, eval_pts) # add offset for non-periodic boundary conditions, TODO: implement Neumann + eta2 = xp.array([0.0]) + eta3 = xp.array([0.0]) + ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") exact_eval = fun_exact(ee1, ee2, ee3) # boundary conditions @@ -426,50 +459,54 @@ def test_evaluation_SPH_Np_convergence_1d(boxes_per_dim, bc_x, eval_pts, tessela ) particles.draw_markers(sort=False, verbose=False) - particles.mpi_sort_markers() + if comm is not None: + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] test_eval = particles.eval_density(ee1, ee2, ee3, h1=h1, h2=h2, h3=h3) - all_eval = np.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + if comm is None: + all_eval = test_eval + else: + all_eval = xp.zeros_like(test_eval) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) - if show_plot and comm.Get_rank() == 0: + if show_plot and rank == 0: plt.figure() plt.plot(ee1.squeeze(), exact_eval.squeeze(), label="exact") plt.plot(ee1.squeeze(), all_eval.squeeze(), "--.", label="eval_sph") - plt.title(f"{Np = }, {ppb = }") + plt.title(f"{Np =}, {ppb =}") # plt.savefig(f"fun_{Np}_{ppb}.png") - diff = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) + diff = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) err_vec += [diff] - print(f"{Np = }, {ppb = }, {diff = }") + print(f"{Np =}, {ppb =}, {diff =}") if tesselation: - fit = np.polyfit(np.log(ppbs), np.log(err_vec), 1) + fit = xp.polyfit(xp.log(ppbs), xp.log(err_vec), 1) xvec = ppbs else: - fit = np.polyfit(np.log(Nps), np.log(err_vec), 1) + fit = xp.polyfit(xp.log(Nps), xp.log(err_vec), 1) xvec = Nps - if show_plot and comm.Get_rank() == 0: + if show_plot and rank == 0: plt.figure(figsize=(12, 8)) plt.loglog(xvec, err_vec, label="Convergence") - plt.loglog(xvec, np.exp(fit[1]) * np.array(xvec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") + plt.loglog(xvec, xp.exp(fit[1]) * xp.array(xvec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") plt.legend() plt.show() # plt.savefig(f"Convergence_SPH_{tesselation=}") - if comm.Get_rank() == 0: - print(f"\n{bc_x = }, {eval_pts = }, {tesselation = }, {fit[0] = }") + if rank == 0: + print(f"\n{bc_x =}, {eval_pts =}, {tesselation =}, {fit[0] =}") if tesselation: assert fit[0] < 2e-3 else: - assert np.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate + assert xp.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate @pytest.mark.parametrize("boxes_per_dim", [(12, 1, 1)]) @@ -477,7 +514,12 @@ def test_evaluation_SPH_Np_convergence_1d(boxes_per_dim, bc_x, eval_pts, tessela @pytest.mark.parametrize("eval_pts", [11, 16]) @pytest.mark.parametrize("tesselation", [False, True]) def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselation, show_plot=False): - comm = MPI.COMM_WORLD + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + rank = 0 + else: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() # DOMAIN object dom_type = "Cuboid" @@ -500,17 +542,17 @@ def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselat # perturbation if bc_x in ("periodic", "fixed"): - fun_exact = lambda e1, e2, e3: 1.5 - np.sin(2 * np.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - xp.sin(2 * xp.pi * e1) pert = {"n": perturbations.ModesSin(ls=(1,), amps=(-1e-0,))} elif bc_x == "mirror": - fun_exact = lambda e1, e2, e3: 1.5 - np.cos(2 * np.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - xp.cos(2 * xp.pi * e1) pert = {"n": perturbations.ModesCos(ls=(1,), amps=(-1e-0,))} # exact solution - eta1 = np.linspace(0, 1.0, eval_pts) # add offset for non-periodic boundary conditions, TODO: implement Neumann - eta2 = np.array([0.0]) - eta3 = np.array([0.0]) - ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") + eta1 = xp.linspace(0, 1.0, eval_pts) # add offset for non-periodic boundary conditions, TODO: implement Neumann + eta2 = xp.array([0.0]) + eta3 = xp.array([0.0]) + ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") exact_eval = fun_exact(ee1, ee2, ee3) # boundary conditions @@ -534,27 +576,31 @@ def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselat ) particles.draw_markers(sort=False, verbose=False) - particles.mpi_sort_markers() + if comm is not None: + particles.mpi_sort_markers() particles.initialize_weights() h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] test_eval = particles.eval_density(ee1, ee2, ee3, h1=h1, h2=h2, h3=h3) - all_eval = np.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + if comm is None: + all_eval = test_eval + else: + all_eval = xp.zeros_like(test_eval) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) - if show_plot and comm.Get_rank() == 0: + if show_plot and rank == 0: plt.figure() plt.plot(ee1.squeeze(), exact_eval.squeeze(), label="exact") plt.plot(ee1.squeeze(), all_eval.squeeze(), "--.", label="eval_sph") - plt.title(f"{h1 = }") + plt.title(f"{h1 =}") # plt.savefig(f"fun_{h1}.png") # error in max-norm - diff = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) + diff = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) - print(f"{h1 = }, {diff = }") + print(f"{h1 =}, {diff =}") if tesselation and h1 < 0.256: assert diff < 0.036 @@ -562,23 +608,23 @@ def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselat err_vec += [diff] if tesselation: - fit = np.polyfit(np.log(h_vec[1:5]), np.log(err_vec[1:5]), 1) + fit = xp.polyfit(xp.log(h_vec[1:5]), xp.log(err_vec[1:5]), 1) else: - fit = np.polyfit(np.log(h_vec[:-2]), np.log(err_vec[:-2]), 1) + fit = xp.polyfit(xp.log(h_vec[:-2]), xp.log(err_vec[:-2]), 1) - if show_plot and comm.Get_rank() == 0: + if show_plot and rank == 0: plt.figure(figsize=(12, 8)) plt.loglog(h_vec, err_vec, label="Convergence") - plt.loglog(h_vec, np.exp(fit[1]) * np.array(h_vec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") + plt.loglog(h_vec, xp.exp(fit[1]) * xp.array(h_vec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") plt.legend() plt.show() # plt.savefig("Convergence_SPH") - if comm.Get_rank() == 0: - print(f"\n{bc_x = }, {eval_pts = }, {tesselation = }, {fit[0] = }") + if rank == 0: + print(f"\n{bc_x =}, {eval_pts =}, {tesselation =}, {fit[0] =}") if not tesselation: - assert np.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate + assert xp.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate @pytest.mark.parametrize("boxes_per_dim", [(12, 1, 1)]) @@ -586,7 +632,12 @@ def test_evaluation_SPH_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselat @pytest.mark.parametrize("eval_pts", [11, 16]) @pytest.mark.parametrize("tesselation", [False, True]) def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, tesselation, show_plot=False): - comm = MPI.COMM_WORLD + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + rank = 0 + else: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() # DOMAIN object dom_type = "Cuboid" @@ -607,17 +658,17 @@ def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, te # perturbation if bc_x in ("periodic", "fixed"): - fun_exact = lambda e1, e2, e3: 1.5 - np.sin(2 * np.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - xp.sin(2 * xp.pi * e1) pert = {"n": perturbations.ModesSin(ls=(1,), amps=(-1e-0,))} elif bc_x == "mirror": - fun_exact = lambda e1, e2, e3: 1.5 - np.cos(2 * np.pi * e1) + fun_exact = lambda e1, e2, e3: 1.5 - xp.cos(2 * xp.pi * e1) pert = {"n": perturbations.ModesCos(ls=(1,), amps=(-1e-0,))} # exact solution - eta1 = np.linspace(0, 1.0, eval_pts) - eta2 = np.array([0.0]) - eta3 = np.array([0.0]) - ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") + eta1 = xp.linspace(0, 1.0, eval_pts) + eta2 = xp.array([0.0]) + eta3 = xp.array([0.0]) + ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") exact_eval = fun_exact(ee1, ee2, ee3) # boundary conditions @@ -647,22 +698,27 @@ def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, te ) particles.draw_markers(sort=False, verbose=False) - particles.mpi_sort_markers() + if comm is not None: + particles.mpi_sort_markers() particles.initialize_weights() h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] test_eval = particles.eval_density(ee1, ee2, ee3, h1=h, h2=h2, h3=h3) - all_eval = np.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + + if comm is None: + all_eval = test_eval + else: + all_eval = xp.zeros_like(test_eval) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) # error in max-norm - diff = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) + diff = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) err_vec[-1] += [diff] - if comm.Get_rank() == 0: - print(f"{Np = }, {ppb = }, {diff = }") + if rank == 0: + print(f"{Np =}, {ppb =}, {diff =}") # if show_plot: # plt.figure() # plt.plot(ee1.squeeze(), fun_exact(ee1, ee2, ee3).squeeze(), label="exact") @@ -670,41 +726,41 @@ def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, te # plt.title(f"{h = }, {Np = }") # # plt.savefig(f"fun_h{h}_N{Np}_ppb{ppb}.png") - err_vec = np.array(err_vec) - err_min = np.min(err_vec) + err_vec = xp.array(err_vec) + err_min = xp.min(err_vec) - if show_plot and comm.Get_rank() == 0: + if show_plot and rank == 0: if tesselation: - h_mesh, n_mesh = np.meshgrid(np.log10(h_arr), np.log10(ppbs), indexing="ij") + h_mesh, n_mesh = xp.meshgrid(xp.log10(h_arr), xp.log10(ppbs), indexing="ij") if not tesselation: - h_mesh, n_mesh = np.meshgrid(np.log10(h_arr), np.log10(Nps), indexing="ij") + h_mesh, n_mesh = xp.meshgrid(xp.log10(h_arr), xp.log10(Nps), indexing="ij") plt.figure(figsize=(6, 6)) - plt.pcolor(h_mesh, n_mesh, np.log10(err_vec), shading="auto") + plt.pcolor(h_mesh, n_mesh, xp.log10(err_vec), shading="auto") plt.title("Error") plt.colorbar(label="log10(error)") plt.xlabel("log10(h)") plt.ylabel("log10(particles)") - min_indices = np.argmin(err_vec, axis=0) + min_indices = xp.argmin(err_vec, axis=0) min_h_values = [] for mi in min_indices: - min_h_values += [np.log10(h_arr[mi])] + min_h_values += [xp.log10(h_arr[mi])] if tesselation: - log_particles = np.log10(ppbs) + log_particles = xp.log10(ppbs) else: - log_particles = np.log10(Nps) + log_particles = xp.log10(Nps) plt.plot(min_h_values, log_particles, "r-", label="Min error h for each Np", linewidth=2) plt.legend() # plt.savefig("SPH_conv_in_h_and_N.png") plt.show() - if comm.Get_rank() == 0: - print(f"\n{tesselation = }, {bc_x = }, {err_min = }") + if rank == 0: + print(f"\n{tesselation =}, {bc_x =}, {err_min =}") if tesselation: if bc_x == "periodic": - assert np.min(err_vec) < 7.7e-5 + assert xp.min(err_vec) < 7.7e-5 elif bc_x == "fixed": assert err_min < 7.7e-5 else: @@ -721,7 +777,12 @@ def test_evaluation_mc_Np_and_h_convergence_1d(boxes_per_dim, bc_x, eval_pts, te @pytest.mark.parametrize("bc_y", ["periodic", "fixed", "mirror"]) @pytest.mark.parametrize("tesselation", [False, True]) def test_evaluation_SPH_Np_convergence_2d(boxes_per_dim, bc_x, bc_y, tesselation, show_plot=False): - comm = MPI.COMM_WORLD + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + rank = 0 + else: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() # DOMAIN object dom_type = "Cuboid" @@ -746,25 +807,25 @@ def test_evaluation_SPH_Np_convergence_2d(boxes_per_dim, bc_x, bc_y, tesselation # perturbation if bc_x in ("periodic", "fixed"): if bc_y in ("periodic", "fixed"): - fun_exact = lambda x, y, z: 1.5 - np.sin(2 * np.pi / Lx * x) * np.sin(2 * np.pi / Ly * y) + fun_exact = lambda x, y, z: 1.5 - xp.sin(2 * xp.pi / Lx * x) * xp.sin(2 * xp.pi / Ly * y) pert = {"n": perturbations.ModesSinSin(ls=(1,), ms=(1,), amps=(-1e-0,))} elif bc_y == "mirror": - fun_exact = lambda x, y, z: 1.5 - np.sin(2 * np.pi / Lx * x) * np.cos(2 * np.pi / Ly * y) + fun_exact = lambda x, y, z: 1.5 - xp.sin(2 * xp.pi / Lx * x) * xp.cos(2 * xp.pi / Ly * y) pert = {"n": perturbations.ModesSinCos(ls=(1,), ms=(1,), amps=(-1e-0,))} elif bc_x == "mirror": if bc_y in ("periodic", "fixed"): - fun_exact = lambda x, y, z: 1.5 - np.cos(2 * np.pi / Lx * x) * np.sin(2 * np.pi / Ly * y) + fun_exact = lambda x, y, z: 1.5 - xp.cos(2 * xp.pi / Lx * x) * xp.sin(2 * xp.pi / Ly * y) pert = {"n": perturbations.ModesCosSin(ls=(1,), ms=(1,), amps=(-1e-0,))} elif bc_y == "mirror": - fun_exact = lambda x, y, z: 1.5 - np.cos(2 * np.pi / Lx * x) * np.cos(2 * np.pi / Ly * y) + fun_exact = lambda x, y, z: 1.5 - xp.cos(2 * xp.pi / Lx * x) * xp.cos(2 * xp.pi / Ly * y) pert = {"n": perturbations.ModesCosCos(ls=(1,), ms=(1,), amps=(-1e-0,))} # exact solution - eta1 = np.linspace(0, 1.0, 41) - eta2 = np.linspace(0, 1.0, 86) - eta3 = np.array([0.0]) - ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") + eta1 = xp.linspace(0, 1.0, 41) + eta2 = xp.linspace(0, 1.0, 86) + eta3 = xp.array([0.0]) + ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") x, y, z = domain(eta1, eta2, eta3) exact_eval = fun_exact(x, y, z) @@ -791,60 +852,63 @@ def test_evaluation_SPH_Np_convergence_2d(boxes_per_dim, bc_x, bc_y, tesselation n_as_volume_form=True, verbose=False, ) - - if comm.Get_rank() == 0: + if rank == 0: print(f"{particles.domain_array}") particles.draw_markers(sort=False, verbose=False) - particles.mpi_sort_markers() + if comm is not None: + particles.mpi_sort_markers() particles.initialize_weights() h1 = 1 / boxes_per_dim[0] h2 = 1 / boxes_per_dim[1] h3 = 1 / boxes_per_dim[2] test_eval = particles.eval_density(ee1, ee2, ee3, h1=h1, h2=h2, h3=h3, kernel_type="gaussian_2d") - all_eval = np.zeros_like(test_eval) - comm.Allreduce(test_eval, all_eval, op=MPI.SUM) + if comm is None: + all_eval = test_eval + else: + all_eval = xp.zeros_like(test_eval) + comm.Allreduce(test_eval, all_eval, op=MPI.SUM) # error in max-norm - diff = np.max(np.abs(all_eval - exact_eval)) / np.max(np.abs(exact_eval)) + diff = xp.max(xp.abs(all_eval - exact_eval)) / xp.max(xp.abs(exact_eval)) err_vec += [diff] if tesselation: assert diff < 0.06 - if comm.Get_rank() == 0: - print(f"{Np = }, {ppb = }, {diff = }") + if rank == 0: + print(f"{Np =}, {ppb =}, {diff =}") if show_plot: fig, ax = plt.subplots() d = ax.pcolor(ee1.squeeze(), ee2.squeeze(), all_eval.squeeze(), label="eval_sph", vmin=1.0, vmax=2.0) fig.colorbar(d, ax=ax, label="2d_SPH") ax.set_xlabel("ee1") ax.set_ylabel("ee2") - ax.set_title(f"{Np}_{ppb = }") + ax.set_title(f"{Np}_{ppb =}") # fig.savefig(f"2d_sph_{Np}_{ppb}.png") if tesselation: - fit = np.polyfit(np.log(ppbs), np.log(err_vec), 1) + fit = xp.polyfit(xp.log(ppbs), xp.log(err_vec), 1) xvec = ppbs else: - fit = np.polyfit(np.log(Nps), np.log(err_vec), 1) + fit = xp.polyfit(xp.log(Nps), xp.log(err_vec), 1) xvec = Nps - if show_plot and comm.Get_rank() == 0: + if show_plot and rank == 0: plt.figure(figsize=(12, 8)) plt.loglog(xvec, err_vec, label="Convergence") - plt.loglog(xvec, np.exp(fit[1]) * np.array(xvec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") + plt.loglog(xvec, xp.exp(fit[1]) * xp.array(xvec) ** (fit[0]), "--", label=f"fit with slope {fit[0]}") plt.legend() plt.show() # plt.savefig(f"Convergence_SPH_{tesselation=}") - if comm.Get_rank() == 0: - print(f"\n{bc_x = }, {tesselation = }, {fit[0] = }") + if rank == 0: + print(f"\n{bc_x =}, {tesselation =}, {fit[0] =}") if not tesselation: - assert np.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate + assert xp.abs(fit[0] + 0.5) < 0.1 # Monte Carlo rate if __name__ == "__main__": diff --git a/src/struphy/pic/tests/test_tesselation.py b/src/struphy/pic/tests/test_tesselation.py index adc67447b..b138af50a 100644 --- a/src/struphy/pic/tests/test_tesselation.py +++ b/src/struphy/pic/tests/test_tesselation.py @@ -1,9 +1,9 @@ from time import time -import numpy as np +import cunumpy as xp import pytest from matplotlib import pyplot as plt -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from struphy.feec.psydac_derham import Derham from struphy.fields_background.equils import ConstantVelocity @@ -13,7 +13,6 @@ from struphy.pic.utilities import BoundaryParameters, LoadingParameters, WeightsParameters -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("ppb", [8, 12]) @pytest.mark.parametrize("nx", [16, 10, 24]) @pytest.mark.parametrize("ny", [1, 16, 10]) @@ -57,23 +56,22 @@ def test_draw(ppb, nx, ny, nz): zl = particles.domain_array[rank, 6] zr = particles.domain_array[rank, 7] - eta1 = np.linspace(xl, xr, tiles_x + 1)[:-1] + (xr - xl) / (2 * tiles_x) - eta2 = np.linspace(yl, yr, tiles_y + 1)[:-1] + (yr - yl) / (2 * tiles_y) - eta3 = np.linspace(zl, zr, tiles_z + 1)[:-1] + (zr - zl) / (2 * tiles_z) + eta1 = xp.linspace(xl, xr, tiles_x + 1)[:-1] + (xr - xl) / (2 * tiles_x) + eta2 = xp.linspace(yl, yr, tiles_y + 1)[:-1] + (yr - yl) / (2 * tiles_y) + eta3 = xp.linspace(zl, zr, tiles_z + 1)[:-1] + (zr - zl) / (2 * tiles_z) - ee1, ee2, ee3 = np.meshgrid(eta1, eta2, eta3, indexing="ij") + ee1, ee2, ee3 = xp.meshgrid(eta1, eta2, eta3, indexing="ij") e1 = ee1.flatten() e2 = ee2.flatten() e3 = ee3.flatten() # print(f'\n{rank = }, {e1 = }') - assert np.allclose(particles.positions[:, 0], e1) - assert np.allclose(particles.positions[:, 1], e2) - assert np.allclose(particles.positions[:, 2], e3) + assert xp.allclose(particles.positions[:, 0], e1) + assert xp.allclose(particles.positions[:, 1], e2) + assert xp.allclose(particles.positions[:, 2], e3) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("ppb", [8, 12]) @pytest.mark.parametrize("nx", [10, 8, 6]) @pytest.mark.parametrize("ny", [1, 16, 10]) @@ -121,20 +119,20 @@ def test_cell_average(ppb, nx, ny, nz, n_quad, show_plot=False): yl = particles.domain_array[rank, 3] yr = particles.domain_array[rank, 4] - eta1 = np.linspace(xl, xr, tiles_x + 1) - eta2 = np.linspace(yl, yr, tiles_y + 1) + eta1 = xp.linspace(xl, xr, tiles_x + 1) + eta2 = xp.linspace(yl, yr, tiles_y + 1) if ny == nz == 1: plt.figure(figsize=(15, 10)) - plt.plot(particles.positions[:, 0], np.zeros_like(particles.weights), "o", label="markers") + plt.plot(particles.positions[:, 0], xp.zeros_like(particles.weights), "o", label="markers") plt.plot(particles.positions[:, 0], particles.weights, "-o", label="weights") plt.plot( - np.linspace(xl, xr, 100), - particles.f_init(np.linspace(xl, xr, 100), 0.5, 0.5).squeeze(), + xp.linspace(xl, xr, 100), + particles.f_init(xp.linspace(xl, xr, 100), 0.5, 0.5).squeeze(), "--", label="f_init", ) - plt.vlines(np.linspace(xl, xr, nx + 1), 0, 2, label="sorting boxes", color="k") + plt.vlines(xp.linspace(xl, xr, nx + 1), 0, 2, label="sorting boxes", color="k") ax = plt.gca() ax.set_xticks(eta1) ax.set_yticks(eta2) @@ -148,8 +146,8 @@ def test_cell_average(ppb, nx, ny, nz, n_quad, show_plot=False): plt.subplot(1, 2, 1) ax = plt.gca() - ax.set_xticks(np.linspace(0, 1, nx + 1)) - ax.set_yticks(np.linspace(0, 1, ny + 1)) + ax.set_xticks(xp.linspace(0, 1, nx + 1)) + ax.set_yticks(xp.linspace(0, 1, ny + 1)) coloring = particles.weights plt.scatter(particles.positions[:, 0], particles.positions[:, 1], c=coloring, s=40) plt.grid(c="k") @@ -161,12 +159,12 @@ def test_cell_average(ppb, nx, ny, nz, n_quad, show_plot=False): plt.subplot(1, 2, 2) ax = plt.gca() - ax.set_xticks(np.linspace(0, 1, nx + 1)) - ax.set_yticks(np.linspace(0, 1, ny + 1)) + ax.set_xticks(xp.linspace(0, 1, nx + 1)) + ax.set_yticks(xp.linspace(0, 1, ny + 1)) coloring = particles.weights - pos1 = np.linspace(xl, xr, 100) - pos2 = np.linspace(yl, yr, 100) - pp1, pp2 = np.meshgrid(pos1, pos2, indexing="ij") + pos1 = xp.linspace(xl, xr, 100) + pos2 = xp.linspace(yl, yr, 100) + pp1, pp2 = xp.meshgrid(pos1, pos2, indexing="ij") plt.pcolor(pp1, pp2, particles.f_init(pp1, pp2, 0.5).squeeze()) plt.grid(c="k") plt.axis("square") @@ -178,8 +176,8 @@ def test_cell_average(ppb, nx, ny, nz, n_quad, show_plot=False): plt.show() # test - print(f"\n{rank = }, {np.max(np.abs(particles.weights - particles.f_init(particles.positions))) = }") - assert np.max(np.abs(particles.weights - particles.f_init(particles.positions))) < 0.012 + print(f"\n{rank =}, {xp.max(xp.abs(particles.weights - particles.f_init(particles.positions))) =}") + assert xp.max(xp.abs(particles.weights - particles.f_init(particles.positions))) < 0.012 if __name__ == "__main__": diff --git a/src/struphy/pic/utilities.py b/src/struphy/pic/utilities.py index 99b597c9b..3ae645557 100644 --- a/src/struphy/pic/utilities.py +++ b/src/struphy/pic/utilities.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp import struphy.pic.utilities_kernels as utils from struphy.io.options import ( @@ -183,23 +183,23 @@ def __init__( # computations and allocations self._bin_edges = [] for nb, rng in zip(n_bins, ranges): - self._bin_edges += [np.linspace(rng[0], rng[1], nb + 1)] + self._bin_edges += [xp.linspace(rng[0], rng[1], nb + 1)] self._bin_edges = tuple(self.bin_edges) - self._f = np.zeros(n_bins, dtype=float) - self._df = np.zeros(n_bins, dtype=float) + self._f = xp.zeros(n_bins, dtype=float) + self._df = xp.zeros(n_bins, dtype=float) @property def bin_edges(self) -> tuple: return self._bin_edges @property - def f(self) -> np.ndarray: + def f(self) -> xp.ndarray: """The binned distribution function (full-f).""" return self._f @property - def df(self) -> np.ndarray: + def df(self) -> xp.ndarray: """The binned distribution function minus the background (delta-f).""" return self._df @@ -215,23 +215,23 @@ class KernelDensityPlot: def __init__( self, - pts_e1: int = 11, - pts_e2: int = 11, - pts_e3: int = 11, + pts_e1: int = 16, + pts_e2: int = 16, + pts_e3: int = 1, ): - e1 = np.linspace(0.0, 1.0, pts_e1) - e2 = np.linspace(0.0, 1.0, pts_e2) - e3 = np.linspace(0.0, 1.0, pts_e3) - ee1, ee2, ee3 = np.meshgrid(e1, e2, e3, indexing="ij") + e1 = xp.linspace(0.0, 1.0, pts_e1) + e2 = xp.linspace(0.0, 1.0, pts_e2) + e3 = xp.linspace(0.0, 1.0, pts_e3) + ee1, ee2, ee3 = xp.meshgrid(e1, e2, e3, indexing="ij") self._plot_pts = (ee1, ee2, ee3) - self._n_sph = np.zeros(ee1.shape, dtype=float) + self._n_sph = xp.zeros(ee1.shape, dtype=float) @property def plot_pts(self) -> tuple: return self._plot_pts @property - def n_sph(self) -> np.ndarray: + def n_sph(self) -> xp.ndarray: """The evaluated density.""" return self._n_sph @@ -252,15 +252,15 @@ def get_kinetic_energy_particles(fe_coeffs, derham, domain, particles): Particles object. """ - res = np.empty(1, dtype=float) + res = xp.empty(1, dtype=float) utils.canonical_kinetic_particles( res, particles.markers, - np.array(derham.p), + xp.array(derham.p), derham.Vh_fem["0"].knots[0], derham.Vh_fem["0"].knots[1], derham.Vh_fem["0"].knots[2], - np.array( + xp.array( derham.V0.coeff_space.starts, ), *domain.args_map, @@ -285,7 +285,7 @@ def get_electron_thermal_energy(density_0_form, derham, domain, nel1, nel2, nel3 Discrete Derham complex. """ - res = np.empty(1, dtype=float) + res = xp.empty(1, dtype=float) utils.thermal_energy( res, density_0_form._operators[0].matrix._data, diff --git a/src/struphy/pic/utilities_kernels.py b/src/struphy/pic/utilities_kernels.py index d0f3c4e92..cb25cc05f 100644 --- a/src/struphy/pic/utilities_kernels.py +++ b/src/struphy/pic/utilities_kernels.py @@ -1,4 +1,4 @@ -from numpy import abs, empty, log, pi, shape, sign, sqrt, zeros +from numpy import abs, empty, log, mod, pi, shape, sign, sqrt, zeros from pyccel.decorators import stack_array import struphy.bsplines.bsplines_kernels as bsplines_kernels @@ -14,7 +14,7 @@ eval_vectorfield_spline_mpi, get_spans, ) -from struphy.kernel_arguments.pusher_args_kernels import DerhamArguments, DomainArguments +from struphy.kernel_arguments.pusher_args_kernels import DerhamArguments, DomainArguments, MarkerArguments def eval_magnetic_moment_5d( @@ -331,6 +331,71 @@ def eval_magnetic_energy( # -- removed omp: #$ omp end parallel +@stack_array("dfm", "eta") +def eval_magnetic_energy_PBb( + markers: "float[:,:]", + args_derham: "DerhamArguments", + args_domain: "DomainArguments", + first_diagnostics_idx: int, + abs_B0: "float[:,:,:]", + PBb: "float[:,:,:]", +): + r""" + Evaluate :math:`mu_p |B(\boldsymbol \eta_p)_\parallel|` for each marker. + The result is stored at markers[:, first_diagnostics_idx]. + """ + eta = empty(3, dtype=float) + + dfm = empty((3, 3), dtype=float) + + # get number of markers + n_markers = shape(markers)[0] + + for ip in range(n_markers): + # only do something if particle is a "true" particle (i.e. not a hole) + if markers[ip, 0] == -1.0: + continue + + eta[:] = mod(markers[ip, 0:3], 1.0) + + weight = markers[ip, 7] + dweight = markers[ip, 5] + + mu = markers[ip, first_diagnostics_idx + 1] + + # spline evaluation + span1, span2, span3 = get_spans(eta[0], eta[1], eta[2], args_derham) + + # evaluate Jacobian, result in dfm + evaluation_kernels.df( + eta[0], + eta[1], + eta[2], + args_domain, + dfm, + ) + + # abs_B0; 0form + abs_B = eval_0form_spline_mpi( + span1, + span2, + span3, + args_derham, + abs_B0, + ) + + # PBb; 0form + PB_b = eval_0form_spline_mpi( + span1, + span2, + span3, + args_derham, + PBb, + ) + + markers[ip, first_diagnostics_idx] = mu * (abs_B + PB_b) + + @stack_array("v", "dfm", "b2", "norm_b_cart", "temp", "v_perp", "Larmor_r") def eval_guiding_center_from_6d( markers: "float[:,:]", @@ -441,189 +506,101 @@ def eval_guiding_center_from_6d( markers[ip, first_diagnostics_idx + 2] = z - Larmor_r[2] -@stack_array("grad_PB", "tmp") -def accum_gradI_const( - markers: "float[:,:]", - Np: "int", +@stack_array("dfm", "df_t", "g", "g_inv", "gradB, grad_PB_b", "tmp", "eta_mid", "eta_diff") +def eval_gradB_ediff( + args_markers: "MarkerArguments", + args_domain: "DomainArguments", args_derham: "DerhamArguments", - grad_PB1: "float[:,:,:]", - grad_PB2: "float[:,:,:]", - grad_PB3: "float[:,:,:]", - scale: "float", + gradB1: "float[:,:,:]", + gradB2: "float[:,:,:]", + gradB3: "float[:,:,:]", + grad_PB_b1: "float[:,:,:]", + grad_PB_b2: "float[:,:,:]", + grad_PB_b3: "float[:,:,:]", + idx: int, ): r"""TODO""" + + # allocate metric coeffs + dfm = empty((3, 3), dtype=float) + df_t = empty((3, 3), dtype=float) + g = empty((3, 3), dtype=float) + g_inv = empty((3, 3), dtype=float) + # allocate for magnetic field evaluation - grad_PB = empty(3, dtype=float) + gradB = empty(3, dtype=float) + grad_PB_b = empty(3, dtype=float) tmp = empty(3, dtype=float) + eta_mid = empty(3, dtype=float) + eta_diff = empty(3, dtype=float) - # allocate for filling - res = zeros(1, dtype=float) - - # get number of markers - n_markers_loc = shape(markers)[0] + # get marker arguments + markers = args_markers.markers + n_markers = args_markers.n_markers + mu_idx = args_markers.mu_idx + first_init_idx = args_markers.first_init_idx + first_free_idx = args_markers.first_free_idx - for ip in range(n_markers_loc): + for ip in range(n_markers): # only do something if particle is a "true" particle (i.e. not a hole) if markers[ip, 0] == -1.0: continue - # marker positions - eta1 = markers[ip, 0] # mid - eta2 = markers[ip, 1] # mid - eta3 = markers[ip, 2] # mid + # marker positions, mid point + eta_mid[:] = (markers[ip, 0:3] + markers[ip, first_init_idx : first_init_idx + 3]) / 2.0 + eta_mid[:] = mod(eta_mid[:], 1.0) + + eta_diff = markers[ip, 0:3] - markers[ip, first_init_idx : first_init_idx + 3] # marker weight and velocity weight = markers[ip, 5] - mu = markers[ip, 9] + mu = markers[ip, mu_idx] # b-field evaluation - span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) + span1, span2, span3 = get_spans(eta_mid[0], eta_mid[1], eta_mid[2], args_derham) + # print(span1, span2, span3) - # grad_PB; 1form + # evaluate Jacobian, result in dfm + evaluation_kernels.df( + eta_mid[0], + eta_mid[1], + eta_mid[2], + args_domain, + dfm, + ) + + linalg_kernels.transpose(dfm, df_t) + linalg_kernels.matrix_matrix(df_t, dfm, g) + linalg_kernels.matrix_inv(g, g_inv) + + # gradB; 1form eval_1form_spline_mpi( span1, span2, span3, args_derham, - grad_PB1, - grad_PB2, - grad_PB3, - grad_PB, + gradB1, + gradB2, + gradB3, + gradB, ) - tmp[:] = markers[ip, 15:18] - res += linalg_kernels.scalar_dot(tmp, grad_PB) * weight * mu * scale - - return res / Np - - -def accum_en_fB( - markers: "float[:,:]", - Np: "int", - args_derham: "DerhamArguments", - PB: "float[:,:,:]", -): - r"""TODO""" - - # allocate for filling - res = zeros(1, dtype=float) - - # get number of markers - n_markers_loc = shape(markers)[0] - - for ip in range(n_markers_loc): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - # marker positions - eta1 = markers[ip, 0] - eta2 = markers[ip, 1] - eta3 = markers[ip, 2] - - # marker weight and velocity - mu = markers[ip, 9] - weight = markers[ip, 5] - - # b-field evaluation - span1, span2, span3 = get_spans(eta1, eta2, eta3, args_derham) - - B0 = eval_0form_spline_mpi( + # grad_PB_b; 1form + eval_1form_spline_mpi( span1, span2, span3, args_derham, - PB, + grad_PB_b1, + grad_PB_b2, + grad_PB_b3, + grad_PB_b, ) - res += abs(B0) * mu * weight - - return res / Np - - -@stack_array("e", "e_diff") -def check_eta_diff(markers: "float[:,:]"): - r"""TODO""" - # marker position e - e = empty(3, dtype=float) - e_diff = empty(3, dtype=float) - - # get number of markers - n_markers_loc = shape(markers)[0] - - for ip in range(n_markers_loc): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - e[:] = markers[ip, 0:3] - e_diff[:] = e[:] - markers[ip, 9:12] - - for axis in range(3): - if e_diff[axis] > 0.5: - e_diff[axis] -= 1.0 - elif e_diff[axis] < -0.5: - e_diff[axis] += 1.0 - - markers[ip, 15:18] = e_diff[:] - - -@stack_array("e", "e_diff") -def check_eta_diff2(markers: "float[:,:]"): - r"""TODO""" - # marker position e - e = empty(3, dtype=float) - e_diff = empty(3, dtype=float) - - # get number of markers - n_markers_loc = shape(markers)[0] - - for ip in range(n_markers_loc): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - e[:] = markers[ip, 0:3] - e_diff[:] = e[:] - markers[ip, 12:15] - - for axis in range(3): - if e_diff[axis] > 0.5: - e_diff[axis] -= 1.0 - elif e_diff[axis] < -0.5: - e_diff[axis] += 1.0 - - markers[ip, 15:18] = e_diff[:] - - -@stack_array("e", "e_diff", "e_mid") -def check_eta_mid(markers: "float[:,:]"): - r"""TODO""" - # marker position e - e = empty(3, dtype=float) - e_diff = empty(3, dtype=float) - e_mid = empty(3, dtype=float) - - # get number of markers - n_markers_loc = shape(markers)[0] - - for ip in range(n_markers_loc): - # only do something if particle is a "true" particle (i.e. not a hole) - if markers[ip, 0] == -1.0: - continue - - e[:] = markers[ip, 0:3] - markers[ip, 12:15] = e[:] - - e_diff[:] = e[:] - markers[ip, 9:12] - e_mid[:] = (e[:] + markers[ip, 9:12]) / 2.0 - - for axis in range(3): - if e_diff[axis] > 0.5: - e_mid[axis] += 0.5 - elif e_diff[axis] < -0.5: - e_mid[axis] += 0.5 + tmp = gradB + grad_PB_b - markers[ip, 0:3] = e_mid[:] + markers[ip, idx] = linalg_kernels.scalar_dot(eta_diff, tmp) + markers[ip, idx] *= mu @stack_array("dfm", "dfinv", "dfinv_t", "v", "a_form", "dfta_form") diff --git a/src/struphy/polar/basic.py b/src/struphy/polar/basic.py index d863997db..99a95cc47 100644 --- a/src/struphy/polar/basic.py +++ b/src/struphy/polar/basic.py @@ -1,5 +1,5 @@ -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.basic import Vector, VectorSpace from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -19,7 +19,7 @@ class PolarDerhamSpace(VectorSpace): """ def __init__(self, derham, space_id): - assert derham.spl_kind[0] == False, "Spline basis in eta1 must be clamped" + assert not derham.spl_kind[0], "Spline basis in eta1 must be clamped" assert derham.spl_kind[1], "Spline basis in eta2 must be periodic" assert (derham.Nel[1] / 3) % 1 == 0.0, "Number of elements in eta2 must be a multiple of 3" @@ -208,7 +208,7 @@ class PolarVector(Vector): Element of a PolarDerhamSpace. An instance of a PolarVector consists of two parts: - 1. a list of np.arrays of the polar coeffs (not distributed) + 1. a list of xp.arrays of the polar coeffs (not distributed) 2. a tensor product StencilVector/BlockVector of the parent space with inner rings set to zero (distributed). Parameters @@ -223,7 +223,7 @@ def __init__(self, V): self._dtype = V.dtype # initialize polar coeffs - self._pol = [np.zeros((m, n)) for m, n in zip(V.n_polar, V.n3)] + self._pol = [xp.zeros((m, n)) for m, n in zip(V.n_polar, V.n3)] # full tensor product vector self._tp = V.parent_space.zeros() @@ -240,7 +240,7 @@ def dtype(self): @property def pol(self): - """Polar coefficients as np.array.""" + """Polar coefficients as xp.array.""" return self._pol @pol.setter @@ -326,7 +326,7 @@ def toarray(self, allreduce=False): if self.space.comm is not None and allreduce: self.space.comm.Allreduce(MPI.IN_PLACE, out, op=MPI.SUM) - out = np.concatenate((self.pol[0].flatten(), out)) + out = xp.concatenate((self.pol[0].flatten(), out)) else: out1 = self.tp[0].toarray()[self.space.n_rings[0] * self.space.n[1] * self.space.n3[0] :] @@ -339,7 +339,7 @@ def toarray(self, allreduce=False): self.space.comm.Allreduce(MPI.IN_PLACE, out2, op=MPI.SUM) self.space.comm.Allreduce(MPI.IN_PLACE, out3, op=MPI.SUM) - out = np.concatenate( + out = xp.concatenate( ( self.pol[0].flatten(), out1, @@ -347,7 +347,7 @@ def toarray(self, allreduce=False): out2, self.pol[2].flatten(), out3, - ) + ), ) return out @@ -365,7 +365,7 @@ def copy(self, out=None): self._tp.copy(out=w.tp) # copy polar part for n, pl in enumerate(self._pol): - np.copyto(w._pol[n], pl, casting="no") + xp.copyto(w._pol[n], pl, casting="no") return w def __neg__(self): diff --git a/src/struphy/polar/extraction_operators.py b/src/struphy/polar/extraction_operators.py index 14af375e4..1c2a461ce 100644 --- a/src/struphy/polar/extraction_operators.py +++ b/src/struphy/polar/extraction_operators.py @@ -1,4 +1,4 @@ -import numpy as np +import cunumpy as xp # ============================= 2D polar splines (C1) =================================== @@ -47,8 +47,8 @@ def __init__(self, domain, derham): self._pole = (cx[0, 0], cy[0, 0]) - assert np.all(cx[0] == self.pole[0]) - assert np.all(cy[0] == self.pole[1]) + assert xp.all(cx[0] == self.pole[0]) + assert xp.all(cy[0] == self.pole[1]) self._n0 = cx.shape[0] self._n1 = cx.shape[1] @@ -70,14 +70,14 @@ def __init__(self, domain, derham): self._tau = max( [ ((self.cx[1] - self.pole[0]) * (-2)).max(), - ((self.cx[1] - self.pole[0]) - np.sqrt(3) * (self.cy[1] - self.pole[1])).max(), - ((self.cx[1] - self.pole[0]) + np.sqrt(3) * (self.cy[1] - self.pole[1])).max(), - ] + ((self.cx[1] - self.pole[0]) - xp.sqrt(3) * (self.cy[1] - self.pole[1])).max(), + ((self.cx[1] - self.pole[0]) + xp.sqrt(3) * (self.cy[1] - self.pole[1])).max(), + ], ) # barycentric coordinates - self._xi_0 = np.zeros((3, self.n1), dtype=float) - self._xi_1 = np.zeros((3, self.n1), dtype=float) + self._xi_0 = xp.zeros((3, self.n1), dtype=float) + self._xi_1 = xp.zeros((3, self.n1), dtype=float) self._xi_0[:, :] = 1 / 3 @@ -85,12 +85,12 @@ def __init__(self, domain, derham): self._xi_1[1, :] = ( 1 / 3 - 1 / (3 * self.tau) * (self.cx[1] - self.pole[0]) - + np.sqrt(3) / (3 * self.tau) * (self.cy[1] - self.pole[1]) + + xp.sqrt(3) / (3 * self.tau) * (self.cy[1] - self.pole[1]) ) self._xi_1[2, :] = ( 1 / 3 - 1 / (3 * self.tau) * (self.cx[1] - self.pole[0]) - - np.sqrt(3) / (3 * self.tau) * (self.cy[1] - self.pole[1]) + - xp.sqrt(3) / (3 * self.tau) * (self.cy[1] - self.pole[1]) ) # remove small values @@ -102,17 +102,17 @@ def __init__(self, domain, derham): # ============= basis extraction operator for discrete 0-forms ================ # first n_rings tp rings --> "polar coeffs" - e0_blocks_ten_to_pol = np.block([self.xi_0, self.xi_1]) + e0_blocks_ten_to_pol = xp.block([self.xi_0, self.xi_1]) self._e_ten_to_pol["0"] = [[csr(e0_blocks_ten_to_pol)]] # ============ basis extraction operator for discrete 1-forms (Hcurl) ========= # first n_rings tp rings --> "polar coeffs" - e1_11_blocks_ten_to_pol = np.zeros((self.n_polar[1][0], self.n_rings[1][0] * self.n1), dtype=float) - e1_12_blocks_ten_to_pol = np.zeros((self.n_polar[1][0], self.n_rings[1][1] * self.d1), dtype=float) + e1_11_blocks_ten_to_pol = xp.zeros((self.n_polar[1][0], self.n_rings[1][0] * self.n1), dtype=float) + e1_12_blocks_ten_to_pol = xp.zeros((self.n_polar[1][0], self.n_rings[1][1] * self.d1), dtype=float) - e1_21_blocks_ten_to_pol = np.zeros((self.n_polar[1][1], self.n_rings[1][0] * self.n1), dtype=float) - e1_22_blocks_ten_to_pol = np.zeros((self.n_polar[1][1], self.n_rings[1][1] * self.d1), dtype=float) + e1_21_blocks_ten_to_pol = xp.zeros((self.n_polar[1][1], self.n_rings[1][0] * self.n1), dtype=float) + e1_22_blocks_ten_to_pol = xp.zeros((self.n_polar[1][1], self.n_rings[1][1] * self.d1), dtype=float) # 1st component for l in range(2): @@ -135,7 +135,7 @@ def __init__(self, domain, derham): # =============== basis extraction operator for discrete 1-forms (Hdiv) ========= # first n_rings tp rings --> "polar coeffs" - e3_blocks_ten_to_pol = np.zeros((self.n_polar[3][0], self.n_rings[3][0] * self.d1), dtype=float) + e3_blocks_ten_to_pol = xp.zeros((self.n_polar[3][0], self.n_rings[3][0] * self.d1), dtype=float) self._e_ten_to_pol["2"] = [ [csr(e1_22_blocks_ten_to_pol), csr(-e1_21_blocks_ten_to_pol), None], @@ -161,7 +161,7 @@ def __init__(self, domain, derham): self._p_ten_to_ten = {} # first n_rings tp rings --> "polar coeffs" - p0_blocks_ten_to_pol = np.zeros((self.n_polar[0][0], self.n_rings[0][0] * self.n1), dtype=float) + p0_blocks_ten_to_pol = xp.zeros((self.n_polar[0][0], self.n_rings[0][0] * self.n1), dtype=float) # !! NOTE: for odd spline degrees and periodic splines the first Greville point sometimes does NOT start at zero!! if domain.p[1] % 2 != 0 and not (abs(derham.Vh_fem["0"].spaces[1].interpolation_grid[0]) < 1e-14): @@ -176,15 +176,15 @@ def __init__(self, domain, derham): self._p_ten_to_pol["0"] = [[csr(p0_blocks_ten_to_pol)]] # first n_rings + 1 tp rings --> "first tp ring" - p0_blocks_ten_to_ten = np.block([0 * np.identity(self.n1)] * self.n_rings[0][0] + [np.identity(self.n1)]) + p0_blocks_ten_to_ten = xp.block([0 * xp.identity(self.n1)] * self.n_rings[0][0] + [xp.identity(self.n1)]) self._p_ten_to_ten["0"] = [[csr(p0_blocks_ten_to_ten)]] # =========== projection extraction operator for discrete 1-forms (Hcurl) ======== # first n_rings tp rings --> "polar coeffs" - p1_11_blocks_ten_to_pol = np.zeros((self.n_polar[1][0], self.n_rings[1][0] * self.n1), dtype=float) - p1_22_blocks_ten_to_pol = np.zeros((self.n_polar[1][1], self.n_rings[1][1] * self.d1), dtype=float) + p1_11_blocks_ten_to_pol = xp.zeros((self.n_polar[1][0], self.n_rings[1][0] * self.n1), dtype=float) + p1_22_blocks_ten_to_pol = xp.zeros((self.n_polar[1][1], self.n_rings[1][1] * self.d1), dtype=float) # !! NOTE: PSYDAC's first integration interval sometimes start at < 0 !! if derham.Vh_fem["3"].spaces[1].histopolation_grid[0] < -1e-14: @@ -196,8 +196,8 @@ def __init__(self, domain, derham): p1_22_blocks_ten_to_pol[1, (self.d1 + 0 * self.d1 // 3) : (self.d1 + 1 * self.d1 // 3)] = 1.0 p1_22_blocks_ten_to_pol[1, (self.d1 + 1 * self.d1 // 3) : (self.d1 + 2 * self.d1 // 3)] = 1.0 - p1_12_blocks_ten_to_pol = np.zeros((self.n_polar[1][0], self.n_rings[1][1] * self.d1), dtype=float) - p1_21_blocks_ten_to_pol = np.zeros((self.n_polar[1][1], self.n_rings[1][0] * self.d1), dtype=float) + p1_12_blocks_ten_to_pol = xp.zeros((self.n_polar[1][0], self.n_rings[1][1] * self.d1), dtype=float) + p1_21_blocks_ten_to_pol = xp.zeros((self.n_polar[1][1], self.n_rings[1][0] * self.d1), dtype=float) self._p_ten_to_pol["1"] = [ [csr(p1_11_blocks_ten_to_pol), csr(p1_12_blocks_ten_to_pol), None], @@ -206,26 +206,26 @@ def __init__(self, domain, derham): ] # first n_rings + 1 tp rings --> "first tp ring" - p1_11_blocks_ten_to_ten = np.zeros((self.n1, self.n1), dtype=float) + p1_11_blocks_ten_to_ten = xp.zeros((self.n1, self.n1), dtype=float) # !! NOTE: for odd spline degrees and periodic splines the first Greville point sometimes does NOT start at zero!! if domain.p[1] % 2 != 0 and not (abs(derham.Vh_fem["0"].spaces[1].interpolation_grid[0]) < 1e-14): - p1_11_blocks_ten_to_ten[:, 3 * self.n1 // 3 - 1] = -np.roll(self.xi_1[0], -1) - p1_11_blocks_ten_to_ten[:, 1 * self.n1 // 3 - 1] = -np.roll(self.xi_1[1], -1) - p1_11_blocks_ten_to_ten[:, 2 * self.n1 // 3 - 1] = -np.roll(self.xi_1[2], -1) + p1_11_blocks_ten_to_ten[:, 3 * self.n1 // 3 - 1] = -xp.roll(self.xi_1[0], -1) + p1_11_blocks_ten_to_ten[:, 1 * self.n1 // 3 - 1] = -xp.roll(self.xi_1[1], -1) + p1_11_blocks_ten_to_ten[:, 2 * self.n1 // 3 - 1] = -xp.roll(self.xi_1[2], -1) else: p1_11_blocks_ten_to_ten[:, 0 * self.n1 // 3] = -self.xi_1[0] p1_11_blocks_ten_to_ten[:, 1 * self.n1 // 3] = -self.xi_1[1] p1_11_blocks_ten_to_ten[:, 2 * self.n1 // 3] = -self.xi_1[2] - p1_11_blocks_ten_to_ten += np.identity(self.n1) + p1_11_blocks_ten_to_ten += xp.identity(self.n1) - p1_11_blocks_ten_to_ten = np.block([p1_11_blocks_ten_to_ten, np.identity(self.n1)]) + p1_11_blocks_ten_to_ten = xp.block([p1_11_blocks_ten_to_ten, xp.identity(self.n1)]) - p1_22_blocks_ten_to_ten = np.block([0 * np.identity(self.d1)] * self.n_rings[1][1] + [np.identity(self.d1)]) + p1_22_blocks_ten_to_ten = xp.block([0 * xp.identity(self.d1)] * self.n_rings[1][1] + [xp.identity(self.d1)]) - p1_12_blocks_ten_to_ten = np.zeros((self.d1, (self.n_rings[1][1] + 1) * self.d1), dtype=float) - p1_21_blocks_ten_to_ten = np.zeros((self.n1, (self.n_rings[1][0] + 1) * self.n1), dtype=float) + p1_12_blocks_ten_to_ten = xp.zeros((self.d1, (self.n_rings[1][1] + 1) * self.d1), dtype=float) + p1_21_blocks_ten_to_ten = xp.zeros((self.n1, (self.n_rings[1][0] + 1) * self.n1), dtype=float) self._p_ten_to_ten["1"] = [ [csr(p1_11_blocks_ten_to_ten), csr(p1_12_blocks_ten_to_ten), None], @@ -236,7 +236,7 @@ def __init__(self, domain, derham): # ========== projection extraction operator for discrete 1-forms (Hdiv) ========== # first n_rings tp rings --> "polar coeffs" - p3_blocks_ten_to_pol = np.zeros((self.n_polar[3][0], self.n_rings[3][0] * self.d1), dtype=float) + p3_blocks_ten_to_pol = xp.zeros((self.n_polar[3][0], self.n_rings[3][0] * self.d1), dtype=float) self._p_ten_to_pol["2"] = [ [csr(p1_22_blocks_ten_to_pol), csr(p1_21_blocks_ten_to_pol), None], @@ -245,24 +245,24 @@ def __init__(self, domain, derham): ] # first n_rings + 1 tp rings --> "first tp ring" - p3_blocks_ten_to_ten = np.zeros((self.d1, self.d1), dtype=float) + p3_blocks_ten_to_ten = xp.zeros((self.d1, self.d1), dtype=float) - a0 = np.diff(self.xi_1[1], append=self.xi_1[1, 0]) - a1 = np.diff(self.xi_1[2], append=self.xi_1[2, 0]) + a0 = xp.diff(self.xi_1[1], append=self.xi_1[1, 0]) + a1 = xp.diff(self.xi_1[2], append=self.xi_1[2, 0]) # !! NOTE: PSYDAC's first integration interval sometimes start at < 0 !! if derham.Vh_fem["3"].spaces[1].histopolation_grid[0] < -1e-14: p3_blocks_ten_to_ten[:, (0 * self.n1 // 3 + 1) : (1 * self.n1 // 3 + 1)] = ( - -np.roll(a0, +1)[:, None] - np.roll(a1, +1)[:, None] + -xp.roll(a0, +1)[:, None] - xp.roll(a1, +1)[:, None] ) - p3_blocks_ten_to_ten[:, (1 * self.n1 // 3 + 1) : (2 * self.n1 // 3 + 1)] = -np.roll(a1, +1)[:, None] + p3_blocks_ten_to_ten[:, (1 * self.n1 // 3 + 1) : (2 * self.n1 // 3 + 1)] = -xp.roll(a1, +1)[:, None] else: p3_blocks_ten_to_ten[:, 0 * self.n1 // 3 : 1 * self.n1 // 3] = -a0[:, None] - a1[:, None] p3_blocks_ten_to_ten[:, 1 * self.n1 // 3 : 2 * self.n1 // 3] = -a1[:, None] - p3_blocks_ten_to_ten += np.identity(self.d1) + p3_blocks_ten_to_ten += xp.identity(self.d1) - p3_blocks_ten_to_ten = np.block([p3_blocks_ten_to_ten, np.identity(self.d1)]) + p3_blocks_ten_to_ten = xp.block([p3_blocks_ten_to_ten, xp.identity(self.d1)]) self._p_ten_to_ten["2"] = [ [csr(p1_22_blocks_ten_to_ten), csr(p1_21_blocks_ten_to_ten), None], @@ -295,24 +295,24 @@ def __init__(self, domain, derham): # ======================= discrete gradient ====================================== # "polar coeffs" to "polar coeffs" - grad_pol_to_pol_1 = np.zeros((self.n_polar[1][0], self.n_polar[0][0]), dtype=float) - grad_pol_to_pol_2 = np.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) - grad_pol_to_pol_3 = np.identity(self.n_polar[0][0], dtype=float) + grad_pol_to_pol_1 = xp.zeros((self.n_polar[1][0], self.n_polar[0][0]), dtype=float) + grad_pol_to_pol_2 = xp.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) + grad_pol_to_pol_3 = xp.identity(self.n_polar[0][0], dtype=float) self._grad_pol_to_pol = [[csr(grad_pol_to_pol_1)], [csr(grad_pol_to_pol_2)], [csr(grad_pol_to_pol_3)]] # "polar coeffs" to "first tp ring" - grad_pol_to_ten_1 = np.zeros(((self.n_rings[1][0] + 1) * self.n1, self.n_polar[0][0])) - grad_pol_to_ten_2 = np.zeros(((self.n_rings[1][1] + 1) * self.d1, self.n_polar[0][0])) - grad_pol_to_ten_3 = np.zeros(((self.n_rings[0][0] + 1) * self.n1, self.n_polar[0][0])) + grad_pol_to_ten_1 = xp.zeros(((self.n_rings[1][0] + 1) * self.n1, self.n_polar[0][0])) + grad_pol_to_ten_2 = xp.zeros(((self.n_rings[1][1] + 1) * self.d1, self.n_polar[0][0])) + grad_pol_to_ten_3 = xp.zeros(((self.n_rings[0][0] + 1) * self.n1, self.n_polar[0][0])) grad_pol_to_ten_1[-self.n1 :, :] = -self.xi_1.T self._grad_pol_to_ten = [[csr(grad_pol_to_ten_1)], [csr(grad_pol_to_ten_2)], [csr(grad_pol_to_ten_3)]] # eta_3 direction - grad_e3_1 = np.identity(self.n2, dtype=float) - grad_e3_2 = np.identity(self.n2, dtype=float) + grad_e3_1 = xp.identity(self.n2, dtype=float) + grad_e3_2 = xp.identity(self.n2, dtype=float) grad_e3_3 = grad_1d_matrix(derham.spl_kind[2], self.n2) self._grad_e3 = [[csr(grad_e3_1)], [csr(grad_e3_2)], [csr(grad_e3_3)]] @@ -320,14 +320,14 @@ def __init__(self, domain, derham): # =========================== discrete curl ====================================== # "polar coeffs" to "polar coeffs" - curl_pol_to_pol_12 = np.identity(self.n_polar[1][1], dtype=float) - curl_pol_to_pol_13 = np.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) + curl_pol_to_pol_12 = xp.identity(self.n_polar[1][1], dtype=float) + curl_pol_to_pol_13 = xp.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) - curl_pol_to_pol_21 = np.identity(self.n_polar[1][0], dtype=float) - curl_pol_to_pol_23 = np.zeros((self.n_polar[2][1], self.n_polar[0][0]), dtype=float) + curl_pol_to_pol_21 = xp.identity(self.n_polar[1][0], dtype=float) + curl_pol_to_pol_23 = xp.zeros((self.n_polar[2][1], self.n_polar[0][0]), dtype=float) - curl_pol_to_pol_31 = np.zeros((self.n_polar[3][0], self.n_polar[1][0]), dtype=float) - curl_pol_to_pol_32 = np.zeros((self.n_polar[3][0], self.n_polar[1][1]), dtype=float) + curl_pol_to_pol_31 = xp.zeros((self.n_polar[3][0], self.n_polar[1][0]), dtype=float) + curl_pol_to_pol_32 = xp.zeros((self.n_polar[3][0], self.n_polar[1][1]), dtype=float) self._curl_pol_to_pol = [ [None, csr(-curl_pol_to_pol_12), csr(curl_pol_to_pol_13)], @@ -336,14 +336,14 @@ def __init__(self, domain, derham): ] # "polar coeffs" to "first tp ring" - curl_pol_to_ten_12 = np.zeros(((self.n_rings[2][0] + 1) * self.d1, self.n_polar[1][1])) - curl_pol_to_ten_13 = np.zeros(((self.n_rings[2][0] + 1) * self.d1, self.n_polar[0][0])) + curl_pol_to_ten_12 = xp.zeros(((self.n_rings[2][0] + 1) * self.d1, self.n_polar[1][1])) + curl_pol_to_ten_13 = xp.zeros(((self.n_rings[2][0] + 1) * self.d1, self.n_polar[0][0])) - curl_pol_to_ten_21 = np.zeros(((self.n_rings[2][1] + 1) * self.n1, self.n_polar[1][0])) - curl_pol_to_ten_23 = np.zeros(((self.n_rings[2][1] + 1) * self.n1, self.n_polar[0][0])) + curl_pol_to_ten_21 = xp.zeros(((self.n_rings[2][1] + 1) * self.n1, self.n_polar[1][0])) + curl_pol_to_ten_23 = xp.zeros(((self.n_rings[2][1] + 1) * self.n1, self.n_polar[0][0])) - curl_pol_to_ten_31 = np.zeros(((self.n_rings[3][0] + 1) * self.n1, self.n_polar[1][0])) - curl_pol_to_ten_32 = np.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[1][1])) + curl_pol_to_ten_31 = xp.zeros(((self.n_rings[3][0] + 1) * self.n1, self.n_polar[1][0])) + curl_pol_to_ten_32 = xp.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[1][1])) curl_pol_to_ten_23[-self.n1 :, :] = -self.xi_1.T @@ -361,13 +361,13 @@ def __init__(self, domain, derham): # eta_3 direction curl_e3_12 = grad_1d_matrix(derham.spl_kind[2], self.n2) - curl_e3_13 = np.identity(self.d2) + curl_e3_13 = xp.identity(self.d2) curl_e3_21 = grad_1d_matrix(derham.spl_kind[2], self.n2) - curl_e3_23 = np.identity(self.d2) + curl_e3_23 = xp.identity(self.d2) - curl_e3_31 = np.identity(self.n2) - curl_e3_32 = np.identity(self.n2) + curl_e3_31 = xp.identity(self.n2) + curl_e3_32 = xp.identity(self.n2) self._curl_e3 = [ [None, csr(curl_e3_12), csr(curl_e3_13)], @@ -378,16 +378,16 @@ def __init__(self, domain, derham): # =========================== discrete div ====================================== # "polar coeffs" to "polar coeffs" - div_pol_to_pol_1 = np.zeros((self.n_polar[3][0], self.n_polar[2][0]), dtype=float) - div_pol_to_pol_2 = np.zeros((self.n_polar[3][0], self.n_polar[2][1]), dtype=float) - div_pol_to_pol_3 = np.identity(self.n_polar[3][0], dtype=float) + div_pol_to_pol_1 = xp.zeros((self.n_polar[3][0], self.n_polar[2][0]), dtype=float) + div_pol_to_pol_2 = xp.zeros((self.n_polar[3][0], self.n_polar[2][1]), dtype=float) + div_pol_to_pol_3 = xp.identity(self.n_polar[3][0], dtype=float) self._div_pol_to_pol = [[csr(div_pol_to_pol_1), csr(div_pol_to_pol_2), csr(div_pol_to_pol_3)]] # "polar coeffs" to "first tp ring" - div_pol_to_ten_1 = np.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[2][0])) - div_pol_to_ten_2 = np.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[2][1])) - div_pol_to_ten_3 = np.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[3][0])) + div_pol_to_ten_1 = xp.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[2][0])) + div_pol_to_ten_2 = xp.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[2][1])) + div_pol_to_ten_3 = xp.zeros(((self.n_rings[3][0] + 1) * self.d1, self.n_polar[3][0])) for l in range(2): for j in range(self.d1, 2 * self.d1): @@ -398,8 +398,8 @@ def __init__(self, domain, derham): self._div_pol_to_ten = [[csr(div_pol_to_ten_1), csr(div_pol_to_ten_2), csr(div_pol_to_ten_3)]] # eta_3 direction - div_e3_1 = np.identity(self.d2, dtype=float) - div_e3_2 = np.identity(self.d2, dtype=float) + div_e3_1 = xp.identity(self.d2, dtype=float) + div_e3_2 = xp.identity(self.d2, dtype=float) div_e3_3 = grad_1d_matrix(derham.spl_kind[2], self.n2) self._div_e3 = [[csr(div_e3_1), csr(div_e3_2), csr(div_e3_3)]] @@ -539,13 +539,13 @@ def __init__(self, n0, n1): # =========== extraction operators for discrete 0-forms ================== # extraction operator for basis functions - self.E0_11 = spa.csr_matrix(np.ones((1, n1), dtype=float)) + self.E0_11 = spa.csr_matrix(xp.ones((1, n1), dtype=float)) self.E0_22 = spa.identity((n0 - 1) * n1, format="csr") self.E0 = spa.bmat([[self.E0_11, None], [None, self.E0_22]], format="csr") # global projection extraction operator for interpolation points - self.P0_11 = np.zeros((1, n1), dtype=float) + self.P0_11 = xp.zeros((1, n1), dtype=float) self.P0_11[0, 0] = 1.0 @@ -598,7 +598,7 @@ def __init__(self, n0, n1): # ========= discrete polar gradient matrix =============================== # radial dofs (DN) - G11 = np.zeros(((d0 - 0) * n1, 1), dtype=float) + G11 = xp.zeros(((d0 - 0) * n1, 1), dtype=float) G11[:n1, 0] = -1.0 G12 = spa.kron(grad_1d_1[:, 1:], spa.identity(n1)) @@ -606,7 +606,7 @@ def __init__(self, n0, n1): self.G1 = spa.bmat([[G11, G12]], format="csr") # angular dofs (ND) - G21 = np.zeros(((n0 - 1) * d1, 1), dtype=float) + G21 = xp.zeros(((n0 - 1) * d1, 1), dtype=float) G22 = spa.kron(spa.identity(n0 - 1), grad_1d_2, format="csr") self.G2 = spa.bmat([[G21, G22]], format="csr") @@ -619,13 +619,13 @@ def __init__(self, n0, n1): # 2D vector curl (NN --> ND DN) # angular dofs (ND) - VC11 = np.zeros(((n0 - 1) * d1, 1), dtype=float) + VC11 = xp.zeros(((n0 - 1) * d1, 1), dtype=float) VC12 = spa.kron(spa.identity(n0 - 1), grad_1d_2, format="csr") self.VC1 = spa.bmat([[VC11, VC12]], format="csr") # radial dofs (DN) - VC21 = np.zeros(((d0 - 0) * n1, 1), dtype=float) + VC21 = xp.zeros(((d0 - 0) * n1, 1), dtype=float) VC21[:n1, 0] = 1.0 VC22 = -spa.kron(grad_1d_1[:, 1:], spa.identity(n1)) @@ -687,26 +687,26 @@ def __init__(self, cx, cy): self.Nbase2 = (d0 - 1) * d1 # size of control triangle - self.tau = np.array( + self.tau = xp.array( [ (-2 * (cx[1] - self.x0)).max(), - ((cx[1] - self.x0) - np.sqrt(3) * (cy[1] - self.y0)).max(), - ((cx[1] - self.x0) + np.sqrt(3) * (cy[1] - self.y0)).max(), - ] + ((cx[1] - self.x0) - xp.sqrt(3) * (cy[1] - self.y0)).max(), + ((cx[1] - self.x0) + xp.sqrt(3) * (cy[1] - self.y0)).max(), + ], ).max() - self.Xi_0 = np.zeros((3, n1), dtype=float) - self.Xi_1 = np.zeros((3, n1), dtype=float) + self.Xi_0 = xp.zeros((3, n1), dtype=float) + self.Xi_1 = xp.zeros((3, n1), dtype=float) # barycentric coordinates self.Xi_0[:, :] = 1 / 3 self.Xi_1[0, :] = 1 / 3 + 2 / (3 * self.tau) * (cx[1] - self.x0) self.Xi_1[1, :] = ( - 1 / 3 - 1 / (3 * self.tau) * (cx[1] - self.x0) + np.sqrt(3) / (3 * self.tau) * (cy[1] - self.y0) + 1 / 3 - 1 / (3 * self.tau) * (cx[1] - self.x0) + xp.sqrt(3) / (3 * self.tau) * (cy[1] - self.y0) ) self.Xi_1[2, :] = ( - 1 / 3 - 1 / (3 * self.tau) * (cx[1] - self.x0) - np.sqrt(3) / (3 * self.tau) * (cy[1] - self.y0) + 1 / 3 - 1 / (3 * self.tau) * (cx[1] - self.x0) - xp.sqrt(3) / (3 * self.tau) * (cy[1] - self.y0) ) # remove small values @@ -714,13 +714,13 @@ def __init__(self, cx, cy): # =========== extraction operators for discrete 0-forms ================== # extraction operator for basis functions - self.E0_11 = spa.csr_matrix(np.hstack((self.Xi_0, self.Xi_1))) + self.E0_11 = spa.csr_matrix(xp.hstack((self.Xi_0, self.Xi_1))) self.E0_22 = spa.identity((n0 - 2) * n1, format="csr") self.E0 = spa.bmat([[self.E0_11, None], [None, self.E0_22]], format="csr") # global projection extraction operator for interpolation points - self.P0_11 = np.zeros((3, 2 * n1), dtype=float) + self.P0_11 = xp.zeros((3, 2 * n1), dtype=float) self.P0_11[0, n1 + 0 * n1 // 3] = 1.0 self.P0_11[1, n1 + 1 * n1 // 3] = 1.0 @@ -737,8 +737,8 @@ def __init__(self, cx, cy): self.E1C_12 = spa.identity((d0 - 1) * n1) self.E1C_34 = spa.identity((n0 - 2) * d1) - self.E1C_21 = np.zeros((2, 1 * n1), dtype=float) - self.E1C_23 = np.zeros((2, 2 * d1), dtype=float) + self.E1C_21 = xp.zeros((2, 1 * n1), dtype=float) + self.E1C_23 = xp.zeros((2, 2 * d1), dtype=float) # 1st component for s in range(2): @@ -760,22 +760,22 @@ def __init__(self, cx, cy): # extraction operator for interpolation/histopolation in global projector # 1st component - self.P1C_11 = np.zeros((n1, n1), dtype=float) + self.P1C_11 = xp.zeros((n1, n1), dtype=float) self.P1C_12 = spa.identity(n1) self.P1C_23 = spa.identity((d0 - 2) * n1) self.P1C_11[:, 0 * n1 // 3] = -self.Xi_1[0] self.P1C_11[:, 1 * n1 // 3] = -self.Xi_1[1] self.P1C_11[:, 2 * n1 // 3] = -self.Xi_1[2] - self.P1C_11 += np.identity(n1) + self.P1C_11 += xp.identity(n1) # 2nd component - self.P1C_34 = np.zeros((2, 2 * d1), dtype=float) + self.P1C_34 = xp.zeros((2, 2 * d1), dtype=float) self.P1C_45 = spa.identity((n0 - 2) * d1) - self.P1C_34[0, (d1 + 0 * d1 // 3) : (d1 + 1 * d1 // 3)] = np.ones(d1 // 3, dtype=float) - self.P1C_34[1, (d1 + 0 * d1 // 3) : (d1 + 1 * d1 // 3)] = np.ones(d1 // 3, dtype=float) - self.P1C_34[1, (d1 + 1 * d1 // 3) : (d1 + 2 * d1 // 3)] = np.ones(d1 // 3, dtype=float) + self.P1C_34[0, (d1 + 0 * d1 // 3) : (d1 + 1 * d1 // 3)] = xp.ones(d1 // 3, dtype=float) + self.P1C_34[1, (d1 + 0 * d1 // 3) : (d1 + 1 * d1 // 3)] = xp.ones(d1 // 3, dtype=float) + self.P1C_34[1, (d1 + 1 * d1 // 3) : (d1 + 2 * d1 // 3)] = xp.ones(d1 // 3, dtype=float) # combined first and second component self.P1C = spa.bmat( @@ -790,8 +790,8 @@ def __init__(self, cx, cy): # ========================================================================= # ========= extraction operators for discrete 1-forms (H_div) ============= - self.E1D_11 = np.zeros((2, 2 * d1), dtype=float) - self.E1D_13 = np.zeros((2, 1 * n1), dtype=float) + self.E1D_11 = xp.zeros((2, 2 * d1), dtype=float) + self.E1D_13 = xp.zeros((2, 1 * n1), dtype=float) self.E1D_22 = spa.identity((n0 - 2) * d1) self.E1D_34 = spa.identity((d0 - 1) * n1) @@ -834,13 +834,13 @@ def __init__(self, cx, cy): # ========================================================================= # =========== extraction operators for discrete 2-forms =================== - self.E2_1 = np.zeros(((d0 - 1) * d1, d1), dtype=float) + self.E2_1 = xp.zeros(((d0 - 1) * d1, d1), dtype=float) self.E2_2 = spa.identity((d0 - 1) * d1) self.E2 = spa.bmat([[self.E2_1, self.E2_2]], format="csr") # extraction operator for histopolation in global projector - self.P2_11 = np.zeros((d1, d1), dtype=float) + self.P2_11 = xp.zeros((d1, d1), dtype=float) self.P2_12 = spa.identity(d1) self.P2_23 = spa.identity((d0 - 2) * d1) @@ -853,7 +853,7 @@ def __init__(self, cx, cy): # block B self.P2_11[i, 1 * n1 // 3 : 2 * n1 // 3] = -(self.Xi_1[2, (i + 1) % n1] - self.Xi_1[2, i]) - self.P2_11 += np.identity(d1) + self.P2_11 += xp.identity(d1) self.P2 = spa.bmat([[self.P2_11, self.P2_12, None], [None, None, self.P2_23]], format="csr") # ========================================================================= @@ -864,14 +864,14 @@ def __init__(self, cx, cy): # ========================================================================= # ========= discrete polar gradient matrix ================================ - self.G1_1 = np.zeros(((d0 - 1) * n1, 3), dtype=float) + self.G1_1 = xp.zeros(((d0 - 1) * n1, 3), dtype=float) self.G1_1[:n1, :] = -self.Xi_1.T self.G1_2 = spa.kron(grad_1d_1[1:, 2:], spa.identity(n1)) self.G1 = spa.bmat([[self.G1_1, self.G1_2]], format="csr") - self.G2_11 = np.zeros((2, 3), dtype=float) + self.G2_11 = xp.zeros((2, 3), dtype=float) self.G2_11[0, 0] = -1.0 self.G2_11[0, 1] = 1.0 @@ -888,7 +888,7 @@ def __init__(self, cx, cy): # ========= discrete polar curl matrix =================================== # 2D vector curl - self.VC1_11 = np.zeros((2, 3), dtype=float) + self.VC1_11 = xp.zeros((2, 3), dtype=float) self.VC1_11[0, 0] = -1.0 self.VC1_11[0, 1] = 1.0 @@ -900,7 +900,7 @@ def __init__(self, cx, cy): self.VC1 = spa.bmat([[self.VC1_11, None], [None, self.VC1_22]], format="csr") - self.VC2_11 = np.zeros(((d0 - 1) * n1, 3), dtype=float) + self.VC2_11 = xp.zeros(((d0 - 1) * n1, 3), dtype=float) self.VC2_11[:n1, :] = -self.Xi_1.T self.VC2_22 = spa.kron(grad_1d_1[1:, 2:], spa.identity(n1)) @@ -912,7 +912,7 @@ def __init__(self, cx, cy): # 2D scalar curl self.SC1 = -spa.kron(spa.identity(d0 - 1), grad_1d_2) - self.SC2_1 = np.zeros(((d0 - 1) * d1, 2), dtype=float) + self.SC2_1 = xp.zeros(((d0 - 1) * d1, 2), dtype=float) for s in range(2): for j in range(d1): @@ -926,7 +926,7 @@ def __init__(self, cx, cy): # ========================================================================= # ========= discrete polar div matrix ===================================== - self.D1_1 = np.zeros(((d0 - 1) * d1, 2), dtype=float) + self.D1_1 = xp.zeros(((d0 - 1) * d1, 2), dtype=float) for s in range(2): for j in range(d1): @@ -965,24 +965,25 @@ def __init__(self, tensor_space, cx, cy): self.Nbase3_pol = (d0 - 1) * d1 # size of control triangle - self.tau = np.array( - [(-2 * cx[1]).max(), (cx[1] - np.sqrt(3) * cy[1]).max(), (cx[1] + np.sqrt(3) * cy[1]).max()] + self.tau = xp.array( + [(-2 * cx[1]).max(), (cx[1] - xp.sqrt(3) * cy[1]).max(), (cx[1] + xp.sqrt(3) * cy[1]).max()], ).max() - self.Xi_0 = np.zeros((3, n1), dtype=float) - self.Xi_1 = np.zeros((3, n1), dtype=float) + self.Xi_0 = xp.zeros((3, n1), dtype=float) + self.Xi_1 = xp.zeros((3, n1), dtype=float) # barycentric coordinates self.Xi_0[:, :] = 1 / 3 self.Xi_1[0, :] = 1 / 3 + 2 / (3 * self.tau) * cx[1, :, 0] - self.Xi_1[1, :] = 1 / 3 - 1 / (3 * self.tau) * cx[1, :, 0] + np.sqrt(3) / (3 * self.tau) * cy[1, :, 0] - self.Xi_1[2, :] = 1 / 3 - 1 / (3 * self.tau) * cx[1, :, 0] - np.sqrt(3) / (3 * self.tau) * cy[1, :, 0] + self.Xi_1[1, :] = 1 / 3 - 1 / (3 * self.tau) * cx[1, :, 0] + xp.sqrt(3) / (3 * self.tau) * cy[1, :, 0] + self.Xi_1[2, :] = 1 / 3 - 1 / (3 * self.tau) * cx[1, :, 0] - xp.sqrt(3) / (3 * self.tau) * cy[1, :, 0] # =========== extraction operators for discrete 0-forms ================== # extraction operator for basis functions self.E0_pol = spa.bmat( - [[np.hstack((self.Xi_0, self.Xi_1)), None], [None, spa.identity((n0 - 2) * n1)]], format="csr" + [[xp.hstack((self.Xi_0, self.Xi_1)), None], [None, spa.identity((n0 - 2) * n1)]], + format="csr", ) self.E0 = spa.kron(self.E0_pol, spa.identity(n2), format="csr") @@ -1005,7 +1006,7 @@ def __init__(self, tensor_space, cx, cy): for j in range(n1): self.E1_1_pol[(d0 - 1) * n1 + s, j] = self.Xi_1[s + 1, j] - self.Xi_0[s + 1, j] - self.E1_1_pol[: (d0 - 1) * n1, n1:] = np.identity((d0 - 1) * n1) + self.E1_1_pol[: (d0 - 1) * n1, n1:] = xp.identity((d0 - 1) * n1) self.E1_1_pol = self.E1_1_pol.tocsr() # 2nd component @@ -1014,7 +1015,7 @@ def __init__(self, tensor_space, cx, cy): self.E1_2_pol[(d0 - 1) * n1 + s, j] = 0.0 self.E1_2_pol[(d0 - 1) * n1 + s, n1 + j] = self.Xi_1[s + 1, (j + 1) % n1] - self.Xi_1[s + 1, j] - self.E1_2_pol[((d0 - 1) * n1 + 2) :, 2 * d1 :] = np.identity((n0 - 2) * d1) + self.E1_2_pol[((d0 - 1) * n1 + 2) :, 2 * d1 :] = xp.identity((n0 - 2) * d1) self.E1_2_pol = self.E1_2_pol.tocsr() # 3rd component @@ -1043,9 +1044,9 @@ def __init__(self, tensor_space, cx, cy): self.P1_1_pol = self.P1_1_pol.tocsr() # 2nd component - self.P1_2_pol[0, (n1 + 0 * n1 // 3) : (n1 + 1 * n1 // 3)] = np.ones((1, n1 // 3), dtype=float) - self.P1_2_pol[1, (n1 + 0 * n1 // 3) : (n1 + 1 * n1 // 3)] = np.ones((1, n1 // 3), dtype=float) - self.P1_2_pol[1, (n1 + 1 * n1 // 3) : (n1 + 2 * n1 // 3)] = np.ones((1, n1 // 3), dtype=float) + self.P1_2_pol[0, (n1 + 0 * n1 // 3) : (n1 + 1 * n1 // 3)] = xp.ones((1, n1 // 3), dtype=float) + self.P1_2_pol[1, (n1 + 0 * n1 // 3) : (n1 + 1 * n1 // 3)] = xp.ones((1, n1 // 3), dtype=float) + self.P1_2_pol[1, (n1 + 1 * n1 // 3) : (n1 + 2 * n1 // 3)] = xp.ones((1, n1 // 3), dtype=float) self.P1_2_pol[2:, 2 * n1 :] = spa.identity((n0 - 2) * d1) self.P1_2_pol = self.P1_2_pol.tocsr() @@ -1073,7 +1074,7 @@ def __init__(self, tensor_space, cx, cy): self.E2_1_pol[s, j] = 0.0 self.E2_1_pol[s, n1 + j] = self.Xi_1[s + 1, (j + 1) % n1] - self.Xi_1[s + 1, j] - self.E2_1_pol[2 : (2 + (n0 - 2) * d1), 2 * n1 :] = np.identity((n0 - 2) * d1) + self.E2_1_pol[2 : (2 + (n0 - 2) * d1), 2 * n1 :] = xp.identity((n0 - 2) * d1) self.E2_1_pol = self.E2_1_pol.tocsr() # 2nd component @@ -1081,11 +1082,11 @@ def __init__(self, tensor_space, cx, cy): for j in range(n1): self.E2_2_pol[s, j] = -(self.Xi_1[s + 1, j] - self.Xi_0[s + 1, j]) - self.E2_2_pol[(2 + (n0 - 2) * d1) :, 1 * n1 :] = np.identity((d0 - 1) * n1) + self.E2_2_pol[(2 + (n0 - 2) * d1) :, 1 * n1 :] = xp.identity((d0 - 1) * n1) self.E2_2_pol = self.E2_2_pol.tocsr() # 3rd component - self.E2_3_pol[:, 1 * d1 :] = np.identity((d0 - 1) * d1) + self.E2_3_pol[:, 1 * d1 :] = xp.identity((d0 - 1) * d1) self.E2_3_pol = self.E2_3_pol.tocsr() # combined first and second component @@ -1215,7 +1216,7 @@ def __init__(self, tensor_space, cx, cy): [ [None, -spa.kron(spa.identity((n0 - 2) * d1 + 2), grad_1d_3)], [spa.kron(spa.identity((d0 - 1) * n1), grad_1d_3), None], - ] + ], ) # total polar curl diff --git a/src/struphy/polar/linear_operators.py b/src/struphy/polar/linear_operators.py index f6253dd52..0e37c7e76 100644 --- a/src/struphy/polar/linear_operators.py +++ b/src/struphy/polar/linear_operators.py @@ -1,5 +1,5 @@ -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.block import BlockVector, BlockVectorSpace from psydac.linalg.stencil import StencilVector, StencilVectorSpace from scipy.sparse import csr_matrix, identity @@ -334,7 +334,14 @@ class PolarLinearOperator(LinOpWithTransp): """ def __init__( - self, V, W, tp_operator=None, blocks_pol_to_ten=None, blocks_pol_to_pol=None, blocks_e3=None, transposed=False + self, + V, + W, + tp_operator=None, + blocks_pol_to_ten=None, + blocks_pol_to_pol=None, + blocks_e3=None, + transposed=False, ): assert isinstance(V, PolarDerhamSpace) assert isinstance(W, PolarDerhamSpace) @@ -668,7 +675,7 @@ def dot_inner_tp_rings(blocks_e1_e2, blocks_e3, v, out): # loop over codomain components for m, (row_e1_e2, row_e3) in enumerate(zip(blocks_e1_e2, blocks_e3)): - res = np.zeros((n_rows[m], n3_out[m]), dtype=float) + res = xp.zeros((n_rows[m], n3_out[m]), dtype=float) # loop over domain components for n, (block_e1_e2, block_e3) in enumerate(zip(row_e1_e2, row_e3)): @@ -677,7 +684,7 @@ def dot_inner_tp_rings(blocks_e1_e2, blocks_e3, v, out): e1, e2, e3 = in_ends[n] if block_e1_e2 is not None: - tmp = np.zeros((n_rings_in[n], n2, n3_in[n]), dtype=float) + tmp = xp.zeros((n_rings_in[n], n2, n3_in[n]), dtype=float) tmp[:, s2 : e2 + 1, s3 : e3 + 1] = in_vec[n][0 : n_rings_in[n], s2 : e2 + 1, s3 : e3 + 1] res += kron_matvec_2d([block_e1_e2, block_e3], tmp.reshape(n_rings_in[n] * n2, n3_in[n])) @@ -785,7 +792,7 @@ def dot_parts_of_polar(blocks_e1_e2, blocks_e3, v, out): # loop over codomain components for m, (row_e1_e2, row_e3) in enumerate(zip(blocks_e1_e2, blocks_e3)): - res = np.zeros((n_rings_out[m], n2, n3_out[m]), dtype=float) + res = xp.zeros((n_rings_out[m], n2, n3_out[m]), dtype=float) # loop over domain components for n, (block_e1_e2, block_e3) in enumerate(zip(row_e1_e2, row_e3)): @@ -794,7 +801,7 @@ def dot_parts_of_polar(blocks_e1_e2, blocks_e3, v, out): if in_starts[n][0] == 0: s1, s2, s3 = in_starts[n] e1, e2, e3 = in_ends[n] - tmp = np.zeros((n2, n3_in[n]), dtype=float) + tmp = xp.zeros((n2, n3_in[n]), dtype=float) tmp[s2 : e2 + 1, s3 : e3 + 1] = in_tp[n][n_rings_in[n], s2 : e2 + 1, s3 : e3 + 1] res += kron_matvec_2d([block_e1_e2, block_e3], tmp).reshape(n_rings_out[m], n2, n3_out[m]) else: diff --git a/src/struphy/polar/tests/test_legacy_polar_splines.py b/src/struphy/polar/tests/test_legacy_polar_splines.py index eda4c378a..be2bfb654 100644 --- a/src/struphy/polar/tests/test_legacy_polar_splines.py +++ b/src/struphy/polar/tests/test_legacy_polar_splines.py @@ -7,8 +7,8 @@ def test_polar_splines_2D(plot=False): sys.path.append("..") + import cunumpy as xp import matplotlib.pyplot as plt - import numpy as np from mpl_toolkits.mplot3d import Axes3D from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space @@ -42,8 +42,8 @@ def test_polar_splines_2D(plot=False): fig.set_figheight(10) fig.set_figwidth(10) - el_b_1 = np.linspace(0.0, 1.0, Nel[0] + 1) - el_b_2 = np.linspace(0.0, 1.0, Nel[1] + 1) + el_b_1 = xp.linspace(0.0, 1.0, Nel[0] + 1) + el_b_2 = xp.linspace(0.0, 1.0, Nel[1] + 1) grid_x = domain(el_b_1, el_b_2, 0.0, squeeze_out=True)[0] grid_y = domain(el_b_1, el_b_2, 0.0, squeeze_out=True)[1] @@ -108,7 +108,7 @@ def test_polar_splines_2D(plot=False): ) # plot three new polar splines in V0 - etaplot = [np.linspace(0.0, 1.0, 200), np.linspace(0.0, 1.0, 200)] + etaplot = [xp.linspace(0.0, 1.0, 200), xp.linspace(0.0, 1.0, 200)] xplot = [ domain(etaplot[0], etaplot[1], 0.0, squeeze_out=True)[0], domain(etaplot[0], etaplot[1], 0.0, squeeze_out=True)[1], @@ -123,9 +123,9 @@ def test_polar_splines_2D(plot=False): ax3 = fig.add_subplot(133, projection="3d") # coeffs in polar basis - c0_pol1 = np.zeros(space_2d.E0.shape[0], dtype=float) - c0_pol2 = np.zeros(space_2d.E0.shape[0], dtype=float) - c0_pol3 = np.zeros(space_2d.E0.shape[0], dtype=float) + c0_pol1 = xp.zeros(space_2d.E0.shape[0], dtype=float) + c0_pol2 = xp.zeros(space_2d.E0.shape[0], dtype=float) + c0_pol3 = xp.zeros(space_2d.E0.shape[0], dtype=float) c0_pol1[0] = 1.0 c0_pol2[1] = 1.0 @@ -134,7 +134,7 @@ def test_polar_splines_2D(plot=False): ax1.plot_surface( xplot[0], xplot[1], - space_2d.evaluate_NN(etaplot[0], etaplot[1], np.array([0.0]), c0_pol1, "V0")[:, :, 0], + space_2d.evaluate_NN(etaplot[0], etaplot[1], xp.array([0.0]), c0_pol1, "V0")[:, :, 0], cmap="jet", ) ax1.set_xlabel("R [m]", labelpad=5) @@ -144,7 +144,7 @@ def test_polar_splines_2D(plot=False): ax2.plot_surface( xplot[0], xplot[1], - space_2d.evaluate_NN(etaplot[0], etaplot[1], np.array([0.0]), c0_pol2, "V0")[:, :, 0], + space_2d.evaluate_NN(etaplot[0], etaplot[1], xp.array([0.0]), c0_pol2, "V0")[:, :, 0], cmap="jet", ) ax2.set_xlabel("R [m]", labelpad=5) @@ -154,7 +154,7 @@ def test_polar_splines_2D(plot=False): ax3.plot_surface( xplot[0], xplot[1], - space_2d.evaluate_NN(etaplot[0], etaplot[1], np.array([0.0]), c0_pol3, "V0")[:, :, 0], + space_2d.evaluate_NN(etaplot[0], etaplot[1], xp.array([0.0]), c0_pol3, "V0")[:, :, 0], cmap="jet", ) ax3.set_xlabel("R [m]", labelpad=5) diff --git a/src/struphy/polar/tests/test_polar.py b/src/struphy/polar/tests/test_polar.py index 0c8f597d3..ac0113c4f 100644 --- a/src/struphy/polar/tests/test_polar.py +++ b/src/struphy/polar/tests/test_polar.py @@ -1,7 +1,6 @@ import pytest -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 6]]) @pytest.mark.parametrize("p", [[3, 2, 4]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) @@ -168,8 +167,8 @@ def test_spaces(Nel, p, spl_kind): @pytest.mark.parametrize("p", [[3, 2, 2]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) def test_extraction_ops_and_derivatives(Nel, p, spl_kind): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -223,11 +222,11 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): b2_pol.tp = b2_tp p3_pol.tp = p3_tp - np.random.seed(1607) - f0_pol.pol = [np.random.rand(f0_pol.pol[0].shape[0], f0_pol.pol[0].shape[1])] - e1_pol.pol = [np.random.rand(e1_pol.pol[n].shape[0], e1_pol.pol[n].shape[1]) for n in range(3)] - b2_pol.pol = [np.random.rand(b2_pol.pol[n].shape[0], b2_pol.pol[n].shape[1]) for n in range(3)] - p3_pol.pol = [np.random.rand(p3_pol.pol[0].shape[0], p3_pol.pol[0].shape[1])] + xp.random.seed(1607) + f0_pol.pol = [xp.random.rand(f0_pol.pol[0].shape[0], f0_pol.pol[0].shape[1])] + e1_pol.pol = [xp.random.rand(e1_pol.pol[n].shape[0], e1_pol.pol[n].shape[1]) for n in range(3)] + b2_pol.pol = [xp.random.rand(b2_pol.pol[n].shape[0], b2_pol.pol[n].shape[1]) for n in range(3)] + p3_pol.pol = [xp.random.rand(p3_pol.pol[0].shape[0], p3_pol.pol[0].shape[1])] f0_pol_leg = f0_pol.toarray(True) e1_pol_leg = e1_pol.toarray(True) @@ -244,10 +243,10 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): r2_pol = derham.extraction_ops["2"].dot(b2_tp) r3_pol = derham.extraction_ops["3"].dot(p3_tp) - assert np.allclose(r0_pol.toarray(True), space.E0.dot(f0_tp_leg)) - assert np.allclose(r1_pol.toarray(True), space.E1.dot(e1_tp_leg)) - assert np.allclose(r2_pol.toarray(True), space.E2.dot(b2_tp_leg)) - assert np.allclose(r3_pol.toarray(True), space.E3.dot(p3_tp_leg)) + assert xp.allclose(r0_pol.toarray(True), space.E0.dot(f0_tp_leg)) + assert xp.allclose(r1_pol.toarray(True), space.E1.dot(e1_tp_leg)) + assert xp.allclose(r2_pol.toarray(True), space.E2.dot(b2_tp_leg)) + assert xp.allclose(r3_pol.toarray(True), space.E3.dot(p3_tp_leg)) # test transposed extraction operators E0T = derham.extraction_ops["0"].transpose() @@ -278,9 +277,9 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): r2_pol = derham.curl.dot(e1_pol) r3_pol = derham.div.dot(b2_pol) - assert np.allclose(r1_pol.toarray(True), space.G.dot(f0_pol_leg)) - assert np.allclose(r2_pol.toarray(True), space.C.dot(e1_pol_leg)) - assert np.allclose(r3_pol.toarray(True), space.D.dot(b2_pol_leg)) + assert xp.allclose(r1_pol.toarray(True), space.G.dot(f0_pol_leg)) + assert xp.allclose(r2_pol.toarray(True), space.C.dot(e1_pol_leg)) + assert xp.allclose(r3_pol.toarray(True), space.D.dot(b2_pol_leg)) # test transposed derivatives GT = derham.grad.transpose() @@ -291,9 +290,9 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): r1_pol = CT.dot(b2_pol) r2_pol = DT.dot(p3_pol) - assert np.allclose(r0_pol.toarray(True), space.G.T.dot(e1_pol_leg)) - assert np.allclose(r1_pol.toarray(True), space.C.T.dot(b2_pol_leg)) - assert np.allclose(r2_pol.toarray(True), space.D.T.dot(p3_pol_leg)) + assert xp.allclose(r0_pol.toarray(True), space.G.T.dot(e1_pol_leg)) + assert xp.allclose(r1_pol.toarray(True), space.C.T.dot(b2_pol_leg)) + assert xp.allclose(r2_pol.toarray(True), space.D.T.dot(p3_pol_leg)) if rank == 0: print("------------- Test passed ---------------------------") @@ -303,8 +302,8 @@ def test_extraction_ops_and_derivatives(Nel, p, spl_kind): @pytest.mark.parametrize("p", [[4, 3, 2]]) @pytest.mark.parametrize("spl_kind", [[False, True, True], [False, True, False]]) def test_projectors(Nel, p, spl_kind): - import numpy as np - from mpi4py import MPI + import cunumpy as xp + from psydac.ddm.mpi import mpi as MPI from struphy.eigenvalue_solvers.spline_space import Spline_space_1d, Tensor_spline_space from struphy.feec.psydac_derham import Derham @@ -339,7 +338,7 @@ def test_projectors(Nel, p, spl_kind): # function to project on physical domain def fun_scalar(x, y, z): - return np.sin(2 * np.pi * (x)) * np.cos(2 * np.pi * y) * np.sin(2 * np.pi * z) + return xp.sin(2 * xp.pi * (x)) * xp.cos(2 * xp.pi * y) * xp.sin(2 * xp.pi * z) fun_vector = [fun_scalar, fun_scalar, fun_scalar] @@ -370,7 +369,7 @@ def fun3(e1, e2, e3): r0_pol_leg = space.projectors.pi_0(fun0) - assert np.allclose(r0_pol.toarray(True), r0_pol_leg) + assert xp.allclose(r0_pol.toarray(True), r0_pol_leg) if rank == 0: print("Test passed for PI_0 polar projector") @@ -386,7 +385,7 @@ def fun3(e1, e2, e3): r1_pol_leg = space.projectors.pi_1(fun1, with_subs=False) - assert np.allclose(r1_pol.toarray(True), r1_pol_leg) + assert xp.allclose(r1_pol.toarray(True), r1_pol_leg) if rank == 0: print("Test passed for PI_1 polar projector") @@ -402,7 +401,7 @@ def fun3(e1, e2, e3): r2_pol_leg = space.projectors.pi_2(fun2, with_subs=False) - assert np.allclose(r2_pol.toarray(True), r2_pol_leg) + assert xp.allclose(r2_pol.toarray(True), r2_pol_leg) if rank == 0: print("Test passed for PI_2 polar projector") @@ -418,7 +417,7 @@ def fun3(e1, e2, e3): r3_pol_leg = space.projectors.pi_3(fun3, with_subs=False) - assert np.allclose(r3_pol.toarray(True), r3_pol_leg) + assert xp.allclose(r3_pol.toarray(True), r3_pol_leg) if rank == 0: print("Test passed for PI_3 polar projector") diff --git a/src/struphy/post_processing/likwid/plot_likwidproject.py b/src/struphy/post_processing/likwid/plot_likwidproject.py index 4dfae20e7..f4c3bb442 100644 --- a/src/struphy/post_processing/likwid/plot_likwidproject.py +++ b/src/struphy/post_processing/likwid/plot_likwidproject.py @@ -7,8 +7,8 @@ import re import sys +import cunumpy as xp import matplotlib.pyplot as plt -import numpy as np import pandas as pd import plotly.express as px import plotly.graph_objects as go @@ -196,16 +196,16 @@ def plot_roofline( fig.update_xaxes( type="log", # Ensure the x-axis is logarithmic - range=[np.log10(xmin), np.log10(xmax)], + range=[xp.log10(xmin), xp.log10(xmax)], title="Operational intensity (FLOP/Byte)", tickvals=xtick_values, # Set where ticks appear ticktext=[str(t) for t in xtick_values], - # ticktext=[f'$10^{{{int(np.log10(t))}}}$' for t in xtick_values] # Set tick labels + # ticktext=[f'$10^{{{int(xp.log10(t))}}}$' for t in xtick_values] # Set tick labels ) fig.update_yaxes( type="log", # Ensure the x-axis is logarithmic - range=[np.log10(ymin), np.log10(ymax)], + range=[xp.log10(ymin), xp.log10(ymax)], title="Performance [GFLOP/s]", tickvals=ytick_values, # Set where ticks appear ticktext=[str(t) for t in ytick_values], @@ -387,7 +387,7 @@ def plot_speedup( fig.update_layout( # xaxis_title='Job name', - xaxis_title=f"MPI tasks (#)", + xaxis_title="MPI tasks (#)", yaxis_title=re.sub(r"\[.*?\]", "[relative]", metric2), showlegend=True, xaxis_tickformat=".1f", @@ -818,7 +818,7 @@ def load_projects(data_paths, procs_per_clone="any"): ) if (procs_per_clone != "any") and (procs_per_clone != project.procs_per_clone): print( - f"Incorrect number of procs_per_clone: {project.procs_per_clone = } {procs_per_clone = }", + f"Incorrect number of procs_per_clone: {project.procs_per_clone =} {procs_per_clone =}", ) continue project.read_project() diff --git a/src/struphy/post_processing/likwid/plot_time_traces.py b/src/struphy/post_processing/likwid/plot_time_traces.py index 818d96935..7451833cb 100644 --- a/src/struphy/post_processing/likwid/plot_time_traces.py +++ b/src/struphy/post_processing/likwid/plot_time_traces.py @@ -2,8 +2,9 @@ import pickle import re +import cunumpy as xp import matplotlib.pyplot as plt -import numpy as np +import plotly.graph_objects as go import plotly.io as pio # pio.kaleido.scope.mathjax = None @@ -16,19 +17,31 @@ def glob_to_regex(pat: str) -> str: return "^" + esc.replace(r"\*", ".*").replace(r"\?", ".") + "$" +# def plot_region(region_name, groups_include=["*"], groups_skip=[]): +# # skips first +# for pat in groups_skip: +# rx = glob_to_regex(pat) +# if re.fullmatch(rx, region_name): +# return False + +# # includes next +# for pat in groups_include: +# rx = glob_to_regex(pat) +# if re.fullmatch(rx, region_name): +# return True + +# return False + + def plot_region(region_name, groups_include=["*"], groups_skip=[]): - # skips first - for pat in groups_skip: - rx = glob_to_regex(pat) - if re.fullmatch(rx, region_name): - return False + from fnmatch import fnmatch - # includes next - for pat in groups_include: - rx = glob_to_regex(pat) - if re.fullmatch(rx, region_name): + for pattern in groups_skip: + if fnmatch(region_name, pattern): + return False + for pattern in groups_include: + if fnmatch(region_name, pattern): return True - return False @@ -54,7 +67,7 @@ def plot_time_vs_duration( plt.figure(figsize=(10, 6)) for path in paths: - print(f"{path = }") + print(f"{path =}") with open(path, "rb") as file: profiling_data = pickle.load(file) @@ -121,9 +134,9 @@ def plot_avg_duration_bar_chart( # Compute statistics per region regions = sorted(region_durations.keys()) - avg_durations = [np.mean(region_durations[r]) for r in regions] - min_durations = [np.min(region_durations[r]) for r in regions] - max_durations = [np.max(region_durations[r]) for r in regions] + avg_durations = [xp.mean(region_durations[r]) for r in regions] + min_durations = [xp.min(region_durations[r]) for r in regions] + max_durations = [xp.max(region_durations[r]) for r in regions] yerr = [ [avg - min_ for avg, min_ in zip(avg_durations, min_durations)], [max_ - avg for avg, max_ in zip(avg_durations, max_durations)], @@ -131,7 +144,7 @@ def plot_avg_duration_bar_chart( # Plot bar chart with error bars (min-max spans) plt.figure(figsize=(12, 6)) - x = np.arange(len(regions)) + x = xp.arange(len(regions)) plt.bar(x, avg_durations, yerr=yerr, capsize=5, color="skyblue", edgecolor="k") plt.yscale("log") plt.xticks(x, regions, rotation=45, ha="right") @@ -146,21 +159,6 @@ def plot_avg_duration_bar_chart( print(f"Saved average duration bar chart to: {figure_path}") -import plotly.graph_objects as go - - -def plot_region(region_name, groups_include=["*"], groups_skip=[]): - from fnmatch import fnmatch - - for pattern in groups_skip: - if fnmatch(region_name, pattern): - return False - for pattern in groups_include: - if fnmatch(region_name, pattern): - return True - return False - - def plot_gantt_chart_plotly( path: str, output_path: str, @@ -175,7 +173,7 @@ def plot_gantt_chart_plotly( region_start_times = {} for rank_data in profiling_data["rank_data"].values(): for region_name, info in rank_data.items(): - first_start_time = np.min(info["start_times"]) + first_start_time = xp.min(info["start_times"]) if region_name not in region_start_times or first_start_time < region_start_times[region_name]: region_start_times[region_name] = first_start_time @@ -204,7 +202,7 @@ def plot_gantt_chart_plotly( Start=start_times[i], Finish=end_times[i], Duration=durations[i], - ) + ), ) if len(bars) == 0: @@ -226,7 +224,7 @@ def plot_gantt_chart_plotly( name=bar["Rank"], marker_color=rank_color_map[bar["Rank"]], hovertemplate=f"Rank: {bar['Rank']}
    Start: {bar['Start']:.3f}s
    Duration: {bar['Duration']:.3f}s", - ) + ), ) fig.update_layout( @@ -291,7 +289,7 @@ def plot_gantt_chart( region_start_times = {} for rank_data in profiling_data["rank_data"].values(): for region_name, info in rank_data.items(): - first_start_time = np.min(info["start_times"]) + first_start_time = xp.min(info["start_times"]) if region_name not in region_start_times or first_start_time < region_start_times[region_name]: region_start_times[region_name] = first_start_time diff --git a/src/struphy/post_processing/likwid/roofline_plotter.py b/src/struphy/post_processing/likwid/roofline_plotter.py index 1621e5d8d..3a4808bdc 100644 --- a/src/struphy/post_processing/likwid/roofline_plotter.py +++ b/src/struphy/post_processing/likwid/roofline_plotter.py @@ -1,7 +1,7 @@ import glob import pickle -import numpy as np +import cunumpy as xp import pandas as pd import yaml @@ -142,14 +142,14 @@ def add_plot_diagonal( bandwidth_GBps, label="", ymax=1e4, - operational_intensity_FLOPpMB=np.arange(0, 1000, 1), + operational_intensity_FLOPpMB=xp.arange(0, 1000, 1), ): max_performance_GFLOP = operational_intensity_FLOPpMB * bandwidth_GBps (line,) = mfig.axs.plot(operational_intensity_FLOPpMB, max_performance_GFLOP) # Specify the y-value where you want to place the text specific_y = ymax # Interpolate to find the corresponding x-value - specific_x = np.interp( + specific_x = xp.interp( specific_y, max_performance_GFLOP, operational_intensity_FLOPpMB, @@ -209,10 +209,10 @@ def get_average_val( xvec.append(x) yvec.append(y) # print('xvec', xvec, 'yvec', yvec) - xvec = np.array(xvec) - yvec = np.array(yvec) + xvec = xp.array(xvec) + yvec = xp.array(yvec) # print('xvec', xvec, 'yvec', yvec) - return np.average(xvec), np.average(yvec), np.std(xvec), np.std(yvec) + return xp.average(xvec), xp.average(yvec), xp.std(xvec), xp.std(yvec) def get_maximum(path, df_index=-1, metric="DP [MFLOP/s] STAT", column_name="Sum"): diff --git a/src/struphy/post_processing/orbits/orbits_tools.py b/src/struphy/post_processing/orbits/orbits_tools.py index 357e13d4f..97eee89af 100644 --- a/src/struphy/post_processing/orbits/orbits_tools.py +++ b/src/struphy/post_processing/orbits/orbits_tools.py @@ -1,8 +1,8 @@ import os import shutil +import cunumpy as xp import h5py -import numpy as np import yaml from tqdm import tqdm @@ -61,7 +61,7 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): if file.endswith(".npy") ] pproc_nt = len(npy_files_list) - n_markers = np.load(os.path.join(path_orbits, npy_files_list[0])).shape[0] + n_markers = xp.load(os.path.join(path_orbits, npy_files_list[0])).shape[0] # re-ordering npy_files npy_files_list = sorted(npy_files_list) @@ -76,10 +76,10 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): os.mkdir(path_gc) # temporary marker array - temp = np.empty((n_markers, 7), dtype=float) - etas = np.empty((n_markers, 3), dtype=float) - B_cart = np.empty((n_markers, 3), dtype=float) - lost_particles_mask = np.empty(n_markers, dtype=bool) + temp = xp.empty((n_markers, 7), dtype=float) + etas = xp.empty((n_markers, 3), dtype=float) + B_cart = xp.empty((n_markers, 3), dtype=float) + lost_particles_mask = xp.empty(n_markers, dtype=bool) print("Evaluation of guiding center for " + str(species)) @@ -94,13 +94,13 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): file_txt = os.path.join(path_gc, npy_files_list[n][:-4] + ".txt") # call .npy file - temp[:, :] = np.load(os.path.join(path_orbits, npy_files_list[n])) + temp[:, :] = xp.load(os.path.join(path_orbits, npy_files_list[n])) # move ids to last column and save - temp = np.roll(temp, -1, axis=1) + temp = xp.roll(temp, -1, axis=1) # sorting out lost particles - lost_particles_mask = np.all(temp[:, :-1] == 0, axis=1) + lost_particles_mask = xp.all(temp[:, :-1] == 0, axis=1) # domain inverse map etas[~lost_particles_mask, :] = domain.inverse_map( @@ -110,7 +110,7 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): # eval cartesian magnetic filed at marker positions B_cart[~lost_particles_mask, :] = equil.b_cart( - *np.concatenate( + *xp.concatenate( ( etas[:, 0][:, None], etas[:, 1][:, None], @@ -123,10 +123,10 @@ def post_process_orbit_guiding_center(path_in, path_kinetics_species, species): calculate_guiding_center_from_6d(temp, B_cart) # move ids to first column and save - temp = np.roll(temp, 1, axis=1) + temp = xp.roll(temp, 1, axis=1) - np.save(file_npy, temp) - np.savetxt(file_txt, temp[:, :4], fmt="%12.6f", delimiter=", ") + xp.save(file_npy, temp) + xp.savetxt(file_txt, temp[:, :4], fmt="%12.6f", delimiter=", ") def post_process_orbit_classification(path_kinetics_species, species): @@ -168,16 +168,16 @@ def post_process_orbit_classification(path_kinetics_species, species): if file.endswith(".npy") ] pproc_nt = len(npy_files_list) - n_markers = np.load(os.path.join(path_gc, npy_files_list[0])).shape[0] + n_markers = xp.load(os.path.join(path_gc, npy_files_list[0])).shape[0] # re-ordering npy_files npy_files_list = sorted(npy_files_list) # temporary marker array - temp = np.empty((n_markers, 8), dtype=float) - v_parallel = np.empty(n_markers, dtype=float) - trapped_particle_mask = np.empty(n_markers, dtype=bool) - lost_particle_mask = np.empty(n_markers, dtype=bool) + temp = xp.empty((n_markers, 8), dtype=float) + v_parallel = xp.empty(n_markers, dtype=float) + trapped_particle_mask = xp.empty(n_markers, dtype=bool) + lost_particle_mask = xp.empty(n_markers, dtype=bool) print("Classifying guiding center orbits for " + str(species)) @@ -188,16 +188,16 @@ def post_process_orbit_classification(path_kinetics_species, species): # load .npy files file_npy = os.path.join(path_gc, npy_files_list[n]) - temp[:, :-1] = np.load(file_npy) + temp[:, :-1] = xp.load(file_npy) # initial time step if n == 0: v_init = temp[:, 4] - np.save(file_npy, temp) + xp.save(file_npy, temp) continue # synchronizing with former time step - temp[:, -1] = np.load( + temp[:, -1] = xp.load( os.path.join( path_gc, npy_files_list[n - 1], @@ -205,10 +205,10 @@ def post_process_orbit_classification(path_kinetics_species, species): )[:, -1] # call parallel velocity data from .npy file - v_parallel = np.load(os.path.join(path_gc, npy_files_list[n]))[:, 4] + v_parallel = xp.load(os.path.join(path_gc, npy_files_list[n]))[:, 4] # sorting out lost particles - lost_particle_mask = np.all(temp[:, 1:-1] == 0, axis=1) + lost_particle_mask = xp.all(temp[:, 1:-1] == 0, axis=1) # check reverse of parallel velocity trapped_particle_mask[:] = False @@ -221,4 +221,4 @@ def post_process_orbit_classification(path_kinetics_species, species): # assign "-1" at the last index of lost particles temp[lost_particle_mask, -1] = -1 - np.save(file_npy, temp) + xp.save(file_npy, temp) diff --git a/src/struphy/post_processing/post_processing_tools.py b/src/struphy/post_processing/post_processing_tools.py index 0c7893acb..e0759bb63 100644 --- a/src/struphy/post_processing/post_processing_tools.py +++ b/src/struphy/post_processing/post_processing_tools.py @@ -2,8 +2,8 @@ import pickle import shutil +import cunumpy as xp import h5py -import numpy as np import yaml from tqdm import tqdm @@ -138,7 +138,7 @@ def create_femfields( fields : dict Nested dictionary holding :class:`~struphy.feec.psydac_derham.SplineFunction`: fields[t][name] contains the Field with the name "name" in the hdf5 file at time t. - t_grid : np.ndarray + t_grid : xp.ndarray Time grid. """ @@ -156,7 +156,7 @@ def create_femfields( # get fields names, space IDs and time grid from 0-th rank hdf5 file file = h5py.File(os.path.join(path, "data/", "data_proc0.hdf5"), "r") space_ids = {} - print(f"\nReading hdf5 data of following species:") + print("\nReading hdf5 data of following species:") for species, dset in file["feec"].items(): space_ids[species] = {} print(f"{species}:") @@ -276,7 +276,7 @@ def eval_femfields( Returns ------- point_data : dict - Nested dictionary holding values of FemFields on the grid as list of 3d np.arrays: + Nested dictionary holding values of FemFields on the grid as list of 3d xp.arrays: point_data[name][t] contains the values of the field with name "name" in fields[t].keys() at time t. If physical is True, physical components of fields are saved. @@ -299,7 +299,7 @@ def eval_femfields( Nel = params_in.grid.Nel - grids_log = [np.linspace(0.0, 1.0, Nel_i * n_i + 1) for Nel_i, n_i in zip(Nel, celldivide)] + grids_log = [xp.linspace(0.0, 1.0, Nel_i * n_i + 1) for Nel_i, n_i in zip(Nel, celldivide)] grids_phy = [ domain(*grids_log)[0], domain(*grids_log)[1], @@ -326,7 +326,7 @@ def eval_femfields( point_data[species][name][t] = [] # scalar spaces - if isinstance(temp_val, np.ndarray): + if isinstance(temp_val, xp.ndarray): if physical: # push-forward if space_id == "H1": @@ -387,7 +387,7 @@ def eval_femfields( def create_vtk( path: str, - t_grid: np.ndarray, + t_grid: xp.ndarray, grids_phy: list, point_data: dict, *, @@ -400,7 +400,7 @@ def create_vtk( path : str Absolute path of where to store the .vts files. Will then be in path/vtk/step_.vts. - t_grid : np.ndarray + t_grid : xp.ndarray Time grid. grids_phy : 3-list @@ -425,7 +425,7 @@ def create_vtk( # time loop nt = len(t_grid) - 1 - log_nt = int(np.log10(nt)) + 1 + log_nt = int(xp.log10(nt)) + 1 print(f"\nCreating vtk in {path} ...") for n, t in enumerate(tqdm(t_grid)): @@ -542,7 +542,7 @@ def post_process_markers( # get number of time steps and markers nt, n_markers, n_cols = files[0]["kinetic/" + species + "/markers"].shape - log_nt = int(np.log10(int(((nt - 1) / step)))) + 1 + log_nt = int(xp.log10(int(((nt - 1) / step)))) + 1 # directory for .txt files and marker index which will be saved path_orbits = os.path.join(path_out, "orbits") @@ -561,8 +561,8 @@ def post_process_markers( os.mkdir(path_orbits) # temporary array - temp = np.empty((n_markers, len(save_index)), order="C") - lost_particles_mask = np.empty(n_markers, dtype=bool) + temp = xp.empty((n_markers, len(save_index)), order="C") + lost_particles_mask = xp.empty(n_markers, dtype=bool) print(f"Evaluation of {n_markers} marker orbits for {species}") @@ -589,28 +589,28 @@ def post_process_markers( # sorting out lost particles ids = temp[:, -1].astype("int") - ids_lost_particles = np.setdiff1d(np.arange(n_markers), ids) - ids_removed_particles = np.nonzero(temp[:, 0] == -1.0)[0] - ids_lost_particles = np.array(list(set(ids_lost_particles) | set(ids_removed_particles)), dtype=int) + ids_lost_particles = xp.setdiff1d(xp.arange(n_markers), ids) + ids_removed_particles = xp.nonzero(temp[:, 0] == -1.0)[0] + ids_lost_particles = xp.array(list(set(ids_lost_particles) | set(ids_removed_particles)), dtype=int) lost_particles_mask[:] = False lost_particles_mask[ids_lost_particles] = True if len(ids_lost_particles) > 0: # lost markers are saved as [0, ..., 0, ids] temp[lost_particles_mask, -1] = ids_lost_particles - ids = np.unique(np.append(ids, ids_lost_particles)) + ids = xp.unique(xp.append(ids, ids_lost_particles)) - assert np.all(sorted(ids) == np.arange(n_markers)) + assert xp.all(sorted(ids) == xp.arange(n_markers)) # compute physical positions (x, y, z) - pos_phys = domain(np.array(temp[~lost_particles_mask, :3]), change_out_order=True) + pos_phys = domain(xp.array(temp[~lost_particles_mask, :3]), change_out_order=True) temp[~lost_particles_mask, :3] = pos_phys # save numpy - np.save(file_npy, temp) + xp.save(file_npy, temp) # move ids to first column and save txt - temp = np.roll(temp, 1, axis=1) - np.savetxt(file_txt, temp[:, (0, 1, 2, 3, -1)], fmt="%12.6f", delimiter=", ") + temp = xp.roll(temp, 1, axis=1) + xp.savetxt(file_txt, temp[:, (0, 1, 2, 3, -1)], fmt="%12.6f", delimiter=", ") # close hdf5 files for file in files: @@ -692,7 +692,7 @@ def post_process_f( path_slice, "grid_" + slice_names[n_gr] + ".npy", ) - np.save(grid_path, grid[:]) + xp.save(grid_path, grid[:]) # compute distribution function for slice_name in tqdm(files[0]["kinetic/" + species + "/f"]): @@ -713,8 +713,8 @@ def post_process_f( data_df += files[rank]["kinetic/" + species + "/df/" + slice_name][::step] # save distribution functions - np.save(os.path.join(path_slice, "f_binned.npy"), data) - np.save(os.path.join(path_slice, "delta_f_binned.npy"), data_df) + xp.save(os.path.join(path_slice, "f_binned.npy"), data) + xp.save(os.path.join(path_slice, "delta_f_binned.npy"), data_df) if compute_bckgr: # bckgr_params = params["kinetic"][species]["background"] @@ -753,11 +753,11 @@ def post_process_f( # check if file exists and is in slice_name if os.path.exists(filename) and current_slice in slice_names: - grid_tot += [np.load(filename)] + grid_tot += [xp.load(filename)] # otherwise evaluate at zero else: - grid_tot += [np.zeros(1)] + grid_tot += [xp.zeros(1)] # v-grid for comp in range(1, f_bckgr.vdim + 1): @@ -769,15 +769,15 @@ def post_process_f( # check if file exists and is in slice_name if os.path.exists(filename) and current_slice in slice_names: - grid_tot += [np.load(filename)] + grid_tot += [xp.load(filename)] # otherwise evaluate at zero else: - grid_tot += [np.zeros(1)] + grid_tot += [xp.zeros(1)] # correct integrating out in v-direction, TODO: check for 5D Maxwellians - factor *= np.sqrt(2 * np.pi) + factor *= xp.sqrt(2 * xp.pi) - grid_eval = np.meshgrid(*grid_tot, indexing="ij") + grid_eval = xp.meshgrid(*grid_tot, indexing="ij") data_bckgr = f_bckgr(*grid_eval).squeeze() @@ -788,9 +788,9 @@ def post_process_f( data_delta_f = data_df # save distribution function - np.save(os.path.join(path_slice, "delta_f_binned.npy"), data_delta_f) + xp.save(os.path.join(path_slice, "delta_f_binned.npy"), data_delta_f) # add extra axis for data_bckgr since data_delta_f has axis for time series - np.save( + xp.save( os.path.join(path_slice, "f_binned.npy"), data_delta_f + data_bckgr[tuple([None])], ) @@ -866,7 +866,7 @@ def post_process_n_sph( eta2 = files[0]["kinetic/" + species + "/n_sph/" + view].attrs["eta2"] eta3 = files[0]["kinetic/" + species + "/n_sph/" + view].attrs["eta3"] - ee1, ee2, ee3 = np.meshgrid( + ee1, ee2, ee3 = xp.meshgrid( eta1, eta2, eta3, @@ -877,7 +877,7 @@ def post_process_n_sph( path_view, "grid_n_sph.npy", ) - np.save(grid_path, (ee1, ee2, ee3)) + xp.save(grid_path, (ee1, ee2, ee3)) # load n_sph data data = files[0]["kinetic/" + species + "/n_sph/" + view][::step].copy() @@ -885,4 +885,4 @@ def post_process_n_sph( data += files[rank]["kinetic/" + species + "/n_sph/" + view][::step] # save distribution functions - np.save(os.path.join(path_view, "n_sph.npy"), data) + xp.save(os.path.join(path_view, "n_sph.npy"), data) diff --git a/src/struphy/post_processing/pproc_struphy.py b/src/struphy/post_processing/pproc_struphy.py index 8d26c8c8d..940c94ab3 100644 --- a/src/struphy/post_processing/pproc_struphy.py +++ b/src/struphy/post_processing/pproc_struphy.py @@ -2,8 +2,8 @@ import pickle import shutil +import cunumpy as xp import h5py -import numpy as np import yaml import struphy.post_processing.orbits.orbits_tools as orbits_pproc @@ -64,7 +64,7 @@ def main( file = h5py.File(os.path.join(path, "data/", "data_proc0.hdf5"), "r") # save time grid at which post-processing data is created - np.save(os.path.join(path_pproc, "t_grid.npy"), file["time/value"][::step].copy()) + xp.save(os.path.join(path_pproc, "t_grid.npy"), file["time/value"][::step].copy()) if "feec" in file.keys(): exist_fields = True @@ -96,12 +96,17 @@ def main( fields, t_grid = pproc.create_femfields(path, params_in, step=step) point_data, grids_log, grids_phy = pproc.eval_femfields( - params_in, fields, celldivide=[celldivide, celldivide, celldivide] + params_in, + fields, + celldivide=[celldivide, celldivide, celldivide], ) if physical: point_data_phy, grids_log, grids_phy = pproc.eval_femfields( - params_in, fields, celldivide=[celldivide, celldivide, celldivide], physical=True + params_in, + fields, + celldivide=[celldivide, celldivide, celldivide], + physical=True, ) # directory for field data @@ -196,14 +201,19 @@ def main( libpath = struphy.__path__[0] parser = argparse.ArgumentParser( - description="Post-process data of finished Struphy runs to prepare for diagnostics." + description="Post-process data of finished Struphy runs to prepare for diagnostics.", ) # paths of simulation folders parser.add_argument("dir", type=str, metavar="DIR", help="absolute path of simulation ouput folder to post-process") parser.add_argument( - "-s", "--step", type=int, metavar="N", help="do post-processing every N-th time step (default=1)", default=1 + "-s", + "--step", + type=int, + metavar="N", + help="do post-processing every N-th time step (default=1)", + default=1, ) parser.add_argument( @@ -221,11 +231,15 @@ def main( ) parser.add_argument( - "--guiding-center", help="compute guiding-center coordinates (only from Particles6D)", action="store_true" + "--guiding-center", + help="compute guiding-center coordinates (only from Particles6D)", + action="store_true", ) parser.add_argument( - "--classify", help="classify guiding-center trajectories (passing, trapped or lost)", action="store_true" + "--classify", + help="classify guiding-center trajectories (passing, trapped or lost)", + action="store_true", ) parser.add_argument("--no-vtk", help="whether vtk files creation should be skipped", action="store_true") diff --git a/src/struphy/post_processing/profile_struphy.py b/src/struphy/post_processing/profile_struphy.py index 0c0d77e73..43d4be47d 100644 --- a/src/struphy/post_processing/profile_struphy.py +++ b/src/struphy/post_processing/profile_struphy.py @@ -1,7 +1,7 @@ import pickle import sys -import numpy as np +import cunumpy as xp import yaml from matplotlib import pyplot as plt @@ -93,7 +93,7 @@ def main(): + "ncalls".ljust(15) + "totime".ljust(15) + "percall".ljust(15) - + "cumtime".ljust(15) + + "cumtime".ljust(15), ) print("-" * 154) for position, key in enumerate(dicts[0].keys()): @@ -150,17 +150,17 @@ def main(): plt.ylabel("time [s]") plt.title("Strong scaling for Nel=" + str(val["Nel"][0]) + " cells") plt.legend(loc="lower left") - plt.loglog(val["mpi_size"], val["time"][0] / 2 ** np.arange(len(val["time"])), "k--", alpha=0.3) + plt.loglog(val["mpi_size"], val["time"][0] / 2 ** xp.arange(len(val["time"])), "k--", alpha=0.3) # weak scaling plot else: plt.plot(val["mpi_size"], val["time"], label=key) plt.xlabel("mpi_size") plt.ylabel("time [s]") plt.title( - "Weak scaling for cells/mpi_size=" + str(np.prod(val["Nel"][0]) / val["mpi_size"][0]) + "=const." + "Weak scaling for cells/mpi_size=" + str(xp.prod(val["Nel"][0]) / val["mpi_size"][0]) + "=const.", ) plt.legend(loc="upper left") - # plt.loglog(val['mpi_size'], val['time'][0]*np.ones_like(val['time']), 'k--', alpha=0.3) + # plt.loglog(val['mpi_size'], val['time'][0]*xp.ones_like(val['time']), 'k--', alpha=0.3) plt.xscale("log") plt.show() diff --git a/src/struphy/profiling/profiling.py b/src/struphy/profiling/profiling.py index 160002c15..e96749614 100644 --- a/src/struphy/profiling/profiling.py +++ b/src/struphy/profiling/profiling.py @@ -17,8 +17,8 @@ # Import the profiling configuration class and context manager from functools import lru_cache -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import mpi as MPI @lru_cache(maxsize=None) # Cache the import result to avoid repeated imports @@ -170,9 +170,9 @@ def save_to_pickle(cls, file_path): for name, region in cls._regions.items(): local_data[name] = { "ncalls": region.ncalls, - "durations": np.array(region.durations, dtype=np.float64), - "start_times": np.array(region.start_times, dtype=np.float64), - "end_times": np.array(region.end_times, dtype=np.float64), + "durations": xp.array(region.durations, dtype=xp.float64), + "start_times": xp.array(region.start_times, dtype=xp.float64), + "end_times": xp.array(region.end_times, dtype=xp.float64), "config": { "likwid": region.config.likwid, "simulation_label": region.config.simulation_label, @@ -246,7 +246,7 @@ def print_summary(cls): average_duration = total_duration / region.ncalls min_duration = min(region.durations) max_duration = max(region.durations) - std_duration = np.std(region.durations) + std_duration = xp.std(region.durations) else: total_duration = average_duration = min_duration = max_duration = std_duration = 0 @@ -270,16 +270,16 @@ def __init__(self, region_name, time_trace=False): self._region_name = self.config.simulation_label + region_name self._time_trace = time_trace self._ncalls = 0 - self._start_times = np.empty(1, dtype=float) - self._end_times = np.empty(1, dtype=float) - self._durations = np.empty(1, dtype=float) + self._start_times = xp.empty(1, dtype=float) + self._end_times = xp.empty(1, dtype=float) + self._durations = xp.empty(1, dtype=float) self._started = False def __enter__(self): if self._ncalls == len(self._start_times): - self._start_times = np.append(self._start_times, np.zeros_like(self._start_times)) - self._end_times = np.append(self._end_times, np.zeros_like(self._end_times)) - self._durations = np.append(self._durations, np.zeros_like(self._durations)) + self._start_times = xp.append(self._start_times, xp.zeros_like(self._start_times)) + self._end_times = xp.append(self._end_times, xp.zeros_like(self._end_times)) + self._durations = xp.append(self._durations, xp.zeros_like(self._durations)) if self.config.likwid: self._pylikwid().markerstartregion(self.region_name) diff --git a/src/struphy/propagators/__init__.py b/src/struphy/propagators/__init__.py index 2dbef2b10..72067e021 100644 --- a/src/struphy/propagators/__init__.py +++ b/src/struphy/propagators/__init__.py @@ -1,97 +1,98 @@ -from struphy.propagators.propagators_coupling import ( - CurrentCoupling5DCurlb, - CurrentCoupling5DGradB, - CurrentCoupling6DCurrent, - EfieldWeights, - PressureCoupling6D, - VlasovAmpere, -) -from struphy.propagators.propagators_fields import ( - AdiabaticPhi, - CurrentCoupling5DDensity, - CurrentCoupling6DDensity, - FaradayExtended, - Hall, - HasegawaWakatani, - ImplicitDiffusion, - JxBCold, - Magnetosonic, - MagnetosonicCurrentCoupling5D, - MagnetosonicUniform, - Maxwell, - OhmCold, - Poisson, - ShearAlfven, - ShearAlfvenB1, - ShearAlfvenCurrentCoupling5D, - TimeDependentSource, - TwoFluidQuasiNeutralFull, - VariationalDensityEvolve, - VariationalEntropyEvolve, - VariationalMagFieldEvolve, - VariationalMomentumAdvection, - VariationalPBEvolve, - VariationalQBEvolve, - VariationalResistivity, - VariationalViscosity, -) -from struphy.propagators.propagators_markers import ( - PushDeterministicDiffusion, - PushEta, - PushEtaPC, - PushGuidingCenterBxEstar, - PushGuidingCenterParallel, - PushRandomDiffusion, - PushVinEfield, - PushVinSPHpressure, - PushVinViscousPotential, - PushVxB, - StepStaticEfield, -) +# from struphy.propagators.propagators_coupling import ( +# CurrentCoupling5DCurlb, +# CurrentCoupling5DGradB, +# CurrentCoupling6DCurrent, +# EfieldWeights, +# PressureCoupling6D, +# VlasovAmpere, +# ) +# from struphy.propagators.propagators_fields import ( +# AdiabaticPhi, +# CurrentCoupling5DDensity, +# CurrentCoupling6DDensity, +# FaradayExtended, +# Hall, +# HasegawaWakatani, +# ImplicitDiffusion, +# JxBCold, +# Magnetosonic, +# MagnetosonicCurrentCoupling5D, +# MagnetosonicUniform, +# Maxwell, +# OhmCold, +# Poisson, +# ShearAlfven, +# ShearAlfvenB1, +# ShearAlfvenCurrentCoupling5D, +# TimeDependentSource, +# TwoFluidQuasiNeutralFull, +# VariationalDensityEvolve, +# VariationalEntropyEvolve, +# VariationalMagFieldEvolve, +# VariationalMomentumAdvection, +# VariationalPBEvolve, +# VariationalQBEvolve, +# VariationalResistivity, +# VariationalViscosity, +# ) +# from struphy.propagators.propagators_markers import ( +# PushDeterministicDiffusion, +# PushEta, +# PushEtaPC, +# PushGuidingCenterBxEstar, +# PushGuidingCenterParallel, +# PushRandomDiffusion, +# PushVinEfield, +# PushVinSPHpressure, +# PushVinViscousPotential2D, +# PushVinViscousPotential3D, +# PushVxB, +# StepStaticEfield, +# ) -__all__ = [ - "VlasovAmpere", - "EfieldWeights", - "PressureCoupling6D", - "CurrentCoupling6DCurrent", - "CurrentCoupling5DCurlb", - "CurrentCoupling5DGradB", - "Maxwell", - "OhmCold", - "JxBCold", - "ShearAlfven", - "ShearAlfvenB1", - "Hall", - "Magnetosonic", - "MagnetosonicUniform", - "FaradayExtended", - "CurrentCoupling6DDensity", - "ShearAlfvenCurrentCoupling5D", - "MagnetosonicCurrentCoupling5D", - "CurrentCoupling5DDensity", - "ImplicitDiffusion", - "Poisson", - "VariationalMomentumAdvection", - "VariationalDensityEvolve", - "VariationalEntropyEvolve", - "VariationalMagFieldEvolve", - "VariationalPBEvolve", - "VariationalQBEvolve", - "VariationalViscosity", - "VariationalResistivity", - "TimeDependentSource", - "AdiabaticPhi", - "HasegawaWakatani", - "TwoFluidQuasiNeutralFull", - "PushEta", - "PushVxB", - "PushVinEfield", - "PushEtaPC", - "PushGuidingCenterBxEstar", - "PushGuidingCenterParallel", - "StepStaticEfield", - "PushDeterministicDiffusion", - "PushRandomDiffusion", - "PushVinSPHpressure", - "PushVinViscousPotential", -] +# __all__ = [ +# "VlasovAmpere", +# "EfieldWeights", +# "PressureCoupling6D", +# "CurrentCoupling6DCurrent", +# "CurrentCoupling5DCurlb", +# "CurrentCoupling5DGradB", +# "Maxwell", +# "OhmCold", +# "JxBCold", +# "ShearAlfven", +# "ShearAlfvenB1", +# "Hall", +# "Magnetosonic", +# "MagnetosonicUniform", +# "FaradayExtended", +# "CurrentCoupling6DDensity", +# "ShearAlfvenCurrentCoupling5D", +# "CurrentCoupling5DDensity", +# "ImplicitDiffusion", +# "Poisson", +# "VariationalMomentumAdvection", +# "VariationalDensityEvolve", +# "VariationalEntropyEvolve", +# "VariationalMagFieldEvolve", +# "VariationalPBEvolve", +# "VariationalQBEvolve", +# "VariationalViscosity", +# "VariationalResistivity", +# "TimeDependentSource", +# "AdiabaticPhi", +# "HasegawaWakatani", +# "TwoFluidQuasiNeutralFull", +# "PushEta", +# "PushVxB", +# "PushVinEfield", +# "PushEtaPC", +# "PushGuidingCenterBxEstar", +# "PushGuidingCenterParallel", +# "StepStaticEfield", +# "PushDeterministicDiffusion", +# "PushRandomDiffusion", +# "PushVinSPHpressure", +# "PushVinViscousPotential2D", +# "PushVinViscousPotential3D", +# ] diff --git a/src/struphy/propagators/base.py b/src/struphy/propagators/base.py index 7a8878eb5..945107bbd 100644 --- a/src/struphy/propagators/base.py +++ b/src/struphy/propagators/base.py @@ -4,8 +4,7 @@ from dataclasses import dataclass from typing import Literal -import numpy as np -from mpi4py import MPI +import cunumpy as xp from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -72,7 +71,7 @@ def options(self) -> Options: @abstractmethod def options(self, new): assert isinstance(new, self.Options) - if MPI.COMM_WORLD.Get_rank() == 0: + if True: print(f"\nNew options for propagator '{self.__class__.__name__}':") for k, v in new.__dict__.items(): print(f" {k}: {v}") @@ -112,7 +111,7 @@ def update_feec_variables(self, **new_coeffs): assert new.space == old.space # calculate maximum of difference abs(new - old) - diffs[var] = np.max(np.abs(new.toarray() - old.toarray())) + diffs[var] = xp.max(xp.abs(new.toarray() - old.toarray())) # copy new coeffs into old new.copy(out=old) @@ -247,9 +246,9 @@ def add_init_kernel( The arguments for the kernel function. """ if comps is None: - comps = np.array([0]) # case for scalar evaluation + comps = xp.array([0]) # case for scalar evaluation else: - comps = np.array(comps, dtype=int) + comps = xp.array(comps, dtype=int) if not hasattr(self, "_init_kernels"): self._init_kernels = [] @@ -260,7 +259,7 @@ def add_init_kernel( column_nr, comps, args_init, - ) + ), ] def add_eval_kernel( @@ -298,12 +297,12 @@ def add_eval_kernel( """ if isinstance(alpha, int) or isinstance(alpha, float): alpha = [alpha] * 6 - alpha = np.array(alpha) + alpha = xp.array(alpha) if comps is None: - comps = np.array([0]) # case for scalar evaluation + comps = xp.array([0]) # case for scalar evaluation else: - comps = np.array(comps, dtype=int) + comps = xp.array(comps, dtype=int) if not hasattr(self, "_eval_kernels"): self._eval_kernels = [] @@ -315,5 +314,5 @@ def add_eval_kernel( column_nr, comps, args_eval, - ) + ), ] diff --git a/src/struphy/propagators/propagators_coupling.py b/src/struphy/propagators/propagators_coupling.py index cd629ffe4..0b8760b6a 100644 --- a/src/struphy/propagators/propagators_coupling.py +++ b/src/struphy/propagators/propagators_coupling.py @@ -3,10 +3,11 @@ from dataclasses import dataclass from typing import Literal -import numpy as np +import cunumpy as xp from line_profiler import profile -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.block import BlockVector +from psydac.linalg.solvers import inverse from psydac.linalg.stencil import StencilVector from struphy.feec import preconditioner @@ -16,15 +17,19 @@ from struphy.kinetic_background.base import Maxwellian from struphy.kinetic_background.maxwellians import Maxwellian3D from struphy.linear_algebra.schur_solver import SchurSolver -from struphy.linear_algebra.solver import SolverParameters +from struphy.linear_algebra.solver import DiscreteGradientSolverParameters, SolverParameters from struphy.models.variables import FEECVariable, PICVariable +from struphy.ode.utils import ButcherTableau +from struphy.pic import utilities_kernels from struphy.pic.accumulation import accum_kernels, accum_kernels_gc -from struphy.pic.accumulation.particles_to_grid import Accumulator +from struphy.pic.accumulation.filter import FilterParameters +from struphy.pic.accumulation.particles_to_grid import Accumulator, AccumulatorVector from struphy.pic.particles import Particles5D, Particles6D from struphy.pic.pushing import pusher_kernels, pusher_kernels_gc from struphy.pic.pushing.pusher import Pusher from struphy.polar.basic import PolarVector from struphy.propagators.base import Propagator +from struphy.utils.pyccel import Pyccelkernel class VlasovAmpere(Propagator): @@ -141,7 +146,7 @@ def allocate(self): self._info = self.options.solver_params.info # get accumulation kernel - accum_kernel = accum_kernels.vlasov_maxwell + accum_kernel = Pyccelkernel(accum_kernels.vlasov_maxwell) # Initialize Accumulator object particles = self.variables.ions.particles @@ -196,7 +201,7 @@ def allocate(self): self._pusher = Pusher( particles, - pusher_kernels.push_v_with_efield, + Pyccelkernel(pusher_kernels.push_v_with_efield), args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, @@ -247,14 +252,14 @@ def __call__(self, dt): print("Maxdiff e1 for VlasovMaxwell:", max_de) particles = self.variables.ions.particles buffer_idx = particles.bufferindex - max_diff = np.max( - np.abs( - np.sqrt( + max_diff = xp.max( + xp.abs( + xp.sqrt( particles.markers_wo_holes[:, 3] ** 2 + particles.markers_wo_holes[:, 4] ** 2 + particles.markers_wo_holes[:, 5] ** 2, ) - - np.sqrt( + - xp.sqrt( particles.markers_wo_holes[:, buffer_idx + 3] ** 2 + particles.markers_wo_holes[:, buffer_idx + 4] ** 2 + particles.markers_wo_holes[:, buffer_idx + 5] ** 2, @@ -321,54 +326,89 @@ class EfieldWeights(Propagator): """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - if default: - dct = descend_options_dict(dct, []) - - return dct - - def __init__( - self, - e: BlockVector, - particles: Particles6D, - *, - alpha: float = 1.0, - kappa: float = 1.0, - f0: Maxwellian = None, - solver=options(default=True)["solver"], - ): - super().__init__(e, particles) - - if f0 is None: - f0 = Maxwellian3D() - assert isinstance(f0, Maxwellian3D) - - self._alpha = alpha - self._kappa = kappa - self._f0 = f0 - assert self._f0.maxw_params["vth1"] == self._f0.maxw_params["vth2"] == self._f0.maxw_params["vth3"] - self._vth = self._f0.maxw_params["vth1"] - - self._info = solver["info"] + class Variables: + def __init__(self): + self._e: FEECVariable = None + self._ions: PICVariable = None + + @property + def e(self) -> FEECVariable: + return self._e + + @e.setter + def e(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hcurl" + self._e = new + + @property + def ions(self) -> PICVariable: + return self._ions + + @ions.setter + def ions(self, new): + assert isinstance(new, PICVariable) + assert new.space in ("Particles6D", "DeltaFParticles6D") + self._ions = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + alpha: float = 1.0 + kappa: float = 1.0 + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + + def __post_init__(self): + # checks + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + if self.solver_params is None: + self.solver_params = SolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + self._alpha = self.options.alpha + self._kappa = self.options.kappa + + backgrounds = self.variables.ions.backgrounds + # use single Maxwellian + if isinstance(backgrounds, list): + self._f0 = backgrounds[0] + else: + self._f0 = backgrounds + assert isinstance(self._f0, Maxwellian3D), "The background distribution function must be a uniform Maxwellian!" + self._vth = self._f0.maxw_params["vth1"][0] + + self._info = self.options.solver_params.info # Initialize Accumulator object + e = self.variables.e.spline.vector + particles = self.variables.ions.particles + self._accum = Accumulator( particles, "Hcurl", - accum_kernels.linear_vlasov_ampere, + Pyccelkernel(accum_kernels.linear_vlasov_ampere), self.mass_ops, self.domain.args_domain, add_vector=True, @@ -381,18 +421,18 @@ def __init__( self._e_sum = e.space.zeros() # marker storage - self._f0_values = np.zeros(particles.markers.shape[0], dtype=float) - self._old_weights = np.empty(particles.markers.shape[0], dtype=float) + self._f0_values = xp.zeros(particles.markers.shape[0], dtype=float) + self._old_weights = xp.empty(particles.markers.shape[0], dtype=float) # ================================ # ========= Schur Solver ========= # ================================ # Preconditioner - if solver["type"][1] == None: + if self.options.precond == None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(self.mass_ops.M1) # Define block matrix [[A B], [C I]] (without time step size dt in the diagonals) @@ -403,11 +443,9 @@ def __init__( self._schur_solver = SchurSolver( _A, _BC, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], + self.options.solver, + precond=pc, + solver_params=self.options.solver_params, ) # Instantiate particle pusher @@ -423,21 +461,24 @@ def __init__( self._pusher = Pusher( particles, - pusher_kernels.push_weights_with_efield_lin_va, + Pyccelkernel(pusher_kernels.push_weights_with_efield_lin_va), args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, ) def __call__(self, dt): + en = self.variables.e.spline.vector + particles = self.variables.ions.particles + # evaluate f0 and accumulate self._f0_values[:] = self._f0( - self.particles[0].markers[:, 0], - self.particles[0].markers[:, 1], - self.particles[0].markers[:, 2], - self.particles[0].markers[:, 3], - self.particles[0].markers[:, 4], - self.particles[0].markers[:, 5], + particles.markers[:, 0], + particles.markers[:, 1], + particles.markers[:, 2], + particles.markers[:, 3], + particles.markers[:, 4], + particles.markers[:, 5], ) self._accum(self._f0_values) @@ -453,35 +494,34 @@ def __call__(self, dt): # new e-field (no tmps created here) self._e_tmp, info = self._schur_solver( - xn=self.feec_vars[0], + xn=en, Byn=self._e_scale, dt=dt, out=self._e_tmp, ) # Store old weights - self._old_weights[~self.particles[0].holes] = self.particles[0].markers_wo_holes[:, 6] + self._old_weights[~particles.holes] = particles.markers_wo_holes[:, 6] # Compute (e^{n+1} + e^n) (no tmps created here) self._e_sum *= 0.0 - self._e_sum += self.feec_vars[0] + self._e_sum += en self._e_sum += self._e_tmp # Update weights self._pusher(dt) # write new coeffs into self.variables - (max_de,) = self.feec_vars_update(self._e_tmp) + max_de = self.update_feec_variables(e=self._e_tmp) # Print out max differences for weights and e-field if self._info: print("Status for StepEfieldWeights:", info["success"]) print("Iterations for StepEfieldWeights:", info["niter"]) print("Maxdiff e1 for StepEfieldWeights:", max_de) - max_diff = np.max( - np.abs( - self._old_weights[~self.particles[0].holes] - - self.particles[0].markers[~self.particles[0].holes, 6], + max_diff = xp.max( + xp.abs( + self._old_weights[~particles.holes] - particles.markers[~particles.holes, 6], ), ) print("Maxdiff weights for StepEfieldWeights:", max_diff) @@ -511,116 +551,133 @@ class PressureCoupling6D(Propagator): \begin{bmatrix} {\mathbb M^n}(u^{n+1} + u^n) \\ \bar W (V^{n+1} + V^{n} \end{bmatrix} \,. """ - @staticmethod - def options(default=False): - dct = {} - dct["use_perp_model"] = [True, False] - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["filter"] = { - "use_filter": None, - "modes": (1), - "repeat": 1, - "alpha": 0.5, - } - dct["boundary_cut"] = { - "e1": 0.0, - "e2": 0.0, - "e3": 0.0, - } - dct["turn_off"] = False - - if default: - dct = descend_options_dict(dct, []) - - return dct - - def __init__( - self, - particles: Particles5D, - u: BlockVector | PolarVector, - *, - use_perp_model: bool = options(default=True)["use_perp_model"], - u_space: str, - solver: dict = options(default=True)["solver"], - coupling_params: dict, - filter: dict = options(default=True)["filter"], - boundary_cut: dict = options(default=True)["boundary_cut"], - ): - super().__init__(particles, u) - - self._G = self.derham.grad - self._GT = self.derham.grad.transpose() - - self._info = solver["info"] - self._rank = self.derham.comm.Get_rank() - - assert u_space in {"Hcurl", "Hdiv", "H1vec"} - - if u_space == "Hcurl": + class Variables: + def __init__(self): + self._u: FEECVariable = None + self._energetic_ions: PICVariable = None + + @property + def u(self) -> FEECVariable: + return self._u + + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hcurl", "Hdiv", "H1vec") + self._u = new + + @property + def energetic_ions(self) -> PICVariable: + return self._energetic_ions + + @energetic_ions.setter + def energetic_ions(self, new): + assert isinstance(new, PICVariable) + assert new.space == "Particles6D" + self._energetic_ions = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + ep_scale: float = 1.0 + u_space: OptsVecSpace = "Hdiv" + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + filter_params: FilterParameters = None + use_perp_model: bool = True + + def __post_init__(self): + # checks + check_option(self.u_space, OptsVecSpace) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + assert isinstance(self.ep_scale, float) + assert isinstance(self.use_perp_model, bool) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + if self.filter_params is None: + self.filter_params = FilterParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + if self.options.u_space == "H1vec": + self._u_form_int = 0 + else: + self._u_form_int = int(self.derham.space_to_form[self.options.u_space]) + + if self.options.u_space == "Hcurl": id_Mn = "M1n" id_X = "X1" - elif u_space == "Hdiv": + elif self.options.u_space == "Hdiv": id_Mn = "M2n" id_X = "X2" - elif u_space == "H1vec": + elif self.options.u_space == "H1vec": id_Mn = "Mvn" id_X = "Xv" - if u_space == "H1vec": - self._space_key_int = 0 - else: - self._space_key_int = int( - self.derham.space_to_form[u_space], - ) + # call operatros + id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n" + _A = getattr(self.mass_ops, id_M) + self._X = getattr(self.basis_ops, id_X) + self._XT = self._X.transpose() + grad = self.derham.grad + gradT = grad.transpose() # Preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) - pc = pc_class(getattr(self.mass_ops, id_Mn)) + pc_class = getattr(preconditioner, self.options.precond) + pc = pc_class(getattr(self.mass_ops, id_M)) # Call the accumulation and Pusher class - if use_perp_model: - accum_ker = accum_kernels.pc_lin_mhd_6d - pusher_ker = pusher_kernels.push_pc_GXu + if self.options.use_perp_model: + accum_ker = Pyccelkernel(accum_kernels.pc_lin_mhd_6d) + pusher_ker = Pyccelkernel(pusher_kernels.push_pc_GXu) else: - accum_ker = accum_kernels.pc_lin_mhd_6d_full - pusher_ker = pusher_kernels.push_pc_GXu_full - - self._coupling_mat = coupling_params["Ah"] / coupling_params["Ab"] - self._coupling_vec = coupling_params["Ah"] / coupling_params["Ab"] - self._scale_push = 1 - - self._boundary_cut_e1 = boundary_cut["e1"] + accum_ker = Pyccelkernel(accum_kernels.pc_lin_mhd_6d_full) + pusher_ker = Pyccelkernel(pusher_kernels.push_pc_GXu_full) + # define Accumulator and arguments self._ACC = Accumulator( - particles, - "Hcurl", + self.variables.energetic_ions.particles, + "Hcurl", # TODO:check accum_ker, self.mass_ops, self.domain.args_domain, add_vector=True, symmetry="pressure", - filter_params=filter, + filter_params=self.options.filter_params, ) - self._tmp_g1 = self._G.codomain.zeros() - self._tmp_g2 = self._G.codomain.zeros() - self._tmp_g3 = self._G.codomain.zeros() + self._tmp_g1 = grad.codomain.zeros() + self._tmp_g2 = grad.codomain.zeros() + self._tmp_g3 = grad.codomain.zeros() # instantiate Pusher - args_kernel = ( + args_pusher_kernel = ( self.derham.args_derham, self._tmp_g1[0]._data, self._tmp_g1[1]._data, @@ -631,38 +688,20 @@ def __init__( self._tmp_g3[0]._data, self._tmp_g3[1]._data, self._tmp_g3[2]._data, - self._boundary_cut_e1, ) self._pusher = Pusher( - particles, + self.variables.energetic_ions.particles, pusher_ker, - args_kernel, + args_pusher_kernel, self.domain.args_domain, alpha_in_kernel=1.0, ) - # Define operators - self._A = getattr(self.mass_ops, id_Mn) - self._X = getattr(self.basis_ops, id_X) - self._XT = self._X.transpose() - - # Instantiate schur solver with dummy BC - self._schur_solver = SchurSolver( - self._A, - self._XT @ self._X, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], - recycle=solver["recycle"], - ) - - self.u_temp = u.space.zeros() - self.u_temp2 = u.space.zeros() + self.u_temp = self.variables.u.spline.vector.space.zeros() + self.u_temp2 = self.variables.u.spline.vector.space.zeros() self._tmp = self._X.codomain.zeros() - self._BV = u.space.zeros() + self._BV = self.variables.u.spline.vector.space.zeros() self._MAT = [ [self._ACC.operators[0], self._ACC.operators[1], self._ACC.operators[2]], @@ -672,20 +711,32 @@ def __init__( self._GT_VEC = BlockVector(self.derham.Vh["v"]) + _BC = -1 / 4 * self._XT @ self.GT_MAT_G(self.derham, self._MAT) @ self._X + + self._schur_solver = SchurSolver( + _A, + _BC, + self.options.solver, + precond=pc, + solver_params=self.options.solver_params, + ) + def __call__(self, dt): - # current u - un = self.feec_vars[0] - un.update_ghost_regions() + # current FE coeffs + un = self.variables.u.spline.vector + + # operators + grad = self.derham.grad + gradT = grad.transpose() # acuumulate MAT and VEC - self._ACC(self._coupling_mat, self._coupling_vec, self._boundary_cut_e1) + self._ACC( + self.options.ep_scale, + ) # update GT_VEC for i in range(3): - self._GT_VEC[i] = self._GT.dot(self._ACC.vectors[i]) - - # define BC and B dot V of the Schur block matrix [[A, B], [C, I]] - self._schur_solver.BC = -1 / 4 * self._XT @ self.GT_MAT_G(self.derham, self._MAT) @ self._X + self._GT_VEC[i] = gradT.dot(self._ACC.vectors[i]) self._BV = self._XT.dot(self._GT_VEC) * (-1 / 2) @@ -698,9 +749,9 @@ def __call__(self, dt): # calculate GXu Xu = self._X.dot(_u, out=self._tmp) - GXu_1 = self._G.dot(Xu[0], out=self._tmp_g1) - GXu_2 = self._G.dot(Xu[1], out=self._tmp_g2) - GXu_3 = self._G.dot(Xu[2], out=self._tmp_g3) + GXu_1 = grad.dot(Xu[0], out=self._tmp_g1) + GXu_2 = grad.dot(Xu[1], out=self._tmp_g2) + GXu_3 = grad.dot(Xu[2], out=self._tmp_g3) GXu_1.update_ghost_regions() GXu_2.update_ghost_regions() @@ -710,16 +761,16 @@ def __call__(self, dt): self._pusher(dt) # write new coeffs into Propagator.variables - (max_du,) = self.feec_vars_update(un1) + diffs = self.update_feec_variables(u=un1) # update weights in case of control variate - if self.particles[0].control_variate: - self.particles[0].update_weights() + if self.variables.energetic_ions.species.weights_params.control_variate: + self.variables.energetic_ions.particles.update_weights() - if self._info and self._rank == 0: + if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for StepPressurecoupling:", info["success"]) print("Iterations for StepPressurecoupling:", info["niter"]) - print("Maxdiff u1 for StepPressurecoupling:", max_du) + print("Maxdiff u1 for StepPressurecoupling:", diffs["u"]) print() class GT_MAT_G(LinOpWithTransp): @@ -738,8 +789,8 @@ class GT_MAT_G(LinOpWithTransp): def __init__(self, derham, MAT, transposed=False): self._derham = derham - self._G = derham.grad - self._GT = derham.grad.transpose() + self._grad = derham.grad + self._gradT = derham.grad.transpose() self._domain = derham.Vh["v"] self._codomain = derham.Vh["v"] @@ -793,9 +844,9 @@ def dot(self, v, out=None): for i in range(3): for j in range(3): - self._temp += self._MAT[i][j].dot(self._G.dot(v[j])) + self._temp += self._MAT[i][j].dot(self._grad.dot(v[j])) - self._vector[i] = self._GT.dot(self._temp) + self._vector[i] = self._gradT.dot(self._temp) self._temp *= 0.0 self._vector.update_ghost_regions() @@ -825,84 +876,107 @@ class CurrentCoupling6DCurrent(Propagator): :ref:`time_discret`: Crank-Nicolson (implicit mid-point). System size reduction via :class:`~struphy.linear_algebra.schur_solver.SchurSolver`. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["filter"] = { - "use_filter": None, - "modes": (1), - "repeat": 1, - "alpha": 0.5, - } - dct["boundary_cut"] = { - "e1": 0.0, - "e2": 0.0, - "e3": 0.0, - } - dct["turn_off"] = False - - if default: - dct = descend_options_dict(dct, []) - - return dct - - def __init__( - self, - particles: Particles6D, - u: BlockVector, - *, - u_space: str, - b_eq: BlockVector | PolarVector, - b_tilde: BlockVector | PolarVector, - Ab: int = 1, - Ah: int = 1, - epsilon: float = 1.0, - solver: dict = options(default=True)["solver"], - filter: dict = options(default=True)["filter"], - boundary_cut: dict = options(default=True)["boundary_cut"], - ): - super().__init__(particles, u) - - if u_space == "H1vec": - self._space_key_int = 0 - else: - self._space_key_int = int( - self.derham.space_to_form[u_space], - ) + class Variables: + def __init__(self): + self._ions: PICVariable = None + self._u: FEECVariable = None + + @property + def ions(self) -> PICVariable: + return self._ions + + @ions.setter + def ions(self, new): + assert isinstance(new, PICVariable) + assert new.space in ("Particles6D") + self._ions = new + + @property + def u(self) -> FEECVariable: + return self._u + + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hcurl", "Hdiv", "H1vec") + self._u = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + b_tilde: FEECVariable = None + u_space: OptsVecSpace = "Hdiv" + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + filter_params: FilterParameters = None + boundary_cut: tuple = (0.0, 0.0, 0.0) + + def __post_init__(self): + # checks + check_option(self.u_space, OptsVecSpace) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + assert self.b_tilde.space == "Hdiv" + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - self._b_eq = b_eq - self._b_tilde = b_tilde + @profile + def allocate(self): + self._space_key_int = int(self.derham.space_to_form[self.options.u_space]) + + particles = self.variables.ions.particles + u = self.variables.u.spline.vector + self._b_eq = self.projected_equil.b2 + self._b_tilde = self.options.b_tilde.spline.vector + + self._info = self.options.solver_params.info + + if self.derham.comm is None: + self._rank = 0 + else: + self._rank = self.derham.comm.Get_rank() - self._info = solver["info"] - self._rank = self.derham.comm.Get_rank() + Ah = self.variables.ions.species.mass_number + Ab = self.variables.u.species.mass_number + epsilon = self.variables.ions.species.equation_params.epsilon self._coupling_mat = Ah / Ab / epsilon**2 self._coupling_vec = Ah / Ab / epsilon self._scale_push = 1.0 / epsilon - self._boundary_cut_e1 = boundary_cut["e1"] + self._boundary_cut_e1 = self.options.boundary_cut[0] # load accumulator self._accumulator = Accumulator( particles, - u_space, - accum_kernels.cc_lin_mhd_6d_2, + self.options.u_space, + Pyccelkernel(accum_kernels.cc_lin_mhd_6d_2), self.mass_ops, self.domain.args_domain, add_vector=True, symmetry="symm", - filter_params=filter, + filter_params=self.options.filter_params, ) # if self.particles[0].control_variate: @@ -931,17 +1005,17 @@ def __init__( # self.particles[0].f0.n, *quad_pts, kind='0', squeeze_out=False, coordinates='logical') # # memory allocation for magnetic field at quadrature points - # self._b_quad1 = np.zeros_like(self._nuh0_at_quad[0]) - # self._b_quad2 = np.zeros_like(self._nuh0_at_quad[0]) - # self._b_quad3 = np.zeros_like(self._nuh0_at_quad[0]) + # self._b_quad1 = xp.zeros_like(self._nuh0_at_quad[0]) + # self._b_quad2 = xp.zeros_like(self._nuh0_at_quad[0]) + # self._b_quad3 = xp.zeros_like(self._nuh0_at_quad[0]) # # memory allocation for (self._b_quad x self._nuh0_at_quad) * self._coupling_vec - # self._vec1 = np.zeros_like(self._nuh0_at_quad[0]) - # self._vec2 = np.zeros_like(self._nuh0_at_quad[0]) - # self._vec3 = np.zeros_like(self._nuh0_at_quad[0]) + # self._vec1 = xp.zeros_like(self._nuh0_at_quad[0]) + # self._vec2 = xp.zeros_like(self._nuh0_at_quad[0]) + # self._vec3 = xp.zeros_like(self._nuh0_at_quad[0]) # FEM spaces and basis extraction operators for u and b - u_id = self.derham.space_to_form[u_space] + u_id = self.derham.space_to_form[self.options.u_space] self._EuT = self.derham.extraction_ops[u_id].transpose() self._EbT = self.derham.extraction_ops["2"].transpose() @@ -955,15 +1029,15 @@ def __init__( self._u_avg2 = self._EuT.codomain.zeros() # load particle pusher kernel - if u_space == "Hcurl": - kernel = pusher_kernels.push_bxu_Hcurl - elif u_space == "Hdiv": - kernel = pusher_kernels.push_bxu_Hdiv - elif u_space == "H1vec": - kernel = pusher_kernels.push_bxu_H1vec + if self.options.u_space == "Hcurl": + kernel = Pyccelkernel(pusher_kernels.push_bxu_Hcurl) + elif self.options.u_space == "Hdiv": + kernel = Pyccelkernel(pusher_kernels.push_bxu_Hdiv) + elif self.options.u_space == "H1vec": + kernel = Pyccelkernel(pusher_kernels.push_bxu_H1vec) else: raise ValueError( - f'{u_space = } not valid, choose from "Hcurl", "Hdiv" or "H1vec.', + f'{self.options.u_space =} not valid, choose from "Hcurl", "Hdiv" or "H1vec.', ) # instantiate Pusher @@ -990,10 +1064,10 @@ def __init__( _A = getattr(self.mass_ops, "M" + u_id + "n") # preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(_A) _BC = -1 / 4 * self._accumulator.operators[0] @@ -1001,17 +1075,15 @@ def __init__( self._schur_solver = SchurSolver( _A, _BC, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], - recycle=solver["recycle"], + self.options.solver, + precond=pc, + solver_params=self.options.solver_params, ) def __call__(self, dt): # pointer to old coefficients - un = self.feec_vars[0] + particles = self.variables.ions.particles + un = self.variables.u.spline.vector # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) self._b_eq.copy(out=self._b_full1) @@ -1081,11 +1153,11 @@ def __call__(self, dt): self._pusher(self._scale_push * dt) # write new coeffs into Propagator.variables - max_du = self.feec_vars_update(un1) + max_du = self.update_feec_variables(u=un1) # update weights in case of control variate - if self.particles[0].control_variate: - self.particles[0].update_weights() + if particles.control_variate: + particles.update_weights() if self._info and self._rank == 0: print("Status for CurrentCoupling6DCurrent:", info["success"]) @@ -1130,288 +1202,198 @@ class CurrentCoupling5DCurlb(Propagator): For the detail explanation of the notations, see `2022_DriftKineticCurrentCoupling `_. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["filter"] = { - "use_filter": None, - "modes": (1), - "repeat": 1, - "alpha": 0.5, - } - dct["boundary_cut"] = { - "e1": 0.0, - "e2": 0.0, - "e3": 0.0, - } - dct["turn_off"] = False - - if default: - dct = descend_options_dict(dct, []) - - return dct - - def __init__( - self, - particles: Particles5D, - u: BlockVector, - *, - b: BlockVector, - b_eq: BlockVector, - unit_b1: BlockVector, - absB0: StencilVector, - gradB1: BlockVector, - curl_unit_b2: BlockVector, - u_space: str, - solver: dict = options(default=True)["solver"], - filter: dict = options(default=True)["filter"], - coupling_params: dict, - epsilon: float = 1.0, - boundary_cut: dict = options(default=True)["boundary_cut"], - ): - super().__init__(particles, u) - - assert u_space in {"Hcurl", "Hdiv", "H1vec"} - - if u_space == "H1vec": - self._space_key_int = 0 - else: - self._space_key_int = int( - self.derham.space_to_form[u_space], - ) + class Variables: + def __init__(self): + self._u: FEECVariable = None + self._energetic_ions: PICVariable = None - self._epsilon = epsilon - self._b = b - self._b_eq = b_eq - self._unit_b1 = unit_b1 - self._absB0 = absB0 - self._gradB1 = gradB1 - self._curl_norm_b = curl_unit_b2 + @property + def u(self) -> FEECVariable: + return self._u - self._info = solver["info"] - self._rank = self.derham.comm.Get_rank() + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hcurl", "Hdiv", "H1vec") + self._u = new - self._coupling_mat = coupling_params["Ah"] / coupling_params["Ab"] - self._coupling_vec = coupling_params["Ah"] / coupling_params["Ab"] - self._scale_push = 1 + @property + def energetic_ions(self) -> PICVariable: + return self._energetic_ions - self._boundary_cut_e1 = boundary_cut["e1"] + @energetic_ions.setter + def energetic_ions(self, new): + assert isinstance(new, PICVariable) + assert new.space == "Particles5D" + self._energetic_ions = new - u_id = self.derham.space_to_form[u_space] - self._E0T = self.derham.extraction_ops["0"].transpose() - self._EuT = self.derham.extraction_ops[u_id].transpose() - self._E2T = self.derham.extraction_ops["2"].transpose() - self._E1T = self.derham.extraction_ops["1"].transpose() + def __init__(self): + self.variables = self.Variables() - self._unit_b1 = self._E1T.dot(self._unit_b1) - self._curl_norm_b = self._E2T.dot(self._curl_norm_b) - self._curl_norm_b.update_ghost_regions() - self._absB0 = self._E0T.dot(self._absB0) + @dataclass + class Options: + # propagator options + b_tilde: FEECVariable = None + ep_scale: float = 1.0 + u_space: OptsVecSpace = "Hdiv" + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + filter_params: FilterParameters = None - # define system [[A B], [C I]] [u_new, v_new] = [[A -B], [-C I]] [u_old, v_old] (without time step size dt) - _A = getattr(self.mass_ops, "M" + u_id + "n") + def __post_init__(self): + # checks + check_option(self.u_space, OptsVecSpace) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + assert isinstance(self.b_tilde, FEECVariable) + assert isinstance(self.ep_scale, float) - # preconditioner - if solver["type"][1] is None: - pc = None - else: - pc_class = getattr(preconditioner, solver["type"][1]) - pc = pc_class(_A) + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() - # temporary vectors to avoid memory allocation - self._b_full1 = self._b_eq.space.zeros() - self._b_full2 = self._E2T.codomain.zeros() - self._u_new = u.space.zeros() - self._u_avg1 = u.space.zeros() - self._u_avg2 = self._EuT.codomain.zeros() + if self.filter_params is None: + self.filter_params = FilterParameters() - # Call the accumulation and Pusher class + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + if self.options.u_space == "H1vec": + self._u_form_int = 0 + else: + self._u_form_int = int(self.derham.space_to_form[self.options.u_space]) + + # call operatros + id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n" + _A = getattr(self.mass_ops, id_M) + + # Preconditioner + if self.options.precond is None: + pc = None + else: + pc_class = getattr(preconditioner, self.options.precond) + pc = pc_class(getattr(self.mass_ops, id_M)) + + # magnetic equilibrium field + unit_b1 = self.projected_equil.unit_b1 + curl_unit_b1 = self.projected_equil.curl_unit_b1 + self._b2 = self.projected_equil.b2 + + # magnetic field + self._b_tilde = self.options.b_tilde.spline.vector + + # scaling factor + epsilon = self.variables.energetic_ions.species.equation_params.epsilon + + # temporary vectors to avoid memory allocation + self._b_full = self._b2.space.zeros() + self._u_new = self.variables.u.spline.vector.space.zeros() + self._u_avg = self.variables.u.spline.vector.space.zeros() + + # define Accumulator and arguments self._ACC = Accumulator( - particles, - u_space, - accum_kernels_gc.cc_lin_mhd_5d_J1, + self.variables.energetic_ions.particles, + self.options.u_space, + Pyccelkernel(accum_kernels_gc.cc_lin_mhd_5d_curlb), self.mass_ops, self.domain.args_domain, add_vector=True, symmetry="symm", - filter_params=filter, + filter_params=self.options.filter_params, + ) + + self._args_accum_kernel = ( + epsilon, + self.options.ep_scale, + self._b_full[0]._data, + self._b_full[1]._data, + self._b_full[2]._data, + unit_b1[0]._data, + unit_b1[1]._data, + unit_b1[2]._data, + curl_unit_b1[0]._data, + curl_unit_b1[1]._data, + curl_unit_b1[2]._data, + self._u_form_int, ) - if u_space == "Hcurl": - kernel = pusher_kernels_gc.push_gc_cc_J1_Hcurl - elif u_space == "Hdiv": - kernel = pusher_kernels_gc.push_gc_cc_J1_Hdiv - elif u_space == "H1vec": - kernel = pusher_kernels_gc.push_gc_cc_J1_H1vec + # define Pusher + if self.options.u_space == "Hcurl": + pusher_kernel = Pyccelkernel(pusher_kernels_gc.push_gc_cc_J1_Hcurl) + elif self.options.u_space == "Hdiv": + pusher_kernel = Pyccelkernel(pusher_kernels_gc.push_gc_cc_J1_Hdiv) + elif self.options.u_space == "H1vec": + pusher_kernel = Pyccelkernel(pusher_kernels_gc.push_gc_cc_J1_H1vec) else: raise ValueError( - f'{u_space = } not valid, choose from "Hcurl", "Hdiv" or "H1vec.', + f'{self.options.u_space =} not valid, choose from "Hcurl", "Hdiv" or "H1vec.', ) - # instantiate Pusher - args_kernel = ( + args_pusher_kernel = ( self.derham.args_derham, - self._epsilon, - self._b_full2[0]._data, - self._b_full2[1]._data, - self._b_full2[2]._data, - self._unit_b1[0]._data, - self._unit_b1[1]._data, - self._unit_b1[2]._data, - self._curl_norm_b[0]._data, - self._curl_norm_b[1]._data, - self._curl_norm_b[2]._data, - self._u_avg2[0]._data, - self._u_avg2[1]._data, - self._u_avg2[2]._data, - 0.0, + epsilon, + self._b_full[0]._data, + self._b_full[1]._data, + self._b_full[2]._data, + unit_b1[0]._data, + unit_b1[1]._data, + unit_b1[2]._data, + curl_unit_b1[0]._data, + curl_unit_b1[1]._data, + curl_unit_b1[2]._data, + self._u_avg[0]._data, + self._u_avg[1]._data, + self._u_avg[2]._data, ) self._pusher = Pusher( - particles, - kernel, - args_kernel, + self.variables.energetic_ions.particles, + pusher_kernel, + args_pusher_kernel, self.domain.args_domain, alpha_in_kernel=1.0, ) - # define BC and B dot V of the Schur block matrix [[A, B], [C, I]] _BC = -1 / 4 * self._ACC.operators[0] - # call SchurSolver class self._schur_solver = SchurSolver( _A, _BC, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], - recycle=solver["recycle"], + self.options.solver, + precond=pc, + solver_params=self.options.solver_params, ) def __call__(self, dt): - un = self.feec_vars[0] + # current FE coeffs + un = self.variables.u.spline.vector # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) - b_full = self._b_eq.copy(out=self._b_full1) - - if self._b is not None: - self._b_full1 += self._b - - # extract coefficients to tensor product space (in-place) - Eb_full = self._E2T.dot(b_full, out=self._b_full2) - - # update ghost regions because of non-local access in accumulation kernel! - Eb_full.update_ghost_regions() - - # perform accumulation (either with or without control variate) - # if self.particles[0].control_variate: + b_full = self._b2.copy(out=self._b_full) - # # evaluate magnetic field at quadrature points (in-place) - # WeightedMassOperator.eval_quad(self.derham.Vh_fem['2'], self._b_full2, - # out=[self._b_at_quad[0], self._b_at_quad[1], self._b_at_quad[2]]) - - # # evaluate B_parallel - # self._B_para_at_quad = np.sum( - # p * q for p, q in zip(self._unit_b1_at_quad, self._b_at_quad)) - # self._B_para_at_quad += self._unit_b1_dot_curl_norm_b_at_quad - - # # assemble (B x)(curl norm_b)(curl norm_b)(B x) / B_star_para² / det_df³ * (f0.u_para² + f0.vth_para²) * f0.n - # self._mat11[:, :, :] = (self._b_at_quad[1]*self._curl_norm_b_at_quad[2] - - # self._b_at_quad[2]*self._curl_norm_b_at_quad[1])**2 * \ - # self._control_const * self._coupling_mat / \ - # self._det_df_at_quad**3 / self._B_para_at_quad**2 - # self._mat12[:, :, :] = (self._b_at_quad[1]*self._curl_norm_b_at_quad[2] - - # self._b_at_quad[2]*self._curl_norm_b_at_quad[1]) * \ - # (self._b_at_quad[2]*self._curl_norm_b_at_quad[0] - - # self._b_at_quad[0]*self._curl_norm_b_at_quad[2]) * \ - # self._control_const * self._coupling_mat / \ - # self._det_df_at_quad**3 / self._B_para_at_quad**2 - # self._mat13[:, :, :] = (self._b_at_quad[1]*self._curl_norm_b_at_quad[2] - - # self._b_at_quad[2]*self._curl_norm_b_at_quad[1]) * \ - # (self._b_at_quad[0]*self._curl_norm_b_at_quad[1] - - # self._b_at_quad[1]*self._curl_norm_b_at_quad[0]) * \ - # self._control_const * self._coupling_mat / \ - # self._det_df_at_quad**3 / self._B_para_at_quad**2 - # self._mat22[:, :, :] = (self._b_at_quad[2]*self._curl_norm_b_at_quad[0] - - # self._b_at_quad[0]*self._curl_norm_b_at_quad[2])**2 * \ - # self._control_const * self._coupling_mat / \ - # self._det_df_at_quad**3 / self._B_para_at_quad**2 - # self._mat23[:, :, :] = (self._b_at_quad[2]*self._curl_norm_b_at_quad[0] - - # self._b_at_quad[0]*self._curl_norm_b_at_quad[2]) * \ - # (self._b_at_quad[0]*self._curl_norm_b_at_quad[1] - - # self._b_at_quad[1]*self._curl_norm_b_at_quad[0]) * \ - # self._control_const * self._coupling_mat / \ - # self._det_df_at_quad**3 / self._B_para_at_quad**2 - # self._mat33[:, :, :] = (self._b_at_quad[0]*self._curl_norm_b_at_quad[1] - - # self._b_at_quad[1]*self._curl_norm_b_at_quad[0])**2 * \ - # self._control_const * self._coupling_mat / \ - # self._det_df_at_quad**3 / self._B_para_at_quad**2 - - # self._mat21[:, :, :] = -self._mat12 - # self._mat31[:, :, :] = -self._mat13 - # self._mat32[:, :, :] = -self._mat23 - - # # assemble (B x)(curl norm_b) / B_star_para / det_df * (f0.u_para² + f0.vth_para²) * f0.n - # self._vec1[:, :, :] = (self._b_at_quad[1]*self._curl_norm_b_at_quad[2] - - # self._b_at_quad[2]*self._curl_norm_b_at_quad[1]) * \ - # self._control_const * self._coupling_vec / \ - # self._det_df_at_quad / self._B_para_at_quad - # self._vec2[:, :, :] = (self._b_at_quad[2]*self._curl_norm_b_at_quad[0] - - # self._b_at_quad[0]*self._curl_norm_b_at_quad[2]) * \ - # self._control_const * self._coupling_vec / \ - # self._det_df_at_quad / self._B_para_at_quad - # self._vec3[:, :, :] = (self._b_at_quad[0]*self._curl_norm_b_at_quad[1] - - # self._b_at_quad[1]*self._curl_norm_b_at_quad[0]) * \ - # self._control_const * self._coupling_vec / \ - # self._det_df_at_quad / self._B_para_at_quad - - # self._ACC.accumulate(self.particles[0], self._epsilon, - # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, - # self._space_key_int, self._coupling_mat, self._coupling_vec, 0.1, - # control_mat=[[None, self._mat12, self._mat13], - # [self._mat21, None, self._mat23], - # [self._mat31, self._mat32, None]], - # control_vec=[self._vec1, self._vec2, self._vec3]) - # else: - # self._ACC.accumulate(self.particles[0], self._epsilon, - # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, - # self._space_key_int, self._coupling_mat, self._coupling_vec, 0.1) + b_full += self._b_tilde + b_full.update_ghost_regions() self._ACC( - self._epsilon, - Eb_full[0]._data, - Eb_full[1]._data, - Eb_full[2]._data, - self._unit_b1[0]._data, - self._unit_b1[1]._data, - self._unit_b1[2]._data, - self._curl_norm_b[0]._data, - self._curl_norm_b[1]._data, - self._curl_norm_b[2]._data, - self._space_key_int, - self._coupling_mat, - self._coupling_vec, - self._boundary_cut_e1, + *self._args_accum_kernel, ) - # update u coefficients + # solve un1, info = self._schur_solver( un, -self._ACC.vectors[0] / 2, @@ -1420,27 +1402,25 @@ def __call__(self, dt): ) # call pusher kernel with average field (u_new + u_old)/2 and update ghost regions because of non-local access in kernel - _u = un.copy(out=self._u_avg1) + _u = un.copy(out=self._u_avg) _u += un1 _u *= 0.5 - _Eu = self._EuT.dot(_u, out=self._u_avg2) + _u.update_ghost_regions() - _Eu.update_ghost_regions() - - self._pusher(self._scale_push * dt) + self._pusher(dt) - # write new coeffs into Propagator.variables - (max_du,) = self.feec_vars_update(un1) + # update u coefficients + diffs = self.update_feec_variables(u=un1) # update_weights - if self.particles[0].control_variate: - self.particles[0].update_weights() + if self.variables.energetic_ions.species.weights_params.control_variate: + self.variables.energetic_ions.particles.update_weights() - if self._info and self._rank == 0: + if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for CurrentCoupling5DCurlb:", info["success"]) print("Iterations for CurrentCoupling5DCurlb:", info["niter"]) - print("Maxdiff up for CurrentCoupling5DCurlb:", max_du) + print("Maxdiff up for CurrentCoupling5DCurlb:", diffs["u"]) print() @@ -1480,435 +1460,722 @@ class CurrentCoupling5DGradB(Propagator): For the detail explanation of the notations, see `2022_DriftKineticCurrentCoupling `_. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["algo"] = ["rk4", "forward_euler", "heun2", "rk2", "heun3"] - dct["filter"] = { - "use_filter": None, - "modes": (1), - "repeat": 1, - "alpha": 0.5, - } - dct["boundary_cut"] = { - "e1": 0.0, - "e2": 0.0, - "e3": 0.0, - } - dct["turn_off"] = False - - if default: - dct = descend_options_dict(dct, []) - - return dct - - def __init__( - self, - particles: Particles5D, - u: BlockVector, - *, - b: BlockVector, - b_eq: BlockVector, - unit_b1: BlockVector, - unit_b2: BlockVector, - absB0: StencilVector, - gradB1: BlockVector, - curl_unit_b2: BlockVector, - u_space: str, - solver: dict = options(default=True)["solver"], - algo: dict = options(default=True)["algo"], - filter: dict = options(default=True)["filter"], - coupling_params: dict, - epsilon: float = 1.0, - boundary_cut: dict = options(default=True)["boundary_cut"], - ): - from psydac.linalg.solvers import inverse - - from struphy.ode.utils import ButcherTableau - - super().__init__(particles, u) - - assert u_space in {"Hcurl", "Hdiv", "H1vec"} - - if u_space == "H1vec": - self._space_key_int = 0 - else: - self._space_key_int = int( - self.derham.space_to_form[u_space], - ) + class Variables: + def __init__(self): + self._u: FEECVariable = None + self._energetic_ions: PICVariable = None - self._epsilon = epsilon - self._b = b - self._b_eq = b_eq - self._unit_b1 = unit_b1 - self._unit_b2 = unit_b2 - self._absB0 = absB0 - self._gradB1 = gradB1 - self._curl_norm_b = curl_unit_b2 + @property + def u(self) -> FEECVariable: + return self._u - self._info = solver["info"] - self._rank = self.derham.comm.Get_rank() + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hcurl", "Hdiv", "H1vec") + self._u = new - self._coupling_mat = coupling_params["Ah"] / coupling_params["Ab"] - self._coupling_vec = coupling_params["Ah"] / coupling_params["Ab"] - self._scale_push = 1 + @property + def energetic_ions(self) -> PICVariable: + return self._energetic_ions - self._boundary_cut_e1 = boundary_cut["e1"] + @energetic_ions.setter + def energetic_ions(self, new): + assert isinstance(new, PICVariable) + assert new.space == "Particles5D" + self._energetic_ions = new - u_id = self.derham.space_to_form[u_space] - self._E0T = self.derham.extraction_ops["0"].transpose() - self._EuT = self.derham.extraction_ops[u_id].transpose() - self._E1T = self.derham.extraction_ops["1"].transpose() - self._E2T = self.derham.extraction_ops["2"].transpose() + def __init__(self): + self.variables = self.Variables() - self._PB = getattr(self.basis_ops, "PB") + @dataclass + class Options: + # specific literals + OptsAlgo = Literal[ + "discrete_gradient", + "explicit", + ] + # propagator options + b_tilde: FEECVariable = None + ep_scale: float = 1.0 + algo: OptsAlgo = "explicit" + butcher: ButcherTableau = None + u_space: OptsVecSpace = "Hdiv" + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + filter_params: FilterParameters = None + dg_solver_params: DiscreteGradientSolverParameters = None - self._unit_b1 = self._E1T.dot(self._unit_b1) - self._unit_b2 = self._E2T.dot(self._unit_b2) - self._curl_norm_b = self._E2T.dot(self._curl_norm_b) - self._absB0 = self._E0T.dot(self._absB0) + def __post_init__(self): + # checks + check_option(self.algo, self.OptsAlgo) + check_option(self.u_space, OptsVecSpace) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + assert isinstance(self.b_tilde, FEECVariable) + assert isinstance(self.ep_scale, float) - _A = getattr(self.mass_ops, "M" + u_id + "n") + # defaults + if self.algo == "explicit" and self.butcher is None: + self.butcher = ButcherTableau() - # preconditioner - if solver["type"][1] is None: + if self.algo == "discrete_gradient" and self.dg_solver_params is None: + self.dg_solver_params = DiscreteGradientSolverParameters() + + if self.solver_params is None: + self.solver_params = SolverParameters() + + if self.filter_params is None: + self.filter_params = FilterParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + if self.options.u_space == "H1vec": + self._u_form_int = 0 + else: + self._u_form_int = int(self.derham.space_to_form[self.options.u_space]) + + # call operatros + id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n" + self._A = getattr(self.mass_ops, id_M) + self._PB = getattr(self.basis_ops, "PB") + + # Preconditioner + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) - pc = pc_class(_A) + pc_class = getattr(preconditioner, self.options.precond) + pc = pc_class(getattr(self.mass_ops, id_M)) - self._solver = inverse( - _A, - solver["type"][0], + # linear solver + self._A_inv = inverse( + self._A, + self.options.solver, pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], - recycle=solver["recycle"], + tol=self.options.solver_params.tol, + maxiter=self.options.solver_params.maxiter, + verbose=self.options.solver_params.verbose, ) + # magnetic equilibrium field + unit_b1 = self.projected_equil.unit_b1 + curl_unit_b1 = self.projected_equil.curl_unit_b1 + self._b2 = self.projected_equil.b2 + gradB1 = self.projected_equil.gradB1 + absB0 = self.projected_equil.absB0 + + # magnetic field + self._b_tilde = self.options.b_tilde.spline.vector + + # scaling factor + epsilon = self.variables.energetic_ions.species.equation_params.epsilon + + if self.options.algo == "explicit": + # temporary vectors to avoid memory allocation + self._b_full = self._b2.space.zeros() + self._u_new = self.variables.u.spline.vector.space.zeros() + self._u_temp = self.variables.u.spline.vector.space.zeros() + self._ku = self.variables.u.spline.vector.space.zeros() + self._PB_b = self._PB.codomain.zeros() + self._grad_PB_b = self.derham.grad.codomain.zeros() + + # define Accumulator and arguments + self._ACC = Accumulator( + self.variables.energetic_ions.particles, + self.options.u_space, + Pyccelkernel(accum_kernels_gc.cc_lin_mhd_5d_gradB), + self.mass_ops, + self.domain.args_domain, + add_vector=True, + symmetry="symm", + filter_params=self.options.filter_params, + ) - # Call the accumulation and Pusher class - self._ACC = Accumulator( - particles, - u_space, - accum_kernels_gc.cc_lin_mhd_5d_J2, - self.mass_ops, - self.domain.args_domain, - add_vector=True, - symmetry="symm", - filter_params=filter, - ) + self._args_accum_kernel = ( + epsilon, + self.options.ep_scale, + self._b_full[0]._data, + self._b_full[1]._data, + self._b_full[2]._data, + unit_b1[0]._data, + unit_b1[1]._data, + unit_b1[2]._data, + curl_unit_b1[0]._data, + curl_unit_b1[1]._data, + curl_unit_b1[2]._data, + self._grad_PB_b[0]._data, + self._grad_PB_b[1]._data, + self._grad_PB_b[2]._data, + self._u_form_int, + ) - # if self.particles[0].control_variate: + # define Pusher + if self.options.u_space == "Hdiv": + self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_stage_Hdiv + elif self.options.u_space == "H1vec": + self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_stage_H1vec + else: + raise ValueError( + f'{self.options.u_space =} not valid, choose from "Hdiv" or "H1vec.', + ) - # # control variate method is only valid with Maxwellian distributions - # assert isinstance(self.particles[0].f0, Maxwellian) - # assert params['u_space'] == 'Hdiv' + # temp fix due to refactoring of ButcherTableau: + butcher = self.options.butcher + import numpy as np - # self._ACC.init_control_variate(self.mass_ops) + butcher._a = xp.diag(butcher.a, k=-1) + butcher._a = xp.array(list(butcher.a) + [0.0]) - # # evaluate and save n0 at quadrature points - # quad_pts = [quad_grid[nquad].points.flatten() - # for quad_grid, nquad in zip(self.derham.get_quad_grids(self.derham.Vh_fem['0']), self.derham.nquads)] + self._args_pusher_kernel = ( + self.domain.args_domain, + self.derham.args_derham, + epsilon, + self._b_full[0]._data, + self._b_full[1]._data, + self._b_full[2]._data, + unit_b1[0]._data, + unit_b1[1]._data, + unit_b1[2]._data, + curl_unit_b1[0]._data, + curl_unit_b1[1]._data, + curl_unit_b1[2]._data, + self._u_temp[0]._data, + self._u_temp[1]._data, + self._u_temp[2]._data, + self.options.butcher.a, + self.options.butcher.b, + self.options.butcher.c, + ) - # self._n0_at_quad = self.domain.push( - # self.particles[0].f0.n, *quad_pts, kind='0', squeeze_out=False) + else: + # temporary vectors to avoid memory allocation + self._b_full = self._b2.space.zeros() + self._PB_b = self._PB.codomain.zeros() + self._grad_PB_b = self.derham.grad.codomain.zeros() + self._u_old = self.variables.u.spline.vector.space.zeros() + self._u_new = self.variables.u.spline.vector.space.zeros() + self._u_diff = self.variables.u.spline.vector.space.zeros() + self._u_mid = self.variables.u.spline.vector.space.zeros() + self._M2n_dot_u = self.variables.u.spline.vector.space.zeros() + self._ku = self.variables.u.spline.vector.space.zeros() + self._u_temp = self.variables.u.spline.vector.space.zeros() + + # Call the accumulation and Pusher class + accum_kernel_init = accum_kernels_gc.cc_lin_mhd_5d_gradB_dg_init + accum_kernel = accum_kernels_gc.cc_lin_mhd_5d_gradB_dg + self._accum_kernel_en_fB_mid = utilities_kernels.eval_gradB_ediff + + self._args_accum_kernel = ( + epsilon, + self.options.ep_scale, + self._b_tilde[0]._data, + self._b_tilde[1]._data, + self._b_tilde[2]._data, + self._b2[0]._data, + self._b2[1]._data, + self._b2[2]._data, + unit_b1[0]._data, + unit_b1[1]._data, + unit_b1[2]._data, + curl_unit_b1[0]._data, + curl_unit_b1[1]._data, + curl_unit_b1[2]._data, + self._grad_PB_b[0]._data, + self._grad_PB_b[1]._data, + self._grad_PB_b[2]._data, + gradB1[0]._data, + gradB1[1]._data, + gradB1[2]._data, + self._u_form_int, + ) - # # evaluate unit_b1 (1form) dot epsilon * u0_parallel * curl_norm_b/|det(DF)| at quadrature points - # quad_pts_array = self.domain.prepare_eval_pts(*quad_pts)[:3] + self._args_accum_kernel_en_fB_mid = ( + self.domain.args_domain, + self.derham.args_derham, + gradB1[0]._data, + gradB1[1]._data, + gradB1[2]._data, + self._grad_PB_b[0]._data, + self._grad_PB_b[1]._data, + self._grad_PB_b[2]._data, + ) - # u0_parallel_at_quad = self.particles[0].f0.u( - # *quad_pts_array)[0] + self._ACC_init = AccumulatorVector( + self.variables.energetic_ions.particles, + self.options.u_space, + accum_kernel_init, + self.mass_ops, + self.domain.args_domain, + filter_params=self.options.filter_params, + ) - # vth_perp = self.particles[0].f0.vth(*quad_pts_array)[1] + self._ACC = AccumulatorVector( + self.variables.energetic_ions.particles, + self.options.u_space, + accum_kernel, + self.mass_ops, + self.domain.args_domain, + filter_params=self.options.filter_params, + ) - # absB0_at_quad = WeightedMassOperator.eval_quad( - # self.derham.Vh_fem['0'], self._absB0) + self._args_pusher_kernel_init = ( + self.domain.args_domain, + self.derham.args_derham, + epsilon, + self._b_full[0]._data, + self._b_full[1]._data, + self._b_full[2]._data, + unit_b1[0]._data, + unit_b1[1]._data, + unit_b1[2]._data, + curl_unit_b1[0]._data, + curl_unit_b1[1]._data, + curl_unit_b1[2]._data, + self.variables.u.spline.vector[0]._data, + self.variables.u.spline.vector[1]._data, + self.variables.u.spline.vector[2]._data, + ) - # self._det_df_at_quad = self.domain.jacobian_det( - # *quad_pts, squeeze_out=False) + self._args_pusher_kernel = ( + self.domain.args_domain, + self.derham.args_derham, + epsilon, + self._b_full[0]._data, + self._b_full[1]._data, + self._b_full[2]._data, + unit_b1[0]._data, + unit_b1[1]._data, + unit_b1[2]._data, + curl_unit_b1[0]._data, + curl_unit_b1[1]._data, + curl_unit_b1[2]._data, + self._u_mid[0]._data, + self._u_mid[1]._data, + self._u_mid[2]._data, + self._u_temp[0]._data, + self._u_temp[1]._data, + self._u_temp[2]._data, + ) - # self._unit_b1_at_quad = WeightedMassOperator.eval_quad( - # self.derham.Vh_fem['1'], self._unit_b1) + self._pusher_kernel_init = pusher_kernels_gc.push_gc_cc_J2_dg_init_Hdiv + self._pusher_kernel = pusher_kernels_gc.push_gc_cc_J2_dg_Hdiv - # curl_norm_b_at_quad = WeightedMassOperator.eval_quad( - # self.derham.Vh_fem['2'], self._curl_norm_b) + def __call__(self, dt): + # current FE coeffs + un = self.variables.u.spline.vector - # self._unit_b1_dot_curl_norm_b_at_quad = np.sum( - # p * q for p, q in zip(self._unit_b1_at_quad, curl_norm_b_at_quad)) + # particle markers and idx + particles = self.variables.energetic_ions.particles + holes = particles.holes + args_markers = particles.args_markers + markers = args_markers.markers + first_init_idx = args_markers.first_init_idx + first_free_idx = args_markers.first_free_idx - # self._unit_b1_dot_curl_norm_b_at_quad /= self._det_df_at_quad - # self._unit_b1_dot_curl_norm_b_at_quad *= self._epsilon - # self._unit_b1_dot_curl_norm_b_at_quad *= u0_parallel_at_quad + # clear buffer + markers[:, first_init_idx:-2] = 0.0 - # # precalculate constant 2 * f0.vth_perp² / B0 * f0.n for control MAT and VEC - # self._control_const = vth_perp**2 / absB0_at_quad * self._n0_at_quad + # save old marker positions + markers[:, first_init_idx : first_init_idx + 3] = markers[:, :3] - # # assemble the matrix (G_inv)(unit_b1 x)(G_inv) - # G_inv_at_quad = self.domain.metric_inv( - # *quad_pts, squeeze_out=False) + # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) + b_full = self._b2.copy(out=self._b_full) - # self._G_inv_bx_G_inv_at_quad = [[np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad)], - # [np.zeros_like(self._n0_at_quad), np.zeros_like( - # self._n0_at_quad), np.zeros_like(self._n0_at_quad)], - # [np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad)]] + b_full += self._b_tilde + b_full.update_ghost_regions() - # for j in range(3): - # temp = (-self._unit_b1_at_quad[2]*G_inv_at_quad[1, j] + self._unit_b1_at_quad[1]*G_inv_at_quad[2, j], - # self._unit_b1_at_quad[2]*G_inv_at_quad[0, j] - - # self._unit_b1_at_quad[0]*G_inv_at_quad[2, j], - # -self._unit_b1_at_quad[1]*G_inv_at_quad[0, j] + self._unit_b1_at_quad[0]*G_inv_at_quad[1, j]) + if self.options.algo == "explicit": + PB_b = self._PB.dot(b_full, out=self._PB_b) + grad_PB_b = self.derham.grad.dot(PB_b, out=self._grad_PB_b) + grad_PB_b.update_ghost_regions() - # for i in range(3): - # self._G_inv_bx_G_inv_at_quad[i][j] = np.sum( - # p * q for p, q in zip(G_inv_at_quad[i], temp[:])) + # save old u + u_new = un.copy(out=self._u_new) - # # memory allocation of magnetic field at quadrature points - # self._b_at_quad = [np.zeros_like(self._n0_at_quad), - # np.zeros_like(self._n0_at_quad), - # np.zeros_like(self._n0_at_quad)] + for stage in range(self.options.butcher.n_stages): + # accumulate + self._ACC( + *self._args_accum_kernel, + ) - # # memory allocation of parallel magnetic field at quadrature points - # self._B_para_at_quad = np.zeros_like(self._n0_at_quad) + # push particles + self._pusher_kernel( + dt, + stage, + args_markers, + *self._args_pusher_kernel, + ) - # # memory allocation of gradient of parallel magnetic field at quadrature points - # self._grad_PBb_at_quad = (np.zeros_like(self._n0_at_quad), - # np.zeros_like(self._n0_at_quad), - # np.zeros_like(self._n0_at_quad)) - # # memory allocation for temporary matrix - # self._temp = [[np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad)], - # [np.zeros_like(self._n0_at_quad), np.zeros_like( - # self._n0_at_quad), np.zeros_like(self._n0_at_quad)], - # [np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad), np.zeros_like(self._n0_at_quad)]] + if particles.mpi_comm is not None: + particles.mpi_sort_markers() + else: + particles.apply_kinetic_bc() - # # memory allocation for control VEC - # self._vec1 = np.zeros_like(self._n0_at_quad) - # self._vec2 = np.zeros_like(self._n0_at_quad) - # self._vec3 = np.zeros_like(self._n0_at_quad) + # solve linear system for updating u coefficients + ku = self._A_inv.dot(self._ACC.vectors[0], out=self._ku) + info = self._A_inv._info - # choose algorithm - self._butcher = ButcherTableau(algo) - # temp fix due to refactoring of ButcherTableau: - self._butcher._a = np.diag(self._butcher.a, k=-1) - self._butcher._a = np.array(list(self._butcher.a) + [0.0]) + # calculate u^{n+1}_k + u_temp = un.copy(out=self._u_temp) + u_temp += ku * dt * self.options.butcher.a[stage] - # instantiate Pusher - if u_space == "Hdiv": - kernel = pusher_kernels_gc.push_gc_cc_J2_stage_Hdiv - elif u_space == "H1vec": - kernel = pusher_kernels_gc.push_gc_cc_J2_stage_H1vec - else: - raise ValueError( - f'{u_space = } not valid, choose from "Hdiv" or "H1vec.', - ) + u_temp.update_ghost_regions() - args_kernel = (self.derham.args_derham,) + # calculate u^{n+1} + u_new += ku * dt * self.options.butcher.b[stage] - self._pusher = Pusher( - particles, - kernel, - args_kernel, - self.domain.args_domain, - alpha_in_kernel=1.0, - ) + if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: + print("Stage: ", stage) + print("Status for CurrentCoupling5DGradB:", info["success"]) + print("Iterations for CurrentCoupling5DGradB:", info["niter"]) + print() - # temporary vectors to avoid memory allocation - self._b_full1 = self._b_eq.space.zeros() - self._b_full2 = self._E2T.codomain.zeros() - self._u_new = u.space.zeros() - self._Eu_new = self._EuT.codomain.zeros() - self._u_temp1 = u.space.zeros() - self._u_temp2 = u.space.zeros() - self._Eu_temp = self._EuT.codomain.zeros() - self._tmp1 = self._E0T.codomain.zeros() - self._tmp2 = self._gradB1.space.zeros() - self._tmp3 = self._E1T.codomain.zeros() + # update u coefficients + diffs = self.update_feec_variables(u=u_new) - def __call__(self, dt): - un = self.feec_vars[0] + # clear the buffer + markers[:, first_init_idx:-2] = 0.0 - # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) - b_full = self._b_eq.copy(out=self._b_full1) + # update_weights + if self.variables.energetic_ions.species.weights_params.control_variate: + particles.update_weights() - if self._b is not None: - self._b_full1 += self._b + if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: + print("Maxdiff up for CurrentCoupling5DGradB:", diffs["u"]) + print() - PBb = self._PB.dot(self._b, out=self._tmp1) - grad_PBb = self.derham.grad.dot(PBb, out=self._tmp2) - grad_PBb += self._gradB1 + else: + # total number of markers + n_mks_tot = particles.Np + + # relaxation factor + alpha = self.options.dg_solver_params.relaxation_factor + + # eval parallel tilde b and its gradient + PB_b = self._PB.dot(self._b_tilde, out=self._PB_b) + PB_b.update_ghost_regions() + grad_PB_b = self.derham.grad.dot(PB_b, out=self._grad_PB_b) + grad_PB_b.update_ghost_regions() + + # save old u + u_old = un.copy(out=self._u_old) + u_new = un.copy(out=self._u_new) + + # save en_U_old + self._A.dot(un, out=self._M2n_dot_u) + en_U_old = un.inner(self._M2n_dot_u) / 2.0 + + # save en_fB_old + particles.save_magnetic_energy(PB_b) + en_fB_old = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale + en_fB_old /= n_mks_tot + + buffer_array = xp.array([en_fB_old]) + + if particles.mpi_comm is not None: + particles.mpi_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) - Eb_full = self._E2T.dot(b_full, out=self._b_full2) - Eb_full.update_ghost_regions() + if particles.clone_config is not None: + particles.clone_config.inter_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) - Egrad_PBb = self._E1T.dot(grad_PBb, out=self._tmp3) - Egrad_PBb.update_ghost_regions() + en_fB_old = buffer_array[0] + en_tot_old = en_U_old + en_fB_old - # perform accumulation (either with or without control variate) - # if self.particles[0].control_variate: + # initial guess + self._ACC_init(*self._args_accum_kernel) - # # evaluate magnetic field at quadrature points (in-place) - # WeightedMassOperator.eval_quad(self.derham.Vh_fem['2'], self._b_full2, - # out=[self._b_at_quad[0], self._b_at_quad[1], self._b_at_quad[2]]) - - # # evaluate B_parallel - # self._B_para_at_quad = np.sum( - # p * q for p, q in zip(self._unit_b1_at_quad, self._b_at_quad)) - # self._B_para_at_quad += self._unit_b1_dot_curl_norm_b_at_quad - - # # evaluate grad B_parallel - # WeightedMassOperator.eval_quad(self.derham.Vh_fem['1'], self._tmp3, - # out=[self._grad_PBb_at_quad[0], self._grad_PBb_at_quad[1], self._grad_PBb_at_quad[2]]) - - # # assemble temp = (B x)(G_inv)(unit_b1 x)(G_inv) - # for i in range(3): - # self._temp[0][i] = -self._b_at_quad[2]*self._G_inv_bx_G_inv_at_quad[1][i] + \ - # self._b_at_quad[1]*self._G_inv_bx_G_inv_at_quad[2][i] - # self._temp[1][i] = +self._b_at_quad[2]*self._G_inv_bx_G_inv_at_quad[0][i] - \ - # self._b_at_quad[0]*self._G_inv_bx_G_inv_at_quad[2][i] - # self._temp[2][i] = -self._b_at_quad[1]*self._G_inv_bx_G_inv_at_quad[0][i] + \ - # self._b_at_quad[0]*self._G_inv_bx_G_inv_at_quad[1][i] - - # # assemble (temp)(grad B_parallel) / B_star_para * 2 * f0.vth_perp² / B0 * f0.n - # self._vec1[:, :, :] = np.sum(p * q for p, q in zip(self._temp[0][:], self._grad_PBb_at_quad)) * \ - # self._control_const * self._coupling_vec / self._B_para_at_quad - # self._vec2[:, :, :] = np.sum(p * q for p, q in zip(self._temp[1][:], self._grad_PBb_at_quad)) * \ - # self._control_const * self._coupling_vec / self._B_para_at_quad - # self._vec3[:, :, :] = np.sum(p * q for p, q in zip(self._temp[2][:], self._grad_PBb_at_quad)) * \ - # self._control_const * self._coupling_vec / self._B_para_at_quad - - # save old u - _u_new = un.copy(out=self._u_new) - _u_temp = un.copy(out=self._u_temp1) + ku = self._A_inv.dot(self._ACC_init.vectors[0], out=self._ku) + u_new += ku * dt - # save old marker positions - self.particles[0].markers[ - ~self.particles[0].holes, - 11:14, - ] = self.particles[0].markers[~self.particles[0].holes, 0:3] - - for stage in range(self._butcher.n_stages): - # accumulate RHS - # if self.particles[0].control_variate: - # self._ACC.accumulate(self.particles[0], self._epsilon, - # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._unit_b2[0]._data, self._unit_b2[1]._data, self._unit_b2[2]._data, - # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, - # Egrad_PBb[0]._data, Egrad_PBb[1]._data, Egrad_PBb[2]._data, - # self._space_key_int, self._coupling_mat, self._coupling_vec, 0., - # control_vec=[self._vec1, self._vec2, self._vec3]) - # else: - # self._ACC.accumulate(self.particles[0], self._epsilon, - # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._unit_b2[0]._data, self._unit_b2[1]._data, self._unit_b2[2]._data, - # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, - # Egrad_PBb[0]._data, Egrad_PBb[1]._data, Egrad_PBb[2]._data, - # self._space_key_int, self._coupling_mat, self._coupling_vec, 0.) - - self._ACC( - self._epsilon, - Eb_full[0]._data, - Eb_full[1]._data, - Eb_full[2]._data, - self._unit_b1[0]._data, - self._unit_b1[1]._data, - self._unit_b1[2]._data, - self._unit_b2[0]._data, - self._unit_b2[1]._data, - self._unit_b2[2]._data, - self._curl_norm_b[0]._data, - self._curl_norm_b[1]._data, - self._curl_norm_b[2]._data, - Egrad_PBb[0]._data, - Egrad_PBb[1]._data, - Egrad_PBb[2]._data, - self._space_key_int, - self._coupling_mat, - self._coupling_vec, - self._boundary_cut_e1, - ) + u_new.update_ghost_regions() - # push particles - Eu = self._EuT.dot(_u_temp, out=self._Eu_temp) - Eu.update_ghost_regions() + # save en_U_new + self._A.dot(u_new, out=self._M2n_dot_u) + en_U_new = u_new.inner(self._M2n_dot_u) / 2.0 - self._pusher.kernel( + # push eta + self._pusher_kernel_init( dt, - stage, - self.particles[0].args_markers, - self.domain.args_domain, - self.derham.args_derham, - self._epsilon, - Eb_full[0]._data, - Eb_full[1]._data, - Eb_full[2]._data, - self._unit_b1[0]._data, - self._unit_b1[1]._data, - self._unit_b1[2]._data, - self._unit_b2[0]._data, - self._unit_b2[1]._data, - self._unit_b2[2]._data, - self._curl_norm_b[0]._data, - self._curl_norm_b[1]._data, - self._curl_norm_b[2]._data, - Eu[0]._data, - Eu[1]._data, - Eu[2]._data, - self._butcher.a, - self._butcher.b, - self._butcher.c, - self._boundary_cut_e1, + args_markers, + *self._args_pusher_kernel_init, ) - self.particles[0].mpi_sort_markers() + if particles.mpi_comm is not None: + particles.mpi_sort_markers(apply_bc=False) - # solve linear system for updated u coefficients - _ku = self._solver.dot(self._ACC.vectors[0], out=self._u_temp2) + # save en_fB_new + particles.save_magnetic_energy(PB_b) + en_fB_new = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale + en_fB_new /= n_mks_tot - # calculate u^{n+1}_k - _u_temp = un.copy(out=self._u_temp1) - _u_temp += _ku * dt * self._butcher.a[stage] + buffer_array = xp.array([en_fB_new]) - # calculate u^{n+1} - _u_new += _ku * dt * self._butcher.b[stage] + if particles.mpi_comm is not None: + particles.mpi_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) - if self._info and self._rank == 0: - print("Stage:", stage) - print( - "Status for CurrentCoupling5DGradB:", - self._solver._info["success"], + if particles.clone_config is not None: + particles.clone_config.inter_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, ) - print( - "Iterations for CurrentCoupling5DGradB:", - self._solver._info["niter"], + + en_fB_new = buffer_array[0] + + # fixed-point iterations + iter_num = 0 + + while True: + iter_num += 1 + + if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0: + print("# of iteration: ", iter_num) + + # calculate discrete gradient + # save u^{n+1, k} + u_old = u_new.copy(out=self._u_old) + + u_diff = u_old.copy(out=self._u_diff) + u_diff -= un + u_diff.update_ghost_regions() + + u_mid = u_old.copy(out=self._u_mid) + u_mid += un + u_mid /= 2.0 + u_mid.update_ghost_regions() + + # save H^{n+1, k} + markers[~holes, first_free_idx : first_free_idx + 3] = markers[~holes, 0:3] + + # calculate denominator ||z^{n+1, k} - z^n||^2 + sum_u_diff_loc = xp.sum((u_diff.toarray() ** 2)) + + sum_H_diff_loc = xp.sum( + (markers[~holes, :3] - markers[~holes, first_init_idx : first_init_idx + 3]) ** 2, ) - # clear the buffer - if stage == self._butcher.n_stages - 1: - self.particles[0].markers[ - ~self.particles[0].holes, - 11:-1, - ] = 0.0 + buffer_array = xp.array([sum_u_diff_loc]) - # write new coeffs into Propagator.variables - (max_du,) = self.feec_vars_update(_u_new) + if particles.mpi_comm is not None: + particles.mpi_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) - # update_weights - if self.particles[0].control_variate: - self.particles[0].update_weights() + denominator = buffer_array[0] - if self._info and self._rank == 0: - print("Maxdiff up for CurrentCoupling5DGradB:", max_du) - print() + buffer_array = xp.array([sum_H_diff_loc]) + + if particles.mpi_comm is not None: + particles.mpi_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + if particles.clone_config is not None: + particles.clone_config.inter_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + denominator += buffer_array[0] + + # sorting markers at mid-point + if particles.mpi_comm is not None: + particles.mpi_sort_markers(apply_bc=False, alpha=0.5) + + self._accum_kernel_en_fB_mid( + args_markers, + *self._args_accum_kernel_en_fB_mid, + first_free_idx + 3, + ) + en_fB_mid = xp.sum(markers[~holes, first_free_idx + 3].dot(markers[~holes, 5])) * self.options.ep_scale + + en_fB_mid /= n_mks_tot + + buffer_array = xp.array([en_fB_mid]) + + if particles.mpi_comm is not None: + particles.mpi_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + if particles.clone_config is not None: + particles.clone_config.inter_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + en_fB_mid = buffer_array[0] + + if denominator == 0.0: + const = 0.0 + else: + const = (en_fB_new - en_fB_old - en_fB_mid) / denominator + + # update u^{n+1, k} + self._ACC(*self._args_accum_kernel, const) + + ku = self._A_inv.dot(self._ACC.vectors[0], out=self._ku) + + u_new = un.copy(out=self._u_new) + u_new += ku * dt + u_new *= alpha + u_new += u_old * (1.0 - alpha) + + u_new.update_ghost_regions() + + # update en_U_new + self._A.dot(u_new, out=self._M2n_dot_u) + en_U_new = u_new.inner(self._M2n_dot_u) / 2.0 + + # update H^{n+1, k} + self._pusher_kernel( + dt, + args_markers, + *self._args_pusher_kernel, + const, + alpha, + ) + + sum_H_diff_loc = xp.sum( + xp.abs(markers[~holes, 0:3] - markers[~holes, first_free_idx : first_free_idx + 3]), + ) + + if particles.mpi_comm is not None: + particles.mpi_sort_markers(apply_bc=False) + + # update en_fB_new + particles.save_magnetic_energy(PB_b) + en_fB_new = xp.sum(markers[~holes, 8].dot(markers[~holes, 5])) * self.options.ep_scale + en_fB_new /= n_mks_tot + + buffer_array = xp.array([en_fB_new]) + + if particles.mpi_comm is not None: + particles.mpi_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + if particles.clone_config is not None: + particles.clone_config.inter_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + en_fB_new = buffer_array[0] + + # calculate total energy difference + e_diff = xp.abs(en_U_new + en_fB_new - en_tot_old) + + # calculate ||z^{n+1, k} - z^{n+1, k-1|| + sum_u_diff_loc = xp.sum(xp.abs(u_new.toarray() - u_old.toarray())) + + buffer_array = xp.array([sum_u_diff_loc]) + + if particles.mpi_comm is not None: + particles.mpi_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + diff = buffer_array[0] + + buffer_array = xp.array([sum_H_diff_loc]) + + if particles.mpi_comm is not None: + particles.mpi_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + if particles.clone_config is not None: + particles.clone_config.inter_comm.Allreduce( + MPI.IN_PLACE, + buffer_array, + op=MPI.SUM, + ) + + diff += buffer_array[0] + + # check convergence + if diff < self.options.dg_solver_params.tol: + if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0: + print("converged diff: ", diff) + print("converged e_diff: ", e_diff) + + if particles.mpi_comm is not None: + particles.mpi_comm.Barrier() + break + + else: + if self.options.dg_solver_params.verbose and MPI.COMM_WORLD.Get_rank() == 0: + print("not converged diff: ", diff) + print("not converged e_diff: ", e_diff) + + if iter_num == self.options.dg_solver_params.maxiter: + if self.options.dg_solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: + print( + f"{iter_num =}, maxiter={self.options.dg_solver_params.maxiter} reached! diff: {diff}, e_diff: {e_diff}", + ) + if particles.mpi_comm is not None: + particles.mpi_comm.Barrier() + break + + # sorting markers + if particles.mpi_comm is not None: + particles.mpi_sort_markers() + else: + particles.apply_kinetic_bc() + + # update u coefficients + diffs = self.update_feec_variables(u=u_new) + + # clear the buffer + markers[:, first_init_idx:-2] = 0.0 + + # update_weights + if self.variables.energetic_ions.species.weights_params.control_variate: + particles.update_weights() + + if self.options.dg_solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: + print("Maxdiff up for CurrentCoupling5DGradB:", diffs["u"]) + print() diff --git a/src/struphy/propagators/propagators_fields.py b/src/struphy/propagators/propagators_fields.py index a41170a34..462c58f26 100644 --- a/src/struphy/propagators/propagators_fields.py +++ b/src/struphy/propagators/propagators_fields.py @@ -1,18 +1,17 @@ "Only FEEC variables are updated." import copy -from collections.abc import Callable from copy import deepcopy from dataclasses import dataclass -from typing import Literal, get_args +from typing import Callable, Literal, get_args -import numpy as np +import cunumpy as xp import scipy as sc from line_profiler import profile from matplotlib import pyplot as plt -from mpi4py import MPI from numpy import zeros from psydac.api.essential_bc import apply_essential_bc_stencil +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.basic import ComposedLinearOperator, IdentityOperator, ZeroOperator from psydac.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace from psydac.linalg.solvers import inverse @@ -29,34 +28,47 @@ ) from struphy.feec.linear_operators import BoundaryOperator from struphy.feec.mass import WeightedMassOperator, WeightedMassOperators -from struphy.feec.preconditioner import MassMatrixPreconditioner +from struphy.feec.preconditioner import MassMatrixDiagonalPreconditioner, MassMatrixPreconditioner from struphy.feec.projectors import L2Projector from struphy.feec.psydac_derham import Derham, SplineFunction from struphy.feec.variational_utilities import ( BracketOperator, - H1vecMassMatrix_density, + Hdiv0_transport_operator, InternalEnergyEvaluator, KineticEnergyEvaluator, + Pressure_transport_operator, ) from struphy.fields_background.equils import set_defaults from struphy.geometry.utilities import TransformedPformComponent from struphy.initial import perturbations -from struphy.io.options import OptsGenSolver, OptsMassPrecond, OptsSymmSolver, OptsVecSpace, check_option +from struphy.io.options import ( + OptsDirectSolver, + OptsGenSolver, + OptsMassPrecond, + OptsNonlinearSolver, + OptsSaddlePointSolver, + OptsSymmSolver, + OptsVecSpace, + check_option, +) from struphy.io.setup import descend_options_dict from struphy.kinetic_background.base import Maxwellian from struphy.kinetic_background.maxwellians import GyroMaxwellian2D, Maxwellian3D from struphy.linear_algebra.saddle_point import SaddlePointSolver -from struphy.linear_algebra.schur_solver import SchurSolver -from struphy.linear_algebra.solver import SolverParameters +from struphy.linear_algebra.schur_solver import SchurSolver, SchurSolverFull +from struphy.linear_algebra.solver import NonlinearSolverParameters, SolverParameters +from struphy.models.species import Species from struphy.models.variables import FEECVariable, PICVariable, SPHVariable, Variable from struphy.ode.solvers import ODEsolverFEEC from struphy.ode.utils import ButcherTableau, OptsButcher from struphy.pic.accumulation import accum_kernels, accum_kernels_gc +from struphy.pic.accumulation.filter import FilterParameters from struphy.pic.accumulation.particles_to_grid import Accumulator, AccumulatorVector from struphy.pic.base import Particles from struphy.pic.particles import Particles5D, Particles6D from struphy.polar.basic import PolarVector from struphy.propagators.base import Propagator +from struphy.utils.pyccel import Pyccelkernel class Maxwell(Propagator): @@ -272,39 +284,71 @@ class OhmCold(Propagator): \end{bmatrix} \,. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - if default: - dct = descend_options_dict(dct, []) + class Variables: + def __init__(self): + self._j: FEECVariable = None + self._e: FEECVariable = None - return dct + @property + def j(self) -> FEECVariable: + return self._j - def __init__( - self, - j: BlockVector, - e: BlockVector, - *, - alpha: float = 1.0, - epsilon: float = 1.0, - solver: dict = options(default=True)["solver"], - ): - super().__init__(e, j) + @j.setter + def j(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hcurl" + self._j = new + + @property + def e(self) -> FEECVariable: + return self._e + + @e.setter + def e(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hcurl" + self._e = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + + def __post_init__(self): + # checks + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + self._info = self.options.solver_params.info - self._info = solver["info"] - self._alpha = alpha - self._epsilon = epsilon + self._alpha = self.variables.j.species.equation_params.alpha + self._epsilon = self.variables.j.species.equation_params.epsilon # Define block matrix [[A B], [C I]] (without time step size dt in the diagonals) _A = self.mass_ops.M1ninv @@ -312,10 +356,10 @@ def __init__( self._B = -1 / 2 * 1 / self._epsilon * self.mass_ops.M1 # no dt # Preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(self.mass_ops.M1ninv) # Instantiate Schur solver (constant in this case) @@ -324,13 +368,14 @@ def __init__( self._schur_solver = SchurSolver( _A, _BC, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], + self.options.solver, + precond=pc, + solver_params=self.options.solver_params, ) + j = self.variables.j.spline.vector + e = self.variables.e.spline.vector + self._tmp_j1 = j.space.zeros() self._tmp_j2 = j.space.zeros() self._tmp_e1 = e.space.zeros() @@ -338,8 +383,8 @@ def __init__( def __call__(self, dt): # current variables - en = self.feec_vars[0] - jn = self.feec_vars[1] + jn = self.variables.j.spline.vector + en = self.variables.e.spline.vector # in-place solution (no tmps created here) Ben = self._B.dot(en, out=self._tmp_e1) @@ -353,13 +398,13 @@ def __call__(self, dt): en1 += en # write new coeffs into Propagator.variables - max_de, max_dj = self.feec_vars_update(en1, jn1) + diffs = self.update_feec_variables(e=en1, j=jn1) if self._info: print("Status for OhmCold:", info["success"]) print("Iterations for OhmCold:", info["niter"]) - print("Maxdiff e1 for OhmCold:", max_de) - print("Maxdiff j1 for OhmCold:", max_dj) + print("Maxdiff e1 for OhmCold:", diffs["e"]) + print("Maxdiff j1 for OhmCold:", diffs["j"]) print() @@ -379,65 +424,90 @@ class JxBCold(Propagator): \mathbb M_{1/n_0} \left( \mathbf j^{n+1} - \mathbf j^n \right) = \frac{\Delta t}{2} \frac{1}{\varepsilon} \mathbb M_{B_0/n_0} \left( \mathbf j^{n+1} - \mathbf j^n \right)\,. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - if default: - dct = descend_options_dict(dct, []) + class Variables: + def __init__(self): + self._j: FEECVariable = None - return dct + @property + def j(self) -> FEECVariable: + return self._j - def __init__( - self, - j: BlockVector, - *, - epsilon: float = 1.0, - solver: dict = options(default=True)["solver"], - ): - super().__init__(j) + @j.setter + def j(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hcurl" + self._j = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + + def __post_init__(self): + # checks + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - self._info = solver["info"] + @profile + def allocate(self): + self._info = self.options.solver_params.info + + epsilon = self.variables.j.species.equation_params.epsilon # mass matrix in system (M - dt/2 * A)*j^(n + 1) = (M + dt/2 * A)*j^n self._M = self.mass_ops.M1ninv self._A = -1 / epsilon * self.mass_ops.M1Bninv # no dt # Preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(self.mass_ops.M1ninv) # Instantiate linear solver self._solver = inverse( self._M, - solver["type"][0], + self.options.solver, pc=pc, - x0=self.feec_vars[0], - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], + x0=self.variables.j.spline.vector, + tol=self.options.solver_params.tol, + maxiter=self.options.solver_params.maxiter, + verbose=self.options.solver_params.verbose, ) # allocate dummy vectors to avoid temporary array allocations self._rhs_j = self._M.codomain.zeros() - self._j_new = j.space.zeros() + self._j_new = self.variables.j.spline.vector.space.zeros() + @profile def __call__(self, dt): # current variables - jn = self.feec_vars[0] + jn = self.variables.j.spline.vector # define system (M - dt/2 * A)*b^(n + 1) = (M + dt/2 * A)*b^n lhs = self._M - dt / 2.0 * self._A @@ -452,7 +522,7 @@ def __call__(self, dt): info = self._solver._info # write new coeffs into Propagator.variables - max_dj = self.feec_vars_update(jn1)[0] + max_dj = self.update_feec_variables(j=jn1) if self._info: print("Status for FluidCold:", info["success"]) @@ -688,62 +758,91 @@ class ShearAlfvenB1(Propagator): the MHD equilibirum density. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["solver_M1"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - if default: - dct = descend_options_dict(dct, []) + class Variables: + def __init__(self): + self._u: FEECVariable = None + self._b: FEECVariable = None - return dct + @property + def u(self) -> FEECVariable: + return self._u - def __init__( - self, - u: BlockVector, - b: BlockVector, - *, - solver: dict = options(default=True)["solver"], - solver_M1: dict = options(default=True)["solver_M1"], - ): - super().__init__(u, b) + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hdiv") + self._u = new + + @property + def b(self) -> FEECVariable: + return self._b + + @b.setter + def b(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hcurl" + self._b = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + solver_M1: OptsSymmSolver = "pcg" + precond_M1: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params_M1: SolverParameters = None + + def __post_init__(self): + # checks + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + check_option(self.solver_M1, OptsSymmSolver) + check_option(self.precond_M1, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + if self.solver_params_M1 is None: + self.solver_params_M1 = SolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - self._info = solver["info"] + @profile + def allocate(self): + self._info = self.options.solver_params.info # define inverse of M1 - if solver_M1["type"][1] is None: + if self.options.precond_M1 is None: pc = None else: - pc_class = getattr(preconditioner, solver_M1["type"][1]) + pc_class = getattr(preconditioner, self.options.precond_M1) pc = pc_class(self.mass_ops.M1) M1_inv = inverse( self.mass_ops.M1, - solver_M1["type"][0], + self.options.solver_M1, pc=pc, - tol=solver_M1["tol"], - maxiter=solver_M1["maxiter"], - verbose=solver_M1["verbose"], + tol=self.options.solver_params_M1.tol, + maxiter=self.options.solver_params_M1.maxiter, + verbose=self.options.solver_params_M1.verbose, ) # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) @@ -753,10 +852,10 @@ def __init__( self._C = 1 / 2 * M1_inv @ self.derham.curl.T @ self.mass_ops.M2B # Preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(getattr(self.mass_ops, "M2n")) # instantiate Schur solver (constant in this case) @@ -765,24 +864,26 @@ def __init__( self._schur_solver = SchurSolver( _A, _BC, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], + self.options.solver, + precond=pc, + solver_params=self.options.solver_params, ) # allocate dummy vectors to avoid temporary array allocations + u = self.variables.u.spline.vector + b = self.variables.b.spline.vector + self._u_tmp1 = u.space.zeros() self._u_tmp2 = u.space.zeros() self._b_tmp1 = b.space.zeros() self._byn = self._B.codomain.zeros() + @profile def __call__(self, dt): # current variables - un = self.feec_vars[0] - bn = self.feec_vars[1] + un = self.variables.u.spline.vector + bn = self.variables.b.spline.vector # solve for new u coeffs byn = self._B.dot(bn, out=self._byn) @@ -797,13 +898,13 @@ def __call__(self, dt): bn1 += bn # write new coeffs into self.feec_vars - max_du, max_db = self.feec_vars_update(un1, bn1) + max_diffs = self.update_feec_variables(u=un1, b=bn1) if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for ShearAlfvenB1:", info["success"]) print("Iterations for ShearAlfvenB1:", info["niter"]) - print("Maxdiff up for ShearAlfvenB1:", max_du) - print("Maxdiff b2 for ShearAlfvenB1:", max_db) + print("Maxdiff up for ShearAlfvenB1:", max_diffs["u"]) + print("Maxdiff b2 for ShearAlfvenB1:", max_diffs["b"]) print() @@ -827,38 +928,66 @@ class Hall(Propagator): The solution of the above system is based on the Pre-conditioned Biconjugate Gradient Stabilized algortihm (PBiConjugateGradientStab). """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pbicgstab", "MassMatrixPreconditioner"), - ("bicgstab", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - if default: - dct = descend_options_dict(dct, []) + class Variables: + def __init__(self): + self._b: FEECVariable = None - return dct + @property + def b(self) -> FEECVariable: + return self._b - def __init__( - self, - b: BlockVector, - *, - epsilon: float = 1.0, - solver: dict = options(default=True)["solver"], - ): - super().__init__(b) + @b.setter + def b(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hcurl" + self._b = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + solver: OptsGenSolver = "pbicgstab" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + epsilon_from: Species = None + + def __post_init__(self): + # checks + check_option(self.solver, OptsGenSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() - self._info = solver["info"] - self._tol = solver["tol"] - self._maxiter = solver["maxiter"] - self._verbose = solver["verbose"] + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + if self.options.epsilon_from is None: + epsilon = 1.0 + else: + epsilon = self.options.epsilon_from.equation_params.epsilon + + self._info = self.options.solver_params.info + self._tol = self.options.solver_params.tol + self._maxiter = self.options.solver_params.maxiter + self._verbose = self.options.solver_params.verbose # mass matrix in system (M - dt/2 * A)*b^(n + 1) = (M + dt/2 * A)*b^n id_M = "M1" @@ -868,18 +997,18 @@ def __init__( self._A = 1.0 / epsilon * self.derham.curl.T @ self._M2Bn @ self.derham.curl # Preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(getattr(self.mass_ops, id_M)) # Instantiate linear solver self._solver = inverse( self._M, - solver["type"][0], + self.options.solver, pc=pc, - x0=self.feec_vars[0], + x0=self.variables.b.spline.vector, tol=self._tol, maxiter=self._maxiter, verbose=self._verbose, @@ -887,11 +1016,11 @@ def __init__( # allocate dummy vectors to avoid temporary array allocations self._rhs_b = self._M.codomain.zeros() - self._b_new = b.space.zeros() + self._b_new = self.variables.b.spline.vector.space.zeros() def __call__(self, dt): # current variables - bn = self.feec_vars[0] + bn = self.variables.b.spline.vector # define system (M - dt/2 * A)*b^(n + 1) = (M + dt/2 * A)*b^n lhs = self._M - dt / 2.0 * self._A @@ -905,12 +1034,12 @@ def __call__(self, dt): info = self._solver._info # write new coeffs into self.feec_vars - max_db = self.feec_vars_update(bn1) + max_db = self.update_feec_variables(b=bn1) if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for Hall:", info["success"]) print("Iterations for Hall:", info["niter"]) - print("Maxdiff b1 for Hall:", max_db) + print("Maxdiff b1 for Hall:", max_db["b"]) print() @@ -1176,36 +1305,78 @@ class MagnetosonicUniform(Propagator): Solver- and/or other parameters for this splitting step. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pbicgstab", "MassMatrixPreconditioner"), - ("bicgstab", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - if default: - dct = descend_options_dict(dct, []) + class Variables: + def __init__(self): + self._n: FEECVariable = None + self._u: FEECVariable = None + self._p: FEECVariable = None - return dct + @property + def n(self) -> FEECVariable: + return self._n - def __init__( - self, - n: StencilVector, - u: BlockVector, - p: StencilVector, - *, - solver: dict = options(default=True)["solver"], - ): - super().__init__(n, u, p) + @n.setter + def n(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._n = new + + @property + def u(self) -> FEECVariable: + return self._u + + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hcurl", "Hdiv", "H1vec") + self._u = new + + @property + def p(self) -> FEECVariable: + return self._p + + @p.setter + def p(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._p = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + solver: OptsGenSolver = "pbicgstab" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + + def __post_init__(self): + # checks + check_option(self.solver, OptsGenSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options - self._info = solver["info"] + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + self._info = self.options.solver_params.info self._bc = self.derham.dirichlet_bc # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) @@ -1221,10 +1392,10 @@ def __init__( self._QD = getattr(self.basis_ops, id_Q) @ self.derham.div # preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(getattr(self.mass_ops, id_Mn)) # instantiate Schur solver (constant in this case) @@ -1233,14 +1404,16 @@ def __init__( self._schur_solver = SchurSolver( _A, _BC, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], + self.options.solver, + precond=pc, + solver_params=self.options.solver_params, ) # allocate dummy vectors to avoid temporary array allocations + n = self.variables.n.spline.vector + u = self.variables.u.spline.vector + p = self.variables.p.spline.vector + self._u_tmp1 = u.space.zeros() self._u_tmp2 = u.space.zeros() self._p_tmp1 = p.space.zeros() @@ -1248,11 +1421,12 @@ def __init__( self._byn1 = self._B.codomain.zeros() + @profile def __call__(self, dt): # current variables - nn = self.feec_vars[0] - un = self.feec_vars[1] - pn = self.feec_vars[2] + nn = self.variables.n.spline.vector + un = self.variables.u.spline.vector + pn = self.variables.p.spline.vector # solve for new u coeffs byn1 = self._B.dot(pn, out=self._byn1) @@ -1271,18 +1445,14 @@ def __call__(self, dt): nn1 += nn # write new coeffs into self.feec_vars - max_dn, max_du, max_dp = self.feec_vars_update( - nn1, - un1, - pn1, - ) + diffs = self.update_feec_variables(n=nn1, u=un1, p=pn1) if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for Magnetosonic:", info["success"]) print("Iterations for Magnetosonic:", info["niter"]) - print("Maxdiff n3 for Magnetosonic:", max_dn) - print("Maxdiff up for Magnetosonic:", max_du) - print("Maxdiff p3 for Magnetosonic:", max_dp) + print("Maxdiff n3 for Magnetosonic:", diffs["n"]) + print("Maxdiff up for Magnetosonic:", diffs["u"]) + print("Maxdiff p3 for Magnetosonic:", diffs["p"]) print() @@ -1370,13 +1540,13 @@ def __init__(self, a, **params): ] # Initialize Accumulator object for getting density from particles - self._pts_x = 1.0 / (2.0 * self.derham.Nel[0]) * np.polynomial.legendre.leggauss( + self._pts_x = 1.0 / (2.0 * self.derham.Nel[0]) * xp.polynomial.legendre.leggauss( self._nqs[0], )[0] + 1.0 / (2.0 * self.derham.Nel[0]) - self._pts_y = 1.0 / (2.0 * self.derham.Nel[1]) * np.polynomial.legendre.leggauss( + self._pts_y = 1.0 / (2.0 * self.derham.Nel[1]) * xp.polynomial.legendre.leggauss( self._nqs[1], )[0] + 1.0 / (2.0 * self.derham.Nel[1]) - self._pts_z = 1.0 / (2.0 * self.derham.Nel[2]) * np.polynomial.legendre.leggauss( + self._pts_z = 1.0 / (2.0 * self.derham.Nel[2]) * xp.polynomial.legendre.leggauss( self._nqs[2], )[0] + 1.0 / (2.0 * self.derham.Nel[2]) @@ -1416,15 +1586,15 @@ def __call__(self, dt): self._accum_density.accumulate( self._particles, - np.array(self.derham.Nel), - np.array(self._nqs), - np.array( + xp.array(self.derham.Nel), + xp.array(self._nqs), + xp.array( self._pts_x, ), - np.array(self._pts_y), - np.array(self._pts_z), - np.array(self._p_shape), - np.array(self._p_size), + xp.array(self._pts_y), + xp.array(self._pts_z), + xp.array(self._p_shape), + xp.array(self._p_size), ) self._accum_potential.accumulate(self._particles) @@ -1517,65 +1687,70 @@ class CurrentCoupling6DDensity(Propagator): :ref:`time_discret`: Crank-Nicolson (implicit mid-point). """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pbicgstab", "MassMatrixPreconditioner"), - ("bicgstab", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["filter"] = { - "use_filter": None, - "modes": (1), - "repeat": 1, - "alpha": 0.5, - } - dct["boundary_cut"] = { - "e1": 0.0, - "e2": 0.0, - "e3": 0.0, - } - dct["turn_off"] = False - if default: - dct = descend_options_dict(dct, []) + class Variables: + def __init__(self): + self._u: FEECVariable = None - return dct + @property + def u(self) -> FEECVariable: + return self._u - def __init__( - self, - u: BlockVector, - *, - particles: Particles6D, - u_space: str, - b_eq: BlockVector | PolarVector, - b_tilde: BlockVector | PolarVector, - Ab: int = 1, - Ah: int = 1, - epsilon: float = 1.0, - solver: dict = options(default=True)["solver"], - filter: dict = options(default=True)["filter"], - boundary_cut: dict = options(default=True)["boundary_cut"], - ): - super().__init__(u) + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hcurl", "Hdiv", "H1vec") + self._u = new - # assert parameters and expose some quantities to self - if u_space == "H1vec": - self._space_key_int = 0 - else: - self._space_key_int = int( - self.derham.space_to_form[u_space], - ) + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + energetic_ions: PICVariable = None + b_tilde: FEECVariable = None + u_space: OptsVecSpace = "Hdiv" + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + filter_params: FilterParameters = None + boundary_cut: tuple = (0.0, 0.0, 0.0) + + def __post_init__(self): + # checks + check_option(self.u_space, OptsVecSpace) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + assert self.energetic_ions.space == "Particles6D" + assert self.b_tilde.space == "Hdiv" + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + self._space_key_int = int(self.derham.space_to_form[self.options.u_space]) - self._particles = particles - self._b_eq = b_eq - self._b_tilde = b_tilde + particles = self.options.energetic_ions.particles + u = self.variables.u.spline.vector + self._b_eq = self.projected_equil.b2 + self._b_tilde = self.options.b_tilde.spline.vector # if self._particles.control_variate: @@ -1591,65 +1766,70 @@ def __init__( # self._particles.f0.n, *quad_pts, kind='3', squeeze_out=False) # # memory allocation of magnetic field at quadrature points - # self._b_quad1 = np.zeros_like(self._nh0_at_quad) - # self._b_quad2 = np.zeros_like(self._nh0_at_quad) - # self._b_quad3 = np.zeros_like(self._nh0_at_quad) + # self._b_quad1 = xp.zeros_like(self._nh0_at_quad) + # self._b_quad2 = xp.zeros_like(self._nh0_at_quad) + # self._b_quad3 = xp.zeros_like(self._nh0_at_quad) # # memory allocation for self._b_quad x self._nh0_at_quad * self._coupling_const - # self._mat12 = np.zeros_like(self._nh0_at_quad) - # self._mat13 = np.zeros_like(self._nh0_at_quad) - # self._mat23 = np.zeros_like(self._nh0_at_quad) + # self._mat12 = xp.zeros_like(self._nh0_at_quad) + # self._mat13 = xp.zeros_like(self._nh0_at_quad) + # self._mat23 = xp.zeros_like(self._nh0_at_quad) - # self._mat21 = np.zeros_like(self._nh0_at_quad) - # self._mat31 = np.zeros_like(self._nh0_at_quad) - # self._mat32 = np.zeros_like(self._nh0_at_quad) + # self._mat21 = xp.zeros_like(self._nh0_at_quad) + # self._mat31 = xp.zeros_like(self._nh0_at_quad) + # self._mat32 = xp.zeros_like(self._nh0_at_quad) - self._type = solver["type"][0] - self._tol = solver["tol"] - self._maxiter = solver["maxiter"] - self._info = solver["info"] - self._verbose = solver["verbose"] + self._type = self.options.solver + self._tol = self.options.solver_params.tol + self._maxiter = self.options.solver_params.maxiter + self._info = self.options.solver_params.info + self._verbose = self.options.solver_params.verbose + self._recycle = self.options.solver_params.recycle + + Ah = self.options.energetic_ions.species.mass_number + Ab = self.variables.u.species.mass_number + epsilon = self.options.energetic_ions.species.equation_params.epsilon self._coupling_const = Ah / Ab / epsilon - self._boundary_cut_e1 = boundary_cut["e1"] + self._boundary_cut_e1 = self.options.boundary_cut[0] # load accumulator self._accumulator = Accumulator( particles, - u_space, - accum_kernels.cc_lin_mhd_6d_1, + self.options.u_space, + Pyccelkernel(accum_kernels.cc_lin_mhd_6d_1), self.mass_ops, self.domain.args_domain, add_vector=False, symmetry="asym", - filter_params=filter, + filter_params=self.options.filter_params, ) # transposed extraction operator PolarVector --> BlockVector (identity map in case of no polar splines) self._E2T = self.derham.extraction_ops["2"].transpose() # mass matrix in system (M - dt/2 * A)*u^(n + 1) = (M + dt/2 * A)*u^n - u_id = self.derham.space_to_form[u_space] + u_id = self.derham.space_to_form[self.options.u_space] self._M = getattr(self.mass_ops, "M" + u_id + "n") # preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(self._M) # linear solver self._solver = inverse( self._M, - solver["type"][0], + self.options.solver, pc=pc, - x0=self.feec_vars[0], + x0=self.variables.u.spline.vector, tol=self._tol, maxiter=self._maxiter, verbose=self._verbose, - recycle=solver["recycle"], + recycle=self._recycle, ) # temporary vectors to avoid memory allocation @@ -1661,7 +1841,7 @@ def __init__( def __call__(self, dt): # pointer to old coefficients - un = self.feec_vars[0] + un = self.variables.u.spline.vector # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) self._b_eq.copy(out=self._b_full1) @@ -1724,7 +1904,7 @@ def __call__(self, dt): info = self._solver._info # write new coeffs into Propagator.variables - max_du = self.feec_vars_update(un1) + max_du = self.update_feec_variables(u=un1) if self._info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for CurrentCoupling6DDensity:", info["success"]) @@ -1741,9 +1921,9 @@ class ShearAlfvenCurrentCoupling5D(Propagator): \left\{ \begin{aligned} - \int \rho_0 &\frac{\partial \tilde{\mathbf U}}{\partial t} \cdot \mathbf V \, \textnormal{d} \mathbf{x} = \int \left(\tilde{\mathbf B} - \frac{A_\textnormal{h}}{A_b} \iint f^\text{vol} \mu \mathbf{b}_0\textnormal{d} \mu \textnormal{d} v_\parallel \right) \cdot \nabla \times (\mathbf B_0 \times \mathbf V) \, \textnormal{d} \mathbf{x} \quad \forall \, \mathbf V \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}\,, \,, + \int \rho_0 &\frac{\partial \tilde{\mathbf U}}{\partial t} \cdot \mathbf V \, \textnormal{d} \mathbf{x} = \int \left(\tilde{\mathbf B} - \frac{A_\textnormal{h}}{A_b} \iint f^\text{vol} \mu \mathbf{b}_0\textnormal{d} \mu \textnormal{d} v_\parallel \right) \cdot \nabla \times (\tilde{\mathbf B} \times \mathbf V) \, \textnormal{d} \mathbf{x} \quad \forall \, \mathbf V \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}\,, \,, \\ - &\frac{\partial \tilde{\mathbf B}}{\partial t} = - \nabla \times (\mathbf B_0 \times \tilde{\mathbf U}) \,. + &\frac{\partial \tilde{\mathbf B}}{\partial t} = - \nabla \times (\tilde{\mathbf B} \times \tilde{\mathbf U}) \,. \end{aligned} \right. @@ -1756,499 +1936,242 @@ class ShearAlfvenCurrentCoupling5D(Propagator): \end{bmatrix} = \frac{\Delta t}{2} \,. \begin{bmatrix} - 0 & (\mathbb M^{\alpha,n})^{-1} \mathcal {T^\alpha}^\top \mathbb C^\top \\ - \mathbb C \mathcal {T^\alpha} (\mathbb M^{\alpha,n})^{-1} & 0 + 0 & (\mathbb M^{2,n})^{-1} \mathcal {T^2}^\top \mathbb C^\top \\ - \mathbb C \mathcal {T^2} (\mathbb M^{2,n})^{-1} & 0 \end{bmatrix} \begin{bmatrix} - {\mathbb M^{\alpha,n}}(\mathbf u^{n+1} + \mathbf u^n) \\ \mathbb M_2(\mathbf b^{n+1} + \mathbf b^n) + \sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k) \right) + {\mathbb M^{2,n}}(\mathbf u^{n+1} + \mathbf u^n) \\ \mathbb M_2(\mathbf b^{n+1} + \mathbf b^n) + \sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k) \right) \end{bmatrix} \,, where - :math:`\mathcal{T}^\alpha` is a :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators` and - :math:`\mathbb M^{\alpha,n}` is a :class:`~struphy.feec.mass.WeightedMassOperators` being weighted with :math:`\rho_\text{eq}`, the MHD equilibirum density. - :math:`\alpha \in \{1, 2, v\}` denotes the :math:`\alpha`-form space where the operators correspond to. - Moreover, :math:`\sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k)\right)` is accumulated by the kernel :class:`~struphy.pic.accumulation.accum_kernels_gc.cc_lin_mhd_5d_M`. + :math:`\mathcal{T}^2 = \hat \Pi \left[\frac{\tilde{\mathbf B}^2}{\sqrt{g} \times \vec \Lambda^2\right]` and + :math:`\mathbb M^{2,n}` is a :class:`~struphy.feec.mass.WeightedMassOperators` being weighted with :math:`\rho_\text{eq}`, the MHD equilibirum density. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["filter"] = { - "use_filter": None, - "modes": (1), - "repeat": 1, - "alpha": 0.5, - } - dct["boundary_cut"] = { - "e1": 0.0, - "e2": 0.0, - "e3": 0.0, - } - dct["turn_off"] = False + class Variables: + def __init__(self): + self._u: FEECVariable = None + self._b: FEECVariable = None - if default: - dct = descend_options_dict(dct, []) + @property + def u(self) -> FEECVariable: + return self._u - return dct + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hcurl", "Hdiv", "H1vec") + self._u = new - def __init__( - self, - u: BlockVector, - b: BlockVector, - *, - particles: Particles5D, - absB0: StencilVector, - unit_b1: BlockVector, - u_space: str, - solver: dict = options(default=True)["solver"], - filter: dict = options(default=True)["filter"], - coupling_params: dict, - accumulated_magnetization: BlockVector, - boundary_cut: dict = options(default=True)["boundary_cut"], - ): - super().__init__(u, b) - - self._particles = particles - self._unit_b1 = unit_b1 - self._absB0 = absB0 - - self._info = solver["info"] - - self._scale_vec = coupling_params["Ah"] / coupling_params["Ab"] - - self._E1T = self.derham.extraction_ops["1"].transpose() - self._unit_b1 = self._E1T.dot(self._unit_b1) - - self._accumulated_magnetization = accumulated_magnetization - - self._boundary_cut_e1 = boundary_cut["e1"] - - self._ACC = Accumulator( - particles, - u_space, - accum_kernels_gc.cc_lin_mhd_5d_M, - self.mass_ops, - self.domain.args_domain, - add_vector=True, - symmetry="symm", - filter_params=filter, - ) + @property + def b(self) -> FEECVariable: + return self._b - # if self._particles.control_variate: + @b.setter + def b(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hdiv" + self._b = new - # # control variate method is only valid with Maxwellian distributions with "zero perp mean velocity". - # assert isinstance(self._particles.f0, Maxwellian) + def __init__(self): + self.variables = self.Variables() - # self._ACC.init_control_variate(self.mass_ops) + @dataclass + class Options: + # specific literals + OptsAlgo = Literal["implicit", "explicit"] + # propagator options + energetic_ions: PICVariable = None + ep_scale: float = 1.0 + u_space: OptsVecSpace = "Hdiv" + algo: OptsAlgo = "implicit" + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixDiagonalPreconditioner" + solver_params: SolverParameters = None + filter_params: FilterParameters = None + butcher: ButcherTableau = None + nonlinear: bool = True - # # evaluate and save f0.n at quadrature points - # quad_pts = [quad_grid[nquad].points.flatten() - # for quad_grid, nquad in zip(self.derham.get_quad_grids(self.derham.Vh_fem['0']), self.derham.nquads)] + def __post_init__(self): + # checks + check_option(self.u_space, OptsVecSpace) + check_option(self.algo, self.OptsAlgo) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + assert isinstance(self.energetic_ions, PICVariable) + assert self.energetic_ions.space == "Particles5D" + assert isinstance(self.ep_scale, float) + assert isinstance(self.nonlinear, bool) - # n0_at_quad = self.domain.push( - # self._particles.f0.n, *quad_pts, kind='0', squeeze_out=False) + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() - # # evaluate M0 = unit_b1 (1form) / absB0 (0form) * 2 * vth_perp² at quadrature points - # quad_pts_array = self.domain.prepare_eval_pts(*quad_pts)[:3] + if self.filter_params is None: + self.filter_params = FilterParameters() - # vth_perp = self.particles.f0.vth(*quad_pts_array)[1] + if self.algo == "explicit" and self.butcher is None: + self.butcher = ButcherTableau() - # absB0_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['0'], self._absB0) + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options - # unit_b1_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['1'], self._unit_b1) + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - # self._M0_at_quad = unit_b1_at_quad / absB0_at_quad * vth_perp**2 * n0_at_quad * self._scale_vec + @profile + def allocate(self): + self._u_form = self.derham.space_to_form[self.options.u_space] - # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) - id_M = "M" + self.derham.space_to_form[u_space] + "n" - id_T = "T" + self.derham.space_to_form[u_space] + # call operatros + id_M = "M" + self._u_form + "n" + id_T = "T" + self._u_form _A = getattr(self.mass_ops, id_M) _T = getattr(self.basis_ops, id_T) + M2 = self.mass_ops.M2 + curl = self.derham.curl + PB = getattr(self.basis_ops, "PB") - self._B = -1 / 2 * _T.T @ self.derham.curl.T @ self.mass_ops.M2 - self._C = 1 / 2 * self.derham.curl @ _T - self._B2 = -1 / 2 * _T.T @ self.derham.curl.T + # define Accumulator and arguments + self._ACC = AccumulatorVector( + self.options.energetic_ions.particles, + "H1", + Pyccelkernel(accum_kernels_gc.gc_mag_density_0form), + self.mass_ops, + self.domain.args_domain, + filter_params=self.options.filter_params, + ) # Preconditioner - if solver["type"][1] is None: + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(getattr(self.mass_ops, id_M)) - # Instantiate Schur solver (constant in this case) - _BC = self._B @ self._C + if self.options.nonlinear: + # initialize operator TB + self._initialize_projection_operator_TB() - self._schur_solver = SchurSolver( - _A, - _BC, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], - recycle=solver["recycle"], - ) - - # allocate dummy vectors to avoid temporary array allocations - self._u_tmp1 = u.space.zeros() - self._u_tmp2 = u.space.zeros() - self._b_tmp1 = b.space.zeros() - - self._byn = self._B.codomain.zeros() - self._tmp_acc = self._B2.codomain.zeros() - - def __call__(self, dt): - # current variables - un = self.feec_vars[0] - bn = self.feec_vars[1] + _T = _T + self._TB + _TT = _T.T + self._TBT - # perform accumulation (either with or without control variate) - # if self._particles.control_variate: - - # self._ACC.accumulate(self._particles, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._scale_vec, 0., - # control_vec=[self._M0_at_quad[0], self._M0_at_quad[1], self._M0_at_quad[2]]) - # else: - # self._ACC.accumulate(self._particles, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._scale_vec, 0.) - - self._ACC( - self._unit_b1[0]._data, - self._unit_b1[1]._data, - self._unit_b1[2]._data, - self._scale_vec, - self._boundary_cut_e1, - ) - - self._ACC.vectors[0].copy(out=self._accumulated_magnetization) - - # solve for new u coeffs (no tmps created here) - byn = self._B.dot(bn, out=self._byn) - b2acc = self._B2.dot(self._ACC.vectors[0], out=self._tmp_acc) - byn += b2acc - - # b2acc.copy(out=self._accumulated_magnetization) - - un1, info = self._schur_solver(un, byn, dt, out=self._u_tmp1) - - # new b coeffs (no tmps created here) - _u = un.copy(out=self._u_tmp2) - _u += un1 - bn1 = self._C.dot(_u, out=self._b_tmp1) - bn1 *= -dt - bn1 += bn - - # write new coeffs into self.feec_vars - max_du, max_db = self.feec_vars_update(un1, bn1) - - if self._info and MPI.COMM_WORLD.Get_rank() == 0: - print("Status for ShearAlfven:", info["success"]) - print("Iterations for ShearAlfven:", info["niter"]) - print("Maxdiff up for ShearAlfven:", max_du) - print("Maxdiff b2 for ShearAlfven:", max_db) - print() - - -class MagnetosonicCurrentCoupling5D(Propagator): - r""" - :ref:`FEEC ` discretization of the following equations: - find :math:`\tilde \rho \in L^2, \tilde{\mathbf U} \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}, \tilde p \in L^2` such that - - .. math:: - - \left\{ - \begin{aligned} - &\frac{\partial \tilde{\rho}}{\partial t} = - \nabla \cdot (\rho_0 \tilde{\mathbf U}) \,, - \\ - \int \rho_0 &\frac{\partial \tilde{\mathbf U}}{\partial t} \cdot \mathbf V \, \textnormal{d} \mathbf{x} = \int (\nabla \times \mathbf B_0) \times \tilde{\mathbf B} \cdot \mathbf V \, \textnormal{d} \mathbf x + \frac{A_\textnormal{h}}{A_b}\iint f^\text{vol} \mu \mathbf b_0 \cdot \nabla \times (\tilde{\mathbf B} \times \mathbf V) \, \textnormal{d} \mathbf x \textnormal{d} v_\parallel \textnormal{d} \mu + \int \tilde p \nabla \cdot \mathbf V \, \textnormal{d} \mathbf x \qquad \forall \, \mathbf V \in \{H(\textnormal{curl}), H(\textnormal{div}), (H^1)^3\}\,, - \\ - &\frac{\partial \tilde p}{\partial t} = - \nabla \cdot (p_0 \tilde{\mathbf U}) - (\gamma - 1) p_0 \nabla \cdot \tilde{\mathbf U} \,. - \end{aligned} - \right. - - :ref:`time_discret`: Crank-Nicolson (implicit mid-point). System size reduction via :class:`~struphy.linear_algebra.schur_solver.SchurSolver`: - - .. math:: - - \boldsymbol{\rho}^{n+1} - \boldsymbol{\rho}^n = - \frac{\Delta t}{2} \mathbb D \mathcal Q^\alpha (\mathbf u^{n+1} + \mathbf u^n) \,, - - .. math:: - - \begin{bmatrix} - \mathbf u^{n+1} - \mathbf u^n \\ \mathbf p^{n+1} - \mathbf p^n - \end{bmatrix} - = \frac{\Delta t}{2} - \begin{bmatrix} - 0 & (\mathbb M^{\alpha,n})^{-1} {\mathcal U^\alpha}^\top \mathbb D^\top \mathbb M_3 \\ - \mathbb D \mathcal S^\alpha - (\gamma - 1) \mathcal K^\alpha \mathbb D \mathcal U^\alpha & 0 - \end{bmatrix} - \begin{bmatrix} - (\mathbf u^{n+1} + \mathbf u^n) \\ (\mathbf p^{n+1} + \mathbf p^n) - \end{bmatrix} + - \begin{bmatrix} - \Delta t (\mathbb M^{\alpha,n})^{-1}\left[\mathbb M^{\alpha,J} \mathbf b^n + \frac{A_\textnormal{h}}{A_b}{\mathcal{T}^B}^\top \mathbb{C}^\top \sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k) \right)\right] \\ 0 - \end{bmatrix} \,, - - where - :math:`\mathcal U^\alpha`, :math:`\mathcal S^\alpha`, :math:`\mathcal K^\alpha` and :math:`\mathcal Q^\alpha` are :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators` and - :math:`\mathbb M^{\alpha,n}` and :math:`\mathbb M^{\alpha,J}` are :class:`~struphy.feec.mass.WeightedMassOperators` being weighted with :math:`\rho_0` the MHD equilibrium density. - :math:`\alpha \in \{1, 2, v\}` denotes the :math:`\alpha`-form space where the operators correspond to. - Moreover, :math:`\sum_k^{N_p} \omega_k \mu_k \hat{\mathbf b}¹_0 (\boldsymbol \eta_k) \cdot \left(\frac{1}{\sqrt{g(\boldsymbol \eta_k)}} \vec \Lambda² (\boldsymbol \eta_k)\right)` is accumulated by the kernel :class:`~struphy.pic.accumulation.accum_kernels_gc.cc_lin_mhd_5d_M` and - the time-varying projection operator :math:`\mathcal{T}^B` is defined as - - .. math:: - - \mathcal{T}^B_{(\mu,ijk),(\nu,mno)} := \hat \Pi¹_{(\mu,ijk)} \left[ \epsilon_{\mu \alpha \nu} \frac{\tilde{B}^2_\alpha}{\sqrt{g}} \Lambda²_{\nu,mno} \right] \,. - """ - - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pbicgstab", "MassMatrixPreconditioner"), - ("bicgstab", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["filter"] = { - "use_filter": None, - "modes": (0, 1), - "repeat": 3, - "alpha": 0.5, - } - dct["boundary_cut"] = { - "e1": 0.0, - "e2": 0.0, - "e3": 0.0, - } - dct["turn_off"] = False - - if default: - dct = descend_options_dict(dct, []) - - return dct - - def __init__( - self, - n: StencilVector, - u: BlockVector, - p: StencilVector, - *, - particles: Particles5D, - b: BlockVector, - absB0: StencilVector, - unit_b1: BlockVector, - u_space: str, - solver: dict = options(default=True)["solver"], - filter: dict = options(default=True)["filter"], - coupling_params: dict, - boundary_cut: dict = options(default=True)["boundary_cut"], - ): - super().__init__(n, u, p) - - self._particles = particles - self._b = b - self._unit_b1 = unit_b1 - self._absB0 = absB0 - - self._info = solver["info"] - - self._scale_vec = coupling_params["Ah"] / coupling_params["Ab"] - - self._E1T = self.derham.extraction_ops["1"].transpose() - self._unit_b1 = self._E1T.dot(self._unit_b1) - - self._u_id = self.derham.space_to_form[u_space] - if self._u_id == "v": - self._space_key_int = 0 else: - self._space_key_int = int(self._u_id) - - self._boundary_cut_e1 = boundary_cut["e1"] + _TT = _T.T - self._ACC = Accumulator( - particles, - u_space, - accum_kernels_gc.cc_lin_mhd_5d_M, - self.mass_ops, - self.domain.args_domain, - add_vector=True, - symmetry="symm", - filter_params=filter, - ) - - # if self._particles.control_variate: - - # # control variate method is only valid with Maxwellian distributions with "zero perp mean velocity". - # assert isinstance(self._particles.f0, Maxwellian) - - # self._ACC.init_control_variate(self.mass_ops) - - # # evaluate and save f0.n at quadrature points - # quad_pts = [quad_grid[nquad].points.flatten() - # for quad_grid, nquad in zip(self.derham.get_quad_grids(self.derham.Vh_fem['0']), self.derham.nquads)] - - # n0_at_quad = self.domain.push( - # self._particles.f0.n, *quad_pts, kind='0', squeeze_out=False) - - # # evaluate M0 = unit_b1 (1form) / absB0 (0form) * 2 * vth_perp² at quadrature points - # quad_pts_array = self.domain.prepare_eval_pts(*quad_pts)[:3] - - # vth_perp = self.particles.f0.vth(*quad_pts_array)[1] - - # absB0_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['0'], self._absB0) - - # unit_b1_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['1'], self._unit_b1) + if self.options.algo == "implicit": + self._info = self.options.solver_params.info - # self._M0_at_quad = unit_b1_at_quad / absB0_at_quad * vth_perp**2 * n0_at_quad * self._scale_vec + # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) + self._B = -1 / 2 * _TT @ curl.T @ M2 + self._B2 = -1 / 2 * _TT @ curl.T @ PB.T - # define block matrix [[A B], [C I]] (without time step size dt in the diagonals) - id_Mn = "M" + self._u_id + "n" - id_MJ = "M" + self._u_id + "J" + self._C = 1 / 2 * curl @ _T - if self._u_id == "1": - id_S, id_U, id_K, id_Q = "S1", "U1", "K3", "Q1" - elif self._u_id == "2": - id_S, id_U, id_K, id_Q = "S2", None, "K3", "Q2" - elif self._u_id == "v": - id_S, id_U, id_K, id_Q = "Sv", "Uv", "K3", "Qv" + # Instantiate Schur solver (constant in this case) + _BC = self._B @ self._C - self._E2T = self.derham.extraction_ops["2"].transpose() + self._schur_solver = SchurSolver( + _A, + _BC, + self.options.solver, + precond=pc, + solver_params=self.options.solver_params, + ) - _A = getattr(self.mass_ops, id_Mn) - _S = getattr(self.basis_ops, id_S) - _K = getattr(self.basis_ops, id_K) + # allocate dummy vectors to avoid temporary array allocations + self._u_tmp1 = self.variables.u.spline.vector.space.zeros() + self._u_tmp2 = self.variables.u.spline.vector.space.zeros() + self._b_tmp1 = self.variables.b.spline.vector.space.zeros() - # initialize projection operator TB - self._initialize_projection_operator_TB() + self._byn = self._B.codomain.zeros() + self._tmp_acc = self._B2.codomain.zeros() - if id_U is None: - _U, _UT = IdentityOperator(u.space), IdentityOperator(u.space) else: - _U = getattr(self.basis_ops, id_U) - _UT = _U.T - - self._B = -1 / 2.0 * _UT @ self.derham.div.T @ self.mass_ops.M3 - self._C = 1 / 2.0 * (self.derham.div @ _S + 2 / 3.0 * _K @ self.derham.div @ _U) - - self._MJ = getattr(self.mass_ops, id_MJ) - self._DQ = self.derham.div @ getattr(self.basis_ops, id_Q) + self._info = False - self._TC = self._TB.T @ self.derham.curl.T + # define vector field + A_inv = inverse( + _A, + self.options.solver, + pc=pc, + tol=self.options.solver_params.tol, + maxiter=self.options.solver_params.maxiter, + verbose=self.options.solver_params.verbose, + ) + _f1 = A_inv @ _TT @ curl.T @ M2 + _f1_acc = A_inv @ _TT @ curl.T @ PB.T + _f2 = curl @ _T - # preconditioner - if solver["type"][1] is None: - pc = None - else: - pc_class = getattr(preconditioner, solver["type"][1]) - pc = pc_class(getattr(self.mass_ops, id_Mn)) + # allocate output of vector field + out_acc = self.variables.u.spline.vector.space.zeros() + out1 = self.variables.u.spline.vector.space.zeros() + out2 = self.variables.b.spline.vector.space.zeros() - # instantiate Schur solver (constant in this case) - _BC = self._B @ self._C + def f1(t, y1, y2, out: BlockVector = out1): + _f1.dot(y2, out=out) + _f1_acc.dot(self._ACC.vectors[0], out=out_acc) + out += out_acc + out.update_ghost_regions() + return out - self._schur_solver = SchurSolver( - _A, - _BC, - solver["type"][0], - pc=pc, - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], - recycle=solver["recycle"], - ) + def f2(t, y1, y2, out: BlockVector = out2): + _f2.dot(y1, out=out) + out *= -1.0 + out.update_ghost_regions() + return out - # allocate dummy vectors to avoid temporary array allocations - self._u_tmp1 = u.space.zeros() - self._u_tmp2 = u.space.zeros() - self._p_tmp1 = p.space.zeros() - self._n_tmp1 = n.space.zeros() - self._byn1 = self._B.codomain.zeros() - self._byn2 = self._B.codomain.zeros() - self._tmp_acc = self._TC.codomain.zeros() + vector_field = {self.variables.u.spline.vector: f1, self.variables.b.spline.vector: f2} + self._ode_solver = ODEsolverFEEC(vector_field, butcher=self.options.butcher) def __call__(self, dt): - # current variables - nn = self.feec_vars[0] - un = self.feec_vars[1] - pn = self.feec_vars[2] + # update time-dependent operator TB + if self.options.nonlinear: + self._update_weights_TB() - # perform accumulation (either with or without control variate) - # if self._particles.control_variate: - - # self._ACC.accumulate(self._particles, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._scale_vec, 0., - # control_vec=[self._M0_at_quad[0], self._M0_at_quad[1], self._M0_at_quad[2]]) - # else: - # self._ACC.accumulate(self._particles, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._scale_vec, 0.) - - self._ACC( - self._unit_b1[0]._data, - self._unit_b1[1]._data, - self._unit_b1[2]._data, - self._scale_vec, - self._boundary_cut_e1, - ) + # current FE coeffs + un = self.variables.u.spline.vector + bn = self.variables.b.spline.vector - # update time-dependent operator - self._b.update_ghost_regions() - self._update_weights_TB() + # accumulate + self._ACC(self.options.ep_scale) - # solve for new u coeffs (no tmps created here) - byn1 = self._B.dot(pn, out=self._byn1) - byn2 = self._MJ.dot(self._b, out=self._byn2) - b2acc = self._TC.dot(self._ACC.vectors[0], out=self._tmp_acc) - byn2 += b2acc - byn2 *= 1 / 2 - byn1 -= byn2 + if self.options.algo == "implicit": + # solve for new u coeffs (no tmps created here) + byn = self._B.dot(bn, out=self._byn) + b2acc = self._B2.dot(self._ACC.vectors[0], out=self._tmp_acc) + byn += b2acc - un1, info = self._schur_solver(un, byn1, dt, out=self._u_tmp1) + un1, info = self._schur_solver(un, byn, dt, out=self._u_tmp1) - # new p, n, b coeffs (no tmps created here) - _u = un.copy(out=self._u_tmp2) - _u += un1 - pn1 = self._C.dot(_u, out=self._p_tmp1) - pn1 *= -dt - pn1 += pn + # new b coeffs (no tmps created here) + _u = un.copy(out=self._u_tmp2) + _u += un1 + bn1 = self._C.dot(_u, out=self._b_tmp1) + bn1 *= -dt + bn1 += bn - nn1 = self._DQ.dot(_u, out=self._n_tmp1) - nn1 *= -dt / 2 - nn1 += nn + diffs = self.update_feec_variables(u=un1, b=bn1) - # write new coeffs into self.feec_vars - max_dn, max_du, max_dp = self.feec_vars_update( - nn1, - un1, - pn1, - ) + else: + self._ode_solver(0.0, dt) if self._info and MPI.COMM_WORLD.Get_rank() == 0: - print("Status for Magnetosonic:", info["success"]) - print("Iterations for Magnetosonic:", info["niter"]) - print("Maxdiff n3 for Magnetosonic:", max_dn) - print("Maxdiff up for Magnetosonic:", max_du) - print("Maxdiff p3 for Magnetosonic:", max_dp) - print() + if self.options.algo == "implicit": + print("Status for ShearAlfvenCurrentCoupling5D:", info["success"]) + print("Iterations for ShearAlfvenCurrentCoupling5D:", info["niter"]) + print("Maxdiff up for ShearAlfvenCurrentCoupling5D:", diffs["u"]) + print("Maxdiff b2 for ShearAlfvenCurrentCoupling5D:", diffs["b"]) + print() def _initialize_projection_operator_TB(self): r"""Initialize BasisProjectionOperator TB with the time-varying weight. @@ -2261,27 +2184,80 @@ def _initialize_projection_operator_TB(self): # Call the projector and the space P1 = self.derham.P["1"] - Vh = self.derham.Vh_fem[self._u_id] + Vh = self.derham.Vh_fem[self._u_form] # Femfield for the field evaluation self._bf = self.derham.create_spline_function("bf", "Hdiv") - # define temp callable - def tmp(x, y, z): - return 0 * x - # Initialize BasisProjectionOperator if self.derham._with_local_projectors: - self._TB = BasisProjectionOperatorLocal(P1, Vh, [[tmp, tmp, tmp]]) + self._TB = BasisProjectionOperatorLocal( + P1, + Vh, + [ + [None, None, None], + [None, None, None], + [None, None, None], + ], + transposed=False, + use_cache=True, + polar_shift=True, + V_extraction_op=self.derham.extraction_ops[self._u_form], + V_boundary_op=self.derham.boundary_ops[self._u_form], + P_boundary_op=self.derham.boundary_ops["1"], + ) + self._TBT = BasisProjectionOperatorLocal( + P1, + Vh, + [ + [None, None, None], + [None, None, None], + [None, None, None], + ], + transposed=True, + use_cache=True, + polar_shift=True, + V_extraction_op=self.derham.extraction_ops[self._u_form], + V_boundary_op=self.derham.boundary_ops[self._u_form], + P_boundary_op=self.derham.boundary_ops["1"], + ) else: - self._TB = BasisProjectionOperator(P1, Vh, [[tmp, tmp, tmp]]) + self._TB = BasisProjectionOperator( + P1, + Vh, + [ + [None, None, None], + [None, None, None], + [None, None, None], + ], + transposed=False, + use_cache=True, + polar_shift=True, + V_extraction_op=self.derham.extraction_ops[self._u_form], + V_boundary_op=self.derham.boundary_ops[self._u_form], + P_boundary_op=self.derham.boundary_ops["1"], + ) + self._TBT = BasisProjectionOperator( + P1, + Vh, + [ + [None, None, None], + [None, None, None], + [None, None, None], + ], + transposed=True, + use_cache=True, + polar_shift=True, + V_extraction_op=self.derham.extraction_ops[self._u_form], + V_boundary_op=self.derham.boundary_ops[self._u_form], + P_boundary_op=self.derham.boundary_ops["1"], + ) def _update_weights_TB(self): """Updats time-dependent weights of the BasisProjectionOperator TB""" # Update Femfield - self._bf.vector = self._b - self._bf.vector.update_ghost_regions() + self.variables.b.spline.vector.copy(out=self._bf.vector) # define callable weights def bf1(x, y, z): @@ -2299,7 +2275,7 @@ def bf3(x, y, z): fun = [] - if self._u_id == "v": + if self._u_form == "v": for m in range(3): fun += [[]] for n in range(3): @@ -2307,7 +2283,7 @@ def bf3(x, y, z): lambda e1, e2, e3, m=m, n=n: rot_B(e1, e2, e3)[:, :, :, m, n], ] - elif self._u_id == "1": + elif self._u_form == "1": for m in range(3): fun += [[]] for n in range(3): @@ -2333,8 +2309,9 @@ def bf3(x, y, z): / abs(self.domain.jacobian_det(e1, e2, e3, squeeze_out=False)), ] - # Initialize BasisProjectionOperator + # update BasisProjectionOperator self._TB.update_weights(fun) + self._TBT.update_weights(fun) class CurrentCoupling5DDensity(Propagator): @@ -2354,268 +2331,166 @@ class CurrentCoupling5DDensity(Propagator): For the detail explanation of the notations, see `2022_DriftKineticCurrentCoupling `_. """ - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("pbicgstab", "MassMatrixPreconditioner"), - ("bicgstab", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["filter"] = { - "use_filter": None, - "modes": (1), - "repeat": 1, - "alpha": 0.5, - } - dct["boundary_cut"] = { - "e1": 0.0, - "e2": 0.0, - "e3": 0.0, - } - dct["turn_off"] = False + class Variables: + def __init__(self): + self._u: FEECVariable = None - if default: - dct = descend_options_dict(dct, []) + @property + def u(self) -> FEECVariable: + return self._u - return dct + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space in ("Hcurl", "Hdiv", "H1vec") + self._u = new - def __init__( - self, - u: BlockVector, - *, - particles: Particles5D, - b: BlockVector, - b_eq: BlockVector, - unit_b1: BlockVector, - curl_unit_b2: BlockVector, - u_space: str, - solver: dict = options(default=True)["solver"], - coupling_params: dict, - epsilon: float = 1.0, - filter: dict = options(default=True)["filter"], - boundary_cut: dict = options(default=True)["boundary_cut"], - ): - super().__init__(u) + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + energetic_ions: PICVariable = None + b_tilde: FEECVariable = None + ep_scale: float = 1.0 + u_space: OptsVecSpace = "Hdiv" + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + filter_params: FilterParameters = None + + def __post_init__(self): + # checks + check_option(self.u_space, OptsVecSpace) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + assert isinstance(self.energetic_ions, PICVariable) + assert self.energetic_ions.space == "Particles5D" + assert isinstance(self.b_tilde, FEECVariable) + assert isinstance(self.ep_scale, float) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() - # assert parameters and expose some quantities to self - assert isinstance(particles, (Particles5D)) + if self.filter_params is None: + self.filter_params = FilterParameters() - assert u_space in {"Hcurl", "Hdiv", "H1vec"} + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - if u_space == "H1vec": - self._space_key_int = 0 + @profile + def allocate(self): + if self.options.u_space == "H1vec": + self._u_form_int = 0 else: - self._space_key_int = int( - self.derham.space_to_form[u_space], - ) + self._u_form_int = int(self.derham.space_to_form[self.options.u_space]) - self._epsilon = epsilon - self._particles = particles - self._b = b - self._b_eq = b_eq - self._unit_b1 = unit_b1 - self._curl_norm_b = curl_unit_b2 + # call operatros + id_M = "M" + self.derham.space_to_form[self.options.u_space] + "n" + self._A = getattr(self.mass_ops, id_M) - self._info = solver["info"] + # magnetic equilibrium field + unit_b1 = self.projected_equil.unit_b1 + curl_unit_b1 = self.projected_equil.curl_unit_b1 + self._b2 = self.projected_equil.b2 - self._scale_mat = coupling_params["Ah"] / coupling_params["Ab"] / self._epsilon + # scaling factor + epsilon = self.options.energetic_ions.species.equation_params.epsilon - self._boundary_cut_e1 = boundary_cut["e1"] + # temporary vectors to avoid memory allocation + self._b_full = self._b2.space.zeros() + self._rhs_v = self.variables.u.spline.vector.space.zeros() + self._u_new = self.variables.u.spline.vector.space.zeros() - self._accumulator = Accumulator( - particles, - u_space, - accum_kernels_gc.cc_lin_mhd_5d_D, + # define Accumulator and arguments + self._ACC = Accumulator( + self.options.energetic_ions.particles, + self.options.u_space, + Pyccelkernel(accum_kernels_gc.cc_lin_mhd_5d_D), self.mass_ops, self.domain.args_domain, add_vector=False, symmetry="asym", - filter_params=filter, + filter_params=self.options.filter_params, ) - # if self._particles.control_variate: - - # # control variate method is only valid with Maxwellian distributions - # assert isinstance(self._particles.f0, Maxwellian) - # assert params['u_space'] == 'Hdiv' - - # # evaluate and save f0.n / |det(DF)| at quadrature points - # quad_pts = [quad_grid[nquad].points.flatten() - # for quad_grid, nquad in zip(self.derham.get_quad_grids(self.derham.Vh_fem['0']), self.derham.nquads)] - - # self._n0_at_quad = self.domain.push( - # self._particles.f0.n, *quad_pts, kind='3', squeeze_out=False) - - # # prepare field evaluation - # quad_pts_array = self.domain.prepare_eval_pts(*quad_pts)[:3] - - # u0_parallel = self._particles.f0.u(*quad_pts_array)[0] - - # det_df_at_quad = self.domain.jacobian_det(*quad_pts, squeeze_out=False) - - # # evaluate unit_b1 / |det(DF)| at quadrature points - # self._unit_b1_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['1'], self._unit_b1) - # self._unit_b1_at_quad /= det_df_at_quad - - # # evaluate unit_b1 (1form) dot epsilon * f0.u * curl_norm_b (2form) / |det(DF)| at quadrature points - # curl_norm_b_at_quad = WeightedMassOperator.eval_quad(self.derham.Vh_fem['2'], self._curl_norm_b) - - # self._unit_b1_dot_curl_norm_b_at_quad = np.sum(p * q for p, q in zip(self._unit_b1_at_quad, curl_norm_b_at_quad)) - - # self._unit_b1_dot_curl_norm_b_at_quad /= det_df_at_quad - # self._unit_b1_dot_curl_norm_b_at_quad *= self._epsilon - # self._unit_b1_dot_curl_norm_b_at_quad *= u0_parallel - - # # memory allocation for magnetic field at quadrature points - # self._b_quad1 = np.zeros_like(self._n0_at_quad) - # self._b_quad2 = np.zeros_like(self._n0_at_quad) - # self._b_quad3 = np.zeros_like(self._n0_at_quad) - - # # memory allocation for parallel magnetic field at quadrature points - # self._B_para = np.zeros_like(self._n0_at_quad) - - # # memory allocation for control_const at quadrature points - # self._control_const = np.zeros_like(self._n0_at_quad) - - # # memory allocation for self._b_quad x self._nh0_at_quad * self._coupling_const - # self._mat12 = np.zeros_like(self._n0_at_quad) - # self._mat13 = np.zeros_like(self._n0_at_quad) - # self._mat23 = np.zeros_like(self._n0_at_quad) - - # self._mat21 = np.zeros_like(self._n0_at_quad) - # self._mat31 = np.zeros_like(self._n0_at_quad) - # self._mat32 = np.zeros_like(self._n0_at_quad) - - u_id = self.derham.space_to_form[u_space] - self._M = getattr(self.mass_ops, "M" + u_id + "n") - - self._E0T = self.derham.extraction_ops["0"].transpose() - self._EuT = self.derham.extraction_ops[u_id].transpose() - self._E1T = self.derham.extraction_ops["1"].transpose() - self._E2T = self.derham.extraction_ops["2"].transpose() - - self._PB = getattr(self.basis_ops, "PB") - self._unit_b1 = self._E1T.dot(self._unit_b1) + self._args_accum_kernel = ( + epsilon, + self.options.ep_scale, + self._b_full[0]._data, + self._b_full[1]._data, + self._b_full[2]._data, + unit_b1[0]._data, + unit_b1[1]._data, + unit_b1[2]._data, + curl_unit_b1[0]._data, + curl_unit_b1[1]._data, + curl_unit_b1[2]._data, + self._u_form_int, + ) - # preconditioner - if solver["type"][1] is None: - self._pc = None + # Preconditioner + if self.options.precond is None: + pc = None else: - pc_class = getattr(preconditioner, solver["type"][1]) - self._pc = pc_class(self._M) + pc_class = getattr(preconditioner, self.options.precond) + pc = pc_class(getattr(self.mass_ops, id_M)) # linear solver - self._solver = inverse( - self._M, - solver["type"][0], - pc=self._pc, - x0=self.feec_vars[0], - tol=solver["tol"], - maxiter=solver["maxiter"], - verbose=solver["verbose"], - recycle=solver["recycle"], + self._A_inv = inverse( + self._A, + self.options.solver, + pc=pc, + tol=self.options.solver_params.tol, + maxiter=self.options.solver_params.maxiter, + verbose=self.options.solver_params.verbose, ) - # temporary vectors to avoid memory allocation - self._b_full1 = self._b_eq.space.zeros() - self._b_full2 = self._E2T.codomain.zeros() - self._rhs_v = u.space.zeros() - self._u_new = u.space.zeros() - def __call__(self, dt): - # pointer to old coefficients - un = self.feec_vars[0] + # current FE coeffs + un = self.variables.u.spline.vector # sum up total magnetic field b_full1 = b_eq + b_tilde (in-place) - b_full = self._b_eq.copy(out=self._b_full1) - - if self._b is not None: - b_full += self._b - - Eb_full = self._E2T.dot(b_full, out=self._b_full2) - Eb_full.update_ghost_regions() - - # perform accumulation (either with or without control variate) - # if self._particles.control_variate: - - # # evaluate magnetic field at quadrature points (in-place) - # WeightedMassOperator.eval_quad(self.derham.Vh_fem['2'], self._b_full2, - # out=[self._b_quad1, self._b_quad2, self._b_quad3]) - - # # evaluate B_parallel - # self._B_para = np.sum(p * q for p, q in zip(self._unit_b1_at_quad, [self._b_quad1, self._b_quad2, self._b_quad3])) - - # # evaluate coupling_const 1 - B_parallel / B^star_parallel - # self._control_const = 1 - (self._B_para / (self._B_para + self._unit_b1_dot_curl_norm_b_at_quad)) - - # # assemble (B x) - # self._mat12[:, :, :] = self._scale_mat * \ - # self._b_quad3 * self._n0_at_quad * self._control_const - # self._mat13[:, :, :] = -self._scale_mat * \ - # self._b_quad2 * self._n0_at_quad * self._control_const - # self._mat23[:, :, :] = self._scale_mat * \ - # self._b_quad1 * self._n0_at_quad * self._control_const - - # self._mat21[:, :, :] = -self._mat12 - # self._mat31[:, :, :] = -self._mat13 - # self._mat32[:, :, :] = -self._mat23 + b_full = self._b2.copy(out=self._b_full) - # self._accumulator.accumulate(self._particles, self._epsilon, - # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, - # self._space_key_int, self._scale_mat, 0.1, - # control_mat=[[None, self._mat12, self._mat13], - # [self._mat21, None, self._mat23], - # [self._mat31, self._mat32, None]]) - # else: - # self._accumulator.accumulate(self._particles, self._epsilon, - # Eb_full[0]._data, Eb_full[1]._data, Eb_full[2]._data, - # self._unit_b1[0]._data, self._unit_b1[1]._data, self._unit_b1[2]._data, - # self._curl_norm_b[0]._data, self._curl_norm_b[1]._data, self._curl_norm_b[2]._data, - # self._space_key_int, self._scale_mat, 0.) + b_full += self.options.b_tilde.spline.vector + b_full.update_ghost_regions() - self._accumulator( - self._epsilon, - Eb_full[0]._data, - Eb_full[1]._data, - Eb_full[2]._data, - self._unit_b1[0]._data, - self._unit_b1[1]._data, - self._unit_b1[2]._data, - self._curl_norm_b[0]._data, - self._curl_norm_b[1]._data, - self._curl_norm_b[2]._data, - self._space_key_int, - self._scale_mat, - self._boundary_cut_e1, + self._ACC( + *self._args_accum_kernel, ) # define system (M - dt/2 * A)*u^(n + 1) = (M + dt/2 * A)*u^n - lhs = self._M - dt / 2 * self._accumulator.operators[0] - rhs = self._M + dt / 2 * self._accumulator.operators[0] + lhs = self._A - dt / 2 * self._ACC.operators[0] + rhs = self._A + dt / 2 * self._ACC.operators[0] # solve linear system for updated u coefficients (in-place) rhs = rhs.dot(un, out=self._rhs_v) - self._solver.linop = lhs + self._A_inv.linop = lhs - un1 = self._solver.solve(rhs, out=self._u_new) - info = self._solver._info + _u = self._A_inv.solve(rhs, out=self._u_new) + info = self._A_inv._info - # write new coeffs into Propagator.variables - max_du = self.feec_vars_update(un1) + diffs = self.update_feec_variables(u=_u) - if self._info and MPI.COMM_WORLD.Get_rank() == 0: + if self.options.solver_params.info and MPI.COMM_WORLD.Get_rank() == 0: print("Status for CurrentCoupling5DDensity:", info["success"]) print("Iterations for CurrentCoupling5DDensity:", info["niter"]) - print("Maxdiff up for CurrentCoupling5DDensity:", max_du) + print("Maxdiff up for CurrentCoupling5DDensity:", diffs["u"]) print() @@ -2649,35 +2524,6 @@ class ImplicitDiffusion(Propagator): * :math:`\sigma_1=\sigma_2=0` and :math:`\sigma_3 = \Delta t`: **Poisson solver** with a given charge density :math:`\sum_i\rho_i`. * :math:`\sigma_2=0` and :math:`\sigma_1 = \sigma_3 = \Delta t` : Poisson with **adiabatic electrons**. * :math:`\sigma_1=\sigma_2=1` and :math:`\sigma_3 = 0`: **Implicit heat equation**. - - Parameters - ---------- - phi : StencilVector - FE coefficients of the solution as a discrete 0-form. - - sigma_1, sigma_2, sigma_3 : float | int - Equation parameters. - - divide_by_dt : bool - Whether to divide the sigmas by dt during __call__. - - stab_mat : str - Name of the matrix :math:`M^0_{n_0}`. - - diffusion_mat : str - Name of the matrix :math:`M^1_{D_0}`. - - rho : StencilVector or tuple or list - (List of) right-hand side FE coefficients of a 0-form (optional, can be set with a setter later). - Can be either a) StencilVector or b) 2-tuple, or a list of those. - In case b) the first tuple entry must be :class:`~struphy.pic.accumulation.particles_to_grid.AccumulatorVector`, - and the second entry must be :class:`~struphy.pic.base.Particles`. - - x0 : StencilVector - Initial guess for the iterative solver (optional, can be set with a setter later). - - solver : dict - Parameters for the iterative solver (see ``__init__`` for details). """ class Variables: @@ -2709,7 +2555,8 @@ class Options: divide_by_dt: bool = False stab_mat: OptsStabMat = "M0" diffusion_mat: OptsDiffusionMat = "M1" - rho: StencilVector | tuple | list | Callable = None + rho: FEECVariable | Callable | tuple[AccumulatorVector, Particles] | list = None + rho_coeffs: float | list = None x0: StencilVector = None solver: OptsSymmSolver = "pcg" precond: OptsMassPrecond = "MassMatrixPreconditioner" @@ -2744,10 +2591,10 @@ def options(self, new): @profile def allocate(self): # always stabilize - if np.abs(self.options.sigma_1) < 1e-14: + if xp.abs(self.options.sigma_1) < 1e-14: self.options.sigma_1 = 1e-14 if MPI.COMM_WORLD.Get_rank() == 0: - print(f"Stabilizing Poisson solve with {self.options.sigma_1 = }") + print(f"Stabilizing Poisson solve with {self.options.sigma_1 =}") # model parameters self._sigma_1 = self.options.sigma_1 @@ -2758,30 +2605,39 @@ def allocate(self): phi = self.variables.phi.spline.vector # collect rhs - rho = self.options.rho + def verify_rhs(rho) -> StencilVector | FEECVariable | AccumulatorVector: + """Perform preliminary operations on rho to comute the rhs and return the result.""" + if rho is None: + rhs = phi.space.zeros() + elif isinstance(rho, FEECVariable): + assert rho.space == "H1" + rhs = rho + elif isinstance(rho, AccumulatorVector): + rhs = rho + elif isinstance(rho, Callable): + rhs = L2Projector("H1", self.mass_ops).get_dofs(rho, apply_bc=True) + else: + raise TypeError(f"{type(rho) =} is not accepted.") - if rho is None: - self._rho = [phi.space.zeros()] + return rhs + + rho = self.options.rho + if isinstance(rho, list): + self._sources = [] + for r in rho: + self._sources += [verify_rhs(r)] else: - if isinstance(rho, list): - for r in rho: - if isinstance(r, tuple): - assert isinstance(r[0], AccumulatorVector) - assert isinstance(r[1], Particles) - # assert r.space_id == 'H1' - else: - assert r.space == phi.space - elif isinstance(rho, tuple): - assert isinstance(rho[0], AccumulatorVector) - assert isinstance(rho[1], Particles) - # assert rho[0].space_id == 'H1' - rho = [rho] - elif isinstance(rho, Callable): - rho = [rho()] + self._sources = [verify_rhs(rho)] + + # coeffs of rhs + if self.options.rho_coeffs is not None: + if isinstance(self.options.rho_coeffs, (list, tuple)): + self._coeffs = self.options.rho_coeffs else: - assert rho.space == phi.space - rho = [rho] - self._rho = rho + self._coeffs = [self.options.rho_coeffs] + assert len(self._coeffs) == len(self._sources) + else: + self._coeffs = [1.0 for src in self.sources] # initial guess and solver params self._x0 = self.options.x0 @@ -2827,65 +2683,41 @@ def allocate(self): self._tmp = phi.space.zeros() self._rhs = phi.space.zeros() self._rhs2 = phi.space.zeros() + self._tmp_src = phi.space.zeros() @property - def rho(self): + def sources(self) -> list[StencilVector | FEECVariable | AccumulatorVector]: """ - (List of) right-hand side FE coefficients of a 0-form. - The list entries can be either a) StencilVectors or b) 2-tuples; - in the latter case, the first tuple entry must be :class:`~struphy.pic.accumulation.particles_to_grid.AccumulatorVector`, - and the second entry must be :class:`~struphy.pic.base.Particles`. + Right-hand side of the equation (sources). """ - return self._rho + return self._sources - @rho.setter - def rho(self, value): - """In-place setter for StencilVector/PolarVector. - If rho is a list, len(value) msut be len(rho) and value can contain None. + @property + def coeffs(self) -> list[float]: """ - if isinstance(value, list): - assert len(value) == len(self.rho) - for i, (val, r) in enumerate(zip(value, self.rho)): - if val is None: - continue - elif isinstance(val, tuple): - assert isinstance(val[0], AccumulatorVector) - assert isinstance(val[1], Particles) - assert isinstance(r, tuple) - self._rho[i] = val - else: - assert val.space == r.space - r[:] = val[:] - elif isinstance(ValueError, tuple): - assert isinstance(value[0], AccumulatorVector) - assert isinstance(value[1], Particles) - assert len(self.rho) == 1 - # assert rho[0].space_id == 'H1' - self._rho[0] = value - else: - assert value.space == self.derham.Vh["0"] - assert len(self.rho) == 1 - self._rho[0][:] = value[:] + Same length as self.sources. Coefficients multiplied with sources before solve (default is 1.0). + """ + return self._coeffs @property def x0(self): """ psydac.linalg.stencil.StencilVector or struphy.polar.basic.PolarVector. First guess of the iterative solver. """ - return self._x0 + return self.options.x0 @x0.setter - def x0(self, value): + def x0(self, value: StencilVector): """In-place setter for StencilVector/PolarVector. First guess of the iterative solver.""" assert value.space == self.derham.Vh["0"] assert value.space.symbolic_space == "H1", ( f"Right-hand side must be in H1, but is in {value.space.symbolic_space}." ) - if self._x0 is None: - self._x0 = value + if self.options.x0 is None: + self.options.x0 = value else: - self._x0[:] = value[:] + self.options.x0[:] = value[:] @profile def __call__(self, dt): @@ -2905,12 +2737,15 @@ def __call__(self, dt): rhs *= sig_2 self._rhs2 *= 0.0 - for rho in self._rho: - if isinstance(rho, tuple): - rho[0]() # accumulate - self._rhs2 += sig_3 * rho[0].vectors[0] - else: - self._rhs2 += sig_3 * rho + for src, coeff in zip(self.sources, self.coeffs): + if isinstance(src, StencilVector): + self._rhs2 += sig_3 * coeff * src + elif isinstance(src, FEECVariable): + v = src.spline.vector + self._rhs2 += sig_3 * coeff * self.mass_ops.M0.dot(v, out=self._tmp_src) + elif isinstance(src, AccumulatorVector): + src() # accumulate + self._rhs2 += sig_3 * coeff * src.vectors[0] rhs += self._rhs2 @@ -2979,7 +2814,8 @@ class Options: # propagator options stab_eps: float = 0.0 stab_mat: OptsStabMat = "Id" - rho: StencilVector | tuple | list | Callable = None + rho: FEECVariable | Callable | tuple[AccumulatorVector, Particles] | list = None + rho_coeffs: float | list = None x0: StencilVector = None solver: OptsSymmSolver = "pcg" precond: OptsMassPrecond = "MassMatrixPreconditioner" @@ -3049,50 +2885,73 @@ class VariationalMomentumAdvection(Propagator): \hat{\mathbf{u}}_h^{n+1/2} = (\mathbf{u}^{n+1/2})^\top \vec{\boldsymbol \Lambda}^v \in (V_h^0)^3 \,, \qquad \hat{\mathbf A}^1_{\mu,h} = \nabla P_\mu((\mathbf u^{n+1/2})^\top \vec{\boldsymbol \Lambda}^v)] \in V_h^1\,, \qquad \hat{\rho}_h^{n} = (\rho^{n})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \,. """ - @staticmethod - def options(default=False): - dct = {} - dct["lin_solver"] = { - "tol": 1e-12, - "maxiter": 500, - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "verbose": False, - } - dct["nonlin_solver"] = { - "tol": 1e-8, - "maxiter": 100, - "type": ["Newton", "Picard"], - "info": False, - } - if default: - dct = descend_options_dict(dct, []) - return dct + class Variables: + def __init__(self): + self._u: FEECVariable = None - def __init__( - self, - u: BlockVector, - *, - mass_ops: H1vecMassMatrix_density, - lin_solver: dict = options(default=True)["lin_solver"], - nonlin_solver: dict = options(default=True)["nonlin_solver"], - ): - super().__init__(u) + @property + def u(self) -> FEECVariable: + return self._u + + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1vec" + self._u = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # propagator options + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + nonlin_solver: NonlinearSolverParameters = None + + def __post_init__(self): + # checks + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() - assert mass_ops is not None + if self.nonlin_solver is None: + self.nonlin_solver = NonlinearSolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - self._lin_solver = lin_solver - self._nonlin_solver = nonlin_solver + @profile + def allocate(self): + self._lin_solver = self.options.solver_params + self._nonlin_solver = self.options.nonlin_solver - self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = mass_ops + self._Mrho = self.mass_ops.WMM + self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) self._initialize_mass() # bunch of temporaries to avoid allocating in the loop + u = self.variables.u.spline.vector + self._tmp_un1 = u.space.zeros() self._tmp_un12 = u.space.zeros() self._tmp_diff = u.space.zeros() @@ -3108,25 +2967,25 @@ def __init__( self.inv_derivative = inverse( self._Mrho.inv @ self.derivative, "gmres", - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], - verbose=self._lin_solver["verbose"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, + verbose=self._lin_solver.verbose, recycle=True, ) def __call__(self, dt): - if self._nonlin_solver["type"] == "Newton": + if self._nonlin_solver.type == "Newton": self.__call_newton(dt) - elif self._nonlin_solver["type"] == "Picard": + elif self._nonlin_solver.type == "Picard": self.__call_picard(dt) def __call_newton(self, dt): # Initialize variable for Newton iteration - un = self.feec_vars[0] + un = self.variables.u.spline.vector mn = self._Mrho.massop.dot(un, out=self._tmp_mn) mn1 = mn.copy(out=self._tmp_mn1) un1 = un.copy(out=self._tmp_un1) - tol = self._nonlin_solver["tol"] + tol = self.options.nonlin_solver.tol err = tol + 1 # Jacobian matrix for Newton solve self._dt2_brack._scalar = dt / 2 @@ -3134,7 +2993,7 @@ def __call_newton(self, dt): print() print("Newton iteration in VariationalMomentumAdvection") - for it in range(self._nonlin_solver["maxiter"]): + for it in range(self.options.nonlin_solver.maxiter): un12 = un.copy(out=self._tmp_un12) un12 += un1 un12 *= 0.5 @@ -3154,7 +3013,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or np.isnan(err): + if err < tol**2 or xp.isnan(err): break # Newton step @@ -3168,26 +3027,26 @@ def __call_newton(self, dt): un1 -= update mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self.options.nonlin_solver.maxiter - 1 or xp.isnan(err): print( - f"!!!WARNING: Maximum iteration in VariationalMomentumAdvection reached - not converged \n {err = } \n {tol**2 = }", + f"!!!WARNING: Maximum iteration in VariationalMomentumAdvection reached - not converged \n {err =} \n {tol**2 =}", ) - self.feec_vars_update(un1) + self.update_feec_variables(u=un1) def __call_picard(self, dt): # Initialize variable for Picard iteration - un = self.feec_vars[0] + un = self.variables.u.spline.vector mn = self._Mrho.massop.dot(un, out=self._tmp_mn) mn1 = mn.copy(out=self._tmp_mn1) un1 = un.copy(out=self._tmp_un1) - tol = self._nonlin_solver["tol"] + tol = self.options.nonlin_solver.tol err = tol + 1 # Jacobian matrix for Newton solve - for it in range(self._nonlin_solver["maxiter"]): + for it in range(self.options.nonlin_solver.maxiter): # Picard iteration - if err < tol**2 or np.isnan(err): + if err < tol**2 or xp.isnan(err): break # half time step approximation un12 = un.copy(out=self._tmp_un12) @@ -3214,12 +3073,12 @@ def __call_picard(self, dt): # Inverse the mass matrix to get the velocity un1 = self._Mrho.inv.dot(mn1, out=self._tmp_un1) - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self.options.nonlin_solver.maxiter - 1 or xp.isnan(err): print( - f"!!!WARNING: Maximum iteration in VariationalMomentumAdvection reached - not converged \n {err = } \n {tol**2 = }", + f"!!!WARNING: Maximum iteration in VariationalMomentumAdvection reached - not converged \n {err =} \n {tol**2 =}", ) - self.feec_vars_update(un1) + self.update_feec_variables(u=un1) def _initialize_mass(self): """Initialization of the mass matrix solver""" @@ -3292,48 +3151,38 @@ class VariationalDensityEvolve(Propagator): \hat{\mathbf{u}}_h^{k} = (\mathbf{u}^{k})^\top \vec{\boldsymbol \Lambda}^v \in (V_h^0)^3 \, \text{for k in} \{n, n+1/2, n+1\}, \qquad \hat{\rho}_h^{k} = (\rho^{k})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \, \text{for k in} \{n, n+1/2, n+1\} . """ - @staticmethod - def options(default=False): - dct = {} - dct["lin_solver"] = { - "tol": 1e-12, - "maxiter": 500, - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "verbose": False, - "recycle": True, - } - dct["nonlin_solver"] = { - "tol": 1e-8, - "maxiter": 100, - "info": False, - "linearize": False, - } - dct["physics"] = {"gamma": 5 / 3} + class Variables: + def __init__(self): + self._rho: FEECVariable = None + self._u: FEECVariable = None - if default: - dct = descend_options_dict(dct, []) + @property + def rho(self) -> FEECVariable: + return self._rho - return dct + @rho.setter + def rho(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._rho = new - def __init__( - self, - rho: StencilVector, - u: BlockVector, - *, - model: str = "barotropic", - gamma: float = options()["physics"]["gamma"], - s: StencilVector = None, - mass_ops: H1vecMassMatrix_density, - lin_solver: dict = options(default=True)["lin_solver"], - nonlin_solver: dict = options(default=True)["nonlin_solver"], - energy_evaluator: InternalEnergyEvaluator = None, - ): - super().__init__(rho, u) + @property + def u(self) -> FEECVariable: + return self._u + + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1vec" + self._u = new + + def __init__(self): + self.variables = self.Variables() - assert model in [ + @dataclass + class Options: + # specific literals + OptsModel = Literal[ "pressureless", "barotropic", "full", @@ -3344,27 +3193,69 @@ def __init__( "linear_q", "deltaf_q", ] - if model == "full": - assert s is not None - assert mass_ops is not None + # propagator options + model: OptsModel = "barotropic" + gamma: float = 5.0 / 3.0 + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + nonlin_solver: NonlinearSolverParameters = None + s: FEECVariable = None + + def __post_init__(self): + # checks + check_option(self.model, self.OptsModel) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + if self.nonlin_solver is None: + self.nonlin_solver = NonlinearSolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - self._model = model - self._gamma = gamma - self._s = s - self._lin_solver = lin_solver - self._nonlin_solver = nonlin_solver - self._linearize = self._nonlin_solver["linearize"] + @profile + def allocate(self): + if self.options.model == "full": + assert self.options.s is not None - self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) + self._model = self.options.model + self._gamma = self.options.gamma + self._s = self.options.s + self._lin_solver = self.options.solver_params + self._nonlin_solver = self.options.nonlin_solver + self._linearize = self.options.nonlin_solver.linearize - self._Mrho = mass_ops + self._info = self.options.nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + + self._Mrho = self.mass_ops.WMM + self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) # Femfields for the projector self.rhof = self.derham.create_spline_function("rhof", "L2") self.rhof1 = self.derham.create_spline_function("rhof1", "L2") + rho = self.variables.rho.spline.vector + u = self.variables.u.spline.vector + # Projector - self._energy_evaluator = energy_evaluator + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) self._kinetic_evaluator = KineticEnergyEvaluator(self.derham, self.domain, self.mass_ops) self._initialize_projectors_and_mass() if self._model in ["linear", "linear_q"]: @@ -3400,6 +3291,7 @@ def __init__( if self._model in ["linear", "linear_q"]: self._update_Pirho(self.projected_equil.n3) + @profile def __call__(self, dt): self.__call_newton(dt) @@ -3411,15 +3303,15 @@ def __call_newton(self, dt): print("Newton iteration in VariationalDensityEvolve") # Initial variables - rhon = self.feec_vars[0] - un = self.feec_vars[1] + rhon = self.variables.rho.spline.vector + un = self.variables.u.spline.vector if self._model in ["linear", "linear_q"]: advection = self.divPirho.dot(un, out=self._tmp_rho_advection) advection *= dt rhon1 = rhon.copy(out=self._tmp_rhon1) rhon1 -= advection - self.feec_vars_update(rhon1, un) + self.update_feec_variables(rho=rhon1, u=un) return if self._model in ["deltaf", "deltaf_q"]: @@ -3432,7 +3324,7 @@ def __call_newton(self, dt): # Initialize variable for Newton iteration if self._model == "full": - s = self._s + s = self._s.spline.vector else: s = None @@ -3454,10 +3346,10 @@ def __call_newton(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver["tol"] + tol = self._nonlin_solver.tol err = tol + 1 - for it in range(self._nonlin_solver["maxiter"]): + for it in range(self._nonlin_solver.maxiter): # Newton iteration un12 = un.copy(out=self._tmp_un12) @@ -3503,7 +3395,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or np.isnan(err): + if err < tol**2 or xp.isnan(err): break # Derivative for Newton @@ -3533,14 +3425,14 @@ def __call_newton(self, dt): mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalDensityEvolve reached - not converged:\n {err = } \n {tol**2 = }", + f"!!!Warning: Maximum iteration in VariationalDensityEvolve reached - not converged:\n {err =} \n {tol**2 =}", ) self._tmp_un_diff = un1 - un self._tmp_rhon_diff = rhon1 - rhon - self.feec_vars_update(rhon1, un1) + self.update_feec_variables(rho=rhon1, u=un1) def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and `CoordinateProjector` needed to compute the bracket term""" @@ -3576,7 +3468,7 @@ def _initialize_projectors_and_mass(self): # tmps grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._rhof_values = np.zeros(grid_shape, dtype=float) + self._rhof_values = xp.zeros(grid_shape, dtype=float) # Other mass matrices for newton solve self._M_drho = self.mass_ops.create_weighted_mass("L2", "L2") @@ -3609,17 +3501,17 @@ def _initialize_projectors_and_mass(self): self._Jacobian, "pbicgstab", pc=self._Mrho.inv, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], - verbose=self._lin_solver["verbose"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, + verbose=self._lin_solver.verbose, recycle=True, ) # self._inv_Jacobian = inverse(self._Jacobian, # 'gmres', - # tol=self._lin_solver['tol'], - # maxiter=self._lin_solver['maxiter'], - # verbose=self._lin_solver['verbose'], + # tol=self._lin_solver.tol, + # maxiter=self._lin_solver.maxiter, + # verbose=self._lin_solver.verbose, # recycle=True) # L2-projector for V3 @@ -3628,20 +3520,20 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) # tmps - self._eval_dl_drho = np.zeros(grid_shape, dtype=float) + self._eval_dl_drho = xp.zeros(grid_shape, dtype=float) - self._uf_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._uf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._tmp_int_grid = np.zeros(grid_shape, dtype=float) - self._tmp_int_grid2 = np.zeros(grid_shape, dtype=float) - self._rhof_values = np.zeros(grid_shape, dtype=float) - self._rhof1_values = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) + self._tmp_int_grid2 = xp.zeros(grid_shape, dtype=float) + self._rhof_values = xp.zeros(grid_shape, dtype=float) + self._rhof1_values = xp.zeros(grid_shape, dtype=float) if self._model == "full": - self._tmp_de_drho = np.zeros(grid_shape, dtype=float) + self._tmp_de_drho = xp.zeros(grid_shape, dtype=float) gam = self._gamma - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -3649,7 +3541,7 @@ def _initialize_projectors_and_mass(self): ) self._proj_rho2_metric_term = deepcopy(metric) - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -3658,7 +3550,7 @@ def _initialize_projectors_and_mass(self): self._proj_drho_metric_term = deepcopy(metric) if self._linearize: - self._init_dener_drho = np.zeros(grid_shape, dtype=float) + self._init_dener_drho = xp.zeros(grid_shape, dtype=float) def _update_Pirho(self, rho): """Update the weights of the `BasisProjectionOperator` Pirho""" @@ -3672,7 +3564,7 @@ def _update_weighted_MM(self, rho): self._Mrho.update_weight(rho) def _update_linear_form_dl_drho(self, rhon, rhon1, un, un1, sn): - """Update the linearform representing integration in V3 against kynetic energy""" + """Update the linearform representing integration in V3 against kinetic energy""" self._kinetic_evaluator.get_u2_grid(un, un1, self._eval_dl_drho) @@ -3713,11 +3605,15 @@ def _update_linear_form_dl_drho(self, rhon, rhon1, un, un1, sn): def _compute_init_linear_form(self): if abs(self._gamma - 5 / 3) < 1e-3: self._energy_evaluator.evaluate_exact_de_drho_grid( - self.projected_equil.n3, self.projected_equil.s3_monoatomic, out=self._init_dener_drho + self.projected_equil.n3, + self.projected_equil.s3_monoatomic, + out=self._init_dener_drho, ) elif abs(self._gamma - 7 / 5) < 1e-3: self._energy_evaluator.evaluate_exact_de_drho_grid( - self.projected_equil.n3, self.projected_equil.s3_diatomic, out=self._init_dener_drho + self.projected_equil.n3, + self.projected_equil.s3_diatomic, + out=self._init_dener_drho, ) else: raise ValueError("Gamma should be 7/5 or 5/3 for if you want to linearize") @@ -3798,67 +3694,100 @@ class VariationalEntropyEvolve(Propagator): \hat{\mathbf{u}}_h^{k} = (\mathbf{u}^{k})^\top \vec{\boldsymbol \Lambda}^v \in (V_h^0)^3 \, \text{for k in} \{n, n+1/2, n+1\}, \qquad \hat{s}_h^{k} = (s^{k})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \, \text{for k in} \{n, n+1/2, n+1\} \qquad \hat{\rho}_h^{n} = (\rho^{n})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \. """ - @staticmethod - def options(default=False): - dct = {} - dct["lin_solver"] = { - "tol": 1e-12, - "maxiter": 500, - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "verbose": False, - } - dct["nonlin_solver"] = { - "tol": 1e-8, - "maxiter": 100, - "info": False, - "linearize": "False", - } - dct["physics"] = {"gamma": 5 / 3} + class Variables: + def __init__(self): + self._s: FEECVariable = None + self._u: FEECVariable = None - if default: - dct = descend_options_dict(dct, []) + @property + def s(self) -> FEECVariable: + return self._s - return dct + @s.setter + def s(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._s = new - def __init__( - self, - s: StencilVector, - u: BlockVector, - *, - model: str = "full", - gamma: float = options()["physics"]["gamma"], - rho: StencilVector, - mass_ops: H1vecMassMatrix_density, - lin_solver: dict = options(default=True)["lin_solver"], - nonlin_solver: dict = options(default=True)["nonlin_solver"], - energy_evaluator: InternalEnergyEvaluator = None, - ): - super().__init__(s, u) + @property + def u(self) -> FEECVariable: + return self._u - assert model in ["full"] - if model == "full": - assert rho is not None - assert mass_ops is not None + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1vec" + self._u = new - self._model = model - self._gamma = gamma - self._rho = rho - self._lin_solver = lin_solver - self._nonlin_solver = nonlin_solver - self._linearize = self._nonlin_solver["linearize"] + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # specific literals + OptsModel = Literal["full"] + # propagator options + model: OptsModel = "full" + gamma: float = 5.0 / 3.0 + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + nonlin_solver: NonlinearSolverParameters = None + rho: FEECVariable = None + + def __post_init__(self): + # checks + check_option(self.model, self.OptsModel) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + if self.nonlin_solver is None: + self.nonlin_solver = NonlinearSolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + if self.options.model == "full": + assert self.options.rho is not None + + self._model = self.options.model + self._gamma = self.options.gamma + self._rho = self.options.rho + self._lin_solver = self.options.solver_params + self._nonlin_solver = self.options.nonlin_solver + self._linearize = self.options.nonlin_solver.linearize - self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = mass_ops + self._Mrho = self.mass_ops.WMM + self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) # Projector - self._energy_evaluator = energy_evaluator + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop + s = self.variables.s.spline.vector + u = self.variables.u.spline.vector + self._tmp_un1 = u.space.zeros() self._tmp_un2 = u.space.zeros() self._tmp_un12 = u.space.zeros() @@ -3886,12 +3815,12 @@ def __call_newton(self, dt): if self._info: print() print("Newton iteration in VariationalEntropyEvolve") - sn = self.feec_vars[0] - un = self.feec_vars[1] + sn = self.variables.s.spline.vector + un = self.variables.u.spline.vector sn1 = sn.copy(out=self._tmp_sn1) # Initialize variable for Newton iteration - rho = self._rho + rho = self._rho.spline.vector self._update_Pis(sn) mn = self._Mrho.massop.dot(un, out=self._tmp_mn) @@ -3900,10 +3829,10 @@ def __call_newton(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver["tol"] + tol = self._nonlin_solver.tol err = tol + 1 - for it in range(self._nonlin_solver["maxiter"]): + for it in range(self._nonlin_solver.maxiter): # Newton iteration un12 = un.copy(out=self._tmp_un12) @@ -3941,7 +3870,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or np.isnan(err): + if err < tol**2 or xp.isnan(err): break # Derivative for Newton @@ -3963,13 +3892,13 @@ def __call_newton(self, dt): # Multiply by the mass matrix to get the momentum mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalEntropyEvolve reached - not converged:\n {err = } \n {tol**2 = }", + f"!!!Warning: Maximum iteration in VariationalEntropyEvolve reached - not converged:\n {err =} \n {tol**2 =}", ) self._tmp_sn_diff = sn1 - sn self._tmp_un_diff = un1 - un - self.feec_vars_update(sn1, un1) + self.update_feec_variables(s=sn1, u=un1) def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and `CoordinateProjector` needed to compute the bracket term""" @@ -4022,19 +3951,19 @@ def _initialize_projectors_and_mass(self): self._inv_Jacobian = SchurSolverFull( self._Jacobian, - self._lin_solver["type"][0], + self.options.solver, pc=self._Mrho.inv, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], - verbose=self._lin_solver["verbose"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, + verbose=self._lin_solver.verbose, recycle=True, ) # self._inv_Jacobian = inverse(self._Jacobian, # 'gmres', - # tol=self._lin_solver['tol'], - # maxiter=self._lin_solver['maxiter'], - # verbose=self._lin_solver['verbose'], + # tol=self._lin_solver.tol, + # maxiter=self._lin_solver.maxiter, + # verbose=self._lin_solver.verbose, # recycle=True) # prepare for integration of linear form @@ -4050,15 +3979,15 @@ def _initialize_projectors_and_mass(self): ) grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._tmp_int_grid = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) if self._model == "full": - self._tmp_de_ds = np.zeros(grid_shape, dtype=float) + self._tmp_de_ds = xp.zeros(grid_shape, dtype=float) if self._linearize: - self._init_dener_ds = np.zeros(grid_shape, dtype=float) + self._init_dener_ds = xp.zeros(grid_shape, dtype=float) gam = self._gamma - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -4066,7 +3995,7 @@ def _initialize_projectors_and_mass(self): ) self._proj_rho2_metric_term = deepcopy(metric) - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -4099,11 +4028,15 @@ def _update_linear_form_dl_ds(self, rhon, sn, sn1): def _compute_init_linear_form(self): if abs(self._gamma - 5 / 3) < 1e-3: self._energy_evaluator.evaluate_exact_de_ds_grid( - self.projected_equil.n3, self.projected_equil.s3_monoatomic, out=self._init_dener_ds + self.projected_equil.n3, + self.projected_equil.s3_monoatomic, + out=self._init_dener_ds, ) elif abs(self._gamma - 7 / 5) < 1e-3: self._energy_evaluator.evaluate_exact_de_ds_grid( - self.projected_equil.n3, self.projected_equil.s3_diatomic, out=self._init_dener_ds + self.projected_equil.n3, + self.projected_equil.s3_diatomic, + out=self._init_dener_ds, ) else: raise ValueError("Gamma should be 7/5 or 5/3 for if you want to linearize") @@ -4168,58 +4101,91 @@ class VariationalMagFieldEvolve(Propagator): """ - @staticmethod - def options(default=False): - dct = {} - dct["lin_solver"] = { - "tol": 1e-12, - "maxiter": 500, - "non_linear_maxiter": 100, - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "verbose": False, - } - dct["nonlin_solver"] = { - "tol": 1e-8, - "maxiter": 100, - "info": False, - "linearize": False, - } + class Variables: + def __init__(self): + self._u: FEECVariable = None + self._b: FEECVariable = None - if default: - dct = descend_options_dict(dct, []) + @property + def u(self) -> FEECVariable: + return self._u - return dct + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1vec" + self._u = new - def __init__( - self, - b: BlockVector, - u: BlockVector, - *, - model: str = "full", - mass_ops: H1vecMassMatrix_density, - lin_solver: dict = options(default=True)["lin_solver"], - nonlin_solver: dict = options(default=True)["nonlin_solver"], - ): - super().__init__(b, u) + @property + def b(self) -> FEECVariable: + return self._b + + @b.setter + def b(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hdiv" + self._b = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + OptsModel = Literal["full", "full_p", "linear"] + # propagator options + model: OptsModel = "full" + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + nonlin_solver: NonlinearSolverParameters = None + + def __post_init__(self): + # checks + check_option(self.model, self.OptsModel) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + if self.nonlin_solver is None: + self.nonlin_solver = NonlinearSolverParameters(type="Newton") + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - assert model in ["full", "full_p", "linear"] - self._model = model - self._mass_ops = mass_ops - self._lin_solver = lin_solver - self._nonlin_solver = nonlin_solver - self._linearize = self._nonlin_solver["linearize"] + @profile + def allocate(self): + self._model = self.options.model + self._lin_solver = self.options.solver_params + self._nonlin_solver = self.options.nonlin_solver + self._linearize = self._nonlin_solver.linearize - self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = mass_ops + self._Mrho = self.mass_ops.WMM + self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) # Projector self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop + u = self.variables.u.spline.vector + b = self.variables.b.spline.vector + self._tmp_un1 = u.space.zeros() self._tmp_un2 = u.space.zeros() self._tmp_un12 = u.space.zeros() @@ -4250,8 +4216,8 @@ def __call_newton(self, dt): print() print("Newton iteration in VariationalMagFieldEvolve") # Compute implicit approximation of s^{n+1} - bn = self.feec_vars[0] - un = self.feec_vars[1] + un = self.variables.u.spline.vector + bn = self.variables.b.spline.vector bn1 = bn.copy(out=self._tmp_bn1) # Initialize variable for Newton iteration @@ -4264,10 +4230,10 @@ def __call_newton(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver["tol"] + tol = self._nonlin_solver.tol err = tol + 1 - for it in range(self._nonlin_solver["maxiter"]): + for it in range(self._nonlin_solver.maxiter): # Newton iteration # half time step approximation bn12 = bn.copy(out=self._tmp_bn12) @@ -4328,7 +4294,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or np.isnan(err): + if err < tol**2 or xp.isnan(err): break # Derivative for Newton @@ -4350,20 +4316,18 @@ def __call_newton(self, dt): # Multiply by the mass matrix to get the momentum mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalMagFieldEvolve reached - not converged:\n {err = } \n {tol**2 = }", + f"!!!Warning: Maximum iteration in VariationalMagFieldEvolve reached - not converged:\n {err =} \n {tol**2 =}", ) self._tmp_un_diff = un1 - un self._tmp_bn_diff = bn1 - bn - self.feec_vars_update(bn1, un1) + self.update_feec_variables(b=bn1, u=un1) def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" - from struphy.feec.variational_utilities import Hdiv0_transport_operator - self.curlPib = Hdiv0_transport_operator(self.derham) self.curlPibT = self.curlPib.T @@ -4412,15 +4376,13 @@ def _initialize_projectors_and_mass(self): self._Jacobian[1, 0] = self._dt2_curlPib self._Jacobian[1, 1] = self._I2 - from struphy.linear_algebra.schur_solver import SchurSolverFull - self._inv_Jacobian = SchurSolverFull( self._Jacobian, - self._lin_solver["type"][0], + self.options.solver, pc=self._Mrho.inv, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], - verbose=self._lin_solver["verbose"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, + verbose=self._lin_solver.verbose, recycle=True, ) @@ -4438,8 +4400,6 @@ def _update_Pib(self, b): self.curlPibT.update_coeffs(b) def _create_Pib0(self): - from struphy.feec.variational_utilities import Hdiv0_transport_operator - self.curlPib0 = Hdiv0_transport_operator(self.derham) self.curlPibT0 = self.curlPib0.T @@ -4518,79 +4478,137 @@ class VariationalPBEvolve(Propagator): .. math:: - \hat{\mathbf{B}}_h^{n+1/2} = (\mathbf{b}^{n+\frac{1}{2}})^\top \vec{\boldsymbol \Lambda}^2 \in V_h^2 \, - \qquad \hat{\rho}_h^{n} = (\boldsymbol \rho^{n})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \, - \qquad \hat{p}_h^{n+1/2} = (\boldsymbol p^{n+1/2})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \,. + \hat{\mathbf{B}}_h^{n+1/2} = (\mathbf{b}^{n+\frac{1}{2}})^\top \vec{\boldsymbol \Lambda}^2 \in V_h^2 \, + \qquad \hat{\rho}_h^{n} = (\boldsymbol \rho^{n})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \, + \qquad \hat{p}_h^{n+1/2} = (\boldsymbol p^{n+1/2})^\top \vec{\boldsymbol \Lambda}^3 \in V_h^3 \,. + + and :math:`\mathcal{U}^v` is :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators`. + """ + + class Variables: + def __init__(self): + self._p: FEECVariable = None + self._u: FEECVariable = None + self._b: FEECVariable = None + + @property + def p(self) -> FEECVariable: + return self._p + + @p.setter + def p(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._p = new + + @property + def u(self) -> FEECVariable: + return self._u + + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1vec" + self._u = new + + @property + def b(self) -> FEECVariable: + return self._b + + @b.setter + def b(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hdiv" + self._b = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # specific literals + OptsModel = Literal["full_p", "linear", "deltaf"] + # propagator options + model: OptsModel = "full_p" + gamma: float = 5.0 / 3.0 + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + nonlin_solver: NonlinearSolverParameters = None + div_u: FEECVariable = None + u2: FEECVariable = None + pt3: FEECVariable = None + bt2: FEECVariable = None + + def __post_init__(self): + # checks + check_option(self.model, self.OptsModel) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() - and :math:`\mathcal{U}^v` is :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators`. - """ + if self.nonlin_solver is None: + self.nonlin_solver = NonlinearSolverParameters() - @staticmethod - def options(default=False): - dct = {} - dct["lin_solver"] = { - "tol": 1e-12, - "maxiter": 500, - "non_linear_maxiter": 100, - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "verbose": False, - } - dct["nonlin_solver"] = { - "tol": 1e-8, - "maxiter": 100, - "type": ["Picard"], - "info": False, - "linearize": False, - } - dct["physics"] = {"gamma": 5 / 3} + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options - if default: - dct = descend_options_dict(dct, []) + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - return dct + @profile + def allocate(self): + self._model = self.options.model + self._lin_solver = self.options.solver_params + self._nonlin_solver = self.options.nonlin_solver + self._linearize = self.options.nonlin_solver.linearize + self._gamma = self.options.gamma + + if self.options.div_u is None: + self._divu = None + else: + self._divu = self.options.div_u.spline.vector - def __init__( - self, - p: StencilVector, - b: BlockVector, - u: BlockVector, - *, - model: str = "full", - gamma: float = options()["physics"]["gamma"], - mass_ops: H1vecMassMatrix_density, - lin_solver: dict = options(default=True)["lin_solver"], - nonlin_solver: dict = options(default=True)["nonlin_solver"], - div_u: StencilVector | None = None, - u2: BlockVector | None = None, - pt3: StencilVector | None = None, - bt2: BlockVector | None = None, - ): - super().__init__(p, b, u) + if self.options.u2 is None: + self._u2 = None + else: + self._u2 = self.options.u2.spline.vector - assert model in ["full_p", "linear", "deltaf"] - self._model = model - self._mass_ops = mass_ops - self._lin_solver = lin_solver - self._nonlin_solver = nonlin_solver - self._linearize = self._nonlin_solver["linearize"] - self._gamma = gamma + if self.options.pt3 is None: + self._pt3 = None + else: + self._pt3 = self.options.pt3.spline.vector - self._divu = div_u - self._u2 = u2 - self._pt3 = pt3 - self._bt2 = bt2 + if self.options.bt2 is None: + self._bt2 = None + else: + self._bt2 = self.options.bt2.spline.vector - self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) + self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) - self._Mrho = mass_ops + self._Mrho = self.mass_ops.WMM + self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) # Projector self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop + u = self.variables.u.spline.vector + p = self.variables.p.spline.vector + b = self.variables.b.spline.vector + self._tmp_un1 = u.space.zeros() self._tmp_un2 = u.space.zeros() self._tmp_un12 = u.space.zeros() @@ -4624,7 +4642,7 @@ def __init__( self._extracted_b2 = self.derham.extraction_ops["2"].dot(self.projected_equil.b2) def __call__(self, dt): - if self._nonlin_solver["type"] == "Picard": + if self._nonlin_solver.type == "Picard": self.__call_picard(dt) else: raise ValueError("Only Picard solver is implemented for VariationalPBEvolve") @@ -4636,9 +4654,9 @@ def __call_picard(self, dt): print() print("Newton iteration in VariationalPBEvolve") - pn = self.feec_vars[0] - bn = self.feec_vars[1] - un = self.feec_vars[2] + un = self.variables.u.spline.vector + pn = self.variables.p.spline.vector + bn = self.variables.b.spline.vector self._update_Pib(bn) self._update_Projp(pn) @@ -4651,10 +4669,10 @@ def __call_picard(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver["tol"] + tol = self._nonlin_solver.tol err = tol + 1 - for it in range(self._nonlin_solver["maxiter"]): + for it in range(self._nonlin_solver.maxiter): # Picard iteration # half time step approximation @@ -4779,7 +4797,7 @@ def __call_picard(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or np.isnan(err): + if err < tol**2 or xp.isnan(err): break # Derivative for Newton @@ -4801,15 +4819,15 @@ def __call_picard(self, dt): # Multiply by the mass matrix to get the momentum mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalPBEvolve reached - not converged:\n {err = } \n {tol**2 = }", + f"!!!Warning: Maximum iteration in VariationalPBEvolve reached - not converged:\n {err =} \n {tol**2 =}", ) self._tmp_un_diff = un1 - un self._tmp_bn_diff = bn1 - bn self._tmp_pn_diff = pn1 - pn - self.feec_vars_update(pn1, bn1, un1) + self.update_feec_variables(p=pn1, b=bn1, u=un1) self._transop_p.div.dot(un12, out=self._divu) self._transop_p._Uv.dot(un1, out=self._u2) @@ -4835,9 +4853,6 @@ def __call_picard(self, dt): def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import Hdiv0_transport_operator, Pressure_transport_operator - self.curlPib = Hdiv0_transport_operator(self.derham) self.curlPibT = self.curlPib.T self._transop_p = Pressure_transport_operator(self.derham, self.domain, self.basis_ops.Uv, self._gamma) @@ -4853,7 +4868,7 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._tmp_int_grid = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) # Inverse mass matrix needed to compute the error self.pc_Mv = preconditioner.MassMatrixDiagonalPreconditioner( @@ -4925,11 +4940,11 @@ def _initialize_projectors_and_mass(self): self._inv_Jacobian = SchurSolverFull( self._Jacobian, - self._lin_solver["type"][0], + self.options.solver, pc=self._Mrho.inv, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], - verbose=self._lin_solver["verbose"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, + verbose=self._lin_solver.verbose, recycle=True, ) @@ -4948,8 +4963,6 @@ def _update_Pib(self, b): self.curlPibT.update_coeffs(b) def _create_Pib0(self): - from struphy.feec.variational_utilities import Hdiv0_transport_operator - self.curlPib0 = Hdiv0_transport_operator(self.derham) self.curlPibT0 = self.curlPib.T self.curlPib0.update_coeffs(self.projected_equil.b2) @@ -4962,7 +4975,6 @@ def _update_Projp(self, p): def _create_transop0(self): """Update the weights of the `BasisProjectionOperator`""" - from struphy.feec.variational_utilities import Pressure_transport_operator self._transop_p0 = Pressure_transport_operator(self.derham, self.domain, self.basis_ops.Uv, self._gamma) self._transop_p0T = self._transop_p0.T @@ -5071,72 +5083,130 @@ class VariationalQBEvolve(Propagator): and :math:`\mathcal{U}^v` is :class:`~struphy.feec.basis_projection_ops.BasisProjectionOperators`. """ - @staticmethod - def options(default=False): - dct = {} - dct["lin_solver"] = { - "tol": 1e-12, - "maxiter": 500, - "non_linear_maxiter": 100, - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "verbose": False, - } - dct["nonlin_solver"] = { - "tol": 1e-8, - "maxiter": 100, - "type": ["Picard"], - "info": False, - "linearize": False, - } - dct["physics"] = {"gamma": 5 / 3} + class Variables: + def __init__(self): + self._q: FEECVariable = None + self._u: FEECVariable = None + self._b: FEECVariable = None - if default: - dct = descend_options_dict(dct, []) + @property + def q(self) -> FEECVariable: + return self._q - return dct + @q.setter + def q(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._q = new - def __init__( - self, - q: StencilVector, - b: BlockVector, - u: BlockVector, - *, - model: str = "full", - gamma: float = options()["physics"]["gamma"], - mass_ops: H1vecMassMatrix_density, - lin_solver: dict = options(default=True)["lin_solver"], - nonlin_solver: dict = options(default=True)["nonlin_solver"], - div_u: StencilVector | None = None, - u2: BlockVector | None = None, - qt3: StencilVector | None = None, - bt2: BlockVector | None = None, - ): - super().__init__(q, b, u) + @property + def u(self) -> FEECVariable: + return self._u + + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1vec" + self._u = new + + @property + def b(self) -> FEECVariable: + return self._b + + @b.setter + def b(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hdiv" + self._b = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # specific literals + OptsModel = Literal["full_q", "linear_q", "deltaf_q"] + # propagator options + model: OptsModel = "full_q" + gamma: float = 5.0 / 3.0 + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + nonlin_solver: NonlinearSolverParameters = None + div_u: FEECVariable = None + u2: FEECVariable = None + qt3: FEECVariable = None + bt2: FEECVariable = None + + def __post_init__(self): + # checks + check_option(self.model, self.OptsModel) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + if self.nonlin_solver is None: + self.nonlin_solver = NonlinearSolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - assert model in ["full_q", "linear_q", "deltaf_q"] - self._model = model - self._mass_ops = mass_ops - self._lin_solver = lin_solver - self._nonlin_solver = nonlin_solver - self._linearize = self._nonlin_solver["linearize"] - self._gamma = gamma + @profile + def allocate(self): + self._model = self.options.model + self._lin_solver = self.options.solver_params + self._nonlin_solver = self.options.nonlin_solver + self._linearize = self.options.nonlin_solver.linearize + self._gamma = self.options.gamma + + if self.options.div_u is None: + self._divu = None + else: + self._divu = self.options.div_u.spline.vector + + if self.options.u2 is None: + self._u2 = None + else: + self._u2 = self.options.u2.spline.vector + + if self.options.qt3 is None: + self._qt3 = None + else: + self._qt3 = self.options.qt3.spline.vector - self._divu = div_u - self._u2 = u2 - self._qt3 = qt3 - self._bt2 = bt2 + if self.options.bt2 is None: + self._bt2 = None + else: + self._bt2 = self.options.bt2.spline.vector - self._info = self._nonlin_solver["info"] and (self.rank == 0) + self._info = self._nonlin_solver.info and (self.rank == 0) - self._Mrho = mass_ops + self._Mrho = self.mass_ops.WMM + self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) # Projector self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop + u = self.variables.u.spline.vector + q = self.variables.q.spline.vector + b = self.variables.b.spline.vector + self._tmp_un1 = u.space.zeros() self._tmp_un12 = u.space.zeros() self._tmp_bn1 = b.space.zeros() @@ -5168,7 +5238,7 @@ def __init__( self._extracted_q3 = self.derham.extraction_ops["3"].dot(self.projected_equil.q3) def __call__(self, dt): - if self._nonlin_solver["type"] == "Picard": + if self._nonlin_solver.type == "Picard": self.__call_picard(dt) else: raise ValueError("Only Picard solver is implemented for VariationalQBEvolve") @@ -5180,9 +5250,9 @@ def __call_picard(self, dt): print() print("Newton iteration in VariationalQBEvolve") - qn = self.feec_vars[0] - bn = self.feec_vars[1] - un = self.feec_vars[2] + un = self.variables.u.spline.vector + qn = self.variables.q.spline.vector + bn = self.variables.b.spline.vector self._update_Pib(bn) self._update_Projq(qn) @@ -5195,10 +5265,10 @@ def __call_picard(self, dt): un1 = un.copy(out=self._tmp_un1) un1 += self._tmp_un_diff mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - tol = self._nonlin_solver["tol"] + tol = self._nonlin_solver.tol err = tol + 1 - for it in range(self._nonlin_solver["maxiter"]): + for it in range(self._nonlin_solver.maxiter): # Picard iteration # half time step approximation @@ -5316,7 +5386,7 @@ def __call_picard(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if err < tol**2 or np.isnan(err): + if err < tol**2 or xp.isnan(err): break # Derivative for Newton @@ -5340,15 +5410,15 @@ def __call_picard(self, dt): # Multiply by the mass matrix to get the momentum mn1 = self._Mrho.massop.dot(un1, out=self._tmp_mn1) - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self._nonlin_solver.maxiter - 1 or xp.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalPBEvolve reached - not converged:\n {err = } \n {tol**2 = }", + f"!!!Warning: Maximum iteration in VariationalPBEvolve reached - not converged:\n {err =} \n {tol**2 =}", ) self._tmp_un_diff = un1 - un self._tmp_bn_diff = bn1 - bn self._tmp_qn_diff = qn1 - qn - self.feec_vars_update(qn1, bn1, un1) + self.update_feec_variables(q=qn1, b=bn1, u=un1) self._transop_q.div.dot(un12, out=self._divu) self._transop_q._Uv.dot(un1, out=self._u2) @@ -5374,9 +5444,6 @@ def __call_picard(self, dt): def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" - from struphy.feec.projectors import L2Projector - from struphy.feec.variational_utilities import Hdiv0_transport_operator, Pressure_transport_operator - self.curlPib = Hdiv0_transport_operator(self.derham) self.curlPibT = self.curlPib.T self._transop_q = Pressure_transport_operator(self.derham, self.domain, self.basis_ops.Uv, self._gamma / 2.0) @@ -5392,7 +5459,7 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._tmp_int_grid = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) # Inverse mass matrix needed to compute the error self.pc_Mv = preconditioner.MassMatrixDiagonalPreconditioner( @@ -5483,11 +5550,11 @@ def _initialize_projectors_and_mass(self): self._inv_Jacobian = SchurSolverFull3( self._Jacobian, - self._lin_solver["type"][0], + self.options.solver, pc=self._Mrho.inv, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], - verbose=self._lin_solver["verbose"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, + verbose=self._lin_solver.verbose, recycle=True, ) @@ -5506,8 +5573,6 @@ def _update_Pib(self, b): self.curlPibT.update_coeffs(b) def _create_Pib0(self): - from struphy.feec.variational_utilities import Hdiv0_transport_operator - self.curlPib0 = Hdiv0_transport_operator(self.derham) self.curlPibT0 = self.curlPib.T self.curlPib0.update_coeffs(self.projected_equil.b2) @@ -5520,7 +5585,6 @@ def _update_Projq(self, q): def _create_transop0(self): """Update the weights of the `BasisProjectionOperator`""" - from struphy.feec.variational_utilities import Pressure_transport_operator self._transop_q0 = Pressure_transport_operator(self.derham, self.domain, self.basis_ops.Uv, self._gamma / 2.0) self._transop_q0T = self._transop_q0.T @@ -5625,72 +5689,95 @@ class VariationalViscosity(Propagator): """ - @staticmethod - def options(default=False): - dct = {} - dct["lin_solver"] = { - "tol": 1e-12, - "maxiter": 500, - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "verbose": False, - } - dct["nonlin_solver"] = { - "tol": 1e-8, - "maxiter": 100, - "type": ["Newton"], - "info": False, - "fast": False, - } - dct["physics"] = { - "gamma": 1.66666666667, - "mu": 0.0, - "mu_a": 0.0, - "alpha": 0.0, - } + class Variables: + def __init__(self): + self._s: FEECVariable = None + self._u: FEECVariable = None - if default: - dct = descend_options_dict(dct, []) + @property + def s(self) -> FEECVariable: + return self._s - return dct + @s.setter + def s(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._s = new - def __init__( - self, - s: StencilVector, - u: BlockVector, - *, - model: str = "barotropic", - gamma: float = options()["physics"]["gamma"], - rho: StencilVector, - mu: float = options()["physics"]["mu"], - mu_a: float = options()["physics"]["mu_a"], - alpha: float = options()["physics"]["alpha"], - mass_ops: H1vecMassMatrix_density, - lin_solver: dict = options(default=True)["lin_solver"], - nonlin_solver: dict = options(default=True)["nonlin_solver"], - energy_evaluator: InternalEnergyEvaluator = None, - pt3: StencilVector | None = None, - ): - super().__init__(s, u) + @property + def u(self) -> FEECVariable: + return self._u - assert model in ["full", "full_p", "full_q", "linear_p", "linear_q", "deltaf_q"] + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1vec" + self._u = new - self._model = model - self._gamma = gamma - self._lin_solver = lin_solver - self._nonlin_solver = nonlin_solver - self._mu_a = mu_a - self._alpha = alpha - self._mu = mu - self._rho = rho - self._pt3 = pt3 - self._energy_evaluator = energy_evaluator + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # specific literals + OptsModel = Literal["full", "full_p", "full_q", "linear_p", "linear_q", "deltaf_q"] + # propagator options + model: OptsModel = "full" + gamma: float = 5.0 / 3.0 + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixDiagonalPreconditioner" + solver_params: SolverParameters = None + nonlin_solver: NonlinearSolverParameters = None + rho: FEECVariable = None + pt3: FEECVariable = None + mu: float = 0.0 + mu_a: float = 0.0 + alpha: float = 0.0 + + def __post_init__(self): + # checks + check_option(self.model, self.OptsModel) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() - self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) + if self.nonlin_solver is None: + self.nonlin_solver = NonlinearSolverParameters(type="Newton") - self._Mrho = mass_ops + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + self._model = self.options.model + self._gamma = self.options.gamma + self._lin_solver = self.options.solver_params + self._nonlin_solver = self.options.nonlin_solver + self._mu_a = self.options.mu_a + self._alpha = self.options.alpha + self._mu = self.options.mu + self._rho = self.options.rho + self._pt3 = self.options.pt3 + + self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) + + self._Mrho = self.mass_ops.WMM + self._Mrho.inv._options["pc"] = MassMatrixDiagonalPreconditioner(self._Mrho.massop) # Femfields for the projector self.sf = self.derham.create_spline_function("sf", "L2") @@ -5705,9 +5792,13 @@ def __init__( self.gu122f = self.derham.create_spline_function("gu122", "Hcurl") # Projector + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop + u = self.variables.u.spline.vector + s = self.variables.s.spline.vector + self._tmp_un1 = u.space.zeros() self._tmp_un12 = u.space.zeros() self._tmp_sn1 = s.space.zeros() @@ -5724,7 +5815,7 @@ def __init__( self.tot_rhs = s.space.zeros() def __call__(self, dt): - if self._nonlin_solver["type"] == "Newton": + if self._nonlin_solver.type == "Newton": self.__call_newton(dt) else: raise ValueError( @@ -5734,10 +5825,11 @@ def __call__(self, dt): def __call_newton(self, dt): """Solve the non linear system for updating the variables using Newton iteration method""" # Compute dissipation implicitely - sn = self.feec_vars[0] - un = self.feec_vars[1] + sn = self.variables.s.spline.vector + un = self.variables.u.spline.vector + if self._mu < 1.0e-15 and self._mu_a < 1.0e-15 and self._alpha < 1.0e-15: - self.feec_vars_update(sn, un) + self.update_feec_variables(s=sn, u=un) return if self._info: @@ -5755,7 +5847,7 @@ def __call_newton(self, dt): print("information on the linear solver : ", self.inv_lop._info) if self._model == "linear_p" or (self._model == "linear_q" and self._nonlin_solver["fast"]): - self.feec_vars_update(sn, un1) + self.update_feec_variables(s=sn, u=un1) return # Energy balance term @@ -5764,7 +5856,7 @@ def __call_newton(self, dt): # 2) Initial energy and linear form rho = self._rho if self._model in ["deltaf_q", "linear_q"]: - self.sf.vector = self._pt3 + self.sf.vector = self._pt3.spline.vector else: self.sf.vector = sn @@ -5820,7 +5912,7 @@ def __call_newton(self, dt): for it in range(self._nonlin_solver["maxiter"]): if self._model in ["deltaf_q", "linear_q"]: - self.sf1.vector = self._pt3 + self.sf1.vector = self._pt3.spline.vector else: self.sf1.vector = sn1 @@ -5872,7 +5964,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if (err < tol**2 and it > 0) or np.isnan(err): + if (err < tol**2 and it > 0) or xp.isnan(err): # force at least one iteration break @@ -5910,18 +6002,16 @@ def __call_newton(self, dt): else: sn1 += incr - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or xp.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalViscosity reached - not converged:\n {err = } \n {tol**2 = }", + f"!!!Warning: Maximum iteration in VariationalViscosity reached - not converged:\n {err =} \n {tol**2 =}", ) - self.feec_vars_update(sn1, un1) + self.update_feec_variables(s=sn1, u=un1) def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" - from struphy.feec.projectors import L2Projector - Xv = getattr(self.basis_ops, "Xv") Pcoord0 = CoordinateProjector( 0, @@ -5956,12 +6046,12 @@ def _initialize_projectors_and_mass(self): self.M_de_ds = self.mass_ops.create_weighted_mass("L2", "L2") - if self._lin_solver["type"][1] is None: + if self.options.precond is None: self.pc_jac = None else: pc_class = getattr( preconditioner, - self._lin_solver["type"][1], + self.options.precond, ) self.pc_jac = pc_class(self.M_de_ds) @@ -5969,8 +6059,8 @@ def _initialize_projectors_and_mass(self): self.M_de_ds, "pcg", pc=self.pc_jac, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, verbose=False, recycle=True, ) @@ -6011,8 +6101,8 @@ def _initialize_projectors_and_mass(self): self.l_op, "pcg", pc=self._Mrho.inv, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, verbose=False, recycle=True, ) @@ -6048,35 +6138,35 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._guf0_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf2_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf0_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf2_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf120_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf121_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._guf122_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf120_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf121_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._guf122_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._uf1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._uf12_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._uf12_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._gu_sq_values = np.zeros(grid_shape, dtype=float) - self._u_sq_values = np.zeros(grid_shape, dtype=float) - self._gu_init_values = np.zeros(grid_shape, dtype=float) + self._gu_sq_values = xp.zeros(grid_shape, dtype=float) + self._u_sq_values = xp.zeros(grid_shape, dtype=float) + self._gu_init_values = xp.zeros(grid_shape, dtype=float) - self._sf_values = np.zeros(grid_shape, dtype=float) - self._sf1_values = np.zeros(grid_shape, dtype=float) - self._rhof_values = np.zeros(grid_shape, dtype=float) + self._sf_values = xp.zeros(grid_shape, dtype=float) + self._sf1_values = xp.zeros(grid_shape, dtype=float) + self._rhof_values = xp.zeros(grid_shape, dtype=float) - self._e_n1 = np.zeros(grid_shape, dtype=float) - self._e_n = np.zeros(grid_shape, dtype=float) + self._e_n1 = xp.zeros(grid_shape, dtype=float) + self._e_n = xp.zeros(grid_shape, dtype=float) - self._de_s1_values = np.zeros(grid_shape, dtype=float) + self._de_s1_values = xp.zeros(grid_shape, dtype=float) - self._tmp_int_grid = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) gam = self._gamma if self._model == "full": - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6084,7 +6174,7 @@ def _initialize_projectors_and_mass(self): ) self._mass_metric_term = deepcopy(metric) - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6117,7 +6207,7 @@ def _initialize_projectors_and_mass(self): self.pc_jac.update_mass_operator(self.M_de_ds) elif self._model in ["full_q", "linear_q", "deltaf_q"]: - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6125,7 +6215,7 @@ def _initialize_projectors_and_mass(self): ) self._mass_metric_term = deepcopy(metric) - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6133,7 +6223,7 @@ def _initialize_projectors_and_mass(self): ) self._energy_metric = deepcopy(metric) - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6199,7 +6289,7 @@ def _update_artificial_viscosity(self, un, dt): gu_sq_v += gu1_v[i] gu_sq_v += gu2_v[i] - np.sqrt(gu_sq_v, out=gu_sq_v) + xp.sqrt(gu_sq_v, out=gu_sq_v) gu_sq_v *= dt * self._mu_a # /2 @@ -6357,63 +6447,92 @@ class VariationalResistivity(Propagator): """ - @staticmethod - def options(default=False): - dct = {} - dct["lin_solver"] = { - "tol": 1e-12, - "maxiter": 500, - "type": [ - ("pcg", "MassMatrixDiagonalPreconditioner"), - ("cg", None), - ], - "verbose": False, - } - dct["nonlin_solver"] = {"tol": 1e-8, "maxiter": 100, "type": ["Newton"], "info": False, "fast": False} - dct["physics"] = { - "eta": 0.0, - "eta_a": 0.0, - "gamma": 5 / 3, - } - dct["linearize_current"] = False + class Variables: + def __init__(self): + self._s: FEECVariable = None + self._b: FEECVariable = None + + @property + def s(self) -> FEECVariable: + return self._s + + @s.setter + def s(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._s = new + + @property + def b(self) -> FEECVariable: + return self._b + + @b.setter + def b(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hdiv" + self._b = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # specific literals + OptsModel = Literal["full", "full_p", "full_q", "linear_p", "linear_q", "deltaf_q"] + # propagator options + model: OptsModel = "full" + gamma: float = 5.0 / 3.0 + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixDiagonalPreconditioner" + solver_params: SolverParameters = None + nonlin_solver: NonlinearSolverParameters = None + linearize_current: bool = False + rho: FEECVariable = None + pt3: FEECVariable = None + eta: float = 0.0 + eta_a: float = 0.0 - if default: - dct = descend_options_dict(dct, []) + def __post_init__(self): + # checks + check_option(self.model, self.OptsModel) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) - return dct + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() - def __init__( - self, - s: StencilVector, - b: BlockVector, - *, - model: str = "full", - gamma: float = options()["physics"]["gamma"], - rho: StencilVector, - eta: float = options()["physics"]["eta"], - eta_a: float = options()["physics"]["eta_a"], - lin_solver: dict = options(default=True)["lin_solver"], - nonlin_solver: dict = options(default=True)["nonlin_solver"], - linearize_current: dict = options(default=True)["linearize_current"], - energy_evaluator: InternalEnergyEvaluator = None, - pt3: StencilVector | None = None, - ): - super().__init__(s, b) + if self.nonlin_solver is None: + self.nonlin_solver = NonlinearSolverParameters(type="Newton") - assert model in ["full", "full_p", "full_q", "linear_p", "delta_p", "linear_q", "deltaf_q"] + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options - self._energy_evaluator = energy_evaluator - self._model = model - self._gamma = gamma - self._eta = eta - self._eta_a = eta_a - self._lin_solver = lin_solver - self._nonlin_solver = nonlin_solver - self._rho = rho - self._linearize_current = linearize_current - self._pt3 = pt3 + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - self._info = self._nonlin_solver["info"] and (MPI.COMM_WORLD.Get_rank() == 0) + @profile + def allocate(self): + self._model = self.options.model + self._gamma = self.options.gamma + self._eta = self.options.eta + self._eta_a = self.options.eta_a + self._lin_solver = self.options.solver_params + self._nonlin_solver = self.options.nonlin_solver + self._linearize_current = self.options.linearize_current + self._rho = self.options.rho + self._pt3 = self.options.pt3 + + self._info = self._nonlin_solver.info and (MPI.COMM_WORLD.Get_rank() == 0) # Femfields for the projector self.rhof = self.derham.create_spline_function("rhof", "L2") @@ -6425,9 +6544,13 @@ def __init__( self.cbf12 = self.derham.create_spline_function("cBf", "Hcurl") # Projector + self._energy_evaluator = InternalEnergyEvaluator(self.derham, self._gamma) self._initialize_projectors_and_mass() # bunch of temporaries to avoid allocating in the loop + s = self.variables.s.spline.vector + b = self.variables.b.spline.vector + self._tmp_bn1 = b.space.zeros() self._tmp_bn12 = b.space.zeros() self._tmp_sn1 = s.space.zeros() @@ -6444,7 +6567,7 @@ def __init__( ) def __call__(self, dt): - if self._nonlin_solver["type"] == "Newton": + if self._nonlin_solver.type == "Newton": self.__call_newton(dt) else: raise ValueError( @@ -6454,10 +6577,11 @@ def __call__(self, dt): def __call_newton(self, dt): """Solve the non linear system for updating the variables using Newton iteration method""" # Compute dissipation implicitely - sn = self.feec_vars[0] - bn = self.feec_vars[1] + sn = self.variables.s.spline.vector + bn = self.variables.b.spline.vector + if self._eta < 1.0e-15 and self._eta_a < 1.0e-15: - self.feec_vars_update(sn, bn) + self.update_feec_variables(s=sn, b=bn) return if self._info: @@ -6484,17 +6608,17 @@ def __call_newton(self, dt): print("information on the linear solver : ", self.inv_lop._info) if self._model == "linear_p" or (self._model == "linear_q" and self._nonlin_solver["fast"]): - self.feec_vars_update(sn, bn1) + self.update_feec_variables(s=sn, b=bn1) return # Energy balance term # 1) Pointwize energy change energy_change = self._get_energy_change(bn, bn1, total_resistivity) # 2) Initial energy and linear form - rho = self._rho + rho = self._rho.spline.vector self.rhof.vector = rho if self._model in ["deltaf_q", "linear_q"]: - self.sf.vector = self._pt3 + self.sf.vector = self._pt3.spline.vector else: self.sf.vector = sn @@ -6554,7 +6678,7 @@ def __call_newton(self, dt): for it in range(self._nonlin_solver["maxiter"]): if self._model in ["deltaf_q", "linear_q"]: - self.sf1.vector = self._pt3 + self.sf1.vector = self._pt3.spline.vector else: self.sf1.vector = sn1 @@ -6606,7 +6730,7 @@ def __call_newton(self, dt): if self._info: print("iteration : ", it, " error : ", err) - if (err < tol**2 and it > 0) or np.isnan(err): + if (err < tol**2 and it > 0) or xp.isnan(err): break if self._model == "full": @@ -6642,12 +6766,12 @@ def __call_newton(self, dt): else: sn1 += incr - if it == self._nonlin_solver["maxiter"] - 1 or np.isnan(err): + if it == self._nonlin_solver["maxiter"] - 1 or xp.isnan(err): print( - f"!!!Warning: Maximum iteration in VariationalResistivity reached - not converged:\n {err = } \n {tol**2 = }", + f"!!!Warning: Maximum iteration in VariationalResistivity reached - not converged:\n {err =} \n {tol**2 =}", ) - self.feec_vars_update(sn1, bn1) + self.update_feec_variables(s=sn1, b=bn1) # if self._pt3 is not None: # bn12 = bn.copy(out=self._tmp_bn12) @@ -6725,7 +6849,7 @@ def __call_newton(self, dt): # if self._info: # print("iteration : ", it, " error : ", err) - # if (err < tol**2 and it > 0) or np.isnan(err): + # if (err < tol**2 and it > 0) or xp.isnan(err): # break # incr = self.inv_jac.dot(self.tot_rhs, out=self._tmp_sn_incr) @@ -6738,8 +6862,6 @@ def __call_newton(self, dt): def _initialize_projectors_and_mass(self): """Initialization of all the `BasisProjectionOperator` and needed to compute the bracket term""" - from struphy.feec.projectors import L2Projector - pc_M1 = preconditioner.MassMatrixDiagonalPreconditioner( self.mass_ops.M1, ) @@ -6770,12 +6892,12 @@ def _initialize_projectors_and_mass(self): D = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] self.M1_cb = self.mass_ops.create_weighted_mass("Hcurl", "Hcurl", weights=[D, "sqrt_g"]) - if self._lin_solver["type"][1] is None: + if self.options.precond is None: self.pc = None else: pc_class = getattr( preconditioner, - self._lin_solver["type"][1], + self.options.precond, ) self.pc_jac = pc_class(self.M_de_ds) @@ -6783,8 +6905,8 @@ def _initialize_projectors_and_mass(self): self.M_de_ds, "pcg", pc=self.pc_jac, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, verbose=False, recycle=True, ) @@ -6800,12 +6922,12 @@ def _initialize_projectors_and_mass(self): self.r_op = M2 # - self._scaled_stiffness self.l_op = M2 + self._scaled_stiffness + self.phy_cb_stiffness - if self._lin_solver["type"][1] is None: + if self.options.precond is None: self.pc = None else: pc_class = getattr( preconditioner, - self._lin_solver["type"][1], + self.options.precond, ) self.pc = pc_class(M2) @@ -6813,8 +6935,8 @@ def _initialize_projectors_and_mass(self): self.l_op, "pcg", pc=self.pc, - tol=self._lin_solver["tol"], - maxiter=self._lin_solver["maxiter"], + tol=self._lin_solver.tol, + maxiter=self._lin_solver.maxiter, verbose=False, recycle=True, ) @@ -6840,26 +6962,26 @@ def _initialize_projectors_and_mass(self): grid_shape = tuple([len(loc_grid) for loc_grid in integration_grid]) - self._cb12_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] - self._cb1_values = [np.zeros(grid_shape, dtype=float) for i in range(3)] + self._cb12_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] + self._cb1_values = [xp.zeros(grid_shape, dtype=float) for i in range(3)] - self._cb_sq_values = np.zeros(grid_shape, dtype=float) - self._cb_sq_values_init = np.zeros(grid_shape, dtype=float) + self._cb_sq_values = xp.zeros(grid_shape, dtype=float) + self._cb_sq_values_init = xp.zeros(grid_shape, dtype=float) - self._sf_values = np.zeros(grid_shape, dtype=float) - self._sf1_values = np.zeros(grid_shape, dtype=float) - self._rhof_values = np.zeros(grid_shape, dtype=float) + self._sf_values = xp.zeros(grid_shape, dtype=float) + self._sf1_values = xp.zeros(grid_shape, dtype=float) + self._rhof_values = xp.zeros(grid_shape, dtype=float) - self._e_n1 = np.zeros(grid_shape, dtype=float) - self._e_n = np.zeros(grid_shape, dtype=float) + self._e_n1 = xp.zeros(grid_shape, dtype=float) + self._e_n = xp.zeros(grid_shape, dtype=float) - self._de_s1_values = np.zeros(grid_shape, dtype=float) + self._de_s1_values = xp.zeros(grid_shape, dtype=float) - self._tmp_int_grid = np.zeros(grid_shape, dtype=float) + self._tmp_int_grid = xp.zeros(grid_shape, dtype=float) gam = self._gamma if self._model == "full": - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6867,7 +6989,7 @@ def _initialize_projectors_and_mass(self): ) self._mass_metric_term = deepcopy(metric) - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6900,7 +7022,7 @@ def _initialize_projectors_and_mass(self): self.pc_jac.update_mass_operator(self.M_de_ds) elif self._model in ["full_q", "linear_q", "deltaf_q"]: - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6908,7 +7030,7 @@ def _initialize_projectors_and_mass(self): ) self._mass_metric_term = deepcopy(metric) - metric = np.power( + metric = xp.power( self.domain.jacobian_det( *integration_grid, ), @@ -6955,7 +7077,7 @@ def _update_artificial_resistivity(self, bn, dt): for j in range(3): cb_sq_v += cb_v[i] * self._sq_term_metric_no_jac[i, j] * cb_v[j] - np.sqrt(cb_sq_v, out=cb_sq_v) + xp.sqrt(cb_sq_v, out=cb_sq_v) cb_sq_v *= dt * self._eta_a @@ -7053,48 +7175,74 @@ class TimeDependentSource(Propagator): * :math:`h(\omega t) = \sin(\omega t)` """ - @staticmethod - def options(default=False): - dct = {} - dct["omega"] = 1.0 - dct["hfun"] = ["cos", "sin"] - if default: - dct = descend_options_dict(dct, []) - return dct + class Variables: + def __init__(self): + self._source: FEECVariable = None - def __init__( - self, - c: StencilVector, - *, - omega: float = options()["omega"], - hfun: str = options(default=True)["hfun"], - ): - super().__init__(c) + @property + def source(self) -> FEECVariable: + return self._source + + @source.setter + def source(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1" + self._source = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # specific literals + OptsTimeSource = Literal["cos", "sin"] + # propagator options + omega: float = 2.0 * xp.pi + hfun: OptsTimeSource = "cos" + + def __post_init__(self): + # checks + check_option(self.hfun, self.OptsTimeSource) + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - if hfun == "cos": + @profile + def allocate(self): + if self.options.hfun == "cos": def hfun(t): - return np.cos(omega * t) - elif hfun == "sin": + return xp.cos(self.options.omega * t) + elif self.options.hfun == "sin": def hfun(t): - return np.sin(omega * t) + return xp.sin(self.options.omega * t) else: - raise NotImplementedError(f"{hfun = } not implemented.") + raise NotImplementedError(f"{self.options.hfun =} not implemented.") self._hfun = hfun + self._c0 = self.variables.source.spline.vector.copy() + @profile def __call__(self, dt): - print(f"{self.time_state[0] = }") - if self.time_state[0] == 0.0: - self._c0 = self.feec_vars[0].copy() - print("Initial source coeffs set.") - # new coeffs cn1 = self._c0 * self._hfun(self.time_state[0]) # write new coeffs into self.feec_vars - max_dc = self.feec_vars_update(cn1) + # max_dc = self.feec_vars_update(cn1) + self.update_feec_variables(source=cn1) class AdiabaticPhi(Propagator): @@ -7332,63 +7480,100 @@ class HasegawaWakatani(Propagator): Solver parameters for M0 inversion. """ - @staticmethod - def options(default=False): - dct = {} - dct["c_fun"] = ["const"] - dct["kappa"] = 1.0 - dct["nu"] = 0.01 - dct["algo"] = get_args(OptsButcher) - dct["M0_solver"] = { - "type": [ - ("pcg", "MassMatrixPreconditioner"), - ("cg", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - if default: - dct = descend_options_dict(dct, []) - return dct + class Variables: + def __init__(self): + self._n: FEECVariable = None + self._omega: FEECVariable = None - def __init__( - self, - n0: StencilVector, - omega0: StencilVector, - *, - phi: SplineFunction = None, - c_fun: str = options(default=True)["c_fun"], - kappa: float = options(default=True)["kappa"], - nu: float = options(default=True)["nu"], - algo: str = options(default=True)["algo"], - M0_solver: dict = options(default=True)["M0_solver"], - ): - super().__init__(n0, omega0) + @property + def n(self) -> FEECVariable: + return self._n + + @n.setter + def n(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1" + self._n = new + + @property + def omega(self) -> FEECVariable: + return self._omega + + @omega.setter + def omega(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "H1" + self._omega = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # specific literals + OptsCfun = Literal["const"] + # propagator options + phi: FEECVariable = None + c_fun: OptsCfun = "const" + kappa: float = 1.0 + nu: float = 0.01 + butcher: ButcherTableau = None + solver: OptsSymmSolver = "pcg" + precond: OptsMassPrecond = "MassMatrixPreconditioner" + solver_params: SolverParameters = None + + def __post_init__(self): + # checks + check_option(self.c_fun, self.OptsCfun) + check_option(self.solver, OptsSymmSolver) + check_option(self.precond, OptsMassPrecond) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + if self.butcher is None: + self.butcher = ButcherTableau() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): # default phi - if phi is None: - self._phi = self.derham.create_spline_function("phi", "H1") - self._phi.vector[:] = 1.0 - self._phi.vector.update_ghost_regions() - else: - self._phi = phi + if self.options.phi is None: + self.options.phi = FEECVariable(space="H1") + self.options.phi.allocate(derham=self.derham, domain=self.domain) + + self._phi = self.options.phi.spline + self._phi.vector[:] = 1.0 + self._phi.vector.update_ghost_regions() # default c-function - if c_fun == "const": + if self.options.c_fun == "const": c_fun = lambda e1, e2, e3: 0.0 + 0.0 * e1 else: - raise NotImplementedError(f"{c_fun = } is not available.") + raise NotImplementedError(f"{self.options.c_fun =} is not available.") # expose equation parameters - self._kappa = kappa - self._nu = nu + self._kappa = self.options.kappa + self._nu = self.options.nu # get quadrature grid of V0 pts = [grid.flatten() for grid in self.derham.quad_grid_pts["0"]] - mesh_pts = np.meshgrid(*pts, indexing="ij") + mesh_pts = xp.meshgrid(*pts, indexing="ij") # evaluate c(x, y) and metric coeff at local quadrature grid and multiply self._weights = c_fun(*mesh_pts) @@ -7421,13 +7606,13 @@ def __init__( for m in range(3): self._M1hw_weights += [[None, None, None]] - self._phi_5d = np.zeros((*self._phi_at_pts.shape, 3, 3), dtype=float) - self._tmp_5d = np.zeros((*self._phi_at_pts.shape, 3, 3), dtype=float) - self._tmp_5dT = np.zeros((3, 3, *self._phi_at_pts.shape), dtype=float) + self._phi_5d = xp.zeros((*self._phi_at_pts.shape, 3, 3), dtype=float) + self._tmp_5d = xp.zeros((*self._phi_at_pts.shape, 3, 3), dtype=float) + self._tmp_5dT = xp.zeros((3, 3, *self._phi_at_pts.shape), dtype=float) self._phi_5d[:, :, :, 0, 1] = self._phi_at_pts * self._jac_det self._phi_5d[:, :, :, 1, 0] = -self._phi_at_pts * self._jac_det self._tmp_5d[:] = self._jac_inv @ self._phi_5d @ self._jac_invT - self._tmp_5dT[:] = np.transpose(self._tmp_5d, axes=(3, 4, 0, 1, 2)) + self._tmp_5dT[:] = xp.transpose(self._tmp_5d, axes=(3, 4, 0, 1, 2)) self._M1hw_weights[0][1] = self._tmp_5dT[0, 1, :, :, :] self._M1hw_weights[1][0] = self._tmp_5dT[1, 0, :, :, :] @@ -7442,16 +7627,24 @@ def __init__( ) # inverse M0 mass matrix - solver = M0_solver["type"][0] - if M0_solver["type"][1] is None: + solver = self.options.solver + if self.options.precond is None: pc = None else: - pc_class = getattr(preconditioner, M0_solver["type"][1]) + pc_class = getattr(preconditioner, self.options.precond) pc = pc_class(self.mass_ops.M0) - solver_params = deepcopy(M0_solver) # need a copy to pop, otherwise testing fails - solver_params.pop("type") - self._info = solver_params.pop("info") - M0_inv = inverse(M0, solver, pc=pc, **solver_params) + # solver_params = deepcopy(M0_solver) # need a copy to pop, otherwise testing fails + # solver_params.pop("type") + self._info = self.options.solver_params.info + M0_inv = inverse( + M0, + solver, + pc=pc, + tol=self.options.solver_params.tol, + maxiter=self.options.solver_params.maxiter, + verbose=self.options.solver_params.verbose, + recycle=self.options.solver_params.recycle, + ) # basis projection operator df_12 = lambda e1, e2, e3: self.domain.jacobian_inv(e1, e2, e3)[0, 1, :, :, :] @@ -7469,6 +7662,9 @@ def __init__( # print(f"{self._BPO._dof_mat.blocks = }") # pre-allocated helper arrays + n0 = self.variables.n.spline.vector + omega0 = self.variables.omega.spline.vector + self._tmp1 = n0.space.zeros() tmp2 = n0.space.zeros() self._tmp3 = n0.space.zeros() @@ -7476,11 +7672,11 @@ def __init__( tmp5 = n0.space.zeros() # rhs-callables for explicit ode solve - terms1_n = -M0c + grad.T @ self._M1hw @ grad - nu * grad.T @ M1 @ grad + terms1_n = -M0c + grad.T @ self._M1hw @ grad - self.options.nu * grad.T @ M1 @ grad terms1_phi = M0c - terms1_phi_strong = -kappa * self._BPO @ grad + terms1_phi_strong = -self.options.kappa * self._BPO @ grad - terms2_omega = grad.T @ self._M1hw @ grad - nu * grad.T @ M1 @ grad + terms2_omega = grad.T @ self._M1hw @ grad - self.options.nu * grad.T @ M1 @ grad terms2_n = -M0c terms2_phi = M0c @@ -7508,7 +7704,7 @@ def f2(t, n, omega, out=out2): return out vector_field = {n0: f1, omega0: f2} - self._ode_solver = ODEsolverFEEC(vector_field, algo=algo) + self._ode_solver = ODEsolverFEEC(vector_field, butcher=self.options.butcher) def __call__(self, dt): # update time-dependent mass operator @@ -7517,7 +7713,7 @@ def __call__(self, dt): self._phi_5d[:, :, :, 0, 1] = self._phi_at_pts * self._jac_det self._phi_5d[:, :, :, 1, 0] = -self._phi_at_pts * self._jac_det self._tmp_5d[:] = self._jac_inv @ self._phi_5d @ self._jac_invT - self._tmp_5dT[:] = np.transpose(self._tmp_5d, axes=(3, 4, 0, 1, 2)) + self._tmp_5dT[:] = xp.transpose(self._tmp_5d, axes=(3, 4, 0, 1, 2)) self._M1hw_weights[0][1] = self._tmp_5dT[0, 1, :, :, :] self._M1hw_weights[1][0] = self._tmp_5dT[1, 0, :, :, :] @@ -7546,97 +7742,122 @@ class TwoFluidQuasiNeutralFull(Propagator): :ref:`time_discret`: fully implicit. """ - def allocate(self): - pass + class Variables: + def __init__(self): + self._u: FEECVariable = None + self._ue: FEECVariable = None + self._phi: FEECVariable = None - def set_options(self, **kwargs): - pass + @property + def u(self) -> FEECVariable: + return self._u - @staticmethod - def options(default=False): - dct = {} - dct["solver"] = { - "type": [ - ("gmres", None), - ], - "tol": 1.0e-8, - "maxiter": 3000, - "info": False, - "verbose": False, - "recycle": True, - } - dct["nu"] = 1.0 - dct["nu_e"] = 0.01 - dct["override_eq_params"] = [False, {"epsilon": 1.0}] - dct["eps_norm"] = 1.0 - dct["a"] = 1.0 - dct["R0"] = 1.0 - dct["B0"] = 10.0 - dct["Bp"] = 12.5 - dct["alpha"] = 0.1 - dct["beta"] = 1.0 - dct["stab_sigma"] = 0.00001 - dct["variant"] = "Uzawa" - dct["method_to_solve"] = "DirectNPInverse" - dct["preconditioner"] = False - dct["spectralanalysis"] = False - dct["lifting"] = False - dct["dimension"] = "2D" - dct["1D_dt"] = 0.001 - if default: - dct = descend_options_dict(dct, []) + @u.setter + def u(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hdiv" + self._u = new - return dct + @property + def ue(self) -> FEECVariable: + return self._ue - def __init__( - self, - u: BlockVector, - ue: BlockVector, - phi: BlockVector, - *, - nu: float = options(default=True)["nu"], - nu_e: float = options(default=True)["nu_e"], - eps_norm: float = options(default=True)["eps_norm"], - solver: dict = options(default=True)["solver"], - a: float = options(default=True)["a"], - R0: float = options(default=True)["R0"], - B0: float = options(default=True)["B0"], - Bp: float = options(default=True)["Bp"], - alpha: float = options(default=True)["alpha"], - beta: float = options(default=True)["beta"], - stab_sigma: float = options(default=True)["stab_sigma"], - variant: str = options(default=True)["variant"], - method_to_solve: str = options(default=True)["method_to_solve"], - preconditioner: bool = options(default=True)["preconditioner"], - spectralanalysis: bool = options(default=True)["spectralanalysis"], - lifting: bool = options(default=False)["lifting"], - dimension: str = options(default=True)["dimension"], - D1_dt: float = options(default=True)["1D_dt"], - ): - super().__init__(u, ue, phi) + @ue.setter + def ue(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "Hdiv" + self._ue = new + + @property + def phi(self) -> FEECVariable: + return self._phi + + @phi.setter + def phi(self, new): + assert isinstance(new, FEECVariable) + assert new.space == "L2" + self._phi = new + + def __init__(self): + self.variables = self.Variables() + + @dataclass + class Options: + # specific literals + OptsDimension = Literal["1D", "2D", "Restelli", "Tokamak"] + # propagator options + nu: float = 1.0 + nu_e: float = 0.01 + eps_norm: float = 1.0 + solver: OptsGenSolver = "GMRES" + solver_params: SolverParameters = None + a: float = 1.0 + R0: float = 1.0 + B0: float = 10.0 + Bp: float = 12.0 + alpha: float = 0.1 + beta: float = 1.0 + stab_sigma: float = 1e-5 + variant: OptsSaddlePointSolver = "Uzawa" + method_to_solve: OptsDirectSolver = "DirectNPInverse" + preconditioner: bool = False + spectralanalysis: bool = False + lifting: bool = False + dimension: OptsDimension = "2D" + D1_dt: float = 1e-3 + + def __post_init__(self): + # checks + check_option(self.solver, OptsGenSolver) + check_option(self.variant, OptsSaddlePointSolver) + check_option(self.method_to_solve, OptsDirectSolver) + check_option(self.dimension, self.OptsDimension) + + # defaults + if self.solver_params is None: + self.solver_params = SolverParameters() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new - self._info = solver["info"] + @profile + def allocate(self): + self._info = self.options.solver_params.info if self.derham.comm is not None: self._rank = self.derham.comm.Get_rank() else: self._rank = 0 - self._nu = nu - self._nu_e = nu_e - self._eps_norm = eps_norm - self._a = a - self._R0 = R0 - self._B0 = B0 - self._Bp = Bp - self._alpha = alpha - self._beta = beta - self._stab_sigma = stab_sigma - self._variant = variant - self._method_to_solve = method_to_solve - self._preconditioner = preconditioner - self._dimension = dimension - self._spectralanalysis = spectralanalysis - self._lifting = lifting + self._nu = self.options.nu + self._nu_e = self.options.nu_e + self._eps_norm = self.options.eps_norm + self._a = self.options.a + self._R0 = self.options.R0 + self._B0 = self.options.B0 + self._Bp = self.options.Bp + self._alpha = self.options.alpha + self._beta = self.options.beta + self._stab_sigma = self.options.stab_sigma + self._variant = self.options.variant + self._method_to_solve = self.options.method_to_solve + self._preconditioner = self.options.preconditioner + self._dimension = self.options.dimension + self._spectralanalysis = self.options.spectralanalysis + self._lifting = self.options.lifting + + solver_params = self.options.solver_params # Lifting for nontrivial boundary conditions # derham had boundary conditions in eta1 direction, the following is in space Hdiv_0 @@ -7652,13 +7873,13 @@ def __init__( self._mass_opsv0 = WeightedMassOperators( self.derhamv0, self.domain, - verbose=solver["verbose"], + verbose=solver_params.verbose, eq_mhd=self.mass_ops.weights["eq_mhd"], ) self._basis_opsv0 = BasisProjectionOperators( self.derhamv0, self.domain, - verbose=solver["verbose"], + verbose=solver_params.verbose, eq_mhd=self.basis_ops.weights["eq_mhd"], ) else: @@ -7687,7 +7908,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, ) _funy = getattr(callables, "ManufacturedSolutionForceterm")( species="Ions", @@ -7697,7 +7918,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, ) _funelectronsx = getattr(callables, "ManufacturedSolutionForceterm")( species="Electrons", @@ -7707,7 +7928,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, ) _funelectronsy = getattr(callables, "ManufacturedSolutionForceterm")( species="Electrons", @@ -7717,7 +7938,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, ) # get callable(s) for specified init type @@ -7726,16 +7947,32 @@ def __init__( # pullback callable funx = TransformedPformComponent( - forceterm_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain + forceterm_class, + given_in_basis="physical", + out_form="2", + comp=0, + domain=self.domain, ) funy = TransformedPformComponent( - forceterm_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain + forceterm_class, + given_in_basis="physical", + out_form="2", + comp=1, + domain=self.domain, ) fun_electronsx = TransformedPformComponent( - forcetermelectrons_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain + forcetermelectrons_class, + given_in_basis="physical", + out_form="2", + comp=0, + domain=self.domain, ) fun_electronsy = TransformedPformComponent( - forcetermelectrons_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain + forcetermelectrons_class, + given_in_basis="physical", + out_form="2", + comp=1, + domain=self.domain, ) l2_proj = L2Projector(space_id="Hdiv", mass_ops=self.mass_ops) self._F1 = l2_proj([funx, funy, _forceterm_logical]) @@ -7770,22 +8007,46 @@ def __init__( # pullback callable fun_pb_1 = TransformedPformComponent( - forceterm_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain + forceterm_class, + given_in_basis="physical", + out_form="2", + comp=0, + domain=self.domain, ) fun_pb_2 = TransformedPformComponent( - forceterm_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain + forceterm_class, + given_in_basis="physical", + out_form="2", + comp=1, + domain=self.domain, ) fun_pb_3 = TransformedPformComponent( - forceterm_class, fun_basis="physical", out_form="2", comp=2, domain=self.domain + forceterm_class, + given_in_basis="physical", + out_form="2", + comp=2, + domain=self.domain, ) fun_electrons_pb_1 = TransformedPformComponent( - forcetermelectrons_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain + forcetermelectrons_class, + given_in_basis="physical", + out_form="2", + comp=0, + domain=self.domain, ) fun_electrons_pb_2 = TransformedPformComponent( - forcetermelectrons_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain + forcetermelectrons_class, + given_in_basis="physical", + out_form="2", + comp=1, + domain=self.domain, ) fun_electrons_pb_3 = TransformedPformComponent( - forcetermelectrons_class, fun_basis="physical", out_form="2", comp=2, domain=self.domain + forcetermelectrons_class, + given_in_basis="physical", + out_form="2", + comp=2, + domain=self.domain, ) if self._lifting: l2_proj = L2Projector(space_id="Hdiv", mass_ops=self._mass_opsv0) @@ -7808,7 +8069,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -7822,7 +8083,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -7836,7 +8097,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -7850,7 +8111,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -7864,7 +8125,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -7878,7 +8139,7 @@ def __init__( dimension=self._dimension, stab_sigma=self._stab_sigma, eps=self._eps_norm, - dt=D1_dt, + dt=self.options.D1_dt, a=self._a, Bp=self._Bp, alpha=self._alpha, @@ -7891,22 +8152,46 @@ def __init__( # pullback callable fun_pb_1 = TransformedPformComponent( - forceterm_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain + forceterm_class, + given_in_basis="physical", + out_form="2", + comp=0, + domain=self.domain, ) fun_pb_2 = TransformedPformComponent( - forceterm_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain + forceterm_class, + given_in_basis="physical", + out_form="2", + comp=1, + domain=self.domain, ) fun_pb_3 = TransformedPformComponent( - forceterm_class, fun_basis="physical", out_form="2", comp=2, domain=self.domain + forceterm_class, + given_in_basis="physical", + out_form="2", + comp=2, + domain=self.domain, ) fun_electrons_pb_1 = TransformedPformComponent( - forcetermelectrons_class, fun_basis="physical", out_form="2", comp=0, domain=self.domain + forcetermelectrons_class, + given_in_basis="physical", + out_form="2", + comp=0, + domain=self.domain, ) fun_electrons_pb_2 = TransformedPformComponent( - forcetermelectrons_class, fun_basis="physical", out_form="2", comp=1, domain=self.domain + forcetermelectrons_class, + given_in_basis="physical", + out_form="2", + comp=1, + domain=self.domain, ) fun_electrons_pb_3 = TransformedPformComponent( - forcetermelectrons_class, fun_basis="physical", out_form="2", comp=2, domain=self.domain + forcetermelectrons_class, + given_in_basis="physical", + out_form="2", + comp=2, + domain=self.domain, ) if self._lifting: l2_proj = L2Projector(space_id="Hdiv", mass_ops=self._mass_opsv0) @@ -7987,7 +8272,10 @@ def __init__( self._S21 = None if self.derhamv0.with_local_projectors: self._S21 = BasisProjectionOperatorLocal( - self.derhamv0._Ploc["1"], self.derhamv0.Vh_fem["2"], fun, transposed=False + self.derhamv0._Ploc["1"], + self.derhamv0.Vh_fem["2"], + fun, + transposed=False, ) if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): @@ -8097,7 +8385,10 @@ def __init__( self._S21 = None if self.derham.with_local_projectors: self._S21 = BasisProjectionOperatorLocal( - self.derham._Ploc["1"], self.derham.Vh_fem["2"], fun, transposed=False + self.derham._Ploc["1"], + self.derham.Vh_fem["2"], + fun, + transposed=False, ) if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): @@ -8143,9 +8434,9 @@ def __init__( A11np = self._M2np + self._A11np_notimedependency if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - A11np += self._stab_sigma * np.identity(A11np.shape[0]) + A11np += self._stab_sigma * xp.identity(A11np.shape[0]) self.A22np = ( - self._stab_sigma * np.identity(A11np.shape[0]) + self._stab_sigma * xp.identity(A11np.shape[0]) + self._nu_e * ( self._Dnp.T @ self._M3np @ self._Dnp @@ -8154,7 +8445,7 @@ def __init__( + self._M2Bnp / self._eps_norm ) self._A22prenp = ( - np.identity(self.A22np.shape[0]) * self._stab_sigma + xp.identity(self.A22np.shape[0]) * self._stab_sigma ) # + self._nu_e * (self._Dnp.T @ self._M3np @ self._Dnp) elif self._method_to_solve in ("SparseSolver", "ScipySparse"): A11np += self._stab_sigma * sc.sparse.eye(A11np.shape[0], format="csr") @@ -8187,10 +8478,10 @@ def __init__( A=_A, B=_B, F=_F, - solver_name=solver["type"][0], - tol=solver["tol"], - max_iter=solver["maxiter"], - verbose=solver["verbose"], + solver_name=self.options.solver, + tol=self.options.solver_params.tol, + max_iter=self.options.solver_params.maxiter, + verbose=self.options.solver_params.verbose, pc=None, ) # Allocate memory for call @@ -8204,17 +8495,17 @@ def __init__( F=_Fnp, method_to_solve=self._method_to_solve, preconditioner=self._preconditioner, - spectralanalysis=spectralanalysis, - tol=solver["tol"], - max_iter=solver["maxiter"], - verbose=solver["verbose"], + spectralanalysis=self.options.spectralanalysis, + tol=self.options.solver_params.tol, + max_iter=self.options.solver_params.maxiter, + verbose=self.options.solver_params.verbose, ) def __call__(self, dt): # current variables - unfeec = self.feec_vars[0] - uenfeec = self.feec_vars[1] - phinfeec = self.feec_vars[2] + unfeec = self.variables.u.spline.vector + uenfeec = self.variables.ue.spline.vector + phinfeec = self.variables.phi.spline.vector if self._variant == "GMRES": if self._lifting: @@ -8331,13 +8622,13 @@ def __call__(self, dt): uen = _sol1[1] phin = _sol2 # write new coeffs into self.feec_vars - max_du, max_due, max_dphi = self.feec_vars_update(un, uen, phin) + max_du, max_due, max_dphi = self.update_feec_variables(u=un, ue=uen, phi=phin) elif self._variant == "Uzawa": # Numpy A11np = self._M2np / dt + self._A11np_notimedependency if self._method_to_solve in ("DirectNPInverse", "InexactNPInverse"): - A11np += self._stab_sigma * np.identity(A11np.shape[0]) + A11np += self._stab_sigma * xp.identity(A11np.shape[0]) _A22prenp = self._A22prenp A22np = self.A22np elif self._method_to_solve in ("SparseSolver", "ScipySparse"): @@ -8347,7 +8638,7 @@ def __call__(self, dt): # _Anp[1] and _Anppre[1] remain unchanged _Anp = [A11np, A22np] - if self._preconditioner == True: + if self._preconditioner: _A11prenp = self._M2np / dt # + self._A11prenp_notimedependency _Anppre = [_A11prenp, _A22prenp] @@ -8384,20 +8675,24 @@ def __call__(self, dt): _Fnp = [_F1np, _F2np] if self.rank == 0: - if self._preconditioner == True: + if self._preconditioner: self._solver_UzawaNumpy.Apre = _Anppre self._solver_UzawaNumpy.A = _Anp self._solver_UzawaNumpy.F = _Fnp if self._lifting: un, uen, phin, info, residual_norms, spectralresult = self._solver_UzawaNumpy( - u0.vector, ue0.vector, phinfeec + u0.vector, + ue0.vector, + phinfeec, ) un += u_prime.vector.toarray() uen += ue_prime.vector.toarray() else: un, uen, phin, info, residual_norms, spectralresult = self._solver_UzawaNumpy( - unfeec, uenfeec, phinfeec + unfeec, + uenfeec, + phinfeec, ) dimlist = [[shp - 2 * pi for shp, pi in zip(unfeec[i][:].shape, self.derham.p)] for i in range(3)] @@ -8427,10 +8722,10 @@ def __call__(self, dt): e = phi_temp.ends phi_temp[s[0] : e[0] + 1, s[1] : e[1] + 1, s[2] : e[2] + 1] = phin.reshape(*dimphi) else: - print(f"TwoFluidQuasiNeutralFull is only running on one MPI.") + print("TwoFluidQuasiNeutralFull is only running on one MPI.") # write new coeffs into self.feec_vars - max_du, max_due, max_dphi = self.feec_vars_update(u_temp, ue_temp, phi_temp) + max_du, max_due, max_dphi = self.update_feec_variables(u=u_temp, ue=ue_temp, phi=phi_temp) if self._info and self._rank == 0: print("Status for TwoFluidQuasiNeutralFull:", info["success"]) diff --git a/src/struphy/propagators/propagators_markers.py b/src/struphy/propagators/propagators_markers.py index a890e5d27..0360f39de 100644 --- a/src/struphy/propagators/propagators_markers.py +++ b/src/struphy/propagators/propagators_markers.py @@ -4,10 +4,10 @@ from dataclasses import dataclass from typing import Callable, Literal, get_args -import numpy as np +import cunumpy as xp from line_profiler import profile -from mpi4py import MPI from numpy import array, polynomial, random +from psydac.ddm.mpi import mpi as MPI from psydac.linalg.basic import LinearOperator from psydac.linalg.block import BlockVector from psydac.linalg.stencil import StencilVector @@ -18,18 +18,21 @@ from struphy.io.options import ( OptsKernel, OptsMPIsort, + OptsVecSpace, check_option, ) from struphy.io.setup import descend_options_dict from struphy.models.variables import FEECVariable, PICVariable, SPHVariable from struphy.ode.utils import ButcherTableau from struphy.pic.accumulation import accum_kernels, accum_kernels_gc +from struphy.pic.accumulation.particles_to_grid import AccumulatorVector from struphy.pic.base import Particles from struphy.pic.particles import Particles3D, Particles5D, Particles6D, ParticlesSPH from struphy.pic.pushing import eval_kernels_gc, pusher_kernels, pusher_kernels_gc from struphy.pic.pushing.pusher import Pusher from struphy.polar.basic import PolarVector from struphy.propagators.base import Propagator +from struphy.utils.pyccel import Pyccelkernel class PushEta(Propagator): @@ -93,15 +96,15 @@ def options(self, new): @profile def allocate(self): # get kernel - kernel = pusher_kernels.push_eta_stage + kernel = Pyccelkernel(pusher_kernels.push_eta_stage) # define algorithm butcher = self.options.butcher # temp fix due to refactoring of ButcherTableau: - import numpy as np + import cunumpy as xp - butcher._a = np.diag(butcher.a, k=-1) - butcher._a = np.array(list(butcher.a) + [0.0]) + butcher._a = xp.diag(butcher.a, k=-1) + butcher._a = xp.array(list(butcher.a) + [0.0]) args_kernel = ( butcher.a, @@ -156,7 +159,7 @@ def ions(self) -> PICVariable | SPHVariable: @ions.setter def ions(self, new): assert isinstance(new, PICVariable | SPHVariable) - assert new.space in ("Particles6D", "ParticlesSPH") + assert new.space in ("Particles6D", "DeltaFParticles6D", "ParticlesSPH") self._ions = new def __init__(self): @@ -193,6 +196,7 @@ def options(self, new): def allocate(self): # scaling factor self._epsilon = self.variables.ions.species.equation_params.epsilon + assert self.derham is not None, f"{self.__class__.__name__} needs a Derham object." # TODO: treat PolarVector as well, but polar splines are being reworked at the moment if self.projected_equil is not None: @@ -213,11 +217,11 @@ def allocate(self): # define pusher kernel if self.options.algo == "analytic": - kernel = pusher_kernels.push_vxb_analytic + kernel = Pyccelkernel(pusher_kernels.push_vxb_analytic) elif self.options.algo == "implicit": - kernel = pusher_kernels.push_vxb_implicit + kernel = Pyccelkernel(pusher_kernels.push_vxb_implicit) else: - raise ValueError(f"{self.options.algo = } not supported.") + raise ValueError(f"{self.options.algo =} not supported.") # instantiate Pusher args_kernel = ( @@ -286,7 +290,7 @@ def var(self) -> PICVariable | SPHVariable: @var.setter def var(self, new): assert isinstance(new, PICVariable | SPHVariable) - assert new.space in ("Particles6D", "ParticlesSPH") + assert new.space in ("Particles6D", "DeltaFParticles6D", "ParticlesSPH") self._var = new def __init__(self): @@ -355,7 +359,7 @@ def allocate(self): self._pusher = Pusher( self.variables.var.particles, - pusher_kernels.push_v_with_efield, + Pyccelkernel(pusher_kernels.push_v_with_efield), args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, @@ -394,85 +398,106 @@ class PushEtaPC(Propagator): * ``heun3`` (3rd order) """ - @staticmethod - def options(default=False): - dct = {} - dct["use_perp_model"] = [True, False] + class Variables: + def __init__(self): + self._var: PICVariable | SPHVariable = None - if default: - dct = descend_options_dict(dct, []) + @property + def var(self) -> PICVariable | SPHVariable: + return self._var - return dct + @var.setter + def var(self, new): + assert isinstance(new, PICVariable | SPHVariable) + self._var = new - def __init__( - self, - particles: Particles, - *, - u: BlockVector | PolarVector, - use_perp_model: bool = options(default=True)["use_perp_model"], - u_space: str, - ): - super().__init__(particles) + def __init__(self): + self.variables = self.Variables() - assert isinstance(u, (BlockVector, PolarVector)) + @dataclass + class Options: + butcher: ButcherTableau = None + use_perp_model: bool = True + u_tilde: FEECVariable = None + u_space: OptsVecSpace = "Hdiv" - self._u = u + def __post_init__(self): + # checks + check_option(self.u_space, OptsVecSpace) + assert isinstance(self.u_tilde, FEECVariable) - # call Pusher class - if use_perp_model: - if u_space == "Hcurl": - kernel = pusher_kernels.push_pc_eta_rk4_Hcurl - elif u_space == "Hdiv": - kernel = pusher_kernels.push_pc_eta_rk4_Hdiv - elif u_space == "H1vec": - kernel = pusher_kernels.push_pc_eta_rk4_H1vec - else: - raise ValueError( - f'{u_space = } not valid, choose from "Hcurl", "Hdiv" or "H1vec.', - ) + # defaults + if self.butcher is None: + self.butcher = ButcherTableau() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + self._u_tilde = self.options.u_tilde.spline.vector + + # get kernell: + if self.options.u_space == "Hcurl": + kernel = Pyccelkernel(pusher_kernels.push_pc_eta_stage_Hcurl) + elif self.options.u_space == "Hdiv": + kernel = Pyccelkernel(pusher_kernels.push_pc_eta_stage_Hdiv) + elif self.options.u_space == "H1vec": + kernel = Pyccelkernel(pusher_kernels.push_pc_eta_stage_H1vec) else: - if u_space == "Hcurl": - kernel = pusher_kernels.push_pc_eta_rk4_Hcurl_full - elif u_space == "Hdiv": - kernel = pusher_kernels.push_pc_eta_rk4_Hdiv_full - elif u_space == "H1vec": - kernel = pusher_kernels.push_pc_eta_rk4_H1vec_full - else: - raise ValueError( - f'{u_space = } not valid, choose from "Hcurl", "Hdiv" or "H1vec.', - ) + raise ValueError( + f'{self.options.u_space =} not valid, choose from "Hcurl", "Hdiv" or "H1vec.', + ) + + # define algorithm + butcher = self.options.butcher + # temp fix due to refactoring of ButcherTableau: + import cunumpy as xp + + butcher._a = xp.diag(butcher.a, k=-1) + butcher._a = xp.array(list(butcher.a) + [0.0]) args_kernel = ( self.derham.args_derham, - self._u[0]._data, - self._u[1]._data, - self._u[2]._data, + self._u_tilde[0]._data, + self._u_tilde[1]._data, + self._u_tilde[2]._data, + self.options.use_perp_model, + butcher.a, + butcher.b, + butcher.c, ) self._pusher = Pusher( - particles, + self.variables.var.particles, kernel, args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, - n_stages=4, + n_stages=butcher.n_stages, mpi_sort="each", ) def __call__(self, dt): - # check if ghost regions are synchronized - if not self._u[0].ghost_regions_in_sync: - self._u[0].update_ghost_regions() - if not self._u[1].ghost_regions_in_sync: - self._u[1].update_ghost_regions() - if not self._u[2].ghost_regions_in_sync: - self._u[2].update_ghost_regions() + self._u_tilde.update_ghost_regions() self._pusher(dt) # update_weights - if self.particles[0].control_variate: - self.particles[0].update_weights() + if self.variables.var.particles.control_variate: + self.variables.var.particles.update_weights() class PushGuidingCenterBxEstar(Propagator): @@ -718,7 +743,7 @@ def allocate(self): ) # pusher kernel - kernel = pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_1st_order_newton + kernel = Pyccelkernel(pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_1st_order_newton) alpha_in_kernel = 1.0 # evaluate at eta^{n+1,k} and save args_kernel = ( @@ -752,7 +777,7 @@ def allocate(self): ) # evaluate at eta^{n+1,k} and save # pusher kernel - kernel = pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_1st_order + kernel = Pyccelkernel(pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_1st_order) alpha_in_kernel = 0.5 # evaluate at mid-point args_kernel = ( @@ -798,7 +823,7 @@ def allocate(self): ) # evaluate at eta^{n+1,k} and save) # pusher kernel - kernel = pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_2nd_order + kernel = Pyccelkernel(pusher_kernels_gc.push_gc_bxEstar_discrete_gradient_2nd_order) alpha_in_kernel = 0.5 # evaluate at mid-point args_kernel = ( @@ -839,12 +864,12 @@ def allocate(self): else: butcher = self.options.butcher # temp fix due to refactoring of ButcherTableau: - import numpy as np + import cunumpy as xp - butcher._a = np.diag(butcher.a, k=-1) - butcher._a = np.array(list(butcher.a) + [0.0]) + butcher._a = xp.diag(butcher.a, k=-1) + butcher._a = xp.array(list(butcher.a) + [0.0]) - kernel = pusher_kernels_gc.push_gc_bxEstar_explicit_multistage + kernel = Pyccelkernel(pusher_kernels_gc.push_gc_bxEstar_explicit_multistage) args_kernel = ( self.derham.args_derham, @@ -1164,7 +1189,7 @@ def allocate(self): ) # pusher kernel - kernel = pusher_kernels_gc.push_gc_Bstar_discrete_gradient_1st_order_newton + kernel = Pyccelkernel(pusher_kernels_gc.push_gc_Bstar_discrete_gradient_1st_order_newton) alpha_in_kernel = 1.0 # evaluate at eta^{n+1,k} and save args_kernel = ( @@ -1197,7 +1222,7 @@ def allocate(self): ) # evaluate at Z^{n+1,k} and save # pusher kernel - kernel = pusher_kernels_gc.push_gc_Bstar_discrete_gradient_1st_order + kernel = Pyccelkernel(pusher_kernels_gc.push_gc_Bstar_discrete_gradient_1st_order) alpha_in_kernel = 0.5 # evaluate at mid-point args_kernel = ( @@ -1243,7 +1268,7 @@ def allocate(self): ) # evaluate at Z^{n+1,k} and save # pusher kernel - kernel = pusher_kernels_gc.push_gc_Bstar_discrete_gradient_2nd_order + kernel = Pyccelkernel(pusher_kernels_gc.push_gc_Bstar_discrete_gradient_2nd_order) alpha_in_kernel = 0.5 # evaluate at mid-point args_kernel = ( @@ -1287,12 +1312,12 @@ def allocate(self): else: butcher = self.options.butcher # temp fix due to refactoring of ButcherTableau: - import numpy as np + import cunumpy as xp - butcher._a = np.diag(butcher.a, k=-1) - butcher._a = np.array(list(butcher.a) + [0.0]) + butcher._a = xp.diag(butcher.a, k=-1) + butcher._a = xp.array(list(butcher.a) + [0.0]) - kernel = pusher_kernels_gc.push_gc_Bstar_explicit_multistage + kernel = Pyccelkernel(pusher_kernels_gc.push_gc_Bstar_explicit_multistage) args_kernel = ( self.derham.args_derham, @@ -1355,99 +1380,6 @@ def __call__(self, dt): self.variables.ions.particles.update_weights() -class StepStaticEfield(Propagator): - r"""Solve the following system - - .. math:: - - \frac{\text{d} \mathbf{\eta}_p}{\text{d} t} & = DL^{-1} \mathbf{v}_p \,, - - \frac{\text{d} \mathbf{v}_p}{\text{d} t} & = \kappa \, DL^{-T} \mathbf{E} - - which is solved by an average discrete gradient method, implicitly iterating - over :math:`k` (for every particle :math:`p`): - - .. math:: - - \mathbf{\eta}^{n+1}_{k+1} = \mathbf{\eta}^n + \frac{\Delta t}{2} DL^{-1} - \left( \frac{\mathbf{\eta}^{n+1}_k + \mathbf{\eta}^n }{2} \right) \left( \mathbf{v}^{n+1}_k + \mathbf{v}^n \right) \,, - - \mathbf{v}^{n+1}_{k+1} = \mathbf{v}^n + \Delta t \, \kappa \, DL^{-1}\left(\mathbf{\eta}^n\right) - \int_0^1 \left[ \mathbb{\Lambda}\left( \eta^n + \tau (\mathbf{\eta}^{n+1}_k - \mathbf{\eta}^n) \right) \right]^T \mathbf{e} \, \text{d} \tau - - Parameters - ---------- - particles : struphy.pic.particles.Particles6D - Holdes the markers to push. - - **params : dict - Solver- and/or other parameters for this splitting step. - """ - - def __init__(self, particles, **params): - from numpy import floor, polynomial - - super().__init__(particles) - - # parameters - params_default = { - "e_field": BlockVector(self.derham.Vh_fem["1"].coeff_space), - "kappa": 1e2, - } - - params = set_defaults(params, params_default) - self.kappa = params["kappa"] - - assert isinstance(params["e_field"], (BlockVector, PolarVector)) - self._e_field = params["e_field"] - - pn1 = self.derham.p[0] - pd1 = pn1 - 1 - pn2 = self.derham.p[1] - pd2 = pn2 - 1 - pn3 = self.derham.p[2] - pd3 = pn3 - 1 - - # number of quadrature points in direction 1 - n_quad1 = int(floor(pd1 * pn2 * pn3 / 2 + 1)) - # number of quadrature points in direction 2 - n_quad2 = int(floor(pn1 * pd2 * pn3 / 2 + 1)) - # number of quadrature points in direction 3 - n_quad3 = int(floor(pn1 * pn2 * pd3 / 2 + 1)) - - # get quadrature weights and locations - self._loc1, self._weight1 = polynomial.legendre.leggauss(n_quad1) - self._loc2, self._weight2 = polynomial.legendre.leggauss(n_quad2) - self._loc3, self._weight3 = polynomial.legendre.leggauss(n_quad3) - - self._pusher = Pusher( - self.derham, - self.domain, - "push_x_v_static_efield", - ) - - def __call__(self, dt): - """ - TODO - """ - self._pusher( - self.particles[0], - dt, - self._loc1, - self._loc2, - self._loc3, - self._weight1, - self._weight2, - self._weight3, - self._e_field.blocks[0]._data, - self._e_field.blocks[1]._data, - self._e_field.blocks[2]._data, - self.kappa, - array([1e-10, 1e-10]), - 100, - ) - - class PushDeterministicDiffusion(Propagator): r"""For each marker :math:`p`, solves @@ -1469,44 +1401,70 @@ class PushDeterministicDiffusion(Propagator): * Explicit from :class:`~struphy.ode.utils.ButcherTableau` """ - @staticmethod - def options(default=False): - dct = {} - dct["algo"] = ["rk4", "forward_euler", "heun2", "rk2", "heun3"] - dct["diffusion_coefficient"] = 1.0 - if default: - dct = descend_options_dict(dct, []) - return dct + class Variables: + def __init__(self): + self._var: PICVariable = None - def __init__( - self, - particles: Particles3D, - *, - algo: str = options(default=True)["algo"], - bc_type: list = ["periodic", "periodic", "periodic"], - diffusion_coefficient: float = options()["diffusion_coefficient"], - ): - from struphy.pic.accumulation.particles_to_grid import AccumulatorVector + @property + def var(self) -> PICVariable: + return self._var - super().__init__(particles) + @var.setter + def var(self, new): + assert isinstance(new, PICVariable) + assert new.space == "Particles3D" + self._var = new + + def __init__(self): + self.variables = self.Variables() - self._bc_type = bc_type - self._diffusion = diffusion_coefficient + @dataclass + class Options: + butcher: ButcherTableau = None + bc_type: tuple = ("periodic", "periodic", "periodic") + diff_coeff: float = 1.0 + + def __post_init__(self): + # defaults + if self.butcher is None: + self.butcher = ButcherTableau() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + self._bc_type = self.options.bc_type + self._diffusion = self.options.diff_coeff self._tmp = self.derham.Vh["1"].zeros() # choose algorithm - self._butcher = ButcherTableau(algo) + self._butcher = self.options.butcher # temp fix due to refactoring of ButcherTableau: - import numpy as np + import cunumpy as xp + + self._butcher._a = xp.diag(self._butcher.a, k=-1) + self._butcher._a = xp.array(list(self._butcher.a) + [0.0]) - self._butcher._a = np.diag(self._butcher.a, k=-1) - self._butcher._a = np.array(list(self._butcher.a) + [0.0]) + particles = self.variables.var.particles self._u_on_grid = AccumulatorVector( particles, "H1", - accum_kernels.charge_density_0form, + Pyccelkernel(accum_kernels.charge_density_0form), self.mass_ops, self.domain.args_domain, ) @@ -1526,7 +1484,7 @@ def __init__( self._pusher = Pusher( particles, - pusher_kernels.push_deterministic_diffusion_stage, + Pyccelkernel(pusher_kernels.push_deterministic_diffusion_stage), args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, @@ -1538,9 +1496,10 @@ def __call__(self, dt): """ TODO """ + particles = self.variables.var.particles # accumulate - self._u_on_grid(self.particles[0].vdim) + self._u_on_grid() # take gradient pi_u = self._u_on_grid.vectors[0] @@ -1551,8 +1510,8 @@ def __call__(self, dt): self._pusher(dt) # update_weights - if self.particles[0].control_variate: - self.particles[0].update_weights() + if particles.control_variate: + particles.update_weights() class PushRandomDiffusion(Propagator): @@ -1575,36 +1534,64 @@ class PushRandomDiffusion(Propagator): * ``forward_euler`` (1st order) """ - @staticmethod - def options(default=False): - dct = {} - dct["algo"] = ["forward_euler"] - dct["diffusion_coefficient"] = 1.0 - if default: - dct = descend_options_dict(dct, []) - return dct + class Variables: + def __init__(self): + self._var: PICVariable = None - def __init__( - self, - particles: Particles3D, - algo: str = options(default=True)["algo"], - bc_type: list = ["periodic", "periodic", "periodic"], - diffusion_coefficient: float = options()["diffusion_coefficient"], - ): - super().__init__(particles) + @property + def var(self) -> PICVariable: + return self._var - self._bc_type = bc_type - self._diffusion = diffusion_coefficient + @var.setter + def var(self, new): + assert isinstance(new, PICVariable) + assert new.space == "Particles3D" + self._var = new - self._noise = array(self.particles[0].markers[:, :3]) + def __init__(self): + self.variables = self.Variables() - # choose algorithm - self._butcher = ButcherTableau("forward_euler") + @dataclass + class Options: + butcher: ButcherTableau = None + bc_type: tuple = ("periodic", "periodic", "periodic") + diff_coeff: float = 1.0 + + def __post_init__(self): + # defaults + if self.butcher is None: + self.butcher = ButcherTableau() + + @property + def options(self) -> Options: + if not hasattr(self, "_options"): + self._options = self.Options() + return self._options + + @options.setter + def options(self, new): + assert isinstance(new, self.Options) + if MPI.COMM_WORLD.Get_rank() == 0: + print(f"\nNew options for propagator '{self.__class__.__name__}':") + for k, v in new.__dict__.items(): + print(f" {k}: {v}") + self._options = new + + @profile + def allocate(self): + self._bc_type = self.options.bc_type + self._diffusion = self.options.diff_coeff + + particles = self.variables.var.particles + + self._noise = array(particles.markers[:, :3]) + + self._butcher = self.options.butcher # temp fix due to refactoring of ButcherTableau: - import numpy as np + import cunumpy as xp - self._butcher._a = np.diag(self._butcher.a, k=-1) - self._butcher._a = np.array(list(self._butcher.a) + [0.0]) + self._butcher._a = xp.diag(self._butcher.a, k=-1) + self._butcher._a = xp.array(list(self._butcher.a) + [0.0]) # instantiate Pusher args_kernel = ( @@ -1617,7 +1604,7 @@ def __init__( self._pusher = Pusher( particles, - pusher_kernels.push_random_diffusion_stage, + Pyccelkernel(pusher_kernels.push_random_diffusion_stage), args_kernel, self.domain.args_domain, alpha_in_kernel=1.0, @@ -1634,18 +1621,20 @@ def __call__(self, dt): TODO """ + particles = self.variables.var.particles + self._noise[:] = random.multivariate_normal( self._mean, self._cov, - len(self.particles[0].markers), + len(particles.markers), ) # push markers self._pusher(dt) # update_weights - if self.particles[0].control_variate: - self.particles[0].update_weights() + if particles.control_variate: + particles.update_weights() class PushVinSPHpressure(Propagator): @@ -1757,11 +1746,11 @@ def allocate(self): # pusher kernel if self.options.thermodynamics == "isothermal": - kernel = pusher_kernels.push_v_sph_pressure + kernel = Pyccelkernel(pusher_kernels.push_v_sph_pressure) elif self.options.thermodynamics == "polytropic": - kernel = pusher_kernels.push_v_sph_pressure_ideal_gas + kernel = Pyccelkernel(pusher_kernels.push_v_sph_pressure_ideal_gas) - gravity = np.array(self.options.gravity, dtype=float) + gravity = xp.array(self.options.gravity, dtype=float) args_kernel = ( boxes, @@ -1789,7 +1778,7 @@ def __call__(self, dt): self._pusher(dt) -class PushVinViscousPotential(Propagator): +class PushVinViscousPotential2D(Propagator): r"""For each marker :math:`p`, solves .. math:: @@ -1831,6 +1820,137 @@ def __init__( # base class constructor call super().__init__(particles) + # init kernel for evaluating density etc. before each time step. + init_kernel_1 = Pyccelkernel(eval_kernels_gc.sph_mean_velocity_coeffs) + first_free_idx = particles.args_markers.first_free_idx + comps = (0, 1, 2) + + init_kernel_2 = Pyccelkernel(eval_kernels_gc.sph_mean_velocity) + # first_free_idx = particles.args_markers.first_free_idx + # comps = (0, 1, 2) + + init_kernel_3 = Pyccelkernel(eval_kernels_gc.sph_grad_mean_velocity) + comps_tensor = (0, 1, 2, 3, 4, 5, 6, 7, 8) + + init_kernel_4 = Pyccelkernel(eval_kernels_gc.sph_viscosity_tensor) + + boxes = particles.sorting_boxes.boxes + neighbours = particles.sorting_boxes.neighbours + holes = particles.holes + periodic = [bci == "periodic" for bci in particles.bc] + kernel_nr = particles.ker_dct()[kernel_type] + + if kernel_width is None: + kernel_width = tuple([1 / ni for ni in self.particles[0].boxes_per_dim]) + else: + assert all([hi <= 1 / ni for hi, ni in zip(kernel_width, self.particles[0].boxes_per_dim)]) + + # init kernel + args_init = ( + boxes, + neighbours, + holes, + *periodic, + kernel_nr, + *kernel_width, + ) + + self.add_init_kernel( + init_kernel_1, + first_free_idx, + comps, + args_init, + ) + + self.add_init_kernel( + init_kernel_2, + first_free_idx + 3, # +3 so that the previous one is not overwritten + comps, + args_init, + ) + + self.add_init_kernel( + init_kernel_3, + first_free_idx + 6, # +3 so that the previous one is not overwritten + comps_tensor, + args_init, + ) + + self.add_init_kernel( + init_kernel_4, + first_free_idx + 15, + comps_tensor, + args_init, + ) + + kernel = Pyccelkernel(pusher_kernels.push_v_viscosity) + + args_kernel = ( + boxes, + neighbours, + holes, + *periodic, + kernel_nr, + *kernel_width, + ) + + # the Pusher class wraps around all kernels + self._pusher = Pusher( + particles, + kernel, + args_kernel, + self.domain.args_domain, + alpha_in_kernel=0.0, + init_kernels=self.init_kernels, + ) + + def __call__(self, dt): + self.particles[0].put_particles_in_boxes() + self._pusher(dt) + + +class PushVinViscousPotential3D(Propagator): + r"""For each marker :math:`p`, solves + + .. math:: + + \frac{\textnormal d \mathbf v_p(t)}{\textnormal d t} = \kappa_p \sum_{i=1}^N w_i \left( \frac{1}{\rho^{N,h}(\boldsymbol \eta_p)} + \frac{1}{\rho^{N,h}(\boldsymbol \eta_i)} \right) DF^{-\top}\nabla W_h(\boldsymbol \eta_p - \boldsymbol \eta_i) \,, + + where :math:`DF^{-\top}` denotes the inverse transpose Jacobian, and with the smoothed density + + .. math:: + + \rho^{N,h}(\boldsymbol \eta) = \frac 1N \sum_{j=1}^N w_j \, W_h(\boldsymbol \eta - \boldsymbol \eta_j)\,, + + where :math:`W_h(\boldsymbol \eta)` is a smoothing kernel from :mod:`~struphy.pic.sph_smoothing_kernels`. + Time stepping: + + * Explicit from :class:`~struphy.ode.utils.ButcherTableau` + """ + + @staticmethod + def options(default=False): + dct = {} + dct["kernel_type"] = [ker for ker in list(Particles.ker_dct()) if "3d" in ker] + dct["kernel_width"] = None + dct["algo"] = [ + "forward_euler", + ] # "heun2", "rk2", "heun3", "rk4"] + if default: + dct = descend_options_dict(dct, []) + return dct + + def __init__( + self, + particles: ParticlesSPH, + *, + kernel_type: str = "gaussian_3d", + kernel_width: tuple = None, + algo: str = options(default=True)["algo"], # TODO: implement other algos than forward Euler + ): + # base class constructor call + super().__init__(particles) + # init kernel for evaluating density etc. before each time step. init_kernel_1 = eval_kernels_gc.sph_mean_velocity_coeffs first_free_idx = particles.args_markers.first_free_idx @@ -1894,7 +2014,7 @@ def __init__( args_init, ) - kernel = pusher_kernels.push_v_viscosity + kernel = Pyccelkernel(pusher_kernels.push_v_viscosity) args_kernel = ( boxes, diff --git a/src/struphy/propagators/tests/test_gyrokinetic_poisson.py b/src/struphy/propagators/tests/test_gyrokinetic_poisson.py index 4d2d7087e..747ec65c7 100644 --- a/src/struphy/propagators/tests/test_gyrokinetic_poisson.py +++ b/src/struphy/propagators/tests/test_gyrokinetic_poisson.py @@ -1,23 +1,23 @@ +import cunumpy as xp import matplotlib.pyplot as plt -import numpy as np import pytest -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.projectors import L2Projector from struphy.feec.psydac_derham import Derham from struphy.geometry import domains +from struphy.geometry.base import Domain from struphy.linear_algebra.solver import SolverParameters from struphy.models.variables import FEECVariable -from struphy.propagators import ImplicitDiffusion from struphy.propagators.base import Propagator +from struphy.propagators.propagators_fields import ImplicitDiffusion comm = MPI.COMM_WORLD rank = comm.Get_rank() # plt.rcParams.update({'font.size': 22}) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("direction", [0, 1]) @pytest.mark.parametrize("bc_type", ["periodic", "dirichlet", "neumann"]) @pytest.mark.parametrize( @@ -27,7 +27,8 @@ ["Orthogonal", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 3.0}], ], ) -def test_poisson_M1perp_1d(direction, bc_type, mapping, show_plot=False): +@pytest.mark.parametrize("projected_rhs", [False, True]) +def test_poisson_M1perp_1d(direction, bc_type, mapping, projected_rhs, show_plot=False): """ Test the convergence of Poisson solver with M1perp diffusion matrix in 1D by means of manufactured solutions. @@ -38,7 +39,7 @@ def test_poisson_M1perp_1d(direction, bc_type, mapping, show_plot=False): dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain = domain_class(**dom_params) + domain: Domain = domain_class(**dom_params) if dom_type == "Cuboid": Lx = dom_params["r1"] - dom_params["l1"] @@ -55,9 +56,9 @@ def test_poisson_M1perp_1d(direction, bc_type, mapping, show_plot=False): errors = [] h_vec = [] if show_plot: - plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) - plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) - plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) + plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) + plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) + plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) for n, Neli in enumerate(Nels): # boundary conditions (overwritten below) @@ -71,16 +72,16 @@ def test_poisson_M1perp_1d(direction, bc_type, mapping, show_plot=False): if direction == 0: Nel = [Neli, 1, 1] p = [pi, 1, 1] - e1 = np.linspace(0.0, 1.0, 50) + e1 = xp.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [False, True, True] def sol1_xyz(x, y, z): - return np.cos(np.pi / Lx * x) + return xp.cos(xp.pi / Lx * x) def rho1_xyz(x, y, z): - return np.cos(np.pi / Lx * x) * (np.pi / Lx) ** 2 + return xp.cos(xp.pi / Lx * x) * (xp.pi / Lx) ** 2 else: if bc_type == "dirichlet": spl_kind = [False, True, True] @@ -88,24 +89,24 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return np.sin(2 * np.pi / Lx * x) + return xp.sin(2 * xp.pi / Lx * x) def rho1_xyz(x, y, z): - return np.sin(2 * np.pi / Lx * x) * (2 * np.pi / Lx) ** 2 + return xp.sin(2 * xp.pi / Lx * x) * (2 * xp.pi / Lx) ** 2 elif direction == 1: Nel = [1, Neli, 1] p = [1, pi, 1] - e2 = np.linspace(0.0, 1.0, 50) + e2 = xp.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [True, False, True] def sol1_xyz(x, y, z): - return np.cos(np.pi / Ly * y) + return xp.cos(xp.pi / Ly * y) def rho1_xyz(x, y, z): - return np.cos(np.pi / Ly * y) * (np.pi / Ly) ** 2 + return xp.cos(xp.pi / Ly * y) * (xp.pi / Ly) ** 2 else: if bc_type == "dirichlet": spl_kind = [True, False, True] @@ -113,15 +114,15 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return np.sin(2 * np.pi / Ly * y) + return xp.sin(2 * xp.pi / Ly * y) def rho1_xyz(x, y, z): - return np.sin(2 * np.pi / Ly * y) * (2 * np.pi / Ly) ** 2 + return xp.sin(2 * xp.pi / Ly * y) * (2 * xp.pi / Ly) ** 2 else: print("Direction should be either 0 or 1") # create derham object - print(f"{dirichlet_bc = }") + print(f"{dirichlet_bc =}") derham = Derham(Nel, p, spl_kind, dirichlet_bc=dirichlet_bc, comm=comm) # mass matrices @@ -132,10 +133,16 @@ def rho1_xyz(x, y, z): Propagator.mass_ops = mass_ops # pullbacks of right-hand side - def rho1(e1, e2, e3): - return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=True) - - rho_vec = L2Projector("H1", mass_ops).get_dofs(rho1, apply_bc=True) + def rho_pulled(e1, e2, e3): + return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=False) + + # define how to pass rho + if projected_rhs: + rho = FEECVariable(space="H1") + rho.allocate(derham=derham, domain=domain) + rho.spline.vector = derham.P["0"](rho_pulled) + else: + rho = rho_pulled # create Poisson solver solver_params = SolverParameters( @@ -158,7 +165,7 @@ def rho1(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho_vec, + rho=rho, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -176,7 +183,7 @@ def rho1(e1, e2, e3): analytic_value1 = sol1_xyz(x, y, z) if show_plot: - plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }") + plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}") plt.subplot(2, 3, n + 1) if direction == 0: plt.plot(x[:, 0, 0], sol_val1[:, 0, 0], "ob", label="numerical") @@ -186,24 +193,25 @@ def rho1(e1, e2, e3): plt.plot(y[0, :, 0], sol_val1[0, :, 0], "ob", label="numerical") plt.plot(y[0, :, 0], analytic_value1[0, :, 0], "r--", label="exact") plt.xlabel("y") - plt.title(f"{Nel = }") + plt.title(f"{Nel =}") plt.legend() - error = np.max(np.abs(analytic_value1 - sol_val1)) - print(f"{direction = }, {pi = }, {Neli = }, {error=}") + error = xp.max(xp.abs(analytic_value1 - sol_val1)) + print(f"{direction =}, {pi =}, {Neli =}, {error=}") errors.append(error) h = 1 / (Neli) h_vec.append(h) - m, _ = np.polyfit(np.log(Nels), np.log(errors), deg=1) - print(f"For {pi = }, solution converges in {direction=} with rate {-m = } ") - assert -m > (pi + 1 - 0.06) + m, _ = xp.polyfit(xp.log(Nels), xp.log(errors), deg=1) + print(f"For {pi =}, solution converges in {direction=} with rate {-m =} ") + assert -m > (pi + 1 - 0.07) # Plot convergence in 1D if show_plot: plt.figure( - f"Convergence for degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(12, 8) + f"Convergence for degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", + figsize=(12, 8), ) plt.plot(h_vec, errors, "o", label=f"p={p[direction]}") plt.plot( @@ -216,14 +224,13 @@ def rho1(e1, e2, e3): plt.xscale("log") plt.xlabel("Grid Spacing h") plt.ylabel("Error") - plt.title(f"Poisson solver") + plt.title("Poisson solver") plt.legend() if show_plot and rank == 0: plt.show() -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[64, 64, 1]]) @pytest.mark.parametrize("p", [[1, 1, 1], [2, 2, 1]]) @pytest.mark.parametrize("bc_type", ["periodic", "dirichlet", "neumann"]) @@ -234,7 +241,8 @@ def rho1(e1, e2, e3): ["Orthogonal", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 1.0}], ], ) -def test_poisson_M1perp_2d(Nel, p, bc_type, mapping, show_plot=False): +@pytest.mark.parametrize("projected_rhs", [False, True]) +def test_poisson_M1perp_2d(Nel, p, bc_type, mapping, projected_rhs, show_plot=False): """ Test the Poisson solver with M1perp diffusion matrix by means of manufactured solutions in 2D . @@ -245,7 +253,7 @@ def test_poisson_M1perp_2d(Nel, p, bc_type, mapping, show_plot=False): dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain = domain_class(**dom_params) + domain: Domain = domain_class(**dom_params) if dom_type == "Cuboid": Lx = dom_params["r1"] - dom_params["l1"] @@ -256,10 +264,10 @@ def test_poisson_M1perp_2d(Nel, p, bc_type, mapping, show_plot=False): # manufactured solution in 1D (overwritten for "neumann") def sol1_xyz(x, y, z): - return np.sin(2 * np.pi / Lx * x) + return xp.sin(2 * xp.pi / Lx * x) def rho1_xyz(x, y, z): - return np.sin(2 * np.pi / Lx * x) * (2 * np.pi / Lx) ** 2 + return xp.sin(2 * xp.pi / Lx * x) * (2 * xp.pi / Lx) ** 2 # boundary conditions dirichlet_bc = None @@ -269,26 +277,26 @@ def rho1_xyz(x, y, z): # manufactured solution in 2D def sol2_xyz(x, y, z): - return np.sin(2 * np.pi * x / Lx + 4 * np.pi / Ly * y) + return xp.sin(2 * xp.pi * x / Lx + 4 * xp.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = np.sin(2 * np.pi / Lx * x + 4 * np.pi / Ly * y) * (2 * np.pi / Lx) ** 2 - ddy = np.sin(2 * np.pi / Lx * x + 4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 + ddx = xp.sin(2 * xp.pi / Lx * x + 4 * xp.pi / Ly * y) * (2 * xp.pi / Lx) ** 2 + ddy = xp.sin(2 * xp.pi / Lx * x + 4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 return ddx + ddy elif bc_type == "dirichlet": spl_kind = [False, True, True] dirichlet_bc = [(not kd,) * 2 for kd in spl_kind] dirichlet_bc = tuple(dirichlet_bc) - print(f"{dirichlet_bc = }") + print(f"{dirichlet_bc =}") # manufactured solution in 2D def sol2_xyz(x, y, z): - return np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) + return xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (np.pi / Lx) ** 2 - ddy = np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 + ddx = xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (xp.pi / Lx) ** 2 + ddy = xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 return ddx + ddy elif bc_type == "neumann": @@ -296,19 +304,19 @@ def rho2_xyz(x, y, z): # manufactured solution in 2D def sol2_xyz(x, y, z): - return np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) + return xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (np.pi / Lx) ** 2 - ddy = np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 + ddx = xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (xp.pi / Lx) ** 2 + ddy = xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 return ddx + ddy # manufactured solution in 1D def sol1_xyz(x, y, z): - return np.cos(np.pi / Lx * x) + return xp.cos(xp.pi / Lx * x) def rho1_xyz(x, y, z): - return np.cos(np.pi / Lx * x) * (np.pi / Lx) ** 2 + return xp.cos(xp.pi / Lx * x) * (xp.pi / Lx) ** 2 # create derham object derham = Derham(Nel, p, spl_kind, dirichlet_bc=dirichlet_bc, comm=comm) @@ -321,21 +329,29 @@ def rho1_xyz(x, y, z): Propagator.mass_ops = mass_ops # evaluation grid - e1 = np.linspace(0.0, 1.0, 50) - e2 = np.linspace(0.0, 1.0, 50) - e3 = np.linspace(0.0, 1.0, 1) + e1 = xp.linspace(0.0, 1.0, 50) + e2 = xp.linspace(0.0, 1.0, 50) + e3 = xp.linspace(0.0, 1.0, 1) # pullbacks of right-hand side - def rho1(e1, e2, e3): - return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=True) + def rho1_pulled(e1, e2, e3): + return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=False) - def rho2(e1, e2, e3): - return domain.pull(rho2_xyz, e1, e2, e3, kind="0", squeeze_out=True) + def rho2_pulled(e1, e2, e3): + return domain.pull(rho2_xyz, e1, e2, e3, kind="0", squeeze_out=False) - # discrete right-hand sides - l2_proj = L2Projector("H1", mass_ops) - rho_vec1 = l2_proj.get_dofs(rho1, apply_bc=True) - rho_vec2 = l2_proj.get_dofs(rho2, apply_bc=True) + # how to pass right-hand sides + if projected_rhs: + rho1 = FEECVariable(space="H1") + rho1.allocate(derham=derham, domain=domain) + rho1.spline.vector = derham.P["0"](rho1_pulled) + + rho2 = FEECVariable(space="H1") + rho2.allocate(derham=derham, domain=domain) + rho2.spline.vector = derham.P["0"](rho2_pulled) + else: + rho1 = rho1_pulled + rho2 = rho2_pulled # Create Poisson solvers solver_params = SolverParameters( @@ -358,7 +374,7 @@ def rho2(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho_vec1, + rho=rho1, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -378,7 +394,7 @@ def rho2(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho_vec2, + rho=rho2, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -400,12 +416,12 @@ def rho2(e1, e2, e3): analytic_value2 = sol2_xyz(x, y, z) # compute error - error1 = np.max(np.abs(analytic_value1 - sol_val1)) - error2 = np.max(np.abs(analytic_value2 - sol_val2)) + error1 = xp.max(xp.abs(analytic_value1 - sol_val1)) + error2 = xp.max(xp.abs(analytic_value2 - sol_val2)) - print(f"{p = }, {bc_type = }, {mapping = }") - print(f"{error1 = }") - print(f"{error2 = }") + print(f"{p =}, {bc_type =}, {mapping =}") + print(f"{error1 =}") + print(f"{error2 =}") print("") if show_plot and rank == 0: @@ -431,11 +447,10 @@ def rho2(e1, e2, e3): plt.show() assert error1 < 0.0044 - assert error2 < 0.021 + assert error2 < 0.023 @pytest.mark.skip(reason="Not clear if the 2.5d strategy is sound.") -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[32, 32, 16]]) @pytest.mark.parametrize("p", [[1, 1, 1], [2, 2, 1]]) @pytest.mark.parametrize( @@ -459,21 +474,21 @@ def test_poisson_M1perp_3d_compare_2p5d(Nel, p, mapping, show_plot=False): dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain = domain_class(**dom_params) + domain: Domain = domain_class(**dom_params) # boundary conditions spl_kind = [False, True, True] dirichlet_bc = ((True, True), (False, False), (False, False)) # evaluation grid - e1 = np.linspace(0.0, 1.0, 50) - e2 = np.linspace(0.0, 1.0, 60) - e3 = np.linspace(0.0, 1.0, 30) + e1 = xp.linspace(0.0, 1.0, 50) + e2 = xp.linspace(0.0, 1.0, 60) + e3 = xp.linspace(0.0, 1.0, 30) # solution and right-hand side on unit cube def rho(e1, e2, e3): - dd1 = np.sin(np.pi * e1) * np.sin(4 * np.pi * e2) * np.cos(2 * np.pi * e3) * (np.pi) ** 2 - dd2 = np.sin(np.pi * e1) * np.sin(4 * np.pi * e2) * np.cos(2 * np.pi * e3) * (4 * np.pi) ** 2 + dd1 = xp.sin(xp.pi * e1) * xp.sin(4 * xp.pi * e2) * xp.cos(2 * xp.pi * e3) * (xp.pi) ** 2 + dd2 = xp.sin(xp.pi * e1) * xp.sin(4 * xp.pi * e2) * xp.cos(2 * xp.pi * e3) * (4 * xp.pi) ** 2 return dd1 + dd2 # create 3d derham object @@ -489,7 +504,7 @@ def rho(e1, e2, e3): l2_proj = L2Projector("H1", mass_ops) rho_vec = l2_proj.get_dofs(rho, apply_bc=True) - print(f"{rho_vec[:].shape = }") + print(f"{rho_vec[:].shape =}") # Create 3d Poisson solver solver_params = SolverParameters( @@ -515,7 +530,7 @@ def rho(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rho_vec, + rho=rho, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -539,7 +554,6 @@ def rho(e1, e2, e3): _phi_small = FEECVariable(space="H1") _phi_small.allocate(derham=derham, domain=domain) - rhs = derham.create_spline_function("rhs", "H1") poisson_solver_2p5d = ImplicitDiffusion() poisson_solver_2p5d.variables.phi = _phi_small @@ -550,7 +564,7 @@ def rho(e1, e2, e3): sigma_3=1.0, divide_by_dt=True, diffusion_mat="M1perp", - rho=rhs.vector, + rho=rho, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -569,8 +583,6 @@ def rho(e1, e2, e3): t0 = time() t_inner = 0.0 for n in range(s[2], e[2] + 1): - # scale the rhs with Nel[2] !! - rhs.vector[s[0] : e[0] + 1, s[1] : e[1] + 1, 0] = rho_vec[s[0] : e[0] + 1, s[1] : e[1] + 1, n] * Nel[2] t0i = time() poisson_solver_2p5d(dt) t1i = time() @@ -587,8 +599,8 @@ def rho(e1, e2, e3): sol_val_2p5d = domain.push(_phi_2p5d.spline, e1, e2, e3, kind="0") x, y, z = domain(e1, e2, e3) - print("max diff:", np.max(np.abs(sol_val - sol_val_2p5d))) - assert np.max(np.abs(sol_val - sol_val_2p5d)) < 0.026 + print("max diff:", xp.max(xp.abs(sol_val - sol_val_2p5d))) + assert xp.max(xp.abs(sol_val - sol_val_2p5d)) < 0.026 if show_plot and rank == 0: plt.figure("e1-e2 plane", figsize=(24, 16)) @@ -637,7 +649,7 @@ def rho(e1, e2, e3): # mapping = ['Orthogonal', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 1.}] # test_poisson_M1perp_2d(Nel, p, bc_type, mapping, show_plot=True) - Nel = [64, 64, 16] - p = [2, 2, 1] - mapping = ["Cuboid", {"l1": 0.0, "r1": 1.0, "l2": 0.0, "r2": 1.0, "l3": 0.0, "r3": 1.0}] + # Nel = [64, 64, 16] + # p = [2, 2, 1] + # mapping = ["Cuboid", {"l1": 0.0, "r1": 1.0, "l2": 0.0, "r2": 1.0, "l3": 0.0, "r3": 1.0}] # test_poisson_M1perp_3d_compare_2p5d(Nel, p, mapping, show_plot=True) diff --git a/src/struphy/propagators/tests/test_poisson.py b/src/struphy/propagators/tests/test_poisson.py index 5a1de11ba..588aa2aa1 100644 --- a/src/struphy/propagators/tests/test_poisson.py +++ b/src/struphy/propagators/tests/test_poisson.py @@ -1,23 +1,35 @@ +import cunumpy as xp import matplotlib.pyplot as plt -import numpy as np import pytest -from mpi4py import MPI +from psydac.ddm.mpi import mpi as MPI from struphy.feec.mass import WeightedMassOperators from struphy.feec.projectors import L2Projector from struphy.feec.psydac_derham import Derham from struphy.geometry import domains +from struphy.geometry.base import Domain +from struphy.initial import perturbations +from struphy.kinetic_background.maxwellians import Maxwellian3D from struphy.linear_algebra.solver import SolverParameters from struphy.models.variables import FEECVariable -from struphy.propagators import ImplicitDiffusion +from struphy.pic.accumulation.accum_kernels import charge_density_0form +from struphy.pic.accumulation.particles_to_grid import AccumulatorVector +from struphy.pic.particles import Particles6D +from struphy.pic.utilities import ( + BinningPlot, + BoundaryParameters, + LoadingParameters, + WeightsParameters, +) from struphy.propagators.base import Propagator +from struphy.propagators.propagators_fields import ImplicitDiffusion, Poisson +from struphy.utils.pyccel import Pyccelkernel comm = MPI.COMM_WORLD rank = comm.Get_rank() plt.rcParams.update({"font.size": 22}) -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("direction", [0, 1, 2]) @pytest.mark.parametrize("bc_type", ["periodic", "dirichlet", "neumann"]) @pytest.mark.parametrize( @@ -27,7 +39,14 @@ ["Orthogonal", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 3.0}], ], ) -def test_poisson_1d(direction, bc_type, mapping, show_plot=False): +@pytest.mark.parametrize("projected_rhs", [False, True]) +def test_poisson_1d( + direction: int, + bc_type: str, + mapping: list[str, dict], + projected_rhs: bool, + show_plot: bool = False, +): """ Test the convergence of Poisson solver in 1D by means of manufactured solutions. """ @@ -37,7 +56,7 @@ def test_poisson_1d(direction, bc_type, mapping, show_plot=False): dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain = domain_class(**dom_params) + domain: Domain = domain_class(**dom_params) if dom_type == "Cuboid": Lx = dom_params["r1"] - dom_params["l1"] @@ -54,9 +73,9 @@ def test_poisson_1d(direction, bc_type, mapping, show_plot=False): errors = [] h_vec = [] if show_plot: - plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) - plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) - plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(24, 16)) + plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) + plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) + plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", figsize=(24, 16)) for n, Neli in enumerate(Nels): # boundary conditions (overwritten below) @@ -70,16 +89,16 @@ def test_poisson_1d(direction, bc_type, mapping, show_plot=False): if direction == 0: Nel = [Neli, 1, 1] p = [pi, 1, 1] - e1 = np.linspace(0.0, 1.0, 50) + e1 = xp.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [False, True, True] def sol1_xyz(x, y, z): - return np.cos(np.pi / Lx * x) + return xp.cos(xp.pi / Lx * x) def rho1_xyz(x, y, z): - return np.cos(np.pi / Lx * x) * (np.pi / Lx) ** 2 + return xp.cos(xp.pi / Lx * x) * (xp.pi / Lx) ** 2 else: if bc_type == "dirichlet": spl_kind = [False, True, True] @@ -87,24 +106,24 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return np.sin(2 * np.pi / Lx * x) + return xp.sin(2 * xp.pi / Lx * x) def rho1_xyz(x, y, z): - return np.sin(2 * np.pi / Lx * x) * (2 * np.pi / Lx) ** 2 + return xp.sin(2 * xp.pi / Lx * x) * (2 * xp.pi / Lx) ** 2 elif direction == 1: Nel = [1, Neli, 1] p = [1, pi, 1] - e2 = np.linspace(0.0, 1.0, 50) + e2 = xp.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [True, False, True] def sol1_xyz(x, y, z): - return np.cos(np.pi / Ly * y) + return xp.cos(xp.pi / Ly * y) def rho1_xyz(x, y, z): - return np.cos(np.pi / Ly * y) * (np.pi / Ly) ** 2 + return xp.cos(xp.pi / Ly * y) * (xp.pi / Ly) ** 2 else: if bc_type == "dirichlet": spl_kind = [True, False, True] @@ -112,24 +131,24 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return np.sin(2 * np.pi / Ly * y) + return xp.sin(2 * xp.pi / Ly * y) def rho1_xyz(x, y, z): - return np.sin(2 * np.pi / Ly * y) * (2 * np.pi / Ly) ** 2 + return xp.sin(2 * xp.pi / Ly * y) * (2 * xp.pi / Ly) ** 2 elif direction == 2: Nel = [1, 1, Neli] p = [1, 1, pi] - e3 = np.linspace(0.0, 1.0, 50) + e3 = xp.linspace(0.0, 1.0, 50) if bc_type == "neumann": spl_kind = [True, True, False] def sol1_xyz(x, y, z): - return np.cos(np.pi / Lz * z) + return xp.cos(xp.pi / Lz * z) def rho1_xyz(x, y, z): - return np.cos(np.pi / Lz * z) * (np.pi / Lz) ** 2 + return xp.cos(xp.pi / Lz * z) * (xp.pi / Lz) ** 2 else: if bc_type == "dirichlet": spl_kind = [True, True, False] @@ -137,10 +156,10 @@ def rho1_xyz(x, y, z): dirichlet_bc = tuple(dirichlet_bc) def sol1_xyz(x, y, z): - return np.sin(2 * np.pi / Lz * z) + return xp.sin(2 * xp.pi / Lz * z) def rho1_xyz(x, y, z): - return np.sin(2 * np.pi / Lz * z) * (2 * np.pi / Lz) ** 2 + return xp.sin(2 * xp.pi / Lz * z) * (2 * xp.pi / Lz) ** 2 else: print("Direction should be either 0, 1 or 2") @@ -155,10 +174,16 @@ def rho1_xyz(x, y, z): Propagator.mass_ops = mass_ops # pullbacks of right-hand side - def rho1(e1, e2, e3): - return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=True) - - rho_vec = L2Projector("H1", mass_ops).get_dofs(rho1, apply_bc=True) + def rho_pulled(e1, e2, e3): + return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=False) + + # define how to pass rho + if projected_rhs: + rho = FEECVariable(space="H1") + rho.allocate(derham=derham, domain=domain) + rho.spline.vector = derham.P["0"](rho_pulled) + else: + rho = rho_pulled # create Poisson solver solver_params = SolverParameters( @@ -172,14 +197,14 @@ def rho1(e1, e2, e3): _phi = FEECVariable(space="H1") _phi.allocate(derham=derham, domain=domain) - poisson_solver = ImplicitDiffusion() + poisson_solver = Poisson() poisson_solver.variables.phi = _phi poisson_solver.options = poisson_solver.Options( - sigma_1=1e-12, - sigma_2=0.0, - sigma_3=1.0, - rho=rho_vec, + stab_eps=1e-12, + # sigma_2=0.0, + # sigma_3=1.0, + rho=rho, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -197,7 +222,7 @@ def rho1(e1, e2, e3): analytic_value1 = sol1_xyz(x, y, z) if show_plot: - plt.figure(f"degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }") + plt.figure(f"degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}") plt.subplot(2, 3, n + 1) if direction == 0: plt.plot(x[:, 0, 0], sol_val1[:, 0, 0], "ob", label="numerical") @@ -211,24 +236,25 @@ def rho1(e1, e2, e3): plt.plot(z[0, 0, :], sol_val1[0, 0, :], "ob", label="numerical") plt.plot(z[0, 0, :], analytic_value1[0, 0, :], "r--", label="exact") plt.xlabel("z") - plt.title(f"{Nel = }") + plt.title(f"{Nel =}") plt.legend() - error = np.max(np.abs(analytic_value1 - sol_val1)) - print(f"{direction = }, {pi = }, {Neli = }, {error=}") + error = xp.max(xp.abs(analytic_value1 - sol_val1)) + print(f"{direction =}, {pi =}, {Neli =}, {error=}") errors.append(error) h = 1 / (Neli) h_vec.append(h) - m, _ = np.polyfit(np.log(Nels), np.log(errors), deg=1) - print(f"For {pi = }, solution converges in {direction=} with rate {-m = } ") - assert -m > (pi + 1 - 0.06) + m, _ = xp.polyfit(xp.log(Nels), xp.log(errors), deg=1) + print(f"For {pi =}, solution converges in {direction=} with rate {-m =} ") + assert -m > (pi + 1 - 0.07) # Plot convergence in 1D if show_plot: plt.figure( - f"Convergence for degree {pi = }, {direction + 1 = }, {bc_type = }, {mapping[0] = }", figsize=(12, 8) + f"Convergence for degree {pi =}, {direction + 1 =}, {bc_type =}, {mapping[0] =}", + figsize=(12, 8), ) plt.plot(h_vec, errors, "o", label=f"p={p[direction]}") plt.plot( @@ -241,13 +267,168 @@ def rho1(e1, e2, e3): plt.xscale("log") plt.xlabel("Grid Spacing h") plt.ylabel("Error") - plt.title(f"Poisson solver") + plt.title("Poisson solver") plt.legend() if show_plot and rank == 0: plt.show() +@pytest.mark.parametrize( + "mapping", + [ + ["Cuboid", {"l1": 0.0, "r1": 4.0, "l2": 0.0, "r2": 2.0, "l3": 0.0, "r3": 3.0}], + # ["Orthogonal", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 3.0}], + ], +) +def test_poisson_accum_1d(mapping, do_plot=False): + """Pass accumulators as rhs.""" + # create domain object + dom_type = mapping[0] + dom_params = mapping[1] + + domain_class = getattr(domains, dom_type) + domain: Domain = domain_class(**dom_params) + + if dom_type == "Cuboid": + Lx = dom_params["r1"] - dom_params["l1"] + else: + Lx = dom_params["Lx"] + + # create derham object + Nel = (16, 1, 1) + p = (2, 1, 1) + spl_kind = (True, True, True) + derham = Derham(Nel, p, spl_kind, comm=comm) + + # mass matrices + mass_ops = WeightedMassOperators(derham, domain) + + Propagator.derham = derham + Propagator.domain = domain + Propagator.mass_ops = mass_ops + + # 6D particle object + domain_array = derham.domain_array + nprocs = derham.domain_decomposition.nprocs + domain_decomp = (domain_array, nprocs) + + lp = LoadingParameters(ppc=4000, seed=765) + wp = WeightsParameters(control_variate=True) + bp = BoundaryParameters() + + backgr = Maxwellian3D(n=(1.0, None)) + l = 1 + amp = 1e-1 + pert = perturbations.ModesCos(ls=(l,), amps=(amp,)) + maxw = Maxwellian3D(n=(1.0, pert)) + + pert_exact = lambda x, y, z: amp * xp.cos(l * 2 * xp.pi / Lx * x) + phi_exact = lambda x, y, z: amp / (l * 2 * xp.pi / Lx) ** 2 * xp.cos(l * 2 * xp.pi / Lx * x) + e_exact = lambda x, y, z: amp / (l * 2 * xp.pi / Lx) * xp.sin(l * 2 * xp.pi / Lx * x) + + particles = Particles6D( + comm_world=comm, + domain_decomp=domain_decomp, + loading_params=lp, + weights_params=wp, + boundary_params=bp, + domain=domain, + background=backgr, + initial_condition=maxw, + ) + particles.draw_markers() + particles.initialize_weights() + + # particle to grid coupling + kernel = Pyccelkernel(charge_density_0form) + accum = AccumulatorVector(particles, "H1", kernel, mass_ops, domain.args_domain) + # accum() + # if do_plot: + # accum.show_accumulated_spline_field(mass_ops) + + rho = accum + + # create Poisson solver + solver_params = SolverParameters( + tol=1.0e-13, + maxiter=3000, + info=True, + verbose=False, + recycle=False, + ) + + _phi = FEECVariable(space="H1") + _phi.allocate(derham=derham, domain=domain) + + poisson_solver = Poisson() + poisson_solver.variables.phi = _phi + + poisson_solver.options = poisson_solver.Options( + stab_eps=1e-6, + # sigma_2=0.0, + # sigma_3=1.0, + rho=rho, + solver="pcg", + precond="MassMatrixPreconditioner", + solver_params=solver_params, + ) + + poisson_solver.allocate() + + # Solve Poisson (call propagator with dt=1.) + dt = 1.0 + poisson_solver(dt) + + # push numerical solution and compare + e1 = xp.linspace(0.0, 1.0, 50) + e2 = 0.0 + e3 = 0.0 + + num_values = domain.push(_phi.spline, e1, e2, e3, kind="0") + x, y, z = domain(e1, e2, e3) + pert_values = pert_exact(x, y, z) + analytic_values = phi_exact(x, y, z) + e_values = e_exact(x, y, z) + + _e = FEECVariable(space="Hcurl") + _e.allocate(derham=derham, domain=domain) + derham.grad.dot(-_phi.spline.vector, out=_e.spline.vector) + num_values_e = domain.push(_e.spline, e1, e2, e3, kind="1") + + if do_plot: + field = derham.create_spline_function("accum_field", "H1") + field.vector = accum.vectors[0] + accum_values = field(e1, e2, e3) + + plt.figure(figsize=(18, 12)) + plt.subplot(1, 3, 1) + plt.plot(x[:, 0, 0], num_values[:, 0, 0], "ob", label="numerical") + plt.plot(x[:, 0, 0], analytic_values[:, 0, 0], "r--", label="exact") + plt.xlabel("x") + plt.title("phi") + plt.legend() + plt.subplot(1, 3, 2) + plt.plot(x[:, 0, 0], accum_values[:, 0, 0], "ob", label="numerical, without L2-proj") + plt.plot(x[:, 0, 0], pert_values[:, 0, 0], "r--", label="exact") + plt.xlabel("x") + plt.title("rhs") + plt.legend() + plt.subplot(1, 3, 3) + plt.plot(x[:, 0, 0], num_values_e[0][:, 0, 0], "ob", label="numerical") + plt.plot(x[:, 0, 0], e_values[:, 0, 0], "r--", label="exact") + plt.xlabel("x") + plt.title("e_field") + plt.legend() + + plt.show() + + error = xp.max(xp.abs(num_values_e[0][:, 0, 0] - e_values[:, 0, 0])) / xp.max(xp.abs(e_values[:, 0, 0])) + print(f"{error=}") + + assert error < 0.0086 + + @pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[64, 64, 1]]) @pytest.mark.parametrize("p", [[1, 1, 1], [2, 2, 1]]) @@ -259,7 +440,8 @@ def rho1(e1, e2, e3): ["Colella", {"Lx": 4.0, "Ly": 2.0, "alpha": 0.1, "Lz": 1.0}], ], ) -def test_poisson_2d(Nel, p, bc_type, mapping, show_plot=False): +@pytest.mark.parametrize("projected_rhs", [False, True]) +def test_poisson_2d(Nel, p, bc_type, mapping, projected_rhs, show_plot=False): """ Test the Poisson solver by means of manufactured solutions in 2D . """ @@ -269,7 +451,7 @@ def test_poisson_2d(Nel, p, bc_type, mapping, show_plot=False): dom_params = mapping[1] domain_class = getattr(domains, dom_type) - domain = domain_class(**dom_params) + domain: Domain = domain_class(**dom_params) if dom_type == "Cuboid": Lx = dom_params["r1"] - dom_params["l1"] @@ -280,10 +462,10 @@ def test_poisson_2d(Nel, p, bc_type, mapping, show_plot=False): # manufactured solution in 1D (overwritten for "neumann") def sol1_xyz(x, y, z): - return np.sin(2 * np.pi / Lx * x) + return xp.sin(2 * xp.pi / Lx * x) def rho1_xyz(x, y, z): - return np.sin(2 * np.pi / Lx * x) * (2 * np.pi / Lx) ** 2 + return xp.sin(2 * xp.pi / Lx * x) * (2 * xp.pi / Lx) ** 2 # boundary conditions dirichlet_bc = None @@ -293,26 +475,26 @@ def rho1_xyz(x, y, z): # manufactured solution in 2D def sol2_xyz(x, y, z): - return np.sin(2 * np.pi * x / Lx + 4 * np.pi / Ly * y) + return xp.sin(2 * xp.pi * x / Lx + 4 * xp.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = np.sin(2 * np.pi / Lx * x + 4 * np.pi / Ly * y) * (2 * np.pi / Lx) ** 2 - ddy = np.sin(2 * np.pi / Lx * x + 4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 + ddx = xp.sin(2 * xp.pi / Lx * x + 4 * xp.pi / Ly * y) * (2 * xp.pi / Lx) ** 2 + ddy = xp.sin(2 * xp.pi / Lx * x + 4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 return ddx + ddy elif bc_type == "dirichlet": spl_kind = [False, True, True] dirichlet_bc = [(not kd,) * 2 for kd in spl_kind] dirichlet_bc = tuple(dirichlet_bc) - print(f"{dirichlet_bc = }") + print(f"{dirichlet_bc =}") # manufactured solution in 2D def sol2_xyz(x, y, z): - return np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) + return xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (np.pi / Lx) ** 2 - ddy = np.sin(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 + ddx = xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (xp.pi / Lx) ** 2 + ddy = xp.sin(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 return ddx + ddy elif bc_type == "neumann": @@ -320,19 +502,19 @@ def rho2_xyz(x, y, z): # manufactured solution in 2D def sol2_xyz(x, y, z): - return np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) + return xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) def rho2_xyz(x, y, z): - ddx = np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (np.pi / Lx) ** 2 - ddy = np.cos(np.pi * x / Lx) * np.sin(4 * np.pi / Ly * y) * (4 * np.pi / Ly) ** 2 + ddx = xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (xp.pi / Lx) ** 2 + ddy = xp.cos(xp.pi * x / Lx) * xp.sin(4 * xp.pi / Ly * y) * (4 * xp.pi / Ly) ** 2 return ddx + ddy # manufactured solution in 1D def sol1_xyz(x, y, z): - return np.cos(np.pi / Lx * x) + return xp.cos(xp.pi / Lx * x) def rho1_xyz(x, y, z): - return np.cos(np.pi / Lx * x) * (np.pi / Lx) ** 2 + return xp.cos(xp.pi / Lx * x) * (xp.pi / Lx) ** 2 # create derham object derham = Derham(Nel, p, spl_kind, dirichlet_bc=dirichlet_bc, comm=comm) @@ -345,21 +527,29 @@ def rho1_xyz(x, y, z): Propagator.mass_ops = mass_ops # evaluation grid - e1 = np.linspace(0.0, 1.0, 50) - e2 = np.linspace(0.0, 1.0, 50) - e3 = np.linspace(0.0, 1.0, 1) + e1 = xp.linspace(0.0, 1.0, 50) + e2 = xp.linspace(0.0, 1.0, 50) + e3 = xp.linspace(0.0, 1.0, 1) # pullbacks of right-hand side - def rho1(e1, e2, e3): - return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=True) + def rho1_pulled(e1, e2, e3): + return domain.pull(rho1_xyz, e1, e2, e3, kind="0", squeeze_out=False) - def rho2(e1, e2, e3): - return domain.pull(rho2_xyz, e1, e2, e3, kind="0", squeeze_out=True) + def rho2_pulled(e1, e2, e3): + return domain.pull(rho2_xyz, e1, e2, e3, kind="0", squeeze_out=False) - # discrete right-hand sides - l2_proj = L2Projector("H1", mass_ops) - rho_vec1 = l2_proj.get_dofs(rho1, apply_bc=True) - rho_vec2 = l2_proj.get_dofs(rho2, apply_bc=True) + # how to pass right-hand sides + if projected_rhs: + rho1 = FEECVariable(space="H1") + rho1.allocate(derham=derham, domain=domain) + rho1.spline.vector = derham.P["0"](rho1_pulled) + + rho2 = FEECVariable(space="H1") + rho2.allocate(derham=derham, domain=domain) + rho2.spline.vector = derham.P["0"](rho2_pulled) + else: + rho1 = rho1_pulled + rho2 = rho2_pulled # Create Poisson solvers solver_params = SolverParameters( @@ -373,14 +563,14 @@ def rho2(e1, e2, e3): _phi1 = FEECVariable(space="H1") _phi1.allocate(derham=derham, domain=domain) - poisson_solver1 = ImplicitDiffusion() + poisson_solver1 = Poisson() poisson_solver1.variables.phi = _phi1 poisson_solver1.options = poisson_solver1.Options( - sigma_1=1e-8, - sigma_2=0.0, - sigma_3=1.0, - rho=rho_vec1, + stab_eps=1e-8, + # sigma_2=0.0, + # sigma_3=1.0, + rho=rho1, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -389,21 +579,27 @@ def rho2(e1, e2, e3): poisson_solver1.allocate() # _phi1 = derham.create_spline_function("test1", "H1") - # poisson_solver1 = ImplicitDiffusion( + # poisson_solver1 = Poisson( # _phi1.vector, sigma_1=1e-8, sigma_2=0.0, sigma_3=1.0, rho=rho_vec1, solver=solver_params # ) _phi2 = FEECVariable(space="H1") _phi2.allocate(derham=derham, domain=domain) - poisson_solver2 = ImplicitDiffusion() + poisson_solver2 = Poisson() poisson_solver2.variables.phi = _phi2 + stab_eps = 1e-8 + err_lim = 0.03 + if bc_type == "neumann" and dom_type == "Colella": + stab_eps = 1e-4 + err_lim = 0.046 + poisson_solver2.options = poisson_solver2.Options( - sigma_1=1e-8, - sigma_2=0.0, - sigma_3=1.0, - rho=rho_vec2, + stab_eps=stab_eps, + # sigma_2=0.0, + # sigma_3=1.0, + rho=rho2, solver="pcg", precond="MassMatrixPreconditioner", solver_params=solver_params, @@ -412,7 +608,7 @@ def rho2(e1, e2, e3): poisson_solver2.allocate() # _phi2 = derham.create_spline_function("test2", "H1") - # poisson_solver2 = ImplicitDiffusion( + # poisson_solver2 = Poisson( # _phi2.vector, sigma_1=1e-8, sigma_2=0.0, sigma_3=1.0, rho=rho_vec2, solver=solver_params # ) @@ -430,12 +626,12 @@ def rho2(e1, e2, e3): analytic_value2 = sol2_xyz(x, y, z) # compute error - error1 = np.max(np.abs(analytic_value1 - sol_val1)) - error2 = np.max(np.abs(analytic_value2 - sol_val2)) + error1 = xp.max(xp.abs(analytic_value1 - sol_val1)) + error2 = xp.max(xp.abs(analytic_value2 - sol_val2)) - print(f"{p = }, {bc_type = }, {mapping = }") - print(f"{error1 = }") - print(f"{error2 = }") + print(f"{p =}, {bc_type =}, {mapping =}") + print(f"{error1 =}") + print(f"{error2 =}") print("") if show_plot and rank == 0: @@ -463,21 +659,23 @@ def rho2(e1, e2, e3): if p[0] == 1 and bc_type == "neumann" and mapping[0] == "Colella": pass else: - assert error1 < 0.0044 - assert error2 < 0.021 + assert error1 < 0.0053 + assert error2 < err_lim if __name__ == "__main__": - direction = 2 - bc_type = "dirichlet" + # direction = 0 + # bc_type = "dirichlet" mapping = ["Cuboid", {"l1": 0.0, "r1": 4.0, "l2": 0.0, "r2": 2.0, "l3": 0.0, "r3": 3.0}] # mapping = ['Orthogonal', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 3.}] - test_poisson_1d(direction, bc_type, mapping, show_plot=True) + # test_poisson_1d(direction, bc_type, mapping, projected_rhs=True, show_plot=True) # Nel = [64, 64, 1] # p = [2, 2, 1] # bc_type = 'neumann' - # #mapping = ['Cuboid', {'l1': 0., 'r1': 4., 'l2': 0., 'r2': 2., 'l3': 0., 'r3': 3.}] - # #mapping = ['Orthogonal', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 1.}] + # # mapping = ['Cuboid', {'l1': 0., 'r1': 4., 'l2': 0., 'r2': 2., 'l3': 0., 'r3': 3.}] + # # mapping = ['Orthogonal', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 1.}] # mapping = ['Colella', {'Lx': 4., 'Ly': 2., 'alpha': .1, 'Lz': 1.}] - # test_poisson_2d(Nel, p, bc_type, mapping, show_plot=True) + # test_poisson_2d(Nel, p, bc_type, mapping, projected_rhs=True, show_plot=True) + + test_poisson_accum_1d(mapping, do_plot=True) diff --git a/src/struphy/topology/grids.py b/src/struphy/topology/grids.py index 0593bce05..b26887326 100644 --- a/src/struphy/topology/grids.py +++ b/src/struphy/topology/grids.py @@ -17,5 +17,5 @@ class TensorProductGrid: If mpi_dims_mask[i]=False, the i-th dimension will not be decomposed. """ - Nel: tuple = (16, 1, 1) + Nel: tuple = (24, 10, 1) mpi_dims_mask: tuple = (True, True, True) diff --git a/src/struphy/utils/clone_config.py b/src/struphy/utils/clone_config.py index 081f4d862..b6e14bc8d 100644 --- a/src/struphy/utils/clone_config.py +++ b/src/struphy/utils/clone_config.py @@ -1,5 +1,6 @@ -import numpy as np -from mpi4py import MPI +import cunumpy as xp +from psydac.ddm.mpi import MockComm +from psydac.ddm.mpi import mpi as MPI class CloneConfig: @@ -40,6 +41,9 @@ def __init__( self._species_list = None + self._clone_rank = 0 + self._clone_id = 0 + if comm is not None: assert isinstance(comm, MPI.Intracomm) rank = comm.Get_rank() @@ -59,10 +63,11 @@ def __init__( # Create a sub-communicator for each clone self._sub_comm = comm.Split(clone_color, rank) - local_rank = self.sub_comm.Get_rank() + self._clone_rank = self.sub_comm.Get_rank() # Create an inter-clone communicator for cross-clone communication - self._inter_comm = comm.Split(local_rank, rank) + self._inter_comm = comm.Split(self.clone_rank, rank) + self._clone_id = self.inter_comm.Get_rank() def get_Np_clone(self, Np, clone_id=None): """ @@ -116,28 +121,36 @@ def get_Np_global(self, species_name): if "Np" in markers: return markers["Np"] elif "ppc" in markers: - n_cells = np.prod(self.params["grid"]["Nel"], dtype=int) + n_cells = xp.prod(self.params["grid"]["Nel"], dtype=int) return int(markers["ppc"] * n_cells) elif "ppb" in markers: - n_boxes = np.prod(species["boxes_per_dim"], dtype=int) * self.num_clones + n_boxes = xp.prod(species["boxes_per_dim"], dtype=int) * self.num_clones return int(markers["ppb"] * n_boxes) def print_clone_config(self): """Print a table summarizing the clone configuration.""" - comm_world = MPI.COMM_WORLD - rank = comm_world.Get_rank() - size = comm_world.Get_size() + if isinstance(MPI.COMM_WORLD, MockComm): + comm_world = None + rank = 0 + size = 1 + else: + comm_world = MPI.COMM_WORLD + rank = comm_world.Get_rank() + size = comm_world.Get_size() ranks_per_clone = size // self.num_clones clone_color = rank // ranks_per_clone # Gather information from all ranks to the rank 0 process - clone_info = comm_world.gather( - (rank, clone_color, self.clone_rank, self.clone_id), - root=0, - ) + if comm_world is None: + clone_info = [(rank, clone_color, self.clone_rank, self.clone_id)] + else: + clone_info = comm_world.gather( + (rank, clone_color, self.clone_rank, self.clone_id), + root=0, + ) - if comm_world.Get_rank() == 0: + if rank == 0: print(f"\nNumber of clones: {self.num_clones}") # Generate an ASCII table for each clone message = "" @@ -196,7 +209,7 @@ def print_particle_config(self): row = f"{i_clone:6} " # Np = self.params["kinetic"][species_name]["markers"]["Np"] Np = self.get_Np_global(species_name) - n_cells_clone = np.prod(self.params["grid"]["Nel"]) + n_cells_clone = xp.prod(self.params["grid"]["Nel"]) Np_clone = self.get_Np_clone(Np, clone_id=i_clone) ppc_clone = Np_clone / n_cells_clone @@ -217,7 +230,7 @@ def print_particle_config(self): if marker_key in self.params["kinetic"][species_name]["markers"].keys(): params_value = self.params["kinetic"][species_name]["markers"][marker_key] if params_value is not None: - assert sum_value == params_value, f"{sum_value = } and {params_value = }" + assert sum_value == params_value, f"{sum_value =} and {params_value =}" sum_row += f"| {str(sum_value):30} " # Print the final message @@ -253,9 +266,9 @@ def inter_comm(self): @property def clone_rank(self): """Get the rank of the process within its clone's sub_comm.""" - return self.sub_comm.Get_rank() + return self._clone_rank @property def clone_id(self): """Get the clone identifier.""" - return self.inter_comm.Get_rank() + return self._clone_id diff --git a/src/struphy/utils/test_clone_config.py b/src/struphy/utils/test_clone_config.py index 14b590a1e..b1c84139b 100644 --- a/src/struphy/utils/test_clone_config.py +++ b/src/struphy/utils/test_clone_config.py @@ -1,15 +1,20 @@ import pytest -from mpi4py import MPI +from psydac.ddm.mpi import MockComm +from psydac.ddm.mpi import mpi as MPI -@pytest.mark.mpi(min_size=2) @pytest.mark.parametrize("Nel", [[8, 9, 5], [7, 8, 9]]) @pytest.mark.parametrize("Np", [1000, 999]) @pytest.mark.parametrize("num_clones", [1, 2]) def test_clone_config(Nel, Np, num_clones): from struphy.utils.clone_config import CloneConfig - comm = MPI.COMM_WORLD + if isinstance(MPI.COMM_WORLD, MockComm): + comm = None + num_clones = 1 + else: + comm = MPI.COMM_WORLD + species = "ions" params = { "grid": { @@ -19,8 +24,8 @@ def test_clone_config(Nel, Np, num_clones): species: { "markers": { "Np": Np, - } - } + }, + }, }, } @@ -32,7 +37,7 @@ def test_clone_config(Nel, Np, num_clones): # Print outputs pconf.print_clone_config() pconf.print_particle_config() - print(f"{pconf.get_Np_clone(Np) = }") + print(f"{pconf.get_Np_clone(Np) =}") if __name__ == "__main__": diff --git a/src/struphy/utils/utils.py b/src/struphy/utils/utils.py index deed14a86..08b208f95 100644 --- a/src/struphy/utils/utils.py +++ b/src/struphy/utils/utils.py @@ -61,12 +61,12 @@ def save_state(state, libpath=STRUPHY_LIBPATH): def print_all_attr(obj): """Print all object's attributes that do not start with "_" to screen.""" - import numpy as np + import cunumpy as xp for k in dir(obj): if k[0] != "_": v = getattr(obj, k) - if isinstance(v, np.ndarray): + if isinstance(v, xp.ndarray): v = f"{type(getattr(obj, k))} of shape {v.shape}" if "proj_" in k or "quad_grid_" in k: v = "(arrays not displayed)" @@ -202,6 +202,6 @@ def subp_run(cmd, cwd="libpath", check=True): for k, val in state.items(): print(k, val) i_path, o_path, b_path = get_paths(state) - print(f"{i_path = }") - print(f"{o_path = }") - print(f"{b_path = }") + print(f"{i_path =}") + print(f"{o_path =}") + print(f"{b_path =}") From 99b41febca8627f39e13f891c578100862b2f929 Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Fri, 14 Nov 2025 10:01:16 +0100 Subject: [PATCH 3/4] Added test_gui.py and vibe_1.py --- test_gui.py | 293 ++++++++++++++++++++++++++++++++++++++++++ vibe_1.py | 356 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 649 insertions(+) create mode 100644 test_gui.py create mode 100644 vibe_1.py diff --git a/test_gui.py b/test_gui.py new file mode 100644 index 000000000..9602ce872 --- /dev/null +++ b/test_gui.py @@ -0,0 +1,293 @@ +import ast +import base64 +import inspect +import io +import os +import re +import tempfile +from typing import get_type_hints + +# from docutils.core import publish_parts +from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas +from nicegui import ui +from sympy import plot + +import struphy.models as models +from struphy.geometry import domains +from struphy.geometry.domains import Domain +from struphy.models.base import StruphyModel + +CARD_SETUP = "p-4 border-2 border-gray-400 rounded-lg shadow-md" +CARD_SETUP_NO_PAD = "border-2 border-gray-400 rounded-lg shadow-md" + +# Collect available domains +domain_dict = {} +for name, cls in domains.__dict__.items(): + if isinstance(cls, type) and issubclass(cls, Domain) and cls != Domain: + domain_dict[name] = cls +domain_names = list(domain_dict.keys()) + +model_dict = {} +for name, cls in models.__dict__.items(): + if isinstance(cls, type) and issubclass(cls, StruphyModel) and cls != StruphyModel: + model_dict[name] = cls +model_names = list(model_dict.keys()) + +# Globals +model_name = "Maxwell" +domain_name = "Cuboid" +matplotlib_ui = None +plotly_ui = None +param_inputs = {} # store input fields for parameters + + +def show_domain_interactive(): + global matplotlib_ui + global plotly_ui + + # Collect typed params + params = {} + for pname, (input_field, annotation) in param_inputs.items(): + value = input_field.value + # print(f"{pname}: {value} ({annotation})") + try: + if annotation is bool: + params[pname] = bool(value) + elif annotation is int: + params[pname] = int(value) + elif annotation is float: + params[pname] = float(value) + elif annotation is tuple: + params[pname] = t = ast.literal_eval(value) + else: + params[pname] = value # fallback to string + except Exception: + params[pname] = value # fallback if conversion fails + if params[pname] == "None": + params[pname] = None + # print(f"Running simulation with {params}") + # Create domain instance + domain: Domain = domain_dict[domain_name](**params) + + # fig_plotly = domain.show3D_interactive() + # fig_sidetopview = domain.show_plotly() + fig_domain = domain.show_combined_plotly() + # plotly_ui = None + + if plotly_ui is None: + with ( + ui.card() + .classes("p-0 border-2 border-gray-400 rounded-lg shadow-md") + .style("width: 90vw; max-width: 100vw; margin: 0;") + ): + # Plotly figure fills the card completely + plotly_ui = ui.plotly(fig_domain).classes("w-full h-[300px]; margin: 10") + else: + plotly_ui.figure = fig_domain + plotly_ui.update() + + +def show_domain(): + global matplotlib_ui + global plotly_ui + + # Collect typed params + params = {} + for pname, (input_field, annotation) in param_inputs.items(): + value = input_field.value + # print(f"{pname}: {value} ({annotation})") + try: + if annotation is bool: + params[pname] = bool(value) + elif annotation is int: + params[pname] = int(value) + elif annotation is float: + params[pname] = float(value) + elif annotation is tuple: + params[pname] = t = ast.literal_eval(value) + else: + params[pname] = value # fallback to string + except Exception: + params[pname] = value # fallback if conversion fails + if params[pname] == "None": + params[pname] = None + # print(f"Running simulation with {params}") + # Create domain instance + domain: Domain = domain_dict[domain_name](**params) + + fig_matplotlib, ax = domain.show() + + if matplotlib_ui is None: + with ui.card().classes(CARD_SETUP + " w-full h-full"): + ui.label("Simulation domain") + matplotlib_ui = ui.matplotlib(figsize=(12, 6)) + + # Always redraw the figure + fig_matplotlib = matplotlib_ui.figure + fig_matplotlib.clear() + domain.show(fig=fig_matplotlib) + matplotlib_ui.update() + + +def update_domain(value): + """Update selected domain and refresh parameter UI""" + global domain_name, param_inputs + domain_name = value + + # Clear old parameter inputs + param_container.clear() + param_inputs = {} + + # Introspect constructor parameters + type hints + cls = domain_dict[domain_name] + sig = inspect.signature(cls.__init__) + hints = get_type_hints(cls.__init__) + + for pname, param in sig.parameters.items(): + if pname == "self": + continue + + # Determine default value + default = "" if param.default is inspect.Parameter.empty else param.default + + # Get type hint (if any) + annotation = hints.get(pname, str) # fallback to str + + # Choose input widget depending on type + with param_container: + if annotation is bool: + inp = ui.checkbox(pname, value=bool(default)) + elif annotation in (int, float): + inp = ui.number(label=pname, value=default if default != "" else 0) + else: + inp = ui.input(label=pname, value=str(default)) + + # store field and annotation + param_inputs[pname] = (inp, annotation) + + +def update_model(value): + """Update selected domain and refresh parameter UI""" + global model_name + model_name = value + + # Clear old parameter inputs + model_container.clear() + param_inputs = {} + + # Introspect constructor parameters + type hints + cls = model_dict[model_name] + sig = inspect.signature(cls.__init__) + hints = get_type_hints(cls.__init__) + + with model_container: + ui.add_css(""" + .nicegui-markdown a { + color: orange; + text-decoration: none; + } + .nicegui-markdown a:hover { + color: red; + text-decoration: underline; + } + """) + + doc = cls.__doc__ + # print(repr(doc)) + doc = doc.replace(":ref:`normalization`:", "Normalization:") + doc = doc.replace(":ref:`Equations `:", "Equations:") + doc = doc.replace(":ref:`propagators` (called in sequence):", "Propagators:") + + # save docstring to replace lines + tmp_path = os.path.join(os.getcwd(), "tmp.txt") + try: + file = open(tmp_path, "x") + except FileExistsError: + file = open(tmp_path, "w") + file.write(doc) + file.close() + + with open(tmp_path, "r") as file: + doc = r"" + for line in file: + if ":class:`~" in line: + # print(repr(line)) + s1 = line.split("~")[1] + # print(repr(s1)) + s2 = s1.split("`")[0] + # print(repr(s2)) + s3 = s2.removeprefix("struphy.propagators.") + # print(repr(s3)) + if "_fields" in s2: + doc += f"""`{s3} `_ + """ + elif "_markers" in s2: + doc += f"""`{s3} `_ + """ + elif "_coupling" in s2: + doc += f"""`{s3} ` + """ + else: + doc += line + + with ui.card().classes("p-6"): + ui.restructured_text(doc) + + # # Replace refs and classes + # doc = re.sub(r':ref:`([^`]+)`', r'**\1**', doc) + # doc = re.sub(r':class:`([^`]+)`', r'`\1`', doc) + + # # Replace math directive with LaTeX blocks + # doc = re.sub(r'\.\. math::\n\n(.*?)\n\n', r'$\1$\n\n', doc, flags=re.S) + + # # Print plain docstring + # ui.markdown(doc, extras=["latex"]) + + # html_parts = publish_parts(doc, writer_name='html') + # html = html_parts['body'] + + +with ui.tabs().classes("w-full") as tabs: + domain_tab = ui.tab("Domain") + model_tab = ui.tab("Model") + +with ui.tab_panels(tabs, value=domain_tab).classes("w-full"): + with ui.tab_panel(domain_tab): + # UI layout + with ui.card().classes(CARD_SETUP): + with ui.row(): + # ui.label("Select a domain:") + ui.select( + domain_names, + value=domain_name, + on_change=lambda e: update_domain(e.value), + ) + + with ui.card().classes(CARD_SETUP): + param_container = ui.row() # container for parameter fields + + # Initialize with default domain + update_domain(domain_name) + + with ui.row(): # .classes("justify-center"): + ui.button("Show domain", on_click=show_domain) + ui.button("Show domain (interactive)", on_click=show_domain_interactive) + + with ui.tab_panel(model_tab): + # ui.label(f'Model tab') + with ui.card().classes(CARD_SETUP): + with ui.row(): + # ui.label("Select a domain:") + ui.select( + model_names, + value=model_name, + on_change=lambda e: update_model(e.value), + ) + + with ui.card().classes(CARD_SETUP): + model_container = ui.row() # container for parameter fields + + # Initialize with default domain + update_model(model_name) + +ui.run() diff --git a/vibe_1.py b/vibe_1.py new file mode 100644 index 000000000..a25970044 --- /dev/null +++ b/vibe_1.py @@ -0,0 +1,356 @@ +import ast +import base64 +import inspect +import io +import os +import re +import tempfile +from typing import get_type_hints + +# from docutils.core import publish_parts +from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas +from nicegui import ui + +import struphy.models.toy as toymodels +from struphy.geometry import domains +from struphy.geometry.domains import Domain +from struphy.models.base import StruphyModel + +CARD_SETUP = "p-4 border-2 border-gray-400 rounded-lg shadow-md" + +# Collect available domains +code_names = ["Struphy", "DESC", "GVEC", "Tokamaker"] + +domain_dict = {} +for name, cls in domains.__dict__.items(): + if isinstance(cls, type) and issubclass(cls, Domain) and cls != Domain: + domain_dict[name] = cls +domain_names = list(domain_dict.keys()) + +model_dict = {} +for name, cls in toymodels.__dict__.items(): + if isinstance(cls, type) and issubclass(cls, StruphyModel) and cls != StruphyModel: + model_dict[name] = cls +model_names = list(model_dict.keys()) + +# Globals +code_name = "Struphy" +model_name = "Maxwell" +domain_name = "Tokamak" +matplotlib_ui = None +param_inputs = {} # store input fields for parameters + + +def run_simulation(): + global matplotlib_ui + + # Collect typed params + params = {} + for pname, (input_field, annotation) in param_inputs.items(): + value = input_field.value + print(f"{pname}: {value} ({annotation})") + try: + if annotation is bool: + params[pname] = bool(value) + elif annotation is int: + params[pname] = int(value) + elif annotation is float: + params[pname] = float(value) + elif annotation is tuple: + params[pname] = t = ast.literal_eval(value) + else: + params[pname] = value # fallback to string + except Exception: + params[pname] = value # fallback if conversion fails + if params[pname] == "None": + params[pname] = None + # print(f"Running simulation with {params}") + # Create domain instance + domain: Domain = domain_dict[domain_name](**params) + + if matplotlib_ui is None: + # Create card + matplotlib once + with ui.card().classes(CARD_SETUP): + ui.label("Simulation domain") + matplotlib_ui = ui.matplotlib(figsize=(12, 6)) + + # Always redraw the figure + fig = matplotlib_ui.figure + fig.clear() + domain.show(fig=fig) + matplotlib_ui.update() + + +def update_code(value, code_container): + global code_name + code_name = value + + # Clear old parameter inputs + code_container.clear() + + rst = r""" +Tl;dr +===== + +| **Struphy provides easy access to partial differential equations (PDEs) in plasma physics.** +| The package combines *performance* (for HPC), *flexibility* (physics features) and *usability* (documentation). + +*Performance* in Struphy is achieved using three building blocks: + +* `numpy `_ (vectorization) +* `mpi4py `_ (parallelization) +* `pyccel `_ (compilation) + +| Heavy computational kernels are pre-compiled using the Python accelerator `pyccel `_, +| which on average shows `better performance `_ than *Pythran* or *Numba*. + + +| *Flexibility* comes through the possibility of applying different *models* to a plasma physics problem. +| Each model can be run on different *geometries* and can load a variety of *MHD equilibria*. + + +| *Usability* is guaranteed by Struphy's intuitive Python API. +| Moreover, an extensive, maintained documentation is provided. +| In addition, you can learn Struphy through a series of Jupyter notebook `tutorials `_. + """ + + with code_container: + with ui.card().classes("p-6"): + ui.restructured_text(rst) + + +def update_domain(value, param_container): + """Update selected domain and refresh parameter UI""" + global domain_name, param_inputs + domain_name = value + + # Clear old parameter inputs + param_container.clear() + param_inputs = {} + + # Introspect constructor parameters + type hints + cls = domain_dict[domain_name] + sig = inspect.signature(cls.__init__) + hints = get_type_hints(cls.__init__) + + for pname, param in sig.parameters.items(): + if pname == "self": + continue + + # Determine default value + default = "" if param.default is inspect.Parameter.empty else param.default + + # Get type hint (if any) + annotation = hints.get(pname, str) # fallback to str + + # Choose input widget depending on type + with param_container: + if annotation is bool: + inp = ui.checkbox(pname, value=bool(default)) + elif annotation in (int, float): + inp = ui.number(label=pname, value=default if default != "" else 0) + else: + inp = ui.input(label=pname, value=str(default)) + + # store field and annotation + param_inputs[pname] = (inp, annotation) + + +def update_model(value, model_container): + """Update selected domain and refresh parameter UI""" + global model_name + model_name = value + + # Clear old parameter inputs + model_container.clear() + param_inputs = {} + + # Introspect constructor parameters + type hints + cls = model_dict[model_name] + sig = inspect.signature(cls.__init__) + hints = get_type_hints(cls.__init__) + + with model_container: + ui.add_css(""" + .nicegui-markdown a { + color: orange; + text-decoration: none; + } + .nicegui-markdown a:hover { + color: red; + text-decoration: underline; + } + """) + + doc = cls.__doc__ + # print(repr(doc)) + doc = doc.replace(":ref:`normalization`:", "Normalization:") + doc = doc.replace(":ref:`Equations `:", "Equations:") + doc = doc.replace(":ref:`propagators` (called in sequence):", "Propagators:") + + # save docstring to replace lines + tmp_path = os.path.join(os.getcwd(), "tmp.txt") + try: + file = open(tmp_path, "x") + except FileExistsError: + file = open(tmp_path, "w") + file.write(doc) + file.close() + + with open(tmp_path, "r") as file: + doc = r"" + for line in file: + if ":class:`~" in line: + # print(repr(line)) + s1 = line.split("~")[1] + # print(repr(s1)) + s2 = s1.split("`")[0] + # print(repr(s2)) + s3 = s2.removeprefix("struphy.propagators.") + # print(repr(s3)) + if "_fields" in s2: + doc += f"""`{s3} `_ + """ + elif "_markers" in s2: + doc += f"""`{s3} `_ + """ + elif "_coupling" in s2: + doc += f"""`{s3} ` + """ + else: + doc += line + + with ui.card().classes("p-6"): + ui.restructured_text(doc) + + # # Replace refs and classes + # doc = re.sub(r':ref:`([^`]+)`', r'**\1**', doc) + # doc = re.sub(r':class:`([^`]+)`', r'`\1`', doc) + + # # Replace math directive with LaTeX blocks + # doc = re.sub(r'\.\. math::\n\n(.*?)\n\n', r'$\1$\n\n', doc, flags=re.S) + + # # Print plain docstring + # ui.markdown(doc, extras=["latex"]) + + # html_parts = publish_parts(doc, writer_name='html') + # html = html_parts['body'] + + +# --- Page 1: Simulation Center --- + + +@ui.page("/") +def cockpit(): + # 1. Header: Simulation Center + with ui.header().classes("items-center justify-between"): + ui.label("Stellignite - Cockpit").classes("text-3xl font-bold") + + # Main content area: Tabs and Panels + with ui.row().classes("w-full"): + # 2. Left Column: Tabs + with ui.column(): + # The tabs container itself + with ui.tabs().props("vertical").classes("w-full") as tabs: + code = ui.tab("Code") + model = ui.tab("Model") + geometry = ui.tab("Geometry") + env = ui.tab("Environment") + + # 3. Right Panel: Content Display + with ui.column(): + # The tab panels container + with ui.tab_panels(tabs, value=code).classes("w-full h-full p-4 border rounded-lg bg-white shadow"): + with ui.tab_panel(code): + # 3. Panel Content: "Hello World" + with ui.row(): + with ui.column(): + with ui.card().classes(CARD_SETUP): + with ui.row(): + # ui.label("Select a domain:") + ui.select( + code_names, + value=code_name, + on_change=lambda e: update_code(e.value, code_container), + ) + + with ui.column(): + with ui.card().classes(CARD_SETUP): + code_container = ui.row() # container for parameter fields + + # Initialize with default domain + update_code(code_name, code_container) + with ui.tab_panel(model): + # 3. Panel Content: "Hello World" + with ui.row(): + with ui.column(): + with ui.card().classes(CARD_SETUP): + with ui.row(): + # ui.label("Select a domain:") + ui.select( + model_names, + value=model_name, + on_change=lambda e: update_model(e.value, model_container), + ) + + with ui.column(): + with ui.card().classes(CARD_SETUP): + model_container = ui.row() # container for parameter fields + + # Initialize with default domain + update_model(model_name, model_container) + with ui.tab_panel(geometry): + with ui.row(): + with ui.column(): + # UI layout + with ui.card().classes(CARD_SETUP): + with ui.row(): + # ui.label("Select a domain:") + ui.select( + domain_names, + value=domain_name, + on_change=lambda e: update_domain(e.value), + ) + with ui.column(): + with ui.card().classes(CARD_SETUP): + param_container = ui.column() # container for parameter fields + + # Initialize with default domain + update_domain(domain_name, param_container) + + with ui.column(): # .classes("justify-center"): + ui.button("Show domain", on_click=run_simulation) + + # 4. Bottom Right Corner: Checkout Button + with ui.footer(fixed=False): + with ui.row().classes("w-full justify-end"): + # Button directs to the checkout page + ui.button("Checkout", on_click=lambda: ui.navigate.to("/checkout")).classes("text-lg px-8 py-2").props( + "color=green-600" + ) + + +# --- Page 2: Checkout Page --- + + +@ui.page("/checkout") +def checkout_page(): + # Header: Checkout (same as button name) + with ui.header().classes("items-center justify-start"): + ui.button("Back to Cockpit", on_click=lambda: ui.navigate.to("/")) + ui.label("Checkout").classes("text-3xl font-bold ml-4") + + # # Main content area: Text elements with tab names + # with ui.column().classes('w-full max-w-xl mx-auto p-6'): + # ui.label('Contents from Simulation Center Tabs:').classes('text-2xl font-semibold mb-4') + + # # Display text elements with the same names as the left column tabs + # for name in tab_names: + # ui.label(name).classes('text-lg font-medium p-2 border-b border-gray-300') + + # ui.label('Review and confirm your settings.').classes('mt-6 text-gray-600 italic') + + +# --- Run the App --- +if __name__ in {"__main__", "__mp_main__"}: + ui.run() From d3d7e53c2559dc603304478ccff289276712dd65 Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Fri, 14 Nov 2025 10:02:15 +0100 Subject: [PATCH 4/4] Removed files not existing in devel --- .../psydac-2.4.5.dev0-py3-none-any.whl | Bin 240986 -> 0 bytes src/struphy/utils/set_release_dependencies.py | 76 ------------------ 2 files changed, 76 deletions(-) delete mode 100644 src/struphy/psydac-2.4.5.dev0-py3-none-any.whl delete mode 100644 src/struphy/utils/set_release_dependencies.py diff --git a/src/struphy/psydac-2.4.5.dev0-py3-none-any.whl b/src/struphy/psydac-2.4.5.dev0-py3-none-any.whl deleted file mode 100644 index 71dcd07a078831413d1045ef2bc999ff82e392eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240986 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

    U3#tHNA=O_> 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

  • 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\s*([\w\.\-]+)", entry) - if match: - op, version = match.groups() - return f"{op}{version}" - return None - - -def get_max_bound(entry): - match = re.search(r"(<=|<)\s*([\w\.\-]+)", entry) - if match: - op, version = match.groups() - return f"{op}{version}" - return None - - -def get_package_name(entry): - return re.split(r"[<>=~]", entry.strip())[0].replace(" ", "") - - -def generate_updated_entry(package_name, package_deps): - ver_def = package_name - # Always set max version to the currently installed version - ver_def += f"<={package_deps['installed']}" - - if package_deps["min"]: - ver_def += f", {package_deps['min']}" - return ver_def - - -def update_dependencies(dependencies): - for i, entry in enumerate(dependencies): - package_name = get_package_name(entry) - - try: - installed_version = importlib.metadata.version(package_name) - - package_deps = {"installed": installed_version, "min": get_min_bound(entry), "max": get_max_bound(entry)} - - if package_deps["installed"]: - dependencies[i] = generate_updated_entry(package_name, package_deps) - - except importlib.metadata.PackageNotFoundError: - print(f"Warning: {package_name} not installed, skipping...") - continue - - # Remove psydac from the dependencies - for i, entry in enumerate(dependencies): - if "psydac" in entry: - dependencies.pop(i) - - -def main(): - with open("pyproject.toml", "rb") as f: - pyproject_data = tomllib.load(f) - - mandatory_dependencies = pyproject_data["project"]["dependencies"] - optional_dependency_groups = pyproject_data["project"]["optional-dependencies"] - - update_dependencies(mandatory_dependencies) - for group_name, group_deps in optional_dependency_groups.items(): - update_dependencies(group_deps) - - with open("pyproject.toml", "wb") as f: - tomli_w.dump(pyproject_data, f) - - -if __name__ == "__main__": - main()