Ejemplo n.º 1
0
 def __init__(self, ubcalc, raiseExceptionsIfAnglesDoNotMapBackToHkl=True):
     r = raiseExceptionsIfAnglesDoNotMapBackToHkl
     HklCalculatorBase.__init__(self,
                                ubcalc,
                                raiseExceptionsIfAnglesDoNotMapBackToHkl=r)
     self._gammaParameterName = ({
         'arm': 'gamma',
         'base': 'oopgamma'
     }[settings.geometry.gamma_location])
     self.mode_selector = ModeSelector(settings.geometry, None,
                                       self._gammaParameterName)
     self.parameter_manager = VliegParameterManager(
         settings.geometry, settings.hardware, self.mode_selector,
         self._gammaParameterName)
     self.mode_selector.setParameterManager(self.parameter_manager)
    def setup_method(self):

        self.ms = ModeSelector(createMockDiffractometerGeometry(),
                               parameterManager=None)
        self.pm = VliegParameterManager(createMockDiffractometerGeometry(),
                                        None, self.ms)
        self.ms.setParameterManager(self.pm)
Ejemplo n.º 3
0
 def __init__(self, ubcalc, geometry, hardware,
              raiseExceptionsIfAnglesDoNotMapBackToHkl=True):
     r = raiseExceptionsIfAnglesDoNotMapBackToHkl
     HklCalculatorBase.__init__(self, ubcalc, geometry, hardware,
                                raiseExceptionsIfAnglesDoNotMapBackToHkl=r)
     self._gammaParameterName = ({'arm': 'gamma', 'base': 'oopgamma'}
                                 [self._geometry.gamma_location])
     self.mode_selector = ModeSelector(self._geometry, None,
                                       self._gammaParameterName)
     self.parameter_manager = VliegParameterManager(
         self._geometry, self._hardware, self.mode_selector,
         self._gammaParameterName)
     self.mode_selector.setParameterManager(self.parameter_manager)
 def setUp(self):
     self.hw = createMockHardwareMonitor()
     self.ms = ModeSelector(createMockDiffractometerGeometry())
     self.pm = VliegParameterManager(createMockDiffractometerGeometry(),
                                     self.hw, self.ms)
class TestParameterManager(unittest.TestCase):

    def setUp(self):
        self.hw = createMockHardwareMonitor()
        self.ms = ModeSelector(createMockDiffractometerGeometry())
        self.pm = VliegParameterManager(createMockDiffractometerGeometry(),
                                        self.hw, self.ms)

    def testDefaultParameterValues(self):
        self.assertEqual(self.pm.get_constraint('alpha'), 0)
        self.assertEqual(self.pm.get_constraint('gamma'), 0)
        self.assertRaises(DiffcalcException, self.pm.get_constraint,
                          'not-a-parameter-name')

    def testSetParameter(self):
        self.pm.set_constraint('alpha', 10.1)
        self.assertEqual(self.pm.get_constraint('alpha'), 10.1)

    def testSetTrackParameter_isParameterChecked(self):
        self.assertEqual(self.pm.isParameterTracked('alpha'), False)
        self.pm.set_constraint('alpha', 9)

        self.pm.setTrackParameter('alpha', True)
        self.assertEqual(self.pm.isParameterTracked('alpha'), True)
        self.assertRaises(DiffcalcException, self.pm.set_constraint, 'alpha', 10)
        self.hw.get_position.return_value = 888, 11, 999
        self.assertEqual(self.pm.get_constraint('alpha'), 11)

        print self.pm.reportAllParameters()
        print "**"
        print self.ms.reportCurrentMode()
        print self.pm.reportParametersUsedInCurrentMode()

        self.pm.setTrackParameter('alpha', False)
        self.assertEqual(self.pm.isParameterTracked('alpha'), False)
        self.assertEqual(self.pm.get_constraint('alpha'), 11)
        self.hw.get_position.return_value = 888, 12, 999
        self.assertEqual(self.pm.get_constraint('alpha'), 11)
        self.pm.set_constraint('alpha', 13)
        self.assertEqual(self.pm.get_constraint('alpha'), 13)
Ejemplo n.º 6
0
class VliegHklCalculator(HklCalculatorBase):

    def __init__(self, ubcalc, geometry, hardware,
                 raiseExceptionsIfAnglesDoNotMapBackToHkl=True):
        r = raiseExceptionsIfAnglesDoNotMapBackToHkl
        HklCalculatorBase.__init__(self, ubcalc, geometry, hardware,
                                   raiseExceptionsIfAnglesDoNotMapBackToHkl=r)
        self._gammaParameterName = ({'arm': 'gamma', 'base': 'oopgamma'}
                                    [self._geometry.gamma_location])
        self.mode_selector = ModeSelector(self._geometry, None,
                                          self._gammaParameterName)
        self.parameter_manager = VliegParameterManager(
            self._geometry, self._hardware, self.mode_selector,
            self._gammaParameterName)
        self.mode_selector.setParameterManager(self.parameter_manager)

    def __str__(self):
        # should list paramemeters and indicate which are used in selected mode
        result = "Available mode_selector:\n"
        result += self.mode_selector.reportAvailableModes()
        result += '\nCurrent mode:\n'
        result += self.mode_selector.reportCurrentMode()
        result += '\n\nParameters:\n'
        result += self.parameter_manager.reportAllParameters()
        return result

    def _anglesToHkl(self, pos, wavelength):
        """
        Return hkl tuple from VliegPosition in radians and wavelength in
        Angstroms.
        """
        return vliegAnglesToHkl(pos, wavelength, self._getUBMatrix())

    def _anglesToVirtualAngles(self, pos, wavelength):
        """
        Return dictionary of all virtual angles in radians from VliegPosition
        object win radians and wavelength in Angstroms. The virtual angles are:
        Bin, Bout, azimuth and 2theta.
        """

        # Create transformation matrices
        [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices(
            pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi)
        [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
            self._getSigma() * TORAD, self._getTau() * TORAD)

        S = TAU * SIGMA
        y_vector = matrix([[0], [1], [0]])

        # Calculate Bin from equation 15:
        surfacenormal_alpha = OMEGA * CHI * PHI * S * matrix([[0], [0], [1]])
        incoming_alpha = ALPHA.I * y_vector
        minusSinBetaIn = dot3(surfacenormal_alpha, incoming_alpha)
        Bin = asin(bound(-minusSinBetaIn))

        # Calculate Bout from equation 16:
        #  surfacenormal_alpha has just ben calculated
        outgoing_alpha = DELTA * GAMMA * y_vector
        sinBetaOut = dot3(surfacenormal_alpha, outgoing_alpha)
        Bout = asin(bound(sinBetaOut))

        # Calculate 2theta from equation 25:

        cosTwoTheta = dot3(ALPHA * DELTA * GAMMA * y_vector, y_vector)
        twotheta = acos(bound(cosTwoTheta))
        psi = self._anglesToPsi(pos, wavelength)

        return {'Bin': Bin, 'Bout': Bout, 'azimuth': psi, '2theta': twotheta}

    def _hklToAngles(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstroms. The virtual angles are those fixed or
        generated while calculating the position: Bin, Bout and 2theta; and
        azimuth in four and five circle modes.
        """

        if self._getMode().group in ("fourc", "fivecFixedGamma",
                                     "fivecFixedAlpha"):
            return self._hklToAnglesFourAndFiveCirclesModes(h, k, l,
                                                            wavelength)
        elif self._getMode().group == "zaxis":
            return self._hklToAnglesZaxisModes(h, k, l, wavelength)
        else:
            raise RuntimeError(
                'The current mode (%s) has an unrecognised group: %s.'
                 % (self._getMode().name, self._getMode().group))

    def _hklToAnglesFourAndFiveCirclesModes(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstrom for four and five circle modes. The virtual
        angles are those fixed or generated while calculating the position:
        Bin, Bout, 2theta and azimuth.
        """

        # Results in radians during calculations, returned in degreess
        pos = VliegPosition(None, None, None, None, None, None)

        # Normalise hkl
        wavevector = 2 * pi / wavelength
        hklNorm = matrix([[h], [k], [l]]) / wavevector

        # Compute hkl in phi axis coordinate frame
        hklPhiNorm = self._getUBMatrix() * hklNorm

        # Determine Bin and Bout
        if self._getMode().name == '4cPhi':
            Bin = Bout = None
        else:
            Bin, Bout = self._determineBinAndBoutInFourAndFiveCirclesModes(
                                                                    hklNorm)

        # Determine alpha and gamma
        if self._getMode().group == 'fourc':
            pos.alpha, pos.gamma = \
                self._determineAlphaAndGammaForFourCircleModes(hklPhiNorm)
        else:
            pos.alpha, pos.gamma = \
                self._determineAlphaAndGammaForFiveCircleModes(Bin, hklPhiNorm)
        if pos.alpha < -pi:
            pos.alpha += 2 * pi
        if pos.alpha > pi:
            pos.alpha -= 2 * pi

        # Determine delta
        (pos.delta, twotheta) = self._determineDelta(hklPhiNorm, pos.alpha,
                                                     pos.gamma)

        # Determine omega, chi & phi
        pos.omega, pos.chi, pos.phi, psi = \
            self._determineSampleAnglesInFourAndFiveCircleModes(
                hklPhiNorm, pos.alpha, pos.delta, pos.gamma, Bin)
        # (psi will be None in fixed phi mode)

        # Ensure that by default omega is between -90 and 90, by possibly
        # transforming the sample angles
        if self._getMode().name != '4cPhi':  # not in fixed-phi mode
            if pos.omega < -pi / 2 or pos.omega > pi / 2:
                pos = transformC.transform(pos)

        # Gather up the virtual angles calculated along the way...
        #   -pi<psi<=pi
        if psi is not None:
            if psi > pi:
                psi -= 2 * pi
            if psi < (-1 * pi):
                psi += 2 * pi

        v = {'2theta': twotheta, 'Bin': Bin, 'Bout': Bout, 'azimuth': psi}
        return pos, v

    def _hklToAnglesZaxisModes(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstroms for z-axis modes. The virtual angles are those
        fixed or generated while calculating the position: Bin, Bout, and
        2theta.
        """
        # Section 6:

        # Results in radians during calculations, returned in degreess
        pos = VliegPosition(None, None, None, None, None, None)

        # Normalise hkl
        wavevector = 2 * pi / wavelength
        hkl = matrix([[h], [k], [l]])
        hklNorm = hkl * (1.0 / wavevector)

        # Compute hkl in phi axis coordinate frame
        hklPhi = self._getUBMatrix() * hkl
        hklPhiNorm = self._getUBMatrix() * hklNorm

        # Determine Chi and Phi (Equation 29):
        pos.phi = -self._getTau() * TORAD
        pos.chi = -self._getSigma() * TORAD

        # Equation 30:
        [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices(
                None, None, None, None, pos.chi, pos.phi)
        del ALPHA, DELTA, GAMMA, OMEGA
        Hw = CHI * PHI * hklPhi

        # Determine Bin and Bout:
        (Bin, Bout) = self._determineBinAndBoutInZaxisModes(
            Hw[2, 0] / wavevector)

        # Determine Alpha and Gamma (Equation 32):
        pos.alpha = Bin
        pos.gamma = Bout

        # Determine Delta:
        (pos.delta, twotheta) = self._determineDelta(hklPhiNorm, pos.alpha,
                                                     pos.gamma)

        # Determine Omega:
        delta = pos.delta
        gamma = pos.gamma
        d1 = (Hw[1, 0] * sin(delta) * cos(gamma) - Hw[0, 0] *
              (cos(delta) * cos(gamma) - cos(pos.alpha)))
        d2 = (Hw[0, 0] * sin(delta) * cos(gamma) + Hw[1, 0] *
              (cos(delta) * cos(gamma) - cos(pos.alpha)))

        if fabs(d2) < 1e-30:
            pos.omega = sign(d1) * sign(d2) * pi / 2.0
        else:
            pos.omega = atan2(d1, d2)

        # Gather up the virtual angles calculated along the way
        return pos, {'2theta': twotheta, 'Bin': Bin, 'Bout': Bout}

###

    def _determineBinAndBoutInFourAndFiveCirclesModes(self, hklNorm):
        """(Bin, Bout) = _determineBinAndBoutInFourAndFiveCirclesModes()"""
        BinModes = ('4cBin', '5cgBin', '5caBin')
        BoutModes = ('4cBout', '5cgBout', '5caBout')
        BeqModes = ('4cBeq', '5cgBeq', '5caBeq')
        azimuthModes = ('4cAzimuth')
        fixedBusingAndLeviWmodes = ('4cFixedw')

        # Calculate RHS of equation 20
        # RHS (1/K)(S^-1*U*B*H)_3 where H/K = hklNorm
        UB = self._getUBMatrix()
        [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
            self._getSigma() * TORAD, self._getTau() * TORAD)
        #S = SIGMA * TAU
        S = TAU * SIGMA
        RHS = (S.I * UB * hklNorm)[2, 0]

        if self._getMode().name in BinModes:
            Bin = self._getParameter('betain')
            check(Bin != None, "The parameter betain must be set for mode %s" %
                  self._getMode().name)
            Bin = Bin * TORAD
            sinBout = RHS - sin(Bin)
            check(fabs(sinBout) <= 1, "Could not compute Bout")
            Bout = asin(sinBout)

        elif self._getMode().name in BoutModes:
            Bout = self._getParameter('betaout')
            check(Bout != None, "The parameter Bout must be set for mode %s" %
                  self._getMode().name)
            Bout = Bout * TORAD
            sinBin = RHS - sin(Bout)
            check(fabs(sinBin) <= 1, "Could not compute Bin")
            Bin = asin(sinBin)

        elif self._getMode().name in BeqModes:
            sinBeq = RHS / 2
            check(fabs(sinBeq) <= 1, "Could not compute Bin=Bout")
            Bin = Bout = asin(sinBeq)

        elif self._getMode().name in azimuthModes:
            azimuth = self._getParameter('azimuth')
            check(azimuth != None, "The parameter azimuth must be set for "
                  "mode %s" % self._getMode().name)
            del azimuth
            # TODO: codeit
            raise NotImplementedError()

        elif self._getMode().name in fixedBusingAndLeviWmodes:
            bandlomega = self._getParameter('blw')
            check(bandlomega != None, "The parameter abandlomega must be set "
                  "for mode %s" % self._getMode().name)
            del bandlomega
            # TODO: codeit
            raise NotImplementedError()
        else:
            raise RuntimeError("AngleCalculator does not know how to handle "
                               "mode %s" % self._getMode().name)

        return (Bin, Bout)

    def _determineBinAndBoutInZaxisModes(self, Hw3OverK):
        """(Bin, Bout) = _determineBinAndBoutInZaxisModes(HwOverK)"""
        BinModes = ('6czBin')
        BoutModes = ('6czBout')
        BeqModes = ('6czBeq')

        if self._getMode().name in BinModes:
            Bin = self._getParameter('betain')
            check(Bin != None, "The parameter betain must be set for mode %s" %
                  self._getMode().name)
            Bin = Bin * TORAD
            # Equation 32a:
            Bout = asin(Hw3OverK - sin(Bin))

        elif self._getMode().name in BoutModes:
            Bout = self._getParameter('betaout')
            check(Bout != None, "The parameter Bout must be set for mode %s" %
                  self._getMode().name)
            Bout = Bout * TORAD
            # Equation 32b:
            Bin = asin(Hw3OverK - sin(Bout))

        elif self._getMode().name in BeqModes:
            # Equation 32c:
            Bin = Bout = asin(Hw3OverK / 2)

        return (Bin, Bout)

###

    def _determineAlphaAndGammaForFourCircleModes(self, hklPhiNorm):

        if self._getMode().group == 'fourc':
            alpha = self._getParameter('alpha') * TORAD
            gamma = self._getParameter(self._getGammaParameterName()) * TORAD
            check(alpha != None, "alpha parameter must be set in fourc modes")
            check(gamma != None, "gamma parameter must be set in fourc modes")
            return alpha, gamma
        else:
            raise RuntimeError(
                "determineAlphaAndGammaForFourCirclesModes() "
                "is not appropriate for %s modes" % self._getMode().group)

    def _determineAlphaAndGammaForFiveCircleModes(self, Bin, hklPhiNorm):

        ## Solve equation 34 for one possible Y, Yo
        # Calculate surface normal in phi frame
        [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
            self._getSigma() * TORAD, self._getTau() * TORAD)
        S = TAU * SIGMA
        surfaceNormalPhi = S * matrix([[0], [0], [1]])
        # Compute beta in vector
        BetaVector = matrix([[0], [-sin(Bin)], [cos(Bin)]])
        # Find Yo
        Yo = self._findMatrixToTransformAIntoB(surfaceNormalPhi, BetaVector)

        ## Calculate Hv from equation 39
        Z = matrix([[1, 0, 0],
                    [0, cos(Bin), sin(Bin)],
                    [0, -sin(Bin), cos(Bin)]])
        Hv = Z * Yo * hklPhiNorm
        # Fixed gamma:
        if self._getMode().group == 'fivecFixedGamma':
            gamma = self._getParameter(self._getGammaParameterName())
            check(gamma != None,
                  "gamma parameter must be set in fivecFixedGamma modes")
            gamma = gamma * TORAD
            H2 = (hklPhiNorm[0, 0] ** 2 + hklPhiNorm[1, 0] ** 2 +
                  hklPhiNorm[2, 0] ** 2)
            a = -(0.5 * H2 * sin(Bin) - Hv[2, 0])
            b = -(1.0 - 0.5 * H2) * cos(Bin)
            c = cos(Bin) * sin(gamma)
            check((b * b + a * a - c * c) >= 0, 'Could not solve for alpha')
            alpha = 2 * atan2(-(b + sqrt(b * b + a * a - c * c)), -(a + c))

        # Fixed Alpha:
        elif self._getMode().group == 'fivecFixedAlpha':
            alpha = self._getParameter('alpha')
            check(alpha != None,
                  "alpha parameter must be set in fivecFixedAlpha modes")
            alpha = alpha * TORAD
            H2 = (hklPhiNorm[0, 0] ** 2 + hklPhiNorm[1, 0] ** 2 +
                  hklPhiNorm[2, 0] ** 2)
            t0 = ((2 * cos(alpha) * Hv[2, 0] - sin(Bin) * cos(alpha) * H2 +
                  cos(Bin) * sin(alpha) * H2 - 2 * cos(Bin) * sin(alpha)) /
                  (cos(Bin) * 2.0))
            check(abs(t0) <= 1, "Cannot compute gamma: sin(gamma)>1")
            gamma = asin(t0)
        else:
            raise RuntimeError(
                "determineAlphaAndGammaInFiveCirclesModes() is not "
                "appropriate for %s modes" % self._getMode().group)

        return (alpha, gamma)

###

    def _determineDelta(self, hklPhiNorm, alpha, gamma):
        """
        (delta, twotheta) = _determineDelta(hklPhiNorm, alpha, gamma) --
        computes delta for all modes. Also returns twotheta for sanity
        checking. hklPhiNorm is a 3X1 matrix.

        alpha, gamma & delta - in radians.
        h k & l normalised to wavevector and in phi axis coordinates
        """
        h = hklPhiNorm[0, 0]
        k = hklPhiNorm[1, 0]
        l = hklPhiNorm[2, 0]
        # See Vlieg section 5 (with K=1)
        cosdelta = ((1 + sin(gamma) * sin(alpha) - (h * h + k * k + l * l) / 2)
                    / (cos(gamma) * cos(alpha)))
        costwotheta = (cos(alpha) * cos(gamma) * bound(cosdelta) -
                       sin(alpha) * sin(gamma))
        return (acos(bound(cosdelta)), acos(bound(costwotheta)))

    def _determineSampleAnglesInFourAndFiveCircleModes(self, hklPhiNorm, alpha,
                                                       delta, gamma, Bin):
        """
        (omega, chi, phi, psi)=determineNonZAxisSampleAngles(hklPhiNorm, alpha,
        delta, gamma, sigma, tau) where hkl has been normalised by the
        wavevector and is in the phi Axis coordinate frame. All angles in
        radians. hklPhiNorm is a 3X1 matrix
        """

        def equation49through59(psi):
            # equation 49 R = (D^-1)*PI*D*Ro
            PSI = createVliegsPsiTransformationMatrix(psi)
            R = D.I * PSI * D * Ro

            #  eq 57: extract omega from R
            if abs(R[0, 2]) < 1e-20:
                omega = -sign(R[1, 2]) * sign(R[0, 2]) * pi / 2
            else:
                omega = -atan2(R[1, 2], R[0, 2])

            # eq 58: extract chi from R
            sinchi = sqrt(pow(R[0, 2], 2) + pow(R[1, 2], 2))
            sinchi = bound(sinchi)
            check(abs(sinchi) <= 1, 'could not compute chi')
            # (there are two roots to this equation, but only the first is also
            # a solution to R33=cos(chi))
            chi = asin(sinchi)

            # eq 59: extract phi from R
            if abs(R[2, 0]) < 1e-20:
                phi = sign(R[2, 1]) * sign(R[2, 1]) * pi / 2
            else:
                phi = atan2(-R[2, 1], -R[2, 0])
            return omega, chi, phi

        def checkSolution(omega, chi, phi):
            _, _, _, OMEGA, CHI, PHI = createVliegMatrices(
                None, None, None, omega, chi, phi)
            R = OMEGA * CHI * PHI
            RtimesH_phi = R * H_phi
            print ("R*H_phi=%s, Q_alpha=%s" %
                   (R * H_phi.tolist(), Q_alpha.tolist()))
            return not differ(RtimesH_phi, Q_alpha, .0001)

        # Using Vlieg section 7.2

        # Needed througout:
        [ALPHA, DELTA, GAMMA, _, _, _] = createVliegMatrices(
            alpha, delta, gamma, None, None, None)

        ## Find Ro, one possible solution to equation 46: R*H_phi=Q_alpha

        # Normalise hklPhiNorm (As it is currently normalised only to the
        # wavevector)
        normh = norm(hklPhiNorm)
        check(normh >= 1e-10, "reciprical lattice vector too close to zero")
        H_phi = hklPhiNorm * (1 / normh)

        # Create Q_alpha from equation 47, (it comes normalised)
        Q_alpha = ((DELTA * GAMMA) - ALPHA.I) * matrix([[0], [1], [0]])
        Q_alpha = Q_alpha * (1 / norm(Q_alpha))

        if self._getMode().name == '4cPhi':
            ### Use the fixed value of phi as the final constraint ###
            phi = self._getParameter('phi') * TORAD
            PHI = calcPHI(phi)
            H_chi = PHI * H_phi
            omega, chi = _findOmegaAndChiToRotateHchiIntoQalpha(H_chi, Q_alpha)
            return (omega, chi, phi, None)  # psi = None as not calculated
        else:
            ### Use Bin as the final constraint ###

            # Find a solution Ro to Ro*H_phi=Q_alpha
            Ro = self._findMatrixToTransformAIntoB(H_phi, Q_alpha)

            ## equation 50: Find a solution D to D*Q=norm(Q)*[[1],[0],[0]])
            D = self._findMatrixToTransformAIntoB(
                Q_alpha, matrix([[1], [0], [0]]))

            ## Find psi and create PSI

            # eq 54: compute u=D*Ro*S*[[0],[0],[1]], the surface normal in
            # psi frame
            [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
                self._getSigma() * TORAD, self._getTau() * TORAD)
            S = TAU * SIGMA
            [u1], [u2], [u3] = (D * Ro * S * matrix([[0], [0], [1]])).tolist()
            # TODO: If u points along 100, then any psi is a solution. Choose 0
            if not differ([u1, u2, u3], [1, 0, 0], 1e-9):
                psi = 0
                omega, chi, phi = equation49through59(psi)
            else:
                # equation 53: V=A*(D^-1)
                V = ALPHA * D.I
                v21 = V[1, 0]
                v22 = V[1, 1]
                v23 = V[1, 2]
                # equation 55
                a = v22 * u2 + v23 * u3
                b = v22 * u3 - v23 * u2
                c = -sin(Bin) - v21 * u1  # TODO: changed sign from paper

                # equation 44
                # Try first root:
                def myatan2(y, x):
                    if abs(x) < 1e-20 and abs(y) < 1e-20:
                        return pi / 2
                    else:
                        return atan2(y, x)
                psi = 2 * myatan2(-(b - sqrt(b * b + a * a - c * c)), -(a + c))
                #psi = -acos(c/sqrt(a*a+b*b))+atan2(b,a)# -2*pi
                omega, chi, phi = equation49through59(psi)

            # if u points along z axis, the psi could have been either 0 or 180
            if (not differ([u1, u2, u3], [0, 0, 1], 1e-9) and
                abs(psi - pi) < 1e-10):
                # Choose 0 to match that read up by  angles-to-virtual-angles
                psi = 0.
            # if u points a long
            return (omega, chi, phi, psi)

    def _anglesToPsi(self, pos, wavelength):
        """
        pos assumed in radians. -180<= psi <= 180
        """
        # Using Vlieg section 7.2

        # Needed througout:
        [ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices(
                pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi)

        # Solve equation 49 for psi, the rotation of the a reference solution
        # about Qalpha or H_phi##

        # Find Ro, the reference solution to equation 46: R*H_phi=Q_alpha

        # Create Q_alpha from equation 47, (it comes normalised)
        Q_alpha = ((DELTA * GAMMA) - ALPHA.I) * matrix([[0], [1], [0]])
        Q_alpha = Q_alpha * (1 / norm(Q_alpha))

        # Finh H_phi
        h, k, l = self._anglesToHkl(pos, wavelength)
        H_phi = self._getUBMatrix() * matrix([[h], [k], [l]])
        normh = norm(H_phi)
        check(normh >= 1e-10, "reciprical lattice vector too close to zero")
        H_phi = H_phi * (1 / normh)

        # Find a solution Ro to Ro*H_phi=Q_alpha
        # This the reference solution with zero azimuth (psi)
        Ro = self._findMatrixToTransformAIntoB(H_phi, Q_alpha)

        # equation 48:
        R = OMEGA * CHI * PHI

        ## equation 50: Find a solution D to D*Q=norm(Q)*[[1],[0],[0]])
        D = self._findMatrixToTransformAIntoB(Q_alpha, matrix([[1], [0], [0]]))

        # solve equation 49 for psi
        # D*R = PSI*D*Ro
        # D*R*(D*Ro)^-1 = PSI
        PSI = D * R * ((D * Ro).I)

        # Find psi within PSI as defined in equation 51
        PSI_23 = PSI[1, 2]
        PSI_33 = PSI[2, 2]
        psi = atan2(PSI_23, PSI_33)

        #print "PSI: ", PSI.tolist()
        return psi

    def _findMatrixToTransformAIntoB(self, a, b):
        """
        Finds a particular matrix Mo that transforms the unit vector a into the
        unit vector b. Thats is it finds Mo Mo*a=b. a and b 3x1 matrixes and Mo
        is a 3x3 matrix.

        Throws an exception if this is not possible.
            """
        # Maths from the appendix of "Angle caluculations
        # for a 5-circle diffractometer used for surface X-ray diffraction",
        # E. Vlieg, J.F. van der Veen, J.E. Macdonald and M. Miller, J. of
        # Applied Cryst. 20 (1987) 330.
        #                                       - courtesy of Elias Vlieg again

        # equation A2: compute angle xi between vectors a and b
        cosxi = dot3(a, b)
        try:
            cosxi = bound(cosxi)
        except ValueError:
            raise Exception("Could not compute cos(xi), vectors a=%f and b=%f "
                            "must be of unit length" % (norm(a), norm(b)))
        xi = acos(cosxi)

        # Mo is identity matrix if xi zero (math below would blow up)
        if abs(xi) < 1e-10:
            return I

        # equation A3: c=cross(a,b)/sin(xi)
        c = cross3(a, b) * (1 / sin(xi))

        # equation A4: find D matrix that transforms a into the frame
        # x = a; y = c x a; z = c. */
        a1 = a[0, 0]
        a2 = a[1, 0]
        a3 = a[2, 0]
        c1 = c[0, 0]
        c2 = c[1, 0]
        c3 = c[2, 0]
        D = matrix([[a1, a2, a3],
                    [c2 * a3 - c3 * a2, c3 * a1 - c1 * a3, c1 * a2 - c2 * a1],
                    [c1, c2, c3]])

        # equation A5: create Xi to rotate by xi about z-axis
        XI = matrix([[cos(xi), -sin(xi), 0],
                     [sin(xi), cos(xi), 0],
                     [0, 0, 1]])

        # eq A6: compute Mo
        return D.I * XI * D
Ejemplo n.º 7
0
class VliegHklCalculator(HklCalculatorBase):
    def __init__(self,
                 ubcalc,
                 geometry,
                 hardware,
                 raiseExceptionsIfAnglesDoNotMapBackToHkl=True):
        r = raiseExceptionsIfAnglesDoNotMapBackToHkl
        HklCalculatorBase.__init__(self,
                                   ubcalc,
                                   geometry,
                                   hardware,
                                   raiseExceptionsIfAnglesDoNotMapBackToHkl=r)
        self._gammaParameterName = ({
            'arm': 'gamma',
            'base': 'oopgamma'
        }[self._geometry.gamma_location])
        self.mode_selector = ModeSelector(self._geometry, None,
                                          self._gammaParameterName)
        self.parameter_manager = VliegParameterManager(
            self._geometry, self._hardware, self.mode_selector,
            self._gammaParameterName)
        self.mode_selector.setParameterManager(self.parameter_manager)

    def __str__(self):
        # should list paramemeters and indicate which are used in selected mode
        result = "Available mode_selector:\n"
        result += self.mode_selector.reportAvailableModes()
        result += '\nCurrent mode:\n'
        result += self.mode_selector.reportCurrentMode()
        result += '\n\nParameters:\n'
        result += self.parameter_manager.reportAllParameters()
        return result

    def _anglesToHkl(self, pos, wavelength):
        """
        Return hkl tuple from VliegPosition in radians and wavelength in
        Angstroms.
        """
        return vliegAnglesToHkl(pos, wavelength, self._getUBMatrix())

    def _anglesToVirtualAngles(self, pos, wavelength):
        """
        Return dictionary of all virtual angles in radians from VliegPosition
        object win radians and wavelength in Angstroms. The virtual angles are:
        Bin, Bout, azimuth and 2theta.
        """

        # Create transformation matrices
        [ALPHA, DELTA, GAMMA, OMEGA, CHI,
         PHI] = createVliegMatrices(pos.alpha, pos.delta, pos.gamma, pos.omega,
                                    pos.chi, pos.phi)
        [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
            self._getSigma() * TORAD,
            self._getTau() * TORAD)

        S = TAU * SIGMA
        y_vector = matrix([[0], [1], [0]])

        # Calculate Bin from equation 15:
        surfacenormal_alpha = OMEGA * CHI * PHI * S * matrix([[0], [0], [1]])
        incoming_alpha = ALPHA.I * y_vector
        minusSinBetaIn = dot3(surfacenormal_alpha, incoming_alpha)
        Bin = asin(bound(-minusSinBetaIn))

        # Calculate Bout from equation 16:
        #  surfacenormal_alpha has just ben calculated
        outgoing_alpha = DELTA * GAMMA * y_vector
        sinBetaOut = dot3(surfacenormal_alpha, outgoing_alpha)
        Bout = asin(bound(sinBetaOut))

        # Calculate 2theta from equation 25:

        cosTwoTheta = dot3(ALPHA * DELTA * GAMMA * y_vector, y_vector)
        twotheta = acos(bound(cosTwoTheta))
        psi = self._anglesToPsi(pos, wavelength)

        return {'Bin': Bin, 'Bout': Bout, 'azimuth': psi, '2theta': twotheta}

    def _hklToAngles(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstroms. The virtual angles are those fixed or
        generated while calculating the position: Bin, Bout and 2theta; and
        azimuth in four and five circle modes.
        """

        if self._getMode().group in ("fourc", "fivecFixedGamma",
                                     "fivecFixedAlpha"):
            return self._hklToAnglesFourAndFiveCirclesModes(
                h, k, l, wavelength)
        elif self._getMode().group == "zaxis":
            return self._hklToAnglesZaxisModes(h, k, l, wavelength)
        else:
            raise RuntimeError(
                'The current mode (%s) has an unrecognised group: %s.' %
                (self._getMode().name, self._getMode().group))

    def _hklToAnglesFourAndFiveCirclesModes(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstrom for four and five circle modes. The virtual
        angles are those fixed or generated while calculating the position:
        Bin, Bout, 2theta and azimuth.
        """

        # Results in radians during calculations, returned in degreess
        pos = VliegPosition(None, None, None, None, None, None)

        # Normalise hkl
        wavevector = 2 * pi / wavelength
        hklNorm = matrix([[h], [k], [l]]) / wavevector

        # Compute hkl in phi axis coordinate frame
        hklPhiNorm = self._getUBMatrix() * hklNorm

        # Determine Bin and Bout
        if self._getMode().name == '4cPhi':
            Bin = Bout = None
        else:
            Bin, Bout = self._determineBinAndBoutInFourAndFiveCirclesModes(
                hklNorm)

        # Determine alpha and gamma
        if self._getMode().group == 'fourc':
            pos.alpha, pos.gamma = \
                self._determineAlphaAndGammaForFourCircleModes(hklPhiNorm)
        else:
            pos.alpha, pos.gamma = \
                self._determineAlphaAndGammaForFiveCircleModes(Bin, hklPhiNorm)
        if pos.alpha < -pi:
            pos.alpha += 2 * pi
        if pos.alpha > pi:
            pos.alpha -= 2 * pi

        # Determine delta
        (pos.delta, twotheta) = self._determineDelta(hklPhiNorm, pos.alpha,
                                                     pos.gamma)

        # Determine omega, chi & phi
        pos.omega, pos.chi, pos.phi, psi = \
            self._determineSampleAnglesInFourAndFiveCircleModes(
                hklPhiNorm, pos.alpha, pos.delta, pos.gamma, Bin)
        # (psi will be None in fixed phi mode)

        # Ensure that by default omega is between -90 and 90, by possibly
        # transforming the sample angles
        if self._getMode().name != '4cPhi':  # not in fixed-phi mode
            if pos.omega < -pi / 2 or pos.omega > pi / 2:
                pos = transformC.transform(pos)

        # Gather up the virtual angles calculated along the way...
        #   -pi<psi<=pi
        if psi is not None:
            if psi > pi:
                psi -= 2 * pi
            if psi < (-1 * pi):
                psi += 2 * pi

        v = {'2theta': twotheta, 'Bin': Bin, 'Bout': Bout, 'azimuth': psi}
        return pos, v

    def _hklToAnglesZaxisModes(self, h, k, l, wavelength):
        """
        Return VliegPosition and virtual angles in radians from h, k & l and
        wavelength in Angstroms for z-axis modes. The virtual angles are those
        fixed or generated while calculating the position: Bin, Bout, and
        2theta.
        """
        # Section 6:

        # Results in radians during calculations, returned in degreess
        pos = VliegPosition(None, None, None, None, None, None)

        # Normalise hkl
        wavevector = 2 * pi / wavelength
        hkl = matrix([[h], [k], [l]])
        hklNorm = hkl * (1.0 / wavevector)

        # Compute hkl in phi axis coordinate frame
        hklPhi = self._getUBMatrix() * hkl
        hklPhiNorm = self._getUBMatrix() * hklNorm

        # Determine Chi and Phi (Equation 29):
        pos.phi = -self._getTau() * TORAD
        pos.chi = -self._getSigma() * TORAD

        # Equation 30:
        [ALPHA, DELTA, GAMMA, OMEGA, CHI,
         PHI] = createVliegMatrices(None, None, None, None, pos.chi, pos.phi)
        del ALPHA, DELTA, GAMMA, OMEGA
        Hw = CHI * PHI * hklPhi

        # Determine Bin and Bout:
        (Bin,
         Bout) = self._determineBinAndBoutInZaxisModes(Hw[2, 0] / wavevector)

        # Determine Alpha and Gamma (Equation 32):
        pos.alpha = Bin
        pos.gamma = Bout

        # Determine Delta:
        (pos.delta, twotheta) = self._determineDelta(hklPhiNorm, pos.alpha,
                                                     pos.gamma)

        # Determine Omega:
        delta = pos.delta
        gamma = pos.gamma
        d1 = (Hw[1, 0] * sin(delta) * cos(gamma) - Hw[0, 0] *
              (cos(delta) * cos(gamma) - cos(pos.alpha)))
        d2 = (Hw[0, 0] * sin(delta) * cos(gamma) + Hw[1, 0] *
              (cos(delta) * cos(gamma) - cos(pos.alpha)))

        if fabs(d2) < 1e-30:
            pos.omega = sign(d1) * sign(d2) * pi / 2.0
        else:
            pos.omega = atan2(d1, d2)

        # Gather up the virtual angles calculated along the way
        return pos, {'2theta': twotheta, 'Bin': Bin, 'Bout': Bout}

###

    def _determineBinAndBoutInFourAndFiveCirclesModes(self, hklNorm):
        """(Bin, Bout) = _determineBinAndBoutInFourAndFiveCirclesModes()"""
        BinModes = ('4cBin', '5cgBin', '5caBin')
        BoutModes = ('4cBout', '5cgBout', '5caBout')
        BeqModes = ('4cBeq', '5cgBeq', '5caBeq')
        azimuthModes = ('4cAzimuth')
        fixedBusingAndLeviWmodes = ('4cFixedw')

        # Calculate RHS of equation 20
        # RHS (1/K)(S^-1*U*B*H)_3 where H/K = hklNorm
        UB = self._getUBMatrix()
        [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
            self._getSigma() * TORAD,
            self._getTau() * TORAD)
        #S = SIGMA * TAU
        S = TAU * SIGMA
        RHS = (S.I * UB * hklNorm)[2, 0]

        if self._getMode().name in BinModes:
            Bin = self._getParameter('betain')
            check(
                Bin != None, "The parameter betain must be set for mode %s" %
                self._getMode().name)
            Bin = Bin * TORAD
            sinBout = RHS - sin(Bin)
            check(fabs(sinBout) <= 1, "Could not compute Bout")
            Bout = asin(sinBout)

        elif self._getMode().name in BoutModes:
            Bout = self._getParameter('betaout')
            check(
                Bout != None, "The parameter Bout must be set for mode %s" %
                self._getMode().name)
            Bout = Bout * TORAD
            sinBin = RHS - sin(Bout)
            check(fabs(sinBin) <= 1, "Could not compute Bin")
            Bin = asin(sinBin)

        elif self._getMode().name in BeqModes:
            sinBeq = RHS / 2
            check(fabs(sinBeq) <= 1, "Could not compute Bin=Bout")
            Bin = Bout = asin(sinBeq)

        elif self._getMode().name in azimuthModes:
            azimuth = self._getParameter('azimuth')
            check(
                azimuth != None, "The parameter azimuth must be set for "
                "mode %s" % self._getMode().name)
            del azimuth
            # TODO: codeit
            raise NotImplementedError()

        elif self._getMode().name in fixedBusingAndLeviWmodes:
            bandlomega = self._getParameter('blw')
            check(
                bandlomega != None, "The parameter abandlomega must be set "
                "for mode %s" % self._getMode().name)
            del bandlomega
            # TODO: codeit
            raise NotImplementedError()
        else:
            raise RuntimeError("AngleCalculator does not know how to handle "
                               "mode %s" % self._getMode().name)

        return (Bin, Bout)

    def _determineBinAndBoutInZaxisModes(self, Hw3OverK):
        """(Bin, Bout) = _determineBinAndBoutInZaxisModes(HwOverK)"""
        BinModes = ('6czBin')
        BoutModes = ('6czBout')
        BeqModes = ('6czBeq')

        if self._getMode().name in BinModes:
            Bin = self._getParameter('betain')
            check(
                Bin != None, "The parameter betain must be set for mode %s" %
                self._getMode().name)
            Bin = Bin * TORAD
            # Equation 32a:
            Bout = asin(Hw3OverK - sin(Bin))

        elif self._getMode().name in BoutModes:
            Bout = self._getParameter('betaout')
            check(
                Bout != None, "The parameter Bout must be set for mode %s" %
                self._getMode().name)
            Bout = Bout * TORAD
            # Equation 32b:
            Bin = asin(Hw3OverK - sin(Bout))

        elif self._getMode().name in BeqModes:
            # Equation 32c:
            Bin = Bout = asin(Hw3OverK / 2)

        return (Bin, Bout)

###

    def _determineAlphaAndGammaForFourCircleModes(self, hklPhiNorm):

        if self._getMode().group == 'fourc':
            alpha = self._getParameter('alpha') * TORAD
            gamma = self._getParameter(self._getGammaParameterName()) * TORAD
            check(alpha != None, "alpha parameter must be set in fourc modes")
            check(gamma != None, "gamma parameter must be set in fourc modes")
            return alpha, gamma
        else:
            raise RuntimeError("determineAlphaAndGammaForFourCirclesModes() "
                               "is not appropriate for %s modes" %
                               self._getMode().group)

    def _determineAlphaAndGammaForFiveCircleModes(self, Bin, hklPhiNorm):

        ## Solve equation 34 for one possible Y, Yo
        # Calculate surface normal in phi frame
        [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
            self._getSigma() * TORAD,
            self._getTau() * TORAD)
        S = TAU * SIGMA
        surfaceNormalPhi = S * matrix([[0], [0], [1]])
        # Compute beta in vector
        BetaVector = matrix([[0], [-sin(Bin)], [cos(Bin)]])
        # Find Yo
        Yo = self._findMatrixToTransformAIntoB(surfaceNormalPhi, BetaVector)

        ## Calculate Hv from equation 39
        Z = matrix([[1, 0, 0], [0, cos(Bin), sin(Bin)],
                    [0, -sin(Bin), cos(Bin)]])
        Hv = Z * Yo * hklPhiNorm
        # Fixed gamma:
        if self._getMode().group == 'fivecFixedGamma':
            gamma = self._getParameter(self._getGammaParameterName())
            check(gamma != None,
                  "gamma parameter must be set in fivecFixedGamma modes")
            gamma = gamma * TORAD
            H2 = (hklPhiNorm[0, 0]**2 + hklPhiNorm[1, 0]**2 +
                  hklPhiNorm[2, 0]**2)
            a = -(0.5 * H2 * sin(Bin) - Hv[2, 0])
            b = -(1.0 - 0.5 * H2) * cos(Bin)
            c = cos(Bin) * sin(gamma)
            check((b * b + a * a - c * c) >= 0, 'Could not solve for alpha')
            alpha = 2 * atan2(-(b + sqrt(b * b + a * a - c * c)), -(a + c))

        # Fixed Alpha:
        elif self._getMode().group == 'fivecFixedAlpha':
            alpha = self._getParameter('alpha')
            check(alpha != None,
                  "alpha parameter must be set in fivecFixedAlpha modes")
            alpha = alpha * TORAD
            H2 = (hklPhiNorm[0, 0]**2 + hklPhiNorm[1, 0]**2 +
                  hklPhiNorm[2, 0]**2)
            t0 = ((2 * cos(alpha) * Hv[2, 0] - sin(Bin) * cos(alpha) * H2 +
                   cos(Bin) * sin(alpha) * H2 - 2 * cos(Bin) * sin(alpha)) /
                  (cos(Bin) * 2.0))
            check(abs(t0) <= 1, "Cannot compute gamma: sin(gamma)>1")
            gamma = asin(t0)
        else:
            raise RuntimeError(
                "determineAlphaAndGammaInFiveCirclesModes() is not "
                "appropriate for %s modes" % self._getMode().group)

        return (alpha, gamma)


###

    def _determineDelta(self, hklPhiNorm, alpha, gamma):
        """
        (delta, twotheta) = _determineDelta(hklPhiNorm, alpha, gamma) --
        computes delta for all modes. Also returns twotheta for sanity
        checking. hklPhiNorm is a 3X1 matrix.

        alpha, gamma & delta - in radians.
        h k & l normalised to wavevector and in phi axis coordinates
        """
        h = hklPhiNorm[0, 0]
        k = hklPhiNorm[1, 0]
        l = hklPhiNorm[2, 0]
        # See Vlieg section 5 (with K=1)
        cosdelta = ((1 + sin(gamma) * sin(alpha) -
                     (h * h + k * k + l * l) / 2) / (cos(gamma) * cos(alpha)))
        costwotheta = (cos(alpha) * cos(gamma) * bound(cosdelta) -
                       sin(alpha) * sin(gamma))
        return (acos(bound(cosdelta)), acos(bound(costwotheta)))

    def _determineSampleAnglesInFourAndFiveCircleModes(self, hklPhiNorm, alpha,
                                                       delta, gamma, Bin):
        """
        (omega, chi, phi, psi)=determineNonZAxisSampleAngles(hklPhiNorm, alpha,
        delta, gamma, sigma, tau) where hkl has been normalised by the
        wavevector and is in the phi Axis coordinate frame. All angles in
        radians. hklPhiNorm is a 3X1 matrix
        """
        def equation49through59(psi):
            # equation 49 R = (D^-1)*PI*D*Ro
            PSI = createVliegsPsiTransformationMatrix(psi)
            R = D.I * PSI * D * Ro

            #  eq 57: extract omega from R
            if abs(R[0, 2]) < 1e-20:
                omega = -sign(R[1, 2]) * sign(R[0, 2]) * pi / 2
            else:
                omega = -atan2(R[1, 2], R[0, 2])

            # eq 58: extract chi from R
            sinchi = sqrt(pow(R[0, 2], 2) + pow(R[1, 2], 2))
            sinchi = bound(sinchi)
            check(abs(sinchi) <= 1, 'could not compute chi')
            # (there are two roots to this equation, but only the first is also
            # a solution to R33=cos(chi))
            chi = asin(sinchi)

            # eq 59: extract phi from R
            if abs(R[2, 0]) < 1e-20:
                phi = sign(R[2, 1]) * sign(R[2, 1]) * pi / 2
            else:
                phi = atan2(-R[2, 1], -R[2, 0])
            return omega, chi, phi

        def checkSolution(omega, chi, phi):
            _, _, _, OMEGA, CHI, PHI = createVliegMatrices(
                None, None, None, omega, chi, phi)
            R = OMEGA * CHI * PHI
            RtimesH_phi = R * H_phi
            print("R*H_phi=%s, Q_alpha=%s" %
                  (R * H_phi.tolist(), Q_alpha.tolist()))
            return not differ(RtimesH_phi, Q_alpha, .0001)

        # Using Vlieg section 7.2

        # Needed througout:
        [ALPHA, DELTA, GAMMA, _, _,
         _] = createVliegMatrices(alpha, delta, gamma, None, None, None)

        ## Find Ro, one possible solution to equation 46: R*H_phi=Q_alpha

        # Normalise hklPhiNorm (As it is currently normalised only to the
        # wavevector)
        normh = norm(hklPhiNorm)
        check(normh >= 1e-10, "reciprical lattice vector too close to zero")
        H_phi = hklPhiNorm * (1 / normh)

        # Create Q_alpha from equation 47, (it comes normalised)
        Q_alpha = ((DELTA * GAMMA) - ALPHA.I) * matrix([[0], [1], [0]])
        Q_alpha = Q_alpha * (1 / norm(Q_alpha))

        if self._getMode().name == '4cPhi':
            ### Use the fixed value of phi as the final constraint ###
            phi = self._getParameter('phi') * TORAD
            PHI = calcPHI(phi)
            H_chi = PHI * H_phi
            omega, chi = _findOmegaAndChiToRotateHchiIntoQalpha(H_chi, Q_alpha)
            return (omega, chi, phi, None)  # psi = None as not calculated
        else:
            ### Use Bin as the final constraint ###

            # Find a solution Ro to Ro*H_phi=Q_alpha
            Ro = self._findMatrixToTransformAIntoB(H_phi, Q_alpha)

            ## equation 50: Find a solution D to D*Q=norm(Q)*[[1],[0],[0]])
            D = self._findMatrixToTransformAIntoB(Q_alpha,
                                                  matrix([[1], [0], [0]]))

            ## Find psi and create PSI

            # eq 54: compute u=D*Ro*S*[[0],[0],[1]], the surface normal in
            # psi frame
            [SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(
                self._getSigma() * TORAD,
                self._getTau() * TORAD)
            S = TAU * SIGMA
            [u1], [u2], [u3] = (D * Ro * S * matrix([[0], [0], [1]])).tolist()
            # TODO: If u points along 100, then any psi is a solution. Choose 0
            if not differ([u1, u2, u3], [1, 0, 0], 1e-9):
                psi = 0
                omega, chi, phi = equation49through59(psi)
            else:
                # equation 53: V=A*(D^-1)
                V = ALPHA * D.I
                v21 = V[1, 0]
                v22 = V[1, 1]
                v23 = V[1, 2]
                # equation 55
                a = v22 * u2 + v23 * u3
                b = v22 * u3 - v23 * u2
                c = -sin(Bin) - v21 * u1  # TODO: changed sign from paper

                # equation 44
                # Try first root:
                def myatan2(y, x):
                    if abs(x) < 1e-20 and abs(y) < 1e-20:
                        return pi / 2
                    else:
                        return atan2(y, x)

                psi = 2 * myatan2(-(b - sqrt(b * b + a * a - c * c)), -(a + c))
                #psi = -acos(c/sqrt(a*a+b*b))+atan2(b,a)# -2*pi
                omega, chi, phi = equation49through59(psi)

            # if u points along z axis, the psi could have been either 0 or 180
            if (not differ([u1, u2, u3], [0, 0, 1], 1e-9)
                    and abs(psi - pi) < 1e-10):
                # Choose 0 to match that read up by  angles-to-virtual-angles
                psi = 0.
            # if u points a long
            return (omega, chi, phi, psi)

    def _anglesToPsi(self, pos, wavelength):
        """
        pos assumed in radians. -180<= psi <= 180
        """
        # Using Vlieg section 7.2

        # Needed througout:
        [ALPHA, DELTA, GAMMA, OMEGA, CHI,
         PHI] = createVliegMatrices(pos.alpha, pos.delta, pos.gamma, pos.omega,
                                    pos.chi, pos.phi)

        # Solve equation 49 for psi, the rotation of the a reference solution
        # about Qalpha or H_phi##

        # Find Ro, the reference solution to equation 46: R*H_phi=Q_alpha

        # Create Q_alpha from equation 47, (it comes normalised)
        Q_alpha = ((DELTA * GAMMA) - ALPHA.I) * matrix([[0], [1], [0]])
        Q_alpha = Q_alpha * (1 / norm(Q_alpha))

        # Finh H_phi
        h, k, l = self._anglesToHkl(pos, wavelength)
        H_phi = self._getUBMatrix() * matrix([[h], [k], [l]])
        normh = norm(H_phi)
        check(normh >= 1e-10, "reciprical lattice vector too close to zero")
        H_phi = H_phi * (1 / normh)

        # Find a solution Ro to Ro*H_phi=Q_alpha
        # This the reference solution with zero azimuth (psi)
        Ro = self._findMatrixToTransformAIntoB(H_phi, Q_alpha)

        # equation 48:
        R = OMEGA * CHI * PHI

        ## equation 50: Find a solution D to D*Q=norm(Q)*[[1],[0],[0]])
        D = self._findMatrixToTransformAIntoB(Q_alpha, matrix([[1], [0], [0]]))

        # solve equation 49 for psi
        # D*R = PSI*D*Ro
        # D*R*(D*Ro)^-1 = PSI
        PSI = D * R * ((D * Ro).I)

        # Find psi within PSI as defined in equation 51
        PSI_23 = PSI[1, 2]
        PSI_33 = PSI[2, 2]
        psi = atan2(PSI_23, PSI_33)

        #print "PSI: ", PSI.tolist()
        return psi

    def _findMatrixToTransformAIntoB(self, a, b):
        """
        Finds a particular matrix Mo that transforms the unit vector a into the
        unit vector b. Thats is it finds Mo Mo*a=b. a and b 3x1 matrixes and Mo
        is a 3x3 matrix.

        Throws an exception if this is not possible.
            """
        # Maths from the appendix of "Angle caluculations
        # for a 5-circle diffractometer used for surface X-ray diffraction",
        # E. Vlieg, J.F. van der Veen, J.E. Macdonald and M. Miller, J. of
        # Applied Cryst. 20 (1987) 330.
        #                                       - courtesy of Elias Vlieg again

        # equation A2: compute angle xi between vectors a and b
        cosxi = dot3(a, b)
        try:
            cosxi = bound(cosxi)
        except ValueError:
            raise Exception("Could not compute cos(xi), vectors a=%f and b=%f "
                            "must be of unit length" % (norm(a), norm(b)))
        xi = acos(cosxi)

        # Mo is identity matrix if xi zero (math below would blow up)
        if abs(xi) < 1e-10:
            return I

        # equation A3: c=cross(a,b)/sin(xi)
        c = cross3(a, b) * (1 / sin(xi))

        # equation A4: find D matrix that transforms a into the frame
        # x = a; y = c x a; z = c. */
        a1 = a[0, 0]
        a2 = a[1, 0]
        a3 = a[2, 0]
        c1 = c[0, 0]
        c2 = c[1, 0]
        c3 = c[2, 0]
        D = matrix([[a1, a2, a3],
                    [c2 * a3 - c3 * a2, c3 * a1 - c1 * a3, c1 * a2 - c2 * a1],
                    [c1, c2, c3]])

        # equation A5: create Xi to rotate by xi about z-axis
        XI = matrix([[cos(xi), -sin(xi), 0], [sin(xi), cos(xi), 0], [0, 0, 1]])

        # eq A6: compute Mo
        return D.I * XI * D
 def setup_method(self):
     self.hw = createMockHardwareMonitor()
     self.ms = ModeSelector(createMockDiffractometerGeometry())
     self.pm = VliegParameterManager(createMockDiffractometerGeometry(),
                                     self.hw, self.ms)
class TestParameterManager(object):

    def setup_method(self):
        self.hw = createMockHardwareMonitor()
        self.ms = ModeSelector(createMockDiffractometerGeometry())
        self.pm = VliegParameterManager(createMockDiffractometerGeometry(),
                                        self.hw, self.ms)

    def testDefaultParameterValues(self):
        assert self.pm.get_constraint('alpha') == 0
        assert self.pm.get_constraint('gamma') == 0
        with pytest.raises(DiffcalcException):
            self.pm.get_constraint('not-a-parameter-name')

    def testSetParameter(self):
        self.pm.set_constraint('alpha', 10.1)
        assert self.pm.get_constraint('alpha') == 10.1

    def testSetTrackParameter_isParameterChecked(self):
        assert not self.pm.isParameterTracked('alpha')
        self.pm.set_constraint('alpha', 9)

        self.pm.setTrackParameter('alpha', True)
        assert self.pm.isParameterTracked('alpha') == True
        with pytest.raises(DiffcalcException):
            self.pm.set_constraint('alpha', 10)
        self.hw.get_position.return_value = 888, 11, 999
        assert self.pm.get_constraint('alpha') == 11

        print self.pm.reportAllParameters()
        print "**"
        print self.ms.reportCurrentMode()
        print self.pm.reportParametersUsedInCurrentMode()

        self.pm.setTrackParameter('alpha', False)
        assert not self.pm.isParameterTracked('alpha')
        assert self.pm.get_constraint('alpha') == 11
        self.hw.get_position.return_value = 888, 12, 999
        assert self.pm.get_constraint('alpha') == 11
        self.pm.set_constraint('alpha', 13)
        assert self.pm.get_constraint('alpha') == 13