From 2872b97c6d92592d50fb1794a08e5dc74eff171c Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Wed, 13 Nov 2024 11:40:01 -0500 Subject: [PATCH 01/12] Lunar Array Compatibility Enable moon specific code with "world" keyword --- py21cmsense/_utils.py | 39 +++++++++---- py21cmsense/data/farview/P_Tb_ultimate.npy | Bin 0 -> 320 bytes py21cmsense/data/farview/kmag_ultimate.npy | Bin 0 -> 320 bytes py21cmsense/observation.py | 6 +- py21cmsense/observatory.py | 8 ++- py21cmsense/sensitivity.py | 4 +- py21cmsense/theory.py | 61 +++++++++++++++++++++ 7 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 py21cmsense/data/farview/P_Tb_ultimate.npy create mode 100644 py21cmsense/data/farview/kmag_ultimate.npy diff --git a/py21cmsense/_utils.py b/py21cmsense/_utils.py index 6c44f15..c1b0b47 100644 --- a/py21cmsense/_utils.py +++ b/py21cmsense/_utils.py @@ -3,6 +3,9 @@ from astropy import units as un from astropy.coordinates import EarthLocation, SkyCoord from astropy.time import Time +from lunarsky import MoonLocation +from lunarsky import SkyCoord as LunarSkyCoord +from lunarsky import Time as LTime from pyuvdata import utils as uvutils from . import config @@ -34,7 +37,7 @@ def find_nearest(array, value): @un.quantity_input def phase_past_zenith( - time_past_zenith: un.day, bls_enu: np.ndarray, latitude, use_apparent: bool = True + time_past_zenith: un.day, bls_enu: np.ndarray, latitude, world, use_apparent: bool = True ): """Compute UVWs phased to a point rotated from zenith by a certain amount of time. @@ -51,6 +54,8 @@ def phase_past_zenith( The UVWs when phased to zenith. latitude The latitude of the center of the array, in radians. + world + Whether the telescope is on the Earth or Moon. Returns ------- @@ -59,20 +64,34 @@ def phase_past_zenith( """ # Generate ra/dec of zenith at time in the phase_frame coordinate system # to use for phasing - telescope_location = EarthLocation.from_geodetic(lon=0, lat=latitude) + if world == 'earth': + telescope_location = EarthLocation.from_geodetic(lon=0, lat=latitude) + else: + telescope_location = MoonLocation.from_selenodetic(lon=0, lat=latitude) # JD is arbitrary jd = 2454600 - zenith_coord = SkyCoord( - alt=90 * un.deg, - az=0 * un.deg, - obstime=Time(jd, format="jd"), - frame="altaz", - location=telescope_location, - ) - zenith_coord = zenith_coord.transform_to("icrs") + if world == 'earth': + zenith_coord = SkyCoord( + alt=90 * un.deg, + az=0 * un.deg, + obstime=Time(jd, format="jd"), + frame="altaz", + location=telescope_location, + ) + else: + zenith_coord = LunarSkyCoord( + alt=90 * un.deg, + az=0 * un.deg, + obstime=LTime(jd, format="jd"), + frame="lunartopo", + location=telescope_location, + ) + zenith_coord = zenith_coord.transform_to("icrs") + + zenith_coord.obstime.location = telescope_location obstimes = zenith_coord.obstime + time_past_zenith lsts = obstimes.sidereal_time("apparent", longitude=0.0).rad diff --git a/py21cmsense/data/farview/P_Tb_ultimate.npy b/py21cmsense/data/farview/P_Tb_ultimate.npy new file mode 100644 index 0000000000000000000000000000000000000000..ad87b6e31629691236e8326454df123d495a6e11 GIT binary patch literal 320 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= zXCxM+0{I$7COVor3bhL411{yXqe@Jd_dBRxn0j$X$r*?Cf+U$wr_MMm)jyNDYtDHG z-sXlSFDG1Zm|?=K9LRFcq327?5t}t<9J=|8zBM!*bLjT|dg=bvLk{*6YvwM@-s=#3 z(^aSI&^CvjiK?@F&TVj5zNh2qx0}lyLT5BynVhk}p-Cw0;-}YB9W=`xdL7u=<4|zN z;9aarlf$QjYYzT&E^|hFWP?0_rBU(`x{TMhW<5KWPkSPo`ur;mf3$l z`BAqjV~zcrd8c*=@7riUtzB~JB+YI1J}c+;ept8Le!FJ=J|nM#_8av#F1|eDsJ$#x z&}OAyr|hp-6;5-zdBOgZ;cVVx+*j?{*-viy=y}UtaL%)j*4Yp2uN3y?HRz}gFOI~J9>Qp literal 0 HcmV?d00001 diff --git a/py21cmsense/observation.py b/py21cmsense/observation.py index f3a7e02..a39cccb 100755 --- a/py21cmsense/observation.py +++ b/py21cmsense/observation.py @@ -92,16 +92,16 @@ class Observation: time_per_day: tp.Time = attr.ib( 6 * un.hour, - validator=(tp.vld_physical_type("time"), ut.between(0 * un.hour, 24 * un.hour)), + validator=(tp.vld_physical_type("time"), ut.between(0 * un.hour, 655.2 * un.hour)), ) track: tp.Time | None = attr.ib( None, validator=attr.validators.optional( - [tp.vld_physical_type("time"), ut.between(0, 24 * un.hour)] + [tp.vld_physical_type("time"), ut.between(0 * un.hour, 655.2 * un.hour)] ), ) obs_duration: tp.Time = attr.ib( - validator=(tp.vld_physical_type("time"), ut.between(0, 24 * un.hour)), + validator=(tp.vld_physical_type("time"), ut.between(0 * un.hour, 655.2 * un.hour)), ) integration_time: tp.Time = attr.ib( 60 * un.second, validator=(tp.vld_physical_type("time"), ut.positive) diff --git a/py21cmsense/observatory.py b/py21cmsense/observatory.py index 15d3258..8f3ef78 100644 --- a/py21cmsense/observatory.py +++ b/py21cmsense/observatory.py @@ -51,6 +51,8 @@ class Observatory: of the array). Assumed to be in units of meters if no units are supplied. Can be used to limit antennas in arrays like HERA and SKA that have a "core" and "outriggers". The minimum is inclusive, and maximum exclusive. + world : string + A string specifiying whether the telescope is on the Earth or the moon. """ _antpos: tp.Length = attr.ib(eq=attr.cmp_using(eq=np.array_equal)) @@ -66,6 +68,7 @@ class Observatory: min_antpos: tp.Length = attr.ib( default=0.0 * un.m, validator=(tp.vld_physical_type("length"), ut.nonnegative) ) + world: str = attr.ib(default = 'earth') #Add validator stuff later @_antpos.validator def _antpos_validator(self, att, val): @@ -194,7 +197,7 @@ def projected_baselines( bl_wavelengths = baselines.reshape((-1, 3)) * self.metres_to_wavelengths - out = ut.phase_past_zenith(time_offset, bl_wavelengths, self.latitude) + out = ut.phase_past_zenith(time_offset, bl_wavelengths, self.latitude, self.world) out = out.reshape(*orig_shape[:-1], np.size(time_offset), orig_shape[-1]) if np.size(time_offset) == 1: @@ -225,7 +228,10 @@ def longest_baseline(self) -> float: @cached_property def observation_duration(self) -> un.Quantity[un.day]: """The time it takes for the sky to drift through the FWHM.""" + if self.world == "earth": return un.day * self.beam.fwhm / (2 * np.pi * un.rad) + else: + return 27.3 * un.day * self.beam.fwhm / (2 * np.pi * un.rad) def get_redundant_baselines( self, diff --git a/py21cmsense/sensitivity.py b/py21cmsense/sensitivity.py index 7447524..d8f0f8c 100644 --- a/py21cmsense/sensitivity.py +++ b/py21cmsense/sensitivity.py @@ -139,7 +139,7 @@ class PowerSpectrum(Sensitivity): horizon_buffer: tp.Wavenumber = attr.ib(default=0.1 * littleh / un.Mpc) foreground_model: str = attr.ib( - default="moderate", validator=vld.in_(["moderate", "optimistic"]) + default="moderate", validator=vld.in_(["moderate", "optimistic", "ultra_optimistic"]) ) theory_model: TheoryModel = attr.ib() @@ -457,6 +457,8 @@ def horizon_limit(self, umag: float) -> tp.Wavenumber: return horizon + self.horizon_buffer elif self.foreground_model in ["optimistic"]: return horizon * np.sin(self.observation.observatory.beam.first_null / 2) + elif self.foreground_model in ["ultra_optimistic"]: + return horizon def _average_sense_to_1d( self, sense: dict[tp.Wavenumber, tp.Delta], k1d: tp.Wavenumber | None = None diff --git a/py21cmsense/theory.py b/py21cmsense/theory.py index 489452a..df8f0c7 100644 --- a/py21cmsense/theory.py +++ b/py21cmsense/theory.py @@ -159,3 +159,64 @@ def delta_squared(self, z: float, k: np.ndarray) -> un.Quantity[un.mK**2]: ) return self.spline(k) << un.mK**2 + +class FarViewModel(TheoryModel): + """21cmFAST-based theory model explicitly for z=30, [Insert paper link here later]""" + + use_littleh: bool = False + + def __init__(self) -> None: + k_pth = ( + Path(__file__).parent + / "data/farview/kmag_ultimate.npy" + ) + + delta_pth = ( + Path(__file__).parent + / "data/farview/P_Tb_ultimate.npy" + ) + #Should at some point reorganize the data so these steps aren't necessary + k_fixed = np.load(k_pth) + power = np.load(delta_pth) + k_fixed = k_fixed[~np.isnan(power)] + power = power[~np.isnan(power)] + delta = (k_fixed**3 * power) / (2*np.pi**2) + + self.k = k_fixed + self.delta_squared_raw = delta + + self.spline = InterpolatedUnivariateSpline(self.k, self.delta_squared_raw, k=1) + + def delta_squared(self, z: float, k: np.ndarray) -> un.Quantity[un.mK**2]: + """Compute Delta^2(k, z) for the theory model. + + Parameters + ---------- + z + The redshift (should be a float). + k + The wavenumbers, either in units of 1/Mpc if use_littleh=False, or + h/Mpc if use_littleh=True. + + Returns + ------- + delta_squared + An array of delta_squared values in units of mK^2. + """ + if np.any(k > self.k.max()): + warnings.warn( + f"Extrapolating above the simulated theoretical k: {k.max()} > {self.k.max()}", + stacklevel=2, + ) + if np.any(k < self.k.min()): + warnings.warn( + f"Extrapolating below the simulated theoretical k: {k.min()} < {self.k.min()}", + stacklevel=2, + ) + if not 29.5 < z < 30.5: + warnings.warn( + f"Theory power corresponds to z=30, not z={z:.2f}", + stacklevel=2, + ) + + return self.spline(k) << un.mK**2 \ No newline at end of file From beb1ccbcb4e65e754182e9b14bddbc052d6aae31 Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Mon, 25 Nov 2024 14:43:37 -0500 Subject: [PATCH 02/12] Lunar Array Capability --- src/py21cmsense/_utils.py | 38 +++++++++++++++++++++++++--------- src/py21cmsense/observation.py | 6 +++--- src/py21cmsense/observatory.py | 10 +++++++-- src/py21cmsense/sensitivity.py | 4 +++- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/py21cmsense/_utils.py b/src/py21cmsense/_utils.py index 1b4ad4b..96bc3fc 100644 --- a/src/py21cmsense/_utils.py +++ b/src/py21cmsense/_utils.py @@ -5,7 +5,9 @@ from astropy.coordinates import EarthLocation, SkyCoord from astropy.time import Time from pyuvdata import utils as uvutils - +from lunarsky import MoonLocation +from lunarsky import SkyCoord as LunarSkyCoord +from lunarsky import Time as LTime def between(xmin, xmax): """Return an attrs validation function that checks a number is within bounds.""" @@ -39,7 +41,7 @@ def find_nearest(array, value): @un.quantity_input def phase_past_zenith( - time_past_zenith: un.day, bls_enu: np.ndarray, latitude, use_apparent: bool = True + time_past_zenith: un.day, bls_enu: np.ndarray, latitude, world, use_apparent: bool = True ): """Compute UVWs phased to a point rotated from zenith by a certain amount of time. @@ -56,6 +58,8 @@ def phase_past_zenith( The UVWs when phased to zenith. latitude The latitude of the center of the array, in radians. + world + Wether the telescope is on the Earth or Moon. Returns ------- @@ -64,20 +68,34 @@ def phase_past_zenith( """ # Generate ra/dec of zenith at time in the phase_frame coordinate system # to use for phasing - telescope_location = EarthLocation.from_geodetic(lon=0, lat=latitude) + if world == "earth": + telescope_location = EarthLocation.from_geodetic(lon=0, lat=latitude) + else: + telescope_location = MoonLocation.from_selenodetic(lon=0, lat=latitude) # JD is arbitrary jd = 2454600 + + if world == "earth": + zenith_coord = SkyCoord( + alt=90 * un.deg, + az=0 * un.deg, + obstime=Time(jd, format="jd"), + frame="altaz", + location=telescope_location, + ) + else: + zenith_coord = LunarSkyCoord( + alt=90 * un.deg, + az=0 * un.deg, + obstime=LTime(jd, format="jd"), + frame="lunartopo", + location=telescope_location, + ) - zenith_coord = SkyCoord( - alt=90 * un.deg, - az=0 * un.deg, - obstime=Time(jd, format="jd"), - frame="altaz", - location=telescope_location, - ) zenith_coord = zenith_coord.transform_to("icrs") + zenit_coord.obstime.location = telescope_location obstimes = zenith_coord.obstime + time_past_zenith lsts = obstimes.sidereal_time("apparent", longitude=0.0).rad diff --git a/src/py21cmsense/observation.py b/src/py21cmsense/observation.py index f012873..ffea8ec 100644 --- a/src/py21cmsense/observation.py +++ b/src/py21cmsense/observation.py @@ -94,16 +94,16 @@ class Observation: time_per_day: tp.Time = attr.ib( 6 * un.hour, - validator=(tp.vld_physical_type("time"), ut.between(0 * un.hour, 24 * un.hour)), + validator=(tp.vld_physical_type("time"), ut.between(0 * un.hour, 655.2 * un.hour)), ) track: tp.Time | None = attr.ib( None, validator=attr.validators.optional( - [tp.vld_physical_type("time"), ut.between(0, 24 * un.hour)] + [tp.vld_physical_type("time"), ut.between(0, 655.2 * un.hour)] ), ) lst_bin_size: tp.Time = attr.ib( - validator=(tp.vld_physical_type("time"), ut.between(0, 24 * un.hour)), + validator=(tp.vld_physical_type("time"), ut.between(0, 655.2 * un.hour)), ) integration_time: tp.Time = attr.ib( 60 * un.second, validator=(tp.vld_physical_type("time"), ut.positive) diff --git a/src/py21cmsense/observatory.py b/src/py21cmsense/observatory.py index f1bba59..9bce0ca 100644 --- a/src/py21cmsense/observatory.py +++ b/src/py21cmsense/observatory.py @@ -68,6 +68,8 @@ class Observatory: By default it is, so that the beam-crossing time is ``tday * FWHM / (2pi cos(lat))``. This affects both the thermal and sample variance calculations. + world: string + A string specifying whether the telescope is on the Earth or the moon. """ _antpos: tp.Length = attr.ib(eq=attr.cmp_using(eq=np.array_equal)) @@ -84,6 +86,7 @@ class Observatory: default=0.0 * un.m, validator=(tp.vld_physical_type("length"), ut.nonnegative) ) beam_crossing_time_incl_latitude: bool = attr.ib(default=True, converter=bool) + world: str = attr.ib(default = 'earth') #add validator stuff later @_antpos.validator def _antpos_validator(self, att, val): @@ -262,7 +265,7 @@ def projected_baselines( bl_wavelengths = baselines.reshape((-1, 3)) * self.metres_to_wavelengths - out = ut.phase_past_zenith(time_offset, bl_wavelengths, self.latitude) + out = ut.phase_past_zenith(time_offset, bl_wavelengths, self.latitude, self.world) out = out.reshape(*orig_shape[:-1], np.size(time_offset), orig_shape[-1]) if np.size(time_offset) == 1: @@ -294,7 +297,10 @@ def longest_baseline(self) -> float: def observation_duration(self) -> un.Quantity[un.day]: """The time it takes for the sky to drift through the FWHM.""" latfac = np.cos(self.latitude) if self.beam_crossing_time_incl_latitude else 1 - return un.day * self.beam.fwhm / (2 * np.pi * un.rad * latfac) + if self.world == "earth": + return un.day * self.beam.fwhm / (2 * np.pi * un.rad * latfac) + else: + return 27.3 * un.day * self.beam.fwhm/(2 * np.pi * un.rad * latfac) def get_redundant_baselines( self, diff --git a/src/py21cmsense/sensitivity.py b/src/py21cmsense/sensitivity.py index 754e8d7..121a867 100644 --- a/src/py21cmsense/sensitivity.py +++ b/src/py21cmsense/sensitivity.py @@ -151,7 +151,7 @@ class PowerSpectrum(Sensitivity): horizon_buffer: tp.Wavenumber = attr.ib(default=0.1 * littleh / un.Mpc) foreground_model: str = attr.ib( - default="moderate", validator=vld.in_(["moderate", "optimistic"]) + default="moderate", validator=vld.in_(["moderate", "optimistic", "ultra_optimistic"]) ) theory_model: TheoryModel = attr.ib() @@ -471,6 +471,8 @@ def horizon_limit(self, umag: float) -> tp.Wavenumber: return horizon + self.horizon_buffer elif self.foreground_model in ["optimistic"]: return horizon * np.sin(self.observation.observatory.beam.first_null / 2) + elif self.foreground_model in ["ultra_optimistic"]: + return horizon def _average_sense_to_1d( self, sense: dict[tp.Wavenumber, tp.Delta], k1d: tp.Wavenumber | None = None From a30706f82d49ef2f20189bb3e280d66cff6891c4 Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Tue, 3 Dec 2024 23:29:01 -0500 Subject: [PATCH 03/12] Updating Theory Model For FarView --- .../py21cmsense}/data/farview/P_Tb_ultimate.npy | Bin .../py21cmsense}/data/farview/kmag_ultimate.npy | Bin src/py21cmsense/theory.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename {py21cmsense => src/py21cmsense}/data/farview/P_Tb_ultimate.npy (100%) rename {py21cmsense => src/py21cmsense}/data/farview/kmag_ultimate.npy (100%) diff --git a/py21cmsense/data/farview/P_Tb_ultimate.npy b/src/py21cmsense/data/farview/P_Tb_ultimate.npy similarity index 100% rename from py21cmsense/data/farview/P_Tb_ultimate.npy rename to src/py21cmsense/data/farview/P_Tb_ultimate.npy diff --git a/py21cmsense/data/farview/kmag_ultimate.npy b/src/py21cmsense/data/farview/kmag_ultimate.npy similarity index 100% rename from py21cmsense/data/farview/kmag_ultimate.npy rename to src/py21cmsense/data/farview/kmag_ultimate.npy diff --git a/src/py21cmsense/theory.py b/src/py21cmsense/theory.py index de418b7..00fc06b 100644 --- a/src/py21cmsense/theory.py +++ b/src/py21cmsense/theory.py @@ -256,4 +256,4 @@ def delta_squared(self, z: float, k: np.ndarray) -> un.Quantity[un.mK**2]: stacklevel=2, ) - return self.spline(k) << un.mK**2 \ No newline at end of file + return self.spline(k) << un.mK**2 From aeb60548eb25880ade48b6b35f419840e4c85485 Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Wed, 4 Dec 2024 12:02:52 -0500 Subject: [PATCH 04/12] Typo --- src/py21cmsense/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py21cmsense/_utils.py b/src/py21cmsense/_utils.py index 96bc3fc..f7b0944 100644 --- a/src/py21cmsense/_utils.py +++ b/src/py21cmsense/_utils.py @@ -95,7 +95,7 @@ def phase_past_zenith( zenith_coord = zenith_coord.transform_to("icrs") - zenit_coord.obstime.location = telescope_location + zenith_coord.obstime.location = telescope_location obstimes = zenith_coord.obstime + time_past_zenith lsts = obstimes.sidereal_time("apparent", longitude=0.0).rad From 8f706ddc6b882d886579e1ea9fed8cd952406d4c Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Fri, 13 Dec 2024 12:43:57 -0500 Subject: [PATCH 05/12] lunar array support Also includes "ultra_optimistic" no foregrounds model and z=30 theory model. Need to make more user friendly --- src/py21cmsense/observatory.py | 3 ++- src/py21cmsense/sensitivity.py | 2 +- tests/test_uvw.py | 7 +++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/py21cmsense/observatory.py b/src/py21cmsense/observatory.py index 9bce0ca..177336f 100644 --- a/src/py21cmsense/observatory.py +++ b/src/py21cmsense/observatory.py @@ -86,7 +86,8 @@ class Observatory: default=0.0 * un.m, validator=(tp.vld_physical_type("length"), ut.nonnegative) ) beam_crossing_time_incl_latitude: bool = attr.ib(default=True, converter=bool) - world: str = attr.ib(default = 'earth') #add validator stuff later + world: str = attr.ib(default = "earth", validator=vld.in_(["earth", "moon"]) + ) @_antpos.validator def _antpos_validator(self, att, val): diff --git a/src/py21cmsense/sensitivity.py b/src/py21cmsense/sensitivity.py index 121a867..ff947d4 100644 --- a/src/py21cmsense/sensitivity.py +++ b/src/py21cmsense/sensitivity.py @@ -472,7 +472,7 @@ def horizon_limit(self, umag: float) -> tp.Wavenumber: elif self.foreground_model in ["optimistic"]: return horizon * np.sin(self.observation.observatory.beam.first_null / 2) elif self.foreground_model in ["ultra_optimistic"]: - return horizon + return 0 def _average_sense_to_1d( self, sense: dict[tp.Wavenumber, tp.Delta], k1d: tp.Wavenumber | None = None diff --git a/tests/test_uvw.py b/tests/test_uvw.py index 7f281b7..b166627 100644 --- a/tests/test_uvw.py +++ b/tests/test_uvw.py @@ -24,6 +24,7 @@ def test_phase_at_zenith(lat, use_apparent): time_past_zenith=0.0 * un.day, bls_enu=bls_enu, latitude=lat * un.rad, + world = 'earth', use_apparent=use_apparent, ) @@ -45,6 +46,7 @@ def test_phase_past_zenith(use_apparent): time_past_zenith=0.2 * un.day, bls_enu=bls_enu, latitude=0 * un.rad, + world = 'earth', use_apparent=use_apparent, ) ) @@ -67,7 +69,7 @@ def test_phase_past_zenith_shape(): times = np.array([0, 0.1, 0, 0.1]) * un.day # Almost rotated to the horizon. - uvws = phase_past_zenith(time_past_zenith=times, bls_enu=bls_enu, latitude=0 * un.rad) + uvws = phase_past_zenith(time_past_zenith=times, bls_enu=bls_enu, latitude=0 * un.rad, world='earth') assert uvws.shape == (5, 4, 3) assert np.allclose(uvws[0], uvws[2]) # Same baselines @@ -87,11 +89,12 @@ def test_use_apparent(lat): times = np.linspace(-1, 1, 3) * un.hour # Almost rotated to the horizon. - uvws = phase_past_zenith(time_past_zenith=times, bls_enu=bls_enu, latitude=lat * un.rad) + uvws = phase_past_zenith(time_past_zenith=times, bls_enu=bls_enu, latitude=lat * un.rad, world='earth') uvws0 = phase_past_zenith( time_past_zenith=times, bls_enu=bls_enu, latitude=lat * un.rad, + world = 'earth', use_apparent=True, ) From 13e0b28b8c53440d5c89ccdd55b2e2930705b87b Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Fri, 13 Dec 2024 12:56:14 -0500 Subject: [PATCH 06/12] add lunarsky dep to yml --- docs/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/environment.yml b/docs/environment.yml index ea68551..7ea045d 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -24,4 +24,5 @@ dependencies: - setuptools_scm - pip: - furo + - lunarsky>=0.2.5 - sphinx_design From b5db76fd29d753a7332bc21ce274848f86d30597 Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Fri, 13 Dec 2024 13:08:52 -0500 Subject: [PATCH 07/12] adding lunarsky to test deps --- ci/testing.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/testing.yaml b/ci/testing.yaml index 746dc6d..4893415 100644 --- a/ci/testing.yaml +++ b/ci/testing.yaml @@ -19,3 +19,4 @@ dependencies: - matplotlib - pip: - methodtools + - lunarsky>=0.2.5 From 7d40e7d18db68fb53893e89c0018137fe0f60985 Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Tue, 21 Jan 2025 15:23:08 -0500 Subject: [PATCH 08/12] updating validators and tests for moon --- pyproject.toml | 1 + src/py21cmsense/_utils.py | 4 ++-- src/py21cmsense/observation.py | 36 ++++++++++++++++++++++++++++++---- src/py21cmsense/sensitivity.py | 4 ++-- tests/test_observation.py | 11 ++++++++--- tests/test_sensitivity.py | 6 +++++- 6 files changed, 50 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bdc2fb5..1ce04d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ dependencies = [ "rich", "attrs", "hickleable>=0.1.1", + "lunarsky>=0.2.5", ] [project.optional-dependencies] diff --git a/src/py21cmsense/_utils.py b/src/py21cmsense/_utils.py index f7b0944..4f5df71 100644 --- a/src/py21cmsense/_utils.py +++ b/src/py21cmsense/_utils.py @@ -41,7 +41,7 @@ def find_nearest(array, value): @un.quantity_input def phase_past_zenith( - time_past_zenith: un.day, bls_enu: np.ndarray, latitude, world, use_apparent: bool = True + time_past_zenith: un.day, bls_enu: np.ndarray, latitude, world: str = "earth", use_apparent: bool = True ): """Compute UVWs phased to a point rotated from zenith by a certain amount of time. @@ -59,7 +59,7 @@ def phase_past_zenith( latitude The latitude of the center of the array, in radians. world - Wether the telescope is on the Earth or Moon. + Whether the telescope is on the Earth or Moon. Returns ------- diff --git a/src/py21cmsense/observation.py b/src/py21cmsense/observation.py index ffea8ec..cb126c2 100644 --- a/src/py21cmsense/observation.py +++ b/src/py21cmsense/observation.py @@ -93,17 +93,16 @@ class Observation: observatory: obs.Observatory = attr.ib(validator=vld.instance_of(obs.Observatory)) time_per_day: tp.Time = attr.ib( - 6 * un.hour, - validator=(tp.vld_physical_type("time"), ut.between(0 * un.hour, 655.2 * un.hour)), + validator=(tp.vld_physical_type("time")), ) track: tp.Time | None = attr.ib( None, validator=attr.validators.optional( - [tp.vld_physical_type("time"), ut.between(0, 655.2 * un.hour)] + [tp.vld_physical_type("time")] ), ) lst_bin_size: tp.Time = attr.ib( - validator=(tp.vld_physical_type("time"), ut.between(0, 655.2 * un.hour)), + validator=(tp.vld_physical_type("time")), ) integration_time: tp.Time = attr.ib( 60 * un.second, validator=(tp.vld_physical_type("time"), ut.positive) @@ -164,8 +163,30 @@ def __sethstate__(self, d: dict[str, Any]) -> None: d["cosmo"] = Planck15.from_format(d["cosmo"]) self.__dict__.update(d) + @time_per_day.validator + def _time_per_day_vld(self, att, val): + day_length = 24*un.hour if self.observatory.world == 'earth' else 655.2*un.hour + + if not 0*un.hour <= val <= day_length: + raise ValueError(f"time_per_day should be between 0 and {day_length}") + + @track.validator + def _track_vld(self, att, val): + if val != None: + day_length = 24*un.hour if self.observatory.world == 'earth' else 655.2*un.hour + + if not 0*un.hour <= val <= day_length: + raise ValueError(f"track should be between 0 and {day_length}") + + @lst_bin_size.validator def _lst_bin_size_vld(self, att, val): + day_length = 24*un.hour if self.observatory.world == 'earth' else 655.2*un.hour + + if not 0*un.hour <= val <= day_length: + raise ValueError(f"lst_bin_size should be between 0 and {day_length}") + + if val > self.time_per_day: raise ValueError("lst_bin_size must be <= time_per_day") @@ -174,6 +195,13 @@ def _integration_time_vld(self, att, val): if val > self.lst_bin_size: raise ValueError("integration_time must be <= lst_bin_size") + @time_per_day.default + def _time_per_day_default(self): + if self.observatory.world == 'earth': + return 6 * un.hour + else: + return 163.8 * un.hour + @lst_bin_size.default def _lst_bin_size_default(self): # time it takes the sky to drift through beam FWHM diff --git a/src/py21cmsense/sensitivity.py b/src/py21cmsense/sensitivity.py index ff947d4..b9f0492 100644 --- a/src/py21cmsense/sensitivity.py +++ b/src/py21cmsense/sensitivity.py @@ -151,7 +151,7 @@ class PowerSpectrum(Sensitivity): horizon_buffer: tp.Wavenumber = attr.ib(default=0.1 * littleh / un.Mpc) foreground_model: str = attr.ib( - default="moderate", validator=vld.in_(["moderate", "optimistic", "ultra_optimistic"]) + default="moderate", validator=vld.in_(["moderate", "optimistic", "foreground_free"]) ) theory_model: TheoryModel = attr.ib() @@ -471,7 +471,7 @@ def horizon_limit(self, umag: float) -> tp.Wavenumber: return horizon + self.horizon_buffer elif self.foreground_model in ["optimistic"]: return horizon * np.sin(self.observation.observatory.beam.first_null / 2) - elif self.foreground_model in ["ultra_optimistic"]: + elif self.foreground_model in ["foreground_free"]: return 0 def _average_sense_to_1d( diff --git a/tests/test_observation.py b/tests/test_observation.py index 3e163ca..5295565 100644 --- a/tests/test_observation.py +++ b/tests/test_observation.py @@ -15,12 +15,16 @@ def bm(): return GaussianBeam(150.0 * units.MHz, dish_size=14 * units.m) +@pytest.fixture(scope="module", params=["earth","moon"]) +def wd(request): + return request.param @pytest.fixture(scope="module") -def observatory(bm): +def observatory(bm,wd): return Observatory( antpos=np.array([[0, 0, 0], [14, 0, 0], [28, 0, 0], [70, 0, 0]]) * units.m, beam=bm, + world=wd ) @@ -89,9 +93,10 @@ def test_from_yaml(observatory): Observation.from_yaml(3) -def test_huge_lst_bin_size(observatory: Observatory): +def test_huge_lst_bin_size(observatory: Observatory, wd): + lst = 23 * units.hour if wd=="earth" else 627.9 * units.hour with pytest.raises(ValueError, match="lst_bin_size must be <= time_per_day"): - Observation(observatory=observatory, lst_bin_size=23 * units.hour) + Observation(observatory=observatory, lst_bin_size=lst) def test_huge_integration_time(observatory: Observatory): diff --git a/tests/test_sensitivity.py b/tests/test_sensitivity.py index 38c1ec8..8b9ff01 100644 --- a/tests/test_sensitivity.py +++ b/tests/test_sensitivity.py @@ -15,12 +15,16 @@ def bm(): return GaussianBeam(150.0 * units.MHz, dish_size=14 * units.m) +@pytest.fixture(scope="module", params=["earth","moon"]) +def wd(request): + return request.param @pytest.fixture(scope="module") -def observatory(bm): +def observatory(bm, wd): return Observatory( antpos=np.array([[0, 0, 0], [14, 0, 0], [28, 0, 0], [70, 0, 0]]) * units.m, beam=bm, + world=wd ) From be6502c37ec28d4c1f19136b732a5d1560beec6a Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Tue, 21 Jan 2025 16:30:37 -0500 Subject: [PATCH 09/12] more tests --- tests/test_observation.py | 14 ++++++++++++++ tests/test_sensitivity.py | 3 +++ tests/test_theory.py | 15 ++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/test_observation.py b/tests/test_observation.py index 5295565..e926742 100644 --- a/tests/test_observation.py +++ b/tests/test_observation.py @@ -92,12 +92,26 @@ def test_from_yaml(observatory): with pytest.raises(ValueError, match="yaml_file must be a string filepath"): Observation.from_yaml(3) +def test_huge_time_per_day_size(observatory: Observatory, wd): + tpd = 25 * units.hour if wd=="earth" else 682.5 * units.hour + with pytest.raises(ValueError, match="time_per_day should be between 0 and"): + Observation(observatory=observatory, time_per_day=tpd) + +def test_huge_track_size(observatory: Observatory, wd): + tck = 25 * units.hour if wd=="earth" else 682.5 * units.hour + with pytest.raises(ValueError, match="track should be between 0 and"): + Observation(observatory=observatory, track=tck) + def test_huge_lst_bin_size(observatory: Observatory, wd): lst = 23 * units.hour if wd=="earth" else 627.9 * units.hour with pytest.raises(ValueError, match="lst_bin_size must be <= time_per_day"): Observation(observatory=observatory, lst_bin_size=lst) + lst2 = 25 * units.hour if wd=="earth" else 682.5 * units.hour + with pytest.raises(ValueError, match="lst_bin_size should be between 0 and"): + Observation(observatory=observatory, lst_bin_size=lst2) + def test_huge_integration_time(observatory: Observatory): with pytest.raises(ValueError, match="integration_time must be <= lst_bin_size"): diff --git a/tests/test_sensitivity.py b/tests/test_sensitivity.py index 8b9ff01..c945c75 100644 --- a/tests/test_sensitivity.py +++ b/tests/test_sensitivity.py @@ -82,6 +82,9 @@ def test_sensitivity_optimistic(observation): ps = PowerSpectrum(observation=observation, foreground_model="optimistic") assert ps.horizon_limit(10.0) > ps.horizon_limit(5.0) +def test_sensitivity_foreground_free(observation): + ps = PowerSpectrum(observation=observation, foreground_model="foreground_free") + assert ps.horizon_limit(10.0) == 0 def test_infs_in_trms(observation): # default dumb layout should have lots of infs.. diff --git a/tests/test_theory.py b/tests/test_theory.py index 5c4e11a..07fd71d 100644 --- a/tests/test_theory.py +++ b/tests/test_theory.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from py21cmsense.theory import EOS2021, EOS2016Bright, EOS2016Faint, Legacy21cmFAST +from py21cmsense.theory import EOS2021, EOS2016Bright, EOS2016Faint, Legacy21cmFAST, FarViewModel def test_eos_extrapolation(): @@ -38,3 +38,16 @@ def test_eos_2016(): bright = EOS2016Bright() assert faint.delta_squared(9.1, 1.0) != bright.delta_squared(9.1, 1.0) + +def test_FarView(): + theory = FarViewModel() + assert theory.delta_squared(29.6, 1.0) == theory.delta_squared(30.4, 1.0) + + with pytest.warns(UserWarning, match="Theory power corresponds to z=30, not z"): + theory.delta_squared(1.0, 1.0) + + with pytest.warns(UserWarning, match="Extrapolating above the simulated theoretical k"): + theory.delta_squared(30, np.array([0.1, 1e6])) + + with pytest.warns(UserWarning, match="Extrapolating below the simulated theoretical k"): + theory.delta_squared(30, np.array([0.0001, 0.1])) From 442a4d500be6efcf82cd9acee177944b4f9f1c61 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 08:56:42 +0000 Subject: [PATCH 10/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/py21cmsense/_utils.py | 20 ++++++++++---------- src/py21cmsense/observation.py | 20 ++++++++------------ src/py21cmsense/observatory.py | 5 ++--- src/py21cmsense/theory.py | 15 +++++---------- tests/test_observation.py | 18 +++++++++++------- tests/test_sensitivity.py | 8 ++++++-- tests/test_theory.py | 3 ++- tests/test_uvw.py | 14 +++++++++----- 8 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/py21cmsense/_utils.py b/src/py21cmsense/_utils.py index dc99113..4d0e66b 100644 --- a/src/py21cmsense/_utils.py +++ b/src/py21cmsense/_utils.py @@ -4,10 +4,11 @@ from astropy import units as un from astropy.coordinates import EarthLocation, SkyCoord from astropy.time import Time -from pyuvdata import utils as uvutils from lunarsky import MoonLocation from lunarsky import SkyCoord as LunarSkyCoord from lunarsky import Time as LTime +from pyuvdata import utils as uvutils + def between(xmin, xmax): """Return an attrs validation function that checks a number is within bounds.""" @@ -87,7 +88,7 @@ def phase_past_zenith( # JD is arbitrary jd = 2454600 - + if world == "earth": tm = Time(jd, format="jd") @@ -99,14 +100,14 @@ def phase_past_zenith( location=telescope_location, ) else: - tm = LTime(jd, format='jd') + tm = LTime(jd, format="jd") phase_center_coord = LunarSkyCoord( - alt=90 * un.deg, - az=0 * un.deg, - obstime=tm, - frame="lunartopo", - location=telescope_location, - ) + alt=90 * un.deg, + az=0 * un.deg, + obstime=tm, + frame="lunartopo", + location=telescope_location, + ) phase_center_coord = phase_center_coord.transform_to("icrs") @@ -120,7 +121,6 @@ def phase_past_zenith( ) phase_center_coord.obstime.location = telescope_location - obstimes = phase_center_coord.obstime + time_past_zenith lsts = obstimes.sidereal_time("apparent", longitude=0.0).rad diff --git a/src/py21cmsense/observation.py b/src/py21cmsense/observation.py index d0a38f9..1ee7712 100644 --- a/src/py21cmsense/observation.py +++ b/src/py21cmsense/observation.py @@ -102,9 +102,7 @@ class Observation: ) track: tp.Time | None = attr.ib( None, - validator=attr.validators.optional( - [tp.vld_physical_type("time")] - ), + validator=attr.validators.optional([tp.vld_physical_type("time")]), ) lst_bin_size: tp.Time = attr.ib( validator=(tp.vld_physical_type("time")), @@ -171,28 +169,26 @@ def __sethstate__(self, d: dict[str, Any]) -> None: @time_per_day.validator def _time_per_day_vld(self, att, val): - day_length = 24*un.hour if self.observatory.world == 'earth' else 655.2*un.hour + day_length = 24 * un.hour if self.observatory.world == "earth" else 655.2 * un.hour - if not 0*un.hour <= val <= day_length: + if not 0 * un.hour <= val <= day_length: raise ValueError(f"time_per_day should be between 0 and {day_length}") @track.validator def _track_vld(self, att, val): if val != None: - day_length = 24*un.hour if self.observatory.world == 'earth' else 655.2*un.hour + day_length = 24 * un.hour if self.observatory.world == "earth" else 655.2 * un.hour - if not 0*un.hour <= val <= day_length: + if not 0 * un.hour <= val <= day_length: raise ValueError(f"track should be between 0 and {day_length}") - @lst_bin_size.validator def _lst_bin_size_vld(self, att, val): - day_length = 24*un.hour if self.observatory.world == 'earth' else 655.2*un.hour + day_length = 24 * un.hour if self.observatory.world == "earth" else 655.2 * un.hour - if not 0*un.hour <= val <= day_length: + if not 0 * un.hour <= val <= day_length: raise ValueError(f"lst_bin_size should be between 0 and {day_length}") - if val > self.time_per_day: raise ValueError("lst_bin_size must be <= time_per_day") @@ -203,7 +199,7 @@ def _integration_time_vld(self, att, val): @time_per_day.default def _time_per_day_default(self): - if self.observatory.world == 'earth': + if self.observatory.world == "earth": return 6 * un.hour else: return 163.8 * un.hour diff --git a/src/py21cmsense/observatory.py b/src/py21cmsense/observatory.py index 34eecc1..501c0d6 100644 --- a/src/py21cmsense/observatory.py +++ b/src/py21cmsense/observatory.py @@ -86,8 +86,7 @@ class Observatory: default=0.0 * un.m, validator=(tp.vld_physical_type("length"), ut.nonnegative) ) beam_crossing_time_incl_latitude: bool = attr.ib(default=True, converter=bool) - world: str = attr.ib(default = "earth", validator=vld.in_(["earth", "moon"]) - ) + world: str = attr.ib(default="earth", validator=vld.in_(["earth", "moon"])) @_antpos.validator def _antpos_validator(self, att, val): @@ -364,7 +363,7 @@ def observation_duration(self) -> un.Quantity[un.day]: if self.world == "earth": return un.day * self.beam.fwhm / (2 * np.pi * un.rad * latfac) else: - return 27.3 * un.day * self.beam.fwhm/(2 * np.pi * un.rad * latfac) + return 27.3 * un.day * self.beam.fwhm / (2 * np.pi * un.rad * latfac) def get_redundant_baselines( self, diff --git a/src/py21cmsense/theory.py b/src/py21cmsense/theory.py index 00fc06b..7fdad25 100644 --- a/src/py21cmsense/theory.py +++ b/src/py21cmsense/theory.py @@ -197,27 +197,22 @@ def delta_squared(self, z: float, k: np.ndarray) -> un.Quantity[un.mK**2]: return self.spline(k) << un.mK**2 + class FarViewModel(TheoryModel): """21cmFAST-based theory model explicitly for z=30, [Insert paper link here later]""" use_littleh: bool = False def __init__(self) -> None: - k_pth = ( - Path(__file__).parent - / "data/farview/kmag_ultimate.npy" - ) + k_pth = Path(__file__).parent / "data/farview/kmag_ultimate.npy" - delta_pth = ( - Path(__file__).parent - / "data/farview/P_Tb_ultimate.npy" - ) - #Should at some point reorganize the data so these steps aren't necessary + delta_pth = Path(__file__).parent / "data/farview/P_Tb_ultimate.npy" + # Should at some point reorganize the data so these steps aren't necessary k_fixed = np.load(k_pth) power = np.load(delta_pth) k_fixed = k_fixed[~np.isnan(power)] power = power[~np.isnan(power)] - delta = (k_fixed**3 * power) / (2*np.pi**2) + delta = (k_fixed**3 * power) / (2 * np.pi**2) self.k = k_fixed self.delta_squared_raw = delta diff --git a/tests/test_observation.py b/tests/test_observation.py index 035e053..1c2d212 100644 --- a/tests/test_observation.py +++ b/tests/test_observation.py @@ -15,18 +15,20 @@ def bm(): return GaussianBeam(150.0 * units.MHz, dish_size=14 * units.m) -@pytest.fixture(scope="module", params=["earth","moon"]) + +@pytest.fixture(scope="module", params=["earth", "moon"]) def wd(request): return request.param + @pytest.fixture(scope="module") -def observatory(bm,wd): +def observatory(bm, wd): return Observatory( antpos=np.array([[0, 0, 0], [14, 0, 0], [28, 0, 0], [70, 0, 0], [0, 14, 0], [23, -45, 0]]) * units.m, latitude=-32 * units.deg, beam=bm, - world=wd + world=wd, ) @@ -94,23 +96,25 @@ def test_from_yaml(observatory): with pytest.raises(ValueError, match="yaml_file must be a string filepath"): Observation.from_yaml(3) + def test_huge_time_per_day_size(observatory: Observatory, wd): - tpd = 25 * units.hour if wd=="earth" else 682.5 * units.hour + tpd = 25 * units.hour if wd == "earth" else 682.5 * units.hour with pytest.raises(ValueError, match="time_per_day should be between 0 and"): Observation(observatory=observatory, time_per_day=tpd) + def test_huge_track_size(observatory: Observatory, wd): - tck = 25 * units.hour if wd=="earth" else 682.5 * units.hour + tck = 25 * units.hour if wd == "earth" else 682.5 * units.hour with pytest.raises(ValueError, match="track should be between 0 and"): Observation(observatory=observatory, track=tck) def test_huge_lst_bin_size(observatory: Observatory, wd): - lst = 23 * units.hour if wd=="earth" else 627.9 * units.hour + lst = 23 * units.hour if wd == "earth" else 627.9 * units.hour with pytest.raises(ValueError, match="lst_bin_size must be <= time_per_day"): Observation(observatory=observatory, lst_bin_size=lst) - lst2 = 25 * units.hour if wd=="earth" else 682.5 * units.hour + lst2 = 25 * units.hour if wd == "earth" else 682.5 * units.hour with pytest.raises(ValueError, match="lst_bin_size should be between 0 and"): Observation(observatory=observatory, lst_bin_size=lst2) diff --git a/tests/test_sensitivity.py b/tests/test_sensitivity.py index c945c75..6dd66f0 100644 --- a/tests/test_sensitivity.py +++ b/tests/test_sensitivity.py @@ -15,16 +15,18 @@ def bm(): return GaussianBeam(150.0 * units.MHz, dish_size=14 * units.m) -@pytest.fixture(scope="module", params=["earth","moon"]) + +@pytest.fixture(scope="module", params=["earth", "moon"]) def wd(request): return request.param + @pytest.fixture(scope="module") def observatory(bm, wd): return Observatory( antpos=np.array([[0, 0, 0], [14, 0, 0], [28, 0, 0], [70, 0, 0]]) * units.m, beam=bm, - world=wd + world=wd, ) @@ -82,10 +84,12 @@ def test_sensitivity_optimistic(observation): ps = PowerSpectrum(observation=observation, foreground_model="optimistic") assert ps.horizon_limit(10.0) > ps.horizon_limit(5.0) + def test_sensitivity_foreground_free(observation): ps = PowerSpectrum(observation=observation, foreground_model="foreground_free") assert ps.horizon_limit(10.0) == 0 + def test_infs_in_trms(observation): # default dumb layout should have lots of infs.. assert np.any(np.isinf(observation.Trms)) diff --git a/tests/test_theory.py b/tests/test_theory.py index 07fd71d..5620cf7 100644 --- a/tests/test_theory.py +++ b/tests/test_theory.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from py21cmsense.theory import EOS2021, EOS2016Bright, EOS2016Faint, Legacy21cmFAST, FarViewModel +from py21cmsense.theory import EOS2021, EOS2016Bright, EOS2016Faint, FarViewModel, Legacy21cmFAST def test_eos_extrapolation(): @@ -39,6 +39,7 @@ def test_eos_2016(): assert faint.delta_squared(9.1, 1.0) != bright.delta_squared(9.1, 1.0) + def test_FarView(): theory = FarViewModel() assert theory.delta_squared(29.6, 1.0) == theory.delta_squared(30.4, 1.0) diff --git a/tests/test_uvw.py b/tests/test_uvw.py index b166627..9a09b87 100644 --- a/tests/test_uvw.py +++ b/tests/test_uvw.py @@ -24,7 +24,7 @@ def test_phase_at_zenith(lat, use_apparent): time_past_zenith=0.0 * un.day, bls_enu=bls_enu, latitude=lat * un.rad, - world = 'earth', + world="earth", use_apparent=use_apparent, ) @@ -46,7 +46,7 @@ def test_phase_past_zenith(use_apparent): time_past_zenith=0.2 * un.day, bls_enu=bls_enu, latitude=0 * un.rad, - world = 'earth', + world="earth", use_apparent=use_apparent, ) ) @@ -69,7 +69,9 @@ def test_phase_past_zenith_shape(): times = np.array([0, 0.1, 0, 0.1]) * un.day # Almost rotated to the horizon. - uvws = phase_past_zenith(time_past_zenith=times, bls_enu=bls_enu, latitude=0 * un.rad, world='earth') + uvws = phase_past_zenith( + time_past_zenith=times, bls_enu=bls_enu, latitude=0 * un.rad, world="earth" + ) assert uvws.shape == (5, 4, 3) assert np.allclose(uvws[0], uvws[2]) # Same baselines @@ -89,12 +91,14 @@ def test_use_apparent(lat): times = np.linspace(-1, 1, 3) * un.hour # Almost rotated to the horizon. - uvws = phase_past_zenith(time_past_zenith=times, bls_enu=bls_enu, latitude=lat * un.rad, world='earth') + uvws = phase_past_zenith( + time_past_zenith=times, bls_enu=bls_enu, latitude=lat * un.rad, world="earth" + ) uvws0 = phase_past_zenith( time_past_zenith=times, bls_enu=bls_enu, latitude=lat * un.rad, - world = 'earth', + world="earth", use_apparent=True, ) From 97d99bcdb257ba70ac570285c21f45f1354dc46c Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Thu, 23 Jan 2025 12:11:57 -0500 Subject: [PATCH 11/12] test: addressing astropy futurewarning --- src/py21cmsense/_utils.py | 7 +++---- src/py21cmsense/observation.py | 2 +- src/py21cmsense/theory.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/py21cmsense/_utils.py b/src/py21cmsense/_utils.py index 4d0e66b..c0fe654 100644 --- a/src/py21cmsense/_utils.py +++ b/src/py21cmsense/_utils.py @@ -90,7 +90,7 @@ def phase_past_zenith( jd = 2454600 if world == "earth": - tm = Time(jd, format="jd") + tm = Time(jd, format="jd", location=telescope_location) phase_center_coord = SkyCoord( alt=90 * un.deg, @@ -100,7 +100,8 @@ def phase_past_zenith( location=telescope_location, ) else: - tm = LTime(jd, format="jd") + tm = LTime(jd, format="jd", location=telescope_location) + phase_center_coord = LunarSkyCoord( alt=90 * un.deg, az=0 * un.deg, @@ -120,8 +121,6 @@ def phase_past_zenith( location=telescope_location, ) - phase_center_coord.obstime.location = telescope_location - obstimes = phase_center_coord.obstime + time_past_zenith lsts = obstimes.sidereal_time("apparent", longitude=0.0).rad diff --git a/src/py21cmsense/observation.py b/src/py21cmsense/observation.py index 1ee7712..6306901 100644 --- a/src/py21cmsense/observation.py +++ b/src/py21cmsense/observation.py @@ -176,7 +176,7 @@ def _time_per_day_vld(self, att, val): @track.validator def _track_vld(self, att, val): - if val != None: + if val is not None: day_length = 24 * un.hour if self.observatory.world == "earth" else 655.2 * un.hour if not 0 * un.hour <= val <= day_length: diff --git a/src/py21cmsense/theory.py b/src/py21cmsense/theory.py index 7fdad25..189100f 100644 --- a/src/py21cmsense/theory.py +++ b/src/py21cmsense/theory.py @@ -199,7 +199,7 @@ def delta_squared(self, z: float, k: np.ndarray) -> un.Quantity[un.mK**2]: class FarViewModel(TheoryModel): - """21cmFAST-based theory model explicitly for z=30, [Insert paper link here later]""" + """21cmFAST-based theory model explicitly for z=30.""" use_littleh: bool = False From a8faf929bc79ad5bbf5b184c6b92e1a38d51bf7e Mon Sep 17 00:00:00 2001 From: Willow Smith Date: Thu, 23 Jan 2025 16:31:38 -0500 Subject: [PATCH 12/12] fix: updating n_days default for moon making clear in doc strings that a lunar day is 655.2 hours --- src/py21cmsense/observation.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/py21cmsense/observation.py b/src/py21cmsense/observation.py index 6306901..162497d 100644 --- a/src/py21cmsense/observation.py +++ b/src/py21cmsense/observation.py @@ -38,8 +38,9 @@ class Observation: An object defining attributes of the observatory itself (its location etc.) hours_per_day : float or Quantity, optional The number of good observing hours per day. This corresponds to the size of a - low-foreground region in right ascension for a drift scanning instrument. The - total observing time is `n_days*hours_per_day`. Default is 6. + low-foreground region in right ascension for a drift scanning instrument on the Earth. + The total observing time is `n_days*hours_per_day` where a day is 24 hours on the + Earth and 655.2 hours on the moon. Default is 6 (163.8 on moon). If simulating a tracked scan, `hours_per_day` should be a multiple of the length of the track (i.e. for two three-hour tracks per day, `hours_per_day` should be 6). track @@ -59,9 +60,10 @@ class Observation: The bandwidth used for the observation, assumed to be in MHz. Note this is not the total instrument bandwidth, but the redshift range that can be considered co-eval. n_days : int, optional - The number of days observed (for the same set of LSTs). The default is 180, which is the - maximum a particular R.A. can be observed in one year if one only observes at night. - The total observing time is `n_days*hours_per_day`. + The number of days observed (for the same set of LSTs). The default is 180 (6 on moon), + which is the maximum a particular R.A. can be observed in one year if one only observes + at night. The total observing time is `n_days*hours_per_day` where a day is 24 hours on + the Earth and 655.2 hours on the moon. baseline_filters A function that takes a single value: a length-3 array of baseline co-ordinates, and returns a bool indicating whether to include the baseline. Built-in filters @@ -114,7 +116,7 @@ class Observation: bandwidth: tp.Frequency = attr.ib( 8 * un.MHz, validator=(tp.vld_physical_type("frequency"), ut.positive) ) - n_days: int = attr.ib(default=180, converter=int, validator=ut.positive) + n_days: int = attr.ib(converter=int, validator=ut.positive) baseline_filters: tuple[Callable[[tp.Length], bool]] = attr.ib( default=(), converter=tp._tuplify ) @@ -212,6 +214,13 @@ def _lst_bin_size_default(self): else: return self.observatory.observation_duration + @n_days.default + def _n_days_default(self): + if self.observatory.world == "earth": + return 180 + else: + return 6 + @phase_center_dec.default def _phase_center_dec_default(self): return self.observatory.latitude