suns 0.0.3

Sun celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use sciforge::hub::domain::common::constants::{G, K_B, SOLAR_MASS};

use crate::{
    CONVECTIVE_ZONE_BASE_FRAC, PROTON_MASS, SECONDS_PER_DAY, SOLAR_EFFECTIVE_TEMP,
    SOLAR_OMEGA_EQUATORIAL, SOLAR_RADIUS,
};

pub const CONVECTIVE_ZONE_BASE_TEMP: f64 = 2.0e6;
pub const CONVECTIVE_ZONE_TOP_TEMP: f64 = 5778.0;
pub const CONVECTIVE_ZONE_BASE_DENSITY: f64 = 200.0;
pub const CONVECTIVE_ZONE_TOP_DENSITY: f64 = 2e-4;
pub const MIXING_LENGTH_ALPHA: f64 = 1.7;
pub const CONVECTIVE_VELOCITY_DEEP: f64 = 100.0;
pub const CONVECTIVE_VELOCITY_SURFACE: f64 = 2000.0;
pub const CONVECTIVE_TURNOVER_TIME_S: f64 = 2.59e6;
pub const ROSSBY_NUMBER_SOLAR: f64 = 1.96;

pub struct ConvectiveZone {
    pub inner_radius_frac: f64,
    pub outer_radius_frac: f64,
    pub base_temperature_k: f64,
    pub top_temperature_k: f64,
    pub base_density: f64,
    pub top_density: f64,
}

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

impl ConvectiveZone {
    pub fn new() -> Self {
        Self {
            inner_radius_frac: CONVECTIVE_ZONE_BASE_FRAC,
            outer_radius_frac: 1.0,
            base_temperature_k: CONVECTIVE_ZONE_BASE_TEMP,
            top_temperature_k: CONVECTIVE_ZONE_TOP_TEMP,
            base_density: CONVECTIVE_ZONE_BASE_DENSITY,
            top_density: CONVECTIVE_ZONE_TOP_DENSITY,
        }
    }

    pub fn thickness_m(&self) -> f64 {
        (self.outer_radius_frac - self.inner_radius_frac) * SOLAR_RADIUS
    }

    pub fn volume_m3(&self) -> f64 {
        let r_out = self.outer_radius_frac * SOLAR_RADIUS;
        let r_in = self.inner_radius_frac * SOLAR_RADIUS;
        (4.0 / 3.0) * std::f64::consts::PI * (r_out.powi(3) - r_in.powi(3))
    }

    pub fn temperature_at_frac(&self, radius_frac: f64) -> f64 {
        let f = ((radius_frac - self.inner_radius_frac)
            / (self.outer_radius_frac - self.inner_radius_frac))
            .clamp(0.0, 1.0);
        self.base_temperature_k * (1.0 - f) + self.top_temperature_k * f
    }

    pub fn density_at_frac(&self, radius_frac: f64) -> f64 {
        let f = ((radius_frac - self.inner_radius_frac)
            / (self.outer_radius_frac - self.inner_radius_frac))
            .clamp(0.0, 1.0);
        (self.base_density.ln() * (1.0 - f) + self.top_density.ln() * f).exp()
    }

    pub fn pressure_scale_height(&self, radius_frac: f64) -> f64 {
        let t = self.temperature_at_frac(radius_frac);
        let r = radius_frac * SOLAR_RADIUS;
        let g = G * SOLAR_MASS / (r * r);
        K_B * t / (0.61 * PROTON_MASS * g)
    }

    pub fn mixing_length(&self, radius_frac: f64) -> f64 {
        MIXING_LENGTH_ALPHA * self.pressure_scale_height(radius_frac)
    }

    pub fn convective_velocity(&self, radius_frac: f64) -> f64 {
        let f = ((radius_frac - self.inner_radius_frac)
            / (self.outer_radius_frac - self.inner_radius_frac))
            .clamp(0.0, 1.0);
        CONVECTIVE_VELOCITY_DEEP * (1.0 - f) + CONVECTIVE_VELOCITY_SURFACE * f
    }

    pub fn convective_flux(&self, radius_frac: f64) -> f64 {
        let rho = self.density_at_frac(radius_frac);
        let v = self.convective_velocity(radius_frac);
        let cp = 5.0 * K_B / (2.0 * 0.61 * PROTON_MASS);
        let dt = 100.0;
        rho * cp * v * dt
    }

    pub fn turnover_time(&self, radius_frac: f64) -> f64 {
        let l = self.mixing_length(radius_frac);
        let v = self.convective_velocity(radius_frac);
        l / v.max(1e-10)
    }

    pub fn rossby_number(&self, radius_frac: f64) -> f64 {
        let tau = self.turnover_time(radius_frac);
        let omega = *SOLAR_OMEGA_EQUATORIAL;
        2.0 * std::f64::consts::PI / (omega * tau)
    }

    pub fn superadiabaticity(&self) -> f64 {
        1e-7
    }

    pub fn brunt_vaisala_frequency(&self, radius_frac: f64) -> f64 {
        let g = G * SOLAR_MASS / ((radius_frac * SOLAR_RADIUS).powi(2));
        let hp = self.pressure_scale_height(radius_frac);
        let delta = self.superadiabaticity();
        (g * delta / hp).sqrt()
    }

    pub fn total_kinetic_energy(&self) -> f64 {
        let mean_v = (CONVECTIVE_VELOCITY_DEEP + CONVECTIVE_VELOCITY_SURFACE) / 2.0;
        let mean_rho = (self.base_density + self.top_density) / 2.0;
        0.5 * mean_rho * mean_v * mean_v * self.volume_m3()
    }
}

pub fn differential_rotation_angular_velocity(latitude_deg: f64) -> f64 {
    let a = 14.713;
    let b = -2.396;
    let c = -1.787;
    let sin_lat = latitude_deg.to_radians().sin();
    let sin2 = sin_lat * sin_lat;
    (a + b * sin2 + c * sin2 * sin2) * std::f64::consts::PI / 180.0 / SECONDS_PER_DAY
}

pub fn meridional_flow_speed(latitude_deg: f64) -> f64 {
    let max_speed = 15.0;
    max_speed * (2.0 * latitude_deg.to_radians()).sin()
}

pub fn convective_overshoot_depth() -> f64 {
    0.05 * SOLAR_RADIUS
}

pub fn entropy_rain_speed(temperature_deficit: f64) -> f64 {
    let g = G * SOLAR_MASS / (SOLAR_RADIUS * SOLAR_RADIUS);
    let hp = 1.5e5;
    (g * temperature_deficit * hp / SOLAR_EFFECTIVE_TEMP).sqrt()
}