#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CloudType {
Cumulus,
Stratus,
Cirrus,
Cumulonimbus,
Altocumulus,
Stratocumulus,
Nimbostratus,
}
pub struct CloudLayer {
pub cloud_type: CloudType,
pub base_altitude_m: f64,
pub thickness_m: f64,
pub coverage: f64,
pub density: f64,
pub wind_speed_m_s: f64,
pub wind_direction: [f64; 2],
pub droplet_radius_um: f64,
pub ice_fraction: f64,
}
impl CloudLayer {
pub fn cumulus() -> Self {
Self {
cloud_type: CloudType::Cumulus,
base_altitude_m: 2_000.0,
thickness_m: 1_500.0,
coverage: 0.4,
density: 0.3,
wind_speed_m_s: 8.0,
wind_direction: [1.0, 0.0],
droplet_radius_um: 10.0,
ice_fraction: 0.0,
}
}
pub fn stratus() -> Self {
Self {
cloud_type: CloudType::Stratus,
base_altitude_m: 500.0,
thickness_m: 500.0,
coverage: 0.7,
density: 0.5,
wind_speed_m_s: 5.0,
wind_direction: [0.7, 0.7],
droplet_radius_um: 8.0,
ice_fraction: 0.0,
}
}
pub fn cirrus() -> Self {
Self {
cloud_type: CloudType::Cirrus,
base_altitude_m: 8_000.0,
thickness_m: 1_000.0,
coverage: 0.3,
density: 0.05,
wind_speed_m_s: 30.0,
wind_direction: [1.0, 0.2],
droplet_radius_um: 25.0,
ice_fraction: 1.0,
}
}
pub fn cumulonimbus() -> Self {
Self {
cloud_type: CloudType::Cumulonimbus,
base_altitude_m: 1_000.0,
thickness_m: 12_000.0,
coverage: 0.15,
density: 0.8,
wind_speed_m_s: 15.0,
wind_direction: [0.5, 0.5],
droplet_radius_um: 14.0,
ice_fraction: 0.3,
}
}
pub fn stratocumulus() -> Self {
Self {
cloud_type: CloudType::Stratocumulus,
base_altitude_m: 600.0,
thickness_m: 800.0,
coverage: 0.6,
density: 0.4,
wind_speed_m_s: 6.0,
wind_direction: [0.8, 0.3],
droplet_radius_um: 9.0,
ice_fraction: 0.0,
}
}
pub fn nimbostratus() -> Self {
Self {
cloud_type: CloudType::Nimbostratus,
base_altitude_m: 1_500.0,
thickness_m: 3_000.0,
coverage: 0.85,
density: 0.6,
wind_speed_m_s: 10.0,
wind_direction: [0.6, 0.4],
droplet_radius_um: 12.0,
ice_fraction: 0.1,
}
}
}
pub struct CloudSystemEndpoint {
pub layers: Vec<CloudLayer>,
}
impl CloudSystemEndpoint {
pub fn earth_default() -> Self {
Self {
layers: vec![
CloudLayer::cumulus(),
CloudLayer::stratus(),
CloudLayer::cirrus(),
CloudLayer::stratocumulus(),
],
}
}
pub fn earth_stormy() -> Self {
Self {
layers: vec![
CloudLayer::cumulonimbus(),
CloudLayer::nimbostratus(),
CloudLayer::stratus(),
],
}
}
pub fn sample_density(&self, sample_alt: f64) -> f64 {
self.layers
.iter()
.map(|l| {
let alt_dist = (sample_alt - l.base_altitude_m).abs();
let sigma = l.thickness_m * 0.5;
let weight = (-0.5 * (alt_dist / sigma.max(1.0)).powi(2)).exp();
let mie_size_factor = l.droplet_radius_um / 10.0;
let phase_contrib = l.ice_fraction * 0.7 + (1.0 - l.ice_fraction) * 1.0;
l.density * l.coverage * weight * mie_size_factor * phase_contrib
})
.sum::<f64>()
/ self.layers.len().max(1) as f64
}
pub fn wind_transport(&self) -> f64 {
self.layers
.iter()
.map(|l| {
let wind_dir_len = l.wind_direction[0].hypot(l.wind_direction[1]);
l.wind_speed_m_s * wind_dir_len.max(0.01) * l.coverage
})
.sum::<f64>()
/ self.layers.len().max(1) as f64
}
pub fn optical_depth(&self) -> f64 {
self.layers
.iter()
.map(|l| {
let lwc = 0.3 * l.density;
let r_eff = l.droplet_radius_um * 1e-6;
let rho_w = 1000.0;
let tau = 1.5 * 2.0 * lwc * l.thickness_m * l.coverage / (rho_w * r_eff);
let ice_correction = 1.0 - 0.15 * l.ice_fraction;
tau * ice_correction
})
.sum()
}
pub fn total_thickness(&self) -> f64 {
self.layers.iter().map(|l| l.thickness_m * l.coverage).sum()
}
}