Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Optically-Thin Radiative Cooling

Quokka supports optically-thin radiative cooling via pre-computed tables of cooling rate, temperature, sound speed, pressure, and entropy as a function of gas density and specific internal energy. The cooling source term is applied as a Strang-split operator each hydrodynamic timestep.

Physical model

The cooling source term modifies the gas total energy and internal energy each timestep:

where \(\dot{E}_{\rm cool} = \dot{E}_{\rm cool}(\rho, e_{\rm int}) \cdot \rho^2\) is interpolated from a table indexed by density \(\rho\) and specific internal energy \(e_{\rm int} = E_{\rm int}/\rho\), and \(\dot{E}_{\rm heat}\) is an optional constant heating rate per hydrogen atom multiplied by \(n_H\).

Within each cell the ODE

is integrated adaptively using an embedded RK2 (Heun’s method) sub-cycle. Density is held constant during the integration (isochoric cooling).

Cooling tables

The tables bundled with Quokka were generated from Grackle’s Cloudy data using the scripts in extern/cooling/. Each table covers a 2D grid of \((\rho, e_{\rm int})\) with fast_log coordinate spacing (a bit-level approximation to log₂) and stores five output quantities as raw physical values. The indices are:

IndexQuantityUnits
0Cooling rate \(\dot{E}_{\rm cool}/\rho^2\)erg cm³ g⁻² s⁻¹
1TemperatureK
2Sound speedcm s⁻¹
3Pressuredyn cm⁻²
4Entropy \(K = P/\rho^\gamma\)erg cm² g⁻⁵/³

Available table files:

FileUV backgroundPhotoelectric heating
CloudyData_UVB=HM2012_resampled.h5HM2012included
CloudyData_UVB=HM2012_resampled_noPE.h5HM2012excluded
CloudyData_UVB=HM2012_shielded_resampled.h5HM2012 (shielded)included
CloudyData_UVB=HM2012_shielded_resampled_noPE.h5HM2012 (shielded)excluded
isrf_1000Go_grains_resampled.h5ISRF 1000 G₀ + grainsincluded

To regenerate the Grackle-based tables, run extern/cooling/resample_grackle_cooling_tables.sh. To regenerate isrf_1000Go_grains_resampled.h5, run extern/cooling/resample_cloudy_cooling_tables.py directly.

Note: Output transform metadata is intentionally absent from the HDF5 files. Output transforms are a property of the interpolation, not of the data; they are declared at the C++ call site in ResampledCooling.cpp. HDF5 files always contain raw physical values. When H5Reader loads a fast_log output, it applies FastMath::inverse_pow2 (Newton iteration) to each value at load time, so the internal buffer stores the interpolation-space representation that FastMath::pow2 inverts during lookup.

HDF5 table format

All cooling tables follow the self-describing tab1 group format read by quokka::DataTable. The group tab1 inside each HDF5 file contains:

  • Dataset data — shape [5, Nx0, Nx1] (C row-major), holding the five output quantities stacked along the first axis.

  • Dataset grids/rho — physical density grid values (g/cm³), informational only.

  • Dataset grids/eint — physical specific internal energy grid values (erg/g), informational only.

  • Attributes on tab1:

    AttributeTypeDescription
    Ndimint32Number of input dimensions (2)
    Noutint32Number of output quantities (5)
    Nxint32[2]Grid sizes [n_rho, n_eint]
    xlofloat64[2]Physical lower bounds [rho_min, eint_min]
    xhifloat64[2]Physical upper bounds [rho_max, eint_max]
    spacingstring[2]Coordinate spacing type ("fast_log" for both)
    input_namesstring[2]["rho", "eint"]
    output_namesstring[5]Names for the five outputs
    input_unitsstring[2]Physical units of inputs
    output_unitsstring[5]Physical units of outputs
    include_peint321 if photoelectric heating is included, else 0
    cloudy_H_mass_fractionfloat64Hydrogen mass fraction assumed in Cloudy

    String attributes must use fixed-length HDF5 strings (numpy dtype='S'); variable-length HDF5 strings are not supported by H5Reader.

Old-format files (pre-tab1) can be converted in-place with scripts/python/convert_cooling_table_hdf5.py.

Runtime parameters

ParameterTypeDefaultDescription
cooling.enabledbool (0/1)0Enable optically-thin cooling as a Strang-split source term.
cooling.cooling_table_typestring"resampled"Table type. Only "resampled" is supported.
cooling.hdf5_data_filestringrequiredPath to the cooling table HDF5 file.
cooling.read_tables_even_if_disabledbool (0/1)0Read tables at startup even if cooling is disabled (useful for diagnostics).
heating_rate_externalstring""AMReX parser expression for a time-variable external heating rate per H atom (erg s⁻¹ H⁻¹). Variables: time, dt.

See also Runtime parameters for the full parameter table.

Using cooling in a problem

1. Enable cooling in the input file

cooling.enabled = 1
cooling.hdf5_data_file = "../extern/cooling/CloudyData_UVB=HM2012_resampled.h5"

2. Enable cooling in the problem traits

template <> struct Physics_Traits<MyProblem> {
    // ...
    static constexpr bool is_cooling_enabled = true;
};

Quokka will automatically call quokka::ResampledCooling::computeCooling() as a Strang-split operator when is_cooling_enabled = true and cooling.enabled = 1.

3. Access thermodynamic quantities in callbacks

The GPU-safe helper functions in src/cooling/ResampledCooling.hpp interpolate the table at any \((\rho, E_{\rm int})\) point:

#include "cooling/ResampledCooling.hpp"

// inside a GPU lambda, given rho (g/cm³) and Eint (erg/cm³):
const Real T = quokka::ResampledCooling::ComputeTgasFromEgas(rho, Eint, tables);
const Real P = quokka::ResampledCooling::ComputePressureFromRhoEint(rho, Eint, tables);
const Real cs = quokka::ResampledCooling::ComputeSoundSpeedFromRhoEint(rho, Eint, tables);
const Real K  = quokka::ResampledCooling::ComputeEntropyFromRhoEint(rho, Eint, tables);

// invert T → Eint (uses root finding on the T(Eint) table column)
const Real Eint = quokka::ResampledCooling::ComputeEgasFromTgas(rho, T_target, tables);

The tables argument is a resampledGpuConstTables struct obtained from resampled_tables::const_tables(). In problem callbacks that receive SimulationData<problem_t> &simdata, obtain it via:

auto tables = simdata.coolingTables.const_tables();

4. Cooling length estimate

const Real l_cool = quokka::ResampledCooling::ComputeCoolingLength(rho, Eint, tables);

This returns \(c_s , t_{\rm cool}\) at the given state and is useful for AMR refinement criteria.

Python utilities

The scripts in extern/cooling/ support table generation and analysis:

ScriptPurpose
resample_grackle_cooling_tables.pyGenerate Grackle-based cooling tables from the Cloudy HDF5 data.
resample_grackle_cooling_tables.shShell wrapper that regenerates all four bundled Grackle tables.
resample_cloudy_cooling_tables.pyGenerate cooling tables directly from Cloudy output (used for ISRF table).
integrate_cooling_zone.pyNumerically integrate a single cooling zone and compare against Grackle directly.
test_cooling_onezone.pyOne-zone cooling test using integrate_cooling_zone.py.
grackle_tables.pyLow-level reader for the raw Grackle Cloudy HDF5 data.

Install Python dependencies with:

pip install -r extern/cooling/requirements.txt

Test problem

The ResampledCoolingTest problem (src/problems/ResampledCoolingTest/) verifies the cooling integrator by comparing the simulated temperature evolution of a single cell against a Grackle reference solution. The test passes when the L1 error between the two trajectories is below 2%.

To build and run:

quokka buildrun -d 1d ResampledCoolingTest