sounding_analysis/
interpolation.rs

1use crate::{
2    error::{AnalysisError, Result},
3    sounding::{DataRow, Sounding},
4};
5use itertools::{izip, Itertools};
6use metfor::{HectoPascal, Knots, Quantity, WindSpdDir, WindUV};
7use optional::Optioned;
8use std::ops::Sub;
9
10/// Interpolate values from the vertical sounding using pressure as the primary coordinate.
11///
12/// Returns a `DataRow` struct with interpolated values.
13pub fn linear_interpolate_sounding(snd: &Sounding, tgt_p: HectoPascal) -> Result<DataRow> {
14    let pressure: &[Optioned<HectoPascal>] = snd.pressure_profile();
15
16    // What kind of bracket is this?
17    enum BracketType {
18        Bracket(usize, usize),
19        EndEquals(usize),
20    }
21
22    // Map this pair of slice index and pressure points to a BracketType
23    let make_bracket = |pnt_0, pnt_1| -> Option<BracketType> {
24        let (i0, p0): (_, HectoPascal) = pnt_0;
25        let (i1, p1): (_, HectoPascal) = pnt_1;
26
27        // Always assume pressure is sorted in descending order
28        debug_assert!(p0 >= p1);
29        if p0 > tgt_p && p1 < tgt_p {
30            Some(BracketType::Bracket(i0, i1))
31        } else if (p0 - tgt_p).unpack().abs() < std::f64::EPSILON {
32            Some(BracketType::EndEquals(i0))
33        } else if (p1 - tgt_p).unpack().abs() < std::f64::EPSILON {
34            Some(BracketType::EndEquals(i1))
35        } else {
36            None
37        }
38    };
39
40    // Find the levels to interpolate between.
41    pressure
42        .iter()
43        .enumerate()
44        // Remove levels with missing pressure (SHOULD be none...but...) and then unwrap from the
45        // Optioned type
46        .filter_map(|(i, p_val_opt)| p_val_opt.map(|p_val| (i, p_val)))
47        // Look at the levels two at a time...
48        .tuple_windows::<(_, _)>()
49        // Map these pairs to brackets and remove anything that isn't a bracket. Should leave
50        // at most one bracket in the iterator!
51        .filter_map(|(pnt_0, pnt_1)| make_bracket(pnt_0, pnt_1))
52        // Get the first (and only) bracket
53        .next() // Option<BracketType>
54        // Perform the interpolation!
55        .and_then(|bracket| match bracket {
56            BracketType::Bracket(i0, i1) => {
57                let row0 = snd.data_row(i0)?;
58                let row1 = snd.data_row(i1)?;
59                linear_interp_data_rows(row0, row1, tgt_p)
60            }
61            BracketType::EndEquals(i) => snd.data_row(i),
62        })
63        // Map to error
64        .ok_or(AnalysisError::InterpolationError)
65}
66
67/// Interpolate values given two parallel vectors of data and a target value.
68///
69/// Assumes that xs is monotonic.
70#[inline]
71pub fn linear_interpolate<X, Y>(xs: &[Optioned<X>], ys: &[Optioned<Y>], target_x: X) -> Optioned<Y>
72where
73    X: Quantity + optional::Noned + PartialOrd + Sub<X>,
74    <X as Sub<X>>::Output: Quantity + optional::Noned,
75    Y: Quantity + optional::Noned + Sub<Y>,
76    <Y as Sub<Y>>::Output: Quantity,
77{
78    debug_assert_eq!(xs.len(), ys.len());
79
80    enum BracketType<X, Y> {
81        Bracket((X, Y), (X, Y)),
82        EndEqual((X, Y)),
83    }
84
85    let make_bracket = |pnt_0, pnt_1| -> Option<BracketType<X, Y>> {
86        let (x0, _) = pnt_0;
87        let (x1, _) = pnt_1;
88
89        if (x0 < target_x && x1 > target_x) || (x0 > target_x && x1 < target_x) {
90            Some(BracketType::Bracket(pnt_0, pnt_1))
91        } else if (x0 - target_x).unpack().abs() < std::f64::EPSILON {
92            Some(BracketType::EndEqual(pnt_0))
93        } else if (x1 - target_x).unpack().abs() < std::f64::EPSILON {
94            Some(BracketType::EndEqual(pnt_1))
95        } else {
96            None
97        }
98    };
99
100    let value_opt = izip!(xs, ys)
101        // Filter out elements where one of the values is missing, this allows us to skip over
102        // over a point with a missing value and use the points on either side of it for the
103        // interpolation.
104        .filter(|(x, y)| x.is_some() && y.is_some())
105        // Unpack the values from the `Optioned` type
106        .map(|(x, y)| (x.unpack(), y.unpack()))
107        // Look at them in pairs.
108        .tuple_windows::<(_, _)>()
109        // Make a bracket and filter out all levels the don't create a bracket.
110        .filter_map(|(pnt_0, pnt_1)| make_bracket(pnt_0, pnt_1))
111        // Get the first (and only) one that brackets the target value
112        .next() // This is an Option<BracketType>
113        // Map from the bracket type to the interpolated value
114        .map(|val| match val {
115            BracketType::Bracket(pnt_0, pnt_1) => {
116                let (x0, y0) = pnt_0;
117                let (x1, y1) = pnt_1;
118                linear_interp(target_x, x0, x1, y0, y1)
119            }
120            BracketType::EndEqual(pnt) => pnt.1,
121        });
122
123    Optioned::from(value_opt)
124}
125
126#[inline]
127pub(crate) fn linear_interp<X, Y>(x_val: X, x1: X, x2: X, y1: Y, y2: Y) -> Y
128where
129    X: Sub<X> + Copy + std::fmt::Debug + std::cmp::PartialEq,
130    <X as Sub<X>>::Output: Quantity,
131    Y: Quantity + Sub<Y>,
132    <Y as Sub<Y>>::Output: Quantity,
133{
134    debug_assert_ne!(x1, x2);
135
136    let run = (x2 - x1).unpack();
137    let rise = (y2 - y1).unpack();
138    let dx = (x_val - x1).unpack();
139
140    Y::pack(y1.unpack() + dx * (rise / run))
141}
142
143#[inline]
144fn linear_interp_data_rows(row0: DataRow, row1: DataRow, tgt_p: HectoPascal) -> Option<DataRow> {
145    let p0 = row0.pressure.into_option()?;
146    let p1 = row1.pressure.into_option()?;
147
148    let run = p1 - p0;
149    let dp = tgt_p - p0;
150
151    let pressure = Optioned::from(tgt_p);
152    let temperature = eval_linear_interp(row0.temperature, row1.temperature, run, dp);
153    let wet_bulb = eval_linear_interp(row0.wet_bulb, row1.wet_bulb, run, dp);
154    let dew_point = eval_linear_interp(row0.dew_point, row1.dew_point, run, dp);
155    let theta_e = eval_linear_interp(row0.theta_e, row1.theta_e, run, dp);
156
157    // Special interpolation for vectors
158    let wind = if let (Some(w_below), Some(w_above)) =
159        (row0.wind.into_option(), row1.wind.into_option())
160    {
161        let WindUV::<Knots> {
162            u: x_below,
163            v: y_below,
164        } = WindUV::from(w_below);
165        let WindUV::<Knots> {
166            u: x_above,
167            v: y_above,
168        } = WindUV::from(w_above);
169        let dp = dp.unpack();
170        let run = run.unpack();
171
172        let rise_x = x_above - x_below;
173        let rise_y = y_above - y_below;
174
175        let x = x_below + rise_x * (dp / run);
176        let y = y_below + rise_y * (dp / run);
177
178        let interped_wind = WindSpdDir::from(WindUV { u: x, v: y });
179
180        Into::<Optioned<WindSpdDir<Knots>>>::into(interped_wind)
181    } else {
182        optional::Optioned::none()
183    };
184
185    let pvv = eval_linear_interp(row0.pvv, row1.pvv, run, dp);
186    let height = eval_linear_interp(row0.height, row1.height, run, dp);
187    let cloud_fraction = eval_linear_interp(row0.cloud_fraction, row1.cloud_fraction, run, dp);
188
189    let result = DataRow {
190        pressure,
191        temperature,
192        wet_bulb,
193        dew_point,
194        theta_e,
195        wind,
196        pvv,
197        height,
198        cloud_fraction,
199    };
200
201    Some(result)
202}
203
204#[inline]
205fn eval_linear_interp<QX, Y>(
206    low_val: Optioned<Y>,
207    high_val: Optioned<Y>,
208    run: QX,
209    dp: QX,
210) -> Optioned<Y>
211where
212    QX: Quantity + optional::Noned,
213    Y: Quantity + optional::Noned,
214{
215    if low_val.is_some() && high_val.is_some() {
216        let (val_below, val_above) = (low_val.unpack().unpack(), high_val.unpack().unpack());
217        let rise: f64 = (val_above - val_below).unpack();
218        let run: f64 = run.unpack();
219        let dp: f64 = dp.unpack();
220        Optioned::from(Y::pack(val_below + dp * rise / run))
221    } else {
222        Optioned::default()
223    }
224}