def calc_surf_transform(nm): """ This routine calculates the matrix, M, which transforms the indicies of vectors defined in the lab frame basis to the a surface frame defined such that: zs is along the surface normal (parrallel to nm) ys is the projection of the lab frame -y axis onto the surface xs is defined to make it a right handed orthonormal set (and is therefore in the surface plane) Parameters: ----------- * nm is the surface normal vector, defined in the laboratory m-frame (i.e. pointing in an arbitrary direction for a particular gonio setting) - see gonio_psic.py for more details. Notes: ------ The surface transform is defined as follows: |e_xs| |e_x| |vx| |e_ys| = F* |e_y| and F = |vy| |e_zs| |e_z| |vz| where e's are the (cartesian) basis vectors. Given a vector [x,y,z] defined in the lab frame basis [e_x,e_y,e_z], we can compute the indicies of this vector in the surface basis [e_xs,e_ys,e_zs] from: |xs| |x| |ys| = M* |y| |zs| |z| where M = transpose(inv(F)) = inv(transpose(F)) Note see lattice.py for more notes on general transforms, and see gonio_psic.py for notes on calc of the surface normal vector. """ v_z = nm / cartesian_mag(nm) v = num.array([0.,-1.,0.]) v_y = v - (num.dot(v,v_z) * v_z) / (cartesian_mag(v_z)**2.) v_y = v_y / cartesian_mag(v_y) v_x = num.cross(v_y,v_z) v_x = v_x / cartesian_mag(v_x) F = num.array([v_x,v_y,v_z]) M = num.linalg.inv(F.transpose()) return M
def _calc_tau_az(self): """ tau_az = angle between the projection of n in the xy-plane and the x-axis in the phi frame """ # calc n in the lab frame (unrotated) and make a unit vector n_phi = num.dot(self.UB, self.n) n_phi = n_phi / cartesian_mag(n_phi) tau_az = num.arctan2(-n_phi[1], n_phi[0]) tau_az = tau_az * 180. / num.pi self.pangles['tau_az'] = tau_az
def _calc_sigma_az(self): """ sigma_az = angle between the z-axis and n in the phi frame """ # calc n in the lab frame (unrotated) and make a unit vector n_phi = num.dot(self.UB, self.n) n_phi = n_phi / cartesian_mag(n_phi) # note result of acosd is between 0 and pi # get correct sign from the sign of the x-component #sigma_az = num.sign(n_phi[0])*arccosd(n_phi[2]) sigma_az = arccosd(n_phi[2]) self.pangles['sigma_az'] = sigma_az
def _calc_nm(self): """ Calculate the rotated cartesian lab indicies of the reference vector n = nm. Note nm is normalized. Notes: ------ The reference vector n is given in recip lattice indicies (hkl) """ # calc n in the rotated lab frame and make a unit vector n = self.n Z = self.Z UB = self.UB nm = num.dot(num.dot(Z, UB), n) nm = nm / cartesian_mag(nm) self.nm = nm
def _calc_beta(self): """ Calc beta, ie exit angle, or angle btwn k_r and the plane perp to the reference vector n Notes: ------ beta = arcsind(2*sind(tth/2)*cosd(tau)-sind(alpha)) """ # calc normalized kr #delta = self.angles['delta'] #nu = self.angles['nu'] #kr = num.array([sind(delta), # cosd(nu)*cosd(delta), # sind(nu)*cosd(delta)]) nm = self.nm kr = self.kr / cartesian_mag(self.kr) beta = arcsind(num.dot(nm, kr)) self.pangles['beta'] = beta
def surface_intercept_bounds(k,v,diameter): """ Find z-intercepts within bounds We assume that the plane is a circle of fixed diameter, and scale back the intercept to the edge of the circle, This is done along by subtracting a vector that is parrelel to the in-plane projection of the k vector. The magnitude of the subtracted vector is set so the result has magnitude equal to the diameter. If that doesnt result in an intercept then just rescale the original intercept vector so its magnitude is the diameter. This is an approximate way to handle intercepts for circular samples, not recommended for use(!) """ vi = surface_intercept(k,v) r = cartesian_mag(vi) if r <= diameter/2.: return vi # if r>diameter/2 need to scale it back # Therefore solve the following for mag # vi_new = vi - ks*mag/norm(ks), # mag(vi_new) = diameter/2 # therefore end up with a quadratic: ks = num.array(k[0:2]) a = 1. b = -2. * (v[0]*ks[0] + vi[1]*ks[1])/cartesian_mag(ks) c = cartesian_mag(vi)**2. - (diameter/2.)**2. arg = b**2. - 4.*a*c # make sure this method will result in a solution if arg > 0.: mag1 = (-b + num.sqrt(arg))/(2.*a) mag2 = (-b - num.sqrt(arg))/(2.*a) vi1 = vi - ks*mag1/cartesian_mag(ks) vi2 = vi - ks*mag2/cartesian_mag(ks) # chose soln that makes smallest angle # with the original intercept angle1 = num.fabs(cartesian_angle(vi,vi1)) angle2 = num.fabs(cartesian_angle(vi,vi2)) if angle1 < angle2: return vi1 else: return vi2 # otherwise just rescale the original vector else: vi = vi * (diameter/2.) / cartesian_mag(vi) return vi
def test1(): # create a new psic instance psic = Psic(5.6, 5.6, 13, 90, 90, 120, lam=1.3756) # diffr. angles: psic.set_angles(phi=65.78, chi=37.1005, eta=6.6400, mu=0.0, nu=0.0, delta=46.9587) #calc tth, d and magnitude of Q print "\nh=", psic.h print "tth =", psic.pangles['tth'] print "tth =", psic.lattice.tth(psic.h) # reference vector/surface normal in [h,k,l] # n = [0, 0, 1] n = [-0.0348357, -0.00243595, 1] psic.set_n(n) #calc miscut print "\nmiscut=", psic.lattice.angle([0, 0, 1], n, recip=True) # test surf norm n_phi = num.dot(psic.UB, n) n_phi = n_phi / num.fabs(cartesian_mag(n_phi)) print "\nnphi: ", n_phi print "\nsigma_az=", psic.pangles['sigma_az'] print "\ntau_az=", psic.pangles['tau_az'] psic.calc_n(-psic.pangles['sigma_az'], -psic.pangles['tau_az']) print "calc n from sigma and tau az: ", psic.n #calc miscut print "\nmiscut=", psic.lattice.angle([0, 0, 1], psic.n, recip=True) return psic
def active_area(nm,ki=num.array([0.,1.,0.]),kr=num.array([0.,1.,0.]), beam=[],det=None,sample=1.,plot=False,fig=None): """ Calc the area of overlap of beam, sample and detector surface polygon projections. Parameters: ----------- * nm is the surface normal vector, defined in the laboratory frame. * ki and kr are vectors defined in the lab frame parallel to the incident beam and diffracted beam directions respectively (magnitudes are arbitrary) * beam, det and sample are lists that hold the the lab-frame (3D) vectors defining the beam apperature, detector apperature and sample shape If det = None then we ignore it and just compute spill-off correction If sample is a single number we take it as the diamter of a round sample mounted flat. Otherwise sample hould be a list of lab frame vectors that describes the sample polygon. If sample == None, then we assume the sample is infinite in size * If plot = True then makes plot Note: ----- The lab frame coordinate systems is defined such that: x is vertical (perpendicular, pointing to the ceiling of the hutch) y is directed along the incident beam path z make the system right handed and lies in the horizontal scattering plane (i.e. z is parallel to the phi axis) The center (0,0,0) of the lab frame is the rotation center of the instrument. Output: ------- * A_beam = total area of the beam projection into the surface plane * A_int = area of sample illuminated within the detector projection Use to correct scattering data for area effects, including spilloff, i.e. A_ratio = A_int/A_beam Ic = I/A_ratio, I = Idet/Io """ # Calc surf system transformation matrix M = calc_surf_transform(nm) # calc k vectors in the surf frame (i.e. [xs,ys,zs]) # first make them unit vectors, even though no real need to.. ki = ki/cartesian_mag(ki) kr = kr/cartesian_mag(kr) ki_s = num.dot(M,ki) kr_s = num.dot(M,kr) ################################################################# # Get the beam, detector and surface polygons. # Convert vectors to surface frame. # Then for beam and det project the vectors onto # the surface along the k vectors. ie. calc the vector # intercepts with the surface plane # Each of the below polygons is a list of 2D vectors # ie in-plane [x_s, y_s] points ################################################################# # beam if type(beam) == types.ListType: beam_poly = [] if len(beam) < 3: print "Error in beam description" return None for v in beam: vs = num.dot(M,v) int = surface_intercept(ki_s, vs) beam_poly.append(int) else: beam_poly=None # det if type(det) == types.ListType: det_poly = [] if len(det) < 3: print "Error in det description" return None for v in det: vs = num.dot(M,v) int = surface_intercept(kr_s, vs) det_poly.append(int) else: det_poly=None # sample if type(sample) == types.ListType: sam_poly = [] if len(sample) < 3: print "Error in sample description" return None for v in sample: vs = num.dot(M,v) if num.fabs(vs[2]) > 0.01: print "Warning sample hieght problem" sam_poly.append(vs[:2]) sample_shape=True else: sam_poly = None sample_shape = False ##################################################################### # depending on whether we have a sample shape description # or assume a round sample, pass along appropriate # data to compute areas. Note all the polygons are lists # of 2D surface frame vectors (ie in plane vectors) ##################################################################### if sample_shape == False: (A_beam,A_int) = _area_round(beam_poly,det_poly,diameter=sample,plot=plot,fig=fig) else: (A_beam,A_int) = _area_polygon(beam_poly,det_poly,sam_poly,plot=plot,fig=fig) return (A_beam, A_int)