Skip to content

mrw - #196

Open
cpinte wants to merge 11 commits into
mainfrom
mrw
Open

mrw#196
cpinte wants to merge 11 commits into
mainfrom
mrw

Conversation

@cpinte

@cpinte cpinte commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Type of PR:
Bug fix / modification to existing code

Description:
Modified Random Walk (MRW) for the temperature calculation

Adds an optional Modified Random Walk (Min et al. 2009, Robitaille 2010) to the
Monte Carlo temperature calculation: in optically thick cells, packets move by
diffusion steps to the cell wall instead of individual interactions. Energy is
deposited with a dB/dT-weighted absorption opacity, consistent with the
Bjorkman & Wood re-emission.

  • Options: -mrw [gamma] or -gamma_mrw gamma (default gamma = 5), -no_mrw.
    Off by default.
  • Works on cylindrical, spherical and Voronoi grids; LTE grains only (switched
    off with non-equilibrium grains or PAHs).
  • Pinte et al. 2009 benchmark, temperature step: 2.6-3.3x faster at
    tau = 1e5-1e6, 2.4x at tau = 1e9. Temperatures agree with standard MC
    within noise, except the last midplane cells before the dark zone (2-4 % low
    at gamma = 5). With -diff_approx, this offset propagates into the dark zone
    (-0.7 to -1.8 % on average): use gamma = 10 or no MRW for benchmark-quality
    dark-zone temperatures.
  • Unit tests (random-walk sampling, wall distances on all grids, mean
    opacities) and integration tests against standard MC.

Testing:
Updated unit and integration tests

Did you run the botscheck that the code and comments follow the code of conduct? yes

Did you update relevant documentation in the docs directory? yes

- MRW: fix make_MRW_step overwriting the packet y coordinate with
  sample_zeta; return the diffusive path length ct and an outward
  Lambertian surface direction around the sphere normal. Pre-invert
  y_MRW(zeta) by bisection using the Jacobi theta_4 identity for
  y > 0.5 (O(1) lookup, avoids cancellation and endpoint 0/0 or log(0))

- cylindrical_grid: fix sin_phi_lim = 1e300 sentinel at phi = pi/2,
  3*pi/2; fix k0 = 1 out-of-bounds read in azimuthal wall distances and
  guard phi walls with l3D .and. n_az > 1; do not treat z = 0 as a cell
  wall in 2D midplane cells (j = 1); guard virtual cells

- spherical_grid: use cos_theta_lim(abs(thetaj0)) instead of
  cos_phi_lim(thetaj0) (wrong array and out-of-bounds for nz > n_az or
  3D z < 0 cells); handle polar-cap (abs(j) = nz) and 2D midplane cells

- Voronoi: divide bisector distance by norm2(n) instead of |n|^2 (AU
  units) in double precision; return 0 on/outside faces, in star cells,
  and bound by stellar surfaces in star-neighbour cells

- random_numbers, test_mrw: add init_random_streams helper and unit
  tests for the MRW kernel and nearest-wall distances against cross_cell
  ray sweeps on 2D/3D cylindrical, spherical, and 3D Voronoi grids
- Pre-tabulate Rosseland transport mean extinction chi_Rosseland_MRW(T, p_icell)
  and dB/dT-weighted mean absorption opacity kappa_abs_MRW(T, p_icell) in
  init_reemission (OpenMP-parallel init_MRW_opacities), ensuring exact single-pass
  Bjorkman & Wood energy balance and automatic refresh across hydrostatic,
  sublimation, and Phantom iterations

- Add compute_MRW_opacities and eval_MRW_opacities_at_T with log-log temperature
  interpolation, applying kappa_factor(icell) to chi_trans_cell while keeping
  kappa_abs_ref in save_radiation_field's reference convention for xKJ_abs

- Allocate and populate cell-averaged scattering asymmetry tab_g_pos during Step 1
  for both full phase-function (aniso_method=1) and HG (aniso_method=2) modes,
  respecting lisotropic, lno_scattering, lphase_function_file, and empty cells

- Initialize frac = 0.0_dp in Temp_LTE below the T_min floor and guard MRW table
  deallocation in deallocate_temperature_calculation and deallocate_thermal_emission

- Extend test_suite/unit/test_mrw.f90 (88 assertions) to verify grey, anisotropic,
  analytical power-law (beta=2), kappa_factor/ldens0, and opacity-refresh cases
… options

- Declare logical :: lMRW in parameters.f90 (default .false. in init_mcfost.f90).

- Add CLI parsing for -mrw [gamma], -no_mrw, -no-mrw, -gamma_mrw, and -mrw_gamma.

- Condition MRW table allocations on (lRE_LTE .and. lMRW) in thermal_emission.f90
  and tab_g_pos on ((aniso_method == 2) .or. (lTemp .and. lMRW)) in mem.f90.

- Wire active MRW loop into propagate_packet (src/dust_transfer.f90):
  * Gating checks: lMRW, letape_th, lonly_LTE, .not. lmono, .not. lxJ_abs_step1,
    .not. flag_star, .not. flag_ISM, n_iteractions_in_cell >= 3, valid icell.
  * Jump radius safety margin (1 - 1e-5) * d to guarantee interior containment.
  * Absorption deposition xKJ_abs += kappa_abs_ref * ct * Stokes(1) matching
    save_radiation_field conventions.
  * Live opacities update during diffusion stepping via compute_MRW_opacities.
  * Exit state: LTE re-emission wavelength sampling, Stokes depolarisation
    (Q=U=V=0), flag resets, and fresh optical depth generation.

- Add test_mrw_transport() in test_suite/unit/test_mrw.f90 covering gating,
  wall safety across geometries, energy deposition, cell counting, exit
  depolarisation, and a 3D transport acceleration benchmark (122/122 assertions pass).
…arks

- Add test_suite/test_mrw.py covering CLI flag validation (-mrw, -no_mrw,
  -gamma_mrw), temperature accuracy (MC_similar within 0.03%), and ray-traced
  thermal SED consistency.

- Add -gamma_mrw <gamma> description to display_help() in src/init_mcfost.f90.

- Validate temperature profiles against published benchmark codes (MCMax,
  TORUS, MCFOST 2009) with < 1.1% median differences and < 2.6% in vertical cuts.

- Benchmark runtime scaling across tau in [1e3, 1e6], demonstrating up to
  4.1x speedup at tau=1e6 while preserving 0.2% temperature fidelity.

- Verify full test suite passes under DEBUG=yes with -fbounds-check enabled.
…r positions

- tab_g_pos keeps the dust model g(lambda) when a phase function file is
  given (the file is monochromatic, for images): a single g from the file was
  used at all wavelengths, also changing HG runs without MRW

- distance_to_closest_wall_Voronoi: neighbour positions from the ordered
  cache or Voronoi_xyz, as in cross_Voronoi_cell, instead of the Voronoi
  derived type
…not trigger

Pre-compute per cell an upper bound of the MRW transport opacity over the
temperature grid (chi_max_MRW, 0 when d_max * chi_max <= gamma_MRW with
d_max the radius of the volume-equivalent sphere). The temperature and the
MRW opacities are only evaluated when d * chi_max_MRW > gamma_MRW.
Temperatures are bit-identical; temperature step 8-15 % faster
(Pinte 2009 tau=1e4, 1.28e6 packets, 1 thread).

MRW: default gamma = 5, reproducible optically thick benchmark test

gamma = 2 biases the temperature low by 6-9 % in the hot midplane cells
just inside the dark zone (Pinte 2009 tau = 1e4 to 1e6), gamma = 5 reduces
this to 2-4 % while keeping a 2.6-3.3x speed-up over standard MC at
tau >= 1e5. The benchmark test now runs with a fixed seed on 1 thread.
…le_zeta

sample_zeta checked an unsynchronised flag at every sample. The table is
initialised in transfert_poussiere and now also in init_MRW_cells, which
covers library mode.
…zeta

The table of Min et al. (2009) Eq. 7 is only built in init_MRW_cells
(standalone and library mode, only with -mrw): no longer computed in runs
without MRW. sample_zeta stops with an error if the table is not
initialised instead of returning an infinite step time. The unit tests
calling make_MRW_step initialise the table themselves.
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

Code Review by Gemini

Summary of Review

The pull request introduces the Modified Random Walk (MRW) algorithm (Min et al. 2009; Robitaille 2010) to accelerate Monte Carlo temperature calculations in optically thick grid regions across cylindrical, spherical, and 3D Voronoi geometries.

Overall, this is an exceptionally high-quality PR:

  • Physical Correctness: Accurately implements the $dB/dT$-weighted Rosseland transport mean extinction ($\chi_{\text{trans}}$) and absorption opacity ($\kappa_{\text{abs, dB/dT}}$) consistent with the Bjorkman & Wood (2001) re-emission scheme.
  • Numerical Stability: Uses the Jacobi $\theta_4$ Poisson summation identity for $y &gt; 0.5$ in evaluating the exit probability $P(t)$, eliminating numerical cancellation as $y \to 1$.
  • Parallel & Memory Safety: OpenMP loops and live energy deposition into xKJ_abs and xN_abs strictly preserve thread safety using thread-indexed data structures (id).
  • Bug Fixes: Fixes pre-existing bugs in distance_to_closest_wall_sph (which previously misindexed cos_phi_lim with theta indices) and distance_to_closest_wall_Voronoi (which previously omitted the vector norm in the bisector distance denominator).
  • Verification: Includes extensive unit tests (test_mrw.f90) and Python integration tests (test_mrw.py).

Detailed Findings

1. Numerical Stability & Zeta Sampling

  • Function: cumulative_zeta_from_y & initialize_cumulative_zeta (src/MRW.f90)
  • Assessment: The dual-series representation (direct series for $y \le 0.5$ and Jacobi $\theta_4$ transformation for $y &gt; 0.5$) ensures full double-precision accuracy without loss of precision as $y \to 1$. The $O(1)$ inverse lookup table interp_y_from_zeta clamps inputs away from 0 and 1, ensuring -log(y) remains finite and strictly positive.

2. Grid Geometry & Nearest-Wall Distance

  • Files: src/cylindrical_grid.f90, src/spherical_grid.f90, src/Voronoi.f90
  • Assessment:
    • distance_to_closest_wall_sph now correctly uses cos_theta_lim(ja) with $1 \le j_a \le n_z$, resolving array out-of-bound errors on 3D or fine-theta spherical grids.
    • distance_to_closest_wall_Voronoi correctly calculates exact Euclidean distance dot_product(n, p - r) / norm_n to bisector planes and handles stellar surface boundaries.
    • All coordinate distance functions safely return 0.0_dp for boundary/ghost cells, disabling MRW at domain edges where path diffusion approximations are invalid.

3. OpenMP & Thread Safety

  • Files: src/thermal_emission.f90, src/dust_transfer.f90
  • Assessment:
    • init_MRW_opacities uses OpenMP collapse(2) with explicit private loop variables and shared read-only arrays.
    • Photon propagation during MRW steps deposits energy via xKJ_abs(icell, id) and updates packet counters xN_abs(icell, 1, id), where id is the thread index, eliminating race conditions.

Conclusion

The PR is mathematically sound, numerically stable, thread-safe, and thoroughly tested. It is safe to merge.

The MRW exit time now uses an absorbing boundary at R0 + 0.7104/chi (Milne
extrapolation length) and the exit direction follows the exact sphere walk
(ballistic escapes, forward peak, mu (1 + 1.5 mu) for large spheres).

Steps are no longer chained within a cell: a chained step loses the outward
direction of the packet and, with the extrapolated boundary, over-deposits
(+16 % in energy at gamma 2 in a grey slab). Pinte 2009 tau 1e5-1e6: band
temperature bias -7 % -> -2 % at gamma 2, -2.7 % -> within noise at gamma 5,
same run time. -mrw_chain <0|1|2> kept for testing. Unit tests: exit cosine
and mean path against an exact walk at gamma 2, 5, 10.
cross_spherical_cell: at a midplane crossing (lower theta wall of |thetaj| = 1,
z = 0 in double precision) the hemisphere was taken from the sign of z1, zero up
to rounding; it now follows the direction of the packet, and z1 is placed on
that side. New unit check (failed on gfortran and ifort with the old code).

MRW unit test: run single-threaded (nb_proc = 1 sized arrays were overrun by
the OpenMP loop of init_reemission: intermittent heap corruption on macOS arm).
Exit-law peak fraction refitted (exit_peak_fraction, <mu> within 0.0015 of the
exact walk for gamma 2-20). test_mrw.py: compiler-independent check of the
gamma output, no 120 s timeout (macOS Intel runners).
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Code Review by Gemini

Physical / Numerical Purpose

The changes introduce a Modified Random Walk (MRW) acceleration scheme (Min et al. 2009; Robitaille 2010) to the Monte Carlo thermal equilibrium calculation. In optically thick cells ($\tau \gg 1$), packet transport by individual scattering steps is replaced by macroscopic diffusion steps inside spheres bounded by the nearest cell wall. Deposited energy is accumulated continuously using $dB/dT$-weighted Planck mean absorption opacities ($\kappa_{\text{abs,MRW}}$), and exiting packets are resampled at the local updated dust temperature with an anisotropic emergent-flux exit distribution ($p(\mu)$) and Chandrasekhar extrapolation length ($z_{\text{extrap}} = 0.7104$).


Code Review Findings

1. High-Performance & Parallel Computing

  • Thread Safety (dust_transfer.f90, MRW.f90, thermal_emission.f90):
    • Random number generation inside make_MRW_step and sample_exit_direction uses thread-indexed SPRNG streams (stream(id)), avoiding RNG race conditions across OpenMP threads.
    • Energy deposition (xKJ_abs(icell, id)) and cell hit counters (xN_abs(icell, 1, id)) are accumulated per thread id, maintaining OpenMP race-freedom.
  • Cache Locality (thermal_emission.f90, mem.f90):
    • Arrays chi_Rosseland_MRW(n_T, p_Nc) and kappa_abs_MRW(n_T, p_Nc) are allocated with n_T as the fast (first) dimension. Inner loops over temperature (do T = 1, n_T) access contiguous memory columns, adhering to Fortran column-major order.
  • OpenMP Parallelization (thermal_emission.f90):
    • The init_MRW_opacities loop is cleanly parallelized over (icell, T) via !$omp parallel do collapse(2) with all thread-local scalars declared private.

2. Numerical Precision & Stability

  • Poisson Summation Identity (MRW.f90: cumulative_zeta_from_y):
    • Replacing the truncated power series for $y &gt; 0.5$ with the Jacobi $\vartheta_4$ Poisson summation identity ($\sum_k \exp(-\pi^2 (k+1/2)^2 / (-\ln y))$) eliminates catastrophic numerical cancellation as $y \to 1$.
  • Floating-Point Literals:
    • Double-precision constants and literals are consistently specified using _dp (e.g. 5.0_dp, 0.7104_dp, 1.0e-5_dp), preventing accidental single-precision implicit conversions.
  • Division by Zero & Boundary Protections:
    • Proper guards are in place for zero total opacity (chi_trans_wl > tiny_dp), zero cell volume, zero normal norms (norm_n <= tiny_dp), and edge cases where $y \to 0$ or $y \to 1$.

3. Grid Geometry & Radiative Transfer Physics

  • Exact Nearest-Wall Distances (cylindrical_grid.f90, spherical_grid.f90, Voronoi.f90):
    • Cylindrical/Spherical: Bounds distances accurately against radial, elevation cone, and azimuthal plane boundaries. Correctly handles 2D midplane cell symmetry (s4 = huge(1.0_dp) for $j=1$).
    • 3D Voronoi: Distance to bisector planes is computed via signed projection $(p - r) \cdot \hat{n}$. Cells on outer domain boundaries (id_n <= 0), cut cells, or star-containing cells safely return 0.0_dp to disable MRW near boundaries.
    • Coordinate Fix: Fixed a bug where sin_phi_lim evaluated to 1.0d300 instead of $\pm 1$ at $\phi = \pi/2$ or $3\pi/2$.
  • Hemisphere Transition Fix (spherical_grid.f90):
    • Midplane crossings at $z \approx 0$ assign hemisphere index thetaj1 based on direction cosine sign (w < 0), avoiding grid mapping lockup near boundary cones.

4. Testing & Verification

  • Extensive Fortran unit test suite (test_suite/unit/test_mrw.f90) verifies:
    • Analytical diffusion kernel properties and moments ($\langle -\ln y \rangle = \pi^2 / 6$).
    • Nearest-wall distances against 1000-ray sweeps and sphere containment on 2D/3D Cylindrical, Spherical, and Voronoi grids.
    • $dB/dT$-weighted mean opacities against analytical Riemann-zeta solutions ($\beta = 2$ power-law opacity).
  • Pytest suite (test_suite/test_mrw.py) validates CLI flags, temperature accuracy, and SED consistency against standard Monte Carlo.

Recommendations / Minor Suggestions

  1. Thread-Safety Guard for Lookup Table Initialization (src/MRW.f90):
    In initialize_cumulative_zeta(), lzeta_initialized is a saved variable:
    logical, save :: lzeta_initialized = .false.
    init_MRW_cells() currently calls this sequentially during pre-computation before transport starts. To guard against potential future invocation inside a parallel region, consider adding an $omp single or if (lzeta_initialized) return short-circuit before any table writes.

Summary & Conclusion

  • Overall Assessment: Excellent, high-quality PR. The mathematical derivation, parallel execution, numerical safeguards, and grid geometry logic are rigorous and complete.
  • Key Strengths: Poisson summation identity for $\zeta(y)$, comprehensive distance-to-wall calculations across all grid geometries, and exemplary Fortran unit test coverage.
  • Merge Recommendation: Safe to merge.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

ERROR: # Code Review by Gemini
An unexpected error occurred in Client call: 503 UNAVAILABLE. {'error': {'code': 503, 'message': 'This model is currently experiencing high demand. Spikes in demand are usually temporary. Please try again later.', 'status': 'UNAVAILABLE'}}

The MRW step assumes an isotropic start with the re-emission spectrum.
After a scattering by forward-scattering dust the packet still carries
the direction of the net flux over about one transport mean free path,
which an isotropic restart loses (-10 % in energy, -2.4 % in T at
gamma 2 in an irradiated slab with live temperatures; -0.2 % with the
new trigger). The trigger now also requires .not. flag_scatt.
-mrw_after_scatt restores the old trigger (testing).

Pinte 2009 disc, gamma 2, temperature bias (band mean inside the
dark-zone edge): -2.0 -> +0.1 % (tau 1e5), -1.8 -> +0.9 % (tau 1e6,
6 seeds); gamma 5 unchanged. Run time of the temperature calculation
+3-10 % at gamma 2, unchanged at gamma 5.
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Code Review by Gemini

Here is a code review for the Modified Random Walk (MRW) pull request.


Overall Assessment

The PR introduces a Modified Random Walk (MRW) implementation to accelerate Monte Carlo radiative transfer in optically thick regions, along with updates to wall-distance algorithms across cylindrical, spherical, and Voronoi grids. The core Fortran MRW logic and numerical transport routines are well-designed and covered by comprehensive unit/integration tests.

However, there are a few issues—most notably a truncated CMakeLists.txt file, missing/misnamed C++ header files, and a loop-index bug when reallocating star arrays in hydro readers that overwrites existing star accretion properties.


Key Findings & Action Items

1. Truncated CMake File

  • Severity: CRITICAL
  • File: src/CMakeLists.txt (line 40)
  • Problem: src/CMakeLists.txt is incomplete and ends abruptly on line 40 with set(CMAKE.
  • Impact: Any build attempting to configure using CMake will fail immediately with a syntax error.
  • Suggested Fix: Complete or restore the CMakeLists.txt file before merging.

2. Missing C++ Header (mcfost_interface.hpp) and Misnamed Source File

  • Severity: HIGH
  • Files: src/interface_template.cpp (line 1), src/mcfost2hydro.cpp (lines 1–2)
  • Problem:
    1. interface_template.cpp includes "mcfost_interface.hpp", but this header file is missing from the repository diff.
    2. src/mcfost2hydro.cpp starts with header guards (#ifndef MCFOST_INTERFACE_HPP ... #define MCFOST_INTERFACE_HPP) and contains standard header definitions for namespace MCFOST, but has a .cpp file extension.
  • Impact: C++ builds or interface consumers will fail to compile due to missing header files.
  • Suggested Fix: Rename src/mcfost2hydro.cpp to src/mcfost_interface.hpp (or add the missing mcfost_interface.hpp header) and adjust the include path accordingly.

3. Overwriting Existing Star Properties During Reallocation in Hydro Readers

  • Severity: HIGH (Correctness / Data Corruption)

  • Files: src/read_fargo3d.f90, src/read_athena++.f90, src/read_pluto.f90

  • Problem: When reallocating the star array for new sink particles, the loop index for initializing new stars starts at n_stars_old instead of n_stars_old + 1:

    ! Saving if the accretion rate was forced
    allocate(star_old(n_stars_old))
    if (allocated(star)) then
       star_old(:) = star(:)
       deallocate(star)
    endif
    allocate(star(n_stars))
    do i=1, min(n_stars, n_stars_old)
       star(i)%force_Mdot = star_old(i)%force_Mdot
       star(i)%Mdot = star_old(i)%Mdot
    enddo
    ! If we have new stars
    do i=n_stars_old,n_stars   ! <--- BUG: starts at n_stars_old instead of n_stars_old + 1
       star(i)%force_Mdot = .false.
       star(i)%Mdot = 0.
    enddo
    deallocate(star_old)
  • Impact: For i = n_stars_old, the second loop immediately overwrites the force_Mdot and Mdot properties preserved in the first loop. (Note that src/density.f90 correctly uses do i=n_stars_old+1, n_stars).

  • Suggested Fix: Change the loop lower bound in read_fargo3d.f90, read_athena++.f90, and read_pluto.f90:

    do i = n_stars_old + 1, n_stars
       star(i)%force_Mdot = .false.
       star(i)%Mdot = 0.0
    enddo

4. Unintended Planet Initial Velocity in Athena++ Reader

  • Severity: MEDIUM (Physical Validity)

  • File: src/read_athena++.f90

  • Problem: In read_athena++.f90, the vertical velocity component vz for the hardcoded planet properties was changed to 1.0:

    - real, parameter :: vx=0.0, vy=1.0, vz=0.0 ! circular orbit in the midplane (vz was 1)
    + real, parameter :: vx=0.0, vy=1.0, vz=1.0
  • Impact: Giving a midplane planet ($z=0$) a non-zero vertical velocity $v_z = 1.0 \times u_{\text{velocity}}$ ($\approx 30\text{ km/s}$) launches the planet out of the midplane, contradicting the inline comment and standard midplane circular orbit assumption.

  • Suggested Fix: Revert vz to 0.0_dp:

    real, parameter :: vx=0.0_dp, vy=1.0_dp, vz=0.0_dp

5. OpenMP Thread Safety in initialize_cumulative_zeta

  • Severity: LOW
  • File: src/MRW.f90 (lines 74–105)
  • Problem: initialize_cumulative_zeta uses logical, save :: lzeta_initialized = .false. and modifies shared module variables y_MRW and zeta.
  • Impact: While initialize_cumulative_zeta is currently invoked during serial setup (init_MRW_cells), if it is ever called from inside an OpenMP parallel region, simultaneous initialization attempts by multiple threads will result in a data race on y_MRW and zeta.
  • Suggested Fix: Guard initialization with an OpenMP critical section or single construct, or ensure initialization occurs during serial startup before parallel loops execute.

Summary

The PR introduces a valuable performance feature (Modified Random Walk) with strong unit and integration test coverage. Once src/CMakeLists.txt, the C++ header naming, and the hydro reader star loop indices are corrected, the PR should be ready to merge.

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.

1 participant