use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Layer {
pub name: String,
pub thickness: f64,
pub thermal_conductivity: f64,
pub heat_capacity: f64,
pub resistivity: f64,
pub is_magnetic: bool,
}
impl Layer {
pub fn yig(thickness: f64) -> Self {
Self {
name: "YIG".to_string(),
thickness,
thermal_conductivity: 6.0, heat_capacity: 3.0e6, resistivity: 1.0e10, is_magnetic: true,
}
}
pub fn pt(thickness: f64) -> Self {
Self {
name: "Pt".to_string(),
thickness,
thermal_conductivity: 72.0, heat_capacity: 2.8e6, resistivity: 2.0e-7, is_magnetic: false,
}
}
pub fn cu(thickness: f64) -> Self {
Self {
name: "Cu".to_string(),
thickness,
thermal_conductivity: 400.0, heat_capacity: 3.4e6, resistivity: 1.7e-8,
is_magnetic: false,
}
}
pub fn thermal_resistance(&self, area: f64) -> f64 {
self.thickness / (self.thermal_conductivity * area)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ThermalBoundary {
pub conductance: f64,
}
impl ThermalBoundary {
pub fn new(conductance: f64) -> Self {
Self { conductance }
}
pub fn metal_insulator() -> Self {
Self {
conductance: 1.0e8, }
}
pub fn metal_metal() -> Self {
Self { conductance: 5.0e8 }
}
pub fn thermal_resistance(&self, area: f64) -> f64 {
1.0 / (self.conductance * area)
}
pub fn heat_flux(&self, delta_t: f64) -> f64 {
self.conductance * delta_t
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MultilayerStack {
pub layers: Vec<Layer>,
pub boundaries: Vec<ThermalBoundary>,
pub area: f64,
}
impl MultilayerStack {
pub fn new(area: f64) -> Self {
Self {
layers: Vec::new(),
boundaries: Vec::new(),
area,
}
}
pub fn add_layer(&mut self, layer: Layer, boundary: Option<ThermalBoundary>) {
if !self.layers.is_empty() {
let boundary = boundary.unwrap_or_else(|| {
let prev_layer = match self.layers.last() {
Some(layer) => layer,
None => return ThermalBoundary::metal_metal(), };
if prev_layer.is_magnetic != layer.is_magnetic {
ThermalBoundary::metal_insulator()
} else {
ThermalBoundary::metal_metal()
}
});
self.boundaries.push(boundary);
}
self.layers.push(layer);
}
pub fn total_thermal_resistance(&self) -> f64 {
let mut r_total = 0.0;
for layer in &self.layers {
r_total += layer.thermal_resistance(self.area);
}
for boundary in &self.boundaries {
r_total += boundary.thermal_resistance(self.area);
}
r_total
}
pub fn effective_thermal_conductivity(&self) -> f64 {
let total_thickness: f64 = self.layers.iter().map(|l| l.thickness).sum();
let r_total = self.total_thermal_resistance();
if r_total > 0.0 {
total_thickness / (r_total * self.area)
} else {
0.0
}
}
pub fn temperature_profile(&self, t_bottom: f64, t_top: f64) -> Vec<f64> {
let mut temperatures = vec![t_bottom];
let r_total = self.total_thermal_resistance();
let delta_t = t_top - t_bottom;
let mut current_t = t_bottom;
for i in 0..self.layers.len() {
let r_layer = self.layers[i].thermal_resistance(self.area);
let dt_layer = delta_t * (r_layer / r_total);
current_t += dt_layer;
temperatures.push(current_t);
if i < self.boundaries.len() {
let r_interface = self.boundaries[i].thermal_resistance(self.area);
let dt_interface = delta_t * (r_interface / r_total);
current_t += dt_interface;
temperatures.push(current_t);
}
}
temperatures
}
pub fn total_thickness(&self) -> f64 {
self.layers.iter().map(|l| l.thickness).sum()
}
}
impl fmt::Display for Layer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}[{:.1} nm]: κ={:.1} W/(m·K){}",
self.name,
self.thickness * 1e9,
self.thermal_conductivity,
if self.is_magnetic { " (mag)" } else { "" }
)
}
}
impl fmt::Display for ThermalBoundary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ThermalBoundary(G={:.2e} W/(m²·K))", self.conductance)
}
}
impl fmt::Display for MultilayerStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MultilayerStack: {} layers, t_total={:.1} nm, A={:.2e} m²",
self.layers.len(),
self.total_thickness() * 1e9,
self.area
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_layer_creation() {
let yig = Layer::yig(100.0e-9);
assert_eq!(yig.name, "YIG");
assert!(yig.is_magnetic);
let pt = Layer::pt(5.0e-9);
assert_eq!(pt.name, "Pt");
assert!(!pt.is_magnetic);
}
#[test]
fn test_multilayer_stack() {
let mut stack = MultilayerStack::new(1.0e-12);
stack.add_layer(Layer::yig(100.0e-9), None);
stack.add_layer(Layer::pt(5.0e-9), None);
assert_eq!(stack.layers.len(), 2);
assert_eq!(stack.boundaries.len(), 1);
assert!((stack.total_thickness() - 105.0e-9).abs() < 1e-15);
}
#[test]
fn test_thermal_resistance() {
let mut stack = MultilayerStack::new(1.0e-12);
stack.add_layer(Layer::pt(10.0e-9), None);
let r = stack.total_thermal_resistance();
assert!(r > 0.0);
}
#[test]
fn test_temperature_profile() {
let mut stack = MultilayerStack::new(1.0e-12);
stack.add_layer(Layer::yig(100.0e-9), None);
stack.add_layer(Layer::pt(10.0e-9), None);
let profile = stack.temperature_profile(300.0, 310.0);
assert!((profile[0] - 300.0).abs() < 1e-10);
for i in 1..profile.len() {
assert!(profile[i] >= profile[i - 1]);
}
assert!((profile.last().expect("profile should not be empty") - 310.0).abs() < 1e-6);
}
}