use super::train_imports::*;
#[serde_api]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[cfg_attr(feature = "pyo3", pyclass(module = "altrios", subclass, eq))]
pub struct TemperatureTraceBuilder {
pub time: Vec<si::Time>,
pub temp_at_sea_level: Vec<si::ThermodynamicTemperature>,
}
#[pyo3_api]
impl TemperatureTraceBuilder {
#[staticmethod]
#[pyo3(name = "from_csv_file")]
fn from_csv_file_py(pathstr: String) -> anyhow::Result<Self> {
Self::from_csv_file(&pathstr)
}
fn __len__(&self) -> usize {
self.len()
}
}
impl Init for TemperatureTraceBuilder {}
impl SerdeAPI for TemperatureTraceBuilder {}
impl TemperatureTraceBuilder {
fn empty() -> Self {
Self {
time: Vec::new(),
temp_at_sea_level: Vec::new(),
}
}
fn len(&self) -> usize {
self.time.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn push(&mut self, tt_element: TemperatureTraceElement) {
self.time.push(tt_element.time);
self.temp_at_sea_level.push(tt_element.temp_at_sea_level);
}
pub fn trim(&mut self, start_idx: Option<usize>, end_idx: Option<usize>) -> anyhow::Result<()> {
let start_idx = start_idx.unwrap_or(0);
let end_idx = end_idx.unwrap_or_else(|| self.len());
ensure!(end_idx <= self.len(), format_dbg!(end_idx <= self.len()));
self.time = self.time[start_idx..end_idx].to_vec();
Ok(())
}
pub fn from_csv_file(pathstr: &str) -> Result<Self, anyhow::Error> {
let pathbuf = PathBuf::from(&pathstr);
let mut tt = Self::empty();
let file = File::open(pathbuf)?;
let mut rdr = csv::ReaderBuilder::new()
.has_headers(true)
.from_reader(file);
for result in rdr.deserialize() {
let tt_elem: TemperatureTraceElement = result?;
tt.push(tt_elem);
}
if tt.is_empty() {
bail!("Invalid TemperatureTrace file; TemperatureTrace is empty")
} else {
Ok(tt)
}
}
}
impl Default for TemperatureTraceBuilder {
fn default() -> Self {
let time_s: Vec<f64> = (0..1).map(|x| x as f64).collect();
let mut tt = Self {
time: time_s.iter().map(|t| *t * uc::S).collect(),
temp_at_sea_level: vec![(22.0 + 273.15) * uc::KELVIN],
};
tt.init().unwrap();
tt
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct TemperatureTraceElement {
time: si::Time,
pub temp_at_sea_level: si::ThermodynamicTemperature,
}
#[serde_api]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "pyo3", pyclass(module = "altrios", subclass, eq))]
pub struct TemperatureTrace(pub(crate) Interp1DOwned<f64, strategy::Linear>);
#[pyo3_api]
impl TemperatureTrace {}
impl Init for TemperatureTrace {}
impl SerdeAPI for TemperatureTrace {}
impl TemperatureTrace {
pub fn get_temp_at_time_and_elev(
&self,
time: si::Time,
elev: si::Length,
) -> anyhow::Result<si::ThermodynamicTemperature> {
Ok(self.get_temp_at_elev(self.get_temp_at_time_and_sea_level(time)?, elev))
}
fn get_temp_at_time_and_sea_level(
&self,
time: si::Time,
) -> anyhow::Result<si::ThermodynamicTemperature> {
Ok(self
.0
.interpolate(&[time.get::<si::second>()])
.map(|te| (te + uc::CELSIUS_TO_KELVIN) * uc::KELVIN)?)
}
fn get_temp_at_elev(
&self,
temp_at_sea_level: si::ThermodynamicTemperature,
elev: si::Length,
) -> si::ThermodynamicTemperature {
(((15.04 - 0.00649 * elev.get::<si::meter>())
+ (temp_at_sea_level.get::<si::degree_celsius>() - 15.04))
+ uc::CELSIUS_TO_KELVIN)
* uc::KELVIN
}
}
impl Serialize for TemperatureTrace {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let builder: TemperatureTraceBuilder = TemperatureTraceBuilder::try_from(self.clone())
.map_err(|e| serde::ser::Error::custom(format!("{:?}", e)))?;
builder.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for TemperatureTrace {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value: TemperatureTraceBuilder = TemperatureTraceBuilder::deserialize(deserializer)?;
let tt: Self =
Self::try_from(value).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
Ok(tt)
}
}
impl TryFrom<TemperatureTraceBuilder> for TemperatureTrace {
type Error = anyhow::Error;
fn try_from(value: TemperatureTraceBuilder) -> anyhow::Result<Self> {
Ok(Self(Interp1D::new(
value.time.iter().map(|t| t.get::<si::second>()).collect(),
value
.temp_at_sea_level
.iter()
.map(|te| te.get::<si::degree_celsius>())
.collect(),
strategy::Linear,
Extrapolate::Clamp,
)?))
}
}
impl TryFrom<TemperatureTrace> for TemperatureTraceBuilder {
type Error = anyhow::Error;
fn try_from(value: TemperatureTrace) -> anyhow::Result<Self> {
Ok(Self {
time: value.0.data.grid[0].iter().map(|x| *x * uc::S).collect(),
temp_at_sea_level: value
.0
.data
.values
.iter()
.map(|y| (*y + uc::CELSIUS_TO_KELVIN) * uc::KELVIN)
.collect(),
})
}
}
impl Default for TemperatureTrace {
fn default() -> Self {
Self::try_from(TemperatureTraceBuilder::default()).unwrap()
}
}
impl TemperatureTrace {}