1use crate::units::{KmPerSec, Seconds};
12
13pub use crate::constants::C_KM_S;
15
16pub fn lorentz_factor(v: KmPerSec) -> f64 {
21 let beta = v.value() / C_KM_S;
22 assert!(
23 beta.abs() < 1.0,
24 "velocity must be less than c ({} km/s), got {} km/s",
25 C_KM_S,
26 v.value()
27 );
28 1.0 / (1.0 - beta * beta).sqrt()
29}
30
31pub fn beta(v: KmPerSec) -> f64 {
33 v.value() / C_KM_S
34}
35
36pub fn time_dilation_constant_v(coordinate_time: Seconds, v: KmPerSec) -> Seconds {
45 let gamma = lorentz_factor(v);
46 Seconds(coordinate_time.value() / gamma)
47}
48
49pub fn time_dilation_loss(coordinate_time: Seconds, v: KmPerSec) -> Seconds {
55 let proper = time_dilation_constant_v(coordinate_time, v);
56 Seconds(coordinate_time.value() - proper.value())
57}
58
59pub fn kinetic_energy_correction_factor(v: KmPerSec) -> f64 {
68 let beta_val = beta(v);
69 let gamma = lorentz_factor(v);
70
71 if beta_val.abs() < 1e-15 {
73 return 1.0;
74 }
75
76 let relativistic_ke = (gamma - 1.0) * C_KM_S * C_KM_S;
77 let classical_ke = 0.5 * v.value() * v.value();
78 relativistic_ke / classical_ke
79}
80
81pub fn classical_delta_v(exhaust_velocity: KmPerSec, mass_ratio: f64) -> KmPerSec {
85 assert!(mass_ratio > 0.0, "mass ratio must be positive");
86 KmPerSec(exhaust_velocity.value() * mass_ratio.ln())
87}
88
89pub fn relativistic_delta_v(exhaust_velocity: KmPerSec, mass_ratio: f64) -> KmPerSec {
102 assert!(mass_ratio > 0.0, "mass ratio must be positive");
103 let x = exhaust_velocity.value() / C_KM_S * mass_ratio.ln();
104 KmPerSec(C_KM_S * x.tanh())
105}
106
107pub fn delta_v_correction_fraction(exhaust_velocity: KmPerSec, mass_ratio: f64) -> f64 {
115 let classical = classical_delta_v(exhaust_velocity, mass_ratio);
116 let relativistic = relativistic_delta_v(exhaust_velocity, mass_ratio);
117
118 if classical.value().abs() < 1e-15 {
119 return 0.0;
120 }
121
122 (classical.value() - relativistic.value()) / classical.value()
123}
124
125pub fn brachistochrone_times(distance: crate::units::Km, accel_km_s2: f64) -> (Seconds, Seconds) {
144 let d = distance.value();
145 let a = accel_km_s2;
146 let c = C_KM_S;
147
148 let d_half = d / 2.0;
150
151 let x = d_half * a / (c * c) + 1.0;
155 let t_half = (c / a) * (x * x - 1.0).sqrt();
156 let t_total = 2.0 * t_half;
157
158 let tau_half = (c / a) * (a * t_half / c).asinh();
161 let tau_total = 2.0 * tau_half;
162
163 (Seconds(t_total), Seconds(tau_total))
164}
165
166pub fn brachistochrone_peak_velocity(distance: crate::units::Km, accel_km_s2: f64) -> KmPerSec {
172 let d = distance.value();
173 let a = accel_km_s2;
174 let c = C_KM_S;
175
176 let d_half = d / 2.0;
177 let x = d_half * a / (c * c) + 1.0;
178 let t_half = (c / a) * (x * x - 1.0).sqrt();
179
180 let at_over_c = a * t_half / c;
181 let v_peak = c * at_over_c / (1.0 + at_over_c * at_over_c).sqrt();
182
183 KmPerSec(v_peak)
184}
185
186pub fn effects_summary(v: KmPerSec) -> (f64, f64, f64, f64) {
194 let b = beta(v);
195 let g = lorentz_factor(v);
196
197 let td_fraction = 1.0 - 1.0 / g;
199 let td_ppm = td_fraction * 1e6;
200
201 let ke_factor = kinetic_energy_correction_factor(v);
203 let ke_ppm = (ke_factor - 1.0) * 1e6;
204
205 (g, b, td_ppm, ke_ppm)
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::units::Km;
212
213 #[test]
214 fn test_speed_of_light() {
215 assert!((C_KM_S - 299_792.458).abs() < 1e-3);
217 }
218
219 #[test]
220 fn test_lorentz_factor_zero_velocity() {
221 let gamma = lorentz_factor(KmPerSec(0.0));
222 assert!((gamma - 1.0).abs() < 1e-15, "γ at v=0 should be 1.0");
223 }
224
225 #[test]
226 fn test_lorentz_factor_known_values() {
227 let v_01c = KmPerSec(0.1 * C_KM_S);
229 let gamma = lorentz_factor(v_01c);
230 let expected = 1.0 / (1.0 - 0.01_f64).sqrt();
231 assert!(
232 (gamma - expected).abs() < 1e-10,
233 "γ at 0.1c: {} vs expected {}",
234 gamma,
235 expected
236 );
237 }
238
239 #[test]
240 fn test_lorentz_factor_half_c() {
241 let v = KmPerSec(0.5 * C_KM_S);
243 let gamma = lorentz_factor(v);
244 let expected = 1.0 / (0.75_f64).sqrt();
245 assert!(
246 (gamma - expected).abs() < 1e-10,
247 "γ at 0.5c: {} vs expected {}",
248 gamma,
249 expected
250 );
251 }
252
253 #[test]
254 fn test_beta_computation() {
255 let v = KmPerSec(2997.92458); let b = beta(v);
257 assert!((b - 0.01).abs() < 1e-10, "β should be 0.01, got {}", b);
258 }
259
260 #[test]
261 fn test_time_dilation_at_rest() {
262 let t = Seconds(3600.0); let proper = time_dilation_constant_v(t, KmPerSec(0.0));
264 assert!(
265 (proper.value() - 3600.0).abs() < 1e-10,
266 "no time dilation at rest"
267 );
268 }
269
270 #[test]
271 fn test_time_dilation_at_1_percent_c() {
272 let v = KmPerSec(0.01 * C_KM_S);
275 let one_year = Seconds(365.25 * 86400.0);
276 let loss = time_dilation_loss(one_year, v);
277
278 let gamma = lorentz_factor(v);
280 let expected_loss = one_year.value() * (1.0 - 1.0 / gamma);
281 assert!(
282 (loss.value() - expected_loss).abs() < 1e-6,
283 "time loss: {} s vs expected {} s",
284 loss.value(),
285 expected_loss
286 );
287
288 let loss_fraction = loss.value() / one_year.value();
290 assert!(
291 loss_fraction > 1e-5 && loss_fraction < 1e-3,
292 "loss fraction at 1%c: {}",
293 loss_fraction
294 );
295 }
296
297 #[test]
298 fn test_kinetic_energy_correction_at_zero() {
299 let factor = kinetic_energy_correction_factor(KmPerSec(0.0));
300 assert!(
301 (factor - 1.0).abs() < 1e-10,
302 "KE correction at v=0 should be 1.0"
303 );
304 }
305
306 #[test]
307 fn test_kinetic_energy_correction_at_low_v() {
308 let v = KmPerSec(0.01 * C_KM_S);
314 let factor = kinetic_energy_correction_factor(v);
315 assert!(
317 (factor - 1.0).abs() < 0.001,
318 "KE correction at 0.01c: {} (expected ~1.0)",
319 factor
320 );
321 }
322
323 #[test]
324 fn test_kinetic_energy_correction_increases_with_v() {
325 let factor_1 = kinetic_energy_correction_factor(KmPerSec(0.01 * C_KM_S));
326 let factor_5 = kinetic_energy_correction_factor(KmPerSec(0.05 * C_KM_S));
327 let factor_10 = kinetic_energy_correction_factor(KmPerSec(0.10 * C_KM_S));
328
329 assert!(factor_5 > factor_1, "KE correction should increase with v");
330 assert!(factor_10 > factor_5, "KE correction should increase with v");
331 }
332
333 #[test]
334 fn test_classical_delta_v() {
335 let ve = KmPerSec(9806.65); let mr = std::f64::consts::E; let dv = classical_delta_v(ve, mr);
339 assert!(
340 (dv.value() - ve.value()).abs() < 1e-6,
341 "Δv should equal ve for mass ratio e"
342 );
343 }
344
345 #[test]
346 fn test_relativistic_delta_v_low_speed() {
347 let ve = KmPerSec(10.0); let mr = 3.0;
350 let classical = classical_delta_v(ve, mr);
351 let relativistic = relativistic_delta_v(ve, mr);
352
353 let diff = (classical.value() - relativistic.value()).abs();
354 assert!(
355 diff / classical.value() < 1e-6,
356 "at low v, relativistic should match classical: rel diff = {}",
357 diff / classical.value()
358 );
359 }
360
361 #[test]
362 fn test_relativistic_delta_v_less_than_classical() {
363 let ve = KmPerSec(9806.65); let mr = 10.0; let classical = classical_delta_v(ve, mr);
367 let relativistic = relativistic_delta_v(ve, mr);
368
369 assert!(
370 relativistic.value() <= classical.value(),
371 "relativistic ({}) should be ≤ classical ({})",
372 relativistic.value(),
373 classical.value()
374 );
375
376 let correction = delta_v_correction_fraction(ve, mr);
378 assert!(
379 correction > 0.0 && correction < 0.1,
380 "correction at 7.5%c: {}",
381 correction
382 );
383 }
384
385 #[test]
386 fn test_relativistic_delta_v_high_mass_ratio() {
387 let ve = KmPerSec(0.1 * C_KM_S); let mr = 1e10;
391 let relativistic = relativistic_delta_v(ve, mr);
392
393 assert!(
395 relativistic.value() < C_KM_S,
396 "relativistic velocity should be < c"
397 );
398 assert!(
400 relativistic.value() > 0.95 * C_KM_S,
401 "at extreme mass ratio, velocity should be close to c: {} km/s ({}c)",
402 relativistic.value(),
403 relativistic.value() / C_KM_S
404 );
405
406 let mr_extreme = 1e100;
408 let rel_extreme = relativistic_delta_v(ve, mr_extreme);
409 assert!(
410 rel_extreme.value() > relativistic.value(),
411 "higher mass ratio should give higher velocity"
412 );
413 assert!(
414 rel_extreme.value() > 0.999 * C_KM_S,
415 "at extreme mass ratio (1e100), should be very close to c: {} km/s",
416 rel_extreme.value()
417 );
418 }
419
420 #[test]
421 fn test_delta_v_correction_kestrel_ep01() {
422 let ve = KmPerSec(9806.65);
426 let mr = (8497.0_f64 / 9806.65).exp();
427 let correction = delta_v_correction_fraction(ve, mr);
428
429 assert!(correction > 0.0, "correction should be positive");
431 assert!(
432 correction < 0.01,
433 "correction at 2.8%c should be < 1%, got {}%",
434 correction * 100.0
435 );
436 }
437
438 #[test]
439 fn test_brachistochrone_times_classical_limit() {
440 let d = Km(550_630_800.0); let a = 0.032_783; let (t_coord, t_proper) = brachistochrone_times(d, a);
446
447 let t_classical = 2.0 * (d.value() / a).sqrt();
453
454 let rel_diff = (t_coord.value() - t_classical).abs() / t_classical;
456 assert!(
457 rel_diff < 1e-4,
458 "coordinate time should match classical: {} vs {}, rel diff = {}",
459 t_coord.value(),
460 t_classical,
461 rel_diff
462 );
463
464 let td_diff = (t_coord.value() - t_proper.value()).abs() / t_coord.value();
466 assert!(
467 td_diff < 1e-3,
468 "proper time should be close to coordinate time: diff = {}",
469 td_diff
470 );
471 }
472
473 #[test]
474 fn test_brachistochrone_times_high_accel() {
475 let d = Km(149_597_870.7); let a = 1.0; let (t_coord, t_proper) = brachistochrone_times(d, a);
481
482 assert!(
484 t_proper.value() < t_coord.value(),
485 "proper time should be less than coordinate time"
486 );
487 assert!(t_proper.value() > 0.0, "proper time should be positive");
488 }
489
490 #[test]
491 fn test_brachistochrone_peak_velocity() {
492 let d = Km(550_630_800.0);
494 let a = 0.032_783; let v_peak = brachistochrone_peak_velocity(d, a);
497
498 let v_classical = (a * d.value()).sqrt();
502
503 let rel_diff = (v_peak.value() - v_classical).abs() / v_classical;
505 assert!(
506 rel_diff < 1e-4,
507 "peak velocity: {} vs classical {}, diff = {}",
508 v_peak.value(),
509 v_classical,
510 rel_diff
511 );
512
513 assert!(
515 v_peak.value() > 4000.0 && v_peak.value() < 5000.0,
516 "EP01 peak velocity should be ~4249 km/s, got {} km/s",
517 v_peak.value()
518 );
519 }
520
521 #[test]
522 fn test_brachistochrone_peak_velocity_capped_at_c() {
523 let d = Km(1e12); let a = 100.0; let v_peak = brachistochrone_peak_velocity(d, a);
528 assert!(
529 v_peak.value() < C_KM_S,
530 "peak velocity should be < c, got {} km/s",
531 v_peak.value()
532 );
533 }
534
535 #[test]
536 fn test_delta_v_divergence_at_10_percent_c() {
537 let ve = KmPerSec(9806.65); let target_v = 0.1 * C_KM_S; let mr = (target_v / ve.value()).exp();
542
543 let classical = classical_delta_v(ve, mr);
544 let relativistic = relativistic_delta_v(ve, mr);
545 let correction = delta_v_correction_fraction(ve, mr);
546
547 assert!(
549 (classical.value() - target_v).abs() < 1e-6,
550 "classical should give ~0.1c: {} km/s",
551 classical.value()
552 );
553 assert!(
555 relativistic.value() < classical.value(),
556 "relativistic ({}) should be less than classical ({})",
557 relativistic.value(),
558 classical.value()
559 );
560 assert!(
564 correction > 0.003,
565 "at 0.1c, relativistic correction should exceed 0.3%, got {:.4}%",
566 correction * 100.0
567 );
568 assert!(
570 correction < 0.01,
571 "at 0.1c, correction should be < 1%, got {:.4}%",
572 correction * 100.0
573 );
574 }
575
576 #[test]
577 fn test_brachistochrone_time_dilation_increases_with_accel() {
578 let d = Km(149_597_870.7); let (t_coord_low, t_proper_low) = brachistochrone_times(d, 0.01); let (t_coord_high, t_proper_high) = brachistochrone_times(d, 1.0); assert!(t_proper_low.value() < t_coord_low.value());
586 assert!(t_proper_high.value() < t_coord_high.value());
587
588 let dilation_low = 1.0 - t_proper_low.value() / t_coord_low.value();
590 let dilation_high = 1.0 - t_proper_high.value() / t_coord_high.value();
591 assert!(
592 dilation_high > dilation_low,
593 "higher accel should give more time dilation: low={:.2e} high={:.2e}",
594 dilation_low,
595 dilation_high
596 );
597
598 let v_peak = brachistochrone_peak_velocity(d, 1.0);
600 assert!(
601 v_peak.value() > 0.01 * C_KM_S,
602 "peak velocity at 1 km/s² should exceed 1%c: {} km/s",
603 v_peak.value()
604 );
605 }
606
607 #[test]
608 fn test_effects_summary_solar_line_velocities() {
609 let (gamma_1500, beta_1500, td_1500, _ke_1500) = effects_summary(KmPerSec(1500.0));
613 assert!(
614 (beta_1500 - 1500.0 / C_KM_S).abs() < 1e-10,
615 "β at 1500 km/s"
616 );
617 assert!(gamma_1500 > 1.0, "γ should be > 1");
618 assert!(td_1500 > 0.0, "time dilation should be positive");
619
620 assert!(
622 td_1500 < 100.0,
623 "time dilation at 0.5%c should be < 100 ppm, got {} ppm",
624 td_1500
625 );
626
627 let (_gamma_7600, _beta_7600, td_7600, _ke_7600) = effects_summary(KmPerSec(7600.0));
629 assert!(
630 td_7600 > td_1500,
631 "time dilation should increase with velocity"
632 );
633 assert!(
634 td_7600 < 1000.0,
635 "time dilation at 2.5%c should be < 1000 ppm, got {} ppm",
636 td_7600
637 );
638 }
639
640 #[test]
641 fn test_effects_summary_ep01_peak() {
642 let v = KmPerSec(4249.0);
644 let (gamma, beta_val, td_ppm, ke_ppm) = effects_summary(v);
645
646 assert!(
648 (beta_val - 0.01417).abs() < 0.001,
649 "β at 4249 km/s: {}",
650 beta_val
651 );
652
653 assert!(gamma > 1.0 && gamma < 1.001, "γ at 1.4%c: {}", gamma);
655
656 assert!(
658 td_ppm > 50.0 && td_ppm < 200.0,
659 "time dilation at 1.4%c: {} ppm",
660 td_ppm
661 );
662
663 assert!(
665 ke_ppm > 0.0 && ke_ppm < 200.0,
666 "KE correction at 1.4%c: {} ppm",
667 ke_ppm
668 );
669 }
670
671 #[test]
672 fn test_effects_summary_max_peak() {
673 let v = KmPerSec(7600.0);
675 let (gamma, beta_val, td_ppm, ke_ppm) = effects_summary(v);
676
677 assert!(
679 (beta_val - 0.02535).abs() < 0.001,
680 "β at 7600 km/s: {}",
681 beta_val
682 );
683
684 let loss_72h = td_ppm * 1e-6 * 259200.0;
689 assert!(
690 loss_72h > 50.0 && loss_72h < 150.0,
691 "time dilation loss over 72h at 2.5%c should be ~83s, got {} s",
692 loss_72h
693 );
694
695 assert!(
697 td_ppm < 1000.0 && ke_ppm < 1000.0,
698 "corrections at 2.5%c: td={} ppm, ke={} ppm",
699 td_ppm,
700 ke_ppm
701 );
702
703 let expected_gamma = 1.0 / (1.0 - beta_val * beta_val).sqrt();
705 assert!(
706 (gamma - expected_gamma).abs() < 1e-10,
707 "γ consistency check"
708 );
709 }
710}