def test_parameter_set_quantity(): """ Make sure that parameters that start off as quantities can be set to any other quantity, regardless of whether the units of the new quantity are compatible with the original ones. We basically leave it up to the evaluate method to raise errors if there are issues with incompatible units, and we don't check for consistency at the parameter level. """ g = Gaussian1D(1 * u.J, 1 * u.m, 0.1 * u.m) # Try equivalent units g.amplitude = 4 * u.kJ assert_quantity_allclose(g.amplitude, 4 * u.kJ) g.mean = 3 * u.km assert_quantity_allclose(g.mean, 3 * u.km) g.stddev = 2 * u.mm assert_quantity_allclose(g.stddev, 2 * u.mm) # Try different units g.amplitude = 2 * u.s assert_quantity_allclose(g.amplitude, 2 * u.s) g.mean = 2 * u.Jy assert_quantity_allclose(g.mean, 2 * u.Jy)
def test_make_mean_edisp(tmpdir): position = SkyCoord(83.63, 22.01, unit='deg') store = gammapy_extra.filename("datasets/hess-crab4-hd-hap-prod2") data_store = DataStore.from_dir(store) obs1 = data_store.obs(23523) obs2 = data_store.obs(23592) obslist = ObservationList([obs1, obs2]) e_true = EnergyBounds.equal_log_spacing(0.01, 150, 80, "TeV") e_reco = EnergyBounds.equal_log_spacing(0.5, 100, 15, "TeV") rmf = obslist.make_mean_edisp(position=position, e_true=e_true, e_reco=e_reco) assert len(rmf.e_true.nodes) == 80 assert len(rmf.e_reco.nodes) == 15 assert_quantity_allclose(rmf.data[53, 8], 0.0559785805550798) rmf2 = obslist.make_mean_edisp(position=position, e_true=e_true, e_reco=e_reco, low_reco_threshold=Energy(1, "TeV"), high_reco_threshold=Energy(60, "TeV")) i2 = np.where(rmf2.evaluate(e_reco=Energy(0.8, "TeV")) != 0)[0] assert len(i2) == 0 i2 = np.where(rmf2.evaluate(e_reco=Energy(61, "TeV")) != 0)[0] assert len(i2) == 0 i = np.where(rmf.evaluate(e_reco=Energy(1.5, "TeV")) != 0)[0] i2 = np.where(rmf2.evaluate(e_reco=Energy(1.5, "TeV")) != 0)[0] assert_equal(i, i2) i = np.where(rmf.evaluate(e_reco=Energy(55, "TeV")) != 0)[0] i2 = np.where(rmf2.evaluate(e_reco=Energy(55, "TeV")) != 0)[0] assert_equal(i, i2)
def test_psf(self): psf = FermiGalacticCenter.psf() energy = Quantity(110, 'GeV') fraction = 0.68 interpol_param = dict(method='nearest', bounds_error=False) angle = psf.containment_radius(energy, fraction, interpol_param) assert_quantity_allclose(angle, Angle(0.1927459865412511, 'deg'), rtol=1e-2)
def test_scalar_error(self): error_value = 2.5 error = np.ones_like(IMAGE) * error_value props = source_properties(IMAGE, SEGM, error=error_value) props2 = source_properties(IMAGE, SEGM, error) assert_quantity_allclose(props.source_sum, props2.source_sum) assert_quantity_allclose(props.source_sum_err, props2.source_sum_err)
def test_psf(self): psf = FermiVelaRegion.psf() energy = Quantity(110, 'GeV') fraction = 0.68 interpol_param = dict(method='nearest', bounds_error=False) angle = psf.containment_radius(energy, fraction, interpol_param) assert_quantity_allclose(angle, Angle(0.13185321269896136, 'deg'), rtol=1e-1)
def test_edisp(self): edisp = self.obs.load(hdu_type='edisp', hdu_class='edisp_2d') actual = edisp.evaluate(e_true=self.ref_energy, offset=self.ref_theta, migra=self.ref_migra) desired = self.ref_dict['edisp_ref'] assert_quantity_allclose(actual, desired)
def test_psf(self): psf = self.obs.load(hdu_type='psf', hdu_class=self.ref_dict['psf_type']) print(psf) table_psf = psf.to_table_psf(offset=self.ref_offset, theta=self.ref_theta) actual = table_psf.evaluate(energy=self.ref_energy) desired = self.ref_dict['psf_ref'] assert_quantity_allclose(actual[0][4], desired)
def test_period_units(data): t, y, dy, params = data t_unit = units.day y_unit = units.mag model = BoxLeastSquares(t * t_unit, y * y_unit, dy) p = model.autoperiod(params["duration"]) assert p.unit == t_unit p = model.autoperiod(params["duration"] * 24 * units.hour) assert p.unit == t_unit with pytest.raises(units.UnitConversionError): model.autoperiod(params["duration"] * units.mag) p = model.autoperiod(params["duration"], minimum_period=0.5) assert p.unit == t_unit with pytest.raises(units.UnitConversionError): p = model.autoperiod(params["duration"], minimum_period=0.5*units.mag) p = model.autoperiod(params["duration"], maximum_period=0.5) assert p.unit == t_unit with pytest.raises(units.UnitConversionError): p = model.autoperiod(params["duration"], maximum_period=0.5*units.mag) p = model.autoperiod(params["duration"], minimum_period=0.5, maximum_period=1.5) p2 = model.autoperiod(params["duration"], maximum_period=0.5, minimum_period=1.5) assert_quantity_allclose(p, p2)
def test_model(data, with_units): t, y, dy, params = data # Compute the model using linear regression A = np.zeros((len(t), 2)) p = params["period"] dt = np.abs((t-params["transit_time"]+0.5*p) % p-0.5*p) m_in = dt < 0.5*params["duration"] A[~m_in, 0] = 1.0 A[m_in, 1] = 1.0 w = np.linalg.solve(np.dot(A.T, A / dy[:, None]**2), np.dot(A.T, y / dy**2)) model_true = np.dot(A, w) if with_units: t = t * units.day y = y * units.mag dy = dy * units.mag model_true = model_true * units.mag # Compute the model using the periodogram pgram = BoxLeastSquares(t, y, dy) model = pgram.model(t, p, params["duration"], params["transit_time"]) # Make sure that the transit mask is consistent with the model transit_mask = pgram.transit_mask(t, p, params["duration"], params["transit_time"]) transit_mask0 = (model - model.max()) < 0.0 assert_allclose(transit_mask, transit_mask0) assert_quantity_allclose(model, model_true)
def test_ras_pattern(): args_list = [ (-180, 180, apu.deg), (0.1, 1000., apu.m), (0.001, 2, apu.m), (0, None, cnv.dimless), ] check_astro_quantities(ras.ras_pattern, args_list) phi = np.logspace(0, 2, 10) * apu.deg diam = np.linspace(10, 100, 10) * apu.m assert_quantity_allclose( ras.ras_pattern(phi, diam, 0.21 * apu.m), [ 37.82967732, 23.44444444, 17.88888889, 12.33333333, 6.77777778, 0.66666667, -6., -12., -12., -7. ] * cnv.dB ) assert_quantity_allclose( ras.ras_pattern(phi, diam, 0.21 * apu.m, eta_a=20 * apu.percent), [ 30.83997728, 23.44444444, 17.88888889, 12.33333333, 6.77777778, 0.66666667, -6., -12., -12., -7. ] * cnv.dB )
def test_fl_pattern_broadcast(): args_list = [ (-180, 180, apu.deg), (0.1, 1000., apu.m), (0.001, 2, apu.m), (None, None, cnv.dBi), ] check_astro_quantities(fixedlink.fl_pattern, args_list) phi = np.logspace(0, 2, 10) * apu.deg diam = np.linspace(1, 10, 2) * apu.m # apparently, if phi == 1, there is a problem! assert_quantity_allclose( fixedlink.fl_pattern( phi[:, np.newaxis], diam, 0.21 * apu.m, 20 * cnv.dBi ), [ [19.94331066, 0.], [19.84225854, 29.66663739], [19.56107501, 24.11108184], [18.77866514, 18.55552628], [22.99997073, 12.99997073], [17.44441517, 7.44441517], [11.88885961, 1.88885961], [6.33330406, -3.66669594], [-16.77780705, -26.77780705], [-16.77780705, -26.77780705] ] * cnv.dB )
def test_e_max(self, flux_points): if flux_points.sed_type == "dnde": pass elif flux_points.sed_type == "flux": actual = flux_points.e_max desired = 399430.975721694 * u.MeV assert_quantity_allclose(actual.sum(), desired)
def test_define_energy_threshold(self, extraction): # TODO: Find better API for this extraction.define_energy_threshold(method_lo_threshold="area_max", percent=10) assert_quantity_allclose(extraction.observations[0].lo_threshold, 0.6812920690579611 * u.TeV, rtol=1e-3)
def test_e_ref(self, flux_points): actual = flux_points.e_ref if flux_points.sed_type == "dnde": pass elif flux_points.sed_type == "flux": desired = np.sqrt(flux_points.e_min * flux_points.e_max) assert_quantity_allclose(actual, desired)
def test_e_min(self, flux_points): if flux_points.sed_type == "dnde": pass elif flux_points.sed_type == "flux": actual = flux_points.e_min desired = 299530.9757217623 * u.MeV assert_quantity_allclose(actual.sum(), desired)
def test_absorbed_spectral_model(): filename = '$GAMMAPY_EXTRA/datasets/ebl/ebl_franceschini.fits.gz' # absorption values for given redshift redshift = 0.117 absorption = TableModel.read_xspec_model(filename, redshift) # Spectral model corresponding to PKS 2155-304 (quiescent state) index = 3.53 amplitude = 1.81 * 1e-12 * u.Unit('cm-2 s-1 TeV-1') reference = 1 * u.TeV pwl = PowerLaw(index=index, amplitude=amplitude, reference=reference) # EBL + PWL model model = AbsorbedSpectralModel(spectral_model=pwl, table_model=absorption) # Test if the absorption factor at the reference energy # corresponds to the ratio of the absorbed flux # divided by the flux of the spectral model model_ref_energy = model.evaluate(energy=reference) pwl_ref_energy = pwl.evaluate(energy=reference, index=index, amplitude=amplitude, reference=reference) desired = absorption.evaluate(energy=reference, amplitude=1.) actual = model_ref_energy/pwl_ref_energy assert_quantity_allclose(actual, desired)
def test_rotate_recenter(generic_map): rotated_map = generic_map.rotate(20 * u.deg, recenter=True) pixel_array_center = (np.flipud(rotated_map.data.shape) - 1) / 2.0 assert_quantity_allclose( (pixel_array_center + 1) * u.pix, u.Quantity(rotated_map.reference_pixel) # FITS indexes from 1 )
def test_integral_flux_image(self): # For a very small energy band the integral flux should be roughly # differential flux times energy bin width lon, lat, energy = self.spectral_cube.pix2world(0, 0, 0) denergy = 0.001 * energy energy_band = Quantity([energy, energy + denergy]) dflux = self.spectral_cube.flux(lon, lat, energy) expected = dflux * denergy actual = Quantity(self.spectral_cube.integral_flux_image(energy_band).data[0, 0], '1 / (cm2 s sr)') assert_quantity_allclose(actual, expected, rtol=1e-3) # Test a wide energy band energy_band = Quantity([1, 10], 'GeV') image = self.spectral_cube.integral_flux_image(energy_band) actual = image.data.sum() # TODO: the reference result is not verified ... just pasted from the test output. expected = 5.2481972772213124e-02 assert_allclose(actual, expected) # Test integral flux for energy bands with units. energy_band_check = Quantity([1000, 10000], 'MeV') new_image = self.spectral_cube.integral_flux_image(energy_band_check) assert_allclose(new_image.data, image.data) # Test Header Keys expected = [('CDELT1', 0.5), ('CDELT2', 0.5), ('NAXIS1', 61), ('NAXIS2', 21), ('CRVAL1', 0), ('CRVAL2', 0)] for key, value in expected: assert_allclose(np.abs(image.header[key]), value)
def test_hd209458b_exoplanet_orbit_database_apy_gt12(): # Testing intentionally un-stripped string: with pytest.raises(ValueError): params = ExoplanetOrbitDatabase.query_planet('HD 209458 b ', table_path=LOCAL_TABLE_PATH) assert_quantity_allclose(params['R'], 1.320 * u.Unit('R_jup'), atol=0.1 * u.Unit('R_jup'))
def test_srtm_height_data_broadcasting(srtm_temp_dir): args_list = [ (-180, 180, apu.deg), (-90, 90, apu.deg), ] check_astro_quantities(srtm.srtm_height_data, args_list) with srtm.SrtmConf.set(srtm_dir=srtm_temp_dir, interp='linear'): lons = np.arange(12.1005, 12.9, 0.2) * apu.deg lats = np.arange(50.1005, 50.9, 0.2)[:, np.newaxis] * apu.deg heights = srtm.srtm_height_data(lons, lats) assert_quantity_allclose(heights, np.array([ [581.71997070, 484.48001099, 463.79998779, 736.44000244], [613.00000000, 549.88000488, 636.52001953, 678.91998291], [433.44000244, 416.20001221, 704.52001953, 826.08001709], [358.72000122, 395.55999756, 263.83999634, 469.39999390] ]) * apu.m) heights = srtm.srtm_height_data(lons, lats.reshape((2, 2, 1))) assert_quantity_allclose(heights, np.array([ [[581.71997070, 484.48001099, 463.79998779, 736.44000244], [613.00000000, 549.88000488, 636.52001953, 678.91998291]], [[433.44000244, 416.20001221, 704.52001953, 826.08001709], [358.72000122, 395.55999756, 263.83999634, 469.39999390]] ]) * apu.m)
def test_flux_array(self): pix = [2, 2], [3, 3], [4, 4] world = self.spectral_cube.pix2world(*pix) actual = self.spectral_cube.flux(*world) expected = self.spectral_cube.data[4, 3, 2] # Quantity([3.50571123e-07, 2], '1 / (cm2 MeV s sr)') assert_quantity_allclose(actual, expected)
def test_pp_worker(qtbot): # change download option to missing and test, if the results are correct myapp = gui.PycrafGui() qtbot.addWidget(myapp) _set_parameters(myapp.ui) myapp.ui.srtmDownloadComboBox.setCurrentIndex( gui.SRTM_DOWNLOAD_MAPPING.index('missing') ) with qtbot.waitSignal( myapp.my_pp_worker.result_ready[object, object], raising=False, timeout=50000, ): myapp.on_pathprof_compute_pressed() res = myapp.pathprof_results assert_quantity_allclose( res['L_b'][::1000].to(cnv.dB).value, [0., 128.879956, 163.285657, 155.078537, 156.097511, 170.742422] ) assert_quantity_allclose( res['eps_pt'][::1000].to(apu.deg).value, [0., 0.45954562, 0.51161058, 0.51158408, 0.51155677, 0.51152865] ) assert_equal(res['path_type'][::1000], [0, 1, 1, 1, 1, 1])
def test_absorption(): # absorption values for given redshift redshift = 0.117 absorption = Absorption.read_builtin('dominguez') # Spectral model corresponding to PKS 2155-304 (quiescent state) index = 3.53 amplitude = 1.81 * 1e-12 * u.Unit('cm-2 s-1 TeV-1') reference = 1 * u.TeV pwl = PowerLaw(index=index, amplitude=amplitude, reference=reference) # EBL + PWL model model = AbsorbedSpectralModel(spectral_model=pwl, absorption=absorption, parameter=redshift) # Test if the absorption factor at the reference energy # corresponds to the ratio of the absorbed flux # divided by the flux of the spectral model kwargs = dict(index=index, amplitude=amplitude, reference=reference, redshift=redshift) model_ref_energy = model.evaluate(energy=reference, **kwargs) pwl_ref_energy = pwl.evaluate(energy=reference, index=index, amplitude=amplitude, reference=reference) desired = absorption.evaluate(energy=reference, parameter=redshift) actual = model_ref_energy / pwl_ref_energy assert_quantity_allclose(actual, desired)
def test_body_centered_to_icrs_transformation(): vexpress_r_venus = [ -2.707041558060933e03, 1.112962479175306e04, -3.792944408664889e04, ] * u.km vexpress_v_venus = ( [-2.045118200275925e-01, 7.978578482960554e-01, 2.664944903217139e00] * u.km / u.s ) expected_r = [ -3.47202219448080286e07, 9.16853879708216339e07, 4.34117810525591150e07, ] * u.km expected_v = ( [-3.34053728321152121e01, -1.16604776013667291e01, -2.39943678872506838e-01] * u.km / u.s ) r, v = coordinates.body_centered_to_icrs( vexpress_r_venus, vexpress_v_venus, bodies.Venus, time.Time("2014-08-23 00:00", scale="tdb"), ) assert_quantity_allclose(r, expected_r) assert_quantity_allclose(v, expected_v)
def test_icrs_to_body_centered_transformation(): vexpress_r_icrs = [ -3.472125578094885e07, 9.168528034176737e07, 4.341160627674723e07, ] * u.km vexpress_v_icrs = ( [-3.340574196483147e01, -1.165974037637970e01, -2.395829145441408e-01] * u.km / u.s ) expected_r = [ -3.74486105008138566e03, 1.10085874027602295e04, -3.80681106516677464e04, ] * u.km expected_v = ( [-2.04845025352488774e-01, 7.98692896032012989e-01, 2.66498465286454023e00] * u.km / u.s ) r, v = coordinates.icrs_to_body_centered( vexpress_r_icrs, vexpress_v_icrs, bodies.Venus, time.Time("2014-08-23 00:00", scale="tdb"), ) assert_quantity_allclose(r, expected_r) assert_quantity_allclose(v, expected_v)
def test_hd209458b_exoplanets_archive_apy_gt12(): # Testing intentionally un-stripped string: with pytest.raises(ValueError): params = NasaExoplanetArchive.query_planet('HD 209458 b ', table_path=LOCAL_TABLE_PATH) assert_quantity_allclose(params['pl_radj'], 1.320 * u.Unit('R_jup'), atol=0.1 * u.Unit('R_jup'))
def test_make_test_bg_cube_model(): # make a cube bg model with non-equal axes ndetx_bins = 1 ndety_bins = 2 nenergy_bins = 3 bg_cube_model = make_test_bg_cube_model(ndetx_bins=ndetx_bins, ndety_bins=ndety_bins, nenergy_bins=nenergy_bins) # test shape of cube bg model assert len(bg_cube_model.background_cube.data.shape) == 3 assert bg_cube_model.background_cube.data.shape == (nenergy_bins, ndety_bins, ndetx_bins) # make masked bg model bg_cube_model = make_test_bg_cube_model(apply_mask=True) # test that values with (x, y) > (0, 0) are zero x_points = Angle(np.arange(5) + 0.01, 'deg') y_points = Angle(np.arange(5) + 0.01, 'deg') e_points = bg_cube_model.background_cube.energy_bin_centers x_points, y_points, e_points = np.meshgrid(x_points, y_points, e_points, indexing='ij') det_bin_index = bg_cube_model.background_cube.find_coord_bin(Angle([x_points, y_points])) e_bin_index = bg_cube_model.background_cube.find_energy_bin(e_points) bg = bg_cube_model.background_cube.data[e_bin_index, det_bin_index[1], det_bin_index[0]] # assert that values are 0 assert_quantity_allclose(bg, Quantity(0., bg.unit))
def test_stats_worker(qtbot): # change download option to missing and test, if the results are correct myapp = gui.PycrafGui() qtbot.addWidget(myapp) _set_parameters(myapp.ui) myapp.ui.srtmDownloadComboBox.setCurrentIndex( gui.SRTM_DOWNLOAD_MAPPING.index('missing') ) with qtbot.waitSignal( myapp.my_stats_worker.result_ready[object, object], raising=False, timeout=50000, ): myapp.timer.start(10) res = myapp.statistics_results assert_quantity_allclose( res['L_b'][:, ::20].to(cnv.dB).value, [ [138.8771118, 140.8853131, 142.8934803, 144.9016341, 147.7434509], [156.2189124, 158.2270703, 160.2352217, 162.2433706, 164.7989202], [165.3765899, 167.3847525, 169.3929057, 171.4010551, 173.6622298], [174.5007580, 176.5089330, 178.5170906, 180.5252413, 182.7685164], [186.5540705, 188.5623023, 190.5704806, 192.5786379, 194.8213818], [195.9055898, 197.9140057, 199.9222511, 201.9304294, 204.1729092], [210.4921391, 212.5046189, 214.5143471, 216.5229899, 218.7653956], [236.8190545, 238.8738936, 240.8993047, 242.9128866, 245.1563437], [243.0043903, 247.2872525, 251.7308653, 256.0069129, 259.5560927], ])
def test_mask_nomask(self): props = source_properties(IMAGE, SEGM, mask=np.ma.nomask) mask = np.zeros(IMAGE.shape).astype(bool) props2 = source_properties(IMAGE, SEGM, mask=mask) assert_allclose(props.xcentroid.value, props2.xcentroid.value) assert_allclose(props.ycentroid.value, props2.ycentroid.value) assert_quantity_allclose(props.source_sum, props2.source_sum)
def test_lagrange_points_vec(): # Figure 2.36 from Curtis deg60 = 60 * pi / 180 expected_L1 = 326400 * ([1, 0, 0] * u.km) expected_L2 = 449100 * ([1, 0, 0] * u.km) expected_L3 = -381600 * ([1, 0, 0] * u.km) expected_L4 = 384400 * ([cos(deg60), sin(deg60), 0] * u.km) expected_L5 = 384400 * ([cos(deg60), -sin(deg60), 0] * u.km) earth_mass = Earth.mass moon_mass = Moon.mass # Values from Curtis # earth_mass = 5.974e24 * u.kg # moon_mass = 73.48e21 * u.kg L1, L2, L3, L4, L5 = lagrange_points_vec(m1=earth_mass, r1=([0, 0, 0] * u.km), m2=moon_mass, r2=384400 * ([1, 0, 0] * u.km), n=[0, 0, 1] * u.one) assert_quantity_allclose(L1, expected_L1, rtol=1.e-3) assert_quantity_allclose(L2, expected_L2, rtol=1.e-3) assert_quantity_allclose(L3, expected_L3, rtol=1.e-3) assert_quantity_allclose(L4, expected_L4, rtol=1.e-3) assert_quantity_allclose(L5, expected_L5, rtol=1.e-3)
def test_get_body_heliographic_stonyhurst(): # Validate against published values from the Astronomical Almanac (2013) e1 = get_body_heliographic_stonyhurst('earth', '2013-Jan-01') assert_quantity_allclose(e1.lon, 0 * u.deg, atol=1e-12 * u.deg) assert_quantity_allclose(e1.lat, -3.03 * u.deg, atol=5e-3 * u.deg) assert_quantity_allclose(e1.radius, 0.9832947 * u.AU, atol=5e-7 * u.AU) e2 = get_body_heliographic_stonyhurst('earth', '2013-Sep-01') assert_quantity_allclose(e2.lon, 0 * u.deg, atol=1e-12 * u.deg) assert_quantity_allclose(e2.lat, 7.19 * u.deg, atol=5e-3 * u.deg) assert_quantity_allclose(e2.radius, 1.0092561 * u.AU, atol=5e-7 * u.AU)
def test_los_shift_radial_velocity(): # Tests to make sure that with_radial_velocity_shift correctly calculates # the new radial velocity # First check case where observer and/or target aren't specified sc1 = SpectralCoord(500 * u.nm, radial_velocity=1 * u.km / u.s) sc2 = sc1.with_radial_velocity_shift(1 * u.km / u.s) assert_quantity_allclose(sc2.radial_velocity, 2 * u.km / u.s) sc3 = sc1.with_radial_velocity_shift(-3 * u.km / u.s) assert_quantity_allclose(sc3.radial_velocity, -2 * u.km / u.s) with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): sc4 = SpectralCoord(500 * u.nm, radial_velocity=1 * u.km / u.s, observer=gcrs_not_origin) sc5 = sc4.with_radial_velocity_shift(1 * u.km / u.s) assert_quantity_allclose(sc5.radial_velocity, 2 * u.km / u.s) sc6 = sc4.with_radial_velocity_shift(-3 * u.km / u.s) assert_quantity_allclose(sc6.radial_velocity, -2 * u.km / u.s) with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): sc7 = SpectralCoord(500 * u.nm, radial_velocity=1 * u.km / u.s, target=ICRS(10 * u.deg, 20 * u.deg)) sc8 = sc7.with_radial_velocity_shift(1 * u.km / u.s) assert_quantity_allclose(sc8.radial_velocity, 2 * u.km / u.s) sc9 = sc7.with_radial_velocity_shift(-3 * u.km / u.s) assert_quantity_allclose(sc9.radial_velocity, -2 * u.km / u.s) # Check that things still work when both observer and target are specified with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): sc10 = SpectralCoord(500 * u.nm, observer=ICRS(0 * u.deg, 0 * u.deg, distance=1 * u.m), target=ICRS(10 * u.deg, 20 * u.deg, radial_velocity=1 * u.km / u.s, distance=10 * u.kpc)) sc11 = sc10.with_radial_velocity_shift(1 * u.km / u.s) assert_quantity_allclose(sc11.radial_velocity, 2 * u.km / u.s) sc12 = sc10.with_radial_velocity_shift(-3 * u.km / u.s) assert_quantity_allclose(sc12.radial_velocity, -2 * u.km / u.s)
def test_with_observer_stationary_relative_to(): # Simple tests of with_observer_stationary_relative_to to cover different # ways of calling it # The replicate method makes a new object with attributes updated, but doesn't # do any conversion sc1 = SpectralCoord([4000, 5000] * u.AA) with pytest.raises(ValueError, match='This method can only be used if both ' 'observer and target are defined on the ' 'SpectralCoord'): sc1.with_observer_stationary_relative_to('icrs') sc2 = SpectralCoord([4000, 5000] * u.AA, observer=ICRS(0 * u.km, 0 * u.km, 0 * u.km, -1 * u.km / u.s, 0 * u.km / u.s, -1 * u.km / u.s, representation_type='cartesian', differential_type='cartesian'), target=ICRS(0 * u.deg, 45 * u.deg, distance=1 * u.kpc, radial_velocity=2 * u.km / u.s)) # Motion of observer is in opposite direction to target assert_quantity_allclose(sc2.radial_velocity, (2 + 2**0.5) * u.km / u.s) # Change to observer that is stationary in ICRS sc3 = sc2.with_observer_stationary_relative_to('icrs') # Velocity difference is now pure radial velocity of target assert_quantity_allclose(sc3.radial_velocity, 2 * u.km / u.s) # Check setting the velocity in with_observer_stationary_relative_to sc4 = sc2.with_observer_stationary_relative_to( 'icrs', velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) # Observer once again moving away from target but faster assert_quantity_allclose(sc4.radial_velocity, 4 * u.km / u.s) # Check that we can also pass frame classes instead of names sc5 = sc2.with_observer_stationary_relative_to( ICRS, velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) assert_quantity_allclose(sc5.radial_velocity, 4 * u.km / u.s) # And make sure we can also pass instances of classes without data sc6 = sc2.with_observer_stationary_relative_to( ICRS(), velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) assert_quantity_allclose(sc6.radial_velocity, 4 * u.km / u.s) # And with data provided no velocities are present sc7 = sc2.with_observer_stationary_relative_to( ICRS(0 * u.km, 0 * u.km, 0 * u.km, representation_type='cartesian'), velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) assert_quantity_allclose(sc7.radial_velocity, 4 * u.km / u.s) # And also have the ability to pass frames with velocities already defined sc8 = sc2.with_observer_stationary_relative_to( ICRS(0 * u.km, 0 * u.km, 0 * u.km, 2**0.5 * u.km / u.s, 0 * u.km / u.s, 2**0.5 * u.km / u.s, representation_type='cartesian', differential_type='cartesian')) assert_quantity_allclose(sc8.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s) # Make sure that things work properly if passing a SkyCoord sc9 = sc2.with_observer_stationary_relative_to( SkyCoord( ICRS(0 * u.km, 0 * u.km, 0 * u.km, representation_type='cartesian')), velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) assert_quantity_allclose(sc9.radial_velocity, 4 * u.km / u.s) sc10 = sc2.with_observer_stationary_relative_to( SkyCoord( ICRS(0 * u.km, 0 * u.km, 0 * u.km, 2**0.5 * u.km / u.s, 0 * u.km / u.s, 2**0.5 * u.km / u.s, representation_type='cartesian', differential_type='cartesian'))) assert_quantity_allclose(sc10.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s) # But we shouldn't be able to pass both a frame with velocities, and explicit velocities with pytest.raises( ValueError, match= 'frame already has differentials, cannot also specify velocity'): sc2.with_observer_stationary_relative_to( ICRS(0 * u.km, 0 * u.km, 0 * u.km, 2**0.5 * u.km / u.s, 0 * u.km / u.s, 2**0.5 * u.km / u.s, representation_type='cartesian', differential_type='cartesian'), velocity=[-2**0.5, 0, -2**0.5] * u.km / u.s) # Make sure things don't change depending on what frame class is used for reference sc11 = sc2.with_observer_stationary_relative_to( SkyCoord( ICRS(0 * u.km, 0 * u.km, 0 * u.km, 2**0.5 * u.km / u.s, 0 * u.km / u.s, 2**0.5 * u.km / u.s, representation_type='cartesian', differential_type='cartesian')).transform_to(Galactic)) assert_quantity_allclose(sc11.radial_velocity, 0 * u.km / u.s, atol=1e-10 * u.km / u.s)
def test_replicate(): # The replicate method makes a new object with attributes updated, but doesn't # do any conversion sc_init = SpectralCoord([4000, 5000] * u.AA, redshift=2) sc_set_rv = sc_init.replicate(redshift=1) assert_quantity_allclose(sc_set_rv.radial_velocity, 0.6 * c) assert_quantity_allclose(sc_init, [4000, 5000] * u.AA) sc_set_rv = sc_init.replicate(radial_velocity=c / 2) assert_quantity_allclose(sc_set_rv.redshift, np.sqrt(3) - 1) assert_quantity_allclose(sc_init, [4000, 5000] * u.AA) gcrs_origin = GCRS(CartesianRepresentation([0 * u.km, 0 * u.km, 0 * u.km])) with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): sc_init2 = SpectralCoord([4000, 5000] * u.AA, redshift=1, observer=gcrs_origin) with np.errstate(all='ignore'): sc_init2.replicate(redshift=.5) assert_quantity_allclose(sc_init2, [4000, 5000] * u.AA) with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): sc_init3 = SpectralCoord([4000, 5000] * u.AA, redshift=1, target=gcrs_origin) with np.errstate(all='ignore'): sc_init3.replicate(redshift=.5) assert_quantity_allclose(sc_init2, [4000, 5000] * u.AA) with pytest.warns(AstropyUserWarning, match='No velocity defined on frame'): sc_init4 = SpectralCoord([4000, 5000] * u.AA, observer=gcrs_origin, target=gcrs_origin) with pytest.raises( ValueError, match= 'Cannot specify radial velocity or redshift if both target and observer are specified' ): sc_init4.replicate(redshift=.5) sc_init = SpectralCoord([4000, 5000] * u.AA, redshift=2) sc_init_copy = sc_init.replicate(copy=True) sc_init[0] = 6000 * u.AA assert_quantity_allclose(sc_init_copy, [4000, 5000] * u.AA) sc_init = SpectralCoord([4000, 5000] * u.AA, redshift=2) sc_init_ref = sc_init.replicate() sc_init[0] = 6000 * u.AA assert_quantity_allclose(sc_init_ref, [6000, 5000] * u.AA)
def test_apply_relativistic_doppler_shift(): # Frequency sq1 = SpectralQuantity(1 * u.GHz) sq2 = _apply_relativistic_doppler_shift(sq1, 0.5 * c) assert_quantity_allclose(sq2, np.sqrt(1. / 3.) * u.GHz) # Wavelength sq3 = SpectralQuantity(500 * u.nm) sq4 = _apply_relativistic_doppler_shift(sq3, 0.5 * c) assert_quantity_allclose(sq4, np.sqrt(3) * 500 * u.nm) # Energy sq5 = SpectralQuantity(300 * u.eV) sq6 = _apply_relativistic_doppler_shift(sq5, 0.5 * c) assert_quantity_allclose(sq6, np.sqrt(1. / 3.) * 300 * u.eV) # Wavenumber sq7 = SpectralQuantity(0.01 / u.micron) sq8 = _apply_relativistic_doppler_shift(sq7, 0.5 * c) assert_quantity_allclose(sq8, np.sqrt(1. / 3.) * 0.01 / u.micron) # Velocity (doppler_convention='relativistic') sq9 = SpectralQuantity(200 * u.km / u.s, doppler_convention='relativistic', doppler_rest=1 * u.GHz) sq10 = _apply_relativistic_doppler_shift(sq9, 300 * u.km / u.s) assert_quantity_allclose(sq10, 499.999666 * u.km / u.s) assert sq10.doppler_convention == 'relativistic' # Velocity (doppler_convention='optical') sq11 = SpectralQuantity(200 * u.km / u.s, doppler_convention='radio', doppler_rest=1 * u.GHz) sq12 = _apply_relativistic_doppler_shift(sq11, 300 * u.km / u.s) assert_quantity_allclose(sq12, 499.650008 * u.km / u.s) assert sq12.doppler_convention == 'radio' # Velocity (doppler_convention='radio') sq13 = SpectralQuantity(200 * u.km / u.s, doppler_convention='optical', doppler_rest=1 * u.GHz) sq14 = _apply_relativistic_doppler_shift(sq13, 300 * u.km / u.s) assert_quantity_allclose(sq14, 500.350493 * u.km / u.s) assert sq14.doppler_convention == 'optical' # Velocity - check relativistic velocity addition sq13 = SpectralQuantity(0 * u.km / u.s, doppler_convention='relativistic', doppler_rest=1 * u.GHz) sq14 = _apply_relativistic_doppler_shift(sq13, 0.999 * c) assert_quantity_allclose(sq14, 0.999 * c) sq14 = _apply_relativistic_doppler_shift(sq14, 0.999 * c) assert_quantity_allclose(sq14, (0.999 * 2) / (1 + 0.999**2) * c) assert sq14.doppler_convention == 'relativistic' # Cases that should raise errors sq15 = SpectralQuantity(200 * u.km / u.s) with pytest.raises(ValueError, match='doppler_convention not set'): _apply_relativistic_doppler_shift(sq15, 300 * u.km / u.s) sq16 = SpectralQuantity(200 * u.km / u.s, doppler_rest=10 * u.GHz) with pytest.raises(ValueError, match='doppler_convention not set'): _apply_relativistic_doppler_shift(sq16, 300 * u.km / u.s) sq17 = SpectralQuantity(200 * u.km / u.s, doppler_convention='optical') with pytest.raises(ValueError, match='doppler_rest not set'): _apply_relativistic_doppler_shift(sq17, 300 * u.km / u.s)
def test_get_horizons_coord(): # get_horizons_coord() depends on astroquery pytest.importorskip("astroquery") # Validate against published values from the Astronomical Almanac (2013) e1 = get_horizons_coord('Geocenter', '2013-Jan-01') assert_quantity_allclose(e1.lon, 0 * u.deg, atol=5e-6 * u.deg) assert_quantity_allclose(e1.lat, -3.03 * u.deg, atol=5e-3 * u.deg) assert_quantity_allclose(e1.radius, 0.9832947 * u.AU, atol=5e-7 * u.AU) e2 = get_horizons_coord('Geocenter', '2013-Sep-01') assert_quantity_allclose(e1.lon, 0 * u.deg, atol=5e-6 * u.deg) assert_quantity_allclose(e2.lat, 7.19 * u.deg, atol=5e-3 * u.deg) assert_quantity_allclose(e2.radius, 1.0092561 * u.AU, atol=5e-7 * u.AU)
def test_get_body_heliographic_stonyhurst_light_travel_time_array(): # Tests whether requesting an array of locations returns the same answers as individually t1 = Time('2001-02-03 04:05:06') t2 = Time('2011-12-13 14:15:16') venus1 = get_body_heliographic_stonyhurst('venus', t1, observer=get_earth(t1)) venus2 = get_body_heliographic_stonyhurst('venus', t2, observer=get_earth(t2)) both = get_body_heliographic_stonyhurst('venus', [t1, t2], observer=get_earth([t1, t2])) assert_quantity_allclose(venus1.lon, both[0].lon) assert_quantity_allclose(venus1.lat, both[0].lat) assert_quantity_allclose(venus1.radius, both[0].radius) assert_quantity_allclose(venus2.lon, both[1].lon) assert_quantity_allclose(venus2.lat, both[1].lat) assert_quantity_allclose(venus2.radius, both[1].radius)
def test_Rotation2D_quantity(): model = models.Rotation2D(angle=90 * u.deg) x, y = model(1 * u.deg, 0 * u.arcsec) assert_quantity_allclose([x, y], [0, 1] * u.deg, atol=1e-10 * u.deg)
def test_sample_closed_starts_at_min_anomaly_if_in_range(min_nu, ecc, max_nu): result = sample_closed(min_nu, ecc, max_nu) assert_quantity_allclose(result[0], min_nu)
def test_get_horizons_coord_array_time(): # get_horizons_coord() depends on astroquery pytest.importorskip("astroquery") # Validate against published values from the Astronomical Almanac (2013, C8-C13) array_time = Time(['2013-05-01', '2013-06-01', '2013-04-01', '2013-03-01']) e = get_horizons_coord('Geocenter', array_time) assert_quantity_allclose(e[0].lon, 0 * u.deg, atol=5e-6 * u.deg) assert_quantity_allclose(e[0].lat, -4.17 * u.deg, atol=5e-3 * u.deg) assert_quantity_allclose(e[0].radius, 1.0075271 * u.AU, atol=5e-7 * u.AU) assert_quantity_allclose(e[1].lon, 0 * u.deg, atol=5e-6 * u.deg) assert_quantity_allclose(e[1].lat, -0.66 * u.deg, atol=5e-3 * u.deg) assert_quantity_allclose(e[1].radius, 1.0140013 * u.AU, atol=5e-7 * u.AU) assert_quantity_allclose(e[2].lon, 0 * u.deg, atol=5e-6 * u.deg) assert_quantity_allclose(e[2].lat, -6.54 * u.deg, atol=5e-3 * u.deg) assert_quantity_allclose(e[2].radius, 0.9992311 * u.AU, atol=5e-7 * u.AU) assert_quantity_allclose(e[3].lon, 0 * u.deg, atol=5e-6 * u.deg) assert_quantity_allclose(e[3].lat, -7.22 * u.deg, atol=5e-3 * u.deg) assert_quantity_allclose(e[3].radius, 0.9908173 * u.AU, atol=5e-7 * u.AU)
def test_construct(): result = deserialize_class(('astropy.units.Quantity', (10, ), { 'unit': 'deg' })) assert_quantity_allclose(result, 10 * u.deg)
def test_sample_closed_starts_and_ends_at_min_anomaly_if_in_range_and_no_max_given( min_nu, ecc): result = sample_closed(min_nu, ecc) assert_quantity_allclose(result[0], min_nu) assert_quantity_allclose(result[-1], min_nu, atol=1e-14 * u.rad)
def test_convert_from_coe_to_rv(): # Data from Vallado, example 2.5 attractor = Earth r = [6524.384, 6862.875, 6448.296] * u.km v = [4.901327, 5.533756, -1.976341] * u.km / u.s expected_p = 11067.79 * u.km expected_ecc = 0.832853 * u.one expected_inc = 87.870 * u.deg expected_raan = 227.89 * u.deg expected_argp = 53.38 * u.deg expected_nu = 92.335 * u.deg ss = Orbit.from_vectors(attractor, r, v) _, ecc, inc, raan, argp, nu = ss.classical() p = ss.p assert_quantity_allclose(p, expected_p, rtol=1e-4) assert_quantity_allclose(ecc, expected_ecc, rtol=1e-4) assert_quantity_allclose(inc, expected_inc, rtol=1e-4) assert_quantity_allclose(raan, expected_raan, rtol=1e-4) assert_quantity_allclose(argp, expected_argp, rtol=1e-4) assert_quantity_allclose(nu, expected_nu, rtol=1e-4)
def test_goes_lx_obstime(): # Define input values of flux and time. longflux = Quantity([7e-6, 7e-6, 7e-6, 7e-6, 7e-6, 7e-6], unit="W/m**2") shortflux = Quantity([7e-7, 7e-7, 7e-7, 7e-7, 7e-7, 7e-7], unit="W/m**2") obstime = np.array([ datetime.datetime(2014, 1, 1, 0, 0, 0), datetime.datetime(2014, 1, 1, 0, 0, 2), datetime.datetime(2014, 1, 1, 0, 0, 4), datetime.datetime(2014, 1, 1, 0, 0, 6), datetime.datetime(2014, 1, 1, 0, 0, 8), datetime.datetime(2014, 1, 1, 0, 0, 10) ], dtype=object) # Test output when obstime and cumulative kwargs are set. lx_test = goes._goes_lx(longflux, shortflux, obstime) lx_expected = { "longlum": 1.96860565e+18 * Quantity(np.ones(6), unit='W'), "shortlum": 1.96860565e+17 * Quantity(np.ones(6), unit='W'), "longlum_int": Quantity([1.96860565e+19], unit="J"), "shortlum_int": Quantity([1.96860565e+18], unit="J"), "longlum_cumul": Quantity([ 3.93721131e+18, 7.87442262e+18, 1.18116339e+19, 1.57488452e+19, 1.96860565e+19 ], unit="J"), "shortlum_cumul": Quantity([ 3.93721131e+17, 7.87442262e+17, 1.18116339e+18, 1.57488452e+18, 1.96860565e+18 ], unit="J") } assert sorted(lx_test.keys()) == sorted(lx_expected.keys()) assert_quantity_allclose(lx_test["longlum"], lx_expected["longlum"], rtol=0.1) assert_quantity_allclose(lx_test["shortlum"], lx_expected["shortlum"], rtol=0.1) assert_quantity_allclose(lx_test["longlum_int"], lx_expected["longlum_int"], rtol=0.1) assert_quantity_allclose(lx_test["shortlum_int"], lx_expected["shortlum_int"], rtol=0.1) assert_quantity_allclose(lx_test["longlum_cumul"], lx_expected["longlum_cumul"], rtol=0.1) assert_quantity_allclose(lx_test["shortlum_cumul"], lx_expected["shortlum_cumul"], rtol=0.1)
def test_geosync_has_proper_period(): expected_period = 1436 * u.min ss = Orbit.circular(Earth, alt=42164 * u.km - Earth.R) assert_quantity_allclose(ss.period, expected_period, rtol=1e-4)
def test_spectral_model(self): model = self.source.spectral_model energy = u.Quantity(100, 'GeV') desired = u.Quantity(6.8700477298e-12, 'cm-2 GeV-1 s-1') assert_quantity_allclose(model(energy), desired)
def test_mean_anomaly(): assert_quantity_allclose(sun.mean_anomaly("2002/12/12"), 337.538 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.mean_anomaly("2003/03/25"), 79.055 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.mean_anomaly("2005/06/05"), 150.492 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.mean_anomaly("2006/11/17"), 312.860 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.mean_anomaly("2008/07/29"), 203.933 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.mean_anomaly("2011/01/31"), 26.742 * u.deg, atol=1 * u.deg)
def test_geostationary_creation_from_period(attractor, period, expected_a): ss = Orbit.geostationary(attractor=attractor, period=period) assert_quantity_allclose(ss.a, expected_a, rtol=1.0e-7) assert_quantity_allclose(ss.period, period, rtol=1.0e-7)
def test_true_longitude(): # source: http://www.satellite-calculations.com/Satellite/suncalc.htm # values are deviating a little because of lack of time parameter in # true_longitude function assert_quantity_allclose(sun.true_longitude("2002/12/23"), 270.978 * u.deg, atol=1.1 * u.deg) assert_quantity_allclose(sun.true_longitude("2003/01/29"), 308.661 * u.deg, atol=1.1 * u.deg) assert_quantity_allclose(sun.true_longitude("2004/05/12"), 51.617 * u.deg, atol=1.1 * u.deg) assert_quantity_allclose(sun.true_longitude("2006/07/04"), 101.910 * u.deg, atol=1.1 * u.deg) assert_quantity_allclose(sun.true_longitude("2007/09/16"), 172.767 * u.deg, atol=1.1 * u.deg) assert_quantity_allclose(sun.true_longitude("2009/02/11"), 322.394 * u.deg, atol=1.1 * u.deg)
def test_sunearth_distance(): # Source for these values # wolframalpha.com # http://www.wolframalpha.com/input/?i=earth-sun+distance+on+2010%2F02%2F04 assert_quantity_allclose(sun.sunearth_distance("2010/02/04"), 0.9858 * u.AU, atol=1e-3 * u.AU) assert_quantity_allclose(sun.sunearth_distance("2009/04/13"), 1.003 * u.AU, atol=1e-3 * u.AU) assert_quantity_allclose(sun.sunearth_distance("2008/06/20"), 1.016 * u.AU, atol=1e-3 * u.AU) assert_quantity_allclose(sun.sunearth_distance("2007/08/15"), 1.013 * u.AU, atol=1e-3 * u.AU) assert_quantity_allclose(sun.sunearth_distance("2007/10/02"), 1.001 * u.AU, atol=1e-3 * u.AU) assert_quantity_allclose(sun.sunearth_distance("2006/12/27"), 0.9834 * u.AU, atol=1e-3 * u.AU)
def test_spectral_model(self, index, model_type, desired, desired_err): energy = u.Quantity(1, 'GeV') model = self.cat[index].spectral_model assert isinstance(model, model_type) actual = model(energy) assert_quantity_allclose(actual, desired)
def test_apparent_declination(): assert_quantity_allclose(sun.apparent_declination("2002/12/22"), -22.964 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.apparent_declination("2003/1/12"), -21.743 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.apparent_declination("2004/02/13"), -13.478 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.apparent_declination("2005/12/3"), -22.152 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.apparent_declination("2013/02/26"), -8.547 * u.deg, atol=1 * u.deg) assert_quantity_allclose(sun.apparent_declination("2014/05/1"), 15.141 * u.deg, atol=1 * u.deg)
def test_change_plane_twice_restores_original_data(): new_ss = iss.change_plane(Planes.EARTH_ECLIPTIC).change_plane(iss.plane) assert_quantity_allclose(new_ss.r, iss.r) assert_quantity_allclose(new_ss.v, iss.v)
def test_spectral_model_error(self, index, model_type, desired, desired_err): energy = u.Quantity(1, 'GeV') model = self.cat[index].spectral_model actual = model.evaluate_error(energy) assert_quantity_allclose(actual[1], desired_err)
def test_models_evaluate_with_units(model): if not HAS_SCIPY and model['class'] in SCIPY_MODELS: pytest.skip() m = model['class'](**model['parameters']) for args in model['evaluation']: assert_quantity_allclose(m(*args[:-1]), args[-1])
def test_time_to_anomaly(): expected_tof = iss.period / 2 iss_180 = iss.propagate_to_anomaly(180 * u.deg) tof = iss_180.time_to_anomaly(0 * u.deg) assert_quantity_allclose(tof, expected_tof)
def test_snodgrass(seconds_per_day): rot = diff_rot(10 * seconds_per_day, 30 * u.deg, rot_type='snodgrass') assert_quantity_allclose(rot, 135.4232 * u.deg, rtol=1e-3)
def test_hill_radius_given_a(): parent = Body(None, 1 * u.km ** 3 / u.s ** 2, "Parent") body = Body(parent, 1 * u.km ** 3 / u.s ** 2, "Body") r_SOI = hill_radius(body, 1 * u.km, 0.25 * u.one) expected_r_SOI = 520.02096 * u.m assert_quantity_allclose(r_SOI, expected_r_SOI, rtol=1e-8)
def test_allen(seconds_per_day): rot = diff_rot(10 * seconds_per_day, 30 * u.deg, rot_type='allen') assert_quantity_allclose(rot, 136.9 * u.deg, rtol=1e-3)
def test_single(seconds_per_day): rot = diff_rot(10 * seconds_per_day, 30 * u.deg) assert_quantity_allclose(rot, 136.8216 * u.deg, rtol=1e-3)