astronoby 0.7.0 → 0.8.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +87 -3
  4. data/README.md +56 -32
  5. data/UPGRADING.md +50 -21
  6. data/docs/README.md +196 -0
  7. data/docs/angles.md +137 -0
  8. data/docs/celestial_bodies.md +107 -0
  9. data/docs/configuration.md +98 -0
  10. data/docs/coordinates.md +167 -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/twilight_times.md +123 -0
  20. data/lib/astronoby/bodies/earth.rb +8 -2
  21. data/lib/astronoby/bodies/jupiter.rb +17 -0
  22. data/lib/astronoby/bodies/mars.rb +17 -0
  23. data/lib/astronoby/bodies/mercury.rb +21 -0
  24. data/lib/astronoby/bodies/moon.rb +29 -36
  25. data/lib/astronoby/bodies/neptune.rb +21 -0
  26. data/lib/astronoby/bodies/saturn.rb +26 -0
  27. data/lib/astronoby/bodies/solar_system_body.rb +139 -29
  28. data/lib/astronoby/bodies/sun.rb +25 -2
  29. data/lib/astronoby/bodies/uranus.rb +5 -0
  30. data/lib/astronoby/bodies/venus.rb +25 -0
  31. data/lib/astronoby/cache.rb +188 -0
  32. data/lib/astronoby/configuration.rb +92 -0
  33. data/lib/astronoby/constants.rb +4 -1
  34. data/lib/astronoby/constellation.rb +12 -0
  35. data/lib/astronoby/constellations/data.rb +42 -0
  36. data/lib/astronoby/constellations/finder.rb +35 -0
  37. data/lib/astronoby/constellations/repository.rb +20 -0
  38. data/lib/astronoby/coordinates/equatorial.rb +3 -3
  39. data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
  40. data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
  41. data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
  42. data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
  43. data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
  44. data/lib/astronoby/equinox_solstice.rb +2 -2
  45. data/lib/astronoby/events/moon_phases.rb +15 -14
  46. data/lib/astronoby/events/rise_transit_set_calculator.rb +32 -8
  47. data/lib/astronoby/events/twilight_calculator.rb +115 -60
  48. data/lib/astronoby/events/twilight_events.rb +28 -0
  49. data/lib/astronoby/instant.rb +7 -2
  50. data/lib/astronoby/julian_date.rb +78 -0
  51. data/lib/astronoby/mean_obliquity.rb +8 -10
  52. data/lib/astronoby/nutation.rb +11 -3
  53. data/lib/astronoby/observer.rb +1 -1
  54. data/lib/astronoby/precession.rb +48 -38
  55. data/lib/astronoby/reference_frame.rb +2 -1
  56. data/lib/astronoby/reference_frames/apparent.rb +1 -1
  57. data/lib/astronoby/reference_frames/mean_of_date.rb +1 -1
  58. data/lib/astronoby/reference_frames/topocentric.rb +1 -11
  59. data/lib/astronoby/time/greenwich_sidereal_time.rb +2 -2
  60. data/lib/astronoby/true_obliquity.rb +2 -3
  61. data/lib/astronoby/util/time.rb +1 -1
  62. data/lib/astronoby/version.rb +1 -1
  63. data/lib/astronoby.rb +8 -1
  64. metadata +59 -11
  65. data/Gemfile +0 -5
  66. data/Gemfile.lock +0 -102
  67. data/benchmark/README.md +0 -131
  68. data/benchmark/benchmark.rb +0 -259
  69. data/benchmark/data/imcce.csv.zip +0 -0
  70. data/benchmark/data/sun_calc.csv.zip +0 -0
  71. data/lib/astronoby/epoch.rb +0 -22
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+ require "singleton"
5
+
6
+ module Astronoby
7
+ class Cache
8
+ include Singleton
9
+
10
+ Entry = Struct.new(:key, :value, :prev, :next)
11
+
12
+ DEFAULT_MAX_SIZE = 10_000
13
+
14
+ attr_reader :max_size
15
+
16
+ # @param max_size [Integer] Maximum number of entries allowed in the cache
17
+ # @return [void]
18
+ def initialize(max_size = DEFAULT_MAX_SIZE)
19
+ @max_size = max_size
20
+ @hash = {}
21
+ @head = nil
22
+ @tail = nil
23
+ @mutex = Monitor.new
24
+ end
25
+
26
+ # @param key [Object] the cache key
27
+ # @return [Object, nil] the value, or nil if not found
28
+ def [](key)
29
+ @mutex.synchronize do
30
+ entry = @hash[key]
31
+ if entry
32
+ move_to_head(entry)
33
+ entry.value
34
+ end
35
+ end
36
+ end
37
+
38
+ # @param key [Object]
39
+ # @param value [Object]
40
+ # @return [Object] the value set
41
+ def []=(key, value)
42
+ @mutex.synchronize do
43
+ entry = @hash[key]
44
+ if entry
45
+ entry.value = value
46
+ move_to_head(entry)
47
+ else
48
+ entry = Entry.new(key, value)
49
+ add_to_head(entry)
50
+ @hash[key] = entry
51
+ evict_last_recently_used if @hash.size > @max_size
52
+ end
53
+ end
54
+ end
55
+
56
+ # @param key [Object]
57
+ # @yieldreturn [Object] Value to store if key is missing.
58
+ # @return [Object] Cached or computed value.
59
+ def fetch(key)
60
+ return self[key] if @mutex.synchronize { @hash.key?(key) }
61
+
62
+ value = yield
63
+
64
+ @mutex.synchronize do
65
+ self[key] = value unless @hash.key?(key)
66
+ end
67
+
68
+ value
69
+ end
70
+
71
+ # @return [void]
72
+ def clear
73
+ @mutex.synchronize do
74
+ @hash.clear
75
+ @head = @tail = nil
76
+ end
77
+ end
78
+
79
+ # @return [Integer]
80
+ def size
81
+ @mutex.synchronize { @hash.size }
82
+ end
83
+
84
+ # @param new_size [Integer] the new cache limit.
85
+ # @return [void]
86
+ def max_size=(new_size)
87
+ raise ArgumentError, "max_size must be positive" unless new_size > 0
88
+ @mutex.synchronize do
89
+ @max_size = new_size
90
+ while @hash.size > @max_size
91
+ evict_last_recently_used
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def add_to_head(entry)
99
+ entry.prev = nil
100
+ entry.next = @head
101
+ @head.prev = entry if @head
102
+ @head = entry
103
+ @tail ||= entry
104
+ end
105
+
106
+ def move_to_head(entry)
107
+ return if @head == entry
108
+
109
+ # Unlink
110
+ entry.prev.next = entry.next if entry.prev
111
+ entry.next.prev = entry.prev if entry.next
112
+
113
+ # Update tail if needed
114
+ @tail = entry.prev if @tail == entry
115
+
116
+ # Insert as new head
117
+ entry.prev = nil
118
+ entry.next = @head
119
+ @head.prev = entry if @head
120
+ @head = entry
121
+ end
122
+
123
+ def evict_last_recently_used
124
+ if @tail
125
+ @hash.delete(@tail.key)
126
+ if @tail.prev
127
+ @tail = @tail.prev
128
+ @tail.next = nil
129
+ else
130
+ @head = @tail = nil
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ class NullCache
137
+ include Singleton
138
+
139
+ def [](key)
140
+ nil
141
+ end
142
+
143
+ def []=(key, value)
144
+ value
145
+ end
146
+
147
+ def fetch(key)
148
+ yield
149
+ end
150
+
151
+ def clear
152
+ end
153
+
154
+ def size
155
+ 0
156
+ end
157
+
158
+ def max_size=(new_size)
159
+ end
160
+ end
161
+
162
+ class CacheKey
163
+ class << self
164
+ # Generate a cache key with appropriate precision
165
+ # @param type [Symbol] The calculation type (e.g., :geometric, :nutation)
166
+ # @param instant [Astronoby::Instant] The time instant
167
+ # @param components [Array] Additional components for the key
168
+ # @return [Array] The complete cache key
169
+ def generate(type, instant, *components)
170
+ return nil unless Astronoby.configuration.cache_enabled?
171
+
172
+ precision = Astronoby.configuration.cache_precision(type)
173
+ rounded_tt = round_terrestrial_time(instant.tt, precision)
174
+ [type, rounded_tt, *components]
175
+ end
176
+
177
+ # Round terrestrial time to specified precision
178
+ # @param tt [Numeric] Terrestrial time value
179
+ # @param precision [Numeric] Precision in seconds
180
+ # @return [Numeric] Rounded time value
181
+ def round_terrestrial_time(tt, precision)
182
+ return tt if precision <= 0
183
+
184
+ tt.round(precision)
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Configuration
5
+ DEFAULT_PRECISIONS = {
6
+ geometric: 9, # 8.64e-5 seconds
7
+ nutation: 2, # 864 seconds
8
+ precession: 2, # 864 seconds
9
+ rise_transit_set: 5 # 0.1 seconds
10
+ }.freeze
11
+
12
+ attr_accessor :cache_enabled
13
+ attr_reader :cache_precisions
14
+
15
+ def initialize
16
+ @cache_enabled = false
17
+ @cache_precisions = DEFAULT_PRECISIONS.dup
18
+ @cache_instance = nil
19
+ end
20
+
21
+ # Configure cache precision for specific calculation types
22
+ # @param type [Symbol] The calculation type
23
+ # @param precision [Numeric] Precision in rounding of terrestrial time
24
+ # @return [Numeric] the precision for the given type
25
+ def cache_precision(type, precision = nil)
26
+ if precision.nil?
27
+ @cache_precisions[type] || DEFAULT_PRECISIONS[:geometric]
28
+ else
29
+ @cache_precisions[type] = precision
30
+ end
31
+ end
32
+
33
+ # Set all cache precisions at once
34
+ # @param precisions [Hash] Hash of calculation type to precision in rounding
35
+ # of terrestrial time
36
+ # @return [void]
37
+ def cache_precisions=(precisions)
38
+ @cache_precisions = DEFAULT_PRECISIONS.merge(precisions)
39
+ end
40
+
41
+ # Get the cache instance (singleton or null cache)
42
+ # @return [Astronoby::Cache, Astronoby::NullCache] the cache instance
43
+ def cache
44
+ if cache_enabled?
45
+ Cache.instance
46
+ else
47
+ NullCache.instance
48
+ end
49
+ end
50
+
51
+ def cache_enabled?
52
+ @cache_enabled
53
+ end
54
+
55
+ # Reset cache instance (useful for testing or configuration changes)
56
+ # @return [void]
57
+ def reset_cache!
58
+ cache.clear
59
+ end
60
+ end
61
+
62
+ class << self
63
+ # Global configuration instance
64
+ # @return [Astronoby::Configuration] the global configuration instance
65
+ def configuration
66
+ @configuration ||= Configuration.new
67
+ end
68
+
69
+ # Configuration block for setup
70
+ # @example
71
+ # Astronoby.configure do |config|
72
+ # config.cache_enabled = false
73
+ # config.cache_precision(:geometric, 9)
74
+ # end
75
+ def configure
76
+ yield(configuration)
77
+ configuration.reset_cache!
78
+ end
79
+
80
+ # Quick access to cache
81
+ # @return [Astronoby::Cache, Astronoby::NullCache] the cache instance
82
+ def cache
83
+ configuration.cache
84
+ end
85
+
86
+ # Reset configuration to defaults
87
+ # @return [void]
88
+ def reset_configuration!
89
+ @configuration = Configuration.new
90
+ end
91
+ end
92
+ end
@@ -2,9 +2,12 @@
2
2
 
3
3
  module Astronoby
4
4
  class Constants
5
- DAYS_PER_JULIAN_CENTURY = 36525.0
5
+ DAYS_PER_JULIAN_YEAR = 365.25
6
+ DAYS_PER_JULIAN_CENTURY = DAYS_PER_JULIAN_YEAR * 100
6
7
  DAYS_PER_JULIAN_MILLENIA = DAYS_PER_JULIAN_CENTURY * 10
7
8
 
9
+ TROPICAL_YEAR_AT_B1900 = 365.242198781
10
+
8
11
  HOURS_PER_DAY = 24.0
9
12
  DEGREES_PER_CIRCLE = 360.0
10
13
  RADIANS_PER_CIRCLE = 2 * Math::PI
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Constellation
5
+ attr_reader :name, :abbreviation
6
+
7
+ def initialize(name, abbreviation)
8
+ @name = name
9
+ @abbreviation = abbreviation
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Constellations
5
+ class Data
6
+ def self.sorted_right_ascensions
7
+ @sorted_right_ascensions ||=
8
+ read_file("sorted_right_ascensions.dat").map(&:to_f)
9
+ end
10
+
11
+ def self.sorted_declinations
12
+ @sorted_declinations ||=
13
+ read_file("sorted_declinations.dat").map(&:to_f)
14
+ end
15
+
16
+ def self.radec_to_index
17
+ @radec_to_index ||=
18
+ read_file("radec_to_index.dat").map do |line|
19
+ line.split.map(&:to_i)
20
+ end
21
+ end
22
+
23
+ def self.indexed_abbreviations
24
+ @indexed_abbreviations ||=
25
+ read_file("indexed_abbreviations.dat")
26
+ end
27
+
28
+ def self.constellation_names
29
+ @constellation_names ||=
30
+ read_file("constellation_names.dat").map(&:split)
31
+ end
32
+
33
+ def self.read_file(filename)
34
+ File.readlines(data_path(filename)).map(&:strip)
35
+ end
36
+
37
+ def self.data_path(filename)
38
+ File.join(__dir__, "..", "data", "constellations", filename)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Constellations
5
+ class Finder
6
+ # Finds the constellation that contains the given B1875 equatorial
7
+ # coordinates.
8
+ # @param equatorial_coordinates [Astronoby::Coordinates::Equatorial] The
9
+ # B1875 coordinates to search for.
10
+ # @return [Astronoby::Constellation, nil] The constellation that contains
11
+ # the coordinates, or nil if none is found.
12
+ def self.find(equatorial_coordinates)
13
+ ra_hours = equatorial_coordinates.right_ascension.hours
14
+ dec_degrees = equatorial_coordinates.declination.degrees
15
+ ra_index = Data
16
+ .sorted_right_ascensions
17
+ .bsearch_index { _1 >= ra_hours }
18
+ dec_index = Data
19
+ .sorted_declinations
20
+ .bsearch_index { _1 > dec_degrees }
21
+
22
+ return if ra_index.nil? || dec_index.nil?
23
+
24
+ abbreviation_index = Data.radec_to_index.dig(ra_index, dec_index)
25
+
26
+ return if abbreviation_index.nil?
27
+
28
+ constellation_abbreviation =
29
+ Data.indexed_abbreviations[abbreviation_index]
30
+
31
+ Repository.get(constellation_abbreviation)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Constellations
5
+ class Repository
6
+ # Returns a constellation object for the given abbreviation.
7
+ # @param abbreviation [String] The abbreviation of the constellation.
8
+ # @return [Astronoby::Constellation, nil] The constellation object or nil
9
+ # if not found.
10
+ def self.get(abbreviation)
11
+ @constellations ||= Data.constellation_names.map do |abbr, name|
12
+ constellation = Constellation.new(name, abbr)
13
+ [abbr, constellation]
14
+ end.to_h
15
+
16
+ @constellations[abbreviation]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -9,7 +9,7 @@ module Astronoby
9
9
  declination:,
10
10
  right_ascension:,
11
11
  hour_angle: nil,
12
- epoch: Epoch::DEFAULT_EPOCH
12
+ epoch: JulianDate::DEFAULT_EPOCH
13
13
  )
14
14
  @right_ascension = right_ascension
15
15
  @declination = declination
@@ -82,8 +82,8 @@ module Astronoby
82
82
  # Author: J. L. Lawrence
83
83
  # Edition: MIT Press
84
84
  # Chapter: 4 - Orbits and Coordinate Systems
85
- def to_ecliptic(epoch:)
86
- mean_obliquity = MeanObliquity.for_epoch(epoch)
85
+ def to_ecliptic(instant:)
86
+ mean_obliquity = MeanObliquity.at(instant)
87
87
 
88
88
  y = Angle.from_radians(
89
89
  @right_ascension.sin * mean_obliquity.cos +
@@ -0,0 +1,88 @@
1
+ And Andromeda
2
+ Ant Antlia
3
+ Aps Apus
4
+ Aql Aquila
5
+ Aqr Aquarius
6
+ Ara Ara
7
+ Ari Aries
8
+ Aur Auriga
9
+ Boo Boötes
10
+ Cae Caelum
11
+ Cam Camelopardalis
12
+ Cap Capricornus
13
+ Car Carina
14
+ Cas Cassiopeia
15
+ Cen Centaurus
16
+ Cep Cepheus
17
+ Cet Cetus
18
+ Cha Chamaeleon
19
+ Cir Circinus
20
+ CMa Canis Major
21
+ CMi Canis Minor
22
+ Cnc Cancer
23
+ Col Columba
24
+ Com Coma Berenices
25
+ CrA Corona Australis
26
+ CrB Corona Borealis
27
+ Crt Crater
28
+ Cru Crux
29
+ Crv Corvus
30
+ CVn Canes Venatici
31
+ Cyg Cygnus
32
+ Del Delphinus
33
+ Dor Dorado
34
+ Dra Draco
35
+ Equ Equuleus
36
+ Eri Eridanus
37
+ For Fornax
38
+ Gem Gemini
39
+ Gru Grus
40
+ Her Hercules
41
+ Hor Horologium
42
+ Hya Hydra
43
+ Hyi Hydrus
44
+ Ind Indus
45
+ Lac Lacerta
46
+ Leo Leo
47
+ Lep Lepus
48
+ Lib Libra
49
+ LMi Leo Minor
50
+ Lup Lupus
51
+ Lyn Lynx
52
+ Lyr Lyra
53
+ Men Mensa
54
+ Mic Microscopium
55
+ Mon Monoceros
56
+ Mus Musca
57
+ Nor Norma
58
+ Oct Octans
59
+ Oph Ophiuchus
60
+ Ori Orion
61
+ Pav Pavo
62
+ Peg Pegasus
63
+ Per Perseus
64
+ Phe Phoenix
65
+ Pic Pictor
66
+ PsA Piscis Austrinus
67
+ Psc Pisces
68
+ Pup Puppis
69
+ Pyx Pyxis
70
+ Ret Reticulum
71
+ Scl Sculptor
72
+ Sco Scorpius
73
+ Sct Scutum
74
+ Ser Serpens
75
+ Sex Sextans
76
+ Sge Sagitta
77
+ Sgr Sagittarius
78
+ Tau Taurus
79
+ Tel Telescopium
80
+ TrA Triangulum Australe
81
+ Tri Triangulum
82
+ Tuc Tucana
83
+ UMa Ursa Major
84
+ UMi Ursa Minor
85
+ Vel Vela
86
+ Vir Virgo
87
+ Vol Volans
88
+ Vul Vulpecula
@@ -0,0 +1,88 @@
1
+ And
2
+ Ant
3
+ Aps
4
+ Aql
5
+ Aqr
6
+ Ara
7
+ Ari
8
+ Aur
9
+ Boo
10
+ CMa
11
+ CMi
12
+ CVn
13
+ Cae
14
+ Cam
15
+ Cap
16
+ Car
17
+ Cas
18
+ Cen
19
+ Cep
20
+ Cet
21
+ Cha
22
+ Cir
23
+ Cnc
24
+ Col
25
+ Com
26
+ CrA
27
+ CrB
28
+ Crt
29
+ Cru
30
+ Crv
31
+ Cyg
32
+ Del
33
+ Dor
34
+ Dra
35
+ Equ
36
+ Eri
37
+ For
38
+ Gem
39
+ Gru
40
+ Her
41
+ Hor
42
+ Hya
43
+ Hyi
44
+ Ind
45
+ LMi
46
+ Lac
47
+ Leo
48
+ Lep
49
+ Lib
50
+ Lup
51
+ Lyn
52
+ Lyr
53
+ Men
54
+ Mic
55
+ Mon
56
+ Mus
57
+ Nor
58
+ Oct
59
+ Oph
60
+ Ori
61
+ Pav
62
+ Peg
63
+ Per
64
+ Phe
65
+ Pic
66
+ PsA
67
+ Psc
68
+ Pup
69
+ Pyx
70
+ Ret
71
+ Scl
72
+ Sco
73
+ Sct
74
+ Ser
75
+ Sex
76
+ Sge
77
+ Sgr
78
+ Tau
79
+ Tel
80
+ TrA
81
+ Tri
82
+ Tuc
83
+ UMa
84
+ UMi
85
+ Vel
86
+ Vir
87
+ Vol
88
+ Vul