suns 0.0.3

Sun celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use sciforge::hub::domain::common::constants::K_B;

use crate::{
    CHROMOSPHERE_TEMP_MAX, CHROMOSPHERE_TEMP_MIN, CHROMOSPHERE_THICKNESS, PROTON_MASS,
    SOLAR_SURFACE_GRAVITY,
};

pub const CHROMOSPHERE_BASE_DENSITY: f64 = 1e-6;
pub const CHROMOSPHERE_TOP_DENSITY: f64 = 1e-12;
pub const CHROMOSPHERE_BASE_PRESSURE: f64 = 1.0;
pub const HALPHA_WAVELENGTH_M: f64 = 6.563e-7;
pub const CA_II_K_WAVELENGTH_M: f64 = 3.934e-7;
pub const SPICULE_HEIGHT_M: f64 = 1e7;
pub const SPICULE_SPEED_MS: f64 = 2.5e4;
pub const SPICULE_LIFETIME_S: f64 = 300.0;
pub const SPICULE_WIDTH_M: f64 = 5e5;

pub struct Chromosphere {
    pub base_temperature_k: f64,
    pub top_temperature_k: f64,
    pub base_density_kgm3: f64,
    pub top_density_kgm3: f64,
    pub thickness_m: f64,
}

impl Default for Chromosphere {
    fn default() -> Self {
        Self::new()
    }
}

impl Chromosphere {
    pub fn new() -> Self {
        Self {
            base_temperature_k: CHROMOSPHERE_TEMP_MIN,
            top_temperature_k: CHROMOSPHERE_TEMP_MAX,
            base_density_kgm3: CHROMOSPHERE_BASE_DENSITY,
            top_density_kgm3: CHROMOSPHERE_TOP_DENSITY,
            thickness_m: CHROMOSPHERE_THICKNESS,
        }
    }

    pub fn temperature_at_height(&self, height_fraction: f64) -> f64 {
        let f = height_fraction.clamp(0.0, 1.0);
        if f < 0.8 {
            self.base_temperature_k + (6500.0 - self.base_temperature_k) * (f / 0.8)
        } else {
            6500.0 + (self.top_temperature_k - 6500.0) * ((f - 0.8) / 0.2)
        }
    }

    pub fn density_at_height(&self, height_m: f64) -> f64 {
        let scale_height = K_B * 7000.0 / (0.6 * PROTON_MASS * SOLAR_SURFACE_GRAVITY);
        self.base_density_kgm3 * (-height_m / scale_height).exp()
    }

    pub fn pressure_at_height(&self, height_m: f64) -> f64 {
        let rho = self.density_at_height(height_m);
        let t = self.temperature_at_height(height_m / self.thickness_m);
        rho * K_B * t / (0.6 * PROTON_MASS)
    }

    pub fn scale_height(&self, temperature_k: f64) -> f64 {
        K_B * temperature_k / (0.6 * PROTON_MASS * SOLAR_SURFACE_GRAVITY)
    }

    pub fn sound_speed(&self, temperature_k: f64) -> f64 {
        let gamma = 5.0 / 3.0;
        (gamma * K_B * temperature_k / (0.6 * PROTON_MASS)).sqrt()
    }

    pub fn halpha_optical_depth(&self, column_density: f64) -> f64 {
        let sigma_ha = 1e-18;
        column_density * sigma_ha
    }

    pub fn radiative_loss_rate(&self, height_m: f64) -> f64 {
        let n_e = self.density_at_height(height_m) / PROTON_MASS;
        let t = self.temperature_at_height(height_m / self.thickness_m);
        let lambda = 1e-22 * (t / 1e4).powf(-0.5);
        n_e * n_e * lambda
    }

    pub fn acoustic_heating_flux(&self) -> f64 {
        4.5e3
    }

    pub fn mass_column_density(&self) -> f64 {
        self.base_density_kgm3 * self.scale_height(7000.0)
    }
}

pub struct Spicule {
    pub height_m: f64,
    pub velocity_ms: f64,
    pub width_m: f64,
    pub temperature_k: f64,
    pub density_kgm3: f64,
}

impl Default for Spicule {
    fn default() -> Self {
        Self::type_i()
    }
}

impl Spicule {
    pub fn type_i() -> Self {
        Self {
            height_m: SPICULE_HEIGHT_M,
            velocity_ms: SPICULE_SPEED_MS,
            width_m: SPICULE_WIDTH_M,
            temperature_k: 1.5e4,
            density_kgm3: 1e-10,
        }
    }

    pub fn type_ii() -> Self {
        Self {
            height_m: 7e6,
            velocity_ms: 1e5,
            width_m: 3e5,
            temperature_k: 8e4,
            density_kgm3: 5e-11,
        }
    }

    pub fn kinetic_energy(&self) -> f64 {
        let volume = std::f64::consts::PI * (self.width_m / 2.0).powi(2) * self.height_m;
        0.5 * self.density_kgm3 * volume * self.velocity_ms.powi(2)
    }

    pub fn lifetime(&self) -> f64 {
        self.height_m / self.velocity_ms
    }

    pub fn mass_flux(&self) -> f64 {
        self.density_kgm3 * self.velocity_ms
    }
}

pub fn number_of_spicules() -> f64 {
    3e5
}

pub fn total_spicule_mass_flux() -> f64 {
    let s = Spicule::type_i();
    let area = std::f64::consts::PI * (s.width_m / 2.0).powi(2);
    s.mass_flux() * area * number_of_spicules()
}

pub fn transition_region_temperature(height_above_chromo: f64) -> f64 {
    let t_chromo = CHROMOSPHERE_TEMP_MAX;
    let t_corona = 1e6;
    let thickness = crate::TRANSITION_REGION_THICKNESS;
    let f = (height_above_chromo / thickness).clamp(0.0, 1.0);
    t_chromo * ((t_corona / t_chromo).ln() * f).exp()
}