Skip to content

Coherence Length

The coherencelength raytracer operator measures how far a vector field stays aligned along each line of sight. Use cases: magnetic-field coherence in MHD simulations, velocity-field correlations, or any other vector quantity carried by the dataset.

How it works

Each pixel fires one ray that samples the vector field cell-by-cell. The operator opens a segment at the first cell where |v| ≥ min_field_magnitude and stores that cell's direction as the seed. The segment grows while the local direction stays within angle_threshold degrees of the seed. The first cell that exceeds the threshold closes the segment, records its length, and starts a new one seeded from the new direction. Cells below min_field_magnitude are skipped; they don't contribute and they don't terminate the running segment.

Output zarr layout (under projections/):

Key Shape Description
coherencelength (npy, npx) per-pixel arithmetic mean of segment lengths along that ray (cm). Pixels with no segments are 0.0.
coherencelength_segments/segments (N_total,) every segment's length, flat across all rays (cm)
coherencelength_segments/offsets (npx·npy,) start index into segments for each ray
coherencelength_segments/counts (npx·npy,) segment count for each ray (0 if the ray had no signal)

The per-segment arrays only exist when store_segments: true.

Magnetic-Field Coherence on a Cosmological MHD Box

The camera fires an orthogonal grid of rays across a periodic snapshot to map the coherence length of MagneticField. vector_field: MagneticField tells the operator to look up MagneticFieldX/Y/Z among the loaded fields. pbc: true since the box is periodic. angle_threshold: 90° is the loosest physically meaningful setting: a segment ends only when the field flips into the opposite hemisphere. Tighten it (e.g. 30°) for stricter alignment.

Full configuration
coherence_length.yaml
dataset_type: 'pointcloud_voronoi'
driver_type: 'raytracer'
device: "cpu-openmp"

pointcloud_voronoi:
  hilbert_bits: 8
  mesh_cache_mode: "auto"
  loader: gadget
  gadget:
    path: /path/to/snapdir_127
    fields:
      - PositionX
      - PositionY
      - PositionZ
      - Density
      - Temperature
      - MagneticFieldX
      - MagneticFieldY
      - MagneticFieldZ

raytracer:
  pbc: true
  dist_max: 1.01
  min_step: 0
  max_step: 1.0e-2
  outputpath: ./coherence.zr
  overwrite: true
  operators:
    coherencelength:
      mode: manual
      view: orthogonal
      position: [0.01, 0.5, 0.5]
      direction: [1.0, 0.0, 0.0]
      up: [0.0, 0.0, 1.0]
      npixels: [640, 640]
      widths: [0.98, 0.98]
      vector_field: MagneticField
      angle_threshold: 90.0
      min_field_magnitude: 1.0e-20
      store_segments: true
      max_segments_per_ray: 1000

max_segments_per_ray bounds the per-ray segment buffer. Size it to your dataset's worst-case count; rays that exceed it lose their tail segments.

Reading the Output

import numpy as np
import zarr

z = zarr.open("coherence.zr", mode="r")
mean_map_cm = z["projections/coherencelength"][:]            # (npy, npx)
seg_grp     = z["projections/coherencelength_segments"]
segments_cm = seg_grp["segments"][:]
offsets     = seg_grp["offsets"][:]
counts      = seg_grp["counts"][:]

# Per-pixel median (vs. the per-pixel mean THOR writes directly):
npy, npx = mean_map_cm.shape
flat = np.full(npy * npx, np.nan)
for i, (off, c) in enumerate(zip(offsets, counts)):
    if c:
        flat[i] = np.median(segments_cm[off:off + c])
median_map = flat.reshape(npy, npx)

For pooled statistics across all sight lines (histograms, percentiles), use the flat segments array.

Per-pixel mean coherence length on a 25 cMpc/h cosmological MHD box at z=0.

See Also