Beispiel #1
0
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)
Beispiel #3
0
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"])
Beispiel #5
0
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],
                                )
Beispiel #6
0
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 :-)"
Beispiel #7
0
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):
Beispiel #9
0
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 :-)")