Skip to content

fix(phase_change): globalize pTg/TSat Newton solvers so they converge (esp. GPU)#1654

Draft
sbryngelson wants to merge 4 commits into
MFlowCode:masterfrom
sbryngelson:fix-phasechange-newton-convergence
Draft

fix(phase_change): globalize pTg/TSat Newton solvers so they converge (esp. GPU)#1654
sbryngelson wants to merge 4 commits into
MFlowCode:masterfrom
sbryngelson:fix-phasechange-newton-convergence

Conversation

@sbryngelson

@sbryngelson sbryngelson commented Jul 17, 2026

Copy link
Copy Markdown
Member

Description

Follow-up to #1653 (fyi to @JRChreim) That PR bounded the phase-change Newton loops so a non-convergent cell can no longer hang the solver. This PR fixes the underlying non-convergence so those cells actually reach equilibrium — and does so with bounded, uniform iteration counts, which matters most on GPU.

Root cause of the non-convergence. Instrumenting the stock examples/2D_phasechange_bubble showed the non-convergers were s_TSat and s_infinite_ptg_relaxation_k (the pT solver was fine). Both applied a fixed Om = 1e-3 underrelaxation that stalled the iterates far from the root — e.g. the pTg Gibbs residual froze at ~1e3 for the low-pressure vapor cells and never moved. The convergence criterion was never the problem; the solver was.

s_TSat — reformulated + safeguarded

The saturation temperature is the root of Gibbs equality g_l = g_v, which for stiffened gases is the dimensionless, strictly-monotone relation

G(T) = A + B/T + C·ln(T) + D·ln(p_sat + p_inf,l) − ln(p_sat + p_inf,v) = 0

The A/B/C/D coefficients were already computed in s_initialize_phasechange_module — and previously dead (referenced nowhere). This PR solves G(T)=0 with a safeguarded Newton (rtsafe: Newton step when it stays bracketed and reduces the interval, bisection otherwise). G is strictly increasing over [Tsat_min, TCr] for a liquid–vapor pair, so the root is unique; the solver cannot diverge and needs no accurate initial guess. Out-of-window pressures clamp gracefully.

s_infinite_ptg_relaxation_k — globalized

Same analytic 2×2 Jacobian, but the fixed underrelaxation is replaced by a damped Newton with backtracking line search, every step projected onto the physical bounds 0 ≤ ml ≤ mT and pS > pmin, plus a singular-Jacobian guard. The residual evaluation is extracted into a device helper s_compute_ptg_residual so trial states can be evaluated without mutating q_cons_vf (the total reacting mass mT is conserved by construction, so ml is the only mass unknown).

Incidental (in the routines being rewritten)

Why this matters on GPU

These solvers run inside $:GPU_PARALLEL_LOOP calling [seq] device routines, so a warp cannot retire until its slowest cell's loop exits — a single stalled cell stalls 31 idle lanes. The reformulated solvers converge in a bounded, uniform handful of iterations, which is the real GPU win beyond "no hang." New device code uses the GPU_* macros only.

Testing

  • Convergence: instrumented the bubble case — every cell now converges in ≤25 (pTg) / ≤60 (TSat) iterations instead of stalling; no cell hits the max_iter backstop.
  • CPU (gfortran): all 18 phase-change golden tests pass. The 6 model 6 goldens (which exercise pTg/TSat) are regenerated — they shift by ~1e-6 relative (same equilibrium root, now reached with consistent non-lagged auxiliaries); the 12 model 5 / pT-only goldens are unchanged.
  • GPU (nvfortran/OpenACC, H200): all 18 phase-change tests pass against the CPU-generated goldens — CPU and GPU agree to tolerance. The exact 2D_phasechange_bubble case that hung at t_step = 8 on GPU now runs cleanly (reached t_step > 400).
  • The max_iter cap from fix(phase_change): bound Newton relaxation loops by max_iter #1653 is retained as a permanent backstop.

Checklist

  • I added or updated tests for new behavior (regenerated the 6 affected model 6 golden files)
  • I updated documentation if user-facing behavior changed
GPU changes
  • GPU results match CPU results (18/18 phase-change tests, nvfortran/acc vs CPU goldens)
  • Tested on NVIDIA GPU (H200)

Note on stacking

This branch builds on #1653, so until that merges the diff here shows both commits (the max_iter cap + this convergence rewrite). Happy to rebase once #1653 lands, or to fold #1653 into this PR if reviewers prefer the complete fix in one place.

…de#1646)

The three Newton solvers in m_phase_change (s_infinite_pt_relaxation_k, s_infinite_ptg_relaxation_k, s_TSat) looped on a convergence-only condition with no iteration limit. The module parameter max_iter was declared but never referenced (dead since MFlowCode#179), so a cell that fails to converge hung the solver forever (on GPU: kernel never returns, host blocks in cuStreamSynchronize). Reproduced with the stock examples/2D_phasechange_bubble.

Bound each loop by max_iter and accept the last iterate on exhaustion, turning an unbounded hang into a bounded, finite step. Reduce the misleading real-literal init (max_iter = 1e8_wp) to a clean integer (100000). Golden-safe: all 18 phase-change regression tests pass byte-identical, confirming the cap sits above the legitimate convergence ceiling and truncates no converging cell.
…ally converge (MFlowCode#1646)

Beyond bounding the loops, the pTg-equilibrium and saturation-temperature Newton solvers never reached their tolerance for low-pressure cells: a fixed 1e-3 underrelaxation stalled the iterates far from the root (Gibbs residual frozen ~1e3). This reworks both to converge robustly with bounded, uniform iteration counts (no GPU warp divergence).

s_TSat: reformulated onto the dimensionless, strictly-monotone saturation relation G(T)=A+B/T+C*ln(T)+D*ln(p+p_inf,l)-ln(p+p_inf,v)=0 (the A/B/C/D coefficients computed in s_initialize_phasechange_module were previously dead), solved with a safeguarded Newton (rtsafe: Newton step when it stays bracketed and makes progress, bisection otherwise). Cannot diverge, needs no accurate guess.

s_infinite_ptg_relaxation_k: damped Newton with backtracking line search on the same analytic 2x2 Jacobian, every step projected onto the physical bounds 0<=ml<=mT and pS>pmin, with a singular-Jacobian guard. Residual computation extracted into a device helper s_compute_ptg_residual so trial states can be evaluated without mutating q_cons. Also removed dead locals (p_infpTg, TJac) and the now-unused p_infpT argument, and rewrote the pT relative-convergence test in multiply form to avoid dividing by pO (pO=0 on the first pass).

Validation: bubble cells now converge in <=25 (pTg) / <=60 (TSat) iterations instead of stalling. All 18 phase-change golden tests pass on CPU (gfortran) and GPU (nvfortran/acc, H200) - CPU/GPU agree to tolerance. The 6 model-6 goldens shift by ~1e-6 relative (same equilibrium root, consistent non-lagged auxiliaries); model-5/pT goldens unchanged. The max_iter cap from the parent commit is retained as a backstop.
Copilot AI review requested due to automatic review settings July 17, 2026 02:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR addresses phase-change Newton non-convergence (especially problematic on GPU) by replacing fixed underrelaxation with safeguarded Newton methods and by updating phase-change golden artifacts to reflect the new equilibrium solves.

Changes:

  • Reworks s_TSat into a bracketed/safeguarded Newton root find (rtsafe-style) using precomputed saturation-curve coefficients.
  • Replaces fixed-underrelaxed pTg Newton with a damped Newton + backtracking line search and introduces a residual helper for trial evaluations.
  • Regenerates phase-change golden metadata files for the affected model-6 tests.

Reviewed changes

Copilot reviewed 7 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/common/m_phase_change.fpp Implements new safeguarded TSat solver and damped/line-searched pTg Newton; adds iteration/backstop parameters.
tests/D8136392/golden-metadata.txt Regenerated golden metadata reflecting updated equilibrium behavior/environment.
tests/D21F4F38/golden-metadata.txt Regenerated golden metadata reflecting updated equilibrium behavior/environment.
tests/AAE5C27B/golden-metadata.txt Regenerated golden metadata reflecting updated equilibrium behavior/environment.
tests/8D6E051E/golden-metadata.txt Regenerated golden metadata reflecting updated equilibrium behavior/environment.
tests/76C663B7/golden-metadata.txt Regenerated golden metadata reflecting updated equilibrium behavior/environment.
tests/02BD9B5E/golden-metadata.txt Regenerated golden metadata reflecting updated equilibrium behavior/environment.

Comment thread src/common/m_phase_change.fpp Outdated
Comment on lines +565 to +573
! log(p_sat + p_inf,v) is undefined for p_sat + p_inf,v <= 0; no valid saturation temperature
! exists there, so leave the incoming temperature untouched.
if (pSat + ps_inf(vp) <= 0.0_wp) then
TSat = TSIn
return
end if

! calculating the jacobian
dFdT = -(cvs(lp)*gs_min(lp) - cvs(vp)*gs_min(vp))*log(TSat) - (qvps(lp) - qvps(vp)) + cvs(lp)*(gs_min(lp) - 1) &
& *log(pSat + ps_inf(lp)) - cvs(vp)*(gs_min(vp) - 1)*log(pSat + ps_inf(vp))
! Pressure-dependent part of G(T), constant across the iteration.
pterm = D*log(pSat + ps_inf(lp)) - log(pSat + ps_inf(vp))
Comment on lines +432 to +435
! pressure floor (stiffened gas requires pS + ps_inf > 0 for both phases)
pmin = -min(ps_inf(lp), ps_inf(vp)) + 1.0_wp

! calculating the (2D) Jacobian Matrix used in the solution of the pTg-quilibrium model
call s_compute_ptg_residual(ml, mT, pS, j, k, l, q_cons_vf, rhoe, R2D, TS, mCP, mQ, mCVGP, mCVGP2, mCPD)
Comment on lines +464 to +471
detJ = Jac(1, 1)*Jac(2, 2) - Jac(1, 2)*Jac(2, 1)
! singular Jacobian: no usable Newton direction, accept the current (best) state
if (detJ == 0.0_wp) exit

InvJac(1, 1) = Jac(2, 2)/detJ
InvJac(1, 2) = -Jac(1, 2)/detJ
InvJac(2, 1) = -Jac(2, 1)/detJ
InvJac(2, 2) = Jac(1, 1)/detJ
Comment on lines +27 to +32
integer, parameter :: max_iter = 100000 !< max Newton iterations before accepting the last iterate
real(wp), parameter :: pCr = 4.94e7_wp !< Critical pressure of water [Pa]
real(wp), parameter :: TCr = 385.05_wp + 273.15_wp !< Critical temperature of water [K]
real(wp), parameter :: Tsat_min = 1.0_wp !< lower bracket for the saturation-temperature root [K]
real(wp), parameter :: tsat_tol = 1.0e-12_wp !< relative convergence tolerance for the s_TSat root find
integer, parameter :: ptg_ls_max = 30 !< max backtracking-line-search halvings in the pTg solver
Comment on lines +355 to +358
!> Evaluate the pTg-equilibrium residual R2D and temperature TS at a trial state (ml, pS) WITHOUT mutating q_cons_vf, so the
!! Newton driver can line-search. The total reacting mass mT is conserved, so the reacting masses are (ml, mT - ml) and only the
!! inert fluids are read from q_cons_vf. Also returns the mixture sums the Jacobian and the final temperature need.
subroutine s_compute_ptg_residual(ml, mT, pS, j, k, l, q_cons_vf, rhoe, R2D, TS, mCP, mQ, mCVGP, mCVGP2, mCPD)
Comment thread tests/D21F4F38/golden-metadata.txt Outdated
Comment on lines +13 to +15
CMake v3.26.5 on atl1-1-02-008-32-0.pace.gatech.edu

C : GNU v13.3.0 (/usr/bin/cc)
Fortran : GNU v13.3.0 (/usr/bin/gfortran)
C : GNU v12.3.0 (/usr/local/pace-apps/spack/packages/linux-rhel9-x86_64_v3/gcc-11.3.1/gcc-12.3.0-ukkkutsxfl5kpnnaxflpkq2jtliwthfz/bin/gcc)
OpenMP : OFF

Fypp : /home/user/Documents/GitHub/MFC-JRChreim/build/venv/bin/fypp
Fypp : /storage/project/r-sbryngelson3-0/sbryngelson3/MFC-phasechange/build/venv/bin/fypp

@tao13146500073 tao13146500073 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.

Confirmed on my end — the stock 2D_phasechange_bubble that hung at t_step 8 now runs past 200 steps on NVHPC/OpenACC. Also verified convergence independently: dropping max_iter from 100000 to 100 leaves Time/step unchanged (1.422 s both), so no cell is hitting the backstop. My case is 3-fluid (liquid water / vapor / air) with cyl_coord = T, which I think exercises the inert-fluid path in s_compute_ptg_residual that Copilot flagged on pmin — no issue observed.

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.60274% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.53%. Comparing base (e090d56) to head (c392268).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
src/common/m_phase_change.fpp 72.60% 11 Missing and 9 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1654      +/-   ##
==========================================
- Coverage   60.95%   59.53%   -1.42%     
==========================================
  Files          83       83              
  Lines       20003    21141    +1138     
  Branches     2983     3137     +154     
==========================================
+ Hits        12193    12587     +394     
- Misses       5782     6444     +662     
- Partials     2028     2110      +82     

☔ 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.

@sbryngelson
sbryngelson marked this pull request as draft July 17, 2026 13:47
…PU divergence)

The overheated-vapor/subcooled-liquid pT shortcut produced states O(1) different from the pTg equilibrium, selected by a sub-ULP near-tie comparison (TSSL < TSatSL*(1-eps)). Backend rounding differences flipped the branch, inverting the phase and diverging CPU vs GPU on all model-6 cases. pTg's Newton projects the liquid mass onto [0,mT], so it recovers the single-phase limits itself; the shortcuts are removed and pTg is solved unconditionally. Deletes now-dead s_TSat and the saturation-curve coefficients. All 18 phase-change goldens pass CPU and GPU (machine-zero agreement); 6 model-6 goldens regenerated, model-5 unchanged.
…ewton-convergence

# Conflicts:
#	src/common/m_phase_change.fpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants