Skip to content

Commit

Permalink
Test for possibility of bad reindexing operators in minimum cell tran…
Browse files Browse the repository at this point in the history
…sformation in symmetry analysis (dials#2552)

Try to print more useful log output and suggested workarounds and exit cleanly.
Fixes dials#2355
Fixes dials#2130
  • Loading branch information
jbeilstenedmands committed Nov 10, 2023
1 parent b375f18 commit ee29f83
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 1 deletion.
1 change: 1 addition & 0 deletions newsfragments/2552.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``dials.symmetry/dials.cosym``: Avoid crashes when unable to find consistent symmetry during cell reduction routine.
46 changes: 45 additions & 1 deletion src/dials/command_line/symmetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def change_of_basis_ops_to_minimum_cell(
Returns: The experiments and reflections mapped to the minimum cell
"""

logger.info("Mapping all input cells to a common minimum cell")
median_cell = median_unit_cell(experiments)
unit_cells_are_similar = unit_cells_are_similar_to(
experiments, median_cell, relative_length_tolerance, absolute_angle_tolerance
Expand All @@ -184,6 +184,19 @@ def change_of_basis_ops_to_minimum_cell(
cb_op_best_to_min = group["best_subsym"].change_of_basis_op_to_minimum_cell()
cb_ops = [cb_op_best_to_min * group["cb_op_inp_best"]] * len(experiments)
else:
if len(set(centring_symbols)) > 1:
logger.info(
f"Multiple lattice centerings in input cells: {', '.join(list(set(centring_symbols)))}.\n"
+ "Attempting to map to a common minimum cell through the most common lattice group."
)
else:
median_params = ", ".join(f"{i:.2f}" for i in median_cell.parameters())
logger.info(
f"Some input cells are not sufficiently similar to the median cell:\n {median_params}\n"
+ f" within a relative_length_tolerance of {relative_length_tolerance}\n"
+ f" and an absolute_angle_tolerance of {absolute_angle_tolerance}.\n"
+ "Attempting to map to a common minimum cell through the most common lattice group."
)
groups = [
metric_subgroups(
expt.crystal.get_crystal_symmetry(),
Expand All @@ -198,6 +211,7 @@ def change_of_basis_ops_to_minimum_cell(
)
target_group = counter.most_common()[0][0]
cb_ops = []
best_cell = None
for expt in experiments:
groups = metric_subgroups(
expt.crystal.get_crystal_symmetry(),
Expand All @@ -211,6 +225,28 @@ def change_of_basis_ops_to_minimum_cell(
group = g
if group:
cb_ops.append(group["cb_op_inp_best"])
if not best_cell:
best_cell = group["best_subsym"].unit_cell()
else:
this_best_cell = group["best_subsym"].unit_cell()
if not best_cell.is_similar_to(
this_best_cell,
relative_length_tolerance,
absolute_angle_tolerance,
):
best_params = ", ".join(
f"{i:.2f}" for i in best_cell.parameters()
)
this_params = ", ".join(
f"{i:.2f}" for i in this_best_cell.parameters()
)
raise ValueError(
"Exiting symmetry analysis: Unable to map input cells to a minimum cell through\n"
+ f"a consistent best cell in space group {target_group.info()}.\n"
+ f"Incompatible best cells:\n {best_params},\n {this_params},\n"
+ f" within a relative_length_tolerance of {relative_length_tolerance}\n"
+ f" and an absolute_angle_tolerance of {absolute_angle_tolerance}"
)
else:
cb_ops.append(None)
logger.info(
Expand Down Expand Up @@ -329,6 +365,14 @@ def symmetry(experiments, reflection_tables, params=None):
params.relative_length_tolerance,
params.absolute_angle_tolerance,
)
if None in cb_ops:
indices = [str(i + 1) for i, v in enumerate(cb_ops) if v is None]
raise ValueError(
"Exiting symmetry analysis: Unable to match all cells to target symmetry.\n"
+ "This may be avoidable by increasing the absolute_angle_tolerance or relative_length_tolerance,\n"
+ "if the cells are similar enough.\n"
+ f"Alternatively, remove dataset number{'s' if len(indices) > 1 else ''} {', '.join(indices)} from the input"
)
reflection_tables = eliminate_sys_absent(experiments, reflection_tables)
experiments, reflection_tables = apply_change_of_basis_ops(
experiments, reflection_tables, cb_ops
Expand Down
56 changes: 56 additions & 0 deletions tests/command_line/test_cosym.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,62 @@ def test_cosym_with_reference(dials_data, run_in_tmp_path):
assert len(experiments) == 5 # check we have the right number of expts


def test_synthetic_map_cell_issue(run_in_tmp_path):
# Test that the program cleanly exits if a set of unit cells cannot be mapped to a consistent
# minimum cell in the change_of_basis_ops_to_minimum_cell function.
unit_cell = uctbx.unit_cell((5.46, 9.82, 29.58, 95.24, 94.54, 105.21))
space_group = sgtbx.space_group_info("P1").group()
experiments, reflections, _ = generate_experiments_reflections(
space_group=space_group,
unit_cell=unit_cell,
unit_cell_volume=10000,
sample_size=7,
map_to_p1=True,
d_min=2.0,
)
# Set a distribution of cells that will cause the set of cells to fail the cell
# similarity test, and also which don't all find the same best C2 cell.
cells = [
(5.4, 9.9, 29.9, 95.6, 94.6, 105.1),
(5.4, 9.6, 29.6, 97.4, 92.9, 103.1),
(5.4, 9.9, 29.5, 95.1, 96.6, 106.0),
(5.5, 9.6, 29.4, 94.1, 94.7, 105.7),
(5.5, 9.8, 29.6, 96.4, 93.0, 105.2),
(5.5, 10.1, 29.6, 95.2, 94.5, 105.0),
(5.5, 9.8, 29.7, 95.8, 94.0, 103.9),
]
for expt, cell in zip(experiments, cells):
expt.crystal.set_unit_cell(uctbx.unit_cell(cell))

experiments.as_json("tmp.expt")
expt_file = "tmp.expt"
joint_table = flex.reflection_table.concat(reflections)
joint_table.as_file("tmp.refl")
refl_file = "tmp.refl"

args = [
expt_file,
refl_file,
"output.experiments=symmetrized.expt",
"output.reflections=symmetrized.refl",
"output.html=cosym.html",
"output.json=cosym.json",
]

with pytest.raises(Sorry) as e:
dials_cosym.run(args=args)
assert str(e).startswith("Sorry: Exiting symmetry analysis")

# Increase the angle tolerance so that the cells are determined as similar
# and can therefore be correctly mapped to the same cell setting.
args.append("absolute_angle_tolerance=3.0")
dials_cosym.run(args=args)
assert pathlib.Path("symmetrized.refl").is_file()
assert pathlib.Path("symmetrized.expt").is_file()
assert pathlib.Path("cosym.html").is_file()
assert pathlib.Path("cosym.json").is_file()


@pytest.mark.parametrize(
(
"space_group",
Expand Down

0 comments on commit ee29f83

Please sign in to comment.