ephem 0.3.1 → 0.4.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/ephem/computation/chebyshev_polynomial.rb +73 -176
- data/lib/ephem/download.rb +40 -22
- data/lib/ephem/segments/segment.rb +30 -30
- data/lib/ephem/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87a0725b85c4ffbedd33b54f3780202c5e43aef7e7a710658df52a2e187f2dcb
|
4
|
+
data.tar.gz: f0d1aeccf6731b4d34344c80dd652e7cf012719728a97d3e86463fe5e98dbea7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ecffd718100afae3bb1c69e545f36c25c71aad4e9a75d766189a411afa92c1e3f77eda60a02ea17102e57a7d3a9c730b0728e5189ed9cbf2981201cff8bb3e4
|
7
|
+
data.tar.gz: e7ff31012dc8f1ffdf38118fc195ffc8849146c690132e9b6b48766456848cda001d11825c687fe0e58fac38a14ca93dcaee6c6ad4af75c1caf8cec9329704d1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [0.4.0] - 2025-06-09
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* Improve Chebyshev polynomial performance ([#33])
|
8
|
+
* Improve download file management ([#34])
|
9
|
+
* Validate against all kernels and date ranges ([#36])
|
10
|
+
* Add supported Ruby versions ([#35])
|
11
|
+
* Bump rspec from 3.13.0 to 3.13.1 by @dependabot ([#38])
|
12
|
+
* Bump rake from 13.2.1 to 13.3.0 by @dependabot ([#39])
|
13
|
+
* Bump csv from 3.3.4 to 3.3.5 by @dependabot ([#40])
|
14
|
+
|
15
|
+
[#33]: https://github.com/rhannequin/ruby-ephem/pull/33
|
16
|
+
[#34]: https://github.com/rhannequin/ruby-ephem/pull/34
|
17
|
+
[#35]: https://github.com/rhannequin/ruby-ephem/pull/35
|
18
|
+
[#36]: https://github.com/rhannequin/ruby-ephem/pull/36
|
19
|
+
[#38]: https://github.com/rhannequin/ruby-ephem/pull/38
|
20
|
+
[#39]: https://github.com/rhannequin/ruby-ephem/pull/39
|
21
|
+
[#40]: https://github.com/rhannequin/ruby-ephem/pull/40
|
22
|
+
|
23
|
+
**Full Changelog**: https://github.com/rhannequin/ruby-ephem/compare/v0.3.1...v0.4.0
|
24
|
+
|
3
25
|
## [0.3.1] - 2025-05-16
|
4
26
|
|
5
27
|
### Bug fixes
|
@@ -1,192 +1,89 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "numo/narray"
|
4
|
-
|
5
3
|
module Ephem
|
6
4
|
module Computation
|
7
|
-
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# Chebyshev polynomials are mathematical functions that can be used to
|
11
|
-
# approximate other functions with high accuracy. In astronomical
|
12
|
-
# calculations, they are used to approximate positions and velocities of
|
13
|
-
# celestial bodies.
|
14
|
-
#
|
15
|
-
# The polynomial evaluation is done using the Clenshaw algorithm, which is
|
16
|
-
# numerically stable and efficient. For performance optimization, polynomial
|
17
|
-
# values are cached when they need to be used both for position and velocity
|
18
|
-
# calculations.
|
19
|
-
#
|
20
|
-
# @example Calculating position in 3D space
|
21
|
-
# # coefficients is a 2D array where:
|
22
|
-
# # - First dimension is the polynomial degree (n terms)
|
23
|
-
# # - Second dimension is the spatial dimension (3 for x,y,z)
|
24
|
-
# coefficients = Numo::DFloat.cast([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
|
25
|
-
#
|
26
|
-
# # normalized_time must be between -1 and 1
|
27
|
-
# polynomial = ChebyshevPolynomial.new(
|
28
|
-
# coefficients: coefficients,
|
29
|
-
# normalized_time: 0.5
|
30
|
-
# )
|
31
|
-
#
|
32
|
-
# position = polynomial.evaluate
|
5
|
+
##
|
6
|
+
# High-performance, three-dimensional Clenshaw evaluation and derivative
|
7
|
+
# evaluation for Chebyshev polynomials, as used in SPK ephemerides.
|
33
8
|
#
|
34
|
-
# @
|
35
|
-
#
|
36
|
-
# polynomial = ChebyshevPolynomial.new(
|
37
|
-
# coefficients: coefficients,
|
38
|
-
# normalized_time: 0.5,
|
39
|
-
# radius: 32.0 # 32-day time span
|
40
|
-
# )
|
9
|
+
# @see https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/cheby.html
|
10
|
+
# @see https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/FORTRAN/spicelib/spkche.html
|
41
11
|
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
# Initializes a new Chebyshev polynomial calculator
|
47
|
-
#
|
48
|
-
# @param coefficients [Numo::NArray] 2D array of Chebyshev polynomial
|
49
|
-
# coefficients. First dimension represents polynomial terms
|
50
|
-
# (degree + 1). Second dimension represents spatial components
|
51
|
-
# (usually 3 for x,y,z)
|
52
|
-
# @param normalized_time [Float] Time parameter normalized to [-1, 1]
|
53
|
-
# interval
|
54
|
-
# @param radius [Float, nil] Optional scaling factor for derivative
|
55
|
-
# calculations, usually represents the time span of the interval in days
|
56
|
-
#
|
57
|
-
# @raise [Ephem::InvalidInputError] if coefficients are not a 2D
|
58
|
-
# Numo::NArray
|
59
|
-
# @raise [Ephem::InvalidInputError] if normalized_time is outside [-1, 1]
|
60
|
-
def initialize(coefficients:, normalized_time:, radius: nil)
|
61
|
-
validate_inputs(coefficients, normalized_time)
|
62
|
-
|
63
|
-
@coefficients = coefficients
|
64
|
-
@normalized_time = normalized_time
|
65
|
-
@radius = radius
|
66
|
-
@degree = @coefficients.shape[0]
|
67
|
-
@dimension = @coefficients.shape[1]
|
68
|
-
@two_times_normalized_time = 2.0 * @normalized_time
|
69
|
-
@polynomials = nil # Cache for polynomial values
|
70
|
-
end
|
71
|
-
|
72
|
-
# Evaluates the Chebyshev polynomial at the normalized time point
|
73
|
-
#
|
74
|
-
# Uses the Clenshaw algorithm for numerical stability. The algorithm
|
75
|
-
# evaluates the polynomial using a recurrence relation, which is more
|
76
|
-
# stable than direct power series evaluation.
|
77
|
-
#
|
78
|
-
# @return [Numo::DFloat] Evaluation result, array of size dimension
|
79
|
-
# (usually 3)
|
80
|
-
def evaluate
|
81
|
-
@polynomials ||= generate_polynomials(@degree, @dimension)
|
82
|
-
combine_polynomials(@polynomials)
|
83
|
-
end
|
84
|
-
|
85
|
-
# Calculates the derivative of the Chebyshev polynomial
|
86
|
-
#
|
87
|
-
# For astronomical calculations, this typically represents velocity.
|
88
|
-
# For polynomials of degree < 2, returns zero array since the derivative
|
89
|
-
# of constants and linear terms are constant or zero.
|
12
|
+
module ChebyshevPolynomial
|
13
|
+
##
|
14
|
+
# Evaluates a 3D Chebyshev polynomial at a given normalized time.
|
90
15
|
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
# @return [
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
16
|
+
# @param coeffs [Array<Array<Float>>] Array of coefficients; shape is
|
17
|
+
# [n_terms][3].
|
18
|
+
# @param t [Float] The normalized independent variable, in [-1, 1].
|
19
|
+
# @return [Array<Float>] The 3-vector result at t: [x, y, z]
|
20
|
+
def self.evaluate(coeffs, t)
|
21
|
+
n = coeffs.size
|
22
|
+
b1x = b1y = b1z = 0.0
|
23
|
+
b2x = b2y = b2z = 0.0
|
24
|
+
|
25
|
+
k = n - 1
|
26
|
+
while k > 0
|
27
|
+
c = coeffs[k]
|
28
|
+
c0 = c[0]
|
29
|
+
c1 = c[1]
|
30
|
+
c2 = c[2]
|
31
|
+
t2 = 2.0 * t
|
32
|
+
tx = t2 * b1x - b2x + c0
|
33
|
+
ty = t2 * b1y - b2y + c1
|
34
|
+
tz = t2 * b1z - b2z + c2
|
35
|
+
b2x = b1x
|
36
|
+
b2y = b1y
|
37
|
+
b2z = b1z
|
38
|
+
b1x = tx
|
39
|
+
b1y = ty
|
40
|
+
b1z = tz
|
41
|
+
k -= 1
|
108
42
|
end
|
109
43
|
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# Generates the sequence of Chebyshev polynomials
|
116
|
-
# Uses the recurrence relation for Chebyshev polynomials:
|
117
|
-
# T₀(x) = 1
|
118
|
-
# T₁(x) = x
|
119
|
-
# Tₙ₊₁(x) = 2xTₙ(x) - Tₙ₋₁(x)
|
120
|
-
def generate_polynomials(degree, dimension)
|
121
|
-
polynomials = initialize_base_polynomials(dimension)
|
122
|
-
return polynomials if degree <= 2
|
123
|
-
|
124
|
-
build_polynomial_sequence(polynomials, degree)
|
125
|
-
polynomials
|
126
|
-
end
|
127
|
-
|
128
|
-
# Initializes T₀(x) = 1 and T₁(x) = x polynomials
|
129
|
-
def initialize_base_polynomials(dimension)
|
130
|
-
[
|
131
|
-
Numo::DFloat.ones(dimension),
|
132
|
-
@normalized_time * Numo::DFloat.ones(dimension)
|
133
|
-
]
|
134
|
-
end
|
135
|
-
|
136
|
-
def build_polynomial_sequence(polynomials, degree)
|
137
|
-
(2...degree).each do |i|
|
138
|
-
polynomials << compute_next_polynomial(
|
139
|
-
polynomials[i - 2],
|
140
|
-
polynomials[i - 1]
|
141
|
-
)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def compute_next_polynomial(prev_prev, prev)
|
146
|
-
@two_times_normalized_time * prev - prev_prev
|
44
|
+
c0, c1, c2 = coeffs[0]
|
45
|
+
[t * b1x - b2x + c0, t * b1y - b2y + c1, t * b1z - b2z + c2]
|
147
46
|
end
|
148
47
|
|
149
|
-
|
150
|
-
#
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
#
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
48
|
+
##
|
49
|
+
# Evaluates the time derivative of a 3D Chebyshev polynomial at a given
|
50
|
+
# normalized time.
|
51
|
+
#
|
52
|
+
# @param coeffs [Array<Array<Float>>] Array of coefficients; shape is
|
53
|
+
# [n_terms][3].
|
54
|
+
# @param t [Float] The normalized independent variable (in [-1, 1]).
|
55
|
+
# @param radius [Float] The half-length of the time interval (days).
|
56
|
+
# @return [Array<Float>] The 3-vector derivative (velocity), in units per
|
57
|
+
# second.
|
58
|
+
def self.evaluate_derivative(coeffs, t, radius)
|
59
|
+
n = coeffs.size
|
60
|
+
return [0.0, 0.0, 0.0] if n < 2
|
61
|
+
|
62
|
+
d1x = d1y = d1z = 0.0
|
63
|
+
d2x = d2y = d2z = 0.0
|
64
|
+
|
65
|
+
k = n - 1
|
66
|
+
while k > 0
|
67
|
+
c = coeffs[k]
|
68
|
+
c0 = c[0]
|
69
|
+
c1 = c[1]
|
70
|
+
c2 = c[2]
|
71
|
+
t2 = 2.0 * t
|
72
|
+
k2 = 2 * k
|
73
|
+
tx = t2 * d1x - d2x + k2 * c0
|
74
|
+
ty = t2 * d1y - d2y + k2 * c1
|
75
|
+
tz = t2 * d1z - d2z + k2 * c2
|
76
|
+
d2x = d1x
|
77
|
+
d2y = d1y
|
78
|
+
d2z = d1z
|
79
|
+
d1x = tx
|
80
|
+
d1y = ty
|
81
|
+
d1z = tz
|
82
|
+
k -= 1
|
175
83
|
end
|
176
84
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
def calculate_derivative_term(index, deriv, deriv_next)
|
181
|
-
@two_times_normalized_time * deriv -
|
182
|
-
deriv_next +
|
183
|
-
@coefficients[index, true] * 2 * index
|
184
|
-
end
|
185
|
-
|
186
|
-
def scale_derivative(derivative)
|
187
|
-
return derivative unless @radius
|
188
|
-
|
189
|
-
derivative * (SECONDS_PER_DAY / (2.0 * @radius))
|
85
|
+
scale = Ephem::Core::Constants::Time::SECONDS_PER_DAY / (2.0 * radius)
|
86
|
+
[d1x * scale, d1y * scale, d1z * scale]
|
190
87
|
end
|
191
88
|
end
|
192
89
|
end
|
data/lib/ephem/download.rb
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
require "minitar"
|
4
4
|
require "net/http"
|
5
|
+
require "pathname"
|
5
6
|
require "tempfile"
|
6
7
|
require "zlib"
|
8
|
+
require "fileutils"
|
7
9
|
|
8
10
|
module Ephem
|
9
11
|
class Download
|
@@ -94,16 +96,15 @@ module Ephem
|
|
94
96
|
new(name, target).call
|
95
97
|
end
|
96
98
|
|
97
|
-
def initialize(name,
|
99
|
+
def initialize(name, target_path)
|
98
100
|
@name = name
|
99
|
-
@
|
101
|
+
@target_path = Pathname.new(target_path)
|
100
102
|
validate_requested_kernel!
|
101
103
|
end
|
102
104
|
|
103
105
|
def call
|
104
|
-
|
105
|
-
|
106
|
-
|
106
|
+
FileUtils.mkdir_p(@target_path.dirname)
|
107
|
+
jpl_kernel? ? download_jpl : download_imcce
|
107
108
|
true
|
108
109
|
end
|
109
110
|
|
@@ -120,28 +121,45 @@ module Ephem
|
|
120
121
|
JPL_KERNELS.include?(@name)
|
121
122
|
end
|
122
123
|
|
123
|
-
def
|
124
|
-
uri = URI(
|
125
|
-
|
124
|
+
def download_jpl
|
125
|
+
uri = URI.join(JPL_BASE_URL, @name)
|
126
|
+
@target_path.open("wb") do |file|
|
127
|
+
stream_http_to_file(uri, file)
|
128
|
+
end
|
126
129
|
end
|
127
130
|
|
128
|
-
def
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
131
|
+
def download_imcce
|
132
|
+
tar_gz_uri = URI.join(IMCCE_BASE_URL, IMCCE_KERNELS_MATCHING[@name])
|
133
|
+
Tempfile.create(%w[archive .tar.gz]) do |temp_file|
|
134
|
+
stream_http_to_file(tar_gz_uri, temp_file)
|
135
|
+
temp_file.flush
|
136
|
+
temp_file.rewind
|
137
|
+
|
138
|
+
Zlib::GzipReader.open(temp_file.path) do |gz|
|
139
|
+
Minitar::Reader.open(gz) do |tar|
|
140
|
+
tar.each_entry do |entry|
|
141
|
+
next unless entry.full_name == IMCCE_KERNELS[@name]
|
142
|
+
|
143
|
+
@target_path.open("wb") { |file| file.write(entry.read) }
|
144
|
+
|
145
|
+
break
|
146
|
+
end
|
139
147
|
end
|
140
148
|
end
|
141
149
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
150
|
+
end
|
151
|
+
|
152
|
+
def stream_http_to_file(uri, file)
|
153
|
+
Net::HTTP.start(
|
154
|
+
uri.host,
|
155
|
+
uri.port,
|
156
|
+
use_ssl: uri.scheme == "https"
|
157
|
+
) do |http|
|
158
|
+
request = Net::HTTP::Get.new(uri)
|
159
|
+
http.request(request) do |response|
|
160
|
+
response.read_body { |chunk| file.write(chunk) }
|
161
|
+
end
|
162
|
+
end
|
145
163
|
end
|
146
164
|
end
|
147
165
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "numo/narray"
|
4
|
+
|
3
5
|
module Ephem
|
4
6
|
module Segments
|
5
7
|
# Manages data segments within SPICE kernel (SPK) files, providing methods
|
@@ -133,7 +135,7 @@ module Ephem
|
|
133
135
|
# Synchronize access to data loading using a mutex lock
|
134
136
|
# to prevent race conditions in multithreaded environments
|
135
137
|
@data_lock.synchronize do
|
136
|
-
return if @data_loaded
|
138
|
+
return if @data_loaded
|
137
139
|
|
138
140
|
component_count = determine_component_count
|
139
141
|
coefficients_data = load_coefficient_data
|
@@ -180,27 +182,27 @@ module Ephem
|
|
180
182
|
coefficients = Numo::DFloat.cast(coefficients_raw)
|
181
183
|
coefficients = coefficients.reshape(segment_count, record_size)
|
182
184
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
)
|
197
|
-
.transpose(2, 1, 0)
|
185
|
+
@midpoints = coefficients[0...segment_count, 0].to_a
|
186
|
+
@radii = coefficients[0...segment_count, 1].to_a
|
187
|
+
n_terms = coefficient_count
|
188
|
+
n_components = component_count
|
189
|
+
|
190
|
+
@coefficients = Array.new(segment_count) do |i|
|
191
|
+
row = coefficients[i, 2..-1].to_a
|
192
|
+
Array.new(n_terms) do |k|
|
193
|
+
Array.new(n_components) do |j|
|
194
|
+
row[k + j * n_terms]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
198
|
end
|
199
199
|
|
200
200
|
def convert_to_seconds(tdb, tdb2)
|
201
201
|
case tdb
|
202
|
-
when Array
|
202
|
+
when Array
|
203
203
|
tdb.map { |t| time_to_seconds(t, tdb2) }
|
204
|
+
when Numo::NArray
|
205
|
+
tdb.to_a.map { |t| time_to_seconds(t, tdb2) }
|
204
206
|
else
|
205
207
|
time_to_seconds(tdb, tdb2)
|
206
208
|
end
|
@@ -228,20 +230,18 @@ module Ephem
|
|
228
230
|
def generate_single(tdb_seconds)
|
229
231
|
interval = find_interval(tdb_seconds)
|
230
232
|
normalized_time = compute_normalized_time(tdb_seconds, interval)
|
231
|
-
coeffs = @coefficients[true, true, interval]
|
232
233
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
velocity = Computation::ChebyshevPolynomial.
|
239
|
-
|
240
|
-
normalized_time
|
241
|
-
|
242
|
-
)
|
243
|
-
|
244
|
-
[position.to_a, velocity.to_a]
|
234
|
+
coeffs = @coefficients[interval] # already [n_terms][3]
|
235
|
+
position = Computation::ChebyshevPolynomial.evaluate(
|
236
|
+
coeffs,
|
237
|
+
normalized_time
|
238
|
+
)
|
239
|
+
velocity = Computation::ChebyshevPolynomial.evaluate_derivative(
|
240
|
+
coeffs,
|
241
|
+
normalized_time,
|
242
|
+
@radii[interval]
|
243
|
+
)
|
244
|
+
[position, velocity]
|
245
245
|
end
|
246
246
|
|
247
247
|
def generate_multiple(tdb_seconds)
|
data/lib/ephem/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ephem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rémy Hannequin
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 2025-06-09 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: minitar
|
@@ -244,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
244
244
|
- !ruby/object:Gem::Version
|
245
245
|
version: '0'
|
246
246
|
requirements: []
|
247
|
-
rubygems_version: 3.6.
|
247
|
+
rubygems_version: 3.6.2
|
248
248
|
specification_version: 4
|
249
249
|
summary: Compute astronomical ephemerides from NASA/JPL DE and IMCCE INPOP
|
250
250
|
test_files: []
|