titanss 0.0.3

Titanss is a celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use sciforge::hub::prelude::constants::N_A;
use sciforge::hub::prelude::constants::elements::atomic_mass;

pub struct MolecularSpecies {
    pub name: &'static str,
    pub symbol: &'static str,
    pub molar_mass_kg_mol: f64,
    pub volume_fraction: f64,
}

pub struct AtmosphereEndpoint {
    pub planet_radius_m: f64,
    pub atmosphere_height_m: f64,
    pub surface_pressure_pa: f64,
    pub surface_temperature_k: f64,
    pub surface_number_density_m3: f64,
    pub mean_molar_mass_kg_mol: f64,
    pub rayleigh_scale_height_m: f64,
    pub species: Vec<MolecularSpecies>,
    pub sun_irradiance_w_m2: f64,
}

fn n2_molar() -> f64 {
    2.0 * atomic_mass(7) * 1e-3
}
fn ch4_molar() -> f64 {
    (atomic_mass(6) + 4.0 * atomic_mass(1)) * 1e-3
}
fn h2_molar() -> f64 {
    2.0 * atomic_mass(1) * 1e-3
}
fn c2h6_molar() -> f64 {
    (2.0 * atomic_mass(6) + 6.0 * atomic_mass(1)) * 1e-3
}
fn c2h2_molar() -> f64 {
    (2.0 * atomic_mass(6) + 2.0 * atomic_mass(1)) * 1e-3
}
fn hcn_molar() -> f64 {
    (atomic_mass(1) + atomic_mass(6) + atomic_mass(7)) * 1e-3
}
fn ar_molar() -> f64 {
    atomic_mass(18) * 1e-3
}

fn mean_molar_mass() -> f64 {
    0.984 * n2_molar()
        + 0.014 * ch4_molar()
        + 0.001 * h2_molar()
        + 1.3e-5 * c2h6_molar()
        + 2.2e-6 * c2h2_molar()
        + 7.0e-7 * hcn_molar()
        + 4.3e-5 * ar_molar()
}

fn surface_number_density(pressure_pa: f64, temperature_k: f64, molar_mass: f64) -> f64 {
    let rho = pressure_pa / (8.314_462_618 / molar_mass * temperature_k);
    N_A * rho / molar_mass
}

impl AtmosphereEndpoint {
    pub fn titan() -> Self {
        let m_air = mean_molar_mass();
        let pressure = 146_700.0;
        let temperature = 93.7;
        let n_density = surface_number_density(pressure, temperature, m_air);
        let species = vec![
            MolecularSpecies {
                name: "Dinitrogen",
                symbol: "N2",
                molar_mass_kg_mol: n2_molar(),
                volume_fraction: 0.984,
            },
            MolecularSpecies {
                name: "Methane",
                symbol: "CH4",
                molar_mass_kg_mol: ch4_molar(),
                volume_fraction: 0.014,
            },
            MolecularSpecies {
                name: "Dihydrogen",
                symbol: "H2",
                molar_mass_kg_mol: h2_molar(),
                volume_fraction: 0.001,
            },
            MolecularSpecies {
                name: "Ethane",
                symbol: "C2H6",
                molar_mass_kg_mol: c2h6_molar(),
                volume_fraction: 1.3e-5,
            },
            MolecularSpecies {
                name: "Acetylene",
                symbol: "C2H2",
                molar_mass_kg_mol: c2h2_molar(),
                volume_fraction: 2.2e-6,
            },
            MolecularSpecies {
                name: "Hydrogen cyanide",
                symbol: "HCN",
                molar_mass_kg_mol: hcn_molar(),
                volume_fraction: 7.0e-7,
            },
            MolecularSpecies {
                name: "Argon",
                symbol: "Ar",
                molar_mass_kg_mol: ar_molar(),
                volume_fraction: 4.3e-5,
            },
        ];

        Self {
            planet_radius_m: crate::TITAN_RADIUS_M,
            atmosphere_height_m: 600_000.0,
            surface_pressure_pa: pressure,
            surface_temperature_k: temperature,
            surface_number_density_m3: n_density,
            mean_molar_mass_kg_mol: m_air,
            rayleigh_scale_height_m: 21_000.0,
            species,
            sun_irradiance_w_m2: crate::SOLAR_CONSTANT_AT_TITAN_W_M2,
        }
    }

    pub fn rayleigh_density(&self, altitude_m: f64) -> f64 {
        self.surface_number_density_m3 * (-altitude_m / self.rayleigh_scale_height_m).exp()
    }

    pub fn sky_color(&self) -> [f64; 3] {
        [0.60, 0.45, 0.20]
    }

    pub fn direct_transmission(&self, optical_depth: f64, airmass: f64) -> f64 {
        (-optical_depth * airmass).exp()
    }

    pub fn sky_luminance(&self, observer_alt: f64, cos_zenith: f64) -> f64 {
        let cos_z = cos_zenith.max(0.01);
        let altitude_factor = (-observer_alt / self.rayleigh_scale_height_m).exp();
        let n2_frac = self
            .species
            .iter()
            .find(|s| s.symbol == "N2")
            .map(|s| s.volume_fraction)
            .unwrap_or(0.984);
        let ch4_frac = self
            .species
            .iter()
            .find(|s| s.symbol == "CH4")
            .map(|s| s.volume_fraction)
            .unwrap_or(0.014);
        let scatter =
            self.surface_number_density_m3 * (n2_frac + ch4_frac * 2.3) * altitude_factor * cos_z;
        scatter * self.sun_irradiance_w_m2 * 1e-30
    }

    pub fn barometric_density(&self) -> f64 {
        self.surface_pressure_pa
            / (self.surface_temperature_k * 8.314_462_618 / self.mean_molar_mass_kg_mol)
    }

    pub fn shell_volume(&self) -> f64 {
        4.0 / 3.0
            * std::f64::consts::PI
            * ((self.planet_radius_m + self.atmosphere_height_m).powi(3)
                - self.planet_radius_m.powi(3))
    }
}