def get_azel(self): """ Translate the boresight az/el quaternions into azimuth and elevation """ quats = self.cache.reference("boresight_azel") theta, phi = qa.to_position(quats) self._az = (-phi % (2 * np.pi)) self._el = np.pi / 2 - theta if len(self._az) > 0: azmin = np.amin(self._az) azmax = np.amax(self._az) elmin = np.amin(self._el) elmax = np.amax(self._el) else: azmin = 1000 azmax = -1000 elmin = 1000 elmax = -1000 if self.mpicomm is not None: azmin = self.mpicomm.allreduce(azmin, MPI.MIN) azmax = self.mpicomm.allreduce(azmax, MPI.MAX) elmin = self.mpicomm.allreduce(elmin, MPI.MIN) elmax = self.mpicomm.allreduce(elmax, MPI.MAX) self.scan_range = (azmin, azmax, elmin, elmax) return
def _observe_sso(self, sso_az, sso_el, sso_dist, sso_dia, tod, comm, prefix): """ Observe the SSO with each detector in tod """ log = Logger.get() rank = 0 if comm is not None: rank = comm.rank tmr = Timer() if self._report_timing: if comm is not None: comm.Barrier() tmr.start() nsamp = tod.local_samples[1] if rank == 0: log.info("{}Observing the SSO signal".format(prefix)) for det in tod.local_dets: # Cache the output signal cachename = "{}_{}".format(self._out, det) if tod.cache.exists(cachename): ref = tod.cache.reference(cachename) else: ref = tod.cache.create(cachename, np.float64, (nsamp, )) try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det) except Exception as e: azelquat = tod.read_pntg(detector=det, azel=True) # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, phi = qa.to_position(azelquat) # Azimuth is measured in the opposite direction # than longitude az = 2 * np.pi - phi el = np.pi / 2 - theta beam, radius = self._get_beam_map(det, sso_dia) # Interpolate the beam map at appropriate locations x = (az - sso_az) * np.cos(el) y = el - sso_el r = np.sqrt(x**2 + y**2) good = r < radius sig = beam(x[good], y[good], grid=False) ref[:][good] += sig del ref, sig, beam if self._report_timing: if comm is not None: comm.Barrier() if rank == 0: tmr.stop() tmr.report("{}OpSimSSO: Observe signal".format(prefix)) return
def get_elevation_noise(args, comm, data, key="noise"): """ Insert elevation-dependent noise """ timer = Timer() timer.start() # fsample = args.sample_rate for obs in data.obs: tod = obs["tod"] fp = obs["focalplane"] noise = obs[key] for det in tod.local_dets: if det not in noise.keys: raise RuntimeError( 'Detector "{}" does not have a PSD in the noise object'. format(det)) A = fp[det]["A"] C = fp[det]["C"] psd = noise.psd(det) try: # Some TOD classes provide a shortcut to Az/El _, el = tod.read_azel(detector=det) except Exception: azelquat = tod.read_pntg(detector=det, azel=True) # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, _ = qa.to_position(azelquat) el = np.pi / 2 - theta el = np.median(el) # Scale the analytical noise PSD. Pivot is at el = 50 deg. psd[:] *= (A / np.sin(el) + C)**2 timer.stop() if comm.world_rank == 0: timer.report("Elevation noise") return
def subtract_signal(self, tod, cworld, rank, masksampler, mapsampler, local_intervals): """ Subtract a signal estimate from the TOD and update the flags for noise estimation. """ start_signal_subtract = MPI.Wtime() for det in tod.local_dets: if rank == 0: print('Subtracting signal for {}'.format(det), flush=True) tod.cache.report() fsample = self._rimo[det].fsample epsilon = self._rimo[det].epsilon eta = (1 - epsilon) / (1 + epsilon) signal = tod.local_signal(det, name=self._signal) flags = tod.local_flags(det, name=self._flags) flags &= self._detmask for ival in local_intervals: ind = slice(ival.first, ival.last + 1) sig = signal[ind] flg = flags[ind] quat = tod.local_pointing(det)[ind] if self._pol: theta, phi, psi = qa.to_angles(quat) iw = np.ones_like(theta) qw = eta * np.cos(2 * psi) uw = eta * np.sin(2 * psi) iquw = np.column_stack([iw, qw, uw]) else: theta, phi = qa.to_position(quat) if masksampler is not None: maskflg = masksampler.at(theta, phi) < 0.5 flg[maskflg] |= 255 if mapsampler is not None: if self._pol: bg = mapsampler.atpol(theta, phi, iquw) else: bg = mapsampler.at(theta, phi) if self._calibrate_signal_estimate: good = flg == 0 ngood = np.sum(good) if ngood > 1: templates = np.vstack([np.ones(ngood), bg[good]]) invcov = np.dot(templates, templates.T) cov = np.linalg.inv(invcov) proj = np.dot(templates, sig[good]) coeff = np.dot(cov, proj) bg = coeff[0] + coeff[1] * bg sig -= bg cworld.barrier() stop_signal_subtract = MPI.Wtime() if rank == 0: print('TOD signal-subtracted in {:.2f} s'.format( stop_signal_subtract - start_signal_subtract), flush=True) return fsample
def _save_tod(self, obsname, tod, times, istart, nind, ind, comm, common_ref): import pickle rank = 0 if comm is not None: rank = comm.rank t = times[ind] tmin, tmax = t[0], t[-1] outdir = "snapshots" if rank == 0: try: os.makedirs(outdir) except FileExistsError: pass try: good = common_ref[ind] & tod.UNSTABLE == 0 except: good = slice(0, nind) for det in tod.local_dets: # Cache the output signal cachename = "{}_{}".format(self._out, det) ref = tod.cache.reference(cachename)[ind] try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det, local_start=istart, n=nind) except Exception as e: azelquat = tod.read_pntg(detector=det, local_start=istart, n=nind, azel=True) # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, phi = qa.to_position(azelquat) # Azimuth is measured in the opposite direction # than longitude az = 2 * np.pi - phi el = np.pi / 2 - theta fn = os.path.join( outdir, "atm_tod_{}_{}_t_{}_{}.pck".format(obsname, det, int(tmin), int(tmax)), ) with open(fn, "wb") as fout: pickle.dump([det, t[good], az[good], el[good], ref[good]], fout) return
def _observe_sss(self, sssmap, tod, comm, prefix): """ Use healpy bilinear interpolation to observe the ground signal map """ log = Logger.get() rank = 0 if comm is not None: rank = comm.rank timer = Timer() if self._report_timing: if comm is not None: comm.Barrier() timer.start() nsamp = tod.local_samples[1] if rank == 0: log.info("{}Observing the scan-synchronous signal".format(prefix)) for det in tod.local_dets: # Cache the output signal cachename = "{}_{}".format(self._out, det) if tod.cache.exists(cachename): ref = tod.cache.reference(cachename) else: ref = tod.cache.create(cachename, np.float64, (nsamp, )) try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det) phi = 2 * np.pi - az theta = np.pi / 2 - el except Exception as e: azelquat = tod.read_pntg(detector=det, azel=True) # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, phi = qa.to_position(azelquat) # Azimuth is measured in the opposite direction # than longitude # az = 2 * np.pi - phi # el = np.pi / 2 - theta ref[:] += hp.get_interp_val(sssmap, theta, phi) del ref if self._report_timing: if comm is not None: comm.Barrier() if rank == 0: timer.stop() timer.report("{}OpSimSSS: Observe signal".format(prefix)) return
def _get_weights(self, obs): """ Evaluate the special pointing matrix """ tod = obs["tod"] nsample = tod.local_samples[1] focalplane = obs["focalplane"] # Create one constant signal for the observation and make it an # alias for all detectors tod.cache.put( self.dummy_name, np.ones(nsample, dtype=np.float64), replace=True, ) for det in tod.local_dets: # measure the scan direction wrt the local meridian # for each sample quat = tod.read_pntg(detector=det) theta, phi = qa.to_position(quat) theta = np.pi / 2 - theta # scan direction across the reference sample dphi = (np.roll(phi, -1) - np.roll(phi, 1)) dtheta = np.roll(theta, -1) - np.roll(theta, 1) # except first and last sample for dx, x in (dphi, phi), (dtheta, theta): dx[0] = x[1] - x[0] dx[-1] = x[-1] - x[-2] # scale dphi to on-sky dphi *= np.cos(theta) # Avoid overflows tiny = np.abs(dphi) < 1e-30 if np.any(tiny): ang = np.zeros(nsample) ang[tiny] = np.sign(dtheta) * np.sign(dphi) * np.pi / 2 not_tiny = np.logical_not(tiny) ang[not_tiny] = np.arctan(dtheta[not_tiny] / dphi[not_tiny]) else: ang = np.arctan(dtheta / dphi) weights_out = np.vstack( [np.ones(nsample), np.cos(2 * ang), np.sin(2 * ang)]).T weights_name_out = f"{self.weight_name}_{det}" tod.cache.put(weights_name_out, weights_out, replace=True) # Need a constant signal to map signal_name = f"{self.signal_name}_{det}" tod.cache.add_alias(signal_name, self.dummy_name) return
def get_elevation_noise(args, comm, data, key="noise"): """ Insert elevation-dependent noise """ if args.no_elevation_noise: return timer = Timer() timer.start() # fsample = args.sample_rate for obs in data.obs: tod = obs["tod"] fp = obs["focalplane"] noise = obs[key] for det in tod.local_dets: if det not in noise.keys: raise RuntimeError( 'Detector "{}" does not have a PSD in the noise object'. format(det)) A = fp[det]["A"] C = fp[det]["C"] psd = noise.psd(det) # We only consider a small range of samples for the elevation n = tod.local_samples[1] istart = max(0, n // 2 - 1000) istop = min(n, n // 2 + 1000) try: # Some TOD classes provide a shortcut to Az/El el = tod.read_azel(detector=det, local_start=istart, n=istop - istart)[1] except Exception: azelquat = tod.read_pntg(detector=det, azel=True, local_start=istart, n=istop - istart) # Convert Az/El quaternion of the detector back into # angles for the simulation. theta = qa.to_position(azelquat)[0] el = np.pi / 2 - theta el = np.median(el) # Scale the analytical noise PSD. Pivot is at el = 50 deg. psd[:] *= (A / np.sin(el) + C)**2 if comm.world_rank == 0: timer.report_clear("Elevation noise") return
def _save_tod(self, obsname, tod, times, istart, nind, ind, comm, common_ref): import pickle t = times[ind] tmin, tmax = t[0], t[-1] outdir = "snapshots" if comm.rank == 0: try: os.makedirs(outdir) except FileExistsError: pass try: good = common_ref[ind] & tod.UNSTABLE == 0 except: good = slice(0, nind) for det in tod.local_dets: # Cache the output signal cachename = "{}_{}".format(self._out, det) ref = tod.cache.reference(cachename)[ind] try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det, local_start=istart, n=nind) except Exception as e: azelquat = tod.read_pntg( detector=det, local_start=istart, n=nind, azel=True ) # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, phi = qa.to_position(azelquat) # Azimuth is measured in the opposite direction # than longitude az = 2 * np.pi - phi el = np.pi / 2 - theta fn = os.path.join( outdir, "atm_tod_{}_{}_t_{}_{}.pck".format(obsname, det, int(tmin), int(tmax)), ) with open(fn, "wb") as fout: pickle.dump([det, t[good], az[good], el[good], ref[good]], fout) return
def _flag_ssos(self, sso_azs, sso_els, tod, focalplane): """ Flag the SSO for each detector in tod """ nsamp = tod.local_samples[1] for det in tod.local_dets: # Cache the output signal cachename = "{}_{}".format(self.flag_name, det) if tod.cache.exists(cachename): ref = tod.cache.reference(cachename) else: ref = tod.cache.create(cachename, np.uint8, (nsamp, )) try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det) except Exception as e: azelquat = tod.read_pntg(detector=det, azel=True) # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, phi = qa.to_position(azelquat) # Azimuth is measured in the opposite direction # than longitude az = 2 * np.pi - phi el = np.pi / 2 - theta cosel = np.cos(el) for sso_az, sso_el, sso_radius in zip(sso_azs, sso_els, self.sso_radii): # Flag samples within search radius x = (az - sso_az) * cosel y = el - sso_el rsquared = x**2 + y**2 good = rsquared < sso_radius**2 ref[good] |= self.flag_mask del ref return
def exec(self, data): for obs in data.obs: tod = obs["tod"] focalplane = obs["focalplane"] # Get HWP angle chi = tod.local_hwp_angle() for det in tod.local_dets: signal = tod.local_signal(det, self._name) band = focalplane[det]["band"] freq = { "SAT_f030": "027", "SAT_f040": "039", "SAT_f090": "093", "SAT_f150": "145", "SAT_f230": "225", "SAT_f290": "278", }[band] # Get incident angle det_quat = focalplane[det]["quat"] det_theta, det_phi, det_psi = qa.to_angles(det_quat) # Get observing elevation azelquat = tod.read_pntg(detector=det, azel=True) el = np.pi / 2 - qa.to_position(azelquat)[0] # Get polarization weights iweights = np.ones(signal.size) qweights = np.cos(2 * det_psi) uweights = np.sin(2 * det_psi) # Interpolate HWPSS to incident angle theta_deg = np.degrees(det_theta) itheta_high = np.searchsorted(self.thetas, theta_deg) itheta_low = itheta_high - 1 theta_low = self.thetas[itheta_low] theta_high = self.thetas[itheta_high] r = (theta_deg - theta_low) / (theta_high - theta_low) transmission = ( (1 - r) * self.all_stokes[freq]["transmission"][itheta_low] + r * self.all_stokes[freq]["transmission"][itheta_high]) reflection = ( (1 - r) * self.all_stokes[freq]["reflection"][itheta_low] + r * self.all_stokes[freq]["reflection"][itheta_high]) emission = ( (1 - r) * self.all_stokes[freq]["emission"][itheta_low] + r * self.all_stokes[freq]["emission"][itheta_high]) # Scale HWPSS for observing elevation el_ref = np.radians(50) scale = np.sin(el_ref) / np.sin(el) # Observe HWPSS with the detector iquv = (transmission + reflection).T iquss = (iweights * np.interp(chi, self.chis, iquv[0]) + qweights * np.interp(chi, self.chis, iquv[1]) + uweights * np.interp(chi, self.chis, iquv[2])) * scale iquv = emission.T iquss += (iweights * np.interp(chi, self.chis, iquv[0]) + qweights * np.interp(chi, self.chis, iquv[1]) + uweights * np.interp(chi, self.chis, iquv[2])) iquss -= np.median(iquss) # Co-add with the cached signal signal += iquss return
def _observe_atmosphere( self, sim, tod, comm, prefix, common_ref, istart, nind, ind, scan_range, times, absorption, ): azmin, azmax, elmin, elmax = scan_range nsamp = tod.local_samples[1] if self._report_timing: comm.Barrier() tstart = MPI.Wtime() if comm.rank == 0: print(prefix + "Observing the atmosphere", flush=self._flush) for det in tod.local_dets: # Cache the output signal cachename = "{}_{}".format(self._out, det) if tod.cache.exists(cachename): ref = tod.cache.reference(cachename) else: ref = tod.cache.create(cachename, np.float64, (nsamp,)) # Cache the output flags flag_ref = tod.local_flags(det, self._flag_name) if self._apply_flags: good = np.logical_and( common_ref[ind] & self._common_flag_mask == 0, flag_ref[ind] & self._flag_mask == 0, ) ngood = np.sum(good) else: try: good = common_ref[ind] & tod.UNSTABLE == 0 ngood = np.sum(good) except: good = slice(0, nind) ngood = nind if ngood == 0: continue try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det, local_start=istart, n=nind) az = az[good] el = el[good] except Exception as e: azelquat = tod.read_pntg( detector=det, local_start=istart, n=nind, azel=True )[good] # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, phi = qa.to_position(azelquat) # Azimuth is measured in the opposite direction # than longitude az = 2 * np.pi - phi el = np.pi / 2 - theta atmdata = np.zeros(ngood, dtype=np.float64) if np.ptp(az) < np.pi: azmin_det = np.amin(az) azmax_det = np.amax(az) else: # Scanning across the zero azimuth. azmin_det = np.amin(az[az > np.pi]) - 2 * np.pi azmax_det = np.amax(az[az < np.pi]) elmin_det = np.amin(el) elmax_det = np.amax(el) if ( not (azmin <= azmin_det and azmax_det <= azmax) and not ( azmin <= azmin_det - 2 * np.pi and azmax_det - 2 * np.pi <= azmax ) ) or not (elmin <= elmin_det and elmin_det <= elmax): raise RuntimeError( prefix + "Detector Az/El: [{:.5f}, {:.5f}], " "[{:.5f}, {:.5f}] is not contained in " "[{:.5f}, {:.5f}], [{:.5f} {:.5f}]" "".format( azmin_det, azmax_det, elmin_det, elmax_det, azmin, azmax, elmin, elmax, ) ) # Integrate detector signal err = atm_sim_observe(sim, times[ind], az, el, atmdata, ngood, 0) if err != 0: # Observing failed print( prefix + "OpSimAtmosphere: Observing FAILED. " "det = {}, rank = {}".format(det, comm.rank), flush=self._flush, ) atmdata[:] = 0 flag_ref[ind] = 255 if self._gain: atmdata *= self._gain if absorption is not None: # Apply the frequency-dependent absorption-coefficient atmdata *= absorption ref[ind][good] += atmdata del ref err = atm_sim_free(sim) if err != 0: raise RuntimeError(prefix + "Failed to free simulation.") if self._report_timing: comm.Barrier() tstop = MPI.Wtime() if comm.rank == 0 and tstop - tstart > 1: print( prefix + "OpSimAtmosphere: Observed atmosphere " "in {:.2f} s".format(tstop - tstart), flush=self._flush, ) return
def _observe_atmosphere( self, sim, tod, comm, prefix, common_ref, istart, nind, ind, scan_range, times, absorption, ): log = Logger.get() rank = 0 if comm is not None: rank = comm.rank tmr = Timer() if self._report_timing: if comm is not None: comm.Barrier() tmr.start() azmin, azmax, elmin, elmax = scan_range nsamp = tod.local_samples[1] if rank == 0: log.debug("{}Observing the atmosphere".format(prefix)) ngood_tot = 0 nbad_tot = 0 for det in tod.local_dets: # Cache the output signal cachename = "{}_{}".format(self._out, det) if tod.cache.exists(cachename): ref = tod.cache.reference(cachename) else: ref = tod.cache.create(cachename, np.float64, (nsamp,)) # Cache the output flags flag_ref = tod.local_flags(det, self._flag_name) if self._apply_flags: good = np.logical_and( common_ref[ind] & self._common_flag_mask == 0, flag_ref[ind] & self._flag_mask == 0, ) ngood = np.sum(good) else: try: good = common_ref[ind] & tod.UNSTABLE == 0 ngood = np.sum(good) except: good = slice(0, nind) ngood = nind if ngood == 0: continue try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det, local_start=istart, n=nind) az = az[good] el = el[good] except Exception as e: azelquat = tod.read_pntg( detector=det, local_start=istart, n=nind, azel=True )[good] # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, phi = qa.to_position(azelquat) # Azimuth is measured in the opposite direction # than longitude az = 2 * np.pi - phi el = np.pi / 2 - theta if np.ptp(az) < np.pi: azmin_det = np.amin(az) azmax_det = np.amax(az) else: # Scanning across the zero azimuth. azmin_det = np.amin(az[az > np.pi]) - 2 * np.pi azmax_det = np.amax(az[az < np.pi]) elmin_det = np.amin(el) elmax_det = np.amax(el) if ( not (azmin <= azmin_det and azmax_det <= azmax) and not ( azmin <= azmin_det - 2 * np.pi and azmax_det - 2 * np.pi <= azmax ) ) or not (elmin <= elmin_det and elmin_det <= elmax): # DEBUG begin import pickle with open("bad_quats_{}_{}.pck".format(rank, det), "wb") as fout: pickle.dump( [scan_range, az, el, azelquat, tod._boresight_azel], fout ) # DEBUG end raise RuntimeError( prefix + "Detector Az/El: [{:.5f}, {:.5f}], " "[{:.5f}, {:.5f}] is not contained in " "[{:.5f}, {:.5f}], [{:.5f} {:.5f}]" "".format( azmin_det, azmax_det, elmin_det, elmax_det, azmin, azmax, elmin, elmax, ) ) # Integrate detector signal atmdata = np.zeros(ngood, dtype=np.float64) err = sim.observe(times[ind][good], az, el, atmdata, -1.0) if err != 0: # Observing failed bad = np.abs(atmdata) < 1e-30 nbad = np.sum(bad) log.error( "{}OpSimAtmosphere: Observing FAILED for {} ({:.2f} %) samples. " "det = {}, rank = {}".format( prefix, nbad, nbad * 100 / ngood, det, rank ) ) atmdata[bad] = 0 flag_ref[ind][good][bad] = 255 nbad_tot += nbad ngood_tot += ngood if self._gain: atmdata *= self._gain if absorption is not None: # Apply the frequency-dependent absorption-coefficient atmdata *= absorption ref[ind][good] += atmdata del ref if comm is not None: comm.Barrier() ngood_tot = comm.reduce(ngood_tot) nbad_tot = comm.reduce(nbad_tot) if rank == 0 and nbad_tot > 0: log.error( "{}: Observe atmosphere FAILED on {:.2f}% of samples".format( prefix, nbad_tot * 100 / ngood_tot ) ) if self._report_timing: if rank == 0: tmr.stop() log.debug( "{}OpSimAtmosphere: Observe atmosphere: {} seconds".format( prefix, tmr.seconds() ) ) return
def _observe_sso(self, sso_az, sso_el, sso_dist, sso_dia, tod, comm, prefix, focalplane): """ Observe the SSO with each detector in tod """ log = Logger.get() rank = 0 if comm is not None: rank = comm.rank tmr = Timer() if self._report_timing: if comm is not None: comm.Barrier() tmr.start() nsamp = tod.local_samples[1] if rank == 0: log.info("{}Observing the SSO signal".format(prefix)) # FIXME: we should get the center frequency from the bandpass band_dict = {'f030' : 27, 'f040': 39, 'f090': 93, 'f150': 145 , 'f230': 225, 'f290': 285} for band in band_dict.keys(): if band in prefix: # FIXME we use the same, approximate center frequency for # SAT and LAT freq = band_dict[band[4:]] break for det in tod.local_dets: # Cache the output signal cachename = "{}_{}".format(self._out, det) if tod.cache.exists(cachename): ref = tod.cache.reference(cachename) else: ref = tod.cache.create(cachename, np.float64, (nsamp,)) try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det) except Exception as e: azelquat = tod.read_pntg(detector=det, azel=True) # Convert Az/El quaternion of the detector back into # angles for the simulation. theta, phi = qa.to_position(azelquat) # Azimuth is measured in the opposite direction # than longitude az = 2 * np.pi - phi el = np.pi / 2 - theta if "bandpass_transmission" in focalplane[det]: # We have full bandpasses for the detector bandpass_freqs = focalplane[det]["bandpass_freq_ghz"] bandpass = focalplane[det]["bandpass_transmission"] else: if "bandcenter_ghz" in focalplane[det]: # Use detector bandpass from the focalplane center = focalplane[det]["bandcenter_ghz"] width = focalplane[det]["bandwidth_ghz"] else: # Use default values for the entire focalplane if freq is None: raise RuntimeError( "You must supply the nominal frequency if bandpasses " "are not available" ) center = freq width = 0.2 * freq bandpass_freqs = np.array([center - width / 2, center + width / 2]) bandpass = np.ones(2) nstep = 1001 fmin, fmax = bandpass_freqs[0], bandpass_freqs[-1] det_freqs = np.linspace(fmin, fmax, nstep) det_bandpass = np.interp(det_freqs, bandpass_freqs, bandpass) det_bandpass /= np.sum(det_bandpass) self._get_planet_temp(self.sso_name) ttemp_det = np.interp(det_freqs, self.t_freqs, self.ttemp) ttemp_det = np.sum(ttemp_det * det_bandpass) beam, radius = self._get_beam_map(det, sso_dia, ttemp_det) # Interpolate the beam map at appropriate locations x = (az - sso_az) * np.cos(el) y = el - sso_el r = np.sqrt(x ** 2 + y ** 2) good = r < radius sig = beam(x[good], y[good], grid=False) ref[:][good] += sig del ref, sig, beam if self._report_timing: if comm is not None: comm.Barrier() if rank == 0: tmr.stop() tmr.report("{}OpSimSSO: Observe signal".format(prefix)) return
def exec(self, data): for obs in data.obs: tod = obs["tod"] focalplane = obs["focalplane"] # Get HWP angle chi = tod.local_hwp_angle() for det in tod.local_dets: signal = tod.local_signal(det, self._name) band = focalplane[det]["band"] freq = { "f030" : "027", "f040" : "039", "f090" : "093", "f150" : "145", "f230" : "225", "f290" : "278", }[band] # Get incident angle det_quat = focalplane[det]["quat"] theta, phi = qa.to_position(det_quat) # Get observing elevation try: # Some TOD classes provide a shortcut to Az/El az, el = tod.read_azel(detector=det) except Exception as e: azelquat = tod.read_pntg(detector=det, azel=True) el = np.pi / 2 - qa.to_position(azelquat)[0] # Get polarization weights weights = tod.cache.reference("weights_{}".format(det)) iweights, qweights, uweights = weights.T # Interpolate HWPSS to incident angle theta_deg = np.degrees(theta) itheta_high = np.searchsorted(self.thetas, theta_deg) itheta_low = itheta_high - 1 theta_low = self.thetas[itheta_low] theta_high = self.thetas[itheta_high] r = (theta_deg - theta_low) / (theta_high - theta_low) transmission = ( (1 - r) * self.all_stokes[freq]["transmission"][itheta_low] + r * self.all_stokes[freq]["transmission"][itheta_high] ) reflection = ( (1 - r) * self.all_stokes[freq]["reflection"][itheta_low] + r * self.all_stokes[freq]["reflection"][itheta_high] ) emission = ( (1 - r) * self.all_stokes[freq]["emission"][itheta_low] + r * self.all_stokes[freq]["emission"][itheta_high] ) # Scale HWPSS for observing elevation el_ref = np.radians(50) scale = np.sin(el_ref) / np.sin(el) # Observe HWPSS with the detector iquv = (transmission + reflection).T iquss = ( iweights * np.interp(chi, self.chis, iquv[0]) + qweights * np.interp(chi, self.chis, iquv[1]) + uweights * np.interp(chi, self.chis, iquv[2]) ) * scale iquv = emission.T iquss += ( iweights * np.interp(chi, self.chis, iquv[0]) + qweights * np.interp(chi, self.chis, iquv[1]) + uweights * np.interp(chi, self.chis, iquv[2]) ) iquss -= np.median(iquss) # Co-add with the cached signal signal += iquss return
def _sample_maps(self, tod, det, quat, weights=None): """ Perform bilinear interpolation of the stored map. """ thetaname = "theta_{}".format(det) phiname = "phi_{}".format(det) if tod.cache.exists(thetaname): theta = tod.cache.reference(thetaname) phi = tod.cache.reference(phiname) else: theta, phi = qa.to_position(quat) theta = tod.cache.put(thetaname, theta.astype(np.float32, copy=False)) phi = tod.cache.put(phiname, phi.astype(np.float32, copy=False)) if self.scale_skymodel: self.skymodel *= self.scale_skymodel self.skymodel_deriv *= self.scale_skymodel # Temporarily co-add the CMB and the foregrounds self.mapsampler += self.skymodel # bandpass mismatch try: freq = self._freqs[det] except Exception: freq = self._freq delta = freq - self._freq if delta != 0: self.skymodel_deriv *= delta self.mapsampler += self.skymodel_deriv if self._bpm_extra: fn_bpm = self._bpm_extra.replace("DETECTOR", det) if self._global_rank == 0: print(" Adding bandpass mismatch from {}".format(fn_bpm), flush=True) bpm = MapSampler( fn_bpm, pol=False, comm=self._comm, nest=True, nside=self._nside, plug_holes=False, # Work around a bug in the default cray-mpich library that does # not allow releasing MPI shared memory. use_shmem=False, ) self.mapsampler += bpm # Sample the aggregate map if self._global_rank == 0: print(" Sampling signal map", flush=True) signal = self.mapsampler.atpol(theta, phi, weights) # restore original CMB if self._bpm_extra: self.mapsampler -= bpm del bpm self.mapsampler -= self.skymodel if delta != 0: self.mapsampler -= self.skymodel_deriv self.skymodel_deriv /= delta if self.scale_skymodel: self.skymodel /= self.scale_skymodel self.skymodel_deriv /= self.scale_skymodel return signal
def exec(self, data): # the two-level pytoast communicator comm = data.comm # the global communicator cworld = comm.comm_world rank = cworld.Get_rank() margin = self._margin pnt2planets = [] for target in self._targets: pnt2planets.append(Pnt2Planeter(target)) for obs in data.obs: tod = obs['tod'] nsamp = tod.local_samples[1] if self._effdir_out is not None: tod.cache_metadata(self._effdir_out, comm=cworld) timestamps = tod.local_times(margin=margin) if len(timestamps) != nsamp + 2 * margin: raise Exception('Cached time stamps do not include margins.') commonflags = tod.local_common_flags(margin=margin) if len(commonflags) != nsamp + 2 * margin: raise Exception('Cached common flags do not include margins.') phase = tod.local_phase(margin=margin) if len(phase) != nsamp + 2 * margin: raise Exception('Cached phases do not include margins.') velocity = tod.local_velocity(margin=margin) if len(velocity) != nsamp + 2 * margin: raise Exception('Cached velocities do not include margins.') if self._pntmask is not None: pntflag = (commonflags & self._pntmask) != 0 else: pntflag = None intervals = tod.local_intervals(obs['intervals']) starts = [ival.start for ival in intervals] stops = [ival.stop + 1 for ival in intervals] local_starts = np.array(starts) local_stops = np.array(stops) ring_offset = tod.globalfirst_ring for interval in intervals: if interval.last < tod.local_samples[0]: ring_offset += 1 if self._lfi_mode: dipoler = Dipoler(full4pi=True, comm=cworld, RIMO=tod.RIMO) else: dipoler = Dipoler(freq=int(self._freq)) if self._bad_rings is not None: ringmasker = RingMasker(self._bad_rings) else: ringmasker = None if self._bg_map_path is not None \ and 'DETECTOR' not in self._bg_map_path: # All detectors share the same template map mapsampler = MapSampler(self._bg_map_path, pol=self._bg_pol, nside=self._bg_nside, comm=cworld, cache=tod.cache) else: mapsampler = None if self._maskfile: masksampler = MapSampler(self._maskfile, comm=cworld, dtype=np.byte, cache=tod.cache) maskflag = np.zeros(nsamp + 2 * margin, dtype=np.bool) else: masksampler = None maskflag = None # Now the optical channels for det in tod.local_dets: if rank == 0: print('Setting up processing for {}'.format(det), flush=True) if self._lfi_mode: bolo_id = None else: bolo_id = bolo_to_pnt(det) psi_pol = np.radians( (tod.RIMO[det].psi_uv + tod.RIMO[det].psi_pol)) beampar = {} beampar['savebeamobj'] = self._savebeamobj beampar['bsverbose'] = self._verbose beampar['savepath'] = self._out beampar['savedir'] = 'beams' beampar['prefix'] = '' beampar['boloID'] = bolo_id beampar['bsdebug'] = False beampar['datarad'] = self._datarad beampar['dstrrad'] = self._dstrrad beampar['dstrtol'] = self._dstrtol beampar['jupthr'] = self._jupthr beampar['radrms'] = self._radrms beampar['xtfthr'] = self._xtfthr beampar['nslices'] = self._nslices beampar['ntf'] = self._ntf beampar['hybthd'] = self._hybthd beampar['xtfthr'] = self._xtfthr beampar['trans'] = self._trans beampar['hrmax'] = self._hrmax beampar['optical'] = self._optical beampar['bsorder'] = self._bsorder beampar['pixsize'] = self._pixsize beampar['sqmaphpix'] = self._sqmaphpix beampar['rectmaphpixx'] = self._rectmaphpixx beampar['rectmaphpixy'] = self._rectmaphpixy beampar['knotstep'] = self._knotstep beampar['knotextframe'] = self._knotextframe beampar['knotextframex'] = self._knotextframex beampar['knotextframey'] = self._knotextframey beamobj = Beam_Reconstructor(bolo_id, beampar, mpicomm=cworld) if self._bg_map_path is not None \ and 'DETECTOR' in self._bg_map_path: # Detectors have separete template maps mapsampler = MapSampler(self._bg_map_path.replace( 'DETECTOR', det), pol=self._bg_pol, nside=self._bg_nside, comm=cworld, cache=tod.cache) # Read all of the data for this process and process # interval by interval signal = tod.local_signal(det, margin=margin) if len(signal) != nsamp + 2 * margin: raise Exception('Cached signal does not include margins.') flags = tod.local_flags(det, margin=margin) if len(flags) != nsamp + 2 * margin: raise Exception('Cached flags do not include margins.') quat = tod.local_pointing(det, margin=margin) if len(quat) != nsamp + 2 * margin: raise Exception('Cached quats do not include margins.') iquweights = tod.local_weights(det) if len(iquweights) != nsamp + 2 * margin: raise Exception('Cached weights do not include margins.') # Cast the flags into boolean vectors if self._detmask: detflag = (flags & self._detmask) != 0 else: detflag = flags != 0 if self._commonmask is not None: detflag[(commonflags & self._commonmask) != 0] = True if self._ssomask is not None: ssoflag = (flags & self._ssomask) != 0 else: ssoflag = np.zeros(nsamp, dtype=np.bool) # Add ring flags if ringmasker is not None: ringflag = ringmasker.get_mask(timestamps, det) detflag[ringflag] = True else: ringflag = None # Process if rank == 0: print('Processing {}'.format(det), flush=True) signal_out = np.zeros(nsamp + 2 * margin, dtype=np.float64) flags_out = np.zeros(nsamp + 2 * margin, dtype=np.uint8) ring_number = ring_offset - 1 for ring_start, ring_stop in zip(local_starts, local_stops): ring_number += 1 if self._verbose: print('{:4} : Processing ring {:4}'.format( rank, ring_number), flush=True) # Slice without margins ind = slice(ring_start + margin, ring_stop + margin) tme = timestamps[ind] sig = signal[ind].copy() flg = detflag[ind].copy() # Require at least 10% of the signal to be unflagged to # even attempt processing. if np.sum(flg == 0) < 0.1 * len(flg): raise Exception('Too many samples are flagged.') if pntflag is not None: pntflg = pntflag[ind].copy() else: pntflg = np.zeros_like(flg) if margin > 0: pntflg[:margin] = True pntflg[-margin:] = True if np.sum(pntflg == 0) < 10000: raise Exception('Pointing is unstable') q = quat[ind] if self._bg_pol: iquw = iquweights[ind] v = velocity[ind] # Get the dipole dipo = dipoler.dipole(q, velocity=v, det=det) if self._bg_has_dipole: dipo -= dipoler.dipole(q, det=det) # Convert pointing to angles theta, phi = qa.to_position(q) # Sample the (polarized) background map if mapsampler is not None: if self._bg_pol: bg = mapsampler.atpol(theta, phi, iquw) else: bg = mapsampler.at(theta, phi) else: bg = None # Sample the processing mask if masksampler is not None: maskflg = masksampler.at(theta, phi) < 0.5 maskflag[ind] = maskflg else: maskflg = None # Cache planet data here for pnt2planet in pnt2planets: target = pnt2planet.target az, el = pnt2planet.translate(q, tme, psi_pol=psi_pol) beamobj.preproc(tme, sig - dipo - bg, flg + maskflg, az, el, target) # Collect planet data beamobj.cache = [beamobj.cache] cworld.Barrier() beamobj.cache = cworld.gather(beamobj.cache, root=0) # Build the beam and/or solve for the transfer function if rank == 0: try: beamobj.recache() if len(beamobj.cache) == 0: print('WARNING: No samples in cache for {}. No ' 'beam reconstructed.'.format(det)) continue beamobj.scache = beamobj.cache for iiter in range(int(self._bsiter)): if iiter > 0: print('Beam reconstruction, iteration # {}' ''.format(iiter + 1)) t_iter_start = time.time() beamobj.cache = beamobj.scache beamobj.mergedata() beamobj.reconstruct(iiter) t_iter_finish = time.time() print('Beam Reconstruction iteration # {} ' 'completed in a total of {:.2f} s.'.format( iiter, t_iter_finish - t_iter_start)) if iiter < int(self._bsiter): beamobj.update(iiter) if self._beamtofits: beamobj.hmapsave(self._beamtofits, polar=False) if self._beamtofits_polar: beamobj.hmapsave(self._beamtofits_polar, polar=True) except Exception as e: print('Beam reconsruction for {} failed: "{}"' ''.format(det, e)) # If necessary pass through the data again, applying the new # transfer function # If new transfer function was applied, cache and optionally # write out the new TOD # Write detector data signal_out[:] = signal[:] flags_out |= np.uint8(1) * detflag if ssoflag is not None: flags_out |= np.uint8(2) * ssoflag if maskflag is not None: flags_out |= np.uint8(4) * maskflag ind = slice(margin, len(signal_out) - margin) cachename = "{}_{}".format(tod.SIGNAL_NAME, det) tod.cache.put(cachename, signal_out[ind], replace=True) cachename = "{}_{}".format(tod.FLAG_NAME, det) tod.cache.put(cachename, flags_out[ind], replace=True) if self._effdir_out is not None: if rank == 0: print('Saving detector data to {}'.format( self._effdir_out), flush=True) tod.write_tod_and_flags(detector=det, data=signal_out[ind], flags=flags_out[ind], effdir_out=self._effdir_out) commonflags_out = np.zeros_like(commonflags) commonflags_out |= np.uint8(1) * ((commonflags & (255 - self._pntmask)) != 0) if pntflag is not None: commonflags_out |= np.uint8(2) * pntflag if self._output_common_flags is not None: tod.cache.put(tod.COMMON_FLAG_NAME, commonflags_out[ind], replace=True) if self._effdir_out is not None: tod.write_common_flags(flags=commonflags_out[ind], effdir_out=self._effdir_out)