use petir::mathf::RealMath;
use uom::si::f64::{Length, ThermalConductivity, ThermodynamicTemperature, VolumetricPowerDensity};
use uom::si::heat_transfer::watt_per_square_meter_kelvin;
use uom::si::length::meter;
use uom::si::thermal_conductivity::watt_per_meter_kelvin;
use uom::si::thermodynamic_temperature::kelvin;
use uom::si::volumetric_power_density::watt_per_cubic_meter;
use super::mesh::RadialGeometry;
use super::solve::ThermalBoundary;
use super::SteadyConductionError;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RadialLayer {
pub outer_radius: Length,
pub conductivity: ThermalConductivity,
pub heat_source: VolumetricPowerDensity,
}
#[derive(Debug, Clone)]
pub struct RadialClosedForm {
m: i32,
radii: Vec<f64>,
k: Vec<f64>,
q: Vec<f64>,
c: Vec<f64>,
t_outer: Vec<f64>,
}
impl RadialClosedForm {
pub fn new(
geometry: RadialGeometry,
layers: &[RadialLayer],
surface: ThermalBoundary,
) -> Result<Self, SteadyConductionError> {
if layers.is_empty() {
return Err(SteadyConductionError::InvalidInput(
"closed form needs at least one layer".into(),
));
}
let m = geometry.exponent();
let mf = f64::from(m);
let radii: Vec<f64> = layers
.iter()
.map(|l| l.outer_radius.get::<meter>())
.collect();
let k: Vec<f64> = layers
.iter()
.map(|l| l.conductivity.get::<watt_per_meter_kelvin>())
.collect();
let q: Vec<f64> = layers
.iter()
.map(|l| l.heat_source.get::<watt_per_cubic_meter>())
.collect();
let mut previous = 0.0;
for (j, r) in radii.iter().enumerate() {
if !(*r > previous) || !r.is_finite() {
return Err(SteadyConductionError::InvalidInput(format!(
"layer {j} outer radius {r} m must exceed the previous one ({previous} m)"
)));
}
if !(k[j] > 0.0) || !(q[j] >= 0.0) {
return Err(SteadyConductionError::InvalidInput(format!(
"layer {j} needs k > 0 and q''' >= 0 (got k = {}, q''' = {})",
k[j], q[j]
)));
}
previous = *r;
}
let mut c = Vec::with_capacity(layers.len());
let mut q_cum = 0.0;
let mut r_in = 0.0_f64;
for j in 0..layers.len() {
c.push(q_cum - q[j] * r_in.r_powi(m + 1) / (mf + 1.0));
q_cum += q[j] * (radii[j].r_powi(m + 1) - r_in.r_powi(m + 1)) / (mf + 1.0);
r_in = radii[j];
}
let r_surface = radii[radii.len() - 1];
let surface_flux = q_cum / r_surface.r_powi(m); let t_surface = match surface {
ThermalBoundary::FixedTemperature(t) => t.get::<kelvin>(),
ThermalBoundary::Convective {
coefficient,
ambient,
} => {
let h = coefficient.get::<watt_per_square_meter_kelvin>();
if !(h > 0.0) {
return Err(SteadyConductionError::InvalidInput(format!(
"convective surface needs h > 0, got {h} W/(m^2 K)"
)));
}
ambient.get::<kelvin>() + surface_flux / h
}
ThermalBoundary::Adiabatic => {
return Err(SteadyConductionError::InvalidInput(
"an adiabatic surface has no steady state with heat generated inside".into(),
))
}
};
let mut solution = Self {
m,
radii,
k,
q,
c,
t_outer: vec![0.0; layers.len()],
};
let n = layers.len();
solution.t_outer[n - 1] = t_surface;
for j in (0..n - 1).rev() {
solution.t_outer[j] =
solution.t_outer[j + 1] + solution.rise_in_layer(j + 1, solution.radii[j]);
}
Ok(solution)
}
fn rise_in_layer(&self, j: usize, r: f64) -> f64 {
let mf = f64::from(self.m);
let r_out = self.radii[j];
let mut rise = self.q[j] * (r_out * r_out - r * r) / (2.0 * (mf + 1.0));
if self.c[j] != 0.0 {
let g = match self.m {
0 => r_out - r,
1 => (r_out / r).r_ln(),
_ => 1.0 / r - 1.0 / r_out,
};
rise += self.c[j] * g;
}
rise / self.k[j]
}
pub fn temperature_at(
&self,
r: Length,
) -> Result<ThermodynamicTemperature, SteadyConductionError> {
let r = r.get::<meter>();
let r_surface = self.radii[self.radii.len() - 1];
if !(0.0..=r_surface).contains(&r) {
return Err(SteadyConductionError::InvalidInput(format!(
"radius {r} m is outside the body [0, {r_surface}] m"
)));
}
let j = self
.radii
.iter()
.position(|ro| r <= *ro)
.unwrap_or(self.radii.len() - 1);
Ok(ThermodynamicTemperature::new::<kelvin>(
self.t_outer[j] + self.rise_in_layer(j, r),
))
}
#[must_use]
pub fn surface_temperature(&self) -> ThermodynamicTemperature {
ThermodynamicTemperature::new::<kelvin>(self.t_outer[self.t_outer.len() - 1])
}
#[must_use]
pub fn centre_temperature(&self) -> ThermodynamicTemperature {
ThermodynamicTemperature::new::<kelvin>(self.t_outer[0] + self.rise_in_layer(0, 0.0))
}
}