astronoby 0.0.1 → 0.2.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.
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Coordinates
5
+ class Equatorial
6
+ attr_reader :declination, :right_ascension, :hour_angle, :epoch
7
+
8
+ def initialize(
9
+ declination:,
10
+ right_ascension:,
11
+ hour_angle: nil,
12
+ epoch: Epoch::DEFAULT_EPOCH
13
+ )
14
+ @right_ascension = right_ascension
15
+ @declination = declination
16
+ @hour_angle = hour_angle
17
+ @epoch = epoch
18
+ end
19
+
20
+ def compute_hour_angle(time:, longitude:)
21
+ lst = GreenwichSiderealTime
22
+ .from_utc(time.utc)
23
+ .to_lst(longitude: longitude)
24
+
25
+ ha = (lst.time - @right_ascension.hours)
26
+ ha += 24 if ha.negative?
27
+
28
+ Angle.as_hours(ha)
29
+ end
30
+
31
+ def to_horizontal(time:, latitude:, longitude:)
32
+ ha = @hour_angle || compute_hour_angle(time: time, longitude: longitude)
33
+ t0 = @declination.sin * latitude.sin +
34
+ @declination.cos * latitude.cos * ha.cos
35
+ altitude = Angle.asin(t0)
36
+
37
+ t1 = @declination.sin - latitude.sin * altitude.sin
38
+ t2 = t1 / (latitude.cos * altitude.cos)
39
+ azimuth = Angle.acos(t2)
40
+
41
+ if ha.sin.positive?
42
+ azimuth = Angle.as_degrees(BigDecimal("360") - azimuth.degrees)
43
+ end
44
+
45
+ Horizontal.new(
46
+ azimuth: azimuth,
47
+ altitude: altitude,
48
+ latitude: latitude,
49
+ longitude: longitude
50
+ )
51
+ end
52
+
53
+ # Source:
54
+ # Title: Celestial Calculations
55
+ # Author: J. L. Lawrence
56
+ # Edition: MIT Press
57
+ # Chapter: 4 - Orbits and Coordinate Systems
58
+ def to_ecliptic(epoch:)
59
+ mean_obliquity = MeanObliquity.for_epoch(epoch)
60
+
61
+ y = Angle.as_radians(
62
+ @right_ascension.sin * mean_obliquity.cos +
63
+ @declination.tan * mean_obliquity.sin
64
+ )
65
+ x = Angle.as_radians(@right_ascension.cos)
66
+ r = Angle.atan(y.radians / x.radians)
67
+ longitude = Util::Trigonometry.adjustement_for_arctangent(y, x, r)
68
+
69
+ latitude = Angle.asin(
70
+ @declination.sin * mean_obliquity.cos -
71
+ @declination.cos * mean_obliquity.sin * @right_ascension.sin
72
+ )
73
+
74
+ Ecliptic.new(
75
+ latitude: latitude,
76
+ longitude: longitude
77
+ )
78
+ end
79
+
80
+ def to_epoch(epoch)
81
+ Precession.for_equatorial_coordinates(
82
+ coordinates: self,
83
+ epoch: epoch
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Coordinates
5
+ class Horizontal
6
+ attr_reader :azimuth, :altitude, :latitude, :longitude
7
+
8
+ def initialize(
9
+ azimuth:,
10
+ altitude:,
11
+ latitude:,
12
+ longitude:
13
+ )
14
+ @azimuth = azimuth
15
+ @altitude = altitude
16
+ @latitude = latitude
17
+ @longitude = longitude
18
+ end
19
+
20
+ def to_equatorial(time:)
21
+ t0 = @altitude.sin * @latitude.sin +
22
+ @altitude.cos * @latitude.cos * @azimuth.cos
23
+
24
+ declination = Angle.asin(t0)
25
+
26
+ t1 = @altitude.sin - @latitude.sin * declination.sin
27
+
28
+ hour_angle_degrees = Angle
29
+ .acos(t1 / (@latitude.cos * declination.cos))
30
+ .degrees
31
+
32
+ if @azimuth.sin.positive?
33
+ hour_angle_degrees = Angle
34
+ .as_degrees(BigDecimal("360") - hour_angle_degrees)
35
+ .degrees
36
+ end
37
+
38
+ hour_angle_hours = Angle.as_degrees(hour_angle_degrees).hours
39
+ lst = GreenwichSiderealTime
40
+ .from_utc(time.utc)
41
+ .to_lst(longitude: @longitude)
42
+ right_ascension_decimal = lst.time - hour_angle_hours
43
+ right_ascension_decimal += 24 if right_ascension_decimal.negative?
44
+ right_ascension = Angle.as_hours(right_ascension_decimal)
45
+
46
+ Equatorial.new(
47
+ right_ascension: right_ascension,
48
+ declination: declination
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Epoch
5
+ B1900 = 2415020.3135
6
+ J1900 = 2415020.0
7
+ B1950 = 2433282.4235
8
+ J1950 = 2433282.5
9
+ J2000 = 2451545.0
10
+
11
+ DEFAULT_EPOCH = J2000
12
+ DAYS_PER_JULIAN_CENTURY = 36525.0
13
+
14
+ JULIAN_DAY_NUMBER_OFFSET = 0.5
15
+
16
+ def self.from_time(time)
17
+ time.to_datetime.ajd
18
+ end
19
+
20
+ def self.to_utc(epoch)
21
+ DateTime.jd(epoch + JULIAN_DAY_NUMBER_OFFSET).to_time.utc
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class EquinoxSolstice
5
+ # Source:
6
+ # Title: Astronomical Algorithms
7
+ # Author: Jean Meeus
8
+ # Edition: 2nd edition
9
+ # Chapter: 27 - Equinoxes and Soltices
10
+
11
+ EVENTS = [
12
+ MARCH_EQUINOX = 0,
13
+ JUNE_SOLSTICE = 1,
14
+ SEPTEMBER_EQUINOX = 2,
15
+ DECEMBER_SOLSTICE = 3
16
+ ].freeze
17
+
18
+ JDE_COMPONENTS = {
19
+ MARCH_EQUINOX => [
20
+ 2451623.80984,
21
+ 365242.37404,
22
+ 0.05169,
23
+ -0.00411,
24
+ -0.00057
25
+ ],
26
+ JUNE_SOLSTICE => [
27
+ 2451716.56767,
28
+ 365241.62603,
29
+ 0.00325,
30
+ 0.00888,
31
+ -0.00030
32
+ ],
33
+ SEPTEMBER_EQUINOX => [
34
+ 2451810.21715,
35
+ 365242.01767,
36
+ -0.11575,
37
+ 0.00337,
38
+ 0.00078
39
+ ],
40
+ DECEMBER_SOLSTICE => [
41
+ 2451900.05952,
42
+ 365242.74049,
43
+ -0.06223,
44
+ -0.00823,
45
+ 0.00032
46
+ ]
47
+ }
48
+
49
+ PERIODIC_TERMS = [
50
+ [485, 324.96, 1934.136],
51
+ [203, 337.23, 32964.467],
52
+ [199, 342.08, 20.186],
53
+ [182, 27.85, 445267.112],
54
+ [156, 73.14, 45036.886],
55
+ [136, 171.52, 22518.443],
56
+ [77, 222.54, 65928.934],
57
+ [74, 296.72, 3034.906],
58
+ [70, 243.58, 9037.513],
59
+ [58, 119.81, 33718.147],
60
+ [52, 297.17, 150.678],
61
+ [50, 21.02, 2281.226],
62
+ [45, 247.54, 29929.562],
63
+ [44, 325.15, 31555.956],
64
+ [29, 60.93, 4443.417],
65
+ [18, 155.12, 67555.328],
66
+ [17, 288.79, 4562.452],
67
+ [16, 198.04, 62894.029],
68
+ [14, 199.76, 31436.921],
69
+ [12, 95.39, 14577.848],
70
+ [12, 287.11, 31931.756],
71
+ [12, 320.81, 34777.259],
72
+ [9, 227.73, 1222.114],
73
+ [8, 15.45, 16859.074]
74
+ ].freeze
75
+
76
+ def self.march_equinox(year)
77
+ new(year, MARCH_EQUINOX).compute
78
+ end
79
+
80
+ def self.june_solstice(year)
81
+ new(year, JUNE_SOLSTICE).compute
82
+ end
83
+
84
+ def self.september_equinox(year)
85
+ new(year, SEPTEMBER_EQUINOX).compute
86
+ end
87
+
88
+ def self.december_solstice(year)
89
+ new(year, DECEMBER_SOLSTICE).compute
90
+ end
91
+
92
+ def initialize(year, event)
93
+ unless EVENTS.include?(event)
94
+ raise UnsupportedEventError.new(
95
+ "Expected a format between #{EVENTS.join(", ")}, got #{event}"
96
+ )
97
+ end
98
+
99
+ @event = event
100
+ @year = (year.to_i - 2000) / 1000.0
101
+ end
102
+
103
+ def compute
104
+ t = (julian_day - Epoch::J2000) / Epoch::DAYS_PER_JULIAN_CENTURY
105
+ w = Angle.as_degrees(35999.373 * t) - Angle.as_degrees(2.47)
106
+ delta = 1 +
107
+ 0.0334 * w.cos +
108
+ 0.0007 * Angle.as_degrees(w.degrees * 2).cos
109
+
110
+ s = PERIODIC_TERMS.sum do |a, b, c|
111
+ a * (Angle.as_degrees(b) + Angle.as_degrees(c * t)).cos
112
+ end
113
+
114
+ delta_days = 0.00001 * s / delta
115
+ epoch = julian_day + delta_days
116
+ epoch += correction(epoch)
117
+
118
+ Epoch.to_utc(epoch).round
119
+ end
120
+
121
+ private
122
+
123
+ def julian_day
124
+ component = JDE_COMPONENTS[@event]
125
+ component[0] +
126
+ component[1] * @year +
127
+ component[2] * @year**2 +
128
+ component[3] * @year**3 +
129
+ component[4] * @year**4
130
+ end
131
+
132
+ def correction(epoch)
133
+ sun = Sun.new(epoch: epoch)
134
+
135
+ nutation = Nutation.for_ecliptic_longitude(epoch: epoch)
136
+
137
+ earth_radius_vector = 1 / (
138
+ 1 +
139
+ sun.orbital_eccentricity.degrees *
140
+ (sun.true_anomaly - sun.longitude_at_perigee).cos
141
+ )
142
+ aberration = Angle.as_degrees(
143
+ Angle.as_dms(0, 0, 20.4898).degrees / -earth_radius_vector
144
+ )
145
+
146
+ corrected_longitude = sun.ecliptic_coordinates.longitude +
147
+ nutation +
148
+ aberration
149
+
150
+ 58 * Angle.as_degrees(@event * 90 - corrected_longitude.degrees).sin
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,7 @@
1
+ module Astronoby
2
+ class IncompatibleArgumentsError < ArgumentError; end
3
+
4
+ class UnsupportedFormatError < ArgumentError; end
5
+
6
+ class UnsupportedEventError < ArgumentError; end
7
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class GeocentricParallax
5
+ # Source:
6
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
7
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
8
+ # Edition: Cambridge University Press
9
+ # Chapter: 39 - Calculating correction for parallax
10
+
11
+ ASTRONOMICAL_UNIT_IN_METERS = 149_597_870_700
12
+ EARTH_FLATTENING_CORRECTION = BigDecimal("0.996647")
13
+ EARTH_EQUATORIAL_RADIUS = BigDecimal("6378140")
14
+
15
+ # Equatorial horizontal parallax
16
+ # @param distance [Numeric] Distance of the body from the center of the
17
+ # Earth, in meters
18
+ # @return [Astronoby::Angle] Equatorial horizontal parallax angle
19
+ def self.angle(distance:)
20
+ distance_in_earth_radius = distance / EARTH_EQUATORIAL_RADIUS
21
+ Angle.asin(1 / distance_in_earth_radius)
22
+ end
23
+
24
+ # Correct equatorial coordinates with the equatorial horizontal parallax
25
+ # @param latitude [Astronoby::Angle] Observer's latitude
26
+ # @param longitude [Astronoby::Angle] Observer's longitude
27
+ # @param elevation [Numeric] Observer's elevation above sea level in meters
28
+ # @param time [Time] Date-time of the observation
29
+ # @param coordinates [Astronoby::Coordinates::Equatorial]
30
+ # Equatorial coordinates of the observed body
31
+ # @param distance [Numeric] Distance of the observed body from the center of
32
+ # the Earth, in meters
33
+ # @return [Astronoby::Coordinates::Equatorial] Apparent equatorial
34
+ # coordinates with equatorial horizontal parallax
35
+ def self.for_equatorial_coordinates(
36
+ latitude:,
37
+ longitude:,
38
+ elevation:,
39
+ time:,
40
+ coordinates:,
41
+ distance:
42
+ )
43
+ new(
44
+ latitude,
45
+ longitude,
46
+ elevation,
47
+ time,
48
+ coordinates,
49
+ distance
50
+ ).apply
51
+ end
52
+
53
+ # @param latitude [Astronoby::Angle] Observer's latitude
54
+ # @param longitude [Astronoby::Angle] Observer's longitude
55
+ # @param elevation [Numeric] Observer's elevation above sea level in meters
56
+ # @param time [Time] Date-time of the observation
57
+ # @param coordinates [Astronoby::Coordinates::Equatorial] Equatorial
58
+ # coordinates of the observed body
59
+ # @param distance [Numeric] Distance of the observed body from the center of
60
+ # the Earth, in meters
61
+ def initialize(
62
+ latitude,
63
+ longitude,
64
+ elevation,
65
+ time,
66
+ coordinates,
67
+ distance
68
+ )
69
+ @latitude = latitude
70
+ @longitude = longitude
71
+ @elevation = elevation
72
+ @time = time
73
+ @coordinates = coordinates
74
+ @distance = distance
75
+ end
76
+
77
+ def apply
78
+ term1 = Angle.atan(EARTH_FLATTENING_CORRECTION * @latitude.tan)
79
+ quantity1 = term1.cos + elevation_ratio * @latitude.cos
80
+ quantity2 = EARTH_FLATTENING_CORRECTION * term1.sin +
81
+ elevation_ratio * @latitude.sin
82
+
83
+ term2 = quantity1 * hour_angle.sin
84
+ term3 = distance_in_earth_radius * declination.cos -
85
+ quantity1 * hour_angle.cos
86
+
87
+ delta = Angle.atan(term2 / term3)
88
+
89
+ apparent_hour_angle = hour_angle + delta
90
+ apparent_right_ascension = right_ascension - delta
91
+ apparent_declination = Angle.atan(
92
+ (
93
+ apparent_hour_angle.cos *
94
+ (distance_in_earth_radius * declination.sin - quantity2)
95
+ ) / (
96
+ distance_in_earth_radius * declination.cos * hour_angle.cos - quantity1
97
+ )
98
+ )
99
+
100
+ Coordinates::Equatorial.new(
101
+ right_ascension: apparent_right_ascension,
102
+ declination: apparent_declination,
103
+ epoch: @coordinates.epoch
104
+ )
105
+ end
106
+
107
+ private
108
+
109
+ def right_ascension
110
+ @coordinates.right_ascension
111
+ end
112
+
113
+ def declination
114
+ @coordinates.declination
115
+ end
116
+
117
+ def hour_angle
118
+ @_hour_angle ||=
119
+ @coordinates.compute_hour_angle(time: @time, longitude: @longitude)
120
+ end
121
+
122
+ def elevation_ratio
123
+ @elevation / EARTH_EQUATORIAL_RADIUS
124
+ end
125
+
126
+ def distance_in_earth_radius
127
+ @distance / EARTH_EQUATORIAL_RADIUS
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Astronoby
6
+ class MeanObliquity
7
+ # Source:
8
+ # IAU resolution in 2006 in favor of the P03 astronomical model
9
+ # The Astronomical Almanac for 2010
10
+
11
+ EPOCH_OF_REFERENCE = Epoch::DEFAULT_EPOCH
12
+ OBLIQUITY_OF_REFERENCE = 23.4392794
13
+
14
+ def self.for_epoch(epoch)
15
+ return obliquity_of_reference if epoch == EPOCH_OF_REFERENCE
16
+
17
+ t = (epoch - EPOCH_OF_REFERENCE) / Epoch::DAYS_PER_JULIAN_CENTURY
18
+
19
+ Angle.as_degrees(
20
+ obliquity_of_reference.degrees - (
21
+ 46.815 * t -
22
+ 0.0006 * t * t +
23
+ 0.00181 * t * t * t
24
+ ) / 3600
25
+ )
26
+ end
27
+
28
+ def self.obliquity_of_reference
29
+ Angle.as_dms(23, 26, 21.45)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Nutation
5
+ # Source:
6
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
7
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
8
+ # Edition: Cambridge University Press
9
+ # Chapter: 35 - Nutation
10
+
11
+ def initialize(epoch)
12
+ @epoch = epoch
13
+ end
14
+
15
+ def self.for_ecliptic_longitude(epoch:)
16
+ new(epoch).for_ecliptic_longitude
17
+ end
18
+
19
+ def self.for_obliquity_of_the_ecliptic(epoch:)
20
+ new(epoch).for_obliquity_of_the_ecliptic
21
+ end
22
+
23
+ def for_ecliptic_longitude
24
+ Angle.as_dms(
25
+ 0,
26
+ 0,
27
+ (
28
+ -17.2 * moon_ascending_node_longitude.sin -
29
+ 1.3 * Math.sin(2 * sun_mean_longitude.radians)
30
+ )
31
+ )
32
+ end
33
+
34
+ def for_obliquity_of_the_ecliptic
35
+ Angle.as_dms(
36
+ 0,
37
+ 0,
38
+ (
39
+ 9.2 * moon_ascending_node_longitude.cos +
40
+ 0.5 * Math.cos(2 * sun_mean_longitude.radians)
41
+ )
42
+ )
43
+ end
44
+
45
+ private
46
+
47
+ def julian_centuries
48
+ (@epoch - Epoch::J1900) / Epoch::DAYS_PER_JULIAN_CENTURY
49
+ end
50
+
51
+ def sun_mean_longitude
52
+ Angle.as_degrees(
53
+ (279.6967 + 360.0 * (centuries_a - centuries_a.to_i)) % 360
54
+ )
55
+ end
56
+
57
+ def moon_ascending_node_longitude
58
+ Angle.as_degrees(
59
+ (259.1833 - 360.0 * (centuries_b - centuries_b.to_i)) % 360
60
+ )
61
+ end
62
+
63
+ def centuries_a
64
+ 100.002136 * julian_centuries
65
+ end
66
+
67
+ def centuries_b
68
+ 5.372617 * julian_centuries
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Observer
5
+ DEFAULT_ELEVATION = 0
6
+ DEFAULT_TEMPERATURE = BigDecimal("283.15")
7
+ PRESSURE_AT_SEA_LEVEL = BigDecimal("1013.25")
8
+ PASCAL_PER_MILLIBAR = BigDecimal("0.01")
9
+ EARTH_GRAVITATIONAL_ACCELERATION = BigDecimal("9.80665")
10
+ MOLAR_MASS_OF_AIR = BigDecimal("0.0289644")
11
+ UNIVERSAL_GAS_CONSTANT = BigDecimal("8.31432")
12
+
13
+ attr_reader :latitude, :longitude, :elevation, :temperature
14
+
15
+ # @param latitude [Angle] geographic latitude of the observer
16
+ # @param longitude [Angle] geographic longitude of the observer
17
+ # @param elevation [Numeric] geographic elevation (or altitude) of the
18
+ # observer above sea level in meters
19
+ # @param temperature [Numeric] temperature at the observer's location in
20
+ # kelvins
21
+ # @param pressure [Numeric] atmospheric pressure at the observer's
22
+ # location in millibars
23
+ def initialize(
24
+ latitude:,
25
+ longitude:,
26
+ elevation: DEFAULT_ELEVATION,
27
+ temperature: DEFAULT_TEMPERATURE,
28
+ pressure: nil
29
+ )
30
+ @latitude = latitude
31
+ @longitude = longitude
32
+ @elevation = elevation
33
+ @temperature = temperature
34
+ @pressure = pressure
35
+ end
36
+
37
+ # Compute an estimation of the atmospheric pressure based on the elevation
38
+ # and temperature
39
+ #
40
+ # @return [BigDecimal] the atmospheric pressure in millibars.
41
+ def pressure
42
+ @pressure ||= PRESSURE_AT_SEA_LEVEL * pressure_ratio
43
+ end
44
+
45
+ private
46
+
47
+ # Source:
48
+ # Barometric formula
49
+ # https://en.wikipedia.org/wiki/Barometric_formula
50
+ def pressure_ratio
51
+ term1 = EARTH_GRAVITATIONAL_ACCELERATION * MOLAR_MASS_OF_AIR * @elevation
52
+ term2 = UNIVERSAL_GAS_CONSTANT * @temperature
53
+
54
+ Math.exp(-term1 / term2)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "matrix"
4
+
5
+ module Astronoby
6
+ class Precession
7
+ def self.for_equatorial_coordinates(coordinates:, epoch:)
8
+ new(coordinates, epoch).precess
9
+ end
10
+
11
+ def initialize(coordinates, epoch)
12
+ @coordinates = coordinates
13
+ @epoch = epoch
14
+ end
15
+
16
+ # Source:
17
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
18
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
19
+ # Edition: Cambridge University Press
20
+ # Chapter: 34 - Precession
21
+ def precess
22
+ matrix_a = matrix_for_epoch(@coordinates.epoch)
23
+ matrix_b = matrix_for_epoch(@epoch).transpose
24
+
25
+ vector = Vector[
26
+ @coordinates.right_ascension.cos * @coordinates.declination.cos,
27
+ @coordinates.right_ascension.sin * @coordinates.declination.cos,
28
+ @coordinates.declination.sin
29
+ ]
30
+
31
+ s = matrix_a * vector
32
+ w = matrix_b * s
33
+
34
+ Coordinates::Equatorial.new(
35
+ right_ascension: Util::Trigonometry.adjustement_for_arctangent(
36
+ Angle.as_radians(w[1]),
37
+ Angle.as_radians(w[0]),
38
+ Angle.atan(w[1] / w[0])
39
+ ),
40
+ declination: Angle.asin(w[2]),
41
+ epoch: @epoch
42
+ )
43
+ end
44
+
45
+ private
46
+
47
+ def matrix_for_epoch(epoch)
48
+ t = (epoch - Epoch::DEFAULT_EPOCH) / Epoch::DAYS_PER_JULIAN_CENTURY
49
+
50
+ zeta = Angle.as_degrees(
51
+ 0.6406161 * t + 0.0000839 * t * t + 0.000005 * t * t * t
52
+ )
53
+ z = Angle.as_degrees(
54
+ 0.6406161 * t + 0.0003041 * t * t + 0.0000051 * t * t * t
55
+ )
56
+ theta = Angle.as_degrees(
57
+ 0.5567530 * t - 0.0001185 * t * t - 0.0000116 * t * t * t
58
+ )
59
+
60
+ cx = zeta.cos
61
+ sx = zeta.sin
62
+ cz = z.cos
63
+ sz = z.sin
64
+ ct = theta.cos
65
+ st = theta.sin
66
+
67
+ Matrix[
68
+ [
69
+ cx * ct * cz - sx * sz,
70
+ cx * ct * sz + sx * cz,
71
+ cx * st
72
+ ],
73
+ [
74
+ -sx * ct * cz - cx * sz,
75
+ -sx * ct * sz + cx * cz,
76
+ -sx * st
77
+ ],
78
+ [
79
+ -st * cz,
80
+ -st * sz,
81
+ ct
82
+ ]
83
+ ]
84
+ end
85
+ end
86
+ end