OrbitalSimulator/src/bin/simulator.rs
2025-06-20 17:12:56 -04:00

116 lines
3.4 KiB
Rust

// 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<P: AsRef<Path>>(path: P) -> Result<orbital_simulator::config::ConfigFile, Box<dyn Error>> {
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);
})
);
}