Projections API Reference

Functions for creating 2D projections from 3D simulation data.

Exported Functions

Main Projection Function

Function: projection, create 2D projections from 3D simulation data

The projection function uses Julia's multiple dispatch to provide specialized implementations for different data types. Since the complete API documentation is extensive, this section provides focused guidance for each data type.

Periodic boxes

On a periodic run a structure sitting on a box face is split across opposite edges of the map. periodic_recenter rolls a finished projection around the boundary so it appears whole, which is exact for an axis-aligned map because a whole-pixel shift is a translation of the box.

Mera.@project — Macro
@project data quantity... [keyword=value...]

Project several quantities and bind each map to a variable of that name.

projection returns one object whose maps is a dictionary, so pulling several quantities out of it is a line of lookups:

pj = projection(gas, [:sd, :T], myargs=args)
sd = pj.maps[:sd]; T = pj.maps[:T]

This does the same in one line, and asks for the quantities together, which is also the form that lets the projection use its threads (see Performance):

@project gas sd T myargs=args
# sd and T are now the maps, and proj is the full projection object

Keywords pass straight through, so off-axis works the same way:

@project gas sd inclination=60 azimuth=30 binning=:exact

The full object is bound as proj, so the extent, units and everything else stay reachable:

heatmap(proj.extent[1:2], proj.extent[3:4], sd)

Like @loadall, this binds names in your scope. Inside a function or a package, prefer the explicit form above.

See also: projection, @loadall.

Mera.periodic_recenter — Function
periodic_recenter(m; center=[0., 0., 0.], direction=:z, range_unit=:standard, verbose=verbose_mode)

Roll a projection so that center sits in the middle of the map.

On a periodic run a structure on a box face appears split across opposite edges of a projection. This shifts the map around the periodic boundary so it appears whole, and relabels extent and cextent so both are measured from center, which is then at (0, 0).

The shift is by whole pixels, so no value is altered and no interpolation happens: the sum over the map, and hence any total it represents, is unchanged. Only the frame moves.

center follows the usual convention: box fractions with range_unit=:standard (the default), or a physical length with range_unit=:kpc and friends. [:bc] means the box centre. A shift is only meaningful on an axis that actually wraps; if getinfo determined the run is not periodic, this warns rather than refusing, since the map may have come from somewhere else.

Pass the same direction you gave projection. A projection does not record which axis it looked along, so this cannot be inferred: with direction=:z the map spans x and y, with :x it spans y and z, with :y it spans x and z. Giving the wrong one rolls along the wrong axes.

Off-axis projections are not supported and are refused. Rolling only works because a whole-pixel shift of an axis-aligned map is the same thing as translating the box by a lattice vector. An off-axis camera plane is tilted with respect to the box, so no shift of its pixels corresponds to a periodic translation, and the result would be wrong rather than merely approximate.

p = projection(gas, :sd, :Msol_pc2)
q = periodic_recenter(p, center=[0., 0., 0.])   # the blast at the origin, made whole
heatmap(q.extent[1:2], q.extent[3:4], q.maps[:sd])

See also: projection.

Returns a new map of the same type, with the maps and extents shifted.

See Periodic Boxes for what else needs care on a wrapping run.

Performance & Threading Functions

Data Type Support

Hydro and RT projections

The hydro methods dispatch on Union{HydroDataType, RtDataType}, the same call works on an object from getrt.

# Single variable, code units / with a unit
projection(dataobject::Union{HydroDataType, RtDataType}, var::Symbol)
projection(dataobject::Union{HydroDataType, RtDataType}, var::Symbol, unit::Symbol)

# Several variables, one unit each / one unit for all
projection(dataobject::Union{HydroDataType, RtDataType}, vars::Array{Symbol,1}, units::Array{Symbol,1})
projection(dataobject::Union{HydroDataType, RtDataType}, vars::Array{Symbol,1}, unit::Symbol)

RT fields carry no cell mass, so two things differ from hydro:

  • the default weighting is [:volume], and weighting=[:mass] is turned into it; weight by a field instead with e.g. weighting=[:Np1];
  • mode=:standard gives the volume-weighted mean along the line of sight, and mode=:sum the volume-weighted sum $\sum q\,V$ per pixel. For a number density such as :Np1 the whole map then adds up to the photon number of the box; it is not a column density. A column density of mass comes from a hydro object with :sd.

The ionization state (:xHII, :em_recomb, ...) is a hydro quantity and is projected on the hydro object, e.g. projection(gas, :em_recomb; mode=:sum) for a mock recombination map.

Gravity (combined form)

Gravity quantities are projected by passing the gravity object alongside the hydro one, the cells come from the hydro object, the quantity from gravity:

projection(hydro::HydroDataType, gravity::GravDataType, var::Symbol, unit::Symbol)

Common keyword arguments

These are the grid (hydro / gravity / RT) keywords. The particle projection differs: its binning default is :cic, its weighting is a bare Symbol, and it has no nmax, depth, split, refine or refine_to at all (passing one is a MethodError). It adds parttypes, thickness/offset and nlos. See the particle section below and the off-axis cheatsheet for the full split.

KeywordWhat it does
pxsize=[value, :unit]physical size of a map pixel, the preferred way to set resolution
resgrid cells per side instead of a physical pixel size
lmaxsets the default resolution, res = 2^lmax. It does not coarsen the data: the deposit always runs over every level the object holds
direction:x, :y, :z (default :z), or :faceon/:edgeon, which derive the orientation from the data's own angular momentum
los, up, theta, phi, inclination, azimuthoff-axis line of sight: see Off-axis
weightinghow intensive quantities are averaged (see the note below)
mode:standard is a weighted mean, Σ(q·w)/Σw; :sum is Σ(q·w) on grid data (a plain per-pixel total Σq only for :ekin, :etherm and :volume) and Σq on particles. Neither divides by area: only :sd does that, in both modes. :sd and :mass ignore mode entirely
maska boolean array from getmask, applied before projecting; combines with fov
fov, fov_unit, aperturea rotation-invariant camera-plane window; fov=[wx,wy] for a rectangle (needs aperture=:square)
depth, depth_unita slab cut along the line of sight, off-axis only
splitweight partial cells by the volume inside the depth slab or fov sphere, instead of a hard cell-centre cut
refine, refine_tosubdivide the cells the fov sphere crosses so the edge sharpens; need fov and split=true
angmom_radiusrestrict the :faceon/:edgeon/:angmom orientation sum to a sphere; on grid and particle data
binning, nmaxhow a rotated cell is deposited: :ngp, :cic, :overlap (default), :exact
center, range_unitwhich part of the box to project, and in what units
data_center, data_center_unitorigin the map axes and cylindrical/spherical quantities are measured from
xrange, yrange, zrangerestrict the projected volume
max_threadscap the threads used
myargspass a bundle instead of repeating keywords: see Bundling Arguments
`weighting` has a different type for particles

Hydro, gravity and RT take an array: weighting=[:mass], weighting=[:volume], or [:quantity, unit]. Particle projections take a bare symbol: weighting=:mass, :volume, :sph or :voronoi. Passing a symbol to a hydro projection raises TypeError: expected Vector, got Symbol.

Key features:

  • AMR-aware grid mapping with conservative mass preservation
  • Variable-based parallel processing (8+ threads)
  • Mass-weighted averaging for intensive quantities

Common variables: :rho, :T, :sd, :mass, :v, :p, :cs, the box-frame velocity dispersions (:σx, :σy, :σz, :σ, and the cylindrical and spherical ones, all available on both paths), and off-axis only: :vlos/:σlos, :vcamx/:σcamx, :vcamy/:σcamy, :ncell

Tutorial: Hydro Projections, complete examples and usage

Particle Data Projections (PartDataType)

Key Method Signatures:

# Single variable with default units
projection(dataobject::PartDataType, var::Symbol)

# Single variable with custom units
projection(dataobject::PartDataType, var::Symbol, unit::Symbol)

# Multiple variables with custom units
projection(dataobject::PartDataType, vars::Array{Symbol,1}, units::Array{Symbol,1})

# Multiple variables with same units
projection(dataobject::PartDataType, vars::Array{Symbol,1}, unit::Symbol)

Key features:

  • Mass-weighted binning for discrete particles
  • getvar with :age returns stellar ages relative to the snapshot time
  • mode=:sum gives the per-pixel total instead of a weighted mean, which is how you get a real mass map: projection(part, :mass, :Msol; mode=:sum). The default :standard returns the mass-weighted mean particle mass, because a particle has no volume to make :mass extensive.
  • parttypes=[:stars] or [:dm] selects a family, and it sets the camera too, so parttypes=[:dm], direction=:faceon orients on the dark matter alone

Common variables: :mass, :age, :sd, :v, :birth, :metal, :id, :family, and off-axis :vlos/:σlos, :vcamx/:σcamx, :vcamy/:σcamy, :npart

Tutorial: Particle Projections, complete examples and usage

Weighting particles and gas cells: :mass, :volume, :sph, :voronoi

:mass (the default) and :volume deposit each particle at its position. For gas cells of moving-mesh and SPH codes, which carry a :volume, two further choices spread them out:

  • :sph smears each cell over an M4 kernel with $h = \alpha\,(3V/4\pi)^{1/3}$, sph_alpha (default 1.5), at least one pixel. Collisionless particles can use it through :subfind_hsml, which is used as it is. The deposit is renormalised on the pixel grid, so the mass is conserved for any $h$, but only the part that lands on the frame is kept. The same data in a tighter frame therefore holds less mass: on one test field $\int\Sigma\,dA/M$ was 1.000000 at half-widths of 0.50, 0.40 and 0.33 of the box, 0.9934 at 0.31 and 0.9604 at 0.30, as the frame started to clip the kernels. This is also why an axis-aligned and an off-axis :sph map can differ by about 1 %: the two frame the data differently. With los=[0, 0, 1], up=[0, 1, 0] the off-axis camera reproduces the axis-aligned geometry and the difference drops to about 0.01 %. Leave a margin around the data if the total must not depend on the frame.
  • :voronoi samples each line of sight through the nearest cell generator, which follows the moving-mesh cells. Intensive maps are exact; the surface density is approximate. On a cutout, $\int\Sigma\,dA$ falls short of msum without losing mass: msum adds the whole mass of every cell whose generator lies inside, including the part of the cell outside the boundary, while the map integrates only what is inside. The difference falls off as 1/L: on an AREPO zoom it was 4.2 %, 3.4 %, 1.8 % and 1.0 % for half-widths of 200, 400, 800 and 1600 ckpc/h. To integrate a sub-volume exactly, select a region larger than the one you measure. nlos sets the number of samples along each line of sight; by default Mera steps at the scale of the cells, min(pixsize, ½·median(V)^⅓), at most 4096 steps, and verbose=true reports the value used. Set it only to trade accuracy for speed or to check convergence.

Quick Usage Examples

# Hydro data projections
hydro = gethydro(info, ...)
projection(hydro, :rho, :g_cm3)              # Density projection
projection(hydro, :sd, :Msol_pc2)            # Surface density
projection(hydro, [:T, :v], [:K, :km_s])     # Multi-variable

# Particle data projections  
particles = getparticles(info, ...)
projection(particles, :age, :Myr)            # Stellar age
projection(particles, :sd, :Msol_pc2)        # Stellar surface density
projection(particles, :mass, :Msol; mode=:sum)   # mass per pixel (the default is a MEAN)

General Projection Types

Both data types support:

  • Density projections: Surface density maps (:sd)
  • Mass-weighted projections: Intensive quantities with proper averaging
  • Velocity projections: Velocity fields and dispersion maps
  • Custom derived quantities: Temperature, pressure, kinematic analysis

Volume rendering

volumerender gives every cell a colour and an opacity from the value of one quantity and composites the line of sight front to back, so dense gas hides what is behind it. The opacity is set per unit length, so the picture does not change with the pixel size or the zoom. volumelook resolves the colours and the opacity once, volumeorbit turns the view around the object with that look fixed, and volumepath moves the camera between keyframes (angles, centre and zoom), both ready for a movie.

img = volumerender(gas, :rho; unit=:nH, colorrange=(1e-3, 1e2), colormap=:inferno,
                   opacity_gamma=2, tau_unit_length=[300, :pc],
                   inclination=70, axis=:angmom, center=[:bc], range_unit=:kpc, pxsize=[80, :pc])
save("galaxy.png", rotl90(img.image))

This is an illustration tool. For maps with physical units use projection.

Mera.volumerender — Function
volumerender(gas, quantity; look=nothing, supersample=:auto, <look keywords>, <view keywords>)
    -> VolumeRenderType

Volume rendering of one quantity: every cell gets a colour and an opacity from its value, and the line of sight is composited front to back, so dense gas in front hides what is behind it.

img = volumerender(gas, :rho; unit=:nH, colorrange=(1e-3, 1e2), colormap=:inferno,
                   tau_unit_length=[100, :pc], inclination=70, axis=:angmom,
                   center=[:bc], range_unit=:kpc, pxsize=[50, :pc])
save("galaxy.png", rotl90(img.image))

Opacity is defined per unit length (tau_unit_length), so the picture does not change when you change pxsize or zoom. Without it the strength is taken from the data.

A quantity that fills the whole volume, such as temperature, easily turns into fog: give it a long tau_unit_length (a few hundred pc or more), a narrow colorrange around what you want to see, and opacity_gamma > 1 so that only the top of the range becomes opaque.

iso=[1, 10, 100] puts a surface at each of those values: only gas near them is opaque, so you see nested shells instead of a cloud. With opacity=(0, 0) nothing else is drawn at all.

If the picture comes out dark, the gas is collecting little light along the ray. Any of these helps, in this order: raise exposure; shorten tau_unit_length so the gas is more opaque; narrow the colorrange onto the values you care about; lower opacity_gamma toward 1 so faint gas contributes; or raise the low end of opacity. Choose a colour map that is bright at its low end, such as :viridis or :cividis, rather than one that starts black.

Pass look= from volumelook to keep colours and opacity fixed across the frames of a movie; volumeorbit does that for a turning view.

:vlos, the velocity along the line of sight, works as a quantity even though it is not a getvar field: the renderer knows where the camera is. It is signed, so use scale=:linear, a symmetric colorrange and a diverging colour map such as :vik or :berlin. Positive means moving away from you, as in projection.

View keywords are those of the off-axis projection: direction, los, up, inclination, azimuth, axis, position_angle, angle_unit, xrange/yrange/zrange, center, range_unit, pxsize or res, mask, verbose, max_threads.

The look keywords unit, scale, colorrange, percentiles, colormap, opacity, opacity_gamma, iso, iso_width, iso_alpha, exposure, tone, tau_unit_length, tau_target and background are those of volumelook; with look= they are not used. supersample=:auto renders at up to four times finer pixels where cells are smaller than a pixel and averages them back, within about 1.5 GB of memory; an integer sets the factor.

This is an illustration, not a measurement: the colours and opacity are choices. For physical maps, such as a surface density or an optical depth, use projection. Mera's hints about velocity frames and centres are silenced while a picture is made, since they are about physics rather than about drawing; call getvar yourself to see them.

Mera.volumelook — Function
volumelook(gas, quantity; unit=:standard, scale=:log, colorrange=nothing,
           percentiles=(1, 99.9), colormap=:inferno, opacity=(0.0, 1.0), opacity_gamma=1.0,
           iso=[], iso_width=0.05, iso_alpha=0.8, exposure=1.0, tone=:aces,
           tau_unit_length=nothing, tau_target=1.5, background=:black, <view keywords>)
    -> VolumeLook

Resolve how a quantity looks: the colour range, the colour map, and how opaque the gas is.

  • colorrange=(lo, hi) in the quantity's unit; without it the given percentiles of the cell values are used. With scale=:log the range is spaced logarithmically.
  • opacity=(a_lo, a_hi) are the ends of the opacity ramp across the colour range, and opacity_gamma bends it (> 1 keeps low values more transparent). Values below the range are invisible when a_lo = 0.
  • iso=[1.0, 10.0, 100.0] adds a surface at each of those values, in the quantity's unit: gas whose value is close to one of them becomes more opaque, so the shell stands out while everything else stays see-through. iso_width is the half width of a shell measured in the units of the colour range (so in dex when scale=:log), and iso_alpha is the opacity added at its centre. Both take either one number for all shells or one per shell; a surface sitting where the data is thin needs a wider band than one where it is dense. A wide outer shell hides the inner ones when they are equally opaque, so give the outer one a smaller value, for example iso_alpha=[0.15, 0.3, 0.6]. With opacity=(0, 0) you see the surfaces alone.
  • exposure brightens the finished picture, like the exposure of a camera. A thin, see-through rendering is dark simply because little light is collected; exposure=3 makes it three times brighter.
  • tone decides what happens to light brighter than white. :aces (default) and :reinhard bend it smoothly, so bright regions fade toward white and keep their detail, the way a camera or a film renderer behaves. :linear cuts it off instead, which holds colours at full saturation and flattens anything bright into one solid patch. Use :linear only when you want the raw numbers on screen.
  • tau_unit_length=[100, :pc] sets what full opacity means: optical depth 1 over that length. Without it the strength is taken from the data, so that the median optical depth of the map is tau_target.
  • background is the colour behind the gas: :black (default), :white, any colour name, or a colour.
  • mask and verbose apply to the automatic strength only: it uses the cells marked true, and verbose=true prints the projection behind it.

The view keywords (direction, inclination, axis, center, range_unit, pxsize, res, xrange…) are only used for that automatic strength, so pass the ones you will render with.

Pass the result to volumerender as look= to keep every frame of a movie identical in colour and opacity.

Mera.volumeorbit — Function
volumeorbit(gas, quantity; angles=0:2:358, sweep=:azimuth, inclination=70, axis=:angmom,
            look=nothing, fov=nothing, fov_unit=:standard, aperture=:circle,
            center=[:bc], parallel_frames=false, <volumerender keywords>)
    -> Vector{VolumeRenderType}

Render a turning sequence with one fixed look and one fixed frame, ready for a movie.

Like rotation_sequence it selects a sphere around center, so every frame shares the same field of view and the object does not appear to zoom. The look is resolved once from the first angle (or taken from look=), so colours and opacity do not flicker.

frames = volumeorbit(gas, :rho; unit=:nH, angles=0:2:358, inclination=70,
                     center=[:bc], fov=20, fov_unit=:kpc, pxsize=[80, :pc])
mkpath("frames")
for (k, f) in enumerate(frames)
    save(joinpath("frames", "f" * lpad(k, 4, '0') * ".png"), rotl90(f.image))
end
moviefromframes("frames", "orbit.gif"; fps=25)

sweep is :azimuth, :inclination or :position_angle. parallel_frames=true renders frames concurrently with one thread each, which fills the cores when there are many frames and uses more memory.

The look keywords unit, scale, colorrange, percentiles, colormap, opacity, opacity_gamma, iso, iso_width, iso_alpha, exposure, tone, tau_unit_length, tau_target and background are those of volumelook, resolved once for all frames. supersample, verbose and max_threads work as in volumerender; other keywords go to the view.

Mera.volumepath — Function
volumepath(gas, quantity; keyframes, frames=120, res=512, fov_unit=:kpc, axis=:angmom,
           look=nothing, aperture=:square, smooth=true, loop=false,
           parallel_frames=false, <volumerender keywords>)
    -> Vector{VolumeRenderType}

Render a camera path: the view moves between keyframes and every frame comes out the same size, ready for a movie.

Each keyframe is a named tuple; anything you leave out keeps its previous value:

fieldmeaning
inclination, azimuth, position_anglethe view angles, in degrees, about axis
fovhalf the width of the picture, in fov_unit; smaller means closer
centerwhere the camera looks, [x, y, z] in fov_unit, or [:bc]

Angles and centres are interpolated in a straight line, the field of view geometrically (so a zoom runs at a constant rate), and smooth=true eases each segment in and out. loop=true returns to the first keyframe at the end, for a movie that repeats without a jump.

frames = volumepath(gas, :rho; unit=:nH, colorrange=(1e-3, 1e2), tau_unit_length=[300, :pc],
    keyframes = [(inclination=0,  azimuth=0,   fov=20),      # face on, wide
                 (inclination=80, azimuth=180, fov=20),      # tilt and turn
                 (inclination=80, azimuth=360, fov=4)],      # turn further and move in
    frames=180, res=512, fov_unit=:kpc)

mkpath("frames")
for (k, f) in enumerate(frames)
    save(joinpath("frames", "f" * lpad(k, 4, '0') * ".png"), rotl90(f.image))
end
moviefromframes("frames", "path.gif"; fps=30)

The camera is orthographic: it looks at the object from outside and cannot fly through it. For a camera inside the gas you need a perspective renderer.

The look keywords unit, scale, colorrange, percentiles, colormap, opacity, opacity_gamma, iso, iso_width, iso_alpha, exposure, tone, tau_unit_length, tau_target and background are those of volumelook, resolved once from the first keyframe. res is the number of pixels across every frame. supersample, verbose and max_threads work as in volumerender, and parallel_frames=true renders frames concurrently with one thread each.

For a plain turn around an object use volumeorbit, which needs no keyframes.

Mera.VolumeLook — Type
VolumeLook

How a quantity is turned into colour and opacity, resolved to numbers by volumelook: quantity, unit, scale, colorrange, colormap, the opacity ramp and its opacity_gamma, the sigma (per cm) that full opacity means, and the background colour.

Reuse one look for every frame of a movie, so the colours and the opacity do not change from frame to frame.

Mera.VolumeRenderType — Type
VolumeRenderType

The result of volumerender.

  • image: the picture, Matrix{RGB{Float64}} indexed [ix, iy] like a projection map. Save it with save("frame.png", rotl90(m.image)).
  • alpha: how much of each pixel is filled, 1 − transmission, between 0 and 1
  • value: the value of the quantity you actually see, averaged along the ray and weighted by what reaches you (NaN where nothing is visible)
  • look: the VolumeLook used, so the next frame can share it
  • extent, pixsize: code length, measured from the centre of the view (getextent)
  • los, up, cam_right, center: the camera; los points toward the observer
  • supersample, boxlen, scale, info

Drawing the AMR grid on a map

Overlay the cell boundaries of a refinement level on a finished projection, useful for showing where resolution changes relative to a structure. gridoverlay! draws into an existing axis; gridoverlay returns the segments so you can draw them yourself.

Mera.gridoverlay — Function
gridoverlay(dataobject; level=:max, direction=:z, center=[:boxcenter], range_unit=:standard,
            xrange=[missing,missing], yrange=[missing,missing], zrange=[missing,missing],
            unit=:standard) -> (segments, extent, level)

Cell-boundary line segments of the AMR cells at one refinement level, viewed along direction (:x/:y/:z), for overlaying the AMR structure on a map. level is :max (default, the finest), :min, or an integer. The cell edges are de-duplicated.

The result is the 2-D footprint of that level's cells (collapsed along direction), so it suits both a projection and a slice:

  • slice — pass a thin zrange (the slice plane) → the exact in-plane cell grid there;
  • projection — use the full column → where that level's cells project to along the line of sight.

(The two coincide when the refined region is a column-aligned box, and differ for irregular refinement.)

Off-axis views work too: pass the same view keywords as projection — los/up, inclination/azimuth (or theta/phi, position_angle), axis=:angmom, or direction=:faceon/:edgeon. Each cell centre is projected through the camera basis and drawn as a cell-sized square (an approximate indicator; the true tilted-cube silhouette is a hexagon). Off-axis overlays are not edge-de-duplicated, so on dense AMR pick a coarser level or a sub-region to keep the segment count manageable.

Returns a NamedTuple: segments (a Vector{NTuple{4,Float64}} of (x1,y1,x2,y2) in the plane coordinates and unit), extent [xmin,xmax,ymin,ymax], and the level used. Plot with linesegments! — or, after using Makie, the convenience gridoverlay!(ax, go).

p  = projection(gas, :sd)
go = gridoverlay(gas; level=:max)         # the finest-cell grid, where it exists
# overlay go.segments on the heatmap of p.maps[:sd]

angle_unit (:deg or :rad) is the unit of the view angles, as in projection.

Mera.gridoverlay! — Function
gridoverlay!(ax, go; color=(:white,0.3), linewidth=0.4)

Draw a gridoverlay result go onto a Makie axis ax (the AMR cell boundaries). Available after using Makie/CairoMakie.

Performance & Threading

Mera.benchmark_projection_hydro — Function
benchmark_projection_hydro(gas_data, thread_counts::Vector{Int}, n_runs::Int=10, output_file::String="") → Dict

Execute comprehensive AMR hydro projection benchmark with robust statistical analysis.

This function serves as the main coordinator for hydro projection performance testing. It performs AMR structure analysis, data quality validation, executes both single-variable and multi-variable projection benchmarks across specified thread counts, and exports results in multiple formats with comprehensive statistical analysis.

Benchmark Methodology

  • Single-Variable Test: Surface density projection (:sd → Msun/pc²)
  • Multi-Variable Test: 10 simultaneous variable projections: vars = [:v, :σ, :σx, :σy, :σz, :vrcylinder, :vϕcylinder, :σrcylinder, :σϕcylinder, :cs]
  • Statistical Robustness: several repetitions per configuration with coefficient of variation
  • Quality Control: Success rate monitoring (>80% threshold for reliable data)
  • Memory Profiling: Peak memory usage and garbage collection analysis

Threading Analysis

Evaluates performance across thread counts with derived metrics:

  • Speedup: Performance improvement vs single-threaded execution
  • Efficiency: Speedup per thread (percentage of ideal scaling)
  • Memory Scaling: Memory usage patterns across thread configurations

Output Files Generated

  • {output_file}.csv: Structured data for spreadsheet analysis and plotting
  • {output_file}.json: Machine-readable structured data for programmatic access
  • {output_file}_summary.txt: Human-readable performance report with insights

Arguments

  • gas_data: HydroDataType object from loaddata() or gethydrodata()
  • thread_counts::Vector{Int}: Thread counts to benchmark [1, 2, 4, 8, 16, ...]
  • n_runs::Int=10: Statistical repetitions per configuration (10 for robust analysis)
  • output_file::String="": Output filename base (auto-generated timestamp if empty)

Returns

Dictionary containing complete benchmark results with keys:

  • n_threads, test_type, mean_time, std_time, speedup, efficiency
  • mean_memory, success_rate, min_time, max_time, n_runs

Example Usage

# Load RAMSES hydro data
gas_data = loaddata(300, "/path/to/ramses/output/", :hydro)

# Run comprehensive benchmark (single + multi-variable)
results = benchmark_projection_hydro(gas_data, [1, 2, 4, 8, 16], 10, "performance_test")

# Results saved as:
# - performance_test.csv (for plotting with plot_results.jl)
# - performance_test.json (for programmatic analysis)  
# - performance_test_summary.txt (human-readable report)

Performance Insights

The benchmark automatically analyzes threading efficiency and provides guidance:

  • Identifies optimal thread counts for your system and data size
  • Detects threading bottlenecks and memory constraints
  • Quantifies single vs multi-variable projection performance differences
  • Provides statistical confidence intervals for all measurements

Integration Workflow

  1. Data Loading: Use Mera's loaddata() for your RAMSES simulation
  2. Benchmarking: Execute this function with desired thread counts
  3. Visualization: Use plot_results.jl to create performance dashboards
  4. Analysis: Review summary.txt for optimization recommendations
Mera.show_threading_info — Function
show_threading_info()

Display information about Julia threading configuration and recommendations.

Returns nothing.


Not sure which of Mera's map-making tools you want? Projections: which tool compares them and says when to reach for each.

@project projects several quantities in one line and binds each map to a name of its own. See Pipelines.

For complete function documentation: see the Complete API Reference.

Function Reference

Mera.projection — Function
projection()

Print the quantities that can be projected, for cells and for particles, and return nothing.

The projections themselves are the methods projection(data, var, ...).

projection(data, var::Symbol, unit::Symbol=:standard; kwargs...) -> AMRMapsType
projection(data, vars::Vector{Symbol}, units::Vector{Symbol}; kwargs...) -> AMRMapsType
projection(data, vars::Vector{Symbol}, unit::Symbol=:standard; kwargs...) -> AMRMapsType
projection(gas::HydroDataType, grav::GravDataType, var_or_vars, unit_or_units; kwargs...)
projection(part::PartDataType, ...) -> PartMapsType     # see the particle method below
projection()

Project cells onto a map, along a box axis or along any line of sight.

data is a hydro or RT object, or a selection made from one. Every cell is deposited onto the pixels it covers, on every level the object holds, so the total mass of a map is conserved to round-off, for any pixel size and number of threads. :sd gives the surface density; any other quantity a weighted mean along the line of sight. projection() prints the quantities that can be projected.

Arguments

  • data: the cells to project, from gethydro or getrt, or a selection.
  • var, vars: one quantity, or several, e.g. :sd, :rho, :T, :vx, :σz; off-axis also :vlos and :σlos.
  • unit, units: the unit of each map, e.g. :Msol_pc2, :g_cm3, :K, :km_s; :standard means code units. They can also be given as the keywords unit and units.
  • grav: a gravity object for gravity quantities such as :epot, projected on the cells of the hydro object gas. The two objects can be given in either order.

Keywords

Map grid:

  • pxsize = [value, unit]: the size of a pixel, e.g. [50, :pc]; the preferred way to set the resolution. The pixel is boxlen/ceil(boxlen/pxsize), exactly pxsize when it divides the box.
  • res: the number of pixels across the whole box, so a window of a tenth of the box gives a tenth of res pixels. Default 2^lmax.
  • lmax = data.lmax: sets the default res; it does not coarsen the data.

Region:

  • xrange, yrange, zrange: [min, max] of the volume to project, in range_unit and relative to center; missing means the full extent. Off-axis these are world-space ranges.
  • center = [0, 0, 0]: the centre of the map, in range_unit; [:bc] is the box centre.
  • range_unit::Symbol = :standard: :standard means fractions of the box; any length unit in info.scale works, e.g. :kpc.
  • mask: a Bool vector with one entry per cell, e.g. from getmask; only cells marked true are projected.

View (give one of these ways; without any, the axis-aligned view runs):

  • direction::Symbol = :z: :x, :y, :z, or :faceon/:edgeon for a disc, oriented by its angular momentum.
  • inclination, azimuth, axis: tilt the view away from a reference axis (:z by default, :angmom for the object's own spin, or a vector) and turn it about that axis. position_angle rolls the finished image. angle_unit = :deg (or :rad).
  • los, up: the line of sight as a vector, and optionally the up direction of the image.
  • theta, phi: spherical angles of the line of sight; deprecated since 1.8, removed in 2.0.
  • angmom_radius = [r, unit]: compute the :angmom, :faceon and :edgeon orientation from a sphere about center only.

Off-axis frame:

  • fov, fov_unit = :standard, aperture = :circle: a frame of half-width fov fixed in the camera plane, the same at every angle; aperture=:square fills the rectangle, and fov=[wx, wy] makes it rectangular.
  • depth, depth_unit = :standard: keep a slab of this half-thickness along the line of sight.
  • split::Bool = false, refine::Int = 0, refine_to: weight cells cut by the depth slab or the fov sphere by the fraction inside, and subdivide them for a sharper edge.
  • binning::Symbol = :overlap, nmax::Int = 64: how a rotated cell is shared among pixels; :exact is the reference, :cic and :ngp are fast previews.

Averaging:

  • weighting = [:mass]: the weight of the mean, [:quantity] or [:quantity, unit], e.g. [:volume]. RT objects default to [:volume], since their cells carry no mass.
  • mode::Symbol = :standard: :standard gives the weighted mean, :sum the weighted sum per pixel. :sd and :mass are not affected.
  • data_center = center, data_center_unit: the origin of the map axes and of cylindrical and spherical quantities, when it should differ from center.
  • gravity_data: a gravity object, as an alternative to passing grav as an argument.

Output and performance:

  • max_threads::Int = Threads.nthreads(): the most threads to use.
  • verbose::Bool = true, verbose_threads::Bool = false: print a summary, or also the use of the threads.
  • show_progress::Bool = true: show a progress bar.
  • myargs::ArgumentsType: set common keywords in one object; a keyword given directly takes precedence.

Returns

An AMRMapsType (also called HydroMapsType) with

  • maps: a Dict of 2-D arrays, one per quantity, with maps_unit, maps_mode and maps_weight describing each;
  • extent, cextent: the map edges [xmin, xmax, ymin, ymax] in code length, from the box corner or from the centre; getextent(map, :kpc) converts them;
  • pixsize: the pixel size in code length;
  • ranges: the projected region in fractions of the box; center, and off-axis the camera los, up and cam_right;
  • lmin, lmax, lmax_projected, boxlen, scale, info.

In a cosmological run, lengths and densities are proper (physical) at the scale factor of the snapshot. The details of off-axis frames, depth, partial cells and deposits are on the Off-axis API page; the RT and gravity forms on the Projections API page.

Examples

julia> m = projection(gas, :sd, :Msol_pc2; pxsize=[50, :pc], xrange=[-10, 10],
                      yrange=[-10, 10], zrange=[-2, 2], center=[:bc], range_unit=:kpc);

julia> size(m.maps[:sd]), getextent(m, :kpc; center=true)   # 400 x 400 pixels, ±10 kpc

julia> m = projection(gas, [:rho, :T], [:g_cm3, :K]; pxsize=[100, :pc], center=[:bc]);

julia> m = projection(gas, :sd, :Msol_pc2; direction=:edgeon, angmom_radius=[10, :kpc],
                      fov=20, fov_unit=:kpc, aperture=:square, center=[:bc],
                      range_unit=:kpc, pxsize=[100, :pc]);    # an edge-on disc

julia> m = projection(gas, grav, :epot);                     # gravity on the gas cells

See also slice, getextent, getvar, subregion, profile.

projection(part::PartDataType, var::Symbol, unit::Symbol=:standard; kwargs...) -> PartMapsType
projection(part::PartDataType, vars::Vector{Symbol}, units::Vector{Symbol}; kwargs...)
projection(part::PartDataType, vars::Vector{Symbol}, unit::Symbol=:standard; kwargs...)

Project particles onto a map, along a box axis or along any line of sight.

The keywords of the grid method above apply, for the map grid, the region, the view, fov and the output, with these differences:

  • binning::Symbol = :cic: point particles are deposited by :cic or :ngp; :overlap and :exact fall back to :cic.
  • weighting::Symbol = :mass: a symbol, not an array. :mass or :volume deposit each particle at its position. For gas cells with a :volume (moving-mesh and SPH codes), :sph smears each over a kernel of size sph_alpha·(3V/4π)^⅓ (sph_alpha = 1.5), and :voronoi samples each line of sight through the nearest cell, with nlos samples. :sph also works for collisionless particles with a :subfind_hsml column.
  • mode::Symbol = :standard: the mass-weighted mean per pixel; :sum gives the total, so projection(part, :mass, :Msol; mode=:sum) is a mass map.
  • parttypes = [:all]: [:stars] or [:dm] projects one family, which also sets the orientation of :faceon/:edgeon.
  • thickness, thickness_unit, offset, offset_unit: project a slab of this depth along the line of sight, moved by offset; particles have no depth, split, refine or nmax.
  • ref_time = info.time: the time from which stellar ages are counted, in code units.

Returns

A PartMapsType with the same fields as the grid result, and ref_time.

Examples

julia> m = projection(part, :sd, :Msol_pc2; parttypes=[:stars], pxsize=[100, :pc],
                      center=[:bc], range_unit=:kpc);

julia> m = projection(part, :mass, :Msol; mode=:sum, pxsize=[1, :kpc]);   # mass per pixel

julia> m = projection(gas_cells, :sd; weighting=:sph, sph_alpha=2.0);     # smoother kernels

The :sph and :voronoi totals and their dependence on the frame are explained on the Projections API page.

Mera.project — Function
project(data, var [, unit]; res=auto, kwargs...)                      # already-loaded hydro/particles
project(info::InfoType, var [, unit]; vars=auto, lmax=…, kwargs...)   # load hydro, then project
project(path::AbstractString, output::Integer, var [, unit]; kwargs...)  # getinfo + load + project

One-call projection. A high-level convenience that loads the data (when given an InfoType or a path+output) and projects in a single call — the ergonomic equivalent of yt.ProjectionPlot(ds, "z", field).

  • Smart resolution — if res is not given it defaults to 2^lmax capped at 1024, so a deep AMR run doesn't silently allocate an enormous map (pass res= to override; a note is printed when capped).
  • Loading — reads the full hydro state by default (fast); restrict with vars=[:rho] if you know exactly what the projection (and its weighting / view) needs.
  • All other keywords (direction, los, center, range_unit, weight, mode, pxsize, …) are forwarded to projection; the return value is the same map object.

verbose=true prints what is read and a note when the default resolution is coarser than the finest level.