astronoby 0.5.0 → 0.7.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +162 -0
  5. data/Gemfile.lock +54 -34
  6. data/README.md +42 -272
  7. data/UPGRADING.md +238 -0
  8. data/benchmark/README.md +131 -0
  9. data/benchmark/benchmark.rb +259 -0
  10. data/benchmark/data/imcce.csv.zip +0 -0
  11. data/benchmark/data/sun_calc.csv.zip +0 -0
  12. data/lib/astronoby/aberration.rb +56 -31
  13. data/lib/astronoby/angle.rb +20 -16
  14. data/lib/astronoby/angles/dms.rb +2 -2
  15. data/lib/astronoby/angles/hms.rb +2 -2
  16. data/lib/astronoby/bodies/earth.rb +56 -0
  17. data/lib/astronoby/bodies/jupiter.rb +11 -0
  18. data/lib/astronoby/bodies/mars.rb +11 -0
  19. data/lib/astronoby/bodies/mercury.rb +11 -0
  20. data/lib/astronoby/bodies/moon.rb +50 -285
  21. data/lib/astronoby/bodies/neptune.rb +11 -0
  22. data/lib/astronoby/bodies/saturn.rb +11 -0
  23. data/lib/astronoby/bodies/solar_system_body.rb +122 -0
  24. data/lib/astronoby/bodies/sun.rb +16 -220
  25. data/lib/astronoby/bodies/uranus.rb +11 -0
  26. data/lib/astronoby/bodies/venus.rb +11 -0
  27. data/lib/astronoby/constants.rb +13 -1
  28. data/lib/astronoby/coordinates/ecliptic.rb +2 -37
  29. data/lib/astronoby/coordinates/equatorial.rb +25 -7
  30. data/lib/astronoby/coordinates/horizontal.rb +0 -46
  31. data/lib/astronoby/corrections/light_time_delay.rb +90 -0
  32. data/lib/astronoby/deflection.rb +187 -0
  33. data/lib/astronoby/distance.rb +9 -0
  34. data/lib/astronoby/ephem.rb +39 -0
  35. data/lib/astronoby/equinox_solstice.rb +21 -18
  36. data/lib/astronoby/errors.rb +4 -0
  37. data/lib/astronoby/events/moon_phases.rb +2 -1
  38. data/lib/astronoby/events/rise_transit_set_calculator.rb +352 -0
  39. data/lib/astronoby/events/rise_transit_set_event.rb +13 -0
  40. data/lib/astronoby/events/rise_transit_set_events.rb +13 -0
  41. data/lib/astronoby/events/twilight_calculator.rb +166 -0
  42. data/lib/astronoby/events/twilight_event.rb +28 -0
  43. data/lib/astronoby/instant.rb +171 -0
  44. data/lib/astronoby/mean_obliquity.rb +23 -10
  45. data/lib/astronoby/nutation.rb +227 -42
  46. data/lib/astronoby/observer.rb +66 -1
  47. data/lib/astronoby/precession.rb +91 -17
  48. data/lib/astronoby/reference_frame.rb +49 -0
  49. data/lib/astronoby/reference_frames/apparent.rb +60 -0
  50. data/lib/astronoby/reference_frames/astrometric.rb +21 -0
  51. data/lib/astronoby/reference_frames/geometric.rb +20 -0
  52. data/lib/astronoby/reference_frames/mean_of_date.rb +38 -0
  53. data/lib/astronoby/reference_frames/topocentric.rb +82 -0
  54. data/lib/astronoby/time/greenwich_sidereal_time.rb +1 -1
  55. data/lib/astronoby/true_obliquity.rb +2 -1
  56. data/lib/astronoby/util/maths.rb +68 -49
  57. data/lib/astronoby/util/time.rb +466 -32
  58. data/lib/astronoby/vector.rb +36 -0
  59. data/lib/astronoby/velocity.rb +116 -0
  60. data/lib/astronoby/version.rb +1 -1
  61. data/lib/astronoby.rb +26 -5
  62. metadata +81 -18
  63. data/.tool-versions +0 -1
  64. data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +0 -143
  65. data/lib/astronoby/events/observation_events.rb +0 -259
  66. data/lib/astronoby/events/rise_transit_set_iteration.rb +0 -215
  67. data/lib/astronoby/events/twilight_events.rb +0 -121
  68. data/lib/astronoby/util/astrodynamics.rb +0 -60
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Geometric < ReferenceFrame
5
+ def initialize(
6
+ position:,
7
+ velocity:,
8
+ instant:,
9
+ target_body:
10
+ )
11
+ super(
12
+ position: position,
13
+ velocity: velocity,
14
+ instant: instant,
15
+ center_identifier: SolarSystemBody::SOLAR_SYSTEM_BARYCENTER,
16
+ target_body: target_body
17
+ )
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class MeanOfDate < ReferenceFrame
5
+ def self.build_from_geometric(
6
+ instant:,
7
+ target_geometric:,
8
+ earth_geometric:,
9
+ target_body:
10
+ )
11
+ position = target_geometric.position - earth_geometric.position
12
+ velocity = target_geometric.velocity - earth_geometric.velocity
13
+ precession_matrix = Precession.matrix_for(instant)
14
+ corrected_position = Distance.vector_from_m(
15
+ precession_matrix * position.map(&:m)
16
+ )
17
+ corrected_velocity = Velocity.vector_from_mps(
18
+ precession_matrix * velocity.map(&:mps)
19
+ )
20
+
21
+ new(
22
+ position: corrected_position,
23
+ velocity: corrected_velocity,
24
+ instant: instant,
25
+ center_identifier: SolarSystemBody::EARTH,
26
+ target_body: target_body
27
+ )
28
+ end
29
+
30
+ def ecliptic
31
+ @ecliptic ||= begin
32
+ return Coordinates::Ecliptic.zero if distance.zero?
33
+
34
+ equatorial.to_ecliptic(epoch: @instant.tdb)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Topocentric < ReferenceFrame
5
+ def self.build_from_apparent(
6
+ apparent:,
7
+ observer:,
8
+ instant:,
9
+ target_body:
10
+ )
11
+ matrix = observer.earth_fixed_rotation_matrix_for(instant)
12
+
13
+ observer_position = Distance.vector_from_meters(
14
+ matrix * observer.geocentric_position.map(&:m)
15
+ )
16
+ observer_velocity = Velocity.vector_from_mps(
17
+ matrix * observer.geocentric_velocity.map(&:kmps)
18
+ )
19
+
20
+ position = apparent.position - observer_position
21
+ velocity = apparent.velocity - observer_velocity
22
+
23
+ new(
24
+ position: position,
25
+ velocity: velocity,
26
+ instant: instant,
27
+ center_identifier: [observer.longitude, observer.latitude],
28
+ target_body: target_body,
29
+ observer: observer
30
+ )
31
+ end
32
+
33
+ def initialize(
34
+ position:,
35
+ velocity:,
36
+ instant:,
37
+ center_identifier:,
38
+ target_body:,
39
+ observer:
40
+ )
41
+ super(
42
+ position: position,
43
+ velocity: velocity,
44
+ instant: instant,
45
+ center_identifier: center_identifier,
46
+ target_body: target_body
47
+ )
48
+ @observer = observer
49
+ end
50
+
51
+ def angular_diameter
52
+ @angular_radius ||= begin
53
+ return Angle.zero if @position.zero?
54
+
55
+ Angle.from_radians(
56
+ Math.atan(@target_body.class::EQUATORIAL_RADIUS.m / distance.m) * 2
57
+ )
58
+ end
59
+ end
60
+
61
+ def ecliptic
62
+ @ecliptic ||= begin
63
+ return Coordinates::Ecliptic.zero if distance.zero?
64
+
65
+ equatorial.to_ecliptic(epoch: @instant.tdb)
66
+ end
67
+ end
68
+
69
+ def horizontal(refraction: false)
70
+ horizontal = equatorial.to_horizontal(
71
+ time: @instant.to_time,
72
+ observer: @observer
73
+ )
74
+
75
+ if refraction
76
+ Refraction.correct_horizontal_coordinates(coordinates: horizontal)
77
+ else
78
+ horizontal
79
+ end
80
+ end
81
+ end
82
+ end
@@ -65,7 +65,7 @@ module Astronoby
65
65
 
66
66
  utc = SIDEREAL_MINUTE_IN_UT_MINUTE * a
67
67
 
68
- Util::Time.decimal_hour_to_time(date, utc)
68
+ Util::Time.decimal_hour_to_time(date, 0, utc)
69
69
  end
70
70
 
71
71
  def to_lst(longitude:)
@@ -3,8 +3,9 @@
3
3
  module Astronoby
4
4
  class TrueObliquity
5
5
  def self.for_epoch(epoch)
6
+ instant = Instant.from_utc_julian_date(epoch)
6
7
  mean_obliquity = MeanObliquity.for_epoch(epoch)
7
- nutation = Nutation.for_obliquity_of_the_ecliptic(epoch: epoch)
8
+ nutation = Nutation.new(instant: instant).nutation_in_obliquity
8
9
 
9
10
  mean_obliquity + nutation
10
11
  end
@@ -3,69 +3,88 @@
3
3
  module Astronoby
4
4
  module Util
5
5
  module Maths
6
- class << self
7
- # Source:
8
- # Title: Astronomical Algorithms
9
- # Author: Jean Meeus
10
- # Edition: 2nd edition
11
- # Chapter: 3 - Interpolation
6
+ module_function
12
7
 
13
- # @param values [Array<Numeric>] First term
14
- # @param factor [Numeric] Interpolation factor
15
- # @return [Float] Interpolated value
16
- def interpolate(values, factor)
17
- unless factor.between?(0, 1)
18
- raise IncompatibleArgumentsError,
19
- "Interpolation factor must be between 0 and 1, got #{factor}"
20
- end
21
-
22
- if values.length == 3
23
- return interpolate_3_terms(values, factor)
24
- elsif values.length == 5
25
- return interpolate_5_terms(values, factor)
26
- end
8
+ def dot_product(a, b)
9
+ a.zip(b).sum { |x, y| x * y }
10
+ end
27
11
 
28
- raise IncompatibleArgumentsError,
29
- "Only 3 or 5 terms are supported for interpolation"
30
- end
12
+ # Find maximum altitude using quadratic interpolation
13
+ # @param t1, t2, t3 [Time] Three consecutive times
14
+ # @param alt1, alt2, alt3 [Float] Corresponding altitudes
15
+ # @return [::Time] Time of maximum altitude
16
+ def quadratic_maximum(t1, t2, t3, alt1, alt2, alt3)
17
+ # Convert to float seconds for arithmetic
18
+ x1, x2, x3 = t1.to_f, t2.to_f, t3.to_f
19
+ y1, y2, y3 = alt1, alt2, alt3
31
20
 
32
- private
21
+ # Quadratic interpolation formula
22
+ denom = (x1 - x2) * (x1 - x3) * (x2 - x3)
23
+ a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
24
+ b = (x3 * x3 * (y1 - y2) +
25
+ x2 * x2 * (y3 - y1) +
26
+ x1 * x1 * (y2 - y3)) / denom
33
27
 
34
- # @return [Float] Interpolated value
35
- def interpolate_3_terms(terms, factor)
36
- y1, y2, y3 = terms
28
+ # Maximum is at -b/2a
29
+ max_t = -b / (2 * a)
37
30
 
38
- a = y2 - y1
39
- b = y3 - y2
40
- c = b - a
31
+ ::Time.at(max_t)
32
+ end
41
33
 
42
- y2 + (factor / 2.0) * (a + b + factor * c)
34
+ # Linear interpolation between two points
35
+ # @param x1 [Numeric] First x value
36
+ # @param x2 [Numeric] Second x value
37
+ # @param y1 [Numeric] First y value
38
+ # @param y2 [Numeric] Second y value
39
+ # @param target_y [Numeric] Target y value (default: 0)
40
+ # @return [Numeric] Interpolated x value where y=target_y
41
+ def self.linear_interpolate(x1, x2, y1, y2, target_y = 0)
42
+ # Handle horizontal line case (avoid division by zero)
43
+ if (y2 - y1).abs < 1e-10
44
+ # If target_y matches the line's y-value (within precision), return
45
+ # midpoint. Otherwise, return one of the endpoints (no unique solution
46
+ # exists)
47
+ return ((target_y - y1).abs < 1e-10) ? (x1 + x2) / 2.0 : x1
43
48
  end
44
49
 
45
- # @return [Float] Interpolated value
46
- def interpolate_5_terms(terms, factor)
47
- y1, y2, y3, y4, y5 = terms
50
+ # Handle vertical line case
51
+ if (x2 - x1).abs < 1e-10
52
+ # For a vertical line, there's only one x-value possible
53
+ return x1
54
+ end
48
55
 
49
- a = y2 - y1
50
- b = y3 - y2
51
- c = y4 - y3
52
- d = y5 - y4
56
+ # Standard linear interpolation formula
57
+ x1 + (target_y - y1) * (x2 - x1) / (y2 - y1)
58
+ end
53
59
 
54
- e = b - a
55
- f = c - b
56
- g = d - c
60
+ # Creates an array of evenly spaced values between start and stop.
61
+ # @param start [Numeric] The starting value of the sequence
62
+ # @param stop [Numeric] The end value of the sequence
63
+ # @param num [Integer] Number of samples to generate. Default is 50
64
+ # @param endpoint [Boolean] If true, stop is the last sample. Otherwise,
65
+ # it is not included. Default is true
66
+ # @return [Array<Numeric>] Array of evenly spaced values
67
+ # @raise [ArgumentError] If num is less than 1
68
+ def linspace(start, stop, num = 50, endpoint = true)
69
+ raise ArgumentError, "Number of samples must be at least 1" if num < 1
70
+ return [start] if num == 1
57
71
 
58
- h = f - e
59
- j = g - f
72
+ step = if endpoint
73
+ (stop - start) / (num - 1).to_f
74
+ else
75
+ (stop - start) / num.to_f
76
+ end
60
77
 
61
- k = j - h
78
+ result = Array.new(num)
79
+ current = start
62
80
 
63
- y3 +
64
- factor * ((b + c) / 2.0 - (h + j) / 12.0) +
65
- factor**2 * (f / 2.0 - k / 24.0) +
66
- factor**3 * (h + j) / 12.0 +
67
- factor**4 * k / 24.0
81
+ (num - 1).times do |i|
82
+ result[i] = current
83
+ current += step
68
84
  end
85
+
86
+ result[num - 1] = endpoint ? stop : current
87
+ result
69
88
  end
70
89
  end
71
90
  end