def _get_4pi_dipole(self, detector, proper, quats, ind, dipole): # Rotate velocity into the detector frame. This must be the same # frame (Pxx or Dxx) the 4pi coefficients were computed in. # Pure qa.inv(quats) rotates into the Dxx frame # Adding psi_uv rotates into Pxx if self.full4pi == 'npipe': # NPIPE factors are computed in Dxx psi_uv = np.radians(self.rimo[detector].psi_uv) psi_pol = np.radians(self.rimo[detector].psi_pol) polrot = qa.rotation(ZAXIS, -(psi_uv + psi_pol)) vel = qa.rotate(qa.inv(qa.mult(np.atleast_2d(quats)[ind], polrot)), proper * CINV) else: # LFI factors are in Pxx psi_pol = np.radians(self.rimo[detector].psi_pol) polrot = qa.rotation(ZAXIS, -psi_pol) vel = qa.rotate(qa.inv(qa.mult(np.atleast_2d(quats)[ind], polrot)), proper * CINV) dipole_amplitude = self.get_fourpi_prod(vel, detector, 0) # relativistic corrections for the quadrupole vel2 = vel.T.copy() for i in range(3): dipole_amplitude += self.q * vel2[i] \ * self.get_fourpi_prod(vel, detector, i + 1) dipole_amplitude *= self.tcmb if self.full4pi == 'npipe': # Apply beam efficiency correction so the template # reflects unit response to a dipole signal dipole_amplitude /= self._last_params[4] dipole[ind] = dipole_amplitude return
def _get_pntg(self, detector, local_start, n, deaberrate=True, margin=0, velocity=None, full_output=False, satquats=None): if detector[-1] in '01' and detector[-2] != '-': # Single diode, use common radiometer pointing det = to_radiometer(detector) else: det = detector detquat = self.RIMO[det].quat if satquats is None: # Get the satellite attitude satquats, _ = read_eff( local_start - margin, n + 2 * margin, self.globalfirst, self.local_samples[0], self.ringdb, self.ringdb_path, self.freq, self.effdir_pntg, 'attitude', self.satquatmask, self.eff_cache, self.cache, self.filenames[self.effdir_pntg]) satquats = satquats.T.copy() # Rotate into detector frame and convert to desired format quats = qa.mult(qa.norm(satquats), detquat) if deaberrate: # Correct for aberration if velocity is None: velocity = self._get_velocity(local_start, n, margin=margin) # Manipulate the quaternions in buffers not to allocate excessive # Python memory buflen = 10000 for istart in range(0, len(quats), buflen): istop = min(istart + buflen, len(quats)) ind = slice(istart, istop) if deaberrate: vec = qa.rotate(quats[ind], ZAXIS) abvec = np.cross(vec, velocity[ind]) lens = np.linalg.norm(abvec, axis=1) ang = lens * CINV abvec /= np.tile(lens, (3, 1)).T # Normalize for direction abquat = qa.rotation(abvec, -ang) quats[ind] = qa.mult(abquat, quats[ind]) if self.coordquat is not None: quats[ind] = qa.mult(self.coordquat, quats[ind]) if full_output: return quats, satquats else: return quats
def rotate_focalplane(args, data, comm): """ The LAT focalplane projected on the sky rotates as the cryostat (co-rotator) tilts. Usually the tilt is the same as the observing elevation to maintain constant angle between the mirror and the cryostat. This method must be called *before* expanding the detector pointing from boresight. """ log = Logger.get() timer = Timer() timer.start() for obs in data.obs: if obs["telescope"] != "LAT": continue tod = obs["tod"] cache_name = "corotator_angle_deg" if tod.cache.exists(cache_name): corotator_angle = tod.cache.reference(cache_name) else: # If a vector of co-rotator angles isn't already cached, # make one now from the observation metadata. This will # ensure they get recorded in the so3g files. corotator_angle = obs["corotator_angle_deg"] offset, nsample = tod.local_samples tod.cache.put(cache_name, np.zeros(nsample) + corotator_angle) el = np.degrees(tod.read_boresight_el()) rot = qa.rotation( ZAXIS, np.radians(corotator_angle + el + LAT_COROTATOR_OFFSET_DEG) ) quats = tod.read_boresight() quats[:] = qa.mult(quats, rot) try: # If there are horizontal boresight quaternions, they need # to be rotated as well. quats = tod.read_boresight(azel=True) quats[:] = qa.mult(quats, rot) except Exception as e: pass if comm.comm_world is None or comm.comm_world.rank == 0: timer.report_clear("Rotate focalplane") return
def main(): parser = argparse.ArgumentParser( description="This program measures the median offset of subset of " "detectors from boresight.", usage="get_wafer_offset [options] (use --help for details)") group = parser.add_mutually_exclusive_group(required=True) group.add_argument( "--tube_slots", help="Comma-separated list of optics tube slots: c1 (UHF), i5 (UHF), " " i6 (MF), i1 (MF), i3 (MF), i4 (MF), o6 (LF). ") group.add_argument("--wafer_slots", help="Comma-separated list of optics tube slots. ") parser.add_argument("--reverse", action="store_true", help="Reverse offsets") # LAT specific params parser.add_argument( "--corotate-lat", required=False, action="store_true", help="Rotate LAT receiver to maintain focalplane orientation", dest="corotate_lat", ) parser.add_argument( "--no-corotate-lat", required=False, action="store_false", help="Do not Rotate LAT receiver to maintain focalplane orientation", dest="corotate_lat", ) parser.set_defaults(corotate_lat=True) parser.add_argument( "--elevation-deg", required=False, type=np.float, help="Observing elevation", ) args = parser.parse_args() hw = hardware.get_example() # Which telescope? if args.wafer_slots is not None: wafer_slots = args.wafer_slots.split(",") wafer_map = hw.wafer_map() tube_slots = [wafer_map["tube_slots"][ws] for ws in wafer_slots] else: tube_slots = args.tube_slots.split(",") telescope = None for tube_slot in tube_slots: for telescope_name, telescope_data in hw.data["telescopes"].items(): if tube_slot in telescope_data["tube_slots"]: if telescope is None: telescope = telescope_name elif telescope != telescope.name: raise RuntimeError( f"Tubes '{tube_slots}' span more than one telescope") if telescope is None: raise RuntimeError( f"Failed to match tube_slot = '{tube_slot}' with a telescope") # Which detectors? hw.data["detectors"] = hardware.sim_telescope_detectors(hw, telescope) match = {} tube_slots = None if args.wafer_slots is not None: match["wafer_slot"] = args.wafer_slots.split(",") elif args.tube_slots is not None: tube_slots = args.tube_slots.split(",") hw = hw.select(tube_slots=tube_slots, match=match) ndet = len(hw.data["detectors"]) # print(f"tube_slots = {tube_slots}, match = {match} leaves {ndet} detectors") # Optional corotator rotation if telescope == "LAT": if args.corotate_lat: rot = qa.rotation(ZAXIS, np.radians(LAT_COROTATOR_OFFSET_DEG)) else: if args.elevation_deg is None: raise RuntimeError( "You must set the observing elevation when not co-rotating." ) rot = qa.rotation( ZAXIS, np.radians(args.elevation_deg - 60 + LAT_COROTATOR_OFFSET_DEG)) else: if args.elevation_deg is not None: raise RuntimeError("Observing elevation does not matter for SAT") rot = None # Average detector offset vec_mean = np.zeros(3) for det_name, det_data in hw.data["detectors"].items(): quat = det_data["quat"] if rot is not None: quat = qa.mult(rot, quat) vec = qa.rotate(quat, ZAXIS) vec_mean += vec vec_mean /= ndet # Radius all_dist = [] for det_name, det_data in hw.data["detectors"].items(): quat = det_data["quat"] if rot is not None: quat = qa.mult(rot, quat) vec = qa.rotate(quat, ZAXIS) all_dist.append(np.degrees(np.arccos(np.dot(vec_mean, vec)))) dist_max = np.amax(all_dist) # Wafers if args.tube_slots is None: wafer_slots = set(wafer_slots) else: wafer_slots = set() for tube_slot in tube_slots: wafer_slots.update(hw.data["tube_slots"][tube_slot]["wafer_slots"]) waferstring = "" for wafer_slot in sorted(wafer_slots): waferstring += f" {wafer_slot}" # Translate into Az/El offsets at el=0 rot = hp.Rotator(rot=[0, 90, 0]) vec_mean = rot(vec_mean) az_offset, el_offset = hp.vec2dir(vec_mean, lonlat=True) el_offset *= -1 if args.reverse: az_offset *= -1 el_offset *= -1 print(f"{az_offset:.3f} {el_offset:.3f} {dist_max:.3f}" + waferstring) return
def load_fp(args, comm): start = MPI.Wtime() autotimer = timing.auto_timer() fp = None # Load focalplane information nullquat = np.array([0, 0, 0, 1], dtype=np.float64) if comm.comm_world.rank == 0: if args.fp is None: # in this case, create a fake detector at the boresight # with a pure white noise spectrum. fake = {} fake['quat'] = nullquat fake['fwhm'] = 30.0 fake['fknee'] = 0.0 fake['fmin'] = 1e-9 fake['alpha'] = 1.0 fake['NET'] = 1.0 fake['color'] = 'r' fp = {} # Second detector at 22.5 degree polarization angle fp['bore1'] = fake fake2 = {} zrot = qa.rotation(ZAXIS, 22.5 * degree) fake2['quat'] = qa.mult(fake['quat'], zrot) fake2['fwhm'] = 30.0 fake2['fknee'] = 0.0 fake2['fmin'] = 1e-9 fake2['alpha'] = 1.0 fake2['NET'] = 1.0 fake2['color'] = 'r' fp['bore2'] = fake2 # Third detector at 45 degree polarization angle fake3 = {} zrot = qa.rotation(ZAXIS, 45 * degree) fake3['quat'] = qa.mult(fake['quat'], zrot) fake3['fwhm'] = 30.0 fake3['fknee'] = 0.0 fake3['fmin'] = 1e-9 fake3['alpha'] = 1.0 fake3['NET'] = 1.0 fake3['color'] = 'r' fp['bore3'] = fake3 # Fourth detector at 67.5 degree polarization angle fake4 = {} zrot = qa.rotation(ZAXIS, 67.5 * degree) fake4['quat'] = qa.mult(fake['quat'], zrot) fake4['fwhm'] = 30.0 fake4['fknee'] = 0.0 fake4['fmin'] = 1e-9 fake4['alpha'] = 1.0 fake4['NET'] = 1.0 fake4['color'] = 'r' fp['bore4'] = fake4 else: with open(args.fp, 'rb') as p: fp = pickle.load(p) fp = comm.comm_world.bcast(fp, root=0) stop = MPI.Wtime() elapsed = stop - start if comm.comm_world.rank == 0: print('Create focalplane: {:.2f} seconds'.format(stop - start), flush=args.flush) start = stop if args.debug: if comm.comm_world.rank == 0: outfile = '{}/focalplane.png'.format(args.outdir) tt.plot_focalplane(fp, 6, 6, outfile) detectors = sorted(fp.keys()) detweights = {} for d in detectors: net = fp[d]['NET'] detweights[d] = 1.0 / (args.samplerate * net * net) return fp, detweights
def load_fp(args, comm): start = MPI.Wtime() autotimer = timing.auto_timer() fp = None # Load focalplane information nullquat = np.array([0,0,0,1], dtype=np.float64) if comm.comm_world.rank == 0: if args.fp is None: # in this case, create a fake detector at the boresight # with a pure white noise spectrum. fake = {} fake['quat'] = nullquat fake['fwhm'] = 30.0 fake['fknee'] = 0.0 fake['fmin'] = 1e-9 fake['alpha'] = 1.0 fake['NET'] = 1.0 fake['color'] = 'r' fp = {} # Second detector at 22.5 degree polarization angle fp['bore1'] = fake fake2 = {} zrot = qa.rotation(ZAXIS, 22.5*degree) fake2['quat'] = qa.mult(fake['quat'], zrot) fake2['fwhm'] = 30.0 fake2['fknee'] = 0.0 fake2['fmin'] = 1e-9 fake2['alpha'] = 1.0 fake2['NET'] = 1.0 fake2['color'] = 'r' fp['bore2'] = fake2 # Third detector at 45 degree polarization angle fake3 = {} zrot = qa.rotation(ZAXIS, 45*degree) fake3['quat'] = qa.mult(fake['quat'], zrot) fake3['fwhm'] = 30.0 fake3['fknee'] = 0.0 fake3['fmin'] = 1e-9 fake3['alpha'] = 1.0 fake3['NET'] = 1.0 fake3['color'] = 'r' fp['bore3'] = fake3 # Fourth detector at 67.5 degree polarization angle fake4 = {} zrot = qa.rotation(ZAXIS, 67.5*degree) fake4['quat'] = qa.mult(fake['quat'], zrot) fake4['fwhm'] = 30.0 fake4['fknee'] = 0.0 fake4['fmin'] = 1e-9 fake4['alpha'] = 1.0 fake4['NET'] = 1.0 fake4['color'] = 'r' fp['bore4'] = fake4 else: with open(args.fp, 'rb') as p: fp = pickle.load(p) fp = comm.comm_world.bcast(fp, root=0) stop = MPI.Wtime() elapsed = stop - start if comm.comm_world.rank == 0: print('Create focalplane: {:.2f} seconds'.format(stop-start), flush=args.flush) start = stop if args.debug: if comm.comm_world.rank == 0: outfile = '{}/focalplane.png'.format(args.outdir) tt.plot_focalplane(fp, 6, 6, outfile) detectors = sorted(fp.keys()) detweights = {} for d in detectors: net = fp[d]['NET'] detweights[d] = 1.0 / (args.samplerate * net * net) return fp, detweights
def load_focalplane(args, comm, schedule): focalplane = None # Load focalplane information if comm.comm_world is None or comm.comm_world.rank == 0: if args.focalplane is None: detector_data = {} ZAXIS = np.array([0, 0, 1.0]) # in this case, create a fake detector at the boresight # with a pure white noise spectrum. fake = {} fake["quat"] = np.array([0, 0, 0, 1.0]) fake["fwhm"] = 30.0 fake["fknee"] = 0.0 fake["fmin"] = 1e-9 fake["alpha"] = 1.0 fake["NET"] = 1.0 fake["color"] = "r" detector_data["bore1"] = fake # Second detector at 22.5 degree polarization angle fake2 = {} zrot = qa.rotation(ZAXIS, np.radians(22.5)) fake2["quat"] = qa.mult(fake["quat"], zrot) fake2["fwhm"] = 30.0 fake2["fknee"] = 0.0 fake2["fmin"] = 1e-9 fake2["alpha"] = 1.0 fake2["NET"] = 1.0 fake2["color"] = "r" detector_data["bore2"] = fake2 # Third detector at 45 degree polarization angle fake3 = {} zrot = qa.rotation(ZAXIS, np.radians(45)) fake3["quat"] = qa.mult(fake["quat"], zrot) fake3["fwhm"] = 30.0 fake3["fknee"] = 0.0 fake3["fmin"] = 1e-9 fake3["alpha"] = 1.0 fake3["NET"] = 1.0 fake3["color"] = "r" detector_data["bore3"] = fake3 # Fourth detector at 67.5 degree polarization angle fake4 = {} zrot = qa.rotation(ZAXIS, np.radians(67.5)) fake4["quat"] = qa.mult(fake["quat"], zrot) fake4["fwhm"] = 30.0 fake4["fknee"] = 0.0 fake4["fmin"] = 1e-9 fake4["alpha"] = 1.0 fake4["NET"] = 1.0 fake4["color"] = "r" detector_data["bore4"] = fake4 focalplane = Focalplane(detector_data=detector_data, sample_rate=args.sample_rate) else: focalplane = Focalplane(fname_pickle=args.focalplane, sample_rate=args.sample_rate) if comm.comm_world is not None: focalplane = comm.comm_world.bcast(focalplane, root=0) if args.debug: if comm.comm_world is None or comm.comm_world.rank == 0: outfile = "{}/focalplane.png".format(args.outdir) plot_focalplane(focalplane, 6, 6, outfile) schedule.telescope.focalplane = focalplane detweights = focalplane.detweights return detweights
def translate(self, quats, timestamps, psi_pol=None): """ Translate the input quaternions into planet-centric coordinates and convert the offsets to arc minutes. The quaternions ARE EXPECTED to be in galactic coordinates although adding a rotation would be straightforward. The output coordinate system is Pxx, unless either a) quats do not include the psi_pol rotation or b) psi_pol is provided in radians. translate() will then remove the psi_pol rotation from the quaternions """ if timestamps[0] > self.time[-1] or timestamps[-1] < self.time[0]: raise Exception( 'There is no overlap in the stored and provided time stamps.') if psi_pol is not None: pol_quat = qa.rotation(zaxis, -psi_pol) zquat = qa.mult(pol_quat, self.zquat) else: zquat = self.zquat my_quats = qa.mult(quats, zquat) # From X-axis to detector position # Transform the planet positions into quaternions tol = 3600. ind = np.logical_and(self.time >= timestamps[0] - tol, self.time <= timestamps[-1] + tol) nind = np.sum(ind) planettime = self.time[ind] planetglon = self.glon[ind] planetglat = self.glat[ind] planetquat = np.zeros([nind, 4]) # ZYZ rotation to put the X-axis to the planet position phi = planetglon * degree theta = -planetglat * degree psi = 0 planetquat[:, 3] = np.cos(.5 * theta) * np.cos(.5 * (phi + psi)) planetquat[:, 0] = -np.sin(.5 * theta) * np.sin(.5 * (phi - psi)) planetquat[:, 1] = np.sin(.5 * theta) * np.cos(.5 * (phi - psi)) planetquat[:, 2] = np.cos(.5 * theta) * np.sin(.5 * (phi + psi)) targetquats = qa.slerp(timestamps, planettime, planetquat) planetvec = qa.rotate(targetquats, xaxis) # Rotate the planet into Dxx frame (detector on X-axis) planetvec = qa.rotate(qa.inv(my_quats), planetvec) # The detector position relative to the planet is the inverse # of the planet coordinates in Dxx lon, lat = hp.vec2dir(planetvec.T, lonlat=True) az, el = -np.array([lon, lat]) * 60 # To arc minutes return az, el
def from_angles(az, el): elquat = qa.rotation(yaxis, np.radians(90 - el)) azquat = qa.rotation(zaxis, np.radians(az)) return qa.mult(azquat, elquat)
from scipy.constants import c, h, k import healpy as hp import numpy as np import toast.qarray as qa import toast.timing as timing __path__ = os.path.dirname(__file__) PARAM_PATH = os.path.join(__path__, 'lfi_fsl_data/DX12_dBdTcmb_release_S_param.csv') PARAM_PATH_NPIPE = os.path.join(__path__, 'lfi_fsl_data/npipe_s_factors.csv') XAXIS, YAXIS, ZAXIS = np.eye(3, dtype=np.float64) SPINANGLE = np.radians(85) SPINROT = qa.rotation(YAXIS, np.pi / 2 - SPINANGLE) # Inverse light speed in km / s (the assumed unit for velocity) CINV = 1e3 / c class Dipoler(): """ Dipoler objects return the orbital and solar system dipole as seen by a specified detector. """ def __init__(self, solsys_speed=370.082, solsys_glon=264.00, solsys_glat=48.24, TCMB=2.72548, coord='G',