Skip to content

Coupled step: u∥ solves the same equation on both sides of the Ampère gate - #30

Merged
mgyoo86 merged 3 commits into
masterfrom
feature/coupled-ue-diffusion
Oct 6, 2026
Merged

mgyoo86 merged 3 commits into
masterfrom
feature/coupled-ue-diffusion

Conversation

@mgyoo86

@mgyoo86 mgyoo86 commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Coupled step: u∥ solves the same equation on both sides of the Ampère gate

Summary

At the Ampère gate, u∥ passes from update_ue_para! to the coupled solve, and the two did not solve the same equation:

  • Diffusion. The coupled solve left out u∥'s diffusion term $\nabla \cdot \left( \mathbf{D} \nabla u_{e\parallel} \right)$. So Include_ud_diffu_term, on by default, stopped acting once the plasma current reached the gate. MATLAB's Solve_combined_ud_GS_equations_with_coils has the same gap, and the port had kept it as a TODO.
  • θ. The coupled solve fixed θ = 1, where update_ue_para! reads θ_imp.decay.
  • Implicit = false. The coupled solve ignored it. update_ue_para!'s explicit branch applied advection to the partly updated u∥ and had no diffusion, so it was not the θ = 0 case of its own implicit update.

With this PR:

  • The coupled solve's u∥ equation is update_ue_para!'s, term by term and with the same θ. Diffusion uses the same operator, θ-weighted as advection is.
  • Implicit = false is the θ = 0 case of the same equation, for advection and diffusion, in both paths. update_ue_para! keeps its update without a matrix solve.
  • The circuits get their own weight, θ_imp.circuit (default 1), read on both sides of the gate. Before, both sides forced θ = 1 on them.
  • Tests hold the two paths to one equation at θ = 0, ½ and 1, with Implicit on and off. Given the field a step induces, update_ue_para! reproduces the coupled solve's u∥ to rounding, however strong the coupling.

The u∥ equation

With the coefficients at $t^n$, the first row of the coupled step is

$$ \left( \mathbb{1} + \Delta t \left( \theta_u \nu + \theta_\mathrm{op} L \right) \right) u^{n+1} + \frac{q_e b_\phi}{m_e R} \psi^{n+1} = \left( \mathbb{1} - \Delta t \left( \left( 1 - \theta_u \right) \nu + \left( 1 - \theta_\mathrm{op} \right) L \right) \right) u^n + \Delta t a^n + \frac{q_e b_\phi}{m_e R} \psi^n $$

  • $\nu$ is the total momentum-loss rate, and $L = \mathcal{A} - \mathcal{D}$, with $\mathcal{A}$ the advection $\left( \mathbf{u} \cdot \nabla \right)$ and $\mathcal{D}$ the diffusion $\nabla \cdot \left( \mathbf{D} \nabla \right)$. $\mathcal{A}$ and $\mathcal{D}$ are the in-wall operators update_ue_para! uses. $\mathcal{D}$ is new here.
  • $a^n$ collects the explicit sources: the applied and electrostatic fields, the pressure term and the friction with the ions, $\nu_{ei}^\mathrm{eff} u_{i\parallel}$.
  • The weights, the same in both paths: $\theta_u$ = θ_imp.decay on the friction and its ledger Rue_ei. $\theta_\mathrm{op}$ = θ_imp.decay on the nonlocal operators, or 0 with Implicit = false. The friction keeps its weight there: it is local, so the update still needs no matrix, and it is stiff.
  • The induced field carries no θ. The $\psi$ terms are $-\Delta t \left( q_e / m_e \right) b_\phi E_{\phi,\mathrm{self}}$ with $E_{\phi,\mathrm{self}} = -\left( \psi^{n+1} - \psi^n \right) / \left( R \Delta t \right)$, the step's mean induced field. By Faraday's law, $\Delta t E_{\phi,\mathrm{self}}$ is the field's exact integral over the step, whatever θ is.
  • Which $\mathbf{D}$: the same tensor as below the gate, with its perpendicular, turbulent and parallel parts. At the wall, a cross-derivative pair that reaches a node outside it is dropped whole, which keeps the operator conservative. This PR keeps the two paths identical; which $\mathbf{D}$ is right for u∥ is left open:
    • MATLAB's in-wall mode, which test_iFPC.m runs, diffused u∥ and $T_e$ with the turbulent part only. This port has used the summed tensor since In-wall operators on one fixed pattern, updated in place #24.
    • Read as viscosity, the turbulent part is sound: the eddies carry momentum along with the particles. The parallel part is the particle $D_\parallel$, which ambipolarity limits, and that limit does not apply to a viscous flux.
  • Solver: $\mathcal{D}$ lives on the wall pattern, so CoupledBlock keeps its sparsity pattern and its symbolic LU. DirectOuterSolve takes the term in through the same LU.

The circuits' θ

The coils' circuits are

$$ M \left( I^{n+1} - I^n \right) + \Delta t R \left( \theta_c I^{n+1} + \left( 1 - \theta_c \right) I^n \right) = \Delta t V^{n+1/2} - 2\pi \left( \psi_\mathrm{pla}^{n+1} - \psi_\mathrm{pla}^\mathrm{mem} \right) $$

  • $\theta_c$ is θ_imp.circuit. advance_coils! reads it below the gate and the coupled solve above it. CoilSystem.θimp only records the weight its matrices were built with.
  • u∥'s θ does not reach the circuits. Each equation keeps its own weight, and the plasma enters the circuits through its flux change, which carries no θ.
  • Second order at ½. The voltage is taken at mid-step, so θ_imp.circuit = 0.5 makes the circuits second order.

Tests

coupled_ue_para_test.jl. Each test was written first and failed before the change it covers: with diffusion on, at θ = 0 and ½, and for want of θ_imp.circuit. Diffusion off is a control.

  • One equation, any coupling, any θ. The column is dense, so its induced field is many times the applied one. update_ue_para! is given the step's induced field $b_\phi E_{\phi,\mathrm{self}}^{n+1}$. It must reproduce the coupled u∥ and Rue_ei to rounding, at θ = 0, ½ and 1, with Implicit on and off, and with diffusion on and off. The circuits stay at their own weight.
  • Implicit = false is the θ = 0 case. With every u∥ term on and $\theta_u = 0$, the explicit update equals the implicit one at θ = 0 to rounding.
  • Weak coupling, any θ. The column is tenuous, with one passive loop. Ampère on (the coupled solve every step) and Ampère off give the same u∥ inside the wall, to the order of the coupling. Outside the wall $n_e = 0$, so u∥ there carries no current and is not compared.
  • The circuits' weight on both sides of the gate. With no plasma, a loop's current decays as $I^{n+1} = \left( L - \left( 1 - \theta_c \right) \Delta t R \right) / \left( L + \theta_c \Delta t R \right) I^n$, below the gate and above it, at $\theta_c$ = 0, ½ and 1, while u∥ takes ½.

Behaviour changes

  • Runs with Ampère on, the gate open and the default Include_ud_diffu_term = true change above the gate. Below the gate, or with the flag off, nothing changes.
  • θ: with the defaults (θ_imp.decay = θ_imp.circuit = 1) the θ change leaves the coupled solve as it was, its first outer iterate included. A θ_imp.decay other than 1 now applies above the gate too.
  • Implicit = false runs change: advection now acts on $u^n$, and diffusion is included. A golden value of an explicit u∥ test is re-measured; with diffusion off, the old value is reproduced to $10^{-16}$.
  • ImplicitWeights gains circuit. Its positional constructor takes five weights; the keyword form is unchanged.
  • Examples:
    • full_startup is unchanged below the gate. Above it, the plasma current rises a little more slowly, and the gap narrows by the end of the run.
    • force_balance_control keeps its verdict.
    • coupled_step/* and current_diffusion switch diffusion off, so they are unchanged.

Remaining

  • The $\mathbf{D}$ for u∥, above.

…pere gate

- The coupled solve adds u_para diffusion with the operator update_ue_para!
  uses (A_diffu_e), theta-weighted as advection is, and reads theta from
  theta_imp.decay instead of fixing 1; Rue_ei follows it.
- New theta_imp.circuit (default 1) weights the coils' resistive term in
  advance_coils! and in the coupled solve; both forced 1 before.
- The psi predictor is the extrapolated mean induced field, without theta
  (unchanged at theta = 1).
- coupled_ue_para_test.jl: given the step's induced field, update_ue_para!
  reproduces the coupled u_para and Rue_ei to rounding at theta = 0, 0.5, 1;
  Ampere on and off agree at weak coupling; the circuits' theta reaches both
  sides of the gate.
@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.72%. Comparing base (72751ec) to head (08246cc).

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #30      +/-   ##
==========================================
+ Coverage   93.64%   93.72%   +0.07%     
==========================================
  Files          50       50              
  Lines        5130     5131       +1     
==========================================
+ Hits         4804     4809       +5     
+ Misses        326      322       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- update_ue_para!'s explicit branch builds the same right-hand side as the
  implicit one, with advection and diffusion at theta_op = 0, and divides
  by the friction's diagonal. Before, it applied advection to the partly
  updated u_para and had no diffusion.
- The coupled solve honours Implicit: theta_op = 0 for advection and
  diffusion, theta_imp.decay on the friction and Rue_ei.
- Tests: the u_para identity and the weak-coupling comparison with
  Implicit on and off; Implicit = false equals the implicit update at
  theta = 0 to rounding.
- physics_test: the explicit drift golden re-measured (diffusion now in the
  explicit update; the old value is reproduced to 1e-16 with it off).

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.

Copilot review overview

🔵 Needs a closer look

Add coverage for the advertised diffusion-disabled control; circuit documentation also needs updating.

Review effort: Lite
Findings: None

What changed in this PR

Aligns the coupled Ampère solve with update_ue_para!, adding consistent diffusion, θ-weighting, explicit stepping, and independently weighted circuits.

Changes:

  • Adds θ-weighted diffusion and explicit-mode consistency.
  • Adds θ_imp.circuit for circuit integration.
  • Expands coupled-step regression coverage and updates the explicit baseline.
File Summary
test/​unit/​physics/​physics_test.jl Updates the explicit diffusion baseline.
test/​unit/​physics/​coupled_ue_para_test.jl Tests coupled/uncoupled momentum and circuit consistency.
src/​types.jl Adds circuit implicit weighting and documentation.
src/​physics/​physics.jl Updates momentum, diffusion, and circuit stepping logic.

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

- calculate_circuit_matrices!, advance_LR_circuit_step! and CoilSystem now
  state the theta-scheme the circuits run, (M + theta dt R) I^{n+1} =
  M I^n - (1 - theta) dt R I^n + dt V^{n+1/2} - ..., with theta from
  flags.theta_imp.circuit, and list the fields CoilSystem actually has.
- initialize_coil_system! takes theta from flags.theta_imp.circuit, so the
  matrices are built with the run's weight from the start.
- The identity test of the coupled step runs diffusion off as well as on,
  the control the PR describes (lost when the Implicit axis was added).
@mgyoo86
mgyoo86 merged commit 31516b9 into master Oct 6, 2026
5 checks passed
@mgyoo86
mgyoo86 deleted the feature/coupled-ue-diffusion branch October 6, 2026 22:33
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