[docs]@hickleable(evaluate_cached_properties=True)@attr.sclassPrimaryBeam(metaclass=ABCMeta):"""A Base class defining a Primary Beam and the methods it requires to define. Required methods on subclasses are :meth:`area`, :meth:`width`, :meth:`first_null`, :meth:`sq_area` and :meth:`uv_resolution`.. Note that 21cmSense currently only uses the beam width as a means to calculate the beam-crossing time, so precise shape is not very important. For that reason, it is not very important to implement beam sub-classes. """frequency:tp.Frequency=attr.ib(validator=(tp.vld_physical_type("frequency"),ut.positive),)
[docs]defnew(self,**kwargs)->PrimaryBeam:"""Return a clone of this instance, but change kwargs."""returnattr.evolve(self,**kwargs)
[docs]defat(self,frequency:tp.Frequency)->PrimaryBeam:"""Get a copy of the object at a new frequency."""returnattr.evolve(self,frequency=frequency)
@abstractpropertydefarea(self)->un.Quantity[un.steradian]:"""Beam area [units: sr]."""pass@abstractpropertydefwidth(self)->un.Quantity[un.radians]:"""Beam width [units: rad]."""pass@abstractpropertydeffirst_null(self)->un.Quantity[un.radians]:"""An approximation of the first null of the beam."""pass@abstractpropertydefsq_area(self)->un.Quantity[un.steradian]:"""The area of the beam^2."""pass@propertydefb_eff(self)->un.Quantity[un.steradian]:r"""Get the effective beam area (Parsons 2014). Defined as :math:`(\int B(\Omega) d \Omega)^2 / \int B^2 d\Omega`. """returnself.area**2/self.sq_area@abstractpropertydefuv_resolution(self)->un.Quantity[1/un.radians]:"""The UV footprint of the beam."""pass
[docs]@classmethoddeffrom_uvbeam(cls)->PrimaryBeam:"""Generate the beam object from a :class:`pyuvdata.UVBeam` object."""raiseNotImplementedError()
[docs]@attr.s(frozen=True)classGaussianBeam(PrimaryBeam):""" A simple Gaussian Primary beam. Parameters ---------- frequency The fiducial frequency at which the beam operates, assumed to be in MHz unless otherwise defined. dish_size The size of the (assumed circular) dish, assumed to be in meters unless otherwise defined. This generates the beam size. """dish_size:tp.Length=attr.ib(validator=(tp.vld_physical_type("length"),ut.positive))@propertydefwavelength(self)->un.Quantity[un.m]:"""The wavelength of the observation."""return(cnst.c/self.frequency).to("m")@propertydefdish_size_in_lambda(self)->float:"""The dish size in units of wavelengths."""return(self.dish_size/(cnst.c/self.frequency)).to("").value@propertydefuv_resolution(self)->un.Quantity[1/un.radian]:"""The appropriate resolution of a UV cell given the beam size."""returnself.dish_size_in_lambda@propertydefarea(self)->un.Quantity[un.steradian]:"""The integral of the beam over angle, in sr."""return1.13*self.fwhm**2@propertydefwidth(self)->un.Quantity[un.radian]:"""The width of the beam (i.e. sigma), in radians."""returnun.rad*0.45/self.dish_size_in_lambda@propertydeffwhm(self)->un.Quantity[un.radians]:"""The full-width half maximum of the beam."""return2.35*self.width@propertydefsq_area(self)->un.Quantity[un.steradian]:"""The integral of the squared beam, in sr. If frequency is not given, uses the instance's `frequency` """returnself.area/2@propertydeffirst_null(self)->un.Quantity[un.radians]:"""The angle of the first null of the beam. .. note:: The Gaussian beam has no null, and in this case we use the first null for an airy disk. """returnun.rad*1.22/self.dish_size_in_lambda
[docs]@classmethoddeffrom_uvbeam(cls):"""Construct the beam from a :class:`pyuvdata.UVBeam` object."""raiseNotImplementedError("Coming Soon!")
[docs]defclone(self,**kwargs):"""Create a new beam with updated parameters."""returnattr.evolve(self,**kwargs)