twine_models/support/thermo/
state.rs1use std::ops::Div;
2
3use twine_core::StepIntegrable;
4use uom::si::f64::{MassDensity, TemperatureInterval, ThermodynamicTemperature, Time};
5
6#[derive(Debug, Clone, Copy, PartialEq)]
36pub struct State<Fluid> {
37 pub temperature: ThermodynamicTemperature,
38 pub density: MassDensity,
39 pub fluid: Fluid,
40}
41
42impl<Fluid> State<Fluid> {
43 #[must_use]
45 pub fn new(temperature: ThermodynamicTemperature, density: MassDensity, fluid: Fluid) -> Self {
46 Self {
47 temperature,
48 density,
49 fluid,
50 }
51 }
52
53 #[must_use]
55 pub fn with_temperature(self, temperature: ThermodynamicTemperature) -> Self {
56 Self {
57 temperature,
58 ..self
59 }
60 }
61
62 #[must_use]
64 pub fn with_density(self, density: MassDensity) -> Self {
65 Self { density, ..self }
66 }
67
68 #[must_use]
70 pub fn with_fluid(self, fluid: Fluid) -> Self {
71 Self { fluid, ..self }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq)]
108pub struct StateDerivative<FluidDerivative> {
109 pub temperature: <TemperatureInterval as Div<Time>>::Output,
111
112 pub density: <MassDensity as Div<Time>>::Output,
114
115 pub fluid: FluidDerivative,
117}
118
119impl<Fluid> StepIntegrable<Time> for State<Fluid>
120where
121 Fluid: StepIntegrable<Time>,
122{
123 type Derivative = StateDerivative<Fluid::Derivative>;
124
125 fn step(&self, derivative: Self::Derivative, delta: Time) -> Self {
126 Self {
127 temperature: self.temperature + derivative.temperature * delta,
128 density: self.density + derivative.density * delta,
129 fluid: self.fluid.step(derivative.fluid, delta),
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 use twine_core::StepIntegrable;
139 use uom::si::{
140 f64::{MassDensity, TemperatureInterval, ThermodynamicTemperature, Time},
141 mass_density::kilogram_per_cubic_meter,
142 temperature_interval::kelvin as interval_kelvin,
143 thermodynamic_temperature::kelvin,
144 time::second,
145 };
146
147 use crate::support::thermo::fluid::Air;
148
149 #[test]
150 fn step_advances_temperature_and_density() {
151 let state = State {
152 temperature: ThermodynamicTemperature::new::<kelvin>(300.0),
153 density: MassDensity::new::<kilogram_per_cubic_meter>(1.2),
154 fluid: Air,
155 };
156
157 let dt = Time::new::<second>(2.0);
158 let deriv = StateDerivative {
159 temperature: TemperatureInterval::new::<interval_kelvin>(3.0) / dt,
160 density: MassDensity::new::<kilogram_per_cubic_meter>(0.4) / dt,
161 fluid: (),
162 };
163
164 let next = state.step(deriv, dt);
165
166 approx::assert_relative_eq!(
167 next.temperature.get::<kelvin>(),
168 303.0,
169 max_relative = 1e-10,
170 );
171 approx::assert_relative_eq!(
172 next.density.get::<kilogram_per_cubic_meter>(),
173 1.6,
174 max_relative = 1e-10,
175 );
176 }
177
178 #[test]
179 fn step_zero_derivative_is_identity() {
180 let state = State {
181 temperature: ThermodynamicTemperature::new::<kelvin>(290.0),
182 density: MassDensity::new::<kilogram_per_cubic_meter>(1.0),
183 fluid: Air,
184 };
185
186 let dt = Time::new::<second>(1.0);
187 let deriv = StateDerivative {
188 temperature: TemperatureInterval::new::<interval_kelvin>(0.0) / dt,
189 density: MassDensity::new::<kilogram_per_cubic_meter>(0.0) / dt,
190 fluid: (),
191 };
192
193 let next = state.step(deriv, dt);
194
195 assert_eq!(next, state);
196 }
197
198 #[test]
199 fn zst_fluid_markers_step_to_themselves() {
200 use crate::support::thermo::fluid::{CarbonDioxide, Water};
201
202 let dt = Time::new::<second>(1.0);
203
204 assert_eq!(Air.step((), dt), Air);
205 assert_eq!(Water.step((), dt), Water);
206 assert_eq!(CarbonDioxide.step((), dt), CarbonDioxide);
207 }
208}