···2626 Float.is_finite px && Float.is_finite py && Float.is_finite pz
2727 && Float.is_finite vx && Float.is_finite vy && Float.is_finite vz
2828 then begin
2929- let pos = Kepler.Vec3.v px py pz in
3030- let vel = Kepler.Vec3.v vx vy vz in
2929+ let pos = Vec3.v px py pz in
3030+ let vel = Vec3.v vx vy vz in
3131 let dur = (abs_float (Float.of_int n) *. 10.) +. 1. in
3232 let _arc =
3333 Kepler.Propagate.arc ~pos ~vel ~duration_s:dur ~num_points:10
···4545 Int64.float_of_bits (Bytes.get_int64_le b (i * 8))
4646 in
4747 let x = get 0 and y = get 1 and z = get 2 and s = get 3 in
4848- let v = Kepler.Vec3.v x y z in
4949- let _ = Kepler.Vec3.length v in
5050- let _ = Kepler.Vec3.scale s v in
5151- let _ = Kepler.Vec3.add v v in
5252- let _ = Fmt.str "%a" Kepler.Vec3.pp v in
4848+ let v = Vec3.v x y z in
4949+ let _ = Vec3.length v in
5050+ let _ = Vec3.scale s v in
5151+ let _ = Vec3.add v v in
5252+ let _ = Fmt.str "%a" Vec3.pp v in
5353 ()
54545555let suite =
···45454646(** ISS-like circular orbit: analytic and RK4 should agree. *)
4747let test_iss_cross_check () =
4848- let pos = Kepler.Vec3.v 6778. 0. 0. in
4949- let vel = Kepler.Vec3.v 0. (sqrt (398600.4418 /. 6778.)) 0. in
4848+ let pos = Vec3.v 6778. 0. 0. in
4949+ let vel = Vec3.v 0. (sqrt (398600.4418 /. 6778.)) 0. in
5050 let dt = 2700. in
5151 (* 45 minutes *)
5252 let p_rk4 = Kepler.Propagate.at ~pos ~vel ~dt in
···59596060(** Molniya HEO: analytic and RK4 should agree within tolerance. *)
6161let test_molniya_cross_check () =
6262- let pos = Kepler.Vec3.v 6878. 0. 0. in
6363- let vel = Kepler.Vec3.v 0. 10.015 0. in
6262+ let pos = Vec3.v 6878. 0. 0. in
6363+ let vel = Vec3.v 0. 10.015 0. in
6464 let dt = 5400. in
6565 (* 90 minutes *)
6666 let p_rk4 = Kepler.Propagate.at ~pos ~vel ~dt in
···77777878(** After one full period, satellite returns to start. *)
7979let test_full_period_return () =
8080- let pos = Kepler.Vec3.v 6778. 0. 0. in
8181- let vel = Kepler.Vec3.v 0. (sqrt (398600.4418 /. 6778.)) 0. in
8080+ let pos = Vec3.v 6778. 0. 0. in
8181+ let vel = Vec3.v 0. (sqrt (398600.4418 /. 6778.)) 0. in
8282 let period = 2. *. Float.pi *. sqrt ((6778. ** 3.) /. 398600.4418) in
8383 let p = Kepler.Analytic.at ~pos ~vel ~dt:period in
8484 check_float "return x" 2. 6778. p.x;
···86868787(** At dt=0, position is unchanged. *)
8888let test_zero_dt () =
8989- let pos = Kepler.Vec3.v 7000. 1000. 3000. in
9090- let vel = Kepler.Vec3.v (-1.) 6.5 2. in
8989+ let pos = Vec3.v 7000. 1000. 3000. in
9090+ let vel = Vec3.v (-1.) 6.5 2. in
9191 let p = Kepler.Analytic.at ~pos ~vel ~dt:0. in
9292 check_float "dt=0 x" 1e-6 pos.x p.x;
9393 check_float "dt=0 y" 1e-6 pos.y p.y;
···95959696(** Precomputed elements give same result as direct. *)
9797let test_precomputed () =
9898- let pos = Kepler.Vec3.v 6778. 0. 0. in
9999- let vel = Kepler.Vec3.v 0. (sqrt (398600.4418 /. 6778.)) 0. in
9898+ let pos = Vec3.v 6778. 0. 0. in
9999+ let vel = Vec3.v 0. (sqrt (398600.4418 /. 6778.)) 0. in
100100 let el = Kepler.Analytic.precompute ~pos ~vel in
101101 let p1 = Kepler.Analytic.at ~pos ~vel ~dt:1800. in
102102 let p2 = Kepler.Analytic.at_precomputed el ~dt:1800. in
···106106107107(** Arc should produce correct number of points. *)
108108let test_arc_length () =
109109- let pos = Kepler.Vec3.v 7000. 0. 0. in
110110- let vel = Kepler.Vec3.v 0. 7.5 0. in
109109+ let pos = Vec3.v 7000. 0. 0. in
110110+ let vel = Vec3.v 0. 7.5 0. in
111111 let arc = Kepler.Analytic.arc ~pos ~vel ~duration_s:3600. ~num_points:42 in
112112 Alcotest.(check int) "arc length" 42 (Array.length arc)
113113114114(** Radius should be approximately constant for circular orbit. *)
115115let test_circular_radius () =
116116- let pos = Kepler.Vec3.v 6778. 0. 0. in
117117- let vel = Kepler.Vec3.v 0. (sqrt (398600.4418 /. 6778.)) 0. in
116116+ let pos = Vec3.v 6778. 0. 0. in
117117+ let vel = Vec3.v 0. (sqrt (398600.4418 /. 6778.)) 0. in
118118 let arc = Kepler.Analytic.arc ~pos ~vel ~duration_s:5400. ~num_points:100 in
119119 Array.iter
120120 (fun p ->
121121- let r = Kepler.Vec3.length p in
121121+ let r = Vec3.length p in
122122 Alcotest.(check (float 2.)) (Fmt.str "r=%.0f" r) 6778. r)
123123 arc
124124
+33-33
test/test_propagate.ml
···1818(* --- Energy conservation --- *)
19192020(** Specific orbital energy: E = v²/2 - μ/r. Must be conserved. *)
2121-let specific_energy (pos : Kepler.Vec3.t) (vel : Kepler.Vec3.t) =
2222- let r = Kepler.Vec3.length pos in
2323- let v = Kepler.Vec3.length vel in
2121+let specific_energy (pos : Vec3.t) (vel : Vec3.t) =
2222+ let r = Vec3.length pos in
2323+ let v = Vec3.length vel in
2424 (v *. v /. 2.) -. (Kepler.Propagate.mu /. r)
25252626(* --- Test vector 1: ISS-like circular LEO orbit ---
2727 From Vallado Example 2-4 (adapted):
2828 Circular orbit at ~408 km altitude.
2929 r = 6778 km, v = 7.669 km/s (circular velocity). *)
3030-let iss_pos = Kepler.Vec3.v 6778.0 0.0 0.0
3131-let iss_vel = Kepler.Vec3.v 0.0 7.669 0.0
3030+let iss_pos = Vec3.v 6778.0 0.0 0.0
3131+let iss_vel = Vec3.v 0.0 7.669 0.0
32323333let test_iss_energy_conservation () =
3434 let e0 = specific_energy iss_pos iss_vel in
···3737 ~num_points:100
3838 in
3939 (* Check that the last point has similar orbital radius (circular orbit) *)
4040- let r_last = Kepler.Vec3.length arc.(99) in
4141- let r_first = Kepler.Vec3.length arc.(0) in
4040+ let r_last = Vec3.length arc.(99) in
4141+ let r_first = Vec3.length arc.(0) in
4242 (* For a circular orbit, all points should be at ~same radius *)
4343 check_float "radius conservation" 50. r_first r_last;
4444 (* Energy at first/last points approximately matches *)
···4949 Perigee: ~500 km (r_p = 6878 km), Apogee: ~39,873 km (r_a = 46244 km)
5050 At perigee: v_p = sqrt(μ * (2/r_p - 1/a)) where a = (r_p + r_a)/2
5151 a = 26561 km, v_p = 10.015 km/s *)
5252-let molniya_pos = Kepler.Vec3.v 6878.0 0.0 0.0
5353-let molniya_vel = Kepler.Vec3.v 0.0 10.015 0.0
5252+let molniya_pos = Vec3.v 6878.0 0.0 0.0
5353+let molniya_vel = Vec3.v 0.0 10.015 0.0
54545555let test_molniya_energy_conservation () =
5656 let e0 = specific_energy molniya_pos molniya_vel in
···6363 (* The arc array gives positions; we need to recompute vel for energy.
6464 Instead, verify that the orbit reaches near-apogee altitude. *)
6565 let max_r =
6666- Array.fold_left (fun acc p -> Float.max acc (Kepler.Vec3.length p)) 0. arc
6666+ Array.fold_left (fun acc p -> Float.max acc (Vec3.length p)) 0. arc
6767 in
6868 (* Should reach at least 30000 km (partway to apogee) *)
6969 Alcotest.(check (float 1000.)) "molniya max radius" 37000. max_r;
···7373 From Curtis Example 2.12:
7474 GEO radius: 42164 km, circular velocity: 3.075 km/s
7575 Period: 86164 s (sidereal day). *)
7676-let geo_pos = Kepler.Vec3.v 42164.0 0.0 0.0
7777-let geo_vel = Kepler.Vec3.v 0.0 3.075 0.0
7676+let geo_pos = Vec3.v 42164.0 0.0 0.0
7777+let geo_vel = Vec3.v 0.0 3.075 0.0
78787979let test_geo_period () =
8080 (* Propagate one full period *)
···8484 in
8585 (* After one full period, should return near starting point *)
8686 let last = arc.(359) in
8787- let r_last = Kepler.Vec3.length last in
8787+ let r_last = Vec3.length last in
8888 check_float "GEO radius after 1 period" 200. 42164. r_last
89899090(* --- Test vector 4: Polar orbit ---
9191 Inclined orbit: position in XZ plane, velocity in Y direction.
9292 Sun-synchronous ~800 km: r = 7178 km, v = 7.452 km/s *)
9393-let polar_pos = Kepler.Vec3.v 7178.0 0.0 0.0
9494-let polar_vel = Kepler.Vec3.v 0.0 0.0 7.452
9393+let polar_pos = Vec3.v 7178.0 0.0 0.0
9494+let polar_vel = Vec3.v 0.0 0.0 7.452
95959696let test_polar_orbit () =
9797 let arc =
···103103 (* Actually with velocity in Z, the orbit plane is XZ, so Y≈0 *)
104104 let max_y =
105105 Array.fold_left
106106- (fun acc (p : Kepler.Vec3.t) -> Float.max acc (abs_float p.y))
106106+ (fun acc (p : Vec3.t) -> Float.max acc (abs_float p.y))
107107 0. arc
108108 in
109109 Alcotest.(check bool) "stays in XZ plane" true (max_y < 1.0)
···112112 Same as ISS but negative velocity: clockwise orbit.
113113 r = 6778 km, v = -7.669 km/s *)
114114let test_retrograde () =
115115- let pos = Kepler.Vec3.v 6778.0 0.0 0.0 in
116116- let vel = Kepler.Vec3.v 0.0 (-7.669) 0.0 in
115115+ let pos = Vec3.v 6778.0 0.0 0.0 in
116116+ let vel = Vec3.v 0.0 (-7.669) 0.0 in
117117 let arc = Kepler.Propagate.arc ~pos ~vel ~duration_s:5400. ~num_points:100 in
118118 (* Should orbit in opposite direction. After quarter period (~1350s),
119119 should be at ~(0, -6778, 0) instead of (0, 6778, 0) for prograde *)
···124124 From BMW: escape velocity at 6678 km = sqrt(2μ/r) = 10.926 km/s
125125 With v > v_escape, object should fly away indefinitely. *)
126126let test_escape () =
127127- let pos = Kepler.Vec3.v 6678.0 0.0 0.0 in
128128- let vel = Kepler.Vec3.v 0.0 12.0 0.0 in
127127+ let pos = Vec3.v 6678.0 0.0 0.0 in
128128+ let vel = Vec3.v 0.0 12.0 0.0 in
129129 (* > escape velocity *)
130130 let e = specific_energy pos vel in
131131 Alcotest.(check (float 1.0)) "hyperbolic energy" 12.3 e;
132132 let arc = Kepler.Propagate.arc ~pos ~vel ~duration_s:36000. ~num_points:100 in
133133 (* Distance should monotonically increase in forward direction *)
134134- let r_last = Kepler.Vec3.length arc.(99) in
135135- let r_mid = Kepler.Vec3.length arc.(75) in
134134+ let r_last = Vec3.length arc.(99) in
135135+ let r_mid = Vec3.length arc.(75) in
136136 Alcotest.(check bool) "escaping: monotonic" true (r_last > r_mid);
137137 Alcotest.(check bool) "escaping: r_mid far from Earth" true (r_mid > 20000.)
138138···142142 This is a standard test case from Vallado for coordinate transforms
143143 and orbit determination. We verify energy conservation. *)
144144let test_vallado_ex1 () =
145145- let pos = Kepler.Vec3.v 6524.834 6862.875 6448.296 in
146146- let vel = Kepler.Vec3.v 4.901327 5.533756 (-1.976341) in
145145+ let pos = Vec3.v 6524.834 6862.875 6448.296 in
146146+ let vel = Vec3.v 4.901327 5.533756 (-1.976341) in
147147 let e0 = specific_energy pos vel in
148148 Alcotest.(check bool) "bound orbit" true (e0 < 0.);
149149 let arc = Kepler.Propagate.arc ~pos ~vel ~duration_s:7200. ~num_points:100 in
150150 (* Orbit should stay within reasonable bounds *)
151151 let max_r =
152152- Array.fold_left (fun acc p -> Float.max acc (Kepler.Vec3.length p)) 0. arc
152152+ Array.fold_left (fun acc p -> Float.max acc (Vec3.length p)) 0. arc
153153 in
154154 (* High velocity → large apogee; just verify finite positions *)
155155 Alcotest.(check bool) "max radius finite" true (Float.is_finite max_r)
···159159let test_near_parabolic () =
160160 let r = 6678. in
161161 let v_esc = sqrt (2. *. Kepler.Propagate.mu /. r) in
162162- let pos = Kepler.Vec3.v r 0. 0. in
163163- let vel = Kepler.Vec3.v 0. (v_esc -. 0.001) 0. in
162162+ let pos = Vec3.v r 0. 0. in
163163+ let vel = Vec3.v 0. (v_esc -. 0.001) 0. in
164164 let e = specific_energy pos vel in
165165 Alcotest.(check bool) "near-zero energy" true (abs_float e < 0.1);
166166 let arc = Kepler.Propagate.arc ~pos ~vel ~duration_s:36000. ~num_points:50 in
167167 (* Should still produce valid positions *)
168168 Array.iter
169169- (fun (p : Kepler.Vec3.t) ->
170170- let r = Kepler.Vec3.length p in
169169+ (fun (p : Vec3.t) ->
170170+ let r = Vec3.length p in
171171 Alcotest.(check bool) "above surface" true (r > 6000.))
172172 arc
173173···175175 Verify that propagation is time-reversible:
176176 propagate forward then backward should return to start. *)
177177let test_time_reversibility () =
178178- let pos = Kepler.Vec3.v 8000.0 0.0 6000.0 in
179179- let vel = Kepler.Vec3.v 0.0 (-5.0) 6.0 in
178178+ let pos = Vec3.v 8000.0 0.0 6000.0 in
179179+ let vel = Vec3.v 0.0 (-5.0) 6.0 in
180180 (* Forward 3600s *)
181181 let fwd = Kepler.Propagate.arc ~pos ~vel ~duration_s:7200. ~num_points:100 in
182182 (* The midpoint (index 50) should be close to the initial position *)
···189189190190(* --- Test vector 10: Array length --- *)
191191let test_arc_length () =
192192- let pos = Kepler.Vec3.v 7000.0 0.0 0.0 in
193193- let vel = Kepler.Vec3.v 0.0 7.5 0.0 in
192192+ let pos = Vec3.v 7000.0 0.0 0.0 in
193193+ let vel = Vec3.v 0.0 7.5 0.0 in
194194 let arc = Kepler.Propagate.arc ~pos ~vel ~duration_s:3600. ~num_points:42 in
195195 Alcotest.(check int) "arc length" 42 (Array.length arc)
196196
+9-9
test/test_vec3.ml
···33let check_float msg expected actual =
44 Alcotest.(check (float eps)) msg expected actual
5566-let check_vec3 msg (ex, ey, ez) (v : Kepler.Vec3.t) =
66+let check_vec3 msg (ex, ey, ez) (v : Vec3.t) =
77 check_float (msg ^ ".x") ex v.x;
88 check_float (msg ^ ".y") ey v.y;
99 check_float (msg ^ ".z") ez v.z
10101111let test_add () =
1212- let a = Kepler.Vec3.v 1. 2. 3. in
1313- let b = Kepler.Vec3.v 4. 5. 6. in
1414- check_vec3 "add" (5., 7., 9.) (Kepler.Vec3.add a b)
1212+ let a = Vec3.v 1. 2. 3. in
1313+ let b = Vec3.v 4. 5. 6. in
1414+ check_vec3 "add" (5., 7., 9.) (Vec3.add a b)
15151616let test_scale () =
1717- let v = Kepler.Vec3.v 1. 2. 3. in
1818- check_vec3 "scale" (2., 4., 6.) (Kepler.Vec3.scale 2. v)
1717+ let v = Vec3.v 1. 2. 3. in
1818+ check_vec3 "scale" (2., 4., 6.) (Vec3.scale 2. v)
19192020let test_length () =
2121- let v = Kepler.Vec3.v 3. 4. 0. in
2222- check_float "length" 5.0 (Kepler.Vec3.length v)
2121+ let v = Vec3.v 3. 4. 0. in
2222+ check_float "length" 5.0 (Vec3.length v)
23232424-let test_zero () = check_vec3 "zero" (0., 0., 0.) Kepler.Vec3.zero
2424+let test_zero () = check_vec3 "zero" (0., 0., 0.) Vec3.zero
25252626let suite =
2727 ( "vec3",