astronoby 0.7.0 → 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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +145 -3
  4. data/README.md +59 -33
  5. data/UPGRADING.md +75 -21
  6. data/docs/README.md +224 -0
  7. data/docs/angles.md +137 -0
  8. data/docs/configuration.md +98 -0
  9. data/docs/coordinates.md +167 -0
  10. data/docs/deep_sky_bodies.md +101 -0
  11. data/docs/ephem.md +85 -0
  12. data/docs/equinoxes_solstices_times.md +31 -0
  13. data/docs/glossary.md +152 -0
  14. data/docs/instant.md +139 -0
  15. data/docs/moon_phases.md +79 -0
  16. data/docs/observer.md +65 -0
  17. data/docs/reference_frames.md +138 -0
  18. data/docs/rise_transit_set_times.md +119 -0
  19. data/docs/solar_system_bodies.md +107 -0
  20. data/docs/twilight_times.md +123 -0
  21. data/lib/astronoby/angle.rb +6 -2
  22. data/lib/astronoby/angular_velocity.rb +76 -0
  23. data/lib/astronoby/bodies/deep_sky_object.rb +44 -0
  24. data/lib/astronoby/bodies/deep_sky_object_position.rb +127 -0
  25. data/lib/astronoby/bodies/earth.rb +12 -2
  26. data/lib/astronoby/bodies/jupiter.rb +17 -0
  27. data/lib/astronoby/bodies/mars.rb +17 -0
  28. data/lib/astronoby/bodies/mercury.rb +21 -0
  29. data/lib/astronoby/bodies/moon.rb +50 -36
  30. data/lib/astronoby/bodies/neptune.rb +21 -0
  31. data/lib/astronoby/bodies/saturn.rb +26 -0
  32. data/lib/astronoby/bodies/solar_system_body.rb +162 -27
  33. data/lib/astronoby/bodies/sun.rb +25 -2
  34. data/lib/astronoby/bodies/uranus.rb +5 -0
  35. data/lib/astronoby/bodies/venus.rb +25 -0
  36. data/lib/astronoby/cache.rb +189 -0
  37. data/lib/astronoby/configuration.rb +92 -0
  38. data/lib/astronoby/constants.rb +11 -3
  39. data/lib/astronoby/constellation.rb +12 -0
  40. data/lib/astronoby/constellations/data.rb +42 -0
  41. data/lib/astronoby/constellations/finder.rb +35 -0
  42. data/lib/astronoby/constellations/repository.rb +20 -0
  43. data/lib/astronoby/coordinates/equatorial.rb +5 -8
  44. data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
  45. data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
  46. data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
  47. data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
  48. data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
  49. data/lib/astronoby/distance.rb +6 -0
  50. data/lib/astronoby/equinox_solstice.rb +2 -2
  51. data/lib/astronoby/events/extremum_calculator.rb +233 -0
  52. data/lib/astronoby/events/extremum_event.rb +15 -0
  53. data/lib/astronoby/events/moon_phases.rb +15 -14
  54. data/lib/astronoby/events/rise_transit_set_calculator.rb +39 -12
  55. data/lib/astronoby/events/twilight_calculator.rb +116 -61
  56. data/lib/astronoby/events/twilight_events.rb +28 -0
  57. data/lib/astronoby/instant.rb +34 -6
  58. data/lib/astronoby/julian_date.rb +78 -0
  59. data/lib/astronoby/mean_obliquity.rb +8 -10
  60. data/lib/astronoby/nutation.rb +11 -3
  61. data/lib/astronoby/observer.rb +1 -1
  62. data/lib/astronoby/precession.rb +48 -38
  63. data/lib/astronoby/reference_frame.rb +2 -1
  64. data/lib/astronoby/reference_frames/apparent.rb +1 -11
  65. data/lib/astronoby/reference_frames/mean_of_date.rb +1 -1
  66. data/lib/astronoby/reference_frames/topocentric.rb +2 -12
  67. data/lib/astronoby/stellar_propagation.rb +162 -0
  68. data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +22 -0
  69. data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +64 -0
  70. data/lib/astronoby/time/greenwich_sidereal_time.rb +20 -58
  71. data/lib/astronoby/time/local_apparent_sidereal_time.rb +42 -0
  72. data/lib/astronoby/time/local_mean_sidereal_time.rb +42 -0
  73. data/lib/astronoby/time/local_sidereal_time.rb +35 -26
  74. data/lib/astronoby/time/sidereal_time.rb +42 -0
  75. data/lib/astronoby/true_obliquity.rb +2 -3
  76. data/lib/astronoby/util/time.rb +62 -44
  77. data/lib/astronoby/velocity.rb +5 -0
  78. data/lib/astronoby/version.rb +1 -1
  79. data/lib/astronoby.rb +19 -1
  80. metadata +71 -11
  81. data/Gemfile +0 -5
  82. data/Gemfile.lock +0 -102
  83. data/benchmark/README.md +0 -131
  84. data/benchmark/benchmark.rb +0 -259
  85. data/benchmark/data/imcce.csv.zip +0 -0
  86. data/benchmark/data/sun_calc.csv.zip +0 -0
  87. data/lib/astronoby/epoch.rb +0 -22
@@ -0,0 +1,202 @@
1
+ -85.0
2
+ -82.5
3
+ -76.0
4
+ -75.0
5
+ -70.0
6
+ -67.5
7
+ -65.0
8
+ -64.0
9
+ -63.583333333333336
10
+ -61.0
11
+ -60.0
12
+ -59.0
13
+ -58.5
14
+ -58.0
15
+ -57.5
16
+ -57.0
17
+ -56.5
18
+ -55.0
19
+ -54.5
20
+ -54.0
21
+ -53.5
22
+ -53.166666666666664
23
+ -53.0
24
+ -52.5
25
+ -51.5
26
+ -51.0
27
+ -50.75
28
+ -50.0
29
+ -49.0
30
+ -48.166666666666664
31
+ -48.0
32
+ -46.5
33
+ -46.0
34
+ -45.5
35
+ -44.0
36
+ -43.0
37
+ -42.0
38
+ -40.0
39
+ -39.75
40
+ -39.583333333333336
41
+ -37.0
42
+ -36.75
43
+ -36.0
44
+ -35.0
45
+ -33.0
46
+ -31.166666666666668
47
+ -30.0
48
+ -29.5
49
+ -29.166666666666668
50
+ -28.0
51
+ -27.25
52
+ -26.5
53
+ -25.5
54
+ -24.583333333333332
55
+ -24.5
56
+ -24.383333333333333
57
+ -24.0
58
+ -22.0
59
+ -20.0
60
+ -19.25
61
+ -19.0
62
+ -18.25
63
+ -17.0
64
+ -16.0
65
+ -15.0
66
+ -14.5
67
+ -12.033333333333333
68
+ -11.666666666666666
69
+ -11.0
70
+ -10.0
71
+ -9.0
72
+ -8.0
73
+ -7.0
74
+ -6.0
75
+ -4.0
76
+ -3.25
77
+ -1.75
78
+ 0.0
79
+ 1.5
80
+ 1.75
81
+ 2.0
82
+ 2.75
83
+ 3.0
84
+ 4.0
85
+ 4.5
86
+ 5.5
87
+ 6.0
88
+ 6.25
89
+ 7.0
90
+ 7.5
91
+ 8.0
92
+ 8.5
93
+ 9.916666666666666
94
+ 10.0
95
+ 11.0
96
+ 11.833333333333334
97
+ 12.0
98
+ 12.5
99
+ 12.833333333333334
100
+ 13.5
101
+ 14.0
102
+ 14.333333333333334
103
+ 15.0
104
+ 15.5
105
+ 15.75
106
+ 16.0
107
+ 16.166666666666668
108
+ 17.5
109
+ 18.0
110
+ 18.5
111
+ 19.0
112
+ 19.166666666666668
113
+ 19.5
114
+ 20.0
115
+ 20.5
116
+ 21.0
117
+ 21.083333333333332
118
+ 21.25
119
+ 21.5
120
+ 22.0
121
+ 22.833333333333332
122
+ 23.5
123
+ 23.75
124
+ 25.0
125
+ 25.5
126
+ 26.0
127
+ 27.0
128
+ 27.25
129
+ 27.5
130
+ 28.0
131
+ 28.5
132
+ 29.0
133
+ 30.0
134
+ 30.666666666666668
135
+ 30.75
136
+ 31.333333333333332
137
+ 32.0
138
+ 32.083333333333336
139
+ 33.0
140
+ 33.5
141
+ 34.0
142
+ 34.5
143
+ 35.0
144
+ 35.5
145
+ 36.0
146
+ 36.5
147
+ 36.75
148
+ 39.75
149
+ 40.0
150
+ 42.0
151
+ 43.5
152
+ 43.75
153
+ 44.0
154
+ 44.5
155
+ 45.0
156
+ 46.0
157
+ 47.0
158
+ 47.5
159
+ 48.0
160
+ 48.5
161
+ 50.0
162
+ 50.5
163
+ 51.5
164
+ 52.5
165
+ 52.75
166
+ 53.0
167
+ 54.0
168
+ 54.833333333333336
169
+ 55.0
170
+ 55.5
171
+ 56.0
172
+ 56.25
173
+ 57.0
174
+ 57.5
175
+ 58.0
176
+ 58.5
177
+ 59.083333333333336
178
+ 59.5
179
+ 60.0
180
+ 60.916666666666664
181
+ 61.5
182
+ 62.0
183
+ 63.0
184
+ 64.0
185
+ 66.0
186
+ 66.5
187
+ 67.0
188
+ 68.0
189
+ 70.0
190
+ 73.5
191
+ 75.0
192
+ 77.0
193
+ 80.0
194
+ 82.0
195
+ 85.0
196
+ 86.0
197
+ 86.16666666666667
198
+ 86.5
199
+ 88.0
200
+ 89.0
201
+ 89.5
202
+ 90.0
@@ -0,0 +1,237 @@
1
+ 0.06666666666666667
2
+ 0.14166666666666666
3
+ 0.16666666666666666
4
+ 0.3333333333333333
5
+ 0.7166666666666667
6
+ 0.75
7
+ 0.85
8
+ 0.8666666666666667
9
+ 1.1166666666666667
10
+ 1.3333333333333333
11
+ 1.3666666666666667
12
+ 1.4083333333333334
13
+ 1.5833333333333333
14
+ 1.6666666666666667
15
+ 1.7
16
+ 1.8333333333333333
17
+ 1.9083333333333334
18
+ 1.9166666666666667
19
+ 2.0
20
+ 2.0416666666666665
21
+ 2.1666666666666665
22
+ 2.3333333333333335
23
+ 2.4166666666666665
24
+ 2.433333333333333
25
+ 2.5166666666666666
26
+ 2.566666666666667
27
+ 2.65
28
+ 2.6666666666666665
29
+ 2.716666666666667
30
+ 3.0
31
+ 3.1
32
+ 3.1666666666666665
33
+ 3.2
34
+ 3.283333333333333
35
+ 3.3333333333333335
36
+ 3.3666666666666667
37
+ 3.4166666666666665
38
+ 3.5
39
+ 3.5083333333333333
40
+ 3.5833333333333335
41
+ 3.75
42
+ 3.8333333333333335
43
+ 3.8666666666666667
44
+ 4.0
45
+ 4.083333333333333
46
+ 4.266666666666667
47
+ 4.333333333333333
48
+ 4.5
49
+ 4.583333333333333
50
+ 4.616666666666666
51
+ 4.666666666666667
52
+ 4.691666666666666
53
+ 4.7
54
+ 4.75
55
+ 4.833333333333333
56
+ 4.916666666666667
57
+ 4.966666666666667
58
+ 5.0
59
+ 5.083333333333333
60
+ 5.333333333333333
61
+ 5.5
62
+ 5.6
63
+ 5.7
64
+ 5.766666666666667
65
+ 5.833333333333333
66
+ 5.883333333333334
67
+ 6.0
68
+ 6.1
69
+ 6.116666666666666
70
+ 6.166666666666667
71
+ 6.216666666666667
72
+ 6.241666666666666
73
+ 6.308333333333334
74
+ 6.5
75
+ 6.533333333333333
76
+ 6.583333333333333
77
+ 6.8
78
+ 6.833333333333333
79
+ 6.933333333333334
80
+ 7.0
81
+ 7.016666666666667
82
+ 7.2
83
+ 7.366666666666666
84
+ 7.5
85
+ 7.666666666666667
86
+ 7.75
87
+ 7.808333333333334
88
+ 7.883333333333334
89
+ 7.925
90
+ 7.966666666666667
91
+ 8.0
92
+ 8.083333333333334
93
+ 8.166666666666666
94
+ 8.366666666666667
95
+ 8.416666666666666
96
+ 8.45
97
+ 8.583333333333334
98
+ 8.833333333333334
99
+ 9.033333333333333
100
+ 9.083333333333334
101
+ 9.166666666666666
102
+ 9.25
103
+ 9.366666666666667
104
+ 9.583333333333334
105
+ 9.75
106
+ 9.883333333333333
107
+ 10.166666666666666
108
+ 10.25
109
+ 10.5
110
+ 10.583333333333334
111
+ 10.666666666666666
112
+ 10.75
113
+ 10.783333333333333
114
+ 10.833333333333334
115
+ 11.0
116
+ 11.25
117
+ 11.333333333333334
118
+ 11.5
119
+ 11.516666666666667
120
+ 11.833333333333334
121
+ 11.866666666666667
122
+ 12.0
123
+ 12.083333333333334
124
+ 12.25
125
+ 12.333333333333334
126
+ 12.583333333333334
127
+ 12.833333333333334
128
+ 13.0
129
+ 13.25
130
+ 13.5
131
+ 13.583333333333334
132
+ 13.666666666666666
133
+ 13.958333333333334
134
+ 14.0
135
+ 14.033333333333333
136
+ 14.166666666666666
137
+ 14.25
138
+ 14.416666666666666
139
+ 14.5
140
+ 14.533333333333333
141
+ 14.666666666666666
142
+ 14.75
143
+ 14.916666666666666
144
+ 15.05
145
+ 15.083333333333334
146
+ 15.166666666666666
147
+ 15.183333333333334
148
+ 15.25
149
+ 15.333333333333334
150
+ 15.433333333333334
151
+ 15.666666666666666
152
+ 15.75
153
+ 15.916666666666666
154
+ 16.0
155
+ 16.033333333333335
156
+ 16.083333333333332
157
+ 16.166666666666668
158
+ 16.266666666666666
159
+ 16.333333333333332
160
+ 16.375
161
+ 16.420833333333334
162
+ 16.533333333333335
163
+ 16.583333333333332
164
+ 16.75
165
+ 16.833333333333332
166
+ 17.0
167
+ 17.166666666666668
168
+ 17.25
169
+ 17.5
170
+ 17.583333333333332
171
+ 17.6
172
+ 17.666666666666668
173
+ 17.833333333333332
174
+ 17.966666666666665
175
+ 18.0
176
+ 18.175
177
+ 18.233333333333334
178
+ 18.25
179
+ 18.366666666666667
180
+ 18.425
181
+ 18.583333333333332
182
+ 18.662222222222223
183
+ 18.866666666666667
184
+ 19.0
185
+ 19.083333333333332
186
+ 19.166666666666668
187
+ 19.25
188
+ 19.258333333333333
189
+ 19.358333333333334
190
+ 19.4
191
+ 19.416666666666668
192
+ 19.666666666666668
193
+ 19.766666666666666
194
+ 19.833333333333332
195
+ 20.0
196
+ 20.141666666666666
197
+ 20.166666666666668
198
+ 20.25
199
+ 20.3
200
+ 20.333333333333332
201
+ 20.416666666666668
202
+ 20.533333333333335
203
+ 20.536666666666665
204
+ 20.566666666666666
205
+ 20.6
206
+ 20.666666666666668
207
+ 20.833333333333332
208
+ 20.875
209
+ 20.916666666666668
210
+ 21.0
211
+ 21.05
212
+ 21.116666666666667
213
+ 21.25
214
+ 21.333333333333332
215
+ 21.416666666666668
216
+ 21.466666666666665
217
+ 21.666666666666668
218
+ 21.733333333333334
219
+ 21.866666666666667
220
+ 21.875
221
+ 21.908333333333335
222
+ 21.966666666666665
223
+ 22.0
224
+ 22.133333333333333
225
+ 22.316666666666666
226
+ 22.75
227
+ 22.816666666666666
228
+ 22.866666666666667
229
+ 23.0
230
+ 23.166666666666668
231
+ 23.333333333333332
232
+ 23.5
233
+ 23.583333333333332
234
+ 23.75
235
+ 23.833333333333332
236
+ 23.916666666666668
237
+ 24.0
@@ -26,6 +26,12 @@ module Astronoby
26
26
  end
27
27
  alias_method :from_au, :from_astronomical_units
28
28
 
29
+ def from_parsecs(parsecs)
30
+ meters = parsecs * Constants::PARSEC_IN_METERS
31
+ from_meters(meters)
32
+ end
33
+ alias_method :from_pc, :from_parsecs
34
+
29
35
  def vector_from_meters(array)
30
36
  Vector.elements(array.map { from_meters(_1) })
31
37
  end
@@ -110,7 +110,7 @@ module Astronoby
110
110
  private
111
111
 
112
112
  def compute
113
- t = (julian_day - Epoch::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
113
+ t = (julian_day - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
114
114
  w = Angle.from_degrees(35999.373 * t) - Angle.from_degrees(2.47)
115
115
  delta = 1 +
116
116
  0.0334 * w.cos +
@@ -123,7 +123,7 @@ module Astronoby
123
123
  delta_days = 0.00001 * s / delta
124
124
  epoch = julian_day + delta_days
125
125
 
126
- Epoch.to_utc(epoch)
126
+ Instant.from_terrestrial_time(epoch).to_time
127
127
  end
128
128
 
129
129
  def julian_day
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ # Calculates extrema (minima and maxima) in the distance between two
5
+ # celestial bodies over a given time range using adaptive sampling and golden
6
+ # section search refinement.
7
+ class ExtremumCalculator
8
+ # Mathematical constants
9
+ PHI = (1 + Math.sqrt(5)) / 2
10
+ INVPHI = 1 / PHI
11
+ GOLDEN_SECTION_TOLERANCE = 1e-5
12
+
13
+ # Algorithm parameters
14
+ MIN_SAMPLES_PER_PERIOD = 20
15
+ DUPLICATE_THRESHOLD_DAYS = 0.5
16
+ BOUNDARY_BUFFER_DAYS = 0.01
17
+
18
+ # Orbital periods
19
+ ORBITAL_PERIODS = {
20
+ "Astronoby::Moon" => 27.504339,
21
+ "Astronoby::Mercury" => 87.969,
22
+ "Astronoby::Venus" => 224.701,
23
+ "Astronoby::Earth" => 365.256,
24
+ "Astronoby::Mars" => 686.98,
25
+ "Astronoby::Jupiter" => 4332.59,
26
+ "Astronoby::Saturn" => 10759.22,
27
+ "Astronoby::Uranus" => 30688.5,
28
+ "Astronoby::Neptune" => 60182.0
29
+ }.freeze
30
+
31
+ # @param ephem [::Ephem::SPK] Ephemeris data source
32
+ # @param body [Astronoby::SolarSystemBody] The celestial body to track
33
+ # @param primary_body [Astronoby::SolarSystemBody] The reference body
34
+ # (e.g., Sun for planetary orbits)
35
+ # @param samples_per_period [Integer] Number of samples to take per orbital
36
+ # period
37
+ def initialize(
38
+ body:,
39
+ primary_body:,
40
+ ephem:,
41
+ samples_per_period: 60
42
+ )
43
+ @ephem = ephem
44
+ @body = body
45
+ @primary_body = primary_body
46
+ @orbital_period = ORBITAL_PERIODS.fetch(body.name)
47
+ @samples_per_period = samples_per_period
48
+ end
49
+
50
+ # Finds all apoapsis events between two times
51
+ # @param start_time [Time] Start time
52
+ # @param end_time [Time] End time
53
+ # @return [Array<Astronoby::ExtremumEvent>] Array of apoapsis events
54
+ def apoapsis_events_between(start_time, end_time)
55
+ find_extrema(
56
+ Astronoby::Instant.from_time(start_time).tt,
57
+ Astronoby::Instant.from_time(end_time).tt,
58
+ type: :maximum
59
+ )
60
+ end
61
+
62
+ # Finds all periapsis events between two times
63
+ # @param start_time [Time] Start time
64
+ # @param end_time [Time] End time
65
+ # @return [Array<Astronoby::ExtremumEvent>] Array of periapsis events
66
+ def periapsis_events_between(start_time, end_time)
67
+ find_extrema(
68
+ Astronoby::Instant.from_time(start_time).tt,
69
+ Astronoby::Instant.from_time(end_time).tt,
70
+ type: :minimum
71
+ )
72
+ end
73
+
74
+ # Finds extrema (minima or maxima) in the distance between bodies within
75
+ # a time range
76
+ # @param start_jd [Float] Start time in Julian Date (Terrestrial Time)
77
+ # @param end_jd [Float] End time in Julian Date (Terrestrial Time)
78
+ # @param type [Symbol] :maximum or :minimum
79
+ # @return [Array<Astronoby::ExtremumEvent>] Array of extrema events
80
+ def find_extrema(start_jd, end_jd, type: :maximum)
81
+ # 1: Find extrema candidates through adaptive sampling
82
+ candidates = find_extrema_candidates(start_jd, end_jd, type)
83
+
84
+ # 2: Refine each candidate using golden section search
85
+ refined_extrema = candidates
86
+ .map { |candidate| refine_extremum(candidate, type) }
87
+ .compact
88
+
89
+ # 3: Remove duplicates and boundary artifacts
90
+ refined_extrema = remove_duplicates(refined_extrema)
91
+ filter_boundary_artifacts(refined_extrema, start_jd, end_jd)
92
+ end
93
+
94
+ private
95
+
96
+ def distance_at(jd)
97
+ instant = Instant.from_terrestrial_time(jd)
98
+ body_geometric = @body.geometric(ephem: @ephem, instant: instant)
99
+ primary_geometric = @primary_body
100
+ .geometric(ephem: @ephem, instant: instant)
101
+
102
+ distance_vector = body_geometric.position - primary_geometric.position
103
+ distance_vector.magnitude
104
+ end
105
+
106
+ def find_extrema_candidates(start_jd, end_jd, type)
107
+ samples = collect_samples(start_jd, end_jd)
108
+ find_local_extrema_in_samples(samples, type)
109
+ end
110
+
111
+ def collect_samples(start_jd, end_jd)
112
+ duration = end_jd - start_jd
113
+ sample_count = calculate_sample_count(duration)
114
+ step = duration / sample_count
115
+
116
+ (0..sample_count).map do |i|
117
+ jd = start_jd + (i * step)
118
+ {jd: jd, value: distance_at(jd)}
119
+ end
120
+ end
121
+
122
+ def calculate_sample_count(duration)
123
+ # Adaptive sampling: scale with duration and orbital period
124
+ periods_in_range = duration / @orbital_period
125
+ base_samples = (periods_in_range * @samples_per_period).to_i
126
+
127
+ # Ensure minimum sample density for short ranges
128
+ [base_samples, MIN_SAMPLES_PER_PERIOD].max
129
+ end
130
+
131
+ def find_local_extrema_in_samples(samples, type)
132
+ candidates = []
133
+
134
+ # Check each interior point for local extrema
135
+ (1...samples.length - 1).each do |i|
136
+ if local_extremum?(samples, i, type)
137
+ candidates << {
138
+ start_jd: samples[i - 1][:jd],
139
+ end_jd: samples[i + 1][:jd],
140
+ center_jd: samples[i][:jd]
141
+ }
142
+ end
143
+ end
144
+
145
+ candidates
146
+ end
147
+
148
+ def local_extremum?(samples, index, type)
149
+ current_val = samples[index][:value].m
150
+ prev_val = samples[index - 1][:value].m
151
+ next_val = samples[index + 1][:value].m
152
+
153
+ if type == :maximum
154
+ current_val > prev_val && current_val > next_val
155
+ else
156
+ current_val < prev_val && current_val < next_val
157
+ end
158
+ end
159
+
160
+ def refine_extremum(candidate, type)
161
+ golden_section_search(candidate[:start_jd], candidate[:end_jd], type)
162
+ end
163
+
164
+ def golden_section_search(a, b, type)
165
+ return nil if b <= a
166
+
167
+ tol = GOLDEN_SECTION_TOLERANCE * (b - a).abs
168
+
169
+ # Initial points using golden ratio
170
+ x1 = a + (1 - INVPHI) * (b - a)
171
+ x2 = a + INVPHI * (b - a)
172
+
173
+ f1 = distance_at(x1).m
174
+ f2 = distance_at(x2).m
175
+
176
+ while (b - a).abs > tol
177
+ should_keep_left = (type == :maximum) ? (f1 > f2) : (f1 < f2)
178
+
179
+ if should_keep_left
180
+ b = x2
181
+ x2 = x1
182
+ f2 = f1
183
+ x1 = a + (1 - INVPHI) * (b - a)
184
+ f1 = distance_at(x1).m
185
+ else
186
+ a = x1
187
+ x1 = x2
188
+ f1 = f2
189
+ x2 = a + INVPHI * (b - a)
190
+ f2 = distance_at(x2).m
191
+ end
192
+ end
193
+
194
+ mid = (a + b) / 2
195
+ ExtremumEvent.new(
196
+ Instant.from_terrestrial_time(mid),
197
+ distance_at(mid)
198
+ )
199
+ end
200
+
201
+ def remove_duplicates(extrema)
202
+ return extrema if extrema.length <= 1
203
+
204
+ cleaned = [extrema.first]
205
+
206
+ extrema.each_with_index do |current, i|
207
+ next if i == 0
208
+
209
+ is_duplicate = cleaned.any? do |existing|
210
+ time_diff = (
211
+ current.instant.tt - existing.instant.tt
212
+ ).abs
213
+ time_diff < DUPLICATE_THRESHOLD_DAYS
214
+ end
215
+
216
+ cleaned << current unless is_duplicate
217
+ end
218
+
219
+ cleaned
220
+ end
221
+
222
+ def filter_boundary_artifacts(extrema, start_jd, end_jd)
223
+ extrema.reject do |extreme|
224
+ start_diff = (extreme.instant.tt - start_jd).abs
225
+ end_diff = (extreme.instant.tt - end_jd).abs
226
+
227
+ too_close_to_start = start_diff < BOUNDARY_BUFFER_DAYS
228
+ too_close_to_end = end_diff < BOUNDARY_BUFFER_DAYS
229
+ too_close_to_start || too_close_to_end
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ # Represents an extremum event with its timing and value
5
+ class ExtremumEvent
6
+ attr_reader :instant, :value
7
+
8
+ # @param instant [Astronoby::Instant] When the event occurs
9
+ # @param value [Object] The extreme value
10
+ def initialize(instant, value)
11
+ @instant = instant
12
+ @value = value
13
+ end
14
+ end
15
+ end