Example #1
0
 def set_spec_G(self, G, preparsed=False):
     """
     Take the spec G array for the psic geometry
     and set all the relevant orientation info...
     """
     if not preparsed:
         (cell, or0, or1, n) = spec_psic_G(G)
     else:
         (cell, or0, or1, n) = G
     self.n = n
     self.or0 = or0
     self.or1 = or1
     self.lattice = Lattice(*cell)
     self._calc_UB()
Example #2
0
 def set_spec_G(self,G):
     """
     Take the spec G array for the psic geometry
     and set all the relevant orientation info...
     """
     (cell,or0,or1,n) = spec_psic_G(G)
     self.n   = n
     self.or0 = or0
     self.or1 = or1
     self.lattice = Lattice(*cell)
     self._calc_UB()
 def set_spec_G(self,G,preparsed=False):
     """
     Take the spec G array for the psic geometry
     and set all the relevant orientation info...
     """
     if not preparsed:
         (cell,or0,or1,n) = spec_psic_G(G)
     else:
         (cell,or0,or1,n) = G
     self.n   = n
     self.or0 = or0
     self.or1 = or1
     self.lattice = Lattice(*cell)
     self._calc_UB()
    def __init__(self,a=10.,b=10.,c=10.,alpha=90.,beta=90.,gamma=90.,lam=1.0):
        """
        Initialize

        Parameters:
        -----------
        * a,b,c in angstroms 
        * alpha, beta, gamma in degrees,
        * lambda in angstroms
        """
        # set lattice and lambda
        self.lattice = Lattice(a,b,c,alpha,beta,gamma,lam)
        
        # hold gonio angles 
        self.angles={'phi':0.0,'chi':0.0,'eta':0.0,'mu':0.0,
                     'nu':0.0,'delta':0.0}

        # hold psuedo angles
        self.pangles = {}
        self.calc_psuedo = True

        # hold n (reference) vector in HKL
        # eg surface normal vector for psuedo angles
        self.n = num.array([0.,0.,1.],dtype=float)
        
        # Z and calc h
        self.Z  = []
        self.Q  = []
        self.ki = []
        self.kr = []
        self.h = [0.,0.,0.]

        # dummy primary reflection
        tth = self.lattice.tth([0.,0.,1.],lam=lam)
        self.or0={'h':num.array([0.,0.,1.]),
                  'phi':0.0,'chi':0.0,'eta':0.0,'mu':tth/2.,
                  'nu':tth,'delta':0.0,'lam':lam}
        
        # dummy secondary reflection
        tth = self.lattice.tth([0.,1.,0.],lam=lam)
        self.or1={'h':num.array([0.,1.,0.]),
                  'phi':0.0,'chi':0.0,'eta':tth/2.,'mu':0.0,
                  'nu':0.0,'delta':tth,'lam':lam}

        # Compute OR matricies
        self.U = []
        self.B = []
        self.UB = []
        self._calc_UB()
Example #5
0
    def __init__(self,
                 a=10.,
                 b=10.,
                 c=10.,
                 alpha=90.,
                 beta=90.,
                 gamma=90.,
                 lam=1.0):
        """
        Initialize

        Parameters:
        -----------
        * a,b,c in angstroms 
        * alpha, beta, gamma in degrees,
        * lambda in angstroms
        """
        # set lattice and lambda
        self.lattice = Lattice(a, b, c, alpha, beta, gamma, lam)

        # hold gonio angles
        self.angles = {
            'phi': 0.0,
            'chi': 0.0,
            'eta': 0.0,
            'mu': 0.0,
            'nu': 0.0,
            'delta': 0.0
        }

        # hold psuedo angles
        self.pangles = {}
        self.calc_psuedo = True

        # hold n (reference) vector in HKL
        # eg surface normal vector for psuedo angles
        self.n = num.array([0., 0., 1.], dtype=float)

        # Z and calc h
        self.Z = []
        self.Q = []
        self.ki = []
        self.kr = []
        self.h = [0., 0., 0.]

        # dummy primary reflection
        tth = self.lattice.tth([0., 0., 1.], lam=lam)
        self.or0 = {
            'h': num.array([0., 0., 1.]),
            'phi': 0.0,
            'chi': 0.0,
            'eta': 0.0,
            'mu': tth / 2.,
            'nu': tth,
            'delta': 0.0,
            'lam': lam
        }

        # dummy secondary reflection
        tth = self.lattice.tth([0., 1., 0.], lam=lam)
        self.or1 = {
            'h': num.array([0., 1., 0.]),
            'phi': 0.0,
            'chi': 0.0,
            'eta': tth / 2.,
            'mu': 0.0,
            'nu': 0.0,
            'delta': tth,
            'lam': lam
        }

        # Compute OR matricies
        self.U = []
        self.B = []
        self.UB = []
        self._calc_UB()
Example #6
0
class Psic:
    """
    Orientation calculations for Psic geometry.

    The default dummy orientation matrix is set up 
    assuming the sample is mounted such that (001) plane
    is perpendicular to the eta and phi rot axes
    (ie c-axis is parrallel to the eta and phi rot axes)
    and that the b-axis is parallel to the nu and mu rot axes
    (ie parrallel to the lab frame Z)

    """

    ###################################################
    def __init__(self,
                 a=10.,
                 b=10.,
                 c=10.,
                 alpha=90.,
                 beta=90.,
                 gamma=90.,
                 lam=1.0):
        """
        Initialize

        Parameters:
        -----------
        * a,b,c in angstroms 
        * alpha, beta, gamma in degrees,
        * lambda in angstroms
        """
        # set lattice and lambda
        self.lattice = Lattice(a, b, c, alpha, beta, gamma, lam)

        # hold gonio angles
        self.angles = {
            'phi': 0.0,
            'chi': 0.0,
            'eta': 0.0,
            'mu': 0.0,
            'nu': 0.0,
            'delta': 0.0
        }

        # hold psuedo angles
        self.pangles = {}
        self.calc_psuedo = True

        # hold n (reference) vector in HKL
        # eg surface normal vector for psuedo angles
        self.n = num.array([0., 0., 1.], dtype=float)

        # Z and calc h
        self.Z = []
        self.Q = []
        self.ki = []
        self.kr = []
        self.h = [0., 0., 0.]

        # dummy primary reflection
        tth = self.lattice.tth([0., 0., 1.], lam=lam)
        self.or0 = {
            'h': num.array([0., 0., 1.]),
            'phi': 0.0,
            'chi': 0.0,
            'eta': 0.0,
            'mu': tth / 2.,
            'nu': tth,
            'delta': 0.0,
            'lam': lam
        }

        # dummy secondary reflection
        tth = self.lattice.tth([0., 1., 0.], lam=lam)
        self.or1 = {
            'h': num.array([0., 1., 0.]),
            'phi': 0.0,
            'chi': 0.0,
            'eta': tth / 2.,
            'mu': 0.0,
            'nu': 0.0,
            'delta': tth,
            'lam': lam
        }

        # Compute OR matricies
        self.U = []
        self.B = []
        self.UB = []
        self._calc_UB()

    ###################################################
    def __repr__(self, ):
        """ display """
        lout = self.lattice.__repr__()
        lout = "%sPrimary:\n   h=%3.2f,k=%3.2f," % (lout, self.or0['h'][0],
                                                    self.or0['h'][1])
        lout = "%sl=%3.2f, lam=%6.6f\n" % (lout, self.or0['h'][2],
                                           self.or0['lam'])
        lout = "%s   phi=%6.3f,chi=%6.3f," % (lout, self.or0['phi'],
                                              self.or0['chi'])
        lout = "%seta=%6.3f,mu=%6.3f," % (lout, self.or0['eta'],
                                          self.or0['mu'])
        lout = "%snu=%6.3f,delta=%6.3f\n" % (lout, self.or0['nu'],
                                             self.or0['delta'])
        #
        lout = "%sSecondary:\n   h=%3.2f,k=%3.2f," % (lout, self.or1['h'][0],
                                                      self.or1['h'][1])
        lout = "%sl=%3.2f, lam=%6.6f\n" % (lout, self.or1['h'][2],
                                           self.or1['lam'])
        lout = "%s   phi=%6.3f,chi=%6.3f," % (lout, self.or1['phi'],
                                              self.or1['chi'])
        lout = "%seta=%6.3f,mu=%6.3f," % (lout, self.or1['eta'],
                                          self.or1['mu'])
        lout = "%snu=%6.3f,delta=%6.3f\n" % (lout, self.or1['nu'],
                                             self.or1['delta'])
        #
        lout = "%sSetting:" % (lout)
        lout = "%s   h=%3.2f,k=%3.2f,l=%3.2f\n" % (lout, self.h[0], self.h[1],
                                                   self.h[2])
        lout = "%s   phi=%6.3f,chi=%6.3f," % (lout, self.angles['phi'],
                                              self.angles['chi'])
        lout = "%seta=%6.3f,mu=%6.3f," % (lout, self.angles['eta'],
                                          self.angles['mu'])
        lout = "%snu=%6.3f,delta=%6.3f\n" % (lout, self.angles['nu'],
                                             self.angles['delta'])
        #
        if self.calc_psuedo:
            lout = "%s   TTH=%6.3f," % (lout, self.pangles['tth'])
            lout = "%sSIGMA_AZ=%6.3f," % (lout, self.pangles['sigma_az'])
            lout = "%sTAU_AZ=%6.3f," % (lout, self.pangles['tau_az'])
            lout = "%sN_AZ=%6.3f," % (lout, self.pangles['naz'])
            lout = "%sALPHA=%6.3f," % (lout, self.pangles['alpha'])
            lout = "%sBETA=%6.3f\n" % (lout, self.pangles['beta'])
            lout = "%s   TAU=%6.3f," % (lout, self.pangles['tau'])
            lout = "%sPSI=%6.3f," % (lout, self.pangles['psi'])
            lout = "%sQ_AZ=%6.3f," % (lout, self.pangles['qaz'])
            lout = "%sOMEGA=%6.3f," % (lout, self.pangles['omega'])
        #
        return lout

    ###################################################
    def set_lat(self,
                a=None,
                b=None,
                c=None,
                alpha=None,
                beta=None,
                gamma=None,
                lam=None):
        """
        Update lattice parameters and lambda

        Parameters:
        -----------
        * a,b,c in angstroms 
        * alpha, beta, gamma in degrees,
        * lambda in angstroms
        """
        self.lattice.update(a=a,
                            b=b,
                            c=c,
                            alpha=alpha,
                            beta=beta,
                            gamma=gamma,
                            lam=lam)
        self._calc_UB()

    def set_spec_G(self, G, preparsed=False):
        """
        Take the spec G array for the psic geometry
        and set all the relevant orientation info...
        """
        if not preparsed:
            (cell, or0, or1, n) = spec_psic_G(G)
        else:
            (cell, or0, or1, n) = G
        self.n = n
        self.or0 = or0
        self.or1 = or1
        self.lattice = Lattice(*cell)
        self._calc_UB()

    ###################################################
    def set_or0(self,
                h=None,
                phi=None,
                chi=None,
                eta=None,
                mu=None,
                nu=None,
                delta=None,
                lam=None):
        """
        Set / adjust the primary orientation reflection

        Parameters:
        -----------
        * h is the hkl array of the reflection
        * the rest of the parameters are motor angles
          in degrees,
        * lam is the wavelength in angstroms
          If lam = None, then lambda defined for the lattice
          is used.
        """
        if h != None: self.or0['h'] = num.array(h, dtype=float)
        if phi != None: self.or0['phi'] = float(phi)
        if chi != None: self.or0['chi'] = float(chi)
        if eta != None: self.or0['eta'] = float(eta)
        if mu != None: self.or0['mu'] = float(mu)
        if nu != None: self.or0['nu'] = float(nu)
        if delta != None: self.or0['delta'] = float(delta)
        if lam != None: self.or0['lam'] = float(lam)
        self._calc_UB()

    def set_or1(self,
                h=None,
                phi=None,
                chi=None,
                eta=None,
                mu=None,
                nu=None,
                delta=None,
                lam=None):
        """
        Set / adjust the secondary orientation reflection

        Parameters:
        -----------
        * h is the hkl array of the reflection
        * the rest of the parameters are motor angles
          in degrees,
        * lam is the wavelength in angstroms
          If lam = None, then lambda defined for the lattice
          is used.
        """
        if h != None: self.or1['h'] = num.array(h, dtype=float)
        if phi != None: self.or1['phi'] = float(phi)
        if chi != None: self.or1['chi'] = float(chi)
        if eta != None: self.or1['eta'] = float(eta)
        if mu != None: self.or1['mu'] = float(mu)
        if nu != None: self.or1['nu'] = float(nu)
        if delta != None: self.or1['delta'] = float(delta)
        if lam != None: self.or1['lam'] = float(lam)
        self._calc_UB()

    def swap_or(self, ):
        """
        Swap the primary and secondary reflection
        """
        tmp = copy.copy(self.or0)
        self.or0 = copy.copy(self.or1)
        self.or1 = tmp
        self._calc_UB()

    ###################################################
    def _calc_UB(self, ):
        """
        Calculate the orientation matrix, U,
        from the primary and secondary
        reflectons and given lattice

        Note dont really ever use B by itself.  so we
        should combine this and above to calc_UB and
        just store UB??
        """
        # use these, note they are used below on vectors
        # defined in the cartesian lab frame basis
        cross = num.cross
        norm = num.linalg.norm

        #Calculate the B matrix
        (a, b, c, alp, bet, gam) = self.lattice.cell()
        (ar, br, cr, alpr, betr, gamr) = self.lattice.rcell()
        B = num.array([[ar, br * cosd(gamr), cr * cosd(betr)],
                       [0., br * sind(gamr), -cr * sind(betr) * cosd(alp)],
                       [0., 0., 1. / c]])
        self.B = B

        # calc Z and Q for the OR reflections
        Z1 = calc_Z(self.or0['phi'], self.or0['chi'], self.or0['eta'],
                    self.or0['mu'])
        Q1 = calc_Q(self.or0['nu'], self.or0['delta'], self.or0['lam'])
        #
        Z2 = calc_Z(self.or1['phi'], self.or1['chi'], self.or1['eta'],
                    self.or1['mu'])
        Q2 = calc_Q(self.or1['nu'], self.or1['delta'], self.or1['lam'])

        # calc the phi frame coords for diffraction vectors
        # note divide out 2pi since the diffraction condition
        # is 2pi*h = Q
        vphi_1 = num.dot(num.linalg.inv(Z1), (Q1 / (2. * num.pi)))
        vphi_2 = num.dot(num.linalg.inv(Z2), (Q2 / (2. * num.pi)))

        #calc cartesian coords of h vectors
        hc_1 = num.dot(self.B, self.or0['h'])
        hc_2 = num.dot(self.B, self.or1['h'])

        #So at this point the following should be true:
        #     vphi_1 = U*hc_1
        #     vphi_2 = U*hc_2
        # and we could use these relations to solve for U.
        # But U solved directly from above is likely not to be orthogonal
        # since the angles btwn (vphi_1 and vphi_2) and (hc_1 and hc_2) are
        # not exactly the same due to expt errors.....
        # Therefore, get an orthogonal solution for U from the below treatment

        #define the following normalized vectors from hc vectors
        tc_1 = hc_1 / norm(hc_1)
        tc_3 = cross(tc_1, hc_2) / norm(cross(tc_1, hc_2))
        tc_2 = cross(tc_3, tc_1) / norm(cross(tc_3, tc_1))

        #define tphi vectors from vphi vectors
        tphi_1 = vphi_1 / norm(vphi_1)
        tphi_3 = cross(tphi_1, vphi_2) / norm(cross(tphi_1, vphi_2))
        tphi_2 = cross(tphi_3, tphi_1) / norm(cross(tphi_3, tphi_1))

        #define the following matrices
        Tc = num.transpose(num.array([tc_1, tc_2, tc_3]))
        Tphi = num.transpose(num.array([tphi_1, tphi_2, tphi_3]))

        # calc orientation matrix U
        # note either of the below work since Tc is orthogonal
        #self.U = num.dot(Tphi, Tc.transpose())
        self.U = num.dot(Tphi, num.linalg.inv(Tc))

        # calc UB
        self.UB = num.dot(self.U, self.B)

        #update h and psuedo angles...
        self.set_angles()

    ###################################################
    def set_angles(self,
                   phi=None,
                   chi=None,
                   eta=None,
                   mu=None,
                   nu=None,
                   delta=None):
        """
        Set goniometer angles (all in degrees)
        """
        if phi != None: self.angles['phi'] = float(phi)
        if chi != None: self.angles['chi'] = float(chi)
        if eta != None: self.angles['eta'] = float(eta)
        if mu != None: self.angles['mu'] = float(mu)
        if nu != None: self.angles['nu'] = float(nu)
        if delta != None: self.angles['delta'] = float(delta)
        # update h, also calc Z etc..
        self._calc_h()
        # update psuedo
        self._update_psuedo()

    def _calc_h(self, ):
        """
        Calculate the hkl values of the vector that is in the
        diffraction condition for the given set of angles.  

        Notes:
        ------
        Solve for hphi using Z and lab frame Q:
           hphi = inv(Z) * Q / (2*pi)
        then calc h from
           h = inv(UB)*hphi
        """
        self.Z = calc_Z(phi=self.angles['phi'],
                        chi=self.angles['chi'],
                        eta=self.angles['eta'],
                        mu=self.angles['mu'])
        (Q, ki, kr) = calc_Q(self.angles['nu'],
                             self.angles['delta'],
                             self.lattice.lam,
                             ret_k=True)
        self.Q = Q
        self.ki = ki
        self.kr = kr

        hphi = num.dot(num.linalg.inv(self.Z), self.Q) / (2. * num.pi)
        h = num.dot(num.linalg.inv(self.UB), hphi)
        self.h = h

    ###################################################
    def set_n(self, n=[0, 0, 1]):
        """
        Set n, the reference vector used for psuedo angles.
        The n vector is given in hkl values.  see calc_n
        to determine n from chi and phi settings
        """
        self.n = num.array(n, dtype=float)
        self._update_psuedo()

    def calc_n(self, fchi=0.0, fphi=0.0):
        """
        Calculate the hkl values of a reference vector given
        the chi and phi settings that align this
        vector with the eta axis.

        Notes:
        ------
        This algorith is used, for example, 
        to compute the surface normal from the (flat) chi and
        (flat) phi angles that leave an optical reflection in
        a fixed position during an eta rotation 

        Note the vector is normalized such that
        the largest component is unity,
        ie n_hkl isn't a unit vector!
        
        """
        # polar angles
        sig_az = -fchi
        tau_az = -fphi

        # this block converts the chi and phi values to correctly
        # defined polar coordinates, ie 0<= sig_az <= 180deg .....
        if sig_az < 0.:
            sig_az = -1. * sig_az
            if tau_az < 0.:
                tau_az = 180. + tau_az
            elif tau_az > 0.:
                tau_az = tau_az - 180.

        # n in the unrotated lab frame (ie phi frame):
        # this is a unit vector!
        n_phi = num.array([
            sind(sig_az) * cosd(tau_az), -sind(sig_az) * sind(tau_az),
            cosd(sig_az)
        ])
        # n in HKL
        n_hkl = num.dot(num.linalg.inv(self.UB), n_phi)
        n_hkl = n_hkl / num.max(num.abs(n_hkl))

        # note if l-component is negative, then its
        # pointing into the surface (ie assume positive L
        # is the positive direction away from the surface)
        # careful here!!
        if n_hkl[2] < 0.:
            n_hkl = -1. * n_hkl

        # set n which triggers recalc of
        # all the psuedo angles
        self.set_n(n_hkl)

    ###################################################
    ## Pseudo angles
    ###################################################
    def _update_psuedo(self):
        """
        Compute psuedo angles
        
        Note:
        -----
        use this to compute psuedo angles rather than
        individual calls.  ie some psuedo angles depend on others
        so its important that the calc are executed in the correct
        order.  Also important is that _calc_h is called before this...
        """
        self.pangles = {}
        if self.calc_psuedo == True:
            self._calc_tth()
            self._calc_nm()
            self._calc_sigma_az()
            self._calc_tau_az()
            self._calc_naz()
            self._calc_alpha()
            self._calc_beta()
            self._calc_tau()
            self._calc_psi()
            self._calc_qaz()
            self._calc_omega()

    def _calc_tth(self):
        """
        Calculate 2Theta, the scattering angle

        Notes:
        ------
        This should be the same as:
          (ki,kr) = calc_kvecs(nu,delta,lambda)
           tth = cartesian_angle(ki,kr)

        You can also get this given h, the reciprocal lattice
        vector that is in the diffraction condition.  E.g.
          h   = self.calc_h()
          tth = self.lattice.tth(h)
        """
        nu = self.angles['nu']
        delta = self.angles['delta']
        tth = arccosd(cosd(delta) * cosd(nu))
        self.pangles['tth'] = tth

    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_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_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_naz(self):
        """
        calc naz, this is the angle btwn the reference vector n 
        and the yz plane at the given angle settings
        """
        # get norm reference vector in cartesian lab frame
        nm = self.nm
        naz = num.arctan2(nm[0], nm[2])
        naz = num.degrees(naz)
        self.pangles['naz'] = naz

    def _calc_alpha(self):
        """
        Calc alpha, ie incidence angle or angle btwn 
        -1*k_in (which is parallel to lab-y) and the
        plane perp to the reference vector n.
        """
        nm = self.nm
        ki = num.array([0., -1., 0.])
        alpha = arcsind(num.dot(nm, ki))
        self.pangles['alpha'] = alpha

    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 _calc_tau(self):
        """
        Calc tau, this is the angle btwn n and the scattering-plane
        defined by ki and kr.  ie the angle between n and Q

        Notes:
        ------
        Can also calc from:
         tau = acos( cosd(alpha) * cosd(tth/2) * cosd(naz - qaz) ...
                    + sind(alpha) * sind(tth/2) ) 
        """
        tau = cartesian_angle(self.Q, self.nm)
        self.pangles['tau'] = tau

    def _calc_psi(self):
        """
        calc psi, this is the azmuthal angle of n wrt Q. 
        ie for tau != 0, psi is the rotation of n about Q

        Notes:
        ------
        Note this must be calc after tth, tau, and alpha!
        """
        tau = self.pangles['tau']
        tth = self.pangles['tth']
        alpha = self.pangles['alpha']
        #beta = self.calc_beta()
        #xx = (-cosd(tau)*sind(tth/2.) + sind(beta))
        xx = (cosd(tau) * sind(tth / 2.) - sind(alpha))
        denom = (sind(tau) * cosd(tth / 2.))
        if denom == 0:
            self.pangles['psi'] = 0.
            return
        xx = xx / denom
        psi = arccosd(xx)
        self.pangles['psi'] = psi

    def _calc_qaz(self):
        """
        Calc qaz, the angle btwn Q and the yz plane 
        """
        nu = self.angles['nu']
        delta = self.angles['delta']
        qaz = num.arctan2(sind(delta), cosd(delta) * sind(nu))
        qaz = num.degrees(qaz)
        self.pangles['qaz'] = qaz

    def _calc_omega(self):
        """
        calc omega, this is the angle between Q and the plane
        which is perpendicular to the axis of the chi circle.

        Notes:
        ------
        For nu=mu=0 this is the same as the four circle def:
        omega = 0.5*TTH - TH, where TTH is the detector motor (=del)
        and TH is the sample circle (=eta).  Therefore, for 
        mu=nu=0 and del=0.5*eta, omega = 0, which means that Q
        is in the plane perpendicular to the chi axis.

        Note check sign of results??? 
        """
        phi = self.angles['phi']
        chi = self.angles['chi']
        eta = self.angles['eta']
        mu = self.angles['mu']
        H = num.array([[cosd(eta), sind(eta), 0.],
                       [-sind(eta), cosd(eta), 0.], [0., 0., 1.]], float)
        M = num.array([[1., 0., 0.], [0., cosd(mu), -sind(mu)],
                       [0., sind(mu), cosd(mu)]], float)
        # check the mult order here!!!!
        # T = num.dot(H.transpose(),M.transpose())
        T = num.dot(M.transpose(), H.transpose())
        Qpp = num.dot(T, self.Q)
        #omega = -1.*cartesian_angle([Qpp[0], 0, Qpp[2]],Qpp)
        omega = cartesian_angle([Qpp[0], 0, Qpp[2]], Qpp)
        self.pangles['omega'] = omega
class Psic:
    """
    Orientation calculations for Psic geometry.

    The default dummy orientation matrix is set up 
    assuming the sample is mounted such that (001) plane
    is perpendicular to the eta and phi rot axes
    (ie c-axis is parrallel to the eta and phi rot axes)
    and that the b-axis is parallel to the nu and mu rot axes
    (ie parrallel to the lab frame Z)

    """
    ###################################################
    def __init__(self,a=10.,b=10.,c=10.,alpha=90.,beta=90.,gamma=90.,lam=1.0):
        """
        Initialize

        Parameters:
        -----------
        * a,b,c in angstroms 
        * alpha, beta, gamma in degrees,
        * lambda in angstroms
        """
        # set lattice and lambda
        self.lattice = Lattice(a,b,c,alpha,beta,gamma,lam)
        
        # hold gonio angles 
        self.angles={'phi':0.0,'chi':0.0,'eta':0.0,'mu':0.0,
                     'nu':0.0,'delta':0.0}

        # hold psuedo angles
        self.pangles = {}
        self.calc_psuedo = True

        # hold n (reference) vector in HKL
        # eg surface normal vector for psuedo angles
        self.n = num.array([0.,0.,1.],dtype=float)
        
        # Z and calc h
        self.Z  = []
        self.Q  = []
        self.ki = []
        self.kr = []
        self.h = [0.,0.,0.]

        # dummy primary reflection
        tth = self.lattice.tth([0.,0.,1.],lam=lam)
        self.or0={'h':num.array([0.,0.,1.]),
                  'phi':0.0,'chi':0.0,'eta':0.0,'mu':tth/2.,
                  'nu':tth,'delta':0.0,'lam':lam}
        
        # dummy secondary reflection
        tth = self.lattice.tth([0.,1.,0.],lam=lam)
        self.or1={'h':num.array([0.,1.,0.]),
                  'phi':0.0,'chi':0.0,'eta':tth/2.,'mu':0.0,
                  'nu':0.0,'delta':tth,'lam':lam}

        # Compute OR matricies
        self.U = []
        self.B = []
        self.UB = []
        self._calc_UB()

    ###################################################
    def __repr__(self,):
        """ display """
        lout = self.lattice.__repr__()
        lout = "%sPrimary:\n   h=%3.2f,k=%3.2f," % (lout,self.or0['h'][0],self.or0['h'][1])
        lout = "%sl=%3.2f, lam=%6.6f\n" % (lout,self.or0['h'][2],self.or0['lam'])
        lout = "%s   phi=%6.3f,chi=%6.3f," % (lout,self.or0['phi'],self.or0['chi'])
        lout = "%seta=%6.3f,mu=%6.3f," % (lout,self.or0['eta'],self.or0['mu'])
        lout = "%snu=%6.3f,delta=%6.3f\n" % (lout,self.or0['nu'],self.or0['delta'])
        #
        lout = "%sSecondary:\n   h=%3.2f,k=%3.2f," % (lout,self.or1['h'][0],self.or1['h'][1])
        lout = "%sl=%3.2f, lam=%6.6f\n" % (lout,self.or1['h'][2],self.or1['lam'])
        lout = "%s   phi=%6.3f,chi=%6.3f," % (lout,self.or1['phi'],self.or1['chi'])
        lout = "%seta=%6.3f,mu=%6.3f," % (lout,self.or1['eta'],self.or1['mu'])
        lout = "%snu=%6.3f,delta=%6.3f\n" % (lout,self.or1['nu'],self.or1['delta'])
        #
        lout = "%sSetting:" % (lout)
        lout = "%s   h=%3.2f,k=%3.2f,l=%3.2f\n" % (lout,self.h[0],self.h[1],self.h[2])
        lout = "%s   phi=%6.3f,chi=%6.3f," % (lout,self.angles['phi'],self.angles['chi'])
        lout = "%seta=%6.3f,mu=%6.3f," % (lout,self.angles['eta'],self.angles['mu'])
        lout = "%snu=%6.3f,delta=%6.3f\n" % (lout,self.angles['nu'],self.angles['delta'])
        #
        if self.calc_psuedo:
            lout = "%s   TTH=%6.3f," % (lout,self.pangles['tth'])
            lout = "%sSIGMA_AZ=%6.3f," % (lout,self.pangles['sigma_az'])
            lout = "%sTAU_AZ=%6.3f," % (lout,self.pangles['tau_az'])
            lout = "%sN_AZ=%6.3f," % (lout,self.pangles['naz'])
            lout = "%sALPHA=%6.3f," % (lout,self.pangles['alpha'])
            lout = "%sBETA=%6.3f\n" % (lout,self.pangles['beta'])
            lout = "%s   TAU=%6.3f," % (lout,self.pangles['tau'])
            lout = "%sPSI=%6.3f," % (lout,self.pangles['psi'])
            lout = "%sQ_AZ=%6.3f," % (lout,self.pangles['qaz'])
            lout = "%sOMEGA=%6.3f," % (lout,self.pangles['omega'])
        #
        return lout
    
    ###################################################
    def set_lat(self,a=None,b=None,c=None,alpha=None,
                beta=None,gamma=None,lam=None):
        """
        Update lattice parameters and lambda

        Parameters:
        -----------
        * a,b,c in angstroms 
        * alpha, beta, gamma in degrees,
        * lambda in angstroms
        """
        self.lattice.update(a=a,b=b,c=c,alpha=alpha,
                            beta=beta,gamma=gamma,lam=lam)
        self._calc_UB()

    def set_spec_G(self,G,preparsed=False):
        """
        Take the spec G array for the psic geometry
        and set all the relevant orientation info...
        """
        if not preparsed:
            (cell,or0,or1,n) = spec_psic_G(G)
        else:
            (cell,or0,or1,n) = G
        self.n   = n
        self.or0 = or0
        self.or1 = or1
        self.lattice = Lattice(*cell)
        self._calc_UB()

    ################################################### 
    def set_or0(self,h=None,phi=None,chi=None,eta=None,
                mu=None,nu=None,delta=None,lam=None):
        """
        Set / adjust the primary orientation reflection

        Parameters:
        -----------
        * h is the hkl array of the reflection
        * the rest of the parameters are motor angles
          in degrees,
        * lam is the wavelength in angstroms
          If lam = None, then lambda defined for the lattice
          is used.
        """
        if h!=None:     self.or0['h'] = num.array(h,dtype=float)
        if phi!=None:   self.or0['phi']=float(phi)
        if chi!=None:   self.or0['chi']=float(chi)
        if eta!=None:   self.or0['eta']=float(eta)
        if mu!=None:    self.or0['mu']=float(mu)
        if nu!=None:    self.or0['nu']=float(nu)
        if delta!=None: self.or0['delta']=float(delta)
        if lam!= None:  self.or0['lam']=float(lam)
        self._calc_UB()

    def set_or1(self,h=None,phi=None,chi=None,eta=None,
                mu=None,nu=None,delta=None,lam=None):
        """
        Set / adjust the secondary orientation reflection

        Parameters:
        -----------
        * h is the hkl array of the reflection
        * the rest of the parameters are motor angles
          in degrees,
        * lam is the wavelength in angstroms
          If lam = None, then lambda defined for the lattice
          is used.
        """
        if h!=None:     self.or1['h'] = num.array(h,dtype=float)
        if phi!=None:   self.or1['phi']=float(phi)
        if chi!=None:   self.or1['chi']=float(chi)
        if eta!=None:   self.or1['eta']=float(eta)
        if mu!=None:    self.or1['mu']=float(mu)
        if nu!=None:    self.or1['nu']=float(nu)
        if delta!=None: self.or1['delta']=float(delta)
        if lam!= None:  self.or1['lam']=float(lam)
        self._calc_UB()

    def swap_or(self,):
        """
        Swap the primary and secondary reflection
        """
        tmp = copy.copy(self.or0)
        self.or0 = copy.copy(self.or1)
        self.or1 = tmp
        self._calc_UB()

    ################################################### 
    def _calc_UB(self,):
        """
        Calculate the orientation matrix, U,
        from the primary and secondary
        reflectons and given lattice

        Note dont really ever use B by itself.  so we
        should combine this and above to calc_UB and
        just store UB??
        """
        # use these, note they are used below on vectors
        # defined in the cartesian lab frame basis
        cross = num.cross
        norm  = num.linalg.norm

        #Calculate the B matrix
        (a,b,c,alp,bet,gam)       = self.lattice.cell()
        (ar,br,cr,alpr,betr,gamr) = self.lattice.rcell()
        B = num.array([[ar,  br*cosd(gamr),     cr*cosd(betr)        ],
                       [0.,  br*sind(gamr),  -cr*sind(betr)*cosd(alp)],
                       [0.,      0.,                 1./c            ]])
        self.B = B
        
        # calc Z and Q for the OR reflections
        Z1 = calc_Z(self.or0['phi'],self.or0['chi'],self.or0['eta'],self.or0['mu'])
        Q1 = calc_Q(self.or0['nu'],self.or0['delta'],self.or0['lam'])
        #
        Z2 = calc_Z(self.or1['phi'],self.or1['chi'],self.or1['eta'],self.or1['mu'])
        Q2 = calc_Q(self.or1['nu'],self.or1['delta'],self.or1['lam'])

        # calc the phi frame coords for diffraction vectors
        # note divide out 2pi since the diffraction condition
        # is 2pi*h = Q
        vphi_1 = num.dot(num.linalg.inv(Z1), (Q1/(2.*num.pi)))
        vphi_2 = num.dot(num.linalg.inv(Z2), (Q2/(2.*num.pi)))
        
        #calc cartesian coords of h vectors
        hc_1 = num.dot(self.B, self.or0['h'])
        hc_2 = num.dot(self.B, self.or1['h'])

        #So at this point the following should be true:
        #     vphi_1 = U*hc_1
        #     vphi_2 = U*hc_2
        # and we could use these relations to solve for U.
        # But U solved directly from above is likely not to be orthogonal
        # since the angles btwn (vphi_1 and vphi_2) and (hc_1 and hc_2) are 
        # not exactly the same due to expt errors.....
        # Therefore, get an orthogonal solution for U from the below treatment
        
        #define the following normalized vectors from hc vectors 
        tc_1 = hc_1 / norm(hc_1)
        tc_3 = cross(tc_1, hc_2) / norm(cross(tc_1, hc_2))
        tc_2 = cross(tc_3, tc_1) / norm(cross(tc_3, tc_1))

        #define tphi vectors from vphi vectors
        tphi_1 = vphi_1 / norm(vphi_1)
        tphi_3 = cross(tphi_1,vphi_2) / norm(cross(tphi_1,vphi_2))
        tphi_2 = cross(tphi_3,tphi_1) / norm(cross(tphi_3,tphi_1))

        #define the following matrices
        Tc   = num.transpose(num.array([tc_1,tc_2,tc_3]))
        Tphi = num.transpose(num.array([tphi_1,tphi_2,tphi_3]))
        
        # calc orientation matrix U
        # note either of the below work since Tc is orthogonal
        #self.U = num.dot(Tphi, Tc.transpose())
        self.U = num.dot(Tphi, num.linalg.inv(Tc))

        # calc UB
        self.UB = num.dot(self.U,self.B)

        #update h and psuedo angles...
        self.set_angles()

    ###################################################
    def set_angles(self,phi=None,chi=None,eta=None,
                   mu=None,nu=None,delta=None):
        """
        Set goniometer angles (all in degrees)
        """
        if phi!=None:   self.angles['phi']=float(phi)
        if chi!=None:   self.angles['chi']=float(chi)
        if eta!=None:   self.angles['eta']=float(eta)
        if mu!=None:    self.angles['mu']=float(mu)
        if nu!=None:    self.angles['nu']=float(nu)
        if delta!=None: self.angles['delta']=float(delta)
        # update h, also calc Z etc..
        self._calc_h()
        # update psuedo
        self._update_psuedo()
        
    def _calc_h(self,):
        """
        Calculate the hkl values of the vector that is in the
        diffraction condition for the given set of angles.  

        Notes:
        ------
        Solve for hphi using Z and lab frame Q:
           hphi = inv(Z) * Q / (2*pi)
        then calc h from
           h = inv(UB)*hphi
        """
        self.Z = calc_Z(phi=self.angles['phi'],chi=self.angles['chi'],
                        eta=self.angles['eta'],mu=self.angles['mu'])
        (Q,ki,kr) = calc_Q(self.angles['nu'],
                           self.angles['delta'],
                           self.lattice.lam,ret_k=True)
        self.Q=Q
        self.ki=ki
        self.kr=kr
        
        hphi = num.dot(num.linalg.inv(self.Z),self.Q) / (2.*num.pi) 
        h    = num.dot(num.linalg.inv(self.UB),hphi)
        self.h = h
        
    ###################################################
    def set_n(self,n=[0,0,1]):
        """
        Set n, the reference vector used for psuedo angles.
        The n vector is given in hkl values.  see calc_n
        to determine n from chi and phi settings
        """
        self.n = num.array(n,dtype=float)
        self._update_psuedo()

    def calc_n(self,fchi=0.0,fphi=0.0):
        """
        Calculate the hkl values of a reference vector given
        the chi and phi settings that align this
        vector with the eta axis.

        Notes:
        ------
        This algorith is used, for example, 
        to compute the surface normal from the (flat) chi and
        (flat) phi angles that leave an optical reflection in
        a fixed position during an eta rotation 

        Note the vector is normalized such that
        the largest component is unity,
        ie n_hkl isn't a unit vector!
        
        """
        # polar angles
        sig_az = -fchi
        tau_az = -fphi

        # this block converts the chi and phi values to correctly 
        # defined polar coordinates, ie 0<= sig_az <= 180deg .....
        if sig_az < 0.:
            sig_az = -1.*sig_az
            if tau_az < 0.:
                tau_az = 180. + tau_az
            elif tau_az > 0.:
                tau_az = tau_az - 180.

        # n in the unrotated lab frame (ie phi frame):
        # this is a unit vector!
        n_phi = num.array([ sind(sig_az)*cosd(tau_az),
                           -sind(sig_az)*sind(tau_az), 
                                  cosd(sig_az)        ])
        # n in HKL
        n_hkl = num.dot(num.linalg.inv(self.UB),n_phi)
        n_hkl = n_hkl/ num.max(num.abs(n_hkl))
        
        # note if l-component is negative, then its
        # pointing into the surface (ie assume positive L
        # is the positive direction away from the surface)
        # careful here!!
        if n_hkl[2] < 0.:
            n_hkl = -1.*n_hkl

        # set n which triggers recalc of
        # all the psuedo angles
        self.set_n(n_hkl)

    ################################################### 
    ## Pseudo angles
    ###################################################
    def _update_psuedo(self):
        """
        Compute psuedo angles
        
        Note:
        -----
        use this to compute psuedo angles rather than
        individual calls.  ie some psuedo angles depend on others
        so its important that the calc are executed in the correct
        order.  Also important is that _calc_h is called before this...
        """
        self.pangles = {}
        if self.calc_psuedo == True:
            self._calc_tth()
            self._calc_nm()
            self._calc_sigma_az()
            self._calc_tau_az()
            self._calc_naz()
            self._calc_alpha()
            self._calc_beta()
            self._calc_tau()
            self._calc_psi()
            self._calc_qaz()
            self._calc_omega()
    
    def _calc_tth(self):
        """
        Calculate 2Theta, the scattering angle

        Notes:
        ------
        This should be the same as:
          (ki,kr) = calc_kvecs(nu,delta,lambda)
           tth = cartesian_angle(ki,kr)

        You can also get this given h, the reciprocal lattice
        vector that is in the diffraction condition.  E.g.
          h   = self.calc_h()
          tth = self.lattice.tth(h)
        """
        nu    = self.angles['nu']
        delta = self.angles['delta']
        tth   = arccosd(cosd(delta)*cosd(nu))
        self.pangles['tth'] = tth

    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_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_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_naz(self):
        """
        calc naz, this is the angle btwn the reference vector n 
        and the yz plane at the given angle settings
        """
        # get norm reference vector in cartesian lab frame
        nm  = self.nm
        naz = num.arctan2( nm[0], nm[2] )
        naz = num.degrees(naz)
        self.pangles['naz'] = naz

    def _calc_alpha(self):
        """
        Calc alpha, ie incidence angle or angle btwn 
        -1*k_in (which is parallel to lab-y) and the
        plane perp to the reference vector n.
        """
        nm = self.nm
        ki = num.array([0.,-1.,0.])
        alpha = arcsind(num.dot(nm,ki))
        self.pangles['alpha'] = alpha

    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 _calc_tau(self):
        """
        Calc tau, this is the angle btwn n and the scattering-plane
        defined by ki and kr.  ie the angle between n and Q

        Notes:
        ------
        Can also calc from:
         tau = acos( cosd(alpha) * cosd(tth/2) * cosd(naz - qaz) ...
                    + sind(alpha) * sind(tth/2) ) 
        """
        tau = cartesian_angle(self.Q,self.nm)
        self.pangles['tau'] = tau

    def _calc_psi(self):
        """
        calc psi, this is the azmuthal angle of n wrt Q. 
        ie for tau != 0, psi is the rotation of n about Q

        Notes:
        ------
        Note this must be calc after tth, tau, and alpha!
        """
        tau   = self.pangles['tau']
        tth   = self.pangles['tth']
        alpha = self.pangles['alpha']
        #beta = self.calc_beta()
        #xx = (-cosd(tau)*sind(tth/2.) + sind(beta))
        xx    = (cosd(tau)*sind(tth/2.) - sind(alpha))
        denom = (sind(tau)*cosd(tth/2.))
        if denom == 0: 
            self.pangles['psi'] = 0.
            return
        xx    = xx /denom
        psi = arccosd( xx )
        self.pangles['psi'] = psi

    def _calc_qaz(self):
        """
        Calc qaz, the angle btwn Q and the yz plane 
        """
        nu    = self.angles['nu']
        delta = self.angles['delta']
        qaz = num.arctan2(sind(delta), cosd(delta)*sind(nu) )
        qaz = num.degrees(qaz)
        self.pangles['qaz'] = qaz

    def _calc_omega(self):
        """
        calc omega, this is the angle between Q and the plane
        which is perpendicular to the axis of the chi circle.

        Notes:
        ------
        For nu=mu=0 this is the same as the four circle def:
        omega = 0.5*TTH - TH, where TTH is the detector motor (=del)
        and TH is the sample circle (=eta).  Therefore, for 
        mu=nu=0 and del=0.5*eta, omega = 0, which means that Q
        is in the plane perpendicular to the chi axis.

        Note check sign of results??? 
        """
        phi=self.angles['phi']
        chi=self.angles['chi']
        eta=self.angles['eta']
        mu=self.angles['mu']
        H = num.array([[ cosd(eta), sind(eta), 0.],
                       [-sind(eta), cosd(eta), 0.],
                       [   0.,         0.,     1.]],float)
        M  = num.array([[  1.,         0.,     0.      ],
                        [  0.,      cosd(mu), -sind(mu)],
                        [  0.,      sind(mu), cosd(mu)]],float)
        # check the mult order here!!!!
        # T = num.dot(H.transpose(),M.transpose())
        T     = num.dot(M.transpose(),H.transpose())
        Qpp   = num.dot(T,self.Q)
        #omega = -1.*cartesian_angle([Qpp[0], 0, Qpp[2]],Qpp)
        omega = cartesian_angle([Qpp[0], 0, Qpp[2]],Qpp)
        self.pangles['omega'] = omega