Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
1ad4cd6
CUDA version of the linear_vlasov_ampere accumulation
max-models Oct 7, 2026
7de7285
Fuse the pusher preamble into one CUDA kernel
max-models Oct 7, 2026
48c446b
cupy array fixes
max-models Oct 7, 2026
0352dd2
Compile the CUDA kernels in the setup stage (Simulation.compile_cuda_…
max-models Oct 7, 2026
8d1c824
Marker exchange between ranks on the device (CUDA-aware MPI)
max-models Oct 7, 2026
e693095
CUDA version of the vlasov_maxwell accumulation
max-models Oct 7, 2026
14510d2
MHD equilibria on the CuPy backend
max-models Oct 7, 2026
b51e11f
Hand-written CUDA vs code generation: port bstar_parallel_3form both …
max-models Oct 7, 2026
c55990f
Polar splines on the CuPy backend
max-models Oct 7, 2026
1979ea9
Mark every column of the test hole so accumulation kernels skip it
max-models Oct 7, 2026
1b5ddb1
Merge cuda-bugfixes (#709) into cuda-fused-pusher-preamble
max-models Oct 7, 2026
cff3b7f
Merge cuda-fused-pusher-preamble (#708) into compile-cuda-kernels-setup
max-models Oct 7, 2026
8d3ade3
Merge compile-cuda-kernels-setup (#711) into cuda-aware-marker-exchange
max-models Oct 7, 2026
c414599
Merge cuda-aware-marker-exchange (#712) into cuda-linear-vlasov-ampere
max-models Oct 7, 2026
d7d3f2a
Merge cuda-linear-vlasov-ampere (#705) into cuda-vlasov-maxwell
max-models Oct 7, 2026
9c368bb
Merge cuda-vlasov-maxwell (#713) into mhd-equilibria-cupy
max-models Oct 7, 2026
88853ab
Merge mhd-equilibria-cupy (#714) into cuda-codegen-spike
max-models Oct 7, 2026
2a58677
Merge cuda-codegen-spike (#718) into polar-splines-cupy
max-models Oct 7, 2026
256d02d
formatting
max-models Oct 8, 2026
f2e8676
Marker exchange between ranks on the device (CUDA-aware MPI)
max-models Oct 7, 2026
1636822
CUDA version of the linear_vlasov_ampere accumulation
max-models Oct 7, 2026
9da31a6
Mark every column of the test hole so accumulation kernels skip it
max-models Oct 7, 2026
144e362
Merge cuda-linear-vlasov-ampere (#705) into cuda-vlasov-maxwell
max-models Oct 8, 2026
2e92d25
Merge cuda-vlasov-maxwell (#713) into mhd-equilibria-cupy
max-models Oct 8, 2026
b319210
Merge mhd-equilibria-cupy (#714) into cuda-codegen-spike
max-models Oct 8, 2026
02cde3d
Merge cuda-codegen-spike (#718) into polar-splines-cupy
max-models Oct 8, 2026
e27878e
Merge branch 'devel' into cuda-bugfixes
max-models Oct 8, 2026
047c20b
Merge branch 'cuda-bugfixes' into cuda-fused-pusher-preamble
max-models Oct 8, 2026
5226e01
Merge branch 'cuda-fused-pusher-preamble' into compile-cuda-kernels-s…
max-models Oct 8, 2026
fb49941
Merge branch 'compile-cuda-kernels-setup' into cuda-aware-marker-exch…
max-models Oct 8, 2026
18ac9cc
Merge branch 'cuda-linear-vlasov-ampere' into cuda-aware-marker-exchange
max-models Oct 8, 2026
88386b7
Merge branch 'cuda-linear-vlasov-ampere' into cuda-vlasov-maxwell
max-models Oct 8, 2026
2e30c40
Merge branch 'cuda-vlasov-maxwell' into mhd-equilibria-cupy
max-models Oct 8, 2026
024461e
Merge branch 'mhd-equilibria-cupy' into cuda-codegen-spike
max-models Oct 8, 2026
1848f6d
Merge branch 'cuda-codegen-spike' into polar-splines-cupy
max-models Oct 8, 2026
b11ee18
GVEC: marker evaluation, cunumpy 0.6.2 host helpers, robust fake-CuPy…
max-models Oct 8, 2026
b682bc8
Merge mhd-equilibria-cupy (#714) into cuda-codegen-spike
max-models Oct 8, 2026
0e6850e
Merge cuda-codegen-spike (#718) into polar-splines-cupy
max-models Oct 8, 2026
7d18d8c
Remove the numba code-generation prototype (#690 decided: hand-writte…
max-models Oct 9, 2026
4bd43af
Merge cuda-codegen-spike into polar-splines-cupy
max-models Oct 9, 2026
1eb47d3
Merge remote-tracking branch 'origin/devel' into cuda-bugfixes
max-models Oct 9, 2026
3370372
Merge cuda-bugfixes into cuda-fused-pusher-preamble
max-models Oct 9, 2026
0e74e15
Rename first_init_idx to first_pusher_idx
max-models Oct 9, 2026
f4be957
Merge parent into compile-cuda-kernels-setup
max-models Oct 9, 2026
7ed75a6
Merge parent into cuda-linear-vlasov-ampere
max-models Oct 9, 2026
72b33d3
Merge parent into cuda-aware-marker-exchange
max-models Oct 9, 2026
7e92296
Rename first_init_idx to first_pusher_idx in the code new on this branch
max-models Oct 9, 2026
d269195
Merge parent into mhd-equilibria-cupy
max-models Oct 9, 2026
12bf231
Merge parent into cuda-vlasov-maxwell
max-models Oct 9, 2026
8223abf
Skip the new GVEC tests by default (with_gvec=False), like the other …
max-models Oct 9, 2026
49cb3c4
Merge parent into cuda-codegen-spike
max-models Oct 9, 2026
703a947
Rename first_init_idx to first_pusher_idx in the code new on this branch
max-models Oct 9, 2026
ce5affe
Merge parent into polar-splines-cupy
max-models Oct 9, 2026
05bc8d9
Use xp.mpi.exchange
max-models Oct 9, 2026
b5a285a
Merge branch 'cuda-aware-marker-exchange' into cuda-linear-vlasov-ampere
max-models Oct 9, 2026
b5ab891
Merge branch 'cuda-linear-vlasov-ampere' into cuda-vlasov-maxwell
max-models Oct 9, 2026
39e0e8d
Merge branch 'cuda-vlasov-maxwell' into mhd-equilibria-cupy
max-models Oct 9, 2026
a05dcf5
Merge branch 'mhd-equilibria-cupy' into cuda-codegen-spike
max-models Oct 9, 2026
06d7833
Merge branch 'cuda-codegen-spike' into polar-splines-cupy
max-models Oct 9, 2026
028eed6
Merge devel into cuda-bugfixes
max-models Oct 9, 2026
a6e1b19
Fuse the pusher preamble into one CUDA kernel
max-models Oct 7, 2026
c1e299b
Rename first_init_idx to first_pusher_idx
max-models Oct 9, 2026
c3ea640
Compile the CUDA kernels in the setup stage (Simulation.compile_cuda_…
max-models Oct 7, 2026
958c013
formatting
max-models Oct 8, 2026
08665fe
Marker exchange between ranks on the device (CUDA-aware MPI)
max-models Oct 7, 2026
d4abdf6
Use xp.mpi.exchange
max-models Oct 9, 2026
22ed9e0
CUDA version of the linear_vlasov_ampere accumulation
max-models Oct 7, 2026
6dd36db
Mark every column of the test hole so accumulation kernels skip it
max-models Oct 7, 2026
52bc2b9
Rename first_init_idx to first_pusher_idx in the code new on this branch
max-models Oct 9, 2026
8612c67
Merge cuda-linear-vlasov-ampere into cuda-vlasov-maxwell
max-models Oct 10, 2026
dcedf86
Merge cuda-vlasov-maxwell into mhd-equilibria-cupy
max-models Oct 10, 2026
218885a
Merge mhd-equilibria-cupy into cuda-codegen-spike
max-models Oct 10, 2026
faec1b0
Merge cuda-codegen-spike into polar-splines-cupy
max-models Oct 10, 2026
1cad34a
Evaluate the spline-based equilibria on the device; batched field-lin…
max-models Oct 10, 2026
f5079de
Merge mhd-equilibria-cupy into cuda-codegen-spike
max-models Oct 10, 2026
440827f
Merge cuda-codegen-spike into polar-splines-cupy
max-models Oct 10, 2026
70557fb
Pin cunumpy to the commit with copyable fake cupyx splines (cunumpy#97)
max-models Oct 10, 2026
7818b8f
Merge mhd-equilibria-cupy into cuda-codegen-spike
max-models Oct 10, 2026
85b6a89
Merge cuda-codegen-spike into polar-splines-cupy
max-models Oct 10, 2026
822af64
Pin cunumpy to devel with copyable fake cupyx splines (cunumpy#97 mer…
max-models Oct 10, 2026
ffe1485
Merge mhd-equilibria-cupy into cuda-codegen-spike
max-models Oct 10, 2026
13f8b87
Merge cuda-codegen-spike into polar-splines-cupy
max-models Oct 10, 2026
dce63a9
Pin cunumpy to devel (f996393: copyable fake cupyx splines, faster ty…
max-models Oct 10, 2026
8ebb29d
Merge mhd-equilibria-cupy into cuda-codegen-spike
max-models Oct 10, 2026
645b8f8
Merge cuda-codegen-spike into polar-splines-cupy
max-models Oct 10, 2026
8fcb59f
cupy array fixes
max-models Oct 7, 2026
8d8603a
Fuse the pusher preamble into one CUDA kernel
max-models Oct 7, 2026
30d81a1
Rename first_init_idx to first_pusher_idx
max-models Oct 9, 2026
225bcae
Compile the CUDA kernels in the setup stage (Simulation.compile_cuda_…
max-models Oct 7, 2026
d412fc2
formatting
max-models Oct 8, 2026
77a25b5
Marker exchange between ranks on the device (CUDA-aware MPI)
max-models Oct 7, 2026
0ea9085
Use xp.mpi.exchange
max-models Oct 9, 2026
569904f
CUDA version of the linear_vlasov_ampere accumulation
max-models Oct 7, 2026
0d15e06
Mark every column of the test hole so accumulation kernels skip it
max-models Oct 7, 2026
0b6fc99
Rename first_init_idx to first_pusher_idx in the code new on this branch
max-models Oct 9, 2026
e46b01e
Merge branch 'cuda-linear-vlasov-ampere' into cuda-vlasov-maxwell
max-models Oct 12, 2026
6ca659b
Merge branch 'cuda-vlasov-maxwell' into mhd-equilibria-cupy
max-models Oct 12, 2026
707b48d
Merge branch 'mhd-equilibria-cupy' into cuda-codegen-spike
max-models Oct 12, 2026
4e9f2a2
Merge branch 'cuda-codegen-spike' into polar-splines-cupy
max-models Oct 12, 2026
85ad5e0
Formatting
max-models Oct 12, 2026
5825e1f
Fix unit test
max-models Oct 12, 2026
46d7451
Merge branch 'devel' into polar-splines-cupy
max-models Oct 12, 2026
1248722
ruff check --fix
max-models Oct 12, 2026
71818ca
Complete polar spline GPU support after the Kronecker refactor
max-models Oct 12, 2026
72954b1
Update strategy
max-models Oct 12, 2026
b80b20f
update docs
max-models Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/test-PR-unit-mpi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,9 @@ jobs:
source /struphy_fortran_/env_fortran_/bin/activate
cd ${{ env.STRUPHY_PATH }}
mpirun --oversubscribe -n ${{ matrix.n-procs }} env CUNUMPY_FAKE_CUPY=1 pytest -v --with-mpi pic/tests/test_sorting_device.py

- name: Run polar GPU MPI paths on the fake CuPy
run: |
source /struphy_fortran_/env_fortran_/bin/activate
cd ${{ env.STRUPHY_PATH }}
mpirun --oversubscribe -n ${{ matrix.n-procs }} env CUNUMPY_FAKE_CUPY=1 pytest -v --with-mpi polar/tests/test_polar_cupy.py -k mpi_fake
67 changes: 53 additions & 14 deletions CUDA_STRATEGY.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions doc/sections/subsections/numerics-geomFE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,12 @@ For weighted mass matrices, use :class:`~struphy.feec.preconditioner.PolarMassMa
(``precond="PolarMassMatrixPreconditioner"`` in the propagator options);
it is exact for axisymmetric weights and mappings.

On the CuPy backend, extraction, derivatives, Kronecker operators and these solves keep their
coefficients on the device. Factorization and mass-preconditioner assembly run once on the host;
cached device factors apply subsequent solves. Polar MPI collectives support CUDA-aware device
buffers or host staging through ``cunumpy.mpi.mpi_buffer``. The feectools halo exchanges require
CUDA-aware MPI on real GPUs. ``SplineFunction.__call__`` dispatches CUDA kernels for marker
and dense/sparse grid evaluation. The fixed local-grid helper ``eval_tp_fixed_loc`` remains
host-only; distributed field-evaluation MPI handling and coverage are separate follow-ups.

.. image:: ../../pics/polar_derham.png
2 changes: 1 addition & 1 deletion src/struphy/feec/preconditioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ def _polar_mass_groups(mass_operator: WeightedMassOperator, reduction: str) -> l
A_pol = (E @ M12 @ E.T).tocsr()

# unweighted 1d mass matrix in eta_3
A_3 = _mass_matrix_1d(mass_operator, bases[comps[0]][2], 2, _ones_1d)[0].toarray()
A_3 = xp.to_numpy(_mass_matrix_1d(mass_operator, bases[comps[0]][2], 2, _ones_1d)[0].toarray())

groups.append(Kron2Dx1DGroup(comps, A_pol, A_3))
return groups
Expand Down
2 changes: 1 addition & 1 deletion src/struphy/feec/projectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ def solve(self, rhs, transposed=False, apply_bc=False, out=None, x0=None):
krylov = self._polar_krylov[key]

# relative tolerance w.r.t. the norm of the right-hand side
rhs_norm = xp.sqrt(rhs.inner(rhs))
rhs_norm = float(xp.sqrt(rhs.inner(rhs)))
krylov.set_options(x0=x0, tol=self._polar_rtol * rhs_norm if rhs_norm > 0.0 else self._polar_rtol)
x = krylov.solve(rhs, out=out)

Expand Down
15 changes: 5 additions & 10 deletions src/struphy/feec/psydac_derham.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,12 +619,6 @@ def __init__(
raise NotImplementedError(
"Local projectors (DerhamOptions.local_projectors=True) are not supported on the CuPy backend yet."
)
if polar_splines and (xp.get_backend() == "cupy"):
# PolarExtractionBlocksC1 builds SciPy sparse matrices from the control points, and the polar
# extraction operators apply them to the stencil data, which lives on the device on CuPy.
raise NotImplementedError(
"Polar splines (DerhamOptions.polar_splines=True) are not supported on the CuPy backend yet."
)

# number of elements and spline degrees in each direction
assert len(num_elements) == 3
Expand Down Expand Up @@ -2782,16 +2776,17 @@ def _restart_extraction_op(self):
n_comps = W.n_comps

# stack blocks of E (polar coeffs x polar rings); incompatible blocks (None) are zero
rows = xp.cumsum([0] + list(W.n_polar))
cols = xp.cumsum([0] + [n_r * n_2 for n_r, n_2 in zip(W.n_rings, W.n2)])
E_full = xp.zeros((rows[-1], cols[-1]), dtype=float)
# (host setup data on every backend, like the blocks of E)
rows = np.cumsum([0] + list(W.n_polar))
cols = np.cumsum([0] + [n_r * n_2 for n_r, n_2 in zip(W.n_rings, W.n2)])
E_full = np.zeros((rows[-1], cols[-1]), dtype=float)
for m in range(n_comps):
for n in range(n_comps):
if E.blocks_ten_to_pol[m][n] is not None:
E_full[rows[m] : rows[m + 1], cols[n] : cols[n + 1]] = E.blocks_ten_to_pol[m][n].toarray()

# E^T has full column rank, hence pinv(E^T) @ E^T = identity on polar coeffs
L_full = xp.linalg.pinv(E_full.T)
L_full = np.linalg.pinv(E_full.T)

blocks = [
[
Expand Down
17 changes: 0 additions & 17 deletions src/struphy/feec/tests/test_derham_gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,3 @@ def test_local_projectors_not_supported_on_cupy():
"""Local projectors have no device implementation yet; the Derham fails when it is created."""
with cunumpy.use_backend("cupy"), pytest.raises(NotImplementedError, match="Local projectors"):
make_derham(local_projectors=True)


@requires_cupy
def test_polar_splines_not_supported_on_cupy():
"""Polar splines have no device implementation yet; the Derham fails when it is created."""
from maybempi import MPI

from struphy import domains
from struphy.feec.psydac_derham import Derham
from struphy.io.options import DerhamOptions
from struphy.topology.grids import TensorProductGrid

with cunumpy.use_backend("cupy"):
domain = domains.IGAPolarCylinder(num_elements=(8, 6), degree=(2, 3))
options = DerhamOptions(degree=(2, 3, 1), bcs=(("dirichlet", "free"), None, None), polar_splines=True)
with pytest.raises(NotImplementedError, match="Polar splines"):
Derham(TensorProductGrid(num_elements=(8, 6, 4)), options, comm=MPI.COMM_WORLD, domain=domain)
14 changes: 8 additions & 6 deletions src/struphy/polar/basic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING

import cunumpy as xp
from cunumpy.mpi import mpi_buffer
from feectools.linalg.basic import Vector, VectorSpace
from feectools.linalg.block import BlockVector
from feectools.linalg.stencil import StencilVector
Expand Down Expand Up @@ -346,8 +347,9 @@ def toarray(self, allreduce=False):
out = self.tp.toarray()[self.space.n_rings[0] * self.space.n2[0] * self.space.n3[0] :]

# allreduce tensor-product part
if self.space.comm is not None and allreduce:
self.space.comm.Allreduce(MPI.IN_PLACE, out, op=MPI.SUM)
if self.space.comm is not None and self.space.comm.Get_size() > 1 and allreduce:
with mpi_buffer(out, recv=True) as buf:
self.space.comm.Allreduce(MPI.IN_PLACE, buf, op=MPI.SUM)

out = xp.concatenate((self.pol[0].flatten(), out))

Expand All @@ -357,10 +359,10 @@ def toarray(self, allreduce=False):
out3 = self.tp[2].toarray()[self.space.n_rings[2] * self.space.n2[2] * self.space.n3[2] :]

# allreduce tensor-product part
if self.space.comm is not None and allreduce:
self.space.comm.Allreduce(MPI.IN_PLACE, out1, op=MPI.SUM)
self.space.comm.Allreduce(MPI.IN_PLACE, out2, op=MPI.SUM)
self.space.comm.Allreduce(MPI.IN_PLACE, out3, op=MPI.SUM)
if self.space.comm is not None and self.space.comm.Get_size() > 1 and allreduce:
for array in (out1, out2, out3):
with mpi_buffer(array, recv=True) as buf:
self.space.comm.Allreduce(MPI.IN_PLACE, buf, op=MPI.SUM)

out = xp.concatenate(
(
Expand Down
Loading
Loading