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

@@ -0,0 +1,637 @@
###
# 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.ub.calcstate import decode_ubcalcstate
from diffcalc.ub.calcstate import UBCalcState
from diffcalc.ub.crystal import CrystalUnderTest
from diffcalc.ub.reflections import ReflectionList
from diffcalc.ub.persistence import UBCalculationJSONPersister, UBCalculationPersister
from diffcalc.util import DiffcalcException, cross3, dot3, bold
from math import acos, cos, sin, pi
from diffcalc.ub.reference import YouReference
try:
from numpy import matrix, hstack
from numpy.linalg import norm
except ImportError:
from numjy import matrix, hstack
from numjy.linalg import norm
from diffcalc.ub.crystal import CrystalUnderTest
from diffcalc.ub.reflections import ReflectionList
from diffcalc.util import DiffcalcException, cross3, dot3
SMALL = 1e-7
TODEG = 180 / pi
WIDTH = 13
def z(num):
"""Round to zero if small. This is useful to get rid of erroneous
minus signs resulting from float representation close to zero.
"""
if abs(num) < SMALL:
num = 0
return num
#The UB matrix is used to find or set the orientation of a set of
#planes described by an hkl vector. The U matrix can be used to find
#or set the orientation of the crystal lattices' y axis. If there is
#crystal miscut the crystal lattices y axis is not parallel to the
#crystals optical surface normal. For surface diffraction experiments,
#where not only the crystal lattice must be oriented appropriately but
#so must the crystal's optical surface, two angles tau and sigma are
#used to describe the difference between the two. Sigma is (minus) the
#ammount of chi axis rotation and tau (minus) the ammount of phi axis
#rotation needed to move the surface normal into the direction of the
class PaperSpecificUbCalcStrategy(object):
def calculate_q_phi(self, pos):
"""Calculate hkl in the phi frame in units of 2 * pi / lambda from
pos object in radians"""
raise NotImplementedError()
class UBCalculation:
"""A UB matrix calculation for an experiment.
Contains the parameters for the _crystal under test, a list of measured
reflections and, if its been calculated, a UB matrix to be used by the rest
of the code.
"""
def __init__(self, hardware, diffractometerPluginObject,
persister, strategy, include_sigtau=True, include_reference=True):
# The diffractometer geometry is required to map the internal angles
# into those used by this diffractometer (for display only)
self._hardware = hardware
self._geometry = diffractometerPluginObject
self._persister = persister
self._strategy = strategy
self.include_sigtau = include_sigtau
self.include_reference = include_reference
self._clear()
def _get_diffractometer_axes_names(self):
return self._hardware.get_axes_names()
def _clear(self, name=None):
# NOTE the Diffraction calculator is expecting this object to exist in
# the long run. We can't remove this entire object, and recreate it.
# It also contains a required link to the angle calculator.
reflist = ReflectionList(self._geometry, self._get_diffractometer_axes_names())
reference = YouReference(self._get_UB)
self._state = UBCalcState(name=name, reflist=reflist, reference=reference)
self._U = None
self._UB = None
self._state.configure_calc_type()
### State ###
def start_new(self, name):
"""start_new(name) --- creates a new blank ub calculation"""
# Create storage object if name does not exist (TODO)
if name in self._persister.list():
print ("No UBCalculation started: There is already a calculation "
"called: " + name)
print "Saved calculations: " + repr(self._persister.list())
return
self._clear(name)
self.save()
def load(self, name):
state = self._persister.load(name)
if isinstance(self._persister, UBCalculationJSONPersister):
self._state = decode_ubcalcstate(state, self._geometry, self._get_diffractometer_axes_names())
self._state.reference.get_UB = self._get_UB
elif isinstance(self._persister, UBCalculationPersister):
self._state = state
else:
raise Exception('Unexpected persister type: ' + str(self._persister))
if self._state.manual_U is not None:
self.set_U_manually(self._state.manual_U)
elif self._state.manual_UB is not None:
self.set_UB_manually(self._state.manual_UB)
elif self._state.or0 is not None:
if self._state.or1 is None:
self.calculate_UB_from_primary_only()
else:
self.calculate_UB()
else:
pass
def save(self):
self.saveas(self._state.name)
def saveas(self, name):
self._state.name = name
self._persister.save(self._state, name)
def listub(self):
return self._persister.list()
def listub_metadata(self):
return self._persister.list_metadata()
def remove(self, name):
self._persister.remove(name)
if self._state == name:
self._clear(name)
def getState(self):
return self._state.getState()
def __str__(self):
if self._state.name is None:
return "<<< No UB calculation started >>>"
lines = []
lines.append(bold("UBCALC"))
lines.append("")
lines.append(
" name:".ljust(WIDTH) + self._state.name.rjust(9))
if self.include_sigtau:
lines.append("")
lines.append(
" sigma:".ljust(WIDTH) + ("% 9.5f" % self._state.sigma).rjust(9))
lines.append(
" tau:".ljust(WIDTH) + ("% 9.5f" % self._state.tau).rjust(9))
if self.include_reference:
lines.append("")
ub_calculated = self._UB is not None
lines.extend(self._state.reference.repr_lines(ub_calculated, WIDTH))
lines.append("")
lines.append(bold("CRYSTAL"))
lines.append("")
if self._state.crystal is None:
lines.append(" <<< none specified >>>")
else:
lines.extend(self._state.crystal.str_lines())
lines.append("")
lines.append(bold("UB MATRIX"))
lines.append("")
if self._UB is None:
lines.append(" <<< none calculated >>>")
else:
lines.extend(self.str_lines_u())
lines.append("")
lines.extend(self.str_lines_u_angle_and_axis())
lines.append("")
lines.extend(self.str_lines_ub())
lines.append("")
lines.append(bold("REFLECTIONS"))
lines.append("")
lines.extend(self._state.reflist.str_lines())
return '\n'.join(lines)
def str_lines_u(self):
lines = []
fmt = "% 9.5f % 9.5f % 9.5f"
U = self.U
lines.append(" U matrix:".ljust(WIDTH) +
fmt % (z(U[0, 0]), z(U[0, 1]), z(U[0, 2])))
lines.append(' ' * WIDTH + fmt % (z(U[1, 0]), z(U[1, 1]), z(U[1, 2])))
lines.append(' ' * WIDTH + fmt % (z(U[2, 0]), z(U[2, 1]), z(U[2, 2])))
return lines
def str_lines_u_angle_and_axis(self):
lines = []
fmt = "% 9.5f % 9.5f % 9.5f"
y = matrix('0; 0; 1')
rotation_axis = cross3(y, self.U * y)
if abs(norm(rotation_axis)) < SMALL:
lines.append(" U angle:".ljust(WIDTH) + " 0")
else:
rotation_axis = rotation_axis * (1 / norm(rotation_axis))
cos_rotation_angle = dot3(y, self.U * y)
rotation_angle = acos(cos_rotation_angle)
lines.append(" angle:".ljust(WIDTH) + "% 9.5f" % (rotation_angle * TODEG))
lines.append(" axis:".ljust(WIDTH) + fmt % tuple((rotation_axis.T).tolist()[0]))
return lines
def str_lines_ub(self):
lines = []
fmt = "% 9.5f % 9.5f % 9.5f"
UB = self.UB
lines.append(" UB matrix:".ljust(WIDTH) +
fmt % (z(UB[0, 0]), z(UB[0, 1]), z(UB[0, 2])))
lines.append(' ' * WIDTH + fmt % (z(UB[1, 0]), z(UB[1, 1]), z(UB[1, 2])))
lines.append(' ' * WIDTH + fmt % (z(UB[2, 0]), z(UB[2, 1]), z(UB[2, 2])))
return lines
@property
def name(self):
return self._state.name
### Lattice ###
def set_lattice(self, name, *shortform):
"""
Converts a list shortform crystal parameter specification to a six-long
tuple returned as . Returns None if wrong number of input args. See
set_lattice() for a description of the shortforms supported.
shortformLattice -- a tuple as follows:
[a] - assumes cubic
[a,b]) - assumes tetragonal
[a,b,c]) - assumes ortho
[a,b,c,gam]) - assumes mon/hex gam different from 90.
[a,b,c,alp,bet,gam]) - for arbitrary
where all measurements in angstroms and angles in degrees
"""
self._set_lattice_without_saving(name, *shortform)
self.save()
def _set_lattice_without_saving(self, name, *shortform):
sf = shortform
if len(sf) == 1:
fullform = (sf[0], sf[0], sf[0], 90., 90., 90.) # cubic
elif len(sf) == 2:
fullform = (sf[0], sf[0], sf[1], 90., 90., 90.) # tetragonal
elif len(sf) == 3:
fullform = (sf[0], sf[1], sf[2], 90., 90., 90.) # ortho
elif len(sf) == 4:
fullform = (sf[0], sf[1], sf[2], 90., 90., sf[3]) # mon/hex gam
# not 90
elif len(sf) == 5:
raise ValueError("wrong length input to set_lattice")
elif len(sf) == 6:
fullform = sf # triclinic/arbitrary
else:
raise ValueError("wrong length input to set_lattice")
self._set_lattice(name, *fullform)
def _set_lattice(self, name, a, b, c, alpha, beta, gamma):
"""set lattice parameters in degrees"""
if self._state.name is None:
raise DiffcalcException(
"Cannot set lattice until a UBCalcaluation has been started "
"with newubcalc")
self._state.crystal = CrystalUnderTest(name, a, b, c, alpha, beta, gamma)
# Clear U and UB if these exist
if self._U is not None: # (UB will also exist)
print "Warning: the old UB calculation has been cleared."
print " Use 'calcub' to recalculate with old reflections."
### Surface normal stuff ###
def _gettau(self):
"""
Returns tau (in degrees): the (minus) ammount of phi axis rotation ,
that together with some chi axis rotation (minus sigma) brings the
optical surface normal parallelto the omega axis.
"""
return self._state.tau
def _settau(self, tau):
self._state.tau = tau
self.save()
tau = property(_gettau, _settau)
def _getsigma(self):
"""
Returns sigma (in degrees): the (minus) ammount of phi axis rotation ,
that together with some phi axis rotation (minus tau) brings the
optical surface normal parallel to the omega axis.
"""
return self._state.sigma
def _setsigma(self, sigma):
self.state._sigma = sigma
self.save()
sigma = property(_getsigma, _setsigma)
### Reference vector ###
def _get_n_phi(self):
return self._state.reference.n_phi
n_phi = property(_get_n_phi)
def set_n_phi_configured(self, n_phi):
self._state.reference.n_phi_configured = n_phi
self.save()
def set_n_hkl_configured(self, n_hkl):
self._state.reference.n_hkl_configured = n_hkl
self.save()
def print_reference(self):
print '\n'.join(self._state.reference.repr_lines(self.is_ub_calculated()))
### Reflections ###
def add_reflection(self, h, k, l, position, energy, tag, time):
"""add_reflection(h, k, l, position, tag=None) -- adds a reflection
position is in degrees and in the systems internal representation.
"""
if self._state.reflist is None:
raise DiffcalcException("No UBCalculation loaded")
self._state.reflist.add_reflection(h, k, l, position, energy, tag, time)
self.save() # incase autocalculateUbAndReport fails
# If second reflection has just been added then calculateUB
if len(self._state.reflist) == 2:
self._autocalculateUbAndReport()
self.save()
def edit_reflection(self, num, h, k, l, position, energy, tag, time):
"""
edit_reflection(num, h, k, l, position, tag=None) -- adds a reflection
position is in degrees and in the systems internal representation.
"""
if self._state.reflist is None:
raise DiffcalcException("No UBCalculation loaded")
self._state.reflist.edit_reflection(num, h, k, l, position, energy, tag, time)
# If first or second reflection has been changed and there are at least
# two reflections then recalculate UB
if (num == 1 or num == 2) and len(self._state.reflist) >= 2:
self._autocalculateUbAndReport()
self.save()
def get_reflection(self, num):
"""--> ( [h, k, l], position, energy, tag, time
num starts at 1, position in degrees"""
return self._state.reflist.getReflection(num)
def get_reflection_in_external_angles(self, num):
"""--> ( [h, k, l], position, energy, tag, time
num starts at 1, position in degrees"""
return self._state.reflist.get_reflection_in_external_angles(num)
def get_number_reflections(self):
return 0 if self._state.reflist is None else len(self._state.reflist)
def del_reflection(self, reflectionNumber):
self._state.reflist.removeReflection(reflectionNumber)
if ((reflectionNumber == 1 or reflectionNumber == 2) and
(self._U != None)):
self._autocalculateUbAndReport()
self.save()
def swap_reflections(self, num1, num2):
self._state.reflist.swap_reflections(num1, num2)
if ((num1 == 1 or num1 == 2 or num2 == 1 or num2 == 2) and
(self._U != None)):
self._autocalculateUbAndReport()
self.save()
def _autocalculateUbAndReport(self):
if len(self._state.reflist) < 2:
pass
elif self._state.crystal is None:
print ("Not calculating UB matrix as no lattice parameters have "
"been specified.")
elif not self._state.is_okay_to_autocalculate_ub:
print ("Not calculating UB matrix as it has been manually set. "
"Use 'calcub' to explicitly recalculate it.")
else: # okay to autocalculate
if self._UB is None:
print "Calculating UB matrix."
else:
print "Recalculating UB matrix."
self.calculate_UB()
# @property
# def reflist(self):
# return self._state.reflist
### Calculations ###
def set_U_manually(self, m):
"""Manually sets U. matrix must be 3*3 Jama or python matrix.
Turns off aution UB calcualtion."""
# Check matrix is a 3*3 Jama matrix
if m.__class__ != matrix:
m = matrix(m) # assume its a python matrix
if m.shape[0] != 3 or m.shape[1] != 3:
raise ValueError("Expects 3*3 matrix")
if self._UB is None:
print "Calculating UB matrix."
else:
print "Recalculating UB matrix."
self._state.configure_calc_type(manual_U=m)
self._U = m
if self._state.crystal is None:
raise DiffcalcException(
"A crystal must be specified before manually setting U")
self._UB = self._U * self._state.crystal.B
print ("NOTE: A new UB matrix will not be automatically calculated "
"when the orientation reflections are modified.")
self.save()
def set_UB_manually(self, m):
"""Manually sets UB. matrix must be 3*3 Jama or python matrix.
Turns off aution UB calcualtion."""
# Check matrix is a 3*3 Jama matrix
if m.__class__ != matrix:
m = matrix(m) # assume its a python matrix
if m.shape[0] != 3 or m.shape[1] != 3:
raise ValueError("Expects 3*3 matrix")
self._state.configure_calc_type(manual_UB=m)
self._UB = m
self.save()
@property
def U(self):
if self._U is None:
raise DiffcalcException(
"No U matrix has been calculated during this ub calculation")
return self._U
@property
def UB(self):
return self._get_UB()
def is_ub_calculated(self):
return self._UB is not None
def _get_UB(self):
if not self.is_ub_calculated():
raise DiffcalcException(
"No UB matrix has been calculated during this ub calculation")
else:
return self._UB
def calculate_UB(self):
"""
Calculate orientation matrix. Uses first two orientation reflections
as in Busang and Levy, but for the diffractometer in Lohmeier and
Vlieg.
"""
# Major variables:
# h1, h2: user input reciprical lattice vectors of the two reflections
# h1c, h2c: user input vectors in cartesian crystal plane
# pos1, pos2: measured diffractometer positions of the two reflections
# u1a, u2a: measured reflection vectors in alpha frame
# u1p, u2p: measured reflection vectors in phi frame
# Get hkl and angle values for the first two refelctions
if self._state.reflist is None:
raise DiffcalcException("Cannot calculate a U matrix until a "
"UBCalculation has been started with "
"'newub'")
try:
(h1, pos1, _, _, _) = self._state.reflist.getReflection(1)
(h2, pos2, _, _, _) = self._state.reflist.getReflection(2)
except IndexError:
raise DiffcalcException(
"Two reflections are required to calculate a u matrix")
h1 = matrix([h1]).T # row->column
h2 = matrix([h2]).T
pos1.changeToRadians()
pos2.changeToRadians()
# Compute the two reflections' reciprical lattice vectors in the
# cartesian crystal frame
B = self._state.crystal.B
h1c = B * h1
h2c = B * h2
u1p = self._strategy.calculate_q_phi(pos1)
u2p = self._strategy.calculate_q_phi(pos2)
# Create modified unit vectors t1, t2 and t3 in crystal and phi systems
t1c = h1c
t3c = cross3(h1c, h2c)
t2c = cross3(t3c, t1c)
t1p = u1p # FIXED from h1c 9July08
t3p = cross3(u1p, u2p)
t2p = cross3(t3p, t1p)
# ...and nornmalise and check that the reflections used are appropriate
SMALL = 1e-4 # Taken from Vlieg's code
e = DiffcalcException("Invalid orientation reflection(s)")
def normalise(m):
d = norm(m)
if d < SMALL:
raise e
return m / d
t1c = normalise(t1c)
t2c = normalise(t2c)
t3c = normalise(t3c)
t1p = normalise(t1p)
t2p = normalise(t2p)
t3p = normalise(t3p)
Tc = hstack([t1c, t2c, t3c])
Tp = hstack([t1p, t2p, t3p])
self._state.configure_calc_type(or0=1, or1=2)
self._U = Tp * Tc.I
self._UB = self._U * B
self.save()
def calculate_UB_from_primary_only(self):
"""
Calculate orientation matrix with the shortest absolute angle change.
Uses first orientation reflection
"""
# Algorithm from http://www.j3d.org/matrix_faq/matrfaq_latest.html
# Get hkl and angle values for the first two refelctions
if self._state.reflist is None:
raise DiffcalcException(
"Cannot calculate a u matrix until a UBCalcaluation has been "
"started with newub")
try:
(h, pos, _, _, _) = self._state.reflist.getReflection(1)
except IndexError:
raise DiffcalcException(
"One reflection is required to calculate a u matrix")
h = matrix([h]).T # row->column
pos.changeToRadians()
B = self._state.crystal.B
h_crystal = B * h
h_crystal = h_crystal * (1 / norm(h_crystal))
q_measured_phi = self._strategy.calculate_q_phi(pos)
q_measured_phi = q_measured_phi * (1 / norm(q_measured_phi))
rotation_axis = cross3(h_crystal, q_measured_phi)
rotation_axis = rotation_axis * (1 / norm(rotation_axis))
cos_rotation_angle = dot3(h_crystal, q_measured_phi)
rotation_angle = acos(cos_rotation_angle)
uvw = rotation_axis.T.tolist()[0] # TODO: cleanup
print "resulting U angle: %.5f deg" % (rotation_angle * TODEG)
u_repr = (', '.join(['% .5f' % el for el in uvw]))
print "resulting U axis direction: [%s]" % u_repr
u, v, w = uvw
rcos = cos(rotation_angle)
rsin = sin(rotation_angle)
m = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] # TODO: tidy
m[0][0] = rcos + u * u * (1 - rcos)
m[1][0] = w * rsin + v * u * (1 - rcos)
m[2][0] = -v * rsin + w * u * (1 - rcos)
m[0][1] = -w * rsin + u * v * (1 - rcos)
m[1][1] = rcos + v * v * (1 - rcos)
m[2][1] = u * rsin + w * v * (1 - rcos)
m[0][2] = v * rsin + u * w * (1 - rcos)
m[1][2] = -u * rsin + v * w * (1 - rcos)
m[2][2] = rcos + w * w * (1 - rcos)
if self._UB is None:
print "Calculating UB matrix from the first reflection only."
else:
print "Recalculating UB matrix from the first reflection only."
print ("NOTE: A new UB matrix will not be automatically calculated "
"when the orientation reflections are modified.")
self._state.configure_calc_type(or0=1)
self._U = matrix(m)
self._UB = self._U * B
self.save()
def get_hkl_plane_distance(self, hkl):
"""Calculates and returns the distance between planes"""
return self._state.crystal.get_hkl_plane_distance(hkl)

View File

@@ -0,0 +1,180 @@
from diffcalc.hkl.vlieg.geometry import VliegPosition
from diffcalc.ub.crystal import CrystalUnderTest
from diffcalc.ub.reflections import ReflectionList, _Reflection
from math import pi
import datetime # @UnusedImport For crazy time eval code!
from diffcalc.ub.reference import YouReference
try:
from collection import OrderedDict
except ImportError:
from simplejson import OrderedDict
try:
import json
except ImportError:
import simplejson as json
try:
from numpy import matrix
except ImportError:
from numjy import matrix
TODEG = 180 / pi
class UBCalcState():
def __init__(self, name=None, crystal=None, reflist=None, tau=0, sigma=0,
manual_U=None, manual_UB=None, or0=None, or1=None, reference=None):
assert reflist is not None
self.name = name
self.crystal = crystal
self.reflist = reflist
self.tau = tau # degrees
self.sigma = sigma # degrees
self.manual_U = manual_U
self.manual_UB = manual_UB
self.or0 = or0
self.or1 = or1
self.reference = reference
@property
def is_okay_to_autocalculate_ub(self):
nothing_set = ((self.manual_U is None) and
(self.manual_UB is None) and
(self.or0 is None) and
(self.or1 is None))
or0_and_or1_used = (self.or0 is not None) and (self.or1 is not None)
return nothing_set or or0_and_or1_used
def configure_calc_type(self,
manual_U=None,
manual_UB=None,
or0=None,
or1=None):
self.manual_U = manual_U
self.manual_UB = manual_UB
self.or0 = or0
self.or1 = or1
class UBCalcStateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, UBCalcState):
d = OrderedDict()
d['name'] = obj.name
d['crystal'] = obj.crystal
d['reflist'] = obj.reflist
d['tau'] = obj.tau
d['sigma'] = obj.sigma
d['reference'] = obj.reference
d['u'] = obj.manual_U
d['ub'] = obj.manual_UB
d['or0'] = obj.or0
d['or1'] = obj.or1
return d
if isinstance(obj, CrystalUnderTest):
return repr([obj._name, obj._a1, obj._a2, obj._a3, obj._alpha1 * TODEG,
obj._alpha2 * TODEG, obj._alpha3 * TODEG])
if isinstance(obj, matrix):
l = [', '.join((repr(e) for e in row)) for row in obj.tolist()]
return l
if isinstance(obj, ReflectionList):
d = OrderedDict()
for n, ref in enumerate(obj._reflist):
d[str(n+1)] = ref
return d
if isinstance(obj, _Reflection):
d = OrderedDict()
d['tag'] = obj.tag
d['hkl'] = repr([obj.h, obj.k, obj.l])
d['pos'] = repr(list(obj.pos.totuple()))
d['energy'] = obj.energy
dt = eval(obj.time) # e.g. --> datetime.datetime(2013, 8, 5, 15, 47, 7, 962432)
d['time'] = None if dt is None else dt.isoformat()
return d
if isinstance(obj, YouReference):
d = OrderedDict()
if obj.n_hkl_configured is not None:
d['n_hkl_configured'] = repr(obj.n_hkl_configured.T.tolist()[0])
else:
d['n_hkl_configured'] = None
if obj.n_phi_configured is not None:
d['n_phi_configured'] = repr(obj.n_phi_configured.T.tolist()[0])
else:
d['n_phi_configured'] = None
return d
return json.JSONEncoder.default(self, obj)
def decode_ubcalcstate(state, geometry, diffractometer_axes_names):
return UBCalcState(
name=state['name'],
crystal=state['crystal'] and CrystalUnderTest(*eval(state['crystal'])),
reflist=decode_reflist(state['reflist'], geometry, diffractometer_axes_names),
tau=state['tau'],
sigma=state['sigma'],
manual_U=state['u'] and decode_matrix(state['u']),
manual_UB=state['ub'] and decode_matrix(state['ub']),
or0=state['or0'],
or1=state['or1'],
reference=decode_reference(state.get('reference', None))
)
def decode_matrix(rows):
return matrix([[eval(e) for e in row.split(', ')] for row in rows])
def decode_reflist(reflist_dict, geometry, diffractometer_axes_names):
reflections = []
for key in sorted(reflist_dict.keys()):
reflections.append(decode_reflection(reflist_dict[key], geometry))
return ReflectionList(geometry, diffractometer_axes_names, reflections)
def decode_reflection(ref_dict, geometry):
h, k, l = eval(ref_dict['hkl'])
time = ref_dict['time'] and gt(ref_dict['time'])
pos_tuple = eval(ref_dict['pos'])
try:
position = geometry.create_position(*pos_tuple)
except AttributeError:
position = VliegPosition(*pos_tuple)
return _Reflection(h, k, l, position, ref_dict['energy'], str(ref_dict['tag']), repr(time))
def decode_reference(ref_dict):
reference = YouReference(None) # TODO: We can't set get_ub method yet (tangles!)
if ref_dict:
nhkl = ref_dict.get('n_hkl_configured', None)
nphi = ref_dict.get('n_phi_configured', None)
if nhkl:
reference.n_hkl_configured = matrix([eval(nhkl)]).T
if nphi:
reference.n_phi_configured = matrix([eval(nphi)]).T
return reference
# From: http://stackoverflow.com/questions/127803/how-to-parse-iso-formatted-date-in-python
def gt(dt_str):
dt, _, us= dt_str.partition(".")
dt= datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S")
us= int(us.rstrip("Z"), 10)
return dt + datetime.timedelta(microseconds=us)

View File

@@ -0,0 +1,137 @@
###
# 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, cos, sin, acos, sqrt
try:
from numpy import matrix
except ImportError:
from numjy import matrix
TORAD = pi / 180
TODEG = 180 / pi
SMALL = 1e-7
def z(num):
"""Round to zero if small. This is useful to get rid of erroneous
minus signs resulting from float representation close to zero.
"""
if abs(num) < SMALL:
num = 0
return num
class CrystalUnderTest(object):
"""
Contains the lattice parameters and calculated B matrix for the crytsal
under test. Also Calculates the distance between planes at a given hkl
value.
The lattice paraemters can be specified and then if desired saved to a
__library to be loaded later. The parameters are persisted across restarts.
Lattices stored in config/var/crystals.xml .
"""
def __init__(self, name, a, b, c, alpha, beta, gamma):
'''Creates a new lattice and calculates related values.
Keyword arguments:
name -- a string
a,b,c,alpha,beta,gamma -- lengths and angles (in degrees)
'''
self._name = name
# Set the direct lattice parameters
self._a1 = a1 = a
self._a2 = a2 = b
self._a3 = a3 = c
self._alpha1 = alpha1 = alpha * TORAD
self._alpha2 = alpha2 = beta * TORAD
self._alpha3 = alpha3 = gamma * TORAD
# Calculate the reciprocal lattice parameters
self._beta1 = acos((cos(alpha2) * cos(alpha3) - cos(alpha1)) /
(sin(alpha2) * sin(alpha3)))
self._beta2 = beta2 = acos((cos(alpha1) * cos(alpha3) - cos(alpha2)) /
(sin(alpha1) * sin(alpha3)))
self._beta3 = beta3 = acos((cos(alpha1) * cos(alpha2) - cos(alpha3)) /
(sin(alpha1) * sin(alpha2)))
volume = (a1 * a2 * a3 *
sqrt(1 + 2 * cos(alpha1) * cos(alpha2) * cos(alpha3) -
cos(alpha1) ** 2 - cos(alpha2) ** 2 - cos(alpha3) ** 2))
self._b1 = b1 = 2 * pi * a2 * a3 * sin(alpha1) / volume
self._b2 = b2 = 2 * pi * a1 * a3 * sin(alpha2) / volume
self._b3 = b3 = 2 * pi * a1 * a2 * sin(alpha3) / volume
# Calculate the BMatrix from the direct and reciprical parameters.
# Reference: Busang and Levy (1967)
self._bMatrix = matrix([
[b1, b2 * cos(beta3), b3 * cos(beta2)],
[0.0, b2 * sin(beta3), -b3 * sin(beta2) * cos(alpha1)],
[0.0, 0.0, 2 * pi / a3]])
@property
def B(self):
'''
Returns the B matrix, may be null if crystal is not set, or if there
was a problem calculating this'''
return self._bMatrix
def get_hkl_plane_distance(self, hkl):
'''Calculates and returns the distance between planes'''
hkl = matrix([hkl])
bReduced = self._bMatrix / (2 * pi)
bMT = bReduced.I * bReduced.T.I
return 1.0 / sqrt((hkl * bMT.I * hkl.T)[0,0])
def __str__(self):
''' Returns lattice name and all set and calculated parameters'''
return '\n'.join(self.str_lines())
def str_lines(self):
WIDTH = 13
if self._name is None:
return [" none specified"]
b = self._bMatrix
lines = []
lines.append(" name:".ljust(WIDTH) + self._name.rjust(9))
lines.append("")
lines.append(" a, b, c:".ljust(WIDTH) +
"% 9.5f % 9.5f % 9.5f" % (self.getLattice()[1:4]))
lines.append(" " * WIDTH +
"% 9.5f % 9.5f % 9.5f" % (self.getLattice()[4:]))
lines.append("")
fmt = "% 9.5f % 9.5f % 9.5f"
lines.append(" B matrix:".ljust(WIDTH) +
fmt % (z(b[0, 0]), z(b[0, 1]), z(b[0, 2])))
lines.append(' ' * WIDTH + fmt % (z(b[1, 0]), z(b[1, 1]), z(b[1, 2])))
lines.append(' ' * WIDTH + fmt % (z(b[2, 0]), z(b[2, 1]), z(b[2, 2])))
return lines
def getLattice(self):
return(self._name, self._a1, self._a2, self._a3, self._alpha1 * TODEG,
self._alpha2 * TODEG, self._alpha3 * TODEG)

View File

@@ -0,0 +1,151 @@
###
# 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 with_statement
import os, glob
from diffcalc.ub.calcstate import UBCalcStateEncoder
import datetime
try:
import json
except ImportError:
import simplejson as json
def is_writable(directory):
"""Return true if the file is writable from the current user
"""
probe = os.path.join(directory, "probe")
try:
open(probe, 'w')
except IOError:
return False
else:
os.remove(probe)
return True
def check_directory_appropriate(directory):
if not os.path.exists(directory):
raise IOError("'%s' does not exist")
if not os.path.isdir(directory):
raise IOError("'%s' is not a directory")
if not is_writable(directory):
raise IOError("'%s' is not writable")
class UBCalculationJSONPersister(object):
def __init__(self, directory):
check_directory_appropriate(directory)
self.directory = directory
self.description = directory
def filepath(self, name):
return os.path.join(self.directory, name + '.json')
def save(self, state, name):
# FORMAT = '%Y-%m-%d %H:%M:%S'
# time_string = datetime.datetime.strftime(datetime.datetime.now(), FORMAT)
with open(self.filepath(name), 'w') as f:
json.dump(state, f, indent=4, cls=UBCalcStateEncoder)
def load(self, name):
with open(self.filepath(name), 'r') as f:
return json.load(f)
def list(self): # @ReservedAssignment
files = self._get_save_files()
return [os.path.basename(f + '.json').split('.json')[0] for f in files]
def list_metadata(self):
metadata = []
for f in self._get_save_files():
dt = datetime.datetime.fromtimestamp(os.path.getmtime(f))
metadata.append(dt.strftime('%d %b %Y (%H:%M)'))
return metadata
def _get_save_files(self):
files = filter(os.path.isfile, glob.glob(os.path.join(self.directory, '*.json')))
files.sort(key=lambda x: os.path.getmtime(x))
files.reverse()
return files
def remove(self, name):
os.remove(self.filepath(name))
class UBCalculationPersister(object):
"""Attempts to the use the gda's database to store ub calculation state
"""
def __init__(self):
try:
from uk.ac.diamond.daq.persistence.jythonshelf import LocalJythonShelfManager
from uk.ac.diamond.daq.persistence.jythonshelf.LocalDatabase import \
LocalDatabaseException
self.shelf = LocalJythonShelfManager.getLocalObjectShelf(
'diffcalc.ub')
except ImportError, e:
print ("!!! UBCalculationPersister could not import the gda database "
"code: " + repr(e))
self.shelf = None
except LocalDatabaseException, e:
print ("UBCalculationPersister could not connect to the gda "
"database: " + repr(e))
self.shelf = None
self.description = 'GDA sql database'
def save(self, state, key):
if self.shelf is not None:
self.shelf[key] = state
else:
print "<<<no database available to save UB calculation>>>"
def load(self, name):
if self.shelf is not None:
return self.shelf[name]
else:
raise IOError("Could not load UB calculation: no database available")
def list(self): # @ReservedAssignment
if self.shelf is not None:
names = list(self.shelf.keys())
names.sort()
return names
else:
return []
def remove(self, name):
if self.shelf is not None:
del self.shelf[name]
else:
raise IOError("Could not remove UB calculation: no database available")
class UbCalculationNonPersister(UBCalculationPersister):
"""
A version of UBCalculationPersister that simply stores to a local dict
rather than a database. Useful for testing.
"""
def __init__(self):
self.shelf = dict()
self.description = 'memory only'

View File

@@ -0,0 +1,99 @@
from math import pi
try:
from numpy import matrix, hstack
from numpy.linalg import norm
except ImportError:
from numjy import matrix, hstack
from numjy.linalg import norm
from math import pi, sin, cos, tan, acos, atan2, asin, sqrt, atan
from diffcalc.util import DiffcalcException, bound, angle_between_vectors
from diffcalc.util import cross3, z_rotation, x_rotation, dot3
SMALL = 1e-7
TODEG = 180 / pi
class YouReference(object):
def __init__(self, get_UB):
self.get_UB = get_UB # callable
self._n_phi_configured = None
self._n_hkl_configured = None
self._set_n_phi_configured(matrix('0; 0; 1'))
def _set_n_phi_configured(self, n_phi):
self._n_phi_configured = n_phi
self._n_hkl_configured = None
def _get_n_phi_configured(self):
return self._n_phi_configured
n_phi_configured = property(_get_n_phi_configured, _set_n_phi_configured)
def _set_n_hkl_configured(self, n_hkl):
self._n_phi_configured = None
self._n_hkl_configured = n_hkl
def _get_n_hkl_configured(self):
return self._n_hkl_configured
n_hkl_configured = property(_get_n_hkl_configured, _set_n_hkl_configured)
@property
def n_phi(self):
n_phi = (self.get_UB() * self._n_hkl_configured if self._n_phi_configured is None
else self._n_phi_configured)
return n_phi / norm(n_phi)
@property
def n_hkl(self):
n_hkl = (self.get_UB().I * self._n_phi_configured if self._n_hkl_configured is None
else self._n_hkl_configured)
return n_hkl / norm(n_hkl)
def _pretty_vector(self, m):
return ' '.join([('% 9.5f' % e).rjust(9) for e in m.T.tolist()[0]])
def repr_lines(self, ub_calculated, WIDTH=9):
SET_LABEL = ' <- set'
lines = []
if self._n_phi_configured is not None:
nphi_label = SET_LABEL
nhkl_label = ''
elif self._n_hkl_configured is not None:
nphi_label = ''
nhkl_label = SET_LABEL
else:
raise AssertionError("Neither a manual n_phi nor n_hkl is configured")
if ub_calculated:
lines.append(" n_phi:".ljust(WIDTH) + self._pretty_vector(self.n_phi) + nphi_label)
lines.append(" n_hkl:".ljust(WIDTH) + self._pretty_vector(self.n_hkl) + nhkl_label)
rotation_axis = cross3(matrix('0; 0; 1'), self.n_phi)
if abs(norm(rotation_axis)) < SMALL:
lines.append(" miscut:".ljust(WIDTH) + " None")
else:
rotation_axis = rotation_axis * (1 / norm(rotation_axis))
cos_rotation_angle = dot3(matrix('0; 0; 1'), self.n_phi)
rotation_angle = acos(cos_rotation_angle)
lines.append(" miscut:")
lines.append(" angle:".ljust(WIDTH) + "% 9.5f" % (rotation_angle * TODEG))
lines.append(" axis:".ljust(WIDTH) + self._pretty_vector(rotation_axis))
else: # no ub calculated
if self._n_phi_configured is not None:
lines.append(" n_phi:".ljust(WIDTH) + self._pretty_vector(self._n_phi_configured) + SET_LABEL)
elif self._n_hkl_configured is not None:
lines.append(" n_hkl:".ljust(WIDTH) + self._pretty_vector(self._n_hkl_configured) + SET_LABEL)
return lines

View File

@@ -0,0 +1,126 @@
###
# 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 deepcopy
import datetime # @UnusedImport for the eval below
from diffcalc.util import DiffcalcException, bold
from diffcalc.hkl.vlieg.geometry import VliegPosition
class _Reflection:
"""A reflection"""
def __init__(self, h, k, l, position, energy, tag, time):
self.h = float(h)
self.k = float(k)
self.l = float(l)
self.pos = position
self.tag = tag
self.energy = float(energy) # energy=12.39842/lambda
self.wavelength = 12.3984 / self.energy
self.time = time # Saved as e.g. repr(datetime.now())
def __str__(self):
return ("energy=%-6.3f h=%-4.2f k=%-4.2f l=%-4.2f alpha=%-8.4f "
"delta=%-8.4f gamma=%-8.4f omega=%-8.4f chi=%-8.4f "
"phi=%-8.4f %-s %s" % (self.energy, self.h, self.k, self.l,
self.pos.alpha, self.pos.delta, self.pos.gamma, self.pos.omega,
self.pos.chi, self.pos.phi, self.tag, self.time))
class ReflectionList:
def __init__(self, diffractometerPluginObject, externalAngleNames, reflections=None):
self._geometry = diffractometerPluginObject
self._externalAngleNames = externalAngleNames
self._reflist = reflections if reflections else []
def add_reflection(self, h, k, l, position, energy, tag, time):
"""adds a reflection, position in degrees
"""
if type(position) in (list, tuple):
try:
position = self._geometry.create_position(*position)
except AttributeError:
position = VliegPosition(*position)
self._reflist += [_Reflection(h, k, l, position, energy, tag,
time.__repr__())]
def edit_reflection(self, num, h, k, l, position, energy, tag, time):
"""num starts at 1"""
if type(position) in (list, tuple):
position = VliegPosition(*position)
try:
self._reflist[num - 1] = _Reflection(h, k, l, position, energy, tag,
time.__repr__())
except IndexError:
raise DiffcalcException("There is no reflection " + repr(num)
+ " to edit.")
def getReflection(self, num):
"""
getReflection(num) --> ( [h, k, l], position, energy, tag, time ) --
num starts at 1 position in degrees
"""
r = deepcopy(self._reflist[num - 1]) # for convenience
return [r.h, r.k, r.l], deepcopy(r.pos), r.energy, r.tag, eval(r.time)
def get_reflection_in_external_angles(self, num):
"""getReflection(num) --> ( [h, k, l], (angle1...angleN), energy, tag )
-- num starts at 1 position in degrees"""
r = deepcopy(self._reflist[num - 1]) # for convenience
externalAngles = self._geometry.internal_position_to_physical_angles(r.pos)
result = [r.h, r.k, r.l], externalAngles, r.energy, r.tag, eval(r.time)
return result
def removeReflection(self, num):
del self._reflist[num - 1]
def swap_reflections(self, num1, num2):
orig1 = self._reflist[num1 - 1]
self._reflist[num1 - 1] = self._reflist[num2 - 1]
self._reflist[num2 - 1] = orig1
def __len__(self):
return len(self._reflist)
def __str__(self):
return '\n'.join(self.str_lines())
def str_lines(self):
axes = tuple(s.upper() for s in self._externalAngleNames)
if not self._reflist:
return [" <<< none specified >>>"]
lines = []
format = (" %6s %5s %5s %5s " + "%8s " * len(axes) + " TAG")
values = ('ENERGY', 'H', 'K', 'L') + axes
lines.append(bold(format % values))
for n in range(len(self._reflist)):
ref_tuple = self.get_reflection_in_external_angles(n + 1)
[h, k, l], externalAngles, energy, tag, _ = ref_tuple
if tag is None:
tag = ""
format = (" %2d %6.3f % 4.2f % 4.2f % 4.2f " +
"% 8.4f " * len(axes) + " %s")
values = (n + 1, energy, h, k, l) + externalAngles + (tag,)
lines.append(format % values)
return lines

View File

@@ -0,0 +1,532 @@
###
# 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 import settings
from diffcalc.ub.calc import UBCalculation
from math import asin, pi
from datetime import datetime
try:
from numpy import matrix
except ImportError:
from numjy import matrix
from diffcalc.util import getInputWithDefault as promptForInput, \
promptForNumber, promptForList, allnum, isnum, bold
from diffcalc.util import command
TORAD = pi / 180
TODEG = 180 / pi
# When using ipython magic, these functions must not be imported to the top
# level namespace. Doing so will stop them from being called with magic.
__all__ = ['addref', 'c2th', 'calcub', 'delref', 'editref', 'listub', 'loadub',
'newub', 'saveubas', 'setlat', 'setu', 'setub', 'showref', 'swapref',
'trialub', 'checkub', 'ub', 'ubcalc', 'rmub', 'clearref', 'lastub']
if settings.include_sigtau:
__all__.append('sigtau')
if settings.include_reference:
__all__.append('setnphi')
__all__.append('setnhkl')
ubcalc = UBCalculation(settings.hardware,
settings.geometry,
settings.ubcalc_persister,
settings.ubcalc_strategy,
settings.include_sigtau,
settings.include_reference)
### UB state ###
@command
def newub(name=None):
"""newub {'name'} -- start a new ub calculation name
"""
if name is None:
# interactive
name = promptForInput('calculation name')
ubcalc.start_new(name)
setlat()
elif isinstance(name, str):
# just trying might cause confusion here
ubcalc.start_new(name)
else:
raise TypeError()
@command
def loadub(name_or_num):
"""loadub 'name' | num -- load an existing ub calculation
"""
if isinstance(name_or_num, str):
ubcalc.load(name_or_num)
else:
ubcalc.load(ubcalc.listub()[int(name_or_num)])
@command
def lastub():
"""lastub -- load the last used ub calculation
"""
try:
lastub_name = ubcalc.listub()[0]
print "Loading ub calculation: '%s'" % lastub_name
loadub(0)
except IndexError:
print "WARNING: There is no record of the last ub calculation used"
@command
def rmub(name_or_num):
"""rmub 'name'|num -- remove existing ub calculation
"""
if isinstance(name_or_num, str):
ubcalc.remove(name_or_num)
else:
ubcalc.remove(ubcalc.listub()[int(name_or_num)])
@command
def listub():
"""listub -- list the ub calculations available to load.
"""
if hasattr(ubcalc._persister, 'description'):
print "UB calculations in: " + ubcalc._persister.description
else:
print "UB calculations:"
print
ubnames = ubcalc.listub()
# TODO: whole mechanism of making two calls is messy
try:
ub_metadata = ubcalc.listub_metadata()
except AttributeError:
ub_metadata = [''] * len(ubnames)
for n, name, data in zip(range(len(ubnames)), ubnames, ub_metadata):
print "%2i) %-15s %s" % (n, name, data)
@command
def saveubas(name):
"""saveubas 'name' -- save the ub calculation with a new name
"""
if isinstance(name, str):
# just trying might cause confusion here
ubcalc.saveas(name)
else:
raise TypeError()
@command
def ub():
"""ub -- show the complete state of the ub calculation
"""
#wavelength = float(hardware.get_wavelength())
#energy = float(hardware.get_energy())
print ubcalc.__str__()
### UB lattice ###
@command
def setlat(name=None, *args):
"""
setlat -- interactively enter lattice parameters (Angstroms and Deg)
setlat name a -- assumes cubic
setlat name a b -- assumes tetragonal
setlat name a b c -- assumes ortho
setlat name a b c gamma -- assumes mon/hex with gam not equal to 90
setlat name a b c alpha beta gamma -- arbitrary
"""
if name is None: # Interactive
name = promptForInput("crystal name")
a = promptForNumber(' a', 1)
b = promptForNumber(' b', a)
c = promptForNumber(' c', a)
alpha = promptForNumber('alpha', 90)
beta = promptForNumber('beta', 90)
gamma = promptForNumber('gamma', 90)
ubcalc.set_lattice(name, a, b, c, alpha, beta, gamma)
elif (isinstance(name, str) and
len(args) in (1, 2, 3, 4, 6) and
allnum(args)):
# first arg is string and rest are numbers
ubcalc.set_lattice(name, *args)
else:
raise TypeError()
@command
def c2th(hkl, en=None):
"""
c2th [h k l] -- calculate two-theta angle for reflection
"""
if en is None:
wl = settings.hardware.get_wavelength() # @UndefinedVariable
else:
wl = 12.39842 / en
d = ubcalc.get_hkl_plane_distance(hkl)
if wl > (2 * d):
raise ValueError(
'Reflection un-reachable as wavelength (%f) is more than twice\n'
'the plane distance (%f)' % (wl, d))
try:
return 2.0 * asin(wl / (d * 2)) * TODEG
except ValueError as e:
raise ValueError('asin(wl / (d * 2) with wl=%f and d=%f: ' %(wl, d) + e.args[0])
### Surface and reference vector stuff ###
@command
def sigtau(sigma=None, tau=None):
"""sigtau {sigma tau} -- sets or displays sigma and tau"""
if sigma is None and tau is None:
chi = settings.hardware.get_position_by_name('chi') # @UndefinedVariable
phi = settings.hardware.get_position_by_name('phi') # @UndefinedVariable
_sigma, _tau = ubcalc.sigma, ubcalc.tau
print "sigma, tau = %f, %f" % (_sigma, _tau)
print " chi, phi = %f, %f" % (chi, phi)
sigma = promptForInput("sigma", -chi)
tau = promptForInput(" tau", -phi)
ubcalc.sigma = sigma
ubcalc.tau = tau
else:
ubcalc.sigma = float(sigma)
ubcalc.tau = float(tau)
@command
def setnphi(xyz = None):
"""setnphi {[x y z]} -- sets or displays n_phi reference"""
if xyz is None:
ubcalc.print_reference()
else:
ubcalc.set_n_phi_configured(_to_column_vector_triple(xyz))
ubcalc.print_reference()
@command
def setnhkl(hkl):
"""setnhkl {[h k l]} -- sets or displays n_hkl reference"""
if hkl is None:
ubcalc.print_reference()
else:
ubcalc.set_n_hkl_configured(_to_column_vector_triple(hkl))
ubcalc.print_reference()
def _to_column_vector_triple(o):
m = matrix(o)
if m.shape == (1, 3):
return m.T
elif m.shape == (3, 1):
return m
else:
raise ValueError("Unexpected shape matrix: " + m)
### UB refelections ###
@command
def showref():
"""showref -- shows full reflection list"""
if ubcalc._state.reflist:
print '\n'.join(ubcalc._state.reflist.str_lines())
else:
print "<<< No reflections stored >>>"
@command
def addref(*args):
"""
addref -- add reflection interactively
addref [h k l] {'tag'} -- add reflection with current position and energy
addref [h k l] (p1, .., pN) energy {'tag'} -- add arbitrary reflection
"""
if len(args) == 0:
h = promptForNumber('h', 0.)
k = promptForNumber('k', 0.)
l = promptForNumber('l', 0.)
if None in (h, k, l):
_handleInputError("h,k and l must all be numbers")
reply = promptForInput('current pos', 'y')
if reply in ('y', 'Y', 'yes'):
positionList = settings.hardware.get_position() # @UndefinedVariable
energy = settings.hardware.get_energy() # @UndefinedVariable
else:
currentPos = settings.hardware.get_position() # @UndefinedVariable
positionList = []
names = settings.hardware.get_axes_names() # @UndefinedVariable
for i, angleName in enumerate(names):
val = promptForNumber(angleName.rjust(7), currentPos[i])
if val is None:
_handleInputError("Please enter a number, or press"
" Return to accept default!")
return
positionList.append(val)
energy = promptForNumber('energy', settings.hardware.get_energy()) # @UndefinedVariable
if val is None:
_handleInputError("Please enter a number, or press "
"Return to accept default!")
return
muliplier = settings.hardware.energyScannableMultiplierToGetKeV # @UndefinedVariable
energy = energy * muliplier
tag = promptForInput("tag")
if tag == '':
tag = None
pos = settings.geometry.physical_angles_to_internal_position(positionList) # @UndefinedVariable
ubcalc.add_reflection(h, k, l, pos, energy, tag,
datetime.now())
elif len(args) in (1, 2, 3, 4):
args = list(args)
h, k, l = args.pop(0)
if not (isnum(h) and isnum(k) and isnum(l)):
raise TypeError()
if len(args) >= 2:
pos = settings.geometry.physical_angles_to_internal_position( # @UndefinedVariable
args.pop(0))
energy = args.pop(0)
if not isnum(energy):
raise TypeError()
else:
pos = settings.geometry.physical_angles_to_internal_position( # @UndefinedVariable
settings.hardware.get_position()) # @UndefinedVariable
energy = settings.hardware.get_energy() # @UndefinedVariable
if len(args) == 1:
tag = args.pop(0)
if not isinstance(tag, str):
raise TypeError()
else:
tag = None
ubcalc.add_reflection(h, k, l, pos, energy, tag,
datetime.now())
else:
raise TypeError()
@command
def editref(num):
"""editref num -- interactively edit a reflection.
"""
num = int(num)
# Get old reflection values
[oldh, oldk, oldl], oldExternalAngles, oldEnergy, oldTag, oldT = \
ubcalc.get_reflection_in_external_angles(num)
del oldT # current time will be used.
h = promptForNumber('h', oldh)
k = promptForNumber('k', oldk)
l = promptForNumber('l', oldl)
if None in (h, k, l):
_handleInputError("h,k and l must all be numbers")
reply = promptForInput('update position with current hardware setting',
'n')
if reply in ('y', 'Y', 'yes'):
positionList = settings.hardware.get_position() # @UndefinedVariable
energy = settings.hardware.get_energy() # @UndefinedVariable
else:
positionList = []
names = settings.hardware.get_axes_names() # @UndefinedVariable
for i, angleName in enumerate(names):
val = promptForNumber(angleName.rjust(7), oldExternalAngles[i])
if val is None:
_handleInputError("Please enter a number, or press "
"Return to accept default!")
return
positionList.append(val)
energy = promptForNumber('energy', oldEnergy)
if val is None:
_handleInputError("Please enter a number, or press Return "
"to accept default!")
return
energy = energy * settings.hardware.energyScannableMultiplierToGetKeV # @UndefinedVariable
tag = promptForInput("tag", oldTag)
if tag == '':
tag = None
pos = settings.geometry.physical_angles_to_internal_position(positionList) # @UndefinedVariable
ubcalc.edit_reflection(num, h, k, l, pos, energy, tag,
datetime.now())
@command
def delref(num):
"""delref num -- deletes a reflection (numbered from 1)
"""
ubcalc.del_reflection(int(num))
@command
def clearref():
"""clearref -- deletes all the reflections
"""
while ubcalc.get_number_reflections():
ubcalc.del_reflection(1)
@command
def swapref(num1=None, num2=None):
"""
swapref -- swaps first two reflections used for calulating U matrix
swapref num1 num2 -- swaps two reflections (numbered from 1)
"""
if num1 is None and num2 is None:
ubcalc.swap_reflections(1, 2)
elif isinstance(num1, int) and isinstance(num2, int):
ubcalc.swap_reflections(num1, num2)
else:
raise TypeError()
### UB calculations ###
@command
def setu(U=None):
"""setu {[[..][..][..]]} -- manually set u matrix
"""
if U is None:
U = _promptFor3x3MatrixDefaultingToIdentity()
if U is None:
return # an error will have been printed or thrown
if _is3x3TupleOrList(U) or _is3x3Matrix(U):
ubcalc.set_U_manually(U)
else:
raise TypeError("U must be given as 3x3 list or tuple")
@command
def setub(UB=None):
"""setub {[[..][..][..]]} -- manually set ub matrix"""
if UB is None:
UB = _promptFor3x3MatrixDefaultingToIdentity()
if UB is None:
return # an error will have been printed or thrown
if _is3x3TupleOrList(UB):
ubcalc.set_UB_manually(UB)
else:
raise TypeError("UB must be given as 3x3 list or tuple")
def _promptFor3x3MatrixDefaultingToIdentity():
estring = "Please enter a number, or press Return to accept default!"
row1 = promptForList("row1", (1, 0, 0))
if row1 is None:
_handleInputError(estring)
return None
row2 = promptForList("row2", (0, 1, 0))
if row2 is None:
_handleInputError(estring)
return None
row3 = promptForList("row3", (0, 0, 1))
if row3 is None:
_handleInputError(estring)
return None
return [row1, row2, row3]
@command
def calcub():
"""calcub -- (re)calculate u matrix from ref1 and ref2.
"""
ubcalc.calculate_UB()
@command
def trialub():
"""trialub -- (re)calculate u matrix from ref1 only (check carefully).
"""
ubcalc.calculate_UB_from_primary_only()
# This command requires the ubcalc
def checkub():
"""checkub -- show calculated and entered hkl values for reflections.
"""
s = "\n %7s %4s %4s %4s %6s %6s %6s TAG\n" % \
('ENERGY', 'H', 'K', 'L', 'H_COMP', 'K_COMP', 'L_COMP')
s = bold(s)
nref = ubcalc.get_number_reflections()
if not nref:
s += "<<empty>>"
for n in range(nref):
hklguess, pos, energy, tag, _ = ubcalc.get_reflection(n + 1)
wavelength = 12.39842 / energy
hkl = settings.angles_to_hkl_function(pos.inRadians(), wavelength, ubcalc.UB)
h, k, l = hkl
if tag is None:
tag = ""
s += ("% 2d % 6.4f % 4.2f % 4.2f % 4.2f % 6.4f % 6.4f "
"% 6.4f %6s\n" % (n + 1, energy, hklguess[0],
hklguess[1], hklguess[2], h, k, l, tag))
print s
commands_for_help = ['State',
newub,
loadub,
lastub,
listub,
rmub,
saveubas,
ub,
'Lattice',
setlat,
c2th]
if ubcalc.include_reference:
commands_for_help.extend([
'Reference (surface)',
setnphi,
setnhkl])
if ubcalc.include_sigtau:
commands_for_help.extend([
'Surface',
sigtau])
commands_for_help.extend([
'Reflections',
showref,
addref,
editref,
delref,
clearref,
swapref,
'ub matrix',
checkub,
setu,
setub,
calcub,
trialub])
def _is3x3TupleOrList(m):
if type(m) not in (list, tuple):
return False
if len(m) != 3:
return False
for mrow in m:
if type(mrow) not in (list, tuple):
return False
if len(mrow) != 3:
return False
return True
def _is3x3Matrix(m):
return isinstance(m, matrix) and tuple(m.shape) == (3, 3)
def _handleInputError(msg):
raise TypeError(msg)