Skip to content

KroneckerSumSolver: stiffness coefficients and additional Kronecker terms - #102

Merged
spossann merged 2 commits into
devel-tinyfrom
kronecker-sum-solver-mass-terms
Oct 9, 2026
Merged

spossann merged 2 commits into
devel-tinyfrom
kronecker-sum-solver-mass-terms

Conversation

@spossann

@spossann spossann commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Generalizes KroneckerSumSolver (fast diagonalization of sums of Kronecker products), needed by struphy's StiffnessPreconditioner for operators like the adiabatic gyrokinetic Poisson equation:

$$A = \sum_d a_d , M_1 \otimes \dots \otimes S_d \otimes \dots \otimes M_n + \sigma , M_1 \otimes \dots \otimes M_n + \sum_t c_t , F_{t,1} \otimes \dots \otimes F_{t,n}$$

  • stiffness_coeffs: coefficients $a_d$ of the stiffness terms (default 1). $S_d$ defines the eigenbasis also for $a_d = 0$, e.g. a direction in which a perpendicular diffusion has no stiffness.
  • mass_terms: additional terms $c_t , F_{t,1} \otimes \dots \otimes F_{t,n}$ (None for $M_d$), used through their diagonal $\mathrm{diag}(U_d^T F_{t,d} U_d)$ in the eigenbasis. This is exact when the $U_d$ diagonalize the $F_{t,d}$, e.g. for $F = c,c^T/(\mathbb 1^T c)$ with $c = M_d \mathbb 1$ in a direction with $S_d \mathbb 1 = 0$ (removing the mean in a periodic direction: $U_d^T F U_d = e_0 e_0^T$); otherwise it is an approximation.
  • Default arguments give exactly the previous behavior (including the pseudo-inverse threshold).
  • Version 0.7.0 → 0.8.0.

Tests

test_kron_sum_solver_terms_{ser,par}: stiffness coefficients (one of them zero) and a term removing the mean in the two periodic directions; exact (pseudo-)inverse, relative residual < 1e-12. test_kron_stencil_matrix.py: 25 passed serially, 45 on 2 ranks.

Paired struphy PR: struphy-hub/struphy#740

🤖 Generated with Claude Code

…erms; version 0.8.0

- stiffness_coeffs: coefficients a_d of the stiffness terms; S_d defines the eigenbasis also for a_d = 0
- mass_terms: additional terms c_t F_{t,1} x ... x F_{t,n}, used through their diagonal in the eigenbasis
  (exact if the U_d diagonalize the F_{t,d}, e.g. M_d, or c c^T with c = M_d 1 in a direction with
  S_d 1 = 0: removing the mean in a periodic direction)
- default arguments give exactly the previous behavior
- test: coefficients (one zero) and a mean-removing term, serial and parallel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Signed terms can cause cancellation that makes the pseudo-inverse threshold incorrectly discard nonzero eigenvalues.

2 open findings
What changed in this PR

Generalizes KroneckerSumSolver for stiffness preconditioning with weighted stiffness terms and additional Kronecker mass terms.

Changes:

  • Adds stiffness coefficients and additional mass terms.
  • Adds serial and MPI tests for the generalized solver.
  • Bumps the package version to 0.8.0.
File Description
pyproject.toml Updates the package version.
feectools/​linalg/​kron.py Implements and documents generalized Kronecker terms.
feectools/​linalg/​tests/​test_kron_stencil_matrix.py Tests weighted stiffness and mean-removal terms.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread feectools/linalg/kron.py Outdated
Comment thread feectools/linalg/kron.py Outdated
spossann added a commit to struphy-hub/struphy that referenced this pull request Oct 9, 2026
…stab, weighted average in the stabilization

- StiffnessPreconditioner: mid (default M_{k+1}) and stab (default M_k, or "Id") as names of mass
  operators or operators (incl. sums such as M1gyro), stab_average ("eta3", "eta2 eta3", grad only);
  blocks of mid with zero mean have no stiffness term (their 1d stiffness still defines the eigenbasis);
  the averaged stabilization S (I - A) is approximated by mean-removing terms, exact on the logical
  cube; the average is left out of the diagonal scaling (not resolvable by probing)
- WeightedAverageProjection: S-orthogonal projection onto functions constant in the averaged directions,
  S (I - P) is symmetric; equals the plain AverageOperator if the weight of S does not depend on the
  averaged directions; e.g. the flux-surface average with sqrt(g) for M0
- ImplicitDiffusion/PoissonSolve: option stab_average; stab_mat @ (I - P) built in ImplicitDiffusion;
  the stiffness preconditioner gets diffusion_mat, stab_mat and stab_average; MultiGrid rejected with
  stab_average
- PoissonAdiabaticGyrokinetic: stab_average from which_geometry (cylindrical: eta3, toroidal: eta2 eta3);
  with the weighted average the toroidal operator is symmetric (it was not with the plain average,
  pcg was not applicable); MultiGrid rejected
- feectools submodule: KroneckerSumSolver with stiffness coefficients and additional Kronecker terms
  (struphy-hub/feectools#102)
- tests: exactness of the new options on the cube, option checks, WeightedAverageProjection
  (symmetry, projection, plain average), gyrokinetic Poisson with the stiffness preconditioner
  (cylindrical 34 -> 10, toroidal 109 -> 20 PCG iterations)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iagonal for signed terms

With stiffness_coeffs or mass_terms, the cutoff is relative to the global maximum of |Lambda|
(terms may cancel; the sum of their bounds could discard non-zero eigenvalues). Without them the
previous estimate is kept. Docstring: mean-removing factor c c^T / (1^T c). Regression test with
sigma = 1 and a term (eps - 1) M x M x M.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
spossann added a commit to struphy-hub/struphy that referenced this pull request Oct 9, 2026
…s; Kronecker preconditioners in ImplicitDiffusion (#739)

## Summary

Adds two Poisson strong-scaling profiling cases that differ from the
existing ones only in the preconditioner, and makes the preconditioner
choice actually reach the solver in `ImplicitDiffusion` (and thus
`PoissonSolve` and `PoissonAdiabaticGyrokinetic`).

### New profiling cases
- `profiling/examples/Poisson/cube_strong_scaling_massmatrix/` +
`profiling/examples/Poisson/submit_poisson_cube_strong_scaling_massmatrix.py`
(`precond="MassMatrixPreconditioner"`, `maxiter=3000`)
- `profiling/examples/Poisson/cube_strong_scaling_stiffness/` +
`profiling/examples/Poisson/submit_poisson_cube_strong_scaling_stiffness.py`
(`precond="StiffnessPreconditioner"`, `maxiter=100`)

### Poisson cases on HollowCylinder and HollowTorus
- 8 new cases
`profiling/examples/Poisson/hollow_{cylinder,torus}_strong_scaling{,_multigrid,_massmatrix,_stiffness}/`
with submit scripts
`submit_poisson_hollow_{cylinder,torus}_strong_scaling*.py`.
HollowCylinder `a1=1e-2, a2=1, Lz=4`; full HollowTorus (`tor_period=1`)
`a1=1e-2, a2=1, R0=4`.
- All 12 Poisson cases (incl. the 4 cube ones) use the same manufactured
solution: a Gaussian 0-form on the logical domain,
exp(-|eta-0.5|^2/0.1^2), with the analytic -Laplace(phi) per mapping as
source (both `given_in_basis="0"`). The errors are computed at the
pushed-forward evaluation points x = F(eta). The Laplacians agree with a
metric-based finite-difference Laplacian to 3e-6; the error converges at
second order (16^3 -> 64^3); thresholds are calibrated at 64^3.
- `maxiter=3000` for all cases (multigrid on the hollow torus needs ~190
iterations at 32^3, the stiffness preconditioner ~60 on the hollow
mappings at 64^3).
- Slice plots in physical coordinates (cube slices, cylinder
cross-section, torus poloidal plane) or logical coordinates.
- The cube submit scripts are renamed to
`submit_poisson_cube_strong_scaling*.py` (labels unchanged), like the
other cases `submit_poisson_<mapping>_strong_scaling<_precond>.py`.

### Profiling results (local, 64^3, 1 and 2 MPI ranks)

All 12 cases run locally with the submit scripts (rank counts limited to
1 and 2 on a laptop with an i5-1135G7, 4 cores), commits 71e3a91 /
49f252a (no change to the Poisson code in between). Times of the
slowest rank in seconds, from the `profiling_data.h5` files (`solve:
PoissonSolve`, `setup: total`); the iteration counts are the same for 1
and 2 ranks. All runs pass their error checks (max relative error of
phi: cube 9.03e-3, hollow cylinder 9.72e-3, hollow torus 9.85e-3).

| mapping | precond | iterations | solve 1 rank | solve 2 ranks |
speedup | setup 1 rank | setup 2 ranks |
|---|---|---|---|---|---|---|---|
| cube | none | 198 | 26.4 | 21.4 | 1.23 | 14.8 | 8.4 |
| | multigrid | 9 | 20.5 | 10.2 | 2.01 | 29.2 | 16.2 |
| | mass matrix | 423 | 85.6 | 51.6 | 1.66 | 22.0 | 9.7 |
| | stiffness | 2 | **0.4** | **0.2** | 1.93 | 27.3 | 15.5 |
| hollow cylinder | none | 478 | 62.9 | 41.0 | 1.54 | 16.1 | 9.0 |
| | multigrid | 81 | 143.4 | 97.1 | 1.48 | 26.4 | 15.2 |
| | mass matrix | 2885 | 554.6 | 337.4 | 1.64 | 22.8 | 10.2 |
| | stiffness | 57 | **9.4** | **6.1** | 1.53 | 31.1 | 19.4 |
| hollow torus | none | 584 | 80.1 | 56.4 | 1.42 | 17.1 | 10.3 |
| | multigrid | 293 | 519.2 | 372.6 | 1.39 | 30.2 | 18.8 |
| | mass matrix | 3000 (maxiter, res 1.9e-8 vs tol 1e-8) | 568.4 | 355.0
| 1.60 | 18.9 | 11.2 |
| | stiffness | 60–61 | **10.1** | **6.7** | 1.51 | 30.9 | 19.0 |

- The **stiffness preconditioner** gives the fastest solve in all
geometries (exact on the cube; 7–8x faster than unpreconditioned CG on
the hollow mappings), at 10–15 s more setup.
- **Multigrid** pays off only on the cube; on the hollow mappings (a1 =
1e-2) it is slower than no preconditioner.
- The **mass-matrix preconditioner** is the slowest everywhere (it does
not approximate the Laplacian); on the hollow torus it reaches
maxiter=3000 without converging (the error check still passes).
- 1 -> 2 ranks: solve speedups 1.2–2.0, setup speedups 1.6–2.3 (includes
the mass-matrix assembly).

### `MassMatrixPreconditioner`: `diagonal_scaling=True` by default
Affects all propagators that build a mass preconditioner by name and the
multigrid mass smoother. FEEC/multigrid/Poisson/curl-curl/saddle-point
tests pass on 2 ranks (251 passed).

### `PoissonAdiabaticGyrokinetic`: multigrid rejected
Multigrid would be built for the stabilization without the flux-surface
average (applied after `ImplicitDiffusion.allocate()`) and never
updated, see the review comment. Full support of the stiffness
preconditioner for this propagator (M1gyro, M0ad_withT, weighted
average) follows in the stacked PR #740 (paired with
struphy-hub/feectools#102).

### `MassMatrixDiagonalPreconditioner` removed (API change)
- It was `MassMatrixPreconditioner(dim_reduce=None,
diagonal_scaling=True)`; with the new default it is now
`MassMatrixPreconditioner(M, dim_reduce=None)`.
- **`"MassMatrixDiagonalPreconditioner"` removed from `OptsMassPrecond`
and `OptsDiffusionPrecond`** (clean break, params files naming it fail
`check_option`).
- Variational propagators and `variational_utilities`:
`MassMatrixPreconditioner(..., dim_reduce=None)` explicitly (cheap
`update_mass_operator` for weights that change every step), behavior
unchanged; default `precond` option is now `"MassMatrixPreconditioner"`.
- `ShearAlfvenCurrentCoupling5D`: default
`precond="MassMatrixPreconditioner"`, i.e. now `dim_reduce=0` (its mass
matrix is fixed).
- Default `dim_reduce=0` kept: exact for weights/mappings varying only
in eta1 (PCG on a HollowCylinder: 2 iterations vs 7–10 with `None`),
comparable otherwise (HollowTorus 7–9 vs 8–11, Colella 10–85 vs 10–77).

Tests: preconditioner, variational utilities/propagators, current
coupling 5D, variational dissipation and the default-params
fluid/hybrid/visco-resistive model tests pass serially (36);
FEEC/variational tests on 2 ranks (20 passed).

### Submit scripts next to their cases
- All `profiling/submit_*.py` moved to `profiling/examples/<Model>/`,
next to the folder with their params file (Poisson and ToyGyrokinetic).
They put `profiling/` on `sys.path` for the driver modules; `params_dir`
is relative to the new location.
- New options `--language` (`fortran`/`c`) and `--compiler` (defaults
`fortran`/`GNU`, as hardcoded before), passed to `ProfilingCase`.
- `reusable-profiling-clusters.yml` called the nonexistent
`profiling/submit_diocotron_job.py` with `--language`/`--compiler`,
which no script accepted; it now calls
`profiling/examples/ToyGyrokinetic/submit_diocotron_strong_scaling.py`.

Smoke run at 32³ on 2 ranks: no preconditioner 103 iterations, mass
matrix 214, stiffness 2 (identical errors).

### Preconditioner options in `ImplicitDiffusion`
- `precond="MassMatrixPreconditioner"` /
`"MassMatrixDiagonalPreconditioner"` now builds the preconditioner from
`M0`. Before, every value except `"MultiGrid"` was silently ignored
(`pc=None`).
- New `precond="StiffnessPreconditioner"`:
`StiffnessPreconditioner(mass_ops, "grad", sigma=sigma_1)`, rebuilt in
`__call__` when `sigma_1` changes (e.g. with `divide_by_dt`), like
multigrid. `sigma = sigma_1` for any `stab_mat` (with `sigma = 0` the
pseudo-inverse drops the constants, and PCG does not converge for
periodic BCs with `stab_mat="Id"`).
- New option `precond_params: dict`, passed to the preconditioner
constructor (e.g. `{"weights": "unit"}`).
- **Default `precond=None`** (was `"MassMatrixPreconditioner"`, which
had no effect), so existing models keep their behavior. Any
preconditioner requires `solver="pcg"`.
- `"StiffnessPreconditioner"` added to
`LiteralOptions.OptsDiffusionPrecond`.

Note: the mass-matrix preconditioner does not approximate the Laplacian
and makes CG 3–6x *slower* (e.g. 555 vs 143 iterations in the new test);
it is there for the profiling comparison.

### Other changes
- **`PoissonSolve`: default `stab_mat="M0"`** (was `"Id"`); consistent
with `ImplicitDiffusion` and with the weak form ε(ψ, φ) (most examples
already set it explicitly).
- **Bug fix `solver="cg"`:** `pc` was always passed to `inverse`, and
`ConjugateGradient` has no `pc` argument (TypeError). Now passed only
for `"pcg"`.
- **Bug fix `StiffnessPreconditioner`:** the hand-built 1d difference
matrix was wrong for a single periodic element (`+1` overwrote `-1`,
giving `[[1]]` instead of `[[0]]`), adding a spurious stiffness term in
that direction (e.g. 2.5D grids `[Nx, Ny, 1]`). The 1d matrices are now
taken from the 3d derivative blocks via
`DirectionalDerivativeOperator.tokronstencil()` (global on every rank),
so they match the operator by construction. On a 2.5D cube the
preconditioner is exact again (≤ 3 PCG iterations instead of 5–9).

### Tests
- `test_poisson_2d_kronecker_preconds` (periodic/Dirichlet/Neumann ×
`stab_mat` M0/Id): mass-matrix and stiffness preconditioners, and
`solver="cg"`, give the unpreconditioned solution; stiffness needs fewer
iterations.
- `test_implicit_diffusion_stiffness_dt`: the stiffness preconditioner
follows changes of `dt`, exact on the cube.
- `test_stiffness_approximation_unit_cube`: new case with one element in
z (fails without the fix).
- Existing tests that set `precond="MassMatrixPreconditioner"`
(effectively unpreconditioned so far) now use `precond=None`.

Locally on 2 ranks: `feec/tests/test_stiffness_preconditioner.py`,
`propagators/tests/test_poisson.py`,
`propagators/tests/test_gyrokinetic_poisson.py` all pass;
`test_verif_Poisson.py` passes. Other model tests left to CI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
spossann added a commit to struphy-hub/struphy that referenced this pull request Oct 9, 2026
…stab, weighted average in the stabilization

- StiffnessPreconditioner: mid (default M_{k+1}) and stab (default M_k, or "Id") as names of mass
  operators or operators (incl. sums such as M1gyro), stab_average ("eta3", "eta2 eta3", grad only);
  blocks of mid with zero mean have no stiffness term (their 1d stiffness still defines the eigenbasis);
  the averaged stabilization S (I - A) is approximated by mean-removing terms, exact on the logical
  cube; the average is left out of the diagonal scaling (not resolvable by probing)
- WeightedAverageProjection: S-orthogonal projection onto functions constant in the averaged directions,
  S (I - P) is symmetric; equals the plain AverageOperator if the weight of S does not depend on the
  averaged directions; e.g. the flux-surface average with sqrt(g) for M0
- ImplicitDiffusion/PoissonSolve: option stab_average; stab_mat @ (I - P) built in ImplicitDiffusion;
  the stiffness preconditioner gets diffusion_mat, stab_mat and stab_average; MultiGrid rejected with
  stab_average
- PoissonAdiabaticGyrokinetic: stab_average from which_geometry (cylindrical: eta3, toroidal: eta2 eta3);
  with the weighted average the toroidal operator is symmetric (it was not with the plain average,
  pcg was not applicable); MultiGrid rejected
- feectools submodule: KroneckerSumSolver with stiffness coefficients and additional Kronecker terms
  (struphy-hub/feectools#102)
- tests: exactness of the new options on the cube, option checks, WeightedAverageProjection
  (symmetry, projection, plain average), gyrokinetic Poisson with the stiffness preconditioner
  (cylindrical 34 -> 10, toroidal 109 -> 20 PCG iterations)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spossann
spossann merged commit d40c05f into devel-tiny Oct 9, 2026
9 checks passed
spossann added a commit to struphy-hub/struphy that referenced this pull request Oct 9, 2026
…stab, weighted average in the stabilization (#740)

## Summary

Makes `StiffnessPreconditioner` work for `PoissonAdiabaticGyrokinetic`
(and other `ImplicitDiffusion` operators with a general diffusion matrix
and stabilization), and makes the toroidal adiabatic gyrokinetic Poisson
operator symmetric.

Follow-up of #739 (merged), rebased onto `devel`. Addresses the review
comment
#739 (comment)
properly; #739 itself only rejected multigrid for
`PoissonAdiabaticGyrokinetic`.

Points the `feectools` submodule to the branch of
struphy-hub/feectools#102 (`KroneckerSumSolver` with stiffness
coefficients and additional Kronecker terms, feectools 0.8.0). The
`feectools` pin in `pyproject.toml` is raised once 0.8.0 is on PyPI.

### `StiffnessPreconditioner`: `mid`, `stab`, `stab_average`

Preconditioner for $\mathbb d^T \mathbb M_{\textrm{mid}} \mathbb d +
\sigma \, \mathbb S (\mathbb I - \mathbb A)$, all new inputs as strings
with the previous behavior as default:

- `mid`: name of a mass operator in `mass_ops` (e.g. `"M1gyro"`,
`"M1perp"`) or the operator itself (`WeightedMassOperator`, or
sums/multiples such as `M1gyro = M1_MHDeq - M1para_MHDeq`), with the
domain/codomain of $\mathbb M_{k+1}$; default $\mathbb M_{k+1}$. Blocks
with zero mean (e.g. the parallel direction of a perpendicular operator)
have no stiffness term; their 1d stiffness still defines the eigenbasis
(`stiffness_coeffs` in feectools#102).
- `stab`: name or operator with the domain/codomain of $\mathbb M_k$
(e.g. `"M0ad_withT"`), or `"Id"`; default $\mathbb M_k$.
- `stab_average` (gradient only): directions averaged in the
stabilization, e.g. `"eta3"`, `"eta2 eta3"`. The term is approximated by
$\bar s\,(M_1 \otimes M_2 \otimes M_3 - F_1 \otimes F_2 \otimes F_3)$
with $F_d = c_d c_d^T/(\mathbb 1^T c_d)$, $c_d = M_d \mathbb 1$, which
is exact in the eigenbasis of periodic directions (`mass_terms` in
feectools#102). The average couples whole lines and is left out of the
diagonal scaling (probing cannot resolve it; its diagonal is O(1/n)),
which keeps the preconditioner exact on the logical cube.

### `WeightedAverageProjection` (new, `feec/mass.py`)

The $\mathbb S$-orthogonal projection onto functions constant in the
averaged directions, $\Pi = \mathbb E (\mathbb E^T \mathbb S \mathbb
E)^{-1} \mathbb E^T \mathbb S$; then $\mathbb S(\mathbb I - \Pi)$ is
symmetric. For $\mathbb M^0$ this is the flux-surface average with
$\sqrt g$; it equals the plain `AverageOperator` when the weight of
$\mathbb S$ does not depend on the averaged directions (cube, cylinder:
identical to round-off). The reduced matrix is assembled once by colored
probing and factorized (sparse LU); one application costs one
application of $\mathbb S$.

**Why:** with the plain (unweighted) `AverageOperator`, the toroidal
stabilization $\mathbb M^0 (\mathbb I - \mathbb A)$ is not symmetric
($\sqrt g = rR$ depends on $\eta_2$; $|x^T S y - y^T S x| / |x^T S y|
\approx 0.3$), but `PoissonAdiabaticGyrokinetic` solves it with pcg.
**Toroidal results change** (the average is now weighted with $\sqrt g$,
and with $n/T$ of `M0ad_withT`, a flux function for the usual
equilibria); cylindrical results are unchanged.

### `ImplicitDiffusion`, `PoissonSolve`, `PoissonAdiabaticGyrokinetic`

- New option `stab_average`; `ImplicitDiffusion` builds `stab_mat @ (I -
WeightedAverageProjection)` before the preconditioners and passes
`diffusion_mat`, `stab_mat` and `stab_average` to the stiffness
preconditioner. `precond="MultiGrid"` is rejected with `stab_average`.
- `PoissonAdiabaticGyrokinetic`: `stab_average` follows from
`which_geometry` (cylindrical: `"eta3"`, toroidal: `"eta2 eta3"`)
instead of patching `_stab_mat` after `allocate()`; multigrid rejected.

### Results

PCG iterations, `PoissonAdiabaticGyrokinetic` (`M1gyro`, `M0ad_withT`,
epsilon = 0.1, 12x12x8 elements):

| geometry | no preconditioner | stiffness preconditioner |
|---|---|---|
| cylindrical (HollowCylinder) | 34 | 10 |
| toroidal (HollowTorus) | 109 (429 before, non-symmetric) | 20 |

On the unit cube the preconditioner is exact (2 iterations) for constant
anisotropic or perpendicular `mid`, a scaled `stab` or `stab="Id"`
(whose average is Euclidean, $F_d = \mathbb 1 \mathbb 1^T/n$), and
`stab_average` `"eta3"`/`"eta2 eta3"`. `stab_average="eta1 eta2 eta3"`
(global weighted mean) is supported as well.

### Tests

- `test_stiffness_preconditioner_mid_stab_average` (exactness on the
cube), `test_stiffness_preconditioner_mid_stab_options`
- `test_weighted_average_projection.py` (new file in `feec/tests`,
already in the CI shards): symmetry, projection, equality with
`AverageOperator` (Cuboid, HollowCylinder, HollowTorus x M0, M0ad_withT)
- `test_poisson_adiabatic_gyrokinetic_stiffness_preconditioner`
(cylindrical, toroidal): same solution as without preconditioner, fewer
than half the iterations; multigrid rejected

Locally: stiffness, weighted average, gyrokinetic Poisson and Poisson
tests pass serially and on 2 ranks; default-params test of
`DriftKineticElectrostaticAdiabatic` passes; feectools kron tests pass
serially and on 2 ranks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants