Skip to content

Conversation

@hyeoksu-lee
Copy link
Contributor

@hyeoksu-lee hyeoksu-lee commented Dec 12, 2025

User description

Description

This PR refactors the initialization process of sub-grid bubble models (EE and EL) as well as removing the assumption on normalization with R0ref. It involves many things as listed below, but the overall goal is to reduce redundancy, inconsistency and assumptions.

Key changes include:

  1. EE and EL bubbles have independent initialization subroutines and especially EE bubbles with non-polytropic process have so many redundant initialization codes over multiple files. Now they are cleared and both EE and EL bubbles share common initialization subroutines in common/m_helper.fpp.

  2. Some of bubble parameters are defined as fluid_pp% and dimensional, while some others such as Ca, Web, and Re_inv are not fluid_pp and non-dimensional. Furthermore, EL bubbles require reference values as inputs (lag_params%) since non-dimensional quantities are computed inside the code. EE bubbles with non-polytropic process requires pref and rhoref. All of these kind of random input parameters are re-organized as bub_pp% which are shared by EE and EL bubbles.

  3. bub_pp% parameters can be either dimensional or non-dimensional. These quantities should follow the normalization used for other input parameters, except for molecular weights bub_pp%M_v and bub_pp%M_g . Thus, all the input parameters, except for bub_pp%M_v and bub_pp%M_g, should be consistent now.

Minor changes include:

  1. Related examples and tests case files are updated accordingly. In current master, 1D_exp_bubscreen arbitrarily set Ca = 1, which is inconsistent with other parameters. This is corrected and thus the golden file is updated.

  2. Some of EE bubbles routines are refactored and a couple of checkers for non-physical behaviors are added

  3. Subscript _n indicating gas content inside bubbles is replaced with _g.

Issue to be fixed in next PR:

  1. I think Lagrange bubble has a bug due to mixed use of _n and _v. Let me demonstrate this using one of example cases 3D_lagrange_sphbubcollapse. This case's input includes
"fluid_pp(1)%gamma_v": gamma_v,
"fluid_pp(2)%gamma_v": gamma_g,

As usual, 1st fluid is the host liquid and 2nd fluid is the gas content in EL bubble. However, s_start_lagrange_inputs reads the input parameters incorrectly as below

        id_bubbles = num_fluids 
        id_host = num_fluids - 1

        gamma_v = fluid_pp(id_bubbles)%gamma_v
        gamma_n = fluid_pp(id_host)%gamma_v

And in the other parts of the EL code, _n is still treated as gas and _v as vapor. I think this is an obvious bug. But fixing this bug requires to update the golden file, which hinders validating changes introduced in this PR. I will fix this once this PR is merged.

  1. Examples 1D_qbmm and 1D_poly_bubscreen do not have any acoustic source although they are in a bubble screen setup. When I add an acoustic source, 1D_qbmm crashes (even with the master branch).

Fixes #(issue) [optional]

Type of change

Please delete options that are not relevant.

  • Something else

Scope

  • This PR comprises a set of related changes with a common goal

If you cannot check the above box, please split your PR into multiple PRs that each have a common goal.

How Has This Been Tested?

Please describe the tests that you ran to verify your changes.
Provide instructions so we can reproduce.
Please also list any relevant details for your test configuration

  • test suite passed on MacBook M4 Pro
  • 1D bubble screen comparing dimensional and non-dimensional runs

Keller-Miksis + Non-polytropic
t0

Keller-Miksis + Polytropic
t0

Rayleigh-Plesset + Non-polytropic
t0

Rayleigh-Plesset + Polytropic
t0

Test Configuration:

  • What computers and compilers did you use to test this: MacBook M4 Pro

Checklist

  • I have added comments for the new code
  • I added Doxygen docstrings to the new code
  • I have made corresponding changes to the documentation (docs/)
  • I have added regression tests to the test suite so that people can verify in the future that the feature is behaving as expected
  • I have added example cases in examples/ that demonstrate my new feature performing as expected.
    They run to completion and demonstrate "interesting physics"
  • I ran ./mfc.sh format before committing my code
  • New and existing tests pass locally with my changes, including with GPU capability enabled (both NVIDIA hardware with NVHPC compilers and AMD hardware with CRAY compilers) and disabled
  • This PR does not introduce any repeated code (it follows the DRY principle)
  • I cannot think of a way to condense this code and reduce any introduced additional line count

If your code changes any code source files (anything in src/simulation)

To make sure the code is performing as expected on GPU devices, I have:

  • Checked that the code compiles using NVHPC compilers
  • Checked that the code compiles using CRAY compilers
  • Ran the code on either V100, A100, or H100 GPUs and ensured the new feature performed as expected (the GPU results match the CPU results)
  • Ran the code on MI200+ GPUs and ensure the new features performed as expected (the GPU results match the CPU results)
  • Enclosed the new feature via nvtx ranges so that they can be identified in profiles
  • Ran a Nsight Systems profile using ./mfc.sh run XXXX --gpu -t simulation --nsys, and have attached the output file (.nsys-rep) and plain text results to this PR
  • Ran a Rocprof Systems profile using ./mfc.sh run XXXX --gpu -t simulation --rsys --hip-trace, and have attached the output file and plain text results to this PR.
  • Ran my code using various numbers of different GPUs (1, 2, and 8, for example) in parallel and made sure that the results scale similarly to what happens if you run without the new code/feature

PR Type

Enhancement, Tests


Description

  • Consolidated bubble model initialization by creating unified s_initialize_bubbles_model() subroutine in m_helper.fpp, eliminating redundant initialization code across EE and EL bubble implementations

  • Introduced new subgrid_bubble_physical_parameters derived type (bub_pp) to centralize and organize all bubble-related parameters, replacing scattered definitions across fluid_pp% and other structures

  • Replaced R0ref with Eu (Euler number) as the primary nondimensional parameter for bubble pressure calculations

  • Standardized nomenclature throughout codebase by replacing subscript _n with _g for gas-related variables (e.g., R_nR_g, mass_n0mass_g0, gamma_ngam_g)

  • Updated all example cases and test configurations to use the new bub_pp% structure with consistent parameterization

  • Refactored adaptive time-stepping logic in bubble dynamics with improved error checking and validation

  • Added validation checks for non-physical behaviors including negative bubble radius detection

  • Updated MPI communication across preprocessing, simulation, and post-processing modules to broadcast bub_pp% parameters

  • Simplified Lagrange bubble initialization by removing s_start_lagrange_inputs() subroutine

  • Updated QBMM module with normalized gas nomenclature and improved numerical stability checks


Diagram Walkthrough

flowchart LR
  A["Old Structure<br/>fluid_pp% + scattered<br/>bubble params"] -->|"Consolidate"| B["New bub_pp<br/>Structure"]
  C["Multiple Init<br/>Subroutines<br/>EE/EL"] -->|"Unify"| D["s_initialize_bubbles<br/>_model"]
  E["R0ref<br/>Normalization"] -->|"Replace"| F["Eu<br/>Normalization"]
  G["Subscript _n<br/>for gas"] -->|"Standardize"| H["Subscript _g<br/>for gas"]
  B --> I["Updated Examples<br/>& Tests"]
  D --> I
  F --> I
  H --> I
Loading

File Walkthrough

Relevant files
Enhancement
13 files
m_bubbles.fpp
Normalize bubble nomenclature and refactor adaptive time-stepping

src/simulation/m_bubbles.fpp

  • Replaced pref with Eu (Euler number) in bubble pressure calculations
  • Changed subscript _n to _g throughout for gas-related variables (e.g.,
    R_nR_g, mass_n0mass_g0)
  • Updated variable names from gamma_n, phi_vn, phi_nv to gam_g, phi_vg,
    phi_gv
  • Refactored adaptive time-stepping logic with improved error checking
    and tolerance comparisons
  • Added validation checks for negative bubble radius and improved error
    handling
+71/-46 
m_qbmm.fpp
Update QBMM module with normalized gas nomenclature           

src/simulation/m_qbmm.fpp

  • Updated variable references from mass_n0 to mass_g0 and _n subscripts
    to _g
  • Replaced verysmall with sgm_eps for numerical stability checks
  • Simplified temperature gradient calculation by removing intermediate
    T_bar variable
  • Updated thermal conductivity and mixture property calculations with
    new nomenclature
+12/-11 
m_assign_variables.fpp
Update variable assignment with bubble parameters structure

src/pre_process/m_assign_variables.fpp

  • Changed fluid_pp(1)%pv references to bub_pp%pv
  • Updated moment initialization with proper nondimensionalization using
    R0ref, p0ref, rho0ref
  • Fixed scaling factors for variance and covariance terms in moment
    calculations
+20/-11 
m_bubbles_EE.fpp
Refactor Euler-Euler bubble source computation with validation

src/simulation/m_bubbles_EE.fpp

  • Restructured bubble source term computation with improved conditional
    logic
  • Added validation check for negative bubble radius
  • Moved adaptive time-stepping logic inside conditional block for better
    code organization
  • Added small_alf threshold check for void fraction cutoff
+39/-39 
m_variables_conversion.fpp
Update variable conversion with normalized gas nomenclature

src/common/m_variables_conversion.fpp

  • Updated QBMM pressure calculation to use mass_g0 instead of mass_n0
  • Maintained consistent nomenclature for gas mass in bubble state
    conversions
+4/-4     
case.py
Refactor bubble parameters to use bub_pp structure             

examples/2D_bubbly_steady_shock/case.py

  • Reorganized fluid properties with explicit units and clearer naming
    conventions
  • Replaced old parameter names (n_tait, B_tait, gamma_n, M_n, etc.) with
    standardized names (gam_l, pi_inf_l, gam_g, M_g, etc.)
  • Removed pref, rhoref, Ca, Web, Re_inv parameters and replaced with new
    bub_pp% structure for bubble parameters
  • Updated reference value calculations to use consistent normalization
    scheme
+58/-93 
case.py
Refactor QBMM bubble case with new bub_pp parameters         

examples/1D_qbmm/case.py

  • Reorganized fluid properties with explicit units and standardized
    naming
  • Removed old bubble parameter definitions (Ca, Web, Re_inv, pref,
    rhoref)
  • Added comprehensive bub_pp% parameters including thermal properties
    (k_v, k_g, cp_v, cp_g, R_v, R_g)
  • Simplified reference value setup with consistent normalization
+66/-77 
case.py
Refactor bubble screen case with bub_pp parameters             

examples/1D_bubblescreen/case.py

  • Reorganized fluid properties with explicit units and standardized
    naming conventions
  • Removed pref, rhoref, Ca, Web, Re_inv parameters
  • Added bub_pp% structure for bubble parameters with proper
    normalization
  • Updated acoustic source parameters to use normalized pressure and
    wavelength
+61/-76 
case.py
Refactor polydisperse bubble screen case                                 

examples/1D_poly_bubscreen/case.py

  • Reorganized fluid properties with explicit units and clearer naming
  • Removed old bubble parameters (Ca, Web, Re_inv, pref, rhoref)
  • Added bub_pp% structure for bubble parameters with consistent
    normalization
  • Simplified reference value calculations
+52/-74 
case.py
Refactor adaptive bubble collapse case                                     

examples/0D_bubblecollapse_adap/case.py

  • Reorganized fluid properties with explicit units and standardized
    naming
  • Removed old bubble parameters (Ca, Web, Re_inv, pref, rhoref)
  • Added bub_pp% structure for bubble parameters with thermal properties
  • Updated domain and time step calculations with proper normalization
+48/-62 
case.py
Refactor whale bubble annulus case                                             

examples/2D_whale_bubble_annulus/case.py

  • Simplified variable naming and removed unused parameters
  • Removed old bubble parameters (Ca, Web, Re_inv, R0ref)
  • Added bub_pp% structure for bubble parameters
  • Cleaned up patch initialization with consistent volume fraction
    handling
+22/-39 
case.py
Refactor Lagrangian bubble collapse case                                 

examples/3D_lagrange_shbubcollapse/case.py

  • Renamed variables for consistency (gamma_ggam_g, gamma_vgam_v,
    mu_gasmu_g)
  • Removed old lag_params% reference parameters (c0, rho0, T0, x0, Thost)
  • Added comprehensive bub_pp% structure for Lagrangian bubble parameters
    with thermal properties
  • Removed bubble-related properties from fluid_pp()% definitions
+28/-24 
case.py
Refactor Lagrangian bubble screen case                                     

examples/3D_lagrange_bubblescreen/case.py

  • Renamed variables for consistency (gamma_ggam_g, gamma_vgam_v)
  • Removed old lag_params% reference parameters
  • Added comprehensive bub_pp% structure for Lagrangian bubble parameters
  • Removed bubble-related properties from fluid_pp()% definitions
+25/-21 
Refactoring
12 files
m_helper.fpp
Consolidate bubble model initialization into unified subroutine

src/common/m_helper.fpp

  • Created new s_initialize_bubbles_model() subroutine consolidating
    bubble initialization logic
  • Refactored s_initialize_nonpoly() to use bub_pp% parameters instead of
    fluid_pp%
  • Centralized bubble parameter assignment from bub_pp% structure
  • Replaced old variable names (_n, _v) with new nomenclature (_g, _v)
+126/-90
m_global_parameters.fpp
Introduce subgrid bubble parameters structure and reorganize globals

src/simulation/m_global_parameters.fpp

  • Added new bub_pp structure of type subgrid_bubble_physical_parameters
  • Replaced R0ref with Eu as primary nondimensional parameter
  • Reorganized bubble-related global variables with consistent naming (_g
    for gas, _v for vapor)
  • Removed bubble parameters from fluid_pp% structure
  • Updated GPU declarations for new parameter structure
+41/-72 
m_global_parameters.fpp
Add bubble parameters structure to preprocessing module   

src/pre_process/m_global_parameters.fpp

  • Added bub_pp structure declaration for subgrid bubble parameters
  • Reorganized bubble-related variables with consistent naming
    conventions
  • Removed bubble parameters from fluid_pp% initialization
  • Updated default value assignments for new bub_pp% structure
+43/-49 
m_bubbles_EL.fpp
Simplify Lagrange bubble initialization and normalize nomenclature

src/simulation/m_bubbles_EL.fpp

  • Removed s_start_lagrange_inputs() subroutine (initialization now
    handled by s_initialize_bubbles_model())
  • Updated variable references from R_n to R_g and k_nl to k_gl
  • Changed cp_n to cp_g for gas-phase specific heat
  • Updated diffusivity reference from fluid_pp(num_fluids)%D_v to vd
+5/-49   
m_global_parameters.fpp
Add bubble parameters structure to post-processing module

src/post_process/m_global_parameters.fpp

  • Added bub_pp structure for subgrid bubble parameters
  • Reorganized bubble variables with consistent naming (_g for gas, _v
    for vapor)
  • Removed bubble-specific parameters from fluid_pp% structure
  • Updated default value initialization for new parameter structure
+33/-24 
m_start_up.fpp
Consolidate bubble initialization in startup module           

src/simulation/m_start_up.fpp

  • Replaced multiple initialization calls with single
    s_initialize_bubbles_model() call
  • Added bub_pp parameter to subroutine interface
  • Removed redundant quadrature weight and nonpolytropic initialization
    code
  • Simplified bubble model setup logic
+5/-26   
m_derived_types.fpp
Create dedicated subgrid bubble parameters derived type   

src/common/m_derived_types.fpp

  • Removed bubble-related fields from physical_parameters type
  • Created new subgrid_bubble_physical_parameters derived type with
    comprehensive bubble properties
  • Organized bubble parameters into logical groups (reference values,
    properties, thermodynamic constants)
+25/-11 
m_mpi_proxy.fpp
Update MPI communication for bubble parameters structure 

src/pre_process/m_mpi_proxy.fpp

  • Removed bubble parameter broadcasting from fluid_pp% loop
  • Added dedicated MPI broadcast section for bub_pp% structure parameters
  • Simplified fluid parameter broadcasting to core properties only
+10/-10 
m_start_up.fpp
Consolidate bubble initialization in preprocessing startup

src/pre_process/m_start_up.fpp

  • Added bub_pp parameter to input reading interface
  • Replaced multiple bubble initialization calls with unified
    s_initialize_bubbles_model()
  • Simplified bubble setup logic in preprocessing startup
+4/-15   
m_mpi_proxy.fpp
Update MPI communication for bubble parameters structure 

src/simulation/m_mpi_proxy.fpp

  • Removed bubble parameters from fluid_pp% MPI broadcast loop
  • Added dedicated MPI broadcast section for bub_pp% structure
  • Simplified fluid parameter broadcasting to essential properties
+9/-2     
m_mpi_proxy.fpp
Update post-processing MPI communication for bubble parameters

src/post_process/m_mpi_proxy.fpp

  • Removed D_v from fluid_pp% MPI broadcast
  • Added dedicated MPI broadcast section for bub_pp% structure parameters
+9/-1     
m_start_up.fpp
Consolidate bubble initialization in post-processing startup

src/post_process/m_start_up.fpp

  • Added bub_pp parameter to input reading interface
  • Replaced multiple bubble initialization calls with unified
    s_initialize_bubbles_model()
+3/-6     
Error handling
2 files
m_riemann_solvers.fpp
Add NaN detection debugging in Riemann solver                       

src/simulation/m_riemann_solvers.fpp

  • Added debug output block for NaN detection in bubble flux calculations
  • Includes diagnostic printing of primitive variables and intermediate
    calculations
+15/-0   
case_validator.py
Update bubble validation constraints                                         

toolchain/mfc/case_validator.py

  • Commented out validation check for R0ref requirement in non-polytropic
    bubble mode
  • Added validation checks for Lagrangian bubbles requiring polytropic =
    F and thermal = 3
+8/-2     
Miscellaneous
3 files
m_time_steppers.fpp
Add placeholder variables for time-stepping diagnostics   

src/simulation/m_time_steppers.fpp

  • Added variable declarations for denom and nbubble (currently unused)
  • Added commented debug output for time-stepping diagnostics
+4/-0     
m_rhs.fpp
Add placeholder variables for RHS computation                       

src/simulation/m_rhs.fpp

  • Added variable declarations for denom and nbubble (currently unused)
+2/-1     
golden-metadata.txt
Update test golden file metadata                                                 

tests/5CAA4E68/golden-metadata.txt

  • Updated metadata timestamps and system information
  • Changed Git commit hash and branch reference
  • Updated compiler versions and system configuration details
+40/-71 
Configuration changes
2 files
case.py
Refactor bubble screen example with consistent parameterization

examples/1D_exp_bubscreen/case.py

  • Completely refactored case setup with explicit physical property
    definitions
  • Reorganized input parameters to use new bub_pp% structure
  • Replaced arbitrary Ca = 1 with physically consistent cavitation number
    calculation
  • Updated nondimensionalization scheme with explicit reference values
  • Improved code documentation and parameter organization
+66/-129
case_dicts.py
Update parameter dictionaries with bub_pp structure           

toolchain/mfc/run/case_dicts.py

  • Removed Ca, Web, Re_inv from parameter type definitions
  • Added comprehensive bub_pp% parameter definitions for both PRE_PROCESS
    and SIMULATION dictionaries
  • Removed bubble-related properties from fluid_pp()% parameter list
    (moved to bub_pp%)
  • Added bub_pp% parameters to POST_PROCESS dictionary
+18/-10 
Tests
2 files
cases.py
Update test cases with new bubble parameter structure       

toolchain/mfc/test/cases.py

  • Removed old fluid_pp(2)% bubble gas parameters from test case
    definitions
  • Added comprehensive bub_pp% parameters for bubble dynamics tests
  • Updated alter_bubbles and alter_viscosity test functions with new
    parameter structure
  • Added bub_pp% parameters to Lagrangian bubble test case with thermal
    properties
+21/-16 
case.py
Update test case parameters                                                           

toolchain/mfc/test/case.py

  • Removed Ca, Web, Re_inv parameters from test case definition
  • Removed R0ref parameter from test case
  • Removed pref and rhoref parameters (kept but not shown in diff
    context)
+1/-9     
Additional files
1 files
golden.txt +12/-12 

Summary by CodeRabbit

  • New Features

    • Unified sub‑grid bubble parameter group and single initialization pathway for bubble models (EE/EL).
  • Configuration Updates

    • Introduced bub_pp block for bubble/gas/liquid reference parameters and scaling.
    • Standardized fluid naming and moved time/length scaling to reference-based units (p0ref, rho0ref, R0ref, u0, t0).
    • MPI and startup flows updated to broadcast and initialize bub_pp.
  • Removals

    • Deprecated many legacy direct bubble/fluid keys in favor of bub_pp.

✏️ Tip: You can customize this high-level summary in your review settings.

@codeant-ai
Copy link

codeant-ai bot commented Dec 12, 2025

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 12, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Consolidates sub-grid bubble parameters into a new derived type bub_pp, renames/rescales many fluid/bubble constants, centralizes bubble initialization into new helpers, updates MPI/GPU broadcast and startup flows, adjusts bubble solver interfaces and QBMM/math, and updates ~50+ example cases, docs, tests, and toolchain metadata.

Changes

Cohort / File(s) Summary
Derived types
src/common/m_derived_types.fpp
Removed many bubble fields from physical_parameters; added public type subgrid_bubble_physical_parameters with R0ref,p0ref,rho0ref,T0ref,ss,pv,vd,mu_l,mu_v,mu_g,gam_v,gam_g,M_v,M_g,k_v,k_g,cp_v,cp_g,R_v,R_g.
Global parameter containers
src/pre_process/m_global_parameters.fpp, src/simulation/m_global_parameters.fpp, src/post_process/m_global_parameters.fpp
Added public bub_pp of new type and migrated numerous bubble-related public variables/allocatables into it; introduced Eu/gam_m and updated GPU_DECLARE/GPU hint usage and defaults.
Helper / initialization
src/common/m_helper.fpp, src/pre_process/m_start_up.fpp, src/simulation/m_start_up.fpp, src/post_process/m_start_up.fpp
Added impure subroutines s_initialize_bubbles_model() and s_initialize_bubble_vars() to centralize bubble setup; startup flows and namelists updated to call unified initializer when bubbles_euler or bubbles_lagrange are active.
MPI broadcast / proxy
src/pre_process/m_mpi_proxy.fpp, src/post_process/m_mpi_proxy.fpp, src/simulation/m_mpi_proxy.fpp
Reduced per-fluid broadcast list to core fluid fields and added guarded MPI_BCAST blocks to broadcast all bub_pp fields when bubbles_euler or bubbles_lagrange is true.
Simulation: bubble solvers & signatures
src/simulation/m_bubbles.fpp, src/simulation/m_bubbles_EE.fpp, src/simulation/m_bubbles_EL.fpp
Renamed mass/gamma variables (mass_n→mass_g, gamma_n→gam_g), updated solver signatures to accept fmass_g, introduced small-alf branching and adaptive-stepping paths, removed s_start_lagrange_inputs, and adjusted EL/EE internal gas/transfer computations.
QBMM & moments
src/simulation/m_qbmm.fpp
Replaced tiny-constant floors with sgm_eps, adjusted GPU private lists (removed T_bar, added grad_T), migrated mass_n0→mass_g0, and refactored grad_T/T_bar/rho_mw/coefficient math in non-polytropic branch.
Variable conversion & preprocessing
src/common/m_variables_conversion.fpp, src/pre_process/m_assign_variables.fpp
Updated Preston/isothermal initializations to use mass_g0; switched fluid_pp(1)%pvbub_pp%pv; normalized moment scalings to use bub_pp references (R0ref,p0ref,rho0ref) and consistent radius/velocity normalization.
Startup / GPU init & pre-process
src/simulation/m_start_up.fpp, src/pre_process/m_start_up.fpp, src/pre_process/m_mpi_proxy.fpp
Centralized bubble initialization, updated s_initialize_modules and s_initialize_gpu_vars to include bub_pp conditional GPU updates, adjusted namelist ordering to include bub_pp, and removed older polydisperse initialization branches.
Global simulations adjustments
src/simulation/m_global_parameters.fpp, src/pre_process/m_global_parameters.fpp
Exposed bub_pp publicly, expanded allocatables (pb0, mass_g0, mass_v0, Pe_T, k_v, k_g, etc.), adjusted defaulting/initialization and replaced some legacy top-level bubble scalars.
Example cases (bubble-focused)
examples/0D_bubblecollapse_adap/case.py, examples/1D_bubblescreen/case.py, examples/1D_exp_bubscreen/case.py, examples/1D_poly_bubscreen/case.py, examples/1D_qbmm/case.py, examples/2D_bubbly_steady_shock/case.py, examples/2D_whale_bubble_annulus/case.py, examples/3D_lagrange_bubblescreen/case.py, examples/3D_lagrange_shbubcollapse/case.py
Replaced standalone constants with grouped fluid/bubble blocks (Water/Vapor/Air/Bubble/External); renamed n_tait→gam_l, B_tait→pi_inf_l, rho0→rho_l, mul0→mu_l; introduced bub_pp entries and new reference scales (R0ref,p0ref,rho0ref,p0ext,x0,u0,t0,tc); updated scaling for domain/patch/dt/Tfinal.
Other examples & benchmarks
many examples/*/case.py, benchmarks/viscous_weno5_sgb_acoustic/case.py
Normalized numeric literal types (0→0.0) widely; benchmarks updated to use p0ref/rho0ref/u0/p0ext and bub_pp-scaled keys; minor geometry literal float adjustments.
Toolchain / config / tests
toolchain/mfc/run/case_dicts.py, toolchain/mfc/case_validator.py, toolchain/mfc/test/case.py, toolchain/mfc/test/cases.py, tests/5CAA4E68/golden-metadata.txt
Added bub_pp entries to PRE_PROCESS/SIMULATION/POST_PROCESS prototypes; removed COMMON keys Ca, Web, Re_inv, R0ref; validator updated for Lagrange constraints (polytropic=F, thermal=3); test metadata/toolchain snapshots updated.
Docs
docs/documentation/case.md
Documented bub_pp parameter group, deprecated legacy direct bubble parameters, and noted bub_pp requirements per model (EE/EL) including Rayleigh–Plesset option.
Trivial / formatting
src/simulation/m_rhs.fpp, many examples
Minor formatting and literal-type normalization changes (empty-line removal, integer→float literals).

Sequence Diagram(s)

sequenceDiagram
    participant User as User / Case generator
    participant StartUp as s_read_input / s_initialize_modules
    participant Globals as m_global_parameters (bub_pp)
    participant Helper as m_helper (s_initialize_bubbles_model)
    participant MPI as MPI proxy
    participant Simulation as m_bubbles / m_qbmm

    User->>StartUp: provide case + bub_pp inputs
    StartUp->>Globals: populate bub_pp fields (defaults & user)
    StartUp->>Helper: call s_initialize_bubbles_model()
    Helper->>Globals: initialize pb0, mass_g0, mass_v0, weights and derived scales
    Helper->>Simulation: supply initialized bubble arrays/params
    StartUp->>MPI: broadcast core fluid_pp fields
    StartUp->>MPI: if bubbles_euler or bubbles_lagrange -> broadcast bub_pp fields
    MPI->>Simulation: deliver bub_pp and fluid params to ranks
    Simulation->>Simulation: use bub_pp in advance_step / EE/EL / QBMM flows
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Files warranting extra attention:
    • src/simulation/m_bubbles.fpp, m_bubbles_EE.fpp, m_bubbles_EL.fpp — signature changes, mass/gamma renames, adaptive-stepping branches, and call-site propagation.
    • src/common/m_helper.fpp — new s_initialize_bubbles_model()/s_initialize_bubble_vars(): polytropic/qbmm/non-polytropic flows and quadrature handling.
    • src/simulation/m_qbmm.fpp — math refactor (sgm_eps, grad_T/T_bar, moment inversion) and GPU-private list changes.
    • MPI proxy modules (pre/post/simulation) — ensure bub_pp VAR lists and broadcast guards are complete and consistent.
    • Example cases — verify bub_pp assignments and scaled reference values for representative bubble examples.

Possibly related PRs

Suggested reviewers

  • sbryngelson
  • wilfonba

"🐰 I hopped through code with gentle paws,
consolidated bubbles and straightened the laws.
bub_pp in my basket, renames neat and shrewd,
now configs and solvers walk the same root. 🥕"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title "Refactor subgrid bubble models" clearly and concisely summarizes the main objective of the pull request.
Description check ✅ Passed The description is comprehensive and well-organized, covering key changes, minor changes, testing performed, and known issues. It includes a summary, motivation, type of change, scope, testing details with graphs, checklist items, and a detailed diagram walkthrough.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codeant-ai codeant-ai bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Dec 12, 2025
@qodo-code-review
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

New EL path switches from _n to _g (e.g., mass_g0, R_g, k_g) and introduces optional fmass_g in s_vflux and s_advance_EL call chains. Verify all call sites now pass fmass_g consistently; any missed site will cause wrong mixture properties or division-by-zero in fmass_v+fmass_g.

    !!  @param fpb
    !!  @param fmass_v Current mass of vapour
    !!  @param iR0 Bubble size index (EE) or bubble identifier (EL)
    !!  @param fmass_g Current gas mass (EL)
    !!  @param fbeta_c Mass transfer coefficient (EL)
    !!  @param fR_m Mixture gas constant (EL)
    !!  @param fgamma_m Mixture gamma (EL)
elemental subroutine s_vflux(fR, fV, fpb, fmass_v, iR0, vflux, fmass_g, fbeta_c, fR_m, fgamma_m)
    $:GPU_ROUTINE(parallelism='[seq]')
    real(wp), intent(in) :: fR
    real(wp), intent(in) :: fV
    real(wp), intent(in) :: fpb
    real(wp), intent(in) :: fmass_v
    integer, intent(in) :: iR0
    real(wp), intent(out) :: vflux
    real(wp), intent(in), optional :: fmass_g, fbeta_c
    real(wp), intent(out), optional :: fR_m, fgamma_m

    real(wp) :: chi_bar
    real(wp) :: rho_mw_lag
    real(wp) :: grad_chi
    real(wp) :: conc_v

    if (thermal == 3) then !transfer
        ! constant transfer model
        if (bubbles_lagrange) then
            ! Mixture properties (gas+vapor) in the bubble
            conc_v = fmass_v/(fmass_v + fmass_g)
            if (lag_params%massTransfer_model) then
                conc_v = 1._wp/(1._wp + (R_v/R_g)*(fpb/pv - 1._wp))
            end if
            fR_m = (fmass_g*R_g + fmass_v*R_v)
            fgamma_m = conc_v*gam_v + (1._wp - conc_v)*gam_g

            ! Vapor flux
            chi_bar = fmass_v/(fmass_v + fmass_g)
            grad_chi = (chi_bar - conc_v)
            rho_mw_lag = (fmass_g + fmass_v)/(4._wp/3._wp*pi*fR**3._wp)
            vflux = 0._wp
            if (lag_params%massTransfer_model) then
                vflux = -fbeta_c*rho_mw_lag*grad_chi/(1._wp - conc_v)/fR
            end if
        else
            chi_bar = fmass_v/(fmass_v + mass_g0(iR0))
            grad_chi = -Re_trans_c(iR0)*(chi_bar - chi_vw)
            vflux = rho_mw*grad_chi/Pe_c/(1._wp - chi_vw)/fR
Initialization

New initialization prints and sets globals from bub_pp, computes nondimensional groups (Eu, Ca, Web, Re_inv), and allocates arrays. Validate that s_initialize_bubbles_model/s_initialize_bubble_vars are invoked in all code paths replacing removed EL initializer, and that qbmm + polytropic branch sets pb0 consistently with surface tension. Remove or guard debug prints.

!>
      !! bubbles_euler + polytropic
      !! bubbles_euler + non-polytropic
      !! bubbles_lagrange + non-polytropic
impure subroutine s_initialize_bubbles_model()

    ! Allocate memory
    if (bubbles_euler) then
        @:ALLOCATE(weight(nb), R0(nb))
        if (.not. polytropic) then
            @:ALLOCATE(pb0(nb), Pe_T(nb), k_g(nb), k_v(nb), mass_g0(nb), mass_v0(nb))
            @:ALLOCATE(Re_trans_T(nb), Re_trans_c(nb), Im_trans_T(nb), Im_trans_c(nb))
        else if (qbmm) then
            @:ALLOCATE(pb0(nb))
        end if

        ! Compute quadrature weights and nodes for polydisperse simulations
        if (nb > 1) then
            call s_simpson(weight, R0)
        else if (nb == 1) then
            R0 = 1._wp
            weight = 1._wp
        else
            stop 'Invalid value of nb'
        end if
        R0 = R0*bub_pp%R0ref
    end if

    ! Initialize bubble variables
    call s_initialize_bubble_vars()

end subroutine s_initialize_bubbles_model

!>
impure subroutine s_initialize_bubble_vars()

    R0ref = bub_pp%R0ref; p0ref = bub_pp%p0ref
    rho0ref = bub_pp%rho0ref; 
    ss = bub_pp%ss; pv = bub_pp%pv; vd = bub_pp%vd
    mu_l = bub_pp%mu_l; mu_v = bub_pp%mu_v; mu_g = bub_pp%mu_g
    gam_v = bub_pp%gam_v; gam_g = bub_pp%gam_g
    if (.not. polytropic) then
        if (bubbles_euler) then
            M_v = bub_pp%M_v; M_g = bub_pp%M_g
            k_v = bub_pp%k_v; k_g = bub_pp%k_g
        end if
        R_v = bub_pp%R_v; R_g = bub_pp%R_g
        Tw = bub_pp%T0ref
    end if
    if (bubbles_lagrange) then
        cp_v = bub_pp%cp_v; cp_g = bub_pp%cp_g
        k_vl = bub_pp%k_v; k_gl = bub_pp%k_g
    end if

    ! Input quantities
    if (bubbles_euler .and. (.not. polytropic)) then
        if (thermal == 2) then
            gam_m = 1._wp
        else
            gam_m = gam_g
        end if
    end if

    ! Nondimensional numbers
    Eu = p0ref
    Ca = Eu - pv
    if (.not. f_is_default(bub_pp%ss)) Web = 1._wp/ss
    if (.not. f_is_default(bub_pp%mu_l)) Re_inv = mu_l
    if (.not. polytropic) Pe_c = 1._wp/vd

    if (bubbles_euler) then
        ! Initialize variables for non-polytropic (Preston) model
        if (.not. polytropic) then
            call s_initialize_nonpoly()
        end if
        ! Initialize pb based on surface tension for qbmm (polytropic)
        if (qbmm .and. polytropic) then
            pb0 = Eu
            if (.not. f_is_default(Web)) then
                pb0 = pb0 + 2._wp/Web/R0
            end if
        end if
    end if

    print *, Tw, pv, gam_v, gam_g
    print *, k_vl, k_gl
    print *, cp_v, cp_g
    print *, R_v, R_g, vd, Web, Re_inv

end subroutine s_initialize_bubble_vars
Numerical Stability

Replaced verysmall with sgm_eps in variance and wave speed computations. Ensure sgm_eps is always initialized and appropriate for all regimes to avoid negative under-root or overly diffusive behavior; confirm consistency across modules.

                    do l = 0, p
                        do k = 0, n
                            do j = 0, m
                                nb_q = q_cons_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                nR = q_cons_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                nR2 = q_cons_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                R = q_prim_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                R2 = q_prim_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                var = max(R2 - R**2._wp, sgm_eps)
                                if (q <= 2) then
                                    AX = R - sqrt(var)
                                else
                                    AX = R + sqrt(var)
                                end if

                                select case (idir)
                                case (1)
                                    nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                    nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                    nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                    rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2)* &
                                                            (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                case (2)
                                    nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                    nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                    nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                    rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2)* &
                                                            (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                case (3)
                                    if (is_axisym) then
                                        nb_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l))
                                        nR_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l))
                                        nR2_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2)* &
                                                                (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                    else
                                        nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                        nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                        nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2)* &
                                                                (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                    end if
                                end select
                                if (q <= 2) then
                                    select case (idir)
                                    case (1)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (2)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (3)
                                        if (is_axisym) then
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        else
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        end if
                                    end select
                                else
                                    select case (idir)
                                    case (1)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (2)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (3)
                                        if (is_axisym) then
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        else
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        end if
                                    end select
                                end if
                            end do
                        end do
                    end do
                end do
            end do
            $:END_GPU_PARALLEL_LOOP()
        end if

        ! The following block is not repeated and is left as is
        if (idir == 1) then
            $:GPU_PARALLEL_LOOP(private='[i,l,q]', collapse=3)
            do l = 0, p
                do q = 0, n
                    do i = 0, m
                        rhs_vf(alf_idx)%sf(i, q, l) = rhs_vf(alf_idx)%sf(i, q, l) + mom_sp(2)%sf(i, q, l)
                        j = bubxb
                        $:GPU_LOOP(parallelism='[seq]')
                        do k = 1, nb
                            rhs_vf(j)%sf(i, q, l) = rhs_vf(j)%sf(i, q, l) + mom_3d(0, 0, k)%sf(i, q, l)
                            rhs_vf(j + 1)%sf(i, q, l) = rhs_vf(j + 1)%sf(i, q, l) + mom_3d(1, 0, k)%sf(i, q, l)
                            rhs_vf(j + 2)%sf(i, q, l) = rhs_vf(j + 2)%sf(i, q, l) + mom_3d(0, 1, k)%sf(i, q, l)
                            rhs_vf(j + 3)%sf(i, q, l) = rhs_vf(j + 3)%sf(i, q, l) + mom_3d(2, 0, k)%sf(i, q, l)
                            rhs_vf(j + 4)%sf(i, q, l) = rhs_vf(j + 4)%sf(i, q, l) + mom_3d(1, 1, k)%sf(i, q, l)
                            rhs_vf(j + 5)%sf(i, q, l) = rhs_vf(j + 5)%sf(i, q, l) + mom_3d(0, 2, k)%sf(i, q, l)
                            j = j + 6
                        end do
                    end do
                end do
            end do
            $:END_GPU_PARALLEL_LOOP()
        end if

    end subroutine s_compute_qbmm_rhs

    !Coefficient array for non-polytropic model (pb and mv values are accounted in wght_pb and wght_mv)
    subroutine s_coeff_nonpoly(pres, rho, c, coeffs)
        $:GPU_ROUTINE(function_name='s_coeff_nonpoly',parallelism='[seq]', &
            & cray_inline=True)

        real(wp), intent(in) :: pres, rho, c
        real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeffs

        integer :: i1, i2

        coeffs = 0._wp

        do i2 = 0, 2; do i1 = 0, 2
                if ((i1 + i2) <= 2) then
                    if (bubble_model == 3) then
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 1
                            ! RPE
                            coeffs(1, i1, i2) = -1._wp*i2*pres/rho
                            coeffs(2, i1, i2) = -3._wp*i2/2._wp
                            coeffs(3, i1, i2) = i2/rho
                            coeffs(4, i1, i2) = i1
                            if (.not. f_is_default(Re_inv)) coeffs(5, i1, i2) = -4._wp*i2*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(6, i1, i2) = -2._wp*i2/Web/rho
                            coeffs(7, i1, i2) = 0._wp
                        #:endif
                    else if (bubble_model == 2) then
                        ! KM with approximation of 1/(1-V/C) = 1+V/C
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -3._wp*i2/2._wp
                            coeffs(2, i1, i2) = -i2/c
                            coeffs(3, i1, i2) = i2/(2._wp*c*c)
                            coeffs(4, i1, i2) = -i2*pres/rho
                            coeffs(5, i1, i2) = -2._wp*i2*pres/(c*rho)
                            coeffs(6, i1, i2) = -i2*pres/(c*c*rho)
                            coeffs(7, i1, i2) = i2/rho
                            coeffs(8, i1, i2) = 2._wp*i2/(c*rho)
                            coeffs(9, i1, i2) = i2/(c*c*rho)
                            coeffs(10, i1, i2) = -3._wp*i2*gam/(c*rho)
                            coeffs(11, i1, i2) = -3._wp*i2*gam/(c*c*rho)
                            coeffs(12, i1, i2) = i1
                            coeffs(13, i1, i2) = 0._wp
                            coeffs(14, i1, i2) = 0._wp
                            coeffs(15, i1, i2) = 0._wp
                            if (.not. f_is_default(Re_inv)) coeffs(16, i1, i2) = -i2*4._wp*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(17, i1, i2) = -i2*2._wp/Web/rho
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(18, i1, i2) = i2*6._wp*Re_inv/(rho*c)
                                coeffs(19, i1, i2) = -i2*2._wp*Re_inv/(rho*c*c)
                                coeffs(20, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c)
                                coeffs(21, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c*c)
                                coeffs(22, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c)
                                coeffs(23, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c*c)
                                coeffs(24, i1, i2) = i2*16._wp*Re_inv*Re_inv/(rho*rho*c)
                                if (.not. f_is_default(Web)) then
                                    coeffs(25, i1, i2) = i2*8._wp*Re_inv/Web/(rho*rho*c)
                                end if
                                coeffs(26, i1, i2) = -12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                            coeffs(27, i1, i2) = 3._wp*i2*gam*R_v*Tw/(c*rho)
                            coeffs(28, i1, i2) = 3._wp*i2*gam*R_v*Tw/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(29, i1, i2) = 12._wp*i2*gam*R_v*Tw*Re_inv/(rho*rho*c*c)
                            end if
                            coeffs(30, i1, i2) = 3._wp*i2*gam/(c*rho)
                            coeffs(31, i1, i2) = 3._wp*i2*gam/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(32, i1, i2) = 12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                        #:endif
                    end if
                end if
            end do; end do

    end subroutine s_coeff_nonpoly

!Coefficient array for polytropic model (pb for each R0 bin accounted for in wght_pb)
    subroutine s_coeff(pres, rho, c, coeffs)
        $:GPU_ROUTINE(function_name='s_coeff',parallelism='[seq]', &
            & cray_inline=True)

        real(wp), intent(in) :: pres, rho, c
        real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeffs

        integer :: i1, i2

        coeffs = 0._wp

        do i2 = 0, 2; do i1 = 0, 2
                if ((i1 + i2) <= 2) then
                    if (bubble_model == 3) then
                        ! RPE
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -1._wp*i2*pres/rho
                            coeffs(2, i1, i2) = -3._wp*i2/2._wp
                            coeffs(3, i1, i2) = i2/rho
                            coeffs(4, i1, i2) = i1
                            if (.not. f_is_default(Re_inv)) coeffs(5, i1, i2) = -4._wp*i2*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(6, i1, i2) = -2._wp*i2/Web/rho
                            coeffs(7, i1, i2) = i2*pv/rho
                        #:endif
                    else if (bubble_model == 2) then
                        ! KM with approximation of 1/(1-V/C) = 1+V/C
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -3._wp*i2/2._wp
                            coeffs(2, i1, i2) = -i2/c
                            coeffs(3, i1, i2) = i2/(2._wp*c*c)
                            coeffs(4, i1, i2) = -i2*pres/rho
                            coeffs(5, i1, i2) = -2._wp*i2*pres/(c*rho)
                            coeffs(6, i1, i2) = -i2*pres/(c*c*rho)
                            coeffs(7, i1, i2) = i2/rho
                            coeffs(8, i1, i2) = 2._wp*i2/(c*rho)
                            coeffs(9, i1, i2) = i2/(c*c*rho)
                            coeffs(10, i1, i2) = -3._wp*i2*gam/(c*rho)
                            coeffs(11, i1, i2) = -3._wp*i2*gam/(c*c*rho)
                            coeffs(12, i1, i2) = i1
                            coeffs(13, i1, i2) = i2*(pv)/rho
                            coeffs(14, i1, i2) = 2._wp*i2*(pv)/(c*rho)
                            coeffs(15, i1, i2) = i2*(pv)/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) coeffs(16, i1, i2) = -i2*4._wp*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(17, i1, i2) = -i2*2._wp/Web/rho
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(18, i1, i2) = i2*6._wp*Re_inv/(rho*c)
                                coeffs(19, i1, i2) = -i2*2._wp*Re_inv/(rho*c*c)
                                coeffs(20, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c)
                                coeffs(21, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c*c)
                                coeffs(22, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c)
                                coeffs(23, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c*c)
                                coeffs(24, i1, i2) = i2*16._wp*Re_inv*Re_inv/(rho*rho*c)
                                if (.not. f_is_default(Web)) then
                                    coeffs(25, i1, i2) = i2*8._wp*Re_inv/Web/(rho*rho*c)
                                end if
                                coeffs(26, i1, i2) = -12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                        #:endif
                    end if
                end if
            end do; end do

    end subroutine s_coeff

    subroutine s_mom_inv(q_cons_vf, q_prim_vf, momsp, moms3d, pb, rhs_pb, mv, rhs_mv, ix, iy, iz)

        type(scalar_field), dimension(:), intent(inout) :: q_cons_vf, q_prim_vf
        type(scalar_field), dimension(:), intent(inout) :: momsp
        type(scalar_field), dimension(0:, 0:, :), intent(inout) :: moms3d
        real(stp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: pb
        real(wp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: rhs_pb
        real(stp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: mv
        real(wp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: rhs_mv
        type(int_bounds_info), intent(in) :: ix, iy, iz

        real(wp), dimension(nmom) :: moms, msum
        real(wp), dimension(nnode, nb) :: wght, abscX, abscY, wght_pb, wght_mv, wght_ht, ht
        real(wp), dimension(nterms, 0:2, 0:2) :: coeff
        real(wp) :: pres, rho, nbub, c, alf, momsum, drdt, drdt2, chi_vw, x_vw, rho_mw, k_mw, grad_T
        real(wp) :: n_tait, B_tait
        integer :: id1, id2, id3, i1, i2, j, q, r

        is1_qbmm = ix; is2_qbmm = iy; is3_qbmm = iz
        $:GPU_UPDATE(device='[is1_qbmm,is2_qbmm,is3_qbmm]')

        $:GPU_PARALLEL_LOOP(collapse=3, private='[id1,id2,id3,moms, msum, wght, abscX, abscY, wght_pb, wght_mv, wght_ht, coeff, ht, r, q, n_tait, B_tait, pres, rho, nbub, c, alf, momsum, drdt, drdt2, chi_vw, x_vw, rho_mw, k_mw, grad_T, i1, i2, j]')
        do id3 = is3_qbmm%beg, is3_qbmm%end
            do id2 = is2_qbmm%beg, is2_qbmm%end
                do id1 = is1_qbmm%beg, is1_qbmm%end

                    alf = q_prim_vf(alf_idx)%sf(id1, id2, id3)
                    pres = q_prim_vf(E_idx)%sf(id1, id2, id3)
                    rho = q_prim_vf(contxb)%sf(id1, id2, id3)

                    if (bubble_model == 2) then
                        n_tait = 1._wp/gammas(1) + 1._wp
                        B_tait = pi_infs(1)*(n_tait - 1)/n_tait
                        c = n_tait*(pres + B_tait)*(1._wp - alf)/(rho)
                        c = merge(sqrt(c), sgm_eps, c > 0._wp)
                    end if

                    call s_coeff_selector(pres, rho, c, coeff, polytropic)

                    if (alf > small_alf) then
                        nbub = q_cons_vf(bubxb)%sf(id1, id2, id3)
                        $:GPU_LOOP(parallelism='[seq]')
                        do q = 1, nb
                            ! Gather moments for this bubble bin
                            $:GPU_LOOP(parallelism='[seq]')
                            do r = 2, nmom
                                moms(r) = q_prim_vf(bubmoms(q, r))%sf(id1, id2, id3)
                            end do
                            moms(1) = 1._wp
                            call s_chyqmom(moms, wght(:, q), abscX(:, q), abscY(:, q))

                            if (polytropic) then
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    wght_pb(j, q) = wght(j, q)*(pb0(q) - pv)
                                end do
                            else
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    chi_vw = 1._wp/(1._wp + R_v/R_g*(pb(id1, id2, id3, j, q)/pv - 1._wp))
                                    x_vw = M_g*chi_vw/(M_v + (M_g - M_v)*chi_vw)
                                    k_mw = x_vw*k_v(q)/(x_vw + (1._wp - x_vw)*phi_vg) + (1._wp - x_vw)*k_g(q)/(x_vw*phi_gv + 1._wp - x_vw)
                                    rho_mw = pv/(chi_vw*R_v*Tw)
                                    rhs_mv(id1, id2, id3, j, q) = -Re_trans_c(q)*((mv(id1, id2, id3, j, q)/(mv(id1, id2, id3, j, q) + mass_g0(q))) - chi_vw)
                                    rhs_mv(id1, id2, id3, j, q) = rho_mw*rhs_mv(id1, id2, id3, j, q)/Pe_c/(1._wp - chi_vw)/abscX(j, q)
                                    grad_T = -Re_trans_T(q)*((pb(id1, id2, id3, j, q)/pb0(q))*(abscX(j, q)/R0(q))**3*(mass_g0(q) + mass_v0(q))/(mass_g0(q) + mv(id1, id2, id3, j, q)) - 1._wp)
                                    ht(j, q) = pb0(q)*k_mw*grad_T/Pe_T(q)/abscX(j, q)
                                    wght_pb(j, q) = wght(j, q)*(pb(id1, id2, id3, j, q))
                                    wght_mv(j, q) = wght(j, q)*(rhs_mv(id1, id2, id3, j, q))
                                    wght_ht(j, q) = wght(j, q)*ht(j, q)
                                end do
                            end if

                            ! Compute change in moments due to bubble dynamics
                            r = 1
                            $:GPU_LOOP(parallelism='[seq]')
                            do i2 = 0, 2
                                $:GPU_LOOP(parallelism='[seq]')
                                do i1 = 0, 2
                                    if ((i1 + i2) <= 2) then
                                        momsum = 0._wp
                                        $:GPU_LOOP(parallelism='[seq]')
                                        do j = 1, nterms
                                            select case (bubble_model)
                                            case (3)
                                                if (j == 3) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_pb(:, q), momrhs(:, i1, i2, j, q))
                                                else
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght(:, q), momrhs(:, i1, i2, j, q))
                                                end if
                                            case (2)
                                                if ((j >= 7 .and. j <= 9) .or. (j >= 22 .and. j <= 23) .or. (j >= 10 .and. j <= 11) .or. (j == 26)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_pb(:, q), momrhs(:, i1, i2, j, q))
                                                else if ((j >= 27 .and. j <= 29) .and. (.not. polytropic)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_mv(:, q), momrhs(:, i1, i2, j, q))
                                                else if ((j >= 30 .and. j <= 32) .and. (.not. polytropic)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_ht(:, q), momrhs(:, i1, i2, j, q))
                                                else
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght(:, q), momrhs(:, i1, i2, j, q))
                                                end if
                                            end select
                                        end do
                                        moms3d(i1, i2, q)%sf(id1, id2, id3) = nbub*momsum
                                        msum(r) = momsum
                                        r = r + 1
                                    end if
                                end do
                            end do

                            ! Compute change in pb and mv for non-polytropic model
                            if (.not. polytropic) then
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    drdt = msum(2)
                                    drdt2 = merge(-1._wp, 1._wp, j == 1 .or. j == 2)/(2._wp*sqrt(merge(moms(4) - moms(2)**2._wp, sgm_eps, moms(4) - moms(2)**2._wp > 0._wp)))
                                    drdt2 = drdt2*(msum(3) - 2._wp*moms(2)*msum(2))
                                    drdt = drdt + drdt2
                                    rhs_pb(id1, id2, id3, j, q) = (-3._wp*gam*drdt/abscX(j, q))*(pb(id1, id2, id3, j, q))
                                    rhs_pb(id1, id2, id3, j, q) = rhs_pb(id1, id2, id3, j, q) + (3._wp*gam/abscX(j, q))*rhs_mv(id1, id2, id3, j, q)*R_v*Tw
                                    rhs_pb(id1, id2, id3, j, q) = rhs_pb(id1, id2, id3, j, q) + (3._wp*gam/abscX(j, q))*ht(j, q)
                                    rhs_mv(id1, id2, id3, j, q) = rhs_mv(id1, id2, id3, j, q)*(4._wp*pi*abscX(j, q)**2._wp)

                                end do
                            end if
                        end do

                        ! Compute special high-order moments
                        momsp(1)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght, 3._wp, 0._wp, 0._wp)
                        momsp(2)%sf(id1, id2, id3) = 4._wp*pi*nbub*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp)
                        momsp(3)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght, 3._wp, 2._wp, 0._wp)
                        if (abs(gam - 1._wp) <= 1.e-4_wp) then
                            momsp(4)%sf(id1, id2, id3) = 1._wp
                        else
                            if (polytropic) then
                                momsp(4)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght_pb, 3._wp*(1._wp - gam), 0._wp, 3._wp*gam) + pv*f_quad(abscX, abscY, wght, 3._wp, 0._wp, 0._wp) - 4._wp*Re_inv*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp) - (2._wp/Web)*f_quad(abscX, abscY, wght, 2._wp, 0._wp, 0._wp)
                            else
                                momsp(4)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght_pb, 3._wp, 0._wp, 0._wp) - 4._wp*Re_inv*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp) - (2._wp/Web)*f_quad(abscX, abscY, wght, 2._wp, 0._wp, 0._wp)
                            end if
                        end if
                    else
                        $:GPU_LOOP(parallelism='[seq]')
                        do q = 1, nb
                            $:GPU_LOOP(parallelism='[seq]')
                            do i1 = 0, 2
                                $:GPU_LOOP(parallelism='[seq]')
                                do i2 = 0, 2
                                    moms3d(i1, i2, q)%sf(id1, id2, id3) = 0._wp
                                end do
                            end do
                        end do
                        momsp(1)%sf(id1, id2, id3) = 0._wp
                        momsp(2)%sf(id1, id2, id3) = 0._wp
                        momsp(3)%sf(id1, id2, id3) = 0._wp
                        momsp(4)%sf(id1, id2, id3) = 0._wp
                    end if
                end do
            end do
        end do
        $:END_GPU_PARALLEL_LOOP()

    contains
        ! Helper to select the correct coefficient routine
        subroutine s_coeff_selector(pres, rho, c, coeff, polytropic)
            $:GPU_ROUTINE(function_name='s_coeff_selector',parallelism='[seq]', &
                & cray_inline=True)
            real(wp), intent(in) :: pres, rho, c
            real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeff
            logical, intent(in) :: polytropic
            if (polytropic) then
                call s_coeff(pres, rho, c, coeff)
            else
                call s_coeff_nonpoly(pres, rho, c, coeff)
            end if
        end subroutine s_coeff_selector

        subroutine s_chyqmom(momin, wght, abscX, abscY)
            $:GPU_ROUTINE(function_name='s_chyqmom',parallelism='[seq]', &
                & cray_inline=True)

            real(wp), dimension(nmom), intent(in) :: momin
            real(wp), dimension(nnode), intent(inout) :: wght, abscX, abscY

            ! Local variables
            real(wp), dimension(0:2, 0:2) :: moms
            real(wp), dimension(3) :: M1, M3
            real(wp), dimension(2) :: myrho, myrho3, up, up3, Vf
            real(wp) :: bu, bv, d20, d11, d_02, c20, c11, c02
            real(wp) :: mu2, vp21, vp22, rho21, rho22

            ! Assign moments to 2D array for clarity
            moms(0, 0) = momin(1)
            moms(1, 0) = momin(2)
            moms(0, 1) = momin(3)
            moms(2, 0) = momin(4)
            moms(1, 1) = momin(5)
            moms(0, 2) = momin(6)

            ! Compute means and central moments
            bu = moms(1, 0)/moms(0, 0)
            bv = moms(0, 1)/moms(0, 0)
            d20 = moms(2, 0)/moms(0, 0)
            d11 = moms(1, 1)/moms(0, 0)
            d_02 = moms(0, 2)/moms(0, 0)

            c20 = d20 - bu**2._wp
            c11 = d11 - bu*bv
            c02 = d_02 - bv**2._wp

            ! First 1D quadrature (X direction)
            M1 = (/1._wp, 0._wp, c20/)
            call s_hyqmom(myrho, up, M1)
            Vf = c11*up/c20

            ! Second 1D quadrature (Y direction, conditional on X)
            mu2 = max(0._wp, c02 - sum(myrho*(Vf**2._wp)))
            M3 = (/1._wp, 0._wp, mu2/)
            call s_hyqmom(myrho3, up3, M3)

            ! Assign roots and weights for 2D quadrature
            vp21 = up3(1)
            vp22 = up3(2)
            rho21 = myrho3(1)
            rho22 = myrho3(2)

            ! Compute weights (vectorized)
            wght = moms(0, 0)*[myrho(1)*rho21, myrho(1)*rho22, myrho(2)*rho21, myrho(2)*rho22]

            ! Compute abscissas (vectorized)
            abscX = bu + [up(1), up(1), up(2), up(2)]
            abscY = bv + [Vf(1) + vp21, Vf(1) + vp22, Vf(2) + vp21, Vf(2) + vp22]

        end subroutine s_chyqmom

        subroutine s_hyqmom(frho, fup, fmom)
            $:GPU_ROUTINE(function_name='s_hyqmom',parallelism='[seq]', &
                & cray_inline=True)

            real(wp), dimension(2), intent(inout) :: frho, fup
            real(wp), dimension(3), intent(in) :: fmom

            real(wp) :: bu, d2, c2

            bu = fmom(2)/fmom(1)
            d2 = fmom(3)/fmom(1)
            c2 = d2 - bu**2._wp
            frho(1) = fmom(1)/2._wp; 
            frho(2) = fmom(1)/2._wp; 
            c2 = maxval((/c2, sgm_eps/))
            fup(1) = bu - sqrt(c2)
            fup(2) = bu + sqrt(c2)

        end subroutine s_hyqmom

@codeant-ai
Copy link

codeant-ai bot commented Dec 12, 2025

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Swapped gas/vapor properties
    Many bub_pp entries appear to assign gas and vapor properties swapped (e.g. gam, M, cp, k, R). This will silently produce incorrect nondimensional bubble properties and wrong physics. Verify that gas-related keys (suffix _g) get *_g variables and vapor keys (suffix _v) get *_v variables.

  • Swapped properties
    Many bubble property assignments for gas vs. vapor appear to be swapped when populating bub_pp (gamma, molar masses M_, thermal conductivities k_, heat capacities cp_, and gas constants R_). This will silently invert gas/vapor physics and produce incorrect simulations; please verify and correct the mapping so vapor fields use vapor variables and gas fields use gas variables.

  • Units / Gas constant
    The code computes R_v = R_uni / M_v and R_g = R_uni / M_g while R_uni and the molecular masses' units are unclear. This can silently produce incorrect gas constants (factor-of-1000 errors) depending on whether R_uni is J/(mol·K), J/(kmol·K) or another convention and whether M_v/M_g are given in g/mol or kg/mol. Verify and make the unit conversion explicit to avoid subtle physical errors.

  • Simulation timestep/count mismatch
    Nt is computed as int(Tfinal / dt) before dt is subsequently scaled by dt = dt * 0.1. This means the number of time steps Nt is based on the larger pre-scaled dt, producing an inconsistency between the reported/used timestep count and the actual dt used later. Move the dt scaling before computing Nt, or recompute Nt after dt is finalized.

  • Possible Bug (swapped masses)
    In the Lagrangian bubbles block the additions assign 'bub_pp%M_g' = 18.02 and 'bub_pp%M_v' = 28.97 which is the opposite ordering used elsewhere (M_v = 18.02, M_g = 28.97). This looks like a swapped assignment and will likely produce incorrect thermodynamic behavior.

@codeant-ai
Copy link

codeant-ai bot commented Dec 12, 2025

CodeAnt AI finished reviewing your PR.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

13 issues found across 35 files

Prompt for AI agents (all 13 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/common/m_helper.fpp">

<violation number="1" location="src/common/m_helper.fpp:194">
P2: Debug print statements should be removed from production code. These `print *` statements will output internal variables to stdout during execution, which is likely unintended behavior.</violation>
</file>

<file name="toolchain/mfc/run/case_dicts.py">

<violation number="1" location="toolchain/mfc/run/case_dicts.py:110">
P2: Consider extracting the duplicated `bub_pp%` variable list into a constant to avoid maintaining identical lists in three places. If the variable list needs to be updated, all three occurrences must be changed in sync, risking inconsistency.</violation>
</file>

<file name="examples/1D_poly_bubscreen/case.py">

<violation number="1" location="examples/1D_poly_bubscreen/case.py:37">
P2: Variable `nb = 3` is defined but unused - the config hardcodes `&quot;nb&quot;: 1`. Either use the variable (`&quot;nb&quot;: nb`) or remove the unused definition to avoid confusion.</violation>
</file>

<file name="examples/3D_lagrange_shbubcollapse/case.py">

<violation number="1" location="examples/3D_lagrange_shbubcollapse/case.py:167">
P1: Gas and vapor properties are systematically swapped: `bub_pp%gam_v` receives `gam_g` (gas value) and vice versa. Same pattern applies to M_v/M_g, k_v/k_g, cp_v/cp_g, and R_v/R_g. This appears to work around the underlying bug mentioned in the PR description, but makes the code confusing and will cause issues when that bug is fixed. Consider assigning matching properties (`bub_pp%gam_v: gam_v`, etc.) or add clear comments explaining the intentional swap.</violation>
</file>

<file name="src/simulation/m_bubbles_EE.fpp">

<violation number="1" location="src/simulation/m_bubbles_EE.fpp:270">
P1: The `myR &lt; 0` check is placed before the `alf &lt; small_alf` threshold check. When `alf` is very small (essentially no bubbles), `myR` could contain numerical noise or invalid values, but this shouldn&#39;t matter since we&#39;d zero out all sources anyway. Moving this check inside the `else` branch (where `alf &gt;= small_alf`) would prevent spurious aborts when bubble radius is irrelevant.</violation>
</file>

<file name="examples/2D_whale_bubble_annulus/case.py">

<violation number="1" location="examples/2D_whale_bubble_annulus/case.py:18">
P2: Variable `R0ref` is defined but never used. The configuration should use the variable instead of the literal `1.0` to maintain consistency and prevent future bugs if the value needs to be changed.</violation>
</file>

<file name="src/simulation/m_riemann_solvers.fpp">

<violation number="1" location="src/simulation/m_riemann_solvers.fpp:3032">
P2: Inconsistent index for right state: `qR_prim_rs${XYZ}$_vf(j, k, l, i)` should use `j + 1` to access the right state, matching the pattern used elsewhere (e.g., line 4 of this block uses `j + 1`). Using `j` will print incorrect debug information.</violation>
</file>

<file name="examples/0D_bubblecollapse_adap/case.py">

<violation number="1" location="examples/0D_bubblecollapse_adap/case.py:66">
P2: The `dt` variable is computed but never used - a hardcoded `0.0001` is used instead. Either use the computed value or remove the dead code.</violation>
</file>

<file name="examples/2D_bubbly_steady_shock/case.py">

<violation number="1" location="examples/2D_bubbly_steady_shock/case.py:155">
P0: Missing normalization for `pi_inf_l`. The old code normalized `B_tait` by `p0`, but the new formula uses dimensional `pi_inf_l` directly. This should be `gam_l * (pi_inf_l / p0) / (gam_l - 1.0)` to maintain consistency with the `delta` calculation on line 61 and preserve the expected non-dimensional value (~3.94 vs ~399114).</violation>
</file>

<file name="examples/3D_lagrange_bubblescreen/case.py">

<violation number="1" location="examples/3D_lagrange_bubblescreen/case.py:165">
P1: Parameter values are swapped: `bub_pp%gam_v` should receive `gam_v` (1.333) not `gam_g` (1.4). Currently assigning gas-specific heat ratio to the vapor parameter.</violation>
</file>

<file name="src/simulation/m_global_parameters.fpp">

<violation number="1" location="src/simulation/m_global_parameters.fpp:421">
P2: The module variable `Eu` is declared without initialization. If read before being explicitly set, it will contain an indeterminate value. Initialize it at declaration (e.g., `real(wp) :: Eu = dflt_real`) or ensure it&#39;s set in the default-values routine.</violation>
</file>

<file name="src/simulation/m_bubbles.fpp">

<violation number="1" location="src/simulation/m_bubbles.fpp:376">
P1: The mixture gas constant `fR_m` is computed as a sum of mass-weighted constants but not normalized by total mass. This will produce an incorrect value scale. Divide by `(fmass_g + fmass_v)` to compute the correct mass-weighted average: `fR_m = (fmass_g*R_g + fmass_v*R_v)/(fmass_g + fmass_v)`</violation>
</file>

<file name="src/simulation/m_rhs.fpp">

<violation number="1" location="src/simulation/m_rhs.fpp:658">
P3: Local variables `denom` and `nbubble` are declared but appear unused. If they are needed later, initialize them at declaration to avoid use-before-assignment bugs. Otherwise, remove the unused declaration.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (22)
examples/2D_whale_bubble_annulus/case.py (1)

18-19: Variable R0ref defined but not used in configuration.

The variable R0ref = 1.0 is defined but bub_pp%R0ref uses the literal 1.0 instead. For maintainability, use the variable:

-            "bub_pp%R0ref": 1.0,
+            "bub_pp%R0ref": R0ref,
examples/3D_lagrange_bubblescreen/case.py (1)

165-174: Critical: Gas and vapor parameters are systematically swapped.

All _v (vapor) parameters receive _g (gas) values and vice versa. Given the variable definitions:

  • gam_g = 1.4 (gas), gam_v = 1.333 (vapor)
  • MW_g = 28.0 (gas/air), MW_v = 18.0 (vapor/water)

The assignments are inverted:

-            "bub_pp%gam_v": gam_g,
-            "bub_pp%gam_g": gam_v,
-            "bub_pp%M_v": MW_g,
-            "bub_pp%M_g": MW_v,
-            "bub_pp%k_v": k_g * (T0 / (x0 * rho0 * c0 * c0 * c0)),
-            "bub_pp%k_g": k_v * (T0 / (x0 * rho0 * c0 * c0 * c0)),
-            "bub_pp%cp_v": cp_g * (T0 / (c0 * c0)),
-            "bub_pp%cp_g": cp_v * (T0 / (c0 * c0)),
-            "bub_pp%R_v": (R_uni / MW_g) * (T0 / (c0 * c0)),
-            "bub_pp%R_g": (R_uni / MW_v) * (T0 / (c0 * c0)),
+            "bub_pp%gam_v": gam_v,
+            "bub_pp%gam_g": gam_g,
+            "bub_pp%M_v": MW_v,
+            "bub_pp%M_g": MW_g,
+            "bub_pp%k_v": k_v * (T0 / (x0 * rho0 * c0 * c0 * c0)),
+            "bub_pp%k_g": k_g * (T0 / (x0 * rho0 * c0 * c0 * c0)),
+            "bub_pp%cp_v": cp_v * (T0 / (c0 * c0)),
+            "bub_pp%cp_g": cp_g * (T0 / (c0 * c0)),
+            "bub_pp%R_v": (R_uni / MW_v) * (T0 / (c0 * c0)),
+            "bub_pp%R_g": (R_uni / MW_g) * (T0 / (c0 * c0)),

This will result in physically incorrect simulation behavior for heat/mass transfer models.

toolchain/mfc/test/cases.py (1)

849-869: Lagrangian test case bub_pp gas/vapor properties appear swapped.
This matches a previously flagged issue: _g fields get vapor values and _v fields get gas values.

-                        'bub_pp%gam_g': 1.33, 'bub_pp%gam_v': 1.4,
-                        'bub_pp%M_g': 18.02, 'bub_pp%M_v': 28.97, 'bub_pp%k_g': 5.618695895665441e-06,
-                        'bub_pp%k_v': 7.392868685947116e-06, 'bub_pp%R_g': 1347.810235139403, 'bub_pp%R_v': 838.3686723235085,
-                        'bub_pp%cp_v': 2921.2822272326243, 'bub_pp%cp_g': 6134.692677188511
+                        'bub_pp%gam_g': 1.4, 'bub_pp%gam_v': 1.33,
+                        'bub_pp%M_g': 28.97, 'bub_pp%M_v': 18.02, 'bub_pp%k_g': 7.392868685947116e-06,
+                        'bub_pp%k_v': 5.618695895665441e-06, 'bub_pp%R_g': 838.3686723235085, 'bub_pp%R_v': 1347.810235139403,
+                        'bub_pp%cp_g': 2921.2822272326243, 'bub_pp%cp_v': 6134.692677188511
examples/3D_lagrange_shbubcollapse/case.py (1)

158-176: Gas/vapor properties look swapped in bub_pp (likely incorrect).
bub_pp%*_v fields are receiving gas constants (*_g) and vice-versa (including R_v/R_g using the opposite molar weights). This matches a previously flagged issue in this file.

-            "bub_pp%gam_v": gam_g,
-            "bub_pp%gam_g": gam_v,
-            "bub_pp%M_v": MW_g,
-            "bub_pp%M_g": MW_v,
-            "bub_pp%k_v": k_g * (T0 / (x0 * rho0 * c0 * c0 * c0)),
-            "bub_pp%k_g": k_v * (T0 / (x0 * rho0 * c0 * c0 * c0)),
-            "bub_pp%cp_v": cp_g * (T0 / (c0 * c0)),
-            "bub_pp%cp_g": cp_v * (T0 / (c0 * c0)),
-            "bub_pp%R_v": (R_uni / MW_g) * (T0 / (c0 * c0)),
-            "bub_pp%R_g": (R_uni / MW_v) * (T0 / (c0 * c0)),
+            "bub_pp%gam_v": gam_v,
+            "bub_pp%gam_g": gam_g,
+            "bub_pp%M_v": MW_v,
+            "bub_pp%M_g": MW_g,
+            "bub_pp%k_v": k_v * (T0 / (x0 * rho0 * c0 * c0 * c0)),
+            "bub_pp%k_g": k_g * (T0 / (x0 * rho0 * c0 * c0 * c0)),
+            "bub_pp%cp_v": cp_v * (T0 / (c0 * c0)),
+            "bub_pp%cp_g": cp_g * (T0 / (c0 * c0)),
+            "bub_pp%R_v": (R_uni / MW_v) * (T0 / (c0 * c0)),
+            "bub_pp%R_g": (R_uni / MW_g) * (T0 / (c0 * c0)),
examples/2D_bubbly_steady_shock/case.py (1)

154-155: fluid_pp(1)%pi_inf is missing / p0 nondimensionalization.
This matches the previously flagged issue and will massively change the EOS stiffness.

-            "fluid_pp(1)%pi_inf": gam_l * (pi_inf_l) / (gam_l - 1.0),
+            "fluid_pp(1)%pi_inf": gam_l * (pi_inf_l / p0) / (gam_l - 1.0),
src/simulation/m_qbmm.fpp (2)

767-774: Guard non-polytropic denominators against 0 to avoid NaNs/FP exceptions.
This matches the previously flagged issue; the risks are real in several divisions.

-                                    chi_vw = 1._wp/(1._wp + R_v/R_g*(pb(id1, id2, id3, j, q)/pv - 1._wp))
-                                    x_vw = M_g*chi_vw/(M_v + (M_g - M_v)*chi_vw)
-                                    k_mw = x_vw*k_v(q)/(x_vw + (1._wp - x_vw)*phi_vg) + (1._wp - x_vw)*k_g(q)/(x_vw*phi_gv + 1._wp - x_vw)
-                                    rho_mw = pv/(chi_vw*R_v*Tw)
-                                    rhs_mv(id1, id2, id3, j, q) = -Re_trans_c(q)*((mv(id1, id2, id3, j, q)/(mv(id1, id2, id3, j, q) + mass_g0(q))) - chi_vw)
-                                    rhs_mv(id1, id2, id3, j, q) = rho_mw*rhs_mv(id1, id2, id3, j, q)/Pe_c/(1._wp - chi_vw)/abscX(j, q)
-                                    grad_T = -Re_trans_T(q)*((pb(id1, id2, id3, j, q)/pb0(q))*(abscX(j, q)/R0(q))**3*(mass_g0(q) + mass_v0(q))/(mass_g0(q) + mv(id1, id2, id3, j, q)) - 1._wp)
+                                    chi_vw = 1._wp/max( &
+                                      1._wp + R_v/R_g*(pb(id1, id2, id3, j, q)/pv - 1._wp), sgm_eps)
+                                    x_vw = M_g*chi_vw/max(M_v + (M_g - M_v)*chi_vw, sgm_eps)
+                                    k_mw = x_vw*k_v(q)/max(x_vw + (1._wp - x_vw)*phi_vg, sgm_eps) + &
+                                           (1._wp - x_vw)*k_g(q)/max(x_vw*phi_gv + 1._wp - x_vw, sgm_eps)
+                                    rho_mw = pv/max(chi_vw*R_v*Tw, sgm_eps)
+                                    rhs_mv(id1, id2, id3, j, q) = -Re_trans_c(q)*((mv(id1, id2, id3, j, q)/ &
+                                      max(mv(id1, id2, id3, j, q) + mass_g0(q), sgm_eps)) - chi_vw)
+                                    rhs_mv(id1, id2, id3, j, q) = rho_mw*rhs_mv(id1, id2, id3, j, q)/Pe_c/ &
+                                      max(1._wp - chi_vw, sgm_eps)/max(abscX(j, q), sgm_eps)
+                                    grad_T = -Re_trans_T(q)*((pb(id1, id2, id3, j, q)/pb0(q))* &
+                                      (abscX(j, q)/R0(q))**3*(mass_g0(q) + mass_v0(q))/ &
+                                      max(mass_g0(q) + mv(id1, id2, id3, j, q), sgm_eps) - 1._wp)
                                     ht(j, q) = pb0(q)*k_mw*grad_T/Pe_T(q)/abscX(j, q)

955-956: Use scalar max instead of maxval array constructor for c2 clamp.
This matches the previously flagged cleanup.

-            c2 = maxval((/c2, sgm_eps/))
+            c2 = max(c2, sgm_eps)
examples/0D_bubblecollapse_adap/case.py (1)

49-53: dt is computed but not used (hardcoded dt in JSON).
This still computes dt = Tfinal / (Nt - 1) but emits "dt": 0.0001. Either output dt or drop the computed dt to avoid dead code / confusion.

-            "dt": 0.0001,
+            "dt": dt,

Also applies to: 66-66

src/simulation/m_time_steppers.fpp (1)

515-516: Remove unused locals denom and nbubble (or use them).
They’re declared but not referenced in s_tvd_rk in the shown code.

-        real(wp) :: denom, nbubble
src/common/m_variables_conversion.fpp (1)

554-557: Guard against divide-by-zero in Preston pb init (mass_g0 + mass_v0).
Even if “shouldn’t happen”, this is a cheap defensive clamp.

-                        pb(j, k, l, 1, i) = (pb0(i))*(R0(i)**(3._wp))*(mass_g0(i) + mv(j, k, l, 1, i))/(mu - sig)**(3._wp)/(mass_g0(i) + mass_v0(i))
-                        pb(j, k, l, 2, i) = (pb0(i))*(R0(i)**(3._wp))*(mass_g0(i) + mv(j, k, l, 2, i))/(mu - sig)**(3._wp)/(mass_g0(i) + mass_v0(i))
-                        pb(j, k, l, 3, i) = (pb0(i))*(R0(i)**(3._wp))*(mass_g0(i) + mv(j, k, l, 3, i))/(mu + sig)**(3._wp)/(mass_g0(i) + mass_v0(i))
-                        pb(j, k, l, 4, i) = (pb0(i))*(R0(i)**(3._wp))*(mass_g0(i) + mv(j, k, l, 4, i))/(mu + sig)**(3._wp)/(mass_g0(i) + mass_v0(i))
+                        pb(j, k, l, 1, i) = (pb0(i))*(R0(i)**(3._wp))*(mass_g0(i) + mv(j, k, l, 1, i))/(mu - sig)**(3._wp)/max(mass_g0(i) + mass_v0(i), sgm_eps)
+                        pb(j, k, l, 2, i) = (pb0(i))*(R0(i)**(3._wp))*(mass_g0(i) + mv(j, k, l, 2, i))/(mu - sig)**(3._wp)/max(mass_g0(i) + mass_v0(i), sgm_eps)
+                        pb(j, k, l, 3, i) = (pb0(i))*(R0(i)**(3._wp))*(mass_g0(i) + mv(j, k, l, 3, i))/(mu + sig)**(3._wp)/max(mass_g0(i) + mass_v0(i), sgm_eps)
+                        pb(j, k, l, 4, i) = (pb0(i))*(R0(i)**(3._wp))*(mass_g0(i) + mv(j, k, l, 4, i))/(mu + sig)**(3._wp)/max(mass_g0(i) + mass_v0(i), sgm_eps)
src/simulation/m_bubbles_EL.fpp (1)

345-347: LGTM: Mass diffusivity uses vd.

The rename from D_v to vd is consistent with the new bub_pp structure.

Note: Past review comments flagged potential division-by-zero risks for kparticle (Line 341) and vd (Line 345). These concerns remain valid but are pre-existing logic issues, not introduced by this refactor.

src/simulation/m_bubbles_EE.fpp (1)

270-272: s_mpi_abort inside GPU parallel loop is invalid.

Calling s_mpi_abort from within a GPU parallel region is problematic - host-side MPI calls cannot execute on GPU device code. Additionally, when alf < small_alf, the value of myR may contain numerical noise that would trigger a spurious abort.

Consider using a reduction-based error flag (similar to adap_dt_stop_max) and checking after the loop completes. As per coding guidelines, avoid stop/error stop inside GPU device code.

src/pre_process/m_assign_variables.fpp (1)

421-450: QBMM moment-initialization code duplication persists.

The nearly identical QBMM moment-initialization blocks at lines 421-450 (smoothing patch) and lines 628-658 (main patch) duplicate the same logic. This was flagged in a previous review. Consider extracting this into a helper subroutine to reduce maintenance burden and ensure consistent updates.

Also applies to: 628-658

toolchain/mfc/run/case_dicts.py (1)

110-114: Extract duplicated bub_pp% variable list into a constant.

The same list of 20 bub_pp% variables is defined identically in PRE_PROCESS (lines 110-113), SIMULATION (lines 362-365), and POST_PROCESS (lines 514-517). If this list needs updating, all three locations must be modified in sync, risking inconsistency.

Consider extracting to a constant:

# Define once at module level
BUB_PP_VARS = ["R0ref", "p0ref", "rho0ref", "T0ref", "ss", "pv", "vd",
               "mu_l", "mu_v", "mu_g", "gam_v", "gam_g",
               "M_v", "M_g", "k_v", "k_g", "cp_v", "cp_g", "R_v", "R_g"]

# Then use in each section:
for var in BUB_PP_VARS:
    PRE_PROCESS[f"bub_pp%{var}"] = ParamType.REAL

Also applies to: 362-366, 514-518

src/common/m_helper.fpp (1)

241-248: Logic issue: Im_trans_T is zeroed after computation.

The loop at lines 241-246 computes Im_trans_T(ir) via s_transcoeff, but line 247 immediately overwrites the entire array with zeros, discarding the computed values. This appears to be a bug.

If zeroing is intentional (e.g., the imaginary part should be ignored), consider moving the initialization before the loop or adding a comment explaining the intent:

+        ! Note: Imaginary transport coefficients are intentionally zeroed
+        Im_trans_T = 0._wp
         do ir = 1, nb
             call s_transcoeff(omegaN(ir)*R0(ir), Pe_T(ir)*R0(ir), &
                               Re_trans_T(ir), Im_trans_T(ir))
             call s_transcoeff(omegaN(ir)*R0(ir), Pe_c*R0(ir), &
                               Re_trans_c(ir), Im_trans_c(ir))
         end do
-        Im_trans_T = 0._wp

Or if the imaginary part should be preserved, simply remove line 247.

examples/1D_poly_bubscreen/case.py (1)

37-37: Unused variable nb = 3 conflicts with hardcoded "nb": 1 in config.

This was flagged in a previous review. Either use the variable ("nb": nb) or remove the unused definition to avoid confusion.

src/post_process/m_global_parameters.fpp (2)

309-309: Verify initialization of Eu, Ca, Web, Re_inv.

These scalar bubble parameters are declared but the visible code doesn't show their initialization in s_assign_default_values_to_user_inputs. Ensure they are initialized to dflt_real in the defaults subroutine to prevent undefined behavior if read before assignment.


323-323: Initialize module-level G to prevent undefined reads.

The scalar G is declared but not initialized in s_assign_default_values_to_user_inputs. Add G = dflt_real in the defaults subroutine for consistency with other module variables.

src/simulation/m_global_parameters.fpp (2)

421-421: Verify Eu is initialized before use.

The module variable Eu is declared but not visibly initialized in s_assign_default_values_to_user_inputs. Ensure it's set to a default value (e.g., Eu = dflt_real) to prevent undefined reads.


482-482: Verify gam_m initialization.

gam is properly assigned from bub_pp%gam_g in s_initialize_global_parameters_module, but gam_m is not visibly initialized. Since gam_m is used in bubble pressure calculations (f_bpres_dot), ensure it's initialized either in the defaults subroutine or via the bubble model initialization pathway.

src/simulation/m_bubbles.fpp (2)

376-377: Mixture gas constant fR_m calculation may be incorrect.

The mixture gas constant should be a mass-weighted average: (m_g*R_g + m_v*R_v) / (m_g + m_v). The current code computes a sum without normalization, which will produce a value scaled by total mass rather than the correct averaged gas constant.

-                fR_m = (fmass_g*R_g + fmass_v*R_v)
+                fR_m = (fmass_g*R_g + fmass_v*R_v)/(fmass_g + fmass_v)

360-361: Optional arguments used without present() guards.

fmass_g, fbeta_c, fR_m, and fgamma_m are optional but used unguarded inside the bubbles_lagrange branch. If called with bubbles_lagrange=.true. but without these arguments, undefined reads/writes occur. Either make them required or add present() guards.

🧹 Nitpick comments (8)
examples/2D_bubbly_steady_shock/case.py (1)

5-29: Unit annotations for R_uni, M_v/M_g are inconsistent with the numeric conventions used.
You’re using R_uni = 8314.0 with M_* = 18.02/28.97, which is consistent with R_uni in J/(kmol·K) and M_* in kg/kmol (not J/mol/K and g/mol).

src/simulation/m_qbmm.fpp (1)

448-453: Good: flooring var with sgm_eps; consider also guarding AX before it’s used as a divisor.
AX = R - sqrt(var) can approach 0 (or go negative) and later appears in denominators; clamping can prevent inf/NaN blowups.

-                                if (q <= 2) then
-                                    AX = R - sqrt(var)
-                                else
-                                    AX = R + sqrt(var)
-                                end if
+                                if (q <= 2) then
+                                    AX = R - sqrt(var)
+                                else
+                                    AX = R + sqrt(var)
+                                end if
+                                AX = max(AX, sgm_eps)
examples/1D_qbmm/case.py (2)

14-23: Make the thermophysical units explicit (prevents silent scaling mistakes in bub_pp normalization).
Right now k_v/k_g are commented as [] but are used as thermal conductivities in the nondimensionalization. Same for vd (looks like a diffusivity). Suggest tightening comments to match the math.

-vd = 0.242e-4  # []
+vd = 0.242e-4  # [m^2/s] (if this is diffusivity; scaling by x0*u0 implies so)
...
-k_v = 0.019426  # []
+k_v = 0.019426  # [W/m/K]
...
-k_g = 0.02556  # []
+k_g = 0.02556  # [W/m/K]

Also applies to: 171-176


141-142: bub_pp mapping looks consistent; consider consolidating repeated scaling factors for readability.
The fluid_pp(1)%gamma/pi_inf mapping matches the gamma/pi_inf model convention, and the bub_pp nondimensionalization is internally consistent. A small readability win would be factoring shared scales (e.g., inv_rho0x0u0 = 1/(rho0*x0*u0), T0_over_u02 = T0/(u0*u0)).

Also applies to: 157-176

src/pre_process/m_assign_variables.fpp (1)

704-712: Remove commented-out debug code.

These commented-out print statements appear to be debugging artifacts and should be removed before merging to keep the codebase clean.

-        ! if (j == 1) then
-        !     print *, (q_prim_vf(bub_idx%rs(i))%sf(j, k, l), i = 1, nb)
-        !     print *, (q_prim_vf(bub_idx%fullmom(i, 1, 0))%sf(j, k, l), i = 1, nb)
-        !     print *, (R0(i), i = 1, nb)
-        !     print *, patch_icpp(patch_id)%r0
-        !     print *, (bub_idx%rs(i), i = 1, nb)
-        !     print *, (bub_idx%fullmom(i, 1, 0), i = 1, nb)
-        ! end if
-
src/post_process/m_global_parameters.fpp (1)

448-449: Minor style inconsistency: trailing semicolons without subsequent statements.

Lines 448-449 have bub_pp%k_v = dflt_real; and bub_pp%k_g = dflt_real; with trailing semicolons but no corresponding module-level scalar assignments (unlike other fields). This is correct since the module-level k_v and k_g are allocatable arrays, not scalars, but consider removing the trailing semicolons for consistency.

-        bub_pp%k_v = dflt_real; 
-        bub_pp%k_g = dflt_real; 
+        bub_pp%k_v = dflt_real
+        bub_pp%k_g = dflt_real
src/pre_process/m_global_parameters.fpp (1)

608-609: Minor style inconsistency: trailing semicolons without subsequent statements.

Same as in post_process file - consider removing trailing semicolons for consistency.

-        bub_pp%k_v = dflt_real; 
-        bub_pp%k_g = dflt_real; 
+        bub_pp%k_v = dflt_real
+        bub_pp%k_g = dflt_real
src/simulation/m_global_parameters.fpp (1)

697-698: Minor style inconsistency: trailing semicolons without subsequent statements.

Same pattern as other files - consider removing trailing semicolons for consistency.

-        bub_pp%k_v = dflt_real; 
-        bub_pp%k_g = dflt_real; 
+        bub_pp%k_v = dflt_real
+        bub_pp%k_g = dflt_real
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b220ebf and b8c19b0.

📒 Files selected for processing (33)
  • examples/0D_bubblecollapse_adap/case.py (3 hunks)
  • examples/1D_bubblescreen/case.py (4 hunks)
  • examples/1D_exp_bubscreen/case.py (3 hunks)
  • examples/1D_poly_bubscreen/case.py (2 hunks)
  • examples/1D_qbmm/case.py (2 hunks)
  • examples/2D_bubbly_steady_shock/case.py (4 hunks)
  • examples/2D_whale_bubble_annulus/case.py (3 hunks)
  • examples/3D_lagrange_bubblescreen/case.py (3 hunks)
  • examples/3D_lagrange_shbubcollapse/case.py (3 hunks)
  • src/common/m_derived_types.fpp (1 hunks)
  • src/common/m_helper.fpp (2 hunks)
  • src/common/m_variables_conversion.fpp (1 hunks)
  • src/post_process/m_global_parameters.fpp (3 hunks)
  • src/post_process/m_mpi_proxy.fpp (1 hunks)
  • src/post_process/m_start_up.fpp (2 hunks)
  • src/pre_process/m_assign_variables.fpp (4 hunks)
  • src/pre_process/m_global_parameters.fpp (5 hunks)
  • src/pre_process/m_mpi_proxy.fpp (2 hunks)
  • src/pre_process/m_start_up.fpp (2 hunks)
  • src/simulation/m_bubbles.fpp (21 hunks)
  • src/simulation/m_bubbles_EE.fpp (1 hunks)
  • src/simulation/m_bubbles_EL.fpp (2 hunks)
  • src/simulation/m_global_parameters.fpp (6 hunks)
  • src/simulation/m_mpi_proxy.fpp (1 hunks)
  • src/simulation/m_qbmm.fpp (5 hunks)
  • src/simulation/m_rhs.fpp (0 hunks)
  • src/simulation/m_start_up.fpp (3 hunks)
  • src/simulation/m_time_steppers.fpp (1 hunks)
  • tests/5CAA4E68/golden-metadata.txt (8 hunks)
  • toolchain/mfc/case_validator.py (1 hunks)
  • toolchain/mfc/run/case_dicts.py (6 hunks)
  • toolchain/mfc/test/case.py (1 hunks)
  • toolchain/mfc/test/cases.py (4 hunks)
💤 Files with no reviewable changes (1)
  • src/simulation/m_rhs.fpp
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{fpp,f90}: Use 2-space indentation; continuation lines align beneath &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_ pattern (e.g., m_transport)
Name public subroutines with s_ pattern (e.g., s_compute_flux)
Name public functions with f
_ pattern
Keep subroutine size ≤ 500 lines, helper subroutines ≤ 150 lines, functions ≤ 100 lines, files ≤ 1000 lines
Limit routine arguments to ≤ 6; use derived-type params struct if more are needed
Forbid goto statements (except in legacy code), COMMON blocks, and save globals
Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate
Call s_mpi_abort() for errors, never use stop or error stop

**/*.{fpp,f90}: Indent 2 spaces; continuation lines align under &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_<feature> prefix (e.g., m_transport)
Name public subroutines as s_<verb>_<noun> (e.g., s_compute_flux) and functions as f_<verb>_<noun>
Keep private helpers in the module; avoid nested procedures
Enforce size limits: subroutine ≤ 500 lines, helper ≤ 150, function ≤ 100, module/file ≤ 1000
Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct
Avoid goto statements (except unavoidable legacy); avoid global state (COMMON, save)
Every variable must have intent(in|out|inout) specification and appropriate dimension / allocatable / pointer
Use s_mpi_abort(<msg>) for error termination instead of stop
Use !> style documentation for header comments; follow Doxygen Fortran format with !! @param and !! @return tags
Use implicit none statement in all modules
Use private declaration followed by explicit public exports in modules
Use derived types with pointers for encapsulation (e.g., pointer, dimension(:,:,:) => null())
Use pure and elemental attributes for side-effect-free functions; combine them for array ...

Files:

  • src/simulation/m_time_steppers.fpp
  • src/pre_process/m_start_up.fpp
  • src/simulation/m_mpi_proxy.fpp
  • src/common/m_derived_types.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • src/common/m_variables_conversion.fpp
  • src/post_process/m_start_up.fpp
  • src/common/m_helper.fpp
  • src/simulation/m_qbmm.fpp
  • src/simulation/m_bubbles_EL.fpp
  • src/pre_process/m_assign_variables.fpp
  • src/simulation/m_global_parameters.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/simulation/m_start_up.fpp
  • src/post_process/m_global_parameters.fpp
src/simulation/**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/simulation/**/*.{fpp,f90}: Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)
Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up
Avoid stop/error stop inside GPU device code
Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

src/simulation/**/*.{fpp,f90}: Mark GPU-callable helpers with $:GPU_ROUTINE(function_name='...', parallelism='[seq]') immediately after declaration
Do not use OpenACC or OpenMP directives directly; use Fypp macros from src/common/include/parallel_macros.fpp instead
Wrap tight loops with $:GPU_PARALLEL_FOR(private='[...]', copy='[...]') macro; add collapse=n for safe nested loop merging
Declare loop-local variables with private='[...]' in GPU parallel loop macros
Allocate large arrays with managed or move them into a persistent $:GPU_ENTER_DATA(...) region at start-up
Do not place stop or error stop inside device code

Files:

  • src/simulation/m_time_steppers.fpp
  • src/simulation/m_mpi_proxy.fpp
  • src/simulation/m_qbmm.fpp
  • src/simulation/m_bubbles_EL.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/simulation/m_start_up.fpp
src/**/*.fpp

📄 CodeRabbit inference engine (.cursor/rules/mfc-agent-rules.mdc)

src/**/*.fpp: Use .fpp file extension for Fypp preprocessed files; CMake transpiles them to .f90
Start module files with Fypp include for macros: #:include 'macros.fpp'
Use the fypp ASSERT macro for validating conditions: @:ASSERT(predicate, message)
Use fypp macro @:ALLOCATE(var1, var2) for device-aware allocation instead of standard Fortran allocate
Use fypp macro @:DEALLOCATE(var1, var2) for device-aware deallocation instead of standard Fortran deallocate

Files:

  • src/simulation/m_time_steppers.fpp
  • src/pre_process/m_start_up.fpp
  • src/simulation/m_mpi_proxy.fpp
  • src/common/m_derived_types.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • src/common/m_variables_conversion.fpp
  • src/post_process/m_start_up.fpp
  • src/common/m_helper.fpp
  • src/simulation/m_qbmm.fpp
  • src/simulation/m_bubbles_EL.fpp
  • src/pre_process/m_assign_variables.fpp
  • src/simulation/m_global_parameters.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/simulation/m_start_up.fpp
  • src/post_process/m_global_parameters.fpp
🧠 Learnings (29)
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Declare loop-local variables with `private='[...]'` in GPU parallel loop macros

Applied to files:

  • src/simulation/m_time_steppers.fpp
  • src/simulation/m_mpi_proxy.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • src/common/m_variables_conversion.fpp
  • src/common/m_helper.fpp
  • src/simulation/m_qbmm.fpp
  • src/pre_process/m_assign_variables.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)

Applied to files:

  • src/simulation/m_time_steppers.fpp
  • src/simulation/m_mpi_proxy.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • src/common/m_variables_conversion.fpp
  • src/simulation/m_qbmm.fpp
  • src/simulation/m_bubbles_EL.fpp
  • src/pre_process/m_assign_variables.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Avoid stop/error stop inside GPU device code

Applied to files:

  • src/simulation/m_time_steppers.fpp
  • src/simulation/m_mpi_proxy.fpp
  • src/common/m_variables_conversion.fpp
  • src/simulation/m_qbmm.fpp
  • src/simulation/m_bubbles_EL.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Add and update focused tests for changes; ensure no regressions in golden test outputs without clear justification

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
  • src/simulation/m_mpi_proxy.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • src/common/m_variables_conversion.fpp
  • src/simulation/m_qbmm.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Draft a step-by-step plan before making changes; build after each step using `./mfc.sh build -t pre_process simulation -j $(nproc)`

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Do not use OpenACC or OpenMP directives directly; use Fypp macros from `src/common/include/parallel_macros.fpp` instead

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
  • src/simulation/m_mpi_proxy.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Review Fypp macros in src/<subprogram>/include/ before reviewing generated code

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/**/*.fpp : Use `.fpp` file extension for Fypp preprocessed files; CMake transpiles them to `.f90`

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Fypp macros are defined in `src/<subprogram>/include/` directories where `<subprogram>` ∈ {`simulation`,`common`,`pre_process`,`post_process`}; scan these first when understanding macro usage

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Compile with Cray `ftn` or NVIDIA `nvfortran` for GPU offloading; also build CPU-only with GNU `gfortran` and Intel `ifx`/`ifort` for portability

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/**/*.fpp : Start module files with Fypp include for macros: `#:include 'macros.fpp'`

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/**/*.fpp : Use fypp macro `@:ALLOCATE(var1, var2)` for device-aware allocation instead of standard Fortran `allocate`

Applied to files:

  • tests/5CAA4E68/golden-metadata.txt
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `private` declaration followed by explicit `public` exports in modules

Applied to files:

  • src/pre_process/m_start_up.fpp
  • src/simulation/m_mpi_proxy.fpp
  • src/common/m_derived_types.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • src/common/m_helper.fpp
  • src/pre_process/m_assign_variables.fpp
  • src/simulation/m_global_parameters.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight loops with `$:GPU_PARALLEL_FOR(private='[...]', copy='[...]')` macro; add `collapse=n` for safe nested loop merging

Applied to files:

  • src/simulation/m_mpi_proxy.fpp
  • src/pre_process/m_mpi_proxy.fpp
  • src/simulation/m_qbmm.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Mark GPU-callable helpers with `$:GPU_ROUTINE(function_name='...', parallelism='[seq]')` immediately after declaration

Applied to files:

  • src/simulation/m_mpi_proxy.fpp
  • src/simulation/m_qbmm.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use derived types with pointers for encapsulation (e.g., `pointer, dimension(:,:,:) => null()`)

Applied to files:

  • src/common/m_derived_types.fpp
  • src/post_process/m_mpi_proxy.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct

Applied to files:

  • src/pre_process/m_mpi_proxy.fpp
  • src/simulation/m_bubbles.fpp
  • src/post_process/m_mpi_proxy.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Keep private helpers in the module; avoid nested procedures

Applied to files:

  • src/pre_process/m_mpi_proxy.fpp
  • src/common/m_helper.fpp
  • src/simulation/m_bubbles.fpp
  • src/post_process/m_mpi_proxy.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Limit routine arguments to ≤ 6; use derived-type params struct if more are needed

Applied to files:

  • src/pre_process/m_mpi_proxy.fpp
  • src/simulation/m_bubbles.fpp
  • src/post_process/m_mpi_proxy.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Each variable should have one clear purpose; do not use the same variable for multiple purposes

Applied to files:

  • src/pre_process/m_mpi_proxy.fpp
  • toolchain/mfc/run/case_dicts.py
  • src/common/m_helper.fpp
  • src/pre_process/m_assign_variables.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `wp` (working precision) parameter from `m_precision_select` instead of hardcoded precision like `real*8`

Applied to files:

  • src/common/m_variables_conversion.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Every variable must have `intent(in|out|inout)` specification and appropriate `dimension` / `allocatable` / `pointer`

Applied to files:

  • src/common/m_helper.fpp
  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/post_process/m_global_parameters.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up

Applied to files:

  • src/simulation/m_qbmm.fpp
  • src/simulation/m_global_parameters.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Allocate large arrays with `managed` or move them into a persistent `$:GPU_ENTER_DATA(...)` region at start-up

Applied to files:

  • src/simulation/m_qbmm.fpp
  • src/simulation/m_global_parameters.fpp
  • src/pre_process/m_global_parameters.fpp
  • src/simulation/m_bubbles_EE.fpp
  • src/simulation/m_start_up.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate

Applied to files:

  • src/simulation/m_global_parameters.fpp
  • src/simulation/m_bubbles.fpp
  • src/post_process/m_mpi_proxy.fpp
  • src/post_process/m_global_parameters.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `s_mpi_abort(<msg>)` for error termination instead of `stop`

Applied to files:

  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Call s_mpi_abort(<msg>) for errors, never use stop or error stop

Applied to files:

  • src/simulation/m_bubbles_EE.fpp
  • src/post_process/m_mpi_proxy.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Do not place `stop` or `error stop` inside device code

Applied to files:

  • src/simulation/m_bubbles_EE.fpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Github (ubuntu, mpi, debug, true)
  • GitHub Check: Github (ubuntu, mpi, no-debug, true)
  • GitHub Check: Github (ubuntu, no-mpi, single, no-debug, false)
  • GitHub Check: Georgia Tech | Phoenix (gpu-acc)
  • GitHub Check: Github (ubuntu, mpi, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, debug, false)
  • GitHub Check: Georgia Tech | Phoenix (cpu)
  • GitHub Check: Oak Ridge | Frontier (gpu-omp)
  • GitHub Check: Oak Ridge | Frontier (gpu-acc)
  • GitHub Check: Oak Ridge | Frontier (cpu)
  • GitHub Check: Georgia Tech | Phoenix (gpu-omp)
  • GitHub Check: Coverage Test on CodeCov
  • GitHub Check: Code Cleanliness Check
  • GitHub Check: Build & Publish
🔇 Additional comments (46)
toolchain/mfc/test/case.py (1)

59-66: LGTM - Legacy parameters removed in alignment with bub_pp refactor.

The removal of Ca, Web, Re_inv, R0ref, and Lagrangian parameters (c0, rho0, T0, x0, Thost) from BASE_CFG is consistent with the PR's objective to consolidate bubble parameters into the new bub_pp structure. Individual test cases will now specify these via bub_pp%... entries.

src/post_process/m_mpi_proxy.fpp (1)

139-146: LGTM - Scalar broadcasts are correctly implemented.

All bub_pp% members being broadcast (R0ref, p0ref, rho0ref, T0ref, ss, pv, vd, mu_l, mu_v, mu_g, gam_v, gam_g, M_v, M_g, k_v, k_g, cp_v, cp_g, R_v, R_g) are scalar real(wp) values per the derived type definition. Using count=1 is correct.

The conditional guard if (bubbles_euler .or. bubbles_lagrange) ensures these broadcasts only occur when bubble models are active.

tests/5CAA4E68/golden-metadata.txt (1)

1-7: Advisory: Golden metadata contains local development artifacts.

The metadata includes developer-specific paths (e.g., /Users/hyeoksu/MyWork/MFC-local/MFC/bubnorm/) and indicates a "dirty" Git state. While this captures the environment where golden outputs were generated, it may affect reproducibility documentation.

Consider regenerating golden files from a clean Git state if this test case is intended for CI validation.

examples/2D_whale_bubble_annulus/case.py (1)

127-140: LGTM - Bubble parameters correctly structured.

The transition from legacy parameters to the new bub_pp structure is properly implemented:

  • Fluid properties correctly compute gamma and pi_inf using the stiffened gas EOS formulation
  • Bubble parameters (R0ref, p0ref, rho0ref, pv, gam_g) are appropriately nondimensionalized
examples/3D_lagrange_bubblescreen/case.py (2)

143-144: LGTM - Lagrangian bubble configuration flags correctly set.

The addition of "thermal": 3 and "polytropic": "F" aligns with the new validator requirements for bubbles_lagrange and the PR's standardization of non-polytropic Lagrangian bubble models.


157-164: LGTM - Reference and transport parameters correctly assigned.

The bubble reference parameters (R0ref, p0ref, rho0ref, T0ref) and transport properties (ss, pv, vd, mu_l) are correctly nondimensionalized using the reference scales.

examples/3D_lagrange_shbubcollapse/case.py (1)

145-149: Confirm KM + polytropic="F" + thermal=3 is the intended combination for this example.
Given this PR refactors initialization/validation, please double-check this case still exercises the intended physics path (and doesn’t silently fall back / reinterpret flags).

toolchain/mfc/test/cases.py (1)

515-523: bub_pp consolidation in Eulerian bubble tests looks consistent.
The _v/_g fields (gamma, M, k, R) appear correctly assigned in these dictionaries.

Also applies to: 793-801

examples/1D_bubblescreen/case.py (1)

47-65: Ref-based nondimensionalization + bub_pp mapping looks consistent in this example.
Notably pi_inf_l is correctly scaled by p0, and acoustic magnitude/wavelength are scaled by p0/x0.

Also applies to: 124-167

examples/1D_exp_bubscreen/case.py (4)

5-55: Units/nondimensional reference block is coherent here (R_uni/M_ gives J/kg/K).*
This file’s R_uni/M_v/M_g convention is consistent and matches the intended R_v, R_g units.


62-67: dt/Tfinal scaling looks correct (dt = cfldxu0/cphysical).
No concerns with the revised nondimensional CFL expression and the consistent Tpulse*u0/x0 factor in Tfinal.


126-138: p0ext/p0 patch pressure normalization is a good cleanup—verify downstream expects nondimensional “pres”.
This is the right direction; just ensure the consuming Fortran path treats patch_icpp(* )%pres as nondimensional for this example type (especially if any legacy path assumed dimensional Pa).


143-144: bub_pp + acoustic scaling is consistent with the new parameterization.
pa/p0 and wavelength/x0 are the right nondimensional inputs, and the minimal bub_pp set matches what this example appears to need.

Also applies to: 151-158, 167-168

examples/0D_bubblecollapse_adap/case.py (3)

7-43: Refactor to named physical constants + reference scaling looks good.
The reorganized parameter block reads much clearer and aligns with the PR’s bub_pp-centric approach.


61-63: Domain/patch nondimensionalization updates are consistent (L/x0, p0ext/p0, R0ref/x0).
Nothing to flag here.

Also applies to: 97-103


108-118: bub_pp mapping + adap_dt settings + fluid_pp gamma/pi_inf look aligned with the new model.
No issues spotted in these conversions.

Also applies to: 124-125

src/post_process/m_start_up.fpp (2)

98-98: LGTM: bub_pp added to namelist.

This correctly integrates the new bubble parameter container into the post-process input reading.


964-966: LGTM: Unified bubble initialization path.

The conditional check for bubbles_euler .or. bubbles_lagrange and the call to s_initialize_bubbles_model() properly centralizes bubble model initialization, replacing the previous scattered polydisperse/quadrature setup.

src/simulation/m_bubbles_EL.fpp (2)

323-323: LGTM: Gas mass calculation uses R_g.

The gas constant naming change from R_n to R_g aligns with the PR's standardization of gas/vapor nomenclature.


337-338: LGTM: Heat capacity and thermal conductivity use standardized names.

The changes from cp_ncp_g and k_nlk_gl follow the consistent naming convention introduced in this PR.

src/pre_process/m_start_up.fpp (2)

138-139: LGTM: bub_pp added to pre-process namelist.

The bubble parameter container is correctly integrated into the pre-process input handling.


775-777: LGTM: Unified bubble initialization in pre-process.

Consistent with the post-process changes, this centralizes bubble model initialization when either bubbles_euler or bubbles_lagrange is active.

src/pre_process/m_mpi_proxy.fpp (2)

146-148: LGTM: Simplified fluid_pp broadcast.

The removal of D_v from the per-fluid broadcast loop is correct since bubble-related parameters are now consolidated in bub_pp.


159-166: LGTM: Consolidated bub_pp MPI broadcasts.

The new conditional block properly broadcasts all bubble parameters when bubbles_euler or bubbles_lagrange is enabled. This cleanly separates bubble-specific parameters from general fluid properties.

src/simulation/m_mpi_proxy.fpp (2)

191-193: LGTM: Consistent with pre-process MPI proxy.

The fluid_pp broadcast simplification mirrors the pre-process changes.


197-203: LGTM: bub_pp broadcasts in simulation MPI proxy.

Consistent with the pre-process implementation, all bubble parameters are broadcast in a dedicated conditional block.

src/simulation/m_bubbles_EE.fpp (2)

274-293: LGTM: Restructured alpha-based branching.

The new branching logic using small_alf threshold properly handles the low-alpha case by zeroing source terms, and the non-polytropic calculations are correctly placed inside the else branch.

Note: The initialization at lines 188-191 zeros bub_p_src and bub_m_src at the start, so the conditional zeroing at lines 278-281 is safe even for the polytropic case.


296-317: LGTM: Adaptive and non-adaptive time stepping paths.

The restructured adaptive time stepping correctly:

  • Initializes adap_dt_stop inside the conditional block
  • Calls s_advance_step with updated arguments including divu_in%sf(j, k, l) directly
  • Updates conservative variables directly when adapting
  • Falls back to f_rddot computation when not adapting

Line 316: The assignment bub_r_src(j, k, l, q) = q_cons_vf(vs(q))%sf(j, k, l) is correct - it assigns the velocity conservative variable (nRdot) as the radius source term, which is physically appropriate for the bubble radius evolution equation.

src/pre_process/m_assign_variables.fpp (1)

225-226: LGTM!

The replacement of fluid_pp(1)%pv with bub_pp%pv aligns with the PR's goal of centralizing bubble parameters.

src/common/m_derived_types.fpp (1)

354-377: Well-structured new derived type for bubble parameters.

The subgrid_bubble_physical_parameters type is cleanly documented and consolidates bubble-related physical parameters. The field naming follows the new _g/_v convention consistently.

toolchain/mfc/run/case_dicts.py (1)

155-156: LGTM!

The fluid_pp attribute reduction is consistent across all sections and aligns with the migration of bubble-specific parameters to bub_pp.

Also applies to: 421-422, 545-546

src/simulation/m_start_up.fpp (3)

158-158: LGTM!

Adding bub_pp to the user_inputs namelist enables reading the new bubble parameter structure from input files.


1308-1310: LGTM!

The condition change from bubbles_euler .and. nb > 1 to bubbles_euler .or. bubbles_lagrange correctly broadens bubble model initialization to cover both Euler-Euler and Euler-Lagrange approaches, and delegates to the new centralized s_initialize_bubbles_model().


1503-1506: Remove undefined variables from GPU_UPDATE macro at line 1506.

The GPU_UPDATE list contains five undefined variables: R_n, M_n, k_n, gamma_n, and mu_n. These variables do not exist in m_global_parameters or anywhere else in the codebase. The established naming convention uses _v (vapor) and _g (gas/mixture) suffixes, not _n. Remove these five undefined variables from the device list, or replace them with the correct corresponding variables (R_v/R_g, M_v/M_g, k_v/k_g, gam_v/gam_g, mu_v/mu_g) if they were intended.

⛔ Skipped due to learnings
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Declare loop-local variables with `private='[...]'` in GPU parallel loop macros
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Mark GPU-callable helpers with `$:GPU_ROUTINE(function_name='...', parallelism='[seq]')` immediately after declaration
src/common/m_helper.fpp (2)

143-194: LGTM!

The s_initialize_bubble_vars() subroutine correctly initializes bubble variables from bub_pp, computes nondimensional numbers (Eu, Ca, Web, Re_inv, Pe_c), and delegates to s_initialize_nonpoly() for non-polytropic cases. The use of f_is_default guards for optional parameters is appropriate.


110-141: Well-structured bubble model initialization.

The s_initialize_bubbles_model() subroutine properly organizes the initialization sequence: memory allocation, quadrature weight/node computation, and variable initialization. The conditional allocation based on polytropic and qbmm flags is correct.

examples/1D_poly_bubscreen/case.py (1)

136-143: LGTM!

The bub_pp parameter normalizations are dimensionally consistent. Reference values correctly normalize to unity, and physical properties (surface tension, viscosity, vapor pressure) use standard nondimensionalization.

src/post_process/m_global_parameters.fpp (1)

204-207: LGTM!

The new bub_pp container of type subgrid_bubble_physical_parameters is correctly declared and integrates well with the existing module structure.

src/pre_process/m_global_parameters.fpp (2)

508-508: LGTM!

The thermal variable is now properly initialized to dflt_int in the defaults subroutine, addressing the concern from the past review.


216-219: LGTM!

The new bub_pp container declaration follows the established pattern and integrates cleanly with the existing module structure.

src/simulation/m_bubbles.fpp (4)

434-435: Verify grad_T calculation against physical model.

A past review suggested reintroducing the Tw (wall temperature) scaling factor in the grad_T calculation. The current implementation computes a normalized temperature gradient without explicit Tw scaling. Please verify this formulation is consistent with the intended heat transfer model (Ando 2010 or similar).


54-54: LGTM!

The replacement of pref with Eu (Euler number) for the driving pressure offset in the Gilmore bubble model is consistent with the PR's new normalization scheme.


732-734: LGTM!

The early returns when intermediate radii become negative are a sensible numerical stability improvement. Setting err = adap_dt_tol + 1 correctly triggers step size reduction in the adaptive time-stepping algorithm.

Also applies to: 749-751, 766-768


787-789: LGTM!

The error correction for non-oscillating bubbles (where both initial and final velocities are approximately zero) prevents spurious error estimates from division by small numbers.

src/simulation/m_global_parameters.fpp (2)

971-971: LGTM!

The assignment gam = bub_pp%gam_g correctly initializes the gas gamma ratio from the bubble parameter container within the bubble-enabled code path.


731-731: LGTM!

Using the named constant dflt_adap_dt_tol instead of dflt_real provides a sensible default tolerance for adaptive time stepping configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/documentation/case.md (1)

744-782: Correct bubble_model doc: text says “1 and 2” but table defines 1–3.

After introducing Rayleigh–Plesset as bubble_model = 3 (Line 748-749, 781-782), the narrative still says “by an integer of 1 and 2” (Line 780). This is user-facing and can misconfigure runs.

-- `bubble_model` specified a model for spherical bubble dynamics by an integer of 1 and 2.
+- `bubble_model` specifies a model for spherical bubble dynamics by an integer of 1 through 3.
🧹 Nitpick comments (1)
docs/documentation/case.md (1)

749-773: Tighten footnote/marker consistency for required-parameter notation.

The requirement markers are slightly inconsistent (e.g., bub_pp%vd shows † ‡ with an extra space at Line 755). Consider normalizing the markers (*†‡) and ensuring the requirement bullets match the table’s markers to avoid ambiguity as bub_pp expands.

Also applies to: 783-785

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8c19b0 and a4384fd.

📒 Files selected for processing (2)
  • docs/documentation/case.md (2 hunks)
  • src/common/m_derived_types.fpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{fpp,f90}: Use 2-space indentation; continuation lines align beneath &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_ pattern (e.g., m_transport)
Name public subroutines with s_ pattern (e.g., s_compute_flux)
Name public functions with f
_ pattern
Keep subroutine size ≤ 500 lines, helper subroutines ≤ 150 lines, functions ≤ 100 lines, files ≤ 1000 lines
Limit routine arguments to ≤ 6; use derived-type params struct if more are needed
Forbid goto statements (except in legacy code), COMMON blocks, and save globals
Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate
Call s_mpi_abort() for errors, never use stop or error stop

**/*.{fpp,f90}: Indent 2 spaces; continuation lines align under &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_<feature> prefix (e.g., m_transport)
Name public subroutines as s_<verb>_<noun> (e.g., s_compute_flux) and functions as f_<verb>_<noun>
Keep private helpers in the module; avoid nested procedures
Enforce size limits: subroutine ≤ 500 lines, helper ≤ 150, function ≤ 100, module/file ≤ 1000
Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct
Avoid goto statements (except unavoidable legacy); avoid global state (COMMON, save)
Every variable must have intent(in|out|inout) specification and appropriate dimension / allocatable / pointer
Use s_mpi_abort(<msg>) for error termination instead of stop
Use !> style documentation for header comments; follow Doxygen Fortran format with !! @param and !! @return tags
Use implicit none statement in all modules
Use private declaration followed by explicit public exports in modules
Use derived types with pointers for encapsulation (e.g., pointer, dimension(:,:,:) => null())
Use pure and elemental attributes for side-effect-free functions; combine them for array ...

Files:

  • src/common/m_derived_types.fpp
src/**/*.fpp

📄 CodeRabbit inference engine (.cursor/rules/mfc-agent-rules.mdc)

src/**/*.fpp: Use .fpp file extension for Fypp preprocessed files; CMake transpiles them to .f90
Start module files with Fypp include for macros: #:include 'macros.fpp'
Use the fypp ASSERT macro for validating conditions: @:ASSERT(predicate, message)
Use fypp macro @:ALLOCATE(var1, var2) for device-aware allocation instead of standard Fortran allocate
Use fypp macro @:DEALLOCATE(var1, var2) for device-aware deallocation instead of standard Fortran deallocate

Files:

  • src/common/m_derived_types.fpp
🧠 Learnings (2)
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `private` declaration followed by explicit `public` exports in modules

Applied to files:

  • src/common/m_derived_types.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use derived types with pointers for encapsulation (e.g., `pointer, dimension(:,:,:) => null()`)

Applied to files:

  • src/common/m_derived_types.fpp
🪛 markdownlint-cli2 (0.18.1)
docs/documentation/case.md

746-746: Hard tabs
Column: 31

(MD010, no-hard-tabs)


746-746: Hard tabs
Column: 67

(MD010, no-hard-tabs)


747-747: Hard tabs
Column: 31

(MD010, no-hard-tabs)


747-747: Hard tabs
Column: 65

(MD010, no-hard-tabs)


748-748: Hard tabs
Column: 31

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Github (ubuntu, mpi, no-debug, true)
  • GitHub Check: Github (ubuntu, mpi, debug, true)
  • GitHub Check: Github (ubuntu, no-mpi, single, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, debug, false)
  • GitHub Check: Oak Ridge | Frontier (cpu)
  • GitHub Check: Github (ubuntu, mpi, no-debug, false)
  • GitHub Check: Oak Ridge | Frontier (gpu-acc)
  • GitHub Check: Oak Ridge | Frontier (gpu-omp)
  • GitHub Check: Georgia Tech | Phoenix (gpu-acc)
  • GitHub Check: Georgia Tech | Phoenix (gpu-omp)
  • GitHub Check: Georgia Tech | Phoenix (cpu)
  • GitHub Check: Coverage Test on CodeCov
  • GitHub Check: Code Cleanliness Check
  • GitHub Check: Build & Publish
🔇 Additional comments (1)
src/common/m_derived_types.fpp (1)

354-376: LGTM! Well-structured bubble parameter container.

The new subgrid_bubble_physical_parameters type successfully centralizes sub-grid bubble model parameters. The naming convention (_g for gas, _v for vapor) aligns with the PR's standardization effort, and the type is well-documented with appropriate inline comments. All fields use real(wp) for precision consistency.

@codecov
Copy link

codecov bot commented Dec 12, 2025

Codecov Report

❌ Patch coverage is 71.83099% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.99%. Comparing base (8ae42aa) to head (8631676).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/common/m_helper.fpp 69.35% 15 Missing and 4 partials ⚠️
src/simulation/m_bubbles.fpp 48.38% 9 Missing and 7 partials ⚠️
src/simulation/m_bubbles_EE.fpp 48.00% 13 Missing ⚠️
src/pre_process/m_assign_variables.fpp 42.85% 4 Missing ⚠️
src/common/m_variables_conversion.fpp 50.00% 1 Missing ⚠️
src/post_process/m_mpi_proxy.fpp 50.00% 0 Missing and 1 partial ⚠️
src/post_process/m_start_up.fpp 50.00% 0 Missing and 1 partial ⚠️
src/pre_process/m_mpi_proxy.fpp 50.00% 0 Missing and 1 partial ⚠️
src/pre_process/m_start_up.fpp 50.00% 0 Missing and 1 partial ⚠️
src/simulation/m_mpi_proxy.fpp 50.00% 0 Missing and 1 partial ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1085      +/-   ##
==========================================
- Coverage   44.08%   43.99%   -0.09%     
==========================================
  Files          71       71              
  Lines       20332    20289      -43     
  Branches     1981     1982       +1     
==========================================
- Hits         8963     8927      -36     
+ Misses      10236    10225      -11     
- Partials     1133     1137       +4     

☔ View full report in Codecov by Sentry.
📢 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.

@codeant-ai
Copy link

codeant-ai bot commented Dec 12, 2025

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/simulation/m_time_steppers.fpp (1)

515-515: Unused local variables denom and nbubble should be removed.

These variables are declared but never referenced in s_tvd_rk. Remove them to eliminate compiler warnings and dead code.

-        real(wp) :: denom, nbubble
toolchain/mfc/run/case_dicts.py (1)

110-113: Extract duplicated bub_pp% variable list into a constant.

The same 20-variable list for bub_pp% parameters is repeated identically at lines 110-113 (PRE_PROCESS), 362-365 (SIMULATION), and 514-517 (POST_PROCESS). If the variable list needs updating, all three locations must be changed in sync, risking inconsistency.

Consider defining a constant:

BUB_PP_VARS = ["R0ref", "p0ref", "rho0ref", "T0ref", "ss", "pv", "vd",
               "mu_l", "mu_v", "mu_g", "gam_v", "gam_g",
               "M_v", "M_g", "k_v", "k_g", "cp_v", "cp_g", "R_v", "R_g"]

# Then use in each dictionary:
for var in BUB_PP_VARS:
    PRE_PROCESS[f"bub_pp%{var}"] = ParamType.REAL
docs/documentation/case.md (1)

755-757: Hard tabs detected in table rows.

Static analysis flags hard tabs (MD010) in the bubbles_euler, bubbles_lagrange, and bubble_model table rows at columns 31 and 65-67. Replace tabs with spaces for consistent markdown rendering.

Apply this fix to replace hard tabs with spaces:

-| `bubbles_euler`    | Logical	| Ensemble-averaged bubble modeling	|
-| `bubbles_lagrange` | Logical	| Volume-averaged bubble modeling	|
-| `bubble_model`     | Integer	| [1] Gilmore; [2] Keller--Miksis; [3] Rayleigh-Plesset |
+| `bubbles_euler`    | Logical | Ensemble-averaged bubble modeling |
+| `bubbles_lagrange` | Logical | Volume-averaged bubble modeling |
+| `bubble_model`     | Integer | [1] Gilmore; [2] Keller--Miksis; [3] Rayleigh-Plesset |
🧹 Nitpick comments (1)
src/pre_process/m_global_parameters.fpp (1)

263-264: Potential confusion: k_v and k_g declared as both scalars and arrays.

Line 263 declares k_v, k_g as allocatable arrays (real(wp), dimension(:), allocatable :: ... k_v, k_g), but bub_pp%k_v and bub_pp%k_g are scalars in the derived type. This naming collision could cause confusion. Consider renaming one set (e.g., k_v_arr for the array version) or documenting the distinction clearly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a38383 and 8a5dd82.

📒 Files selected for processing (6)
  • docs/documentation/case.md (3 hunks)
  • src/common/m_derived_types.fpp (1 hunks)
  • src/pre_process/m_global_parameters.fpp (5 hunks)
  • src/simulation/m_mpi_proxy.fpp (1 hunks)
  • src/simulation/m_time_steppers.fpp (1 hunks)
  • toolchain/mfc/run/case_dicts.py (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/simulation/m_mpi_proxy.fpp
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{fpp,f90}: Use 2-space indentation; continuation lines align beneath &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_ pattern (e.g., m_transport)
Name public subroutines with s_ pattern (e.g., s_compute_flux)
Name public functions with f
_ pattern
Keep subroutine size ≤ 500 lines, helper subroutines ≤ 150 lines, functions ≤ 100 lines, files ≤ 1000 lines
Limit routine arguments to ≤ 6; use derived-type params struct if more are needed
Forbid goto statements (except in legacy code), COMMON blocks, and save globals
Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate
Call s_mpi_abort() for errors, never use stop or error stop

**/*.{fpp,f90}: Indent 2 spaces; continuation lines align under &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_<feature> prefix (e.g., m_transport)
Name public subroutines as s_<verb>_<noun> (e.g., s_compute_flux) and functions as f_<verb>_<noun>
Keep private helpers in the module; avoid nested procedures
Enforce size limits: subroutine ≤ 500 lines, helper ≤ 150, function ≤ 100, module/file ≤ 1000
Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct
Avoid goto statements (except unavoidable legacy); avoid global state (COMMON, save)
Every variable must have intent(in|out|inout) specification and appropriate dimension / allocatable / pointer
Use s_mpi_abort(<msg>) for error termination instead of stop
Use !> style documentation for header comments; follow Doxygen Fortran format with !! @param and !! @return tags
Use implicit none statement in all modules
Use private declaration followed by explicit public exports in modules
Use derived types with pointers for encapsulation (e.g., pointer, dimension(:,:,:) => null())
Use pure and elemental attributes for side-effect-free functions; combine them for array ...

Files:

  • src/simulation/m_time_steppers.fpp
  • src/common/m_derived_types.fpp
  • src/pre_process/m_global_parameters.fpp
src/simulation/**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/simulation/**/*.{fpp,f90}: Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)
Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up
Avoid stop/error stop inside GPU device code
Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

src/simulation/**/*.{fpp,f90}: Mark GPU-callable helpers with $:GPU_ROUTINE(function_name='...', parallelism='[seq]') immediately after declaration
Do not use OpenACC or OpenMP directives directly; use Fypp macros from src/common/include/parallel_macros.fpp instead
Wrap tight loops with $:GPU_PARALLEL_FOR(private='[...]', copy='[...]') macro; add collapse=n for safe nested loop merging
Declare loop-local variables with private='[...]' in GPU parallel loop macros
Allocate large arrays with managed or move them into a persistent $:GPU_ENTER_DATA(...) region at start-up
Do not place stop or error stop inside device code

Files:

  • src/simulation/m_time_steppers.fpp
src/**/*.fpp

📄 CodeRabbit inference engine (.cursor/rules/mfc-agent-rules.mdc)

src/**/*.fpp: Use .fpp file extension for Fypp preprocessed files; CMake transpiles them to .f90
Start module files with Fypp include for macros: #:include 'macros.fpp'
Use the fypp ASSERT macro for validating conditions: @:ASSERT(predicate, message)
Use fypp macro @:ALLOCATE(var1, var2) for device-aware allocation instead of standard Fortran allocate
Use fypp macro @:DEALLOCATE(var1, var2) for device-aware deallocation instead of standard Fortran deallocate

Files:

  • src/simulation/m_time_steppers.fpp
  • src/common/m_derived_types.fpp
  • src/pre_process/m_global_parameters.fpp
🧠 Learnings (7)
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Declare loop-local variables with `private='[...]'` in GPU parallel loop macros

Applied to files:

  • src/simulation/m_time_steppers.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)

Applied to files:

  • src/simulation/m_time_steppers.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Avoid stop/error stop inside GPU device code

Applied to files:

  • src/simulation/m_time_steppers.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `private` declaration followed by explicit `public` exports in modules

Applied to files:

  • src/common/m_derived_types.fpp
  • src/pre_process/m_global_parameters.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Allocate large arrays with `managed` or move them into a persistent `$:GPU_ENTER_DATA(...)` region at start-up

Applied to files:

  • src/pre_process/m_global_parameters.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up

Applied to files:

  • src/pre_process/m_global_parameters.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Each variable should have one clear purpose; do not use the same variable for multiple purposes

Applied to files:

  • toolchain/mfc/run/case_dicts.py
🧬 Code graph analysis (3)
src/common/m_derived_types.fpp (1)
toolchain/mfc/test/cases.py (1)
  • alter_viscosity (786-835)
docs/documentation/case.md (1)
toolchain/mfc/test/cases.py (1)
  • alter_viscosity (786-835)
toolchain/mfc/run/case_dicts.py (1)
toolchain/mfc/case.py (1)
  • Case (19-292)
🪛 markdownlint-cli2 (0.18.1)
docs/documentation/case.md

755-755: Hard tabs
Column: 31

(MD010, no-hard-tabs)


755-755: Hard tabs
Column: 67

(MD010, no-hard-tabs)


756-756: Hard tabs
Column: 31

(MD010, no-hard-tabs)


756-756: Hard tabs
Column: 65

(MD010, no-hard-tabs)


757-757: Hard tabs
Column: 31

(MD010, no-hard-tabs)


803-803: Hard tabs
Column: 29

(MD010, no-hard-tabs)


804-804: Hard tabs
Column: 8

(MD010, no-hard-tabs)


804-804: Hard tabs
Column: 27

(MD010, no-hard-tabs)


805-805: Hard tabs
Column: 16

(MD010, no-hard-tabs)


805-805: Hard tabs
Column: 27

(MD010, no-hard-tabs)


805-805: Hard tabs
Column: 30

(MD010, no-hard-tabs)


806-806: Hard tabs
Column: 10

(MD010, no-hard-tabs)


806-806: Hard tabs
Column: 30

(MD010, no-hard-tabs)


806-806: Hard tabs
Column: 32

(MD010, no-hard-tabs)


807-807: Hard tabs
Column: 32

(MD010, no-hard-tabs)


808-808: Hard tabs
Column: 10

(MD010, no-hard-tabs)


808-808: Hard tabs
Column: 27

(MD010, no-hard-tabs)


808-808: Hard tabs
Column: 30

(MD010, no-hard-tabs)


809-809: Hard tabs
Column: 10

(MD010, no-hard-tabs)


809-809: Hard tabs
Column: 27

(MD010, no-hard-tabs)


809-809: Hard tabs
Column: 30

(MD010, no-hard-tabs)


810-810: Hard tabs
Column: 10

(MD010, no-hard-tabs)


810-810: Hard tabs
Column: 27

(MD010, no-hard-tabs)


810-810: Hard tabs
Column: 30

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: Coverage Test on CodeCov
  • GitHub Check: Github (ubuntu, mpi, no-debug, false)
  • GitHub Check: Georgia Tech | Phoenix (cpu)
  • GitHub Check: Github (ubuntu, no-mpi, single, no-debug, false)
  • GitHub Check: Github (macos, mpi, debug, false)
  • GitHub Check: Georgia Tech | Phoenix (gpu-acc)
  • GitHub Check: Github (ubuntu, mpi, debug, true)
  • GitHub Check: Github (ubuntu, mpi, debug, false)
  • GitHub Check: Github (ubuntu, mpi, no-debug, true)
  • GitHub Check: Github (macos, mpi, no-debug, false)
  • GitHub Check: Georgia Tech | Phoenix (gpu-omp)
  • GitHub Check: Oak Ridge | Frontier (gpu-acc)
  • GitHub Check: Oak Ridge | Frontier (gpu-omp)
  • GitHub Check: Oak Ridge | Frontier (cpu)
  • GitHub Check: Code Cleanliness Check
  • GitHub Check: Build & Publish
🔇 Additional comments (7)
src/common/m_derived_types.fpp (2)

356-378: Well-structured new derived type for subgrid bubble parameters.

The subgrid_bubble_physical_parameters type cleanly consolidates bubble-related physical parameters that were previously scattered. The Doxygen-style comments clearly document each field's purpose (host vs gas, liquid/vapor/gas states). This improves maintainability and aligns with the PR objective of centralizing bubble parameter handling.


346-354: Slimmed physical_parameters type is appropriate.

The removal of bubble-specific fields (mul0, ss, pv, gamma_v, M_v, mu_v, k_v, cp_v, D_v) from physical_parameters is correct since these are now consolidated in subgrid_bubble_physical_parameters. The remaining fields (gamma, pi_inf, Re, cv, qv, qvp, G) are general fluid properties that belong here.

src/pre_process/m_global_parameters.fpp (2)

508-508: thermal is properly initialized.

The thermal variable is correctly initialized to dflt_int in the default values subroutine. This addresses the uninitialized variable concern.


610-611: Delete this comment — the initialization is correct.

k_v and k_g are allocatable arrays (declared at line 263), not scalar variables. The initialization at lines 610–611 is correct: only bub_pp%k_v and bub_pp%k_g should be initialized here. The pattern at lines 596–609 and 612–615 applies to scalar variables like R0ref, p0ref, etc., which have both member and standalone forms. Arrays like k_v and k_g do not have scalar counterparts and should not be initialized as scalars.

Likely an incorrect or invalid review comment.

toolchain/mfc/run/case_dicts.py (1)

155-156: Approve: Streamlined fluid_pp attributes.

The fluid_pp attributes are correctly reduced to ["gamma", "pi_inf", "G", "cv", "qv", "qvp"], removing bubble-specific parameters that are now in bub_pp. This aligns with the type changes in m_derived_types.fpp.

docs/documentation/case.md (2)

753-778: Comprehensive documentation for bub_pp parameters.

The new parameter table clearly documents all bub_pp% fields with appropriate footnote markers indicating which parameters are required for EE vs EL models, and polytropic vs non-polytropic cases. The descriptions are clear and consistent with the type definition in m_derived_types.fpp.


792-793: Good addition of bub_pp description.

The new description clearly states that bub_pp specifies simulation parameters for both EE and EL bubble models, providing helpful context for users.

@hyeoksu-lee
Copy link
Contributor Author

@sbryngelson yeah, it passed this time!

@sbryngelson
Copy link
Member

/improve

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR performs a comprehensive refactoring of sub-grid bubble models (Euler-Euler and Euler-Lagrange) to eliminate redundancy and improve consistency. The key architectural change is the introduction of a unified bub_pp (bubble physical parameters) structure that centralizes all bubble-related parameters previously scattered across fluid_pp% and other locations. The PR also standardizes nomenclature by replacing subscript _n (originally for gas) with _g throughout the codebase, and replaces R0ref with Eu (Euler number) as the primary nondimensional parameter.

Key changes:

  • Consolidated EE and EL bubble initialization into a single s_initialize_bubbles_model() subroutine
  • Introduced subgrid_bubble_physical_parameters derived type to organize all bubble parameters
  • Updated all example cases and test configurations to use the new bub_pp% structure

Reviewed changes

Copilot reviewed 73 out of 74 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/common/m_derived_types.fpp Defines new subgrid_bubble_physical_parameters type with 18 bubble-related fields
src/common/m_helper.fpp Creates unified s_initialize_bubbles_model() and s_initialize_bubble_vars() subroutines
src/*/m_global_parameters.fpp Adds bub_pp structure declaration and removes bubble params from fluid_pp
src/simulation/m_bubbles.fpp Updates nomenclature (_n_g), replaces pref with Eu, refactors adaptive time-stepping
src/simulation/m_bubbles_EE.fpp Restructures source computation with improved validation
src/simulation/m_bubbles_EL.fpp Removes s_start_lagrange_inputs(), simplifies initialization
src/*/m_mpi_proxy.fpp Updates MPI broadcasts for new bub_pp structure
toolchain/mfc/run/case_dicts.py Adds bub_pp% parameters, removes old bubble params from fluid_pp()%
toolchain/mfc/case_validator.py Updates validation logic for Lagrangian bubbles
examples/*/case.py Updates 13+ example cases with new bub_pp% structure

Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com>
@codeant-ai
Copy link

codeant-ai bot commented Dec 15, 2025

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
docs/documentation/case.md (2)

753-781: Remove hard tabs and standardize †‡ marker in sub-grid bubble parameter table.

markdownlint still reports MD010 hard tabs on the header rows; also the †‡ marker for bub_pp%vd is inconsistent with nearby markers.

Suggested patch:

-| `bubbles_euler`    | Logical	| Ensemble-averaged bubble modeling	|
-| `bubbles_lagrange` | Logical	| Volume-averaged bubble modeling	|
-| `bubble_model`     | Integer	| [1] Gilmore; [2] Keller--Miksis; [3] Rayleigh-Plesset |
+| `bubbles_euler`    | Logical | Ensemble-averaged bubble modeling |
+| `bubbles_lagrange` | Logical | Volume-averaged bubble modeling   |
+| `bubble_model`     | Integer | [1] Gilmore; [2] Keller--Miksis; [3] Rayleigh-Plesset |
@@
-| `bub_pp%vd`† ‡     | Real    | Vapor diffusivity in gas (bubble) |
+| `bub_pp%vd`†‡      | Real    | Vapor diffusivity in gas (bubble) |

798-810: Fix remaining hard tabs and small wording issues in ensemble-averaged bubble model table.

These rows still contain hard tabs (MD010) and a minor wording glitch in the qbmm description.

Suggested patch:

-| `bubbles_euler`   | Logical | Ensemble-averaged bubble modeling	|
+| `bubbles_euler`   | Logical | Ensemble-averaged bubble modeling |
@@
-| `thermal` 		    | Integer | Thermal model: [1] Adiabatic; [2] Isothermal; [3] Transfer |
+| `thermal`         | Integer | Thermal model: [1] Adiabatic; [2] Isothermal; [3] Transfer |
@@
-| `poly_sigma`      | Real 	  |	Standard deviation for probability density function of polydisperse bubble populations |
-| `qbmm`            | Logical |	Quadrature by  method of moments |
-| `dist_type`       | Integer |	Joint probability density function for bubble radius and velocity (only for ``qbmm = 'T'``) |
-| `sigR`            | Real    |	Standard deviation for the probability density function of bubble radius (only for ``qbmm = 'T'``) (EE)|
-| `sigV`            | Real    |	Standard deviation for the probability density function of bubble velocity (only for ``qbmm = 'T'``) (EE)|
-| `rhoRV`           | Real    |	Correlation coefficient for the joint probability density function of bubble radius and velocity (only for ``qbmm = 'T'``) |
+| `poly_sigma`      | Real    | Standard deviation for probability density function of polydisperse bubble populations |
+| `qbmm`            | Logical | Quadrature by method of moments |
+| `dist_type`       | Integer | Joint probability density function for bubble radius and velocity (only for ``qbmm = 'T'``) |
+| `sigR`            | Real    | Standard deviation for the probability density function of bubble radius (only for ``qbmm = 'T'``) (EE)|
+| `sigV`            | Real    | Standard deviation for the probability density function of bubble velocity (only for ``qbmm = 'T'``) (EE)|
+| `rhoRV`           | Real    | Correlation coefficient for the joint probability density function of bubble radius and velocity (only for ``qbmm = 'T'``) |
src/simulation/m_bubbles.fpp (3)

434-435: Verify temperature gradient scaling factor.

A previous review indicated that the Tw (wall temperature) scaling factor may be missing from the grad_T calculation. The current formula computes a dimensionless temperature ratio, but it's unclear whether dimensional consistency requires the Tw factor. Please verify the physical correctness of this expression against the governing equations.

If Tw scaling is required, apply this diff:

-            grad_T = -Re_trans_T(iR0)*((fpb/pb0(iR0))*(fR/R0(iR0))**3 &
-                                       *(mass_g0(iR0) + mass_v0(iR0))/(mass_g0(iR0) + fmass_v) - 1._wp)
+            grad_T = -Re_trans_T(iR0)*Tw*((fpb/pb0(iR0))*(fR/R0(iR0))**3 &
+                                          *(mass_g0(iR0) + mass_v0(iR0))/(mass_g0(iR0) + fmass_v) - 1._wp)

360-361: Make optional arguments required or add present() guards.

The optional dummy arguments fmass_g, fbeta_c, fR_m, and fgamma_m are used unconditionally on lines 372, 376–377, 380, and 382 without present() checks. This can lead to undefined behavior if call sites omit these arguments. Since all current call sites (e.g., line 816) pass these arguments, the safest fix is to make them required by removing the optional attribute.

Apply this diff to make the arguments required:

-        real(wp), intent(in), optional :: fmass_g, fbeta_c
-        real(wp), intent(out), optional :: fR_m, fgamma_m
+        real(wp), intent(in) :: fmass_g, fbeta_c
+        real(wp), intent(out) :: fR_m, fgamma_m

376-376: Normalize the mixture gas constant by total mass.

The mixture gas constant fR_m is computed as a sum of mass-weighted constants but is not normalized by total mass. This produces an incorrect value scale. The formula should compute a mass-weighted average by dividing by (fmass_g + fmass_v).

Apply this diff to fix the normalization:

-                fR_m = (fmass_g*R_g + fmass_v*R_v)
+                fR_m = (fmass_g*R_g + fmass_v*R_v)/(fmass_g + fmass_v)
🧹 Nitpick comments (2)
examples/0D_bubblecollapse_adap/case.py (1)

14-24: Unused vapor and air properties.

The variables gam_v, M_v, mu_v, k_v, M_g, mu_g, and k_g are defined but never used in the configuration dictionary. Only gam_g is used at line 114. Since this case uses polytropic: "T", these non-polytropic parameters aren't needed.

Consider removing the unused variables to reduce clutter, or add a comment indicating they're kept for reference/future non-polytropic configurations.

src/simulation/m_bubbles.fpp (1)

784-786: Consider extracting the threshold as a named constant.

The hardcoded threshold 1.e-12_wp for detecting non-oscillating bubbles (line 784) would be more maintainable as a named module-level constant (e.g., velocity_tolerance). This would improve code clarity and make it easier to adjust the threshold if needed.

Example refactor:

+    real(wp), parameter :: velocity_tolerance = 1.e-12_wp
     ...
-        if (max(abs(myV_tmp(1)), abs(myV_tmp(4))) < 1.e-12_wp) then
+        if (max(abs(myV_tmp(1)), abs(myV_tmp(4))) < velocity_tolerance) then
             err_V = 0._wp
         end if
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9fda20 and 6246e3c.

📒 Files selected for processing (3)
  • docs/documentation/case.md (1 hunks)
  • examples/0D_bubblecollapse_adap/case.py (3 hunks)
  • src/simulation/m_bubbles.fpp (20 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{fpp,f90}: Use 2-space indentation; continuation lines align beneath &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_ pattern (e.g., m_transport)
Name public subroutines with s_ pattern (e.g., s_compute_flux)
Name public functions with f
_ pattern
Keep subroutine size ≤ 500 lines, helper subroutines ≤ 150 lines, functions ≤ 100 lines, files ≤ 1000 lines
Limit routine arguments to ≤ 6; use derived-type params struct if more are needed
Forbid goto statements (except in legacy code), COMMON blocks, and save globals
Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate
Call s_mpi_abort() for errors, never use stop or error stop

**/*.{fpp,f90}: Indent 2 spaces; continuation lines align under &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_<feature> prefix (e.g., m_transport)
Name public subroutines as s_<verb>_<noun> (e.g., s_compute_flux) and functions as f_<verb>_<noun>
Keep private helpers in the module; avoid nested procedures
Enforce size limits: subroutine ≤ 500 lines, helper ≤ 150, function ≤ 100, module/file ≤ 1000
Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct
Avoid goto statements (except unavoidable legacy); avoid global state (COMMON, save)
Every variable must have intent(in|out|inout) specification and appropriate dimension / allocatable / pointer
Use s_mpi_abort(<msg>) for error termination instead of stop
Use !> style documentation for header comments; follow Doxygen Fortran format with !! @param and !! @return tags
Use implicit none statement in all modules
Use private declaration followed by explicit public exports in modules
Use derived types with pointers for encapsulation (e.g., pointer, dimension(:,:,:) => null())
Use pure and elemental attributes for side-effect-free functions; combine them for array ...

Files:

  • src/simulation/m_bubbles.fpp
src/simulation/**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/simulation/**/*.{fpp,f90}: Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)
Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up
Avoid stop/error stop inside GPU device code
Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

src/simulation/**/*.{fpp,f90}: Mark GPU-callable helpers with $:GPU_ROUTINE(function_name='...', parallelism='[seq]') immediately after declaration
Do not use OpenACC or OpenMP directives directly; use Fypp macros from src/common/include/parallel_macros.fpp instead
Wrap tight loops with $:GPU_PARALLEL_FOR(private='[...]', copy='[...]') macro; add collapse=n for safe nested loop merging
Declare loop-local variables with private='[...]' in GPU parallel loop macros
Allocate large arrays with managed or move them into a persistent $:GPU_ENTER_DATA(...) region at start-up
Do not place stop or error stop inside device code

Files:

  • src/simulation/m_bubbles.fpp
src/**/*.fpp

📄 CodeRabbit inference engine (.cursor/rules/mfc-agent-rules.mdc)

src/**/*.fpp: Use .fpp file extension for Fypp preprocessed files; CMake transpiles them to .f90
Start module files with Fypp include for macros: #:include 'macros.fpp'
Use the fypp ASSERT macro for validating conditions: @:ASSERT(predicate, message)
Use fypp macro @:ALLOCATE(var1, var2) for device-aware allocation instead of standard Fortran allocate
Use fypp macro @:DEALLOCATE(var1, var2) for device-aware deallocation instead of standard Fortran deallocate

Files:

  • src/simulation/m_bubbles.fpp
🧠 Learnings (9)
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight loops with `$:GPU_PARALLEL_FOR(private='[...]', copy='[...]')` macro; add `collapse=n` for safe nested loop merging

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Every variable must have `intent(in|out|inout)` specification and appropriate `dimension` / `allocatable` / `pointer`

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Limit routine arguments to ≤ 6; use derived-type params struct if more are needed

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Mark GPU-callable helpers with `$:GPU_ROUTINE(function_name='...', parallelism='[seq]')` immediately after declaration

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `wp` (working precision) parameter from `m_precision_select` instead of hardcoded precision like `real*8`

Applied to files:

  • src/simulation/m_bubbles.fpp
🧬 Code graph analysis (1)
examples/0D_bubblecollapse_adap/case.py (1)
toolchain/mfc/test/cases.py (1)
  • alter_viscosity (786-835)
🪛 markdownlint-cli2 (0.18.1)
docs/documentation/case.md

755-755: Hard tabs
Column: 31

(MD010, no-hard-tabs)


755-755: Hard tabs
Column: 67

(MD010, no-hard-tabs)


756-756: Hard tabs
Column: 31

(MD010, no-hard-tabs)


756-756: Hard tabs
Column: 65

(MD010, no-hard-tabs)


757-757: Hard tabs
Column: 31

(MD010, no-hard-tabs)


800-800: Hard tabs
Column: 66

(MD010, no-hard-tabs)


802-802: Hard tabs
Column: 13

(MD010, no-hard-tabs)


805-805: Hard tabs
Column: 28

(MD010, no-hard-tabs)


805-805: Hard tabs
Column: 32

(MD010, no-hard-tabs)


806-806: Hard tabs
Column: 32

(MD010, no-hard-tabs)


807-807: Hard tabs
Column: 32

(MD010, no-hard-tabs)


808-808: Hard tabs
Column: 32

(MD010, no-hard-tabs)


809-809: Hard tabs
Column: 32

(MD010, no-hard-tabs)


810-810: Hard tabs
Column: 32

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Github (ubuntu, mpi, no-debug, true)
  • GitHub Check: Github (ubuntu, mpi, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, debug, true)
  • GitHub Check: Github (ubuntu, no-mpi, single, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, debug, false)
  • GitHub Check: Code Cleanliness Check
  • GitHub Check: Coverage Test on CodeCov
  • GitHub Check: Build & Publish
🔇 Additional comments (11)
docs/documentation/case.md (1)

789-792: bubble_model / bub_pp descriptions look consistent and clear.

The mapping of bubble_model = 1,2,3 to Gilmore / Keller–Miksis / Rayleigh–Plesset and the brief bub_pp description match the new centralized-parameter design.

examples/0D_bubblecollapse_adap/case.py (1)

54-128: LGTM!

The configuration dictionary is well-structured with consistent nondimensionalization. The dt issue from past reviews has been properly resolved—the computed value is now used at line 66. The bub_pp parameters are correctly scaled, and the fluid properties follow the stiffened gas EOS formulation.

src/simulation/m_bubbles.fpp (9)

54-54: LGTM: Euler number integration.

The replacement of pref with Eu in the Gilmore branch (line 54) and the addition of Eu in the Keller-Miksis polytropic branch (line 267) correctly implements the PR's objective to use Euler number as the primary nondimensional parameter for bubble pressure calculations.

Also applies to: 267-267


332-336: LGTM: Gas nomenclature standardization.

The systematic renaming from _n (subscript n) to _g (subscript g) for gas-related properties (R_g, M_g, k_g, phi_gv, phi_vg) correctly implements the PR's standardized nomenclature for gas-related variables.


468-468: LGTM: Consistent fmass_g parameter propagation.

The addition of fmass_g (gas mass) to the signatures of s_advance_step, s_advance_substep, and s_advance_EL correctly implements the PR's gas-centric parameter handling. The parameter flows consistently through the call chain: s_advance_steps_advance_substeps_advance_ELs_vflux.

Also applies to: 477-477, 694-694, 704-704, 807-807, 810-810


482-482: LGTM: Clearer time step initialization.

The refactored time step initialization (declaring h0 separately at line 482, retrieving it from s_initial_substep_h at line 489, and explicitly assigning h = h0 at line 490) makes the initial time step selection more explicit and easier to follow.

Also applies to: 489-490


513-516: LGTM: Efficient early-exit for tolerance violations.

The new early-exit checks (lines 513–516, 525–528, 539–542) improve efficiency by immediately shrinking the time step and retrying when intermediate error estimates exceed adap_dt_tol, rather than continuing with computations that will ultimately be rejected.

Also applies to: 525-528, 539-542


554-555: LGTM: Enhanced acceptance criteria (after syntax fix).

The addition of err(4) and err(5) checks to the acceptance criteria (lines 554–555) provides more rigorous error control by also validating relative differences between one-step and two-half-step solutions for both radius and velocity. This is a good practice for adaptive time stepping.

Note: This depends on fixing the syntax errors on lines 544–545.


729-731: LGTM: Negative radius validation.

The new negative radius checks at each Runge-Kutta stage (lines 729–731, 746–748, 763–765) correctly implement the PR's stated objective to add validation checks. Setting err = adap_dt_tol + 1._wp and returning immediately prevents further computation with unphysical bubble states and forces a smaller time step on retry.

Also applies to: 746-748, 763-765


720-720: LGTM: Complete parameter propagation at call sites.

All call sites to s_advance_EL (lines 720, 737, 754, 771) and s_vflux (line 816) correctly pass the new fmass_g parameter, completing the parameter propagation chain for gas-centric bubble modeling.

Also applies to: 737-737, 754-754, 771-771, 816-816


372-390: LGTM: Correct dual-model gas mass handling.

The s_vflux subroutine correctly handles gas mass differently for Lagrangian bubbles (using the passed fmass_g parameter at lines 372, 380, 382) versus ensemble-averaged bubbles (using the indexed array mass_g0(iR0) at line 388). The documentation at line 347 appropriately clarifies that iR0 serves as either a bubble size index (EE) or bubble identifier (EL).

@sbryngelson
Copy link
Member

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Swapped gas/vapor propertiesMany bub_pp entries appear to assign gas and vapor properties swapped (e.g. gam, M, cp, k, R). This will silently produce incorrect nondimensional bubble properties and wrong physics. Verify that gas-related keys (suffix _g) get *_g variables and vapor keys (suffix _v) get *_v variables.
  • Swapped propertiesMany bubble property assignments for gas vs. vapor appear to be swapped when populating bub_pp (gamma, molar masses M__, thermal conductivities k__, heat capacities cp__, and gas constants R__). This will silently invert gas/vapor physics and produce incorrect simulations; please verify and correct the mapping so vapor fields use vapor variables and gas fields use gas variables.
  • Units / Gas constantThe code computes R_v = R_uni / M_v and R_g = R_uni / M_g while R_uni and the molecular masses' units are unclear. This can silently produce incorrect gas constants (factor-of-1000 errors) depending on whether R_uni is J/(mol·K), J/(kmol·K) or another convention and whether M_v/M_g are given in g/mol or kg/mol. Verify and make the unit conversion explicit to avoid subtle physical errors.
  • Simulation timestep/count mismatchNt is computed as int(Tfinal / dt) before dt is subsequently scaled by dt = dt * 0.1. This means the number of time steps Nt is based on the larger pre-scaled dt, producing an inconsistency between the reported/used timestep count and the actual dt used later. Move the dt scaling before computing Nt, or recompute Nt after dt is finalized.
  • Possible Bug (swapped masses)In the Lagrangian bubbles block the additions assign 'bub_pp%M_g' = 18.02 and 'bub_pp%M_v' = 28.97 which is the opposite ordering used elsewhere (M_v = 18.02, M_g = 28.97). This looks like a swapped assignment and will likely produce incorrect thermodynamic behavior.

can you look at these @hyeoksu-lee ? they seem like possible bugs (maybe not all of them)

@sbryngelson
Copy link
Member

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue
New EL path switches from _n to _g (e.g., mass_g0, R_g, k_g) and introduces optional fmass_g in s_vflux and s_advance_EL call chains. Verify all call sites now pass fmass_g consistently; any missed site will cause wrong mixture properties or division-by-zero in fmass_v+fmass_g.

    !!  @param fpb
    !!  @param fmass_v Current mass of vapour
    !!  @param iR0 Bubble size index (EE) or bubble identifier (EL)
    !!  @param fmass_g Current gas mass (EL)
    !!  @param fbeta_c Mass transfer coefficient (EL)
    !!  @param fR_m Mixture gas constant (EL)
    !!  @param fgamma_m Mixture gamma (EL)
elemental subroutine s_vflux(fR, fV, fpb, fmass_v, iR0, vflux, fmass_g, fbeta_c, fR_m, fgamma_m)
    $:GPU_ROUTINE(parallelism='[seq]')
    real(wp), intent(in) :: fR
    real(wp), intent(in) :: fV
    real(wp), intent(in) :: fpb
    real(wp), intent(in) :: fmass_v
    integer, intent(in) :: iR0
    real(wp), intent(out) :: vflux
    real(wp), intent(in), optional :: fmass_g, fbeta_c
    real(wp), intent(out), optional :: fR_m, fgamma_m

    real(wp) :: chi_bar
    real(wp) :: rho_mw_lag
    real(wp) :: grad_chi
    real(wp) :: conc_v

    if (thermal == 3) then !transfer
        ! constant transfer model
        if (bubbles_lagrange) then
            ! Mixture properties (gas+vapor) in the bubble
            conc_v = fmass_v/(fmass_v + fmass_g)
            if (lag_params%massTransfer_model) then
                conc_v = 1._wp/(1._wp + (R_v/R_g)*(fpb/pv - 1._wp))
            end if
            fR_m = (fmass_g*R_g + fmass_v*R_v)
            fgamma_m = conc_v*gam_v + (1._wp - conc_v)*gam_g

            ! Vapor flux
            chi_bar = fmass_v/(fmass_v + fmass_g)
            grad_chi = (chi_bar - conc_v)
            rho_mw_lag = (fmass_g + fmass_v)/(4._wp/3._wp*pi*fR**3._wp)
            vflux = 0._wp
            if (lag_params%massTransfer_model) then
                vflux = -fbeta_c*rho_mw_lag*grad_chi/(1._wp - conc_v)/fR
            end if
        else
            chi_bar = fmass_v/(fmass_v + mass_g0(iR0))
            grad_chi = -Re_trans_c(iR0)*(chi_bar - chi_vw)
            vflux = rho_mw*grad_chi/Pe_c/(1._wp - chi_vw)/fR

Initialization
New initialization prints and sets globals from bub_pp, computes nondimensional groups (Eu, Ca, Web, Re_inv), and allocates arrays. Validate that s_initialize_bubbles_model/s_initialize_bubble_vars are invoked in all code paths replacing removed EL initializer, and that qbmm + polytropic branch sets pb0 consistently with surface tension. Remove or guard debug prints.

!>
      !! bubbles_euler + polytropic
      !! bubbles_euler + non-polytropic
      !! bubbles_lagrange + non-polytropic
impure subroutine s_initialize_bubbles_model()

    ! Allocate memory
    if (bubbles_euler) then
        @:ALLOCATE(weight(nb), R0(nb))
        if (.not. polytropic) then
            @:ALLOCATE(pb0(nb), Pe_T(nb), k_g(nb), k_v(nb), mass_g0(nb), mass_v0(nb))
            @:ALLOCATE(Re_trans_T(nb), Re_trans_c(nb), Im_trans_T(nb), Im_trans_c(nb))
        else if (qbmm) then
            @:ALLOCATE(pb0(nb))
        end if

        ! Compute quadrature weights and nodes for polydisperse simulations
        if (nb > 1) then
            call s_simpson(weight, R0)
        else if (nb == 1) then
            R0 = 1._wp
            weight = 1._wp
        else
            stop 'Invalid value of nb'
        end if
        R0 = R0*bub_pp%R0ref
    end if

    ! Initialize bubble variables
    call s_initialize_bubble_vars()

end subroutine s_initialize_bubbles_model

!>
impure subroutine s_initialize_bubble_vars()

    R0ref = bub_pp%R0ref; p0ref = bub_pp%p0ref
    rho0ref = bub_pp%rho0ref; 
    ss = bub_pp%ss; pv = bub_pp%pv; vd = bub_pp%vd
    mu_l = bub_pp%mu_l; mu_v = bub_pp%mu_v; mu_g = bub_pp%mu_g
    gam_v = bub_pp%gam_v; gam_g = bub_pp%gam_g
    if (.not. polytropic) then
        if (bubbles_euler) then
            M_v = bub_pp%M_v; M_g = bub_pp%M_g
            k_v = bub_pp%k_v; k_g = bub_pp%k_g
        end if
        R_v = bub_pp%R_v; R_g = bub_pp%R_g
        Tw = bub_pp%T0ref
    end if
    if (bubbles_lagrange) then
        cp_v = bub_pp%cp_v; cp_g = bub_pp%cp_g
        k_vl = bub_pp%k_v; k_gl = bub_pp%k_g
    end if

    ! Input quantities
    if (bubbles_euler .and. (.not. polytropic)) then
        if (thermal == 2) then
            gam_m = 1._wp
        else
            gam_m = gam_g
        end if
    end if

    ! Nondimensional numbers
    Eu = p0ref
    Ca = Eu - pv
    if (.not. f_is_default(bub_pp%ss)) Web = 1._wp/ss
    if (.not. f_is_default(bub_pp%mu_l)) Re_inv = mu_l
    if (.not. polytropic) Pe_c = 1._wp/vd

    if (bubbles_euler) then
        ! Initialize variables for non-polytropic (Preston) model
        if (.not. polytropic) then
            call s_initialize_nonpoly()
        end if
        ! Initialize pb based on surface tension for qbmm (polytropic)
        if (qbmm .and. polytropic) then
            pb0 = Eu
            if (.not. f_is_default(Web)) then
                pb0 = pb0 + 2._wp/Web/R0
            end if
        end if
    end if

    print *, Tw, pv, gam_v, gam_g
    print *, k_vl, k_gl
    print *, cp_v, cp_g
    print *, R_v, R_g, vd, Web, Re_inv

end subroutine s_initialize_bubble_vars

Numerical Stability
Replaced verysmall with sgm_eps in variance and wave speed computations. Ensure sgm_eps is always initialized and appropriate for all regimes to avoid negative under-root or overly diffusive behavior; confirm consistency across modules.

                    do l = 0, p
                        do k = 0, n
                            do j = 0, m
                                nb_q = q_cons_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                nR = q_cons_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                nR2 = q_cons_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                R = q_prim_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                R2 = q_prim_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                var = max(R2 - R**2._wp, sgm_eps)
                                if (q <= 2) then
                                    AX = R - sqrt(var)
                                else
                                    AX = R + sqrt(var)
                                end if

                                select case (idir)
                                case (1)
                                    nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                    nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                    nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                    rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2)* &
                                                            (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                case (2)
                                    nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                    nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                    nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                    rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2)* &
                                                            (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                case (3)
                                    if (is_axisym) then
                                        nb_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l))
                                        nR_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l))
                                        nR2_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2)* &
                                                                (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                    else
                                        nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                        nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                        nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2)* &
                                                                (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                    end if
                                end select
                                if (q <= 2) then
                                    select case (idir)
                                    case (1)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (2)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (3)
                                        if (is_axisym) then
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        else
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        end if
                                    end select
                                else
                                    select case (idir)
                                    case (1)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (2)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (3)
                                        if (is_axisym) then
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        else
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        end if
                                    end select
                                end if
                            end do
                        end do
                    end do
                end do
            end do
            $:END_GPU_PARALLEL_LOOP()
        end if

        ! The following block is not repeated and is left as is
        if (idir == 1) then
            $:GPU_PARALLEL_LOOP(private='[i,l,q]', collapse=3)
            do l = 0, p
                do q = 0, n
                    do i = 0, m
                        rhs_vf(alf_idx)%sf(i, q, l) = rhs_vf(alf_idx)%sf(i, q, l) + mom_sp(2)%sf(i, q, l)
                        j = bubxb
                        $:GPU_LOOP(parallelism='[seq]')
                        do k = 1, nb
                            rhs_vf(j)%sf(i, q, l) = rhs_vf(j)%sf(i, q, l) + mom_3d(0, 0, k)%sf(i, q, l)
                            rhs_vf(j + 1)%sf(i, q, l) = rhs_vf(j + 1)%sf(i, q, l) + mom_3d(1, 0, k)%sf(i, q, l)
                            rhs_vf(j + 2)%sf(i, q, l) = rhs_vf(j + 2)%sf(i, q, l) + mom_3d(0, 1, k)%sf(i, q, l)
                            rhs_vf(j + 3)%sf(i, q, l) = rhs_vf(j + 3)%sf(i, q, l) + mom_3d(2, 0, k)%sf(i, q, l)
                            rhs_vf(j + 4)%sf(i, q, l) = rhs_vf(j + 4)%sf(i, q, l) + mom_3d(1, 1, k)%sf(i, q, l)
                            rhs_vf(j + 5)%sf(i, q, l) = rhs_vf(j + 5)%sf(i, q, l) + mom_3d(0, 2, k)%sf(i, q, l)
                            j = j + 6
                        end do
                    end do
                end do
            end do
            $:END_GPU_PARALLEL_LOOP()
        end if

    end subroutine s_compute_qbmm_rhs

    !Coefficient array for non-polytropic model (pb and mv values are accounted in wght_pb and wght_mv)
    subroutine s_coeff_nonpoly(pres, rho, c, coeffs)
        $:GPU_ROUTINE(function_name='s_coeff_nonpoly',parallelism='[seq]', &
            & cray_inline=True)

        real(wp), intent(in) :: pres, rho, c
        real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeffs

        integer :: i1, i2

        coeffs = 0._wp

        do i2 = 0, 2; do i1 = 0, 2
                if ((i1 + i2) <= 2) then
                    if (bubble_model == 3) then
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 1
                            ! RPE
                            coeffs(1, i1, i2) = -1._wp*i2*pres/rho
                            coeffs(2, i1, i2) = -3._wp*i2/2._wp
                            coeffs(3, i1, i2) = i2/rho
                            coeffs(4, i1, i2) = i1
                            if (.not. f_is_default(Re_inv)) coeffs(5, i1, i2) = -4._wp*i2*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(6, i1, i2) = -2._wp*i2/Web/rho
                            coeffs(7, i1, i2) = 0._wp
                        #:endif
                    else if (bubble_model == 2) then
                        ! KM with approximation of 1/(1-V/C) = 1+V/C
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -3._wp*i2/2._wp
                            coeffs(2, i1, i2) = -i2/c
                            coeffs(3, i1, i2) = i2/(2._wp*c*c)
                            coeffs(4, i1, i2) = -i2*pres/rho
                            coeffs(5, i1, i2) = -2._wp*i2*pres/(c*rho)
                            coeffs(6, i1, i2) = -i2*pres/(c*c*rho)
                            coeffs(7, i1, i2) = i2/rho
                            coeffs(8, i1, i2) = 2._wp*i2/(c*rho)
                            coeffs(9, i1, i2) = i2/(c*c*rho)
                            coeffs(10, i1, i2) = -3._wp*i2*gam/(c*rho)
                            coeffs(11, i1, i2) = -3._wp*i2*gam/(c*c*rho)
                            coeffs(12, i1, i2) = i1
                            coeffs(13, i1, i2) = 0._wp
                            coeffs(14, i1, i2) = 0._wp
                            coeffs(15, i1, i2) = 0._wp
                            if (.not. f_is_default(Re_inv)) coeffs(16, i1, i2) = -i2*4._wp*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(17, i1, i2) = -i2*2._wp/Web/rho
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(18, i1, i2) = i2*6._wp*Re_inv/(rho*c)
                                coeffs(19, i1, i2) = -i2*2._wp*Re_inv/(rho*c*c)
                                coeffs(20, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c)
                                coeffs(21, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c*c)
                                coeffs(22, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c)
                                coeffs(23, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c*c)
                                coeffs(24, i1, i2) = i2*16._wp*Re_inv*Re_inv/(rho*rho*c)
                                if (.not. f_is_default(Web)) then
                                    coeffs(25, i1, i2) = i2*8._wp*Re_inv/Web/(rho*rho*c)
                                end if
                                coeffs(26, i1, i2) = -12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                            coeffs(27, i1, i2) = 3._wp*i2*gam*R_v*Tw/(c*rho)
                            coeffs(28, i1, i2) = 3._wp*i2*gam*R_v*Tw/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(29, i1, i2) = 12._wp*i2*gam*R_v*Tw*Re_inv/(rho*rho*c*c)
                            end if
                            coeffs(30, i1, i2) = 3._wp*i2*gam/(c*rho)
                            coeffs(31, i1, i2) = 3._wp*i2*gam/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(32, i1, i2) = 12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                        #:endif
                    end if
                end if
            end do; end do

    end subroutine s_coeff_nonpoly

!Coefficient array for polytropic model (pb for each R0 bin accounted for in wght_pb)
    subroutine s_coeff(pres, rho, c, coeffs)
        $:GPU_ROUTINE(function_name='s_coeff',parallelism='[seq]', &
            & cray_inline=True)

        real(wp), intent(in) :: pres, rho, c
        real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeffs

        integer :: i1, i2

        coeffs = 0._wp

        do i2 = 0, 2; do i1 = 0, 2
                if ((i1 + i2) <= 2) then
                    if (bubble_model == 3) then
                        ! RPE
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -1._wp*i2*pres/rho
                            coeffs(2, i1, i2) = -3._wp*i2/2._wp
                            coeffs(3, i1, i2) = i2/rho
                            coeffs(4, i1, i2) = i1
                            if (.not. f_is_default(Re_inv)) coeffs(5, i1, i2) = -4._wp*i2*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(6, i1, i2) = -2._wp*i2/Web/rho
                            coeffs(7, i1, i2) = i2*pv/rho
                        #:endif
                    else if (bubble_model == 2) then
                        ! KM with approximation of 1/(1-V/C) = 1+V/C
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -3._wp*i2/2._wp
                            coeffs(2, i1, i2) = -i2/c
                            coeffs(3, i1, i2) = i2/(2._wp*c*c)
                            coeffs(4, i1, i2) = -i2*pres/rho
                            coeffs(5, i1, i2) = -2._wp*i2*pres/(c*rho)
                            coeffs(6, i1, i2) = -i2*pres/(c*c*rho)
                            coeffs(7, i1, i2) = i2/rho
                            coeffs(8, i1, i2) = 2._wp*i2/(c*rho)
                            coeffs(9, i1, i2) = i2/(c*c*rho)
                            coeffs(10, i1, i2) = -3._wp*i2*gam/(c*rho)
                            coeffs(11, i1, i2) = -3._wp*i2*gam/(c*c*rho)
                            coeffs(12, i1, i2) = i1
                            coeffs(13, i1, i2) = i2*(pv)/rho
                            coeffs(14, i1, i2) = 2._wp*i2*(pv)/(c*rho)
                            coeffs(15, i1, i2) = i2*(pv)/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) coeffs(16, i1, i2) = -i2*4._wp*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(17, i1, i2) = -i2*2._wp/Web/rho
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(18, i1, i2) = i2*6._wp*Re_inv/(rho*c)
                                coeffs(19, i1, i2) = -i2*2._wp*Re_inv/(rho*c*c)
                                coeffs(20, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c)
                                coeffs(21, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c*c)
                                coeffs(22, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c)
                                coeffs(23, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c*c)
                                coeffs(24, i1, i2) = i2*16._wp*Re_inv*Re_inv/(rho*rho*c)
                                if (.not. f_is_default(Web)) then
                                    coeffs(25, i1, i2) = i2*8._wp*Re_inv/Web/(rho*rho*c)
                                end if
                                coeffs(26, i1, i2) = -12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                        #:endif
                    end if
                end if
            end do; end do

    end subroutine s_coeff

    subroutine s_mom_inv(q_cons_vf, q_prim_vf, momsp, moms3d, pb, rhs_pb, mv, rhs_mv, ix, iy, iz)

        type(scalar_field), dimension(:), intent(inout) :: q_cons_vf, q_prim_vf
        type(scalar_field), dimension(:), intent(inout) :: momsp
        type(scalar_field), dimension(0:, 0:, :), intent(inout) :: moms3d
        real(stp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: pb
        real(wp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: rhs_pb
        real(stp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: mv
        real(wp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: rhs_mv
        type(int_bounds_info), intent(in) :: ix, iy, iz

        real(wp), dimension(nmom) :: moms, msum
        real(wp), dimension(nnode, nb) :: wght, abscX, abscY, wght_pb, wght_mv, wght_ht, ht
        real(wp), dimension(nterms, 0:2, 0:2) :: coeff
        real(wp) :: pres, rho, nbub, c, alf, momsum, drdt, drdt2, chi_vw, x_vw, rho_mw, k_mw, grad_T
        real(wp) :: n_tait, B_tait
        integer :: id1, id2, id3, i1, i2, j, q, r

        is1_qbmm = ix; is2_qbmm = iy; is3_qbmm = iz
        $:GPU_UPDATE(device='[is1_qbmm,is2_qbmm,is3_qbmm]')

        $:GPU_PARALLEL_LOOP(collapse=3, private='[id1,id2,id3,moms, msum, wght, abscX, abscY, wght_pb, wght_mv, wght_ht, coeff, ht, r, q, n_tait, B_tait, pres, rho, nbub, c, alf, momsum, drdt, drdt2, chi_vw, x_vw, rho_mw, k_mw, grad_T, i1, i2, j]')
        do id3 = is3_qbmm%beg, is3_qbmm%end
            do id2 = is2_qbmm%beg, is2_qbmm%end
                do id1 = is1_qbmm%beg, is1_qbmm%end

                    alf = q_prim_vf(alf_idx)%sf(id1, id2, id3)
                    pres = q_prim_vf(E_idx)%sf(id1, id2, id3)
                    rho = q_prim_vf(contxb)%sf(id1, id2, id3)

                    if (bubble_model == 2) then
                        n_tait = 1._wp/gammas(1) + 1._wp
                        B_tait = pi_infs(1)*(n_tait - 1)/n_tait
                        c = n_tait*(pres + B_tait)*(1._wp - alf)/(rho)
                        c = merge(sqrt(c), sgm_eps, c > 0._wp)
                    end if

                    call s_coeff_selector(pres, rho, c, coeff, polytropic)

                    if (alf > small_alf) then
                        nbub = q_cons_vf(bubxb)%sf(id1, id2, id3)
                        $:GPU_LOOP(parallelism='[seq]')
                        do q = 1, nb
                            ! Gather moments for this bubble bin
                            $:GPU_LOOP(parallelism='[seq]')
                            do r = 2, nmom
                                moms(r) = q_prim_vf(bubmoms(q, r))%sf(id1, id2, id3)
                            end do
                            moms(1) = 1._wp
                            call s_chyqmom(moms, wght(:, q), abscX(:, q), abscY(:, q))

                            if (polytropic) then
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    wght_pb(j, q) = wght(j, q)*(pb0(q) - pv)
                                end do
                            else
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    chi_vw = 1._wp/(1._wp + R_v/R_g*(pb(id1, id2, id3, j, q)/pv - 1._wp))
                                    x_vw = M_g*chi_vw/(M_v + (M_g - M_v)*chi_vw)
                                    k_mw = x_vw*k_v(q)/(x_vw + (1._wp - x_vw)*phi_vg) + (1._wp - x_vw)*k_g(q)/(x_vw*phi_gv + 1._wp - x_vw)
                                    rho_mw = pv/(chi_vw*R_v*Tw)
                                    rhs_mv(id1, id2, id3, j, q) = -Re_trans_c(q)*((mv(id1, id2, id3, j, q)/(mv(id1, id2, id3, j, q) + mass_g0(q))) - chi_vw)
                                    rhs_mv(id1, id2, id3, j, q) = rho_mw*rhs_mv(id1, id2, id3, j, q)/Pe_c/(1._wp - chi_vw)/abscX(j, q)
                                    grad_T = -Re_trans_T(q)*((pb(id1, id2, id3, j, q)/pb0(q))*(abscX(j, q)/R0(q))**3*(mass_g0(q) + mass_v0(q))/(mass_g0(q) + mv(id1, id2, id3, j, q)) - 1._wp)
                                    ht(j, q) = pb0(q)*k_mw*grad_T/Pe_T(q)/abscX(j, q)
                                    wght_pb(j, q) = wght(j, q)*(pb(id1, id2, id3, j, q))
                                    wght_mv(j, q) = wght(j, q)*(rhs_mv(id1, id2, id3, j, q))
                                    wght_ht(j, q) = wght(j, q)*ht(j, q)
                                end do
                            end if

                            ! Compute change in moments due to bubble dynamics
                            r = 1
                            $:GPU_LOOP(parallelism='[seq]')
                            do i2 = 0, 2
                                $:GPU_LOOP(parallelism='[seq]')
                                do i1 = 0, 2
                                    if ((i1 + i2) <= 2) then
                                        momsum = 0._wp
                                        $:GPU_LOOP(parallelism='[seq]')
                                        do j = 1, nterms
                                            select case (bubble_model)
                                            case (3)
                                                if (j == 3) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_pb(:, q), momrhs(:, i1, i2, j, q))
                                                else
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght(:, q), momrhs(:, i1, i2, j, q))
                                                end if
                                            case (2)
                                                if ((j >= 7 .and. j <= 9) .or. (j >= 22 .and. j <= 23) .or. (j >= 10 .and. j <= 11) .or. (j == 26)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_pb(:, q), momrhs(:, i1, i2, j, q))
                                                else if ((j >= 27 .and. j <= 29) .and. (.not. polytropic)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_mv(:, q), momrhs(:, i1, i2, j, q))
                                                else if ((j >= 30 .and. j <= 32) .and. (.not. polytropic)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_ht(:, q), momrhs(:, i1, i2, j, q))
                                                else
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght(:, q), momrhs(:, i1, i2, j, q))
                                                end if
                                            end select
                                        end do
                                        moms3d(i1, i2, q)%sf(id1, id2, id3) = nbub*momsum
                                        msum(r) = momsum
                                        r = r + 1
                                    end if
                                end do
                            end do

                            ! Compute change in pb and mv for non-polytropic model
                            if (.not. polytropic) then
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    drdt = msum(2)
                                    drdt2 = merge(-1._wp, 1._wp, j == 1 .or. j == 2)/(2._wp*sqrt(merge(moms(4) - moms(2)**2._wp, sgm_eps, moms(4) - moms(2)**2._wp > 0._wp)))
                                    drdt2 = drdt2*(msum(3) - 2._wp*moms(2)*msum(2))
                                    drdt = drdt + drdt2
                                    rhs_pb(id1, id2, id3, j, q) = (-3._wp*gam*drdt/abscX(j, q))*(pb(id1, id2, id3, j, q))
                                    rhs_pb(id1, id2, id3, j, q) = rhs_pb(id1, id2, id3, j, q) + (3._wp*gam/abscX(j, q))*rhs_mv(id1, id2, id3, j, q)*R_v*Tw
                                    rhs_pb(id1, id2, id3, j, q) = rhs_pb(id1, id2, id3, j, q) + (3._wp*gam/abscX(j, q))*ht(j, q)
                                    rhs_mv(id1, id2, id3, j, q) = rhs_mv(id1, id2, id3, j, q)*(4._wp*pi*abscX(j, q)**2._wp)

                                end do
                            end if
                        end do

                        ! Compute special high-order moments
                        momsp(1)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght, 3._wp, 0._wp, 0._wp)
                        momsp(2)%sf(id1, id2, id3) = 4._wp*pi*nbub*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp)
                        momsp(3)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght, 3._wp, 2._wp, 0._wp)
                        if (abs(gam - 1._wp) <= 1.e-4_wp) then
                            momsp(4)%sf(id1, id2, id3) = 1._wp
                        else
                            if (polytropic) then
                                momsp(4)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght_pb, 3._wp*(1._wp - gam), 0._wp, 3._wp*gam) + pv*f_quad(abscX, abscY, wght, 3._wp, 0._wp, 0._wp) - 4._wp*Re_inv*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp) - (2._wp/Web)*f_quad(abscX, abscY, wght, 2._wp, 0._wp, 0._wp)
                            else
                                momsp(4)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght_pb, 3._wp, 0._wp, 0._wp) - 4._wp*Re_inv*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp) - (2._wp/Web)*f_quad(abscX, abscY, wght, 2._wp, 0._wp, 0._wp)
                            end if
                        end if
                    else
                        $:GPU_LOOP(parallelism='[seq]')
                        do q = 1, nb
                            $:GPU_LOOP(parallelism='[seq]')
                            do i1 = 0, 2
                                $:GPU_LOOP(parallelism='[seq]')
                                do i2 = 0, 2
                                    moms3d(i1, i2, q)%sf(id1, id2, id3) = 0._wp
                                end do
                            end do
                        end do
                        momsp(1)%sf(id1, id2, id3) = 0._wp
                        momsp(2)%sf(id1, id2, id3) = 0._wp
                        momsp(3)%sf(id1, id2, id3) = 0._wp
                        momsp(4)%sf(id1, id2, id3) = 0._wp
                    end if
                end do
            end do
        end do
        $:END_GPU_PARALLEL_LOOP()

    contains
        ! Helper to select the correct coefficient routine
        subroutine s_coeff_selector(pres, rho, c, coeff, polytropic)
            $:GPU_ROUTINE(function_name='s_coeff_selector',parallelism='[seq]', &
                & cray_inline=True)
            real(wp), intent(in) :: pres, rho, c
            real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeff
            logical, intent(in) :: polytropic
            if (polytropic) then
                call s_coeff(pres, rho, c, coeff)
            else
                call s_coeff_nonpoly(pres, rho, c, coeff)
            end if
        end subroutine s_coeff_selector

        subroutine s_chyqmom(momin, wght, abscX, abscY)
            $:GPU_ROUTINE(function_name='s_chyqmom',parallelism='[seq]', &
                & cray_inline=True)

            real(wp), dimension(nmom), intent(in) :: momin
            real(wp), dimension(nnode), intent(inout) :: wght, abscX, abscY

            ! Local variables
            real(wp), dimension(0:2, 0:2) :: moms
            real(wp), dimension(3) :: M1, M3
            real(wp), dimension(2) :: myrho, myrho3, up, up3, Vf
            real(wp) :: bu, bv, d20, d11, d_02, c20, c11, c02
            real(wp) :: mu2, vp21, vp22, rho21, rho22

            ! Assign moments to 2D array for clarity
            moms(0, 0) = momin(1)
            moms(1, 0) = momin(2)
            moms(0, 1) = momin(3)
            moms(2, 0) = momin(4)
            moms(1, 1) = momin(5)
            moms(0, 2) = momin(6)

            ! Compute means and central moments
            bu = moms(1, 0)/moms(0, 0)
            bv = moms(0, 1)/moms(0, 0)
            d20 = moms(2, 0)/moms(0, 0)
            d11 = moms(1, 1)/moms(0, 0)
            d_02 = moms(0, 2)/moms(0, 0)

            c20 = d20 - bu**2._wp
            c11 = d11 - bu*bv
            c02 = d_02 - bv**2._wp

            ! First 1D quadrature (X direction)
            M1 = (/1._wp, 0._wp, c20/)
            call s_hyqmom(myrho, up, M1)
            Vf = c11*up/c20

            ! Second 1D quadrature (Y direction, conditional on X)
            mu2 = max(0._wp, c02 - sum(myrho*(Vf**2._wp)))
            M3 = (/1._wp, 0._wp, mu2/)
            call s_hyqmom(myrho3, up3, M3)

            ! Assign roots and weights for 2D quadrature
            vp21 = up3(1)
            vp22 = up3(2)
            rho21 = myrho3(1)
            rho22 = myrho3(2)

            ! Compute weights (vectorized)
            wght = moms(0, 0)*[myrho(1)*rho21, myrho(1)*rho22, myrho(2)*rho21, myrho(2)*rho22]

            ! Compute abscissas (vectorized)
            abscX = bu + [up(1), up(1), up(2), up(2)]
            abscY = bv + [Vf(1) + vp21, Vf(1) + vp22, Vf(2) + vp21, Vf(2) + vp22]

        end subroutine s_chyqmom

        subroutine s_hyqmom(frho, fup, fmom)
            $:GPU_ROUTINE(function_name='s_hyqmom',parallelism='[seq]', &
                & cray_inline=True)

            real(wp), dimension(2), intent(inout) :: frho, fup
            real(wp), dimension(3), intent(in) :: fmom

            real(wp) :: bu, d2, c2

            bu = fmom(2)/fmom(1)
            d2 = fmom(3)/fmom(1)
            c2 = d2 - bu**2._wp
            frho(1) = fmom(1)/2._wp; 
            frho(2) = fmom(1)/2._wp; 
            c2 = maxval((/c2, sgm_eps/))
            fup(1) = bu - sqrt(c2)
            fup(2) = bu + sqrt(c2)

        end subroutine s_hyqmom

some of these seeem reasonably worth looking at too, as a double check

@hyeoksu-lee
Copy link
Contributor Author

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Swapped gas/vapor propertiesMany bub_pp entries appear to assign gas and vapor properties swapped (e.g. gam, M, cp, k, R). This will silently produce incorrect nondimensional bubble properties and wrong physics. Verify that gas-related keys (suffix _g) get *_g variables and vapor keys (suffix _v) get *_v variables.
  • Swapped propertiesMany bubble property assignments for gas vs. vapor appear to be swapped when populating bub_pp (gamma, molar masses M__, thermal conductivities k__, heat capacities cp__, and gas constants R__). This will silently invert gas/vapor physics and produce incorrect simulations; please verify and correct the mapping so vapor fields use vapor variables and gas fields use gas variables.
  • Units / Gas constantThe code computes R_v = R_uni / M_v and R_g = R_uni / M_g while R_uni and the molecular masses' units are unclear. This can silently produce incorrect gas constants (factor-of-1000 errors) depending on whether R_uni is J/(mol·K), J/(kmol·K) or another convention and whether M_v/M_g are given in g/mol or kg/mol. Verify and make the unit conversion explicit to avoid subtle physical errors.
  • Simulation timestep/count mismatchNt is computed as int(Tfinal / dt) before dt is subsequently scaled by dt = dt * 0.1. This means the number of time steps Nt is based on the larger pre-scaled dt, producing an inconsistency between the reported/used timestep count and the actual dt used later. Move the dt scaling before computing Nt, or recompute Nt after dt is finalized.
  • Possible Bug (swapped masses)In the Lagrangian bubbles block the additions assign 'bub_pp%M_g' = 18.02 and 'bub_pp%M_v' = 28.97 which is the opposite ordering used elsewhere (M_v = 18.02, M_g = 28.97). This looks like a swapped assignment and will likely produce incorrect thermodynamic behavior.

can you look at these @hyeoksu-lee ? they seem like possible bugs (maybe not all of them)

The first two (Swapped gas/vapor properties / Swapped properties) and the last one (Possible Bug (swapped masses)) are requiring fixes. As noted in the PR description, I believe the current master has the swapped properties bug in EL bubble model. Therefore, I made them swapped to make this PR passes CI test, and I am planning to fix this bug in the next PR. This is because the I don't want to fix too many things in this PR. I can fix this in this PR if you think it's more appropriate.

The third and fourth one (Units/ Gas constant / Simulation tilmestep/count mismatch) are just following what the current master is doing..

@sbryngelson
Copy link
Member

Make a GH Issue for this one

The first two (Swapped gas/vapor properties / Swapped properties) and the last one (Possible Bug (swapped masses)) are requiring fixes. As noted in the PR description, I believe the current master has the swapped properties bug in EL bubble model. Therefore, I made them swapped to make this PR passes CI test, and I am planning to fix this bug in the next PR. This is because the I don't want to fix too many things in this PR. I can fix this in this PR if you think it's more appropriate.

and I'm merging

@hyeoksu-lee
Copy link
Contributor Author

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review
Possible Issue
New EL path switches from _n to _g (e.g., mass_g0, R_g, k_g) and introduces optional fmass_g in s_vflux and s_advance_EL call chains. Verify all call sites now pass fmass_g consistently; any missed site will cause wrong mixture properties or division-by-zero in fmass_v+fmass_g.

    !!  @param fpb
    !!  @param fmass_v Current mass of vapour
    !!  @param iR0 Bubble size index (EE) or bubble identifier (EL)
    !!  @param fmass_g Current gas mass (EL)
    !!  @param fbeta_c Mass transfer coefficient (EL)
    !!  @param fR_m Mixture gas constant (EL)
    !!  @param fgamma_m Mixture gamma (EL)
elemental subroutine s_vflux(fR, fV, fpb, fmass_v, iR0, vflux, fmass_g, fbeta_c, fR_m, fgamma_m)
    $:GPU_ROUTINE(parallelism='[seq]')
    real(wp), intent(in) :: fR
    real(wp), intent(in) :: fV
    real(wp), intent(in) :: fpb
    real(wp), intent(in) :: fmass_v
    integer, intent(in) :: iR0
    real(wp), intent(out) :: vflux
    real(wp), intent(in), optional :: fmass_g, fbeta_c
    real(wp), intent(out), optional :: fR_m, fgamma_m

    real(wp) :: chi_bar
    real(wp) :: rho_mw_lag
    real(wp) :: grad_chi
    real(wp) :: conc_v

    if (thermal == 3) then !transfer
        ! constant transfer model
        if (bubbles_lagrange) then
            ! Mixture properties (gas+vapor) in the bubble
            conc_v = fmass_v/(fmass_v + fmass_g)
            if (lag_params%massTransfer_model) then
                conc_v = 1._wp/(1._wp + (R_v/R_g)*(fpb/pv - 1._wp))
            end if
            fR_m = (fmass_g*R_g + fmass_v*R_v)
            fgamma_m = conc_v*gam_v + (1._wp - conc_v)*gam_g

            ! Vapor flux
            chi_bar = fmass_v/(fmass_v + fmass_g)
            grad_chi = (chi_bar - conc_v)
            rho_mw_lag = (fmass_g + fmass_v)/(4._wp/3._wp*pi*fR**3._wp)
            vflux = 0._wp
            if (lag_params%massTransfer_model) then
                vflux = -fbeta_c*rho_mw_lag*grad_chi/(1._wp - conc_v)/fR
            end if
        else
            chi_bar = fmass_v/(fmass_v + mass_g0(iR0))
            grad_chi = -Re_trans_c(iR0)*(chi_bar - chi_vw)
            vflux = rho_mw*grad_chi/Pe_c/(1._wp - chi_vw)/fR

Initialization
New initialization prints and sets globals from bub_pp, computes nondimensional groups (Eu, Ca, Web, Re_inv), and allocates arrays. Validate that s_initialize_bubbles_model/s_initialize_bubble_vars are invoked in all code paths replacing removed EL initializer, and that qbmm + polytropic branch sets pb0 consistently with surface tension. Remove or guard debug prints.

!>
      !! bubbles_euler + polytropic
      !! bubbles_euler + non-polytropic
      !! bubbles_lagrange + non-polytropic
impure subroutine s_initialize_bubbles_model()

    ! Allocate memory
    if (bubbles_euler) then
        @:ALLOCATE(weight(nb), R0(nb))
        if (.not. polytropic) then
            @:ALLOCATE(pb0(nb), Pe_T(nb), k_g(nb), k_v(nb), mass_g0(nb), mass_v0(nb))
            @:ALLOCATE(Re_trans_T(nb), Re_trans_c(nb), Im_trans_T(nb), Im_trans_c(nb))
        else if (qbmm) then
            @:ALLOCATE(pb0(nb))
        end if

        ! Compute quadrature weights and nodes for polydisperse simulations
        if (nb > 1) then
            call s_simpson(weight, R0)
        else if (nb == 1) then
            R0 = 1._wp
            weight = 1._wp
        else
            stop 'Invalid value of nb'
        end if
        R0 = R0*bub_pp%R0ref
    end if

    ! Initialize bubble variables
    call s_initialize_bubble_vars()

end subroutine s_initialize_bubbles_model

!>
impure subroutine s_initialize_bubble_vars()

    R0ref = bub_pp%R0ref; p0ref = bub_pp%p0ref
    rho0ref = bub_pp%rho0ref; 
    ss = bub_pp%ss; pv = bub_pp%pv; vd = bub_pp%vd
    mu_l = bub_pp%mu_l; mu_v = bub_pp%mu_v; mu_g = bub_pp%mu_g
    gam_v = bub_pp%gam_v; gam_g = bub_pp%gam_g
    if (.not. polytropic) then
        if (bubbles_euler) then
            M_v = bub_pp%M_v; M_g = bub_pp%M_g
            k_v = bub_pp%k_v; k_g = bub_pp%k_g
        end if
        R_v = bub_pp%R_v; R_g = bub_pp%R_g
        Tw = bub_pp%T0ref
    end if
    if (bubbles_lagrange) then
        cp_v = bub_pp%cp_v; cp_g = bub_pp%cp_g
        k_vl = bub_pp%k_v; k_gl = bub_pp%k_g
    end if

    ! Input quantities
    if (bubbles_euler .and. (.not. polytropic)) then
        if (thermal == 2) then
            gam_m = 1._wp
        else
            gam_m = gam_g
        end if
    end if

    ! Nondimensional numbers
    Eu = p0ref
    Ca = Eu - pv
    if (.not. f_is_default(bub_pp%ss)) Web = 1._wp/ss
    if (.not. f_is_default(bub_pp%mu_l)) Re_inv = mu_l
    if (.not. polytropic) Pe_c = 1._wp/vd

    if (bubbles_euler) then
        ! Initialize variables for non-polytropic (Preston) model
        if (.not. polytropic) then
            call s_initialize_nonpoly()
        end if
        ! Initialize pb based on surface tension for qbmm (polytropic)
        if (qbmm .and. polytropic) then
            pb0 = Eu
            if (.not. f_is_default(Web)) then
                pb0 = pb0 + 2._wp/Web/R0
            end if
        end if
    end if

    print *, Tw, pv, gam_v, gam_g
    print *, k_vl, k_gl
    print *, cp_v, cp_g
    print *, R_v, R_g, vd, Web, Re_inv

end subroutine s_initialize_bubble_vars

Numerical Stability
Replaced verysmall with sgm_eps in variance and wave speed computations. Ensure sgm_eps is always initialized and appropriate for all regimes to avoid negative under-root or overly diffusive behavior; confirm consistency across modules.

                    do l = 0, p
                        do k = 0, n
                            do j = 0, m
                                nb_q = q_cons_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                nR = q_cons_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                nR2 = q_cons_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                R = q_prim_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                R2 = q_prim_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                var = max(R2 - R**2._wp, sgm_eps)
                                if (q <= 2) then
                                    AX = R - sqrt(var)
                                else
                                    AX = R + sqrt(var)
                                end if

                                select case (idir)
                                case (1)
                                    nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                    nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                    nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j - 1, k, l) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                    rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2)* &
                                                            (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                case (2)
                                    nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                    nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                    nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k - 1, l) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                    rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2)* &
                                                            (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                case (3)
                                    if (is_axisym) then
                                        nb_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l))
                                        nR_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l))
                                        nR2_dot = q_prim_vf(contxe + idir)%sf(j, k, l)*(flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2)* &
                                                                (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                    else
                                        nb_dot = flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + (i - 1)*nmom)%sf(j, k, l)
                                        nR_dot = flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 1 + (i - 1)*nmom)%sf(j, k, l)
                                        nR2_dot = flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l - 1) - flux_n_vf(bubxb + 3 + (i - 1)*nmom)%sf(j, k, l)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2)* &
                                                                (nR_dot*nb_q - nR*nb_dot)*(pb(j, k, l, q, i))
                                    end if
                                end select
                                if (q <= 2) then
                                    select case (idir)
                                    case (1)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (2)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (3)
                                        if (is_axisym) then
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        else
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) + 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        end if
                                    end select
                                else
                                    select case (idir)
                                    case (1)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dx(j)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (2)
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                        rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dy(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                    case (3)
                                        if (is_axisym) then
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*y_cc(k)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        else
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (nR2_dot*nb_q - nR2*nb_dot)*(pb(j, k, l, q, i))
                                            rhs_pb(j, k, l, q, i) = rhs_pb(j, k, l, q, i) - 3._wp*gam/(dz(l)*AX*nb_q**2*sqrt(var)*2._wp)* &
                                                                    (-2._wp*(nR/nb_q)*(nR_dot*nb_q - nR*nb_dot))*(pb(j, k, l, q, i))
                                        end if
                                    end select
                                end if
                            end do
                        end do
                    end do
                end do
            end do
            $:END_GPU_PARALLEL_LOOP()
        end if

        ! The following block is not repeated and is left as is
        if (idir == 1) then
            $:GPU_PARALLEL_LOOP(private='[i,l,q]', collapse=3)
            do l = 0, p
                do q = 0, n
                    do i = 0, m
                        rhs_vf(alf_idx)%sf(i, q, l) = rhs_vf(alf_idx)%sf(i, q, l) + mom_sp(2)%sf(i, q, l)
                        j = bubxb
                        $:GPU_LOOP(parallelism='[seq]')
                        do k = 1, nb
                            rhs_vf(j)%sf(i, q, l) = rhs_vf(j)%sf(i, q, l) + mom_3d(0, 0, k)%sf(i, q, l)
                            rhs_vf(j + 1)%sf(i, q, l) = rhs_vf(j + 1)%sf(i, q, l) + mom_3d(1, 0, k)%sf(i, q, l)
                            rhs_vf(j + 2)%sf(i, q, l) = rhs_vf(j + 2)%sf(i, q, l) + mom_3d(0, 1, k)%sf(i, q, l)
                            rhs_vf(j + 3)%sf(i, q, l) = rhs_vf(j + 3)%sf(i, q, l) + mom_3d(2, 0, k)%sf(i, q, l)
                            rhs_vf(j + 4)%sf(i, q, l) = rhs_vf(j + 4)%sf(i, q, l) + mom_3d(1, 1, k)%sf(i, q, l)
                            rhs_vf(j + 5)%sf(i, q, l) = rhs_vf(j + 5)%sf(i, q, l) + mom_3d(0, 2, k)%sf(i, q, l)
                            j = j + 6
                        end do
                    end do
                end do
            end do
            $:END_GPU_PARALLEL_LOOP()
        end if

    end subroutine s_compute_qbmm_rhs

    !Coefficient array for non-polytropic model (pb and mv values are accounted in wght_pb and wght_mv)
    subroutine s_coeff_nonpoly(pres, rho, c, coeffs)
        $:GPU_ROUTINE(function_name='s_coeff_nonpoly',parallelism='[seq]', &
            & cray_inline=True)

        real(wp), intent(in) :: pres, rho, c
        real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeffs

        integer :: i1, i2

        coeffs = 0._wp

        do i2 = 0, 2; do i1 = 0, 2
                if ((i1 + i2) <= 2) then
                    if (bubble_model == 3) then
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 1
                            ! RPE
                            coeffs(1, i1, i2) = -1._wp*i2*pres/rho
                            coeffs(2, i1, i2) = -3._wp*i2/2._wp
                            coeffs(3, i1, i2) = i2/rho
                            coeffs(4, i1, i2) = i1
                            if (.not. f_is_default(Re_inv)) coeffs(5, i1, i2) = -4._wp*i2*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(6, i1, i2) = -2._wp*i2/Web/rho
                            coeffs(7, i1, i2) = 0._wp
                        #:endif
                    else if (bubble_model == 2) then
                        ! KM with approximation of 1/(1-V/C) = 1+V/C
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -3._wp*i2/2._wp
                            coeffs(2, i1, i2) = -i2/c
                            coeffs(3, i1, i2) = i2/(2._wp*c*c)
                            coeffs(4, i1, i2) = -i2*pres/rho
                            coeffs(5, i1, i2) = -2._wp*i2*pres/(c*rho)
                            coeffs(6, i1, i2) = -i2*pres/(c*c*rho)
                            coeffs(7, i1, i2) = i2/rho
                            coeffs(8, i1, i2) = 2._wp*i2/(c*rho)
                            coeffs(9, i1, i2) = i2/(c*c*rho)
                            coeffs(10, i1, i2) = -3._wp*i2*gam/(c*rho)
                            coeffs(11, i1, i2) = -3._wp*i2*gam/(c*c*rho)
                            coeffs(12, i1, i2) = i1
                            coeffs(13, i1, i2) = 0._wp
                            coeffs(14, i1, i2) = 0._wp
                            coeffs(15, i1, i2) = 0._wp
                            if (.not. f_is_default(Re_inv)) coeffs(16, i1, i2) = -i2*4._wp*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(17, i1, i2) = -i2*2._wp/Web/rho
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(18, i1, i2) = i2*6._wp*Re_inv/(rho*c)
                                coeffs(19, i1, i2) = -i2*2._wp*Re_inv/(rho*c*c)
                                coeffs(20, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c)
                                coeffs(21, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c*c)
                                coeffs(22, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c)
                                coeffs(23, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c*c)
                                coeffs(24, i1, i2) = i2*16._wp*Re_inv*Re_inv/(rho*rho*c)
                                if (.not. f_is_default(Web)) then
                                    coeffs(25, i1, i2) = i2*8._wp*Re_inv/Web/(rho*rho*c)
                                end if
                                coeffs(26, i1, i2) = -12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                            coeffs(27, i1, i2) = 3._wp*i2*gam*R_v*Tw/(c*rho)
                            coeffs(28, i1, i2) = 3._wp*i2*gam*R_v*Tw/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(29, i1, i2) = 12._wp*i2*gam*R_v*Tw*Re_inv/(rho*rho*c*c)
                            end if
                            coeffs(30, i1, i2) = 3._wp*i2*gam/(c*rho)
                            coeffs(31, i1, i2) = 3._wp*i2*gam/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(32, i1, i2) = 12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                        #:endif
                    end if
                end if
            end do; end do

    end subroutine s_coeff_nonpoly

!Coefficient array for polytropic model (pb for each R0 bin accounted for in wght_pb)
    subroutine s_coeff(pres, rho, c, coeffs)
        $:GPU_ROUTINE(function_name='s_coeff',parallelism='[seq]', &
            & cray_inline=True)

        real(wp), intent(in) :: pres, rho, c
        real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeffs

        integer :: i1, i2

        coeffs = 0._wp

        do i2 = 0, 2; do i1 = 0, 2
                if ((i1 + i2) <= 2) then
                    if (bubble_model == 3) then
                        ! RPE
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -1._wp*i2*pres/rho
                            coeffs(2, i1, i2) = -3._wp*i2/2._wp
                            coeffs(3, i1, i2) = i2/rho
                            coeffs(4, i1, i2) = i1
                            if (.not. f_is_default(Re_inv)) coeffs(5, i1, i2) = -4._wp*i2*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(6, i1, i2) = -2._wp*i2/Web/rho
                            coeffs(7, i1, i2) = i2*pv/rho
                        #:endif
                    else if (bubble_model == 2) then
                        ! KM with approximation of 1/(1-V/C) = 1+V/C
                        #:if not MFC_CASE_OPTIMIZATION or nterms > 7
                            coeffs(1, i1, i2) = -3._wp*i2/2._wp
                            coeffs(2, i1, i2) = -i2/c
                            coeffs(3, i1, i2) = i2/(2._wp*c*c)
                            coeffs(4, i1, i2) = -i2*pres/rho
                            coeffs(5, i1, i2) = -2._wp*i2*pres/(c*rho)
                            coeffs(6, i1, i2) = -i2*pres/(c*c*rho)
                            coeffs(7, i1, i2) = i2/rho
                            coeffs(8, i1, i2) = 2._wp*i2/(c*rho)
                            coeffs(9, i1, i2) = i2/(c*c*rho)
                            coeffs(10, i1, i2) = -3._wp*i2*gam/(c*rho)
                            coeffs(11, i1, i2) = -3._wp*i2*gam/(c*c*rho)
                            coeffs(12, i1, i2) = i1
                            coeffs(13, i1, i2) = i2*(pv)/rho
                            coeffs(14, i1, i2) = 2._wp*i2*(pv)/(c*rho)
                            coeffs(15, i1, i2) = i2*(pv)/(c*c*rho)
                            if (.not. f_is_default(Re_inv)) coeffs(16, i1, i2) = -i2*4._wp*Re_inv/rho
                            if (.not. f_is_default(Web)) coeffs(17, i1, i2) = -i2*2._wp/Web/rho
                            if (.not. f_is_default(Re_inv)) then
                                coeffs(18, i1, i2) = i2*6._wp*Re_inv/(rho*c)
                                coeffs(19, i1, i2) = -i2*2._wp*Re_inv/(rho*c*c)
                                coeffs(20, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c)
                                coeffs(21, i1, i2) = i2*4._wp*pres*Re_inv/(rho*rho*c*c)
                                coeffs(22, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c)
                                coeffs(23, i1, i2) = -i2*4._wp*Re_inv/(rho*rho*c*c)
                                coeffs(24, i1, i2) = i2*16._wp*Re_inv*Re_inv/(rho*rho*c)
                                if (.not. f_is_default(Web)) then
                                    coeffs(25, i1, i2) = i2*8._wp*Re_inv/Web/(rho*rho*c)
                                end if
                                coeffs(26, i1, i2) = -12._wp*i2*gam*Re_inv/(rho*rho*c*c)
                            end if
                        #:endif
                    end if
                end if
            end do; end do

    end subroutine s_coeff

    subroutine s_mom_inv(q_cons_vf, q_prim_vf, momsp, moms3d, pb, rhs_pb, mv, rhs_mv, ix, iy, iz)

        type(scalar_field), dimension(:), intent(inout) :: q_cons_vf, q_prim_vf
        type(scalar_field), dimension(:), intent(inout) :: momsp
        type(scalar_field), dimension(0:, 0:, :), intent(inout) :: moms3d
        real(stp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: pb
        real(wp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: rhs_pb
        real(stp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: mv
        real(wp), dimension(idwbuff(1)%beg:, idwbuff(2)%beg:, idwbuff(3)%beg:, 1:, 1:), intent(inout) :: rhs_mv
        type(int_bounds_info), intent(in) :: ix, iy, iz

        real(wp), dimension(nmom) :: moms, msum
        real(wp), dimension(nnode, nb) :: wght, abscX, abscY, wght_pb, wght_mv, wght_ht, ht
        real(wp), dimension(nterms, 0:2, 0:2) :: coeff
        real(wp) :: pres, rho, nbub, c, alf, momsum, drdt, drdt2, chi_vw, x_vw, rho_mw, k_mw, grad_T
        real(wp) :: n_tait, B_tait
        integer :: id1, id2, id3, i1, i2, j, q, r

        is1_qbmm = ix; is2_qbmm = iy; is3_qbmm = iz
        $:GPU_UPDATE(device='[is1_qbmm,is2_qbmm,is3_qbmm]')

        $:GPU_PARALLEL_LOOP(collapse=3, private='[id1,id2,id3,moms, msum, wght, abscX, abscY, wght_pb, wght_mv, wght_ht, coeff, ht, r, q, n_tait, B_tait, pres, rho, nbub, c, alf, momsum, drdt, drdt2, chi_vw, x_vw, rho_mw, k_mw, grad_T, i1, i2, j]')
        do id3 = is3_qbmm%beg, is3_qbmm%end
            do id2 = is2_qbmm%beg, is2_qbmm%end
                do id1 = is1_qbmm%beg, is1_qbmm%end

                    alf = q_prim_vf(alf_idx)%sf(id1, id2, id3)
                    pres = q_prim_vf(E_idx)%sf(id1, id2, id3)
                    rho = q_prim_vf(contxb)%sf(id1, id2, id3)

                    if (bubble_model == 2) then
                        n_tait = 1._wp/gammas(1) + 1._wp
                        B_tait = pi_infs(1)*(n_tait - 1)/n_tait
                        c = n_tait*(pres + B_tait)*(1._wp - alf)/(rho)
                        c = merge(sqrt(c), sgm_eps, c > 0._wp)
                    end if

                    call s_coeff_selector(pres, rho, c, coeff, polytropic)

                    if (alf > small_alf) then
                        nbub = q_cons_vf(bubxb)%sf(id1, id2, id3)
                        $:GPU_LOOP(parallelism='[seq]')
                        do q = 1, nb
                            ! Gather moments for this bubble bin
                            $:GPU_LOOP(parallelism='[seq]')
                            do r = 2, nmom
                                moms(r) = q_prim_vf(bubmoms(q, r))%sf(id1, id2, id3)
                            end do
                            moms(1) = 1._wp
                            call s_chyqmom(moms, wght(:, q), abscX(:, q), abscY(:, q))

                            if (polytropic) then
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    wght_pb(j, q) = wght(j, q)*(pb0(q) - pv)
                                end do
                            else
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    chi_vw = 1._wp/(1._wp + R_v/R_g*(pb(id1, id2, id3, j, q)/pv - 1._wp))
                                    x_vw = M_g*chi_vw/(M_v + (M_g - M_v)*chi_vw)
                                    k_mw = x_vw*k_v(q)/(x_vw + (1._wp - x_vw)*phi_vg) + (1._wp - x_vw)*k_g(q)/(x_vw*phi_gv + 1._wp - x_vw)
                                    rho_mw = pv/(chi_vw*R_v*Tw)
                                    rhs_mv(id1, id2, id3, j, q) = -Re_trans_c(q)*((mv(id1, id2, id3, j, q)/(mv(id1, id2, id3, j, q) + mass_g0(q))) - chi_vw)
                                    rhs_mv(id1, id2, id3, j, q) = rho_mw*rhs_mv(id1, id2, id3, j, q)/Pe_c/(1._wp - chi_vw)/abscX(j, q)
                                    grad_T = -Re_trans_T(q)*((pb(id1, id2, id3, j, q)/pb0(q))*(abscX(j, q)/R0(q))**3*(mass_g0(q) + mass_v0(q))/(mass_g0(q) + mv(id1, id2, id3, j, q)) - 1._wp)
                                    ht(j, q) = pb0(q)*k_mw*grad_T/Pe_T(q)/abscX(j, q)
                                    wght_pb(j, q) = wght(j, q)*(pb(id1, id2, id3, j, q))
                                    wght_mv(j, q) = wght(j, q)*(rhs_mv(id1, id2, id3, j, q))
                                    wght_ht(j, q) = wght(j, q)*ht(j, q)
                                end do
                            end if

                            ! Compute change in moments due to bubble dynamics
                            r = 1
                            $:GPU_LOOP(parallelism='[seq]')
                            do i2 = 0, 2
                                $:GPU_LOOP(parallelism='[seq]')
                                do i1 = 0, 2
                                    if ((i1 + i2) <= 2) then
                                        momsum = 0._wp
                                        $:GPU_LOOP(parallelism='[seq]')
                                        do j = 1, nterms
                                            select case (bubble_model)
                                            case (3)
                                                if (j == 3) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_pb(:, q), momrhs(:, i1, i2, j, q))
                                                else
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght(:, q), momrhs(:, i1, i2, j, q))
                                                end if
                                            case (2)
                                                if ((j >= 7 .and. j <= 9) .or. (j >= 22 .and. j <= 23) .or. (j >= 10 .and. j <= 11) .or. (j == 26)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_pb(:, q), momrhs(:, i1, i2, j, q))
                                                else if ((j >= 27 .and. j <= 29) .and. (.not. polytropic)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_mv(:, q), momrhs(:, i1, i2, j, q))
                                                else if ((j >= 30 .and. j <= 32) .and. (.not. polytropic)) then
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght_ht(:, q), momrhs(:, i1, i2, j, q))
                                                else
                                                    momsum = momsum + coeff(j, i1, i2)*(R0(q)**momrhs(3, i1, i2, j, q))*f_quad2D(abscX(:, q), abscY(:, q), wght(:, q), momrhs(:, i1, i2, j, q))
                                                end if
                                            end select
                                        end do
                                        moms3d(i1, i2, q)%sf(id1, id2, id3) = nbub*momsum
                                        msum(r) = momsum
                                        r = r + 1
                                    end if
                                end do
                            end do

                            ! Compute change in pb and mv for non-polytropic model
                            if (.not. polytropic) then
                                $:GPU_LOOP(parallelism='[seq]')
                                do j = 1, nnode
                                    drdt = msum(2)
                                    drdt2 = merge(-1._wp, 1._wp, j == 1 .or. j == 2)/(2._wp*sqrt(merge(moms(4) - moms(2)**2._wp, sgm_eps, moms(4) - moms(2)**2._wp > 0._wp)))
                                    drdt2 = drdt2*(msum(3) - 2._wp*moms(2)*msum(2))
                                    drdt = drdt + drdt2
                                    rhs_pb(id1, id2, id3, j, q) = (-3._wp*gam*drdt/abscX(j, q))*(pb(id1, id2, id3, j, q))
                                    rhs_pb(id1, id2, id3, j, q) = rhs_pb(id1, id2, id3, j, q) + (3._wp*gam/abscX(j, q))*rhs_mv(id1, id2, id3, j, q)*R_v*Tw
                                    rhs_pb(id1, id2, id3, j, q) = rhs_pb(id1, id2, id3, j, q) + (3._wp*gam/abscX(j, q))*ht(j, q)
                                    rhs_mv(id1, id2, id3, j, q) = rhs_mv(id1, id2, id3, j, q)*(4._wp*pi*abscX(j, q)**2._wp)

                                end do
                            end if
                        end do

                        ! Compute special high-order moments
                        momsp(1)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght, 3._wp, 0._wp, 0._wp)
                        momsp(2)%sf(id1, id2, id3) = 4._wp*pi*nbub*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp)
                        momsp(3)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght, 3._wp, 2._wp, 0._wp)
                        if (abs(gam - 1._wp) <= 1.e-4_wp) then
                            momsp(4)%sf(id1, id2, id3) = 1._wp
                        else
                            if (polytropic) then
                                momsp(4)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght_pb, 3._wp*(1._wp - gam), 0._wp, 3._wp*gam) + pv*f_quad(abscX, abscY, wght, 3._wp, 0._wp, 0._wp) - 4._wp*Re_inv*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp) - (2._wp/Web)*f_quad(abscX, abscY, wght, 2._wp, 0._wp, 0._wp)
                            else
                                momsp(4)%sf(id1, id2, id3) = f_quad(abscX, abscY, wght_pb, 3._wp, 0._wp, 0._wp) - 4._wp*Re_inv*f_quad(abscX, abscY, wght, 2._wp, 1._wp, 0._wp) - (2._wp/Web)*f_quad(abscX, abscY, wght, 2._wp, 0._wp, 0._wp)
                            end if
                        end if
                    else
                        $:GPU_LOOP(parallelism='[seq]')
                        do q = 1, nb
                            $:GPU_LOOP(parallelism='[seq]')
                            do i1 = 0, 2
                                $:GPU_LOOP(parallelism='[seq]')
                                do i2 = 0, 2
                                    moms3d(i1, i2, q)%sf(id1, id2, id3) = 0._wp
                                end do
                            end do
                        end do
                        momsp(1)%sf(id1, id2, id3) = 0._wp
                        momsp(2)%sf(id1, id2, id3) = 0._wp
                        momsp(3)%sf(id1, id2, id3) = 0._wp
                        momsp(4)%sf(id1, id2, id3) = 0._wp
                    end if
                end do
            end do
        end do
        $:END_GPU_PARALLEL_LOOP()

    contains
        ! Helper to select the correct coefficient routine
        subroutine s_coeff_selector(pres, rho, c, coeff, polytropic)
            $:GPU_ROUTINE(function_name='s_coeff_selector',parallelism='[seq]', &
                & cray_inline=True)
            real(wp), intent(in) :: pres, rho, c
            real(wp), dimension(nterms, 0:2, 0:2), intent(out) :: coeff
            logical, intent(in) :: polytropic
            if (polytropic) then
                call s_coeff(pres, rho, c, coeff)
            else
                call s_coeff_nonpoly(pres, rho, c, coeff)
            end if
        end subroutine s_coeff_selector

        subroutine s_chyqmom(momin, wght, abscX, abscY)
            $:GPU_ROUTINE(function_name='s_chyqmom',parallelism='[seq]', &
                & cray_inline=True)

            real(wp), dimension(nmom), intent(in) :: momin
            real(wp), dimension(nnode), intent(inout) :: wght, abscX, abscY

            ! Local variables
            real(wp), dimension(0:2, 0:2) :: moms
            real(wp), dimension(3) :: M1, M3
            real(wp), dimension(2) :: myrho, myrho3, up, up3, Vf
            real(wp) :: bu, bv, d20, d11, d_02, c20, c11, c02
            real(wp) :: mu2, vp21, vp22, rho21, rho22

            ! Assign moments to 2D array for clarity
            moms(0, 0) = momin(1)
            moms(1, 0) = momin(2)
            moms(0, 1) = momin(3)
            moms(2, 0) = momin(4)
            moms(1, 1) = momin(5)
            moms(0, 2) = momin(6)

            ! Compute means and central moments
            bu = moms(1, 0)/moms(0, 0)
            bv = moms(0, 1)/moms(0, 0)
            d20 = moms(2, 0)/moms(0, 0)
            d11 = moms(1, 1)/moms(0, 0)
            d_02 = moms(0, 2)/moms(0, 0)

            c20 = d20 - bu**2._wp
            c11 = d11 - bu*bv
            c02 = d_02 - bv**2._wp

            ! First 1D quadrature (X direction)
            M1 = (/1._wp, 0._wp, c20/)
            call s_hyqmom(myrho, up, M1)
            Vf = c11*up/c20

            ! Second 1D quadrature (Y direction, conditional on X)
            mu2 = max(0._wp, c02 - sum(myrho*(Vf**2._wp)))
            M3 = (/1._wp, 0._wp, mu2/)
            call s_hyqmom(myrho3, up3, M3)

            ! Assign roots and weights for 2D quadrature
            vp21 = up3(1)
            vp22 = up3(2)
            rho21 = myrho3(1)
            rho22 = myrho3(2)

            ! Compute weights (vectorized)
            wght = moms(0, 0)*[myrho(1)*rho21, myrho(1)*rho22, myrho(2)*rho21, myrho(2)*rho22]

            ! Compute abscissas (vectorized)
            abscX = bu + [up(1), up(1), up(2), up(2)]
            abscY = bv + [Vf(1) + vp21, Vf(1) + vp22, Vf(2) + vp21, Vf(2) + vp22]

        end subroutine s_chyqmom

        subroutine s_hyqmom(frho, fup, fmom)
            $:GPU_ROUTINE(function_name='s_hyqmom',parallelism='[seq]', &
                & cray_inline=True)

            real(wp), dimension(2), intent(inout) :: frho, fup
            real(wp), dimension(3), intent(in) :: fmom

            real(wp) :: bu, d2, c2

            bu = fmom(2)/fmom(1)
            d2 = fmom(3)/fmom(1)
            c2 = d2 - bu**2._wp
            frho(1) = fmom(1)/2._wp; 
            frho(2) = fmom(1)/2._wp; 
            c2 = maxval((/c2, sgm_eps/))
            fup(1) = bu - sqrt(c2)
            fup(2) = bu + sqrt(c2)

        end subroutine s_hyqmom

some of these seeem reasonably worth looking at too, as a double check

For the first one regarding EL bubbles, what I did is just replacing _n with _g and integrating the initialization subroutine with EE bubbles. Basically there is no significant change in EL model, so I believe this should be fine. Also, the test suite passes without any issues.

The second one should be fine.

For the third one, verysmall was not small enough when the sub-grid bubble simulations ran with dimensional quantities. Therefore, I replaced it with sgm_eps, and I believe this is fine.

@sbryngelson
Copy link
Member

sounds good! will merge soon. oh and for the EL bubbles issue please tag @wilfonba he will know what's going on (also tag @dgvacarevelo it might be his test)

@hyeoksu-lee
Copy link
Contributor Author

sounds good! will merge soon. oh and for the EL bubbles issue please tag @wilfonba he will know what's going on (also tag @dgvacarevelo it might be his test)

Ok I'll do

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
examples/0D_bubblecollapse_adap/case.py (1)

15-24: Several vapor and gas properties are defined but unused.

The following properties are defined but not referenced in the configuration dictionary: gam_v, M_v, mu_v, k_v, M_g, mu_g, k_g. Only gam_g is used (line 114).

If these are intended for documentation purposes or future extensions, consider adding a comment to clarify. Otherwise, they can be removed to reduce clutter.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6246e3c and f3c4b0c.

📒 Files selected for processing (2)
  • examples/0D_bubblecollapse_adap/case.py (3 hunks)
  • src/simulation/m_bubbles.fpp (20 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{fpp,f90}: Use 2-space indentation; continuation lines align beneath &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_ pattern (e.g., m_transport)
Name public subroutines with s_ pattern (e.g., s_compute_flux)
Name public functions with f
_ pattern
Keep subroutine size ≤ 500 lines, helper subroutines ≤ 150 lines, functions ≤ 100 lines, files ≤ 1000 lines
Limit routine arguments to ≤ 6; use derived-type params struct if more are needed
Forbid goto statements (except in legacy code), COMMON blocks, and save globals
Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate
Call s_mpi_abort() for errors, never use stop or error stop

**/*.{fpp,f90}: Indent 2 spaces; continuation lines align under &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_<feature> prefix (e.g., m_transport)
Name public subroutines as s_<verb>_<noun> (e.g., s_compute_flux) and functions as f_<verb>_<noun>
Keep private helpers in the module; avoid nested procedures
Enforce size limits: subroutine ≤ 500 lines, helper ≤ 150, function ≤ 100, module/file ≤ 1000
Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct
Avoid goto statements (except unavoidable legacy); avoid global state (COMMON, save)
Every variable must have intent(in|out|inout) specification and appropriate dimension / allocatable / pointer
Use s_mpi_abort(<msg>) for error termination instead of stop
Use !> style documentation for header comments; follow Doxygen Fortran format with !! @param and !! @return tags
Use implicit none statement in all modules
Use private declaration followed by explicit public exports in modules
Use derived types with pointers for encapsulation (e.g., pointer, dimension(:,:,:) => null())
Use pure and elemental attributes for side-effect-free functions; combine them for array ...

Files:

  • src/simulation/m_bubbles.fpp
src/simulation/**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/simulation/**/*.{fpp,f90}: Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)
Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up
Avoid stop/error stop inside GPU device code
Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

src/simulation/**/*.{fpp,f90}: Mark GPU-callable helpers with $:GPU_ROUTINE(function_name='...', parallelism='[seq]') immediately after declaration
Do not use OpenACC or OpenMP directives directly; use Fypp macros from src/common/include/parallel_macros.fpp instead
Wrap tight loops with $:GPU_PARALLEL_FOR(private='[...]', copy='[...]') macro; add collapse=n for safe nested loop merging
Declare loop-local variables with private='[...]' in GPU parallel loop macros
Allocate large arrays with managed or move them into a persistent $:GPU_ENTER_DATA(...) region at start-up
Do not place stop or error stop inside device code

Files:

  • src/simulation/m_bubbles.fpp
src/**/*.fpp

📄 CodeRabbit inference engine (.cursor/rules/mfc-agent-rules.mdc)

src/**/*.fpp: Use .fpp file extension for Fypp preprocessed files; CMake transpiles them to .f90
Start module files with Fypp include for macros: #:include 'macros.fpp'
Use the fypp ASSERT macro for validating conditions: @:ASSERT(predicate, message)
Use fypp macro @:ALLOCATE(var1, var2) for device-aware allocation instead of standard Fortran allocate
Use fypp macro @:DEALLOCATE(var1, var2) for device-aware deallocation instead of standard Fortran deallocate

Files:

  • src/simulation/m_bubbles.fpp
🧠 Learnings (12)
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight loops with `$:GPU_PARALLEL_FOR(private='[...]', copy='[...]')` macro; add `collapse=n` for safe nested loop merging

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Do not use OpenACC or OpenMP directives directly; use Fypp macros from `src/common/include/parallel_macros.fpp` instead

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Mark GPU-callable helpers with `$:GPU_ROUTINE(function_name='...', parallelism='[seq]')` immediately after declaration

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Declare loop-local variables with `private='[...]'` in GPU parallel loop macros

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Every variable must have `intent(in|out|inout)` specification and appropriate `dimension` / `allocatable` / `pointer`

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Limit routine arguments to ≤ 6; use derived-type params struct if more are needed

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `wp` (working precision) parameter from `m_precision_select` instead of hardcoded precision like `real*8`

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Avoid stop/error stop inside GPU device code

Applied to files:

  • src/simulation/m_bubbles.fpp
🧬 Code graph analysis (1)
examples/0D_bubblecollapse_adap/case.py (1)
toolchain/mfc/test/cases.py (1)
  • alter_viscosity (786-835)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Github (ubuntu, mpi, debug, false)
  • GitHub Check: Github (macos, mpi, no-debug, false)
  • GitHub Check: Github (ubuntu, no-mpi, single, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, no-debug, true)
  • GitHub Check: Github (ubuntu, mpi, debug, true)
  • GitHub Check: Github (macos, mpi, debug, false)
  • GitHub Check: Code Cleanliness Check
  • GitHub Check: Coverage Test on CodeCov
  • GitHub Check: Build & Publish
🔇 Additional comments (7)
examples/0D_bubblecollapse_adap/case.py (1)

108-125: LGTM! Configuration properly uses bub_pp structure.

The bubble parameter configuration correctly uses the new bub_pp structure with appropriate nondimensionalization, and the adaptive time-stepping parameters are properly configured. The fluid parameters are correctly computed from the liquid properties.

src/simulation/m_bubbles.fpp (6)

54-54: LGTM! Euler number (Eu) correctly replaces previous normalization.

The use of Eu for bubble pressure nondimensionalization in both Gilmore (line 54) and Keller-Miksis (line 267) models is consistent with the PR's objective to standardize on Eu-based scaling.

Also applies to: 267-267


332-338: Naming updated consistently from gas subscript _n to _g.

The bubble wall property calculations now use R_g, M_g, and k_g instead of the previous R_n, M_n, and k_n, aligning with the PR's nomenclature standardization.


434-440: Naming updated consistently to use mass_g0 and gam_m.

The internal bubble pressure derivative calculations now use mass_g0 (instead of mass_n0) and gam_m nomenclature, consistent with the PR's standardization efforts.


482-490: LGTM! Adaptive time-stepping initialization and error handling improved.

The explicit h0 initialization (lines 482-490) clarifies the initial step size computation, and the use of sgm_eps in relative error calculations (lines 544-545) with special handling for non-oscillating bubbles (line 546) enhances numerical stability.

Also applies to: 544-546


729-731: LGTM! Early-exit guards prevent negative radius continuation.

The early-exit conditions when bubble radius becomes negative at any substep stage (lines 729-731, 746-748, 763-765) properly trigger step-size reduction by setting err > adap_dt_tol, preventing unphysical states from propagating.

Also applies to: 746-748, 763-765


806-820: LGTM! Lagrangian bubble advancement correctly propagates fmass_g.

The s_advance_EL subroutine signature and implementation correctly incorporate the fmass_g parameter, passing it through to s_vflux for proper mixture property calculations.

@codeant-ai
Copy link

codeant-ai bot commented Dec 16, 2025

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/simulation/m_bubbles.fpp (2)

360-361: Unguarded optional parameters can cause undefined behavior.

The optional parameters fmass_g, fbeta_c, fR_m, and fgamma_m are read or written at lines 372–386 without present() guards. If a caller omits these arguments, the routine will produce undefined behavior (reading uninitialized values or writing to absent dummies).

While current callers (line 816) do pass these arguments, the optional declaration without guards is fragile and risky for future maintenance.

Recommended fix: Either remove the optional attribute (making them required) or add present() guards before each use:

-        real(wp), intent(in), optional :: fmass_g, fbeta_c
-        real(wp), intent(out), optional :: fR_m, fgamma_m
+        real(wp), intent(in) :: fmass_g, fbeta_c
+        real(wp), intent(out) :: fR_m, fgamma_m

Alternatively, if they must remain optional, add guards:

        if (present(fmass_g) .and. present(fR_m) .and. present(fgamma_m)) then
            ! ... existing logic at lines 372-386
        end if

Based on past review comments.


376-376: Missing normalization in mixture gas constant calculation.

The mixture gas constant fR_m is computed as a sum of mass-weighted constants but is not normalized by total mass. This produces an incorrectly scaled value with units of [mass × specific_gas_constant] instead of [specific_gas_constant].

Downstream usage (lines 427, 430, 817) expects fR_m to be the mixture-averaged specific gas constant, so this error will break thermodynamic calculations for Lagrangian bubbles.

Apply this diff to compute the correct mass-weighted average:

-                fR_m = (fmass_g*R_g + fmass_v*R_v)
+                fR_m = (fmass_g*R_g + fmass_v*R_v)/(fmass_g + fmass_v)

Based on past review comments.

🧹 Nitpick comments (3)
src/simulation/m_bubbles.fpp (3)

544-547: Inconsistent division-by-zero protection in relative error calculations.

Line 545 guards against division by very small velocity with a check on line 546, but line 544 lacks similar protection for very small radius. If myR_tmp1(4) is near zero (but positive), the relative error err(4) can become arbitrarily large or unstable.

Apply this diff to use consistent safe-denominator logic for both terms:

-                err(4) = abs((myR_tmp1(4) - myR_tmp2(4))/myR_tmp1(4))
-                err(5) = abs((myV_tmp1(4) - myV_tmp2(4))/myV_tmp1(4))
-                if (abs(myV_tmp1(4)) < verysmall) err(5) = 0._wp
+                err(4) = abs((myR_tmp1(4) - myR_tmp2(4))/max(abs(myR_tmp1(4)), verysmall))
+                err(5) = abs((myV_tmp1(4) - myV_tmp2(4))/max(abs(myV_tmp1(4)), verysmall))

Alternatively, zero out err(4) when myR_tmp1(4) is very small, similar to line 546.

Based on past review comments.


784-786: Consider using a named constant for velocity threshold.

The hardcoded threshold 1.e-12_wp at line 784 for detecting non-oscillating bubbles is reasonable for double precision, but using a named constant (e.g., velocity_threshold or reusing verysmall/sgm_eps) would improve maintainability and make the intent clearer.

Example:

        if (max(abs(myV_tmp(1)), abs(myV_tmp(4))) < verysmall) then
            err_V = 0._wp
        end if

This aligns with the check at line 546 and centralizes the threshold definition.


807-820: Clarify parameter name for vapor mass rate.

The output parameter at line 813 is renamed from fdMvdt_tmp to advance_EL, but the latter is vague and doesn't convey that it represents the time derivative of vapor mass (dMv/dt). The original name was clearer.

Consider reverting to a more descriptive name:

-        real(wp), intent(out) :: advance_EL
+        real(wp), intent(out) :: fdMvdt_tmp

Or use an even clearer name like dMv_dt or vapor_mass_rate.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3c4b0c and 8631676.

📒 Files selected for processing (1)
  • src/simulation/m_bubbles.fpp (20 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{fpp,f90}: Use 2-space indentation; continuation lines align beneath &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_ pattern (e.g., m_transport)
Name public subroutines with s_ pattern (e.g., s_compute_flux)
Name public functions with f
_ pattern
Keep subroutine size ≤ 500 lines, helper subroutines ≤ 150 lines, functions ≤ 100 lines, files ≤ 1000 lines
Limit routine arguments to ≤ 6; use derived-type params struct if more are needed
Forbid goto statements (except in legacy code), COMMON blocks, and save globals
Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate
Call s_mpi_abort() for errors, never use stop or error stop

**/*.{fpp,f90}: Indent 2 spaces; continuation lines align under &
Use lower-case keywords and intrinsics (do, end subroutine, etc.)
Name modules with m_<feature> prefix (e.g., m_transport)
Name public subroutines as s_<verb>_<noun> (e.g., s_compute_flux) and functions as f_<verb>_<noun>
Keep private helpers in the module; avoid nested procedures
Enforce size limits: subroutine ≤ 500 lines, helper ≤ 150, function ≤ 100, module/file ≤ 1000
Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct
Avoid goto statements (except unavoidable legacy); avoid global state (COMMON, save)
Every variable must have intent(in|out|inout) specification and appropriate dimension / allocatable / pointer
Use s_mpi_abort(<msg>) for error termination instead of stop
Use !> style documentation for header comments; follow Doxygen Fortran format with !! @param and !! @return tags
Use implicit none statement in all modules
Use private declaration followed by explicit public exports in modules
Use derived types with pointers for encapsulation (e.g., pointer, dimension(:,:,:) => null())
Use pure and elemental attributes for side-effect-free functions; combine them for array ...

Files:

  • src/simulation/m_bubbles.fpp
src/simulation/**/*.{fpp,f90}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/simulation/**/*.{fpp,f90}: Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)
Allocate large GPU arrays with managed memory or move them into persistent !$acc enter data regions at start-up
Avoid stop/error stop inside GPU device code
Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

src/simulation/**/*.{fpp,f90}: Mark GPU-callable helpers with $:GPU_ROUTINE(function_name='...', parallelism='[seq]') immediately after declaration
Do not use OpenACC or OpenMP directives directly; use Fypp macros from src/common/include/parallel_macros.fpp instead
Wrap tight loops with $:GPU_PARALLEL_FOR(private='[...]', copy='[...]') macro; add collapse=n for safe nested loop merging
Declare loop-local variables with private='[...]' in GPU parallel loop macros
Allocate large arrays with managed or move them into a persistent $:GPU_ENTER_DATA(...) region at start-up
Do not place stop or error stop inside device code

Files:

  • src/simulation/m_bubbles.fpp
src/**/*.fpp

📄 CodeRabbit inference engine (.cursor/rules/mfc-agent-rules.mdc)

src/**/*.fpp: Use .fpp file extension for Fypp preprocessed files; CMake transpiles them to .f90
Start module files with Fypp include for macros: #:include 'macros.fpp'
Use the fypp ASSERT macro for validating conditions: @:ASSERT(predicate, message)
Use fypp macro @:ALLOCATE(var1, var2) for device-aware allocation instead of standard Fortran allocate
Use fypp macro @:DEALLOCATE(var1, var2) for device-aware deallocation instead of standard Fortran deallocate

Files:

  • src/simulation/m_bubbles.fpp
🧠 Learnings (10)
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Ensure GPU code compiles with Cray ftn, NVIDIA nvfortran, GNU gfortran, and Intel ifx/ifort compilers

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight GPU loops with !$acc parallel loop gang vector default(present) reduction(...); add collapse(n) when safe; declare loop-local variables with private(...)

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Wrap tight loops with `$:GPU_PARALLEL_FOR(private='[...]', copy='[...]')` macro; add `collapse=n` for safe nested loop merging

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Mark GPU-callable helpers with `$:GPU_ROUTINE(function_name='...', parallelism='[seq]')` immediately after declaration

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Every argument must have explicit intent; use dimension/allocatable/pointer as appropriate

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Every variable must have `intent(in|out|inout)` specification and appropriate `dimension` / `allocatable` / `pointer`

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to **/*.{fpp,f90} : Limit routine arguments to ≤ 6; use derived-type params struct if more are needed

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Limit subroutines to ≤ 6 arguments; otherwise pass a derived-type 'params' struct

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:46.909Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .cursor/rules/mfc-agent-rules.mdc:0-0
Timestamp: 2025-11-24T21:50:46.909Z
Learning: Applies to **/*.{fpp,f90} : Use `wp` (working precision) parameter from `m_precision_select` instead of hardcoded precision like `real*8`

Applied to files:

  • src/simulation/m_bubbles.fpp
📚 Learning: 2025-11-24T21:50:16.713Z
Learnt from: CR
Repo: MFlowCode/MFC PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T21:50:16.713Z
Learning: Applies to src/simulation/**/*.{fpp,f90} : Avoid stop/error stop inside GPU device code

Applied to files:

  • src/simulation/m_bubbles.fpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (16)
  • GitHub Check: Github (ubuntu, mpi, debug, true)
  • GitHub Check: Github (ubuntu, no-mpi, single, no-debug, false)
  • GitHub Check: Github (ubuntu, mpi, debug, false)
  • GitHub Check: Github (macos, mpi, debug, false)
  • GitHub Check: Github (ubuntu, mpi, no-debug, true)
  • GitHub Check: Github (ubuntu, mpi, no-debug, false)
  • GitHub Check: Github (macos, mpi, no-debug, false)
  • GitHub Check: Oak Ridge | Frontier (gpu-omp)
  • GitHub Check: Georgia Tech | Phoenix (cpu)
  • GitHub Check: Georgia Tech | Phoenix (gpu-omp)
  • GitHub Check: Oak Ridge | Frontier (gpu-acc)
  • GitHub Check: Georgia Tech | Phoenix (gpu-acc)
  • GitHub Check: Oak Ridge | Frontier (cpu)
  • GitHub Check: Coverage Test on CodeCov
  • GitHub Check: Code Cleanliness Check
  • GitHub Check: Build & Publish
🔇 Additional comments (4)
src/simulation/m_bubbles.fpp (4)

482-482: Good separation of initial and working step sizes.

Introducing h0 to store the initial step size guess and using h as the working variable improves code clarity and follows good adaptive time-stepping practice.

Also applies to: 489-490


513-542: Efficient early-exit optimization for adaptive time-stepping.

The early-exit checks at lines 513–516, 525–528, and 539–542 avoid unnecessary computation of subsequent Runge–Kutta stages when an earlier stage already exceeds the error tolerance. This is a standard and effective optimization for adaptive ODE solvers.


729-731: Robust sanity checks for negative bubble radius.

The early-exit checks at lines 729–731, 746–748, and 763–765 detect physically invalid negative radii during Runge–Kutta stages and force step rejection by setting err > adap_dt_tol. This improves the robustness of the adaptive time-stepper and prevents unphysical states from propagating.

Also applies to: 746-748, 763-765


434-435: Verify whether wall temperature Tw scaling is missing.

A previous reviewer flagged that the grad_T calculation may be missing a Tw (wall temperature) scaling factor that was present in the original implementation. The current dimensionless formula computes:

grad_T = -Re_trans_T(iR0)*((fpb/pb0(iR0))*(fR/R0(iR0))**3 
                           *(mass_g0(iR0) + mass_v0(iR0))/(mass_g0(iR0) + fmass_v) - 1._wp)

Without access to the prior version or physics derivation, I cannot definitively confirm whether Tw should multiply the entire expression to produce a dimensional temperature gradient.

Run the following script to check the git history for this formula:

#!/bin/bash
# Search git history for changes to grad_T calculation in m_bubbles
git log -p --all -S "grad_T" -- "**/m_bubbles.f*" | head -200

Based on past review comments.

@hyeoksu-lee
Copy link
Contributor Author

@sbryngelson could you re-run the failed test suite? The error seems irrelevant to the code change.

mfc: OK > :) Running syscheck:

+ mpirun -np 1 /Users/runner/work/MFC/MFC/build/install/36dd4af3e9/bin/syscheck
 ptl_tool: problems getting address for index 0 (kernel index -1)
--------------------------------------------------------------------------
You requested support for tool connections, but no available
interfaces were found after applying any include or exclude directives:

  Include:    NULL
  Exclude:    NULL
  Available:  

Please adjust your include or exclude to allow selection of an
available interface.
--------------------------------------------------------------------------

mfc: ERROR > :( /Users/runner/work/MFC/MFC/build/install/36dd4af3e9/bin/syscheck failed with exit code 213.

@sbryngelson sbryngelson merged commit 2902000 into MFlowCode:master Dec 16, 2025
47 of 48 checks passed
@hyeoksu-lee hyeoksu-lee deleted the bubnorm branch December 16, 2025 19:54
@hyeoksu-lee hyeoksu-lee mentioned this pull request Dec 17, 2025
21 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Review effort 4/5 size:XXL This PR changes 1000+ lines, ignoring generated files

Development

Successfully merging this pull request may close these issues.

3 participants