ephem 0.4.1 → 0.5.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/.tool-versions +1 -1
- data/CHANGELOG.md +121 -32
- data/README.md +81 -4
- data/benchmarks/run.rb +431 -0
- data/lib/ephem/cli.rb +26 -13
- data/lib/ephem/computation/chebyshev_polynomial.rb +63 -4
- data/lib/ephem/core/orientation.rb +118 -0
- data/lib/ephem/core/rotation.rb +82 -0
- data/lib/ephem/core/state.rb +5 -0
- data/lib/ephem/download.rb +33 -3
- data/lib/ephem/excerpt.rb +17 -12
- data/lib/ephem/io/binary_reader.rb +6 -4
- data/lib/ephem/io/daf.rb +18 -1
- data/lib/ephem/io/record_parser.rb +2 -2
- data/lib/ephem/io/summary_manager.rb +14 -15
- data/lib/ephem/pck.rb +118 -0
- data/lib/ephem/segments/base_segment.rb +25 -8
- data/lib/ephem/segments/chebyshev_type2.rb +142 -0
- data/lib/ephem/segments/orientation_group.rb +59 -0
- data/lib/ephem/segments/orientation_segment.rb +118 -0
- data/lib/ephem/segments/orientation_source.rb +17 -0
- data/lib/ephem/segments/position_group.rb +46 -0
- data/lib/ephem/segments/registry.rb +9 -3
- data/lib/ephem/segments/segment.rb +24 -224
- data/lib/ephem/segments/segment_group.rb +84 -0
- data/lib/ephem/spk.rb +20 -9
- data/lib/ephem/version.rb +1 -1
- data/lib/ephem.rb +9 -0
- metadata +27 -17
data/benchmarks/run.rb
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "benchmark/ips"
|
|
4
|
+
require "objspace"
|
|
5
|
+
require_relative "../lib/ephem"
|
|
6
|
+
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# Configuration
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
ROOT = File.expand_path("..", __dir__)
|
|
12
|
+
SPK_FULL = File.join(ROOT, "spec", "support", "data", "de432s.bsp")
|
|
13
|
+
SPK_EXCERPT = File
|
|
14
|
+
.join(ROOT, "spec", "support", "data", "de421_2000_excerpt.bsp")
|
|
15
|
+
PCK_EXCERPT = File
|
|
16
|
+
.join(ROOT, "spec", "support", "data", "moon_pa_de440_excerpt.bpc")
|
|
17
|
+
|
|
18
|
+
JD_J2000 = Ephem::Core::Constants::Time::J2000_EPOCH # 2451545.0
|
|
19
|
+
JD_TEST = 2459000.0 # 2020-09-30, well within de432s range
|
|
20
|
+
|
|
21
|
+
SEQUENTIAL_STEPS = 1000
|
|
22
|
+
|
|
23
|
+
# Body pairs available in DE432s: [center, target]
|
|
24
|
+
BODY_PAIRS = {
|
|
25
|
+
"Sun" => [0, 10],
|
|
26
|
+
"Earth-Moon Bary" => [0, 3],
|
|
27
|
+
"Mars Bary" => [0, 4],
|
|
28
|
+
"Jupiter Bary" => [0, 5],
|
|
29
|
+
"Saturn Bary" => [0, 6]
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Helpers
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
def separator(title)
|
|
37
|
+
puts
|
|
38
|
+
puts "=" * 70
|
|
39
|
+
puts " #{title}"
|
|
40
|
+
puts "=" * 70
|
|
41
|
+
puts
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ensure_file!(path)
|
|
45
|
+
return if File.exist?(path)
|
|
46
|
+
abort "SPK file not found: #{path}\n" \
|
|
47
|
+
"Run specs first or place the file manually."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# Preflight
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
ensure_file!(SPK_FULL)
|
|
55
|
+
ensure_file!(SPK_EXCERPT)
|
|
56
|
+
|
|
57
|
+
puts "Ephem Benchmark Suite"
|
|
58
|
+
puts "-" * 70
|
|
59
|
+
puts "Ruby: #{RUBY_VERSION} (#{RUBY_PLATFORM})"
|
|
60
|
+
puts "Ephem: #{Ephem::VERSION}"
|
|
61
|
+
puts "SPK: #{File.basename(SPK_FULL)} (#{(File.size(SPK_FULL) / 1024.0 / 1024).round(2)} MB)"
|
|
62
|
+
puts "Excerpt: #{File.basename(SPK_EXCERPT)} (#{(File.size(SPK_EXCERPT) / 1024.0).round(1)} KB)"
|
|
63
|
+
puts "J2000: #{JD_J2000}"
|
|
64
|
+
puts "Test JD: #{JD_TEST}"
|
|
65
|
+
puts "-" * 70
|
|
66
|
+
|
|
67
|
+
# Pre-open the main SPK file and warm up data for hot-path benchmarks
|
|
68
|
+
spk = Ephem::SPK.open(SPK_FULL)
|
|
69
|
+
segment_emb = spk[0, 3] # Earth-Moon Barycenter
|
|
70
|
+
segment_sun = spk[0, 10] # Sun
|
|
71
|
+
|
|
72
|
+
# Trigger lazy data loading so hot-path benchmarks don't include I/O
|
|
73
|
+
segment_emb.compute(JD_TEST)
|
|
74
|
+
segment_sun.compute(JD_TEST)
|
|
75
|
+
|
|
76
|
+
# Pre-compute time arrays for sequential/random benchmarks
|
|
77
|
+
sequential_times = Array.new(SEQUENTIAL_STEPS) { |i| JD_TEST + i }
|
|
78
|
+
random_times = sequential_times.shuffle
|
|
79
|
+
|
|
80
|
+
# =========================================================================
|
|
81
|
+
# 1. SPK FILE OPENING
|
|
82
|
+
# =========================================================================
|
|
83
|
+
|
|
84
|
+
separator "1. SPK File Opening"
|
|
85
|
+
|
|
86
|
+
GC.start
|
|
87
|
+
Benchmark.ips do |x|
|
|
88
|
+
x.report("SPK.open (full 10MB)") do
|
|
89
|
+
s = Ephem::SPK.open(SPK_FULL)
|
|
90
|
+
s.close
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
x.report("SPK.open (excerpt 54KB)") do
|
|
94
|
+
s = Ephem::SPK.open(SPK_EXCERPT)
|
|
95
|
+
s.close
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
x.compare!
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# =========================================================================
|
|
102
|
+
# 2. FIRST DATA LOAD (COLD START)
|
|
103
|
+
# =========================================================================
|
|
104
|
+
|
|
105
|
+
separator "2. First Data Load (Cold Start)"
|
|
106
|
+
|
|
107
|
+
puts "Measures the cost of the first compute() call on a segment,"
|
|
108
|
+
puts "which triggers lazy loading of coefficient data from disk."
|
|
109
|
+
puts
|
|
110
|
+
|
|
111
|
+
GC.start
|
|
112
|
+
Benchmark.ips do |x|
|
|
113
|
+
x.report("cold compute (load + eval)") do
|
|
114
|
+
segment_emb.clear_data
|
|
115
|
+
segment_emb.compute(JD_TEST)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
x.report("warm compute (cached)") do
|
|
119
|
+
segment_emb.compute(JD_TEST)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
x.compare!
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Ensure data is loaded again for subsequent benchmarks
|
|
126
|
+
segment_emb.compute(JD_TEST)
|
|
127
|
+
|
|
128
|
+
# =========================================================================
|
|
129
|
+
# 3. SINGLE POSITION COMPUTATION (HOT PATH)
|
|
130
|
+
# =========================================================================
|
|
131
|
+
|
|
132
|
+
separator "3. Single Position Computation (Hot Path)"
|
|
133
|
+
|
|
134
|
+
puts "segment.compute(time) — Chebyshev eval + Vector creation"
|
|
135
|
+
puts
|
|
136
|
+
|
|
137
|
+
GC.start
|
|
138
|
+
Benchmark.ips do |x|
|
|
139
|
+
x.report("compute (position)") { segment_emb.compute(JD_TEST) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# =========================================================================
|
|
143
|
+
# 4. SINGLE STATE COMPUTATION
|
|
144
|
+
# =========================================================================
|
|
145
|
+
|
|
146
|
+
separator "4. Single State Computation"
|
|
147
|
+
|
|
148
|
+
puts "segment.compute_and_differentiate(time) — position + velocity"
|
|
149
|
+
puts
|
|
150
|
+
|
|
151
|
+
GC.start
|
|
152
|
+
Benchmark.ips do |x|
|
|
153
|
+
x.report("compute_and_differentiate (state)") do
|
|
154
|
+
segment_emb.compute_and_differentiate(JD_TEST)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# =========================================================================
|
|
159
|
+
# 5. POSITION vs STATE (COMPARISON)
|
|
160
|
+
# =========================================================================
|
|
161
|
+
|
|
162
|
+
separator "5. Position vs State (Direct Comparison)"
|
|
163
|
+
|
|
164
|
+
GC.start
|
|
165
|
+
Benchmark.ips do |x|
|
|
166
|
+
x.report("compute (position only)") { segment_emb.compute(JD_TEST) }
|
|
167
|
+
x.report("compute_and_differentiate") do
|
|
168
|
+
segment_emb.compute_and_differentiate(JD_TEST)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
x.compare!
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# =========================================================================
|
|
175
|
+
# 6. SEQUENTIAL TIME ACCESS
|
|
176
|
+
# =========================================================================
|
|
177
|
+
|
|
178
|
+
separator "6. Sequential Time Access (#{SEQUENTIAL_STEPS} days)"
|
|
179
|
+
|
|
180
|
+
puts "Tests interval-finding binary search with temporal locality."
|
|
181
|
+
puts "The @last_interval cache should accelerate sequential access."
|
|
182
|
+
puts
|
|
183
|
+
|
|
184
|
+
GC.start
|
|
185
|
+
Benchmark.ips do |x|
|
|
186
|
+
x.report("sequential (#{SEQUENTIAL_STEPS} days)") do
|
|
187
|
+
sequential_times.each { |t| segment_emb.compute(t) }
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# =========================================================================
|
|
192
|
+
# 7. RANDOM TIME ACCESS
|
|
193
|
+
# =========================================================================
|
|
194
|
+
|
|
195
|
+
separator "7. Random Time Access (#{SEQUENTIAL_STEPS} days, shuffled)"
|
|
196
|
+
|
|
197
|
+
puts "Same times as above but shuffled — measures interval cache miss impact."
|
|
198
|
+
puts
|
|
199
|
+
|
|
200
|
+
GC.start
|
|
201
|
+
Benchmark.ips do |x|
|
|
202
|
+
x.report("sequential access") do
|
|
203
|
+
sequential_times.each { |t| segment_emb.compute(t) }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
x.report("random access") do
|
|
207
|
+
random_times.each { |t| segment_emb.compute(t) }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
x.compare!
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# =========================================================================
|
|
214
|
+
# 8. BATCH COMPUTATION
|
|
215
|
+
# =========================================================================
|
|
216
|
+
|
|
217
|
+
separator "8. Batch vs Loop Computation"
|
|
218
|
+
|
|
219
|
+
batch_sizes = [10, 100, 1000]
|
|
220
|
+
|
|
221
|
+
batch_sizes.each do |n|
|
|
222
|
+
times = sequential_times.first(n)
|
|
223
|
+
|
|
224
|
+
puts "--- Batch size: #{n} ---"
|
|
225
|
+
GC.start
|
|
226
|
+
Benchmark.ips do |x|
|
|
227
|
+
x.report("loop (#{n} times)") do
|
|
228
|
+
times.each { |t| segment_emb.compute_and_differentiate(t) }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
x.report("batch (#{n} times)") do
|
|
232
|
+
segment_emb.compute_and_differentiate(times)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
x.compare!
|
|
236
|
+
end
|
|
237
|
+
puts
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# =========================================================================
|
|
241
|
+
# 9. MULTIPLE BODIES
|
|
242
|
+
# =========================================================================
|
|
243
|
+
|
|
244
|
+
separator "9. Multiple Bodies (Computation Speed by Target)"
|
|
245
|
+
|
|
246
|
+
# Pre-load all segments
|
|
247
|
+
body_segments = {}
|
|
248
|
+
BODY_PAIRS.each do |name, (center, target)|
|
|
249
|
+
seg = spk[center, target]
|
|
250
|
+
seg.compute(JD_TEST) # warm up
|
|
251
|
+
body_segments[name] = seg
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
GC.start
|
|
255
|
+
Benchmark.ips do |x|
|
|
256
|
+
body_segments.each do |name, seg|
|
|
257
|
+
x.report(name) { seg.compute(JD_TEST) }
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
x.compare!
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# =========================================================================
|
|
264
|
+
# 10. CHEBYSHEV POLYNOMIAL (MICRO-BENCHMARK)
|
|
265
|
+
# =========================================================================
|
|
266
|
+
|
|
267
|
+
separator "10. Chebyshev Polynomial Evaluation (Micro)"
|
|
268
|
+
|
|
269
|
+
puts "Direct ChebyshevPolynomial.evaluate / evaluate_derivative calls"
|
|
270
|
+
puts "with real coefficients extracted from a loaded segment."
|
|
271
|
+
puts
|
|
272
|
+
|
|
273
|
+
# Extract real coefficient data from the loaded segment
|
|
274
|
+
coefficients = segment_emb.instance_variable_get(:@coefficients)
|
|
275
|
+
radii = segment_emb.instance_variable_get(:@radii)
|
|
276
|
+
|
|
277
|
+
# Pick the first interval's coefficients
|
|
278
|
+
test_coeffs = coefficients[0]
|
|
279
|
+
test_radius = radii[0]
|
|
280
|
+
test_t = 0.0 # midpoint of the interval (normalized)
|
|
281
|
+
|
|
282
|
+
puts "Polynomial degree: #{test_coeffs.size} terms"
|
|
283
|
+
puts "Components per term: #{test_coeffs.first.size}"
|
|
284
|
+
puts
|
|
285
|
+
|
|
286
|
+
GC.start
|
|
287
|
+
Benchmark.ips do |x|
|
|
288
|
+
x.report("evaluate (position)") do
|
|
289
|
+
Ephem::Computation::ChebyshevPolynomial.evaluate(test_coeffs, test_t)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
x.report("evaluate_derivative (velocity)") do
|
|
293
|
+
Ephem::Computation::ChebyshevPolynomial.evaluate_derivative(
|
|
294
|
+
test_coeffs, test_t, test_radius
|
|
295
|
+
)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
x.report("evaluate_with_derivative (pos+vel, 1 pass)") do
|
|
299
|
+
Ephem::Computation::ChebyshevPolynomial.evaluate_with_derivative(
|
|
300
|
+
test_coeffs, test_t, test_radius
|
|
301
|
+
)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
x.compare!
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# =========================================================================
|
|
308
|
+
# 11. VECTOR OPERATIONS
|
|
309
|
+
# =========================================================================
|
|
310
|
+
|
|
311
|
+
separator "11. Vector Operations"
|
|
312
|
+
|
|
313
|
+
v1 = Ephem::Core::Vector.new(1.0, 2.0, 3.0)
|
|
314
|
+
v2 = Ephem::Core::Vector.new(4.0, 5.0, 6.0)
|
|
315
|
+
|
|
316
|
+
GC.start
|
|
317
|
+
Benchmark.ips do |x|
|
|
318
|
+
x.report("Vector.new") { Ephem::Core::Vector.new(1.0, 2.0, 3.0) }
|
|
319
|
+
x.report("Vector + Vector") { v1 + v2 }
|
|
320
|
+
x.report("Vector - Vector") { v1 - v2 }
|
|
321
|
+
x.report("Vector.dot") { v1.dot(v2) }
|
|
322
|
+
x.report("Vector.cross") { v1.cross(v2) }
|
|
323
|
+
x.report("Vector.magnitude") { v1.magnitude }
|
|
324
|
+
x.report("Vector.to_a") { v1.to_a }
|
|
325
|
+
|
|
326
|
+
x.compare!
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# =========================================================================
|
|
330
|
+
# 12. MEMORY PROFILING
|
|
331
|
+
# =========================================================================
|
|
332
|
+
|
|
333
|
+
separator "12. Memory Profiling"
|
|
334
|
+
|
|
335
|
+
# --- 12a. Object allocations per compute call ---
|
|
336
|
+
puts "--- Object allocations per call ---"
|
|
337
|
+
puts
|
|
338
|
+
|
|
339
|
+
# Warm up
|
|
340
|
+
segment_emb.compute(JD_TEST)
|
|
341
|
+
segment_emb.compute_and_differentiate(JD_TEST)
|
|
342
|
+
|
|
343
|
+
# Measure compute
|
|
344
|
+
GC.start
|
|
345
|
+
GC.disable
|
|
346
|
+
before = GC.stat[:total_allocated_objects]
|
|
347
|
+
segment_emb.compute(JD_TEST)
|
|
348
|
+
after = GC.stat[:total_allocated_objects]
|
|
349
|
+
GC.enable
|
|
350
|
+
puts " compute (position): #{after - before} objects allocated"
|
|
351
|
+
|
|
352
|
+
# Measure compute_and_differentiate
|
|
353
|
+
GC.start
|
|
354
|
+
GC.disable
|
|
355
|
+
before = GC.stat[:total_allocated_objects]
|
|
356
|
+
segment_emb.compute_and_differentiate(JD_TEST)
|
|
357
|
+
after = GC.stat[:total_allocated_objects]
|
|
358
|
+
GC.enable
|
|
359
|
+
puts " compute_and_differentiate: #{after - before} objects allocated"
|
|
360
|
+
|
|
361
|
+
# Measure batch of 100
|
|
362
|
+
times_100 = sequential_times.first(100)
|
|
363
|
+
GC.start
|
|
364
|
+
GC.disable
|
|
365
|
+
before = GC.stat[:total_allocated_objects]
|
|
366
|
+
segment_emb.compute_and_differentiate(times_100)
|
|
367
|
+
after = GC.stat[:total_allocated_objects]
|
|
368
|
+
GC.enable
|
|
369
|
+
alloc_100 = after - before
|
|
370
|
+
puts " batch compute_and_diff (100): #{alloc_100} objects (#{(alloc_100 / 100.0).round(1)}/call)"
|
|
371
|
+
|
|
372
|
+
puts
|
|
373
|
+
|
|
374
|
+
# --- 12b. Segment data memory footprint ---
|
|
375
|
+
puts "--- Segment data memory footprint ---"
|
|
376
|
+
puts
|
|
377
|
+
|
|
378
|
+
# Force a fresh load and measure
|
|
379
|
+
segment_fresh = spk[0, 3]
|
|
380
|
+
segment_fresh.clear_data
|
|
381
|
+
|
|
382
|
+
GC.start
|
|
383
|
+
before_mem = ObjectSpace.memsize_of_all
|
|
384
|
+
segment_fresh.compute(JD_TEST) # triggers load
|
|
385
|
+
GC.start
|
|
386
|
+
after_mem = ObjectSpace.memsize_of_all
|
|
387
|
+
|
|
388
|
+
delta_kb = (after_mem - before_mem) / 1024.0
|
|
389
|
+
puts " Segment [0,3] data load: ~#{delta_kb.round(1)} KB"
|
|
390
|
+
|
|
391
|
+
# Report coefficient array size
|
|
392
|
+
coeffs = segment_fresh.instance_variable_get(:@coefficients)
|
|
393
|
+
if coeffs
|
|
394
|
+
puts " Coefficient records: #{coeffs.size}"
|
|
395
|
+
puts " Terms per record: #{coeffs.first&.size}"
|
|
396
|
+
puts " Components per term: #{coeffs.first&.first&.size}"
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# =========================================================================
|
|
400
|
+
# 13. PCK ORIENTATION (BINARY PCK)
|
|
401
|
+
# =========================================================================
|
|
402
|
+
|
|
403
|
+
separator "13. PCK Orientation (Binary PCK)"
|
|
404
|
+
|
|
405
|
+
puts "angles_at (Euler angles) vs orientation_at (angles + rates)"
|
|
406
|
+
puts
|
|
407
|
+
|
|
408
|
+
pck = Ephem::PCK.open(PCK_EXCERPT)
|
|
409
|
+
moon = pck[31008] # MOON_PA_DE440 frame
|
|
410
|
+
moon.angles_at(JD_J2000) # warm up
|
|
411
|
+
|
|
412
|
+
orientation_times = Array.new(SEQUENTIAL_STEPS) { |index| JD_J2000 + index }
|
|
413
|
+
|
|
414
|
+
GC.start
|
|
415
|
+
Benchmark.ips do |x|
|
|
416
|
+
x.report("angles_at (scalar)") { moon.angles_at(JD_J2000) }
|
|
417
|
+
x.report("orientation_at (scalar)") { moon.orientation_at(JD_J2000) }
|
|
418
|
+
x.report("angles_at (batch 1000)") { moon.angles_at(orientation_times) }
|
|
419
|
+
|
|
420
|
+
x.compare!
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
pck.close
|
|
424
|
+
|
|
425
|
+
# =========================================================================
|
|
426
|
+
# Cleanup
|
|
427
|
+
# =========================================================================
|
|
428
|
+
|
|
429
|
+
spk.close
|
|
430
|
+
|
|
431
|
+
separator "Benchmark Complete"
|
data/lib/ephem/cli.rb
CHANGED
|
@@ -47,22 +47,22 @@ module Ephem
|
|
|
47
47
|
Ruby Ephem - A tool for working with JPL Ephemerides
|
|
48
48
|
|
|
49
49
|
Commands:
|
|
50
|
-
excerpt - Create an excerpt of an SPK
|
|
50
|
+
excerpt - Create an excerpt of an SPK or binary PCK kernel
|
|
51
51
|
help - Show this help message
|
|
52
52
|
|
|
53
53
|
Excerpt command:
|
|
54
54
|
ruby-ephem excerpt [options] START_DATE END_DATE INPUT_FILE OUTPUT_FILE
|
|
55
55
|
|
|
56
56
|
Options:
|
|
57
|
-
--targets TARGET_IDS - Comma-separated list of target IDs to
|
|
58
|
-
(default: all
|
|
57
|
+
--targets TARGET_IDS - Comma-separated list of target/body IDs to
|
|
58
|
+
include (default: all)
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
Examples:
|
|
61
61
|
ruby-ephem excerpt --targets 3,10,399 2000-01-01 2030-01-01 de440s.bsp excerpt.bsp
|
|
62
|
+
ruby-ephem excerpt --targets 31008 2000-01-01 2030-01-01 moon_pa_de440.bpc moon_excerpt.bpc
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
2000-01-01 to 2030-01-01.
|
|
64
|
+
The input kernel kind (SPK or binary PCK) is detected automatically and
|
|
65
|
+
the excerpt is written in the same format.
|
|
66
66
|
HELP
|
|
67
67
|
end
|
|
68
68
|
|
|
@@ -125,9 +125,9 @@ module Ephem
|
|
|
125
125
|
puts "Including all targets"
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
kernel = open_kernel(input_file)
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
excerpt_kernel = kernel.excerpt(
|
|
131
131
|
output_path: output_file,
|
|
132
132
|
start_jd: start_jd,
|
|
133
133
|
end_jd: end_jd,
|
|
@@ -136,8 +136,8 @@ module Ephem
|
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
puts "Excerpt created successfully!"
|
|
139
|
-
puts "Original segments: #{
|
|
140
|
-
puts "Excerpt segments: #{
|
|
139
|
+
puts "Original segments: #{kernel.segments.size}"
|
|
140
|
+
puts "Excerpt segments: #{excerpt_kernel.segments.size}"
|
|
141
141
|
|
|
142
142
|
original_size = File.size(input_file)
|
|
143
143
|
excerpt_size = File.size(output_file)
|
|
@@ -148,12 +148,25 @@ module Ephem
|
|
|
148
148
|
puts "Original: #{original_size} bytes"
|
|
149
149
|
puts "Excerpt: #{excerpt_size} bytes"
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
kernel.close
|
|
152
|
+
excerpt_kernel.close
|
|
153
153
|
rescue => e
|
|
154
154
|
puts "Error creating excerpt: #{e.message}"
|
|
155
155
|
puts e.backtrace if options[:debug]
|
|
156
156
|
end
|
|
157
157
|
end
|
|
158
|
+
|
|
159
|
+
def self.open_kernel(path)
|
|
160
|
+
daf = Ephem::IO::DAF.new(File.open(path, "rb"))
|
|
161
|
+
|
|
162
|
+
if daf.file_type == :pck
|
|
163
|
+
Ephem::PCK.new(daf: daf)
|
|
164
|
+
else
|
|
165
|
+
Ephem::SPK.new(daf: daf)
|
|
166
|
+
end
|
|
167
|
+
rescue
|
|
168
|
+
daf&.close
|
|
169
|
+
raise
|
|
170
|
+
end
|
|
158
171
|
end
|
|
159
172
|
end
|
|
@@ -22,13 +22,13 @@ module Ephem
|
|
|
22
22
|
b1x = b1y = b1z = 0.0
|
|
23
23
|
b2x = b2y = b2z = 0.0
|
|
24
24
|
|
|
25
|
+
t2 = 2.0 * t
|
|
25
26
|
k = n - 1
|
|
26
27
|
while k > 0
|
|
27
28
|
c = coeffs[k]
|
|
28
29
|
c0 = c[0]
|
|
29
30
|
c1 = c[1]
|
|
30
31
|
c2 = c[2]
|
|
31
|
-
t2 = 2.0 * t
|
|
32
32
|
tx = t2 * b1x - b2x + c0
|
|
33
33
|
ty = t2 * b1y - b2y + c1
|
|
34
34
|
tz = t2 * b1z - b2z + c2
|
|
@@ -52,9 +52,9 @@ module Ephem
|
|
|
52
52
|
# @param coeffs [Array<Array<Float>>] Array of coefficients; shape is
|
|
53
53
|
# [n_terms][3].
|
|
54
54
|
# @param t [Float] The normalized independent variable (in [-1, 1]).
|
|
55
|
-
# @param radius [Float] The half-length of the time interval (
|
|
55
|
+
# @param radius [Float] The half-length of the time interval (seconds).
|
|
56
56
|
# @return [Array<Float>] The 3-vector derivative (velocity), in units per
|
|
57
|
-
#
|
|
57
|
+
# day.
|
|
58
58
|
def self.evaluate_derivative(coeffs, t, radius)
|
|
59
59
|
n = coeffs.size
|
|
60
60
|
return [0.0, 0.0, 0.0] if n < 2
|
|
@@ -62,13 +62,13 @@ module Ephem
|
|
|
62
62
|
d1x = d1y = d1z = 0.0
|
|
63
63
|
d2x = d2y = d2z = 0.0
|
|
64
64
|
|
|
65
|
+
t2 = 2.0 * t
|
|
65
66
|
k = n - 1
|
|
66
67
|
while k > 0
|
|
67
68
|
c = coeffs[k]
|
|
68
69
|
c0 = c[0]
|
|
69
70
|
c1 = c[1]
|
|
70
71
|
c2 = c[2]
|
|
71
|
-
t2 = 2.0 * t
|
|
72
72
|
k2 = 2 * k
|
|
73
73
|
tx = t2 * d1x - d2x + k2 * c0
|
|
74
74
|
ty = t2 * d1y - d2y + k2 * c1
|
|
@@ -85,6 +85,65 @@ module Ephem
|
|
|
85
85
|
scale = Ephem::Core::Constants::Time::SECONDS_PER_DAY / (2.0 * radius)
|
|
86
86
|
[d1x * scale, d1y * scale, d1z * scale]
|
|
87
87
|
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Evaluates a 3D Chebyshev polynomial and its time derivative in a single
|
|
91
|
+
# pass. It runs the same value and derivative recurrences as {evaluate}
|
|
92
|
+
# and {evaluate_derivative}, but fused into one loop so the coefficient
|
|
93
|
+
# fetch and loop control are shared. Results are bit-for-bit identical to
|
|
94
|
+
# calling the two methods separately.
|
|
95
|
+
#
|
|
96
|
+
# @param coeffs [Array<Array<Float>>] coefficients; shape [n_terms][3].
|
|
97
|
+
# @param t [Float] normalized independent variable, in [-1, 1].
|
|
98
|
+
# @param radius [Float] half-length of the time interval (seconds).
|
|
99
|
+
# @return [Array(Array<Float>, Array<Float>)] [position, velocity], with
|
|
100
|
+
# velocity in units per day.
|
|
101
|
+
def self.evaluate_with_derivative(coeffs, t, radius)
|
|
102
|
+
n = coeffs.size
|
|
103
|
+
b1x = b1y = b1z = 0.0
|
|
104
|
+
b2x = b2y = b2z = 0.0
|
|
105
|
+
d1x = d1y = d1z = 0.0
|
|
106
|
+
d2x = d2y = d2z = 0.0
|
|
107
|
+
|
|
108
|
+
t2 = 2.0 * t
|
|
109
|
+
k = n - 1
|
|
110
|
+
while k > 0
|
|
111
|
+
c = coeffs[k]
|
|
112
|
+
c0 = c[0]
|
|
113
|
+
c1 = c[1]
|
|
114
|
+
c2 = c[2]
|
|
115
|
+
k2 = 2 * k
|
|
116
|
+
|
|
117
|
+
bx = t2 * b1x - b2x + c0
|
|
118
|
+
by = t2 * b1y - b2y + c1
|
|
119
|
+
bz = t2 * b1z - b2z + c2
|
|
120
|
+
dx = t2 * d1x - d2x + k2 * c0
|
|
121
|
+
dy = t2 * d1y - d2y + k2 * c1
|
|
122
|
+
dz = t2 * d1z - d2z + k2 * c2
|
|
123
|
+
|
|
124
|
+
b2x = b1x
|
|
125
|
+
b2y = b1y
|
|
126
|
+
b2z = b1z
|
|
127
|
+
b1x = bx
|
|
128
|
+
b1y = by
|
|
129
|
+
b1z = bz
|
|
130
|
+
d2x = d1x
|
|
131
|
+
d2y = d1y
|
|
132
|
+
d2z = d1z
|
|
133
|
+
d1x = dx
|
|
134
|
+
d1y = dy
|
|
135
|
+
d1z = dz
|
|
136
|
+
k -= 1
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
c0, c1, c2 = coeffs[0]
|
|
140
|
+
position = [t * b1x - b2x + c0, t * b1y - b2y + c1, t * b1z - b2z + c2]
|
|
141
|
+
|
|
142
|
+
scale = Ephem::Core::Constants::Time::SECONDS_PER_DAY / (2.0 * radius)
|
|
143
|
+
velocity = [d1x * scale, d1y * scale, d1z * scale]
|
|
144
|
+
|
|
145
|
+
[position, velocity]
|
|
146
|
+
end
|
|
88
147
|
end
|
|
89
148
|
end
|
|
90
149
|
end
|