def test_analog_pointing(self): analog_pointing = self.ma.analog_pointing( pointing=Pointing.target_tracking( target=FixedTarget.from_name("Vir A"), time=Time(["2022-01-01T11:00:00", "2022-01-01T14:00:00"]), duration=TimeDelta(7200, format="sec")), configuration=NenuFAR_Configuration(beamsquint_correction=False, beamsquint_frequency=20 * u.MHz)) assert analog_pointing._custom_ho_coordinates.shape == (2, ) assert analog_pointing._custom_ho_coordinates[ 0].alt.deg == pytest.approx(21.55, 1e-2) assert analog_pointing._custom_ho_coordinates[ 1].alt.deg == pytest.approx(11.98, 1e-2) analog_pointing = self.ma.analog_pointing( pointing=Pointing.target_tracking( target=FixedTarget.from_name("Vir A"), time=Time(["2022-01-01T11:00:00", "2022-01-01T14:00:00"]), duration=TimeDelta(7200, format="sec")), configuration=NenuFAR_Configuration(beamsquint_correction=True, beamsquint_frequency=20 * u.MHz)) assert analog_pointing._custom_ho_coordinates[ 0].alt.deg == pytest.approx(12.26, 1e-2) assert analog_pointing._custom_ho_coordinates[ 1].alt.deg == pytest.approx(11.98, 1e-2)
def test_fixedtarget_set(): src = FixedTarget.from_name(name="Cyg A", time=Time("2022-01-01T12:00:00")) set_times = src.set_time(t_min=Time("2022-01-01T12:00:00"), elevation=0 * u.deg, duration=TimeDelta(86400 * 2, format="sec")) assert set_times.size == 2 assert set_times[0].jd == pytest.approx(2459581.489, 1e-3) assert set_times[1].jd == pytest.approx(2459582.486, 1e-3) set_time = src.next_set_time(time=Time("2022-01-01T12:00:00"), elevation=10 * u.deg) assert set_time.isscalar assert set_time.jd == pytest.approx(2459581.398, 1e-3) set_time = src.previous_set_time(time=Time("2022-01-01T12:00:00"), elevation=10 * u.deg) assert set_time.isscalar assert set_time.jd == pytest.approx(2459580.400, 1e-3) src_circum = FixedTarget.from_name(name="Cas A", time=Time("2022-01-01T12:00:00")) set_times = src_circum.set_time(t_min=Time("2022-01-01T12:00:00"), elevation=0 * u.deg, duration=TimeDelta(86400 * 2, format="sec")) assert set_times.size == 0 set_times = src_circum.set_time(t_min=Time("2022-01-01T12:00:00"), elevation=40 * u.deg, duration=TimeDelta(86400 * 2, format="sec")) assert set_times.size == 2 assert set_times[0].jd == pytest.approx(2459581.431, 1e-3) assert set_times[1].jd == pytest.approx(2459582.429, 1e-3)
def test_fixedtarget_rise(): src = FixedTarget.from_name(name="Cyg A", time=Time("2022-01-01T12:00:00")) rise_times = src.rise_time(t_min=Time("2022-01-01T12:00:00"), elevation=0 * u.deg, duration=TimeDelta(86400 * 2, format="sec")) assert rise_times.size == 2 assert rise_times[0].jd == pytest.approx(2459581.601, 1e-3) assert rise_times[1].jd == pytest.approx(2459582.599, 1e-3) rise_time = src.next_rise_time(time=Time("2022-01-01T12:00:00"), elevation=10 * u.deg) assert rise_time.isscalar assert rise_time.jd == pytest.approx(2459581.692, 1e-3) rise_time = src.previous_rise_time(time=Time("2022-01-01T12:00:00"), elevation=10 * u.deg) assert rise_time.isscalar assert rise_time.jd == pytest.approx(2459580.695, 1e-3) src_circum = FixedTarget.from_name(name="Cas A", time=Time("2022-01-01T12:00:00")) rise_times = src_circum.rise_time(t_min=Time("2022-01-01T12:00:00"), elevation=0 * u.deg, duration=TimeDelta(86400 * 2, format="sec")) assert rise_times.size == 0 rise_times = src_circum.rise_time(t_min=Time("2022-01-01T12:00:00"), elevation=40 * u.deg, duration=TimeDelta(86400 * 2, format="sec")) assert rise_times.size == 2 assert rise_times[0].jd == pytest.approx(2459581.941, 1e-3) assert rise_times[1].jd == pytest.approx(2459582.939, 1e-3)
def test_pointing_target_tracking(): pointing = Pointing.target_tracking(target=FixedTarget.from_name("Cas A"), time=Time("2022-01-01T12:00:00")) assert pointing.coordinates.size == 1 pointing = Pointing.target_tracking( target=FixedTarget.from_name("Cas A"), time=Time(["2022-01-01T12:00:00", "2022-01-01T14:00:00"])) assert pointing.coordinates.size == 2 assert pointing.horizontal_coordinates[1].az.deg == pytest.approx( 48.45, 1e-2) pointing = Pointing.target_tracking( target=SolarSystemTarget.from_name("Sun"), time=Time(["2022-01-01T12:00:00", "2022-01-01T14:00:00"])) assert np.unique(pointing.coordinates.ra.deg).size == 2
def test_pointing_target_transit(): pointing = Pointing.target_transit(target=FixedTarget.from_name("Cyg A"), t_min=Time("2022-01-01T12:00:00"), duration=TimeDelta(3600, format="sec"), dt=TimeDelta(3600, format="sec"), azimuth=180 * u.deg) assert pointing.custom_ho_coordinates[0, 0].az.deg == pytest.approx( 179.954, 1e-3) assert pointing.custom_ho_coordinates[0, 0].alt.deg == pytest.approx( 83.416, 1e-3)
def test_fixedtarget_meridian_transit(): src = FixedTarget.from_name(name="Cyg A", time=Time("2022-01-01T12:00:00")) transit_time = src.meridian_transit(t_min=Time("2022-01-01T12:00:00"), duration=TimeDelta(86400, format='sec'), precision=TimeDelta(5, format='sec'), fast_compute=True) assert transit_time[0].jd == pytest.approx(2459581.0464, 1e-4) transit_times = src.meridian_transit(t_min=Time("2022-01-01T12:00:00"), duration=TimeDelta(86400 * 2, format='sec'), precision=TimeDelta(5, format='sec'), fast_compute=False) assert transit_times.size == 2 assert transit_times[1].jd == pytest.approx(2459582.0437, 1e-4) transit_time = src.next_meridian_transit(time=Time("2022-01-01T12:00:00")) assert transit_time.isscalar assert transit_time.jd == pytest.approx(2459581.0464, 1e-4) transit_time = src.previous_meridian_transit( time=Time("2022-01-01T12:00:00")) assert transit_time.isscalar assert transit_time.jd == pytest.approx(2459580.0491, 1e-4) az_transit = src.azimuth_transit(azimuth=200 * u.deg, t_min=Time("2022-01-01T12:00:00")) assert az_transit.size == 1 assert az_transit.jd == pytest.approx(2459581.0551, 1e-4) az_transit = src.azimuth_transit(azimuth=330 * u.deg, t_min=Time("2022-01-01T12:00:00")) assert az_transit.size == 0 src_circum = FixedTarget.from_name(name="Cas A", time=Time("2022-01-01T12:00:00")) az_transit = src_circum.azimuth_transit(azimuth=350 * u.deg, t_min=Time("2022-01-01T12:00:00")) assert az_transit.size == 2 assert az_transit[0].jd == pytest.approx(2459581.1987, 1e-4) assert az_transit[1].jd == pytest.approx(2459581.6345, 1e-4)
def test_fixedtarget_properties(): src = FixedTarget(coordinates=SkyCoord(300, 40, unit="deg"), time=Time("2022-01-01T12:00:00")) ha = src.hour_angle(fast_compute=True) assert ha[0].deg == pytest.approx(343.315, 1e-3) ha = src.hour_angle(fast_compute=False) assert ha[0].deg == pytest.approx(343.311, 1e-3) lst = src.local_sidereal_time(fast_compute=True) assert lst[0].deg == pytest.approx(283.315, 1e-3) lst = src.local_sidereal_time(fast_compute=False) assert lst[0].deg == pytest.approx(283.311, 1e-3) assert src.culmination_azimuth.to(u.deg).value == 180.0 assert not src.is_circumpolar src = FixedTarget(coordinates=SkyCoord(100, 80, unit="deg"), time=Time("2022-01-01T12:00:00")) assert src.is_circumpolar
def save_png(self, figname: str, beam_contours: bool = True, show_sources: bool = True, **kwargs): """ """ image_center = altaz_to_radec( SkyCoord( self.analog_pointing.az, self.analog_pointing.alt, frame=AltAz( obstime=self.tv_image.time[0], location=nenufar_position ) ) ) #kwargs = {} if show_sources: src_names = [] src_position = [] with open(join(dirname(__file__), "nenufar_tv_sources.json")) as src_file: sources = json.load(src_file) for name in sources["FixedSources"]: src = FixedTarget.from_name(name, time=self.tv_image.time[0]) if src.coordinates.separation(image_center) <= 0.8*self.fov_radius: src_names.append(name) src_position.append(src.coordinates) for name in sources["SolarSystemSources"]: src = SolarSystemTarget.from_name(name, time=self.tv_image.time[0]) if src.coordinates.separation(image_center) <= 0.8*self.fov_radius: src_names.append(name) src_position.append(src.coordinates) if len(src_position) != 0: kwargs["text"] = (SkyCoord(src_position), src_names, "white") if beam_contours: # Simulate the array factor ma = MiniArray() af_sky = ma.array_factor( sky=HpxSky( resolution=0.2*u.deg, time=self.tv_image.time[0], frequency=self.tv_image.frequency[0] ), pointing=Pointing( coordinates=image_center, time=self.tv_image.time[0] ) ) # Normalize the array factor af = af_sky[0, 0, 0].compute() af_normalized = af/af.max() kwargs["contour"] = (af_normalized, np.arange(0.5, 1, 0.2), "copper") # Plot self.tv_image[0, 0, 0].plot( center=image_center, radius=self.fov_radius - 2.5*u.deg, figname=figname, colorbar_label=f"Stokes {self.tv_image.polarization[0]}", **kwargs ) return
def make_nearfield(self, radius: u.Quantity = 400*u.m, npix: int = 64, sources: list = [] ): r""" Computes the Near-field image from the cross-correlation statistics data :math:`\mathcal{V}`. The distances between each Mini-Array :math:`{\rm MA}_i` and the ground positions :math:`Delta` is: .. math:: d_{\rm{MA}_i} (x, y) = \sqrt{ ({\rm MA}_{i, x} - \Delta_x)^2 + ({\rm MA}_{i, y} - \Delta_y)^2 + \left( {\rm MA}_{i, z} - \sum_j \frac{{\rm MA}_{j, z}}{n_{\rm MA}} - 1 \right)^2 } Then, the near-field image :math:`n_f` can be retrieved as follows (:math:`k` and :math:`l` being two distinct Mini-Arrays): .. math:: n_f (x, y) = \sum_{k, l} \left| \sum_{\nu} \langle \mathcal{V}_{\nu, k, l}(t) \rangle_t e^{2 \pi i \left( d_{{\rm MA}_k} - d_{{\rm MA}_l} \right) (x, y) \frac{\nu}{c}} \right| .. note:: To simulate astrophysical source of brightness :math:`\mathcal{B}` footprint on the near-field, its visibility per baseline of Mini-Arrays :math:`k` and :math:`l` are computed as: .. math:: \mathcal{V}_{{\rm simu}, k, l} = \mathcal{B} e^{2 \pi i \left( \mathbf{r}_k - \mathbf{r}_l \right) \cdot \mathbf{u} \frac{\nu}{c}} with :math:`\mathbf{r}` the ENU position of the Mini-Arrays, :math:`\mathbf{u} = \left( \cos(\theta) \sin(\phi), \cos(\theta) \cos(\phi), sin(\theta) \right)` the ground projection vector (in East-North-Up coordinates), (:math:`\phi` and :math:`\theta` are the source horizontal coordinates azimuth and elevation respectively). :param radius: Radius of the ground image. Default is ``400m``. :type radius: :class:`~astropy.units.Quantity` :param npix: Number of pixels of the image size. Default is ``64``. :type npix: `int` :param sources: List of source names for which their near-field footprint may be computed. Only sources above 10 deg elevation will be considered. :type sources: `list` :returns: Tuple of near-field image and a dictionnary containing all source footprints. :rtype: `tuple`(:class:`~numpy.ndarray`, `dict`) :Example: from nenupy.io.xst import XST xst = XST("xst_file.fits") nearfield, src_dict = xst.make_nearfield(sources=["Cas A", "Sun"]) .. versionadded:: 1.1.0 """ def compute_nearfield_imprint(visibilities, phase): # Phase and average in frequency nearfield = np.mean( visibilities[..., None, None] * phase, axis=0 ) # Average in baselines nearfield = np.nanmean(np.abs(nearfield), axis=0) with ProgressBar() if log.getEffectiveLevel() <= logging.INFO else DummyCtMgr(): return nearfield.compute() # Mini-Array positions in ENU coordinates nenufar = NenuFAR()[self.mini_arrays] ma_etrs = l93_to_etrs(nenufar.antenna_positions) ma_enu = etrs_to_enu(ma_etrs) # Treat baselines ma1, ma2 = np.tril_indices(self.mini_arrays.size, 0) cross_mask = ma1 != ma2 # Mean time of observation obs_time = self.time[0] + (self.time[-1] - self.time[0])/2. # Delays at the ground radius_m = radius.to(u.m).value ground_granularity = np.linspace(-radius_m, radius_m, npix) posx, posy = np.meshgrid(ground_granularity, ground_granularity) posz = np.ones_like(posx) * (np.average(ma_enu[:, 2]) + 1) ground_grid = np.stack((posx, posy, posz), axis=2) ground_distances = np.sqrt( np.sum( (ma_enu[:, None, None, :] - ground_grid[None])**2, axis=-1 ) ) grid_delays = ground_distances[ma1] - ground_distances[ma2] # (nvis, npix, npix) n_bsl = ma1[cross_mask].size grid_delays = da.from_array( grid_delays[cross_mask], chunks=(np.floor(n_bsl/os.cpu_count()), npix, npix) ) # Mean in time the visibilities vis = np.mean( self.value, axis=0 )[..., cross_mask] # (nfreqs, nvis) vis = da.from_array( vis, chunks=(1, np.floor(n_bsl/os.cpu_count()))#(self.frequency.size, np.floor(n_bsl/os.cpu_count())) ) # Make the nearfield image log.info( f"Computing nearfield (time: {self.time.size}, frequency: {self.frequency.size}, baselines: {vis.shape[1]}, pixels: {posx.size})... " ) wvl = wavelength(self.frequency).to(u.m).value phase = np.exp(2.j * np.pi * (grid_delays[None, ...]/wvl[:, None, None, None])) log.debug("Computing the phase term...") with ProgressBar() if log.getEffectiveLevel() <= logging.INFO else DummyCtMgr(): phase = phase.compute() log.debug("Computing the nearf-field...") nearfield = compute_nearfield_imprint(vis, phase) # Compute nearfield imprints for other sources simu_sources = {} for src_name in sources: # Check that the source is visible if src_name.lower() in ["sun", "moon", "venus", "mars", "jupiter", "saturn", "uranus", "neptune"]: src = SolarSystemTarget.from_name(name=src_name, time=obs_time) else: src = FixedTarget.from_name(name=src_name, time=obs_time) altaz = src.horizontal_coordinates#[0] if altaz.alt.deg <= 10: log.debug(f"{src_name}'s elevation {altaz[0].alt.deg}<=10deg, not considered for nearfield imprint.") continue # Projection from AltAz to ENU vector az_rad = altaz.az.rad el_rad = altaz.alt.rad cos_az = np.cos(az_rad) sin_az = np.sin(az_rad) cos_el = np.cos(el_rad) sin_el = np.sin(el_rad) to_enu = np.array( [cos_el*sin_az, cos_el*cos_az, sin_el] ) # src_delays = np.matmul( # ma_enu[ma1] - ma_enu[ma2], # to_enu # ) # src_delays = da.from_array( # src_delays[cross_mask, :], # chunks=((np.floor(n_bsl/os.cpu_count()), npix, npix), 1) # ) ma1_enu = da.from_array( ma_enu[ma1[cross_mask]], chunks=np.floor(n_bsl/os.cpu_count()) ) ma2_enu = da.from_array( ma_enu[ma2[cross_mask]], chunks=np.floor(n_bsl/os.cpu_count()) ) src_delays = np.matmul( ma1_enu - ma2_enu, to_enu ) # Simulate visibilities src_vis = np.exp(2.j * np.pi * (src_delays/wvl)) src_vis = np.swapaxes(src_vis, 1, 0) log.debug(f"Computing the nearf-field imprint of {src_name}...") simu_sources[src_name] = compute_nearfield_imprint(src_vis, phase) return nearfield, simu_sources
def _select_target_type(source_name): # Check whether the source name matches a solar system object if source_name.upper() in SolarSystemSource._member_names_: return SolarSystemTarget.from_name(source_name, time=time) else: return FixedTarget.from_name(source_name, time=time)