def test_create_sph_harm_table(test_reflection_table, mock_exp): """Simple test for the spherical harmonic table, constructing the table step by step, and verifying the values of a few easy-to-calculate entries. This also acts as a test for the calc_theta_phi function as well.""" rt, exp = test_reflection_table, mock_exp reflection_table = calc_crystal_frame_vectors(rt, exp) theta_phi = calc_theta_phi(reflection_table["s0c"]) expected = [ (pi / 2.0, 0.0), (pi / 2.0, -1.0 * pi / 4.0), (pi / 2.0, -1.0 * pi / 2.0), ] for v1, v2 in zip(theta_phi, expected): assert v1 == pytest.approx(v2) theta_phi_2 = calc_theta_phi(reflection_table["s1c"]) expected = [ (pi / 4.0, 0.0), (pi / 4.0, -1.0 * pi / 4.0), (pi / 4.0, -1.0 * pi / 2.0), ] for v1, v2 in zip(theta_phi_2, expected): assert v1 == pytest.approx(v2) sph_h_t = create_sph_harm_table(theta_phi, theta_phi_2, 2) Y10 = ((3.0 / (8.0 * pi)) ** 0.5) / 2.0 Y20 = -1.0 * ((5.0 / (256.0 * pi)) ** 0.5) assert sph_h_t[1, 0] == pytest.approx(Y10) assert sph_h_t[1, 1] == pytest.approx(Y10) assert sph_h_t[1, 2] == pytest.approx(Y10) assert sph_h_t[5, 0] == pytest.approx(Y20) assert sph_h_t[5, 1] == pytest.approx(Y20) assert sph_h_t[5, 2] == pytest.approx(Y20)
def test_create_sph_harm_table(test_reflection_table, mock_exp): """Simple test for the spherical harmonic table, constructing the table step by step, and verifying the values of a few easy-to-calculate entries. This also acts as a test for the calc_theta_phi function as well.""" rt, exp = test_reflection_table, mock_exp reflection_table = calc_crystal_frame_vectors(rt, exp) theta_phi = calc_theta_phi(reflection_table["s0c"]) assert approx_equal( list(theta_phi), [(pi / 2.0, 0.0), (pi / 2.0, -1.0 * pi / 4.0), (pi / 2.0, -1.0 * pi / 2.0)], ) theta_phi_2 = calc_theta_phi(reflection_table["s1c"]) assert approx_equal( list(theta_phi_2), [(pi / 4.0, 0.0), (pi / 4.0, -1.0 * pi / 4.0), (pi / 4.0, -1.0 * pi / 2.0)], ) sph_h_t = create_sph_harm_table(theta_phi, theta_phi_2, 2) Y10 = ((3.0 / (8.0 * pi)) ** 0.5) / 2.0 Y20 = -1.0 * ((5.0 / (256.0 * pi)) ** 0.5) assert approx_equal(sph_h_t[1, 0], Y10) assert approx_equal(sph_h_t[1, 1], Y10) assert approx_equal(sph_h_t[1, 2], Y10) assert approx_equal(sph_h_t[5, 0], Y20) assert approx_equal(sph_h_t[5, 1], Y20) assert approx_equal(sph_h_t[5, 2], Y20)
def test_calc_crystal_frame_vectors(test_reflection_table, mock_exp): """Test the namesake function, to check that the vectors are correctly rotated into the crystal frame.""" rt, exp = test_reflection_table, mock_exp s0_vec = (1.0, 0.0, 0.0) s1_vec = (1.0 / sqrt(2.0), 0.0, 1.0 / sqrt(2.0)) reflection_table = calc_crystal_frame_vectors(rt, exp) assert list(reflection_table["s0"]) == list( flex.vec3_double([s0_vec, s0_vec, s0_vec]) ) assert approx_equal( list(reflection_table["s0c"]), list( flex.vec3_double( [s0_vec, (1.0 / sqrt(2.0), -1.0 / sqrt(2.0), 0.0), (0.0, -1.0, 0.0)] ) ), ) assert approx_equal( list(reflection_table["s1c"]), list( flex.vec3_double( [ s1_vec, (1.0 / 2.0, -1.0 / 2.0, 1.0 / sqrt(2.0)), (0.0, -1.0 / sqrt(2.0), 1.0 / sqrt(2.0)), ] ) ), )
def test_calc_crystal_frame_vectors_single_axis_gonio( test_reflection_table, test_experiment_singleaxisgonio ): """Test the namesake function, to check that the vectors are correctly rotated into the crystal frame.""" rt, exp = test_reflection_table, test_experiment_singleaxisgonio reflection_table = calc_crystal_frame_vectors(rt, exp) # s0c and s1c are normalised. s0c points towards the source. # as the crystal rotates about the x axis, the s0 vector moves in the y-z plane towards -y expected_s0c = [ (0.0, 0.0, -1.0), (0.0, -1.0 / sqrt(2.0), -1.0 / sqrt(2.0)), (0.0, -1.0, 0.0), ] for v1, v2 in zip(reflection_table["s0c"], expected_s0c): assert v1 == pytest.approx(v2) # the s1c vector should have fixed x-component, rotating in the y-z plane towards +y expected_s1c = [ (1.0 / sqrt(2.0), 0.0, 1.0 / sqrt(2.0)), (1.0 / sqrt(2.0), 0.5, 0.5), (1.0 / sqrt(2.0), 1.0 / sqrt(2.0), 0.0), ] for v1, v2 in zip(reflection_table["s1c"], expected_s1c): assert v1 == pytest.approx(v2) # now test redefined coordinates so that the lab x-axis is along the # z-axis in the crystal frame alignment_axis = (1.0, 0.0, 0.0) reflection_table["s1c"] = align_axis_along_z( alignment_axis, reflection_table["s1c"] ) reflection_table["s0c"] = align_axis_along_z( alignment_axis, reflection_table["s0c"] ) expected_s0c_realigned = [ (1.0, 0.0, 0.0), (1.0 / sqrt(2.0), -1.0 / sqrt(2.0), 0.0), (0.0, -1.0, 0.0), ] for v1, v2 in zip(reflection_table["s0c"], expected_s0c_realigned): assert v1 == pytest.approx(v2) expected_s1c_realigned = [ (-1.0 / sqrt(2.0), 0.0, 1.0 / sqrt(2.0)), (-0.5, 0.5, 1.0 / sqrt(2.0)), (0.0, 1.0 / sqrt(2.0), 1.0 / sqrt(2.0)), ] for v1, v2 in zip(reflection_table["s1c"], expected_s1c_realigned): assert v1 == pytest.approx(v2)
def test_create_sph_harm_table(test_reflection_table, test_experiment_singleaxisgonio): """Simple test for the spherical harmonic table, constructing the table step by step, and verifying the values of a few easy-to-calculate entries. This also acts as a test for the calc_theta_phi function as well.""" rt, exp = test_reflection_table, test_experiment_singleaxisgonio reflection_table = calc_crystal_frame_vectors(rt, exp) reflection_table["s0c"] = align_axis_along_z( (1.0, 0.0, 0.0), reflection_table["s0c"] ) reflection_table["s1c"] = align_axis_along_z( (1.0, 0.0, 0.0), reflection_table["s1c"] ) theta_phi = calc_theta_phi(reflection_table["s0c"]) # so s0c vectors realigned in xyz is # (1.0, 0.0, 0.0), # (1.0 / sqrt(2.0), -1.0 / sqrt(2.0), 0.0), # (0.0, -1.0, 0.0), # physics conventions, theta from 0 to pi, phi from 0 to 2pi expected = [ (pi / 2.0, 0.0), (pi / 2.0, 7.0 * pi / 4.0), (pi / 2.0, 3.0 * pi / 2.0), ] for v1, v2 in zip(theta_phi, expected): assert v1 == pytest.approx(v2) theta_phi_2 = calc_theta_phi(reflection_table["s1c"]) expected = [ (pi / 4.0, pi), (pi / 4.0, 3 * pi / 4.0), (pi / 4.0, 1.0 * pi / 2.0), ] for v1, v2 in zip(theta_phi_2, expected): assert v1 == pytest.approx(v2) sph_h_t = create_sph_harm_table(theta_phi, theta_phi_2, 2) Y10 = ((3.0 / (8.0 * pi)) ** 0.5) / 2.0 Y20 = -1.0 * ((5.0 / (256.0 * pi)) ** 0.5) assert sph_h_t[1, 0] == pytest.approx(Y10) assert sph_h_t[1, 1] == pytest.approx(Y10) assert sph_h_t[1, 2] == pytest.approx(Y10) assert sph_h_t[5, 0] == pytest.approx(Y20) assert sph_h_t[5, 1] == pytest.approx(Y20) assert sph_h_t[5, 2] == pytest.approx(Y20)
def test_reflection_selection(dials_regression): """Use a real dataset to test the selection algorithm.""" data_dir = os.path.join(dials_regression, "xia2-28") pickle_path = os.path.join(data_dir, "20_integrated.pickle") sequence_path = os.path.join(data_dir, "20_integrated_experiments.json") reflection_table = flex.reflection_table.from_file(pickle_path) experiment = load.experiment_list(sequence_path, check_format=False)[0] reflection_table["intensity"] = reflection_table["intensity.sum.value"] reflection_table["variance"] = reflection_table["intensity.sum.variance"] reflection_table["inverse_scale_factor"] = flex.double( reflection_table.size(), 1.0) reflection_table = reflection_table.select( reflection_table["variance"] > 0) reflection_table = reflection_table.select( reflection_table.get_flags(reflection_table.flags.integrated, all=True)) Ih_table_block = IhTable( [reflection_table], experiment.crystal.get_space_group()).Ih_table_blocks[0] reflection_table["phi"] = (reflection_table["xyzobs.px.value"].parts()[2] * experiment.scan.get_oscillation()[1]) reflection_table = calc_crystal_frame_vectors(reflection_table, experiment) Ih_table_block.Ih_table["s1c"] = reflection_table["s1c"].select( Ih_table_block.Ih_table["loc_indices"]) indices = select_highly_connected_reflections(Ih_table_block, experiment, min_per_area=10, n_resolution_bins=10) assert len(indices) > 1710 and len(indices) < 1800 # Give a high min_per_area to check that all reflections with multiplciity > 1 # are selected. indices = select_highly_connected_reflections(Ih_table_block, experiment, min_per_area=50, n_resolution_bins=10) # this dataset has 48 reflections with multiplicity = 1 assert len(indices) == reflection_table.size() - 48
def test_calc_crystal_frame_vectors(test_reflection_table, mock_exp): """Test the namesake function, to check that the vectors are correctly rotated into the crystal frame.""" rt, exp = test_reflection_table, mock_exp s0_vec = (1.0, 0.0, 0.0) s1_vec = (1.0 / sqrt(2.0), 0.0, 1.0 / sqrt(2.0)) reflection_table = calc_crystal_frame_vectors(rt, exp) assert list(reflection_table["s0"]) == list( flex.vec3_double([s0_vec, s0_vec, s0_vec]) ) expected = [s0_vec, (1.0 / sqrt(2.0), -1.0 / sqrt(2.0), 0.0), (0.0, -1.0, 0.0)] for v1, v2 in zip(reflection_table["s0c"], expected): assert v1 == pytest.approx(v2) expected = [ s1_vec, (1.0 / 2.0, -1.0 / 2.0, 1.0 / sqrt(2.0)), (0.0, -1.0 / sqrt(2.0), 1.0 / sqrt(2.0)), ] for v1, v2 in zip(reflection_table["s1c"], expected): assert v1 == pytest.approx(v2)
def create(cls, params, experiment, reflection_table, for_multi=False): """Perform reflection_table preprocessing and create a SingleScaler.""" cls.ensure_experiment_identifier(params, experiment, reflection_table) logger.info( "Preprocessing data for scaling. The id assigned to this \n" "dataset is %s, and the scaling model type being applied is %s. \n", list(reflection_table.experiment_identifiers().values())[0], experiment.scaling_model.id_, ) reflection_table, reasons = cls.filter_bad_reflections( reflection_table) if "inverse_scale_factor" not in reflection_table: reflection_table["inverse_scale_factor"] = flex.double( reflection_table.size(), 1.0) elif (reflection_table["inverse_scale_factor"].count(0.0) == reflection_table.size()): reflection_table["inverse_scale_factor"] = flex.double( reflection_table.size(), 1.0) reflection_table = choose_scaling_intensities( reflection_table, params.reflection_selection.intensity_choice) excluded_for_scaling = reflection_table.get_flags( reflection_table.flags.excluded_for_scaling) user_excluded = reflection_table.get_flags( reflection_table.flags.user_excluded_in_scaling) reasons.add_reason("user excluded", user_excluded.count(True)) reasons.add_reason("excluded for scaling", excluded_for_scaling.count(True)) n_excluded = (excluded_for_scaling | user_excluded).count(True) if n_excluded == reflection_table.size(): logger.info( "All reflections were determined to be unsuitable for scaling." ) logger.info(reasons) raise BadDatasetForScalingException( """Unable to use this dataset for scaling""") else: logger.info( "%s/%s reflections not suitable for scaling\n%s", n_excluded, reflection_table.size(), reasons, ) if not for_multi: determine_reflection_selection_parameters(params, [experiment], [reflection_table]) if params.reflection_selection.method == "intensity_ranges": reflection_table = quasi_normalisation(reflection_table, experiment) if (params.reflection_selection.method in (None, Auto, "auto", "quasi_random")) or ( experiment.scaling_model.id_ == "physical" and "absorption" in experiment.scaling_model.components): if experiment.scan: # calc theta and phi cryst reflection_table["phi"] = ( reflection_table["xyzobs.px.value"].parts()[2] * experiment.scan.get_oscillation()[1]) reflection_table = calc_crystal_frame_vectors( reflection_table, experiment) return SingleScaler(params, experiment, reflection_table, for_multi)
def test_equality_of_two_harmonic_table_methods(dials_regression, run_in_tmpdir): from dials_scaling_ext import calc_theta_phi, calc_lookup_index from dxtbx.serialize import load from dials.util.options import OptionParser from libtbx import phil from dials.algorithms.scaling.scaling_library import create_scaling_model data_dir = os.path.join(dials_regression, "xia2-28") pickle_path = os.path.join(data_dir, "20_integrated.pickle") sequence_path = os.path.join(data_dir, "20_integrated_experiments.json") phil_scope = phil.parse( """ include scope dials.command_line.scale.phil_scope """, process_includes=True, ) optionparser = OptionParser(phil=phil_scope, check_format=False) params, _ = optionparser.parse_args(args=[], quick_parse=True) params.model = "physical" lmax = 2 params.physical.lmax = lmax reflection_table = flex.reflection_table.from_file(pickle_path) experiments = load.experiment_list(sequence_path, check_format=False) experiments = create_scaling_model(params, experiments, [reflection_table]) experiment = experiments[0] # New method reflection_table["phi"] = ( reflection_table["xyzobs.px.value"].parts()[2] * experiment.scan.get_oscillation()[1] ) reflection_table = calc_crystal_frame_vectors(reflection_table, experiment) theta_phi_0 = calc_theta_phi(reflection_table["s0c"]) # array of tuples in radians theta_phi_1 = calc_theta_phi(reflection_table["s1c"]) points_per_degree = 2 s0_lookup_index = calc_lookup_index(theta_phi_0, points_per_degree) s1_lookup_index = calc_lookup_index(theta_phi_1, points_per_degree) print(list(s0_lookup_index[0:20])) print(list(s1_lookup_index[0:20])) coefficients_list = create_sph_harm_lookup_table(lmax, points_per_degree) experiment.scaling_model.components["absorption"].data = { "s0_lookup": s0_lookup_index, "s1_lookup": s1_lookup_index, } experiment.scaling_model.components[ "absorption" ].coefficients_list = coefficients_list assert experiment.scaling_model.components["absorption"]._mode == "memory" experiment.scaling_model.components["absorption"].update_reflection_data() absorption = experiment.scaling_model.components["absorption"] harmonic_values_list = absorption.harmonic_values[0] experiment.scaling_model.components["absorption"].parameters = flex.double( [0.1, -0.1, 0.05, 0.02, 0.01, -0.05, 0.12, -0.035] ) scales, derivatives = experiment.scaling_model.components[ "absorption" ].calculate_scales_and_derivatives() # Old method: old_data = {"sph_harm_table": create_sph_harm_table(theta_phi_0, theta_phi_1, lmax)} experiment.scaling_model.components["absorption"].data = old_data assert experiment.scaling_model.components["absorption"]._mode == "speed" experiment.scaling_model.components["absorption"].update_reflection_data() old_harmonic_values = absorption.harmonic_values[0] for i in range(0, 8): print(i) assert list(harmonic_values_list[i]) == pytest.approx( list(old_harmonic_values.col(i).as_dense_vector()), abs=0.01 ) experiment.scaling_model.components["absorption"].parameters = flex.double( [0.1, -0.1, 0.05, 0.02, 0.01, -0.05, 0.12, -0.035] ) scales_1, derivatives_1 = experiment.scaling_model.components[ "absorption" ].calculate_scales_and_derivatives() assert list(scales_1) == pytest.approx(list(scales), abs=0.001) assert list(scales_1) != [1.0] * len(scales_1)
def create(cls, params, experiment, reflection_table, for_multi=False): """Perform reflection_table preprocessing and create a SingleScaler.""" cls.ensure_experiment_identifier(experiment, reflection_table) logger.info( "The scaling model type being applied is %s. \n", experiment.scaling_model.id_, ) try: reflection_table = cls.filter_bad_reflections( reflection_table, partiality_cutoff=params.cut_data.partiality_cutoff, min_isigi=params.cut_data.min_isigi, intensity_choice=params.reflection_selection.intensity_choice, ) except ValueError: raise BadDatasetForScalingException # combine partial measurements of same reflection, to handle those reflections # that were split by dials.integrate - changes size of reflection table. reflection_table = sum_partial_reflections(reflection_table) if "inverse_scale_factor" not in reflection_table: reflection_table["inverse_scale_factor"] = flex.double( reflection_table.size(), 1.0) elif (reflection_table["inverse_scale_factor"].count(0.0) == reflection_table.size()): reflection_table["inverse_scale_factor"] = flex.double( reflection_table.size(), 1.0) reflection_table = choose_initial_scaling_intensities( reflection_table, params.reflection_selection.intensity_choice) excluded_for_scaling = reflection_table.get_flags( reflection_table.flags.excluded_for_scaling) user_excluded = reflection_table.get_flags( reflection_table.flags.user_excluded_in_scaling) reasons = Reasons() reasons.add_reason("user excluded", user_excluded.count(True)) reasons.add_reason("excluded for scaling", excluded_for_scaling.count(True)) n_excluded = (excluded_for_scaling | user_excluded).count(True) if n_excluded == reflection_table.size(): logger.info( "All reflections were determined to be unsuitable for scaling." ) logger.info(reasons) raise BadDatasetForScalingException( """Unable to use this dataset for scaling""") else: logger.info( "Excluding %s/%s reflections\n%s", n_excluded, reflection_table.size(), reasons, ) if params.reflection_selection.method == "intensity_ranges": reflection_table = quasi_normalisation(reflection_table, experiment) if (params.reflection_selection.method in (None, Auto, "auto", "quasi_random")) or ( experiment.scaling_model.id_ == "physical" and "absorption" in experiment.scaling_model.components): if experiment.scan: reflection_table = calc_crystal_frame_vectors( reflection_table, experiment) alignment_axis = (1.0, 0.0, 0.0) reflection_table["s0c"] = align_axis_along_z( alignment_axis, reflection_table["s0c"]) reflection_table["s1c"] = align_axis_along_z( alignment_axis, reflection_table["s1c"]) try: scaler = SingleScaler(params, experiment, reflection_table, for_multi) except BadDatasetForScalingException as e: raise ValueError(e) else: return scaler
def test_equality_of_two_harmonic_table_methods(dials_data): location = dials_data("l_cysteine_dials_output", pathlib=True) refl = location / "20_integrated.pickle" expt = location / "20_integrated_experiments.json" phil_scope = phil.parse( """ include scope dials.command_line.scale.phil_scope """, process_includes=True, ) parser = ArgumentParser(phil=phil_scope, check_format=False) params, _ = parser.parse_args(args=[], quick_parse=True) params.model = "physical" lmax = 2 params.physical.lmax = lmax reflection_table = flex.reflection_table.from_file(refl) experiments = load.experiment_list(expt, check_format=False) experiments = create_scaling_model(params, experiments, [reflection_table]) experiment = experiments[0] # New method reflection_table["phi"] = (reflection_table["xyzobs.px.value"].parts()[2] * experiment.scan.get_oscillation()[1]) reflection_table = calc_crystal_frame_vectors(reflection_table, experiment) reflection_table["s1c"] = align_axis_along_z((1.0, 0.0, 0.0), reflection_table["s1c"]) reflection_table["s0c"] = align_axis_along_z((1.0, 0.0, 0.0), reflection_table["s0c"]) theta_phi_0 = calc_theta_phi( reflection_table["s0c"]) # array of tuples in radians theta_phi_1 = calc_theta_phi(reflection_table["s1c"]) points_per_degree = 4 s0_lookup_index = calc_lookup_index(theta_phi_0, points_per_degree) s1_lookup_index = calc_lookup_index(theta_phi_1, points_per_degree) print(list(s0_lookup_index[0:20])) print(list(s1_lookup_index[0:20])) coefficients_list = create_sph_harm_lookup_table(lmax, points_per_degree) experiment.scaling_model.components["absorption"].data = { "s0_lookup": s0_lookup_index, "s1_lookup": s1_lookup_index, } experiment.scaling_model.components[ "absorption"].coefficients_list = coefficients_list assert experiment.scaling_model.components["absorption"]._mode == "memory" experiment.scaling_model.components["absorption"].update_reflection_data() absorption = experiment.scaling_model.components["absorption"] harmonic_values_list = absorption.harmonic_values[0] experiment.scaling_model.components["absorption"].parameters = flex.double( [0.1, -0.1, 0.05, 0.02, 0.01, -0.05, 0.12, -0.035]) scales, derivatives = experiment.scaling_model.components[ "absorption"].calculate_scales_and_derivatives() # Old method: old_data = { "sph_harm_table": create_sph_harm_table(theta_phi_0, theta_phi_1, lmax) } experiment.scaling_model.components["absorption"].data = old_data assert experiment.scaling_model.components["absorption"]._mode == "speed" experiment.scaling_model.components["absorption"].update_reflection_data() old_harmonic_values = absorption.harmonic_values[0] for i in range(0, 8): print(i) assert list(harmonic_values_list[i]) == pytest.approx(list( old_harmonic_values.col(i).as_dense_vector()), abs=0.01) experiment.scaling_model.components["absorption"].parameters = flex.double( [0.1, -0.1, 0.05, 0.02, 0.01, -0.05, 0.12, -0.035]) scales_1, derivatives_1 = experiment.scaling_model.components[ "absorption"].calculate_scales_and_derivatives() assert list(scales_1) == pytest.approx(list(scales), abs=0.001) assert list(scales_1) != [1.0] * len(scales_1)
def test_calc_crystal_frame_vectors_multi_axis_gonio(test_reflection_table): """Test the namesake function, to check that the vectors are correctly rotated into the crystal frame.""" experiments = test_experiments_multiaxisgonio() table = generate_reflection_table() # for the first scan, the rotation axis is the (1,0,0) direction, like the # single axis gonio test case above, so check that first. table = calc_crystal_frame_vectors(table, experiments[0]) # s0c and s1c are normalised. s0c points towards the source. # as the crystal rotates about the x axis, the s0 vector moves in the y-z plane towards -y expected_s0c = [ (0.0, 0.0, -1.0), (0.0, -1.0 / sqrt(2.0), -1.0 / sqrt(2.0)), (0.0, -1.0, 0.0), ] for v1, v2 in zip(table["s0c"], expected_s0c): assert v1 == pytest.approx(v2) # the s1c vector should have fixed x-component, rotating in the y-z plane towards +y expected_s1c = [ (1.0 / sqrt(2.0), 0.0, 1.0 / sqrt(2.0)), (1.0 / sqrt(2.0), 0.5, 0.5), (1.0 / sqrt(2.0), 1.0 / sqrt(2.0), 0.0), ] for v1, v2 in zip(table["s1c"], expected_s1c): assert v1 == pytest.approx(v2) # for second scan, the rotation axis is the (1,1,0) direction table = generate_reflection_table().select(flex.bool([True, False, True])) table = calc_crystal_frame_vectors(table, experiments[1]) # s0c and s1c are normalised. s0c points towards the source. # as the crystal rotates about the (1,1,0) axis, the s0 vector rotates towards (1, -sqrt2, -1) # the y-z plane towards -y expected_s0c = [ (0.0, 0.0, -1.0), (0.5, -1.0 / sqrt(2.0), -0.5), ] for v1, v2 in zip(table["s0c"], expected_s0c): assert v1 == pytest.approx(v2) # the s1c vector should rotate to +y expected_s1c = [ (1.0 / sqrt(2.0), 0.0, 1.0 / sqrt(2.0)), (0.0, 1.0, 0.0), ] for v1, v2 in zip(table["s1c"], expected_s1c): assert v1 == pytest.approx(v2) # now test redefined coordinates so that the lab x-axis is along the # z-axis in the crystal frame alignment_axis = (1.0, 0.0, 0.0) table["s1c"] = align_axis_along_z(alignment_axis, table["s1c"]) table["s0c"] = align_axis_along_z(alignment_axis, table["s0c"]) expected_s0c_realigned = [ (1.0, 0.0, 0.0), (0.5, -1.0 / sqrt(2.0), 0.5), ] for v1, v2 in zip(table["s0c"], expected_s0c_realigned): assert v1 == pytest.approx(v2) # the s1c vector should rotate to +y expected_s1c_realigned = [ (-1.0 / sqrt(2.0), 0.0, 1.0 / sqrt(2.0)), (0.0, 1.0, 0.0), ] for v1, v2 in zip(table["s1c"], expected_s1c_realigned): assert v1 == pytest.approx(v2)