def setup_method(self): self.ms = ModeSelector(createMockDiffractometerGeometry(), parameterManager=None) self.pm = VliegParameterManager(createMockDiffractometerGeometry(), None, self.ms) self.ms.setParameterManager(self.pm)
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(self): self.ms = ModeSelector(createMockDiffractometerGeometry(), parameterManager=None) self.pm = VliegParameterManager(createMockDiffractometerGeometry(), None, self.ms) self.ms.setParameterManager(self.pm)
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
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)
class TestModeSelector(unittest.TestCase): def setUp(self): self.ms = ModeSelector(createMockDiffractometerGeometry(), parameterManager=None) self.pm = VliegParameterManager(createMockDiffractometerGeometry(), None, self.ms) self.ms.setParameterManager(self.pm) def testSetModeByIndex(self): self.ms.setModeByIndex(0) self.assert_(self.ms.getMode().name == '4cFixedw') self.ms.setModeByIndex(1) self.assert_(self.ms.getMode().name == '4cBeq') def testGetMode(self): # tested implicetely by testSetmode pass def testShowAvailableModes(self): print self.ms.reportAvailableModes()
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)
class TestModeSelector(object): def setup_method(self): self.ms = ModeSelector(createMockDiffractometerGeometry(), parameterManager=None) self.pm = VliegParameterManager(createMockDiffractometerGeometry(), None, self.ms) self.ms.setParameterManager(self.pm) def testSetModeByIndex(self): self.ms.setModeByIndex(0) assert self.ms.getMode().name == '4cFixedw' self.ms.setModeByIndex(1) assert self.ms.getMode().name == '4cBeq' def testGetMode(self): # tested implicetely by testSetmode pass def testShowAvailableModes(self): print self.ms.reportAvailableModes()
def setUp(self): self.hw = createMockHardwareMonitor() self.ms = ModeSelector(createMockDiffractometerGeometry()) self.pm = VliegParameterManager(createMockDiffractometerGeometry(), self.hw, self.ms)
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
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)