def _beam_derivatives(self, isel, parameterisation=None, reflections=None): """helper function to extend the derivatives lists by derivatives of the beam parameterisations""" # Get required data s0 = self._s0.select(isel) s0u = self._s0u.select(isel) wl = self._wavelength.select(isel) r = self._r.select(isel) e1 = self._e1.select(isel) q = self._q.select(isel) c0 = self._c0.select(isel) DeltaPsi = self._DeltaPsi.select(isel) D = self._D.select(isel) # get the derivatives of the beam vector wrt the parameters ds0_dbeam_p = parameterisation.get_ds_dp(use_none_as_null=True) dDeltaPsi_dp = [] dpv_dp = [] # loop through the parameters for der in ds0_dbeam_p: if der is None: dpv_dp.append(None) dDeltaPsi_dp.append(None) continue # repeat the derivative in an array ds0 = flex.vec3_double(len(s0u), der.elems) # we need the derivative of the unit beam direction too. This requires # scaling by the wavelength and projection onto the Ewald sphere scaled = ds0 * wl ds0u = scaled.dot(c0) * c0 + scaled.dot(e1) * e1 # calculate the derivative of DeltaPsi for this parameter dDeltaPsi = -1.0 * (r.dot(ds0)) / (e1.cross(r).dot(s0)) dDeltaPsi_dp.append(dDeltaPsi) # calculate (d[r]/d[e1])(d[e1]/dp) de1_dp = c0.cross(ds0u) dr_de1 = dRq_de(DeltaPsi, e1, q) drde_dedp = dr_de1 * de1_dp #dp = 1.e-8 # finite step size for the parameter #del_e1 = de1_dp * dp #e1f = e1 + del_e1 * 0.5 #rfwd = q.rotate_around_origin(e1f, DeltaPsi) #e1r = e1 - del_e1 * 0.5 #rrev = q.rotate_around_origin(e1r, DeltaPsi) #drde_dedp = (rfwd - rrev) * (1 / dp) # calculate the derivative of pv for this parameter dpv_dp.append(D * (ds0 + e1.cross(r) * dDeltaPsi + drde_dedp)) return dpv_dp, dDeltaPsi_dp
def _xl_unit_cell_derivatives(self, isel, parameterisation=None, reflections=None): """helper function to extend the derivatives lists by derivatives of the crystal unit cell parameterisations""" # Get required data U = self._U.select(isel) h = self._h.select(isel) e1 = self._e1.select(isel) DeltaPsi = self._DeltaPsi.select(isel) s1 = self._s1.select(isel) q = self._q.select(isel) q_scalar = self._q_scalar.select(isel) qq = self._qq.select(isel) q0 = self._q0.select(isel) r = self._r.select(isel) s0 = self._s0.select(isel) s0u = self._s0u.select(isel) D = self._D.select(isel) # get derivatives of the B matrix wrt the parameters dB_dxluc_p = parameterisation.get_ds_dp(use_none_as_null=True) dDeltaPsi_dp = [] dpv_dp = [] # loop through the parameters for der in dB_dxluc_p: if der is None: dpv_dp.append(None) dDeltaPsi_dp.append(None) continue der_mat = flex.mat3_double(len(U), der.elems) # calculate the derivative of q for this parameter dq = U * der_mat * h # calculate the derivative of r for this parameter dr = dq.rotate_around_origin(e1, DeltaPsi) # calculate the derivative of DeltaPsi for this parameter dDeltaPsi = -1.0 * (dr.dot(s1)) / (e1.cross(r).dot(s0)) dDeltaPsi_dp.append(dDeltaPsi) # derivative of the axis e1 q_dot_dq = q.dot(dq) dq0 = (q_scalar * dq - (q_dot_dq * q0)) / qq de1_dp = dq0.cross(s0u) # calculate (d[r]/d[e1])(d[e1]/dp) dr_de1 = dRq_de(DeltaPsi, e1, q) drde_dedp = dr_de1 * de1_dp # calculate the partial derivative of r wrt change in rotation # axis e1 by finite differences dp = 1.e-8 del_e1 = de1_dp * dp e1f = e1 + del_e1 * 0.5 rfwd = q.rotate_around_origin(e1f , DeltaPsi) e1r = e1 - del_e1 * 0.5 rrev = q.rotate_around_origin(e1r , DeltaPsi) drde_dedp = (rfwd - rrev) * (1 / dp) # calculate the derivative of pv for this parameter dpv = D * (dr + e1.cross(r) * dDeltaPsi + drde_dedp) dpv_dp.append(dpv) return dpv_dp, dDeltaPsi_dp
def tst_use_in_stills_parameterisation_for_crystal(crystal_param=0): # test use of analytical expression in stills prediction parameterisation from scitbx import matrix from math import pi, sqrt, atan2 import random print print "Test use of analytical expressions in stills prediction " + "parameterisation for crystal parameters" # crystal model from dxtbx.model.crystal import crystal_model from dials.algorithms.refinement.parameterisation.crystal_parameters import ( CrystalOrientationParameterisation, CrystalUnitCellParameterisation, ) crystal = crystal_model((20, 0, 0), (0, 30, 0), (0, 0, 40), space_group_symbol="P 1") # random reorientation e = matrix.col((random.random(), random.random(), random.random())).normalize() angle = random.random() * 180 crystal.rotate_around_origin(e, angle) wl = 1.1 s0 = matrix.col((0, 0, 1 / wl)) s0u = s0.normalize() # these are stills, but need a rotation axis for the Reeke algorithm axis = matrix.col((1, 0, 0)) # crystal parameterisations xlop = CrystalOrientationParameterisation(crystal) xlucp = CrystalUnitCellParameterisation(crystal) # Find some reflections close to the Ewald sphere from dials.algorithms.spot_prediction.reeke import reeke_model U = crystal.get_U() B = crystal.get_B() UB = U * B dmin = 4 hkl = reeke_model(UB, UB, axis, s0, dmin, margin=1).generate_indices() # choose first reflection for now, calc quantities relating to q h = matrix.col(hkl[0]) q = UB * h q0 = q.normalize() q_scalar = q.length() qq = q_scalar * q_scalar # calculate the axis of closest rotation e1 = q0.cross(s0u).normalize() # calculate c0, a vector orthogonal to s0u and e1 c0 = s0u.cross(e1).normalize() # calculate q1 q1 = q0.cross(e1).normalize() # calculate DeltaPsi a = 0.5 * qq * wl b = sqrt(qq - a * a) r = -1.0 * a * s0u + b * c0 DeltaPsi = -1.0 * atan2(r.dot(q1), r.dot(q0)) # Checks on the reflection prediction from libtbx.test_utils import approx_equal # 1. check r is on the Ewald sphere s1 = s0 + r assert approx_equal(s1.length(), s0.length()) # 2. check DeltaPsi is correct tst = q.rotate_around_origin(e1, DeltaPsi) assert approx_equal(tst, r) # choose the derivative with respect to a particular parameter. if crystal_param < 3: dU_dp = xlop.get_ds_dp()[crystal_param] dq = dU_dp * B * h else: dB_dp = xlucp.get_ds_dp()[crystal_param - 3] dq = U * dB_dp * h # NKS method of calculating d[q0]/dp q_dot_dq = q.dot(dq) dqq = 2.0 * q_dot_dq dq_scalar = dqq / q_scalar dq0_dp = (q_scalar * dq - (q_dot_dq * q0)) / qq # orthogonal to q0, as expected. print "NKS [q0].(d[q0]/dp) = {0} (should be 0.0)".format(q0.dot(dq0_dp)) # intuitive method of calculating d[q0]/dp, based on the fact that # it must be orthogonal to q0, i.e. in the plane containing q1 and e1 scaled = dq / q.length() dq0_dp = scaled.dot(q1) * q1 + scaled.dot(e1) * e1 # orthogonal to q0, as expected. print "DGW [q0].(d[q0]/dp) = {0} (should be 0.0)".format(q0.dot(dq0_dp)) # So it doesn't matter which method I use to calculate d[q0]/dp, as # both methods give the same results # use the fact that -e1 == q0.cross(q1) to redefine the derivative d[e1]/dp # from Sauter et al. (2014) (A.22) de1_dp = -1.0 * dq0_dp.cross(q1) # this *is* orthogonal to e1, as expected. print "[e1].(d[e1]/dp) = {0} (should be 0.0)".format(e1.dot(de1_dp)) # calculate (d[r]/d[e1])(d[e1]/dp) analytically from scitbx.array_family import flex from dials_refinement_helpers_ext import dRq_de dr_de1 = matrix.sqr(dRq_de(flex.double([DeltaPsi]), flex.vec3_double([e1]), flex.vec3_double([q]))[0]) print "Analytical calculation for (d[r]/d[e1])(d[e1]/dp):" print dr_de1 * de1_dp # now calculate using finite differences. dp = 1.0e-8 del_e1 = de1_dp * dp e1f = e1 + del_e1 * 0.5 rfwd = q.rotate_around_origin(e1f, DeltaPsi) e1r = e1 - del_e1 * 0.5 rrev = q.rotate_around_origin(e1r, DeltaPsi) print "Finite difference estimate for (d[r]/d[e1])(d[e1]/dp):" print (rfwd - rrev) * (1 / dp) print "These are essentially the same :-)"
def tst_use_in_stills_parameterisation_for_beam(beam_param=0): # test use of analytical expression in stills prediction parameterisation from scitbx import matrix from math import pi import random print print "Test use of analytical expressions in stills prediction " + "parameterisation for beam parameters" # beam model from dxtbx.model.experiment import beam_factory from dials.algorithms.refinement.parameterisation.beam_parameters import BeamParameterisation beam = beam_factory().make_beam(matrix.col((0, 0, 1)), wavelength=1.1) s0 = matrix.col(beam.get_s0()) s0u = matrix.col(beam.get_unit_s0()) # beam parameterisation bp = BeamParameterisation(beam) # choose the derivative with respect to a particular parameter. 0: mu1, # 1: mu2, 2: nu. ds0_dp = bp.get_ds_dp()[beam_param] # pick a random point on (the positive octant of) the Ewald sphere to rotate s1 = matrix.col((random.random(), random.random(), random.random())).normalize() * s0.length() r = s1 - s0 r0 = r.normalize() # calculate the axis of rotation e1 = r0.cross(s0u).normalize() # calculate c0, a vector orthogonal to s0u and e1 c0 = s0u.cross(e1).normalize() # convert to derivative of the unit beam direction. This involves scaling # by the wavelength, then projection back onto the Ewald sphere. scaled = ds0_dp * beam.get_wavelength() ds0u_dp = scaled.dot(c0) * c0 + scaled.dot(e1) * e1 # rotate relp off Ewald sphere a small angle (up to 1 deg) DeltaPsi = random.uniform(-pi / 180, pi / 180) q = r.rotate_around_origin(e1, -DeltaPsi) q0 = q.normalize() from libtbx.test_utils import approx_equal assert approx_equal(q0.cross(s0u).normalize(), e1) # use the fact that e1 == c0.cross(s0u) to redefine the derivative d[e1]/dp # from Sauter et al. (2014) (A.3) de1_dp = c0.cross(ds0u_dp) # unlike the previous definition this *is* orthogonal to e1, as expected. print "[e1].(d[e1]/dp) = {0} (should be 0.0)".format(e1.dot(de1_dp)) # calculate (d[r]/d[e1])(d[e1]/dp) analytically from scitbx.array_family import flex from dials_refinement_helpers_ext import dRq_de dr_de1 = matrix.sqr(dRq_de(flex.double([DeltaPsi]), flex.vec3_double([e1]), flex.vec3_double([q]))[0]) print "Analytical calculation for (d[r]/d[e1])(d[e1]/dp):" print dr_de1 * de1_dp # now calculate using finite differences. dp = 1.0e-8 del_e1 = de1_dp * dp e1f = e1 + del_e1 * 0.5 rfwd = q.rotate_around_origin(e1f, DeltaPsi) e1r = e1 - del_e1 * 0.5 rrev = q.rotate_around_origin(e1r, DeltaPsi) print "Finite difference estimate for (d[r]/d[e1])(d[e1]/dp):" print (rfwd - rrev) * (1 / dp) print "These are now the same :-)"
def work(): import random import math from scitbx import matrix # pick random rotation axis & angle - pick spherical coordinate basis to make # life easier in performing finite difference calculations t = 2 * math.pi * random.random() r = 2 * math.pi * random.random() s = math.pi * random.random() k = krs(r, s) # finite difference version ndRr, ndRs = dR_drs_fd(r, s, t) # now call on derivative calculation dRr, dRs = dR_drs_calc(r, s, t) # G&Y eqn 7 dRk = dR_dki(t, k) # G&Y eqn 9 dRk2 = gallego_yezzi_eqn9(t, k) dkdr = dk_dr(r, s) dkds = dk_ds(r, s) print "dR/dr" print "FD" print ndRr print "Analytical (G&Y 7)" m = matrix.sqr((0, 0, 0, 0, 0, 0, 0, 0, 0)) for i in range(3): m += dkdr[i] * dRk[i] print m print "Analytical (G&Y 9)" m = matrix.sqr((0, 0, 0, 0, 0, 0, 0, 0, 0)) for i in range(3): m += dkdr[i] * dRk2[i] print m print "Analytical (old)" print dRr print "dR/ds" print "FD" print ndRs print "Analytical (G&Y 7)" m = matrix.sqr((0, 0, 0, 0, 0, 0, 0, 0, 0)) for i in range(3): m += dkds[i] * dRk[i] print m print "Analytical (G&Y 9)" m = matrix.sqr((0, 0, 0, 0, 0, 0, 0, 0, 0)) for i in range(3): m += dkds[i] * dRk2[i] print m print "Analytical (old)" print dRs # Finally test the direct derivative of a rotated vector wrt the axis elements # versus finite differences # make a random vector to rotate u = matrix.col((random.random(), random.random(), random.random())) # calc derivatives of rotated vector (Gallego & Yezzi equn 8) from dials_refinement_helpers_ext import dRq_de from scitbx.array_family import flex dr_de = dRq_de(flex.double([t]), flex.vec3_double([k]), flex.vec3_double([u])) print print "d[r]/d[e], where [r] = [R][u] is a rotation about [e] (G&Y 8)" print matrix.sqr(dr_de[0]) print "Compare with FD calculation" dr_de_FD = [dR_ki * u for dR_ki in dRk] dr_de_FD = [elt for vec in dr_de_FD for elt in vec] # flatten list dr_de_FD = matrix.sqr(dr_de_FD).transpose() # make a matrix print dr_de_FD
def tst_use_in_stills_parameterisation_for_crystal(crystal_param=0): # test use of analytical expression in stills prediction parameterisation from scitbx import matrix from math import pi, sqrt, atan2 import random print() print("Test use of analytical expressions in stills prediction " + "parameterisation for crystal parameters") # crystal model from dxtbx.model.crystal import crystal_model from dials.algorithms.refinement.parameterisation.crystal_parameters import ( CrystalOrientationParameterisation, CrystalUnitCellParameterisation, ) crystal = crystal_model((20, 0, 0), (0, 30, 0), (0, 0, 40), space_group_symbol="P 1") # random reorientation e = matrix.col( (random.random(), random.random(), random.random())).normalize() angle = random.random() * 180 crystal.rotate_around_origin(e, angle) wl = 1.1 s0 = matrix.col((0, 0, 1 / wl)) s0u = s0.normalize() # these are stills, but need a rotation axis for the Reeke algorithm axis = matrix.col((1, 0, 0)) # crystal parameterisations xlop = CrystalOrientationParameterisation(crystal) xlucp = CrystalUnitCellParameterisation(crystal) # Find some reflections close to the Ewald sphere from dials.algorithms.spot_prediction.reeke import reeke_model U = crystal.get_U() B = crystal.get_B() UB = U * B dmin = 4 hkl = reeke_model(UB, UB, axis, s0, dmin, margin=1).generate_indices() # choose first reflection for now, calc quantities relating to q h = matrix.col(hkl[0]) q = UB * h q0 = q.normalize() q_scalar = q.length() qq = q_scalar * q_scalar # calculate the axis of closest rotation e1 = q0.cross(s0u).normalize() # calculate c0, a vector orthogonal to s0u and e1 c0 = s0u.cross(e1).normalize() # calculate q1 q1 = q0.cross(e1).normalize() # calculate DeltaPsi a = 0.5 * qq * wl b = sqrt(qq - a * a) r = -1.0 * a * s0u + b * c0 DeltaPsi = -1.0 * atan2(r.dot(q1), r.dot(q0)) # Checks on the reflection prediction from libtbx.test_utils import approx_equal # 1. check r is on the Ewald sphere s1 = s0 + r assert approx_equal(s1.length(), s0.length()) # 2. check DeltaPsi is correct tst = q.rotate_around_origin(e1, DeltaPsi) assert approx_equal(tst, r) # choose the derivative with respect to a particular parameter. if crystal_param < 3: dU_dp = xlop.get_ds_dp()[crystal_param] dq = dU_dp * B * h else: dB_dp = xlucp.get_ds_dp()[crystal_param - 3] dq = U * dB_dp * h # NKS method of calculating d[q0]/dp q_dot_dq = q.dot(dq) dqq = 2.0 * q_dot_dq dq_scalar = dqq / q_scalar dq0_dp = (q_scalar * dq - (q_dot_dq * q0)) / qq # orthogonal to q0, as expected. print("NKS [q0].(d[q0]/dp) = {0} (should be 0.0)".format(q0.dot(dq0_dp))) # intuitive method of calculating d[q0]/dp, based on the fact that # it must be orthogonal to q0, i.e. in the plane containing q1 and e1 scaled = dq / q.length() dq0_dp = scaled.dot(q1) * q1 + scaled.dot(e1) * e1 # orthogonal to q0, as expected. print("DGW [q0].(d[q0]/dp) = {0} (should be 0.0)".format(q0.dot(dq0_dp))) # So it doesn't matter which method I use to calculate d[q0]/dp, as # both methods give the same results # use the fact that -e1 == q0.cross(q1) to redefine the derivative d[e1]/dp # from Sauter et al. (2014) (A.22) de1_dp = -1.0 * dq0_dp.cross(q1) # this *is* orthogonal to e1, as expected. print("[e1].(d[e1]/dp) = {0} (should be 0.0)".format(e1.dot(de1_dp))) # calculate (d[r]/d[e1])(d[e1]/dp) analytically from scitbx.array_family import flex from dials_refinement_helpers_ext import dRq_de dr_de1 = matrix.sqr( dRq_de(flex.double([DeltaPsi]), flex.vec3_double([e1]), flex.vec3_double([q]))[0]) print("Analytical calculation for (d[r]/d[e1])(d[e1]/dp):") print(dr_de1 * de1_dp) # now calculate using finite differences. dp = 1.0e-8 del_e1 = de1_dp * dp e1f = e1 + del_e1 * 0.5 rfwd = q.rotate_around_origin(e1f, DeltaPsi) e1r = e1 - del_e1 * 0.5 rrev = q.rotate_around_origin(e1r, DeltaPsi) print("Finite difference estimate for (d[r]/d[e1])(d[e1]/dp):") print((rfwd - rrev) * (1 / dp)) print("These are essentially the same :-)")
def tst_use_in_stills_parameterisation_for_beam(beam_param=0): # test use of analytical expression in stills prediction parameterisation from scitbx import matrix from math import pi import random print() print("Test use of analytical expressions in stills prediction " + "parameterisation for beam parameters") # beam model from dxtbx.model.experiment import beam_factory from dials.algorithms.refinement.parameterisation.beam_parameters import ( BeamParameterisation, ) beam = beam_factory().make_beam(matrix.col((0, 0, 1)), wavelength=1.1) s0 = matrix.col(beam.get_s0()) s0u = matrix.col(beam.get_unit_s0()) # beam parameterisation bp = BeamParameterisation(beam) # choose the derivative with respect to a particular parameter. 0: mu1, # 1: mu2, 2: nu. ds0_dp = bp.get_ds_dp()[beam_param] # pick a random point on (the positive octant of) the Ewald sphere to rotate s1 = (matrix.col( (random.random(), random.random(), random.random())).normalize() * s0.length()) r = s1 - s0 r0 = r.normalize() # calculate the axis of rotation e1 = r0.cross(s0u).normalize() # calculate c0, a vector orthogonal to s0u and e1 c0 = s0u.cross(e1).normalize() # convert to derivative of the unit beam direction. This involves scaling # by the wavelength, then projection back onto the Ewald sphere. scaled = ds0_dp * beam.get_wavelength() ds0u_dp = scaled.dot(c0) * c0 + scaled.dot(e1) * e1 # rotate relp off Ewald sphere a small angle (up to 1 deg) DeltaPsi = random.uniform(-pi / 180, pi / 180) q = r.rotate_around_origin(e1, -DeltaPsi) q0 = q.normalize() from libtbx.test_utils import approx_equal assert approx_equal(q0.cross(s0u).normalize(), e1) # use the fact that e1 == c0.cross(s0u) to redefine the derivative d[e1]/dp # from Sauter et al. (2014) (A.3) de1_dp = c0.cross(ds0u_dp) # unlike the previous definition this *is* orthogonal to e1, as expected. print("[e1].(d[e1]/dp) = {0} (should be 0.0)".format(e1.dot(de1_dp))) # calculate (d[r]/d[e1])(d[e1]/dp) analytically from scitbx.array_family import flex from dials_refinement_helpers_ext import dRq_de dr_de1 = matrix.sqr( dRq_de(flex.double([DeltaPsi]), flex.vec3_double([e1]), flex.vec3_double([q]))[0]) print("Analytical calculation for (d[r]/d[e1])(d[e1]/dp):") print(dr_de1 * de1_dp) # now calculate using finite differences. dp = 1.0e-8 del_e1 = de1_dp * dp e1f = e1 + del_e1 * 0.5 rfwd = q.rotate_around_origin(e1f, DeltaPsi) e1r = e1 - del_e1 * 0.5 rrev = q.rotate_around_origin(e1r, DeltaPsi) print("Finite difference estimate for (d[r]/d[e1])(d[e1]/dp):") print((rfwd - rrev) * (1 / dp)) print("These are now the same :-)")
def work(): import random import math from scitbx import matrix # pick random rotation axis & angle - pick spherical coordinate basis to make # life easier in performing finite difference calculations t = 2 * math.pi * random.random() r = 2 * math.pi * random.random() s = math.pi * random.random() k = krs(r, s) # finite difference version ndRr, ndRs = dR_drs_fd(r, s, t) # now call on derivative calculation dRr, dRs = dR_drs_calc(r, s, t) # G&Y eqn 7 dRk = dR_dki(t, k) # G&Y eqn 9 dRk2 = gallego_yezzi_eqn9(t, k) dkdr = dk_dr(r, s) dkds = dk_ds(r, s) print("dR/dr") print("FD") print(ndRr) print("Analytical (G&Y 7)") m = matrix.sqr((0, 0, 0, 0, 0, 0, 0, 0, 0)) for i in range(3): m += dkdr[i] * dRk[i] print(m) print("Analytical (G&Y 9)") m = matrix.sqr((0, 0, 0, 0, 0, 0, 0, 0, 0)) for i in range(3): m += dkdr[i] * dRk2[i] print(m) print("Analytical (old)") print(dRr) print("dR/ds") print("FD") print(ndRs) print("Analytical (G&Y 7)") m = matrix.sqr((0, 0, 0, 0, 0, 0, 0, 0, 0)) for i in range(3): m += dkds[i] * dRk[i] print(m) print("Analytical (G&Y 9)") m = matrix.sqr((0, 0, 0, 0, 0, 0, 0, 0, 0)) for i in range(3): m += dkds[i] * dRk2[i] print(m) print("Analytical (old)") print(dRs) # Finally test the direct derivative of a rotated vector wrt the axis elements # versus finite differences # make a random vector to rotate u = matrix.col((random.random(), random.random(), random.random())) # calc derivatives of rotated vector (Gallego & Yezzi equn 8) from dials_refinement_helpers_ext import dRq_de from scitbx.array_family import flex dr_de = dRq_de(flex.double([t]), flex.vec3_double([k]), flex.vec3_double([u])) print() print("d[r]/d[e], where [r] = [R][u] is a rotation about [e] (G&Y 8)") print(matrix.sqr(dr_de[0])) print("Compare with FD calculation") dr_de_FD = [dR_ki * u for dR_ki in dRk] dr_de_FD = [elt for vec in dr_de_FD for elt in vec] # flatten list dr_de_FD = matrix.sqr(dr_de_FD).transpose() # make a matrix print(dr_de_FD)