use optional::Optioned;
use metfor;
use sounding_base::{DataRow, Profile::*, Sounding};
use error::*;
use interpolation::{linear_interp, linear_interpolate_sounding};
use profile::equivalent_potential_temperature;
#[derive(Debug, Clone, Copy)]
pub struct Parcel {
pub temperature: f64,
pub pressure: f64,
pub dew_point: f64,
}
impl Parcel {
pub fn theta(&self) -> Result<f64> {
Ok(metfor::theta_kelvin(self.pressure, self.temperature)?)
}
pub fn theta_e(&self) -> Result<f64> {
Ok(metfor::theta_e_kelvin(
self.temperature,
self.dew_point,
self.pressure,
)?)
}
pub fn mixing_ratio(&self) -> Result<f64> {
Ok(metfor::mixing_ratio(self.dew_point, self.pressure)?)
}
pub fn virtual_temperature_c(&self) -> Result<f64> {
Ok(metfor::virtual_temperature_c(
self.temperature,
self.dew_point,
self.pressure,
)?)
}
pub fn from_datarow(mut dr: DataRow) -> Option<Self> {
let temperature = dr.temperature.take()?;
let pressure = dr.pressure.take()?;
let dew_point = dr.dew_point.take()?;
Some(Parcel {
temperature,
pressure,
dew_point,
})
}
}
pub fn mixed_layer_parcel(snd: &Sounding) -> Result<Parcel> {
use sounding_base::Profile::*;
let press = snd.get_profile(Pressure);
let t = snd.get_profile(Temperature);
let dp = snd.get_profile(DewPoint);
if press.is_empty() || t.is_empty() || dp.is_empty() {
return Err(AnalysisError::MissingProfile);
}
let bottom_p = press
.iter()
.filter_map(|&p| p.clone().take())
.nth(0)
.ok_or(AnalysisError::NoDataProfile)?;
let (sum_p, sum_t, sum_mw) = izip!(press, t, dp)
.filter_map(|(p, t, dp)| {
if p.is_some() && t.is_some() && dp.is_some() {
Some((p.unpack(), t.unpack(), dp.unpack()))
} else {
None
}
})
.take_while(|&(p, _, _)| p >= bottom_p - 100.0)
.filter_map(|(p, t, dp)| {
metfor::theta_kelvin(p, t).ok().and_then(|theta| {
metfor::mixing_ratio(dp, p)
.ok()
.and_then(|mw| Some((p, theta, mw)))
})
})
.fold((0.0f64, 0.0f64, 0.0f64), |acc, (p, theta, mw)| {
let (sum_p, sum_t, sum_mw) = acc;
(sum_p + p, sum_t + theta * p, sum_mw + mw * p)
});
if sum_p == 0.0 {
return Err(AnalysisError::NotEnoughData);
}
let pressure = bottom_p;
let temperature = metfor::temperature_c_from_theta(sum_t / sum_p, bottom_p)?;
let dew_point = metfor::dew_point_from_p_and_mw(bottom_p, sum_mw / sum_p)
.map_err(|_| AnalysisError::InvalidInput)?;
Ok(Parcel {
temperature,
pressure,
dew_point,
})
}
pub fn surface_parcel(snd: &Sounding) -> Result<Parcel> {
use sounding_base::Surface::*;
let pressure = snd
.get_surface_value(StationPressure)
.ok_or(AnalysisError::MissingValue)?;
let temperature = snd
.get_surface_value(Temperature)
.ok_or(AnalysisError::MissingValue)?;
let dew_point = snd
.get_surface_value(DewPoint)
.ok_or(AnalysisError::MissingValue)?;
Ok(Parcel {
temperature,
pressure,
dew_point,
})
}
pub fn pressure_parcel(snd: &Sounding, pressure: f64) -> Result<Parcel> {
let row = linear_interpolate_sounding(snd, pressure)?;
Parcel::from_datarow(row).ok_or(AnalysisError::MissingValue)
}
pub fn most_unstable_parcel(snd: &Sounding) -> Result<Parcel> {
use sounding_base::Profile::*;
let temp_vec: Vec<Optioned<f64>>;
let theta_e = if !snd.get_profile(ThetaE).is_empty() {
snd.get_profile(ThetaE)
} else {
temp_vec = equivalent_potential_temperature(snd);
&temp_vec
};
let press = snd.get_profile(Pressure);
let top_pressure = press
.iter()
.filter(|p| p.is_some())
.nth(0)
.ok_or(AnalysisError::NotEnoughData)?
.unpack()
- 300.0;
let (idx, _) = izip!(0.., press, theta_e)
.take_while(|&(_, press_opt, _)| {
if press_opt.is_some() {
if press_opt.unpack() >= top_pressure {
true
} else {
false }
} else {
true }
}).filter_map(|(idx, _, theta_e_opt)| {
if theta_e_opt.is_some() {
Some((idx, theta_e_opt.unpack()))
} else {
None
}
}).fold(
(::std::usize::MAX, ::std::f64::MIN),
|(max_idx, max_val), (i, theta_e)| {
if theta_e > max_val {
(i, theta_e)
} else {
(max_idx, max_val)
}
},
);
let row = snd.get_data_row(idx).ok_or(AnalysisError::NoDataProfile)?;
Parcel::from_datarow(row).ok_or(AnalysisError::MissingValue)
}
pub fn convective_parcel(snd: &Sounding) -> Result<Parcel> {
let Parcel {
dew_point: dp,
pressure: initial_p,
..
} = mixed_layer_parcel(snd)?;
let tgt_mw = metfor::mixing_ratio(dp, initial_p)?;
let pressure = snd.get_profile(Pressure);
let temperature = snd.get_profile(Temperature);
let (tgt_p, tgt_t) = izip!(pressure, temperature)
.filter_map(|(p, t)| {
let p = p.into_option()?;
let t = t.into_option()?;
let mw = metfor::mixing_ratio(t, p).ok()?;
Some((p, t, mw))
}).skip_while(|(_, _, mw)| *mw <= tgt_mw)
.scan((0.0, 0.0, 0.0), |(old_p, old_t, old_mw), (p, t, mw)| {
let result = if mw < tgt_mw && *old_mw >= tgt_mw {
let tgt_p = linear_interp(tgt_mw, *old_mw, mw, *old_p, p);
let tgt_t = linear_interp(tgt_mw, *old_mw, mw, *old_t, t);
Some(Some((tgt_p, tgt_t)))
} else {
Some(None)
};
*old_p = p;
*old_t = t;
*old_mw = mw;
result
}).filter_map(|opt_opt| opt_opt)
.nth(0)
.ok_or(AnalysisError::NotEnoughData)?;
let tgt_theta = metfor::theta_kelvin(tgt_p, tgt_t)?;
let tgt_t = metfor::temperature_c_from_theta(tgt_theta, initial_p)?;
Ok(Parcel {
pressure: initial_p,
temperature: tgt_t,
dew_point: dp,
})
}