This commit is contained in:
2019-03-20 13:52:00 +01:00
parent 3084fe0510
commit 5db0f78aee
910 changed files with 191152 additions and 322 deletions

View File

View File

@@ -0,0 +1,153 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from math import pi
from diffcalc.util import DiffcalcException, differ
from diffcalc import settings
TORAD = pi / 180
TODEG = 180 / pi
class HklCalculatorBase(object):
def __init__(self, ubcalc,
raiseExceptionsIfAnglesDoNotMapBackToHkl=False):
self._ubcalc = ubcalc # to get the UBMatrix, tau and sigma
self.raiseExceptionsIfAnglesDoNotMapBackToHkl = \
raiseExceptionsIfAnglesDoNotMapBackToHkl
def anglesToHkl(self, pos, wavelength):
"""
Return hkl tuple and dictionary of all virtual angles in degrees from
Position in degrees and wavelength in Angstroms.
"""
h, k, l = self._anglesToHkl(pos.inRadians(), wavelength)
paramDict = self.anglesToVirtualAngles(pos, wavelength)
return ((h, k, l), paramDict)
def anglesToVirtualAngles(self, pos, wavelength):
"""
Return dictionary of all virtual angles in degrees from Position object
in degrees and wavelength in Angstroms.
"""
anglesDict = self._anglesToVirtualAngles(pos.inRadians(), wavelength)
for name in anglesDict:
anglesDict[name] = anglesDict[name] * TODEG
return anglesDict
def hklToAngles(self, h, k, l, wavelength):
"""
Return verified Position and all virtual angles in degrees from
h, k & l and wavelength in Angstroms.
The calculated Position is verified by checking that it maps back using
anglesToHkl() to the requested hkl value.
Those virtual angles fixed or generated while calculating the position
are verified by by checking that they map back using
anglesToVirtualAngles to the virtual angles for the given position.
Throws a DiffcalcException if either check fails and
raiseExceptionsIfAnglesDoNotMapBackToHkl is True, otherwise displays a
warning.
"""
# Update tracked parameters. During this calculation parameter values
# will be read directly from self._parameters instead of via
# self.getParameter which would trigger another potentially time-costly
# position update.
self.parameter_manager.update_tracked()
pos, virtualAngles = self._hklToAngles(h, k, l, wavelength) # in rad
# to degrees:
pos.changeToDegrees()
for key, val in virtualAngles.items():
if val is not None:
virtualAngles[key] = val * TODEG
self._verify_pos_map_to_hkl(h, k, l, wavelength, pos)
virtualAnglesReadback = self._verify_virtual_angles(h, k, l, wavelength, pos, virtualAngles)
return pos, virtualAnglesReadback
def _verify_pos_map_to_hkl(self, h, k, l, wavelength, pos):
hkl, _ = self.anglesToHkl(pos, wavelength)
e = 0.001
if ((abs(hkl[0] - h) > e) or (abs(hkl[1] - k) > e) or
(abs(hkl[2] - l) > e)):
s = "ERROR: The angles calculated for hkl=(%f,%f,%f) were %s.\n" % (h, k, l, str(pos))
s += "Converting these angles back to hkl resulted in hkl="\
"(%f,%f,%f)" % (hkl[0], hkl[1], hkl[2])
if self.raiseExceptionsIfAnglesDoNotMapBackToHkl:
raise DiffcalcException(s)
else:
print s
def _verify_virtual_angles(self, h, k, l, wavelength, pos, virtualAngles):
# Check that the virtual angles calculated/fixed during the hklToAngles
# those read back from pos using anglesToVirtualAngles
virtualAnglesReadback = self.anglesToVirtualAngles(pos, wavelength)
for key, val in virtualAngles.items():
if val != None: # Some values calculated in some mode_selector
r = virtualAnglesReadback[key]
if ((differ(val, r, .00001) and differ(val, r + 360, .00001) and differ(val, r - 360, .00001))):
s = "ERROR: The angles calculated for hkl=(%f,%f,%f) with"\
" mode=%s were %s.\n" % (h, k, l, self.repr_mode(), str(pos))
s += "During verification the virtual angle %s resulting "\
"from (or set for) this calculation of %f" % (key, val)
s += "did not match that calculated by "\
"anglesToVirtualAngles of %f" % virtualAnglesReadback[key]
if self.raiseExceptionsIfAnglesDoNotMapBackToHkl:
raise DiffcalcException(s)
else:
print s
return virtualAnglesReadback
def repr_mode(self):
pass
### Collect all math access to context here
def _getUBMatrix(self):
return self._ubcalc.UB
def _getMode(self):
return self.mode_selector.getMode()
def _getSigma(self):
return self._ubcalc.sigma
def _getTau(self):
return self._ubcalc.tau
def _getParameter(self, name):
# Does not use context.getParameter as this will trigger a costly
# parameter collection
pm = self.parameter_manager
return pm.getParameterWithoutUpdatingTrackedParemeters(name)
def _getGammaParameterName(self):
return self._gammaParameterName

View File

@@ -0,0 +1,55 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from diffcalc.util import allnum
def getNameFromScannableOrString(o):
try: # it may be a scannable
return o.getName()
except AttributeError:
return str(o)
raise TypeError()
class DummyParameterManager(object):
def getParameterDict(self):
return {}
def _setParameter(self, name, value):
raise KeyError(name)
def _getParameter(self, name):
raise KeyError(name)
def update_tracked(self):
pass
def sim(self, scn, hkl):
"""sim hkl scn -- simulates moving scannable (not all)
"""
if not isinstance(hkl, (tuple, list)):
raise TypeError
if not allnum(hkl):
raise TypeError()
try:
print scn.simulateMoveTo(hkl)
except AttributeError:
raise TypeError("The first argument does not support simulated moves")

View File

@@ -0,0 +1,847 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from math import pi, asin, acos, sin, cos, sqrt, atan2, fabs, atan
from diffcalc import settings
try:
from numpy import matrix
from numpy.linalg import norm
except ImportError:
from numjy import matrix
from numjy.linalg import norm
from diffcalc.hkl.calcbase import HklCalculatorBase
from diffcalc.hkl.vlieg.transform import TransformCInRadians
from diffcalc.util import dot3, cross3, bound, differ
from diffcalc.hkl.vlieg.geometry import createVliegMatrices, \
createVliegsPsiTransformationMatrix, \
createVliegsSurfaceTransformationMatrices, calcPHI
from diffcalc.hkl.vlieg.geometry import VliegPosition
from diffcalc.hkl.vlieg.constraints import VliegParameterManager
from diffcalc.hkl.vlieg.constraints import ModeSelector
from diffcalc.ub.calc import PaperSpecificUbCalcStrategy
TORAD = pi / 180
TODEG = 180 / pi
transformC = TransformCInRadians()
PREFER_POSITIVE_CHI_SOLUTIONS = True
I = matrix('1 0 0; 0 1 0; 0 0 1')
y = matrix('0; 1; 0')
def check(condition, ErrorOrStringOrCallable, *args):
"""
fail = check(condition, ErrorOrString) -- if condition is false raises the
Exception passed in, or creates one from a string. If a callable function
is passed in this is called with any args specified and the thing returns
false.
"""
# TODO: Remove (really nasty) check function
if condition == False:
if callable(ErrorOrStringOrCallable):
ErrorOrStringOrCallable(*args)
return False
elif isinstance(ErrorOrStringOrCallable, str):
raise Exception(ErrorOrStringOrCallable)
else: # assume input is an exception
raise ErrorOrStringOrCallable
return True
def sign(x):
if x < 0:
return -1
else:
return 1
def vliegAnglesToHkl(pos, wavelength, UBMatrix):
"""
Returns hkl indices from pos object in radians.
"""
wavevector = 2 * pi / wavelength
# Create transformation matrices
[ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices(
pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi)
# Create the plane normal vector in the alpha axis coordinate frame
qa = ((DELTA * GAMMA) - ALPHA.I) * matrix([[0], [wavevector], [0]])
# Transform the plane normal vector from the alpha frame to reciprical
# lattice frame.
hkl = UBMatrix.I * PHI.I * CHI.I * OMEGA.I * qa
return hkl[0, 0], hkl[1, 0], hkl[2, 0]
class VliegUbCalcStrategy(PaperSpecificUbCalcStrategy):
def calculate_q_phi(self, pos):
[ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI] = createVliegMatrices(
pos.alpha, pos.delta, pos.gamma, pos.omega, pos.chi, pos.phi)
u1a = (DELTA * GAMMA - ALPHA.I) * y
u1p = PHI.I * CHI.I * OMEGA.I * u1a
return u1p
class VliegHklCalculator(HklCalculatorBase):
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 __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 _findOmegaAndChiToRotateHchiIntoQalpha(h_chi, q_alpha):
"""
(omega, chi) = _findOmegaAndChiToRotateHchiIntoQalpha(H_chi, Q_alpha)
Solves for omega and chi in OMEGA*CHI*h_chi = q_alpha where h_chi and
q_alpha are 3x1 matrices with unit length. Omega and chi are returned in
radians.
Throws an exception if this is not possible.
"""
def solve(a, b, c):
"""
x1,x2 = solve(a , b, c)
solves for the two solutions to x in equations of the form
a*sin(x) + b*cos(x) = c
by using the trigonometric identity
a*sin(x) + b*cos(x) = a*sin(x)+b*cos(x)=sqrt(a**2+b**2)-sin(x+p)
where
p = atan(b/a) + {0 if a>=0
{pi if a<0
"""
if a == 0:
p = pi / 2 if b >= 0 else - pi / 2
else:
p = atan(b / a)
if a < 0:
p = p + pi
guts = c / sqrt(a ** 2 + b ** 2)
if guts < -1:
guts = -1
elif guts > 1:
guts = 1
left1 = asin(guts)
left2 = pi - left1
return (left1 - p, left2 - p)
def ne(a, b):
"""
shifts a and b in between -pi and pi and tests for near equality
"""
def shift(a):
if a > pi:
return a - 2 * pi
elif a <= -pi:
return a + 2 * pi
else:
return a
return abs(shift(a) - shift(b)) < .0000001
# 1. Compute some solutions
h_chi1 = h_chi[0, 0]
h_chi2 = h_chi[1, 0]
h_chi3 = h_chi[2, 0]
q_alpha1 = q_alpha[0, 0]
q_alpha2 = q_alpha[1, 0]
q_alpha3 = q_alpha[2, 0]
try:
# a) Solve for chi using Equation 3
chi1, chi2 = solve(-h_chi1, h_chi3, q_alpha3)
# b) Solve for omega Equation 1 and each chi
B = h_chi1 * cos(chi1) + h_chi3 * sin(chi1)
eq1omega11, eq1omega12 = solve(h_chi2, B, q_alpha1)
B = h_chi1 * cos(chi2) + h_chi3 * sin(chi2)
eq1omega21, eq1omega22 = solve(h_chi2, B, q_alpha1)
# c) Solve for omega Equation 2 and each chi
A = -h_chi1 * cos(chi1) - h_chi3 * sin(chi1)
eq2omega11, eq2omega12 = solve(A, h_chi2, q_alpha2)
A = -h_chi1 * cos(chi2) - h_chi3 * sin(chi2)
eq2omega21, eq2omega22 = solve(A, h_chi2, q_alpha2)
except ValueError, e:
raise ValueError(
str(e) + ":\nProblem in fixed-phi calculation for:\nh_chi: " +
str(h_chi.tolist()) + " q_alpha: " + str(q_alpha.tolist()))
# 2. Choose values of chi and omega that are solutions to equations 1 and 2
solutions = []
# a) Check the chi1 solutions
print "_findOmegaAndChiToRotateHchiIntoQalpha:"
if ne(eq1omega11, eq2omega11) or ne(eq1omega11, eq2omega12):
# print "1: eq1omega11, chi1 = ", eq1omega11, chi1
solutions.append((eq1omega11, chi1))
if ne(eq1omega12, eq2omega11) or ne(eq1omega12, eq2omega12):
# print "2: eq1omega12, chi1 = ", eq1omega12, chi1
solutions.append((eq1omega12, chi1))
# b) Check the chi2 solutions
if ne(eq1omega21, eq2omega21) or ne(eq1omega21, eq2omega22):
# print "3: eq1omega21, chi2 = ", eq1omega21, chi2
solutions.append((eq1omega21, chi2))
if ne(eq1omega22, eq2omega21) or ne(eq1omega22, eq2omega22):
# print "4: eq1omega22, chi2 = ", eq1omega22, chi2
solutions.append((eq1omega22, chi2))
# print solutions
# print "*"
if len(solutions) == 0:
e = "h_chi: " + str(h_chi.tolist())
e += " q_alpha: " + str(q_alpha.tolist())
e += ("\nchi1:%4f eq1omega11:%4f eq1omega12:%4f eq2omega11:%4f "
"eq2omega12:%4f" % (chi1 * TODEG, eq1omega11 * TODEG,
eq1omega12 * TODEG, eq2omega11 * TODEG, eq2omega12 * TODEG))
e += ("\nchi2:%4f eq1omega21:%4f eq1omega22:%4f eq2omega21:%4f "
"eq2omega22:%4f" % (chi2 * TODEG, eq1omega21 * TODEG,
eq1omega22 * TODEG, eq2omega21 * TODEG, eq2omega22 * TODEG))
raise Exception("Could not find simultaneous solution for this fixed "
"phi mode problem\n" + e)
if not PREFER_POSITIVE_CHI_SOLUTIONS:
return solutions[0]
positive_chi_solutions = [sol for sol in solutions if sol[1] > 0]
if len(positive_chi_solutions) == 0:
print "WARNING: A +ve chi solution was requested, but none were found."
print " Returning a -ve one. Try the mapper"
return solutions[0]
if len(positive_chi_solutions) > 1:
print ("INFO: Multiple +ve chi solutions were found [(omega, chi) ...]"
" = " + str(positive_chi_solutions))
print " Returning the first"
return positive_chi_solutions[0]

View File

@@ -0,0 +1,336 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from copy import copy
from diffcalc.util import DiffcalcException
class Mode(object):
def __init__(self, index, name, group, description, parameterNames,
implemented=True):
self.index = index
self.group = group
self.name = name
self.description = description
self.parameterNames = parameterNames
self.implemented = implemented
def __repr__(self):
return "%i) %s" % (self.index, self.description)
def __str__(self):
return self.__repr__()
def usesParameter(self, name):
return name in self.parameterNames
class ModeSelector(object):
def __init__(self, geometry, parameterManager=None,
gammaParameterName='gamma'):
self.parameter_manager = parameterManager
self._geometry = geometry
self._gammaParameterName = gammaParameterName
self._modelist = {} # indexed by non-contiguous mode number
self._configureAvailableModes()
self._selectedIndex = 1
def setParameterManager(self, manager):
"""
Required as a ParameterManager and ModelSelector are mutually tied
together in practice
"""
self.parameter_manager = manager
def _configureAvailableModes(self):
gammaName = self._gammaParameterName
ml = self._modelist
ml[0] = Mode(0, '4cFixedw', 'fourc', 'fourc fixed-bandlw',
['alpha', gammaName, 'blw'], False)
ml[1] = Mode(1, '4cBeq', 'fourc', 'fourc bisecting',
['alpha', gammaName])
ml[2] = Mode(2, '4cBin', 'fourc', 'fourc incoming',
['alpha', gammaName, 'betain'])
ml[3] = Mode(3, '4cBout', 'fourc', 'fourc outgoing',
['alpha', gammaName, 'betaout'])
ml[4] = Mode(4, '4cAzimuth', 'fourc', 'fourc azimuth',
['alpha', gammaName, 'azimuth'], False)
ml[5] = Mode(5, '4cPhi', 'fourc', 'fourc fixed-phi',
['alpha', gammaName, 'phi'])
ml[10] = Mode(10, '5cgBeq', 'fivecFixedGamma', 'fivec bisecting',
[gammaName])
ml[11] = Mode(11, '5cgBin', 'fivecFixedGamma', 'fivec incoming',
[gammaName, 'betain'])
ml[12] = Mode(12, '5cgBout', 'fivecFixedGamma', 'fivec outgoing',
[gammaName, 'betaout'])
ml[13] = Mode(13, '5caBeq', 'fivecFixedAlpha', 'fivec bisecting',
['alpha'])
ml[14] = Mode(14, '5caBin', 'fivecFixedAlpha', 'fivec incoming',
['alpha', 'betain'])
ml[15] = Mode(15, '5caBout', 'fivecFixedAlpha', 'fivec outgoing',
['alpha', 'betaout'])
ml[20] = Mode(20, '6czBeq', 'zaxis', 'zaxis bisecting',
[])
ml[21] = Mode(21, '6czBin', 'zaxis', 'zaxis incoming',
['betain'])
ml[22] = Mode(22, '6czBout', 'zaxis', 'zaxiz outgoing',
['betaout'])
def setModeByIndex(self, index):
if index in self._modelist:
self._selectedIndex = index
else:
raise DiffcalcException("mode %r is not defined" % index)
def setModeByName(self, name):
def findModeWithName(name):
for index, mode in self._modelist.items():
if mode.name == name:
return index, mode
raise ValueError
try:
index, mode = findModeWithName(name)
except ValueError:
raise DiffcalcException(
'Unknown mode. The diffraction calculator supports these '
'modeSelector: %s' % self._supportedModes.keys())
if self._geometry.supports_mode_group(mode.group):
self._selectedIndex = index
else:
raise DiffcalcException(
"Mode %s not supported for this diffractometer (%s)." %
(name, self._geometry.name))
def getMode(self):
return self._modelist[self._selectedIndex]
def reportCurrentMode(self):
return self.getMode().__str__()
def reportAvailableModes(self):
result = ''
indecis = self._modelist.keys()
indecis.sort()
for index in indecis:
mode = self._modelist[index]
if self._geometry.supports_mode_group(mode.group):
paramString = ''
flags = ''
pm = self.parameter_manager
for paramName in pm.getUserChangableParametersForMode(mode):
paramString += paramName + ", "
if paramString:
paramString = paramString[:-2] # remove trailing commas
if not mode.implemented:
flags += "(Not impl.)"
result += ('%2i) %-15s (%s) %s\n' % (mode.index,
mode.description, paramString, flags))
return result
class VliegParameterManager(object):
def __init__(self, geometry, hardware, modeSelector,
gammaParameterName='gamma'):
self._geometry = geometry
self._hardware = hardware
self._modeSelector = modeSelector
self._gammaParameterName = gammaParameterName
self._parameters = {}
self._defineParameters()
def _defineParameters(self):
# Set default fixed values (In degrees if angles)
self._parameters = {}
self._parameters['alpha'] = 0
self._parameters[self._gammaParameterName] = 0
self._parameters['blw'] = None # Busing and Levi omega!
self._parameters['betain'] = None
self._parameters['betaout'] = None
self._parameters['azimuth'] = None
self._parameters['phi'] = None
self._parameterDisplayOrder = (
'alpha', self._gammaParameterName, 'betain', 'betaout', 'azimuth',
'phi', 'blw')
self._trackableParameters = ('alpha', self._gammaParameterName, 'phi')
self._trackedParameters = []
# Overide parameters that are unchangable for this diffractometer
for (name, value) in self._geometry.fixed_parameters.items():
if name not in self._parameters:
raise RuntimeError(
"The %s diffractometer geometry specifies a fixed "
"parameter %s that is not used by the diffractometer "
"calculator" % (self._geometry.getName, name))
self._parameters[name] = value
def reportAllParameters(self):
self.update_tracked()
result = ''
for name in self._parameterDisplayOrder:
flags = ""
if not self._modeSelector.getMode().usesParameter(name):
flags += '(not relevant in this mode)'
if self._geometry.parameter_fixed(name):
flags += ' (fixed by this diffractometer)'
if self.isParameterTracked(name):
flags += ' (tracking hardware)'
value = self._parameters[name]
if value is None:
value = '---'
else:
value = float(value)
result += '%s: %s %s\n' % (name.rjust(8), value, flags)
return result
def reportParametersUsedInCurrentMode(self):
self.update_tracked()
result = ''
for name in self.getUserChangableParametersForMode(
self._modeSelector.getMode()):
flags = ""
value = self._parameters[name]
if value is None:
value = '---'
else:
value = float(value)
if self.isParameterTracked(name):
flags += ' (tracking hardware)'
result += '%s: %s %s\n' % (name.rjust(8), value, flags)
return result
def getUserChangableParametersForMode(self, mode=None):
"""
(p1,p2...p3) = getUserChangableParametersForMode(mode) returns a list
of parameters names used in this mode for this diffractometer geometry.
Checks current mode if no mode specified.
"""
if mode is None:
mode = self._mode
result = []
for name in self._parameterDisplayOrder:
if self._isParameterChangeable(name, mode):
result += [name]
return result
### Fixed parameters stuff ###
def set_constraint(self, name, value):
if not name in self._parameters:
raise DiffcalcException("No fixed parameter %s is used by the "
"diffraction calculator" % name)
if self._geometry.parameter_fixed(name):
raise DiffcalcException(
"The parameter %s cannot be changed: It has been fixed by the "
"%s diffractometer geometry"
% (name, self._geometry.name))
if self.isParameterTracked(name):
# for safety and to avoid confusion:
raise DiffcalcException(
"Cannot change parameter %s as it is set to track an axis.\n"
"To turn this off use a command like 'trackalpha 0'." % name)
if not self.isParameterUsedInSelectedMode(name):
print ("WARNING: The parameter %s is not used in mode %i" %
(name, self._modeSelector.getMode().index))
self._parameters[name] = value
def isParameterUsedInSelectedMode(self, name):
return self._modeSelector.getMode().usesParameter(name)
def getParameterWithoutUpdatingTrackedParemeters(self, name):
try:
return self._parameters[name]
except KeyError:
raise DiffcalcException("No fixed parameter %s is used by the "
"diffraction calculator" % name)
def get_constraint(self, name):
self.update_tracked()
return self.getParameterWithoutUpdatingTrackedParemeters(name)
def getParameterDict(self):
self.update_tracked()
return copy(self._parameters)
@property
def settable_constraint_names(self):
"""list of all available constraints that have settable values"""
return sorted(self.getParameterDict().keys())
def setTrackParameter(self, name, switch):
if not name in self._parameters.keys():
raise DiffcalcException("No fixed parameter %s is used by the "
"diffraction calculator" % name)
if not name in self._trackableParameters:
raise DiffcalcException("Parameter %s is not trackable" % name)
if not self._isParameterChangeable(name):
print ("WARNING: Parameter %s is not used in mode %i" %
(name, self._mode.index))
if switch:
if name not in self._trackedParameters:
self._trackedParameters.append(name)
else:
if name in self._trackedParameters:
self._trackedParameters.remove(name)
def isParameterTracked(self, name):
return (name in self._trackedParameters)
def update_tracked(self):
"""Note that the name of a tracked parameter MUST map into the name of
an external diffractometer angle
"""
if self._trackedParameters:
externalAnglePositionArray = self._hardware.get_position()
externalAngleNames = list(self._hardware.get_axes_names())
for name in self._trackedParameters:
self._parameters[name] = \
externalAnglePositionArray[externalAngleNames.index(name)]
def _isParameterChangeable(self, name, mode=None):
"""
Returns true if parameter is used in a mode (current mode if none
specified), AND if it is not locked by the diffractometer geometry
"""
if mode is None:
mode = self._modeSelector.getMode()
return (mode.usesParameter(name) and
not self._geometry.parameter_fixed(name))

View File

@@ -0,0 +1,523 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from math import tan, cos, sin, asin, atan, pi, fabs
try:
from numpy import matrix
except ImportError:
from numjy import matrix
from diffcalc.util import x_rotation, z_rotation, y_rotation
from diffcalc.util import AbstractPosition
from diffcalc.util import bound, nearlyEqual
TORAD = pi / 180
TODEG = 180 / pi
def calcALPHA(alpha):
return x_rotation(alpha)
def calcDELTA(delta):
return z_rotation(-delta)
def calcGAMMA(gamma):
return x_rotation(gamma)
def calcOMEGA(omega):
return z_rotation(-omega)
def calcCHI(chi):
return y_rotation(chi)
def calcPHI(phi):
return z_rotation(-phi)
def createVliegMatrices(alpha=None, delta=None, gamma=None, omega=None,
chi=None, phi=None):
ALPHA = None if alpha is None else calcALPHA(alpha)
DELTA = None if delta is None else calcDELTA(delta)
GAMMA = None if gamma is None else calcGAMMA(gamma)
OMEGA = None if omega is None else calcOMEGA(omega)
CHI = None if chi is None else calcCHI(chi)
PHI = None if phi is None else calcPHI(phi)
return ALPHA, DELTA, GAMMA, OMEGA, CHI, PHI
def createVliegsSurfaceTransformationMatrices(sigma, tau):
"""[SIGMA, TAU] = createVliegsSurfaceTransformationMatrices(sigma, tau)
angles in radians
"""
SIGMA = matrix([[cos(sigma), 0, sin(sigma)],
[0, 1, 0], \
[-sin(sigma), 0, cos(sigma)]])
TAU = matrix([[cos(tau), sin(tau), 0],
[-sin(tau), cos(tau), 0],
[0, 0, 1]])
return(SIGMA, TAU)
def createVliegsPsiTransformationMatrix(psi):
"""PSI = createPsiTransformationMatrices(psi)
angles in radians
"""
return matrix([[1, 0, 0],
[0, cos(psi), sin(psi)],
[0, -sin(psi), cos(psi)]])
class VliegPosition(AbstractPosition):
"""The position of all six diffractometer axis"""
def __init__(self, alpha=None, delta=None, gamma=None, omega=None,
chi=None, phi=None):
self.alpha = alpha
self.delta = delta
self.gamma = gamma
self.omega = omega
self.chi = chi
self.phi = phi
def clone(self):
return VliegPosition(self.alpha, self.delta, self.gamma, self.omega,
self.chi, self.phi)
def changeToRadians(self):
self.alpha *= TORAD
self.delta *= TORAD
self.gamma *= TORAD
self.omega *= TORAD
self.chi *= TORAD
self.phi *= TORAD
def changeToDegrees(self):
self.alpha *= TODEG
self.delta *= TODEG
self.gamma *= TODEG
self.omega *= TODEG
self.chi *= TODEG
self.phi *= TODEG
def inRadians(self):
pos = self.clone()
pos.changeToRadians()
return pos
def inDegrees(self):
pos = self.clone()
pos.changeToDegrees()
return pos
def nearlyEquals(self, pos2, maxnorm):
for a, b in zip(self.totuple(), pos2.totuple()):
if abs(a - b) > maxnorm:
return False
return True
def totuple(self):
return (self.alpha, self.delta, self.gamma, self.omega,
self.chi, self.phi)
def __str__(self):
return ("VliegPosition(alpha %r delta: %r gamma: %r omega: %r chi: %r"
" phi: %r)" % self.totuple())
def __repr__(self):
return self.__str__()
def __eq__(self, b):
return self.nearlyEquals(b, .001)
class VliegGeometry(object):
# Required methods
def __init__(self, name, supported_mode_groups, fixed_parameters,
gamma_location):
"""
Set geometry name (String), list of supported mode groups (list of
strings), list of axis names (list of strings). Define the parameters
e.g. alpha and gamma for a four circle (dictionary). Define wether the
gamma angle is on the 'arm' or the 'base'; used only by AngleCalculator
to interpret the gamma parameter in fixed gamma mode: for instruments
with gamma on the base, rather than on the arm as the code assume
internally, the two methods physical_angles_to_internal_position and
internal_position_to_physical_angles must still be used.
"""
if gamma_location not in ('arm', 'base', None):
raise RuntimeError(
"Gamma must be on either 'arm' or 'base' or None")
self.name = name
self.supported_mode_groups = supported_mode_groups
self.fixed_parameters = fixed_parameters
self.gamma_location = gamma_location
def physical_angles_to_internal_position(self, physicalAngles):
raise NotImplementedError()
def internal_position_to_physical_angles(self, physicalAngles):
raise NotImplementedError()
### Do not overide these these ###
def supports_mode_group(self, name):
return name in self.supported_mode_groups
def parameter_fixed(self, name): # parameter_fixed
return name in self.fixed_parameters.keys()
class SixCircleGammaOnArmGeometry(VliegGeometry):
"""
This six-circle diffractometer geometry simply passes through the
angles from a six circle diffractometer with the same geometry and
angle names as those defined in Vliegs's paper defined internally.
"""
def __init__(self):
VliegGeometry.__init__(
self,
name='sixc_gamma_on_arm',
supported_mode_groups=('fourc', 'fivecFixedGamma',
'fivecFixedAlpha', 'zaxis'),
fixed_parameters={},
gamma_location='arm')
def physical_angles_to_internal_position(self, physicalAngles):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
assert (len(physicalAngles) == 6), "Wrong length of input list"
return VliegPosition(*physicalAngles)
def internal_position_to_physical_angles(self, internalPosition):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
return internalPosition.totuple()
class SixCircleGeometry(VliegGeometry):
"""
This six-circle diffractometer geometry simply passes through the
angles from a six circle diffractometer with the same geometry and
angle names as those defined in Vliegs's paper defined internally.
"""
def __init__(self):
VliegGeometry.__init__(
self,
name='sixc',
supported_mode_groups=('fourc', 'fivecFixedGamma',
'fivecFixedAlpha', 'zaxis'),
fixed_parameters={},
gamma_location='base')
self.hardwareMonitor = None
#(deltaA, gammaA) = gammaOnBaseToArm(deltaB, gammaB, alpha) (all in radians)
#(deltaB, gammaB) = gammaOnArmToBase(deltaA, gammaA, alpha) (all in radians)
def physical_angles_to_internal_position(self, physicalAngles):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
assert (len(physicalAngles) == 6), "Wrong length of input list"
alpha, deltaB, gammaB, omega, chi, phi = physicalAngles
(deltaA, gammaA) = gammaOnBaseToArm(
deltaB * TORAD, gammaB * TORAD, alpha * TORAD)
return VliegPosition(
alpha, deltaA * TODEG, gammaA * TODEG, omega, chi, phi)
def internal_position_to_physical_angles(self, internalPosition):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
alpha, deltaA, gammaA, omega, chi, phi = internalPosition.totuple()
deltaB, gammaB = gammaOnArmToBase(
deltaA * TORAD, gammaA * TORAD, alpha * TORAD)
deltaB, gammaB = deltaB * TODEG, gammaB * TODEG
if self.hardwareMonitor is not None:
gammaName = self.hardwareMonitor.get_axes_names()[2]
minGamma = self.hardwareMonitor.get_lower_limit(gammaName)
maxGamma = self.hardwareMonitor.get_upper_limit(gammaName)
if maxGamma is not None:
if gammaB > maxGamma:
gammaB = gammaB - 180
deltaB = 180 - deltaB
if minGamma is not None:
if gammaB < minGamma:
gammaB = gammaB + 180
deltaB = 180 - deltaB
return alpha, deltaB, gammaB, omega, chi, phi
class FivecWithGammaOnBase(SixCircleGeometry):
def __init__(self):
VliegGeometry.__init__(
self,
name='fivec_with_gamma',
supported_mode_groups=('fourc', 'fivecFixedGamma'),
fixed_parameters={'alpha': 0.0},
gamma_location='base')
self.hardwareMonitor = None
def physical_angles_to_internal_position(self, physicalAngles):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(d,g,o,c,p)
"""
assert (len(physicalAngles) == 5), "Wrong length of input list"
return SixCircleGeometry.physical_angles_to_internal_position(
self, (0,) + tuple(physicalAngles))
def internal_position_to_physical_angles(self, internalPosition):
""" (d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
return SixCircleGeometry.internal_position_to_physical_angles(
self, internalPosition)[1:]
class Fivec(VliegGeometry):
"""
This five-circle diffractometer geometry is for diffractometers with the
same geometry and angle names as those defined in Vliegs's paper defined
internally, but with no out plane detector arm gamma."""
def __init__(self):
VliegGeometry.__init__(self,
name='fivec',
supported_mode_groups=('fourc', 'fivecFixedGamma'),
fixed_parameters={'gamma': 0.0},
gamma_location='arm'
)
def physical_angles_to_internal_position(self, physicalAngles):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
assert (len(physicalAngles) == 5), "Wrong length of input list"
physicalAngles = tuple(physicalAngles)
angles = physicalAngles[0:2] + (0.0,) + physicalAngles[2:]
return VliegPosition(*angles)
def internal_position_to_physical_angles(self, internalPosition):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
sixAngles = internalPosition.totuple()
return sixAngles[0:2] + sixAngles[3:]
class Fourc(VliegGeometry):
"""
This five-circle diffractometer geometry is for diffractometers with the
same geometry and angle names as those defined in Vliegs's paper defined
internally, but with no out plane detector arm gamma."""
def __init__(self):
VliegGeometry.__init__(self,
name='fourc',
supported_mode_groups=('fourc'),
fixed_parameters={'gamma': 0.0, 'alpha': 0.0},
gamma_location='arm'
)
def physical_angles_to_internal_position(self, physicalAngles):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
assert (len(physicalAngles) == 4), "Wrong length of input list"
physicalAngles = tuple(physicalAngles)
angles = (0.0, physicalAngles[0], 0.0) + physicalAngles[1:]
return VliegPosition(*angles)
def internal_position_to_physical_angles(self, internalPosition):
""" (a,d,g,o,c,p) = physicalAnglesToInternal(a,d,g,o,c,p)
"""
sixAngles = internalPosition.totuple()
return sixAngles[1:2] + sixAngles[3:]
def sign(x):
if x < 0:
return -1
else:
return 1
"""
Based on: Elias Vlieg, "A (2+3)-Type Surface Diffractometer: Mergence of
the z-axis and (2+2)-Type Geometries", J. Appl. Cryst. (1998). 31.
198-203
"""
def solvesEq8(alpha, deltaA, gammaA, deltaB, gammaB):
tol = 1e-6
return (nearlyEqual(sin(deltaA) * cos(gammaA), sin(deltaB), tol) and
nearlyEqual(cos(deltaA) * cos(gammaA),
cos(gammaB - alpha) * cos(deltaB), tol) and
nearlyEqual(sin(gammaA), sin(gammaB - alpha) * cos(deltaB), tol))
GAMMAONBASETOARM_WARNING = '''
WARNING: This diffractometer has the gamma circle attached to the
base rather than the end of
the delta arm as Vlieg's paper defines. A conversion has
been made from the physical angles to their internal
representation (gamma-on-base-to-arm). This conversion has
forced gamma to be positive by applying the mapping:
delta --> 180+delta
gamma --> 180+gamma.
This should have no adverse effect.
'''
def gammaOnBaseToArm(deltaB, gammaB, alpha):
"""
(deltaA, gammaA) = gammaOnBaseToArm(deltaB, gammaB, alpha) (all in
radians)
Maps delta and gamma for an instrument where the gamma circle rests on
the base to the case where it is on the delta arm.
There are always two possible solutions. To get the second apply the
transform:
delta --> 180+delta (flip to opposite side of circle)
gamma --> 180+gamma (flip to opposite side of circle)
This code will return the solution where gamma is between 0 and 180.
"""
### Equation11 ###
if fabs(cos(gammaB - alpha)) < 1e-20:
deltaA1 = sign(tan(deltaB)) * sign(cos(gammaB - alpha)) * pi / 2
else:
deltaA1 = atan(tan(deltaB) / cos(gammaB - alpha))
# ...second root
if deltaA1 <= 0:
deltaA2 = deltaA1 + pi
else:
deltaA2 = deltaA1 - pi
### Equation 12 ###
gammaA1 = asin(bound(cos(deltaB) * sin(gammaB - alpha)))
# ...second root
if gammaA1 >= 0:
gammaA2 = pi - gammaA1
else:
gammaA2 = -pi - gammaA1
# Choose the delta solution that fits equations 8
if solvesEq8(alpha, deltaA1, gammaA1, deltaB, gammaB):
deltaA, gammaA = deltaA1, gammaA1
elif solvesEq8(alpha, deltaA2, gammaA1, deltaB, gammaB):
deltaA, gammaA = deltaA2, gammaA1
print "gammaOnBaseToArm choosing 2nd delta root (to internal)"
elif solvesEq8(alpha, deltaA1, gammaA2, deltaB, gammaB):
print "gammaOnBaseToArm choosing 2nd gamma root (to internal)"
deltaA, gammaA = deltaA1, gammaA2
elif solvesEq8(alpha, deltaA2, gammaA2, deltaB, gammaB):
print "gammaOnBaseToArm choosing 2nd delta root and 2nd gamma root"
deltaA, gammaA = deltaA2, gammaA2
else:
raise RuntimeError(
"No valid solutions found mapping from gamma-on-base to gamma-on-arm")
return deltaA, gammaA
GAMMAONARMTOBASE_WARNING = '''
WARNING: This diffractometer has the gamma circle attached to the base
rather than the end of the delta arm as Vlieg's paper defines.
A conversion has been made from the internal representation of
angles to physical angles (gamma-on-arm-to-base). This
conversion has forced gamma to be positive by applying the
mapping:
delta --> 180-delta
gamma --> 180+gamma.
This should have no adverse effect.
'''
def gammaOnArmToBase(deltaA, gammaA, alpha):
"""
(deltaB, gammaB) = gammaOnArmToBase(deltaA, gammaA, alpha) (all in
radians)
Maps delta and gamma for an instrument where the gamma circle is on
the delta arm to the case where it rests on the base.
There are always two possible solutions. To get the second apply the
transform:
delta --> 180-delta (reflect and flip to opposite side)
gamma --> 180+gamma (flip to opposite side)
This code will return the solution where gamma is positive, but will
warn if a sign change was made.
"""
### Equation 9 ###
deltaB1 = asin(bound(sin(deltaA) * cos(gammaA)))
# ...second root:
if deltaB1 >= 0:
deltaB2 = pi - deltaB1
else:
deltaB2 = -pi - deltaB1
### Equation 10 ###:
if fabs(cos(deltaA)) < 1e-20:
gammaB1 = sign(tan(gammaA)) * sign(cos(deltaA)) * pi / 2 + alpha
else:
gammaB1 = atan(tan(gammaA) / cos(deltaA)) + alpha
#... second root:
if gammaB1 <= 0:
gammaB2 = gammaB1 + pi
else:
gammaB2 = gammaB1 - pi
### Choose the solution that fits equation 8 ###
if (solvesEq8(alpha, deltaA, gammaA, deltaB1, gammaB1) and
0 <= gammaB1 <= pi):
deltaB, gammaB = deltaB1, gammaB1
elif (solvesEq8(alpha, deltaA, gammaA, deltaB2, gammaB1) and
0 <= gammaB1 <= pi):
deltaB, gammaB = deltaB2, gammaB1
print "gammaOnArmToBase choosing 2nd delta root (to physical)"
elif (solvesEq8(alpha, deltaA, gammaA, deltaB1, gammaB2) and
0 <= gammaB2 <= pi):
print "gammaOnArmToBase choosing 2nd gamma root (to physical)"
deltaB, gammaB = deltaB1, gammaB2
elif (solvesEq8(alpha, deltaA, gammaA, deltaB2, gammaB2)
and 0 <= gammaB2 <= pi):
print "gammaOnArmToBase choosing 2nd delta root and 2nd gamma root"
deltaB, gammaB = deltaB2, gammaB2
else:
raise RuntimeError(
"No valid solutions found mapping gamma-on-arm to gamma-on-base")
return deltaB, gammaB

View File

@@ -0,0 +1,139 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from diffcalc.hkl.common import getNameFromScannableOrString
from diffcalc.util import command
from diffcalc import settings
from diffcalc.ub import ub
from diffcalc.hkl.vlieg.calc import VliegHklCalculator
__all__ = ['hklmode', 'setpar', 'trackalpha', 'trackgamma', 'trackphi',
'parameter_manager', 'hklcalc']
hklcalc = VliegHklCalculator(ub.ubcalc)
parameter_manager = hklcalc.parameter_manager
def __str__(self):
return hklcalc.__str__()
@command
def hklmode(num=None):
"""hklmode {num} -- changes mode or shows current and available modes and all settings""" #@IgnorePep8
if num is None:
print hklcalc.__str__()
else:
hklcalc.mode_selector.setModeByIndex(int(num))
pm = hklcalc.parameter_manager
print (hklcalc.mode_selector.reportCurrentMode() + "\n" +
pm.reportParametersUsedInCurrentMode())
def _setParameter(name, value):
hklcalc.parameter_manager.set_constraint(name, value)
def _getParameter(name):
return hklcalc.parameter_manager.get_constraint(name)
@command
def setpar(scannable_or_string=None, val=None):
"""setpar {parameter_scannable {{val}} -- sets or shows a parameter'
setpar {parameter_name {val}} -- sets or shows a parameter'
"""
if scannable_or_string is None:
#show all
parameterDict = hklcalc.parameter_manager.getParameterDict()
names = parameterDict.keys()
names.sort()
for name in names:
print _representParameter(name)
else:
name = getNameFromScannableOrString(scannable_or_string)
if val is None:
_representParameter(name)
else:
oldval = _getParameter(name)
_setParameter(name, float(val))
print _representParameter(name, oldval, float(val))
def _representParameter(name, oldval=None, newval=None):
flags = ''
if hklcalc.parameter_manager.isParameterTracked(name):
flags += '(tracking hardware) '
if settings.geometry.parameter_fixed(name): # @UndefinedVariable
flags += '(fixed by geometry) '
pm = hklcalc.parameter_manager
if not pm.isParameterUsedInSelectedMode(name):
flags += '(not relevant in this mode) '
if oldval is None:
val = _getParameter(name)
if val is None:
val = "---"
else:
val = str(val)
return "%s: %s %s" % (name, val, flags)
else:
return "%s: %s --> %f %s" % (name, oldval, newval, flags)
def _checkInputAndSetOrShowParameterTracking(name, b=None):
"""
for track-parameter commands: If no args displays parameter settings,
otherwise sets the tracking switch for the given parameter and displays
settings.
"""
# set if arg given
if b is not None:
hklcalc.parameter_manager.setTrackParameter(name, b)
# Display:
lastValue = _getParameter(name)
if lastValue is None:
lastValue = "---"
else:
lastValue = str(lastValue)
flags = ''
if hklcalc.parameter_manager.isParameterTracked(name):
flags += '(tracking hardware)'
print "%s: %s %s" % (name, lastValue, flags)
@command
def trackalpha(b=None):
"""trackalpha {boolean} -- determines wether alpha parameter will track alpha axis""" #@IgnorePep8
_checkInputAndSetOrShowParameterTracking('alpha', b)
@command
def trackgamma(b=None):
"""trackgamma {boolean} -- determines wether gamma parameter will track alpha axis""" #@IgnorePep8
_checkInputAndSetOrShowParameterTracking('gamma', b)
@command
def trackphi(b=None):
"""trackphi {boolean} -- determines wether phi parameter will track phi axis""" #@IgnorePep8
_checkInputAndSetOrShowParameterTracking('phi', b)
commands_for_help = ['Mode',
hklmode,
setpar,
trackalpha,
trackgamma,
trackphi]

View File

@@ -0,0 +1,480 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from diffcalc.util import command
from copy import copy
from math import pi
from diffcalc.hkl.vlieg.geometry import VliegPosition as P
SMALL = 1e-10
class Transform(object):
def transform(self, pos):
raise RuntimeError('Not implemented')
### Transforms, currently for definition and testing the theory only
class TransformC(Transform):
'''Flip omega, invert chi and flip phi
'''
def transform(self, pos):
pos = pos.clone()
pos.omega -= 180
pos.chi *= -1
pos.phi -= 180
return pos
class TransformB(Transform):
'''Flip chi, and invert and flip omega
'''
def transform(self, pos):
pos = pos.clone()
pos.chi -= 180
pos.omega = 180 - pos.omega
return pos
class TransformA(Transform):
'''Invert scattering plane: invert delta and omega and flip chi'''
def transform(self, pos):
pos = pos.clone()
pos.delta *= -1
pos.omega *= -1
pos.chi -= 180
return pos
class TransformCInRadians(Transform):
'''
Flip omega, invert chi and flip phi. Using radians and keeping
-pi<omega<=pi and 0<=phi<=360
'''
def transform(self, pos):
pos = pos.clone()
if pos.omega > 0:
pos.omega -= pi
else:
pos.omega += pi
pos.chi *= -1
pos.phi += pi
return pos
###
transformsFromSector = {
0: (),
1: ('c',),
2: ('a',),
3: ('a', 'c'),
4: ('b', 'c'),
5: ('b',),
6: ('a', 'b', 'c'),
7: ('a', 'b')
}
sectorFromTransforms = {}
for k, v in transformsFromSector.iteritems():
sectorFromTransforms[v] = k
class VliegPositionTransformer(object):
def __init__(self, geometry, hardware, solution_transformer):
self._geometry = geometry
self._hardware = hardware
self._solution_transformer = solution_transformer
solution_transformer.limitCheckerFunction = self.is_position_within_limits
def transform(self, pos):
# 1. Choose the correct sector/transforms
return self._solution_transformer.transformPosition(pos)
def is_position_within_limits(self, position):
'''where position is Position object in degrees'''
angleTuple = self._geometry.internal_position_to_physical_angles(position)
angleTuple = self._hardware.cut_angles(angleTuple)
return self._hardware.is_position_within_limits(angleTuple)
class VliegTransformSelector(object):
'''All returned angles are between -180. and 180. -180.<=angle<180.
'''
### basic sector selection
def __init__(self):
self.transforms = []
self.autotransforms = []
self.autosectors = []
self.limitCheckerFunction = None # inject
self.sector = None
self.setSector(0)
def setSector(self, sector):
if not 0 <= sector <= 7:
raise ValueError('%i must between 0 and 7.' % sector)
self.sector = sector
self.transforms = list(transformsFromSector[sector])
def setTransforms(self, transformList):
transformList = list(transformList)
transformList.sort()
self.sector = sectorFromTransforms[tuple(transformList)]
self.transforms = transformList
def addTransorm(self, transformName):
if transformName not in ('a', 'b', 'c'):
raise ValueError('%s is not a recognised transform. Try a, b or c'
% transformName)
if transformName in self.transforms:
print "WARNING, transform %s is already selected"
else:
self.setTransforms(self.transforms + [transformName])
def removeTransorm(self, transformName):
if transformName not in ('a', 'b', 'c'):
raise ValueError('%s is not a recognised transform. Try a, b or c'
% transformName)
if transformName in self.transforms:
new = copy(self.transforms)
new.remove(transformName)
self.setTransforms(new)
else:
print "WARNING, transform %s was not selected" % transformName
def addAutoTransorm(self, transformOrSector):
'''
If input is a string (letter), tags one of the transofrms as being a
candidate for auto application. If a number, tags a sector as being a
candidate for auto application, and removes similar tags for any
transforms (as the two are incompatable).
'''
if type(transformOrSector) == str:
transform = transformOrSector
if transform not in ('a', 'b', 'c'):
raise ValueError(
'%s is not a recognised transform. Try a, b or c' %
transform)
if transform not in self.autotransforms:
self.autosectors = []
self.autotransforms.append(transform)
else:
print "WARNING: %s is already set to auto apply" % transform
elif type(transformOrSector) == int:
sector = transformOrSector
if not 0 <= sector <= 7:
raise ValueError('%i must between 0 and 7.' % sector)
if sector not in self.autosectors:
self.autotransforms = []
self.autosectors.append(sector)
else:
print "WARNING: %i is already set to auto apply" % sector
else:
raise ValueError("Input must be 'a', 'b' or 'c', "
"or 1,2,3,4,5,6 or 7.")
def removeAutoTransform(self, transformOrSector):
if type(transformOrSector) == str:
transform = transformOrSector
if transform not in ('a', 'b', 'c'):
raise ValueError("%s is not a recognised transform. "
"Try a, b or c" % transform)
if transform in self.autotransforms:
self.autotransforms.remove(transform)
else:
print "WARNING: %s is not set to auto apply" % transform
elif type(transformOrSector) == int:
sector = transformOrSector
if not 0 <= sector <= 7:
raise ValueError('%i must between 0 and 7.' % sector)
if sector in self.autosectors:
self.autosectors.remove(sector)
else:
print "WARNING: %s is not set to auto apply" % sector
else:
raise ValueError("Input must be 'a', 'b' or 'c', "
"or 1,2,3,4,5,6 or 7.")
def setAutoSectors(self, sectorList):
for sector in sectorList:
if not 0 <= sector <= 7:
raise ValueError('%i must between 0 and 7.' % sector)
self.autosectors = list(sectorList)
def transformPosition(self, pos):
pos = self.transformNWithoutCut(self.sector, pos)
cutpos = self.cutPosition(pos)
# -180 <= cutpos < 180, NOT the externally applied cuts
if len(self.autosectors) > 0:
if self.is_position_within_limits(cutpos):
return cutpos
else:
return self.autoTransformPositionBySector(cutpos)
if len(self.autotransforms) > 0:
if self.is_position_within_limits(cutpos):
return cutpos
else:
return self.autoTransformPositionByTransforms(pos)
#else
return cutpos
def transformNWithoutCut(self, n, pos):
if n == 0:
return P(pos.alpha, pos.delta, pos.gamma,
pos.omega, pos.chi, pos.phi)
if n == 1:
return P(pos.alpha, pos.delta, pos.gamma,
pos.omega - 180., -pos.chi, pos.phi - 180.)
if n == 2:
return P(pos.alpha, -pos.delta, pos.gamma,
-pos.omega, pos.chi - 180., pos.phi)
if n == 3:
return P(pos.alpha, -pos.delta, pos.gamma,
180. - pos.omega, 180. - pos.chi, pos.phi - 180.)
if n == 4:
return P(pos.alpha, pos.delta, pos.gamma,
-pos.omega, 180. - pos.chi, pos.phi - 180.)
if n == 5:
return P(pos.alpha, pos.delta, pos.gamma,
180. - pos.omega, pos.chi - 180., pos.phi)
if n == 6:
return P(pos.alpha, -pos.delta, pos.gamma,
pos.omega, -pos.chi, pos.phi - 180.)
if n == 7:
return P(pos.alpha, -pos.delta, pos.gamma,
pos.omega - 180., pos.chi, pos.phi)
else:
raise Exception("sector must be between 0 and 7")
### autosector
def hasAutoSectorsOrTransformsToApply(self):
return len(self.autosectors) > 0 or len(self.autotransforms) > 0
def autoTransformPositionBySector(self, pos):
okaysectors = []
okaypositions = []
for sector in self.autosectors:
newpos = self.transformNWithoutCut(sector, pos)
if self.is_position_within_limits(newpos):
okaysectors.append(sector)
okaypositions.append(newpos)
if len(okaysectors) == 0:
raise Exception(
"Autosector could not find a sector (from %s) to move %s into "
"limits." % (self.autosectors, str(pos)))
if len(okaysectors) > 1:
print ("WARNING: Autosector found multiple sectors that would "
"move %s to move into limits: %s" % (str(pos), okaysectors))
print ("INFO: Autosector changed sector from %i to %i" %
(self.sector, okaysectors[0]))
self.sector = okaysectors[0]
return okaypositions[0]
def autoTransformPositionByTransforms(self, pos):
possibleTransforms = self.createListOfPossibleTransforms()
okaytransforms = []
okaypositions = []
for transforms in possibleTransforms:
sector = sectorFromTransforms[tuple(transforms)]
newpos = self.cutPosition(self.transformNWithoutCut(sector, pos))
if self.is_position_within_limits(newpos):
okaytransforms.append(transforms)
okaypositions.append(newpos)
if len(okaytransforms) == 0:
raise Exception(
"Autosector could not find a sector (from %r) to move %r into "
"limits." % (self.autosectors, pos))
if len(okaytransforms) > 1:
print ("WARNING: Autosector found multiple sectors that would "
"move %s to move into limits: %s" %
(repr(pos), repr(okaytransforms)))
print ("INFO: Autosector changed selected transforms from %r to %r" %
(self.transforms, okaytransforms[0]))
self.setTransforms(okaytransforms[0])
return okaypositions[0]
def createListOfPossibleTransforms(self):
def vary(possibleTransforms, name):
result = []
for transforms in possibleTransforms:
# add the original.
result.append(transforms)
# add a modified one
toadd = list(copy(transforms))
if name in transforms:
toadd.remove(name)
else:
toadd.append(name)
toadd.sort()
result.append(toadd)
return result
# start with the currently selected list of transforms
if len(self.transforms) == 0:
possibleTransforms = [()]
else:
possibleTransforms = copy(self.transforms)
for name in self.autotransforms:
possibleTransforms = vary(possibleTransforms, name)
return possibleTransforms
def is_position_within_limits(self, pos):
'''where pos os a poistion object in degrees'''
return self.limitCheckerFunction(pos)
def __repr__(self):
def createPrefix(transform):
if transform in self.transforms:
s = '*on* '
else:
s = 'off '
if len(self.autotransforms) > 0:
if transform in self.autotransforms:
s += '*auto*'
else:
s += ' '
return s
s = 'Transforms/sector:\n'
s += (' %s (a transform) Invert scattering plane: invert delta and '
'omega and flip chi\n' % createPrefix('a'))
s += (' %s (b transform) Flip chi, and invert and flip omega\n' %
createPrefix('b'))
s += (' %s (c transform) Flip omega, invert chi and flip phi\n' %
createPrefix('c'))
s += ' Current sector: %i (Spec fourc equivalent)\n' % self.sector
if len(self.autosectors) > 0:
s += ' Auto sectors: %s\n' % self.autosectors
return s
def cutPosition(self, position):
'''Cuts angles at -180.; moves each argument between -180. and 180.
'''
def cut(a):
if a is None:
return None
else:
if a < (-180. - SMALL):
return a + 360.
if a > (180. + SMALL):
return a - 360.
return a
return P(cut(position.alpha), cut(position.delta), cut(position.gamma),
cut(position.omega), cut(position.chi), cut(position.phi))
def getNameFromScannableOrString(o):
try: # it may be a scannable
return o.getName()
except AttributeError:
return str(o)
class TransformCommands(object):
def __init__(self, sector_selector):
self._sectorSelector = sector_selector
@command
def transform(self):
"""transform -- show transform configuration"""
print self._sectorSelector.__repr__()
@command
def transforma(self, *args):
"""transforma {on|off|auto|manual} -- configure transform A application
"""
self._transform('transforma', 'a', args)
@command
def transformb(self, *args):
"""transformb {on|off|auto|manual} -- configure transform B application
"""
self._transform('transformb', 'b', args)
@command
def transformc(self, *args):
"""transformc {on|off|auto|manual} -- configure transform C application
"""
self._transform('transformc', 'c', args)
def _transform(self, commandName, transformName, args):
if len(args) == 0:
print self._sectorSelector.__repr__()
return
# get name
if len(args) != 1:
raise TypeError()
if type(args[0]) is not str:
raise TypeError()
ss = self._sectorSelector
if args[0] == 'on':
ss.addTransorm(transformName)
elif args[0] == 'off':
ss.removeTransorm(transformName)
elif args[0] == 'auto':
ss.addAutoTransorm(transformName)
elif args[0] == 'manual':
ss.removeAutoTransform(transformName)
else:
raise TypeError()
print self._sectorSelector.__repr__()
@command
def sector(self, sector=None):
"""sector {0-7} -- Select or display sector (a la Spec)
"""
if sector is None:
print self._sectorSelector.__repr__()
else:
if type(sector) is not int and not (0 <= sector <= 7):
raise TypeError()
self._sectorSelector.setSector(sector)
print self._sectorSelector.__repr__()
@command
def autosector(self, *args):
"""autosector [None] [0-7] [0-7]... -- Set sectors that might be automatically applied""" #@IgnorePep8
if len(args) == 0:
print self._sectorSelector.__repr__()
elif len(args) == 1 and args[0] is None:
self._sectorSelector.setAutoSectors([])
print self._sectorSelector.__repr__()
else:
sectorList = []
for arg in args:
if type(arg) is not int:
raise TypeError()
sectorList.append(arg)
self._sectorSelector.setAutoSectors(sectorList)
print self._sectorSelector.__repr__()

View File

@@ -0,0 +1,292 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from math import pi, asin, acos, atan2, sin, cos, sqrt
try:
from numpy import matrix
except ImportError:
from numjy import matrix
from diffcalc.log import logging
from diffcalc.util import bound, AbstractPosition, DiffcalcException,\
x_rotation, z_rotation
from diffcalc.hkl.vlieg.geometry import VliegGeometry
from diffcalc.ub.calc import PaperSpecificUbCalcStrategy
from diffcalc.hkl.calcbase import HklCalculatorBase
from diffcalc.hkl.common import DummyParameterManager
logger = logging.getLogger("diffcalc.hkl.willmot.calcwill")
CHOOSE_POSITIVE_GAMMA = True
TORAD = pi / 180
TODEG = 180 / pi
I = matrix('1 0 0; 0 1 0; 0 0 1')
SMALL = 1e-10
TEMPORARY_CONSTRAINTS_DICT_RAD = {'betain': 2 * TORAD}
def create_matrices(delta, gamma, omegah, phi):
return (calc_DELTA(delta), calc_GAMMA(gamma), calc_OMEGAH(omegah),
calc_PHI(phi))
def calc_DELTA(delta):
return x_rotation(delta) # (39)
def calc_GAMMA(gamma):
return z_rotation(gamma) # (40)
def calc_OMEGAH(omegah):
return x_rotation(omegah) # (41)
def calc_PHI(phi):
return z_rotation(phi) # (42)
def angles_to_hkl_phi(delta, gamma, omegah, phi):
"""Calculate hkl matrix in phi frame in units of 2*pi/lambda
"""
DELTA, GAMMA, OMEGAH, PHI = create_matrices(delta, gamma, omegah, phi)
H_lab = (GAMMA * DELTA - I) * matrix([[0], [1], [0]]) # (43)
H_phi = PHI.I * OMEGAH.I * H_lab # (44)
return H_phi
def angles_to_hkl(delta, gamma, omegah, phi, wavelength, UB):
"""Calculate hkl matrix in reprical lattice space in units of 1/Angstrom
"""
H_phi = angles_to_hkl_phi(delta, gamma, omegah, phi) * 2 * pi / wavelength
hkl = UB.I * H_phi # (5)
return hkl
class WillmottHorizontalPosition(AbstractPosition):
def __init__(self, delta=None, gamma=None, omegah=None, phi=None):
self.delta = delta
self.gamma = gamma
self.omegah = omegah
self.phi = phi
def clone(self):
return WillmottHorizontalPosition(self.delta, self.gamma, self.omegah,
self.phi)
def changeToRadians(self):
self.delta *= TORAD
self.gamma *= TORAD
self.omegah *= TORAD
self.phi *= TORAD
def changeToDegrees(self):
self.delta *= TODEG
self.gamma *= TODEG
self.omegah *= TODEG
self.phi *= TODEG
def totuple(self):
return (self.delta, self.gamma, self.omegah, self.phi)
def __str__(self):
return ('WillmottHorizontalPosition('
'delta: %.4f gamma: %.4f omegah: %.4f phi: %.4f)' %
(self.delta, self.gamma, self.omegah, self.phi))
class WillmottHorizontalGeometry(object):
def __init__(self):
self.name = 'willmott_horizontal'
def physical_angles_to_internal_position(self, physicalAngles):
return WillmottHorizontalPosition(*physicalAngles)
def internal_position_to_physical_angles(self, internalPosition):
return internalPosition.totuple()
def create_position(self, delta, gamma, omegah, phi):
return WillmottHorizontalPosition(delta, gamma, omegah, phi)
class WillmottHorizontalUbCalcStrategy(PaperSpecificUbCalcStrategy):
def calculate_q_phi(self, pos):
H_phi = angles_to_hkl_phi(*pos.totuple())
return matrix(H_phi.tolist())
class DummyConstraints(object):
@property
def reference(self):
"""dictionary of constrained reference circles"""
return TEMPORARY_CONSTRAINTS_DICT_RAD
class ConstraintAdapter(object):
def __init__(self, constraints):
self._constraints = constraints
def getParameterDict(self):
names = self._constraints.available
return dict(zip(names, [None] * len(names)))
def setParameter(self, name, value):
self._constraints.set_constraint(name, value)
def get(self, name):
if name in self._constraints.all:
val = self._constraints.get_value(name)
return 999 if val is None else val
else:
return 999
def update_tracked(self):
pass
class WillmottHorizontalCalculator(HklCalculatorBase):
def __init__(self, ubcalc, constraints,
raiseExceptionsIfAnglesDoNotMapBackToHkl=True):
""""
Where constraints.reference is a one element dict with the key either
('betain', 'betaout' or 'equal') and the value a number or None for
'betain_eq_betaout'
"""
HklCalculatorBase.__init__(self, ubcalc,
raiseExceptionsIfAnglesDoNotMapBackToHkl)
if constraints is not None:
self.constraints = constraints
self.parameter_manager = ConstraintAdapter(constraints)
else:
self.constraints = DummyConstraints()
self.parameter_manager = DummyParameterManager()
@property
def _UB(self):
return self._ubcalc.UB
def _anglesToHkl(self, pos, wavelength):
"""
Calculate miller indices from position in radians.
"""
hkl_matrix = angles_to_hkl(pos.delta, pos.gamma, pos.omegah, pos.phi,
wavelength, self._UB)
return hkl_matrix[0, 0], hkl_matrix[1, 0], hkl_matrix[2, 0],
def _anglesToVirtualAngles(self, pos, wavelength):
"""
Calculate virtual-angles in radians from position in radians.
Return theta, alpha, and beta in a dictionary.
"""
betain = pos.omegah # (52)
hkl = angles_to_hkl(pos.delta, pos.gamma, pos.omegah, pos.phi,
wavelength, self._UB)
H_phi = self._UB * hkl
H_phi = H_phi / (2 * pi / wavelength)
l_phi = H_phi[2, 0]
sin_betaout = l_phi - sin(betain)
betaout = asin(bound(sin_betaout)) # (54)
cos_2theta = cos(pos.delta) * cos(pos.gamma)
theta = acos(bound(cos_2theta)) / 2.
return {'theta': theta, 'betain': betain, 'betaout': betaout}
def _hklToAngles(self, h, k, l, wavelength):
"""
Calculate position and virtual angles in radians for a given hkl.
"""
H_phi = self._UB * matrix([[h], [k], [l]]) # units: 1/Angstrom
H_phi = H_phi / (2 * pi / wavelength) # units: 2*pi/wavelength
h_phi = H_phi[0, 0]
k_phi = H_phi[1, 0]
l_phi = H_phi[2, 0] # (5)
### determine betain (omegah) and betaout ###
if not self.constraints.reference:
raise ValueError("No reference constraint has been constrained.")
ref_name, ref_value = self.constraints.reference.items()[0]
if ref_value is not None:
ref_value *= TORAD
if ref_name == 'betain':
betain = ref_value
betaout = asin(bound(l_phi - sin(betain))) # (53)
elif ref_name == 'betaout':
betaout = ref_value
betain = asin(bound(l_phi - sin(betaout))) # (54)
elif ref_name == 'bin_eq_bout':
betain = betaout = asin(bound(l_phi / 2)) # (55)
else:
raise ValueError("Unexpected constraint name'%s'." % ref_name)
if abs(betain) < SMALL:
raise DiffcalcException('required betain was 0 degrees (requested '
'q is perpendicular to surface normal)')
if betain < -SMALL:
raise DiffcalcException("betain was -ve (%.4f)" % betain)
# logger.info('betain = %.4f, betaout = %.4f',
# betain * TODEG, betaout * TODEG)
omegah = betain # (52)
### determine H_lab (X, Y and Z) ###
Y = -(h_phi ** 2 + k_phi ** 2 + l_phi ** 2) / 2 # (45)
Z = (sin(betaout) + sin(betain) * (Y + 1)) / cos(omegah) # (47)
X_squared = (h_phi ** 2 + k_phi ** 2 -
((cos(betain) * Y + sin(betain) * Z) ** 2)) # (48)
if (X_squared < 0) and (abs(X_squared) < SMALL):
X_squared = 0
Xpositive = sqrt(X_squared)
if CHOOSE_POSITIVE_GAMMA:
X = -Xpositive
else:
X = Xpositive
# logger.info('H_lab (X,Y,Z) = [%.4f, %.4f, %.4f]', X, Y, Z)
### determine diffractometer angles ###
gamma = atan2(-X, Y + 1) # (49)
if (abs(gamma) < SMALL):
# degenerate case, only occurs when q || z
delta = 2 * omegah
else:
delta = atan2(Z * sin(gamma), -X) # (50)
M = cos(betain) * Y + sin(betain) * Z
phi = atan2(h_phi * M - k_phi * X, h_phi * X + k_phi * M) # (51)
pos = WillmottHorizontalPosition(delta, gamma, omegah, phi)
virtual_angles = {'betain': betain, 'betaout': betaout}
return pos, virtual_angles

View File

@@ -0,0 +1,58 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from diffcalc.hkl.common import getNameFromScannableOrString
from diffcalc.util import command
class WillmottHklCommands(object):
def __init__(self, hklcalc):
self._hklcalc = hklcalc
self.commands = [self.con,
self.uncon,
self.cons]
def __str__(self):
return self._hklcalc.__str__()
@command
def con(self, scn_or_string):
"""con <constraint> -- constrains constraint
"""
name = getNameFromScannableOrString(scn_or_string)
self._hklcalc.constraints.constrain(name)
print self._report_constraints()
@command
def uncon(self, scn_or_string):
"""uncon <constraint> -- unconstrains constraint
"""
name = getNameFromScannableOrString(scn_or_string)
self._hklcalc.constraints.unconstrain(name)
print self._report_constraints()
@command
def cons(self):
"""cons -- list available constraints and values
"""
print self._report_constraints()
def _report_constraints(self):
return (self._hklcalc.constraints.build_display_table_lines() + '\n\n' +
self._hklcalc.constraints._report_constraints())

View File

@@ -0,0 +1,156 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from diffcalc.util import DiffcalcException
def filter_dict(d, keys):
"""Return a copy of d containing only keys that are in keys"""
##return {k: d[k] for k in keys} # requires Python 2.6
return dict((k, d[k]) for k in keys if k in d.keys())
ref_constraints = ('betain', 'betaout', 'bin_eq_bout')
valueless_constraints = ('bin_eq_bout')
all_constraints = ref_constraints
class WillmottConstraintManager(object):
"""Constraints in degrees.
"""
def __init__(self):
self._constrained = {'bin_eq_bout': None}
@property
def available_constraint_names(self):
"""list of all available constraints"""
return all_constraints
@property
def all(self): # @ReservedAssignment
"""dictionary of all constrained values"""
return self._constrained.copy()
@property
def reference(self):
"""dictionary of constrained reference circles"""
return filter_dict(self.all, ref_constraints)
@property
def constrained_names(self):
"""ordered tuple of constained circles"""
names = self.all.keys()
names.sort(key=lambda name: list(all_constraints).index(name))
return tuple(names)
def is_constrained(self, name):
return name in self._constrained
def get_value(self, name):
return self._constrained[name]
def _build_display_table(self):
constraint_types = (ref_constraints,)
num_rows = max([len(col) for col in constraint_types])
max_name_width = max(
[len(name) for name in sum(constraint_types[:2], ())])
# headings
lines = [' ' + 'REF'.ljust(max_name_width)]
lines.append(' ' + '=' * max_name_width + ' ')
# constraint rows
for n_row in range(num_rows):
cells = []
for col in constraint_types:
name = col[n_row] if n_row < len(col) else ''
cells.append(self._label_constraint(name))
cells.append(('%-' + str(max_name_width) + 's ') % name)
lines.append(''.join(cells))
lines.append
return '\n'.join(lines)
def _report_constraints(self):
if not self.reference:
return "!!! No reference constraint set"
name, val = self.reference.items()[0]
if name in valueless_constraints:
return " %s" % name
else:
if val is None:
return "!!! %s: ---" % name
else:
return " %s: %.4f" % (name, val)
def _label_constraint(self, name):
if name == '':
label = ' '
elif (self.is_constrained(name) and (self.get_value(name) is None) and
name not in valueless_constraints):
label = 'o-> '
elif self.is_constrained(name):
label = '--> '
else:
label = ' '
return label
def constrain(self, name):
if name in self.all:
return "%s is already constrained." % name.capitalize()
elif name in ref_constraints:
return self._constrain_reference(name)
else:
raise DiffcalcException('%s is not a valid constraint name')
def _constrain_reference(self, name):
if self.reference:
constrained_name = self.reference.keys()[0]
del self._constrained[constrained_name]
self._constrained[name] = None
return '%s constraint replaced.' % constrained_name.capitalize()
else:
self._constrained[name] = None
def unconstrain(self, name):
if name in self._constrained:
del self._constrained[name]
else:
return "%s was not already constrained." % name.capitalize()
###
def _check_constraint_settable(self, name, verb):
if name not in all_constraints:
raise DiffcalcException(
'Could not %(verb)s %(name)s as this is not an available '
'constraint.' % locals())
elif name not in self.all.keys():
raise DiffcalcException(
'Could not %(verb)s %(name)s as this is not currently '
'constrained.' % locals())
elif name in valueless_constraints:
raise DiffcalcException(
'Could not %(verb)s %(name)s as this constraint takes no '
'value.' % locals())
def set_constraint(self, name, value): # @ReservedAssignment
self._check_constraint_settable(name, 'set')
old_value = self.all[name]
old = str(old_value) if old_value is not None else '---'
self._constrained[name] = float(value)
new = str(value)
return "%(name)s : %(old)s --> %(new)s" % locals()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,418 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from math import pi
from diffcalc import settings
try:
from numpy import matrix
except ImportError:
from numjy import matrix
from diffcalc.util import DiffcalcException, bold
TODEG = 180 / pi
TORAD = pi / 180
NUNAME = 'gam'
def filter_dict(d, keys):
"""Return a copy of d containing only keys that are in keys"""
##return {k: d[k] for k in keys} # requires Python 2.6
return dict((k, d[k]) for k in keys if k in d.keys())
det_constraints = ('delta', NUNAME, 'qaz', 'naz')
ref_constraints = ('a_eq_b', 'alpha', 'beta', 'psi')
samp_constraints = ('mu', 'eta', 'chi', 'phi', 'mu_is_' + NUNAME, 'bisect', 'omega')
valueless_constraints = ('a_eq_b', 'mu_is_' + NUNAME, 'bisect')
all_constraints = det_constraints + ref_constraints + samp_constraints
number_single_sample = (len(det_constraints) * len(ref_constraints) *
len(samp_constraints))
class YouConstraintManager(object):
def __init__(self, fixed_constraints = {}):
self._constrained = {}
# self._tracking = []
self.n_phi = matrix([[0], [0], [1]])
self._hide_detector_constraint = False # default
self._fixed_samp_constraints = ()
self._fix_constraints(fixed_constraints)
def __str__(self):
lines = []
# TODO: Put somewhere with access to UB matrix!
# WIDTH = 13
# n_phi = self.n_phi
# fmt = "% 9.5f % 9.5f % 9.5f"
# lines.append(" n_phi:".ljust(WIDTH) +
# fmt % (n_phi[0, 0], n_phi[1, 0], n_phi[2, 0]))
# if self._getUBMatrix():
# n_cryst = self._getUMatrix().I * self.n_phi
# lines.append(" n_cryst:".ljust(WIDTH) +
# fmt % (n_cryst[0, 0], n_cryst[1, 0], n_cryst[2, 0]))
# n_recip = self._getUBMatrix().I * self.n_phi
# lines.append(" n_recip:".ljust(WIDTH) +
# fmt % (n_recip[0, 0], n_recip[1, 0], n_recip[2, 0]))
# else:
# lines.append(
# " n_cryst:".ljust(WIDTH) + ' "<<< No U matrix >>>"')
# lines.append(
# " n_recip:".ljust(WIDTH) + ' "<<< No UB matrix >>>"')
lines.extend(self.build_display_table_lines())
lines.append("")
lines.extend(self.report_constraints_lines())
lines.append("")
if (self.is_fully_constrained() and
not self.is_current_mode_implemented()):
lines.append(
" Sorry, this constraint combination is not implemented")
lines.append(" Type 'help con' for available combinations")
else:
lines.append(" Type 'help con' for instructions") # okay
return '\n'.join(lines)
@property
def available_constraint_names(self):
"""list of all available constraints"""
return all_constraints
@property
def settable_constraint_names(self):
"""list of all available constraints that have settable values"""
all_copy = list(all_constraints)
for valueless in valueless_constraints:
all_copy.remove(valueless)
return all_copy
@property
def all(self): # @ReservedAssignment
"""dictionary of all constrained values"""
return self._constrained.copy()
@property
def detector(self):
"""dictionary of constrained detector circles"""
return filter_dict(self.all, det_constraints[:-1])
@property
def reference(self):
"""dictionary of constrained reference circles"""
return filter_dict(self.all, ref_constraints)
@property
def sample(self):
"""dictionary of constrained sample circles"""
return filter_dict(self.all, samp_constraints)
@property
def naz(self):
"""dictionary with naz and value if constrained"""
return filter_dict(self.all, ('naz',))
@property
def constrained_names(self):
"""ordered tuple of constained circles"""
names = self.all.keys()
names.sort(key=lambda name: list(all_constraints).index(name))
return tuple(names)
@property
def available_names(self):
"""ordered tuple of fixed circles"""
names = [name for name in self.all.keys() if not self.is_constraint_fixed(name)]
names.sort(key=lambda name: list(all_constraints).index(name))
return tuple(names)
def _fix_constraints(self, fixed_constraints):
for name in fixed_constraints:
self.constrain(name)
self.set_constraint(name, fixed_constraints[name])
if self.detector or self.naz:
self._hide_detector_constraint = True
fixed_samp_constraints = list(self.sample.keys())
if 'mu' in self.sample or NUNAME in self.detector:
fixed_samp_constraints.append('mu_is_' + NUNAME)
self._fixed_samp_constraints = tuple(fixed_samp_constraints)
def is_constrained(self, name):
return name in self._constrained
def get_value(self, name):
return self._constrained[name]
def build_display_table_lines(self):
unfixed_samp_constraints = list(samp_constraints)
for name in self._fixed_samp_constraints:
unfixed_samp_constraints.remove(name)
if self._hide_detector_constraint:
constraint_types = (ref_constraints, unfixed_samp_constraints)
else:
constraint_types = (det_constraints, ref_constraints,
unfixed_samp_constraints)
num_rows = max([len(col) for col in constraint_types])
max_name_width = max(
[len(name) for name in sum(constraint_types[:-1], ())])
cells = []
header_cells = []
if not self._hide_detector_constraint:
header_cells.append(bold(' ' + 'DET'.ljust(max_name_width)))
header_cells.append(bold(' ' + 'REF'.ljust(max_name_width)))
header_cells.append(bold(' ' + 'SAMP'))
cells.append(header_cells)
underline_cells = [' ' + '-' * max_name_width] * len(constraint_types)
cells.append(underline_cells)
for n_row in range(num_rows):
row_cells = []
for col in constraint_types:
name = col[n_row] if n_row < len(col) else ''
row_cells.append(self._label_constraint(name))
ext_name = settings.geometry.map_to_external_name(name)
row_cells.append(('%-' + str(max_name_width) + 's') % ext_name)
cells.append(row_cells)
lines = [' '.join(row_cells).rstrip() for row_cells in cells]
return lines
def _report_constraint(self, name):
val = self.get_constraint(name)
ext_name = settings.geometry.map_to_external_name(name)
if name in valueless_constraints:
return " %s" % ext_name
else:
if val is None:
return "! %-5s: ---" % ext_name
else:
ext_name, ext_val = settings.geometry.map_to_external_position(name, val)
return " %-5s: %.4f" % (ext_name, ext_val)
def report_constraints_lines(self):
lines = []
required = 3 - len(self.all)
if required == 0:
pass
elif required == 1:
lines.append('! 1 more constraint required')
else:
lines.append('! %d more constraints required' % required)
lines.extend([self._report_constraint(name) for name in self.available_names])
return lines
def is_fully_constrained(self):
return len(self.all) == 3
def is_current_mode_implemented(self):
if not self.is_fully_constrained():
raise ValueError("Three constraints required")
if len(self.sample) == 3:
if set(self.sample.keys()) == set(['chi', 'phi', 'eta']):
return True
return False
if len(self.sample) == 1:
return ('omega' not in set(self.sample.keys()) and
'bisect' not in set(self.sample.keys()))
if len(self.reference) == 1:
return (set(self.sample.keys()) == set(['chi', 'phi']) or
set(self.sample.keys()) == set(['chi', 'eta']) or
set(self.sample.keys()) == set(['chi', 'mu']) or
set(self.sample.keys()) == set(['mu', 'eta']))
if len(self.detector) == 1:
return (set(self.sample.keys()) == set(['chi', 'phi']) or
set(self.sample.keys()) == set(['mu', 'eta']) or
set(self.sample.keys()) == set(['mu', 'phi']) or
set(self.sample.keys()) == set(['eta', 'phi']) or
set(self.sample.keys()) == set(['mu', 'bisect']) or
set(self.sample.keys()) == set(['eta', 'bisect']) or
set(self.sample.keys()) == set(['omega', 'bisect']))
return False
def _label_constraint(self, name):
if name == '':
label = ' '
# elif self.is_tracking(name): # implies constrained
# label = '~~> '
elif (self.is_constrained(name) and (self.get_value(name) is None) and
name not in valueless_constraints):
label = 'o->'
elif self.is_constrained(name):
label = '-->'
else:
label = ' '
return label
def constrain(self, name):
ext_name = settings.geometry.map_to_external_name(name)
if self.is_constraint_fixed(name):
raise DiffcalcException('%s constraint cannot be changed' % ext_name)
if name in self.all:
return "%s is already constrained." % ext_name.capitalize()
elif name in det_constraints:
return self._constrain_detector(name)
elif name in ref_constraints:
return self._constrain_reference(name)
elif name in samp_constraints:
return self._constrain_sample(name)
else:
raise DiffcalcException("%s is not a valid constraint name. Type 'con' for a table of constraint name" % ext_name)
def is_constraint_fixed(self, name):
return ((name in det_constraints and self._hide_detector_constraint) or
(name in samp_constraints and name in self._fixed_samp_constraints))
def _constrain_detector(self, name):
if self.naz:
del self._constrained['naz']
self._constrained[name] = None
return 'Naz constraint replaced.'
elif self.detector:
constrained_name = self.detector.keys()[0]
del self._constrained[constrained_name]
self._constrained[name] = None
return'%s constraint replaced.' % constrained_name.capitalize()
elif len(self.all) == 3: # and no detector
raise self._could_not_constrain_exception(name)
else:
self._constrained[name] = None
def _could_not_constrain_exception(self, name):
ext_name = settings.geometry.map_to_external_name(name)
names = [settings.geometry.map_to_external_name(nm) for nm in self.available_names]
if len(names) > 1:
names_fmt = 'one of the\nangles ' + ', '.join(names[:-1]) + ' or ' + names[-1]
else:
names_fmt = 'angle ' + names[0]
return DiffcalcException(
"%s could not be constrained. First un-constrain %s "
"(with 'uncon')" %
(ext_name.capitalize(), names_fmt))
def _constrain_reference(self, name):
if self.reference:
constrained_name = self.reference.keys()[0]
elif len(self._constrained) < 3:
constrained_name = None
elif len(self.available_names) == 1:
constrained_name = self.available_names[0]
else:
raise self._could_not_constrain_exception(name)
try:
del self._constrained[constrained_name]
self._constrained[name] = None
ext_constrained_name = settings.geometry.map_to_external_name(constrained_name)
return '%s constraint replaced.' % ext_constrained_name.capitalize()
except KeyError:
self._constrained[name] = None
def _constrain_sample(self, name):
if len(self._constrained) < 3:
constrained_name = None
elif len(self.available_names) == 1:
# Only one settable constraint
constrained_name = self.available_names[0]
elif len(self.sample) == 1:
# (detector and reference constraints set)
# it is clear which sample constraint to remove
constrained_name = self.sample.keys()[0]
if self.is_constraint_fixed(constrained_name):
raise self._could_not_constrain_exception(name)
else:
# else: three constraints are set
raise self._could_not_constrain_exception(name)
try:
del self._constrained[constrained_name]
self._constrained[name] = None
ext_constrained_name = settings.geometry.map_to_external_name(constrained_name)
return '%s constraint replaced.' % ext_constrained_name.capitalize()
except KeyError:
self._constrained[name] = None
def unconstrain(self, name):
ext_name = settings.geometry.map_to_external_name(name)
if self.is_constraint_fixed(name):
raise DiffcalcException('%s constraint cannot be removed' % ext_name)
if name in self._constrained:
del self._constrained[name]
else:
return "%s was not already constrained." % ext_name.capitalize()
def _check_constraint_settable(self, name):
ext_name = settings.geometry.map_to_external_name(name)
if name not in all_constraints:
raise DiffcalcException(
'Could not set %(ext_name)s. This is not an available '
'constraint.' % locals())
elif name not in self.all.keys():
raise DiffcalcException(
'Could not set %(ext_name)s. This is not currently '
'constrained.' % locals())
elif name in valueless_constraints:
raise DiffcalcException(
'Could not set %(ext_name)s. This constraint takes no '
'value.' % locals())
def clear_constraints(self):
self._constrained = {}
def set_constraint(self, name, value): # @ReservedAssignment
ext_name = settings.geometry.map_to_external_name(name)
if self.is_constraint_fixed(name):
raise DiffcalcException('%s constraint cannot be changed' % ext_name)
self._check_constraint_settable(name)
# if name in self._tracking:
# raise DiffcalcException(
# "Could not set %s as this constraint is configured to track "
# "its associated\nphysical angle. First remove this tracking "
# "(use 'untrack %s').""" % (name, name))
old_value = self.get_constraint(name)
try:
old_str = '---' if old_value is None else str(old_value)
except Exception:
old_str = '---'
try:
self._constrained[name] = float(value) * TORAD
except Exception:
raise DiffcalcException('Cannot set %s constraint. Invalid input value.' % ext_name)
try:
new_str = '---' if value is None else str(value)
except Exception:
new_str = '---'
return "%s : %s --> %s" % (name, old_str, new_str)
def get_constraint(self, name):
value = self.all[name]
return None if value is None else value * TODEG

View File

@@ -0,0 +1,311 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from math import pi
from diffcalc.util import AbstractPosition, DiffcalcException
from diffcalc import settings
TORAD = pi / 180
TODEG = 180 / pi
from diffcalc.util import x_rotation, z_rotation, y_rotation
from diffcalc.hkl.you.constraints import NUNAME
class YouGeometry(object):
def __init__(self, name, fixed_constraints, beamline_axes_transform=None):
self.name = name
self.fixed_constraints = fixed_constraints
# beamline_axes_transform matrix is composed of columns of the
# beamline basis vector coordinates in the diffcalc coordinate system,
# i.e. it transforms the beamline coordinate system into the diffcalc one.
self.beamline_axes_transform = beamline_axes_transform
def map_to_internal_position(self, name, value):
return name, value
def map_to_external_position(self, name, value):
return name, value
def map_to_internal_name(self, name):
return name
def map_to_external_name(self, name):
return name
def physical_angles_to_internal_position(self, physical_angle_tuple):
raise NotImplementedError()
def internal_position_to_physical_angles(self, internal_position):
raise NotImplementedError()
def create_position(self, *args):
return YouPosition(*args, unit='DEG')
class YouRemappedGeometry(YouGeometry):
"""For a diffractometer with angles:
delta, eta, chi, phi
"""
def __init__(self, name, fixed_constraints, beamline_axes_transform=None):
YouGeometry.__init__(self, name, fixed_constraints, beamline_axes_transform)
# Order should match scannable order in _fourc group for mapping to work correctly
self._scn_mapping_to_int = ()
self._scn_mapping_to_ext = ()
def map_to_internal_name(self, name):
scn_names = settings.hardware.diffhw.getInputNames()
try:
idx_name = scn_names.index(name)
you_name, _ = self._scn_mapping_to_int[idx_name]
return you_name
except ValueError:
return name
def map_to_external_name(self, name):
scn_names = settings.hardware.diffhw.getInputNames()
for idx, (you_name, _) in enumerate(self._scn_mapping_to_ext):
if you_name == name:
return scn_names[idx]
return name
def map_to_internal_position(self, name, value):
scn_names = settings.hardware.diffhw.getInputNames()
try:
idx_name = scn_names.index(name)
except ValueError:
return name, value
new_name, op = self._scn_mapping_to_int[idx_name]
try:
return new_name, op(value)
except TypeError:
return new_name, None
def map_to_external_position(self, name, value):
try:
(idx, _, op), = tuple((i, nm, o) for i, (nm, o) in enumerate(self._scn_mapping_to_ext) if nm == name)
except ValueError:
return name, value
scn_names = settings.hardware.diffhw.getInputNames()
try:
ext_name = scn_names[idx]
except ValueError:
return name, value
try:
return ext_name, op(value)
except TypeError:
return ext_name, None
def physical_angles_to_internal_position(self, physical_angle_tuple):
you_angles = {}
scn_names = settings.hardware.diffhw.getInputNames()
for scn_name, phys_angle in zip(scn_names, physical_angle_tuple):
name, val = self.map_to_internal_position(scn_name, phys_angle)
you_angles[name] = val
you_angles.update(self.fixed_constraints)
angle_values = tuple(you_angles[name] for name in YouPosition.get_names())
return YouPosition(*angle_values, unit='DEG')
def internal_position_to_physical_angles(self, internal_position):
clone_position = internal_position.clone()
clone_position.changeToDegrees()
you_angles = clone_position.todict()
res = []
for name, _ in self._scn_mapping_to_ext:
_, val = self.map_to_external_position(name, you_angles[name])
res.append(val)
return tuple(res)
#==============================================================================
#==============================================================================
# Geometry plugins for use with 'You' hkl calculation engine
#==============================================================================
#==============================================================================
class SixCircle(YouGeometry):
def __init__(self, beamline_axes_transform=None):
YouGeometry.__init__(self, 'sixc', {}, beamline_axes_transform)
def physical_angles_to_internal_position(self, physical_angle_tuple):
# mu, delta, nu, eta, chi, phi
return YouPosition(*physical_angle_tuple, unit='DEG')
def internal_position_to_physical_angles(self, internal_position):
clone_position = internal_position.clone()
clone_position.changeToDegrees()
return clone_position.totuple()
class FourCircle(YouGeometry):
"""For a diffractometer with angles:
delta, eta, chi, phi
"""
def __init__(self, beamline_axes_transform=None):
YouGeometry.__init__(self, 'fourc', {'mu': 0, NUNAME: 0}, beamline_axes_transform)
def physical_angles_to_internal_position(self, physical_angle_tuple):
# mu, delta, nu, eta, chi, phi
delta, eta, chi, phi = physical_angle_tuple
return YouPosition(0, delta, 0, eta, chi, phi, 'DEG')
def internal_position_to_physical_angles(self, internal_position):
clone_position = internal_position.clone()
clone_position.changeToDegrees()
_, delta, _, eta, chi, phi = clone_position.totuple()
return delta, eta, chi, phi
class FiveCircle(YouGeometry):
"""For a diffractometer with angles:
delta, nu, eta, chi, phi
"""
def __init__(self, beamline_axes_transform=None):
YouGeometry.__init__(self, 'fivec', {'mu': 0}, beamline_axes_transform)
def physical_angles_to_internal_position(self, physical_angle_tuple):
# mu, delta, nu, eta, chi, phi
delta, nu, eta, chi, phi = physical_angle_tuple
return YouPosition(0, delta, nu, eta, chi, phi, 'DEG')
def internal_position_to_physical_angles(self, internal_position):
clone_position = internal_position.clone()
clone_position.changeToDegrees()
_, delta, nu, eta, chi, phi = clone_position.totuple()
return delta, nu, eta, chi, phi
#==============================================================================
def create_you_matrices(mu=None, delta=None, nu=None, eta=None, chi=None,
phi=None):
"""
Create transformation matrices from H. You's paper.
"""
MU = None if mu is None else calcMU(mu)
DELTA = None if delta is None else calcDELTA(delta)
NU = None if nu is None else calcNU(nu)
ETA = None if eta is None else calcETA(eta)
CHI = None if chi is None else calcCHI(chi)
PHI = None if phi is None else calcPHI(phi)
return MU, DELTA, NU, ETA, CHI, PHI
def calcNU(nu):
return x_rotation(nu)
def calcDELTA(delta):
return z_rotation(-delta)
def calcMU(mu_or_alpha):
return x_rotation(mu_or_alpha)
def calcETA(eta):
return z_rotation(-eta)
def calcCHI(chi):
return y_rotation(chi)
def calcPHI(phi):
return z_rotation(-phi)
class YouPosition(AbstractPosition):
def __init__(self, mu, delta, nu, eta, chi, phi, unit):
self.mu = mu
self.delta = delta
self.nu = nu
self.eta = eta
self.chi = chi
self.phi = phi
if unit not in ['DEG', 'RAD']:
raise DiffcalcException("Invalid angle unit value %s." % str(unit))
else:
self.unit = unit
def clone(self):
return YouPosition(self.mu, self.delta, self.nu, self.eta, self.chi,
self.phi, self.unit)
def changeToRadians(self):
if self.unit == 'DEG':
self.mu *= TORAD
self.delta *= TORAD
self.nu *= TORAD
self.eta *= TORAD
self.chi *= TORAD
self.phi *= TORAD
self.unit = 'RAD'
elif self.unit == 'RAD':
return
else:
raise DiffcalcException("Invalid angle unit value %s." % str(self.unit))
def changeToDegrees(self):
if self.unit == 'RAD':
self.mu *= TODEG
self.delta *= TODEG
self.nu *= TODEG
self.eta *= TODEG
self.chi *= TODEG
self.phi *= TODEG
self.unit = 'DEG'
elif self.unit == 'DEG':
return
else:
raise DiffcalcException("Invalid angle unit value %s." % str(self.unit))
@staticmethod
def get_names():
return ('mu', 'delta', NUNAME, 'eta', 'chi', 'phi')
def totuple(self):
return (self.mu, self.delta, self.nu, self.eta, self.chi, self.phi)
def todict(self):
return dict(zip(self.get_names(), self.totuple()))
def __str__(self):
fmt_tuple = sum(zip(self.get_names(), self.totuple()), ()) + (self.unit,)
return ("YouPosition(%s: %r %s: %r %s: %r %s: %r %s: %r %s: %r) in %s"
% fmt_tuple)
def __eq__(self, other):
return self.totuple() == other.totuple()
class WillmottHorizontalPosition(YouPosition):
def __init__(self, delta=None, gamma=None, omegah=None, phi=None):
self.mu = 0
self.delta = delta
self.nu = gamma
self.eta = omegah
self.chi = -90
self.phi = phi
self.unit= 'DEG'

View File

@@ -0,0 +1,197 @@
###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from __future__ import absolute_import
from diffcalc.hkl.common import getNameFromScannableOrString
from diffcalc.util import command
from diffcalc.hkl.you.calc import YouHklCalculator
from diffcalc import settings
import diffcalc.ub.ub
from diffcalc.hkl.you.constraints import YouConstraintManager
__all__ = ['allhkl', 'con', 'uncon', 'hklcalc', 'constraint_manager']
_fixed_constraints = settings.geometry.fixed_constraints # @UndefinedVariable
constraint_manager = YouConstraintManager(_fixed_constraints)
hklcalc = YouHklCalculator(diffcalc.ub.ub.ubcalc, constraint_manager)
def __str__(self):
return hklcalc.__str__()
@command
def con(*args):
"""
con -- list available constraints and values
con <name> {val} -- constrains and optionally sets one constraint
con <name> {val} <name> {val} <name> {val} -- clears and then fully constrains
Select three constraints using 'con' and 'uncon'. Choose up to one
from each of the sample and detector columns and up to three from
the sample column.
Not all constraint combinations are currently available:
1 x samp: all
2 x samp and 1 x ref: chi & phi
chi & eta
chi & mu
mu & eta
2 x samp and 1 x det: chi & phi
mu & eta
mu & phi
eta & phi
bisect & mu
bisect & eta
bisect & omega
3 x samp: eta, chi & phi
See also 'uncon'
"""
args = list(args)
msg = _handle_con(args)
if (hklcalc.constraints.is_fully_constrained() and
not hklcalc.constraints.is_current_mode_implemented()):
msg += ("\n\nWARNING: The selected constraint combination "
"is not implemented.\n\nType 'help con' to see implemented combinations")
if msg:
print msg
diffcalc.ub.ub.ubcalc.save()
def _handle_con(args):
if not args:
return hklcalc.constraints.__str__()
if len(args) > 6:
raise TypeError("Unexpected args: " + str(args))
cons_value_pairs = []
while args:
scn_or_str = args.pop(0)
ext_name = getNameFromScannableOrString(scn_or_str)
if args and isinstance(args[0], (int, long, float)):
ext_value = args.pop(0)
else:
try:
ext_value = settings.hardware.get_position_by_name(ext_name)
except ValueError:
ext_value = None
cons_name, cons_value = settings.geometry.map_to_internal_position(ext_name, ext_value)
cons_value_pairs.append((cons_name, cons_value))
if len(cons_value_pairs) == 1:
pass
elif len(cons_value_pairs) == 3:
hklcalc.constraints.clear_constraints()
else:
raise TypeError("Either one or three constraints must be specified")
for name, value in cons_value_pairs:
hklcalc.constraints.constrain(name)
if value is not None:
hklcalc.constraints.set_constraint(name, value)
return '\n'.join(hklcalc.constraints.report_constraints_lines())
@command
def uncon(scn_or_string):
"""uncon <name> -- remove constraint
See also 'con'
"""
ext_name = getNameFromScannableOrString(scn_or_string)
cons_name = settings.geometry.map_to_internal_name(ext_name)
hklcalc.constraints.unconstrain(cons_name)
print '\n'.join(hklcalc.constraints.report_constraints_lines())
diffcalc.ub.ub.ubcalc.save()
@command
def allhkl(hkl, wavelength=None):
"""allhkl [h k l] -- print all hkl solutions ignoring limits
"""
_hardware = settings.hardware
_geometry = settings.geometry
if wavelength is None:
wavelength = _hardware.get_wavelength()
h, k, l = hkl
pos_virtual_angles_pairs = hklcalc.hkl_to_all_angles(
h, k, l, wavelength)
cells = []
# virtual_angle_names = list(pos_virtual_angles_pairs[0][1].keys())
# virtual_angle_names.sort()
virtual_angle_names = ['qaz', 'psi', 'naz', 'tau', 'theta', 'alpha', 'beta', 'bin', 'bout']
header_cells = list(_hardware.get_axes_names()) + [' '] + virtual_angle_names
cells.append(['%9s' % s for s in header_cells])
cells.append([''] * len(header_cells))
for pos, virtual_angles in pos_virtual_angles_pairs:
row_cells = []
angle_tuple = _geometry.internal_position_to_physical_angles(pos)
angle_tuple = _hardware.cut_angles(angle_tuple)
for val in angle_tuple:
row_cells.append('%9.4f' % val)
row_cells.append('|')
for name in virtual_angle_names:
row_cells.append('%9.4f' % virtual_angles[name])
cells.append(row_cells)
column_widths = []
for col in range(len(cells[0])):
widths = []
for row in range(len(cells)):
cell = cells[row][col]
width = len(cell.strip())
widths.append(width)
column_widths.append(max(widths))
lines = []
for row_cells in cells:
trimmed_row_cells = []
for cell, width in zip(row_cells, column_widths):
trimmed_cell = cell.strip().rjust(width)
trimmed_row_cells.append(trimmed_cell)
lines.append(' '.join(trimmed_row_cells))
print '\n'.join(lines)
commands_for_help = ['Constraints',
con,
uncon,
'Hkl',
allhkl
]

View File

@@ -0,0 +1,52 @@
###
# Copyright 2008-2018 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from diffcalc.ub.calcstate import UBCalcStateEncoder, UBCalcState
from diffcalc.util import DiffcalcException, TODEG
class YouStateEncoder(UBCalcStateEncoder):
def default(self, obj):
if isinstance(obj, UBCalcState):
d = UBCalcStateEncoder.default(self, obj)
from diffcalc.hkl.you.hkl import constraint_manager
d['constraints'] = constraint_manager.all
return d
return UBCalcStateEncoder.default(self, obj)
@staticmethod
def decode_ubcalcstate(state, geometry, diffractometer_axes_names, multiplier):
# Backwards compatibility code
try:
cons_dict = state['constraints']
from diffcalc.hkl.you.hkl import constraint_manager
for cons_name, val in cons_dict.iteritems():
try:
constraint_manager.constrain(cons_name)
if val is not None:
constraint_manager.set_constraint(cons_name, val * TODEG)
except DiffcalcException:
print 'WARNING: Ignoring constraint %s' % cons_name
except KeyError:
pass
return UBCalcStateEncoder.decode_ubcalcstate(state, geometry, diffractometer_axes_names, multiplier)