View on GitHub

DIALS // knowledgebase

community resources, howtos, meeting minutes

Fixing Bugs

Introduction

Find a bug during routine work - trying to integrate multi-lattice still data fails with an error:

Initialising
Processing reference reflections
 read 575 strong spots
 removing 89 unindexed reflections
 removing 34 reflections marked as bad for refinement
 using 452 indexed reflections
 found 123 junk reflections
dials Internal Error:
/Users/graeme/svn/cctbx/modules/dials/array_family/boost_python/flex_reflection_table.cc(508):
DIALS_ASSERT(id[i] < num_expr) failure.

Looking through the code to dials.index this appears to mean that there are reflections in there which have an experiment id which does not correspond to an experiment. Reviewing dials.index.log; see

Refinement failed:
Too few reflections to parameterise Crystal8Phi1, Crystal8Phi2, Crystal8Phi3, Crystal8g_param_0, Crystal8g_param_1.
Try modifying refinement.parameterisation.auto_reduction options

Which suggests that indexing may have taken place, but not refinement. Quick jiffy script confirms that something is wrong:

from dials.array_family import flex
from dxtbx.model.experiment_list import ExperimentList

refl = flex.reflection_table.from_file("indexed.refl")
expt = ExperimentList.from_file("indexed.expt")

ids = set(refl["id"]) - set([-1])

for i in ids:
    e = expt[i]

=> create an issue:

https://github.com/dials/dials/issues/1349

This includes the input data, a script to reproduce the issue and this script to verify the problem.

The Fix

Make some changes to fix the issue, identifying another potential issue along the way:

https://github.com/dials/dials/issues/1351

and

https://github.com/dials/dials/pull/1350

The Test Data

Now this pull request may fix the issue but it would be good to ensure that it does not come back - so the data included in the example are uploaded to the dials-data-files repo:

https://github.com/dials/data-files/commit/2e890287a925b69a3b5a55284166f71c4d173978

Since they are really small this is fine and efficient, then added to the dials-data repo:

https://github.com/dials/data/pull/210

This includes a bunch of information about the data so people can see what it’s about and where it came from - this data was my data so was at liberty to make it public. This is the included information, in a yaml file:

name: Multiple lattices for indexing stills, from VMXi thaumatin grid scan
author: Graeme Winter (2019)
license: CC-BY 4.0
url: https://ispyb.diamond.ac.uk/dc/visit/nt24686-7/id/3906663
description: >
  Experiment and reflection file data corresponding to image 07603 (i.e.
  split 07602) from a position in a thaumatin grid scan where multiple
  crystal lattices overlap. 

data:
 - url: https://github.com/dials/data-files/raw/2e890287a925b69a3b5a55284166f71c4d173978/vmxi_thaumatin_grid_index/split_07602.expt
 - url: https://github.com/dials/data-files/raw/2e890287a925b69a3b5a55284166f71c4d173978/vmxi_thaumatin_grid_index/split_07602.refl

There was much to-and-fro with this as you make a PR, then merge it, then automated systems take over to annotate it and publish an update to dials-data. So after that,

pip install -U dials_data

gives you access to the new files.

The Test

These data can then be used to add a regression test to the PR, so not only has a bug been found and fixed, but it prevents it from coming back - this is essentially codifying the information in the original issue:

from dxtbx.model import ExperimentList
from dials.array_family import flex
import procrunner


def test_all_expt_ids_have_expts(dials_data, tmpdir):
    result = procrunner.run(
        [
            "dials.index",
            dials_data("vmxi_thaumatin_grid_index").join("split_07602.expt"),
            dials_data("vmxi_thaumatin_grid_index").join("split_07602.refl"),
            "stills.indexer=sequences",
            "indexing.method=real_space_grid_search",
            "space_group=P4",
            "unit_cell=58,58,150,90,90,90",
            "max_lattices=8",
            "beam.fix=all",
            "detector.fix=all",
        ],
        working_directory=tmpdir,
    )
    assert not result.returncode and not result.stderr
    assert tmpdir.join("indexed.expt").check(file=1)
    assert tmpdir.join("indexed.refl").check(file=1)

    refl = flex.reflection_table.from_file(tmpdir / "indexed.refl")
    expt = ExperimentList.from_file(tmpdir / "indexed.expt")

    ids = set(refl["id"]) - set([-1])

    assert max(ids) + 1 == len(expt)

In future the

tmpdir

fixture will be replaced with

tmp_path

so it’s worth checking recently added tests for the best syntax to use.

Finishing Up

Last but not least a news fragment was added so when the change set is merged we have a record of what changed for the release notes - this is necessarily brief, but may be useful for an end-user to see that a bug they reported has been fixed:

dials.index: In multi-lattice indexing, ensure that reflections corresponding to lattices where refinement failed are flagged as unindexed.

When the PR is squash-merged the tests will be in the repository and will be copied to the release branch if the fix is cherry-picked, along with the news. As the issues were mentioned in the commit messages they will also be automatically closed, and anyone looking in the future can get more detail on what the change set was about by following those links.

The PR can then also be discussed to achieve consensus on how the changes are implemented, which on balance usually improves the overall sanity of the change set.

Overall the time spent documenting the issue and adding tests was much greater than the time spent fixing the actual issue, but this investment means that it does not come back and the provenance for the change set is clear.