sciforge 0.0.3

A comprehensive scientific computing library in pure Rust with zero dependencies
Documentation
use sciforge::benchmark::decode::decode;
use sciforge::benchmark::engine::bench;
use sciforge::benchmark::export::{Entry, export};
use sciforge::benchmark::report::generate;
use sciforge::hub::prelude::astronomy::stellar;
use sciforge::hub::prelude::constants::*;
use std::fs;
use std::path::Path;

struct Star {
    name: &'static str,
    spectral: &'static str,
    category: &'static str,
    mass_solar: f64,
    radius_solar: f64,
    temp_k: f64,
    distance_pc: f64,
    apparent_mag: f64,
}

const STARS: [Star; 10] = [
    Star {
        name: "Sun",
        spectral: "G2V",
        category: "Main Sequence",
        mass_solar: 1.0,
        radius_solar: 1.0,
        temp_k: 5778.0,
        distance_pc: 4.848e-6,
        apparent_mag: -26.74,
    },
    Star {
        name: "Sirius A",
        spectral: "A1V",
        category: "Main Sequence",
        mass_solar: 2.063,
        radius_solar: 1.711,
        temp_k: 9940.0,
        distance_pc: 2.637,
        apparent_mag: -1.46,
    },
    Star {
        name: "Betelgeuse",
        spectral: "M1Ia",
        category: "Supergiant",
        mass_solar: 16.5,
        radius_solar: 887.0,
        temp_k: 3600.0,
        distance_pc: 168.1,
        apparent_mag: 0.42,
    },
    Star {
        name: "Rigel",
        spectral: "B8Ia",
        category: "Supergiant",
        mass_solar: 21.0,
        radius_solar: 78.9,
        temp_k: 12100.0,
        distance_pc: 264.6,
        apparent_mag: 0.13,
    },
    Star {
        name: "Vega",
        spectral: "A0V",
        category: "Main Sequence",
        mass_solar: 2.135,
        radius_solar: 2.362,
        temp_k: 9602.0,
        distance_pc: 7.68,
        apparent_mag: 0.03,
    },
    Star {
        name: "Proxima Centauri",
        spectral: "M5.5Ve",
        category: "Red Dwarf",
        mass_solar: 0.122,
        radius_solar: 0.1542,
        temp_k: 3042.0,
        distance_pc: 1.301,
        apparent_mag: 11.13,
    },
    Star {
        name: "Polaris",
        spectral: "F7Ib",
        category: "Supergiant",
        mass_solar: 5.4,
        radius_solar: 37.5,
        temp_k: 6015.0,
        distance_pc: 132.6,
        apparent_mag: 1.98,
    },
    Star {
        name: "Aldebaran",
        spectral: "K5III",
        category: "Giant",
        mass_solar: 1.16,
        radius_solar: 44.13,
        temp_k: 3910.0,
        distance_pc: 20.43,
        apparent_mag: 0.86,
    },
    Star {
        name: "Deneb",
        spectral: "A2Ia",
        category: "Supergiant",
        mass_solar: 19.0,
        radius_solar: 203.0,
        temp_k: 8525.0,
        distance_pc: 802.0,
        apparent_mag: 1.25,
    },
    Star {
        name: "Barnard's Star",
        spectral: "M4Ve",
        category: "Red Dwarf",
        mass_solar: 0.144,
        radius_solar: 0.196,
        temp_k: 3134.0,
        distance_pc: 1.834,
        apparent_mag: 9.51,
    },
];

fn main() {
    let dir = Path::new("output/examples/star_catalog");
    fs::create_dir_all(dir).unwrap();

    let lum_strs: Vec<String> = STARS
        .iter()
        .map(|s| {
            let r = s.radius_solar * SOLAR_RADIUS;
            let l = stellar::luminosity_from_radius_temp(r, s.temp_k);
            format!("{:.3e} W", l)
        })
        .collect();

    let abs_mag_strs: Vec<String> = STARS
        .iter()
        .map(|s| {
            let m = stellar::absolute_magnitude(s.apparent_mag, s.distance_pc);
            format!("{:.2}", m)
        })
        .collect();

    let schwarzschild_strs: Vec<String> = STARS
        .iter()
        .map(|s| {
            let r = stellar::schwarzschild_radius(s.mass_solar * SOLAR_MASS);
            format!("{:.2} m", r)
        })
        .collect();

    let lifetime_strs: Vec<String> = STARS
        .iter()
        .map(|s| {
            let l_solar = stellar::mass_luminosity_relation(s.mass_solar);
            let t = stellar::main_sequence_lifetime(s.mass_solar, l_solar);
            let gyr = t / (3.156e7 * 1e9);
            if gyr > 1.0 {
                format!("{:.2} Gyr", gyr)
            } else {
                format!("{:.1} Myr", gyr * 1000.0)
            }
        })
        .collect();

    let wien_strs: Vec<String> = STARS
        .iter()
        .map(|s| {
            let wl = stellar::wien_peak_wavelength(s.temp_k);
            format!("{:.0} nm", wl * 1e9)
        })
        .collect();

    let mass_strs: Vec<String> = STARS
        .iter()
        .map(|s| format!("{:.3} M\u{2609}", s.mass_solar))
        .collect();
    let radius_strs: Vec<String> = STARS
        .iter()
        .map(|s| format!("{:.3} R\u{2609}", s.radius_solar))
        .collect();
    let temp_strs: Vec<String> = STARS.iter().map(|s| format!("{:.0} K", s.temp_k)).collect();

    let mut metrics_store: Vec<(String, String, _)> = Vec::new();

    let exp_names: Vec<String> = STARS
        .iter()
        .map(|s| format!("{}_stellar", s.name))
        .collect();

    for (idx, s) in STARS.iter().enumerate() {
        let r_m = s.radius_solar * SOLAR_RADIUS;
        let t_k = s.temp_k;
        let mass_s = s.mass_solar;
        let m = bench(&exp_names[idx], "f64", 5000, move || {
            let mut v = 0u64;
            for i in 0..100 {
                let l = stellar::luminosity_from_radius_temp(r_m, t_k + i as f64);
                let rs = stellar::schwarzschild_radius(mass_s * SOLAR_MASS);
                v = v.wrapping_add(l.to_bits()).wrapping_add(rs.to_bits());
            }
            v
        });
        let result = format!(
            "{} ({}) | L={} | M_abs={} | R_s={} | MS lifetime={} | Wien \u{03bb}_max={}",
            s.name,
            s.spectral,
            lum_strs[idx],
            abs_mag_strs[idx],
            schwarzschild_strs[idx],
            lifetime_strs[idx],
            wien_strs[idx]
        );
        let rpt = generate(&m, &result);
        assert!(rpt.csv.starts_with("experiment_name"));
        metrics_store.push((s.name.to_string(), result, m));
    }

    let spectral_symbols: Vec<&str> = STARS
        .iter()
        .map(|s| match s.spectral.chars().next().unwrap() {
            'O' => "O",
            'B' => "B",
            'A' => "A",
            'F' => "F",
            'G' => "G",
            'K' => "K",
            'M' => "M",
            _ => "?",
        })
        .collect();

    let entries: Vec<Entry<'_>> = metrics_store
        .iter()
        .enumerate()
        .map(|(idx, (label, result, m))| Entry {
            metrics: m,
            result: result.as_str(),
            label: label.as_str(),
            tags: vec![
                ("category", STARS[idx].category),
                ("symbol", spectral_symbols[idx]),
                ("name", STARS[idx].name),
                ("spectral", STARS[idx].spectral),
                ("mass", mass_strs[idx].as_str()),
                ("radius", radius_strs[idx].as_str()),
                ("temperature", temp_strs[idx].as_str()),
                ("luminosity", lum_strs[idx].as_str()),
                ("abs_magnitude", abs_mag_strs[idx].as_str()),
                ("schwarzschild", schwarzschild_strs[idx].as_str()),
                ("ms_lifetime", lifetime_strs[idx].as_str()),
                ("wien_peak", wien_strs[idx].as_str()),
            ],
            grid_row: Some((idx as u8 / 5) + 1),
            grid_col: Some((idx as u8 % 5) + 1),
        })
        .collect();

    let summary = export(
        "Star Catalog \u{2014} Luminosity, Magnitude, Schwarzschild Radius & Main Sequence Lifetime",
        &entries,
        dir,
    ).unwrap();
    assert!(summary.files_written > 0);

    let bmk_path = dir.join("bmk/star_catalog.bmk");
    let bytes = fs::read(&bmk_path).unwrap();
    let view = decode(&bytes).unwrap();
    assert!(!view.experiment_name.is_empty());
}