def test_change_of_basis_ops_to_minimum_cell_mpro(): input_ucs = [ (46.023, 55.001, 64.452, 64.744, 78.659, 89.824), (44.747, 53.916, 62.554, 114.985, 99.610, 90.736), ] # Setup the input experiments and reflection tables expts = ExperimentList() for uc in input_ucs: uc = uctbx.unit_cell(uc) sg = sgtbx.space_group_info("P1").group() B = scitbx.matrix.sqr(uc.fractionalization_matrix()).transpose() expts.append( Experiment(crystal=Crystal(B, space_group=sg, reciprocal=True))) # Actually run the method we are testing cb_ops = change_of_basis_ops_to_minimum_cell( expts, max_delta=5, relative_length_tolerance=0.05, absolute_angle_tolerance=2) expts.change_basis(cb_ops, in_place=True) assert symmetry.unit_cells_are_similar_to( expts, median_unit_cell(expts), relative_length_tolerance=0.05, absolute_angle_tolerance=2, )
def test_change_of_basis_ops_to_minimum_cell_with_outlier(): symmetries = [ crystal.symmetry(unit_cell=uc, space_group="P1") for uc in ( (52.8868, 52.8868, 333.522, 90, 90, 120), (52.6503, 53.0292, 333.783, 89.9872, 89.2247, 60.8078), (52.9571, 53.0005, 334.255, 90.0493, 90.0042, 119.893), ( 54.4465, 56.5677, 355.775, 93.4376, 90.0999, 118.256, ), # This is an outlier (52.9235, 52.9235, 335.296, 90, 90, 120), (53.4531, 53.4531, 322.909, 90, 90, 120), ) ] # Setup the input experiments and reflection tables expts = ExperimentList() for cs in symmetries: B = scitbx.matrix.sqr( cs.unit_cell().fractionalization_matrix()).transpose() expts.append( Experiment(crystal=Crystal( B, space_group=cs.space_group(), reciprocal=True))) # Actually run the method we are testing cb_ops = change_of_basis_ops_to_minimum_cell( expts, max_delta=5, relative_length_tolerance=0.05, absolute_angle_tolerance=2) assert cb_ops.count(None) == 1 assert cb_ops[3] is None expts = ExperimentList( [expt for expt, cb_op in zip(expts, cb_ops) if cb_op]) cb_ops = [cb_op for cb_op in cb_ops if cb_op] expts.change_basis(cb_ops, in_place=True) assert symmetry.unit_cells_are_similar_to( expts, median_unit_cell(expts), relative_length_tolerance=0.05, absolute_angle_tolerance=2, )
def test_change_of_basis_ops_to_minimum_cell_1037(mocker): # See https://github.com/dials/dials/issues/1037 input_ucs = [ ( 4.805202948916906, 12.808064769657364, 16.544899201125446, 106.45808502003258, 90.0065567098825, 100.77735674275475, ), ( 4.808011343212577, 12.821894835790472, 16.557339561965573, 106.48431244651402, 90.0252848479048, 100.77252933676507, ), ( 4.8096632137789985, 12.815648858527567, 16.55931712239122, 106.48990701341536, 90.01703141314147, 100.80397887485773, ), ( 4.807294085194974, 12.822386757910516, 16.560411742466663, 106.43185845358086, 90.02067929544215, 100.79522302759383, ), ] # Setup the input experiments and reflection tables expts = ExperimentList() for uc in input_ucs: uc = uctbx.unit_cell(uc) sg = sgtbx.space_group_info("P1").group() B = scitbx.matrix.sqr(uc.fractionalization_matrix()).transpose() expts.append( Experiment(crystal=Crystal(B, space_group=sg, reciprocal=True))) # We want to spy on the return value of this function mocker.spy(symmetry, "unit_cells_are_similar_to") # Actually run the method we are testing cb_ops = change_of_basis_ops_to_minimum_cell( expts, max_delta=5, relative_length_tolerance=0.05, absolute_angle_tolerance=2) import pytest_mock if getattr(pytest_mock, "version", "").startswith("1."): assert symmetry.unit_cells_are_similar_to.return_value is True else: assert symmetry.unit_cells_are_similar_to.spy_return is True cb_ops_as_xyz = [cb_op.as_xyz() for cb_op in cb_ops] assert len(set(cb_ops_as_xyz)) == 1 # Actual cb_ops are machine dependent (sigh) assert cb_ops_as_xyz[0] in ("x,y,z", "-x,y,-z", "x-y,-y,-z")
def test_map_to_minimum_cell(): # Input and expected output input_ucs = [ (39.7413, 183.767, 140.649, 90, 90, 90), (40.16, 142.899, 92.4167, 90, 102.48, 90), (180.613, 40.1558, 142.737, 90, 90.0174, 90), ] input_sgs = ["C 2 2 21", "P 1 2 1", "C 1 2 1"] input_hkl = [ [(1, -75, -71), (1, -73, -70), (1, -71, -69)], [(14, -37, -36), (-2, -35, -46), (-3, -34, -47)], [(-31, -5, -3), (-25, -3, -3), (-42, -8, -2)], ] expected_ucs = [ (39.7413, 94.00755450320204, 140.649, 90.0, 90.0, 77.79717980856927), (40.16, 92.46399390642911, 142.899, 90.0, 90.0, 77.3882749092846), ( 40.1558, 92.51154528306184, 142.73699999999997, 89.9830147351441, 90.0, 77.46527404307477, ), ] expected_output_hkl = [ [(-1, 37, -71), (-1, 36, -70), (-1, 35, -69)], [(-14, 22, 37), (2, 48, 35), (3, 50, 34)], [(-5, 13, -3), (-3, 11, -3), (-8, 17, -2)], ] # Setup the input experiments and reflection tables expts = ExperimentList() reflections = [] for uc, sg, hkl in zip(input_ucs, input_sgs, input_hkl): uc = uctbx.unit_cell(uc) sg = sgtbx.space_group_info(sg).group() B = scitbx.matrix.sqr(uc.fractionalization_matrix()).transpose() expts.append( Experiment(crystal=Crystal(B, space_group=sg, reciprocal=True))) refl = flex.reflection_table() refl["miller_index"] = flex.miller_index(hkl) reflections.append(refl) # Actually run the method we are testing cb_ops = change_of_basis_ops_to_minimum_cell( expts, max_delta=5, relative_length_tolerance=0.05, absolute_angle_tolerance=2) cb_ops_as_xyz = [cb_op.as_xyz() for cb_op in cb_ops] # Actual cb_ops are machine dependent (sigh) assert (cb_ops_as_xyz == [ "-x+y,-2*y,z", "-x+z,-z,-y", "x+y,-2*x,z", ] or cb_ops_as_xyz == ["x-y,2*y,z", "x-z,z,-y", "-x-y,2*x,z"]) expts_min, reflections = apply_change_of_basis_ops(expts, reflections, cb_ops) # Verify that the unit cells have been transformed as expected for expt, uc in zip(expts, expected_ucs): assert expt.crystal.get_unit_cell().parameters() == pytest.approx( uc, abs=4e-2) # Space group should be set to P1 assert [ expt.crystal.get_space_group().type().number() for expt in expts_min ] == [ 1, 1, 1, ] # Verify that the reflections have been reindexed as expected # Because the exact choice of minimum cell can be platform-dependent, # compare the magnitude, but not the sign of the output hkl values for refl, expected_hkl in zip(reflections, expected_output_hkl): for hkl, e_hkl in zip(refl["miller_index"], expected_hkl): assert [abs(h) for h in hkl] == [abs(eh) for eh in e_hkl]
def __init__(self, experiments, reflections, uuid_cache_in, params=None, do_plot=False, i_plot=None, output_dir=None): super(dials_cl_cosym_wrapper, self).__init__( events=["run_cosym", "performed_unit_cell_clustering"]) if params is None: params = phil_scope.extract() self.params = params self._reflections = [] for refl, expt in zip(reflections, experiments): sel = get_selection_for_valid_image_ranges(refl, expt) self._reflections.append(refl.select(sel)) self._experiments, self._reflections = self._filter_min_reflections( experiments, self._reflections, uuid_cache_in) self.ids_to_identifiers_map = {} for table in self._reflections: self.ids_to_identifiers_map.update(table.experiment_identifiers()) self.identifiers_to_ids_map = { value: key for key, value in self.ids_to_identifiers_map.items() } if len(self._experiments) > 1: # perform unit cell clustering identifiers = self._unit_cell_clustering(self._experiments) if len(identifiers) < len(self._experiments): logger.info( "Selecting subset of %i datasets for cosym analysis: %s" % (len(identifiers), str(identifiers))) self._experiments, self._reflections = select_datasets_on_identifiers( self._experiments, self._reflections, use_datasets=identifiers) self.uuid_cache = [ self.uuid_cache[int(id)] for id in identifiers ] # Map experiments and reflections to minimum cell cb_ops = change_of_basis_ops_to_minimum_cell( self._experiments, params.lattice_symmetry_max_delta, params.relative_length_tolerance, params.absolute_angle_tolerance, ) in_cb_ops = len(cb_ops) exclude = [ expt.identifier for expt, cb_op in zip(self._experiments, cb_ops) if not cb_op ] if len(exclude): logger.info( "Rejecting {} datasets from cosym analysis "\ "(couldn't determine consistent cb_op to minimum cell):\n"\ "{}".format(len(exclude), exclude) ) self._experiments, self._reflections = select_datasets_on_identifiers( self._experiments, self._reflections, exclude_datasets=exclude) cb_ops = list(filter(None, cb_ops)) ex_cb_ops = len(cb_ops) #Normally we expect that all the cb_ops are the same (applicable for PSI with P63) assertion_dict = {} for cb_op in cb_ops: key_ = cb_op.as_hkl() assertion_dict[key_] = assertion_dict.get(key_, 0) assertion_dict[key_] += 1 if len(assertion_dict) != 1: # unexpected, there is normally only 1 cb operator to minimum cell from libtbx.mpi4py import MPI mpi_rank = MPI.COMM_WORLD.Get_rank() mpi_size = MPI.COMM_WORLD.Get_size() print( "RANK %02d, # experiments %d, after exclusion %d, unexpectedly there are %d unique cb_ops: %s" % (mpi_rank, in_cb_ops, ex_cb_ops, len(assertion_dict), ", ".join([ "%s:%d" % (key, assertion_dict[key]) for key in assertion_dict ]))) # revisit with different use cases later # In fact we need all cb_ops to match because the user might supply # a custom reindexing operator and we need to consistently tranform # it from the conventional basis into the minimum basis. Therefore, # force them all to match, but make sure user is aware. if not params.single_cb_op_to_minimum: raise RuntimeError( 'There are >1 different cb_ops to minimum and \ cosym.single_cb_op_to_minimum is not True') else: best_cb_op_str = max(assertion_dict, key=assertion_dict.get) best_cb_op = None for cb_op in cb_ops: if cb_op.as_hkl() == best_cb_op_str: best_cb_op = cb_op break assert best_cb_op is not None cb_ops = [best_cb_op] * len(cb_ops) self.cb_op_to_minimum = cb_ops # Eliminate reflections that are systematically absent due to centring # of the lattice, otherwise they would lead to non-integer miller indices # when reindexing to a primitive setting self._reflections = eliminate_sys_absent(self._experiments, self._reflections) self._experiments, self._reflections = apply_change_of_basis_ops( self._experiments, self._reflections, cb_ops) # transform models into miller arrays datasets = filtered_arrays_from_experiments_reflections( self.experiments, self.reflections, outlier_rejection_after_filter=False, partiality_threshold=params.partiality_threshold, ) datasets = [ ma.as_anomalous_array().merge_equivalents().array() for ma in datasets ] # opportunity here to subclass as defined above, instead of the dials-implemented version self.cosym_analysis = CosymAnalysis( datasets, self.params, do_plot=do_plot, i_plot=i_plot, plot_fname=self.params.plot.filename, plot_format=self.params.plot.format, output_dir=output_dir, cb_op=cb_ops[0])
def __init__(self, experiments, reflections, params=None): super(cosym, self).__init__( events=["run_cosym", "performed_unit_cell_clustering"]) if params is None: params = phil_scope.extract() self.params = params self._reflections = [] for refl, expt in zip(reflections, experiments): sel = get_selection_for_valid_image_ranges(refl, expt) self._reflections.append(refl.select(sel)) self._experiments, self._reflections = self._filter_min_reflections( experiments, self._reflections) self.ids_to_identifiers_map = {} for table in self._reflections: self.ids_to_identifiers_map.update(table.experiment_identifiers()) self.identifiers_to_ids_map = { value: key for key, value in self.ids_to_identifiers_map.items() } if len(self._experiments) > 1: # perform unit cell clustering identifiers = self._unit_cell_clustering(self._experiments) if len(identifiers) < len(self._experiments): logger.info( "Selecting subset of %i datasets for cosym analysis: %s", len(identifiers), str(identifiers), ) self._experiments, self._reflections = select_datasets_on_identifiers( self._experiments, self._reflections, use_datasets=identifiers) # Map experiments and reflections to minimum cell cb_ops = change_of_basis_ops_to_minimum_cell( self._experiments, params.lattice_symmetry_max_delta, params.relative_length_tolerance, params.absolute_angle_tolerance, ) exclude = [ expt.identifier for expt, cb_op in zip(self._experiments, cb_ops) if not cb_op ] if len(exclude): logger.info( f"Rejecting {len(exclude)} datasets from cosym analysis " f"(couldn't determine consistent cb_op to minimum cell):\n" f"{exclude}", ) self._experiments, self._reflections = select_datasets_on_identifiers( self._experiments, self._reflections, exclude_datasets=exclude) cb_ops = list(filter(None, cb_ops)) # Eliminate reflections that are systematically absent due to centring # of the lattice, otherwise they would lead to non-integer miller indices # when reindexing to a primitive setting self._reflections = eliminate_sys_absent(self._experiments, self._reflections) self._experiments, self._reflections = apply_change_of_basis_ops( self._experiments, self._reflections, cb_ops) # transform models into miller arrays datasets = filtered_arrays_from_experiments_reflections( self.experiments, self.reflections, outlier_rejection_after_filter=False, partiality_threshold=params.partiality_threshold, ) datasets = [ ma.as_anomalous_array().merge_equivalents().array() for ma in datasets ] self.cosym_analysis = CosymAnalysis(datasets, self.params)
def __init__(self, experiments, reflections, uuid_cache_in, params=None, do_plot=False, i_plot=None, output_dir=None): super(dials_cl_cosym_wrapper, self).__init__( events=["run_cosym", "performed_unit_cell_clustering"]) if params is None: params = phil_scope.extract() self.params = params self._reflections = [] for refl, expt in zip(reflections, experiments): sel = get_selection_for_valid_image_ranges(refl, expt) self._reflections.append(refl.select(sel)) self._experiments, self._reflections = self._filter_min_reflections( experiments, self._reflections, uuid_cache_in) self.ids_to_identifiers_map = {} for table in self._reflections: self.ids_to_identifiers_map.update(table.experiment_identifiers()) self.identifiers_to_ids_map = { value: key for key, value in self.ids_to_identifiers_map.items() } if len(self._experiments) > 1: # perform unit cell clustering identifiers = self._unit_cell_clustering(self._experiments) if len(identifiers) < len(self._experiments): logger.info( "Selecting subset of %i datasets for cosym analysis: %s" % (len(identifiers), str(identifiers))) self._experiments, self._reflections = select_datasets_on_identifiers( self._experiments, self._reflections, use_datasets=identifiers) # Map experiments and reflections to minimum cell cb_ops = change_of_basis_ops_to_minimum_cell( self._experiments, params.lattice_symmetry_max_delta, params.relative_length_tolerance, params.absolute_angle_tolerance, ) exclude = [ expt.identifier for expt, cb_op in zip(self._experiments, cb_ops) if not cb_op ] if len(exclude): logger.info( "Rejecting {} datasets from cosym analysis "\ "(couldn't determine consistent cb_op to minimum cell):\n"\ "{}".format(len(exclude), exclude) ) self._experiments, self._reflections = select_datasets_on_identifiers( self._experiments, self._reflections, exclude_datasets=exclude) cb_ops = list(filter(None, cb_ops)) # Eliminate reflections that are systematically absent due to centring # of the lattice, otherwise they would lead to non-integer miller indices # when reindexing to a primitive setting self._reflections = eliminate_sys_absent(self._experiments, self._reflections) self._experiments, self._reflections = apply_change_of_basis_ops( self._experiments, self._reflections, cb_ops) # transform models into miller arrays datasets = filtered_arrays_from_experiments_reflections( self.experiments, self.reflections, outlier_rejection_after_filter=False, partiality_threshold=params.partiality_threshold, ) datasets = [ ma.as_anomalous_array().merge_equivalents().array() for ma in datasets ] # opportunity here to subclass as defined above, instead of the dials-implemented version self.cosym_analysis = CosymAnalysis( datasets, self.params, do_plot=do_plot, i_plot=i_plot, plot_fname=self.params.plot.filename, plot_format=self.params.plot.format, output_dir=output_dir) #Fixed in subclass: parent class apparently erases the knowledge of input-to-minimum cb_ops. # without storing the op in self, we can never trace back to input setting. self.cb_op_to_minimum = cb_ops #Not sure yet, we may be assuming that all the cb_ops are the same (applicable for PSI with P63) assertion_set = set(cb_ops) assert len( assertion_set ) == 1 # guarantees all are the same; revisit with different use cases later