Render drivers API
These are the top-level entry points. They compose the submodules — geodesics, simulation input, radiation — at root scope, which keeps Radiation free of a hard dependency on Simulation.
Two-phase render
render traces and caches geodesics, then integrates radiative transfer along them. render_amr wraps that in adaptive image-plane refinement (White, 2022), tiling the image and subdividing blocks flagged by relative-gradient and relative-Laplacian indicators.
BlackLightPlus.render — Function
render(cfg::RunConfig, snapshot; polarized=cfg.polarized, C=80.0, spacetime=nothing)
-> NamedTuple (I, Q, U, V) — each npix×npix (Q/U/V ≡ 0 for unpolarized)Full pipeline on an AthenaK snapshot (.athdf/.bin path or pre-built AthenaKSnapshot): camera → threaded geodesics → per-ray fluid sampling + invariant coefficients → transfer march → images. spacetime (default KerrSchildCartesian(cfg.bh_a)) injects an alternative metric, e.g. a static SuperposedKerrSchild superposition (frozen orbital phase, no puncture boost). C is the sampling density (step h = (r − r_h)/C); raise it (C ≥ 160) to reduce null-constraint drift on plunging rays.
BlackLightPlus.render_amr — Function
render_amr(cfg, snapshot_path; block=8, max_level=2, polarized=false,
tracer=trace_rays_sampled, stokes=nothing,
rel_grad_cut=0.15, rel_grad_frac=0.03,
rel_lapl_cut=1.0, rel_lapl_frac=0.03,
abs_grad_cut=nothing, abs_grad_count=5)
-> (I, Q, U, V, levels, nrays)Adaptive-refinement render: mosaic images at cfg.camera.resolution * 2^max_level per side (layout as render), the per-base-block finest-level map, and the total ray count; the resolution must be a multiple of block. Default indicator: relative gradient + Laplacian; setting abs_grad_cut selects White 2022's value-based absolute gradient instead and disables the relative pair (see _block_needs_refinement) — use it on dim-background decks where the relative pair saturates. tracer is the phase-1 backend of the default Stokes closure (e.g. a GPU tracer). stokes, when given, replaces that closure wholesale:
stokes(coords::Vector{Tuple{Float64,Float64}}) -> 4 × length(coords) Matrixrows I,Q,U,V; coords[k] = (α, β) in the camera_rays_at image-plane convention. This is the seam for a fused single-pass GPU imager (built over march_rays_gpu_amr); the snapshot/sampler/coefficient/tracer stack is then not built, so snapshot_path and the plasma config go unused.
Analytic render
render_analytic uses the exact-Kerr elliptic tracer and needs no simulation data. photon_ring_layers renders the $n = 0, 1, 2$ photon-ring images separately on their lensing bands.
BlackLightPlus.render_analytic — Function
render_analytic(cfg::RunConfig; bands=(0,1,2), ro=cfg.camera.distance, source=nothing)
-> NamedTuple (I, Ib, rs, ts, phis, redshift_sign, bands)The :analytic exact-Kerr tracer: for every camera pixel and ring order n ∈ bands, direct-invert the equatorial geodesic (analytic_emission) and evaluate the equatorial source there. Returns per-band observable maps — BL source radius rs, time-of-flight ts, azimuth phis, redshift_sign (each npix×npix, NaN off the disk) — the per-band images Ib, and I = Σ_n Ib[n]. source = nothing uses the placeholder r⁻² emissivity; an EquatorialDisk paints the AART g-disk redshift source with the Doppler+lensing brightness. Camera→Bardeen map: αBardeen = −αimage, βBardeen = βimage (validated against the numerical tracer). Threaded over pixels.
BlackLightPlus.photon_ring_layers — Function
photon_ring_layers(cfg::RunConfig; disk, bands=(0,1,2), dx=0.02, box=12.0,
npts=100, ro=cfg.camera.distance, fudge=1.0)
-> NamedTuple (I, Ib, masks, grid, N, box, dx, bands)Layered photon-ring render of the equatorial disk (AART's band loop): for each ring order n ∈ bands, build its lensing band on the shared Bardeen (α,β) grid over [-box, box]² at spacing dx, direct-invert analytic_emission on the in-band pixels only, and paint equatorial_disk_intensity. Restricting to the band mask is what keeps the n ≥ 1 rings razor-thin (the valid flag alone over-counts high-n landings). Returns the per-band images Ib[n] (N×N, rows = β) and masks[n], the composite I = Σ_n fudge^{[n>0]} Ib[n], and the shared grid metadata; all layers share N/coordinates so they sum directly. Threaded.