// Standard library use std::error::Error; use std::fs::File; use std::fs; use std::io::BufReader; use std::path::Path; use std::time::{Duration,Instant}; // External crates use clap::Parser; use log::{info, debug}; use indicatif::{ProgressBar, ProgressStyle}; use humantime; // Internal modules from the library crate use orbital_simulator::simulation::Simulation; use orbital_simulator::types::{norm_time, real_body}; use orbital_simulator as _; // for mod resolution #[derive(Parser, Debug)] #[command( version, about="Orbital mechanics simulator", long_about = "Given initial conditions you provide to --config, \ this program will numerically integrate and determinate their \ paths based off Newton's law of gravity.")] struct Args { ///Config file for initial conditions #[arg(short, long)] config: String, /// Time to run simulation for (e.g. 10s, 5m, 2h, 100d) #[arg(short, long, value_parser = humantime::parse_duration)] time: Duration, ///Step size for simulation (seconds) #[arg(short, long, default_value_t = 10.0)] step_size: f64, ///How often to update progress bar and save (seconds) #[arg(short = 'P', long, default_value_t = 1000)] steps_per_save: usize, /// Filename to save trajectory to #[arg(short, long)] output_file: String, } fn read_config>(path: P) -> Result> { let content = fs::read_to_string(&path)?; let conf = toml::from_str(&content)?; Ok(conf) } //fn parse_time(arg: &str) fn format_duration_single_unit(dur: Duration) -> String { let secs = dur.as_secs_f64(); const MINUTE: f64 = 60.0; const HOUR: f64 = 60.0 * MINUTE; const DAY: f64 = 24.0 * HOUR; const YEAR: f64 = 365.25 * DAY; if secs >= YEAR { format!("{:.0} years", (secs / YEAR).round()) } else if secs >= DAY { format!("{:.0} days", (secs / DAY).round()) } else if secs >= HOUR { format!("{:.0} hours", (secs / HOUR).round()) } else if secs >= MINUTE { format!("{:.0} minutes", (secs / MINUTE).round()) } else { format!("{:.0} seconds", secs.round()) } } fn main() { env_logger::init(); let args = Args::parse(); info!("Loading initial parameters from {}", args.config); let conf = read_config(args.config).unwrap(); for body in &conf.bodies { info!("Loaded {} with mass {:.3e} kg", body.name, body.mass); debug!("R_i = {:?}, V_i = {:?}", body.position, body.velocity); } let n_steps = (args.time.as_secs() as f64 / args.step_size) as usize; let pb = ProgressBar::new(n_steps.try_into().unwrap()); pb.set_style( ProgressStyle::with_template("[{elapsed_precise}] {bar:40.cyan/blue} {percent_precise}% \n\ Time remaining: {eta} \n\ Current simulation speed: {msg}") .unwrap() .progress_chars("=>-"), ); let mut sim = Simulation::new( conf.bodies, norm_time(args.step_size), args.steps_per_save, args.output_file, ); let start = Instant::now(); sim.run(n_steps, Some(|| { let elapsed = start.elapsed().as_secs() as f64 + 1.0; let speed = Duration::from_secs_f64(pb.position() as f64 * args.step_size / elapsed); pb.set_message(format!("{} /sec", format_duration_single_unit(speed))); pb.inc(args.steps_per_save as u64); }) ); }