def get_4piconv(self, ch, theta, phi, psi, horn_pointing=False): l.info('Computing dipole temperature with 4pi convolver') vel = qarray.amplitude(self.satellite_v).flatten() beta = vel / physcon.c gamma = 1./np.sqrt(1-beta**2) unit_vel = self.satellite_v/vel[:,None] if horn_pointing: # psi comes from the S channel, so there is no need # to remove psi_pol psi_nopol = psi else: # remove psi_pol psi_nopol = psi - np.radians(ch.get_instrument_db_field("psi_pol")) # rotate vel to ecliptic # phi around z #ecl_rotation = qarray.rotation([0,0,1], -phi) # theta around y ecl_rotation = qarray.norm( qarray.mult( qarray.rotation([0,0,1], -psi_nopol) , qarray.mult( qarray.rotation([0,1,0], -theta) , qarray.rotation([0,0,1], -phi) ) )) # psi around z #ecl_rotation = qarray.mult(qarray.rotation([0,0,1], -psi) , ecl_rotation) # vel in beam ref frame vel_rad = qarray.rotate(ecl_rotation, unit_vel) cosdir = qarray.arraylist_dot(vel_rad, self.beam_sum[ch.tag]).flatten() #return beta * cosdir * T_CMB return (1. / ( gamma * (1 - beta * cosdir ) ) - 1) * T_CMB
def angles2siam(theta, phi, psi): mat_spin2boresight=qarray.rotation([0,1,0], np.pi/2-SPIN2BORESIGHT) mat_theta_phi = qarray.rotation([-math.sin(phi),math.cos(phi),0], theta) mat_psi = qarray.rotation([0,0,1], psi) # detector points to X axis total = qarray.mult(mat_spin2boresight, qarray.mult(mat_theta_phi, mat_psi)) # siam is defined as pointing to Z axis return np.dot(qarray.to_rotmat(total[0]), np.array([[0,0,1],[0,1,0],[1,0,0]]))
def boresight_sim(nsim=1000, qprec=None, samplerate=23.0, spinperiod=10.0, spinangle=30.0, precperiod=93.0, precangle=65.0): spinrate = 1.0 / (60.0 * spinperiod) spinangle = spinangle * np.pi / 180.0 precrate = 1.0 / (60.0 * precperiod) precangle = precangle * np.pi / 180.0 xaxis = np.array([1,0,0], dtype=np.float64) yaxis = np.array([0,1,0], dtype=np.float64) zaxis = np.array([0,0,1], dtype=np.float64) satrot = None if qprec is None: satrot = np.tile(qa.rotation(np.array([0.0, 1.0, 0.0]), np.pi/2), nsim).reshape(-1,4) elif qprec.flatten().shape[0] == 4: satrot = np.tile(qprec, nsim).reshape(-1,4) elif qprec.shape == (nsim, 4): satrot = qprec else: raise RuntimeError("qprec has wrong dimensions") # Time-varying rotation about precession axis. # Increment per sample is # (2pi radians) X (precrate) / (samplerate) # Construct quaternion from axis / angle form. precang = np.arange(nsim, dtype=np.float64) precang *= 2.0 * np.pi * precrate / samplerate # (zaxis, precang) cang = np.cos(0.5 * precang) sang = np.sin(0.5 * precang) precaxis = np.multiply(sang.reshape(-1,1), np.tile(zaxis, nsim).reshape(-1,3)) precrot = np.concatenate((precaxis, cang.reshape(-1,1)), axis=1) # Rotation which performs the precession opening angle precopen = qa.rotation(np.array([1.0, 0.0, 0.0]), precangle) # Time-varying rotation about spin axis. Increment # per sample is # (2pi radians) X (spinrate) / (samplerate) # Construct quaternion from axis / angle form. spinang = np.arange(nsim, dtype=np.float64) spinang *= 2.0 * np.pi * spinrate / samplerate cang = np.cos(0.5 * spinang) sang = np.sin(0.5 * spinang) spinaxis = np.multiply(sang.reshape(-1,1), np.tile(zaxis, nsim).reshape(-1,3)) spinrot = np.concatenate((spinaxis, cang.reshape(-1,1)), axis=1) # Rotation which performs the spin axis opening angle spinopen = qa.rotation(np.array([1.0, 0.0, 0.0]), spinangle) # compose final rotation boresight = qa.mult(satrot, qa.mult(precrot, qa.mult(precopen, qa.mult(spinrot, spinopen)))) return boresight
def ptcor(obt, ptcorfile): # Boresight rotation of 85 degrees in order to get in inscan-xscan reference frame q_str_LOS = qarray.rotation(np.array([0,1,0]), np.radians(90-85)) # read variable correction for current OD from file delta_inscan, delta_xscan = read_ptcor(obt, ptcorfile) # rotation in inscan-xscan reference frame qcor = qarray.mult( qarray.rotation(np.array([0,1,0]), delta_xscan), qarray.rotation(np.array([1,0,0]), delta_inscan) ) qcor_tot = qarray.mult(q_str_LOS, qarray.mult(qcor, qarray.inv(q_str_LOS))) return qcor_tot
def ang_to_quat(offsets): """Convert cartesian angle offsets and rotation into quaternions. Each offset contains two angles specifying the distance from the Z axis in orthogonal directions (called "X" and "Y"). The third angle is the rotation about the Z axis. A quaternion is computed that first rotates about the Z axis and then rotates this axis to the specified X/Y angle location. Args: offsets (list of arrays): Each item of the list has 3 elements for the X / Y angle offsets in radians and the rotation in radians about the Z axis. Returns: (list): List of quaternions, one for each item in the input list. """ out = list() zaxis = np.array([0, 0, 1], dtype=np.float64) for off in offsets: angrot = qa.rotation(zaxis, off[2]) wx = np.sin(off[0]) wy = np.sin(off[1]) wz = np.sqrt(1.0 - (wx * wx + wy * wy)) wdir = np.array([wx, wy, wz]) posrot = qa.from_vectors(zaxis, wdir) out.append(qa.mult(posrot, angrot)) return out
def wobble(obt, wobble_psi2_model=get_wobble_psi2_maris, offset=0): """Gets array of OBT and returns an array of quaternions""" R_psi1 = qarray.inv(qarray.rotation([0,0,1], private.WOBBLE_DX7['psi1_ref'])) R_psi2 = qarray.inv(qarray.rotation([0,1,0], private.WOBBLE_DX7['psi2_ref'])) psi2 = wobble_psi2_model(obt) - offset R_psi2T = qarray.rotation([0,1,0], psi2) wobble_rotation = qarray.mult(qarray.inv(R_psi1), qarray.mult(R_psi2T , qarray.mult(R_psi2 , R_psi1) ) ) #debug_here() return wobble_rotation
def ahf_wobble(obt): """Pointing period by pointing period correction for psi1 and psi2 from the AHF observation files""" R_psi1 = qarray.inv(qarray.rotation([0,0,1], private.WOBBLE['psi1_ref'])) R_psi2 = qarray.inv(qarray.rotation([0,1,0], private.WOBBLE['psi2_ref'])) psi1, psi2 = get_ahf_wobble(obt) R_psi2T = qarray.rotation([0,1,0], psi2) R_psi1T = qarray.rotation([0,0,1], psi1) wobble_rotation = qarray.mult(R_psi1T, qarray.mult(R_psi2T , qarray.mult(R_psi2 , R_psi1) ) ) return wobble_rotation
def get_4piconv_dx10(self, ch, theta, phi, psi): l.info('Computing dipole temperature with 4pi convolver') rel_vel = self.satellite_v/physcon.c # remove psi_pol psi_nopol = psi - np.radians(ch.get_instrument_db_field("psi_pol")) # rotate vel to horn reference frame tohorn_rotation = qarray.norm( qarray.mult( qarray.rotation([0,0,1], -psi_nopol) , qarray.mult( qarray.rotation([0,1,0], -theta) , qarray.rotation([0,0,1], -phi) ) )) # vel in beam ref frame vel_rad = qarray.rotate(tohorn_rotation, rel_vel) dipole_amplitude = self.get_fourpi_prod(vel_rad, ["S100", "S010", "S001"], ch) # relative corrections dipole_amplitude += vel_rad[:,0] * self.get_fourpi_prod(vel_rad, ["S200", "S110", "S101"], ch)/2 dipole_amplitude += vel_rad[:,1] * self.get_fourpi_prod(vel_rad, ["S110", "S020", "S011"], ch)/2 dipole_amplitude += vel_rad[:,2] * self.get_fourpi_prod(vel_rad, ["S101", "S011", "S002"], ch)/2 return dipole_amplitude * T_CMB
def triangle(npos, width, rotate=None): """Compute positions in an equilateral triangle layout. Args: npos (int): The number of positions packed onto wafer=3 width (float): distance between tubes in degrees rotate (array, optional): Optional array of rotation angles in degrees to apply to each position. Returns: (array): Array of quaternions for the positions. """ zaxis = np.array([0, 0, 1], dtype=np.float64) sixty = np.pi / 3.0 thirty = np.pi / 6.0 rtthree = np.sqrt(3.0) rtthreebytwo = 0.5 * rtthree tubedist = width * np.pi / 180.0 result = np.zeros((npos, 4), dtype=np.float64) posangarr = np.array([sixty * 3.0 + thirty, -thirty, thirty * 3.0]) for pos in range(npos): posang = posangarr[pos] posdist = tubedist / rtthree posx = np.sin(posdist) * np.cos(posang) posy = np.sin(posdist) * np.sin(posang) posz = np.cos(posdist) posdir = np.array([posx, posy, posz], dtype=np.float64) norm = np.sqrt(np.dot(posdir, posdir)) posdir /= norm posrot = qa.from_vectors(zaxis, posdir) if rotate is None: result[pos] = posrot else: prerot = qa.rotation(zaxis, rotate[pos] * np.pi / 180.0) result[pos] = qa.mult(posrot, prerot) return result
timestamps = np.zeros(nsamp, dtype="double") timestamps[:] = np.arange(nsamp) dets = ["1A", "1B", "2A", "2B"] detstring = dets2detstring(dets) ndet = len(dets) x_axis, y_axis, z_axis = np.eye(3) # Earth angle_each_day = np.radians(360 / 365.25) angles = timestamps * angle_each_day / 3600 / 24 rot_earth_orbit = qa.rotation(z_axis, angles) # Precession prec_period_seconds = 1 * 3600 prec_ang_speed = 2 * np.pi / prec_period_seconds rot_prec_opening = qa.rotation(z_axis, -np.radians(40)) prec_angles = (timestamps * prec_ang_speed) % (2 * np.pi) rot_prec = qa.mult(qa.rotation(x_axis, prec_angles), rot_prec_opening) # Spin spin_period_seconds = 60 spin_ang_speed = 2 * np.pi / spin_period_seconds spin_angles = (timestamps * spin_ang_speed) % (2 * np.pi) rot_opening = qa.rotation(z_axis, -np.radians(10))
def rhombus_layout(npos, width, rotate=None): """Compute positions in a hexagon layout. This particular rhombus geometry is essentially a third of a hexagon. In other words the aspect ratio of the rhombus is constrained to have the long dimension be sqrt(3) times the short dimension. The rhombus is projected on the sphere and centered on the Z axis. The X axis is along the short direction. The Y axis is along the longer direction. For example:: O Y ^ O O | O O O | O O O O +--> X O O O O O O Each position is numbered 0..npos-1. The first position is at the "top", and then the positions are numbered moving downward and left to right. The extent of the rhombus is directly specified by the width parameter which is the angular extent along the X direction. Args: npos (int): The number of positions in the rhombus. width (float): The angle (in degrees) subtended by the width along the X axis. rotate (array, optional): Optional array of rotation angles in degrees to apply to each position. Returns: (array): Array of quaternions for the positions. """ zaxis = np.array([0, 0, 1], dtype=np.float64) rtthree = np.sqrt(3.0) angwidth = width * np.pi / 180.0 dim = rhomb_dim(npos) # find the angular packing size of one detector posdiam = angwidth / (dim - 1) result = np.zeros((npos, 4), dtype=np.float64) for pos in range(npos): posrow, poscol = rhomb_row_col(npos, pos) rowang = 0.5 * rtthree * ((dim - 1) - posrow) * posdiam relrow = posrow if posrow >= dim: relrow = (2 * dim - 2) - posrow colang = (float(poscol) - float(relrow) / 2.0) * posdiam distang = np.sqrt(rowang**2 + colang**2) zang = np.cos(distang) posdir = np.array([colang, rowang, zang], dtype=np.float64) norm = np.sqrt(np.dot(posdir, posdir)) posdir /= norm posrot = qa.from_vectors(zaxis, posdir) if rotate is None: result[pos] = posrot else: prerot = qa.rotation(zaxis, rotate[pos] * np.pi / 180.0) result[pos] = qa.mult(posrot, prerot) return result
def hex_layout(npos, width, rotate=None): """Compute positions in a hexagon layout. Place the given number of positions in a hexagonal layout projected on the sphere and centered at z axis. The width specifies the angular extent from vertex to vertex along the "X" axis. For example:: Y ^ O O O | O O O O | O O + O O +--> X O O O O O O O Each position is numbered 0..npos-1. The first position is at the center, and then the positions are numbered moving outward in rings. Args: npos (int): The number of positions packed onto wafer. width (float): The angle (in degrees) subtended by the width along the X axis. rotate (array, optional): Optional array of rotation angles in degrees to apply to each position. Returns: (array): Array of quaternions for the positions. """ zaxis = np.array([0, 0, 1], dtype=np.float64) nullquat = np.array([0, 0, 0, 1], dtype=np.float64) sixty = np.pi / 3.0 thirty = np.pi / 6.0 rtthree = np.sqrt(3.0) rtthreebytwo = 0.5 * rtthree angdiameter = width * np.pi / 180.0 # find the angular packing size of one detector nrings = hex_nring(npos) posdiam = angdiameter / (2 * nrings - 2) result = np.zeros((npos, 4), dtype=np.float64) for pos in range(npos): if pos == 0: # center position has no offset posrot = nullquat else: # Not at the center, find ring for this position test = pos - 1 ring = 1 while (test - 6 * ring) >= 0: test -= 6 * ring ring += 1 sectors = int(test / ring) sectorsteps = np.mod(test, ring) # Convert angular steps around the ring into the angle and distance # in polar coordinates. Each "sector" of 60 degrees is essentially # an equilateral triangle, and each step is equally spaced along # the edge opposite the vertex: # # O # O O (step 2) # O O (step 1) # X O O O (step 0) # # For a given ring, "R" (center is R=0), there are R steps along # the sector edge. The line from the origin to the opposite edge # that bisects this triangle has length R*sqrt(3)/2. For each # equally-spaced step, we use the right triangle formed with this # bisection line to compute the angle and radius within this # sector. # The distance from the origin to the midpoint of the opposite # side. midline = rtthreebytwo * float(ring) # the distance along the opposite edge from the midpoint (positive # or negative) edgedist = float(sectorsteps) - 0.5 * float(ring) # the angle relative to the midpoint line (positive or negative) relang = np.arctan2(edgedist, midline) # total angle is based on number of sectors we have and the angle # within the final sector. posang = sectors * sixty + thirty + relang posdist = rtthreebytwo * posdiam * float(ring) / np.cos(relang) posx = np.sin(posdist) * np.cos(posang) posy = np.sin(posdist) * np.sin(posang) posz = np.cos(posdist) posdir = np.array([posx, posy, posz], dtype=np.float64) norm = np.sqrt(np.dot(posdir, posdir)) posdir /= norm posrot = qa.from_vectors(zaxis, posdir) if rotate is None: result[pos] = posrot else: prerot = qa.rotation(zaxis, rotate[pos] * np.pi / 180.0) result[pos] = qa.mult(posrot, prerot) return result
timestamps[:] = np.arange(nsamp) dets = ["1A", "1B", "2A", "2B"] detstring = dets2detstring(dets) ndet = len(dets) spin_period_seconds = 60 x_axis, y_axis, z_axis = np.eye(3) spin_ang_speed = 2 * np.pi / spin_period_seconds spin_angles = (timestamps * spin_ang_speed) % (2 * np.pi) rot_opening = qa.rotation(z_axis, -np.radians(10)) rot_spin = qa.mult(qa.rotation(x_axis, spin_angles), rot_opening) bore_v = qa.rotate(rot_spin, x_axis) pix_1det = hp.vec2pix(nside, bore_v[:, 0], bore_v[:, 1], bore_v[:, 2], nest=True) pixels = np.tile(pix_1det, ndet) del pix_1det, bore_v, rot_spin pars = {} pars["base_first"] = 60.0 pars["fsample"] = fsample
def test_rotation(self): np.testing.assert_array_almost_equal( qarray.rotation(np.array([0,0,1]), np.radians(30)), np.array([0, 0, np.sin(np.radians(15)), np.cos(np.radians(15))]) )
# In[ ]: #target_ut_h = ut_h.values # ### Elevation and spinning # Elevation is a rotation with respect to the `y` axis of the opening angle # In[ ]: # In[ ]: q_elev = qa.rotation(y, np.radians(OPENING_ANGLE)) # Rotation is a rotation with respect to the `z` axis # In[ ]: rotation_speed = np.radians(-1 * 360/60) az = rotation_speed * (target_ut_h * 3600.) % (2*np.pi) q_rotation = qa.rotation(z, az) # We compose the rotations
nside = 32 npix = 12 * nside**2 fsample = 1 nsamp = 3600 * 24 # number of time ordered data samples nnz = 3 # number or non zero pointing weights, typically 3 for IQU timestamps = np.zeros(nsamp, dtype="double") timestamps[:] = np.arange(nsamp) x_axis, y_axis, z_axis = np.eye(3) # Earth angle_each_day = np.radians(360 / 365.25) angles = timestamps * angle_each_day / 3600 / 24 rot_earth_orbit = qa.rotation(z_axis, angles) # Precession prec_period_seconds = 1 * 3600 prec_ang_speed = 2 * np.pi / prec_period_seconds rot_prec_opening = qa.rotation(z_axis, -np.radians(40)) prec_angles = (timestamps * prec_ang_speed) % (2 * np.pi) rot_prec = qa.mult(qa.rotation(x_axis, prec_angles), rot_prec_opening) # Spin spin_period_seconds = 60 spin_ang_speed = 2 * np.pi / spin_period_seconds spin_angles = (timestamps * spin_ang_speed) % (2 * np.pi) rot_opening = qa.rotation(z_axis, -np.radians(10))