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::orbits;
use sciforge::hub::prelude::constants::*;
use std::fs;
use std::path::Path;

struct Planet {
    name: &'static str,
    symbol: &'static str,
    mass: f64,
    radius: f64,
    semi_major: f64,
    category: &'static str,
}

const PLANETS: [Planet; 8] = [
    Planet {
        name: "Mercury",
        symbol: "\u{263F}",
        mass: MERCURY_MASS,
        radius: MERCURY_RADIUS,
        semi_major: MERCURY_SEMI_MAJOR_AXIS,
        category: "Terrestrial",
    },
    Planet {
        name: "Venus",
        symbol: "\u{2640}",
        mass: VENUS_MASS,
        radius: VENUS_RADIUS,
        semi_major: VENUS_SEMI_MAJOR_AXIS,
        category: "Terrestrial",
    },
    Planet {
        name: "Earth",
        symbol: "\u{2641}",
        mass: EARTH_MASS,
        radius: EARTH_RADIUS,
        semi_major: EARTH_SEMI_MAJOR_AXIS,
        category: "Terrestrial",
    },
    Planet {
        name: "Mars",
        symbol: "\u{2642}",
        mass: MARS_MASS,
        radius: MARS_RADIUS,
        semi_major: MARS_SEMI_MAJOR_AXIS,
        category: "Terrestrial",
    },
    Planet {
        name: "Jupiter",
        symbol: "\u{2643}",
        mass: JUPITER_MASS,
        radius: JUPITER_RADIUS,
        semi_major: JUPITER_SEMI_MAJOR_AXIS,
        category: "Gas Giant",
    },
    Planet {
        name: "Saturn",
        symbol: "\u{2644}",
        mass: SATURN_MASS,
        radius: SATURN_RADIUS,
        semi_major: SATURN_SEMI_MAJOR_AXIS,
        category: "Gas Giant",
    },
    Planet {
        name: "Uranus",
        symbol: "\u{26E2}",
        mass: URANUS_MASS,
        radius: URANUS_RADIUS,
        semi_major: URANUS_SEMI_MAJOR_AXIS,
        category: "Ice Giant",
    },
    Planet {
        name: "Neptune",
        symbol: "\u{2646}",
        mass: NEPTUNE_MASS,
        radius: NEPTUNE_RADIUS,
        semi_major: NEPTUNE_SEMI_MAJOR_AXIS,
        category: "Ice Giant",
    },
];

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

    let mu_sun = G * SOLAR_MASS;

    let kepler_strs: Vec<String> = PLANETS
        .iter()
        .map(|p| {
            let t = orbits::kepler_period(p.semi_major, mu_sun);
            format!("{:.2} days", t / 86400.0)
        })
        .collect();

    let circ_vel_strs: Vec<String> = PLANETS
        .iter()
        .map(|p| {
            let v = orbits::circular_velocity(mu_sun, p.semi_major);
            format!("{:.2} km/s", v / 1000.0)
        })
        .collect();

    let esc_vel_strs: Vec<String> = PLANETS
        .iter()
        .map(|p| {
            let v = orbits::escape_velocity(G * p.mass, p.radius);
            format!("{:.2} km/s", v / 1000.0)
        })
        .collect();

    let hohmann_strs: Vec<String> = PLANETS
        .iter()
        .map(|p| {
            if (p.semi_major - EARTH_SEMI_MAJOR_AXIS).abs() < 1.0 {
                "— (origin)".to_string()
            } else {
                let dv = orbits::hohmann_delta_v(mu_sun, EARTH_SEMI_MAJOR_AXIS, p.semi_major);
                format!("{:.2} km/s", dv / 1000.0)
            }
        })
        .collect();

    let mass_strs: Vec<String> = PLANETS
        .iter()
        .map(|p| format!("{:.3e} kg", p.mass))
        .collect();
    let radius_strs: Vec<String> = PLANETS
        .iter()
        .map(|p| format!("{:.0} km", p.radius / 1000.0))
        .collect();
    let sma_strs: Vec<String> = PLANETS
        .iter()
        .map(|p| format!("{:.3} AU", p.semi_major / AU))
        .collect();

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

    let exp_names: Vec<String> = PLANETS
        .iter()
        .map(|p| format!("{}_orbit", p.name))
        .collect();

    for (idx, p) in PLANETS.iter().enumerate() {
        let semi = p.semi_major;
        let m = bench(&exp_names[idx], "f64", 5000, move || {
            let mut v = 0u64;
            for i in 0..100 {
                let t = orbits::kepler_period(semi + i as f64, mu_sun);
                v = v.wrapping_add(t.to_bits());
            }
            v
        });
        let result = format!(
            "{} | T={} | v_circ={} | v_esc={} | Hohmann \u{0394}v={}",
            p.name, kepler_strs[idx], circ_vel_strs[idx], esc_vel_strs[idx], hohmann_strs[idx]
        );
        let r = generate(&m, &result);
        assert!(r.csv.starts_with("experiment_name"));
        metrics_store.push((p.name.to_string(), result, m));
    }

    let cat_strs: Vec<String> = PLANETS.iter().map(|p| p.category.to_string()).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", cat_strs[idx].as_str()),
                ("symbol", PLANETS[idx].symbol),
                ("name", PLANETS[idx].name),
                ("mass", mass_strs[idx].as_str()),
                ("radius", radius_strs[idx].as_str()),
                ("semi_major", sma_strs[idx].as_str()),
                ("kepler_period", kepler_strs[idx].as_str()),
                ("circular_vel", circ_vel_strs[idx].as_str()),
                ("escape_vel", esc_vel_strs[idx].as_str()),
                ("hohmann_dv", hohmann_strs[idx].as_str()),
            ],
            grid_row: Some(1),
            grid_col: Some((idx + 1) as u8),
        })
        .collect();

    let summary = export(
        "Solar System \u{2014} Orbital Mechanics (Kepler, Hohmann, Escape Velocities)",
        &entries,
        dir,
    )
    .unwrap();
    assert!(summary.files_written > 0);

    let bmk_dir = dir.join("bmk");
    let last_bmk = fs::read(bmk_dir.join("solar_system.bmk")).unwrap();
    let view = decode(&last_bmk).unwrap();
    assert!(!view.experiment_name.is_empty());
}