Skip to content

Commit 66ec794

Browse files
Add macrocycles of refinement to baseline indexer (#90)
Also make sure to update all the models (detector and beam) for the best candidate in the initial candidate refinement.
1 parent 2aca540 commit 66ec794

File tree

7 files changed

+167
-11
lines changed

7 files changed

+167
-11
lines changed

baseline/indexer/indexer.cc

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "flood_fill.cc"
3333
#include "gemmi/symmetry.hpp"
3434
#include "peaks_to_rlvs.cc"
35+
#include "refine_crystal.cc"
3536
#include "scan_static_predictor.cc"
3637
#include "score_crystals.cc"
3738
#include "xyz_to_rlp.cc"
@@ -68,6 +69,12 @@ int main(int argc, char **argv) {
6869
.help("The maximum number of candidate lattices to refine during indexing")
6970
.default_value<size_t>(50)
7071
.scan<'u', size_t>();
72+
parser.add_argument("--macro-cycles")
73+
.help(
74+
"The number of macrocycles of refinement to run after the initial indexing. "
75+
"Set to zero for no post-indexing refinement.")
76+
.default_value<size_t>(5)
77+
.scan<'u', size_t>();
7178
parser.add_argument("--test")
7279
.help("Enable additional output for testing")
7380
.default_value<bool>(false)
@@ -115,6 +122,7 @@ int main(int argc, char **argv) {
115122
std::string filename = parser.get<std::string>("refl");
116123
double max_cell = parser.get<float>("max-cell");
117124
size_t max_refine = parser.get<size_t>("max-refine");
125+
size_t macro_cycles = parser.get<size_t>("macro-cycles");
118126

119127
// Parse the experiment list (a json file) and load the models.
120128
// Will be moved to dx2.
@@ -161,25 +169,39 @@ int main(int argc, char **argv) {
161169
// coordinates on the detector into reciprocal space.
162170
xyz_to_rlp_results results = xyz_to_rlp(xyzobs_px, panel, beam, scan, gonio);
163171
logger.info("Number of reflections: {}", results.rlp.extent(0));
172+
uint32_t n_points = parser.get<uint32_t>("fft-npoints");
173+
174+
// Determine the d_min limit of the data, will be used in macrocycles of refinement.
175+
double d_min_data;
176+
std::vector<double> d_values(results.rlp.extent(0), 0);
177+
for (int i = 0; i < results.rlp.extent(0); ++i) {
178+
d_values[i] = 1.0 / Eigen::Map<Vector3d>(&results.rlp(i, 0)).norm();
179+
}
180+
d_min_data = *std::min_element(d_values.begin(), d_values.end());
181+
logger.debug("dmin of highest resolution spot: {:.5f}", d_min_data);
164182

165-
// If a resolution limit was not specified, determine from the highest resolution spot.
166183
double d_min;
167184
if (parser.is_used("dmin")) {
168185
d_min = parser.get<float>("dmin");
169186
} else {
170-
std::vector<double> d_values(results.rlp.extent(0), 0);
171-
for (int i = 0; i < results.rlp.extent(0); ++i) {
172-
d_values[i] = 1.0 / Eigen::Map<Vector3d>(&results.rlp(i, 0)).norm();
173-
}
174-
d_min = *std::min_element(d_values.begin(), d_values.end());
175-
logger.info("Setting dmin based on highest resolution spot: {:.5f}", d_min);
187+
/* rough calculation of suitable d_min based on max cell
188+
see also Campbell, J. (1998). J. Appl. Cryst., 31(3), 407-413.
189+
fft_cell should be greater than twice max_cell, so say:
190+
fft_cell = 2.5 * max_cell
191+
then:
192+
fft_cell = n_points * d_min/2
193+
2.5 * max_cell = n_points * d_min/2
194+
a little bit of rearrangement:
195+
d_min = 5 * max_cell/n_points. */
196+
d_min = 5.0 * max_cell / n_points;
197+
d_min = std::max(d_min, d_min_data);
198+
logger.info("Setting dmin to {:.5f}", d_min);
176199
}
177200

178201
// b_iso is an isotropic b-factor used to weight the points when doing the fft.
179202
// i.e. high resolution (weaker) spots are downweighted by the expected
180203
// intensity fall-off as as function of resolution.
181204
double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05);
182-
uint32_t n_points = parser.get<uint32_t>("fft-npoints");
183205
logger.info("Setting b_iso = {:.3f}", b_iso);
184206

185207
// Create an array to store the fft result. This is a 3D grid of points, typically 256^3.
@@ -374,6 +396,55 @@ int main(int argc, char **argv) {
374396
expt.beam().set_s0(best_beam.get_s0());
375397
expt.detector().update(best_panel.get_d_matrix());
376398

399+
// Now do macrocycles of refinement
400+
if (macro_cycles) {
401+
std::vector<double> d_steps;
402+
double d_min_step = (d_min - d_min_data) / macro_cycles;
403+
logger.info("Performing {} macro cycles with a dmin step of {:.3f}",
404+
macro_cycles,
405+
d_min_step);
406+
for (int i = 0; i < macro_cycles; ++i) {
407+
d_steps.push_back(d_min - (i + 1) * d_min_step);
408+
}
409+
for (int i = 0; i < macro_cycles; ++i) {
410+
double d_min = d_steps[i];
411+
logger.info("Performing macro cycle {} with d_min={:.3f}", i + 1, d_min);
412+
results = xyz_to_rlp(
413+
xyzobs_px, expt.detector().panels()[0], expt.beam(), scan, gonio);
414+
415+
// Make a selection on dmin and rotation angle like dials
416+
std::vector<bool> selection(results.rlp.extent(0), true);
417+
double osc_trim_limit = scan.get_oscillation()[0] + 360.0;
418+
for (int i = 0; i < results.rlp.extent(0); ++i) {
419+
Eigen::Map<Vector3d> rlp_i(&results.rlp(i, 0));
420+
if (1.0 / rlp_i.norm() <= d_min) {
421+
selection[i] = false;
422+
} else if (results.xyzobs_mm(i, 2) * RAD2DEG > osc_trim_limit) {
423+
selection[i] = false;
424+
}
425+
}
426+
ReflectionTable reflections;
427+
reflections.add_column(std::string("flags"), flags.size(), 1, flags);
428+
reflections.add_column(std::string("xyzobs.mm.value"),
429+
results.xyzobs_mm.extent(0),
430+
3,
431+
results.xyzobs_mm_data);
432+
reflections.add_column(
433+
std::string("s1"), results.s1.extent(0), 3, results.s1_data);
434+
reflections.add_column(
435+
std::string("rlp"), results.rlp.extent(0), 3, results.rlp_data);
436+
reflections.add_column(std::string("entering"), enterings);
437+
const ReflectionTable filtered = reflections.select(selection);
438+
439+
refine_crystal(expt.crystal(),
440+
filtered,
441+
gonio,
442+
expt.beam(),
443+
expt.detector().panels()[0],
444+
scan_width);
445+
}
446+
}
447+
377448
// Save the indexed experiment list.
378449
json elist_out = expt.to_json();
379450
std::string efile_name = "indexed.expt";
@@ -511,7 +582,7 @@ int main(int argc, char **argv) {
511582
flags_ = strong_reflections.column<std::size_t>("flags");
512583
flag_span = flags_.value();
513584
} else {
514-
auto flag_span = flags_.value();
585+
flag_span = flags_.value();
515586
for (int i = 0; i < assign_results.miller_indices.extent(0); ++i) {
516587
if ((assign_results.miller_indices(i, 0)) != 0
517588
| (assign_results.miller_indices(i, 1) != 0)
@@ -530,7 +601,7 @@ int main(int argc, char **argv) {
530601
strong_reflections);
531602
// reset the predicted flags as these are observed not predicted
532603
for (int i = 0; i < flag_span.extent(0); ++i) {
533-
flag_span(i, 0) = flag_span(i, 0) & ~predicted_value;
604+
flag_span(i, 0) &= ~predicted_value;
534605
}
535606

536607
// Save the indexed reflection table.

baseline/indexer/refine_candidate.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#pragma once
2+
13
#include <dx2/beam.hpp>
24
#include <dx2/crystal.hpp>
35
#include <dx2/detector.hpp>
@@ -88,5 +90,7 @@ double refine_indexing_candidate(Crystal &crystal,
8890
// updated during the refinement
8991
crystal.set_A_matrix(target.orientation_parameterisation().get_state()
9092
* target.cell_parameterisation().get_state());
93+
panel.update(target.detector_parameterisation().get_state());
94+
beam.set_s0(target.beam_parameterisation().get_state());
9195
return xyrmsd;
9296
}

baseline/indexer/refine_crystal.cc

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#include <dx2/beam.hpp>
2+
#include <dx2/crystal.hpp>
3+
#include <dx2/detector.hpp>
4+
#include <dx2/experiment.hpp>
5+
#include <dx2/goniometer.hpp>
6+
#include <dx2/reflection.hpp>
7+
#include <vector>
8+
9+
#include "assign_indices.cc"
10+
#include "ffs_logger.hpp"
11+
#include "refine_candidate.cc"
12+
#include "reflection_filter.cc"
13+
14+
void refine_crystal(Crystal &crystal,
15+
ReflectionTable const &obs,
16+
Goniometer gonio,
17+
MonochromaticBeam &beam,
18+
Panel &panel,
19+
double scan_width) {
20+
std::vector<int> miller_indices_data;
21+
int count;
22+
auto preassign = std::chrono::system_clock::now();
23+
24+
// First assign miller indices to the data using the crystal model.
25+
auto rlp_ = obs.column<double>("rlp");
26+
const mdspan_type<double> &rlp = rlp_.value();
27+
auto xyzobs_mm_ = obs.column<double>("xyzobs.mm.value");
28+
const mdspan_type<double> &xyzobs_mm = xyzobs_mm_.value();
29+
assign_indices_results results =
30+
assign_indices_global(crystal.get_A_matrix(), rlp, xyzobs_mm);
31+
auto t2 = std::chrono::system_clock::now();
32+
std::chrono::duration<double> elapsed_time = t2 - preassign;
33+
logger.debug("Time for assigning indices: {:.5f}s", elapsed_time.count());
34+
35+
logger.info("Indexed {}/{} reflections",
36+
results.number_indexed,
37+
results.miller_indices.extent(0));
38+
39+
auto t3 = std::chrono::system_clock::now();
40+
std::chrono::duration<double> elapsed_time1 = t3 - t2;
41+
logger.debug("Time for correct: {:.5f}s", elapsed_time1.count());
42+
43+
// Perform filtering of the data prior to candidate refinement.
44+
ReflectionTable sel_obs = reflection_filter_preevaluation(
45+
obs, results.miller_indices, gonio, crystal, beam, panel, scan_width, 100);
46+
auto postfilter = std::chrono::system_clock::now();
47+
std::chrono::duration<double> elapsed_timefilter = postfilter - t3;
48+
logger.debug("Time for reflection_filter: {:.5f}s", elapsed_timefilter.count());
49+
50+
auto t4 = std::chrono::system_clock::now();
51+
double xyrmsd = refine_indexing_candidate(crystal, gonio, beam, panel, sel_obs);
52+
std::chrono::duration<double> elapsed_time_refine =
53+
std::chrono::system_clock::now() - t4;
54+
auto flags_ = sel_obs.column<std::size_t>("flags");
55+
int n_refl = flags_.value().extent(0);
56+
logger.debug("Time for refinement: {:.5f}s", elapsed_time_refine.count());
57+
logger.info("rmsd_xy {:.5f} on {} reflections", xyrmsd, n_refl);
58+
}

baseline/indexer/reflection_filter.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#pragma once
12
#include <Eigen/Dense>
23
#include <dx2/beam.hpp>
34
#include <dx2/crystal.hpp>

baseline/refiner/target.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#pragma once
12

23
#include <cmath>
34
#include <dx2/beam.hpp>

dx2

tests/test_baseline_indexer.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import subprocess
55
from pathlib import Path
66

7+
import h5py
8+
import numpy as np
9+
710

811
def test_baseline_indexer(tmp_path, dials_data):
912
indexer_path: str | Path | None = os.getenv("INDEXER")
@@ -194,6 +197,16 @@ def test_baseline_indexer(tmp_path, dials_data):
194197
}
195198
assert candidate_crystals == expected_crystals_output
196199

200+
assert (tmp_path / "indexed.refl").is_file()
201+
assert (tmp_path / "indexed.expt").is_file()
202+
with h5py.File(tmp_path / "indexed.refl") as f:
203+
flags = f["/dials/processing/group_0/flags"]
204+
assert len(flags) == 703
205+
n_indexed = np.sum(np.array(flags, dtype=int) == 36)
206+
n_unindexed = np.sum(np.array(flags, dtype=int) == 32)
207+
assert n_indexed == 55
208+
assert n_unindexed == 648
209+
197210

198211
def test_baseline_indexer_c2sum(tmp_path, dials_data):
199212
indexer_path: str | Path | None = os.getenv("INDEXER")
@@ -367,3 +380,11 @@ def test_baseline_indexer_c2sum(tmp_path, dials_data):
367380
},
368381
}
369382
assert candidate_crystals == expected_crystals_output
383+
384+
with h5py.File(tmp_path / "indexed.refl") as f:
385+
flags = f["/dials/processing/group_0/flags"]
386+
assert len(flags) == 107999
387+
n_indexed = np.sum(np.array(flags, dtype=int) == 36)
388+
n_unindexed = np.sum(np.array(flags, dtype=int) == 32)
389+
assert n_indexed == 107265
390+
assert n_unindexed == 734

0 commit comments

Comments
 (0)