def test_getq_c_and_python(self): """cystal_calc module: getq() in C and python.""" #Angles for testing az = 1.23 elev = 0.32 wl = 2.32 rot_matrix = rotation_matrix(0.31, -0.23, 0.43) q1 = getq_python(az, elev, wl, rot_matrix) q2 = getq(az, elev, wl, rot_matrix) assert np.allclose(q1, q2) , "Python and C getq match within float error." #assert np.all(q1 == q2) , "Python and C getq match exactly." #Now test for an array az = np.linspace(0, pi, 50) elev = az / 2 rot_matrix = rotation_matrix(0.31, -0.23, 0.43) several_q = getq_python(az, elev, wl, rot_matrix) assert several_q.shape == (3, 50), "Correct shape" wl = az + 1 several_q = getq_python(az, elev, wl, rot_matrix) assert several_q.shape == (3, 50), "Correct shape with wl as array"
def test_getq(self): """cystal_calc module: getq().""" #Zero-level test q1 = getq(0, 0, 1.0, rotation_matrix(0,0,0)) assert not np.any(q1), "Non-scattered beam gives a q of 0,0,0" q1 = getq(-np.pi, 0.0, 1.0, rotation_matrix(0,0,0)).flatten() answer = np.array([0,0,-2.])*2*np.pi assert np.allclose(q1, answer), "Back-scattered beam gives a q of %s; result was %s" % (answer, q1) q1 = getq(-np.pi, 0.0, 2.0, rotation_matrix(0,0,0)).flatten() answer = np.array([0,0,-1.])*2*np.pi assert np.allclose(q1, answer), "Back-scattered beam at wl = 2 Angstrom gives a q of %s; result was %s" % (answer, q1) q1 = getq(-np.pi, 0, 3, rotation_matrix(0,0,0)).flatten() answer = np.array([0,0, -2.0/3.0])*2*np.pi assert np.allclose(q1, answer), "Getq handles integer inputs." q1 = getq(0, np.pi/2, 1.0, rotation_matrix(0,0,0)).flatten() answer = np.array([0,+1.,-1.])*2*np.pi assert np.allclose(q1, answer), "Vertically-scattered beam gives a q of %s; result was %s" % (answer, q1)
def test_getq_inelastic(self): #Angles for testing az = 1.23 elev = 0.32 wl = 2.32 wl_input = 1.02 rot_matrix = rotation_matrix(0.31, -0.23, 0.43) q1 = getq_python(az, elev, wl, rot_matrix, wl_input=wl_input) q2 = getq(az, elev, wl, rot_matrix, wl_input=wl_input) print "python:", q1 print "c:", q2 assert np.allclose(q1[0].flatten(), np.array(q2[0]).flatten()) , "Python and C getq (inelastic) match within float error. %s (python); %s (C)" % (q1, q2) assert np.allclose(q1[1].flatten(), np.array(q2[1]).flatten()) , "Python and C getq (inelastic) match within float error. %s (python); %s (C)" % (q1, q2)
def test_getq_with_rotation(self): "cystal_calc module: getq() test with sample rotation. The q-rotation matrix always uses OPPOSITE angles" #Rotate sample +pi/2 in phi. q_rot_m = rotation_matrix(-np.pi/2,0,0) q1 = getq(-np.pi, 0.0, 1.0, q_rot_m).flatten() answer = np.array([+2.,0.,0.])*2*np.pi assert np.allclose(q1, answer), "Back-scattered beam, with phi rotation, gives a q of %s; result was %s" % (answer, q1) q1 = getq(0, pi/2, 1.0, q_rot_m).flatten() answer = np.array([+1.,+1.,0.])*2*np.pi assert np.allclose(q1, answer), "Vertically-scattered beam, with phi rotation, gives a q of %s; result was %s" % (answer, q1) #Rotate sample +pi/2 in chi. No effect on back-scattered q_rot_m = rotation_matrix(0,-np.pi/2,0) q1 = getq(-np.pi, 0.0, 1.0, q_rot_m).flatten() answer = np.array([0.,0.,-2.])*2*np.pi assert np.allclose(q1, answer), "Back-scattered beam, with chi rotation, gives a q of %s; result was %s" % (answer, q1) q_rot_m = rotation_matrix(0,0.2345,0) q1 = getq(-np.pi, 0.0, 1.0, q_rot_m).flatten() answer = np.array([0.,0.,-2.])*2*np.pi assert np.allclose(q1, answer), "Back-scattered beam, with any chi rotation, gives a q of %s; result was %s" % (answer, q1)
def test_scattered_beam(self): """cystal_calc module: get_scattered_q_vector() tests.""" #Easy UB matrix test UB = np.identity(3) rot_matrix = rotation_matrix(0, 0, 0) #Single vector hkl = column([1,2,3]) scattered_q = get_scattered_q_vector(hkl, rot_matrix, UB) assert scattered_q.shape==(3,1), "Returns column-wise vector." assert np.allclose(scattered_q, column([1.,2.,3.])), "Identity operation." #Several vectors hkl = column([1,2,3]) + np.arange(0, 10) scattered_q = get_scattered_q_vector(hkl, rot_matrix, UB) assert scattered_q.shape==(3,10), "Returns column-wise array."
def make_UB_matrix(lattice_lengths, lattice_angles, sample_phi, sample_chi, sample_omega, U_matrix=None): """Make a UB matrix from the given parameters. Parameters: lattice_lengths: the DIRECT lattice lengths a,b,c in Angstroms. lattice_angles: tuple. the DIRECT lattice lengths alpha, beta, gamma, in radians. alpha = angle between b and c; beta = angle between a and c; gamma = angle between b and c sample_phi, sample_chi, sample_omega: These three angles (in radians) determine how the crystal is mounted. Here's how it works: 0 - Instrument coordinates: +Y is vertical, +Z is the beam direction. 1 - Start with the crystal mounted so that 'a' (real lattice vector) is exactly aligned towards +X, and 'b' is pointing towards +Y in the XY plane, and 'c' is pointing towards +Z 2 - Do the phi rotation = around the Y axis (vertical). 3 - Do the chi rotation = around the Z axis. 4 - Do the omega rotation = around the Y axis again. For example, if 'a' is pointing up, use 0, +90, 0. if 'a' is pointing down along the beam direction, use -90, 0, 0 (I think) U_matrix : specify to use this U matrix instead of the angles """ #Get the reciprocal vectors (a_star, b_star, c_star) = make_reciprocal_lattice(lattice_lengths, lattice_angles) #The B matrix is such that B.hkl = the q-vector (minus some rotations) # so, B is a matrix with each column = to a_star, b_star, c_star # B.hkl = a_star*h + b_star*k + c_star*l B = numpy_utils.vectors_to_matrix(a_star, b_star, c_star) #Now lets make a rotation matrix U to account for sample mounting. # We used the same set of phi,chi,omega rotations as for sample orientations. U = numpy_utils.rotation_matrix(sample_phi, sample_chi, sample_omega) if not U_matrix is None: U = U_matrix #Return the UB matrix product. U*Bget_q_from_hkl return np.dot(U, B)
def calculate_pixel_angles(self): """Given the center angle and other geometry of the detector, calculate the azimuth and elevation angle of every pixel.""" x = np.linspace(-self.width/2, self.width/2, self.xpixels) y = np.linspace(-self.height/2, self.height/2, self.ypixels) #Starting x, y, z position (px, py) = np.meshgrid(x,y) #Start with a Z equal to the given distance pz = np.zeros( px.shape ) + self.distance #Reshape into a nx3 buncha columns, where the columns are the pixels XYZ positions num = self.xpixels * self.ypixels pixels = np.array( [ px.flatten(), py.flatten(), pz.flatten()] ) #Ok, first rotate the detector around its center by angle. #Since this is rotation around z axis, it is a chi angle rot = rotation_matrix(0, self.rotation, 0) #Now do the elevation rotation, by rotating around the x axis. # Elevation is positive above the horizontal plane - means the x_rotation angle has to be negative rot = np.dot(x_rotation_matrix(-self.elevation_center), rot) #Finally add an azimuthal rotation (around the y axis, or phi) rot = np.dot(rotation_matrix(self.azimuth_center, 0, 0), rot) #Save it for later self.pixel_rotation_matrix = rot #This applies to the pixels pixels = np.dot(rot, pixels) #Save em - for plotting, mostly. Indices go Z, Y, X self.pixels = np.reshape(pixels, (3, self.ypixels, self.xpixels) ) #Save the corners self.corners = list() self.corners.append(self.pixels[:, 0, 0]) #One corner self.corners.append(self.pixels[:, 0, -1]) #The end in X self.corners.append(self.pixels[:, -1, -1]) #Max x and Y self.corners.append(self.pixels[:, -1, 0]) #The end in Y #This is the base point - the center of the detector base_pixel = column([0, 0, self.distance]) #Do the same rotation as the rest of the pixels base_pixel = np.dot(rot, base_pixel) self.base_point = column(base_pixel) #Horizontal and vertical vector self.horizontal = column(self.corners[1] - self.corners[0]) self.vertical = column(self.corners[3] - self.corners[0]) #Normalize them self.vertical /= vector_length(self.vertical) self.horizontal /= vector_length(self.horizontal) #Normal vector: take a vector pointing in positive Z, and the do the same rotation as all the pixels. self.normal = column(np.cross(self.horizontal.flatten(), self.vertical.flatten() )) assert np.allclose(vector_length(self.normal), 1.0), "normal vector is normalized." #Another way to calculate the normal. Compare it other_normal = np.dot(rot, column([0,0,1])).flatten() assert np.allclose(self.normal.flatten(), other_normal), "Match between two methods of calculating normals. %s vs %s" % (self.normal.flatten(), other_normal) #Now, we calculate the azimuth and elevation angle of each pixel. x=pixels[0,:] y=pixels[1,:] z=pixels[2,:] self.azimuthal_angle = np.reshape( np.arctan2(x, z), (self.ypixels, self.xpixels) ) self.elevation_angle = np.reshape( np.arctan(y / np.sqrt(x**2 + z**2)), (self.ypixels, self.xpixels) )
UB = make_UB_matrix(lat, angles, np.deg2rad(90), np.deg2rad(0), np.deg2rad(0)) M = np.array([[0,0,0.05], [0,0.1,0], [-0.2,0,0]]) * 2 * pi assert np.allclose(M, UB), "UB matrix for orthorombic lattice, phi=+90" UB = make_UB_matrix(lat, angles, np.deg2rad(0), np.deg2rad(90), np.deg2rad(0)) M = np.array([[0,-0.1,0], [0.2,0,0], [0,0,0.05]]) * 2 * pi assert np.allclose(M, UB), "UB matrix for orthorombic lattice, chi=+90" def test_get_sample_rotation_matrix_to_get_beam(self): beam_wanted = column([0,1,0]) ub_matrix = np.identity(3) hkl = column([0,1,1]) (R, wl) = get_sample_rotation_matrix_to_get_beam(beam_wanted, hkl, ub_matrix) assert np.allclose(R, np.array([[1,0,0],[0,0,1],[0,-1,0]])), "get_sample_rotation_matrix, case 1" # #Lets give it a starting matrix # start_R = rotation_matrix(0,np.pi/4, 0) # (R, wl) = get_sample_rotation_matrix_to_get_beam(beam_wanted, hkl, ub_matrix, start_R) # assert np.allclose(R, np.array([[1,0,0],[0,0,1],[0,-1,0]])), "get_sample_rotation_matrix, with a starting matrix. We got %s" % R #--------------------------------------------------------------------- if __name__ == "__main__": rot_matrix = numpy_utils.rotation_matrix(np.rad2deg(45), np.rad2deg(90), 0) print rot_matrix start = column([-1,0,0]) print start rot = np.dot(rot_matrix, start) print rot #unittest.main()
def calculate_pixel_angles(self): """Given the center angle and other geometry of the detector, calculate the azimuth and elevation angle of every pixel.""" x = np.linspace(-self.width / 2, self.width / 2, self.xpixels) y = np.linspace(-self.height / 2, self.height / 2, self.ypixels) #Starting x, y, z position (px, py) = np.meshgrid(x, y) #Start with a Z equal to the given distance pz = np.zeros(px.shape) + self.distance #Reshape into a nx3 buncha columns, where the columns are the pixels XYZ positions num = self.xpixels * self.ypixels pixels = np.array([px.flatten(), py.flatten(), pz.flatten()]) #Ok, first rotate the detector around its center by angle. #Since this is rotation around z axis, it is a chi angle rot = rotation_matrix(0, self.rotation, 0) #Now do the elevation rotation, by rotating around the x axis. # Elevation is positive above the horizontal plane - means the x_rotation angle has to be negative rot = np.dot(x_rotation_matrix(-self.elevation_center), rot) #Finally add an azimuthal rotation (around the y axis, or phi) rot = np.dot(rotation_matrix(self.azimuth_center, 0, 0), rot) #Save it for later self.pixel_rotation_matrix = rot #This applies to the pixels pixels = np.dot(rot, pixels) #Save em - for plotting, mostly. Indices go Z, Y, X self.pixels = np.reshape(pixels, (3, self.ypixels, self.xpixels)) #Save the corners self.corners = list() self.corners.append(self.pixels[:, 0, 0]) #One corner self.corners.append(self.pixels[:, 0, -1]) #The end in X self.corners.append(self.pixels[:, -1, -1]) #Max x and Y self.corners.append(self.pixels[:, -1, 0]) #The end in Y #This is the base point - the center of the detector base_pixel = column([0, 0, self.distance]) #Do the same rotation as the rest of the pixels base_pixel = np.dot(rot, base_pixel) self.base_point = column(base_pixel) #Horizontal and vertical vector self.horizontal = column(self.corners[1] - self.corners[0]) self.vertical = column(self.corners[3] - self.corners[0]) #Normalize them self.vertical /= vector_length(self.vertical) self.horizontal /= vector_length(self.horizontal) #Normal vector: take a vector pointing in positive Z, and the do the same rotation as all the pixels. self.normal = column( np.cross(self.horizontal.flatten(), self.vertical.flatten())) assert np.allclose(vector_length(self.normal), 1.0), "normal vector is normalized." #Another way to calculate the normal. Compare it other_normal = np.dot(rot, column([0, 0, 1])).flatten() assert np.allclose( self.normal.flatten(), other_normal ), "Match between two methods of calculating normals. %s vs %s" % ( self.normal.flatten(), other_normal) #Now, we calculate the azimuth and elevation angle of each pixel. x = pixels[0, :] y = pixels[1, :] z = pixels[2, :] self.azimuthal_angle = np.reshape(np.arctan2(x, z), (self.ypixels, self.xpixels)) self.elevation_angle = np.reshape(np.arctan(y / np.sqrt(x**2 + z**2)), (self.ypixels, self.xpixels))