use satellitesfactory::config::parameters::*;
use satellitesfactory::engine::evolution::*;
use satellitesfactory::engine::formation::*;
use satellitesfactory::engine::generator::*;
use satellitesfactory::engine::orbits::*;
use satellitesfactory::observables::astrometry::*;
use satellitesfactory::observables::photometry::*;
use satellitesfactory::observables::spectra::*;
use satellitesfactory::physics::gravitation::*;
use satellitesfactory::physics::interiors::*;
use satellitesfactory::physics::tidal_dynamics::*;
use satellitesfactory::types::captured::{CapturedComposition, CapturedSatellite};
use satellitesfactory::types::icy_moon::IcyMoon;
use satellitesfactory::types::regular::{RegularSatellite, SatelliteClass};
use satellitesfactory::types::ring_moon::{RingAssociation, RingMoon};
use satellitesfactory::types::subsurface_ocean_moon::SubsurfaceOceanMoon;
use satellitesfactory::types::volcanic_moon::VolcanicMoon;
use satellitesfactory::utils::helpers::*;
struct ValidationResult {
module: &'static str,
checks: usize,
passed: usize,
}
impl ValidationResult {
fn new(module: &'static str) -> Self {
Self {
module,
checks: 0,
passed: 0,
}
}
fn check(&mut self, ok: bool) {
self.checks += 1;
if ok {
self.passed += 1;
}
}
}
fn validate_types() -> ValidationResult {
let mut v = ValidationResult::new("types");
let moon = RegularSatellite::new(
"Moon",
"Earth",
MOON_MASS,
MOON_RADIUS,
2.03e-4,
6.687 * DEG,
OrbitalElements::from_km_deg(384_400.0, 0.054_9, 5.145, 125.08, 318.15, 135.27),
)
.with_composition(SatelliteClass::Regular, 0.12, 0.0, 0.83);
v.check((moon.mass_moon() - 1.0).abs() < 0.01);
v.check(moon.surface_gravity() > 1.6 && moon.surface_gravity() < 1.7);
v.check(moon.escape_velocity() > 2300.0 && moon.escape_velocity() < 2400.0);
v.check(moon.ice_mass() == 0.0);
v.check(moon.rock_mass() > 0.0);
v.check(moon.moment_of_inertia_factor() > 0.3 && moon.moment_of_inertia_factor() < 0.4);
let io = RegularSatellite::new(
"Io",
"Jupiter",
IO_MASS,
IO_RADIUS,
1.86e-3,
0.05 * DEG,
OrbitalElements::from_km_deg(421_700.0, 0.004_1, 0.036, 0.0, 0.0, 0.0),
)
.with_composition(SatelliteClass::Regular, 0.63, 0.0, 0.94);
v.check(io.mass > moon.mass);
v.check(io.mean_density() > 3500.0);
let triton = CapturedSatellite::new(
"Triton",
"Neptune",
TRITON_MASS,
TRITON_RADIUS,
0.0,
0.0,
OrbitalElements::from_km_deg(354_759.0, 0.000_016, 156.865, 0.0, 0.0, 0.0),
)
.with_capture(0.76, true, 300.0, CapturedComposition::IceRich);
v.check(triton.is_retrograde());
v.check(triton.capture_energy() > 0.0);
let enceladus = RingMoon::new(
"Enceladus",
"Saturn",
ENCELADUS_MASS,
ENCELADUS_RADIUS,
OrbitalElements::from_km_deg(238_042.0, 0.004_7, 0.009, 0.0, 0.0, 0.0),
)
.with_ring(0.99, RingAssociation::Embedded, true, 0.57);
v.check(enceladus.is_cryovolcanic());
v.check(enceladus.plume_escape_fraction() > 0.0);
v.check(enceladus.ice_mass() > 0.0);
let io_vol = VolcanicMoon::new(
"Io",
"Jupiter",
IO_MASS,
IO_RADIUS,
1.86e-3,
0.05 * DEG,
OrbitalElements::from_km_deg(421_700.0, 0.004_1, 0.036, 0.0, 0.0, 0.0),
);
v.check(io_vol.surface_gravity() > 1.7);
v.check(io_vol.tidal_heating(JUPITER_MASS) > 1e10);
let ganymede_icy = IcyMoon::new(
"Ganymede",
"Jupiter",
GANYMEDE_MASS,
GANYMEDE_RADIUS,
1.27e-4,
0.17 * DEG,
OrbitalElements::from_km_deg(1_070_400.0, 0.001_3, 0.20, 0.0, 0.0, 0.0),
)
.with_surface(0.43, 0.40, 0.55, 110.0);
v.check(ganymede_icy.ice_mass() > 0.0);
v.check(ganymede_icy.mean_density() > 1900.0);
let europa_ocean = SubsurfaceOceanMoon::new(
"Europa",
"Jupiter",
EUROPA_MASS,
EUROPA_RADIUS,
4.35e-4,
0.1 * DEG,
OrbitalElements::from_km_deg(671_100.0, 0.009_0, 0.47, 0.0, 0.0, 0.0),
)
.with_ocean(0.67, 15.0e3, 50.0, 30.0);
v.check(europa_ocean.ocean_depth() > 0.0);
v.check(europa_ocean.habitability_index(JUPITER_MASS) > 0.0);
v
}
fn validate_physics() -> ValidationResult {
let mut v = ValidationResult::new("physics");
let grav = SatelliteGravity::new(MOON_MASS, MOON_RADIUS);
v.check(grav.surface_acceleration() > 1.6 && grav.surface_acceleration() < 1.7);
v.check(grav.escape_velocity() > 2300.0);
v.check(grav.orbital_period(384_400.0e3) > 0.0);
v.check(grav.sphere_of_influence(EARTH_MASS, 384_400.0e3) > 0.0);
let f = two_body_force(EARTH_MASS, MOON_MASS, 384_400.0e3);
v.check(f > 1.9e20 && f < 2.1e20);
let rl = satellitesfactory::physics::gravitation::roche_limit(SATURN_RADIUS, 687.0, 917.0);
v.check(rl > SATURN_RADIUS);
let tidal = TidalSystem::new(IO_MASS, IO_RADIUS, JUPITER_MASS, 421_700.0e3, 0.004_1)
.with_tidal(JUPITER_RADIUS, 100.0, 0.015);
v.check(tidal.tidal_heating_rate() > 0.0);
v.check(tidal.tidal_heating_flux() > 0.0);
v.check(tidal.equilibrium_temperature_from_tidal() > 0.0);
let enc_tidal = TidalSystem::new(
ENCELADUS_MASS,
ENCELADUS_RADIUS,
SATURN_MASS,
238_042.0e3,
0.004_7,
)
.with_tidal(SATURN_RADIUS, 20.0, 0.01);
v.check(enc_tidal.tidal_heating_rate() > 0.0);
v.check(enc_tidal.circularization_timescale() > 0.0);
let interior = SatelliteInterior::new(EUROPA_MASS, EUROPA_RADIUS, 0.08, 0.86);
v.check(interior.ocean_depth_estimate() > 0.0);
v.check(interior.central_pressure() > 0.0);
v.check(interior.gravitational_binding_energy() > 0.0);
let io_int = SatelliteInterior::new(IO_MASS, IO_RADIUS, 0.0, 0.94);
v.check(io_int.ocean_depth_estimate() == 0.0);
v
}
fn validate_engine() -> ValidationResult {
let mut v = ValidationResult::new("engine");
let elem = OrbitalElements::from_km_deg(384_400.0, 0.054_9, 5.145, 125.08, 318.15, 135.27);
let (pos, vel) = elements_to_state(&elem, EARTH_MASS + MOON_MASS);
let r = (pos[0] * pos[0] + pos[1] * pos[1] + pos[2] * pos[2]).sqrt();
v.check(r > 3.5e8 && r < 4.1e8);
let spd = (vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]).sqrt();
v.check(spd > 900.0 && spd < 1100.0);
let ea = solve_kepler(1.0, 0.5, 1e-14);
v.check((ea - 0.5 * ea.sin() - 1.0).abs() < 1e-12);
let (r12, r23) = laplace_resonance_check(1.769, 3.551, 7.155);
v.check((r12 - 2.0).abs() < 0.02);
v.check((r23 - 2.0).abs() < 0.02);
let fm = FormationModel::new(
JUPITER_MASS,
JUPITER_RADIUS,
0.02,
30.0 * JUPITER_RADIUS,
400.0,
);
v.check(fm.disk_mass() > 0.0);
v.check(fm.ice_line_radius() > fm.parent_radius);
v.check(fm.maximum_satellite_mass() > 0.0);
let prob = FormationModel::capture_probability(200.0, 1000.0, 0.5);
v.check(prob > 0.0 && prob < 1.0);
let evo = SatelliteEvolution::new(
MOON_MASS,
MOON_RADIUS,
384_400.0e3,
0.054_9,
EARTH_MASS,
EARTH_RADIUS,
);
v.check(evo.tidal_migration_rate(27.0) > 0.0);
v.check(evo.orbital_decay_timescale(27.0) > 0.0);
let kozai = kozai_lidov_max_eccentricity(0.7);
v.check(kozai > 0.0 && kozai < 1.0);
let config = SystemGeneratorConfig::new(
"Jupiter",
JUPITER_MASS,
JUPITER_RADIUS,
2.0 * JUPITER_RADIUS,
30.0 * JUPITER_RADIUS,
)
.with_counts(4, 2, 3, 0, 0);
let generated = generate_system(&config);
v.check(generated.len() == 9);
v
}
fn validate_observables() -> ValidationResult {
let mut v = ValidationResult::new("observables");
let eq_t = equilibrium_temperature(3.828e26, 5.2 * AU, 0.34);
v.check(eq_t > 100.0 && eq_t < 200.0);
let phase = phase_curve_lambertian(1.0);
v.check(phase > 0.0);
let td = transit_depth_satellite(EUROPA_RADIUS, 6.957e8);
v.check(td > 0.0 && td < 1e-4);
let bd = band_depth(1.0, 0.7);
v.check((bd - 0.3).abs() < 0.01);
let wii = water_ice_index(0.8, 0.4);
v.check(wii > 1.5);
let comp = composition_from_albedo_and_density(0.99, 1200.0);
v.check(comp == SurfaceComposition::WaterIce);
let ang = angular_separation_arcsec(10.0 * 3.086e16, 5.2 * AU);
v.check(ang > 0.0);
let occ = occultation_duration(EUROPA_RADIUS, 13_740.0, JUPITER_RADIUS, 0.0);
v.check(occ > 0.0);
let ltt = light_travel_time(AU);
v.check((ltt - 499.0).abs() < 1.0);
v
}
fn validate_utils() -> ValidationResult {
let mut v = ValidationResult::new("utils");
v.check((lerp(0.0, 10.0, 0.5) - 5.0).abs() < 1e-12);
v.check((lerp(0.0, 10.0, 0.0)).abs() < 1e-12);
v.check((lerp(0.0, 10.0, 1.0) - 10.0).abs() < 1e-12);
let integral = simpson_integrate(|x| x * x, 0.0, 1.0, 100);
v.check((integral - 1.0 / 3.0).abs() < 1e-6);
let r = log_range(1.0, 1000.0, 4);
v.check(r.len() == 4);
v.check((r[0] - 1.0).abs() < 0.01);
v.check((r[3] - 1000.0).abs() < 1.0);
let s = format_si(1.5e9);
v.check(s.contains("G"));
v.check((clamp(-1.0, 0.0, 1.0)).abs() < 1e-12);
v.check((clamp(2.0, 0.0, 1.0) - 1.0).abs() < 1e-12);
v
}
fn main() {
let results = [
validate_types(),
validate_physics(),
validate_engine(),
validate_observables(),
validate_utils(),
];
let total_checks: usize = results.iter().map(|r| r.checks).sum();
let total_passed: usize = results.iter().map(|r| r.passed).sum();
for r in &results {
assert!(
r.passed == r.checks,
"FAIL: {} — {}/{} checks passed",
r.module,
r.passed,
r.checks
);
}
assert!(
total_passed == total_checks,
"OVERALL: {total_passed}/{total_checks} checks passed"
);
}