Multi-Doppler Wind Synthesis (Stage 1)

Daisho.WindGridAccumulatorType
WindGridAccumulator

Streamed per-gridpoint dual-Doppler normal system. Every plane is accumulated in the Cartesian basis; the struct carries no frame (the output frame is supplied to finalize_wind, so one accumulator can be finalized in several frames without re-gridding).

Fields

  • scalar::ScalarGridAccumulator: the embedded scalar grid for all configured fields (velocity included). "Wind" is "scalar grid + wind solve," produced in one geometry pass — the grid_spec and sweeps live here (single source of truth) and are reached via acc.scalar.grid_spec / acc.scalar.sweeps.
  • velocity_field::String: the :velocity-tagged field projected per gate.
  • n_unknowns::Int: 2 for stage 1 (u, v); 3 reserved (u, v, w, deferred).
  • AtWA::Array{Float64}: (n_packed, trailing…) packed Σ w·ggᵀ.
  • AtWb::Array{Float64}: (n_unknowns, trailing…) Σ w·v_r·g.
  • M2::Array{Float64}: (n_packed, trailing…) packed Σ w²·ggᵀ (sandwich cov).
  • weight_total::Array{Float64}: (trailing…) Σ w.
  • gate_count::Array{Int32}: (trailing…) contributing-gate count — diagnostic only (not a QC threshold; used for the intrinsic "did any data land here" check and as an informational output field). No radar identity is tracked.
  • schema_version::Int, fill_value/undetect: as in ScalarGridAccumulator.

The trailing dims come from wind_accumulator_dims (stage 1: (nz, ny, nx)). This struct is the stage-2 handoff artifact: its AtWA/AtWb/M2/weight_total/counts are exactly what a variational stage needs to recover masked baseline points. finalize_wind produces the single-stage product; the accumulator is the richer input for stage 2.

source
Daisho.wind_accumulator_dimsFunction
wind_accumulator_dims(grid_spec) -> NTuple

Trailing array dimensions for the wind accumulator planes (without the leading packed/component axis). Stage 1 supports the 3D shapes only: :volume_3d and :latlon_3d(nz, ny, nx).

source
Daisho.finalize_windFunction
finalize_wind(acc::WindGridAccumulator, p::DaishoParameters;
              frame::SynthesisFrame = CartesianFrame()) -> SynthesisOutput

Solve the per-gridpoint dual-Doppler normal system and produce the single-stage retrieval product. For each grid point (§2.3–2.4 of the stage-1 plan):

  1. Point estimate [u; v] = (AᵀWA)⁻¹ · AᵀWb from the packed 2×2 AᵀWA, with D = S_aa·S_bb − S_ab² the determinant (→0 along the baseline).
  2. Equal-variance "sandwich" covariance cov_cart = σ²_vr · Ci · M2 · Ci (σ²_vr = velocity_variance; at equal gate weights M2 = AᵀWA and this collapses to σ²_vr·Ci, the CEDRIC Eq-13 Σg² form).
  3. Rotation into the active output frame: [c1; c2] = R·[u; v], cov_frame = R·cov_cart·Rᵀ, with R = rotation_at(frame, x, y, z); the in-frame σ are sqrt of the covariance diagonal.

Masking is non-destructive: where the system is solvable, comp1, comp2, sigma1, sigma2 are always written, and quality_flag records, independently per component, which max_sigma threshold was exceeded. Two intrinsic (non-tunable) states blank the components to fill_value: no data (no weighted gate reached the point) and singular (D ≤ 0 / non-finite cov, e.g. a single look direction or a baseline point). Stage 1 solves n_unknowns == 2 only.

source
Daisho.SynthesisOutputType
SynthesisOutput

Single-stage retrieval product returned by finalize_wind. The grids are named frame-relatively: for CartesianFrame, comp1/comp2 carry U/V and sigma1/sigma2 carry σu/σv. n_gates is informational (not a flag).

quality_flag (Int8) bit encoding — non-destructive: where the system is solvable, comp1, comp2, sigma1, sigma2 are always written regardless of bits:

  • 0 = solvable and both in-frame σ within thresholds
  • bit 1 (0x01) = σ₁ above max_sigma[1]
  • bit 2 (0x02) = σ₂ above max_sigma[2]
  • bit 3 (0x04) = singular: ≥1 gate but D ≤ 0 / σ non-finite → components fill_value
  • bit 4 (0x08) = no data: no weighted gate reached the point → components fill_value
source
Daisho.SynthesisFrameType
SynthesisFrame

Abstraction for the orthonormal output frame the wind is expressed in. Because the rotation R = rotation_at(frame, x, y, z) depends only on the grid point (not the gate), it factors out of every accumulated sum, so accumulation stays Cartesian and the frame is applied only at finalize. A concrete frame provides:

Stage 1 ships only CartesianFrame. Plane/polar/coplane frames are pure additions later (no accumulator change).

source
Daisho.CartesianFrameType
CartesianFrame()

The identity output frame: components are (U, V) (zonal, meridional) and the rotation is the identity, so the finalize-time view equals the accumulated Cartesian solution.

source
Daisho.component_namesFunction
component_names(frame) -> (String, String)

The two in-frame component names. CartesianFrame("U", "V").

source
Daisho.rotation_atFunction
rotation_at(frame, x, y, z) -> Matrix{Float64}

The N×N orthonormal rotation taking the Cartesian solution into the output frame at grid point (x, y, z). CartesianFrame ⇒ the 2×2 identity.

source
Daisho.SynthesisParametersType
SynthesisParameters

Stage-1 dual-Doppler wind-retrieval QC, loaded from the optional [synthesis] block. Quality is judged only by the per-component normalized uncertainty (CEDRIC DTEST style), evaluated in the output frame — there is no radar or gate count threshold, since geometry adequacy already shows up in σ. Masking is non-destructive: where the system is solvable, the components and their σ are always written; the quality flag records which σ threshold(s) failed.

Fields

  • velocity_variance::Float64: assumed radial-velocity variance σ²_vr (m/s)²; 1.0 ⇒ normalized (CEDRIC USTD/VSTD) σ units. It only rescales the covariance to physical units and never enters the gridding weights.
  • max_sigma::Vector{Float64}: per-output-component maximum normalized σ, indexed by frame component. The thresholds are independent (one component may be well-determined while the other is not). For the stage-1 CartesianFrame, max_sigma[1]/max_sigma[2] are σu / σv (TOML keys max_sigma_1 / max_sigma_2); for future plane/polar frames they bind to in-plane/cross-plane or tangential/radial. Storing a vector means adding a named frame needs no schema change.
  • max_elevation::Float64: maximum line-of-sight elevation (degrees) a gate may have to enter the 2-unknown (u, v) normal system. The dual-Doppler approximation drops the vertical term w·sin(el), so steep beams (airborne tail radars reach ±70°) contaminate the horizontal solve; gates above this angle still grid every scalar field but are excluded from the wind solve. Optional (TOML key max_elevation); defaults to 45.0.
source
Daisho.merge_wind_accumulators!Function
merge_wind_accumulators!(dst, src) -> dst

Combine src into dst in place. Because the accumulation is linear — every plane is a simple sum of per-gate rank-1 updates — the merge is an elementwise add of AtWA/AtWb/M2/weight_total/gate_count. The embedded scalar grids (and the sweeps provenance they carry) are merged via merge_accumulators!. This is what makes the streaming model valid across files and radars: gridding sweeps A and B into one accumulator equals gridding them separately and merging. Strict compatibility is required (identical velocity_field, n_unknowns, [io] sentinels, and — enforced by the scalar merge — grid_spec/fields/grid_type); no silent coercion.

source
Daisho.write_wind_synthesisFunction
write_wind_synthesis(file, out::SynthesisOutput, p::DaishoParameters;
                     index_time, start_time=index_time, stop_time=index_time) -> file

Write a SynthesisOutput to a CF-1.12 gridded NetCDF file, mirroring the write_gridded_radar_volume layout (X/Y/Z + time dims, projected coordinates, a Transverse Mercator grid_mapping, and per-point latitude/longitude). Field names follow the active output frame's component_names: the two components, their *STD uncertainties, plus DET, NGATES, and QFLAG. For the stage-1 CartesianFrame these are U, V, USTD, VSTD, DET, NGATES, QFLAG (with CF standard_name eastward_wind/northward_wind); a polar/plane frame later emits its own names (e.g. VT, VR) through the same adapter. CF global attributes come from [grid.metadata]. Any pre-existing file is deleted first.

mask_quality (default true) writes the QC'd product: the wind components and their σ are blanked to fill_value wherever quality_flag != 0 (σ above threshold, singular, or no data), so the file carries only points that passed the σ thresholds. DET/NGATES/QFLAG are always written unmasked as diagnostics, and the SynthesisOutput / accumulator are untouched (the full non-destructive field remains available for stage 2). Pass mask_quality=false to write every solvable point.

source