microdiff_run.f
204 lines · 1 definitions · on GitHub (7be4133)
Python version: microdiff_run.py, 166 lines, on the right (switch to Python above the code).
Purpose
Diffuses one species over one time step on the equally spaced grid, for MICRODIFF, and returns the change in its abundance. The comments describe the diffusion equation as two terms: one with $d\ln P/dr$ and the abundance gradient (solved explicitly, by the two-step Lax–Wendroff scheme of Numerical Recipes), and one with the second derivative of the abundance (solved implicitly).
How it fits
- Inputs: the equal grid and midpoint variables from MICRODIFF_MTE: mass
EM, radiusER, density, temperature, $d\ln P/dr$, $\nabla$ and the three-species abundancesESPEC(3,·); the spacingDR;DTandSTOTin Bahcall–Loeb units; and the species' weightTHEA, chargeTHEZand columnJ(1 for hydrogen, 3 for metals or a light element). - Who calls it: only
MICRODIFF, once per diffused species. - What it calls: MICRODIFF_COD (the coefficients, twice), LAX_WENDROF1 and LAX_WENDROF2 (the explicit term), GET_IMP_DIFFCO (the matrix) and TRIDIAG_GS (its solution).
- Output:
ESPEC(J,·)is overwritten with the change $X^\mathrm{new}_i - X^\mathrm{old}_i$;EDXDR(the hydrogen concentration gradient, filled on the hydrogen call) is kept for later calls. A line per iteration goes toISHORT, and a warning toIOWRandISHORTif the iteration does not converge. - Modules:
NMLPAR_MOD(GRTOL,NITER_GS),CONSTANTS_MOD($4\pi$,C4PI),IOUNITS_MOD(ISHORT,IOWR) andDIMENSIONS_MOD(JSON). The unused/THEAGE/ofrp_1is no longer declared.
The method
Let $X_i$ be the abundance at grid point $i$ (radius $r_i$, mass $m_i$), and $X_{i+1/2}$ at the midpoints.
- Coefficients at the points.
MICRODIFF_CODgives the advective coefficient $C_{1,i}$ (ECOD1) and the diffusion coefficient $C_{2,i}$ (ECOD2). -
Lax–Wendroff, first half step (
LAX_WENDROF1), the change at the midpoints: $$\Delta X_{i+1/2} = \frac{4\pi\,\Delta t}{2}\;\frac{C_{1,i+1} - C_{1,i}}{m_{i+1} - m_i},$$ where the first cell takes $C_{1,1} = 0$ and $m_1 = 0$, and the last cell $C_{1,N} = 0$ and $m_N =$STOT, so the first and last cells reach to the centre and the surface. -
Coefficients at the midpoints, from the half-step abundances $X_{i+1/2} + \Delta X_{i+1/2}$.
-
Lax–Wendroff, full step (
LAX_WENDROF2), at the points: $$X'_i = X_i + 4\pi\,\Delta t\;\frac{C_{1,i+1/2} - C_{1,i-1/2}}{m_{i+1/2} - m_{i-1/2}},$$ with $C_{1,1/2} = m_{1/2} = 0$ at the first point, and $C_{1,N+1/2} = 0$, $m_{N+1/2} =$STOTat the last. -
The implicit term. With $\alpha_i = 4\pi\,\Delta t / (\Delta r\, \Delta m_i)$ (
ALPHA, where $\Delta m_i = m_{i+1/2} - m_{i-1/2}$, from the centre for $i=1$ and toSTOTfor $i=N$),GET_IMP_DIFFCObuilds andTRIDIAG_GSsolves $$X_i - \alpha_i\left[C_{2,i+1/2}\,(X_{i+1}-X_i) - C_{2,i-1/2}\,(X_i - X_{i-1})\right] = X'_i .$$ The loop repeats until the largest change between iterations is belowGRTOL, at mostNITER_GStimes.The comments name the elements of an older use. They speak of "the original run of light element mass fractions," "the new run of LI," and "the corrections to the hydrogen abundance" (line 82↗; line 172↗; lines 176–177↗), but
EDis loaded fromESPEC(J,·)(line 80↗) for whichever speciesJthe caller passes, so they could say "the diffused species." -
Result: $\Delta X_i = X^\mathrm{new}_i - X_i$ in
ESPEC(J,·).
Design
The change rather than the new abundance is returned, as the comments explain, so that the interpolation back to the model grid adds less error. The first and last cells hold the masses of the convective core and envelope, whose changes MICRODIFF_ETM then applies to every point inside them.
From YREC's documentation
Physics Options: GRTOL (default 1.0D-8) is the numerical tolerance on the abundance changes from diffusion, and NITER_GS (10) the largest number of iterations of the solver.
Suspected bugs, checked by running the code
Each bug is marked by what running the code showed: (changes results, large) or (small), the fix moving a run clearly beyond a null change; (stops the run); (wrong output), the fix changing only what is written; (harmless in the runs), within the noise; (never occurred), the code ran but the fault did not; (not reached); (cannot be reached), a guard prevents it; (withdrawn), the suspicion was wrong.
1. (never occurred) With NITER_GS = 0, the implicit term is skipped and the warning prints old values.
(line 152↗; lines 178–196↗) With NITER_GS = 0 the loop never runs: The implicit (diffusive) term is not applied at all, and the warning "MICRODIFF FAILED TO CONVERGE" prints DXMAX and IMAX left from an earlier call (all locals are SAVEd). Only with NITER_GS = 0 (the default is 10).
A possible fix: reject NITER_GS < 1 where the namelist is read, or set DXMAX = 0.0D0 and IMAX = 0 before the loop so that the message is at least true.
Checked by running YREC (Nov 9, 2026; a line added at each suspected path counted how often it was taken, the outputs otherwise unchanged): The loop was entered at every settling call in the seven runs with settling (testsuite/Test_solarGS98_dif, testsuite/Test_solarAAG21_dif, testsuite/Test_solarM22M_difkur, testsuite/Test_solarGS98_difrotmixper18p4d1p0, testsuite/Test_m0p8feh+0p0GS98_rotp8p0d0p1mag1e4std_TAMS, testsuite/Test_m1p4feh+0p0GS98_dif_TAMS, and testsuite/Test_solarAAG21_dif with a custom mixture), up to 18,515 times per run, always with NITER_GS = 10. Not reached: It needs NITER_GS = 0, and every case sets 10. No fix was run, since the page's fixes only stop the run or correct the message. Intent: none found.
2. (harmless in the runs) The convergence test is one-sided.
(lines 178–190↗) DXMAX takes the largest signed change ED(I)-ED_P(I), starting from zero, so negative changes are never seen, in the test or in the printed line. Correct today, since the second iteration reproduces the first (question 1); it would end the loop too early if the iterations changed anything.
A possible fix:
microdiff_run.f
@@ lines 177–184 @@
C ENOUGH TO EXIT.
DXMAX = 0.0D0
IMAX = 0
+C 11/09/26 KC via Claude
+C code changed to test the size of the change, not its signed value.
DO I = 1,NPT
- DX = ED(I)-ED_P(I)
+ DX = ABS(ED(I)-ED_P(I))
IF(DX.GT.DXMAX)THEN
DXMAX = DX
IMAX = I
Checked by running YREC (Nov 9, 2026; a line added at each suspected path counted how often it was taken, the outputs otherwise unchanged): The test ran in all seven runs with settling, 4,496 to 22,220 times per run, never more than two iterations a call. In 14 to 259 first iterations per run, the largest signed change was below GRTOL while the largest change in size, $2.0\times10^{-7}$ to $1.87\times10^{-6}$, was not, so the loop ended one iteration early. With the fix, testsuite/Test_m1p4feh+0p0GS98_dif_TAMS and testsuite/Test_solarM22M_difkur give identical tracks and stored models; the .short file differs beyond 2% (presumably its iteration lines; not checked). This agrees with the above: The second iteration repeats the first. Intent: none found.
3. (cannot be reached) The midpoint change is shifted by one point.
(lines 155–157↗) ED_H(I) = 0.5*(ED(I)+ED(I-1)-…) for I = 2..NPT assigns to midpoint I the mean of points I-1 and I, while in MICRODIFF_MTE midpoint I lies between points I and I+1 (microdiff_mte.f, lines 85–93↗); midpoint 1 keeps its value from LAX_WENDROF1. No effect today, since EQCOD2X_H is zero (question 1), but it would matter if EQCOD2X_H were used.
A possible fix:
microdiff_run.f
@@ lines 152–159 @@
DO 100 ITER=1,NITER_GS
C FIND CHANGE IN D AT THE ZONE MIDPOINTS, GIVEN CHANGE IN D AT
C THE ZONE CENTERS.
- DO I = 2,NPT
- ED_H(I) = 0.5D0*(ED(I)+ED(I-1)-ED_P(I)-ED_P(I-1))
+C 11/09/26 KC via Claude
+C code changed to put the change between points I and I+1 at
+C midpoint I, as MICRODIFF_MTE places the midpoints.
+ DO I = 1,NPT-1
+ ED_H(I) = 0.5D0*(ED(I+1)+ED(I)-ED_P(I+1)-ED_P(I))
ENDDO
C STORE CURRENT RUN OF ABUNDANCES IN VECTOR ED_P. THE ITERATION
C LOOP IS COMPLETED ONCE ED - ED_P IS EVERYWHERE LESS THAN THE TOLERANCE
Checked by running YREC (Nov 9, 2026; a line added at each suspected path counted how often it was taken, the outputs otherwise unchanged): The midpoint changes were computed 4,496 to 22,220 times per run in all seven runs with settling, and EQCOD2X_H was zero every time. It cannot matter at 7be4133, since EQCOD2X_H is set to zero just before every use (lines 162–165↗); what remains is fragile code. The fix was not run. Intent: The comment there says EQCOD2X_H "IS ONLY USED IN GRSETT (OLD SETTLING ROUTINE), BUT RETAINED FOR LEGACY" (lines 160–161↗).
Definitions
subroutineMICRODIFF_RUN
One species, one step: explicit two-step Lax–Wendroff for the advective term, then an implicit tridiagonal solve for the diffusive term (equations above). Returns the change in abundance.
Uses modules CONSTANTS_MOD, DIMENSIONS_MOD, IOUNITS_MOD, NMLPAR_MOD
Called by MICRODIFF ×3
Flowchart of its calls, in order (6)
Change log
The changes since rp_1
Changed since rp_1. Declarations only: COMMON blocks to modules, IMPLICIT NONE.