korba 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d032139625c6722891cef771fc76ffb90e8b082f89146ebb860a5f8034004b15
4
- data.tar.gz: 354e2f0f7c382059bf7a424ffd952c32b56b6ae616b5c7bd42f50a148d2f42c3
3
+ metadata.gz: 82d326cac0ea342432bbb082f3abf220a6104073c070bc9c634bab9bb981c388
4
+ data.tar.gz: 0254dd92e20901966e424acf29a1c1cdc4c44128499e2c85b3fac3cb10fe89f9
5
5
  SHA512:
6
- metadata.gz: 6f73a3a19efff8c89299173d67dbfa1aaf754ce1c0828148f7bd3933cd57d8de8ba74352cc12849fcd405882c964f345353e1f6d4204b4e451d00a8a6ebf5337
7
- data.tar.gz: 293456b12a49007bb1f1c1acc6201fcf99678e5427b1c81942d9f1e53ab5c9cc8716ffdeaaa562846c45ef2b245400f7d73758a05494da91df1528da9e06cba0
6
+ metadata.gz: a8c434b74a17a64f5ad4367a41391ec85345021f1b1de5ad741b9632a49ae92d123eb8aac9dd22e262a01953012862862a396fe9e5b6429dd5a126412d813bc4
7
+ data.tar.gz: f6e6c90bc931c98854823baa0089b6dff556b70def4a1a0d72f1544e633cc9745097506b00f422812ddbb4f598d640df53d18dfd6576145a2b19361cdc75270c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.9.0] - 2026-05-04
2
+
3
+ - Enable orbit propagation with Keplerian elements
4
+
5
+ ## [0.8.0] - 2026-04-19
6
+
7
+ - Enable orbit propagation using 4th-order Runge-Kutta
8
+ - Enable conversion from Cartesian to Keplerian elements
9
+
1
10
  ## [0.7.1] - 2026-04-05
2
11
 
3
12
  - FIX: Fixed calculation when orbital inclination and eccentricity are 0
data/lib/korba/car.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Korba
4
4
  class Car
5
+ include OrbitUtils
6
+
5
7
  attr_reader :object_name, :epoch, :x, :y, :z, :vx, :vy, :vz
6
8
 
7
9
  def initialize(object_name:, epoch:, x:, y:, z:, vx:, vy:, vz:)
@@ -14,5 +16,85 @@ module Korba
14
16
  @vy = vy
15
17
  @vz = vz
16
18
  end
19
+
20
+ def to_kep
21
+ inclination = rad_to_deg(Math.atan2(Math.sqrt(specific_angular_momentum_vector[0] ** 2 + specific_angular_momentum_vector[1] ** 2), specific_angular_momentum_vector[2]))
22
+ ra_of_asc_node = rad_to_deg(Math.atan2(specific_angular_momentum_vector[0], -specific_angular_momentum_vector[1]))
23
+
24
+ Kep.new(object_name:,
25
+ epoch:,
26
+ semi_major_axis:,
27
+ eccentricity:,
28
+ inclination:,
29
+ ra_of_asc_node:,
30
+ arg_of_pericenter: normalize_deg(arg_of_latitude - true_anomaly),
31
+ mean_anomaly: rad_to_deg(deg_to_rad(eccentric_anomaly) - eccentricity * Math.sin(deg_to_rad(eccentric_anomaly))))
32
+ end
33
+
34
+ private
35
+
36
+ def position_vector
37
+ @position_vector ||= Vector[x, y, z]
38
+ end
39
+
40
+ def velocity_vector
41
+ @velocity_vector ||= Vector[vx, vy, vz]
42
+ end
43
+
44
+ def distance
45
+ @distance ||= position_vector.norm
46
+ end
47
+
48
+ def velocity
49
+ @velocity ||= velocity_vector.norm
50
+ end
51
+
52
+ def semi_major_axis
53
+ @semi_major_axis ||= Constant::GME * distance / (2 * Constant::GME - distance * velocity ** 2)
54
+ end
55
+
56
+ def eccentricity
57
+ @eccentricity ||= Math.sqrt(((semi_major_axis - distance) / semi_major_axis) ** 2 + (position_vector.dot(velocity_vector) ** 2) / (Constant::GME * semi_major_axis))
58
+ end
59
+
60
+ def eccentric_anomaly
61
+ @eccentric_anomaly ||= rad_to_deg(Math.atan2(Math.sqrt(semi_major_axis / Constant::GME) * position_vector.dot(velocity_vector), semi_major_axis - distance))
62
+ end
63
+
64
+ def true_anomaly
65
+ factor = (Math.cos(deg_to_rad(eccentric_anomaly)) - eccentricity) / (1 - eccentricity * Math.cos(deg_to_rad(eccentric_anomaly)))
66
+ rad_to_deg(Math.atan2(Math.sqrt(1 - factor ** 2), factor))
67
+ end
68
+
69
+ def angular_momentum_vector
70
+ @angular_momentum_vector ||= position_vector.cross(velocity_vector)
71
+ end
72
+
73
+ def raan_vector
74
+ @raan_vector ||= Vector[0, 0, 1].cross(angular_momentum_vector)
75
+ end
76
+
77
+ def eccentricity_vector
78
+ @eccentricity_vector ||= ((velocity ** 2 - Constant::GME / distance) * position_vector - (position_vector * velocity_vector) * velocity_vector) / Constant::GME
79
+ end
80
+
81
+ def specific_angular_momentum_vector
82
+ @specific_angular_momentum_vector ||= position_vector.cross(velocity_vector)
83
+ end
84
+
85
+ def raan_vector
86
+ @raan_vector ||= Vector[0, 0, 1].cross(specific_angular_momentum_vector)
87
+ end
88
+
89
+ def arg_of_latitude
90
+ y = position_vector.dot(raan_vector)
91
+ x = position_vector.norm * raan_vector.norm
92
+ arg_of_latitude = (Math.atan2(
93
+ Math.sqrt(1 - (y / x) ** 2),
94
+ y / x
95
+ ))
96
+ arg_of_latitude = 2 * Math::PI - arg_of_latitude if z < 0
97
+ rad_to_deg(arg_of_latitude)
98
+ end
17
99
  end
18
100
  end
@@ -5,6 +5,7 @@ module Korba
5
5
  # geocentric gravitational constant in m3/s2
6
6
  GME = 3.9860044188e14
7
7
  # nominal equatorial Earth radius in m
8
- EARTH_RADIUS = 6378137
8
+ EARTH_RADIUS = 6378137.0
9
+ J2 = 1.08262668e-3
9
10
  end
10
11
  end
data/lib/korba/kep.rb CHANGED
@@ -42,6 +42,14 @@ module Korba
42
42
  v_angle_factor = deg_to_rad(arg_of_pericenter + true_anomaly - path_angle)
43
43
  vector_v = velocity * (-Math.sin(v_angle_factor) * vector_omega + (Math.cos(v_angle_factor)) * vector_m)
44
44
 
45
+ # NOTE: こっちでもいい
46
+ # factor_matrix = Matrix[[Math.cos(deg_to_rad(ra_of_asc_node)), -Math.sin(deg_to_rad(ra_of_asc_node)) * Math.cos(deg_to_rad(inclination))],
47
+ # [Math.sin(deg_to_rad(ra_of_asc_node)), Math.cos(deg_to_rad(ra_of_asc_node)) * Math.cos(deg_to_rad(inclination))],
48
+ # [0, Math.sin(deg_to_rad(inclination))]] *
49
+ # Matrix[[Math.cos(deg_to_rad(arg_of_pericenter)), -Math.sin(deg_to_rad(arg_of_pericenter))], [Math.sin(deg_to_rad(arg_of_pericenter)), Math.cos(deg_to_rad(arg_of_pericenter))]]
50
+ # vector_r = Vector[*(factor_matrix * Matrix[[Math.cos(deg_to_rad(true_anomaly)) - eccentricity], [Math.sqrt(1 - eccentricity ** 2) * Math.sin(deg_to_rad(true_anomaly))]] * semi_major_axis).to_a.flatten]
51
+ # vector_v = Vector[*(factor_matrix * Matrix[[-Math.sin(deg_to_rad(true_anomaly))], [Math.sqrt(1 - eccentricity ** 2) * Math.cos(deg_to_rad(true_anomaly))]] * Math.sqrt(Constant::GME * semi_major_axis) / vector_r.norm).to_a.flatten]
52
+
45
53
  Car.new(object_name:, epoch:, x: vector_r[0], y: vector_r[1], z: vector_r[2], vx: vector_v[0], vy: vector_v[1], vz: vector_v[2])
46
54
  end
47
55
  end
@@ -7,6 +7,10 @@ module Korba
7
7
  (Constant::GME / (mean_motion * 2.0 * Math::PI / 86400.0) ** 2.0) ** (1.0 / 3.0)
8
8
  end
9
9
 
10
+ def period
11
+ 2.0 * Math::PI * Math.sqrt(semi_major_axis ** 3 / Constant::GME)
12
+ end
13
+
10
14
  def height_at_perigee
11
15
  semi_major_axis * (1 - eccentricity) - Constant::EARTH_RADIUS
12
16
  end
@@ -50,10 +54,7 @@ module Korba
50
54
  end
51
55
 
52
56
  def normalize_rad(rad)
53
- rad = rad + 2.0 * Math::PI if rad < 0
54
- normalize_rad = rad > 2.0 * Math::PI ? rad - 2.0 * Math::PI : rad
55
- normalize_rad(normalize_rad) if normalize_rad != rad
56
- normalize_rad
57
+ rad % (2.0 * Math::PI)
57
58
  end
58
59
 
59
60
  def rad_to_deg(rad)
@@ -62,10 +63,7 @@ module Korba
62
63
  end
63
64
 
64
65
  def normalize_deg(deg)
65
- deg = deg + 360.0 if deg < 0
66
- normalized_deg = deg > 360.0 ? deg - 360.0 : deg
67
- normalize_deg(normalized_deg) if normalized_deg != deg
68
- normalized_deg
66
+ deg % 360.0
69
67
  end
70
68
  end
71
69
  end
@@ -0,0 +1,54 @@
1
+ module Korba
2
+ module Propagator
3
+ class Kepler
4
+ include OrbitUtils
5
+
6
+ def initialize(initial_kep, disable_j2: false)
7
+ @initial_kep = initial_kep
8
+ @disable_j2 = disable_j2
9
+ end
10
+
11
+ def propagate(seconds_after_epoch)
12
+ mean_motion = Math.sqrt(Constant::GME / @initial_kep.semi_major_axis ** 3)
13
+ mean_anomaly = rad_to_deg(deg_to_rad(@initial_kep.mean_anomaly) + mean_motion * seconds_after_epoch)
14
+
15
+ Kep.new(
16
+ object_name: @initial_kep.object_name,
17
+ epoch: @initial_kep.epoch + seconds_after_epoch,
18
+ semi_major_axis: @initial_kep.semi_major_axis,
19
+ eccentricity: @initial_kep.eccentricity,
20
+ inclination: @initial_kep.inclination,
21
+ ra_of_asc_node: ra_of_asc_node(seconds_after_epoch),
22
+ arg_of_pericenter: arg_of_pericenter(seconds_after_epoch),
23
+ mean_anomaly: mean_anomaly,
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def ra_of_asc_node(seconds_after_epoch)
30
+ return @initial_kep.ra_of_asc_node if @disable_j2
31
+
32
+ rad_to_deg(deg_to_rad(@initial_kep.ra_of_asc_node) + ra_of_asc_node_dot_radian * seconds_after_epoch)
33
+ end
34
+
35
+ def ra_of_asc_node_dot_radian
36
+ -(3 * Math::PI * Constant::J2 *
37
+ (Constant::EARTH_RADIUS / @initial_kep.semi_major_axis * (1 - @initial_kep.eccentricity ** 2)) ** 2 *
38
+ Math.cos(deg_to_rad(@initial_kep.inclination))) / @initial_kep.period
39
+ end
40
+
41
+ def arg_of_pericenter(seconds_after_epoch)
42
+ return @initial_kep.arg_of_pericenter if @disable_j2
43
+
44
+ rad_to_deg(deg_to_rad(@initial_kep.arg_of_pericenter) + arg_of_pericenter_dot_radian * seconds_after_epoch)
45
+ end
46
+
47
+ def arg_of_pericenter_dot_radian
48
+ (3 * Math::PI / 2.0 * Constant::J2 *
49
+ (Constant::EARTH_RADIUS / @initial_kep.semi_major_axis * (1 - @initial_kep.eccentricity ** 2)) ** 2 *
50
+ (5 * (Math.cos(deg_to_rad(@initial_kep.inclination)) ** 2) - 1)) / @initial_kep.period
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ module Korba
2
+ module Propagator
3
+ class Rk4
4
+ def initialize(initial_car, disable_j2: false)
5
+ @initial_car = initial_car
6
+ @disable_j2 = disable_j2
7
+ end
8
+
9
+ def propagate(seconds_after_epoch, dt = 1.0)
10
+ steps = (seconds_after_epoch / dt).to_i
11
+ t = 0.0
12
+ y = Vector[@initial_car.x, @initial_car.y, @initial_car.z, @initial_car.vx, @initial_car.vy, @initial_car.vz]
13
+
14
+ steps.times do
15
+ y = step(t, y, dt)
16
+ t += dt
17
+ end
18
+
19
+ Car.new(
20
+ object_name: @initial_car.object_name,
21
+ epoch: @initial_car.epoch + seconds_after_epoch,
22
+ x: y[0],
23
+ y: y[1],
24
+ z: y[2],
25
+ vx: y[3],
26
+ vy: y[4],
27
+ vz: y[5],
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ def step(t, y, dt)
34
+ k1 = f(t, y)
35
+ k2 = f(t + dt / 2.0, y + k1 * (dt / 2.0))
36
+ k3 = f(t + dt / 2.0, y + k2 * (dt / 2.0))
37
+ k4 = f(t + dt, y + k3 * dt)
38
+
39
+ y + (k1 + 2 * k2 + 2 * k3 + k4) * (dt / 6.0)
40
+ end
41
+
42
+ def f(t, y)
43
+ position_vector = Vector[y[0], y[1], y[2]]
44
+ velocity_vector = Vector[y[3], y[4], y[5]]
45
+ r = position_vector.magnitude
46
+ base_acceleration_vector = -Constant::GME * position_vector / (r ** 3)
47
+ acceleration_vector = @disable_j2 ? base_acceleration_vector : add_j2_effect(base_acceleration_vector, r, position_vector[2])
48
+
49
+ Vector[velocity_vector[0], velocity_vector[1], velocity_vector[2], acceleration_vector[0], acceleration_vector[1], acceleration_vector[2]]
50
+ end
51
+
52
+ def add_j2_effect(base_acceleration_vector, r, z)
53
+ j2_factor = (3.0 / 2.0) * Constant::J2 * (Constant::EARTH_RADIUS / r) ** 2
54
+ factor = 5 * (z / r) ** 2
55
+ [
56
+ base_acceleration_vector[0] * (1 + j2_factor * (1 - factor)),
57
+ base_acceleration_vector[1] * (1 + j2_factor * (1 - factor)),
58
+ base_acceleration_vector[2] * (1 + j2_factor * (3 - factor)),
59
+ ]
60
+ end
61
+ end
62
+ end
63
+ end