def _find_crossing_times(finding_function: Callable, t_min: Time, duration: TimeDelta, precision: TimeDelta) -> np.ndarray: """ """ # Set t_min to a higher dimension in preparation for multiple matches t_min = t_min.reshape((1, )) # At each iteration, dt will be reduced by down_factor down_factor = 5 dt = duration / down_factor # If duration is too big, intial dt is quite big as well # therefore, we set the max dt to 6h max_dt = TimeDelta(6 * 3600, format="sec") if dt > max_dt: down_factor = int(np.ceil(duration / max_dt)) dt = duration / down_factor # Loop until the precision is reached while dt * down_factor > precision: # Prepare the time array upon which the coordinates are computed n_steps = np.ceil(duration / dt) times = t_min[:, None] + np.arange(n_steps + 1) * dt # Find the indices depending on the function to apply transit_indices = finding_function(times) # Update t_min at the spots where the transit(s) have been found t_min = times[transit_indices] if t_min.size == 0: # Nothing has been found return Time([], format='jd') elif t_min.isscalar: t_min = t_min.reshape((1, )) # Next loop will occur on the last time step only duration = dt dt /= down_factor return times[transit_indices] + dt / 2.
def _get_time(cls, control, num_energies, packets, pad_after): times = [] durations = [] start = 0 for i, (ns, it) in enumerate(control['num_samples', 'integration_time']): off_sets = np.array(packets.get('NIX00485')[start:start + ns]) * it base_time = Time( scet_to_datetime( f'{control["scet_coarse"][i]}:{control["scet_fine"][i]}')) start_times = base_time + off_sets end_times = base_time + off_sets + it cur_time = start_times + (end_times - start_times) / 2 times.extend(cur_time) durations.extend([it] * ns) start += ns time = Time(times) time = Time( np.pad(time.datetime64, (0, pad_after), constant_values=time[-1].datetime64)) time = time.reshape(-1, num_energies) duration = np.pad(np.hstack(durations), (0, pad_after)).reshape(-1, num_energies) * it.unit return duration, time
def _make_schedule(self, blocks): pre_filled = np.array([[block.start_time, block.end_time] for block in self.schedule.scheduled_blocks]) if len(pre_filled) == 0: a = self.schedule.start_time filled_times = Time([a - 1*u.hour, a - 1*u.hour, a - 1*u.minute, a - 1*u.minute]) pre_filled = filled_times.reshape((2, 2)) else: filled_times = Time(pre_filled.flatten()) pre_filled = filled_times.reshape((int(len(filled_times)/2), 2)) for b in blocks: if b.constraints is None: b._all_constraints = self.constraints else: b._all_constraints = self.constraints + b.constraints # to make sure the scheduler has some constraint to work off of # and to prevent scheduling of targets below the horizon # TODO : change default constraints to [] and switch to append if b._all_constraints is None: b._all_constraints = [AltitudeConstraint(min=0 * u.deg)] b.constraints = [AltitudeConstraint(min=0 * u.deg)] elif not any(isinstance(c, AltitudeConstraint) for c in b._all_constraints): b._all_constraints.append(AltitudeConstraint(min=0 * u.deg)) if b.constraints is None: b.constraints = [AltitudeConstraint(min=0 * u.deg)] else: b.constraints.append(AltitudeConstraint(min=0 * u.deg)) b._duration_offsets = u.Quantity([0*u.second, b.duration/2, b.duration]) b.observer = self.observer current_time = self.schedule.start_time while (len(blocks) > 0) and (current_time < self.schedule.end_time): # first compute the value of all the constraints for each block # given the current starting time block_transitions = [] block_constraint_results = [] for b in blocks: # first figure out the transition if len(self.schedule.observing_blocks) > 0: trans = self.transitioner( self.schedule.observing_blocks[-1], b, current_time, self.observer) else: trans = None block_transitions.append(trans) transition_time = 0*u.second if trans is None else trans.duration times = current_time + transition_time + b._duration_offsets # make sure it isn't in a pre-filled slot if (any((current_time < filled_times) & (filled_times < times[2])) or any(abs(pre_filled.T[0]-current_time) < 1*u.second)): block_constraint_results.append(0) else: constraint_res = [] for constraint in b._all_constraints: constraint_res.append(constraint( self.observer, b.target, times)) # take the product over all the constraints *and* times block_constraint_results.append(np.prod(constraint_res)) # now identify the block that's the best bestblock_idx = np.argmax(block_constraint_results) if block_constraint_results[bestblock_idx] == 0.: # if even the best is unobservable, we need a gap current_time += self.gap_time else: # If there's a best one that's observable, first get its transition trans = block_transitions.pop(bestblock_idx) if trans is not None: self.schedule.insert_slot(trans.start_time, trans) current_time += trans.duration # now assign the block itself times and add it to the schedule newb = blocks.pop(bestblock_idx) newb.start_time = current_time current_time += newb.duration newb.end_time = current_time newb.constraints_value = block_constraint_results[bestblock_idx] self.schedule.insert_slot(newb.start_time, newb) return self.schedule
def __getitem__(self, time: Time): """ """ starts = (self.time).jd stops = (self.time + self.duration).jd if time.isscalar: time = time.reshape(1,) if self.coordinates.isscalar: # coordinates = self.coordinates.reshape(1,) coordinates = SkyCoord( np.repeat(self.coordinates.ra.deg, self.time.size), np.repeat(self.coordinates.dec.deg, self.time.size), unit="deg" ) else: coordinates = self.coordinates ras = coordinates.ra.deg decs = coordinates.dec.deg ra = [] dec = [] custom_az = [] custom_el = [] for t in time.jd: # Find the corresponding RA/Dec mask = (t >= starts) & (t < stops) if np.all(~mask): # No match t = Time(t, format="jd") log.warning( f"Default zenith pointing at {t.isot}." ) zenith = SkyCoord( 0*u.deg, 90*u.deg, frame=AltAz( obstime=t, location=self.observer ) ).transform_to(ICRS) ra.append(zenith.ra) dec.append(zenith.dec) if hasattr(self, "custom_ho_coordinates"): custom_az.append(0*u.deg) custom_el.append(90*u.deg) else: # there is a match ra.append(ras[mask][0]) dec.append(decs[mask][0]) if hasattr(self, "custom_ho_coordinates"): custom_az.append(self.custom_ho_coordinates[mask].az[0]) custom_el.append(self.custom_ho_coordinates[mask].alt[0]) pointing = Pointing( coordinates=SkyCoord( ra, dec, unit='deg' ), time=time, ) if hasattr(self, "custom_ho_coordinates"): #pointing.custom_ho_coordinates = self.custom_ho_coordinates pointing.custom_ho_coordinates = SkyCoord( custom_az, custom_el, frame=AltAz( obstime=time,#.reshape(time.size, 1), location=nenufar_position ) ) return pointing
def _make_schedule(self, blocks): pre_filled = np.array([[block.start_time, block.end_time] for block in self.schedule.scheduled_blocks]) if len(pre_filled) == 0: a = self.schedule.start_time filled_times = Time([ a - 1 * u.hour, a - 1 * u.hour, a - 1 * u.minute, a - 1 * u.minute ]) pre_filled = filled_times.reshape((2, 2)) else: filled_times = Time(pre_filled.flatten()) pre_filled = filled_times.reshape((int(len(filled_times) / 2), 2)) for b in blocks: if b.constraints is None: b._all_constraints = self.constraints else: b._all_constraints = self.constraints + b.constraints if b._all_constraints is None: b._all_constraints = [AltitudeConstraint(min=0 * u.deg)] b.constraints = [AltitudeConstraint(min=0 * u.deg)] elif not any( isinstance(c, AltitudeConstraint) for c in b._all_constraints): b._all_constraints.append(AltitudeConstraint(min=0 * u.deg)) if b.constraints is None: b.constraints = [AltitudeConstraint(min=0 * u.deg)] else: b.constraints.append(AltitudeConstraint(min=0 * u.deg)) b._duration_offsets = u.Quantity( [0 * u.second, b.duration / 2, b.duration]) b.observer = self.observer current_time = self.schedule.start_time while (len(blocks) > 0) and (current_time < self.schedule.end_time): block_transitions = [] block_constraint_results = [] for b in blocks: if len(self.schedule.observing_blocks) > 0: trans = self.transitioner( self.schedule.observing_blocks[-1], b, current_time, self.observer) else: trans = None block_transitions.append(trans) transition_time = 0 * u.second if trans is None else trans.duration times = current_time + transition_time + b._duration_offsets if (any((current_time < filled_times) & (filled_times < times[2])) or any( abs(pre_filled.T[0] - current_time) < 1 * u.second)): block_constraint_results.append(0) else: constraint_res = [] for constraint in b._all_constraints: constraint_res.append( constraint(self.observer, [b.target], times)) block_constraint_results.append(np.prod(constraint_res)) bestblock_idx = np.argmax(block_constraint_results) if block_constraint_results[bestblock_idx] == 0.: current_time += self.gap_time else: trans = block_transitions.pop(bestblock_idx) if trans is not None: self.schedule.insert_slot(trans.start_time, trans) current_time += trans.duration newb = blocks.pop(bestblock_idx) newb.start_time = current_time current_time += newb.duration newb.end_time = current_time newb.constraints_value = block_constraint_results[ bestblock_idx] self.schedule.insert_slot(newb.start_time, newb) return self.schedule
class TestManipulation: """Manipulation of Time objects, ensuring attributes are done correctly.""" def setup(self): mjd = np.arange(50000, 50010) frac = np.arange(0., 0.999, 0.2) if use_masked_data: frac = np.ma.array(frac) frac[1] = np.ma.masked self.t0 = Time(mjd[:, np.newaxis] + frac, format='mjd', scale='utc') self.t1 = Time(mjd[:, np.newaxis] + frac, format='mjd', scale='utc', location=('45d', '50d')) self.t2 = Time(mjd[:, np.newaxis] + frac, format='mjd', scale='utc', location=(np.arange(len(frac)), np.arange(len(frac)))) # Note: location is along last axis only. self.t2 = Time(mjd[:, np.newaxis] + frac, format='mjd', scale='utc', location=(np.arange(len(frac)), np.arange(len(frac)))) def test_ravel(self, masked): t0_ravel = self.t0.ravel() assert t0_ravel.shape == (self.t0.size, ) assert np.all(t0_ravel.jd1 == self.t0.jd1.ravel()) assert np.may_share_memory(t0_ravel.jd1, self.t0.jd1) assert t0_ravel.location is None t1_ravel = self.t1.ravel() assert t1_ravel.shape == (self.t1.size, ) assert np.all(t1_ravel.jd1 == self.t1.jd1.ravel()) assert np.may_share_memory(t1_ravel.jd1, self.t1.jd1) assert t1_ravel.location is self.t1.location t2_ravel = self.t2.ravel() assert t2_ravel.shape == (self.t2.size, ) assert np.all(t2_ravel.jd1 == self.t2.jd1.ravel()) assert np.may_share_memory(t2_ravel.jd1, self.t2.jd1) assert t2_ravel.location.shape == t2_ravel.shape # Broadcasting and ravelling cannot be done without a copy. assert not np.may_share_memory(t2_ravel.location, self.t2.location) def test_flatten(self, masked): t0_flatten = self.t0.flatten() assert t0_flatten.shape == (self.t0.size, ) assert t0_flatten.location is None # Flatten always makes a copy. assert not np.may_share_memory(t0_flatten.jd1, self.t0.jd1) t1_flatten = self.t1.flatten() assert t1_flatten.shape == (self.t1.size, ) assert not np.may_share_memory(t1_flatten.jd1, self.t1.jd1) assert t1_flatten.location is not self.t1.location assert t1_flatten.location == self.t1.location t2_flatten = self.t2.flatten() assert t2_flatten.shape == (self.t2.size, ) assert not np.may_share_memory(t2_flatten.jd1, self.t2.jd1) assert t2_flatten.location.shape == t2_flatten.shape assert not np.may_share_memory(t2_flatten.location, self.t2.location) def test_transpose(self, masked): t0_transpose = self.t0.transpose() assert t0_transpose.shape == (5, 10) assert np.all(t0_transpose.jd1 == self.t0.jd1.transpose()) assert np.may_share_memory(t0_transpose.jd1, self.t0.jd1) assert t0_transpose.location is None t1_transpose = self.t1.transpose() assert t1_transpose.shape == (5, 10) assert np.all(t1_transpose.jd1 == self.t1.jd1.transpose()) assert np.may_share_memory(t1_transpose.jd1, self.t1.jd1) assert t1_transpose.location is self.t1.location t2_transpose = self.t2.transpose() assert t2_transpose.shape == (5, 10) assert np.all(t2_transpose.jd1 == self.t2.jd1.transpose()) assert np.may_share_memory(t2_transpose.jd1, self.t2.jd1) assert t2_transpose.location.shape == t2_transpose.shape assert np.may_share_memory(t2_transpose.location, self.t2.location) # Only one check on T, since it just calls transpose anyway. t2_T = self.t2.T assert t2_T.shape == (5, 10) assert np.all(t2_T.jd1 == self.t2.jd1.T) assert np.may_share_memory(t2_T.jd1, self.t2.jd1) assert t2_T.location.shape == t2_T.location.shape assert np.may_share_memory(t2_T.location, self.t2.location) def test_diagonal(self, masked): t0_diagonal = self.t0.diagonal() assert t0_diagonal.shape == (5, ) assert np.all(t0_diagonal.jd1 == self.t0.jd1.diagonal()) assert t0_diagonal.location is None assert np.may_share_memory(t0_diagonal.jd1, self.t0.jd1) t1_diagonal = self.t1.diagonal() assert t1_diagonal.shape == (5, ) assert np.all(t1_diagonal.jd1 == self.t1.jd1.diagonal()) assert t1_diagonal.location is self.t1.location assert np.may_share_memory(t1_diagonal.jd1, self.t1.jd1) t2_diagonal = self.t2.diagonal() assert t2_diagonal.shape == (5, ) assert np.all(t2_diagonal.jd1 == self.t2.jd1.diagonal()) assert t2_diagonal.location.shape == t2_diagonal.shape assert np.may_share_memory(t2_diagonal.jd1, self.t2.jd1) assert np.may_share_memory(t2_diagonal.location, self.t2.location) def test_swapaxes(self, masked): t0_swapaxes = self.t0.swapaxes(0, 1) assert t0_swapaxes.shape == (5, 10) assert np.all(t0_swapaxes.jd1 == self.t0.jd1.swapaxes(0, 1)) assert np.may_share_memory(t0_swapaxes.jd1, self.t0.jd1) assert t0_swapaxes.location is None t1_swapaxes = self.t1.swapaxes(0, 1) assert t1_swapaxes.shape == (5, 10) assert np.all(t1_swapaxes.jd1 == self.t1.jd1.swapaxes(0, 1)) assert np.may_share_memory(t1_swapaxes.jd1, self.t1.jd1) assert t1_swapaxes.location is self.t1.location t2_swapaxes = self.t2.swapaxes(0, 1) assert t2_swapaxes.shape == (5, 10) assert np.all(t2_swapaxes.jd1 == self.t2.jd1.swapaxes(0, 1)) assert np.may_share_memory(t2_swapaxes.jd1, self.t2.jd1) assert t2_swapaxes.location.shape == t2_swapaxes.shape assert np.may_share_memory(t2_swapaxes.location, self.t2.location) def test_reshape(self, masked): t0_reshape = self.t0.reshape(5, 2, 5) assert t0_reshape.shape == (5, 2, 5) assert np.all(t0_reshape.jd1 == self.t0._time.jd1.reshape(5, 2, 5)) assert np.all(t0_reshape.jd2 == self.t0._time.jd2.reshape(5, 2, 5)) assert np.may_share_memory(t0_reshape.jd1, self.t0.jd1) assert np.may_share_memory(t0_reshape.jd2, self.t0.jd2) assert t0_reshape.location is None t1_reshape = self.t1.reshape(2, 5, 5) assert t1_reshape.shape == (2, 5, 5) assert np.all(t1_reshape.jd1 == self.t1.jd1.reshape(2, 5, 5)) assert np.may_share_memory(t1_reshape.jd1, self.t1.jd1) assert t1_reshape.location is self.t1.location # For reshape(5, 2, 5), the location array can remain the same. t2_reshape = self.t2.reshape(5, 2, 5) assert t2_reshape.shape == (5, 2, 5) assert np.all(t2_reshape.jd1 == self.t2.jd1.reshape(5, 2, 5)) assert np.may_share_memory(t2_reshape.jd1, self.t2.jd1) assert t2_reshape.location.shape == t2_reshape.shape assert np.may_share_memory(t2_reshape.location, self.t2.location) # But for reshape(5, 5, 2), location has to be broadcast and copied. t2_reshape2 = self.t2.reshape(5, 5, 2) assert t2_reshape2.shape == (5, 5, 2) assert np.all(t2_reshape2.jd1 == self.t2.jd1.reshape(5, 5, 2)) assert np.may_share_memory(t2_reshape2.jd1, self.t2.jd1) assert t2_reshape2.location.shape == t2_reshape2.shape assert not np.may_share_memory(t2_reshape2.location, self.t2.location) t2_reshape_t = self.t2.reshape(10, 5).T assert t2_reshape_t.shape == (5, 10) assert np.may_share_memory(t2_reshape_t.jd1, self.t2.jd1) assert t2_reshape_t.location.shape == t2_reshape_t.shape assert np.may_share_memory(t2_reshape_t.location, self.t2.location) # Finally, reshape in a way that cannot be a view. t2_reshape_t_reshape = t2_reshape_t.reshape(10, 5) assert t2_reshape_t_reshape.shape == (10, 5) assert not np.may_share_memory(t2_reshape_t_reshape.jd1, self.t2.jd1) assert ( t2_reshape_t_reshape.location.shape == t2_reshape_t_reshape.shape) assert not np.may_share_memory(t2_reshape_t_reshape.location, t2_reshape_t.location) def test_shape_setting(self, masked): t0_reshape = self.t0.copy() mjd = t0_reshape.mjd # Creates a cache of the mjd attribute t0_reshape.shape = (5, 2, 5) assert t0_reshape.shape == (5, 2, 5) assert mjd.shape != t0_reshape.mjd.shape # Cache got cleared assert np.all(t0_reshape.jd1 == self.t0._time.jd1.reshape(5, 2, 5)) assert np.all(t0_reshape.jd2 == self.t0._time.jd2.reshape(5, 2, 5)) assert t0_reshape.location is None # But if the shape doesn't work, one should get an error. t0_reshape_t = t0_reshape.T with pytest.raises(AttributeError): t0_reshape_t.shape = (10, 5) # check no shape was changed. assert t0_reshape_t.shape == t0_reshape.T.shape assert t0_reshape_t.jd1.shape == t0_reshape.T.shape assert t0_reshape_t.jd2.shape == t0_reshape.T.shape t1_reshape = self.t1.copy() t1_reshape.shape = (2, 5, 5) assert t1_reshape.shape == (2, 5, 5) assert np.all(t1_reshape.jd1 == self.t1.jd1.reshape(2, 5, 5)) # location is a single element, so its shape should not change. assert t1_reshape.location.shape == () # For reshape(5, 2, 5), the location array can remain the same. # Note that we need to work directly on self.t2 here, since any # copy would cause location to have the full shape. self.t2.shape = (5, 2, 5) assert self.t2.shape == (5, 2, 5) assert self.t2.jd1.shape == (5, 2, 5) assert self.t2.jd2.shape == (5, 2, 5) assert self.t2.location.shape == (5, 2, 5) assert self.t2.location.strides == (0, 0, 24) # But for reshape(50), location would need to be copied, so this # should fail. oldshape = self.t2.shape with pytest.raises(AttributeError): self.t2.shape = (50, ) # check no shape was changed. assert self.t2.jd1.shape == oldshape assert self.t2.jd2.shape == oldshape assert self.t2.location.shape == oldshape # reset t2 to its original. self.setup() def test_squeeze(self, masked): t0_squeeze = self.t0.reshape(5, 1, 2, 1, 5).squeeze() assert t0_squeeze.shape == (5, 2, 5) assert np.all(t0_squeeze.jd1 == self.t0.jd1.reshape(5, 2, 5)) assert np.may_share_memory(t0_squeeze.jd1, self.t0.jd1) assert t0_squeeze.location is None t1_squeeze = self.t1.reshape(1, 5, 1, 2, 5).squeeze() assert t1_squeeze.shape == (5, 2, 5) assert np.all(t1_squeeze.jd1 == self.t1.jd1.reshape(5, 2, 5)) assert np.may_share_memory(t1_squeeze.jd1, self.t1.jd1) assert t1_squeeze.location is self.t1.location t2_squeeze = self.t2.reshape(1, 1, 5, 2, 5, 1, 1).squeeze() assert t2_squeeze.shape == (5, 2, 5) assert np.all(t2_squeeze.jd1 == self.t2.jd1.reshape(5, 2, 5)) assert np.may_share_memory(t2_squeeze.jd1, self.t2.jd1) assert t2_squeeze.location.shape == t2_squeeze.shape assert np.may_share_memory(t2_squeeze.location, self.t2.location) def test_add_dimension(self, masked): t0_adddim = self.t0[:, np.newaxis, :] assert t0_adddim.shape == (10, 1, 5) assert np.all(t0_adddim.jd1 == self.t0.jd1[:, np.newaxis, :]) assert np.may_share_memory(t0_adddim.jd1, self.t0.jd1) assert t0_adddim.location is None t1_adddim = self.t1[:, :, np.newaxis] assert t1_adddim.shape == (10, 5, 1) assert np.all(t1_adddim.jd1 == self.t1.jd1[:, :, np.newaxis]) assert np.may_share_memory(t1_adddim.jd1, self.t1.jd1) assert t1_adddim.location is self.t1.location t2_adddim = self.t2[:, :, np.newaxis] assert t2_adddim.shape == (10, 5, 1) assert np.all(t2_adddim.jd1 == self.t2.jd1[:, :, np.newaxis]) assert np.may_share_memory(t2_adddim.jd1, self.t2.jd1) assert t2_adddim.location.shape == t2_adddim.shape assert np.may_share_memory(t2_adddim.location, self.t2.location) def test_take(self, masked): t0_take = self.t0.take((5, 2)) assert t0_take.shape == (2, ) assert np.all(t0_take.jd1 == self.t0._time.jd1.take((5, 2))) assert t0_take.location is None t1_take = self.t1.take((2, 4), axis=1) assert t1_take.shape == (10, 2) assert np.all(t1_take.jd1 == self.t1.jd1.take((2, 4), axis=1)) assert t1_take.location is self.t1.location t2_take = self.t2.take((1, 3, 7), axis=0) assert t2_take.shape == (3, 5) assert np.all(t2_take.jd1 == self.t2.jd1.take((1, 3, 7), axis=0)) assert t2_take.location.shape == t2_take.shape t2_take2 = self.t2.take((5, 15)) assert t2_take2.shape == (2, ) assert np.all(t2_take2.jd1 == self.t2.jd1.take((5, 15))) assert t2_take2.location.shape == t2_take2.shape def test_broadcast(self, masked): """Test using a callable method.""" t0_broadcast = self.t0._apply(np.broadcast_to, shape=(3, 10, 5)) assert t0_broadcast.shape == (3, 10, 5) assert np.all(t0_broadcast.jd1 == self.t0.jd1) assert np.may_share_memory(t0_broadcast.jd1, self.t0.jd1) assert t0_broadcast.location is None t1_broadcast = self.t1._apply(np.broadcast_to, shape=(3, 10, 5)) assert t1_broadcast.shape == (3, 10, 5) assert np.all(t1_broadcast.jd1 == self.t1.jd1) assert np.may_share_memory(t1_broadcast.jd1, self.t1.jd1) assert t1_broadcast.location is self.t1.location t2_broadcast = self.t2._apply(np.broadcast_to, shape=(3, 10, 5)) assert t2_broadcast.shape == (3, 10, 5) assert np.all(t2_broadcast.jd1 == self.t2.jd1) assert np.may_share_memory(t2_broadcast.jd1, self.t2.jd1) assert t2_broadcast.location.shape == t2_broadcast.shape assert np.may_share_memory(t2_broadcast.location, self.t2.location)
def attenuation_from_zenith(self, coordinates, time: Time = Time.now(), frequency: u.Quantity = 50 * u.MHz, polarization: Polarization = Polarization.NW): """ Returns the attenuation factor evaluated at given ``coordinates`` compared to the zenithal Mini-Array beam gain. :param coordinates: Sky positions equatorial coordinates. :type coordinates: :class:`~astropy.coordinates.SkyCoord` :param time: UTC time at which the attenuation is evaluated. Default is ``now``. :type time: :class:`~astropy.time.Time` :param frequency: Frequency at which the attenuation is evaluated. Default is ``50 MHz``. :type frquency: :class:`~astropy.units.Quantity` :param polarization: NenuFAR antenna polarization. Default is ``Polarization.NW``. :type polarization: :class:`~nenupy.instru.nenufar.Polarization` :returns: Attenuation factor shaped as ``(time, frequency, polarization, coordinates)``. ``NaN`` is returned for any ``coordinates`` that is below the horizon. :rtype: :class:`~numpy.ndarray` :Example: >>> from nenupy.instru.nenufar import MiniArray >>> from astropy.coordinates import SkyCoord >>> ma = MiniArray(index=0) >>> attenuation = ma.attenuation_from_zenith( coordinates=SkyCoord.from_name("Cyg A") ) >>> from nenupy.instru.nenufar import MiniArray >>> from astropy.coordinates import SkyCoord >>> import astropy.units as u >>> ma = MiniArray(index=0) >>> attenuation = ma.attenuation_from_zenith( coordinates=SkyCoord.from_name("Cyg A"), frequency=np.linspace(20, 80, 10)*u.MHz ) .. versionadded:: 2.0.0 """ # Define the pointing towards the zenith pointing = Pointing.zenith_tracking(time=time.reshape((1, )), duration=TimeDelta(10, format="sec")) # Compute the local zenith in equatorial coordinates local_zenith = SkyCoord( 180, 90, unit="deg", frame=AltAz(obstime=time, location=nenufar_position)).transform_to( coordinates.frame) # Find the coordinates below the horizon and compute a mask input_coord_altaz = radec_to_altaz(radec=coordinates, time=time) invisible_mask = input_coord_altaz.alt.deg <= 0 # Concatenate local_zenith and coordinates if coordinates.obstime is None: coordinates.obstime = local_zenith.obstime if coordinates.location is None: coordinates.location = local_zenith.location if coordinates.isscalar: coordinates = coordinates.reshape((1, )) coordinates = coordinates.insert(0, local_zenith) # Prepare a Sky instance for the beam simulation sky = Sky(coordinates=coordinates, frequency=frequency, time=time, polarization=polarization) # Compute the beam beam = self.beam(sky=sky, pointing=pointing) # Compute the attenuation factor relative to the zenith (first member) values = beam.value.compute() output_values = values[..., 1:] / np.expand_dims(values[..., 0], 3) output_values[..., invisible_mask] = np.nan return output_values
class TestManipulation(): """Manipulation of Time objects, ensuring attributes are done correctly.""" def setup(self): mjd = np.arange(50000, 50010) frac = np.arange(0., 0.999, 0.2) if use_masked_data: frac = np.ma.array(frac) frac[1] = np.ma.masked self.t0 = Time(mjd[:, np.newaxis] + frac, format='mjd', scale='utc') self.t1 = Time(mjd[:, np.newaxis] + frac, format='mjd', scale='utc', location=('45d', '50d')) self.t2 = Time(mjd[:, np.newaxis] + frac, format='mjd', scale='utc', location=(np.arange(len(frac)), np.arange(len(frac)))) # Note: location is along last axis only. self.t2 = Time(mjd[:, np.newaxis] + frac, format='mjd', scale='utc', location=(np.arange(len(frac)), np.arange(len(frac)))) def test_ravel(self, masked): t0_ravel = self.t0.ravel() assert t0_ravel.shape == (self.t0.size,) assert np.all(t0_ravel.jd1 == self.t0.jd1.ravel()) assert np.may_share_memory(t0_ravel.jd1, self.t0.jd1) assert t0_ravel.location is None t1_ravel = self.t1.ravel() assert t1_ravel.shape == (self.t1.size,) assert np.all(t1_ravel.jd1 == self.t1.jd1.ravel()) assert np.may_share_memory(t1_ravel.jd1, self.t1.jd1) assert t1_ravel.location is self.t1.location t2_ravel = self.t2.ravel() assert t2_ravel.shape == (self.t2.size,) assert np.all(t2_ravel.jd1 == self.t2.jd1.ravel()) assert np.may_share_memory(t2_ravel.jd1, self.t2.jd1) assert t2_ravel.location.shape == t2_ravel.shape # Broadcasting and ravelling cannot be done without a copy. assert not np.may_share_memory(t2_ravel.location, self.t2.location) def test_flatten(self, masked): t0_flatten = self.t0.flatten() assert t0_flatten.shape == (self.t0.size,) assert t0_flatten.location is None # Flatten always makes a copy. assert not np.may_share_memory(t0_flatten.jd1, self.t0.jd1) t1_flatten = self.t1.flatten() assert t1_flatten.shape == (self.t1.size,) assert not np.may_share_memory(t1_flatten.jd1, self.t1.jd1) assert t1_flatten.location is not self.t1.location assert t1_flatten.location == self.t1.location t2_flatten = self.t2.flatten() assert t2_flatten.shape == (self.t2.size,) assert not np.may_share_memory(t2_flatten.jd1, self.t2.jd1) assert t2_flatten.location.shape == t2_flatten.shape assert not np.may_share_memory(t2_flatten.location, self.t2.location) def test_transpose(self, masked): t0_transpose = self.t0.transpose() assert t0_transpose.shape == (5, 10) assert np.all(t0_transpose.jd1 == self.t0.jd1.transpose()) assert np.may_share_memory(t0_transpose.jd1, self.t0.jd1) assert t0_transpose.location is None t1_transpose = self.t1.transpose() assert t1_transpose.shape == (5, 10) assert np.all(t1_transpose.jd1 == self.t1.jd1.transpose()) assert np.may_share_memory(t1_transpose.jd1, self.t1.jd1) assert t1_transpose.location is self.t1.location t2_transpose = self.t2.transpose() assert t2_transpose.shape == (5, 10) assert np.all(t2_transpose.jd1 == self.t2.jd1.transpose()) assert np.may_share_memory(t2_transpose.jd1, self.t2.jd1) assert t2_transpose.location.shape == t2_transpose.shape assert np.may_share_memory(t2_transpose.location, self.t2.location) # Only one check on T, since it just calls transpose anyway. t2_T = self.t2.T assert t2_T.shape == (5, 10) assert np.all(t2_T.jd1 == self.t2.jd1.T) assert np.may_share_memory(t2_T.jd1, self.t2.jd1) assert t2_T.location.shape == t2_T.location.shape assert np.may_share_memory(t2_T.location, self.t2.location) def test_diagonal(self, masked): t0_diagonal = self.t0.diagonal() assert t0_diagonal.shape == (5,) assert np.all(t0_diagonal.jd1 == self.t0.jd1.diagonal()) assert t0_diagonal.location is None assert np.may_share_memory(t0_diagonal.jd1, self.t0.jd1) t1_diagonal = self.t1.diagonal() assert t1_diagonal.shape == (5,) assert np.all(t1_diagonal.jd1 == self.t1.jd1.diagonal()) assert t1_diagonal.location is self.t1.location assert np.may_share_memory(t1_diagonal.jd1, self.t1.jd1) t2_diagonal = self.t2.diagonal() assert t2_diagonal.shape == (5,) assert np.all(t2_diagonal.jd1 == self.t2.jd1.diagonal()) assert t2_diagonal.location.shape == t2_diagonal.shape assert np.may_share_memory(t2_diagonal.jd1, self.t2.jd1) assert np.may_share_memory(t2_diagonal.location, self.t2.location) def test_swapaxes(self, masked): t0_swapaxes = self.t0.swapaxes(0, 1) assert t0_swapaxes.shape == (5, 10) assert np.all(t0_swapaxes.jd1 == self.t0.jd1.swapaxes(0, 1)) assert np.may_share_memory(t0_swapaxes.jd1, self.t0.jd1) assert t0_swapaxes.location is None t1_swapaxes = self.t1.swapaxes(0, 1) assert t1_swapaxes.shape == (5, 10) assert np.all(t1_swapaxes.jd1 == self.t1.jd1.swapaxes(0, 1)) assert np.may_share_memory(t1_swapaxes.jd1, self.t1.jd1) assert t1_swapaxes.location is self.t1.location t2_swapaxes = self.t2.swapaxes(0, 1) assert t2_swapaxes.shape == (5, 10) assert np.all(t2_swapaxes.jd1 == self.t2.jd1.swapaxes(0, 1)) assert np.may_share_memory(t2_swapaxes.jd1, self.t2.jd1) assert t2_swapaxes.location.shape == t2_swapaxes.shape assert np.may_share_memory(t2_swapaxes.location, self.t2.location) def test_reshape(self, masked): t0_reshape = self.t0.reshape(5, 2, 5) assert t0_reshape.shape == (5, 2, 5) assert np.all(t0_reshape.jd1 == self.t0._time.jd1.reshape(5, 2, 5)) assert np.all(t0_reshape.jd2 == self.t0._time.jd2.reshape(5, 2, 5)) assert np.may_share_memory(t0_reshape.jd1, self.t0.jd1) assert np.may_share_memory(t0_reshape.jd2, self.t0.jd2) assert t0_reshape.location is None t1_reshape = self.t1.reshape(2, 5, 5) assert t1_reshape.shape == (2, 5, 5) assert np.all(t1_reshape.jd1 == self.t1.jd1.reshape(2, 5, 5)) assert np.may_share_memory(t1_reshape.jd1, self.t1.jd1) assert t1_reshape.location is self.t1.location # For reshape(5, 2, 5), the location array can remain the same. t2_reshape = self.t2.reshape(5, 2, 5) assert t2_reshape.shape == (5, 2, 5) assert np.all(t2_reshape.jd1 == self.t2.jd1.reshape(5, 2, 5)) assert np.may_share_memory(t2_reshape.jd1, self.t2.jd1) assert t2_reshape.location.shape == t2_reshape.shape assert np.may_share_memory(t2_reshape.location, self.t2.location) # But for reshape(5, 5, 2), location has to be broadcast and copied. t2_reshape2 = self.t2.reshape(5, 5, 2) assert t2_reshape2.shape == (5, 5, 2) assert np.all(t2_reshape2.jd1 == self.t2.jd1.reshape(5, 5, 2)) assert np.may_share_memory(t2_reshape2.jd1, self.t2.jd1) assert t2_reshape2.location.shape == t2_reshape2.shape assert not np.may_share_memory(t2_reshape2.location, self.t2.location) t2_reshape_t = self.t2.reshape(10, 5).T assert t2_reshape_t.shape == (5, 10) assert np.may_share_memory(t2_reshape_t.jd1, self.t2.jd1) assert t2_reshape_t.location.shape == t2_reshape_t.shape assert np.may_share_memory(t2_reshape_t.location, self.t2.location) # Finally, reshape in a way that cannot be a view. t2_reshape_t_reshape = t2_reshape_t.reshape(10, 5) assert t2_reshape_t_reshape.shape == (10, 5) assert not np.may_share_memory(t2_reshape_t_reshape.jd1, self.t2.jd1) assert (t2_reshape_t_reshape.location.shape == t2_reshape_t_reshape.shape) assert not np.may_share_memory(t2_reshape_t_reshape.location, t2_reshape_t.location) def test_shape_setting(self, masked): t0_reshape = self.t0.copy() mjd = t0_reshape.mjd # Creates a cache of the mjd attribute t0_reshape.shape = (5, 2, 5) assert t0_reshape.shape == (5, 2, 5) assert mjd.shape != t0_reshape.mjd.shape # Cache got cleared assert np.all(t0_reshape.jd1 == self.t0._time.jd1.reshape(5, 2, 5)) assert np.all(t0_reshape.jd2 == self.t0._time.jd2.reshape(5, 2, 5)) assert t0_reshape.location is None # But if the shape doesn't work, one should get an error. t0_reshape_t = t0_reshape.T with pytest.raises(AttributeError): t0_reshape_t.shape = (10, 5) # check no shape was changed. assert t0_reshape_t.shape == t0_reshape.T.shape assert t0_reshape_t.jd1.shape == t0_reshape.T.shape assert t0_reshape_t.jd2.shape == t0_reshape.T.shape t1_reshape = self.t1.copy() t1_reshape.shape = (2, 5, 5) assert t1_reshape.shape == (2, 5, 5) assert np.all(t1_reshape.jd1 == self.t1.jd1.reshape(2, 5, 5)) # location is a single element, so its shape should not change. assert t1_reshape.location.shape == () # For reshape(5, 2, 5), the location array can remain the same. # Note that we need to work directly on self.t2 here, since any # copy would cause location to have the full shape. self.t2.shape = (5, 2, 5) assert self.t2.shape == (5, 2, 5) assert self.t2.jd1.shape == (5, 2, 5) assert self.t2.jd2.shape == (5, 2, 5) assert self.t2.location.shape == (5, 2, 5) assert self.t2.location.strides == (0, 0, 24) # But for reshape(50), location would need to be copied, so this # should fail. oldshape = self.t2.shape with pytest.raises(AttributeError): self.t2.shape = (50,) # check no shape was changed. assert self.t2.jd1.shape == oldshape assert self.t2.jd2.shape == oldshape assert self.t2.location.shape == oldshape # reset t2 to its original. self.setup() def test_squeeze(self, masked): t0_squeeze = self.t0.reshape(5, 1, 2, 1, 5).squeeze() assert t0_squeeze.shape == (5, 2, 5) assert np.all(t0_squeeze.jd1 == self.t0.jd1.reshape(5, 2, 5)) assert np.may_share_memory(t0_squeeze.jd1, self.t0.jd1) assert t0_squeeze.location is None t1_squeeze = self.t1.reshape(1, 5, 1, 2, 5).squeeze() assert t1_squeeze.shape == (5, 2, 5) assert np.all(t1_squeeze.jd1 == self.t1.jd1.reshape(5, 2, 5)) assert np.may_share_memory(t1_squeeze.jd1, self.t1.jd1) assert t1_squeeze.location is self.t1.location t2_squeeze = self.t2.reshape(1, 1, 5, 2, 5, 1, 1).squeeze() assert t2_squeeze.shape == (5, 2, 5) assert np.all(t2_squeeze.jd1 == self.t2.jd1.reshape(5, 2, 5)) assert np.may_share_memory(t2_squeeze.jd1, self.t2.jd1) assert t2_squeeze.location.shape == t2_squeeze.shape assert np.may_share_memory(t2_squeeze.location, self.t2.location) def test_add_dimension(self, masked): t0_adddim = self.t0[:, np.newaxis, :] assert t0_adddim.shape == (10, 1, 5) assert np.all(t0_adddim.jd1 == self.t0.jd1[:, np.newaxis, :]) assert np.may_share_memory(t0_adddim.jd1, self.t0.jd1) assert t0_adddim.location is None t1_adddim = self.t1[:, :, np.newaxis] assert t1_adddim.shape == (10, 5, 1) assert np.all(t1_adddim.jd1 == self.t1.jd1[:, :, np.newaxis]) assert np.may_share_memory(t1_adddim.jd1, self.t1.jd1) assert t1_adddim.location is self.t1.location t2_adddim = self.t2[:, :, np.newaxis] assert t2_adddim.shape == (10, 5, 1) assert np.all(t2_adddim.jd1 == self.t2.jd1[:, :, np.newaxis]) assert np.may_share_memory(t2_adddim.jd1, self.t2.jd1) assert t2_adddim.location.shape == t2_adddim.shape assert np.may_share_memory(t2_adddim.location, self.t2.location) def test_take(self, masked): t0_take = self.t0.take((5, 2)) assert t0_take.shape == (2,) assert np.all(t0_take.jd1 == self.t0._time.jd1.take((5, 2))) assert t0_take.location is None t1_take = self.t1.take((2, 4), axis=1) assert t1_take.shape == (10, 2) assert np.all(t1_take.jd1 == self.t1.jd1.take((2, 4), axis=1)) assert t1_take.location is self.t1.location t2_take = self.t2.take((1, 3, 7), axis=0) assert t2_take.shape == (3, 5) assert np.all(t2_take.jd1 == self.t2.jd1.take((1, 3, 7), axis=0)) assert t2_take.location.shape == t2_take.shape t2_take2 = self.t2.take((5, 15)) assert t2_take2.shape == (2,) assert np.all(t2_take2.jd1 == self.t2.jd1.take((5, 15))) assert t2_take2.location.shape == t2_take2.shape def test_broadcast(self, masked): """Test using a callable method.""" t0_broadcast = self.t0._apply(np.broadcast_to, shape=(3, 10, 5)) assert t0_broadcast.shape == (3, 10, 5) assert np.all(t0_broadcast.jd1 == self.t0.jd1) assert np.may_share_memory(t0_broadcast.jd1, self.t0.jd1) assert t0_broadcast.location is None t1_broadcast = self.t1._apply(np.broadcast_to, shape=(3, 10, 5)) assert t1_broadcast.shape == (3, 10, 5) assert np.all(t1_broadcast.jd1 == self.t1.jd1) assert np.may_share_memory(t1_broadcast.jd1, self.t1.jd1) assert t1_broadcast.location is self.t1.location t2_broadcast = self.t2._apply(np.broadcast_to, shape=(3, 10, 5)) assert t2_broadcast.shape == (3, 10, 5) assert np.all(t2_broadcast.jd1 == self.t2.jd1) assert np.may_share_memory(t2_broadcast.jd1, self.t2.jd1) assert t2_broadcast.location.shape == t2_broadcast.shape assert np.may_share_memory(t2_broadcast.location, self.t2.location)
def radec_to_altaz(radec: SkyCoord, time: Time, observer: EarthLocation = nenufar_position, fast_compute: bool = True) -> SkyCoord: r""" Converts a celestial object equatorial coordinates to horizontal coordinates. If ``fast_compute=True`` is selected, the computation is accelerated using Local Sidereal Time approximation (see :func:`~nenupy.astro.astro_tools.local_sidereal_time`). The altitude :math:`\theta` and azimuth :math:`\varphi` are computed as follows: .. math:: \cases{ \sin(\theta) = \sin(\delta) \sin(l) + \cos(\delta) \cos(l) \cos(h)\\ \cos(\varphi) = \frac{\sin(\delta) - \sin(l) \sin(\theta)}{\cos(l)\cos(\varphi)} } with :math:`\delta` the object's declination, :math:`l` the ``observer``'s latitude and :math:`h` the Local Hour Angle (see :func:`~nenupy.astro.astro_tools.hour_angle`). If :math:`\sin(h) \geq 0`, then :math:`\varphi = 2 \pi - \varphi`. Otherwise, :meth:`~astropy.coordinates.SkyCoord.transform_to` is used. :param radec: Celestial object equatorial coordinates. :type radec: :class:`~astropy.coordinates.SkyCoord` :param time: Coordinated universal time. :type time: :class:`~astropy.time.Time` :param observer: Earth location where the observer is at. Default is NenuFAR's position. :type observer: :class:`~astropy.coordinates.EarthLocation` :param fast_compute: If set to ``True``, it enables faster computation time for the conversion, mainly relying on an approximation of the local sidereal time. All other values would lead to accurate coordinates computation. Differences in coordinates values are of the order of :math:`10^{-2}` degrees or less. :type fast_compute: `bool` :returns: Celestial object's horizontal coordinates. If either ``radec`` or ``time`` are 1D arrays, the resulting object will be of shape ``(time, positions)``. :rtype: :class:`~astropy.coordinates.SkyCoord` :Example: .. code-block:: python from nenupy.astro import radec_to_altaz from astropy.time import Time from astropy.coordinates import SkyCoord altaz = radec_to_altaz( radec=SkyCoord([300, 200], [45, 45], unit="deg"), time=Time("2022-01-01T12:00:00"), fast_compute=True ) """ if not time.isscalar: time = time.reshape((time.size, 1)) radec = radec.reshape((1, radec.size)) if fast_compute: radec = radec.transform_to(FK5(equinox=time)) ha = hour_angle(radec=radec, time=time, observer=observer, fast_compute=fast_compute) two_pi = Angle(360.0, unit='deg') ha = hour_angle(radec=radec, time=time, observer=observer, fast_compute=fast_compute) sin_dec = np.sin(radec.dec.rad) cos_dec = np.cos(radec.dec.rad) sin_lat = np.sin(observer.lat.rad) cos_lat = np.cos(observer.lat.rad) # Compute elevation sin_elevation = sin_dec * sin_lat + cos_dec * cos_lat * np.cos(ha.rad) elevation = Latitude(np.arcsin(sin_elevation), unit="rad") # Compute azimuth cos_azimuth = (sin_dec - np.sin(elevation.rad)*sin_lat)/\ (np.cos(elevation.rad)*cos_lat) azimuth = Longitude(np.arccos(cos_azimuth), unit="rad") if azimuth.isscalar: if np.sin(ha.rad) > 0: azimuth *= -1 azimuth += two_pi else: posMask = np.sin(ha.rad) > 0 azimuth[posMask] *= -1 azimuth[posMask] += two_pi return SkyCoord(azimuth, elevation, frame=AltAz(obstime=time, location=observer)) else: return radec.transform_to(AltAz(obstime=time, location=observer))