Multi-Doppler Wind Synthesis (Stage 1)
Daisho.WindGridAccumulator — Type
WindGridAccumulatorStreamed 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 — thegrid_specandsweepslive here (single source of truth) and are reached viaacc.scalar.grid_spec/acc.scalar.sweeps.velocity_field::String: the:velocity-tagged field projected per gate.n_unknowns::Int:2for stage 1 (u, v);3reserved (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 inScalarGridAccumulator.
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.
Daisho.wind_accumulator_dims — Function
wind_accumulator_dims(grid_spec) -> NTupleTrailing 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).
Daisho.finalize_wind — Function
finalize_wind(acc::WindGridAccumulator, p::DaishoParameters;
frame::SynthesisFrame = CartesianFrame()) -> SynthesisOutputSolve 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):
- Point estimate
[u; v] = (AᵀWA)⁻¹ · AᵀWbfrom the packed 2×2AᵀWA, withD = S_aa·S_bb − S_ab²the determinant (→0 along the baseline). - Equal-variance "sandwich" covariance
cov_cart = σ²_vr · Ci · M2 · Ci(σ²_vr = velocity_variance; at equal gate weightsM2 = AᵀWAand this collapses toσ²_vr·Ci, the CEDRIC Eq-13Σg²form). - Rotation into the active output frame:
[c1; c2] = R·[u; v],cov_frame = R·cov_cart·Rᵀ, withR = rotation_at(frame, x, y, z); the in-frame σ aresqrtof 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.
Daisho.SynthesisOutput — Type
SynthesisOutputSingle-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) = σ₁ abovemax_sigma[1] - bit
2(0x02) = σ₂ abovemax_sigma[2] - bit
3(0x04) = singular: ≥1 gate butD ≤ 0/ σ non-finite → componentsfill_value - bit
4(0x08) = no data: no weighted gate reached the point → componentsfill_value
Daisho.SynthesisFrame — Type
SynthesisFrameAbstraction 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:
component_names(frame) -> (String, String)rotation_at(frame, x, y, z) -> R(N×N orthonormal)
Stage 1 ships only CartesianFrame. Plane/polar/coplane frames are pure additions later (no accumulator change).
Daisho.CartesianFrame — Type
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.
Daisho.component_names — Function
component_names(frame) -> (String, String)The two in-frame component names. CartesianFrame ⇒ ("U", "V").
Daisho.rotation_at — Function
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.
Daisho.SynthesisParameters — Type
SynthesisParametersStage-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-1CartesianFrame,max_sigma[1]/max_sigma[2]are σu / σv (TOML keysmax_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 termw·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 keymax_elevation); defaults to45.0.
Daisho.save_wind_accumulator — Function
save_wind_accumulator(path, accum::WindGridAccumulator) -> pathPersist a WindGridAccumulator to JLD2. Reload with load_wind_accumulator. Mirrors save_accumulator.
Daisho.load_wind_accumulator — Function
load_wind_accumulator(path) -> WindGridAccumulatorRead a WindGridAccumulator saved by save_wind_accumulator. Raises if the on-disk schema_version does not match WIND_ACCUMULATOR_SCHEMA_VERSION.
Daisho.merge_wind_accumulators! — Function
merge_wind_accumulators!(dst, src) -> dstCombine 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.
Daisho.write_wind_synthesis — Function
write_wind_synthesis(file, out::SynthesisOutput, p::DaishoParameters;
index_time, start_time=index_time, stop_time=index_time) -> fileWrite 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.