altrios_core/train/
environment.rs1use super::train_imports::*;
2
3#[serde_api]
4#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
5#[cfg_attr(feature = "pyo3", pyclass(module = "altrios", subclass, eq))]
6pub struct TemperatureTraceBuilder {
8 pub time: Vec<si::Time>,
10 pub temp_at_sea_level: Vec<si::ThermodynamicTemperature>,
12}
13
14#[pyo3_api]
15impl TemperatureTraceBuilder {
16 #[staticmethod]
17 #[pyo3(name = "from_csv_file")]
18 fn from_csv_file_py(pathstr: String) -> anyhow::Result<Self> {
19 Self::from_csv_file(&pathstr)
20 }
21
22 fn __len__(&self) -> usize {
23 self.len()
24 }
25}
26
27impl Init for TemperatureTraceBuilder {}
28impl SerdeAPI for TemperatureTraceBuilder {}
29
30impl TemperatureTraceBuilder {
31 fn empty() -> Self {
32 Self {
33 time: Vec::new(),
34 temp_at_sea_level: Vec::new(),
35 }
36 }
37
38 fn len(&self) -> usize {
39 self.time.len()
40 }
41
42 pub fn is_empty(&self) -> bool {
43 self.len() == 0
44 }
45
46 pub fn push(&mut self, tt_element: TemperatureTraceElement) {
47 self.time.push(tt_element.time);
48 self.temp_at_sea_level.push(tt_element.temp_at_sea_level);
49 }
50
51 pub fn trim(&mut self, start_idx: Option<usize>, end_idx: Option<usize>) -> anyhow::Result<()> {
52 let start_idx = start_idx.unwrap_or(0);
53 let end_idx = end_idx.unwrap_or_else(|| self.len());
54 ensure!(end_idx <= self.len(), format_dbg!(end_idx <= self.len()));
55
56 self.time = self.time[start_idx..end_idx].to_vec();
57 Ok(())
58 }
59
60 pub fn from_csv_file(pathstr: &str) -> Result<Self, anyhow::Error> {
62 let pathbuf = PathBuf::from(&pathstr);
63
64 let mut tt = Self::empty();
66
67 let file = File::open(pathbuf)?;
68 let mut rdr = csv::ReaderBuilder::new()
69 .has_headers(true)
70 .from_reader(file);
71 for result in rdr.deserialize() {
72 let tt_elem: TemperatureTraceElement = result?;
73 tt.push(tt_elem);
74 }
75 if tt.is_empty() {
76 bail!("Invalid TemperatureTrace file; TemperatureTrace is empty")
77 } else {
78 Ok(tt)
79 }
80 }
81}
82
83impl Default for TemperatureTraceBuilder {
84 fn default() -> Self {
85 let time_s: Vec<f64> = (0..1).map(|x| x as f64).collect();
86 let mut tt = Self {
87 time: time_s.iter().map(|t| *t * uc::S).collect(),
88 temp_at_sea_level: vec![(22.0 + 273.15) * uc::KELVIN],
89 };
90 tt.init().unwrap();
91 tt
92 }
93}
94
95#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
97pub struct TemperatureTraceElement {
98 time: si::Time,
100 pub temp_at_sea_level: si::ThermodynamicTemperature,
102}
103
104#[serde_api]
105#[derive(Clone, Debug, PartialEq)]
106#[cfg_attr(feature = "pyo3", pyclass(module = "altrios", subclass, eq))]
107pub struct TemperatureTrace(pub(crate) Interp1DOwned<f64, strategy::Linear>);
109
110#[pyo3_api]
111impl TemperatureTrace {}
112
113impl Init for TemperatureTrace {}
114impl SerdeAPI for TemperatureTrace {}
115
116impl TemperatureTrace {
117 pub fn get_temp_at_time_and_elev(
118 &self,
119 time: si::Time,
120 elev: si::Length,
121 ) -> anyhow::Result<si::ThermodynamicTemperature> {
122 Ok(self.get_temp_at_elev(self.get_temp_at_time_and_sea_level(time)?, elev))
123 }
124
125 fn get_temp_at_time_and_sea_level(
126 &self,
127 time: si::Time,
128 ) -> anyhow::Result<si::ThermodynamicTemperature> {
129 Ok(self
130 .0
131 .interpolate(&[time.get::<si::second>()])
132 .map(|te| (te + uc::CELSIUS_TO_KELVIN) * uc::KELVIN)?)
133 }
134
135 fn get_temp_at_elev(
140 &self,
141 temp_at_sea_level: si::ThermodynamicTemperature,
142 elev: si::Length,
143 ) -> si::ThermodynamicTemperature {
144 (((15.04 - 0.00649 * elev.get::<si::meter>())
145 + (temp_at_sea_level.get::<si::degree_celsius>() - 15.04))
146 + uc::CELSIUS_TO_KELVIN)
147 * uc::KELVIN
148 }
149}
150
151impl Serialize for TemperatureTrace {
152 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
153 where
154 S: serde::Serializer,
155 {
156 let builder: TemperatureTraceBuilder = TemperatureTraceBuilder::try_from(self.clone())
157 .map_err(|e| serde::ser::Error::custom(format!("{:?}", e)))?;
158 builder.serialize(serializer)
159 }
160}
161
162impl<'de> Deserialize<'de> for TemperatureTrace {
163 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
164 where
165 D: serde::Deserializer<'de>,
166 {
167 let value: TemperatureTraceBuilder = TemperatureTraceBuilder::deserialize(deserializer)?;
168 let tt: Self =
169 Self::try_from(value).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
170 Ok(tt)
171 }
172}
173
174impl TryFrom<TemperatureTraceBuilder> for TemperatureTrace {
175 type Error = anyhow::Error;
176 fn try_from(value: TemperatureTraceBuilder) -> anyhow::Result<Self> {
177 Ok(Self(Interp1D::new(
178 value.time.iter().map(|t| t.get::<si::second>()).collect(),
179 value
180 .temp_at_sea_level
181 .iter()
182 .map(|te| te.get::<si::degree_celsius>())
183 .collect(),
184 strategy::Linear,
185 Extrapolate::Clamp,
186 )?))
187 }
188}
189
190impl TryFrom<TemperatureTrace> for TemperatureTraceBuilder {
191 type Error = anyhow::Error;
192 fn try_from(value: TemperatureTrace) -> anyhow::Result<Self> {
193 Ok(Self {
194 time: value.0.data.grid[0].iter().map(|x| *x * uc::S).collect(),
195 temp_at_sea_level: value
196 .0
197 .data
198 .values
199 .iter()
200 .map(|y| (*y + uc::CELSIUS_TO_KELVIN) * uc::KELVIN)
201 .collect(),
202 })
203 }
204}
205
206impl Default for TemperatureTrace {
207 fn default() -> Self {
208 Self::try_from(TemperatureTraceBuilder::default()).unwrap()
209 }
210}
211
212impl TemperatureTrace {}