1# Licensed under a 3-clause BSD style license - see LICENSE.rst 2 3import os 4import warnings 5from pathlib import Path 6 7import pytest 8import numpy as np 9 10from astropy.tests.helper import assert_quantity_allclose 11from astropy.utils.data import get_pkg_data_filename 12from astropy.utils.iers import iers 13from astropy import units as u 14from astropy.table import QTable 15from astropy.time import Time, TimeDelta 16 17CI = os.environ.get('CI', False) 18 19FILE_NOT_FOUND_ERROR = getattr(__builtins__, 'FileNotFoundError', OSError) 20 21try: 22 iers.IERS_A.open('finals2000A.all') # check if IERS_A is available 23except OSError: 24 HAS_IERS_A = False 25else: 26 HAS_IERS_A = True 27 28IERS_A_EXCERPT = get_pkg_data_filename(os.path.join('data', 'iers_a_excerpt')) 29 30 31def setup_module(): 32 # Need auto_download so that IERS_B won't be loaded and cause tests to 33 # fail. Files to be downloaded are handled appropriately in the tests. 34 iers.conf.auto_download = True 35 36 37def teardown_module(): 38 # This setting is to be consistent with astropy/conftest.py 39 iers.conf.auto_download = False 40 41 42class TestBasic(): 43 """Basic tests that IERS_B returns correct values""" 44 45 @pytest.mark.parametrize('iers_cls', (iers.IERS_B, iers.IERS)) 46 def test_simple(self, iers_cls): 47 """Test the default behaviour for IERS_B and IERS.""" 48 # Arguably, IERS itself should not be used at all, but it used to 49 # provide IERS_B by default so we check that it continues to do so. 50 # Eventually, IERS should probably be deprecated. 51 iers_cls.close() 52 assert iers_cls.iers_table is None 53 iers_tab = iers_cls.open() 54 assert iers_cls.iers_table is not None 55 assert iers_cls.iers_table is iers_tab 56 assert isinstance(iers_tab, QTable) 57 assert isinstance(iers_tab, iers.IERS_B) 58 assert (iers_tab['UT1_UTC'].unit / u.second).is_unity() 59 assert (iers_tab['PM_x'].unit / u.arcsecond).is_unity() 60 assert (iers_tab['PM_y'].unit / u.arcsecond).is_unity() 61 jd1 = np.array([2456108.5, 2456108.5, 2456108.5, 2456109.5, 2456109.5]) 62 jd2 = np.array([0.49999421, 0.99997685, 0.99998843, 0., 0.5]) 63 ut1_utc = iers_tab.ut1_utc(jd1, jd2) 64 assert isinstance(ut1_utc, u.Quantity) 65 assert (ut1_utc.unit / u.second).is_unity() 66 # IERS files change at the 0.1 ms level; see gh-6981 67 assert_quantity_allclose(ut1_utc, [-0.5868211, -0.5868184, -0.5868184, 68 0.4131816, 0.41328895] * u.s, 69 atol=0.1*u.ms) 70 # should be future-proof; surely we've moved to another planet by then 71 with pytest.raises(IndexError): 72 ut1_utc2, status2 = iers_tab.ut1_utc(1e11, 0.) 73 # also check it returns the right status 74 ut1_utc2, status2 = iers_tab.ut1_utc(jd1, jd2, return_status=True) 75 assert np.all(status2 == iers.FROM_IERS_B) 76 ut1_utc4, status4 = iers_tab.ut1_utc(1e11, 0., return_status=True) 77 assert status4 == iers.TIME_BEYOND_IERS_RANGE 78 79 # check it works via Time too 80 t = Time(jd1, jd2, format='jd', scale='utc') 81 ut1_utc3 = iers_tab.ut1_utc(t) 82 assert_quantity_allclose(ut1_utc3, [-0.5868211, -0.5868184, -0.5868184, 83 0.4131816, 0.41328895] * u.s, 84 atol=0.1*u.ms) 85 86 # Table behaves properly as a table (e.g. can be sliced) 87 assert len(iers_tab[:2]) == 2 88 89 def test_open_filename(self): 90 iers.IERS_B.close() 91 iers.IERS_B.open(iers.IERS_B_FILE) 92 assert iers.IERS_B.iers_table is not None 93 assert isinstance(iers.IERS_B.iers_table, QTable) 94 iers.IERS_B.close() 95 with pytest.raises(FILE_NOT_FOUND_ERROR): 96 iers.IERS_B.open('surely this does not exist') 97 98 def test_open_network_url(self): 99 iers.IERS_A.close() 100 iers.IERS_A.open(Path(IERS_A_EXCERPT).as_uri()) 101 assert iers.IERS_A.iers_table is not None 102 assert isinstance(iers.IERS_A.iers_table, QTable) 103 iers.IERS_A.close() 104 105 106class TestIERS_AExcerpt(): 107 def test_simple(self): 108 # Test the IERS A reader. It is also a regression tests that ensures 109 # values do not get overridden by IERS B; see #4933. 110 iers_tab = iers.IERS_A.open(IERS_A_EXCERPT) 111 112 assert (iers_tab['UT1_UTC'].unit / u.second).is_unity() 113 assert 'P' in iers_tab['UT1Flag'] 114 assert 'I' in iers_tab['UT1Flag'] 115 assert 'B' in iers_tab['UT1Flag'] 116 assert np.all((iers_tab['UT1Flag'] == 'I') | 117 (iers_tab['UT1Flag'] == 'P') | 118 (iers_tab['UT1Flag'] == 'B')) 119 120 assert (iers_tab['dX_2000A'].unit / u.marcsec).is_unity() 121 assert (iers_tab['dY_2000A'].unit / u.marcsec).is_unity() 122 assert 'P' in iers_tab['NutFlag'] 123 assert 'I' in iers_tab['NutFlag'] 124 assert 'B' in iers_tab['NutFlag'] 125 assert np.all((iers_tab['NutFlag'] == 'P') | 126 (iers_tab['NutFlag'] == 'I') | 127 (iers_tab['NutFlag'] == 'B')) 128 129 assert (iers_tab['PM_x'].unit / u.arcsecond).is_unity() 130 assert (iers_tab['PM_y'].unit / u.arcsecond).is_unity() 131 assert 'P' in iers_tab['PolPMFlag'] 132 assert 'I' in iers_tab['PolPMFlag'] 133 assert 'B' in iers_tab['PolPMFlag'] 134 assert np.all((iers_tab['PolPMFlag'] == 'P') | 135 (iers_tab['PolPMFlag'] == 'I') | 136 (iers_tab['PolPMFlag'] == 'B')) 137 138 t = Time([57053., 57054., 57055.], format='mjd') 139 ut1_utc, status = iers_tab.ut1_utc(t, return_status=True) 140 assert status[0] == iers.FROM_IERS_B 141 assert np.all(status[1:] == iers.FROM_IERS_A) 142 # These values are *exactly* as given in the table, so they should 143 # match to double precision accuracy. 144 assert_quantity_allclose(ut1_utc, 145 [-0.4916557, -0.4925323, -0.4934373] * u.s, 146 atol=0.1*u.ms) 147 148 dcip_x, dcip_y, status = iers_tab.dcip_xy(t, return_status=True) 149 assert status[0] == iers.FROM_IERS_B 150 assert np.all(status[1:] == iers.FROM_IERS_A) 151 # These values are *exactly* as given in the table, so they should 152 # match to double precision accuracy. 153 print(dcip_x) 154 print(dcip_y) 155 assert_quantity_allclose(dcip_x, 156 [-0.086, -0.093, -0.087] * u.marcsec, 157 atol=1.*u.narcsec) 158 assert_quantity_allclose(dcip_y, 159 [0.094, 0.081, 0.072] * u.marcsec, 160 atol=1*u.narcsec) 161 162 pm_x, pm_y, status = iers_tab.pm_xy(t, return_status=True) 163 assert status[0] == iers.FROM_IERS_B 164 assert np.all(status[1:] == iers.FROM_IERS_A) 165 assert_quantity_allclose(pm_x, 166 [0.003734, 0.004581, 0.004623] * u.arcsec, 167 atol=0.1*u.marcsec) 168 assert_quantity_allclose(pm_y, 169 [0.310824, 0.313150, 0.315517] * u.arcsec, 170 atol=0.1*u.marcsec) 171 172 # Table behaves properly as a table (e.g. can be sliced) 173 assert len(iers_tab[:2]) == 2 174 175 176@pytest.mark.skipif('not HAS_IERS_A') 177class TestIERS_A(): 178 179 def test_simple(self): 180 """Test that open() by default reads a 'finals2000A.all' file.""" 181 # Ensure we remove any cached table (gh-5131). 182 iers.IERS_A.close() 183 iers_tab = iers.IERS_A.open() 184 jd1 = np.array([2456108.5, 2456108.5, 2456108.5, 2456109.5, 2456109.5]) 185 jd2 = np.array([0.49999421, 0.99997685, 0.99998843, 0., 0.5]) 186 ut1_utc, status = iers_tab.ut1_utc(jd1, jd2, return_status=True) 187 assert np.all(status == iers.FROM_IERS_B) 188 assert_quantity_allclose(ut1_utc, [-0.5868211, -0.5868184, -0.5868184, 189 0.4131816, 0.41328895] * u.s, 190 atol=0.1*u.ms) 191 ut1_utc2, status2 = iers_tab.ut1_utc(1e11, 0., return_status=True) 192 assert status2 == iers.TIME_BEYOND_IERS_RANGE 193 194 tnow = Time.now() 195 196 ut1_utc3, status3 = iers_tab.ut1_utc(tnow, return_status=True) 197 assert status3 == iers.FROM_IERS_A_PREDICTION 198 assert ut1_utc3 != 0. 199 200 201class TestIERS_Auto(): 202 203 def setup_class(self): 204 """Set up useful data for the tests. 205 """ 206 self.N = 40 207 self.ame = 30.0 208 self.iers_a_file_1 = get_pkg_data_filename( 209 os.path.join('data', 'finals2000A-2016-02-30-test')) 210 self.iers_a_file_2 = get_pkg_data_filename( 211 os.path.join('data', 'finals2000A-2016-04-30-test')) 212 self.iers_a_url_1 = Path(self.iers_a_file_1).as_uri() 213 self.iers_a_url_2 = Path(self.iers_a_file_2).as_uri() 214 self.t = Time.now() + TimeDelta(10, format='jd') * np.arange(self.N) 215 216 def teardown_method(self, method): 217 """Run this after every test. 218 """ 219 iers.IERS_Auto.close() 220 221 def test_interpolate_error_formatting(self): 222 """Regression test: make sure the error message in 223 IERS_Auto._check_interpolate_indices() is formatted correctly. 224 """ 225 with iers.conf.set_temp('iers_auto_url', self.iers_a_url_1): 226 with iers.conf.set_temp('iers_auto_url_mirror', self.iers_a_url_1): 227 with iers.conf.set_temp('auto_max_age', self.ame): 228 with pytest.raises(ValueError) as err: 229 iers_table = iers.IERS_Auto.open() 230 with warnings.catch_warnings(): 231 # Ignoring this if it comes up -- IERS_Auto predictive 232 # values are older than 30.0 days but downloading the 233 # latest table did not find newer values 234 warnings.simplefilter('ignore', iers.IERSStaleWarning) 235 iers_table.ut1_utc(self.t.jd1, self.t.jd2) 236 assert str(err.value) == iers.INTERPOLATE_ERROR.format(self.ame) 237 238 def test_auto_max_age_none(self): 239 """Make sure that iers.INTERPOLATE_ERROR's advice about setting 240 auto_max_age = None actually works. 241 """ 242 with iers.conf.set_temp('iers_auto_url', self.iers_a_url_1): 243 with iers.conf.set_temp('auto_max_age', None): 244 iers_table = iers.IERS_Auto.open() 245 delta = iers_table.ut1_utc(self.t.jd1, self.t.jd2) 246 assert isinstance(delta, np.ndarray) 247 assert delta.shape == (self.N,) 248 assert_quantity_allclose(delta, np.array([-0.2246227]*self.N)*u.s) 249 250 def test_auto_max_age_minimum(self): 251 """Check that the minimum auto_max_age is enforced. 252 """ 253 with iers.conf.set_temp('iers_auto_url', self.iers_a_url_1): 254 with iers.conf.set_temp('auto_max_age', 5.0): 255 with pytest.raises(ValueError) as err: 256 iers_table = iers.IERS_Auto.open() 257 _ = iers_table.ut1_utc(self.t.jd1, self.t.jd2) 258 assert str(err.value) == 'IERS auto_max_age configuration value must be larger than 10 days' 259 260 def test_no_auto_download(self): 261 with iers.conf.set_temp('auto_download', False): 262 t = iers.IERS_Auto.open() 263 assert type(t) is iers.IERS_B 264 265 @pytest.mark.remote_data 266 def test_simple(self): 267 268 with iers.conf.set_temp('iers_auto_url', self.iers_a_url_1): 269 270 dat = iers.IERS_Auto.open() 271 assert dat['MJD'][0] == 57359.0 * u.d 272 assert dat['MJD'][-1] == 57539.0 * u.d 273 274 # Pretend we are accessing at a time 7 days after start of predictive data 275 predictive_mjd = dat.meta['predictive_mjd'] 276 dat._time_now = Time(predictive_mjd, format='mjd') + 7 * u.d 277 278 # Look at times before and after the test file begins. 0.1292905 is 279 # the IERS-B value from MJD=57359. The value in 280 # finals2000A-2016-02-30-test has been replaced at this point. 281 assert np.allclose(dat.ut1_utc(Time(50000, format='mjd').jd).value, 0.1293286) 282 assert np.allclose(dat.ut1_utc(Time(60000, format='mjd').jd).value, -0.2246227) 283 284 # Now pretend we are accessing at time 60 days after start of predictive data. 285 # There will be a warning when downloading the file doesn't give new data 286 # and an exception when extrapolating into the future with insufficient data. 287 dat._time_now = Time(predictive_mjd, format='mjd') + 60 * u.d 288 assert np.allclose(dat.ut1_utc(Time(50000, format='mjd').jd).value, 0.1293286) 289 with pytest.warns(iers.IERSStaleWarning, match='IERS_Auto predictive ' 290 'values are older') as warns, \ 291 pytest.raises(ValueError, match='interpolating from IERS_Auto ' 292 'using predictive values'): 293 dat.ut1_utc(Time(60000, format='mjd').jd) 294 assert len(warns) == 1 295 296 # Warning only if we are getting return status 297 with pytest.warns(iers.IERSStaleWarning, match='IERS_Auto ' 298 'predictive values are older') as warns: 299 dat.ut1_utc(Time(60000, format='mjd').jd, return_status=True) 300 assert len(warns) == 1 301 302 # Now set auto_max_age = None which says that we don't care how old the 303 # available IERS-A file is. There should be no warnings or exceptions. 304 with iers.conf.set_temp('auto_max_age', None): 305 dat.ut1_utc(Time(60000, format='mjd').jd) 306 307 # Now point to a later file with same values but MJD increased by 308 # 60 days and see that things work. dat._time_now is still the same value 309 # as before, i.e. right around the start of predictive values for the new file. 310 # (In other words this is like downloading the latest file online right now). 311 with iers.conf.set_temp('iers_auto_url', self.iers_a_url_2): 312 313 # Look at times before and after the test file begins. This forces a new download. 314 assert np.allclose(dat.ut1_utc(Time(50000, format='mjd').jd).value, 0.1293286) 315 assert np.allclose(dat.ut1_utc(Time(60000, format='mjd').jd).value, -0.3) 316 317 # Now the time range should be different. 318 assert dat['MJD'][0] == 57359.0 * u.d 319 assert dat['MJD'][-1] == (57539.0 + 60) * u.d 320 321 322@pytest.mark.remote_data 323def test_IERS_B_parameters_loading_into_IERS_Auto(): 324 A = iers.IERS_Auto.open() 325 B = iers.IERS_B.open() 326 327 ok_A = A["MJD"] <= B["MJD"][-1] 328 assert not np.all(ok_A), "IERS B covers all of IERS A: should not happen" 329 330 # We only overwrite IERS_B values in the IERS_A table that were already 331 # there in the first place. Better take that into account. 332 ok_A &= np.isfinite(A["UT1_UTC_B"]) 333 334 i_B = np.searchsorted(B["MJD"], A["MJD"][ok_A]) 335 336 assert np.all(np.diff(i_B) == 1), "Valid region not contiguous" 337 assert np.all(A["MJD"][ok_A] == B["MJD"][i_B]) 338 # Check that values are copied correctly. Since units are not 339 # necessarily the same, we use allclose with very strict tolerance. 340 for name in ("UT1_UTC", "PM_x", "PM_y", "dX_2000A", "dY_2000A"): 341 assert_quantity_allclose( 342 A[name][ok_A], B[name][i_B], rtol=1e-15, 343 err_msg=("Bug #9206 IERS B parameter {} not copied over " 344 "correctly to IERS Auto".format(name))) 345 346 347# Issue with FTP, rework test into previous one when it's fixed 348@pytest.mark.skipif("CI", reason="Flaky on CI") 349@pytest.mark.remote_data 350def test_iers_a_dl(): 351 iersa_tab = iers.IERS_A.open(iers.IERS_A_URL, cache=False) 352 try: 353 # some basic checks to ensure the format makes sense 354 assert len(iersa_tab) > 0 355 assert 'UT1_UTC_A' in iersa_tab.colnames 356 finally: 357 iers.IERS_A.close() 358 359 360@pytest.mark.remote_data 361def test_iers_a_dl_mirror(): 362 iersa_tab = iers.IERS_A.open(iers.IERS_A_URL_MIRROR, cache=False) 363 try: 364 # some basic checks to ensure the format makes sense 365 assert len(iersa_tab) > 0 366 assert 'UT1_UTC_A' in iersa_tab.colnames 367 finally: 368 iers.IERS_A.close() 369 370 371@pytest.mark.remote_data 372def test_iers_b_dl(): 373 iersb_tab = iers.IERS_B.open(iers.IERS_B_URL, cache=False) 374 try: 375 # some basic checks to ensure the format makes sense 376 assert len(iersb_tab) > 0 377 assert 'UT1_UTC' in iersb_tab.colnames 378 finally: 379 iers.IERS_B.close() 380