1"""Basic tests of the Skyfield API module and its contents."""
2
3import numpy as np
4from assay import assert_raises
5from skyfield import api, positionlib
6from skyfield.api import Topos
7from skyfield.errors import EphemerisRangeError
8
9def ts():
10    yield api.load.timescale()
11
12def test_sending_jd_that_is_not_a_julian_date():
13    earth = api.load('de421.bsp')['earth']
14    with assert_raises(ValueError, r"please provide the at\(\) method"
15                       " with a Time instance as its argument,"
16                       " instead of the value 'blah'"):
17        earth.at('blah')
18
19def test_apparent_position_class(ts):
20    e = api.load('de421.bsp')
21    p = e['earth'].at(ts.utc(2014, 2, 9, 14, 50)).observe(e['mars']).apparent()
22    assert isinstance(p, positionlib.Apparent)
23
24def test_astrometric_position_class(ts):
25    e = api.load('de421.bsp')
26    p = e['earth'].at(ts.utc(2014, 2, 9, 14, 50)).observe(e['mars'])
27    assert isinstance(p, positionlib.Astrometric)
28
29def test_ephemeris_contains_method(ts):
30    e = api.load('de421.bsp')
31    assert (399 in e) is True
32    assert (398 in e) is False
33    assert ('earth' in e) is True
34    assert ('Earth' in e) is True
35    assert ('EARTH' in e) is True
36    assert ('ceres' in e) is False
37
38def test_exception_raised_for_dates_outside_ephemeris(ts):
39    eph = api.load('de421.bsp')
40    message = (
41        'ephemeris segment only covers dates 1899-07-28 23:59:18Z'
42        ' through 2053-10-08 23:58:51Z UT'
43    )
44    with assert_raises(EphemerisRangeError, message) as a:
45        eph['earth'].at(ts.tt(4096))
46
47    e = a.exception
48    assert e.args == (message,)
49    assert e.start_time.tdb == 2414864.5
50    assert e.end_time.tdb == 2471184.5
51    assert e.time_mask == [True]
52    assert e.segment is eph['earth'].vector_functions[0].spk_segment
53
54def test_planet_position_class(ts):
55    e = api.load('de421.bsp')
56    p = e['mars'].at(ts.utc(2014, 2, 9, 14, 50))
57    assert isinstance(p, positionlib.Barycentric)
58
59def test_star_position_class(ts):
60    e = api.load('de421.bsp')
61    star = api.Star(ra_hours=0, dec_degrees=0)
62    p = e['earth'].at(ts.utc(2014, 2, 9, 15, 1)).observe(star)
63    assert isinstance(p, positionlib.Astrometric)
64
65def test_star_vector_from_earth(ts):
66    t = ts.tt_jd(api.T0)
67    eph = api.load('de421.bsp')
68    e = eph['earth'].at(t)
69
70    star = api.Star(ra_hours=[1.0, 2.0], dec_degrees=[+3.0, +4.0])
71    p = e.observe(star)
72    assert p.position.au.shape == (3, 2)
73    assert p.velocity.au_per_d.shape == (3, 2)
74    assert p.t.shape == (2,)
75    assert (p.t.tt == api.T0).all()
76    a = p.apparent()
77
78    a1 = e.observe(api.Star(ra_hours=1.0, dec_degrees=+3.0)).apparent()
79    a2 = e.observe(api.Star(ra_hours=2.0, dec_degrees=+4.0)).apparent()
80    assert (a1.position.au == a.position.au[:,0]).all()
81    assert (a2.position.au == a.position.au[:,1]).all()
82
83def test_star_vector_from_topos(ts):
84    t = ts.tt_jd(api.T0)
85    eph = api.load('de421.bsp')
86    boston = eph['earth'] + Topos('42.3583 N', '71.0636 W')
87    b = boston.at(t)
88
89    star = api.Star(ra_hours=[1.0, 2.0], dec_degrees=[+3.0, +4.0])
90    p = b.observe(star)
91    assert p.position.au.shape == (3, 2)
92    assert p.velocity.au_per_d.shape == (3, 2)
93    assert p.t.shape == (2,)
94    assert (p.t.tt == api.T0).all()
95    a = p.apparent()
96
97    a1 = b.observe(api.Star(ra_hours=1.0, dec_degrees=+3.0)).apparent()
98    a2 = b.observe(api.Star(ra_hours=2.0, dec_degrees=+4.0)).apparent()
99    assert (a1.position.au == a.position.au[:,0]).all()
100    assert (a2.position.au == a.position.au[:,1]).all()
101
102def test_altaz_needs_topos(ts):
103    e = api.load('de421.bsp')
104    earth = e['earth']
105    moon = e['moon']
106    apparent = earth.at(ts.utc(2016)).observe(moon).apparent()
107    with assert_raises(ValueError, 'from a specific Earth location'):
108        apparent.altaz()
109
110def test_from_altaz_needs_topos():
111    p = positionlib.ICRF([0.0, 0.0, 0.0])
112    with assert_raises(ValueError, 'to compute an altazimuth position'):
113        p.from_altaz(alt_degrees=0, az_degrees=0)
114
115def test_from_altaz_parameters(ts):
116    usno = Topos('38.9215 N', '77.0669 W', elevation_m=92.0)
117    t = ts.tt(jd=api.T0)
118    p = usno.at(t)
119    a = api.Angle(degrees=10.0)
120    d = api.Distance(au=0.234)
121    with assert_raises(ValueError, 'the alt= parameter with an Angle'):
122        p.from_altaz(alt='Bad value', alt_degrees=0, az_degrees=0)
123    with assert_raises(ValueError, 'the az= parameter with an Angle'):
124        p.from_altaz(az='Bad value', alt_degrees=0, az_degrees=0)
125    p.from_altaz(alt=a, alt_degrees='bad', az_degrees=0)
126    p.from_altaz(az=a, alt_degrees=0, az_degrees='bad')
127    assert str(p.from_altaz(alt=a, az=a).distance()) == '0.1 au'
128    assert str(p.from_altaz(alt=a, az=a, distance=d).distance()) == '0.234 au'
129
130def test_github_81(ts):
131    t = ts.utc(1980, 1, 1)
132    assert t == t
133    assert (t == 61) is False  # used to die with AttributeError for "tt"
134
135def test_github_500_does_zero_position_trigger_numpy_warnings(ts):
136    zero_vector = np.array([0.0, 0.0, 0.0])
137    t = ts.utc(2020, 12, 14)
138    p = positionlib.Astrometric(zero_vector, zero_vector, t=t, center=0)
139    p._ephemeris = api.load('de421.bsp')
140    with np.errstate(all='raise'):
141        p.apparent().radec()
142