Per-Halo Physics Loop

This page is the hub for the SAGE26 physics walkthrough. It describes what happens inside evolve_galaxies() – the function that advances every galaxy in a halo from one snapshot to the next – and links out to the four detailed module pages.

Source: src/core_build_model.c

In one paragraph

evolve_galaxies() takes a halo and the galaxy list inherited from its progenitors, advances every galaxy through the snapshot interval, and writes the result back. Internally the interval is split into a number of substeps and the same physics block runs on every substep: gas infall, reincorporation, satellite stripping, cooling, star formation and feedback, then mergers and disruption. Two regimes (CGM vs hot halo) are classified once at the top of the function and reused throughout.

Sub-stepping

Each snapshot interval is integrated in STEPS = 10 substeps by default (defined in src/macros.h). At high redshift the snapshot spacing can exceed the halo dynamical time t_dyn = R_vir / V_vir, so evolve_galaxies() raises the substep count adaptively:

effective_steps = clamp(ceil(STEPS * deltaT_total / t_dyn), STEPS, MAX_STEPS)

with MAX_STEPS = 30. The SFR history arrays (SFHMassDisk[STEPS], SFHMassBulge[STEPS], etc.) remain sized to the fixed STEPS. Adaptive substeps are mapped back into those bins via step_bin = (step * STEPS) / effective_steps, so the output schema is stable regardless of the integration cadence.

Order of operations

evolve_galaxies() runs a few setup steps once per snapshot, then enters a substep loop that contains two inner per-galaxy loops.

Once per snapshot (before the substep loop):

#

Step

Where

1

Halo concentration (if ConcentrationOn > 0)

get_halo_concentration()

2

CGM regime classification (if CGMrecipeOn)

determine_and_store_regime()

3

FFB regime classification (if FeedbackFreeModeOn)

determine_and_store_ffb_regime()

4

Compute the total infalling gas for the snapshot interval

infall_recipe()

5

Choose effective_steps (the adaptive sub-step count)

inline in evolve_galaxies

Inner per-galaxy loop (each substep, every galaxy):

#

Step

Where

6

Central only: inject the per-substep share of infall into HotGas or CGMgas

add_infall_to_hot()

7

Central only: return ejected gas to the hot reservoir (if ReIncorporationFactor > 0)

reincorporate_gas()

8

Type 1 satellite with HotGas > 0: strip hot gas to the central

strip_from_satellite()

9

Cool gas from the hot/CGM reservoir into ColdGas (regime-aware if CGMrecipeOn, else classical)

cooling_recipe_regime_aware() / cooling_recipe()

10

Form stars and apply SN + AGN feedback (or FFB SF if flagged)

starformation_and_feedback()

Inner satellite/merger loop (each substep, after the per-galaxy loop):

#

Step

Where

11

Decrement MergTime; if the satellite is past the disruption threshold, either disrupt (timer still running) or merge (timer elapsed)

disrupt_satellite_to_ICS() / deal_with_galaxy_merger()

The per-snapshot infall total is divided by effective_steps and injected as infallingGas / effective_steps per substep in step 6.

Detailed treatment of each physics step lives in its own page:

Post-step bookkeeping

After the substep loop completes, evolve_galaxies() does several end-of-snapshot tasks:

  • Normalise Cooling, Heating, and OutflowRate to per-unit-time by dividing the accumulated values by deltaT.

  • Sum TotalSatelliteBaryons on the central from each remaining satellite.

  • Re-attach the galaxy list to haloaux[] for downstream output.

  • Shift mergeIntoID to account for merged galaxies that will not be written out (output indices are a contiguous range, so every preceding merged galaxy bumps the index down by one).

The two-regime split as a code path map

The CGM model (CGMrecipeOn = 1) classifies every galaxy as Regime 0 (CGM / precipitation, below the Dekel & Birnboim 2006 M_shock) or Regime 1 (hot halo, classical Croton+06). The classification controls several decisions in the substep loop:

Step

Regime 0 (CGM)

Regime 1 (hot halo)

Infall destination

CGMgas

HotGas

Cooling recipe

cooling_recipe_cgm() (primary)

cooling_recipe_hot() (primary) + cooling_recipe_cgm() for any residual CGMgas

Density profile

CGMDensityProfile (uniform / NFW / beta)

isothermal

AGN suppression mechanism

r_heat ratchet capped at R_vir

r_heat ratchet (no R_vir cap)

Precipitation criterion

t_cool / t_ff (Voit 2015)

none – isothermal r_cool from Sutherland-Dopita cooling time

When CGMrecipeOn = 0, determine_and_store_regime() is not called at all (the Regime field is left unset) and the substep loop dispatches cooling to cooling_recipe() -> cooling_recipe_hot() for every galaxy regardless of mass, recovering the original SAGE behaviour.

Galaxy types

The substep loop branches on galaxies[p].Type:

Type

Meaning

Special handling

0

Central of its FoF halo

Receives infall, reincorporation, cooling, SF

1

Satellite with its own dark matter subhalo

Hot gas is stripped to the central; otherwise evolves like a central

2

Orphan satellite (subhalo lost)

No hot reservoir; only SF from existing cold gas; merger timer counts down

Galaxies with mergeType > 0 are skipped in the loop – they have already been absorbed by a merger event earlier in the snapshot.

Reading the source

The function is intentionally a flat, top-to-bottom dispatcher with no hidden state. To trace any single galaxy through one snapshot, follow these landmarks in core_build_model.c:

  1. Pre-step classifications – lines around evolve_galaxies entry.

  2. The for(int step = 0; step < effective_steps; step++) loop body.

  3. Inside it, the for(int p = 0; p < ngal; p++) per-galaxy loop for cooling and SF, followed by the satellite/merger loop.

  4. After the substep loop, the per-galaxy normalisation block and the haloaux[] linkage block.

Where to go next