Gemmi C++ API
Loading...
Searching...
No Matches
fourier.hpp
Go to the documentation of this file.
1// Copyright 2019 Global Phasing Ltd.
2//
3// Fourier transform applied to map coefficients.
4
5#ifndef GEMMI_FOURIER_HPP_
6#define GEMMI_FOURIER_HPP_
7
8#include <array>
9#include <complex> // for std::conj
10#include "recgrid.hpp" // for ReciprocalGrid
11#include "math.hpp" // for rad
12#include "symmetry.hpp" // for GroupOps, Op
13#include "fail.hpp" // for fail
14
15#ifdef __MINGW32__ // MinGW may have problem with std::mutex etc
16# define POCKETFFT_CACHE_SIZE 0
17#endif
18#define POCKETFFT_NO_MULTITHREADING
19#include "third_party/pocketfft_hdronly.h"
20
21namespace gemmi {
22
23// Returns angle in [0, 360). When it's negative but approximately zero,
24// printing 0.00 than 360.00 might be better, hence the second arg.
25// The default value is for similar precision as float around 360:
26// std::nextafter(360.f, 361.f) - 360.f -> 3.052e-5
27template<typename T>
28double phase_in_angles(const std::complex<T>& v, double eps=2e-5) {
29 double angle = gemmi::deg(std::arg(v));
30 if (angle < -eps)
31 angle += 360.;
32 return std::max(0., angle); // the order matters when angle is -0.0
33}
34
35// the first arg is usually Mtz::data
36inline void add_asu_f_phi_to_float_vector(std::vector<float>& float_data,
37 const AsuData<std::complex<float>>& asu_data) {
38 float_data.reserve(float_data.size() + asu_data.v.size() * 5);
39 for (const auto& item : asu_data.v) {
40 for (int i = 0; i != 3; ++i)
41 float_data.push_back((float) item.hkl[i]);
42 float_data.push_back(std::abs(item.value));
43 float_data.push_back((float) gemmi::phase_in_angles(item.value));
44 }
45}
46
47template<typename DataProxy>
48std::array<int, 3> get_size_for_hkl(const DataProxy& data,
49 std::array<int, 3> min_size,
50 double sample_rate) {
51 // adjust min_size by checking Miller indices in the data
52 for (size_t i = 0; i < data.size(); i += data.stride()) {
53 Miller hkl = data.get_hkl(i);
54 for (int j = 0; j != 3; ++j) {
55 int v = 2 * std::abs(hkl[j]) + 1;
56 if (v > min_size[j])
57 min_size[j] = v;
58 }
59 }
60 std::array<double, 3> dsize{{(double)min_size[0],
61 (double)min_size[1],
62 (double)min_size[2]}};
63 if (sample_rate > 0) {
64 const UnitCell& cell = data.unit_cell();
65 double max_1_d2 = 0;
66 for (size_t i = 0; i < data.size(); i += data.stride())
67 max_1_d2 = std::max(max_1_d2, cell.calculate_1_d2(data.get_hkl(i)));
68 double inv_d_min = std::sqrt(max_1_d2);
69 std::array<double, 3> cellr = {{cell.ar, cell.br, cell.cr}};
70 for (int i = 0; i < 3; ++i)
71 dsize[i] = std::max(dsize[i], sample_rate * inv_d_min / cellr[i]);
72 }
73 return good_grid_size(dsize, GridSizeRounding::Up, data.spacegroup());
74}
75
76template<typename DataProxy>
77bool data_fits_into(const DataProxy& data, std::array<int, 3> size) {
78 for (size_t i = 0; i < data.size(); i += data.stride()) {
79 Miller hkl = data.get_hkl(i);
80 for (int j = 0; j != 3; ++j)
81 if (2 * std::abs(hkl[j]) >= size[j])
82 return false;
83 }
84 return true;
85}
86
87template<typename T>
89 const T default_val = T(); // initialized to 0 or 0+0i
90 if (grid.axis_order == AxisOrder::XYZ) {
91 for (int w = 0; w != (grid.half_l ? 1 : grid.nw); ++w) {
92 int w_ = w == 0 ? 0 : grid.nw - w;
93 for (int v = 0; v != grid.nv; ++v) {
94 int v_ = v == 0 ? 0 : grid.nv - v;
95 for (int u = 0; u != grid.nu; ++u) {
96 size_t idx = grid.index_q(u, v, w);
97 if (grid.data[idx] == default_val) {
98 int u_ = u == 0 ? 0 : grid.nu - u;
99 size_t inv_idx = grid.index_q(u_, v_, w_);
100 grid.data[idx] = friedel_mate_value(grid.data[inv_idx]);
101 }
102 }
103 }
104 }
105 } else { // grid.axis_order == AxisOrder::ZYX
106 for (int w = 0; w != grid.nw; ++w) {
107 int w_ = w == 0 ? 0 : grid.nw - w;
108 for (int v = 0; v != grid.nv; ++v) {
109 int v_ = v == 0 ? 0 : grid.nv - v;
110 if (grid.half_l) {
111 size_t idx = grid.index_q(0, v, w);
112 if (grid.data[idx] == default_val) {
113 size_t inv_idx = grid.index_q(0, v_, w_);
114 grid.data[idx] = friedel_mate_value(grid.data[inv_idx]);
115 }
116 } else {
117 for (int u = 0; u != grid.nu; ++u) {
118 size_t idx = grid.index_q(u, v, w);
119 if (grid.data[idx] == default_val) {
120 int u_ = u == 0 ? 0 : grid.nu - u;
121 size_t inv_idx = grid.index_q(u_, v_, w_);
122 grid.data[idx] = friedel_mate_value(grid.data[inv_idx]);
123 }
124 }
125 }
126 }
127 }
128 }
129}
130
131template<typename T, typename DataProxy>
132void initialize_hkl_grid(ReciprocalGrid<T>& grid, const DataProxy& data,
133 std::array<int, 3> size, bool half_l,
134 AxisOrder axis_order) {
135 if (data.size() == 0)
136 fail("No data.");
137 if (!data.spacegroup())
138 fail("No spacegroup.");
139 check_grid_factors(data.spacegroup(), size);
140 grid.unit_cell = data.unit_cell();
141 grid.half_l = half_l;
142 grid.axis_order = axis_order;
143 grid.spacegroup = data.spacegroup();
144 if (half_l)
145 size[2] = size[2] / 2 + 1;
146 if (axis_order == AxisOrder::ZYX)
147 std::swap(size[0], size[2]);
148 grid.set_size_without_checking(size[0], size[1], size[2]);
149}
150
151template<typename DataProxy>
152struct FPhiProxy : DataProxy {
153 FPhiProxy(const DataProxy& data_proxy, size_t f_col, size_t phi_col)
154 : DataProxy(data_proxy), f_col_(f_col), phi_col_(phi_col) {
156 fail("Map coefficients not found.");
157 }
158 using real = typename DataProxy::num_type;
159 real get_f(size_t offset) const { return this->get_num(offset + f_col_); }
160 double get_phi(size_t offset) const { return rad(this->get_num(offset + phi_col_)); }
161private:
162 size_t f_col_, phi_col_;
163};
164
165// If half_l is true, grid has only data with l>=0.
166// Parameter size can be obtained from get_size_for_hkl().
167template<typename T, typename FPhi>
169 std::array<int, 3> size, bool half_l,
170 AxisOrder axis_order=AxisOrder::XYZ) {
171 FPhiGrid<T> grid;
172 initialize_hkl_grid(grid, fphi, size, half_l, axis_order);
173 const std::complex<T> default_val; // initialized to 0+0i
174 GroupOps ops = grid.spacegroup->operations();
175 for (size_t i = 0; i < fphi.size(); i += fphi.stride()) {
176 Miller hkl = fphi.get_hkl(i);
177 T f = (T) fphi.get_f(i);
178 if (f == 0.f) // is there enough of F=0 to justify this 'if'?
179 continue;
180 double phi = fphi.get_phi(i);
181 for (const Op& op : ops.sym_ops) {
182 auto hklp = op.apply_to_hkl(hkl);
183 int lp = hklp[2];
184 if (axis_order == AxisOrder::ZYX)
185 std::swap(hklp[0], hklp[2]);
186 if (!grid.has_index(hklp[0], hklp[1], hklp[2]))
187 continue;
188 int sign = (!half_l || lp >= 0 ? 1 : -1);
189 size_t idx = grid.index_n(sign * hklp[0], sign * hklp[1], sign * hklp[2]);
190 if (grid.data[idx] == default_val) {
191 T theta = sign * T(phi + op.phase_shift(hkl));
192 grid.data[idx] = std::complex<T>(f * std::cos(theta), f * std::sin(theta));
193 }
194 }
195 }
196 if (!ops.is_centrosymmetric())
197 add_friedel_mates(grid);
198 return grid;
199}
200
201template<typename T, typename DataProxy>
202ReciprocalGrid<T> get_value_on_grid(const DataProxy& data, size_t column,
203 std::array<int, 3> size, bool half_l,
204 AxisOrder axis_order=AxisOrder::XYZ) {
206 initialize_hkl_grid(grid, data, size, half_l, axis_order);
207
208 if (column >= data.stride())
209 fail("Map coefficients not found.");
210 GroupOps ops = grid.spacegroup->operations();
211 for (size_t i = 0; i < data.size(); i += data.stride()) {
212 Miller hkl = data.get_hkl(i);
213 T val = (T) data.get_num(i + column);
214 if (val != 0.) {
215 for (const Op& op : ops.sym_ops) {
216 auto hklp = op.apply_to_hkl(hkl);
217 int lp = hklp[2];
218 if (axis_order == AxisOrder::ZYX)
219 std::swap(hklp[0], hklp[2]);
220 if (!grid.has_index(hklp[0], hklp[1], hklp[2]))
221 continue;
222 int sign = (!half_l || lp >= 0 ? 1 : -1);
223 size_t idx = grid.index_n(sign * hklp[0], sign * hklp[1], sign * hklp[2]);
224 if (grid.data[idx] == 0.) // 0 is the default value
225 grid.data[idx] = val;
226 }
227 }
228 }
229 if (!ops.is_centrosymmetric())
230 add_friedel_mates(grid);
231 return grid;
232}
233
234
235template<typename T>
236void transform_f_phi_grid_to_map_(FPhiGrid<T>&& hkl, Grid<T>& map) {
237 // NaNs are not good for FFT, so we change them to 0.
238 // x -> conj(x) is equivalent to changing axis direction before FFT.
239 for (std::complex<T>& x : hkl.data)
240 if (std::isnan(x.imag()))
241 x = 0;
242 else
243 x.imag(-x.imag());
244 map.spacegroup = hkl.spacegroup;
245 map.unit_cell = hkl.unit_cell;
246 map.axis_order = hkl.axis_order;
247 if (hkl.axis_order == AxisOrder::XYZ) {
248 int nw = hkl.half_l ? 2 * (hkl.nw - 1) : hkl.nw;
249 map.set_size(hkl.nu, hkl.nv, nw);
250 } else { // hkl.axis_order == AxisOrder::ZYX
251 int nu = hkl.half_l ? 2 * (hkl.nu - 1) : hkl.nu;
252 check_grid_factors(map.spacegroup, {{hkl.nw, hkl.nv, nu}});
253 map.set_size_without_checking(nu, hkl.nv, hkl.nw);
254 }
255 // FIXME set_size_without_checking is changing axis_order - bad
256 map.axis_order = hkl.axis_order;
257 pocketfft::shape_t shape{(size_t)hkl.nw, (size_t)hkl.nv, (size_t)hkl.nu};
258 std::ptrdiff_t s = sizeof(T);
259 pocketfft::stride_t stride{2*s * hkl.nv * hkl.nu, 2*s * hkl.nu, 2*s};
260 pocketfft::shape_t axes{2, 1, 0};
261 if (hkl.axis_order == AxisOrder::ZYX)
262 std::swap(axes[0], axes[2]);
263 T norm = T(1.0 / hkl.unit_cell.volume);
264 if (hkl.half_l) {
265 size_t last_axis = axes.back();
266 axes.pop_back();
267 pocketfft::c2c<T>(shape, stride, stride, axes, pocketfft::BACKWARD,
268 &hkl.data[0], &hkl.data[0], norm);
269 pocketfft::stride_t stride_out{s * map.nu * map.nv, s * map.nu, s};
270 shape[0] = (size_t) map.nw;
271 shape[2] = (size_t) map.nu;
272 pocketfft::c2r<T>(shape, stride, stride_out, last_axis, pocketfft::BACKWARD,
273 &hkl.data[0], &map.data[0], 1.0f);
274 } else {
275 pocketfft::c2c<T>(shape, stride, stride, axes, pocketfft::BACKWARD,
276 &hkl.data[0], &hkl.data[0], norm);
277 assert(map.data.size() == hkl.data.size());
278 for (size_t i = 0; i != map.data.size(); ++i)
279 map.data[i] = hkl.data[i].real();
280 }
281}
282
283template<typename T>
285 Grid<T> map;
286 transform_f_phi_grid_to_map_(std::forward<FPhiGrid<T>>(hkl), map);
287 return map;
288}
289
290template<typename T, typename FPhi>
292 std::array<int, 3> size,
293 double sample_rate,
294 bool exact_size=false,
295 AxisOrder order=AxisOrder::XYZ) {
296 if (exact_size) {
297 gemmi::check_grid_factors(fphi.spacegroup(), size);
298 } else {
299 size = get_size_for_hkl(fphi, size, sample_rate);
300 }
301 return transform_f_phi_grid_to_map(get_f_phi_on_grid<T>(fphi, size, true, order));
302}
303
304template<typename T, typename FPhi>
306 std::array<int, 3> min_size,
307 double sample_rate,
308 std::array<int, 3> exact_size,
309 AxisOrder order=AxisOrder::XYZ) {
310 bool exact = (exact_size[0] != 0 || exact_size[1] != 0 || exact_size[2] != 0);
312 sample_rate, exact, order);
313}
314
315template<typename T>
316FPhiGrid<T> transform_map_to_f_phi(const Grid<T>& map, bool half_l, bool use_scale=true) {
317 if (half_l && map.axis_order == AxisOrder::ZYX)
318 fail("transform_map_to_f_phi(): half_l + ZYX order are not supported yet");
319 FPhiGrid<T> hkl;
320 hkl.unit_cell = map.unit_cell;
321 hkl.spacegroup = map.spacegroup;
322 hkl.axis_order = map.axis_order;
323 hkl.half_l = half_l;
324 int half_nw = map.nw / 2 + 1;
325 hkl.set_size_without_checking(map.nu, map.nv, half_l ? half_nw : map.nw);
326 T norm = use_scale ? T(map.unit_cell.volume / map.point_count()) : 1;
327 pocketfft::shape_t shape{(size_t)map.nw, (size_t)map.nv, (size_t)map.nu};
328 std::ptrdiff_t s = sizeof(T);
329 pocketfft::stride_t stride_in{s * hkl.nv * hkl.nu, s * hkl.nu, s};
330 pocketfft::stride_t stride{2*s * hkl.nv * hkl.nu, 2*s * hkl.nu, 2*s};
331 pocketfft::r2c<T>(shape, stride_in, stride, /*axis=*/0, pocketfft::FORWARD,
332 &map.data[0], &hkl.data[0], norm);
333 shape[0] = half_nw;
334 pocketfft::c2c<T>(shape, stride, stride, {1, 2}, pocketfft::FORWARD,
335 &hkl.data[0], &hkl.data[0], 1.0f);
336 if (!half_l) // add Friedel pairs
337 for (int w = half_nw; w != hkl.nw; ++w) {
338 int w_ = hkl.nw - w;
339 for (int v = 0; v != hkl.nv; ++v) {
340 int v_ = v == 0 ? 0 : hkl.nv - v;
341 for (int u = 0; u != hkl.nu; ++u) {
342 int u_ = u == 0 ? 0 : hkl.nu - u;
343 size_t idx = hkl.index_q(u, v, w);
344 size_t inv_idx = hkl.index_q(u_, v_, w_);
345 hkl.data[idx] = hkl.data[inv_idx]; // conj() is called later
346 }
347 }
348 }
349 for (int i = 0; i != hkl.nu * hkl.nv * half_nw; ++i)
350 hkl.data[i].imag(-hkl.data[i].imag());
351 return hkl;
352}
353
354} // namespace gemmi
355#endif
fail(), unreachable() and __declspec/__attribute__ macros
Math utilities. 3D linear algebra.
AxisOrder
Order of grid axis.
Definition grid.hpp:24
double phase_in_angles(const std::complex< T > &v, double eps=2e-5)
Definition fourier.hpp:28
MtzDataProxy data_proxy(const Mtz &mtz)
Definition mtz.hpp:596
constexpr double deg(double angle)
Definition math.hpp:31
FPhiGrid< T > get_f_phi_on_grid(const FPhi &fphi, std::array< int, 3 > size, bool half_l, AxisOrder axis_order=AxisOrder::XYZ)
Definition fourier.hpp:168
void add_friedel_mates(ReciprocalGrid< T > &grid)
Definition fourier.hpp:88
std::array< int, 3 > Miller
A synonym for convenient passing of hkl.
Definition unitcell.hpp:129
void add_asu_f_phi_to_float_vector(std::vector< float > &float_data, const AsuData< std::complex< float > > &asu_data)
Definition fourier.hpp:36
constexpr double rad(double angle)
Definition math.hpp:32
Grid< T > transform_f_phi_to_map2(const FPhi &fphi, std::array< int, 3 > min_size, double sample_rate, std::array< int, 3 > exact_size, AxisOrder order=AxisOrder::XYZ)
Definition fourier.hpp:305
FPhiGrid< T > transform_map_to_f_phi(const Grid< T > &map, bool half_l, bool use_scale=true)
Definition fourier.hpp:316
std::array< int, 3 > good_grid_size(std::array< double, 3 > limit, GridSizeRounding rounding, const SpaceGroup *sg)
Definition grid.hpp:78
T friedel_mate_value(T v)
Definition recgrid.hpp:14
void check_grid_factors(const SpaceGroup *sg, std::array< int, 3 > size)
Definition grid.hpp:128
Grid< T > transform_f_phi_grid_to_map(FPhiGrid< T > &&hkl)
Definition fourier.hpp:284
void initialize_hkl_grid(ReciprocalGrid< T > &grid, const DataProxy &data, std::array< int, 3 > size, bool half_l, AxisOrder axis_order)
Definition fourier.hpp:132
void fail(const std::string &msg)
Definition fail.hpp:59
std::array< int, 3 > get_size_for_hkl(const DataProxy &data, std::array< int, 3 > min_size, double sample_rate)
Definition fourier.hpp:48
Grid< T > transform_f_phi_to_map(const FPhi &fphi, std::array< int, 3 > size, double sample_rate, bool exact_size=false, AxisOrder order=AxisOrder::XYZ)
Definition fourier.hpp:291
bool data_fits_into(const DataProxy &data, std::array< int, 3 > size)
Definition fourier.hpp:77
ReciprocalGrid< T > get_value_on_grid(const DataProxy &data, size_t column, std::array< int, 3 > size, bool half_l, AxisOrder axis_order=AxisOrder::XYZ)
Definition fourier.hpp:202
Definition seqid.hpp:151
ReciprocalGrid – grid for reciprocal space data.
FPhiProxy(const DataProxy &data_proxy, size_t f_col, size_t phi_col)
Definition fourier.hpp:153
double get_phi(size_t offset) const
Definition fourier.hpp:160
typename DataProxy::num_type real
Definition fourier.hpp:158
real get_f(size_t offset) const
Definition fourier.hpp:159
size_t stride() const
Definition mtz.hpp:567
Unit cell.
Definition unitcell.hpp:165
double ar
reciprocal parameters a*, b*, c*, alpha*, beta*, gamma*
Definition unitcell.hpp:177
double calculate_1_d2(const Miller &hkl) const
Definition unitcell.hpp:573
Crystallographic Symmetry. Space Groups. Coordinate Triplets.