class ModelState(object): """ A class to keep track of the model state """ def __init__( self, experiment, mosaicity_parameterisation, wavelength_parameterisation=None, fix_mosaic_spread=False, fix_wavelength_spread=True, fix_unit_cell=False, fix_orientation=False, ): """ Initialise the model state """ # Save the crystal model self.experiment = experiment self.crystal = experiment.crystal # The U and P parameterisation self._U_parameterisation = CrystalOrientationParameterisation( self.crystal) self._B_parameterisation = CrystalUnitCellParameterisation( self.crystal) # The M and L parameterisations self._M_parameterisation = mosaicity_parameterisation self._L_parameterisation = wavelength_parameterisation # Set the flags to fix parameters self._is_mosaic_spread_fixed = fix_mosaic_spread self._is_wavelength_spread_fixed = fix_wavelength_spread self._is_unit_cell_fixed = fix_unit_cell self._is_orientation_fixed = fix_orientation # Check wavelength parameterisation if not self.is_wavelength_spread_fixed: assert self._L_parameterisation is not None @property def is_orientation_fixed(self) -> bool: return self._is_orientation_fixed @property def is_unit_cell_fixed(self) -> bool: return self._is_unit_cell_fixed @property def is_mosaic_spread_fixed(self) -> bool: return self._is_mosaic_spread_fixed @property def is_mosaic_spread_angular(self) -> bool: return self._M_parameterisation.is_angular() @property def is_wavelength_spread_fixed(self) -> bool: return self._is_wavelength_spread_fixed @property def unit_cell(self): return self.crystal.get_unit_cell() @property def U_matrix(self) -> np.array: return np.array([self.crystal.get_U()], dtype=np.float64).reshape(3, 3) @property def B_matrix(self) -> np.array: return np.array([self.crystal.get_B()], dtype=np.float64).reshape(3, 3) @property def A_matrix(self) -> np.array: return np.array([self.crystal.get_A()], dtype=np.float64).reshape(3, 3) @property def mosaicity_covariance_matrix(self) -> np.array: return self._M_parameterisation.sigma() @property def wavelength_spread(self) -> flex.double: if self._L_parameterisation is not None: return self._L_parameterisation.sigma() return flex.double() @property def U_params(self) -> np.array: """Get the parameters of the orientation parameterisation""" return np.array(self._U_parameterisation.get_param_vals(), dtype=np.float64) @U_params.setter def U_params(self, params) -> None: self._U_parameterisation.set_param_vals(tuple( float(i) for i in params)) @property def B_params(self) -> np.array: """Get the parameters of the orientation parameterisation""" return np.array(self._B_parameterisation.get_param_vals(), dtype=np.float64) @B_params.setter def B_params(self, params) -> None: self._B_parameterisation.set_param_vals(tuple( float(i) for i in params)) @property def M_params(self) -> np.array: "Parameters of the mosaicity parameterisation" return self._M_parameterisation.parameters @M_params.setter def M_params(self, params) -> None: self._M_parameterisation.parameters = params @property def L_params(self) -> np.array: "Parameters of the Lambda (wavelength) parameterisation" if self._L_parameterisation is not None: return np.array(self._L_parameterisation.parameters, dtype=np.float64) return np.array([]) @L_params.setter def L_params(self, params: flex.double) -> None: if self._L_parameterisation is not None: self._L_parameterisation.parameters = params @property def dU_dp(self) -> np.array: """ Get the first derivatives of U w.r.t its parameters """ ds_dp = self._U_parameterisation.get_ds_dp() n = len(ds_dp) s = np.array([ds_dp[i] for i in range(n)], dtype=np.float64).reshape(n, 3, 3) return s @property def dB_dp(self) -> np.array: """ Get the first derivatives of B w.r.t its parameters """ ds_dp = self._B_parameterisation.get_ds_dp() n = len(ds_dp) s = np.array([ds_dp[i] for i in range(n)], dtype=np.float64).reshape(n, 3, 3) return s @property def dM_dp(self) -> np.array: """ Get the first derivatives of M w.r.t its parameters """ return self._M_parameterisation.first_derivatives() @property def dL_dp(self) -> flex.double: """ Get the first derivatives of L w.r.t its parameters """ if self._L_parameterisation is not None: return self._L_parameterisation.first_derivatives() return flex.double() @property def active_parameters(self) -> np.array: """ The active parameters in order: U, B, M, L, W """ active_params = [] if not self.is_orientation_fixed: active_params.append(self.U_params) if not self.is_unit_cell_fixed: active_params.append(self.B_params) if not self.is_mosaic_spread_fixed: active_params.append(self.M_params) if not self.is_wavelength_spread_fixed: active_params.append(self.L_params) active_params = np.concatenate(active_params) assert len(active_params) > 0 return active_params @active_parameters.setter def active_parameters(self, params) -> None: """ Set the active parameters in order: U, B, M, L, W """ if not self.is_orientation_fixed: n_U_params = len(self.U_params) temp = params[:n_U_params] params = params[n_U_params:] self.U_params = temp if not self.is_unit_cell_fixed: n_B_params = len(self.B_params) temp = params[:n_B_params] params = params[n_B_params:] self.B_params = temp if not self.is_mosaic_spread_fixed: n_M_params = self.M_params.size temp = params[:n_M_params] params = params[n_M_params:] self.M_params = np.array(temp) if not self.is_wavelength_spread_fixed: n_L_params = self.L_params.size temp = params[:n_L_params] self.L_params = temp @property def parameter_labels(self) -> List[str]: """ Get the parameter labels """ labels = [] if not self.is_orientation_fixed: labels += [f"Crystal_U_{i}" for i in range(self.U_params.size)] if not self.is_unit_cell_fixed: labels += [f"Crystal_B_{i}" for i in range(self.B_params.size)] if not self.is_mosaic_spread_fixed: labels += [f"Mosaicity_{i}" for i in range(self.M_params.size)] if not self.is_wavelength_spread_fixed: labels.append("Wavelength_Spread") assert len(labels) > 0 return labels
from dials.algorithms.refinement.restraints.restraints import SingleUnitCellTie uct = SingleUnitCellTie(xluc_param, [None] * 6, [None] * 6) from scitbx.math import angle_derivative_wrt_vectors B = matrix.sqr(crystal.get_B()) O = (B.transpose()).inverse() a, b, c, aa, bb, cc = crystal.get_unit_cell().parameters() aa *= DEG2RAD bb *= DEG2RAD cc *= DEG2RAD Ut = matrix.sqr(crystal.get_U()).transpose() avec, bvec, cvec = [Ut * vec for vec in crystal.get_real_space_vectors()] # calculate d[B^T]/dp dB_dp = xluc_param.get_ds_dp() dBT_dp = [dB.transpose() for dB in dB_dp] # calculate d[O]/dp dO_dp = [-O * dBT * O for dBT in dBT_dp] # function to get analytical derivative of angles wrt vectors def dangle(u, v): return [matrix.col(e) for e in angle_derivative_wrt_vectors(u, v)] dalpha_db, dalpha_dc = dangle(bvec, cvec) dbeta_da, dbeta_dc = dangle(avec, cvec) dgamma_da, dgamma_db = dangle(avec, bvec)
def crystal_parameters(): from dxtbx.model.experiment.experiment_list import ExperimentListFactory from dials.array_family import flex from scitbx import matrix experiments = ExperimentListFactory.from_json_file("experiments.json") reflections = flex.reflection_table.from_pickle("reflections.pickle") beam = experiments[0].beam detector = experiments[0].detector goniometer = experiments[0].goniometer scan = experiments[0].scan crystal = experiments[0].crystal from dials.algorithms.refinement.parameterisation.crystal_parameters import ( CrystalUnitCellParameterisation, ) parameters = CrystalUnitCellParameterisation(crystal) print("") print("Parameters") for param in parameters.get_params(): print(param.name, param.value) print("") print("db/dp") for db_dp in parameters.get_ds_dp(): print(db_dp.round(5)) print("") print("B matrix") B = crystal.get_B().round(7) print(B) print(B.transpose() * B) # from rstbx.symmetry.constraints.parameter_reduction import symmetrize_reduce_enlarge # temp = symmetrize_reduce_enlarge(crystal.get_space_group()) # temp.set_orientation(crystal.get_B()) # independent = temp.forward_independent_parameters() # print independent # new_mm = temp.enlarge(independent) # temp.Bconverter.validate_and_setG(new_mm) # B = temp.Bconverter.back_as_orientation() # print matrix.sqr(B.reciprocal_matrix()).round(5) # from cctbx import sgtbx # from rstbx.symmetry.constraints import AGconvert # constraints = sgtbx.tensor_rank_2_constraints(space_group=crystal.get_space_group(), reciprocal_space=True) # params = constraints.all_params(independent_params=tuple(p.value for p in # parameters.get_params())) # from cctbx.crystal_orientation import crystal_orientation # converter = AGconvert() # converter.forward(crystal_orientation(crystal.get_B(), False)) # converter.validate_and_setG(params) # orientation = converter.back_as_orientation() # print orientation # print "" print("SUM(p * dp/dp)") MAT = [ p.value * db_dp for p, db_dp in zip(parameters.get_params(), parameters.get_ds_dp()) ] COV = sum(MAT[1:], MAT[0]) print(2 * COV.round(7)) print((2 * COV - crystal.get_B()).round(7))
def test(dials_regression): from libtbx.phil import parse from libtbx.test_utils import approx_equal from scitbx import matrix from dials.algorithms.refinement.parameterisation.crystal_parameters import ( CrystalOrientationParameterisation, CrystalUnitCellParameterisation, ) # Get modules to build models and minimiser using PHIL from dials.tests.algorithms.refinement import setup_geometry # Symmetry constrained parameterisation for the unit cell DEG2RAD = math.pi / 180.0 RAD2DEG = 180.0 / math.pi master_phil = parse( """ include scope dials.tests.algorithms.refinement.geometry_phil include scope dials.tests.algorithms.refinement.minimiser_phil """, process_includes=True, ) # make cell more oblique args = [ "a.direction.close_to.sd=5", "b.direction.close_to.sd=5", "c.direction.close_to.sd=5", ] models = setup_geometry.Extract(master_phil, cmdline_args=args) crystal = models.crystal # a hexagonal crystal is a good test case for behaviour of oblique cells do_hexagonal = True if do_hexagonal: from dxtbx.model.experiment_list import ExperimentListFactory experiments = ExperimentListFactory.from_json_file( os.path.join( dials_regression, "refinement_test_data", "multi_stills", "combined_experiments.json", ), check_format=False, ) crystal = experiments[0].crystal # derive finite difference gradients of various quantities wrt each param def check_fd_gradients(parameterisation): mp = parameterisation p_vals = mp.get_param_vals() deltas = [1.0e-7 for p in p_vals] assert len(deltas) == len(p_vals) fd_grad = [] # get matrix to unset rotations of unit cell vectors Ut = matrix.sqr(mp.get_model().get_U()).transpose() for i, delta in enumerate(deltas): val = p_vals[i] p_vals[i] -= delta / 2.0 mp.set_param_vals(p_vals) rev_uc = mp.get_model().get_unit_cell().parameters() rev_vec = mp.get_model().get_real_space_vectors() rev_vec = [Ut * vec for vec in rev_vec] rev_B = matrix.sqr(mp.get_model().get_B()) rev_O = rev_B.transpose().inverse() p_vals[i] += delta mp.set_param_vals(p_vals) fwd_uc = mp.get_model().get_unit_cell().parameters() fwd_vec = mp.get_model().get_real_space_vectors() fwd_vec = [Ut * vec for vec in fwd_vec] fwd_B = matrix.sqr(mp.get_model().get_B()) fwd_O = fwd_B.transpose().inverse() fd_uc = [(f - r) / delta for f, r in zip(fwd_uc, rev_uc)] fd_vec = [(f - r) / delta for f, r in zip(fwd_vec, rev_vec)] fd_B = (fwd_B - rev_B) / delta fd_O = (fwd_O - rev_O) / delta fd_grad.append( { "da_dp": fd_uc[0], "db_dp": fd_uc[1], "dc_dp": fd_uc[2], "daa_dp": fd_uc[3], "dbb_dp": fd_uc[4], "dcc_dp": fd_uc[5], "davec_dp": fd_vec[0], "dbvec_dp": fd_vec[1], "dcvec_dp": fd_vec[2], "dB_dp": fd_B, "dO_dp": fd_O, } ) p_vals[i] = val # return to the initial state mp.set_param_vals(p_vals) return fd_grad assert CrystalOrientationParameterisation(crystal) xluc_param = CrystalUnitCellParameterisation(crystal) from dials.algorithms.refinement.restraints.restraints import SingleUnitCellTie assert SingleUnitCellTie(xluc_param, [0] * 6, [0] * 6) from scitbx.math import angle_derivative_wrt_vectors B = matrix.sqr(crystal.get_B()) O = (B.transpose()).inverse() a, b, c, aa, bb, cc = crystal.get_unit_cell().parameters() aa *= DEG2RAD bb *= DEG2RAD cc *= DEG2RAD Ut = matrix.sqr(crystal.get_U()).transpose() avec, bvec, cvec = [Ut * vec for vec in crystal.get_real_space_vectors()] # calculate d[B^T]/dp dB_dp = xluc_param.get_ds_dp() dBT_dp = [dB.transpose() for dB in dB_dp] # calculate d[O]/dp dO_dp = [-O * dBT * O for dBT in dBT_dp] # function to get analytical derivative of angles wrt vectors def dangle(u, v): return [matrix.col(e) for e in angle_derivative_wrt_vectors(u, v)] dalpha_db, dalpha_dc = dangle(bvec, cvec) dbeta_da, dbeta_dc = dangle(avec, cvec) dgamma_da, dgamma_db = dangle(avec, bvec) # get all FD derivatives fd_grad = check_fd_gradients(xluc_param) # look at each parameter for i, dO in enumerate(dO_dp): # print # print "***** PARAMETER {0} *****".format(i) # print "dB_dp analytical" # print dB_dp[i] # print "dB_dp FD" # print fd_grad[i]['dB_dp'] # print # dB_dp is good. What about dO_dp? # print "O MATRIX" # print "dO_dp analytical" # print dO.round(6) # print "dO_dp FD" # print fd_grad[i]['dO_dp'].round(6) # print assert approx_equal(dO, fd_grad[i]["dO_dp"]) # extract derivatives of each unit cell vector wrt p dav_dp, dbv_dp, dcv_dp = dO.transpose().as_list_of_lists() dav_dp = matrix.col(dav_dp) dbv_dp = matrix.col(dbv_dp) dcv_dp = matrix.col(dcv_dp) # check these are correct vs FD # print "CELL VECTORS" # diff = dav_dp - fd_grad[i]['davec_dp'] # print 2 * diff.length() / (dav_dp.length() + fd_grad[i]['davec_dp'].length()) * 100 # print 'davec_dp analytical: {0:.5f} {1:.5f} {2:.5f}'.format(*dav_dp.elems) # print 'davec_dp finite diff: {0:.5f} {1:.5f} {2:.5f}'.format(*fd_grad[i]['davec_dp'].elems) assert approx_equal(dav_dp, fd_grad[i]["davec_dp"]) # diff = dbv_dp - fd_grad[i]['dbvec_dp'] # print 2 * diff.length() / (dbv_dp.length() + fd_grad[i]['dbvec_dp'].length()) * 100 # print 'dbvec_dp analytical: {0:.5f} {1:.5f} {2:.5f}'.format(*dbv_dp.elems) # print 'dbvec_dp finite diff: {0:.5f} {1:.5f} {2:.5f}'.format(*fd_grad[i]['dbvec_dp'].elems) assert approx_equal(dbv_dp, fd_grad[i]["dbvec_dp"]) # diff = dcv_dp - fd_grad[i]['dcvec_dp'] # print 2 * diff.length() / (dcv_dp.length() + fd_grad[i]['dcvec_dp'].length()) * 100 # print 'dcvec_dp analytical: {0:.5f} {1:.5f} {2:.5f}'.format(*dcv_dp.elems) # print 'dcvec_dp finite diff: {0:.5f} {1:.5f} {2:.5f}'.format(*fd_grad[i]['dcvec_dp'].elems) # print assert approx_equal(dcv_dp, fd_grad[i]["dcvec_dp"]) # print "CELL LENGTHS" da_dp = 1.0 / a * avec.dot(dav_dp) # print "d[a]/dp{2} analytical: {0:.5f} FD: {1:.5f}".format(da_dp, fd_grad[i]['da_dp'], i) assert approx_equal(da_dp, fd_grad[i]["da_dp"]) db_dp = 1.0 / b * bvec.dot(dbv_dp) # print "d[b]/dp{2} analytical: {0:.5f} FD: {1:.5f}".format(db_dp, fd_grad[i]['db_dp'], i) assert approx_equal(db_dp, fd_grad[i]["db_dp"]) dc_dp = 1.0 / c * cvec.dot(dcv_dp) # print "d[c]/dp{2} analytical: {0:.5f} FD: {1:.5f}".format(dc_dp, fd_grad[i]['dc_dp'], i) assert approx_equal(dc_dp, fd_grad[i]["dc_dp"]) # print # print "CELL ANGLES" daa_dp = RAD2DEG * (dbv_dp.dot(dalpha_db) + dcv_dp.dot(dalpha_dc)) dbb_dp = RAD2DEG * (dav_dp.dot(dbeta_da) + dcv_dp.dot(dbeta_dc)) dcc_dp = RAD2DEG * (dav_dp.dot(dgamma_da) + dbv_dp.dot(dgamma_db)) # print "d[alpha]/dp{2} analytical: {0:.5f} FD: {1:.5f}".format(daa_dp, fd_grad[i]['daa_dp'], i) # print "d[beta]/dp{2} analytical: {0:.5f} FD: {1:.5f}".format(dbb_dp, fd_grad[i]['dbb_dp'], i) # print "d[gamma]/dp{2} analytical: {0:.5f} FD: {1:.5f}".format(dcc_dp, fd_grad[i]['dcc_dp'], i) assert approx_equal(daa_dp, fd_grad[i]["daa_dp"]) assert approx_equal(dbb_dp, fd_grad[i]["dbb_dp"]) assert approx_equal(dcc_dp, fd_grad[i]["dcc_dp"])
def test(): import random import textwrap from cctbx.uctbx import unit_cell from libtbx.test_utils import approx_equal def random_direction_close_to(vector): return vector.rotate_around_origin( matrix.col((random.random(), random.random(), random.random())).normalize(), random.gauss(0, 1.0), deg=True, ) # make a random P1 crystal and parameterise it a = random.uniform(10, 50) * random_direction_close_to( matrix.col((1, 0, 0))) b = random.uniform(10, 50) * random_direction_close_to( matrix.col((0, 1, 0))) c = random.uniform(10, 50) * random_direction_close_to( matrix.col((0, 0, 1))) xl = Crystal(a, b, c, space_group_symbol="P 1") xl_op = CrystalOrientationParameterisation(xl) xl_ucp = CrystalUnitCellParameterisation(xl) null_mat = matrix.sqr((0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) # compare analytical and finite difference derivatives an_ds_dp = xl_op.get_ds_dp() fd_ds_dp = get_fd_gradients(xl_op, [1.0e-6 * pi / 180] * 3) for e, f in zip(an_ds_dp, fd_ds_dp): assert approx_equal((e - f), null_mat, eps=1.0e-6) an_ds_dp = xl_ucp.get_ds_dp() fd_ds_dp = get_fd_gradients(xl_ucp, [1.0e-7] * xl_ucp.num_free()) for e, f in zip(an_ds_dp, fd_ds_dp): assert approx_equal((e - f), null_mat, eps=1.0e-6) # random initial orientations with a random parameter shift at each attempts = 100 for i in range(attempts): # make a random P1 crystal and parameterise it a = random.uniform(10, 50) * random_direction_close_to( matrix.col((1, 0, 0))) b = random.uniform(10, 50) * random_direction_close_to( matrix.col((0, 1, 0))) c = random.uniform(10, 50) * random_direction_close_to( matrix.col((0, 0, 1))) xl = Crystal(a, b, c, space_group_symbol="P 1") xl_op = CrystalOrientationParameterisation(xl) xl_uc = CrystalUnitCellParameterisation(xl) # apply a random parameter shift to the orientation p_vals = xl_op.get_param_vals() p_vals = random_param_shift( p_vals, [1000 * pi / 9, 1000 * pi / 9, 1000 * pi / 9]) xl_op.set_param_vals(p_vals) # compare analytical and finite difference derivatives xl_op_an_ds_dp = xl_op.get_ds_dp() xl_op_fd_ds_dp = get_fd_gradients(xl_op, [1.0e-5 * pi / 180] * 3) # apply a random parameter shift to the unit cell. We have to # do this in a way that is respectful to metrical constraints, # so don't modify the parameters directly; modify the cell # constants and extract the new parameters cell_params = xl.get_unit_cell().parameters() cell_params = random_param_shift(cell_params, [1.0] * 6) new_uc = unit_cell(cell_params) newB = matrix.sqr(new_uc.fractionalization_matrix()).transpose() S = symmetrize_reduce_enlarge(xl.get_space_group()) S.set_orientation(orientation=newB) X = S.forward_independent_parameters() xl_uc.set_param_vals(X) xl_uc_an_ds_dp = xl_ucp.get_ds_dp() # now doing finite differences about each parameter in turn xl_uc_fd_ds_dp = get_fd_gradients(xl_ucp, [1.0e-7] * xl_ucp.num_free()) for j in range(3): assert approx_equal((xl_op_fd_ds_dp[j] - xl_op_an_ds_dp[j]), null_mat, eps=1.0e-6), textwrap.dedent("""\ Failure in try {i} failure for parameter number {j} of the orientation parameterisation with fd_ds_dp = {fd} and an_ds_dp = {an} so that difference fd_ds_dp - an_ds_dp = {diff} """).format( i=i, j=j, fd=xl_op_fd_ds_dp[j], an=xl_op_an_ds_dp[j], diff=xl_op_fd_ds_dp[j] - xl_op_an_ds_dp[j], ) for j in range(xl_ucp.num_free()): assert approx_equal((xl_uc_fd_ds_dp[j] - xl_uc_an_ds_dp[j]), null_mat, eps=1.0e-6), textwrap.dedent("""\ Failure in try {i} failure for parameter number {j} of the unit cell parameterisation with fd_ds_dp = {fd} and an_ds_dp = {an} so that difference fd_ds_dp - an_ds_dp = {diff} """).format( i=i, j=j, fd=xl_uc_fd_ds_dp[j], an=xl_uc_an_ds_dp[j], diff=xl_uc_fd_ds_dp[j] - xl_uc_an_ds_dp[j], )
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 :-)"
class ModelState(object): """ A class to keep track of the model state """ def __init__( self, experiment, mosaicity_parameterisation, fix_mosaic_spread=False, fix_wavelength_spread=False, fix_unit_cell=False, fix_orientation=False, ): """ Initialise the model state """ # Save the crystal model self.experiment = experiment self.crystal = experiment.crystal # The U and P parameterisation self.U_parameterisation = CrystalOrientationParameterisation( self.crystal) self.B_parameterisation = CrystalUnitCellParameterisation(self.crystal) # The M, L and W parameterisations self.M_parameterisation = mosaicity_parameterisation # Set the flags to fix parameters self._is_mosaic_spread_fixed = fix_mosaic_spread self._is_wavelength_spread_fixed = fix_wavelength_spread self._is_unit_cell_fixed = fix_unit_cell self._is_orientation_fixed = fix_orientation def is_orientation_fixed(self): """ Return whether the orientation is fixed """ return self._is_orientation_fixed def is_unit_cell_fixed(self): """ Return whether the unit cell is fixed """ return self._is_unit_cell_fixed def is_mosaic_spread_fixed(self): """ Return whether the mosaic spread is fixed """ return self._is_mosaic_spread_fixed def is_mosaic_spread_angular(self): """ Return whether the mosaic spread is angular """ return self.M_parameterisation.is_angular() def is_wavelength_spread_fixed(self): """ Return whether the wavelength spread is fixed """ return self._is_wavelength_spread_fixed def get_unit_cell(self): """ Get the crystal unit cell """ return self.crystal.get_unit_cell() def get_U(self): """ Get the crystal U matrix """ return matrix.sqr(self.crystal.get_U()) def get_B(self): """ Get the crystal B matrix """ return matrix.sqr(self.crystal.get_B()) def get_A(self): """ Get the crystal A matrix """ return matrix.sqr(self.crystal.get_A()) def get_M(self): """ Get the Sigma M matrix """ return self.M_parameterisation.sigma() def get_U_params(self): """ Get the U parameters """ return flex.double(self.U_parameterisation.get_param_vals()) def get_B_params(self): """ Get the B parameters """ return flex.double(self.B_parameterisation.get_param_vals()) def get_M_params(self): """ Get the M parameters """ return self.M_parameterisation.parameters() def set_U_params(self, params): """ Set the U parameters """ return self.U_parameterisation.set_param_vals(params) def set_B_params(self, params): """ Set the B parameters """ return self.B_parameterisation.set_param_vals(params) def set_M_params(self, params): """ Set the M parameters """ return self.M_parameterisation.set_parameters(params) def num_U_params(self): """ Get the number of U parameters """ return len(self.get_U_params()) def num_B_params(self): """ Get the number of B parameters """ return len(self.get_B_params()) def num_M_params(self): """ Get the number of M parameters """ return len(self.get_M_params()) def get_dU_dp(self): """ Get the first derivatives of U w.r.t its parameters """ return flex.mat3_double(self.U_parameterisation.get_ds_dp()) def get_dB_dp(self): """ Get the first derivatives of B w.r.t its parameters """ return flex.mat3_double(self.B_parameterisation.get_ds_dp()) def get_dM_dp(self): """ Get the first derivatives of M w.r.t its parameters """ return self.M_parameterisation.first_derivatives() def get_active_parameters(self): """ Get the active parameters in order: U, B, M, L, W """ active_params = flex.double() if not self._is_orientation_fixed: active_params.extend(self.get_U_params()) if not self._is_unit_cell_fixed: active_params.extend(self.get_B_params()) if not self._is_mosaic_spread_fixed: active_params.extend(self.get_M_params()) if not self._is_wavelength_spread_fixed: active_params.extend(self.get_L_params()) assert len(active_params) > 0 return active_params def set_active_parameters(self, params): """ Set the active parameters in order: U, B, M, L, W """ if not self._is_orientation_fixed: temp = params[:self.num_U_params()] params = params[self.num_U_params():] self.set_U_params(temp) if not self._is_unit_cell_fixed: temp = params[:self.num_B_params()] params = params[self.num_B_params():] self.set_B_params(temp) if not self._is_mosaic_spread_fixed: temp = params[:self.num_M_params()] params = params[self.num_M_params():] self.set_M_params(temp) if not self._is_wavelength_spread_fixed: temp = params[:self.num_L_params()] params = params[self.num_L_params():] self.set_L_params(temp) def get_labels(self): """ Get the parameter labels """ labels = [] if not self._is_orientation_fixed: for i in range(len(self.get_U_params())): labels.append("Crystal_U_%d" % i) if not self._is_unit_cell_fixed: for i in range(len(self.get_B_params())): labels.append("Crystal_B_%d" % i) if not self._is_mosaic_spread_fixed: for i in range(len(self.get_M_params())): labels.append("Mosaicity_%d" % i) if not self._is_wavelength_spread_fixed: labels.append("Wavelength_Spread") assert len(labels) > 0 return labels
uct = SingleUnitCellTie(xluc_param, [None]*6, [None]*6) from dials.algorithms.refinement.refinement_helpers import \ AngleDerivativeWrtVectorElts B = crystal.get_B() O = (B.transpose()).inverse() a, b, c, aa, bb, cc = crystal.get_unit_cell().parameters() aa *= DEG2RAD bb *= DEG2RAD cc *= DEG2RAD Ut = crystal.get_U().transpose() avec, bvec, cvec = [Ut * vec for vec in crystal.get_real_space_vectors()] # calculate d[B^T]/dp dB_dp = xluc_param.get_ds_dp() dBT_dp = [dB.transpose() for dB in dB_dp] # calculate d[O]/dp dO_dp = [-O * dBT * O for dBT in dBT_dp] # objects to get derivative of angles wrt vectors dalpha = AngleDerivativeWrtVectorElts(bvec, cvec) dbeta = AngleDerivativeWrtVectorElts(avec, cvec) dgamma = AngleDerivativeWrtVectorElts(avec, bvec) # get all FD derivatives fd_grad = check_fd_gradients(xluc_param) # look at each parameter for i, dO in enumerate(dO_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 :-)")