Repository navigation
v3.5.0
Changelog
Struphy 3.5.0 - 2026-10-21
Headlines
- Struphy on the feectools CUDA stack: the
feectoolssubmodule moves to the feectools CUDA stack, which runs on the CuPy backend with MPI on device buffers, one GPU per MPI rank, and CUDA kernels for the stencildot,transpose,innerandaxpy. Field solves of a whole model can now stay on the GPU, also on several ranks. #680 - Geometry evaluation on the GPU for all analytic mappings: CUDA versions of
f,df, the metric,pull/push/tranand the four geometry entry kernels for all analytic mappings, soDomain.__call__,jacobian_detand the transforms run on the device. This lifts the Cuboid-only restriction of the CUDA kernels; before, particle runs on the CuPy backend failed at setup. #681 - Spline mappings on the GPU:
IGAPolarCylinder,IGAPolarTorus,Tokamak, GVEC and DESC domains can be created on the CuPy backend (the one-time setup runs on the host), and the spline mappings are evaluated on the device, so every mapping now runs on the GPU. #683 - MHD equilibria on the CuPy backend: every equilibrium in
fields_background/equils.pycan be created and evaluated on the CuPy backend. SciPy-, gvec- and DESC-based setups and evaluations run on the host through the new helperssetup_on_host,host_callandevaluate_on_host; the time loop uses the projected equilibrium as before. #714 - Polar splines on the CuPy backend: polar extraction, derivatives, commuting projectors (
schur,lu,ilu) and mass preconditioners work on the GPU with device work arrays, cached device triangular solves and CUDA-aware MPI for the polar reductions. #720 - Kronecker stiffness preconditioner: new
StiffnessPreconditionerfor operators of the formd^T M d + sigma M, built on the new Kronecker machinery of feectools (factors on groups of axes,KroneckerSumSolverby fast diagonalization).MassMatrixPreconditionerandMassMatrixDiagonalPreconditionershare one refactored implementation, andMassMatrixPreconditionergets the optionweight_reduction="midpoint" | "average". #731 - CurlCurl model and curl-curl preconditioners: new model
CurlCurlfor(C^T M2 C + sigma M1) e = j, with the newHiptmairXuPreconditionerand a geometric multigrid with Hiptmair smoother (MultiGridOptions(hiptmair=True)). With the Jacobi smoother, the multigrid is the only option whose iteration count is robust insigmaon the torus. #744 - Struct-of-arrays marker buffer:
Particles.markersis stored with one column per marker, shape(n_cols, n_markers_max)and indexedmarkers[j, ip], so every marker quantity is one contiguous block on the CPU and on the GPU. #774 - Guidelines for AI coding agents: coding guidelines for changing Struphy (
AGENTS.mdin the repository, plus guides for adding models and propagators), and usage guidelines shipped with the package. The new commandstruphy agentsprints the usage guide, andstruphy agents --installinstalls thesetup-simulationandpostprocess-simulationskills into a user's project. #797
API changes
ImplicitDiffusion.OptionsandPoissonSolve.Optionsacceptprecond="MultiGrid"and a new fieldmultigrid: MultiGridOptionsfor the new geometric multigrid preconditioner (MPI, 3D semi-coarsening, operators rebuilt on the coarse grids). The oldmultigrid_solveris removed. #91Compiler()andCompiler.compile()take a new argumentdebug(defaultFalse), andstruphy compile --debugcalls pyccel with its--debugoption. #677MultiGridOptionsis exported fromstruphy.Output.evaluate()drops the time dimension whentis a single number, and theMultiGridSolverwrapper is removed in favour ofinverse(..., pc=...). #682- MPI is now handled by maybempi: the environment variable that overrides the serial/MPI decision is
MAYBEMPI=0/1(STRUPHY_MPIis no longer read), and the serial stand-in behaves like MPI on one rank instead of returningNone. #701 - The commands
struphy formatandstruphy lintare removed; useruff check --fixandruff formatdirectly. The options--linters,--iterationsand-yofstruphy build-init-filesare removed. #730 - The
precondoption ofImplicitDiffusion.Options,PoissonSolve.OptionsandPoissonAdiabaticGyrokinetic.Optionsnow reaches the solver (it was ignored before) and defaults toNone; the new optionprecond_paramspasses preconditioner settings, and"StiffnessPreconditioner"is a new choice. The defaultstab_matofPoissonSolve.Optionschanges from"Id"to"M0", and the defaultprecondofShearAlfvenCurrentCoupling5D.Options,VariationalResistivity.OptionsandVariationalViscosity.Optionschanges from"MassMatrixDiagonalPreconditioner"to"MassMatrixPreconditioner". #739 - New option
stab_averageinImplicitDiffusion.Options,PoissonSolve.OptionsandPoissonAdiabaticGyrokinetic.Optionssets the directions averaged in the stabilization (e.g."eta3"). With it,StiffnessPreconditionerworks for the adiabatic gyrokinetic Poisson operator, which is now symmetric. #740 - The propagator
PoissonAdiabaticGyrokineticis renamed toGyrokineticPoissonSolve(modulestruphy.propagators.gyrokinetic_poisson_solve), without an alias. ItsOptionsget amultigridfield, andprecond="MultiGrid"now works together withstab_average. #742 CurlCurlSolvenow solves(C^T M2 C + sigma M1) e = j(the mass term had a negative sign, which made the operator indefinite). Itsprecondoption, which was ignored, now selectsNone(default),"MassMatrixPreconditioner","StiffnessPreconditioner","HiptmairXu"or"MultiGrid", with new optionsprecond_paramsandmultigrid.FEECVariablegets the argumentprojection="commuting" | "L2",MultiGridOptionsthe fieldhiptmair, andTimeDependentSourceaccepts variables in every FEEC space. #744- Polar splines use 2D x 1D Kronecker solves for the commuting projectors and mass matrices, so building a polar
Derhamtakes 0.1 s instead of minutes. The new optionDerhamOptions.polar_poloidal_solver("schur"default,"lu","ilu") selects the poloidal solver. #747 - New
Simulation.compile_cuda_kernels(), called bySimulation.run()afterallocate(), compiles all CUDA kernels of the time loop before the first step and fails fast if one has no CUDA version;Simulation.kernels()lists them. #711 - New option
enforce_compatibilityinPoissonSolve.Options(defaultTrue),ImplicitDiffusion.OptionsandGyrokineticPoissonSolve.Options(defaultFalse). It removes the net charge from the right-hand side of singular (e.g. periodic) solves; before, the constant mode ofphigrew to(net charge) / stab_eps, which costE = -grad phimost of its digits in PIC runs. #737 - New
Output.standard_snapshots()(first, middle and last saved time step),Output.field_form()andOutput.field_basis(); the new argumentvtk_stepsofOutput.pproc()writes VTK files only for selected time steps. The PR also adds the ITPA TAE benchmark examples forLinearMHDandShearAlfven. #398 ShearAlfvenPropagator.Optionsacceptsprecond="MultiGrid"with a new fieldmultigrid(experimental, not the default). The PR also adds physics descriptions to all examples and corrects the growth rates in the two-stream and Weibel post-processing, which were used as rates of the energies instead of the amplitudes. #778- The marker buffer
Particles.markersis transposed to shape(n_cols, n_markers_max)and indexedmarkers[j, ip](wasmarkers[ip, j]);Particles.n_rowsis renamed ton_markers_max. Scripts that access the markers directly have to be adapted. #774 ShearAlfvenPropagator.Optionsacceptsprecond="StiffnessPreconditioner", a Kronecker preconditioner of the Schur operator, with the new optionexb_correction(defaultTrue) for the auxiliary space of the E x B flows. #781- New command
struphy agents(--path,--install [DIR]) for the agent usage guide and skills. #797 struphy.api.modelsnow also exportsCurlCurl,ToyDriftandIncompressibleNavierStokesSPH. #798GyrokineticPoissonSolve.Optionsacceptswhich_geometry=None(Boltzmann response for all modes, no flux-surface average), andImplicitDiffusion.Optionsacceptsstab_mat="M0ad_withT"anddiffusion_mat=None. #800
Physics models
- New model
GyrokineticPoissonfor the gyrokinetic Poisson equation with adiabatic electrons, built likePoisson, with profiling cases on a hollow cylinder and a hollow torus compared against an exact solution. #742 - New model
CurlCurl(TimeDependentSourcefollowed byCurlCurlSolve, likePoisson). Its source uses theL2projection, so that the gradient part of the projection error is not amplified by1/sigma. #744 - New models
ShearAlfvenCurlCurlandShearAlfvenCurlCurlwithProj(propagatorsShearAlfvenCurlCurlPropagatorandShearAlfvenCurlCurlwithProjPropagator): the shear Alfven equations with the magnetic field in H(curl), so that the implicit step is a curl-curl solve.ShearAlfvenCurlCurluses the Hiptmair-Xu preconditioner and has a small numerical damping;ShearAlfvenCurlCurlwithProjconserves the energy exactly. #780 - New model
VlasovPoissonAdiabatic(full-orbit ions, adiabatic electrons), the 6D counterpart ofDriftKineticElectrostaticAdiabatic, with the new propagatorAdiabaticPoissonSolveandslab_drift_waveexamples for both models. The PR also fixes the full-f Poisson background. #800 (renamed fromVlasovPoissonElectrostaticAdiabaticin #804) - New model
VlasovPoissonAdiabaticPauli: guiding-center ions computed with classical Pauli particles (Xiao and Qin, Comput. Phys. Commun. 265, 107981 (2021)), i.e. full-orbit markers with a magnetic moment (PauliParticles6D) pushed with time steps larger than the cyclotron period. It reuses the full-orbit pushers with the magnetic-moment force, adds the guiding-center polarization term to the quasi-neutrality equation (AdiabaticPoissonSolve.Options(polarization=True)), and comes withtokamak_orbitsandslab_drift_waveexamples. #805
Bug fixes
- Fix stale ghost regions after writes to a
PolarVector: thetpsetter andset_tp_rings_to_zerodid not mark the ghost regions out of sync, so a following stencildotread old ghost values. #771 - Polar splines: the commuting projector no longer applies the boundary operator with
apply_bc=False(wrong projections with Dirichlet BCs), the polar BiCGStab uses a relative tolerance, and the polar operators apply without allocations. #746 - Fix the total energy at t=0 in the Vlasov-Ampere/Maxwell models: the ghost regions of the initial electric field were not updated (wrong first push near periodic boundaries), and the control-variate weights were reset to full-f after the initial Poisson solve.738 https://github.com/struphy-hub/struphy/pull/
- Fix the setup of sorting boxes on the CuPy backend, which failed in
SortingBoxes._set_boxesand in theParticlesSPHsetup; the box sorting kernels move to kernel folders with an explicit host fallback on CuPy. #736
Internals
- CUDA strategy, part 6:
Particlescan be created on the CuPy backend, withcuda_args_markersfor the CUDA kernels. #651 struphy compile -j Nfailed at random because a kernel dependency imported as a function (from x_kernels import func) was not detected; it now is. #661- CUDA strategy, part 7:
Derhamcan be created on the CuPy backend; the data describing the spline spaces stays on the host, and only coefficients and stencil matrices live on the device. #652 - CUDA strategy, part 8: the kernel argument classes are passed to CUDA kernels as C structs defined once in a shared header, instead of long flat signatures. #653
LinOpWithTranspis removed; struphy operators subclass the feectoolsLinearOperatorand use its generictoarray()/tosparse(). #662- CUDA strategy, part 9: the pusher and evaluation kernels move to one folder per kernel (
pic/pushing/kernels/<name>/), so each can get its CUDA version next to it. Code that imported e.g.struphy.pic.pushing.pusher_kernelshas to import the kernel from its folder. #663 - CUDA strategy, part 10: device helpers for B-splines, the Cuboid Jacobian, 3x3 linear algebra and the kinetic boundary conditions. #665
- CUDA strategy, part 11: the first CUDA kernel,
push_eta_stage, for explicit Runge-Kutta position pushes. #666 - CUDA strategy, part 12: CUDA versions of
push_vxb_analyticandpush_vxb_implicit, and reflection and spline evaluation on the device, so theVlasovmodel runs on the GPU for Cuboid mappings. #667 - CUDA strategy, part 13: struphy's own kernel dispatch and argument classes are replaced by their cunumpy equivalents. #668
- CUDA strategy, part 14: the accumulation kernels move to kernel folders, and the first Vlasov-Ampere CUDA kernels (
push_v_with_efield,push_weights_with_efield_lin_va,charge_density_0form) are added, with CPU emulation tests of every CUDA kernel. #670 - CUDA strategy, part 15: each kernel folder declares its kernel with cunumpy's
Kernel.from_folder, and call sites import kernels instead of looking them up in catalogs. #671 - CUDA strategy, part 16: one pyccel and one CUDA argument class per argument type, chosen by the owner at creation. #679
- Refactor of the weight evaluation in
WeightedMassOperators.create_weighted_mass, and 2D mass-matrix tests that use less than 1 GB of memory. #684 - Move to cunumpy 0.6.1. #685
- Refactor of
WeightedMassOperator.__init__, which also fixes anAttributeErrorfor symmetric operators with spline weights.toarray()/tosparse()now include the extraction and boundary operators; the matrix-only versions areto_array_mat_only()/to_sparse_mat_only(). #702 - Update ruff to 0.16.10 with the previous lint rule set. #732
- feectools inner products stay on the device on the CuPy backend, so a CG iteration copies one scalar to the host instead of two or more. #716
- Fix two host/device array mix-ups in
SplineFunction.__init__andSortingBoxes._set_boxesthat stopped particle runs on the CuPy backend. #709 - Fuse the pusher preamble into one CUDA kernel, about 2x faster on the GPU. #708
- Marker exchange between MPI ranks on the device with CUDA-aware MPI (staged through host memory otherwise). #712
- CUDA version of the
linear_vlasov_ampereaccumulation (LinearVlasovAmpereOneSpecies). #705 - CUDA version of the
vlasov_maxwellaccumulation (VlasovAmpereOneSpecies,VlasovMaxwellOneSpecies). #713 - Hand-written CUDA port of the guiding-center kernel
bstar_parallel_3form; CUDA kernels are written by hand, not generated from the pyccel source. #718 - New argument classes
SplineArguments/CudaSplineArgumentsfor the spline evaluation kernels ofSplineFunction. #721 - Field evaluation, scalars, binning and saving of the diagnostics run on the device on the CuPy backend, with one host copy per dataset and output step. #724
- The FEEC assembly kernels move to one folder per kernel (
feec/kernels/<name>/), with a host fallback on CuPy. #725 - All pyccel kernels declare the arguments they write to (
outputs), so the CuPy fallback copies back only those. #723 - The CUDA device helpers live in C++ namespaces named after the pyccel modules they port. #722
- End-to-end GPU test of
VlasovAmpereOneSpecieson 1 and 2 ranks with a transfer budget for the time loop. It also fixes the ghost regions of the mid-point field inVlasovAmpereCouplingon every backend. #734 - New benchmark script
profiling/benchmarks/vlasov_benchmark.pyforVlasovAmpereOneSpeciesandVlasovMaxwellOneSpecieson the NumPy and CuPy backends. #735 - Polar spline factorizations are shared between the solvers of one
Derham(e.g. 88 sparse LU calls reduced to 9). #760 - The GitLab CI at MPCDF now runs only the GPU tests; all CPU tests, linting and docs run on GitHub Actions. #710
- Polar splines in
StiffnessPreconditioner(grad, curl) and instab_average(PolarWeightedAverageProjection), soGyrokineticPoissonruns with polar splines. It also fixes simulations on domains with a pole, which crashed on the renamed optionpolar_ck. #758 - Profiling examples comparing polar and tensor-product splines for
Poisson,CurlCurlandGyrokineticPoissonon a cylinder and a torus; fixes the post-processing of polar fields. #759 - Particles on domains with a pole: markers are pushed through the axis, a new axis boundary map handles markers that cross it, and
PushVxBand the guiding-center propagators work with polar splines. It also fixes the reflecting boundary forParticles5D, which overwrote the weights. #761 - Profiling examples with long-time particle equilibria for
VlasovandGuidingCenter(cuboid, and cylinder and torus without hole). #762 - Fix the OpenMP pragmas so that all kernels compile with
struphy compile --openmp, and compile the feectools kernels with OpenMP too. #757 - The profiling cases move from
profiling/examples/toexamples/<Model>/<case>/withparams_,pproc_andsubmit_files; the diocotron example moves toexamples/ToyDrift/. #768 - The time-dependent examples save their standard figures at the first, middle and last saved snapshot, and run small with
--test-run. #773 - Pushers and accumulations process only the marker slots that can hold markers (
Particles.n_markers_used), not the trailing holes of the buffer. #775 - Faster setup of the Hiptmair-Xu preconditioner (7.6 s to 3.0 s per rank in the TAE example), with an exact stiffness diagonal and the new hook
Propagator.prepare_time_step(dt), which builds preconditioners before the time loop. #796 DriftKineticElectrostaticAdiabaticsolves the Poisson equation between its two guiding-center pushers, so that with Strang splitting both pushes use a current potential. #799- The docs preview of PRs skips compiling Struphy and executing the notebooks; the quickstart gets its 2D figure. #802
- The repository
struphy-parameter-filesis renamed to struphy-examples, which is synced on every release; README and docs link it and the website. #801 - Verification of the TAE frequency of
ShearAlfven,ShearAlfvenCurlCurlandShearAlfvenCurlCurlwithProjwith a new low-mode TAE example; the curl-curl models use free boundary conditions for the magnetic field, which removes most of their energy loss. #803
Struphy 3.4.0 - 2026-10-01
Headlines
- New lazy, xarray-based post-processing API:
OutputreplacesPostProcessor,PlottingData,sim.pproc()andsim.load_plotting_data().Simulation.run()now returns anOutput, andOutput("path/to/run")oropen_output()opens a finished run; post-processed fields, binned distributions, SPH densities and orbits are materialized on demand and returned asxarrayobjects with named dimensions, coordinates and units. #370 - Parallel kernel compilation:
struphy compile -j N(andCompiler(jobs=...)) compiles the pyccel kernels in parallel while preserving their dependency order; kernel dependencies are now detected by static AST parsing instead of importing the kernels. #382 - More efficient MPI sorting of markers: new
Pusherkeywordspushes_eta(required) andlocal_eval_onlycallmpi_sort_markersonly when markers have actually moved, cutting the number of sorts by 2x to 10x depending on the model with unchanged results, and giving good strong scaling for e.g.VlasovAmpereOneSpeciesandColdPlasmaVlasov. #415 - One folder per mapping: each of the 14 domains now lives in its own folder under
struphy/geometry/domains/, together with its own<name>_kernels.pyfor the mapping and Jacobian kernels; this prepares the translation to other backends such as CUDA. Imports likefrom struphy.geometry.domains import Cuboidstill work. #418 (merged via #417)
API changes
Outputandopen_outputreplacePostProcessorandPlottingDatainstruphy.__all__. OnSimulation,load_plotting_data()andplotting_dataare removed, andSimulation.outputandSimulation.from_output(path_out)are new.Domaingainsouter_boundary_mesh(), and theVariableclasses gainto_dict(). #370Compiler.__init__()andCompiler.compile()take a newjobsargument (default 1, serial) for parallel kernel compilation. #382- New option
HasegawaWakataniStep.Options.coupling(default1.0) sets the coupling coefficientCforc_fun="const", which was previously hard-coded to zero. #388 - The default of
parallelinOutput.pproc()andOutput.evaluate()changed fromFalsetoNone: post-processing now runs on all ranks when the MPI job has as many ranks as the saved run, and serially on rank 0 otherwise. #410 - New option
fast(defaultFalse) inVariationalViscosity.OptionsandVariationalResistivity.Options, replacing the leftoverfastkey of the old dict-based solver parameters. #454 - The misspelled field
LoadingParameters.dir_exrernalis renamed todir_external, as documented. #479 Simulation.spawn_sister()takes a newcommargument, so a sister simulation runs on the same communicator as its parent instead ofMPI.COMM_WORLD. #556- The
moment_factorssetters ofMaxwellian3D,GyroMaxwellian2D,GyroMaxwellian2DvperpandCanonicalMaxwellian2Dnow take a dict that is merged into the existing factors, e.g.f0.moment_factors = {"n": 3.0}; before, every assignment failed with aTypeError. #627 CanonicalMaxwellian2D.eval_psic()takes a new keyworduse_cbufs(defaultTrue) to bypass the result buffers. #637
Physics models
- None.
Bug fixes
- Fix
create_vtkin post-processing crashing onlog10(0)when no time steps were saved. #381 - Fix stale values in nested scalar sums (
a + b + c) inScalars.update(), which caused apparent drifts in derived quantities such as the total energy. #385 KineticEnergyPICandKineticEnergySPHno longer divide byN_ptwice, which made the kinetic energy of every PIC and SPH modelN_ptimes too small. #387- Fix
GyroMaxwellian2D.velocity_jacobian_detfor a callableB0, which now returns one value per marker. #394 - Zero-initialise
norm_b_prodin the pusher kernelpush_gc_cc_J2_stage_H1vec; its diagonal held uninitialised memory, corruptingCurrentCoupling5DGradBwithu_space="H1vec". #459 - Fix
PushRandomDiffusionadding the full Wiener increment at every Runge-Kutta stage, which made the effective diffusion coefficient 16 D withrk4. Each stage now adds itsb[i]fraction, and the default scheme isforward_euler(Euler-Maruyama). #458 - Fix the neutralising background in full-f
ImplicitDiffusion/PoissonSolvebeing multiplied by the mass matrixM0twice, which gave a large spurious initial potential inVlasovAmpereOneSpecies,ToyDriftandDriftKineticElectrostaticAdiabatic. #460 - Fix the missing charge source in the initial Poisson solve of
LinearVlasovMaxwellOneSpecies, which left the initial electric field at zero. #457 - Fix
dot_inner_tp_ringsinPolarLinearOperatorwriting the first tensor-product ring on every rank, which silently corrupted coefficients wheneta1is split across MPI ranks. #462 - Fix the missing barotropic pressure term in
VariationalDensityEvolvewithmodel="barotropic", which madeVariationalBarotropicFluidbehave like a pressureless fluid. #463 - Fix crashes of
VariationalViscosityandVariationalResistivityfor any non-zeromu/eta, caused by dict-style access to the solver dataclass, a wrong argument toeval_3formand an in-place update on aFEECVariable. #454 - Fix the missing
/pocin the eta1-derivatives of theHollowCylinderJacobian, which was wrong for anypoc != 1. #455 - Fix the missing factor
sin the eta1-derivatives of thePoweredEllipticCylinderJacobian, a 100% error for the defaults = 0.5. #464 Magnetosonic.allocateno longer re-allocates ab_fieldpassed in by the model, which dropped the initial conditions of the shared magnetic field in the linear MHD models. #461- Fix the missing toroidal-field term in
AxisymmMHDequilibrium.gradB_xyz; the gradient of |B| forEQDSKequilibriumwas off by about 4%. #465 EQDSKequilibriumnow rescales the boundary fluxpsi1to Struphy units likepsi0, fixing runs with non-defaultbase_units. #466- Fix a copy-paste error in the
G[0,2]metric entry of thehybrid_weightkernel, wrong for any mapping withdf[2,2] != 0such asCuboidorColella. #468 - Fix an
AttributeError(undefined_extracted_q2) inVariationalQBEvolvewithlinearize=True, which made every linearized run fail on its first step. #469 - Post-processing now tracks the saved markers,
fandn_sphper kinetic species, fixing aKeyErrorwhen only some species save particle data. #470 - Fix corner neighbour ranks left as
NoneinParticles._get_neighbouring_procfor mixed periodic/non-periodic boundaries, which broke SPH ghost-box communication. #471 ButcherTableau.a_stagenow raisesNotImplementedErrorfor the"3/8 rule"instead of silently dropping coefficients and degrading particle pushes to first order. #472- Fix
StencilMatrixFreeMassOperator.transposepassing coefficient spaces instead of FEM spaces, which crashed.Tandtransposed=Truefor matrix-free weighted mass operators. #473 - Fix the argument order in
AverageOperator.transpose, which made.Talways crash. #478 KineticEnergyPICno longer counts the magnetic momentmuas a velocity forParticles5D, removing a spurious energy term in all drift-kinetic models. #474- Add the missing equilibrium gradient
grad_PBeqin theH1vecbranch of the accumulation kernelcc_lin_mhd_5d_gradB, so bothu_spacechoices ofCurrentCoupling5DGradBgive the same physics. #476 - The SPH linear smoothing-kernel gradients now vanish at zero separation, so particles no longer push on themselves. #475
- Fix
loading="external"for particles, which failed with aTypeErrorinParticles.draw_markers; the parameter field is renamed todir_external. #479 - Fix
BasisProjectionOperatorLocal.update_weightsfor transposed operators, which kept applying the construction-time weights (e.g. inBracketOperator). #480 - The Uzawa path of
TwoFluidQuasiNeutralFullnow includesM2/dtin itsA11block, so it solves the time step instead of the steady problem. #483 BasisProjectionOperatorLocalnow treatsNoneweights as zero, fixing crashes ofBracketOperatorwithderham.with_local_projectors. #481- ... and 67 more bug fixes, see the closed PRs.
Internals
test_pprocgets ashow_plotsafeguard so the test doesn't open plot windows. #372- Update the managed dependency bounds in
pyproject.toml. #374 - Lazy imports:
import struphyand the public API objects, models and propagators are resolved on first access, and expensive imports are moved into the operations that need them, reducing Python startup time. #383 M2Bnnow uses the equilibrium field componentseq_mhd.b2_*instead of the curl of a projected vector potential, so the Hall operator keeps uniform background fields in periodic domains. #384LinearMHDandLinearExtendedMHDuniformget a new scalaren_thermal(quadratic compressional energy) that replaces the linear pressure diagnosticen_pinen_tot, removing the spurious drift of the total energy. #386- Replace Python lists by scalars in
surface_kernel_3d_matofmass_kernels.py, removing the kernels' only dependency on gFTL and with it the need forcmake >= 3.28instruphy compile. #389 - New features for quickly plotting the kinetic initial condition with the show-distribution-function utilities. #390
- New
ProjectorNoBCclass that wraps projectors to prevent applying Dirichlet boundary conditions. #395 - Rename the post-processing dimensions
e1,e2,e3toeta1,eta2,eta3, and pass slices directly toevaluate()instead ofisel(). #406 Outputintegrates the optional plasma-plots package: when it is installed,out.plot,out.analysisand the.plasmaaccessor on every product are available, e.g.out.plot.energies(). Install it withpip install "struphy[pproc]". #408- Kinetic boundary conditions are applied per marker inside the pusher kernels by the new kernel
apply_kinetic_bc_marker, speeding up position updates 2x-4x. #412 - Particle kernel setup is refactored into explicit
KernelSetupobjects (struphy/pic/pushing/kernel_setup.py). #413 - Bump the
feectoolsdependency to 0.1.11, which was missed in #410. #453 - CUDA strategy, part 1: proof of concept for running Struphy kernels on NVIDIA GPUs via CuPy next to the pyccel kernels (
CudaKernelinstruphy/utils/kernel_backends.py). #643 - CUDA strategy, part 2:
CudaKernel.from_file()loads CUDA kernels from<name>_cuda.cufiles next to the pyccel module. #644 - CUDA strategy, part 3:
KernelCatalogdiscovers kernels from the file layout, and a missing CUDA kernel raises a clear error on the GPU backend instead of silently falling back to the CPU. #645 - CUDA strategy, part 4:
Pusheraccepts aKernel(pyccel/CUDA pair) and chooses the kernel for the active backend once at setup; CPU behaviour is unchanged. #646 - CUDA strategy, part 5:
Domain.cuda_args_domainbuilds the CUDA kernel arguments of a domain on the device, andDomaindeep-copies correctly on the CuPy backend. #649
Struphy 3.3.0 - 2026-09-11
Headlines
- Enable automated scaling test workflow: running a profiling case across multiple MPI ranks, emitting per-run JSON metadata, packaging results into a publishable folder structure, and optionally uploading them to the struphy-hub/profiling-data repository. #266
- New tutorials and verification tests for SPH: #261
- tutorials/tutorial_viscous_euler_sph.ipynb
- tutorials/tutorial_velocity_diffusion_sph.ipynb
- tutorials/tutorial_hagen_poiseuille_sph.ipynb
- tutorials/tutorial_dam_break_sph.ipynb
- New guiding-center 5D phase-space conventions: switching from
(v_\parallel, v_\perp)to(v_\parallel, \mu)as the standard coordinates inParticles5D. Use consistentmu_idxhandling across particle kernels and PIC utilities. Introduced newCanonicalMaxwellian2Dwith caching capabilities. #275 - New surface (boundary) integral operators in the FEEC layer: enable assembly/application of boundary mass operators needed for non-homogeneous derivative boundary conditions. #303
- Enable parallel post processing on each rank. The default is still serial post-processing, which means that
parallel_pproc=Trueneeds to be passed for parallel work. #309 - Added memory estimates in struphy and feectools: Adds per-rank memory footprint reporting for particles and key linear-operator types, including a dry_run construction mode for StencilMatrix to estimate allocation size without actually allocating. #363
API changes
Variableshave been entirely removed fromPropagator'sOptionsclasses. Such "Options"-variables were not updated by thePropagator, but nevertheless needed in the sub-step. From now on, such variables have to be passed to thePropagatorconstructor, which is done internally in the model file. The user cannot pass these variables anymore in the launch file throughOptions(). #262- Added a switch in
EnvironmentOptionsthat lets you turn off writing of restart checkpoint data, this is convenient for benchmarking. #345
Physics models
- New propagator
PoissonAdiabaticGyrokineticfor the electric potential in gyrokinetic simulations with adiabatic electrons. #260 - New examples for model
DriftKineticElectrostaticAdiabatic: ITG in cylinderical geometry and cyclone base case (still issues due to noise). #260 - New model for TAE energetic particle simulations: The new API now supports TAE energetic particle simulations with the model
LinearMHDDriftkineticCC. #275 - New model
IncompressibleNavierStokesSPH: Incompressible Navier-Stokes equations solved using SPH with the Chorin projection scheme. The Poisson equation is solved on a grid with pressure in H1. This is a PIC scheme, not meshless anymore. #284 - Three new verification tests for
IncompressibleNavierStokesSPH(Chorin projection). #308 - Added
Poissonsolver case to the profiling examples. #302
Bug fixes
- Fix bug in
mpi_sort_markers: add missingself.update_ghost_particles()to get the correct number of valid_mks. #261 - Fix bug in matrix-matrix multiplication during weights assembling in
WeightedMassOperators.create_weighted_mass(). #267 - Fix incorrect accumulation kernels for
basis_u == 1andbasis_u == vin drift-kinetic-MHD hybrid scheme. #352
User news and internals
- For PIC, moved the factor
1/Npinto the definition of the weights. #261 - New files for sph kernels:
eval_kernels_sph.pyandpusher_kernels_sph.py. #261 - Added a new
Compilerclass wrapping the existingstruphy_compileCLI logic for API use. #281 - Added a
to_json()method to the Simulation class. #282 - Added workflow to build a docs preview for existing PRs. #283
- Added a new
to_json()method to theSimulationclass. #282 - Added a new workflow to build a docs preview for existing PRs. #283
- New accumulation kernel
div_u_weak_1formfor accumulating the divergence of the velocity field; new classParticlesToGridfor passing the accumulation parameters to a propagator in the model init; and new logic inImplicitDiffusionfor passing accumulation and filter parameters, respectively. #284 - Renamed propagator
PushVinEfield->PushVinForceField. #308 - Use config.json instead of files in postprocessing. Instead of exporting all the classes as pickled binary objects, export the main information about the simulation as a json file called
config.json. #314 - Nightly automatic check of dependency bounds. #318
- Enforce that feectools submodule should be pointing to the last commit. #320
- Only allocate the full eval grid on rank 0. Grids are now rank local,
_create_eval_gridsreturns global grids plus per-rank slices fromderham.domain_array, so each rank holds onlygrids_log_loc. The physical grid is built on rank 0 only. #322 - Added
setup/modules.jsonfor loading modules on known HPC systems. #326 - Added setters to all properties of the
Simulationclass. #336 - Added more profiling regions. #344
- Fixed the
xp.all(axis=1)bottleneck (10x speedup). #347 - Column major fix of apply markers bc. Added a pre-allocated
self._eta_bc_buf = xp.zeros((self.n_rows, 3))buffer (1.3x speedup). #348 _sendrecv_get_destinationsnow checks neighbor ranks first, and only falls back to checking the rest if some markers remain unmatched (up to 1.5x speedup). #350- Updated profiling setup (
scoper-profilerset to 0.5.0). Big improvements for GPU profiling and also line-by-line profiling for the regions we already defined using the tool without having to useline_profilerdirectly. #354 and #357 - Enable setting log file with environment variable. #358
- Improve docstring helpers. Added
_html,_markdown,_latexmethods to the model/domain/equilibria/perturbation baseclasses. Theinfo(),pde(), ... methods remain the same, they just use the_htmlmethods and display the code. #361 - Split up docstrings for the
Domain,Perturbation, andFluidEquilibriaclasses, mirroring the pattern already used byStruphyModel. #362
Struphy 3.2.0 - 2026-06-09
Headlines
- New Quickstart, Userguide, Tutorials and Developer's guide in the documentation: #252
- Each
Propagatornow sits in his own .py file: #236 - New propagator
CurlCurlSolve()for curl-curl problems: #245 - New classmethods to inspect the model docstring (in a jupyter notebook for example): #229
API changes
- Change in the signature of
ParticleSpecies.set_markers(), which is used in parameter files featuring particles. The new classesSortingParametersandSavingParametersreplace the methodsset_sorting_boxesandset_save_data. Instances of the new classes are passed toset_markers(): #247
User news
- Clean-up logging levels for simulation output: #247
- New plotting functionality for kinetic backgrounds: #239
- Addition of a matrix-free averaging operator for distributed FEEC data: #246
- New default for
boxes_per_dimistuple = (1, 1, 1): #247
Bug fixes
- Adaptation to general equilibria and new tests of the gyrokinetic Poisson solve: #238
Struphy 3.1.0 - 2026-04-24
Headlines
- Refactoring of the
Derhamclass: #212- Renamed
Nel -> num_elements,p -> degree,nq_pr -> nquads_proj,polar_ck -> polar_splines - The type of spline basis functions is now set via the argument
bcs, which is a tuple of length three. It holds the type for each direction:Nonefor periodic, or a tuple of length two with eitherfreeordirichletto indicate the type of clamped splines.bcsreplaces the two argumentsspl_kindanddirichlet_bc - The lifting is defined for each
FEECVariableseparately through the new attributelifting_function
- Renamed
- Refactoring of
WeightedMassOperators.create_weighted_massmethod: #227- The signature remains largely the same, except that list input for weights has been replaced by tuple input. Moreover, within the tuple, one can now pass a
SplineFunctionobject, either from H1 or L2 space, which will be multiplied to the constant weights during.assemble(). This allows for time dependent mass operators in nonlinear simulations (formerly treated with workarounds). - In the constructor of
create_weighted_mass, we now evaluate all callables derived fromweights: tuple[str]on the integration grid, multiply them together and then pass them as xp.arrays toWeightedMassOperatorfor building the object. This leads to much simpler code than in the previous version, where we built composed functions and passed them as weights. - In
MassMatrixPreconditioner, to build the Kronecker matrices from 1D matrices without domain decomposition, the values of the weights are now retrieved via an MPI sub-communicator, because the weights in M0, M1 etc. are now given as local xp.arrays, not as callables anymore.
- The signature remains largely the same, except that list input for weights has been replaced by tuple input. Moreover, within the tuple, one can now pass a
- Use
logginginstead of print statements; use functionset_logging_levelfrom the API to set the logging level of all handlers. With pytest you can usepytest --logging-level DEBUG src/struphyto set the loggong level: #199 and #219 - New user guide: #200
- Remove two submodules
struphy-parameter-filesandstruphy-tutorialsin favor of the new foldersexamples/ortutorials/: #206
API changes
base_unitsis removed from theStruphyModelconstructor; all equation parameters that can be seen in the model docstring can be passed to the model constructor: #222
User news
- Add
to_dictandfrom_dictmethods to theSimulationclass: #186 - Add
__repr__and__repr_no_defaults__methods to most classes in the API: #193 - Using
pyvista; dddshow_3dandcreate_geometry_meshmethods toDomainclass: #195 - Add iterators to models and domains: #196
- Added export and from_file methods to
SimulationBaseclass: #197 - Added name and description to the Simulation class: #198
- New model
ToyGyrokineticto simulate the diocotron instability: #201 - New classes for scalar quantities tracked during simulation (via new type
Scalar): #220 - The components of a model docstring are now available as class methods: #229
Bug fixes
- Weights in initial Poisson solves of kinetic models without control variate fixed: #192
Struphy 3.0.4 - 2026-02-27
Bug fixes
- New lower bound on pyccel is set to 2.2.0, due to this pyccel bug fix: pyccel/pyccel#2567
Struphy 3.0.3 - 2026-02-23
Headlines
- New class
Simulationinherits the genericSimulationBase, both are in the new folderstruphy/simulations/. The most important methods are:Simulation.run()Simulation.pproc()Simulation.load_plotting_data()Simulation.spawn_sister()(my new favorite!)
These are tested in the new tutorials: https://github.com/struphy-hub/struphy-tutorials/tree/use-species-properties
The file main.py has been deleted.
The Simulation takes a model as input. Other API classes are passed as well (see tutorials).
The model is viewed as everything related to the PDE, i.e. its variables, initial conditions etc. The simulation deals with the rest (geometry, derham, environment etc.)
Some important changes to the logic: The model does not have access to derham, mass_ops etc. anymore, these can be called from Propagator when needed. Solves that need to happen before the time stepping (like initial Poisson solves) are moved to model.allocate_helpers().
-
The default launch file has been improved. See Tutorial 2 or test with
struphy params MODEL. The files in the submodulestruphy-parameter-fileshave been adapted. -
Several new classes have been introduced for post processing and plotting data, see
post_processing_tools.py. The most important ones arePostProcessorandPlottingData. Dictionaries in the plotting data have been replaced by classes. Many classes now feature the__repr__dunder for customized printing.
API changes
New classes exposed: Simulation, PostProcessor and PlottingData.
User news
- Add
set_zero_velocityargument intoLoadingParameters, enforcing velocities of all particles along specified axis to always be zero: #176 - New model
ViscousEulerSPHreplacesEulerSPH. The evaluation of the viscosity tensor has been implemented and tested for SPH methods. Unit tests for evaluation of the fluid velocity and its gradients (needed in the viscosity tensor) have been improved: #160
Struphy 3.0.2 - 2026-02-06
Headlines
- Added a public API. This allows imports like
from struphy import equils: #168 - New default compile language is Fortran: #158
- Moved each model to its own file. Calling sub-processes must be avoided in the future because of incompatibility with MPI: #152
User news
- Added binning of higher order moments (current density, energy tensor) of f and delta f: #162
Developer news
- Use
pyccel 2.1: #153 - Added three submodules:
struphy-parameter-files,struphy-tutorialsandfeectools. The Struphy repo should be cloned withgit clone --recurse-submodules https://github.com/struphy-hub/struphy.gitto init and update the submodules. Also, rungit submodule updateregularly to get updates from the submodules. See #154 - Introduced class
options.LiteralOptionsfor parsing literals. MovedUnitstophysics.py: #167
Bug fixes
- Use
struphy.io.options.Unitsin equils. This enables the use of GVEC, EQDSK and DESC in the new framework: #158
Struphy 3.0.1 - 2025-12-11
Headlines
Psydac is now installed via pip from our fork renamed to feectools, which is published on PyPI. This avoids installing from a .whl file.
Functionality remains unchanged, but future releases of Struphy are much easier and quicker. We have kept the option to install the upstream psydac, once it is also on PyPI.
See #147.
User news
None
Developer news
- Removed legacy code (eigenvalue solver): #129
- Add context manager to h5py.File() calls: #135
- Fix undefined variables: #141
Bug fixes
- Fix setter in DESCequilibirum, update quickstart guide: #132
- Set defaults for given_in_basis: "0" for scalar and "v" for vector-valued: #136
- Fix the restart function: #143
Struphy 3.0.0 - 2025-11-13
Headlines
Struphy 3 represents a major refactoring with breaking changes with respect to Struphy 2, in particular:
- The
.ymlparameter files cannot be used anymore. Simulation parameters have to be transferred to the new.pylaunch files that are generated fromstruphy params MODEL. See the Struphy README for a quick introduction. - The console command
struphy run ...has been deprecated. The new way to launch simulations is by executing the.pylaunch file, for instance withpython params_MODEL.py. - Other deprecated console commands are
struphy pprocandstruphy units. Post-processing is now done through the API viamain.pproc(). - The Struphy repo has moved to Github. The old Gitlab repo will persist but not be maintained any longer. Issues, discussion and PRs will solely take place on the new Github repo.
User news
- Please consult the Struphy README and links therein to get familiar with the new workflows.
- New tutorials can be found on mybinder.
Developer news
Struphy has been refactored with the following principles in mind:
- get rid of console commands and increase the use of the Struphy API wherever possible
- become even more object-oriented
- use
Classesinstead ofdictswherever possible - use
Literalsto show options for string arguments
In Struphy 3, models feature the following important objects:
ParticleSpecies,FieldSpecies,FluidSpecies
Each species is a collection of Variables:
PICVariable,FEECVariable,SPHVariable
These variables are updated by Propagators. All options for a simluation can be set in the new .py launch file.
Bug fixes
- Incorporate psydac updates: #109
- Auto install Psydac on first Struphy import: #118
- Remove MPI Barrier responsible for deadlock: #121
Struphy 2.6.0 - 2025-11-12
Headlines
- This is a test run for the relaease of Struphy 3.0 from the new Github repo
Struphy 2.5.0 and prior releases
- See Gitlab