From 96733f0b699be359ae7129511b2ad9a81490e827 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:56:09 +0000 Subject: [PATCH 01/72] Add start of an indexer program --- CMakeLists.txt | 1 + h5read/CMakeLists.txt | 2 +- h5read/include/h5read.h | 2 + h5read/src/h5read_processed.cc | 33 ++++++++++++++++ indexer/CMakeLists.txt | 18 +++++++++ indexer/indexer.cc | 71 ++++++++++++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 h5read/src/h5read_processed.cc create mode 100644 indexer/CMakeLists.txt create mode 100644 indexer/indexer.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index c10b2f1..b8c07bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,3 +44,4 @@ FetchContent_MakeAvailable(argparse) add_subdirectory(h5read) add_subdirectory(baseline) add_subdirectory(spotfinder) +add_subdirectory(indexer) diff --git a/h5read/CMakeLists.txt b/h5read/CMakeLists.txt index a41d971..e65d922 100644 --- a/h5read/CMakeLists.txt +++ b/h5read/CMakeLists.txt @@ -12,7 +12,7 @@ include(AlwaysColourCompilation) find_package(HDF5) -add_library(h5read src/h5read.c src/h5read.cc) +add_library(h5read src/h5read.c src/h5read.cc src/h5read_processed.cc) target_include_directories(h5read PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) target_link_libraries(h5read PUBLIC $) diff --git a/h5read/include/h5read.h b/h5read/include/h5read.h index bc3b28c..406a0f7 100644 --- a/h5read/include/h5read.h +++ b/h5read/include/h5read.h @@ -103,6 +103,8 @@ h5read_handle *h5read_parse_standard_args(int argc, char **argv); #include #include +std::vector read_xyzobs_data(std::string filename, std::string array_name); + class Image { private: std::shared_ptr _handle; diff --git a/h5read/src/h5read_processed.cc b/h5read/src/h5read_processed.cc new file mode 100644 index 0000000..f828ea7 --- /dev/null +++ b/h5read/src/h5read_processed.cc @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +std::vector read_xyzobs_data(string filename, string array_name){ + auto start_time = std::chrono::high_resolution_clock::now(); + hid_t file = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); + hid_t dataset = H5Dopen(file, array_name.c_str(), H5P_DEFAULT); + hid_t datatype = H5Dget_type(dataset); + size_t datatype_size = H5Tget_size(datatype); + hid_t dataspace = H5Dget_space(dataset); + size_t num_elements = H5Sget_simple_extent_npoints(dataspace); + hid_t space = H5Dget_space(dataset); + std::vector data_out(num_elements); + + H5Dread(dataset, datatype, H5S_ALL, space, H5P_DEFAULT, &data_out[0]); + float total_time = + std::chrono::duration_cast>( + std::chrono::high_resolution_clock::now() - start_time) + .count(); + std::cout << "XYZOBS READ TIME " << total_time << "s" << std::endl; + + H5Dclose(dataset); + H5Fclose(file); + return data_out; +} + diff --git a/indexer/CMakeLists.txt b/indexer/CMakeLists.txt new file mode 100644 index 0000000..f8d7aa1 --- /dev/null +++ b/indexer/CMakeLists.txt @@ -0,0 +1,18 @@ +project(indexer CXX CUDA) + +find_package(CUDAToolkit REQUIRED) + +add_executable(indexer + indexer.cc +) +target_link_libraries(indexer + PRIVATE + fmt + h5read + argparse + standalone + CUDA::cudart + CUDA::nppif + lodepng +) +target_compile_options(indexer PRIVATE "$<$,$>:-G>") diff --git a/indexer/indexer.cc b/indexer/indexer.cc new file mode 100644 index 0000000..c8df807 --- /dev/null +++ b/indexer/indexer.cc @@ -0,0 +1,71 @@ +#include +#include +#include +#include "common.hpp" +#include "cuda_common.hpp" +#include "h5read.h" +#include "standalone.h" +#include +#include + +int main(int argc, char **argv) { + auto parser = CUDAArgumentParser(); + parser.add_h5read_arguments(); //will use h5 file to get metadata + parser.add_argument("--images") + .help("Maximum number of images to process") + .metavar("NUM") + .scan<'u', uint32_t>(); + auto args = parser.parse_args(argc, argv); + + + std::unique_ptr reader_ptr; + + // Wait for read-readiness + + reader_ptr = args.file.empty() ? std::make_unique() + : std::make_unique(args.file); + // Bind this as a reference + Reader &reader = *reader_ptr; + + auto reader_mutex = std::mutex{}; + + uint32_t num_images = parser.is_used("images") ? parser.get("images") + : reader.get_number_of_images(); + + + // first get the Beam properties; wavelength and s0 vector + auto wavelength_opt = reader.get_wavelength(); + if (!wavelength_opt) { + printf( + "Error: No wavelength provided. Please pass wavelength using: " + "--wavelength\n"); + std::exit(1); + } + float wavelength = wavelength_opt.value(); + printf("INDEXER: Got wavelength from file: %f Å\n", wavelength); + //FIXME don't assume s0 vector get from file + std::array s0 {0.0, 0.0, -1.0/wavelength}; + + // now get detector properties + // need fast, slow, norm and origin, plus pixel size + //std::optional> pixel_size = reader.get_pixel_size(); + float pixel_size_x = 0.075; + //FIXME remove assumption of pixel sizes being same in analysis code. + + std::array fast_axis {1.0, 0.0, 0.0}; //FIXME get through reader + std::array slow_axis {0.0, 1.0, 0.0}; //FIXME get through reader + // ^ change basis from nexus to iucr/imgcif convention (invert x and z) + + std::array normal {0.0, 0.0, 1.0}; // fast_axis cross slow_axis + std::array origin {-75.61, 79.95, -150.0}; // FIXME + //Vector3d origin {-75.61, 79.95, -150.0}; // FIXME + + // now get scan properties + + // get processed reflection data from spotfinding + std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24/h5_file/strong.refl"; + std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; + std::vector data = read_xyzobs_data(filename, array_name); + std::cout << data[0] << std::endl; + std::cout << data[data.size()-1] << std::endl; +} \ No newline at end of file From d60712b38619beb9479ecfbdd26f0e8101e85fad Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:53:25 +0000 Subject: [PATCH 02/72] Add xyz_to_rlp function as a first step working example --- indexer/indexer.cc | 29 ++++++++-- indexer/xyz_to_rlp.cc | 122 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 indexer/xyz_to_rlp.cc diff --git a/indexer/indexer.cc b/indexer/indexer.cc index c8df807..f190da7 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -7,6 +7,11 @@ #include "standalone.h" #include #include +#include +#include "xyz_to_rlp.cc" + +using Eigen::Vector3d; +using Eigen::Matrix3d; int main(int argc, char **argv) { auto parser = CUDAArgumentParser(); @@ -53,19 +58,35 @@ int main(int argc, char **argv) { //FIXME remove assumption of pixel sizes being same in analysis code. std::array fast_axis {1.0, 0.0, 0.0}; //FIXME get through reader - std::array slow_axis {0.0, 1.0, 0.0}; //FIXME get through reader + std::array slow_axis {0.0, -1.0, 0.0}; //FIXME get through reader // ^ change basis from nexus to iucr/imgcif convention (invert x and z) std::array normal {0.0, 0.0, 1.0}; // fast_axis cross slow_axis - std::array origin {-75.61, 79.95, -150.0}; // FIXME - //Vector3d origin {-75.61, 79.95, -150.0}; // FIXME + //std::array origin {-75.61, 79.95, -150.0}; // FIXME + Vector3d origin {-75.61, 79.95, -150.0}; // FIXME + + Matrix3d d_matrix{{fast_axis[0], slow_axis[0], normal[0]+origin[0], + fast_axis[1], slow_axis[1], normal[1]+origin[1], + fast_axis[2], slow_axis[2], normal[2]+origin[2]}}; + // now get scan properties e.g. + int image_range_start = 0; + double osc_start = 0.0; + double osc_width = 0.1; - // now get scan properties + // finally gonio properties e.g. + Matrix3d fixed_rotation{{0.965028,0.0598562,-0.255222},{-0.128604,-0.74028,-0.659883},{-0.228434,0.669628,-0.706694}}; + Vector3d rotation_axis {1.0,0.0,0.0}; + Matrix3d setting_rotation {{1,0,0},{0,1,0},{0,0,1}}; // get processed reflection data from spotfinding std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24/h5_file/strong.refl"; std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; std::vector data = read_xyzobs_data(filename, array_name); + std::vector rlp = xyz_to_rlp( + data, fixed_rotation, d_matrix, wavelength, pixel_size_x,image_range_start, + osc_start, osc_width, rotation_axis, setting_rotation); std::cout << data[0] << std::endl; std::cout << data[data.size()-1] << std::endl; + std::cout << rlp[0][0] << std::endl; + std::cout << rlp[rlp.size()-1][0] << std::endl; } \ No newline at end of file diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc new file mode 100644 index 0000000..25020ae --- /dev/null +++ b/indexer/xyz_to_rlp.cc @@ -0,0 +1,122 @@ +#include +#include +#include + +using Eigen::Matrix3d; +using Eigen::Vector3d; + +class SimpleBeam { +public: + double wavelength; + Vector3d s0; + + SimpleBeam(double wavelength) { + this->wavelength = wavelength; + s0 = {0.0, 0.0, -1.0 / wavelength}; + } +}; + +class SimpleDetector { +public: + Matrix3d d_matrix; + double pixel_size; // in mm + + SimpleDetector(Matrix3d d_matrix, double pixel_size) { + this->d_matrix = d_matrix; + this->pixel_size = pixel_size; + } +}; + +class SimpleScan { +public: + int image_range_start; + double osc_start; + double osc_width; + + SimpleScan(int image_range_start, double osc_start, double osc_width) { + this->image_range_start = image_range_start; + this->osc_start = osc_start; + this->osc_width = osc_width; + } +}; + +class SimpleGonio { +public: + Matrix3d sample_rotation; + Vector3d rotation_axis; + Matrix3d setting_rotation; + Matrix3d sample_rotation_inverse; + Matrix3d setting_rotation_inverse; + + SimpleGonio(Matrix3d sample_rotation, + Vector3d rotation_axis, + Matrix3d setting_rotation) { + this->sample_rotation = sample_rotation; + rotation_axis.normalize(); + this->rotation_axis = rotation_axis; + this->setting_rotation = setting_rotation; + sample_rotation_inverse = this->sample_rotation.inverse(); + setting_rotation_inverse = this->setting_rotation.inverse(); + } +}; + +std::vector xyz_to_rlp( + std::vector xyzobs_px, + Matrix3d sample_rotation, + Matrix3d detector_d_matrix, + double wavelength, + double pixel_size_mm, + int image_range_start, + double osc_start, + double osc_width, + Vector3d rotation_axis, + Matrix3d setting_rotation) { + auto start = std::chrono::system_clock::now(); + // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method + SimpleBeam beam(wavelength); + SimpleDetector detector(detector_d_matrix, pixel_size_mm); + SimpleScan scan(image_range_start, osc_start, osc_width); + SimpleGonio gonio(sample_rotation, rotation_axis, setting_rotation); + + float DEG2RAD = M_PI / 180.0; + + std::vector rlp(xyzobs_px.size() / 3); + for (int i = 0; i < rlp.size(); ++i) { + // first convert detector pixel positions into mm + int vec_idx= 3*i; + double x1 = xyzobs_px[vec_idx]; + double x2 = xyzobs_px[vec_idx+1]; + double x3 = xyzobs_px[vec_idx+2]; + double x_mm = x1 * detector.pixel_size; + double y_mm = x2 * detector.pixel_size; + // convert the image 'z' coordinate to rotation angle based on the scan data + double rot_angle = + (((x3 + 1 - scan.image_range_start) * scan.osc_width) + scan.osc_start) * DEG2RAD; + + // calculate the s1 vector using the detector d matrix + Vector3d m = {x_mm, y_mm, 1.0}; + Vector3d s1_i = detector.d_matrix * m; + s1_i.normalize(); + // convert into inverse ansgtroms + Vector3d s1_this = s1_i / beam.wavelength; + + // now apply the goniometer matrices + // see https://dials.github.io/documentation/conventions.html for full conventions + // rlp = F^-1 * R'^-1 * S^-1 * (s1-s0) + Vector3d S = gonio.setting_rotation_inverse * (s1_this - beam.s0); + double cos = std::cos(-1.0 * rot_angle); + double sin = std::sin(-1.0 * rot_angle); + Vector3d rlp_this = (S * cos) + + (gonio.rotation_axis * gonio.rotation_axis.dot(S) * (1 - cos)) + + (sin * gonio.rotation_axis.cross(S)); + + // lp_this = S.rotate_around_origin(gonio.rotation_axis, -1.0 * rot_angle); + rlp[i] = gonio.sample_rotation_inverse * rlp_this; + } + + auto end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + std::cout << "elapsed time for xyz_to_rlp: " << elapsed_seconds.count() << "s" + << std::endl; + return rlp; +} \ No newline at end of file From 8b68a8948eb1d3cea7bc94ee325635a664b97d30 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:58:16 +0000 Subject: [PATCH 03/72] Add fft3d with pocketfft --- include/pocketfft_hdronly.h | 3578 +++++++++++++++++++++++++++++++++++ indexer/fft3d.cc | 125 ++ indexer/indexer.cc | 24 +- indexer/xyz_to_rlp.cc | 2 +- 4 files changed, 3722 insertions(+), 7 deletions(-) create mode 100644 include/pocketfft_hdronly.h create mode 100644 indexer/fft3d.cc diff --git a/include/pocketfft_hdronly.h b/include/pocketfft_hdronly.h new file mode 100644 index 0000000..d75ada6 --- /dev/null +++ b/include/pocketfft_hdronly.h @@ -0,0 +1,3578 @@ +/* +This file is part of pocketfft. + +Copyright (C) 2010-2021 Max-Planck-Society +Copyright (C) 2019-2020 Peter Bell + +For the odd-sized DCT-IV transforms: + Copyright (C) 2003, 2007-14 Matteo Frigo + Copyright (C) 2003, 2007-14 Massachusetts Institute of Technology + +Authors: Martin Reinecke, Peter Bell + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +* Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef POCKETFFT_HDRONLY_H +#define POCKETFFT_HDRONLY_H + +#ifndef __cplusplus +#error This file is C++ and requires a C++ compiler. +#endif + +#if !(__cplusplus >= 201103L || _MSVC_LANG+0L >= 201103L) +#error This file requires at least C++11 support. +#endif + +#ifndef POCKETFFT_CACHE_SIZE +#define POCKETFFT_CACHE_SIZE 0 +#endif + +#include +#include +#include +#include +#include +#include +#include +#if POCKETFFT_CACHE_SIZE!=0 +#include +#include +#endif + +#ifndef POCKETFFT_NO_MULTITHREADING +#include +#include +#include +#include +#include +#include +#include + +#ifdef POCKETFFT_PTHREADS +# include +#endif +#endif + +#if defined(__GNUC__) +#define POCKETFFT_NOINLINE __attribute__((noinline)) +#define POCKETFFT_RESTRICT __restrict__ +#elif defined(_MSC_VER) +#define POCKETFFT_NOINLINE __declspec(noinline) +#define POCKETFFT_RESTRICT __restrict +#else +#define POCKETFFT_NOINLINE +#define POCKETFFT_RESTRICT +#endif + +namespace pocketfft { + +namespace detail { +using std::size_t; +using std::ptrdiff_t; + +// Always use std:: for functions +template T cos(T) = delete; +template T sin(T) = delete; +template T sqrt(T) = delete; + +using shape_t = std::vector; +using stride_t = std::vector; + +constexpr bool FORWARD = true, + BACKWARD = false; + +// only enable vector support for gcc>=5.0 and clang>=5.0 +#ifndef POCKETFFT_NO_VECTORS +#define POCKETFFT_NO_VECTORS +#if defined(__INTEL_COMPILER) +// do nothing. This is necessary because this compiler also sets __GNUC__. +#elif defined(__clang__) +// AppleClang has their own version numbering +#ifdef __apple_build_version__ +# if (__clang_major__ > 9) || (__clang_major__ == 9 && __clang_minor__ >= 1) +# undef POCKETFFT_NO_VECTORS +# endif +#elif __clang_major__ >= 5 +# undef POCKETFFT_NO_VECTORS +#endif +#elif defined(__GNUC__) +#if __GNUC__>=5 +#undef POCKETFFT_NO_VECTORS +#endif +#endif +#endif + +template struct VLEN { static constexpr size_t val=1; }; + +#ifndef POCKETFFT_NO_VECTORS +#if (defined(__AVX512F__)) +template<> struct VLEN { static constexpr size_t val=16; }; +template<> struct VLEN { static constexpr size_t val=8; }; +#elif (defined(__AVX__)) +template<> struct VLEN { static constexpr size_t val=8; }; +template<> struct VLEN { static constexpr size_t val=4; }; +#elif (defined(__SSE2__)) +template<> struct VLEN { static constexpr size_t val=4; }; +template<> struct VLEN { static constexpr size_t val=2; }; +#elif (defined(__VSX__)) +template<> struct VLEN { static constexpr size_t val=4; }; +template<> struct VLEN { static constexpr size_t val=2; }; +#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) +template<> struct VLEN { static constexpr size_t val=4; }; +template<> struct VLEN { static constexpr size_t val=2; }; +#else +#define POCKETFFT_NO_VECTORS +#endif +#endif + +#if __cplusplus >= 201703L +inline void *aligned_alloc(size_t align, size_t size) + { + // aligned_alloc() requires that the requested size is a multiple of "align" + void *ptr = ::aligned_alloc(align,(size+align-1)&(~(align-1))); + if (!ptr) throw std::bad_alloc(); + return ptr; + } +inline void aligned_dealloc(void *ptr) + { free(ptr); } +#else // portable emulation +inline void *aligned_alloc(size_t align, size_t size) + { + align = std::max(align, alignof(max_align_t)); + void *ptr = malloc(size+align); + if (!ptr) throw std::bad_alloc(); + void *res = reinterpret_cast + ((reinterpret_cast(ptr) & ~(uintptr_t(align-1))) + uintptr_t(align)); + (reinterpret_cast(res))[-1] = ptr; + return res; + } +inline void aligned_dealloc(void *ptr) + { if (ptr) free((reinterpret_cast(ptr))[-1]); } +#endif + +template class arr + { + private: + T *p; + size_t sz; + +#if defined(POCKETFFT_NO_VECTORS) + static T *ralloc(size_t num) + { + if (num==0) return nullptr; + void *res = malloc(num*sizeof(T)); + if (!res) throw std::bad_alloc(); + return reinterpret_cast(res); + } + static void dealloc(T *ptr) + { free(ptr); } +#else + static T *ralloc(size_t num) + { + if (num==0) return nullptr; + void *ptr = aligned_alloc(64, num*sizeof(T)); + return static_cast(ptr); + } + static void dealloc(T *ptr) + { aligned_dealloc(ptr); } +#endif + + public: + arr() : p(0), sz(0) {} + arr(size_t n) : p(ralloc(n)), sz(n) {} + arr(arr &&other) + : p(other.p), sz(other.sz) + { other.p=nullptr; other.sz=0; } + ~arr() { dealloc(p); } + + void resize(size_t n) + { + if (n==sz) return; + dealloc(p); + p = ralloc(n); + sz = n; + } + + T &operator[](size_t idx) { return p[idx]; } + const T &operator[](size_t idx) const { return p[idx]; } + + T *data() { return p; } + const T *data() const { return p; } + + size_t size() const { return sz; } + }; + +template struct cmplx { + T r, i; + cmplx() {} + cmplx(T r_, T i_) : r(r_), i(i_) {} + void Set(T r_, T i_) { r=r_; i=i_; } + void Set(T r_) { r=r_; i=T(0); } + cmplx &operator+= (const cmplx &other) + { r+=other.r; i+=other.i; return *this; } + templatecmplx &operator*= (T2 other) + { r*=other; i*=other; return *this; } + templatecmplx &operator*= (const cmplx &other) + { + T tmp = r*other.r - i*other.i; + i = r*other.i + i*other.r; + r = tmp; + return *this; + } + templatecmplx &operator+= (const cmplx &other) + { r+=other.r; i+=other.i; return *this; } + templatecmplx &operator-= (const cmplx &other) + { r-=other.r; i-=other.i; return *this; } + template auto operator* (const T2 &other) const + -> cmplx + { return {r*other, i*other}; } + template auto operator+ (const cmplx &other) const + -> cmplx + { return {r+other.r, i+other.i}; } + template auto operator- (const cmplx &other) const + -> cmplx + { return {r-other.r, i-other.i}; } + template auto operator* (const cmplx &other) const + -> cmplx + { return {r*other.r-i*other.i, r*other.i + i*other.r}; } + template auto special_mul (const cmplx &other) const + -> cmplx + { + using Tres = cmplx; + return fwd ? Tres(r*other.r+i*other.i, i*other.r-r*other.i) + : Tres(r*other.r-i*other.i, r*other.i+i*other.r); + } +}; +template inline void PM(T &a, T &b, T c, T d) + { a=c+d; b=c-d; } +template inline void PMINPLACE(T &a, T &b) + { T t = a; a+=b; b=t-b; } +template inline void MPINPLACE(T &a, T &b) + { T t = a; a-=b; b=t+b; } +template cmplx conj(const cmplx &a) + { return {a.r, -a.i}; } +template void special_mul (const cmplx &v1, const cmplx &v2, cmplx &res) + { + res = fwd ? cmplx(v1.r*v2.r+v1.i*v2.i, v1.i*v2.r-v1.r*v2.i) + : cmplx(v1.r*v2.r-v1.i*v2.i, v1.r*v2.i+v1.i*v2.r); + } + +template void ROT90(cmplx &a) + { auto tmp_=a.r; a.r=-a.i; a.i=tmp_; } +template void ROTX90(cmplx &a) + { auto tmp_= fwd ? -a.r : a.r; a.r = fwd ? a.i : -a.i; a.i=tmp_; } + +// +// twiddle factor section +// +template class sincos_2pibyn + { + private: + using Thigh = typename std::conditional<(sizeof(T)>sizeof(double)), T, double>::type; + size_t N, mask, shift; + arr> v1, v2; + + static cmplx calc(size_t x, size_t n, Thigh ang) + { + x<<=3; + if (x<4*n) // first half + { + if (x<2*n) // first quadrant + { + if (x(std::cos(Thigh(x)*ang), std::sin(Thigh(x)*ang)); + return cmplx(std::sin(Thigh(2*n-x)*ang), std::cos(Thigh(2*n-x)*ang)); + } + else // second quadrant + { + x-=2*n; + if (x(-std::sin(Thigh(x)*ang), std::cos(Thigh(x)*ang)); + return cmplx(-std::cos(Thigh(2*n-x)*ang), std::sin(Thigh(2*n-x)*ang)); + } + } + else + { + x=8*n-x; + if (x<2*n) // third quadrant + { + if (x(std::cos(Thigh(x)*ang), -std::sin(Thigh(x)*ang)); + return cmplx(std::sin(Thigh(2*n-x)*ang), -std::cos(Thigh(2*n-x)*ang)); + } + else // fourth quadrant + { + x-=2*n; + if (x(-std::sin(Thigh(x)*ang), -std::cos(Thigh(x)*ang)); + return cmplx(-std::cos(Thigh(2*n-x)*ang), -std::sin(Thigh(2*n-x)*ang)); + } + } + } + + public: + POCKETFFT_NOINLINE sincos_2pibyn(size_t n) + : N(n) + { + constexpr auto pi = 3.141592653589793238462643383279502884197L; + Thigh ang = Thigh(0.25L*pi/n); + size_t nval = (n+2)/2; + shift = 1; + while((size_t(1)< operator[](size_t idx) const + { + if (2*idx<=N) + { + auto x1=v1[idx&mask], x2=v2[idx>>shift]; + return cmplx(T(x1.r*x2.r-x1.i*x2.i), T(x1.r*x2.i+x1.i*x2.r)); + } + idx = N-idx; + auto x1=v1[idx&mask], x2=v2[idx>>shift]; + return cmplx(T(x1.r*x2.r-x1.i*x2.i), -T(x1.r*x2.i+x1.i*x2.r)); + } + }; + +struct util // hack to avoid duplicate symbols + { + static POCKETFFT_NOINLINE size_t largest_prime_factor (size_t n) + { + size_t res=1; + while ((n&1)==0) + { res=2; n>>=1; } + for (size_t x=3; x*x<=n; x+=2) + while ((n%x)==0) + { res=x; n/=x; } + if (n>1) res=n; + return res; + } + + static POCKETFFT_NOINLINE double cost_guess (size_t n) + { + constexpr double lfp=1.1; // penalty for non-hardcoded larger factors + size_t ni=n; + double result=0.; + while ((n&1)==0) + { result+=2; n>>=1; } + for (size_t x=3; x*x<=n; x+=2) + while ((n%x)==0) + { + result+= (x<=5) ? double(x) : lfp*double(x); // penalize larger prime factors + n/=x; + } + if (n>1) result+=(n<=5) ? double(n) : lfp*double(n); + return result*double(ni); + } + + /* returns the smallest composite of 2, 3, 5, 7 and 11 which is >= n */ + static POCKETFFT_NOINLINE size_t good_size_cmplx(size_t n) + { + if (n<=12) return n; + + size_t bestfac=2*n; + for (size_t f11=1; f11n) + { + if (x>=1; + } + else + return n; + } + } + return bestfac; + } + + /* returns the smallest composite of 2, 3, 5 which is >= n */ + static POCKETFFT_NOINLINE size_t good_size_real(size_t n) + { + if (n<=6) return n; + + size_t bestfac=2*n; + for (size_t f5=1; f5n) + { + if (x>=1; + } + else + return n; + } + } + return bestfac; + } + + static size_t prod(const shape_t &shape) + { + size_t res=1; + for (auto sz: shape) + res*=sz; + return res; + } + + static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, bool inplace) + { + auto ndim = shape.size(); + if (ndim<1) throw std::runtime_error("ndim must be >= 1"); + if ((stride_in.size()!=ndim) || (stride_out.size()!=ndim)) + throw std::runtime_error("stride dimension mismatch"); + if (inplace && (stride_in!=stride_out)) + throw std::runtime_error("stride mismatch"); + } + + static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, bool inplace, + const shape_t &axes) + { + sanity_check(shape, stride_in, stride_out, inplace); + auto ndim = shape.size(); + shape_t tmp(ndim,0); + for (auto ax : axes) + { + if (ax>=ndim) throw std::invalid_argument("bad axis number"); + if (++tmp[ax]>1) throw std::invalid_argument("axis specified repeatedly"); + } + } + + static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, bool inplace, + size_t axis) + { + sanity_check(shape, stride_in, stride_out, inplace); + if (axis>=shape.size()) throw std::invalid_argument("bad axis number"); + } + +#ifdef POCKETFFT_NO_MULTITHREADING + static size_t thread_count (size_t /*nthreads*/, const shape_t &/*shape*/, + size_t /*axis*/, size_t /*vlen*/) + { return 1; } +#else + static size_t thread_count (size_t nthreads, const shape_t &shape, + size_t axis, size_t vlen) + { + if (nthreads==1) return 1; + size_t size = prod(shape); + size_t parallel = size / (shape[axis] * vlen); + if (shape[axis] < 1000) + parallel /= 4; + size_t max_threads = nthreads == 0 ? + std::thread::hardware_concurrency() : nthreads; + return std::max(size_t(1), std::min(parallel, max_threads)); + } +#endif + }; + +namespace threading { + +#ifdef POCKETFFT_NO_MULTITHREADING + +constexpr inline size_t thread_id() { return 0; } +constexpr inline size_t num_threads() { return 1; } + +template +void thread_map(size_t /* nthreads */, Func f) + { f(); } + +#else + +inline size_t &thread_id() + { + static thread_local size_t thread_id_=0; + return thread_id_; + } +inline size_t &num_threads() + { + static thread_local size_t num_threads_=1; + return num_threads_; + } +static const size_t max_threads = std::max(1u, std::thread::hardware_concurrency()); + +class latch + { + std::atomic num_left_; + std::mutex mut_; + std::condition_variable completed_; + using lock_t = std::unique_lock; + + public: + latch(size_t n): num_left_(n) {} + + void count_down() + { + lock_t lock(mut_); + if (--num_left_) + return; + completed_.notify_all(); + } + + void wait() + { + lock_t lock(mut_); + completed_.wait(lock, [this]{ return is_ready(); }); + } + bool is_ready() { return num_left_ == 0; } + }; + +template class concurrent_queue + { + std::queue q_; + std::mutex mut_; + std::atomic size_; + using lock_t = std::lock_guard; + + public: + + void push(T val) + { + lock_t lock(mut_); + ++size_; + q_.push(std::move(val)); + } + + bool try_pop(T &val) + { + if (size_ == 0) return false; + lock_t lock(mut_); + // Queue might have been emptied while we acquired the lock + if (q_.empty()) return false; + + val = std::move(q_.front()); + --size_; + q_.pop(); + return true; + } + + bool empty() const { return size_==0; } + }; + +// C++ allocator with support for over-aligned types +template struct aligned_allocator + { + using value_type = T; + template + aligned_allocator(const aligned_allocator&) {} + aligned_allocator() = default; + + T *allocate(size_t n) + { + void* mem = aligned_alloc(alignof(T), n*sizeof(T)); + return static_cast(mem); + } + + void deallocate(T *p, size_t /*n*/) + { aligned_dealloc(p); } + }; + +class thread_pool + { + // A reasonable guess, probably close enough for most hardware + static constexpr size_t cache_line_size = 64; + struct alignas(cache_line_size) worker + { + std::thread thread; + std::condition_variable work_ready; + std::mutex mut; + std::atomic_flag busy_flag = ATOMIC_FLAG_INIT; + std::function work; + + void worker_main( + std::atomic &shutdown_flag, + std::atomic &unscheduled_tasks, + concurrent_queue> &overflow_work) + { + using lock_t = std::unique_lock; + bool expect_work = true; + while (!shutdown_flag || expect_work) + { + std::function local_work; + if (expect_work || unscheduled_tasks == 0) + { + lock_t lock(mut); + // Wait until there is work to be executed + work_ready.wait(lock, [&]{ return (work || shutdown_flag); }); + local_work.swap(work); + expect_work = false; + } + + bool marked_busy = false; + if (local_work) + { + marked_busy = true; + local_work(); + } + + if (!overflow_work.empty()) + { + if (!marked_busy && busy_flag.test_and_set()) + { + expect_work = true; + continue; + } + marked_busy = true; + + while (overflow_work.try_pop(local_work)) + { + --unscheduled_tasks; + local_work(); + } + } + + if (marked_busy) busy_flag.clear(); + } + } + }; + + concurrent_queue> overflow_work_; + std::mutex mut_; + std::vector> workers_; + std::atomic shutdown_; + std::atomic unscheduled_tasks_; + using lock_t = std::lock_guard; + + void create_threads() + { + lock_t lock(mut_); + size_t nthreads=workers_.size(); + for (size_t i=0; ibusy_flag.clear(); + worker->work = nullptr; + worker->thread = std::thread([worker, this] + { + worker->worker_main(shutdown_, unscheduled_tasks_, overflow_work_); + }); + } + catch (...) + { + shutdown_locked(); + throw; + } + } + } + + void shutdown_locked() + { + shutdown_ = true; + for (auto &worker : workers_) + worker.work_ready.notify_all(); + + for (auto &worker : workers_) + if (worker.thread.joinable()) + worker.thread.join(); + } + + public: + explicit thread_pool(size_t nthreads): + workers_(nthreads) + { create_threads(); } + + thread_pool(): thread_pool(max_threads) {} + + ~thread_pool() { shutdown(); } + + void submit(std::function work) + { + lock_t lock(mut_); + if (shutdown_) + throw std::runtime_error("Work item submitted after shutdown"); + + ++unscheduled_tasks_; + + // First check for any idle workers and wake those + for (auto &worker : workers_) + if (!worker.busy_flag.test_and_set()) + { + --unscheduled_tasks_; + { + lock_t lock(worker.mut); + worker.work = std::move(work); + } + worker.work_ready.notify_one(); + return; + } + + // If no workers were idle, push onto the overflow queue for later + overflow_work_.push(std::move(work)); + } + + void shutdown() + { + lock_t lock(mut_); + shutdown_locked(); + } + + void restart() + { + shutdown_ = false; + create_threads(); + } + }; + +inline thread_pool & get_pool() + { + static thread_pool pool; +#ifdef POCKETFFT_PTHREADS + static std::once_flag f; + std::call_once(f, + []{ + pthread_atfork( + +[]{ get_pool().shutdown(); }, // prepare + +[]{ get_pool().restart(); }, // parent + +[]{ get_pool().restart(); } // child + ); + }); +#endif + + return pool; + } + +/** Map a function f over nthreads */ +template +void thread_map(size_t nthreads, Func f) + { + if (nthreads == 0) + nthreads = max_threads; + + if (nthreads == 1) + { f(); return; } + + auto & pool = get_pool(); + latch counter(nthreads); + std::exception_ptr ex; + std::mutex ex_mut; + for (size_t i=0; i lock(ex_mut); + ex = std::current_exception(); + } + counter.count_down(); + }); + } + counter.wait(); + if (ex) + std::rethrow_exception(ex); + } + +#endif + +} + +// +// complex FFTPACK transforms +// + +template class cfftp + { + private: + struct fctdata + { + size_t fct; + cmplx *tw, *tws; + }; + + size_t length; + arr> mem; + std::vector fact; + + void add_factor(size_t factor) + { fact.push_back({factor, nullptr, nullptr}); } + +template void pass2 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+2*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k(CC(i,0,k)-CC(i,1,k),WA(0,i),CH(i,k,1)); + } + } + } + +#define POCKETFFT_PREP3(idx) \ + T t0 = CC(idx,0,k), t1, t2; \ + PM (t1,t2,CC(idx,1,k),CC(idx,2,k)); \ + CH(idx,k,0)=t0+t1; +#define POCKETFFT_PARTSTEP3a(u1,u2,twr,twi) \ + { \ + T ca=t0+t1*twr; \ + T cb{-t2.i*twi, t2.r*twi}; \ + PM(CH(0,k,u1),CH(0,k,u2),ca,cb) ;\ + } +#define POCKETFFT_PARTSTEP3b(u1,u2,twr,twi) \ + { \ + T ca=t0+t1*twr; \ + T cb{-t2.i*twi, t2.r*twi}; \ + special_mul(ca+cb,WA(u1-1,i),CH(i,k,u1)); \ + special_mul(ca-cb,WA(u2-1,i),CH(i,k,u2)); \ + } +template void pass3 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tw1r=-0.5, + tw1i= (fwd ? -1: 1) * T0(0.8660254037844386467637231707529362L); + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+3*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k void pass4 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+4*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k(t4); + PM(CH(0,k,0),CH(0,k,2),t2,t3); + PM(CH(0,k,1),CH(0,k,3),t1,t4); + } + else + for (size_t k=0; k(t4); + PM(CH(0,k,0),CH(0,k,2),t2,t3); + PM(CH(0,k,1),CH(0,k,3),t1,t4); + } + for (size_t i=1; i(t4); + CH(i,k,0) = t2+t3; + special_mul(t1+t4,WA(0,i),CH(i,k,1)); + special_mul(t2-t3,WA(1,i),CH(i,k,2)); + special_mul(t1-t4,WA(2,i),CH(i,k,3)); + } + } + } + +#define POCKETFFT_PREP5(idx) \ + T t0 = CC(idx,0,k), t1, t2, t3, t4; \ + PM (t1,t4,CC(idx,1,k),CC(idx,4,k)); \ + PM (t2,t3,CC(idx,2,k),CC(idx,3,k)); \ + CH(idx,k,0).r=t0.r+t1.r+t2.r; \ + CH(idx,k,0).i=t0.i+t1.i+t2.i; + +#define POCKETFFT_PARTSTEP5a(u1,u2,twar,twbr,twai,twbi) \ + { \ + T ca,cb; \ + ca.r=t0.r+twar*t1.r+twbr*t2.r; \ + ca.i=t0.i+twar*t1.i+twbr*t2.i; \ + cb.i=twai*t4.r twbi*t3.r; \ + cb.r=-(twai*t4.i twbi*t3.i); \ + PM(CH(0,k,u1),CH(0,k,u2),ca,cb); \ + } + +#define POCKETFFT_PARTSTEP5b(u1,u2,twar,twbr,twai,twbi) \ + { \ + T ca,cb,da,db; \ + ca.r=t0.r+twar*t1.r+twbr*t2.r; \ + ca.i=t0.i+twar*t1.i+twbr*t2.i; \ + cb.i=twai*t4.r twbi*t3.r; \ + cb.r=-(twai*t4.i twbi*t3.i); \ + special_mul(ca+cb,WA(u1-1,i),CH(i,k,u1)); \ + special_mul(ca-cb,WA(u2-1,i),CH(i,k,u2)); \ + } +template void pass5 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tw1r= T0(0.3090169943749474241022934171828191L), + tw1i= (fwd ? -1: 1) * T0(0.9510565162951535721164393333793821L), + tw2r= T0(-0.8090169943749474241022934171828191L), + tw2i= (fwd ? -1: 1) * T0(0.5877852522924731291687059546390728L); + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+5*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k(da,WA(u1-1,i),CH(i,k,u1)); \ + special_mul(db,WA(u2-1,i),CH(i,k,u2)); \ + } + +template void pass7(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tw1r= T0(0.6234898018587335305250048840042398L), + tw1i= (fwd ? -1 : 1) * T0(0.7818314824680298087084445266740578L), + tw2r= T0(-0.2225209339563144042889025644967948L), + tw2i= (fwd ? -1 : 1) * T0(0.9749279121818236070181316829939312L), + tw3r= T0(-0.9009688679024191262361023195074451L), + tw3i= (fwd ? -1 : 1) * T0(0.433883739117558120475768332848359L); + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+7*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k void ROTX45(T &a) const + { + constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); + if (fwd) + { auto tmp_=a.r; a.r=hsqt2*(a.r+a.i); a.i=hsqt2*(a.i-tmp_); } + else + { auto tmp_=a.r; a.r=hsqt2*(a.r-a.i); a.i=hsqt2*(a.i+tmp_); } + } +template void ROTX135(T &a) const + { + constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); + if (fwd) + { auto tmp_=a.r; a.r=hsqt2*(a.i-a.r); a.i=hsqt2*(-tmp_-a.i); } + else + { auto tmp_=a.r; a.r=hsqt2*(-a.r-a.i); a.i=hsqt2*(tmp_-a.i); } + } + +template void pass8 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+8*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k(a3); + + ROTX90(a7); + PMINPLACE(a5,a7); + ROTX45(a5); + ROTX135(a7); + + PM(a0,a4,CC(0,0,k),CC(0,4,k)); + PM(a2,a6,CC(0,2,k),CC(0,6,k)); + PM(CH(0,k,0),CH(0,k,4),a0+a2,a1); + PM(CH(0,k,2),CH(0,k,6),a0-a2,a3); + ROTX90(a6); + PM(CH(0,k,1),CH(0,k,5),a4+a6,a5); + PM(CH(0,k,3),CH(0,k,7),a4-a6,a7); + } + else + for (size_t k=0; k(a3); + + ROTX90(a7); + PMINPLACE(a5,a7); + ROTX45(a5); + ROTX135(a7); + + PM(a0,a4,CC(0,0,k),CC(0,4,k)); + PM(a2,a6,CC(0,2,k),CC(0,6,k)); + PM(CH(0,k,0),CH(0,k,4),a0+a2,a1); + PM(CH(0,k,2),CH(0,k,6),a0-a2,a3); + ROTX90(a6); + PM(CH(0,k,1),CH(0,k,5),a4+a6,a5); + PM(CH(0,k,3),CH(0,k,7),a4-a6,a7); + } + for (size_t i=1; i(a7); + PMINPLACE(a1,a3); + ROTX90(a3); + PMINPLACE(a5,a7); + ROTX45(a5); + ROTX135(a7); + PM(a0,a4,CC(i,0,k),CC(i,4,k)); + PM(a2,a6,CC(i,2,k),CC(i,6,k)); + PMINPLACE(a0,a2); + CH(i,k,0) = a0+a1; + special_mul(a0-a1,WA(3,i),CH(i,k,4)); + special_mul(a2+a3,WA(1,i),CH(i,k,2)); + special_mul(a2-a3,WA(5,i),CH(i,k,6)); + ROTX90(a6); + PMINPLACE(a4,a6); + special_mul(a4+a5,WA(0,i),CH(i,k,1)); + special_mul(a4-a5,WA(4,i),CH(i,k,5)); + special_mul(a6+a7,WA(2,i),CH(i,k,3)); + special_mul(a6-a7,WA(6,i),CH(i,k,7)); + } + } + } + + +#define POCKETFFT_PREP11(idx) \ + T t1 = CC(idx,0,k), t2, t3, t4, t5, t6, t7, t8, t9, t10, t11; \ + PM (t2,t11,CC(idx,1,k),CC(idx,10,k)); \ + PM (t3,t10,CC(idx,2,k),CC(idx, 9,k)); \ + PM (t4,t9 ,CC(idx,3,k),CC(idx, 8,k)); \ + PM (t5,t8 ,CC(idx,4,k),CC(idx, 7,k)); \ + PM (t6,t7 ,CC(idx,5,k),CC(idx, 6,k)); \ + CH(idx,k,0).r=t1.r+t2.r+t3.r+t4.r+t5.r+t6.r; \ + CH(idx,k,0).i=t1.i+t2.i+t3.i+t4.i+t5.i+t6.i; + +#define POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,out1,out2) \ + { \ + T ca = t1 + t2*x1 + t3*x2 + t4*x3 + t5*x4 +t6*x5, \ + cb; \ + cb.i=y1*t11.r y2*t10.r y3*t9.r y4*t8.r y5*t7.r; \ + cb.r=-(y1*t11.i y2*t10.i y3*t9.i y4*t8.i y5*t7.i ); \ + PM(out1,out2,ca,cb); \ + } +#define POCKETFFT_PARTSTEP11a(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5) \ + POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,CH(0,k,u1),CH(0,k,u2)) +#define POCKETFFT_PARTSTEP11(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5) \ + { \ + T da,db; \ + POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,da,db) \ + special_mul(da,WA(u1-1,i),CH(i,k,u1)); \ + special_mul(db,WA(u2-1,i),CH(i,k,u2)); \ + } + +template void pass11 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tw1r= T0(0.8412535328311811688618116489193677L), + tw1i= (fwd ? -1 : 1) * T0(0.5406408174555975821076359543186917L), + tw2r= T0(0.4154150130018864255292741492296232L), + tw2i= (fwd ? -1 : 1) * T0(0.9096319953545183714117153830790285L), + tw3r= T0(-0.1423148382732851404437926686163697L), + tw3i= (fwd ? -1 : 1) * T0(0.9898214418809327323760920377767188L), + tw4r= T0(-0.6548607339452850640569250724662936L), + tw4i= (fwd ? -1 : 1) * T0(0.7557495743542582837740358439723444L), + tw5r= T0(-0.9594929736144973898903680570663277L), + tw5i= (fwd ? -1 : 1) * T0(0.2817325568414296977114179153466169L); + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+11*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k void passg (size_t ido, size_t ip, + size_t l1, T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa, + const cmplx * POCKETFFT_RESTRICT csarr) const + { + const size_t cdim=ip; + size_t ipph = (ip+1)/2; + size_t idl1 = ido*l1; + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+cdim*c)]; }; + auto CX = [cc, ido, l1](size_t a, size_t b, size_t c) -> T& + { return cc[a+ido*(b+l1*c)]; }; + auto CX2 = [cc, idl1](size_t a, size_t b) -> T& + { return cc[a+idl1*b]; }; + auto CH2 = [ch, idl1](size_t a, size_t b) -> const T& + { return ch[a+idl1*b]; }; + + arr> wal(ip); + wal[0] = cmplx(1., 0.); + for (size_t i=1; i(csarr[i].r,fwd ? -csarr[i].i : csarr[i].i); + + for (size_t k=0; kip) iwal-=ip; + cmplx xwal=wal[iwal]; + iwal+=l; if (iwal>ip) iwal-=ip; + cmplx xwal2=wal[iwal]; + for (size_t ik=0; ikip) iwal-=ip; + cmplx xwal=wal[iwal]; + for (size_t ik=0; ik(x1,wa[idij],CX(i,k,j)); + idij=(jc-1)*(ido-1)+i-1; + special_mul(x2,wa[idij],CX(i,k,jc)); + } + } + } + } + +template void pass_all(T c[], T0 fct) const + { + if (length==1) { c[0]*=fct; return; } + size_t l1=1; + arr ch(length); + T *p1=c, *p2=ch.data(); + + for(size_t k1=0; k1 (ido, l1, p1, p2, fact[k1].tw); + else if(ip==8) + pass8(ido, l1, p1, p2, fact[k1].tw); + else if(ip==2) + pass2(ido, l1, p1, p2, fact[k1].tw); + else if(ip==3) + pass3 (ido, l1, p1, p2, fact[k1].tw); + else if(ip==5) + pass5 (ido, l1, p1, p2, fact[k1].tw); + else if(ip==7) + pass7 (ido, l1, p1, p2, fact[k1].tw); + else if(ip==11) + pass11 (ido, l1, p1, p2, fact[k1].tw); + else + { + passg(ido, ip, l1, p1, p2, fact[k1].tw, fact[k1].tws); + std::swap(p1,p2); + } + std::swap(p1,p2); + l1=l2; + } + if (p1!=c) + { + if (fct!=1.) + for (size_t i=0; i void exec(T c[], T0 fct, bool fwd) const + { fwd ? pass_all(c, fct) : pass_all(c, fct); } + + private: + POCKETFFT_NOINLINE void factorize() + { + size_t len=length; + while ((len&7)==0) + { add_factor(8); len>>=3; } + while ((len&3)==0) + { add_factor(4); len>>=2; } + if ((len&1)==0) + { + len>>=1; + // factor 2 should be at the front of the factor list + add_factor(2); + std::swap(fact[0].fct, fact.back().fct); + } + for (size_t divisor=3; divisor*divisor<=len; divisor+=2) + while ((len%divisor)==0) + { + add_factor(divisor); + len/=divisor; + } + if (len>1) add_factor(len); + } + + size_t twsize() const + { + size_t twsize=0, l1=1; + for (size_t k=0; k11) + twsize+=ip; + l1*=ip; + } + return twsize; + } + + void comp_twiddle() + { + sincos_2pibyn twiddle(length); + size_t l1=1; + size_t memofs=0; + for (size_t k=0; k11) + { + fact[k].tws=mem.data()+memofs; + memofs+=ip; + for (size_t j=0; j class rfftp + { + private: + struct fctdata + { + size_t fct; + T0 *tw, *tws; + }; + + size_t length; + arr mem; + std::vector fact; + + void add_factor(size_t factor) + { fact.push_back({factor, nullptr, nullptr}); } + +/* (a+ib) = conj(c+id) * (e+if) */ +template inline void MULPM + (T1 &a, T1 &b, T2 c, T2 d, T3 e, T3 f) const + { a=c*e+d*f; b=c*f-d*e; } + +template void radf2 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+2*c)]; }; + + for (size_t k=0; k void radf3(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 taur=-0.5, taui=T0(0.8660254037844386467637231707529362L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+3*c)]; }; + + for (size_t k=0; k void radf4(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+4*c)]; }; + + for (size_t k=0; k void radf5(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tr11= T0(0.3090169943749474241022934171828191L), + ti11= T0(0.9510565162951535721164393333793821L), + tr12= T0(-0.8090169943749474241022934171828191L), + ti12= T0(0.5877852522924731291687059546390728L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+5*c)]; }; + + for (size_t k=0; k void radfg(size_t ido, size_t ip, size_t l1, + T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa, const T0 * POCKETFFT_RESTRICT csarr) const + { + const size_t cdim=ip; + size_t ipph=(ip+1)/2; + size_t idl1 = ido*l1; + + auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> T& + { return cc[a+ido*(b+cdim*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return ch[a+ido*(b+l1*c)]; }; + auto C1 = [cc,ido,l1] (size_t a, size_t b, size_t c) -> T& + { return cc[a+ido*(b+l1*c)]; }; + auto C2 = [cc,idl1] (size_t a, size_t b) -> T& + { return cc[a+idl1*b]; }; + auto CH2 = [ch,idl1] (size_t a, size_t b) -> T& + { return ch[a+idl1*b]; }; + + if (ido>1) + { + for (size_t j=1, jc=ip-1; j=ip) iang-=ip; + T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; + iang+=l; if (iang>=ip) iang-=ip; + T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; + iang+=l; if (iang>=ip) iang-=ip; + T0 ar3=csarr[2*iang], ai3=csarr[2*iang+1]; + iang+=l; if (iang>=ip) iang-=ip; + T0 ar4=csarr[2*iang], ai4=csarr[2*iang+1]; + for (size_t ik=0; ik=ip) iang-=ip; + T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; + iang+=l; if (iang>=ip) iang-=ip; + T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; + for (size_t ik=0; ik=ip) iang-=ip; + T0 ar=csarr[2*iang], ai=csarr[2*iang+1]; + for (size_t ik=0; ik void radb2(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+2*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + + for (size_t k=0; k void radb3(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 taur=-0.5, taui=T0(0.8660254037844386467637231707529362L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+3*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + + for (size_t k=0; k void radb4(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+4*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + + for (size_t k=0; k void radb5(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tr11= T0(0.3090169943749474241022934171828191L), + ti11= T0(0.9510565162951535721164393333793821L), + tr12= T0(-0.8090169943749474241022934171828191L), + ti12= T0(0.5877852522924731291687059546390728L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+5*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + + for (size_t k=0; k void radbg(size_t ido, size_t ip, size_t l1, + T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa, const T0 * POCKETFFT_RESTRICT csarr) const + { + const size_t cdim=ip; + size_t ipph=(ip+1)/ 2; + size_t idl1 = ido*l1; + + auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+cdim*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto C1 = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto C2 = [cc,idl1](size_t a, size_t b) -> T& + { return cc[a+idl1*b]; }; + auto CH2 = [ch,idl1](size_t a, size_t b) -> T& + { return ch[a+idl1*b]; }; + + for (size_t k=0; kip) iang-=ip; + T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; + iang+=l; if(iang>ip) iang-=ip; + T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; + iang+=l; if(iang>ip) iang-=ip; + T0 ar3=csarr[2*iang], ai3=csarr[2*iang+1]; + iang+=l; if(iang>ip) iang-=ip; + T0 ar4=csarr[2*iang], ai4=csarr[2*iang+1]; + for (size_t ik=0; ikip) iang-=ip; + T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; + iang+=l; if(iang>ip) iang-=ip; + T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; + for (size_t ik=0; ikip) iang-=ip; + T0 war=csarr[2*iang], wai=csarr[2*iang+1]; + for (size_t ik=0; ik void copy_and_norm(T *c, T *p1, T0 fct) const + { + if (p1!=c) + { + if (fct!=1.) + for (size_t i=0; i void exec(T c[], T0 fct, bool r2hc) const + { + if (length==1) { c[0]*=fct; return; } + size_t nf=fact.size(); + arr ch(length); + T *p1=c, *p2=ch.data(); + + if (r2hc) + for(size_t k1=0, l1=length; k1>=2; } + if ((len%2)==0) + { + len>>=1; + // factor 2 should be at the front of the factor list + add_factor(2); + std::swap(fact[0].fct, fact.back().fct); + } + for (size_t divisor=3; divisor*divisor<=len; divisor+=2) + while ((len%divisor)==0) + { + add_factor(divisor); + len/=divisor; + } + if (len>1) add_factor(len); + } + + size_t twsize() const + { + size_t twsz=0, l1=1; + for (size_t k=0; k5) twsz+=2*ip; + l1*=ip; + } + return twsz; + } + + void comp_twiddle() + { + sincos_2pibyn twid(length); + size_t l1=1; + T0 *ptr=mem.data(); + for (size_t k=0; k5) // special factors required by *g functions + { + fact[k].tws=ptr; ptr+=2*ip; + fact[k].tws[0] = 1.; + fact[k].tws[1] = 0.; + for (size_t i=2, ic=2*ip-2; i<=ic; i+=2, ic-=2) + { + fact[k].tws[i ] = twid[i/2*(length/ip)].r; + fact[k].tws[i+1] = twid[i/2*(length/ip)].i; + fact[k].tws[ic] = twid[i/2*(length/ip)].r; + fact[k].tws[ic+1] = -twid[i/2*(length/ip)].i; + } + } + l1*=ip; + } + } + + public: + POCKETFFT_NOINLINE rfftp(size_t length_) + : length(length_) + { + if (length==0) throw std::runtime_error("zero-length FFT requested"); + if (length==1) return; + factorize(); + mem.resize(twsize()); + comp_twiddle(); + } +}; + +// +// complex Bluestein transforms +// + +template class fftblue + { + private: + size_t n, n2; + cfftp plan; + arr> mem; + cmplx *bk, *bkf; + + template void fft(cmplx c[], T0 fct) const + { + arr> akf(n2); + + /* initialize a_k and FFT it */ + for (size_t m=0; m(c[m],bk[m],akf[m]); + auto zero = akf[0]*T0(0); + for (size_t m=n; m(bkf[0]); + for (size_t m=1; m<(n2+1)/2; ++m) + { + akf[m] = akf[m].template special_mul(bkf[m]); + akf[n2-m] = akf[n2-m].template special_mul(bkf[m]); + } + if ((n2&1)==0) + akf[n2/2] = akf[n2/2].template special_mul(bkf[n2/2]); + + /* inverse FFT */ + plan.exec (akf.data(),1.,false); + + /* multiply by b_k */ + for (size_t m=0; m(bk[m])*fct; + } + + public: + POCKETFFT_NOINLINE fftblue(size_t length) + : n(length), n2(util::good_size_cmplx(n*2-1)), plan(n2), mem(n+n2/2+1), + bk(mem.data()), bkf(mem.data()+n) + { + /* initialize b_k */ + sincos_2pibyn tmp(2*n); + bk[0].Set(1, 0); + + size_t coeff=0; + for (size_t m=1; m=2*n) coeff-=2*n; + bk[m] = tmp[coeff]; + } + + /* initialize the zero-padded, Fourier transformed b_k. Add normalisation. */ + arr> tbkf(n2); + T0 xn2 = T0(1)/T0(n2); + tbkf[0] = bk[0]*xn2; + for (size_t m=1; m void exec(cmplx c[], T0 fct, bool fwd) const + { fwd ? fft(c,fct) : fft(c,fct); } + + template void exec_r(T c[], T0 fct, bool fwd) + { + arr> tmp(n); + if (fwd) + { + auto zero = T0(0)*c[0]; + for (size_t m=0; m(tmp.data(),fct); + c[0] = tmp[0].r; + std::copy_n (&tmp[1].r, n-1, &c[1]); + } + else + { + tmp[0].Set(c[0],c[0]*0); + std::copy_n (c+1, n-1, &tmp[1].r); + if ((n&1)==0) tmp[n/2].i=T0(0)*c[0]; + for (size_t m=1; 2*m(tmp.data(),fct); + for (size_t m=0; m class pocketfft_c + { + private: + std::unique_ptr> packplan; + std::unique_ptr> blueplan; + size_t len; + + public: + POCKETFFT_NOINLINE pocketfft_c(size_t length) + : len(length) + { + if (length==0) throw std::runtime_error("zero-length FFT requested"); + size_t tmp = (length<50) ? 0 : util::largest_prime_factor(length); + if (tmp*tmp <= length) + { + packplan=std::unique_ptr>(new cfftp(length)); + return; + } + double comp1 = util::cost_guess(length); + double comp2 = 2*util::cost_guess(util::good_size_cmplx(2*length-1)); + comp2*=1.5; /* fudge factor that appears to give good overall performance */ + if (comp2>(new fftblue(length)); + else + packplan=std::unique_ptr>(new cfftp(length)); + } + + template POCKETFFT_NOINLINE void exec(cmplx c[], T0 fct, bool fwd) const + { packplan ? packplan->exec(c,fct,fwd) : blueplan->exec(c,fct,fwd); } + + size_t length() const { return len; } + }; + +// +// flexible (FFTPACK/Bluestein) real-valued 1D transform +// + +template class pocketfft_r + { + private: + std::unique_ptr> packplan; + std::unique_ptr> blueplan; + size_t len; + + public: + POCKETFFT_NOINLINE pocketfft_r(size_t length) + : len(length) + { + if (length==0) throw std::runtime_error("zero-length FFT requested"); + size_t tmp = (length<50) ? 0 : util::largest_prime_factor(length); + if (tmp*tmp <= length) + { + packplan=std::unique_ptr>(new rfftp(length)); + return; + } + double comp1 = 0.5*util::cost_guess(length); + double comp2 = 2*util::cost_guess(util::good_size_cmplx(2*length-1)); + comp2*=1.5; /* fudge factor that appears to give good overall performance */ + if (comp2>(new fftblue(length)); + else + packplan=std::unique_ptr>(new rfftp(length)); + } + + template POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool fwd) const + { packplan ? packplan->exec(c,fct,fwd) : blueplan->exec_r(c,fct,fwd); } + + size_t length() const { return len; } + }; + + +// +// sine/cosine transforms +// + +template class T_dct1 + { + private: + pocketfft_r fftplan; + + public: + POCKETFFT_NOINLINE T_dct1(size_t length) + : fftplan(2*(length-1)) {} + + template POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool ortho, + int /*type*/, bool /*cosine*/) const + { + constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); + size_t N=fftplan.length(), n=N/2+1; + if (ortho) + { c[0]*=sqrt2; c[n-1]*=sqrt2; } + arr tmp(N); + tmp[0] = c[0]; + for (size_t i=1; i class T_dst1 + { + private: + pocketfft_r fftplan; + + public: + POCKETFFT_NOINLINE T_dst1(size_t length) + : fftplan(2*(length+1)) {} + + template POCKETFFT_NOINLINE void exec(T c[], T0 fct, + bool /*ortho*/, int /*type*/, bool /*cosine*/) const + { + size_t N=fftplan.length(), n=N/2-1; + arr tmp(N); + tmp[0] = tmp[n+1] = c[0]*0; + for (size_t i=0; i class T_dcst23 + { + private: + pocketfft_r fftplan; + std::vector twiddle; + + public: + POCKETFFT_NOINLINE T_dcst23(size_t length) + : fftplan(length), twiddle(length) + { + sincos_2pibyn tw(4*length); + for (size_t i=0; i POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool ortho, + int type, bool cosine) const + { + constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); + size_t N=length(); + size_t NS2 = (N+1)/2; + if (type==2) + { + if (!cosine) + for (size_t k=1; k class T_dcst4 + { + private: + size_t N; + std::unique_ptr> fft; + std::unique_ptr> rfft; + arr> C2; + + public: + POCKETFFT_NOINLINE T_dcst4(size_t length) + : N(length), + fft((N&1) ? nullptr : new pocketfft_c(N/2)), + rfft((N&1)? new pocketfft_r(N) : nullptr), + C2((N&1) ? 0 : N/2) + { + if ((N&1)==0) + { + sincos_2pibyn tw(16*N); + for (size_t i=0; i POCKETFFT_NOINLINE void exec(T c[], T0 fct, + bool /*ortho*/, int /*type*/, bool cosine) const + { + size_t n2 = N/2; + if (!cosine) + for (size_t k=0, kc=N-1; k y(N); + { + size_t i=0, m=n2; + for (; mexec(y.data(), fct, true); + { + auto SGN = [](size_t i) + { + constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); + return (i&2) ? -sqrt2 : sqrt2; + }; + c[n2] = y[0]*SGN(n2+1); + size_t i=0, i1=1, k=1; + for (; k> y(n2); + for(size_t i=0; iexec(y.data(), fct, true); + for(size_t i=0, ic=n2-1; i std::shared_ptr get_plan(size_t length) + { +#if POCKETFFT_CACHE_SIZE==0 + return std::make_shared(length); +#else + constexpr size_t nmax=POCKETFFT_CACHE_SIZE; + static std::array, nmax> cache; + static std::array last_access{{0}}; + static size_t access_counter = 0; + static std::mutex mut; + + auto find_in_cache = [&]() -> std::shared_ptr + { + for (size_t i=0; ilength()==length)) + { + // no need to update if this is already the most recent entry + if (last_access[i]!=access_counter) + { + last_access[i] = ++access_counter; + // Guard against overflow + if (access_counter == 0) + last_access.fill(0); + } + return cache[i]; + } + + return nullptr; + }; + + { + std::lock_guard lock(mut); + auto p = find_in_cache(); + if (p) return p; + } + auto plan = std::make_shared(length); + { + std::lock_guard lock(mut); + auto p = find_in_cache(); + if (p) return p; + + size_t lru = 0; + for (size_t i=1; i class cndarr: public arr_info + { + protected: + const char *d; + + public: + cndarr(const void *data_, const shape_t &shape_, const stride_t &stride_) + : arr_info(shape_, stride_), + d(reinterpret_cast(data_)) {} + const T &operator[](ptrdiff_t ofs) const + { return *reinterpret_cast(d+ofs); } + }; + +template class ndarr: public cndarr + { + public: + ndarr(void *data_, const shape_t &shape_, const stride_t &stride_) + : cndarr::cndarr(const_cast(data_), shape_, stride_) + {} + T &operator[](ptrdiff_t ofs) + { return *reinterpret_cast(const_cast(cndarr::d+ofs)); } + }; + +template class multi_iter + { + private: + shape_t pos; + const arr_info &iarr, &oarr; + ptrdiff_t p_ii, p_i[N], str_i, p_oi, p_o[N], str_o; + size_t idim, rem; + + void advance_i() + { + for (int i_=int(pos.size())-1; i_>=0; --i_) + { + auto i = size_t(i_); + if (i==idim) continue; + p_ii += iarr.stride(i); + p_oi += oarr.stride(i); + if (++pos[i] < iarr.shape(i)) + return; + pos[i] = 0; + p_ii -= ptrdiff_t(iarr.shape(i))*iarr.stride(i); + p_oi -= ptrdiff_t(oarr.shape(i))*oarr.stride(i); + } + } + + public: + multi_iter(const arr_info &iarr_, const arr_info &oarr_, size_t idim_) + : pos(iarr_.ndim(), 0), iarr(iarr_), oarr(oarr_), p_ii(0), + str_i(iarr.stride(idim_)), p_oi(0), str_o(oarr.stride(idim_)), + idim(idim_), rem(iarr.size()/iarr.shape(idim)) + { + auto nshares = threading::num_threads(); + if (nshares==1) return; + if (nshares==0) throw std::runtime_error("can't run with zero threads"); + auto myshare = threading::thread_id(); + if (myshare>=nshares) throw std::runtime_error("impossible share requested"); + size_t nbase = rem/nshares; + size_t additional = rem%nshares; + size_t lo = myshare*nbase + ((myshare=0; --i_) + { + auto i = size_t(i_); + p += arr.stride(i); + if (++pos[i] < arr.shape(i)) + return; + pos[i] = 0; + p -= ptrdiff_t(arr.shape(i))*arr.stride(i); + } + } + ptrdiff_t ofs() const { return p; } + size_t remaining() const { return rem; } + }; + +class rev_iter + { + private: + shape_t pos; + const arr_info &arr; + std::vector rev_axis; + std::vector rev_jump; + size_t last_axis, last_size; + shape_t shp; + ptrdiff_t p, rp; + size_t rem; + + public: + rev_iter(const arr_info &arr_, const shape_t &axes) + : pos(arr_.ndim(), 0), arr(arr_), rev_axis(arr_.ndim(), 0), + rev_jump(arr_.ndim(), 1), p(0), rp(0) + { + for (auto ax: axes) + rev_axis[ax]=1; + last_axis = axes.back(); + last_size = arr.shape(last_axis)/2 + 1; + shp = arr.shape(); + shp[last_axis] = last_size; + rem=1; + for (auto i: shp) + rem *= i; + } + void advance() + { + --rem; + for (int i_=int(pos.size())-1; i_>=0; --i_) + { + auto i = size_t(i_); + p += arr.stride(i); + if (!rev_axis[i]) + rp += arr.stride(i); + else + { + rp -= arr.stride(i); + if (rev_jump[i]) + { + rp += ptrdiff_t(arr.shape(i))*arr.stride(i); + rev_jump[i] = 0; + } + } + if (++pos[i] < shp[i]) + return; + pos[i] = 0; + p -= ptrdiff_t(shp[i])*arr.stride(i); + if (rev_axis[i]) + { + rp -= ptrdiff_t(arr.shape(i)-shp[i])*arr.stride(i); + rev_jump[i] = 1; + } + else + rp -= ptrdiff_t(shp[i])*arr.stride(i); + } + } + ptrdiff_t ofs() const { return p; } + ptrdiff_t rev_ofs() const { return rp; } + size_t remaining() const { return rem; } + }; + +template struct VTYPE {}; +template using vtype_t = typename VTYPE::type; + +#ifndef POCKETFFT_NO_VECTORS +template<> struct VTYPE + { + using type = float __attribute__ ((vector_size (VLEN::val*sizeof(float)))); + }; +template<> struct VTYPE + { + using type = double __attribute__ ((vector_size (VLEN::val*sizeof(double)))); + }; +template<> struct VTYPE + { + using type = long double __attribute__ ((vector_size (VLEN::val*sizeof(long double)))); + }; +#endif + +template arr alloc_tmp(const shape_t &shape, + size_t axsize, size_t elemsize) + { + auto othersize = util::prod(shape)/axsize; + auto tmpsize = axsize*((othersize>=VLEN::val) ? VLEN::val : 1); + return arr(tmpsize*elemsize); + } +template arr alloc_tmp(const shape_t &shape, + const shape_t &axes, size_t elemsize) + { + size_t fullsize=util::prod(shape); + size_t tmpsize=0; + for (size_t i=0; i=VLEN::val) ? VLEN::val : 1); + if (sz>tmpsize) tmpsize=sz; + } + return arr(tmpsize*elemsize); + } + +template void copy_input(const multi_iter &it, + const cndarr> &src, cmplx> *POCKETFFT_RESTRICT dst) + { + for (size_t i=0; i void copy_input(const multi_iter &it, + const cndarr &src, vtype_t *POCKETFFT_RESTRICT dst) + { + for (size_t i=0; i void copy_input(const multi_iter &it, + const cndarr &src, T *POCKETFFT_RESTRICT dst) + { + if (dst == &src[it.iofs(0)]) return; // in-place + for (size_t i=0; i void copy_output(const multi_iter &it, + const cmplx> *POCKETFFT_RESTRICT src, ndarr> &dst) + { + for (size_t i=0; i void copy_output(const multi_iter &it, + const vtype_t *POCKETFFT_RESTRICT src, ndarr &dst) + { + for (size_t i=0; i void copy_output(const multi_iter &it, + const T *POCKETFFT_RESTRICT src, ndarr &dst) + { + if (src == &dst[it.oofs(0)]) return; // in-place + for (size_t i=0; i struct add_vec { using type = vtype_t; }; +template struct add_vec> + { using type = cmplx>; }; +template using add_vec_t = typename add_vec::type; + +template +POCKETFFT_NOINLINE void general_nd(const cndarr &in, ndarr &out, + const shape_t &axes, T0 fct, size_t nthreads, const Exec & exec, + const bool allow_inplace=true) + { + std::shared_ptr plan; + + for (size_t iax=0; iaxlength())) + plan = get_plan(len); + + threading::thread_map( + util::thread_count(nthreads, in.shape(), axes[iax], VLEN::val), + [&] { + constexpr auto vlen = VLEN::val; + auto storage = alloc_tmp(in.shape(), len, sizeof(T)); + const auto &tin(iax==0? in : out); + multi_iter it(tin, out, axes[iax]); +#ifndef POCKETFFT_NO_VECTORS + if (vlen>1) + while (it.remaining()>=vlen) + { + it.advance(vlen); + auto tdatav = reinterpret_cast *>(storage.data()); + exec(it, tin, out, tdatav, *plan, fct); + } +#endif + while (it.remaining()>0) + { + it.advance(1); + auto buf = allow_inplace && it.stride_out() == sizeof(T) ? + &out[it.oofs(0)] : reinterpret_cast(storage.data()); + exec(it, tin, out, buf, *plan, fct); + } + }); // end of parallel region + fct = T0(1); // factor has been applied, use 1 for remaining axes + } + } + +struct ExecC2C + { + bool forward; + + template void operator () ( + const multi_iter &it, const cndarr> &in, + ndarr> &out, T * buf, const pocketfft_c &plan, T0 fct) const + { + copy_input(it, in, buf); + plan.exec(buf, fct, forward); + copy_output(it, buf, out); + } + }; + +template void copy_hartley(const multi_iter &it, + const vtype_t *POCKETFFT_RESTRICT src, ndarr &dst) + { + for (size_t j=0; j void copy_hartley(const multi_iter &it, + const T *POCKETFFT_RESTRICT src, ndarr &dst) + { + dst[it.oofs(0)] = src[0]; + size_t i=1, i1=1, i2=it.length_out()-1; + for (i=1; i void operator () ( + const multi_iter &it, const cndarr &in, ndarr &out, + T * buf, const pocketfft_r &plan, T0 fct) const + { + copy_input(it, in, buf); + plan.exec(buf, fct, true); + copy_hartley(it, buf, out); + } + }; + +struct ExecDcst + { + bool ortho; + int type; + bool cosine; + + template + void operator () (const multi_iter &it, const cndarr &in, + ndarr &out, T * buf, const Tplan &plan, T0 fct) const + { + copy_input(it, in, buf); + plan.exec(buf, fct, ortho, type, cosine); + copy_output(it, buf, out); + } + }; + +template POCKETFFT_NOINLINE void general_r2c( + const cndarr &in, ndarr> &out, size_t axis, bool forward, T fct, + size_t nthreads) + { + auto plan = get_plan>(in.shape(axis)); + size_t len=in.shape(axis); + threading::thread_map( + util::thread_count(nthreads, in.shape(), axis, VLEN::val), + [&] { + constexpr auto vlen = VLEN::val; + auto storage = alloc_tmp(in.shape(), len, sizeof(T)); + multi_iter it(in, out, axis); +#ifndef POCKETFFT_NO_VECTORS + if (vlen>1) + while (it.remaining()>=vlen) + { + it.advance(vlen); + auto tdatav = reinterpret_cast *>(storage.data()); + copy_input(it, in, tdatav); + plan->exec(tdatav, fct, true); + for (size_t j=0; j0) + { + it.advance(1); + auto tdata = reinterpret_cast(storage.data()); + copy_input(it, in, tdata); + plan->exec(tdata, fct, true); + out[it.oofs(0)].Set(tdata[0]); + size_t i=1, ii=1; + if (forward) + for (; i POCKETFFT_NOINLINE void general_c2r( + const cndarr> &in, ndarr &out, size_t axis, bool forward, T fct, + size_t nthreads) + { + auto plan = get_plan>(out.shape(axis)); + size_t len=out.shape(axis); + threading::thread_map( + util::thread_count(nthreads, in.shape(), axis, VLEN::val), + [&] { + constexpr auto vlen = VLEN::val; + auto storage = alloc_tmp(out.shape(), len, sizeof(T)); + multi_iter it(in, out, axis); +#ifndef POCKETFFT_NO_VECTORS + if (vlen>1) + while (it.remaining()>=vlen) + { + it.advance(vlen); + auto tdatav = reinterpret_cast *>(storage.data()); + for (size_t j=0; jexec(tdatav, fct, false); + copy_output(it, tdatav, out); + } +#endif + while (it.remaining()>0) + { + it.advance(1); + auto tdata = reinterpret_cast(storage.data()); + tdata[0]=in[it.iofs(0)].r; + { + size_t i=1, ii=1; + if (forward) + for (; iexec(tdata, fct, false); + copy_output(it, tdata, out); + } + }); // end of parallel region + } + +struct ExecR2R + { + bool r2h, forward; + + template void operator () ( + const multi_iter &it, const cndarr &in, ndarr &out, T * buf, + const pocketfft_r &plan, T0 fct) const + { + copy_input(it, in, buf); + if ((!r2h) && forward) + for (size_t i=2; i void c2c(const shape_t &shape, const stride_t &stride_in, + const stride_t &stride_out, const shape_t &axes, bool forward, + const std::complex *data_in, std::complex *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr> ain(data_in, shape, stride_in); + ndarr> aout(data_out, shape, stride_out); + general_nd>(ain, aout, axes, fct, nthreads, ExecC2C{forward}); + } + +template void dct(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + int type, const T *data_in, T *data_out, T fct, bool ortho, size_t nthreads=1) + { + if ((type<1) || (type>4)) throw std::invalid_argument("invalid DCT type"); + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr ain(data_in, shape, stride_in); + ndarr aout(data_out, shape, stride_out); + const ExecDcst exec{ortho, type, true}; + if (type==1) + general_nd>(ain, aout, axes, fct, nthreads, exec); + else if (type==4) + general_nd>(ain, aout, axes, fct, nthreads, exec); + else + general_nd>(ain, aout, axes, fct, nthreads, exec); + } + +template void dst(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + int type, const T *data_in, T *data_out, T fct, bool ortho, size_t nthreads=1) + { + if ((type<1) || (type>4)) throw std::invalid_argument("invalid DST type"); + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr ain(data_in, shape, stride_in); + ndarr aout(data_out, shape, stride_out); + const ExecDcst exec{ortho, type, false}; + if (type==1) + general_nd>(ain, aout, axes, fct, nthreads, exec); + else if (type==4) + general_nd>(ain, aout, axes, fct, nthreads, exec); + else + general_nd>(ain, aout, axes, fct, nthreads, exec); + } + +template void r2c(const shape_t &shape_in, + const stride_t &stride_in, const stride_t &stride_out, size_t axis, + bool forward, const T *data_in, std::complex *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape_in)==0) return; + util::sanity_check(shape_in, stride_in, stride_out, false, axis); + cndarr ain(data_in, shape_in, stride_in); + shape_t shape_out(shape_in); + shape_out[axis] = shape_in[axis]/2 + 1; + ndarr> aout(data_out, shape_out, stride_out); + general_r2c(ain, aout, axis, forward, fct, nthreads); + } + +template void r2c(const shape_t &shape_in, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + bool forward, const T *data_in, std::complex *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape_in)==0) return; + util::sanity_check(shape_in, stride_in, stride_out, false, axes); + r2c(shape_in, stride_in, stride_out, axes.back(), forward, data_in, data_out, + fct, nthreads); + if (axes.size()==1) return; + + shape_t shape_out(shape_in); + shape_out[axes.back()] = shape_in[axes.back()]/2 + 1; + auto newaxes = shape_t{axes.begin(), --axes.end()}; + c2c(shape_out, stride_out, stride_out, newaxes, forward, data_out, data_out, + T(1), nthreads); + } + +template void c2r(const shape_t &shape_out, + const stride_t &stride_in, const stride_t &stride_out, size_t axis, + bool forward, const std::complex *data_in, T *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape_out)==0) return; + util::sanity_check(shape_out, stride_in, stride_out, false, axis); + shape_t shape_in(shape_out); + shape_in[axis] = shape_out[axis]/2 + 1; + cndarr> ain(data_in, shape_in, stride_in); + ndarr aout(data_out, shape_out, stride_out); + general_c2r(ain, aout, axis, forward, fct, nthreads); + } + +template void c2r(const shape_t &shape_out, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + bool forward, const std::complex *data_in, T *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape_out)==0) return; + if (axes.size()==1) + return c2r(shape_out, stride_in, stride_out, axes[0], forward, + data_in, data_out, fct, nthreads); + util::sanity_check(shape_out, stride_in, stride_out, false, axes); + auto shape_in = shape_out; + shape_in[axes.back()] = shape_out[axes.back()]/2 + 1; + auto nval = util::prod(shape_in); + stride_t stride_inter(shape_in.size()); + stride_inter.back() = sizeof(cmplx); + for (int i=int(shape_in.size())-2; i>=0; --i) + stride_inter[size_t(i)] = + stride_inter[size_t(i+1)]*ptrdiff_t(shape_in[size_t(i+1)]); + arr> tmp(nval); + auto newaxes = shape_t{axes.begin(), --axes.end()}; + c2c(shape_in, stride_in, stride_inter, newaxes, forward, data_in, tmp.data(), + T(1), nthreads); + c2r(shape_out, stride_inter, stride_out, axes.back(), forward, + tmp.data(), data_out, fct, nthreads); + } + +template void r2r_fftpack(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + bool real2hermitian, bool forward, const T *data_in, T *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr ain(data_in, shape, stride_in); + ndarr aout(data_out, shape, stride_out); + general_nd>(ain, aout, axes, fct, nthreads, + ExecR2R{real2hermitian, forward}); + } + +template void r2r_separable_hartley(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + const T *data_in, T *data_out, T fct, size_t nthreads=1) + { + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr ain(data_in, shape, stride_in); + ndarr aout(data_out, shape, stride_out); + general_nd>(ain, aout, axes, fct, nthreads, ExecHartley{}, + false); + } + +template void r2r_genuine_hartley(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + const T *data_in, T *data_out, T fct, size_t nthreads=1) + { + if (util::prod(shape)==0) return; + if (axes.size()==1) + return r2r_separable_hartley(shape, stride_in, stride_out, axes, data_in, + data_out, fct, nthreads); + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + shape_t tshp(shape); + tshp[axes.back()] = tshp[axes.back()]/2+1; + arr> tdata(util::prod(tshp)); + stride_t tstride(shape.size()); + tstride.back()=sizeof(std::complex); + for (size_t i=tstride.size()-1; i>0; --i) + tstride[i-1]=tstride[i]*ptrdiff_t(tshp[i]); + r2c(shape, stride_in, tstride, axes, true, data_in, tdata.data(), fct, nthreads); + cndarr> atmp(tdata.data(), tshp, tstride); + ndarr aout(data_out, shape, stride_out); + simple_iter iin(atmp); + rev_iter iout(aout, axes); + while(iin.remaining()>0) + { + auto v = atmp[iin.ofs()]; + aout[iout.ofs()] = v.r+v.i; + aout[iout.rev_ofs()] = v.r-v.i; + iin.advance(); iout.advance(); + } + } + +} // namespace detail + +using detail::FORWARD; +using detail::BACKWARD; +using detail::shape_t; +using detail::stride_t; +using detail::c2c; +using detail::c2r; +using detail::r2c; +using detail::r2r_fftpack; +using detail::r2r_separable_hartley; +using detail::r2r_genuine_hartley; +using detail::dct; +using detail::dst; + +} // namespace pocketfft + +#undef POCKETFFT_NOINLINE +#undef POCKETFFT_RESTRICT + +#endif // POCKETFFT_HDRONLY_H diff --git a/indexer/fft3d.cc b/indexer/fft3d.cc new file mode 100644 index 0000000..6c3e9a5 --- /dev/null +++ b/indexer/fft3d.cc @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using Eigen::Matrix3d; +using Eigen::Vector3d; +using Eigen::Vector3i; + +#define _USE_MATH_DEFINES +#include + +using namespace pocketfft; + +//std::tuple>, std::vector> +void map_centroids_to_reciprocal_space_grid_cpp( + std::vector const& reciprocal_space_vectors, + std::vector> &data_in, + std::vector &selection, + double d_min, + double b_iso = 0) { + const int n_points = 256; + const double rlgrid = 2 / (d_min * n_points); + const double one_over_rlgrid = 1 / rlgrid; + const int half_n_points = n_points / 2; + //std::vector selection(reciprocal_space_vectors.size(), true); + + //std::vector> data_in(256 * 256 * 256); + for (int i = 0; i < reciprocal_space_vectors.size(); i++) { + const Vector3d v = reciprocal_space_vectors[i]; + const double v_length = v.norm(); + const double d_spacing = 1 / v_length; + if (d_spacing < d_min) { + selection[i] = false; + continue; + } + Vector3i coord; + for (int j = 0; j < 3; j++) { + coord[j] = ((int)round(v[j] * one_over_rlgrid)) + half_n_points; + } + if ((coord.maxCoeff() >= n_points) || coord.minCoeff() < 0) { + selection[i] = false; + continue; + } + double T; + if (b_iso != 0) { + T = std::exp(-b_iso * v_length * v_length / 4.0); + } else { + T = 1; + } + size_t index = coord[2] + (256 * coord[1]) + (256 * 256 * coord[0]); + data_in[index] = {T, 0.0}; + } + //return std::make_tuple(data_in, selection); +} + +std::tuple, std::vector> fft3d( + std::vector const& reciprocal_space_vectors, + double d_min, + double b_iso = 0) { + auto start = std::chrono::system_clock::now(); + + std::vector> complex_data_in(256 * 256 * 256); + std::vector> data_out(256 * 256 * 256); + std::vector real_out(256 * 256 * 256); + std::vector used_in_indexing(reciprocal_space_vectors.size(), true); + auto t1 = std::chrono::system_clock::now(); + + ///std::vector> complex_data_in; + ///std::vector used_in_indexing; + ///std::tie(complex_data_in, used_in_indexing) = + map_centroids_to_reciprocal_space_grid_cpp(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso); + auto t2 = std::chrono::system_clock::now(); + + shape_t shape_in{256, 256, 256}; + stride_t stride_in{sizeof(std::complex), + sizeof(std::complex) * 256, + sizeof(std::complex) * 256 + * 256}; // must have the size of each element. Must have + // size() equal to shape_in.size() + stride_t stride_out{sizeof(std::complex), + sizeof(std::complex) * 256, + sizeof(std::complex) * 256 + * 256}; // must have the size of each element. Must + // have size() equal to shape_in.size() + shape_t axes{0, 1, 2}; // 0 to shape.size()-1 inclusive + bool forward{FORWARD}; + + double fct{1.0f}; + size_t nthreads = 20; // use all threads available - is this working? + + c2c(shape_in, + stride_in, + stride_out, + axes, + forward, + complex_data_in.data(), + data_out.data(), + fct, + nthreads); + auto t3 = std::chrono::system_clock::now(); + + for (int i = 0; i < real_out.size(); ++i) { + real_out[i] = std::pow(data_out[i].real(), 2); + } + auto t4 = std::chrono::system_clock::now(); + + std::chrono::duration elapsed_seconds = t4 - start; + std::chrono::duration elapsed_map = t2 - t1; + std::chrono::duration elapsed_make_arrays = t1 - start; + std::chrono::duration elapsed_c2c = t3 - t2; + std::chrono::duration elapsed_square = t4 - t3; + std::cout << "Total time for fft3d: " << elapsed_seconds.count() << "s" << std::endl; + + std::cout << "elapsed time for making data arrays: " << elapsed_make_arrays.count() << "s" << std::endl; + std::cout << "elapsed time for map_to_recip: " << elapsed_map.count() << "s" << std::endl; + std::cout << "elapsed time for c2c: " << elapsed_c2c.count() << "s" << std::endl; + std::cout << "elapsed time for squaring: " << elapsed_square.count() << "s" << std::endl; + + return std::make_tuple(real_out, used_in_indexing); +} \ No newline at end of file diff --git a/indexer/indexer.cc b/indexer/indexer.cc index f190da7..5910b4c 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -9,6 +9,7 @@ #include #include #include "xyz_to_rlp.cc" +#include "fft3d.cc" using Eigen::Vector3d; using Eigen::Matrix3d; @@ -46,10 +47,11 @@ int main(int argc, char **argv) { "--wavelength\n"); std::exit(1); } - float wavelength = wavelength_opt.value(); - printf("INDEXER: Got wavelength from file: %f Å\n", wavelength); + float wavelength_f = wavelength_opt.value(); + printf("INDEXER: Got wavelength from file: %f Å\n", wavelength_f); //FIXME don't assume s0 vector get from file - std::array s0 {0.0, 0.0, -1.0/wavelength}; + double wavelength = 0.976254; + Vector3d s0 {0.0, 0.0, -1.0/wavelength}; // now get detector properties // need fast, slow, norm and origin, plus pixel size @@ -63,7 +65,8 @@ int main(int argc, char **argv) { std::array normal {0.0, 0.0, 1.0}; // fast_axis cross slow_axis //std::array origin {-75.61, 79.95, -150.0}; // FIXME - Vector3d origin {-75.61, 79.95, -150.0}; // FIXME + //Vector3d origin {-75.61, 79.95, -150.0}; // FIXME + Vector3d origin {-153.61, 162.446, -200.453}; Matrix3d d_matrix{{fast_axis[0], slow_axis[0], normal[0]+origin[0], fast_axis[1], slow_axis[1], normal[1]+origin[1], @@ -74,19 +77,28 @@ int main(int argc, char **argv) { double osc_width = 0.1; // finally gonio properties e.g. - Matrix3d fixed_rotation{{0.965028,0.0598562,-0.255222},{-0.128604,-0.74028,-0.659883},{-0.228434,0.669628,-0.706694}}; + Matrix3d fixed_rotation{{1,0,0},{0,1,0},{0,0,1}};//{{0.965028,0.0598562,-0.255222},{-0.128604,-0.74028,-0.659883},{-0.228434,0.669628,-0.706694}}; Vector3d rotation_axis {1.0,0.0,0.0}; Matrix3d setting_rotation {{1,0,0},{0,1,0},{0,0,1}}; // get processed reflection data from spotfinding - std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24/h5_file/strong.refl"; + std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl"; std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; std::vector data = read_xyzobs_data(filename, array_name); + std::vector rlp = xyz_to_rlp( data, fixed_rotation, d_matrix, wavelength, pixel_size_x,image_range_start, osc_start, osc_width, rotation_axis, setting_rotation); + std::cout << data[0] << std::endl; std::cout << data[data.size()-1] << std::endl; std::cout << rlp[0][0] << std::endl; std::cout << rlp[rlp.size()-1][0] << std::endl; + std::cout << "Number of reflections: " << rlp.size() << std::endl; + + + std::vector real_fft; + std::vector used_in_indexing; + std::tie(real_fft, used_in_indexing) = fft3d(rlp, 1.8); + std::cout << real_fft[0] << " " << used_in_indexing[0] << std::endl; } \ No newline at end of file diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index 25020ae..93c79f1 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -61,7 +61,7 @@ class SimpleGonio { }; std::vector xyz_to_rlp( - std::vector xyzobs_px, + const std::vector &xyzobs_px, Matrix3d sample_rotation, Matrix3d detector_d_matrix, double wavelength, From 29f3355cb8df9c5466c6788f19dc28189ffae8f0 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:34:13 +0000 Subject: [PATCH 04/72] Tidying, comment out parser for now --- indexer/fft3d.cc | 14 ++++---------- indexer/indexer.cc | 21 +++++++++++++-------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/indexer/fft3d.cc b/indexer/fft3d.cc index 6c3e9a5..e8fd5a6 100644 --- a/indexer/fft3d.cc +++ b/indexer/fft3d.cc @@ -16,7 +16,6 @@ using Eigen::Vector3i; using namespace pocketfft; -//std::tuple>, std::vector> void map_centroids_to_reciprocal_space_grid_cpp( std::vector const& reciprocal_space_vectors, std::vector> &data_in, @@ -27,9 +26,7 @@ void map_centroids_to_reciprocal_space_grid_cpp( const double rlgrid = 2 / (d_min * n_points); const double one_over_rlgrid = 1 / rlgrid; const int half_n_points = n_points / 2; - //std::vector selection(reciprocal_space_vectors.size(), true); - //std::vector> data_in(256 * 256 * 256); for (int i = 0; i < reciprocal_space_vectors.size(); i++) { const Vector3d v = reciprocal_space_vectors[i]; const double v_length = v.norm(); @@ -55,24 +52,21 @@ void map_centroids_to_reciprocal_space_grid_cpp( size_t index = coord[2] + (256 * coord[1]) + (256 * 256 * coord[0]); data_in[index] = {T, 0.0}; } - //return std::make_tuple(data_in, selection); } -std::tuple, std::vector> fft3d( +std::vector fft3d( std::vector const& reciprocal_space_vectors, + std::vector &real_out, double d_min, double b_iso = 0) { auto start = std::chrono::system_clock::now(); std::vector> complex_data_in(256 * 256 * 256); std::vector> data_out(256 * 256 * 256); - std::vector real_out(256 * 256 * 256); + std::vector used_in_indexing(reciprocal_space_vectors.size(), true); auto t1 = std::chrono::system_clock::now(); - ///std::vector> complex_data_in; - ///std::vector used_in_indexing; - ///std::tie(complex_data_in, used_in_indexing) = map_centroids_to_reciprocal_space_grid_cpp(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso); auto t2 = std::chrono::system_clock::now(); @@ -121,5 +115,5 @@ std::tuple, std::vector> fft3d( std::cout << "elapsed time for c2c: " << elapsed_c2c.count() << "s" << std::endl; std::cout << "elapsed time for squaring: " << elapsed_square.count() << "s" << std::endl; - return std::make_tuple(real_out, used_in_indexing); + return used_in_indexing; } \ No newline at end of file diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 5910b4c..05f323c 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -10,21 +10,24 @@ #include #include "xyz_to_rlp.cc" #include "fft3d.cc" +#include using Eigen::Vector3d; using Eigen::Matrix3d; int main(int argc, char **argv) { + + auto t1 = std::chrono::system_clock::now(); auto parser = CUDAArgumentParser(); - parser.add_h5read_arguments(); //will use h5 file to get metadata + /*parser.add_h5read_arguments(); //will use h5 file to get metadata parser.add_argument("--images") .help("Maximum number of images to process") .metavar("NUM") - .scan<'u', uint32_t>(); + .scan<'u', uint32_t>();*/ auto args = parser.parse_args(argc, argv); - std::unique_ptr reader_ptr; + /*std::unique_ptr reader_ptr; // Wait for read-readiness @@ -48,7 +51,7 @@ int main(int argc, char **argv) { std::exit(1); } float wavelength_f = wavelength_opt.value(); - printf("INDEXER: Got wavelength from file: %f Å\n", wavelength_f); + printf("INDEXER: Got wavelength from file: %f Å\n", wavelength_f);*/ //FIXME don't assume s0 vector get from file double wavelength = 0.976254; Vector3d s0 {0.0, 0.0, -1.0/wavelength}; @@ -96,9 +99,11 @@ int main(int argc, char **argv) { std::cout << rlp[rlp.size()-1][0] << std::endl; std::cout << "Number of reflections: " << rlp.size() << std::endl; - - std::vector real_fft; - std::vector used_in_indexing; - std::tie(real_fft, used_in_indexing) = fft3d(rlp, 1.8); + std::vector real_fft(256*256*256); + std::vector used_in_indexing = fft3d(rlp, real_fft, 1.8); + auto t2 = std::chrono::system_clock::now(); std::cout << real_fft[0] << " " << used_in_indexing[0] << std::endl; + std::chrono::duration elapsed_time = t2 - t1; + std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; + } \ No newline at end of file From 6da51289bf63b62fb25d12d9d768c57758f2f596 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:27:27 +0000 Subject: [PATCH 05/72] Add last bit of indexing part 1 - getting candidate vecs --- indexer/fft3d.cc | 5 + indexer/flood_fill.cc | 195 +++++++++++++++++++++++++++++++++++++ indexer/indexer.cc | 75 +++++++++----- indexer/simple_models.cc | 103 ++++++++++++++++++++ indexer/sites_to_vecs.cc | 204 +++++++++++++++++++++++++++++++++++++++ indexer/xyz_to_rlp.cc | 86 +++-------------- 6 files changed, 570 insertions(+), 98 deletions(-) create mode 100644 indexer/flood_fill.cc create mode 100644 indexer/simple_models.cc create mode 100644 indexer/sites_to_vecs.cc diff --git a/indexer/fft3d.cc b/indexer/fft3d.cc index e8fd5a6..9ea54ca 100644 --- a/indexer/fft3d.cc +++ b/indexer/fft3d.cc @@ -26,6 +26,7 @@ void map_centroids_to_reciprocal_space_grid_cpp( const double rlgrid = 2 / (d_min * n_points); const double one_over_rlgrid = 1 / rlgrid; const int half_n_points = n_points / 2; + int count = 0; for (int i = 0; i < reciprocal_space_vectors.size(); i++) { const Vector3d v = reciprocal_space_vectors[i]; @@ -50,8 +51,12 @@ void map_centroids_to_reciprocal_space_grid_cpp( T = 1; } size_t index = coord[2] + (256 * coord[1]) + (256 * 256 * coord[0]); + if (!data_in[index].real()){ + count++; + } data_in[index] = {T, 0.0}; } + std::cout << "Number of centroids used: " << count << std::endl; } std::vector fft3d( diff --git a/indexer/flood_fill.cc b/indexer/flood_fill.cc new file mode 100644 index 0000000..7043a2e --- /dev/null +++ b/indexer/flood_fill.cc @@ -0,0 +1,195 @@ +#include +#include +#include +#include +#include +#include +#include +#define _USE_MATH_DEFINES +#include + +using Eigen::Vector3d; +using Eigen::Vector3i; + +// Define a modulo function that returns python style modulo for negative numbers. +int modulo(int i, int n) { + return (i % n + n) % n; +} + +std::tuple,std::vector> +flood_fill(std::vector const& grid, + double rmsd_cutoff = 15.0, + int n_points = 256) { + auto start = std::chrono::system_clock::now(); + // First calc rmsd and use this to create a binary grid + double sumg = 0.0; + for (int i = 0; i < grid.size(); ++i) { + sumg += grid[i]; + } + double meang = sumg / grid.size(); + double sum_delta_sq = 0.0; + for (int i = 0; i < grid.size(); ++i) { + sum_delta_sq += std::pow(grid[i] - meang, 2); + } + double rmsd = std::pow(sum_delta_sq / grid.size(), 0.5); + std::vector grid_binary(n_points * n_points * n_points, 0); + double cutoff = rmsd_cutoff * rmsd; + int count = 0; + for (int i = 0; i < grid.size(); i++) { + if (grid[i] >= cutoff) { + grid_binary[i] = 1; + count++; + } + } + std::cout << "Nonzero grid binary: " << count << std::endl; + auto t2 = std::chrono::system_clock::now(); + std::chrono::duration elapsed_time = t2 - start; + std::cout << "Time for first part of flood fill: " << elapsed_time.count() << "s" << std::endl; + + // Now do flood fill. Wrap around the edge in all three dimensions. + int n_voids = 0; + std::stack stack; + std::vector> accumulators; + int target = 1; + int replacement = 2; + std::vector grid_points_per_void; + int accumulator_index = 0; + int total = n_points * n_points * n_points; + int n_sq = n_points * n_points; + int n_sq_minus_n = n_points * (n_points - 1); + int nn_sq_minus_n = n_points * n_points * (n_points - 1); + + for (int i = 0; i < grid_binary.size(); i++) { + if (grid_binary[i] == target) { + // Convert the array index into xyz coordinates. + // Store xyz coordinates on the stack, but index the array with 1D index. + int x = i % n_points; + int y = (i % n_sq) / n_points; + int z = i / n_sq; + Vector3i xyz = {x, y, z}; + stack.push(xyz); + grid_binary[i] = replacement; + std::vector this_accumulator; + accumulators.push_back(this_accumulator); + n_voids++; + grid_points_per_void.push_back(0); + + while (!stack.empty()) { + Vector3i this_xyz = stack.top(); + stack.pop(); + accumulators[accumulator_index].push_back(this_xyz); + grid_points_per_void[accumulator_index]++; + + int x_plus = this_xyz[0] + 1; + int modx = modulo(this_xyz[0], n_points); + int mody = modulo(this_xyz[1], n_points) * n_points; + int modz = modulo(this_xyz[2], n_points) * n_sq; + + // For x,y,z, check locations +-1 on the grid and add to stack if match. + + int array_index = modulo(x_plus, n_points) + mody + modz; + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {x_plus, this_xyz[1], this_xyz[2]}; + stack.push(new_xyz); + } + int x_minus = this_xyz[0] - 1; + array_index = modulo(x_minus, n_points) + mody + modz; + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {x_minus, this_xyz[1], this_xyz[2]}; + stack.push(new_xyz); + } + + int y_plus = this_xyz[1] + 1; + array_index = modx + (modulo(y_plus, n_points) * n_points) + modz; + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {this_xyz[0], y_plus, this_xyz[2]}; + stack.push(new_xyz); + } + int y_minus = this_xyz[1] - 1; + array_index = modx + (modulo(y_minus, n_points) * n_points) + modz; + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {this_xyz[0], y_minus, this_xyz[2]}; + stack.push(new_xyz); + } + + int z_plus = this_xyz[2] + 1; + array_index = modx + mody + (modulo(z_plus, n_points) * n_sq); + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {this_xyz[0], this_xyz[1], z_plus}; + stack.push(new_xyz); + } + int z_minus = this_xyz[2] - 1; + array_index = modx + mody + (modulo(z_minus, n_points) * n_sq); + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {this_xyz[0], this_xyz[1], z_minus}; + stack.push(new_xyz); + } + } + replacement++; + accumulator_index++; + } + } + auto t3 = std::chrono::system_clock::now(); + std::chrono::duration elapsed_time2 = t3 - t2; + std::cout << "Time for second part of flood fill: " << elapsed_time2.count() << "s" << std::endl; + + // Now calculate the unweighted centres of mass of each group. + std::vector centres_of_mass_frac(n_voids); + for (int i = 0; i < accumulators.size(); i++) { + std::vector values = accumulators[i]; + int n = values.size(); + int divisor = n * n_points; + double x = 0.0; + double y = 0.0; + double z = 0.0; + for (int j = 0; j < n; j++) { + x += values[j][0]; + y += values[j][1]; + z += values[j][2]; + } + x /= divisor; + y /= divisor; + z /= divisor; + centres_of_mass_frac[i] = {z, y, x}; + } + return std::make_tuple(grid_points_per_void, centres_of_mass_frac); +} + +std::tuple, std::vector> flood_fill_filter( + std::vector grid_points_per_void, + std::vector centres_of_mass_frac, + double peak_volume_cutoff = 0.15){ + // now filter out based on iqr range and peak_volume_cutoff + std::vector grid_points_per_void_unsorted(grid_points_per_void); + std::sort(grid_points_per_void.begin(), grid_points_per_void.end()); + int Q3_index = grid_points_per_void.size() * 3 / 4; + int Q1_index = grid_points_per_void.size() / 4; + int iqr = grid_points_per_void[Q3_index] - grid_points_per_void[Q1_index]; + int iqr_multiplier = 5; + int cut = (iqr * iqr_multiplier) + grid_points_per_void[Q3_index]; + /*for (int i = grid_points_per_void.size() - 1; i >= 0; i--) { + if (grid_points_per_void_unsorted[i] > cut) { + grid_points_per_void_unsorted.erase(grid_points_per_void_unsorted.begin() + i); + centres_of_mass_frac.erase(centres_of_mass_frac.begin() + i); + } + }*/ + while (grid_points_per_void[grid_points_per_void.size() - 1] > cut) { + grid_points_per_void.pop_back(); + } + int max_val = grid_points_per_void[grid_points_per_void.size() - 1]; + + int peak_cutoff = (int)(peak_volume_cutoff * max_val); + for (int i = grid_points_per_void_unsorted.size() - 1; i >= 0; i--) { + if (grid_points_per_void_unsorted[i] <= peak_cutoff) { + grid_points_per_void_unsorted.erase(grid_points_per_void_unsorted.begin() + i); + centres_of_mass_frac.erase(centres_of_mass_frac.begin() + i); + } + } + return std::make_tuple(grid_points_per_void_unsorted, centres_of_mass_frac); +} \ No newline at end of file diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 05f323c..6446445 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -9,8 +9,11 @@ #include #include #include "xyz_to_rlp.cc" +#include "flood_fill.cc" +#include "sites_to_vecs.cc" #include "fft3d.cc" #include +#include "simple_models.cc" using Eigen::Vector3d; using Eigen::Matrix3d; @@ -52,57 +55,83 @@ int main(int argc, char **argv) { } float wavelength_f = wavelength_opt.value(); printf("INDEXER: Got wavelength from file: %f Å\n", wavelength_f);*/ + + + //TODO + // Get metadata from file + // implement px_to_mm parallax corrections + // implement max cell/d_min estimation. + //FIXME don't assume s0 vector get from file - double wavelength = 0.976254; - Vector3d s0 {0.0, 0.0, -1.0/wavelength}; + double wavelength = 0.9762535307519975; + //Vector3d s0 {0.0, 0.0, -1.0/wavelength}; // now get detector properties // need fast, slow, norm and origin, plus pixel size //std::optional> pixel_size = reader.get_pixel_size(); - float pixel_size_x = 0.075; - //FIXME remove assumption of pixel sizes being same in analysis code. - std::array fast_axis {1.0, 0.0, 0.0}; //FIXME get through reader - std::array slow_axis {0.0, -1.0, 0.0}; //FIXME get through reader + + std::array fast_axis {1.0, 0.0, 0.0}; //FIXME get through reader + std::array slow_axis {0.0, -1.0, 0.0}; //FIXME get through reader // ^ change basis from nexus to iucr/imgcif convention (invert x and z) - std::array normal {0.0, 0.0, 1.0}; // fast_axis cross slow_axis + std::array normal {0.0, 0.0, 0.0}; // fast_axis cross slow_axis //std::array origin {-75.61, 79.95, -150.0}; // FIXME //Vector3d origin {-75.61, 79.95, -150.0}; // FIXME - Vector3d origin {-153.61, 162.446, -200.453}; + Vector3d origin {-153.60993960268158, 162.44624026693077, -200.45297988785603}; + + Matrix3d d_matrix{{fast_axis[0], slow_axis[0], origin[0]},{ + fast_axis[1], slow_axis[1], origin[1]}, + {fast_axis[2], slow_axis[2], origin[2]}}; + //FIXME remove assumption of pixel sizes being same in analysis code. + double pixel_size_x = 0.075; + double mu = 3.9220780876; + double t0 = 0.45; - Matrix3d d_matrix{{fast_axis[0], slow_axis[0], normal[0]+origin[0], - fast_axis[1], slow_axis[1], normal[1]+origin[1], - fast_axis[2], slow_axis[2], normal[2]+origin[2]}}; // now get scan properties e.g. - int image_range_start = 0; + int image_range_start = 1; double osc_start = 0.0; - double osc_width = 0.1; + double osc_width = 0.10002778549596769; // finally gonio properties e.g. Matrix3d fixed_rotation{{1,0,0},{0,1,0},{0,0,1}};//{{0.965028,0.0598562,-0.255222},{-0.128604,-0.74028,-0.659883},{-0.228434,0.669628,-0.706694}}; Vector3d rotation_axis {1.0,0.0,0.0}; Matrix3d setting_rotation {{1,0,0},{0,1,0},{0,0,1}}; + // Make the dxtbx-like models + SimpleDetector detector(d_matrix, pixel_size_x, mu, t0, true); + SimpleScan scan(image_range_start, osc_start, osc_width); + SimpleGonio gonio(fixed_rotation, rotation_axis, setting_rotation); + SimpleBeam beam(wavelength); + // get processed reflection data from spotfinding std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl"; std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; std::vector data = read_xyzobs_data(filename, array_name); - std::vector rlp = xyz_to_rlp( - data, fixed_rotation, d_matrix, wavelength, pixel_size_x,image_range_start, - osc_start, osc_width, rotation_axis, setting_rotation); + std::vector rlp = xyz_to_rlp(data, detector, beam, scan, gonio); - std::cout << data[0] << std::endl; - std::cout << data[data.size()-1] << std::endl; - std::cout << rlp[0][0] << std::endl; - std::cout << rlp[rlp.size()-1][0] << std::endl; std::cout << "Number of reflections: " << rlp.size() << std::endl; + + double d_min = 1.31; + double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); + double max_cell = 33.8; + std::cout << "Setting b_iso =" << b_iso << std::endl; + + std::vector real_fft(256*256*256, 0.0); + std::vector used_in_indexing = fft3d(rlp, real_fft, d_min, b_iso); + + std::cout << real_fft[0] << " " << used_in_indexing[0] << std::endl; + + std::vector grid_points_per_void; + std::vector centres_of_mass_frac; + std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft); + std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); + + std::vector candidate_vecs = + sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); - std::vector real_fft(256*256*256); - std::vector used_in_indexing = fft3d(rlp, real_fft, 1.8); auto t2 = std::chrono::system_clock::now(); - std::cout << real_fft[0] << " " << used_in_indexing[0] << std::endl; std::chrono::duration elapsed_time = t2 - t1; std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; diff --git a/indexer/simple_models.cc b/indexer/simple_models.cc new file mode 100644 index 0000000..5658eea --- /dev/null +++ b/indexer/simple_models.cc @@ -0,0 +1,103 @@ +#ifndef INDEXER_SIMPLE_MODELS +#define INDEXER_SIMPLE_MODELS +#include +#include + +using Eigen::Matrix3d; +using Eigen::Vector3d; + +class SimpleBeam { +public: + double wavelength; + Vector3d s0; + + SimpleBeam(double wavelength) { + this->wavelength = wavelength; + s0 = {0.0, 0.0, -1.0 / wavelength}; + } +}; + +double attenuation_length(double mu, double t0, + Vector3d s1, + Vector3d fast, + Vector3d slow, + Vector3d origin) { + Vector3d normal = fast.cross(slow); + double distance = origin.dot(normal); + if (distance < 0) { + normal = -normal; + } + double cos_t = s1.dot(normal); + //DXTBX_ASSERT(mu > 0 && cos_t > 0); + return (1.0 / mu) - (t0 / cos_t + 1.0 / mu) * exp(-mu * t0 / cos_t); +} + +class SimpleDetector { +public: + Matrix3d d_matrix; + double pixel_size; // in mm + double mu; + double t0; + bool parallax_correction; + std::array px_to_mm(double x, double y) const; + + SimpleDetector(Matrix3d d_matrix, double pixel_size, double mu=0.0, double t0=0.0, bool parallax_correction=false) { + this->d_matrix = d_matrix; + this->pixel_size = pixel_size; + this->mu = mu; + this->t0 = t0; + this->parallax_correction = parallax_correction; + } +}; + +std::array SimpleDetector::px_to_mm(double x, double y) const{ + double x1 = x*pixel_size; + double x2 = y*pixel_size; + if (!parallax_correction){ + return std::array{x1,x2}; + } + Vector3d fast = d_matrix.col(0); + Vector3d slow = d_matrix.col(1); + Vector3d origin = d_matrix.col(2); + Vector3d s1 = origin + x1*fast + x2*slow; + s1.normalize(); + double o = attenuation_length(mu, t0, s1, fast, slow, origin); + double c1 = x1 - (s1.dot(fast))*o; + double c2 = x2 - (s1.dot(slow))*o; + return std::array{c1,c2}; +} + +class SimpleScan { +public: + int image_range_start; + double osc_start; + double osc_width; + + SimpleScan(int image_range_start, double osc_start, double osc_width) { + this->image_range_start = image_range_start; + this->osc_start = osc_start; + this->osc_width = osc_width; + } +}; + +class SimpleGonio { +public: + Matrix3d sample_rotation; + Vector3d rotation_axis; + Matrix3d setting_rotation; + Matrix3d sample_rotation_inverse; + Matrix3d setting_rotation_inverse; + + SimpleGonio(Matrix3d sample_rotation, + Vector3d rotation_axis, + Matrix3d setting_rotation) { + this->sample_rotation = sample_rotation; + rotation_axis.normalize(); + this->rotation_axis = rotation_axis; + this->setting_rotation = setting_rotation; + sample_rotation_inverse = this->sample_rotation.inverse(); + setting_rotation_inverse = this->setting_rotation.inverse(); + } +}; + +#endif // INDEXER_SIMPLE_MODELS \ No newline at end of file diff --git a/indexer/sites_to_vecs.cc b/indexer/sites_to_vecs.cc new file mode 100644 index 0000000..ebdbfa3 --- /dev/null +++ b/indexer/sites_to_vecs.cc @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include + +using Eigen::Vector3d; + + +#define _USE_MATH_DEFINES +#include + +class VectorGroup { +public: + void add(Vector3d vec, int weight) { + vectors.push_back(vec); + weights.push_back(weight); + } + Vector3d mean() { + int n = vectors.size(); + double sum_x = 0.0; + double sum_y = 0.0; + double sum_z = 0.0; + for (const Vector3d& i : vectors) { + sum_x += i[0]; + sum_y += i[1]; + sum_z += i[2]; + } + Vector3d m = {sum_x / n, sum_y / n, sum_z / n}; + return m; + } + std::vector vectors{}; + std::vector weights{}; +}; + +struct SiteData { + Vector3d site; + double length; + int volume; +}; +bool compare_site_data(const SiteData& a, const SiteData& b) { + return a.length < b.length; +} +bool compare_site_data_volume(const SiteData& a, const SiteData& b) { + return a.volume > b.volume; +} + +double vector_length(Vector3d v) { + return std::pow(std::pow(v[0], 2) + std::pow(v[1], 2) + std::pow(v[2], 2), 0.5); +} + +double angle_between_vectors_degrees(Vector3d v1, Vector3d v2) { + double l1 = vector_length(v1); + double l2 = vector_length(v2); + double dot = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; + double normdot = dot / (l1 * l2); + if (std::abs(normdot - 1.0) < 1E-6) { + return 0.0; + } + if (std::abs(normdot + 1.0) < 1E-6) { + return 180.0; + } + double angle = std::acos(normdot) * 180.0 / M_PI; + return angle; +} + +bool is_approximate_integer_multiple(Vector3d v1, + Vector3d v2, + double relative_length_tolerance = 0.2, + double angular_tolerance = 5.0) { + double angle = angle_between_vectors_degrees(v1, v2); + if ((angle < angular_tolerance) || (std::abs(180 - angle) < angular_tolerance)) { + double l1 = vector_length(v1); + double l2 = vector_length(v2); + if (l1 > l2) { + std::swap(l1, l2); + } + double n = l2 / l1; + if (std::abs(std::round(n) - n) < relative_length_tolerance) { + return true; + } + } + return false; +} + +std::vector sites_to_vecs( + std::vector centres_of_mass_frac, + std::vector grid_points_per_void, + double d_min, + double min_cell = 3.0, + double max_cell = 92.3) { + auto start = std::chrono::system_clock::now(); + + int n_points = 256; + double fft_cell_length = n_points * d_min / 2.0; + // sites_mod_short and convert to cartesian + for (int i = 0; i < centres_of_mass_frac.size(); i++) { + for (size_t j = 0; j < 3; j++) { + if (centres_of_mass_frac[i][j] > 0.5) { + centres_of_mass_frac[i][j]--; + } + centres_of_mass_frac[i][j] *= fft_cell_length; + } + } + + // now do some filtering + std::vector filtered_data; + for (int i = 0; i < centres_of_mass_frac.size(); i++) { + auto v = centres_of_mass_frac[i]; + double length = + std::pow(std::pow(v[0], 2) + std::pow(v[1], 2) + std::pow(v[2], 2), 0.5); + if ((length > min_cell) && (length < 2 * max_cell)) { + SiteData site_data = {centres_of_mass_frac[i], length, grid_points_per_void[i]}; + filtered_data.push_back(site_data); + } + } + // now sort filtered data + + // need to sort volumes and sites by length for group_vectors, and also filter by max + // and min cell + //std::stable_sort(filtered_data.begin(), filtered_data.end(), compare_site_data); + + // now 'group vectors' + double relative_length_tolerance = 0.1; + double angular_tolerance = 5.0; + std::vector vector_groups{}; + for (int i = 0; i < filtered_data.size(); i++) { + bool matched_group = false; + double length = filtered_data[i].length; + for (int j = 0; j < vector_groups.size(); j++) { + Vector3d mean_v = vector_groups[j].mean(); + double mean_v_length = vector_length(mean_v); + if ((std::abs(mean_v_length - length) / std::max(mean_v_length, length)) + < relative_length_tolerance) { + double angle = angle_between_vectors_degrees(mean_v, filtered_data[i].site); + if (angle < angular_tolerance) { + vector_groups[j].add(filtered_data[i].site, filtered_data[i].volume); + matched_group = true; + break; + } else if (std::abs(180 - angle) < angular_tolerance) { + vector_groups[j].add(-1.0 * filtered_data[i].site, filtered_data[i].volume); + matched_group = true; + break; + } + } + } + if (!matched_group) { + VectorGroup group = VectorGroup(); + group.add(filtered_data[i].site, filtered_data[i].volume); + vector_groups.push_back(group); + } + } + std::vector grouped_data; + for (int i = 0; i < vector_groups.size(); i++) { + Vector3d site = vector_groups[i].mean(); + int max = *std::max_element(vector_groups[i].weights.begin(), + vector_groups[i].weights.end()); + SiteData site_data = {site, vector_length(site), max}; + grouped_data.push_back(site_data); + } + std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data_volume); + std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data); + + // std::vector unique_vectors; + // std::vector unique_volumes; + std::vector unique_sites; + for (int i = 0; i < grouped_data.size(); i++) { + bool is_unique = true; + Vector3d v = grouped_data[i].site; + for (int j = 0; j < unique_sites.size(); j++) { + if (unique_sites[j].volume > grouped_data[i].volume) { + if (is_approximate_integer_multiple(unique_sites[j].site, v)) { + std::cout << "rejecting " << vector_length(v) << ": is integer multiple of " + << vector_length(unique_sites[j].site) << std::endl; + is_unique = false; + break; + } + } + } + if (is_unique) { + // std::cout << v[0] << " " << v[1] << " " << v[2] << std::endl; + // unique_vectors.push_back(v); + // unique_volumes.push_back(grouped_data[i].volume); + SiteData site{v, vector_length(v), grouped_data[i].volume}; + unique_sites.push_back(site); + } + } + // now sort by peak volume again + std::stable_sort(unique_sites.begin(), unique_sites.end(), compare_site_data_volume); + std::vector unique_vectors_sorted; + std::cout << "Candidate basis vectors: " << std::endl; + for (int i = 0; i < unique_sites.size(); i++) { + unique_vectors_sorted.push_back(unique_sites[i].site); + std::cout << i << " " << unique_sites[i].length << " " << unique_sites[i].volume << std::endl; + } + + auto end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + std::cout << "elapsed time for sites_to_vecs: " << elapsed_seconds.count() << "s" + << std::endl; + return unique_vectors_sorted; +} \ No newline at end of file diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index 93c79f1..c05e894 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -1,85 +1,21 @@ #include #include #include +#include "simple_models.cc" using Eigen::Matrix3d; using Eigen::Vector3d; -class SimpleBeam { -public: - double wavelength; - Vector3d s0; - - SimpleBeam(double wavelength) { - this->wavelength = wavelength; - s0 = {0.0, 0.0, -1.0 / wavelength}; - } -}; - -class SimpleDetector { -public: - Matrix3d d_matrix; - double pixel_size; // in mm - - SimpleDetector(Matrix3d d_matrix, double pixel_size) { - this->d_matrix = d_matrix; - this->pixel_size = pixel_size; - } -}; - -class SimpleScan { -public: - int image_range_start; - double osc_start; - double osc_width; - - SimpleScan(int image_range_start, double osc_start, double osc_width) { - this->image_range_start = image_range_start; - this->osc_start = osc_start; - this->osc_width = osc_width; - } -}; - -class SimpleGonio { -public: - Matrix3d sample_rotation; - Vector3d rotation_axis; - Matrix3d setting_rotation; - Matrix3d sample_rotation_inverse; - Matrix3d setting_rotation_inverse; - - SimpleGonio(Matrix3d sample_rotation, - Vector3d rotation_axis, - Matrix3d setting_rotation) { - this->sample_rotation = sample_rotation; - rotation_axis.normalize(); - this->rotation_axis = rotation_axis; - this->setting_rotation = setting_rotation; - sample_rotation_inverse = this->sample_rotation.inverse(); - setting_rotation_inverse = this->setting_rotation.inverse(); - } -}; - std::vector xyz_to_rlp( const std::vector &xyzobs_px, - Matrix3d sample_rotation, - Matrix3d detector_d_matrix, - double wavelength, - double pixel_size_mm, - int image_range_start, - double osc_start, - double osc_width, - Vector3d rotation_axis, - Matrix3d setting_rotation) { + const SimpleDetector &detector, + const SimpleBeam &beam, + const SimpleScan &scan, + const SimpleGonio &gonio) { auto start = std::chrono::system_clock::now(); // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method - SimpleBeam beam(wavelength); - SimpleDetector detector(detector_d_matrix, pixel_size_mm); - SimpleScan scan(image_range_start, osc_start, osc_width); - SimpleGonio gonio(sample_rotation, rotation_axis, setting_rotation); - - float DEG2RAD = M_PI / 180.0; + double DEG2RAD = M_PI / 180.0; std::vector rlp(xyzobs_px.size() / 3); for (int i = 0; i < rlp.size(); ++i) { // first convert detector pixel positions into mm @@ -87,14 +23,13 @@ std::vector xyz_to_rlp( double x1 = xyzobs_px[vec_idx]; double x2 = xyzobs_px[vec_idx+1]; double x3 = xyzobs_px[vec_idx+2]; - double x_mm = x1 * detector.pixel_size; - double y_mm = x2 * detector.pixel_size; + std::array xymm = detector.px_to_mm(x1,x2); // convert the image 'z' coordinate to rotation angle based on the scan data double rot_angle = (((x3 + 1 - scan.image_range_start) * scan.osc_width) + scan.osc_start) * DEG2RAD; - + // calculate the s1 vector using the detector d matrix - Vector3d m = {x_mm, y_mm, 1.0}; + Vector3d m = {xymm[0], xymm[1], 1.0}; Vector3d s1_i = detector.d_matrix * m; s1_i.normalize(); // convert into inverse ansgtroms @@ -106,11 +41,12 @@ std::vector xyz_to_rlp( Vector3d S = gonio.setting_rotation_inverse * (s1_this - beam.s0); double cos = std::cos(-1.0 * rot_angle); double sin = std::sin(-1.0 * rot_angle); + // rlp_this = S.rotate_around_origin(gonio.rotation_axis, -1.0 * rot_angle); Vector3d rlp_this = (S * cos) + (gonio.rotation_axis * gonio.rotation_axis.dot(S) * (1 - cos)) + (sin * gonio.rotation_axis.cross(S)); - // lp_this = S.rotate_around_origin(gonio.rotation_axis, -1.0 * rot_angle); + rlp[i] = gonio.sample_rotation_inverse * rlp_this; } From cb7aa4f8ab421812b596a2abaaefe12ad572918f Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:14:36 +0000 Subject: [PATCH 06/72] Try extracting some metadata from the h5 file through the reader. --- h5read/include/h5read.h | 7 +++++ h5read/src/h5read.c | 41 +++++++++++++++++++++++++++++ indexer/indexer.cc | 57 ++++++++++++++++++++++++++--------------- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/h5read/include/h5read.h b/h5read/include/h5read.h index 406a0f7..65a5ba3 100644 --- a/h5read/include/h5read.h +++ b/h5read/include/h5read.h @@ -50,6 +50,7 @@ size_t h5read_get_image_fast(h5read_handle *obj); void h5read_get_trusted_range(h5read_handle *obj, image_t_type *min, image_t_type *max); /// Get the wavelength for this dataset float h5read_get_wavelength(h5read_handle *obj); +double* h5read_get_module_offsets(h5read_handle *obj); /// Get the pixel size for this dataset float h5read_get_pixel_size_slow(h5read_handle *obj); float h5read_get_pixel_size_fast(h5read_handle *obj); @@ -160,6 +161,7 @@ class Reader { virtual size_t get_number_of_images() const = 0; virtual std::array get_trusted_range() const = 0; virtual std::array image_shape() const = 0; + //virtual std::array get_module_offsets() const = 0; virtual std::optional> get_mask() const = 0; virtual std::optional get_wavelength() const = 0; virtual std::optional> get_pixel_size() @@ -262,6 +264,11 @@ class H5Read : public Reader { } } + std::array get_module_offsets() const { + double* vals = h5read_get_module_offsets(_handle.get()); + return {vals[0], vals[1], vals[2]}; + } + virtual std::optional> get_pixel_size() const { return {{h5read_get_pixel_size_slow(_handle.get()), h5read_get_pixel_size_fast(_handle.get())}}; diff --git a/h5read/src/h5read.c b/h5read/src/h5read.c index b8780a3..4fa6fde 100644 --- a/h5read/src/h5read.c +++ b/h5read/src/h5read.c @@ -50,6 +50,7 @@ struct _h5read_handle { float pixel_size_x, pixel_size_y; float detector_distance; float beam_center_x, beam_center_y; + double module_offsets[3]; }; void h5read_free(h5read_handle *obj) { @@ -401,6 +402,10 @@ float h5read_get_wavelength(h5read_handle *obj) { return obj->wavelength; } +double* h5read_get_module_offsets(h5read_handle *obj){ + return obj->module_offsets; +} + float h5read_get_pixel_size_slow(h5read_handle *obj) { return obj->pixel_size_x; } @@ -591,6 +596,28 @@ herr_t _read_single_value_image_t_type(hid_t origin, return 0; } +/// Read an attribute of a HDF5 group that is a vector. +herr_t _read_object_attribute_array(hid_t origin, const char *path, const char *attrname, double *destination) { + hid_t dataset = H5Dopen(origin, path, H5P_DEFAULT); + if (dataset < 0) { + return dataset; + } + //hid_t datatype = H5Dget_type(dataset); + hid_t attr = H5Aopen(dataset, attrname, H5P_DEFAULT); + hid_t attrdatatype = H5Aget_type(attr); + if (H5Aread( + attr, attrdatatype, destination) + < 0) { + fprintf(stderr, + "Error: While reading array attribute: Unspecified data reading error.\n", + path); + exit(1); + } + H5Aclose(attr); + H5Dclose(dataset); + return 0; +} + /// Read a single float value out of an HDF5 dataset /// /// If the dataset is present, but contains multiple elements, sets destination @@ -668,6 +695,18 @@ void read_wavelength(h5read_handle *obj) { } } +void read_module_offsets(h5read_handle *obj){ + if (_read_object_attribute_array(obj->master_file, + "/entry/instrument/detector/module/module_offset", + "vector", + *(&obj->module_offsets)) + < 0){ + obj->module_offsets[0] = -1; + obj->module_offsets[1] = -1; + obj->module_offsets[2] = -1; + } +} + void read_detector_metadata(h5read_handle *obj) { if (_read_single_value_float(obj->master_file, "/entry/instrument/detector/x_pixel_size", @@ -924,6 +963,8 @@ h5read_handle *h5read_open(const char *master_filename) { read_detector_metadata(file); + read_module_offsets(file); + read_mask(file); setup_data(file); diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 6446445..db31085 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -20,24 +20,24 @@ using Eigen::Matrix3d; int main(int argc, char **argv) { - auto t1 = std::chrono::system_clock::now(); + auto parser = CUDAArgumentParser(); - /*parser.add_h5read_arguments(); //will use h5 file to get metadata + parser.add_h5read_arguments(); //will use h5 file to get metadata parser.add_argument("--images") .help("Maximum number of images to process") .metavar("NUM") - .scan<'u', uint32_t>();*/ + .scan<'u', uint32_t>(); auto args = parser.parse_args(argc, argv); - /*std::unique_ptr reader_ptr; + std::unique_ptr reader_ptr; // Wait for read-readiness reader_ptr = args.file.empty() ? std::make_unique() : std::make_unique(args.file); // Bind this as a reference - Reader &reader = *reader_ptr; + H5Read &reader = *reader_ptr; auto reader_mutex = std::mutex{}; @@ -54,37 +54,54 @@ int main(int argc, char **argv) { std::exit(1); } float wavelength_f = wavelength_opt.value(); - printf("INDEXER: Got wavelength from file: %f Å\n", wavelength_f);*/ - - + printf("INDEXER: Got wavelength from file: %f Å\n", wavelength_f); + auto distance_opt = reader.get_detector_distance(); + if (!distance_opt) { + printf("Error: No detector distance found in file."); + std::exit(1); + } + float distance_f = distance_opt.value()*1000; + printf("INDEXER: Got detector distance from file: %f mm\n", distance_f); + auto pixel_size_f = reader.get_pixel_size(); + float pixel_size_x = pixel_size_f.value()[0]*1000; + printf("INDEXER: Got detector pixel_size from file: %f mm\n", pixel_size_x); + + std::array module_offsets = reader.get_module_offsets(); + double origin_x = module_offsets[0] * 1000; + double origin_y = module_offsets[1] * 1000; + printf("INDEXER: Got detector origin x from file: %f mm\n", origin_x); + printf("INDEXER: Got detector origin y from file: %f mm\n", origin_y); + //TODO // Get metadata from file - // implement px_to_mm parallax corrections - // implement max cell/d_min estimation. + // implement max cell/d_min estimation. - will need annlib if want same result as dials. //FIXME don't assume s0 vector get from file - double wavelength = 0.9762535307519975; - //Vector3d s0 {0.0, 0.0, -1.0/wavelength}; + //double wavelength = 0.9762535307519975; // now get detector properties // need fast, slow, norm and origin, plus pixel size - //std::optional> pixel_size = reader.get_pixel_size(); - + - std::array fast_axis {1.0, 0.0, 0.0}; //FIXME get through reader + std::array fast_axis {1.0, 0.0, 0.0}; //FIXME get through reader - but const for I03 for now std::array slow_axis {0.0, -1.0, 0.0}; //FIXME get through reader // ^ change basis from nexus to iucr/imgcif convention (invert x and z) - std::array normal {0.0, 0.0, 0.0}; // fast_axis cross slow_axis + //std::array normal {0.0, 0.0, 0.0}; // fast_axis cross slow_axis //std::array origin {-75.61, 79.95, -150.0}; // FIXME //Vector3d origin {-75.61, 79.95, -150.0}; // FIXME - Vector3d origin {-153.60993960268158, 162.44624026693077, -200.45297988785603}; + //Vector3d origin {-153.60993960268158, 162.44624026693077, -200.45297988785603}; + Vector3d origin {-1.0*origin_x, origin_y, -1.0*distance_f}; Matrix3d d_matrix{{fast_axis[0], slow_axis[0], origin[0]},{ fast_axis[1], slow_axis[1], origin[1]}, {fast_axis[2], slow_axis[2], origin[2]}}; + //FIXME remove assumption of pixel sizes being same in analysis code. - double pixel_size_x = 0.075; + //double pixel_size_x = pixel_size[0];//0.075; + + // Thickness not currently written to nxs file? Then need to calc mu from thickness. + // Required to get equivalent results to dials. double mu = 3.9220780876; double t0 = 0.45; @@ -97,12 +114,12 @@ int main(int argc, char **argv) { Matrix3d fixed_rotation{{1,0,0},{0,1,0},{0,0,1}};//{{0.965028,0.0598562,-0.255222},{-0.128604,-0.74028,-0.659883},{-0.228434,0.669628,-0.706694}}; Vector3d rotation_axis {1.0,0.0,0.0}; Matrix3d setting_rotation {{1,0,0},{0,1,0},{0,0,1}}; - + auto t1 = std::chrono::system_clock::now(); // Make the dxtbx-like models SimpleDetector detector(d_matrix, pixel_size_x, mu, t0, true); SimpleScan scan(image_range_start, osc_start, osc_width); SimpleGonio gonio(fixed_rotation, rotation_axis, setting_rotation); - SimpleBeam beam(wavelength); + SimpleBeam beam(wavelength_f); // get processed reflection data from spotfinding std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl"; From 160c5214f490ae34d18ac37cedca7c323d104273 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:07:15 +0000 Subject: [PATCH 07/72] Add reading of scan start and osc width --- h5read/include/h5read.h | 6 ++++++ h5read/src/h5read.c | 39 +++++++++++++++++++++++++++++++++++++++ indexer/indexer.cc | 15 ++++++++++----- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/h5read/include/h5read.h b/h5read/include/h5read.h index 65a5ba3..2865ec1 100644 --- a/h5read/include/h5read.h +++ b/h5read/include/h5read.h @@ -54,6 +54,8 @@ double* h5read_get_module_offsets(h5read_handle *obj); /// Get the pixel size for this dataset float h5read_get_pixel_size_slow(h5read_handle *obj); float h5read_get_pixel_size_fast(h5read_handle *obj); +double h5read_get_oscillation_start(h5read_handle *obj); +double h5read_get_oscillation_width(h5read_handle *obj); float h5read_get_detector_distance(h5read_handle *obj); float h5read_get_beam_center_x(h5read_handle *obj); float h5read_get_beam_center_y(h5read_handle *obj); @@ -268,6 +270,10 @@ class H5Read : public Reader { double* vals = h5read_get_module_offsets(_handle.get()); return {vals[0], vals[1], vals[2]}; } + std::array get_oscillation() const { + return {{h5read_get_oscillation_start(_handle.get()), + h5read_get_oscillation_width(_handle.get())}}; + } virtual std::optional> get_pixel_size() const { return {{h5read_get_pixel_size_slow(_handle.get()), diff --git a/h5read/src/h5read.c b/h5read/src/h5read.c index 4fa6fde..ba654e0 100644 --- a/h5read/src/h5read.c +++ b/h5read/src/h5read.c @@ -51,6 +51,8 @@ struct _h5read_handle { float detector_distance; float beam_center_x, beam_center_y; double module_offsets[3]; + double oscillation_start; + double oscillation_width; }; void h5read_free(h5read_handle *obj) { @@ -421,6 +423,12 @@ float h5read_get_beam_center_x(h5read_handle *obj) { float h5read_get_beam_center_y(h5read_handle *obj) { return obj->beam_center_y; } +double h5read_get_oscillation_start(h5read_handle *obj){ + return obj->oscillation_start; +} +double h5read_get_oscillation_width(h5read_handle *obj){ + return obj->oscillation_width; +} #ifdef HAVE_HDF5 void read_mask(h5read_handle *obj) { @@ -707,6 +715,35 @@ void read_module_offsets(h5read_handle *obj){ } } +void read_oscillation_start_and_width(h5read_handle *obj){ + char omega_path[] = "/entry/sample/sample_omega/omega"; + + hid_t omega_dataset = H5Dopen(obj->master_file, omega_path, H5P_DEFAULT); + if (omega_dataset < 0){ + //We're allowed no omega scan e.g. grid + obj->oscillation_start=0; + obj->oscillation_width=0; + return; + } + hid_t datatype = H5Dget_type(omega_dataset); + hid_t omega_info = H5Dget_space(omega_dataset); + int size = H5Sget_simple_extent_npoints(omega_info); + if (size<2){ + fprintf(stderr, "Error: While reading oscillation, size<2\n"); + exit(1); + } + double* raw_omega = (double *)malloc(sizeof(double) * size); + void* buffer = (void *)raw_omega; + if (H5Dread(omega_dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer) < 0) { + fprintf(stderr, "Error: While reading oscillation\n"); + exit(1); + } + obj->oscillation_start=raw_omega[0]; + obj->oscillation_width=raw_omega[1] - raw_omega[0]; + free(raw_omega); + H5Dclose(omega_dataset); +} + void read_detector_metadata(h5read_handle *obj) { if (_read_single_value_float(obj->master_file, "/entry/instrument/detector/x_pixel_size", @@ -965,6 +1002,8 @@ h5read_handle *h5read_open(const char *master_filename) { read_module_offsets(file); + read_oscillation_start_and_width(file); + read_mask(file); setup_data(file); diff --git a/indexer/indexer.cc b/indexer/indexer.cc index db31085..a3093e4 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -101,17 +101,22 @@ int main(int argc, char **argv) { //double pixel_size_x = pixel_size[0];//0.075; // Thickness not currently written to nxs file? Then need to calc mu from thickness. - // Required to get equivalent results to dials. + // Required to get equivalent results to dials. Const for I03 Eiger double mu = 3.9220780876; double t0 = 0.45; // now get scan properties e.g. - int image_range_start = 1; - double osc_start = 0.0; - double osc_width = 0.10002778549596769; + int image_range_start = 1; // a 'dials' thing. + double osc_start = reader.get_oscillation()[0]; + double osc_width = reader.get_oscillation()[1]; + printf("INDEXER: Got osc start from file: %f\n", osc_start); + printf("INDEXER: Got osc width from file: %f\n", osc_width); + //double osc_start = 0.0; + //double osc_width = 0.10002778549596769; // finally gonio properties e.g. - Matrix3d fixed_rotation{{1,0,0},{0,1,0},{0,0,1}};//{{0.965028,0.0598562,-0.255222},{-0.128604,-0.74028,-0.659883},{-0.228434,0.669628,-0.706694}}; + // Const for I03 Eiger + Matrix3d fixed_rotation{{1,0,0},{0,1,0},{0,0,1}}; Vector3d rotation_axis {1.0,0.0,0.0}; Matrix3d setting_rotation {{1,0,0},{0,1,0},{0,0,1}}; auto t1 = std::chrono::system_clock::now(); From b21a08bf86b214e871fe7ce7c5a311ad4337c785 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:16:01 +0000 Subject: [PATCH 08/72] Add a more formal beam model --- CMakeLists.txt | 1 + dx2/beam.h | 53 +++++++++++++++++++++++++++++++ {indexer => dx2}/simple_models.cc | 4 +-- indexer/CMakeLists.txt | 1 + indexer/indexer.cc | 13 ++++++-- indexer/xyz_to_rlp.cc | 9 ++++-- 6 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 dx2/beam.h rename {indexer => dx2}/simple_models.cc (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index b8c07bc..145dff6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ include(AlwaysColourCompilation) include_directories(include) +include_directories(dx2) # Dependency fetching set(FETCHCONTENT_QUIET OFF) diff --git a/dx2/beam.h b/dx2/beam.h new file mode 100644 index 0000000..113a6c1 --- /dev/null +++ b/dx2/beam.h @@ -0,0 +1,53 @@ +#ifndef DX2_MODEL_BEAM_H +#define DX2_MODEL_BEAM_H +#include +using Eigen::Vector3d; + +class Beam { +// A monochromatic beam +public: + Beam()=default; + Beam(double wavelength); + Beam(Vector3d s0); + double get_wavelength() const; + void set_wavelength(double wavelength); + Vector3d get_s0() const; + void set_s0(Vector3d s0); + +protected: + double wavelength_{0.0}; + Vector3d sample_to_source_direction_{0.0,0.0,1.0}; //called direction_ in dxtbx + double divergence_{0.0}; // "beam divergence - be more specific with name?" + double sigma_divergence_{0.0}; // standard deviation of the beam divergence + Vector3d polarization_normal_{0.0,1.0,0.0}; + double polarization_fraction_{0.999}; + double flux_{0.0}; + double transmission_{1.0}; + double sample_to_source_distance_{0.0}; // FIXME is this really needed? +}; + +Beam::Beam(double wavelength) : wavelength_{wavelength} {} + +Beam::Beam(Vector3d s0){ + double len = s0.norm(); + wavelength_ = 1.0 / len; + sample_to_source_direction_ = -1.0 * s0 / len; +} + +double Beam::get_wavelength() const { + return wavelength_; +} +void Beam::set_wavelength(double wavelength){ + wavelength_ = wavelength; +} + +Vector3d Beam::get_s0() const { + return -sample_to_source_direction_ / wavelength_; +} +void Beam::set_s0(Vector3d s0){ + double len = s0.norm(); + wavelength_ = 1.0 / len; + sample_to_source_direction_ = -1.0 * s0 / len; +} + +#endif //DX2_MODEL_BEAM_H \ No newline at end of file diff --git a/indexer/simple_models.cc b/dx2/simple_models.cc similarity index 99% rename from indexer/simple_models.cc rename to dx2/simple_models.cc index 5658eea..a337c0a 100644 --- a/indexer/simple_models.cc +++ b/dx2/simple_models.cc @@ -6,7 +6,7 @@ using Eigen::Matrix3d; using Eigen::Vector3d; -class SimpleBeam { +/*class SimpleBeam { public: double wavelength; Vector3d s0; @@ -15,7 +15,7 @@ class SimpleBeam { this->wavelength = wavelength; s0 = {0.0, 0.0, -1.0 / wavelength}; } -}; +};*/ double attenuation_length(double mu, double t0, Vector3d s1, diff --git a/indexer/CMakeLists.txt b/indexer/CMakeLists.txt index f8d7aa1..ad1dde6 100644 --- a/indexer/CMakeLists.txt +++ b/indexer/CMakeLists.txt @@ -14,5 +14,6 @@ target_link_libraries(indexer CUDA::cudart CUDA::nppif lodepng + nlohmann_json::nlohmann_json ) target_compile_options(indexer PRIVATE "$<$,$>:-G>") diff --git a/indexer/indexer.cc b/indexer/indexer.cc index a3093e4..ae0abb3 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -1,6 +1,7 @@ #include #include #include +#include #include "common.hpp" #include "cuda_common.hpp" #include "h5read.h" @@ -14,10 +15,12 @@ #include "fft3d.cc" #include #include "simple_models.cc" +#include "beam.h" +#include using Eigen::Vector3d; using Eigen::Matrix3d; - +using json = nlohmann::json; int main(int argc, char **argv) { @@ -72,6 +75,12 @@ int main(int argc, char **argv) { printf("INDEXER: Got detector origin x from file: %f mm\n", origin_x); printf("INDEXER: Got detector origin y from file: %f mm\n", origin_y); + + std::string imported_expt = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt"; + std::ifstream f(imported_expt); + json elist_json_obj = json::parse(f); + double wl = elist_json_obj["beam"][0]["wavelength"]; + std::cout << "JSON WL " << wl << std::endl; //TODO // Get metadata from file // implement max cell/d_min estimation. - will need annlib if want same result as dials. @@ -124,7 +133,7 @@ int main(int argc, char **argv) { SimpleDetector detector(d_matrix, pixel_size_x, mu, t0, true); SimpleScan scan(image_range_start, osc_start, osc_width); SimpleGonio gonio(fixed_rotation, rotation_axis, setting_rotation); - SimpleBeam beam(wavelength_f); + Beam beam(wavelength_f); // get processed reflection data from spotfinding std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl"; diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index c05e894..b64497f 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -2,6 +2,7 @@ #include #include #include "simple_models.cc" +#include "beam.h" using Eigen::Matrix3d; using Eigen::Vector3d; @@ -9,7 +10,7 @@ using Eigen::Vector3d; std::vector xyz_to_rlp( const std::vector &xyzobs_px, const SimpleDetector &detector, - const SimpleBeam &beam, + const Beam &beam, const SimpleScan &scan, const SimpleGonio &gonio) { auto start = std::chrono::system_clock::now(); @@ -17,6 +18,8 @@ std::vector xyz_to_rlp( double DEG2RAD = M_PI / 180.0; std::vector rlp(xyzobs_px.size() / 3); + Vector3d s0 = beam.get_s0(); + double wl = beam.get_wavelength(); for (int i = 0; i < rlp.size(); ++i) { // first convert detector pixel positions into mm int vec_idx= 3*i; @@ -33,12 +36,12 @@ std::vector xyz_to_rlp( Vector3d s1_i = detector.d_matrix * m; s1_i.normalize(); // convert into inverse ansgtroms - Vector3d s1_this = s1_i / beam.wavelength; + Vector3d s1_this = s1_i / wl; // now apply the goniometer matrices // see https://dials.github.io/documentation/conventions.html for full conventions // rlp = F^-1 * R'^-1 * S^-1 * (s1-s0) - Vector3d S = gonio.setting_rotation_inverse * (s1_this - beam.s0); + Vector3d S = gonio.setting_rotation_inverse * (s1_this - s0); double cos = std::cos(-1.0 * rot_angle); double sin = std::sin(-1.0 * rot_angle); // rlp_this = S.rotate_around_origin(gonio.rotation_axis, -1.0 * rot_angle); From 256e66e117c7e8e3eef16166c4f417e1cfd2331c Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:53:26 +0000 Subject: [PATCH 09/72] Add initialization from json data --- dx2/beam.h | 19 +++++++++++++++++++ indexer/indexer.cc | 15 ++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/dx2/beam.h b/dx2/beam.h index 113a6c1..80d4725 100644 --- a/dx2/beam.h +++ b/dx2/beam.h @@ -9,6 +9,10 @@ class Beam { Beam()=default; Beam(double wavelength); Beam(Vector3d s0); + Beam(double wavelength, Vector3d direction, double divergence, + double sigma_divergence, Vector3d polarization_normal, + double polarization_fraction, double flux, + double transmission, double sample_to_source_distance); double get_wavelength() const; void set_wavelength(double wavelength); Vector3d get_s0() const; @@ -34,6 +38,21 @@ Beam::Beam(Vector3d s0){ sample_to_source_direction_ = -1.0 * s0 / len; } +// full constructor for to-from json +Beam::Beam(double wavelength, Vector3d direction, double divergence, + double sigma_divergence, Vector3d polarization_normal, + double polarization_fraction, double flux, + double transmission, double sample_to_source_distance) + : wavelength_{wavelength}, + sample_to_source_direction_{direction}, + divergence_{divergence}, + sigma_divergence_{sigma_divergence}, + polarization_normal_{polarization_normal}, + polarization_fraction_{polarization_fraction}, + flux_{flux}, transmission_{transmission}, + sample_to_source_distance_{sample_to_source_distance} {} + + double Beam::get_wavelength() const { return wavelength_; } diff --git a/indexer/indexer.cc b/indexer/indexer.cc index ae0abb3..28631aa 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -79,8 +79,17 @@ int main(int argc, char **argv) { std::string imported_expt = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt"; std::ifstream f(imported_expt); json elist_json_obj = json::parse(f); - double wl = elist_json_obj["beam"][0]["wavelength"]; - std::cout << "JSON WL " << wl << std::endl; + auto beam_obj = elist_json_obj["beam"][0]; + printf("DIRECTION %f", beam_obj["direction"][0]); + Vector3d dir{{beam_obj["direction"][0], beam_obj["direction"][1], beam_obj["direction"][2]}}; + Vector3d pol_n{{beam_obj["polarization_normal"][0],beam_obj["polarization_normal"][1],beam_obj["polarization_normal"][2]}}; + Beam beam( + beam_obj["wavelength"], dir, + beam_obj["divergence"], beam_obj["sigma_divergence"], + pol_n, beam_obj["polarization_fraction"], + beam_obj["flux"], beam_obj["transmission"], + beam_obj["sample_to_source_distance"]); + //TODO // Get metadata from file // implement max cell/d_min estimation. - will need annlib if want same result as dials. @@ -133,7 +142,7 @@ int main(int argc, char **argv) { SimpleDetector detector(d_matrix, pixel_size_x, mu, t0, true); SimpleScan scan(image_range_start, osc_start, osc_width); SimpleGonio gonio(fixed_rotation, rotation_axis, setting_rotation); - Beam beam(wavelength_f); + //Beam beam(wavelength_f); // get processed reflection data from spotfinding std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl"; From 2819da5b9f9038b9b79664d2f85e027538175745 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:09:28 +0000 Subject: [PATCH 10/72] Add json serialization, add dx2 scan class --- dx2/beam.h | 70 +++++++++++++++++++++++++++++++++++- dx2/scan.h | 84 +++++++++++++++++++++++++++++++++++++++++++ dx2/simple_models.cc | 4 +-- indexer/indexer.cc | 26 ++++++++------ indexer/xyz_to_rlp.cc | 9 +++-- 5 files changed, 177 insertions(+), 16 deletions(-) create mode 100644 dx2/scan.h diff --git a/dx2/beam.h b/dx2/beam.h index 80d4725..30e11fe 100644 --- a/dx2/beam.h +++ b/dx2/beam.h @@ -1,7 +1,9 @@ #ifndef DX2_MODEL_BEAM_H #define DX2_MODEL_BEAM_H #include +#include using Eigen::Vector3d; +using json = nlohmann::json; class Beam { // A monochromatic beam @@ -13,6 +15,8 @@ class Beam { double sigma_divergence, Vector3d polarization_normal, double polarization_fraction, double flux, double transmission, double sample_to_source_distance); + Beam(json beam_data); + json to_json() const; double get_wavelength() const; void set_wavelength(double wavelength); Vector3d get_s0() const; @@ -38,7 +42,7 @@ Beam::Beam(Vector3d s0){ sample_to_source_direction_ = -1.0 * s0 / len; } -// full constructor for to-from json +// full constructor Beam::Beam(double wavelength, Vector3d direction, double divergence, double sigma_divergence, Vector3d polarization_normal, double polarization_fraction, double flux, @@ -52,6 +56,70 @@ Beam::Beam(double wavelength, Vector3d direction, double divergence, flux_{flux}, transmission_{transmission}, sample_to_source_distance_{sample_to_source_distance} {} +// constructor from json data +Beam::Beam(json beam_data) { + // minimal required keys + std::vector required_keys = { + "wavelength" + }; + for (const auto &key : required_keys) { + if (beam_data.find(key) == beam_data.end()) { + throw std::invalid_argument( + "Key " + key + " is missing from the input beam JSON"); + } + } + wavelength_ = beam_data["wavelength"]; + /* additional potential keys + "direction", "polarization_normal", "polarization_fraction", + "divergence", "sigma_divergence", "flux", "transmission", + "sample_to_source_distance" + */ + if (beam_data.find("direction") != beam_data.end()){ + Vector3d direction{{beam_data["direction"][0], beam_data["direction"][1], beam_data["direction"][2]}}; + sample_to_source_direction_ = direction; + } + if (beam_data.find("divergence") != beam_data.end()){ + divergence_ = beam_data["divergence"]; + } + if (beam_data.find("sigma_divergence") != beam_data.end()){ + sigma_divergence_ = beam_data["sigma_divergence"]; + } + if (beam_data.find("polarization_normal") != beam_data.end()){ + Vector3d pn{ + {beam_data["polarization_normal"][0], + beam_data["polarization_normal"][1], + beam_data["polarization_normal"][2]}}; + polarization_normal_ = pn; + } + if (beam_data.find("polarization_fraction") != beam_data.end()){ + polarization_fraction_ = beam_data["polarization_fraction"]; + } + if (beam_data.find("flux") != beam_data.end()){ + flux_ = beam_data["flux"]; + } + if (beam_data.find("transmission") != beam_data.end()){ + transmission_ = beam_data["transmission"]; + } + if (beam_data.find("sample_to_source_distance") != beam_data.end()){ + sample_to_source_distance_ = beam_data["sample_to_source_distance"]; + } +} + +// serialize to json format +json Beam::to_json() const { + // create a json object that conforms to a dials model serialization. + json beam_data = {{"__id__", "monochromatic"}, {"probe", "x-ray"}}; + beam_data["wavelength"] = wavelength_; + beam_data["direction"] = sample_to_source_direction_; + beam_data["divergence"] = divergence_; + beam_data["sigma_divergence"] = sigma_divergence_; + beam_data["polarization_normal"] = polarization_normal_; + beam_data["polarization_fraction"] = polarization_fraction_; + beam_data["flux"] = flux_; + beam_data["transmission"] = transmission_; + beam_data["sample_to_source_distance"] = sample_to_source_distance_; + return beam_data; +} double Beam::get_wavelength() const { return wavelength_; diff --git a/dx2/scan.h b/dx2/scan.h new file mode 100644 index 0000000..9cba89a --- /dev/null +++ b/dx2/scan.h @@ -0,0 +1,84 @@ +#ifndef DX2_MODEL_SCAN_H +#define DX2_MODEL_SCAN_H +#include +using json = nlohmann::json; + +class Scan { +// A class to represent the physical measurement, consisting of the number of images, +// starting oscillation and a constant oscillation width between sequential images. +// This class MUST NOT be modified during processing or used to track additional metadata. +public: + Scan()=default; + Scan(std::array image_range, std::array oscillation); + Scan(json scan_data); + std::array get_image_range() const; + std::array get_oscillation() const; + json to_json() const; + +protected: + std::array image_range_{{0,0}}; + int num_images_{0}; + double oscillation_width_{0.0}; + double oscillation_start_{0.0}; +}; + +Scan::Scan(std::array image_range, std::array oscillation) + : image_range_{image_range}{ + num_images_ = image_range_[1] - image_range_[0]+1; + oscillation_start_ = oscillation[0]; + oscillation_width_ = oscillation[1]; +} + +Scan::Scan(json scan_data){ + // minimal required keys are image range and ["properties"]:"oscillation" + std::vector required_keys = { + "image_range", "properties" + }; + for (const auto &key : required_keys) { + if (scan_data.find(key) == scan_data.end()) { + throw std::invalid_argument( + "Key " + key + " is missing from the input scan JSON"); + } + } + image_range_ = scan_data["image_range"]; + num_images_ = image_range_[1] - image_range_[0]+1; + if (scan_data["properties"].find("oscillation") == scan_data["properties"].end()){ + throw std::invalid_argument( + "Key oscillation is missing from the input scan['properties'] JSON"); + } + std::vector oscillation; + json osc_array = scan_data["properties"]["oscillation"]; + // Just read the first two oscillation values as that is all that is needed + if (osc_array.size() < 2){ + throw std::invalid_argument( + "scan['properties']['oscillation'] has <2 values in the scan JSON"); + } + for (json::iterator it = osc_array.begin(); it != osc_array.begin()+2; ++it) { + oscillation.push_back(*it); + } + oscillation_start_ = oscillation[0]; + oscillation_width_ = oscillation[1] - oscillation[0]; +} + +json Scan::to_json() const { + json scan_data; + scan_data["image_range"] = image_range_; + scan_data["batch_offset"] = 0; // We MUST NOT use batch offsets in dx2, written out here for compatibility + std::vector oscillation_array(num_images_); + for (int i=0; i Scan::get_image_range() const { + return image_range_; +} + +std::array Scan::get_oscillation() const { + return {oscillation_start_, oscillation_width_}; +} + +#endif //DX2_MODEL_SCAN_H \ No newline at end of file diff --git a/dx2/simple_models.cc b/dx2/simple_models.cc index a337c0a..cd1de6a 100644 --- a/dx2/simple_models.cc +++ b/dx2/simple_models.cc @@ -67,7 +67,7 @@ std::array SimpleDetector::px_to_mm(double x, double y) const{ return std::array{c1,c2}; } -class SimpleScan { +/*class SimpleScan { public: int image_range_start; double osc_start; @@ -78,7 +78,7 @@ class SimpleScan { this->osc_start = osc_start; this->osc_width = osc_width; } -}; +};*/ class SimpleGonio { public: diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 28631aa..84d47d4 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -16,6 +16,7 @@ #include #include "simple_models.cc" #include "beam.h" +#include "scan.h" #include using Eigen::Vector3d; @@ -79,16 +80,16 @@ int main(int argc, char **argv) { std::string imported_expt = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt"; std::ifstream f(imported_expt); json elist_json_obj = json::parse(f); - auto beam_obj = elist_json_obj["beam"][0]; - printf("DIRECTION %f", beam_obj["direction"][0]); - Vector3d dir{{beam_obj["direction"][0], beam_obj["direction"][1], beam_obj["direction"][2]}}; - Vector3d pol_n{{beam_obj["polarization_normal"][0],beam_obj["polarization_normal"][1],beam_obj["polarization_normal"][2]}}; - Beam beam( - beam_obj["wavelength"], dir, - beam_obj["divergence"], beam_obj["sigma_divergence"], - pol_n, beam_obj["polarization_fraction"], - beam_obj["flux"], beam_obj["transmission"], - beam_obj["sample_to_source_distance"]); + json beam_data = elist_json_obj["beam"][0]; + Beam beam(beam_data); + json beam_out = beam.to_json(); + std::ofstream file("test_beam.json"); + file << beam_out.dump(4); + json scan_data = elist_json_obj["scan"][0]; + Scan scan(scan_data); + json scan_out = scan.to_json(); + std::ofstream scanfile("test_scan.json"); + scanfile << scan_out.dump(4); //TODO // Get metadata from file @@ -140,8 +141,11 @@ int main(int argc, char **argv) { auto t1 = std::chrono::system_clock::now(); // Make the dxtbx-like models SimpleDetector detector(d_matrix, pixel_size_x, mu, t0, true); - SimpleScan scan(image_range_start, osc_start, osc_width); + //SimpleScan scan(image_range_start, osc_start, osc_width); SimpleGonio gonio(fixed_rotation, rotation_axis, setting_rotation); + /*std::array image_range{{1,3600}}; + std::array oscillation{{osc_start, osc_width}}; + Scan scan(image_range, oscillation);*/ //Beam beam(wavelength_f); // get processed reflection data from spotfinding diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index b64497f..070b1aa 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -3,6 +3,7 @@ #include #include "simple_models.cc" #include "beam.h" +#include "scan.h" using Eigen::Matrix3d; using Eigen::Vector3d; @@ -11,7 +12,7 @@ std::vector xyz_to_rlp( const std::vector &xyzobs_px, const SimpleDetector &detector, const Beam &beam, - const SimpleScan &scan, + const Scan &scan, const SimpleGonio &gonio) { auto start = std::chrono::system_clock::now(); // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method @@ -20,6 +21,10 @@ std::vector xyz_to_rlp( std::vector rlp(xyzobs_px.size() / 3); Vector3d s0 = beam.get_s0(); double wl = beam.get_wavelength(); + std::array oscillation = scan.get_oscillation(); + double osc_width = oscillation[1]; + double osc_start = oscillation[0]; + int image_range_start = scan.get_image_range()[0]; for (int i = 0; i < rlp.size(); ++i) { // first convert detector pixel positions into mm int vec_idx= 3*i; @@ -29,7 +34,7 @@ std::vector xyz_to_rlp( std::array xymm = detector.px_to_mm(x1,x2); // convert the image 'z' coordinate to rotation angle based on the scan data double rot_angle = - (((x3 + 1 - scan.image_range_start) * scan.osc_width) + scan.osc_start) * DEG2RAD; + (((x3 + 1 - image_range_start) * osc_width) + osc_start) * DEG2RAD; // calculate the s1 vector using the detector d matrix Vector3d m = {xymm[0], xymm[1], 1.0}; From d2a9d76b856bc989f95cab87237ee67805710098 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:47:19 +0000 Subject: [PATCH 11/72] Add gonio dx2 model --- dx2/beam.h | 1 + dx2/goniometer.h | 161 ++++++++++++++++++++++++++++++++++++++++++ dx2/simple_models.cc | 43 ----------- indexer/indexer.cc | 18 +++-- indexer/xyz_to_rlp.cc | 14 ++-- 5 files changed, 184 insertions(+), 53 deletions(-) create mode 100644 dx2/goniometer.h diff --git a/dx2/beam.h b/dx2/beam.h index 30e11fe..7f2bdb6 100644 --- a/dx2/beam.h +++ b/dx2/beam.h @@ -2,6 +2,7 @@ #define DX2_MODEL_BEAM_H #include #include + using Eigen::Vector3d; using json = nlohmann::json; diff --git a/dx2/goniometer.h b/dx2/goniometer.h new file mode 100644 index 0000000..0341ab8 --- /dev/null +++ b/dx2/goniometer.h @@ -0,0 +1,161 @@ +#ifndef DX2_MODEL_GONIO_H +#define DX2_MODEL_GONIO_H +#include +#include +#include + +using json = nlohmann::json; +using Eigen::Matrix3d; +using Eigen::Vector3d; + +Matrix3d axis_and_angle_as_matrix(Vector3d axis, double angle, bool deg=false){ + double q0=0.0; + double q1=0.0; + double q2=0.0; + double q3=0.0; + if (deg) { + angle *= M_PI / 180.0; + } + if (!(std::fmod(angle, 2.0*M_PI))){ + q0=1.0; + } + else { + double h = 0.5 * angle; + q0 = std::cos(h); + double s = std::sin(h); + Vector3d n = axis / axis.norm(); + q1 = n[0]*s; + q2 = n[1]*s; + q3 = n[2]*s; + } + Matrix3d m{ + {2*(q0*q0+q1*q1)-1, 2*(q1*q2-q0*q3), 2*(q1*q3+q0*q2)}, + {2*(q1*q2+q0*q3), 2*(q0*q0+q2*q2)-1, 2*(q2*q3-q0*q1)}, + {2*(q1*q3-q0*q2), 2*(q2*q3+q0*q1), 2*(q0*q0+q3*q3)-1}}; + return m; +} + +class Goniometer { +// A class to represent a multi-axis goniometer. +public: + Goniometer()=default; + Goniometer( + std::vector axes, + std::vector angles, + std::vector names, + std::size_t scan_axis); + Goniometer(json goniometer_data); + Matrix3d get_setting_rotation() const; + Matrix3d get_sample_rotation() const; + Vector3d get_rotation_axis() const; + json to_json() const; + +protected: + void init(); // Sets the matrices from the axes and angles + Matrix3d calculate_setting_rotation(); + Matrix3d calculate_sample_rotation(); + Matrix3d sample_rotation_{{1,0,0},{0,1,0},{0,0,1}}; // F + Vector3d rotation_axis_{{1.0,0.0,0.0}}; // R' + Matrix3d setting_rotation_{{1,0,0},{0,1,0},{0,0,1}}; // S + std::vector axes_{{1.0,0.0,0.0}}; + std::vector angles_{{0.0}}; + std::vector names_{"omega"}; + std::size_t scan_axis_{0}; +}; + +void Goniometer::init(){ + // Sets the matrices from the axes and angles + setting_rotation_ = calculate_setting_rotation(); + sample_rotation_ = calculate_sample_rotation(); + rotation_axis_ = axes_[scan_axis_]; +} + +Matrix3d Goniometer::calculate_setting_rotation(){ + Matrix3d setting_rotation{{1,0,0},{0,1,0},{0,0,1}}; + for (std::size_t i = scan_axis_ + 1; i < axes_.size(); i++) { + Matrix3d R = axis_and_angle_as_matrix(axes_[i], angles_[i], true); + setting_rotation = R * setting_rotation; + } + return setting_rotation; +} + +Matrix3d Goniometer::calculate_sample_rotation(){ + Matrix3d sample_rotation{{1,0,0},{0,1,0},{0,0,1}}; + for (std::size_t i = 0; i < scan_axis_; i++) { + Matrix3d R = axis_and_angle_as_matrix(axes_[i], angles_[i], true); + sample_rotation = R * sample_rotation; + } + return sample_rotation; +} + +Goniometer::Goniometer( + std::vector axes, + std::vector angles, + std::vector names, + std::size_t scan_axis) + : axes_{axes.begin(), axes.end()}, + angles_{angles.begin(), angles.end()}, + names_{names.begin(), names.end()}, + scan_axis_{scan_axis}{ + if (scan_axis_ >= axes_.size()){ + throw std::invalid_argument("Goniometer scan axis number is out of range of axis length"); + } + init(); +} + +Matrix3d Goniometer::get_setting_rotation() const{ + return setting_rotation_; +} + +Matrix3d Goniometer::get_sample_rotation() const { + return sample_rotation_; +} + +Vector3d Goniometer::get_rotation_axis() const { + return rotation_axis_; +} + +Goniometer::Goniometer(json goniometer_data){ + std::vector required_keys = { + "axes", "angles", "names", "scan_axis" + }; + for (const auto &key : required_keys) { + if (goniometer_data.find(key) == goniometer_data.end()) { + throw std::invalid_argument( + "Key " + key + " is missing from the input goniometer JSON"); + } + } + std::vector axes; + for (json::iterator it=goniometer_data["axes"].begin();it != goniometer_data["axes"].end();it++){ + Vector3d axis; + axis[0] = (*it)[0]; + axis[1] = (*it)[1]; + axis[2] = (*it)[2]; + axes.push_back(axis); + } + axes_ = axes; + std::vector angles; + for (json::iterator it=goniometer_data["angles"].begin();it != goniometer_data["angles"].end();it++){ + angles.push_back(*it); + } + angles_ = angles; + std::vector names; + for (json::iterator it=goniometer_data["names"].begin();it != goniometer_data["names"].end();it++){ + names.push_back(*it); + } + names_ = names; + scan_axis_ = goniometer_data["scan_axis"]; + init(); +} + +json Goniometer::to_json() const { + json goniometer_data; + goniometer_data["axes"] = axes_; + goniometer_data["angles"] = angles_; + goniometer_data["names"] = names_; + goniometer_data["scan_axis"] = scan_axis_; + return goniometer_data; +} + + +#endif //DX2_MODEL_GONIO_H \ No newline at end of file diff --git a/dx2/simple_models.cc b/dx2/simple_models.cc index cd1de6a..adacbd8 100644 --- a/dx2/simple_models.cc +++ b/dx2/simple_models.cc @@ -6,17 +6,6 @@ using Eigen::Matrix3d; using Eigen::Vector3d; -/*class SimpleBeam { -public: - double wavelength; - Vector3d s0; - - SimpleBeam(double wavelength) { - this->wavelength = wavelength; - s0 = {0.0, 0.0, -1.0 / wavelength}; - } -};*/ - double attenuation_length(double mu, double t0, Vector3d s1, Vector3d fast, @@ -67,37 +56,5 @@ std::array SimpleDetector::px_to_mm(double x, double y) const{ return std::array{c1,c2}; } -/*class SimpleScan { -public: - int image_range_start; - double osc_start; - double osc_width; - - SimpleScan(int image_range_start, double osc_start, double osc_width) { - this->image_range_start = image_range_start; - this->osc_start = osc_start; - this->osc_width = osc_width; - } -};*/ - -class SimpleGonio { -public: - Matrix3d sample_rotation; - Vector3d rotation_axis; - Matrix3d setting_rotation; - Matrix3d sample_rotation_inverse; - Matrix3d setting_rotation_inverse; - - SimpleGonio(Matrix3d sample_rotation, - Vector3d rotation_axis, - Matrix3d setting_rotation) { - this->sample_rotation = sample_rotation; - rotation_axis.normalize(); - this->rotation_axis = rotation_axis; - this->setting_rotation = setting_rotation; - sample_rotation_inverse = this->sample_rotation.inverse(); - setting_rotation_inverse = this->setting_rotation.inverse(); - } -}; #endif // INDEXER_SIMPLE_MODELS \ No newline at end of file diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 84d47d4..47b506d 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -17,6 +17,7 @@ #include "simple_models.cc" #include "beam.h" #include "scan.h" +#include "goniometer.h" #include using Eigen::Vector3d; @@ -82,14 +83,20 @@ int main(int argc, char **argv) { json elist_json_obj = json::parse(f); json beam_data = elist_json_obj["beam"][0]; Beam beam(beam_data); - json beam_out = beam.to_json(); + /*json beam_out = beam.to_json(); std::ofstream file("test_beam.json"); - file << beam_out.dump(4); + file << beam_out.dump(4);*/ json scan_data = elist_json_obj["scan"][0]; Scan scan(scan_data); - json scan_out = scan.to_json(); + /*json scan_out = scan.to_json(); std::ofstream scanfile("test_scan.json"); - scanfile << scan_out.dump(4); + scanfile << scan_out.dump(4);*/ + + json gonio_data = elist_json_obj["goniometer"][0]; + Goniometer gonio(gonio_data); + /*json gonio_out = gonio.to_json(); + std::ofstream goniofile("test_gonio.json"); + goniofile << gonio_out.dump(4);*/ //TODO // Get metadata from file @@ -142,7 +149,8 @@ int main(int argc, char **argv) { // Make the dxtbx-like models SimpleDetector detector(d_matrix, pixel_size_x, mu, t0, true); //SimpleScan scan(image_range_start, osc_start, osc_width); - SimpleGonio gonio(fixed_rotation, rotation_axis, setting_rotation); + //SimpleGonio gonio(fixed_rotation, rotation_axis, setting_rotation); + /*std::array image_range{{1,3600}}; std::array oscillation{{osc_start, osc_width}}; Scan scan(image_range, oscillation);*/ diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index 070b1aa..05bf234 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -4,6 +4,7 @@ #include "simple_models.cc" #include "beam.h" #include "scan.h" +#include "goniometer.h" using Eigen::Matrix3d; using Eigen::Vector3d; @@ -13,7 +14,7 @@ std::vector xyz_to_rlp( const SimpleDetector &detector, const Beam &beam, const Scan &scan, - const SimpleGonio &gonio) { + const Goniometer &gonio) { auto start = std::chrono::system_clock::now(); // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method @@ -25,6 +26,9 @@ std::vector xyz_to_rlp( double osc_width = oscillation[1]; double osc_start = oscillation[0]; int image_range_start = scan.get_image_range()[0]; + Matrix3d setting_rotation_inverse = gonio.get_setting_rotation().inverse(); + Matrix3d sample_rotation_inverse = gonio.get_sample_rotation().inverse(); + Vector3d rotation_axis = gonio.get_rotation_axis(); for (int i = 0; i < rlp.size(); ++i) { // first convert detector pixel positions into mm int vec_idx= 3*i; @@ -46,16 +50,16 @@ std::vector xyz_to_rlp( // now apply the goniometer matrices // see https://dials.github.io/documentation/conventions.html for full conventions // rlp = F^-1 * R'^-1 * S^-1 * (s1-s0) - Vector3d S = gonio.setting_rotation_inverse * (s1_this - s0); + Vector3d S = setting_rotation_inverse * (s1_this - s0); double cos = std::cos(-1.0 * rot_angle); double sin = std::sin(-1.0 * rot_angle); // rlp_this = S.rotate_around_origin(gonio.rotation_axis, -1.0 * rot_angle); Vector3d rlp_this = (S * cos) - + (gonio.rotation_axis * gonio.rotation_axis.dot(S) * (1 - cos)) - + (sin * gonio.rotation_axis.cross(S)); + + (rotation_axis * rotation_axis.dot(S) * (1 - cos)) + + (sin * rotation_axis.cross(S)); - rlp[i] = gonio.sample_rotation_inverse * rlp_this; + rlp[i] = sample_rotation_inverse * rlp_this; } auto end = std::chrono::system_clock::now(); From 63fc297d5154f0a333c5437fe83747e967249a79 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:14:45 +0000 Subject: [PATCH 12/72] Include eigen, move models dir --- CMakeLists.txt | 2 +- {dx2 => dx2_test_models}/beam.h | 0 {dx2 => dx2_test_models}/goniometer.h | 0 {dx2 => dx2_test_models}/scan.h | 0 {dx2 => dx2_test_models}/simple_models.cc | 0 indexer/CMakeLists.txt | 13 +++++++++++++ 6 files changed, 14 insertions(+), 1 deletion(-) rename {dx2 => dx2_test_models}/beam.h (100%) rename {dx2 => dx2_test_models}/goniometer.h (100%) rename {dx2 => dx2_test_models}/scan.h (100%) rename {dx2 => dx2_test_models}/simple_models.cc (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 145dff6..21ee7e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ include(AlwaysColourCompilation) include_directories(include) -include_directories(dx2) +include_directories(dx2_test_models) # Dependency fetching set(FETCHCONTENT_QUIET OFF) diff --git a/dx2/beam.h b/dx2_test_models/beam.h similarity index 100% rename from dx2/beam.h rename to dx2_test_models/beam.h diff --git a/dx2/goniometer.h b/dx2_test_models/goniometer.h similarity index 100% rename from dx2/goniometer.h rename to dx2_test_models/goniometer.h diff --git a/dx2/scan.h b/dx2_test_models/scan.h similarity index 100% rename from dx2/scan.h rename to dx2_test_models/scan.h diff --git a/dx2/simple_models.cc b/dx2_test_models/simple_models.cc similarity index 100% rename from dx2/simple_models.cc rename to dx2_test_models/simple_models.cc diff --git a/indexer/CMakeLists.txt b/indexer/CMakeLists.txt index ad1dde6..1e0c813 100644 --- a/indexer/CMakeLists.txt +++ b/indexer/CMakeLists.txt @@ -2,6 +2,18 @@ project(indexer CXX CUDA) find_package(CUDAToolkit REQUIRED) +# Automatic Dependencies +set(FETCHCONTENT_QUIET OFF) +include(FetchContent) +FetchContent_Declare( + Eigen3 + GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git + GIT_TAG 3.4.0 + EXCLUDE_FROM_ALL + FIND_PACKAGE_ARGS +) +FetchContent_MakeAvailable(Eigen3) + add_executable(indexer indexer.cc ) @@ -9,6 +21,7 @@ target_link_libraries(indexer PRIVATE fmt h5read + Eigen3::Eigen argparse standalone CUDA::cudart From 4cf521034bd45bf19e7eb246e801bfe73c862e82 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:00:31 +0000 Subject: [PATCH 13/72] Use dx2 repo --- CMakeLists.txt | 3 +- dx2_test_models/beam.h | 141 --------------------------- dx2_test_models/goniometer.h | 161 ------------------------------- dx2_test_models/scan.h | 84 ---------------- dx2_test_models/simple_models.cc | 60 ------------ indexer/CMakeLists.txt | 1 + indexer/indexer.cc | 8 +- indexer/xyz_to_rlp.cc | 8 +- 8 files changed, 10 insertions(+), 456 deletions(-) delete mode 100644 dx2_test_models/beam.h delete mode 100644 dx2_test_models/goniometer.h delete mode 100644 dx2_test_models/scan.h delete mode 100644 dx2_test_models/simple_models.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 21ee7e8..f91185e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,6 @@ include(AlwaysColourCompilation) include_directories(include) -include_directories(dx2_test_models) # Dependency fetching set(FETCHCONTENT_QUIET OFF) @@ -41,7 +40,7 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(argparse) - +add_subdirectory(dx2) add_subdirectory(h5read) add_subdirectory(baseline) add_subdirectory(spotfinder) diff --git a/dx2_test_models/beam.h b/dx2_test_models/beam.h deleted file mode 100644 index 7f2bdb6..0000000 --- a/dx2_test_models/beam.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef DX2_MODEL_BEAM_H -#define DX2_MODEL_BEAM_H -#include -#include - -using Eigen::Vector3d; -using json = nlohmann::json; - -class Beam { -// A monochromatic beam -public: - Beam()=default; - Beam(double wavelength); - Beam(Vector3d s0); - Beam(double wavelength, Vector3d direction, double divergence, - double sigma_divergence, Vector3d polarization_normal, - double polarization_fraction, double flux, - double transmission, double sample_to_source_distance); - Beam(json beam_data); - json to_json() const; - double get_wavelength() const; - void set_wavelength(double wavelength); - Vector3d get_s0() const; - void set_s0(Vector3d s0); - -protected: - double wavelength_{0.0}; - Vector3d sample_to_source_direction_{0.0,0.0,1.0}; //called direction_ in dxtbx - double divergence_{0.0}; // "beam divergence - be more specific with name?" - double sigma_divergence_{0.0}; // standard deviation of the beam divergence - Vector3d polarization_normal_{0.0,1.0,0.0}; - double polarization_fraction_{0.999}; - double flux_{0.0}; - double transmission_{1.0}; - double sample_to_source_distance_{0.0}; // FIXME is this really needed? -}; - -Beam::Beam(double wavelength) : wavelength_{wavelength} {} - -Beam::Beam(Vector3d s0){ - double len = s0.norm(); - wavelength_ = 1.0 / len; - sample_to_source_direction_ = -1.0 * s0 / len; -} - -// full constructor -Beam::Beam(double wavelength, Vector3d direction, double divergence, - double sigma_divergence, Vector3d polarization_normal, - double polarization_fraction, double flux, - double transmission, double sample_to_source_distance) - : wavelength_{wavelength}, - sample_to_source_direction_{direction}, - divergence_{divergence}, - sigma_divergence_{sigma_divergence}, - polarization_normal_{polarization_normal}, - polarization_fraction_{polarization_fraction}, - flux_{flux}, transmission_{transmission}, - sample_to_source_distance_{sample_to_source_distance} {} - -// constructor from json data -Beam::Beam(json beam_data) { - // minimal required keys - std::vector required_keys = { - "wavelength" - }; - for (const auto &key : required_keys) { - if (beam_data.find(key) == beam_data.end()) { - throw std::invalid_argument( - "Key " + key + " is missing from the input beam JSON"); - } - } - wavelength_ = beam_data["wavelength"]; - /* additional potential keys - "direction", "polarization_normal", "polarization_fraction", - "divergence", "sigma_divergence", "flux", "transmission", - "sample_to_source_distance" - */ - if (beam_data.find("direction") != beam_data.end()){ - Vector3d direction{{beam_data["direction"][0], beam_data["direction"][1], beam_data["direction"][2]}}; - sample_to_source_direction_ = direction; - } - if (beam_data.find("divergence") != beam_data.end()){ - divergence_ = beam_data["divergence"]; - } - if (beam_data.find("sigma_divergence") != beam_data.end()){ - sigma_divergence_ = beam_data["sigma_divergence"]; - } - if (beam_data.find("polarization_normal") != beam_data.end()){ - Vector3d pn{ - {beam_data["polarization_normal"][0], - beam_data["polarization_normal"][1], - beam_data["polarization_normal"][2]}}; - polarization_normal_ = pn; - } - if (beam_data.find("polarization_fraction") != beam_data.end()){ - polarization_fraction_ = beam_data["polarization_fraction"]; - } - if (beam_data.find("flux") != beam_data.end()){ - flux_ = beam_data["flux"]; - } - if (beam_data.find("transmission") != beam_data.end()){ - transmission_ = beam_data["transmission"]; - } - if (beam_data.find("sample_to_source_distance") != beam_data.end()){ - sample_to_source_distance_ = beam_data["sample_to_source_distance"]; - } -} - -// serialize to json format -json Beam::to_json() const { - // create a json object that conforms to a dials model serialization. - json beam_data = {{"__id__", "monochromatic"}, {"probe", "x-ray"}}; - beam_data["wavelength"] = wavelength_; - beam_data["direction"] = sample_to_source_direction_; - beam_data["divergence"] = divergence_; - beam_data["sigma_divergence"] = sigma_divergence_; - beam_data["polarization_normal"] = polarization_normal_; - beam_data["polarization_fraction"] = polarization_fraction_; - beam_data["flux"] = flux_; - beam_data["transmission"] = transmission_; - beam_data["sample_to_source_distance"] = sample_to_source_distance_; - return beam_data; -} - -double Beam::get_wavelength() const { - return wavelength_; -} -void Beam::set_wavelength(double wavelength){ - wavelength_ = wavelength; -} - -Vector3d Beam::get_s0() const { - return -sample_to_source_direction_ / wavelength_; -} -void Beam::set_s0(Vector3d s0){ - double len = s0.norm(); - wavelength_ = 1.0 / len; - sample_to_source_direction_ = -1.0 * s0 / len; -} - -#endif //DX2_MODEL_BEAM_H \ No newline at end of file diff --git a/dx2_test_models/goniometer.h b/dx2_test_models/goniometer.h deleted file mode 100644 index 0341ab8..0000000 --- a/dx2_test_models/goniometer.h +++ /dev/null @@ -1,161 +0,0 @@ -#ifndef DX2_MODEL_GONIO_H -#define DX2_MODEL_GONIO_H -#include -#include -#include - -using json = nlohmann::json; -using Eigen::Matrix3d; -using Eigen::Vector3d; - -Matrix3d axis_and_angle_as_matrix(Vector3d axis, double angle, bool deg=false){ - double q0=0.0; - double q1=0.0; - double q2=0.0; - double q3=0.0; - if (deg) { - angle *= M_PI / 180.0; - } - if (!(std::fmod(angle, 2.0*M_PI))){ - q0=1.0; - } - else { - double h = 0.5 * angle; - q0 = std::cos(h); - double s = std::sin(h); - Vector3d n = axis / axis.norm(); - q1 = n[0]*s; - q2 = n[1]*s; - q3 = n[2]*s; - } - Matrix3d m{ - {2*(q0*q0+q1*q1)-1, 2*(q1*q2-q0*q3), 2*(q1*q3+q0*q2)}, - {2*(q1*q2+q0*q3), 2*(q0*q0+q2*q2)-1, 2*(q2*q3-q0*q1)}, - {2*(q1*q3-q0*q2), 2*(q2*q3+q0*q1), 2*(q0*q0+q3*q3)-1}}; - return m; -} - -class Goniometer { -// A class to represent a multi-axis goniometer. -public: - Goniometer()=default; - Goniometer( - std::vector axes, - std::vector angles, - std::vector names, - std::size_t scan_axis); - Goniometer(json goniometer_data); - Matrix3d get_setting_rotation() const; - Matrix3d get_sample_rotation() const; - Vector3d get_rotation_axis() const; - json to_json() const; - -protected: - void init(); // Sets the matrices from the axes and angles - Matrix3d calculate_setting_rotation(); - Matrix3d calculate_sample_rotation(); - Matrix3d sample_rotation_{{1,0,0},{0,1,0},{0,0,1}}; // F - Vector3d rotation_axis_{{1.0,0.0,0.0}}; // R' - Matrix3d setting_rotation_{{1,0,0},{0,1,0},{0,0,1}}; // S - std::vector axes_{{1.0,0.0,0.0}}; - std::vector angles_{{0.0}}; - std::vector names_{"omega"}; - std::size_t scan_axis_{0}; -}; - -void Goniometer::init(){ - // Sets the matrices from the axes and angles - setting_rotation_ = calculate_setting_rotation(); - sample_rotation_ = calculate_sample_rotation(); - rotation_axis_ = axes_[scan_axis_]; -} - -Matrix3d Goniometer::calculate_setting_rotation(){ - Matrix3d setting_rotation{{1,0,0},{0,1,0},{0,0,1}}; - for (std::size_t i = scan_axis_ + 1; i < axes_.size(); i++) { - Matrix3d R = axis_and_angle_as_matrix(axes_[i], angles_[i], true); - setting_rotation = R * setting_rotation; - } - return setting_rotation; -} - -Matrix3d Goniometer::calculate_sample_rotation(){ - Matrix3d sample_rotation{{1,0,0},{0,1,0},{0,0,1}}; - for (std::size_t i = 0; i < scan_axis_; i++) { - Matrix3d R = axis_and_angle_as_matrix(axes_[i], angles_[i], true); - sample_rotation = R * sample_rotation; - } - return sample_rotation; -} - -Goniometer::Goniometer( - std::vector axes, - std::vector angles, - std::vector names, - std::size_t scan_axis) - : axes_{axes.begin(), axes.end()}, - angles_{angles.begin(), angles.end()}, - names_{names.begin(), names.end()}, - scan_axis_{scan_axis}{ - if (scan_axis_ >= axes_.size()){ - throw std::invalid_argument("Goniometer scan axis number is out of range of axis length"); - } - init(); -} - -Matrix3d Goniometer::get_setting_rotation() const{ - return setting_rotation_; -} - -Matrix3d Goniometer::get_sample_rotation() const { - return sample_rotation_; -} - -Vector3d Goniometer::get_rotation_axis() const { - return rotation_axis_; -} - -Goniometer::Goniometer(json goniometer_data){ - std::vector required_keys = { - "axes", "angles", "names", "scan_axis" - }; - for (const auto &key : required_keys) { - if (goniometer_data.find(key) == goniometer_data.end()) { - throw std::invalid_argument( - "Key " + key + " is missing from the input goniometer JSON"); - } - } - std::vector axes; - for (json::iterator it=goniometer_data["axes"].begin();it != goniometer_data["axes"].end();it++){ - Vector3d axis; - axis[0] = (*it)[0]; - axis[1] = (*it)[1]; - axis[2] = (*it)[2]; - axes.push_back(axis); - } - axes_ = axes; - std::vector angles; - for (json::iterator it=goniometer_data["angles"].begin();it != goniometer_data["angles"].end();it++){ - angles.push_back(*it); - } - angles_ = angles; - std::vector names; - for (json::iterator it=goniometer_data["names"].begin();it != goniometer_data["names"].end();it++){ - names.push_back(*it); - } - names_ = names; - scan_axis_ = goniometer_data["scan_axis"]; - init(); -} - -json Goniometer::to_json() const { - json goniometer_data; - goniometer_data["axes"] = axes_; - goniometer_data["angles"] = angles_; - goniometer_data["names"] = names_; - goniometer_data["scan_axis"] = scan_axis_; - return goniometer_data; -} - - -#endif //DX2_MODEL_GONIO_H \ No newline at end of file diff --git a/dx2_test_models/scan.h b/dx2_test_models/scan.h deleted file mode 100644 index 9cba89a..0000000 --- a/dx2_test_models/scan.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef DX2_MODEL_SCAN_H -#define DX2_MODEL_SCAN_H -#include -using json = nlohmann::json; - -class Scan { -// A class to represent the physical measurement, consisting of the number of images, -// starting oscillation and a constant oscillation width between sequential images. -// This class MUST NOT be modified during processing or used to track additional metadata. -public: - Scan()=default; - Scan(std::array image_range, std::array oscillation); - Scan(json scan_data); - std::array get_image_range() const; - std::array get_oscillation() const; - json to_json() const; - -protected: - std::array image_range_{{0,0}}; - int num_images_{0}; - double oscillation_width_{0.0}; - double oscillation_start_{0.0}; -}; - -Scan::Scan(std::array image_range, std::array oscillation) - : image_range_{image_range}{ - num_images_ = image_range_[1] - image_range_[0]+1; - oscillation_start_ = oscillation[0]; - oscillation_width_ = oscillation[1]; -} - -Scan::Scan(json scan_data){ - // minimal required keys are image range and ["properties"]:"oscillation" - std::vector required_keys = { - "image_range", "properties" - }; - for (const auto &key : required_keys) { - if (scan_data.find(key) == scan_data.end()) { - throw std::invalid_argument( - "Key " + key + " is missing from the input scan JSON"); - } - } - image_range_ = scan_data["image_range"]; - num_images_ = image_range_[1] - image_range_[0]+1; - if (scan_data["properties"].find("oscillation") == scan_data["properties"].end()){ - throw std::invalid_argument( - "Key oscillation is missing from the input scan['properties'] JSON"); - } - std::vector oscillation; - json osc_array = scan_data["properties"]["oscillation"]; - // Just read the first two oscillation values as that is all that is needed - if (osc_array.size() < 2){ - throw std::invalid_argument( - "scan['properties']['oscillation'] has <2 values in the scan JSON"); - } - for (json::iterator it = osc_array.begin(); it != osc_array.begin()+2; ++it) { - oscillation.push_back(*it); - } - oscillation_start_ = oscillation[0]; - oscillation_width_ = oscillation[1] - oscillation[0]; -} - -json Scan::to_json() const { - json scan_data; - scan_data["image_range"] = image_range_; - scan_data["batch_offset"] = 0; // We MUST NOT use batch offsets in dx2, written out here for compatibility - std::vector oscillation_array(num_images_); - for (int i=0; i Scan::get_image_range() const { - return image_range_; -} - -std::array Scan::get_oscillation() const { - return {oscillation_start_, oscillation_width_}; -} - -#endif //DX2_MODEL_SCAN_H \ No newline at end of file diff --git a/dx2_test_models/simple_models.cc b/dx2_test_models/simple_models.cc deleted file mode 100644 index adacbd8..0000000 --- a/dx2_test_models/simple_models.cc +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef INDEXER_SIMPLE_MODELS -#define INDEXER_SIMPLE_MODELS -#include -#include - -using Eigen::Matrix3d; -using Eigen::Vector3d; - -double attenuation_length(double mu, double t0, - Vector3d s1, - Vector3d fast, - Vector3d slow, - Vector3d origin) { - Vector3d normal = fast.cross(slow); - double distance = origin.dot(normal); - if (distance < 0) { - normal = -normal; - } - double cos_t = s1.dot(normal); - //DXTBX_ASSERT(mu > 0 && cos_t > 0); - return (1.0 / mu) - (t0 / cos_t + 1.0 / mu) * exp(-mu * t0 / cos_t); -} - -class SimpleDetector { -public: - Matrix3d d_matrix; - double pixel_size; // in mm - double mu; - double t0; - bool parallax_correction; - std::array px_to_mm(double x, double y) const; - - SimpleDetector(Matrix3d d_matrix, double pixel_size, double mu=0.0, double t0=0.0, bool parallax_correction=false) { - this->d_matrix = d_matrix; - this->pixel_size = pixel_size; - this->mu = mu; - this->t0 = t0; - this->parallax_correction = parallax_correction; - } -}; - -std::array SimpleDetector::px_to_mm(double x, double y) const{ - double x1 = x*pixel_size; - double x2 = y*pixel_size; - if (!parallax_correction){ - return std::array{x1,x2}; - } - Vector3d fast = d_matrix.col(0); - Vector3d slow = d_matrix.col(1); - Vector3d origin = d_matrix.col(2); - Vector3d s1 = origin + x1*fast + x2*slow; - s1.normalize(); - double o = attenuation_length(mu, t0, s1, fast, slow, origin); - double c1 = x1 - (s1.dot(fast))*o; - double c2 = x2 - (s1.dot(slow))*o; - return std::array{c1,c2}; -} - - -#endif // INDEXER_SIMPLE_MODELS \ No newline at end of file diff --git a/indexer/CMakeLists.txt b/indexer/CMakeLists.txt index 1e0c813..fbcb8b0 100644 --- a/indexer/CMakeLists.txt +++ b/indexer/CMakeLists.txt @@ -21,6 +21,7 @@ target_link_libraries(indexer PRIVATE fmt h5read + dx2 Eigen3::Eigen argparse standalone diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 47b506d..cc4b321 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -14,11 +14,11 @@ #include "sites_to_vecs.cc" #include "fft3d.cc" #include -#include "simple_models.cc" -#include "beam.h" -#include "scan.h" -#include "goniometer.h" #include +#include +#include +#include +#include using Eigen::Vector3d; using Eigen::Matrix3d; diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index 05bf234..f097f20 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -1,10 +1,10 @@ #include #include #include -#include "simple_models.cc" -#include "beam.h" -#include "scan.h" -#include "goniometer.h" +#include +#include +#include +#include using Eigen::Matrix3d; using Eigen::Vector3d; From c3404ccd790a4576fb489e41f1dc317f412a2f90 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:41:02 +0000 Subject: [PATCH 14/72] Add command line parsing, remove reader, update to dx2 detector --- h5read/src/h5read_processed.cc | 5 ++ indexer/indexer.cc | 134 +++++++-------------------------- indexer/xyz_to_rlp.cc | 2 +- 3 files changed, 35 insertions(+), 106 deletions(-) diff --git a/h5read/src/h5read_processed.cc b/h5read/src/h5read_processed.cc index f828ea7..e8402b1 100644 --- a/h5read/src/h5read_processed.cc +++ b/h5read/src/h5read_processed.cc @@ -11,6 +11,11 @@ using namespace std; std::vector read_xyzobs_data(string filename, string array_name){ auto start_time = std::chrono::high_resolution_clock::now(); hid_t file = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); + if (file < 0){ + std::cout << "Error: Unable to open " << filename.c_str() << " as a hdf5 reflection table" < #include #include @@ -19,70 +18,47 @@ #include #include #include +#include +#include +#include using Eigen::Vector3d; using Eigen::Matrix3d; using json = nlohmann::json; + int main(int argc, char **argv) { - + auto t1 = std::chrono::system_clock::now(); auto parser = CUDAArgumentParser(); - parser.add_h5read_arguments(); //will use h5 file to get metadata - parser.add_argument("--images") - .help("Maximum number of images to process") - .metavar("NUM") - .scan<'u', uint32_t>(); + parser.add_argument("-e", "--expt") + .help("Path to the DIALS expt file"); + parser.add_argument("-r", "--refl") + .help("Path to the h5 reflection table file containing spotfinding results"); auto args = parser.parse_args(argc, argv); - - std::unique_ptr reader_ptr; - - // Wait for read-readiness - - reader_ptr = args.file.empty() ? std::make_unique() - : std::make_unique(args.file); - // Bind this as a reference - H5Read &reader = *reader_ptr; - - auto reader_mutex = std::mutex{}; - - uint32_t num_images = parser.is_used("images") ? parser.get("images") - : reader.get_number_of_images(); - - - // first get the Beam properties; wavelength and s0 vector - auto wavelength_opt = reader.get_wavelength(); - if (!wavelength_opt) { - printf( - "Error: No wavelength provided. Please pass wavelength using: " - "--wavelength\n"); + if (!parser.is_used("--expt")){ + fmt::print("Error: must specify experiment list file with --expt\n"); std::exit(1); } - float wavelength_f = wavelength_opt.value(); - printf("INDEXER: Got wavelength from file: %f Å\n", wavelength_f); - auto distance_opt = reader.get_detector_distance(); - if (!distance_opt) { - printf("Error: No detector distance found in file."); + if (!parser.is_used("--refl")){ + fmt::print("Error: must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); std::exit(1); } - float distance_f = distance_opt.value()*1000; - printf("INDEXER: Got detector distance from file: %f mm\n", distance_f); - auto pixel_size_f = reader.get_pixel_size(); - float pixel_size_x = pixel_size_f.value()[0]*1000; - printf("INDEXER: Got detector pixel_size from file: %f mm\n", pixel_size_x); - - std::array module_offsets = reader.get_module_offsets(); - double origin_x = module_offsets[0] * 1000; - double origin_y = module_offsets[1] * 1000; - printf("INDEXER: Got detector origin x from file: %f mm\n", origin_x); - printf("INDEXER: Got detector origin y from file: %f mm\n", origin_y); + std::string imported_expt = parser.get("--expt"); + std::string filename = parser.get("--refl"); - - std::string imported_expt = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt"; + //std::string imported_expt = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt"; std::ifstream f(imported_expt); - json elist_json_obj = json::parse(f); + json elist_json_obj; + try { + elist_json_obj = json::parse(f); + } + catch(json::parse_error& ex){ + std::cerr << "Error: Unable to read " << imported_expt.c_str() << "; json parse error at byte " << ex.byte << std::endl; + std::exit(1); + } json beam_data = elist_json_obj["beam"][0]; - Beam beam(beam_data); + MonoXrayBeam beam(beam_data); /*json beam_out = beam.to_json(); std::ofstream file("test_beam.json"); file << beam_out.dump(4);*/ @@ -98,66 +74,14 @@ int main(int argc, char **argv) { std::ofstream goniofile("test_gonio.json"); goniofile << gonio_out.dump(4);*/ + json detector_data = elist_json_obj["detector"][0]; + SimpleDetector detector(detector_data); + //TODO - // Get metadata from file // implement max cell/d_min estimation. - will need annlib if want same result as dials. - //FIXME don't assume s0 vector get from file - //double wavelength = 0.9762535307519975; - - // now get detector properties - // need fast, slow, norm and origin, plus pixel size - - - std::array fast_axis {1.0, 0.0, 0.0}; //FIXME get through reader - but const for I03 for now - std::array slow_axis {0.0, -1.0, 0.0}; //FIXME get through reader - // ^ change basis from nexus to iucr/imgcif convention (invert x and z) - - //std::array normal {0.0, 0.0, 0.0}; // fast_axis cross slow_axis - //std::array origin {-75.61, 79.95, -150.0}; // FIXME - //Vector3d origin {-75.61, 79.95, -150.0}; // FIXME - //Vector3d origin {-153.60993960268158, 162.44624026693077, -200.45297988785603}; - Vector3d origin {-1.0*origin_x, origin_y, -1.0*distance_f}; - - Matrix3d d_matrix{{fast_axis[0], slow_axis[0], origin[0]},{ - fast_axis[1], slow_axis[1], origin[1]}, - {fast_axis[2], slow_axis[2], origin[2]}}; - - //FIXME remove assumption of pixel sizes being same in analysis code. - //double pixel_size_x = pixel_size[0];//0.075; - - // Thickness not currently written to nxs file? Then need to calc mu from thickness. - // Required to get equivalent results to dials. Const for I03 Eiger - double mu = 3.9220780876; - double t0 = 0.45; - - // now get scan properties e.g. - int image_range_start = 1; // a 'dials' thing. - double osc_start = reader.get_oscillation()[0]; - double osc_width = reader.get_oscillation()[1]; - printf("INDEXER: Got osc start from file: %f\n", osc_start); - printf("INDEXER: Got osc width from file: %f\n", osc_width); - //double osc_start = 0.0; - //double osc_width = 0.10002778549596769; - - // finally gonio properties e.g. - // Const for I03 Eiger - Matrix3d fixed_rotation{{1,0,0},{0,1,0},{0,0,1}}; - Vector3d rotation_axis {1.0,0.0,0.0}; - Matrix3d setting_rotation {{1,0,0},{0,1,0},{0,0,1}}; - auto t1 = std::chrono::system_clock::now(); - // Make the dxtbx-like models - SimpleDetector detector(d_matrix, pixel_size_x, mu, t0, true); - //SimpleScan scan(image_range_start, osc_start, osc_width); - //SimpleGonio gonio(fixed_rotation, rotation_axis, setting_rotation); - - /*std::array image_range{{1,3600}}; - std::array oscillation{{osc_start, osc_width}}; - Scan scan(image_range, oscillation);*/ - //Beam beam(wavelength_f); - // get processed reflection data from spotfinding - std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl"; + //std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl"; std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; std::vector data = read_xyzobs_data(filename, array_name); diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index f097f20..d6174d8 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -12,7 +12,7 @@ using Eigen::Vector3d; std::vector xyz_to_rlp( const std::vector &xyzobs_px, const SimpleDetector &detector, - const Beam &beam, + const MonochromaticBeam &beam, const Scan &scan, const Goniometer &gonio) { auto start = std::chrono::system_clock::now(); From 39b80be6f178124f8f57ae099395c660e978d657 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 8 Nov 2024 09:34:55 +0000 Subject: [PATCH 15/72] Add pocketfft as dependency --- include/pocketfft_hdronly.h | 3578 ----------------------------------- indexer/CMakeLists.txt | 13 + 2 files changed, 13 insertions(+), 3578 deletions(-) delete mode 100644 include/pocketfft_hdronly.h diff --git a/include/pocketfft_hdronly.h b/include/pocketfft_hdronly.h deleted file mode 100644 index d75ada6..0000000 --- a/include/pocketfft_hdronly.h +++ /dev/null @@ -1,3578 +0,0 @@ -/* -This file is part of pocketfft. - -Copyright (C) 2010-2021 Max-Planck-Society -Copyright (C) 2019-2020 Peter Bell - -For the odd-sized DCT-IV transforms: - Copyright (C) 2003, 2007-14 Matteo Frigo - Copyright (C) 2003, 2007-14 Massachusetts Institute of Technology - -Authors: Martin Reinecke, Peter Bell - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. -* Neither the name of the copyright holder nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef POCKETFFT_HDRONLY_H -#define POCKETFFT_HDRONLY_H - -#ifndef __cplusplus -#error This file is C++ and requires a C++ compiler. -#endif - -#if !(__cplusplus >= 201103L || _MSVC_LANG+0L >= 201103L) -#error This file requires at least C++11 support. -#endif - -#ifndef POCKETFFT_CACHE_SIZE -#define POCKETFFT_CACHE_SIZE 0 -#endif - -#include -#include -#include -#include -#include -#include -#include -#if POCKETFFT_CACHE_SIZE!=0 -#include -#include -#endif - -#ifndef POCKETFFT_NO_MULTITHREADING -#include -#include -#include -#include -#include -#include -#include - -#ifdef POCKETFFT_PTHREADS -# include -#endif -#endif - -#if defined(__GNUC__) -#define POCKETFFT_NOINLINE __attribute__((noinline)) -#define POCKETFFT_RESTRICT __restrict__ -#elif defined(_MSC_VER) -#define POCKETFFT_NOINLINE __declspec(noinline) -#define POCKETFFT_RESTRICT __restrict -#else -#define POCKETFFT_NOINLINE -#define POCKETFFT_RESTRICT -#endif - -namespace pocketfft { - -namespace detail { -using std::size_t; -using std::ptrdiff_t; - -// Always use std:: for functions -template T cos(T) = delete; -template T sin(T) = delete; -template T sqrt(T) = delete; - -using shape_t = std::vector; -using stride_t = std::vector; - -constexpr bool FORWARD = true, - BACKWARD = false; - -// only enable vector support for gcc>=5.0 and clang>=5.0 -#ifndef POCKETFFT_NO_VECTORS -#define POCKETFFT_NO_VECTORS -#if defined(__INTEL_COMPILER) -// do nothing. This is necessary because this compiler also sets __GNUC__. -#elif defined(__clang__) -// AppleClang has their own version numbering -#ifdef __apple_build_version__ -# if (__clang_major__ > 9) || (__clang_major__ == 9 && __clang_minor__ >= 1) -# undef POCKETFFT_NO_VECTORS -# endif -#elif __clang_major__ >= 5 -# undef POCKETFFT_NO_VECTORS -#endif -#elif defined(__GNUC__) -#if __GNUC__>=5 -#undef POCKETFFT_NO_VECTORS -#endif -#endif -#endif - -template struct VLEN { static constexpr size_t val=1; }; - -#ifndef POCKETFFT_NO_VECTORS -#if (defined(__AVX512F__)) -template<> struct VLEN { static constexpr size_t val=16; }; -template<> struct VLEN { static constexpr size_t val=8; }; -#elif (defined(__AVX__)) -template<> struct VLEN { static constexpr size_t val=8; }; -template<> struct VLEN { static constexpr size_t val=4; }; -#elif (defined(__SSE2__)) -template<> struct VLEN { static constexpr size_t val=4; }; -template<> struct VLEN { static constexpr size_t val=2; }; -#elif (defined(__VSX__)) -template<> struct VLEN { static constexpr size_t val=4; }; -template<> struct VLEN { static constexpr size_t val=2; }; -#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) -template<> struct VLEN { static constexpr size_t val=4; }; -template<> struct VLEN { static constexpr size_t val=2; }; -#else -#define POCKETFFT_NO_VECTORS -#endif -#endif - -#if __cplusplus >= 201703L -inline void *aligned_alloc(size_t align, size_t size) - { - // aligned_alloc() requires that the requested size is a multiple of "align" - void *ptr = ::aligned_alloc(align,(size+align-1)&(~(align-1))); - if (!ptr) throw std::bad_alloc(); - return ptr; - } -inline void aligned_dealloc(void *ptr) - { free(ptr); } -#else // portable emulation -inline void *aligned_alloc(size_t align, size_t size) - { - align = std::max(align, alignof(max_align_t)); - void *ptr = malloc(size+align); - if (!ptr) throw std::bad_alloc(); - void *res = reinterpret_cast - ((reinterpret_cast(ptr) & ~(uintptr_t(align-1))) + uintptr_t(align)); - (reinterpret_cast(res))[-1] = ptr; - return res; - } -inline void aligned_dealloc(void *ptr) - { if (ptr) free((reinterpret_cast(ptr))[-1]); } -#endif - -template class arr - { - private: - T *p; - size_t sz; - -#if defined(POCKETFFT_NO_VECTORS) - static T *ralloc(size_t num) - { - if (num==0) return nullptr; - void *res = malloc(num*sizeof(T)); - if (!res) throw std::bad_alloc(); - return reinterpret_cast(res); - } - static void dealloc(T *ptr) - { free(ptr); } -#else - static T *ralloc(size_t num) - { - if (num==0) return nullptr; - void *ptr = aligned_alloc(64, num*sizeof(T)); - return static_cast(ptr); - } - static void dealloc(T *ptr) - { aligned_dealloc(ptr); } -#endif - - public: - arr() : p(0), sz(0) {} - arr(size_t n) : p(ralloc(n)), sz(n) {} - arr(arr &&other) - : p(other.p), sz(other.sz) - { other.p=nullptr; other.sz=0; } - ~arr() { dealloc(p); } - - void resize(size_t n) - { - if (n==sz) return; - dealloc(p); - p = ralloc(n); - sz = n; - } - - T &operator[](size_t idx) { return p[idx]; } - const T &operator[](size_t idx) const { return p[idx]; } - - T *data() { return p; } - const T *data() const { return p; } - - size_t size() const { return sz; } - }; - -template struct cmplx { - T r, i; - cmplx() {} - cmplx(T r_, T i_) : r(r_), i(i_) {} - void Set(T r_, T i_) { r=r_; i=i_; } - void Set(T r_) { r=r_; i=T(0); } - cmplx &operator+= (const cmplx &other) - { r+=other.r; i+=other.i; return *this; } - templatecmplx &operator*= (T2 other) - { r*=other; i*=other; return *this; } - templatecmplx &operator*= (const cmplx &other) - { - T tmp = r*other.r - i*other.i; - i = r*other.i + i*other.r; - r = tmp; - return *this; - } - templatecmplx &operator+= (const cmplx &other) - { r+=other.r; i+=other.i; return *this; } - templatecmplx &operator-= (const cmplx &other) - { r-=other.r; i-=other.i; return *this; } - template auto operator* (const T2 &other) const - -> cmplx - { return {r*other, i*other}; } - template auto operator+ (const cmplx &other) const - -> cmplx - { return {r+other.r, i+other.i}; } - template auto operator- (const cmplx &other) const - -> cmplx - { return {r-other.r, i-other.i}; } - template auto operator* (const cmplx &other) const - -> cmplx - { return {r*other.r-i*other.i, r*other.i + i*other.r}; } - template auto special_mul (const cmplx &other) const - -> cmplx - { - using Tres = cmplx; - return fwd ? Tres(r*other.r+i*other.i, i*other.r-r*other.i) - : Tres(r*other.r-i*other.i, r*other.i+i*other.r); - } -}; -template inline void PM(T &a, T &b, T c, T d) - { a=c+d; b=c-d; } -template inline void PMINPLACE(T &a, T &b) - { T t = a; a+=b; b=t-b; } -template inline void MPINPLACE(T &a, T &b) - { T t = a; a-=b; b=t+b; } -template cmplx conj(const cmplx &a) - { return {a.r, -a.i}; } -template void special_mul (const cmplx &v1, const cmplx &v2, cmplx &res) - { - res = fwd ? cmplx(v1.r*v2.r+v1.i*v2.i, v1.i*v2.r-v1.r*v2.i) - : cmplx(v1.r*v2.r-v1.i*v2.i, v1.r*v2.i+v1.i*v2.r); - } - -template void ROT90(cmplx &a) - { auto tmp_=a.r; a.r=-a.i; a.i=tmp_; } -template void ROTX90(cmplx &a) - { auto tmp_= fwd ? -a.r : a.r; a.r = fwd ? a.i : -a.i; a.i=tmp_; } - -// -// twiddle factor section -// -template class sincos_2pibyn - { - private: - using Thigh = typename std::conditional<(sizeof(T)>sizeof(double)), T, double>::type; - size_t N, mask, shift; - arr> v1, v2; - - static cmplx calc(size_t x, size_t n, Thigh ang) - { - x<<=3; - if (x<4*n) // first half - { - if (x<2*n) // first quadrant - { - if (x(std::cos(Thigh(x)*ang), std::sin(Thigh(x)*ang)); - return cmplx(std::sin(Thigh(2*n-x)*ang), std::cos(Thigh(2*n-x)*ang)); - } - else // second quadrant - { - x-=2*n; - if (x(-std::sin(Thigh(x)*ang), std::cos(Thigh(x)*ang)); - return cmplx(-std::cos(Thigh(2*n-x)*ang), std::sin(Thigh(2*n-x)*ang)); - } - } - else - { - x=8*n-x; - if (x<2*n) // third quadrant - { - if (x(std::cos(Thigh(x)*ang), -std::sin(Thigh(x)*ang)); - return cmplx(std::sin(Thigh(2*n-x)*ang), -std::cos(Thigh(2*n-x)*ang)); - } - else // fourth quadrant - { - x-=2*n; - if (x(-std::sin(Thigh(x)*ang), -std::cos(Thigh(x)*ang)); - return cmplx(-std::cos(Thigh(2*n-x)*ang), -std::sin(Thigh(2*n-x)*ang)); - } - } - } - - public: - POCKETFFT_NOINLINE sincos_2pibyn(size_t n) - : N(n) - { - constexpr auto pi = 3.141592653589793238462643383279502884197L; - Thigh ang = Thigh(0.25L*pi/n); - size_t nval = (n+2)/2; - shift = 1; - while((size_t(1)< operator[](size_t idx) const - { - if (2*idx<=N) - { - auto x1=v1[idx&mask], x2=v2[idx>>shift]; - return cmplx(T(x1.r*x2.r-x1.i*x2.i), T(x1.r*x2.i+x1.i*x2.r)); - } - idx = N-idx; - auto x1=v1[idx&mask], x2=v2[idx>>shift]; - return cmplx(T(x1.r*x2.r-x1.i*x2.i), -T(x1.r*x2.i+x1.i*x2.r)); - } - }; - -struct util // hack to avoid duplicate symbols - { - static POCKETFFT_NOINLINE size_t largest_prime_factor (size_t n) - { - size_t res=1; - while ((n&1)==0) - { res=2; n>>=1; } - for (size_t x=3; x*x<=n; x+=2) - while ((n%x)==0) - { res=x; n/=x; } - if (n>1) res=n; - return res; - } - - static POCKETFFT_NOINLINE double cost_guess (size_t n) - { - constexpr double lfp=1.1; // penalty for non-hardcoded larger factors - size_t ni=n; - double result=0.; - while ((n&1)==0) - { result+=2; n>>=1; } - for (size_t x=3; x*x<=n; x+=2) - while ((n%x)==0) - { - result+= (x<=5) ? double(x) : lfp*double(x); // penalize larger prime factors - n/=x; - } - if (n>1) result+=(n<=5) ? double(n) : lfp*double(n); - return result*double(ni); - } - - /* returns the smallest composite of 2, 3, 5, 7 and 11 which is >= n */ - static POCKETFFT_NOINLINE size_t good_size_cmplx(size_t n) - { - if (n<=12) return n; - - size_t bestfac=2*n; - for (size_t f11=1; f11n) - { - if (x>=1; - } - else - return n; - } - } - return bestfac; - } - - /* returns the smallest composite of 2, 3, 5 which is >= n */ - static POCKETFFT_NOINLINE size_t good_size_real(size_t n) - { - if (n<=6) return n; - - size_t bestfac=2*n; - for (size_t f5=1; f5n) - { - if (x>=1; - } - else - return n; - } - } - return bestfac; - } - - static size_t prod(const shape_t &shape) - { - size_t res=1; - for (auto sz: shape) - res*=sz; - return res; - } - - static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, - const stride_t &stride_in, const stride_t &stride_out, bool inplace) - { - auto ndim = shape.size(); - if (ndim<1) throw std::runtime_error("ndim must be >= 1"); - if ((stride_in.size()!=ndim) || (stride_out.size()!=ndim)) - throw std::runtime_error("stride dimension mismatch"); - if (inplace && (stride_in!=stride_out)) - throw std::runtime_error("stride mismatch"); - } - - static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, - const stride_t &stride_in, const stride_t &stride_out, bool inplace, - const shape_t &axes) - { - sanity_check(shape, stride_in, stride_out, inplace); - auto ndim = shape.size(); - shape_t tmp(ndim,0); - for (auto ax : axes) - { - if (ax>=ndim) throw std::invalid_argument("bad axis number"); - if (++tmp[ax]>1) throw std::invalid_argument("axis specified repeatedly"); - } - } - - static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, - const stride_t &stride_in, const stride_t &stride_out, bool inplace, - size_t axis) - { - sanity_check(shape, stride_in, stride_out, inplace); - if (axis>=shape.size()) throw std::invalid_argument("bad axis number"); - } - -#ifdef POCKETFFT_NO_MULTITHREADING - static size_t thread_count (size_t /*nthreads*/, const shape_t &/*shape*/, - size_t /*axis*/, size_t /*vlen*/) - { return 1; } -#else - static size_t thread_count (size_t nthreads, const shape_t &shape, - size_t axis, size_t vlen) - { - if (nthreads==1) return 1; - size_t size = prod(shape); - size_t parallel = size / (shape[axis] * vlen); - if (shape[axis] < 1000) - parallel /= 4; - size_t max_threads = nthreads == 0 ? - std::thread::hardware_concurrency() : nthreads; - return std::max(size_t(1), std::min(parallel, max_threads)); - } -#endif - }; - -namespace threading { - -#ifdef POCKETFFT_NO_MULTITHREADING - -constexpr inline size_t thread_id() { return 0; } -constexpr inline size_t num_threads() { return 1; } - -template -void thread_map(size_t /* nthreads */, Func f) - { f(); } - -#else - -inline size_t &thread_id() - { - static thread_local size_t thread_id_=0; - return thread_id_; - } -inline size_t &num_threads() - { - static thread_local size_t num_threads_=1; - return num_threads_; - } -static const size_t max_threads = std::max(1u, std::thread::hardware_concurrency()); - -class latch - { - std::atomic num_left_; - std::mutex mut_; - std::condition_variable completed_; - using lock_t = std::unique_lock; - - public: - latch(size_t n): num_left_(n) {} - - void count_down() - { - lock_t lock(mut_); - if (--num_left_) - return; - completed_.notify_all(); - } - - void wait() - { - lock_t lock(mut_); - completed_.wait(lock, [this]{ return is_ready(); }); - } - bool is_ready() { return num_left_ == 0; } - }; - -template class concurrent_queue - { - std::queue q_; - std::mutex mut_; - std::atomic size_; - using lock_t = std::lock_guard; - - public: - - void push(T val) - { - lock_t lock(mut_); - ++size_; - q_.push(std::move(val)); - } - - bool try_pop(T &val) - { - if (size_ == 0) return false; - lock_t lock(mut_); - // Queue might have been emptied while we acquired the lock - if (q_.empty()) return false; - - val = std::move(q_.front()); - --size_; - q_.pop(); - return true; - } - - bool empty() const { return size_==0; } - }; - -// C++ allocator with support for over-aligned types -template struct aligned_allocator - { - using value_type = T; - template - aligned_allocator(const aligned_allocator&) {} - aligned_allocator() = default; - - T *allocate(size_t n) - { - void* mem = aligned_alloc(alignof(T), n*sizeof(T)); - return static_cast(mem); - } - - void deallocate(T *p, size_t /*n*/) - { aligned_dealloc(p); } - }; - -class thread_pool - { - // A reasonable guess, probably close enough for most hardware - static constexpr size_t cache_line_size = 64; - struct alignas(cache_line_size) worker - { - std::thread thread; - std::condition_variable work_ready; - std::mutex mut; - std::atomic_flag busy_flag = ATOMIC_FLAG_INIT; - std::function work; - - void worker_main( - std::atomic &shutdown_flag, - std::atomic &unscheduled_tasks, - concurrent_queue> &overflow_work) - { - using lock_t = std::unique_lock; - bool expect_work = true; - while (!shutdown_flag || expect_work) - { - std::function local_work; - if (expect_work || unscheduled_tasks == 0) - { - lock_t lock(mut); - // Wait until there is work to be executed - work_ready.wait(lock, [&]{ return (work || shutdown_flag); }); - local_work.swap(work); - expect_work = false; - } - - bool marked_busy = false; - if (local_work) - { - marked_busy = true; - local_work(); - } - - if (!overflow_work.empty()) - { - if (!marked_busy && busy_flag.test_and_set()) - { - expect_work = true; - continue; - } - marked_busy = true; - - while (overflow_work.try_pop(local_work)) - { - --unscheduled_tasks; - local_work(); - } - } - - if (marked_busy) busy_flag.clear(); - } - } - }; - - concurrent_queue> overflow_work_; - std::mutex mut_; - std::vector> workers_; - std::atomic shutdown_; - std::atomic unscheduled_tasks_; - using lock_t = std::lock_guard; - - void create_threads() - { - lock_t lock(mut_); - size_t nthreads=workers_.size(); - for (size_t i=0; ibusy_flag.clear(); - worker->work = nullptr; - worker->thread = std::thread([worker, this] - { - worker->worker_main(shutdown_, unscheduled_tasks_, overflow_work_); - }); - } - catch (...) - { - shutdown_locked(); - throw; - } - } - } - - void shutdown_locked() - { - shutdown_ = true; - for (auto &worker : workers_) - worker.work_ready.notify_all(); - - for (auto &worker : workers_) - if (worker.thread.joinable()) - worker.thread.join(); - } - - public: - explicit thread_pool(size_t nthreads): - workers_(nthreads) - { create_threads(); } - - thread_pool(): thread_pool(max_threads) {} - - ~thread_pool() { shutdown(); } - - void submit(std::function work) - { - lock_t lock(mut_); - if (shutdown_) - throw std::runtime_error("Work item submitted after shutdown"); - - ++unscheduled_tasks_; - - // First check for any idle workers and wake those - for (auto &worker : workers_) - if (!worker.busy_flag.test_and_set()) - { - --unscheduled_tasks_; - { - lock_t lock(worker.mut); - worker.work = std::move(work); - } - worker.work_ready.notify_one(); - return; - } - - // If no workers were idle, push onto the overflow queue for later - overflow_work_.push(std::move(work)); - } - - void shutdown() - { - lock_t lock(mut_); - shutdown_locked(); - } - - void restart() - { - shutdown_ = false; - create_threads(); - } - }; - -inline thread_pool & get_pool() - { - static thread_pool pool; -#ifdef POCKETFFT_PTHREADS - static std::once_flag f; - std::call_once(f, - []{ - pthread_atfork( - +[]{ get_pool().shutdown(); }, // prepare - +[]{ get_pool().restart(); }, // parent - +[]{ get_pool().restart(); } // child - ); - }); -#endif - - return pool; - } - -/** Map a function f over nthreads */ -template -void thread_map(size_t nthreads, Func f) - { - if (nthreads == 0) - nthreads = max_threads; - - if (nthreads == 1) - { f(); return; } - - auto & pool = get_pool(); - latch counter(nthreads); - std::exception_ptr ex; - std::mutex ex_mut; - for (size_t i=0; i lock(ex_mut); - ex = std::current_exception(); - } - counter.count_down(); - }); - } - counter.wait(); - if (ex) - std::rethrow_exception(ex); - } - -#endif - -} - -// -// complex FFTPACK transforms -// - -template class cfftp - { - private: - struct fctdata - { - size_t fct; - cmplx *tw, *tws; - }; - - size_t length; - arr> mem; - std::vector fact; - - void add_factor(size_t factor) - { fact.push_back({factor, nullptr, nullptr}); } - -template void pass2 (size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const cmplx * POCKETFFT_RESTRICT wa) const - { - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+2*c)]; }; - auto WA = [wa, ido](size_t x, size_t i) - { return wa[i-1+x*(ido-1)]; }; - - if (ido==1) - for (size_t k=0; k(CC(i,0,k)-CC(i,1,k),WA(0,i),CH(i,k,1)); - } - } - } - -#define POCKETFFT_PREP3(idx) \ - T t0 = CC(idx,0,k), t1, t2; \ - PM (t1,t2,CC(idx,1,k),CC(idx,2,k)); \ - CH(idx,k,0)=t0+t1; -#define POCKETFFT_PARTSTEP3a(u1,u2,twr,twi) \ - { \ - T ca=t0+t1*twr; \ - T cb{-t2.i*twi, t2.r*twi}; \ - PM(CH(0,k,u1),CH(0,k,u2),ca,cb) ;\ - } -#define POCKETFFT_PARTSTEP3b(u1,u2,twr,twi) \ - { \ - T ca=t0+t1*twr; \ - T cb{-t2.i*twi, t2.r*twi}; \ - special_mul(ca+cb,WA(u1-1,i),CH(i,k,u1)); \ - special_mul(ca-cb,WA(u2-1,i),CH(i,k,u2)); \ - } -template void pass3 (size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const cmplx * POCKETFFT_RESTRICT wa) const - { - constexpr T0 tw1r=-0.5, - tw1i= (fwd ? -1: 1) * T0(0.8660254037844386467637231707529362L); - - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+3*c)]; }; - auto WA = [wa, ido](size_t x, size_t i) - { return wa[i-1+x*(ido-1)]; }; - - if (ido==1) - for (size_t k=0; k void pass4 (size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const cmplx * POCKETFFT_RESTRICT wa) const - { - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+4*c)]; }; - auto WA = [wa, ido](size_t x, size_t i) - { return wa[i-1+x*(ido-1)]; }; - - if (ido==1) - for (size_t k=0; k(t4); - PM(CH(0,k,0),CH(0,k,2),t2,t3); - PM(CH(0,k,1),CH(0,k,3),t1,t4); - } - else - for (size_t k=0; k(t4); - PM(CH(0,k,0),CH(0,k,2),t2,t3); - PM(CH(0,k,1),CH(0,k,3),t1,t4); - } - for (size_t i=1; i(t4); - CH(i,k,0) = t2+t3; - special_mul(t1+t4,WA(0,i),CH(i,k,1)); - special_mul(t2-t3,WA(1,i),CH(i,k,2)); - special_mul(t1-t4,WA(2,i),CH(i,k,3)); - } - } - } - -#define POCKETFFT_PREP5(idx) \ - T t0 = CC(idx,0,k), t1, t2, t3, t4; \ - PM (t1,t4,CC(idx,1,k),CC(idx,4,k)); \ - PM (t2,t3,CC(idx,2,k),CC(idx,3,k)); \ - CH(idx,k,0).r=t0.r+t1.r+t2.r; \ - CH(idx,k,0).i=t0.i+t1.i+t2.i; - -#define POCKETFFT_PARTSTEP5a(u1,u2,twar,twbr,twai,twbi) \ - { \ - T ca,cb; \ - ca.r=t0.r+twar*t1.r+twbr*t2.r; \ - ca.i=t0.i+twar*t1.i+twbr*t2.i; \ - cb.i=twai*t4.r twbi*t3.r; \ - cb.r=-(twai*t4.i twbi*t3.i); \ - PM(CH(0,k,u1),CH(0,k,u2),ca,cb); \ - } - -#define POCKETFFT_PARTSTEP5b(u1,u2,twar,twbr,twai,twbi) \ - { \ - T ca,cb,da,db; \ - ca.r=t0.r+twar*t1.r+twbr*t2.r; \ - ca.i=t0.i+twar*t1.i+twbr*t2.i; \ - cb.i=twai*t4.r twbi*t3.r; \ - cb.r=-(twai*t4.i twbi*t3.i); \ - special_mul(ca+cb,WA(u1-1,i),CH(i,k,u1)); \ - special_mul(ca-cb,WA(u2-1,i),CH(i,k,u2)); \ - } -template void pass5 (size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const cmplx * POCKETFFT_RESTRICT wa) const - { - constexpr T0 tw1r= T0(0.3090169943749474241022934171828191L), - tw1i= (fwd ? -1: 1) * T0(0.9510565162951535721164393333793821L), - tw2r= T0(-0.8090169943749474241022934171828191L), - tw2i= (fwd ? -1: 1) * T0(0.5877852522924731291687059546390728L); - - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+5*c)]; }; - auto WA = [wa, ido](size_t x, size_t i) - { return wa[i-1+x*(ido-1)]; }; - - if (ido==1) - for (size_t k=0; k(da,WA(u1-1,i),CH(i,k,u1)); \ - special_mul(db,WA(u2-1,i),CH(i,k,u2)); \ - } - -template void pass7(size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const cmplx * POCKETFFT_RESTRICT wa) const - { - constexpr T0 tw1r= T0(0.6234898018587335305250048840042398L), - tw1i= (fwd ? -1 : 1) * T0(0.7818314824680298087084445266740578L), - tw2r= T0(-0.2225209339563144042889025644967948L), - tw2i= (fwd ? -1 : 1) * T0(0.9749279121818236070181316829939312L), - tw3r= T0(-0.9009688679024191262361023195074451L), - tw3i= (fwd ? -1 : 1) * T0(0.433883739117558120475768332848359L); - - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+7*c)]; }; - auto WA = [wa, ido](size_t x, size_t i) - { return wa[i-1+x*(ido-1)]; }; - - if (ido==1) - for (size_t k=0; k void ROTX45(T &a) const - { - constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); - if (fwd) - { auto tmp_=a.r; a.r=hsqt2*(a.r+a.i); a.i=hsqt2*(a.i-tmp_); } - else - { auto tmp_=a.r; a.r=hsqt2*(a.r-a.i); a.i=hsqt2*(a.i+tmp_); } - } -template void ROTX135(T &a) const - { - constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); - if (fwd) - { auto tmp_=a.r; a.r=hsqt2*(a.i-a.r); a.i=hsqt2*(-tmp_-a.i); } - else - { auto tmp_=a.r; a.r=hsqt2*(-a.r-a.i); a.i=hsqt2*(tmp_-a.i); } - } - -template void pass8 (size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const cmplx * POCKETFFT_RESTRICT wa) const - { - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+8*c)]; }; - auto WA = [wa, ido](size_t x, size_t i) - { return wa[i-1+x*(ido-1)]; }; - - if (ido==1) - for (size_t k=0; k(a3); - - ROTX90(a7); - PMINPLACE(a5,a7); - ROTX45(a5); - ROTX135(a7); - - PM(a0,a4,CC(0,0,k),CC(0,4,k)); - PM(a2,a6,CC(0,2,k),CC(0,6,k)); - PM(CH(0,k,0),CH(0,k,4),a0+a2,a1); - PM(CH(0,k,2),CH(0,k,6),a0-a2,a3); - ROTX90(a6); - PM(CH(0,k,1),CH(0,k,5),a4+a6,a5); - PM(CH(0,k,3),CH(0,k,7),a4-a6,a7); - } - else - for (size_t k=0; k(a3); - - ROTX90(a7); - PMINPLACE(a5,a7); - ROTX45(a5); - ROTX135(a7); - - PM(a0,a4,CC(0,0,k),CC(0,4,k)); - PM(a2,a6,CC(0,2,k),CC(0,6,k)); - PM(CH(0,k,0),CH(0,k,4),a0+a2,a1); - PM(CH(0,k,2),CH(0,k,6),a0-a2,a3); - ROTX90(a6); - PM(CH(0,k,1),CH(0,k,5),a4+a6,a5); - PM(CH(0,k,3),CH(0,k,7),a4-a6,a7); - } - for (size_t i=1; i(a7); - PMINPLACE(a1,a3); - ROTX90(a3); - PMINPLACE(a5,a7); - ROTX45(a5); - ROTX135(a7); - PM(a0,a4,CC(i,0,k),CC(i,4,k)); - PM(a2,a6,CC(i,2,k),CC(i,6,k)); - PMINPLACE(a0,a2); - CH(i,k,0) = a0+a1; - special_mul(a0-a1,WA(3,i),CH(i,k,4)); - special_mul(a2+a3,WA(1,i),CH(i,k,2)); - special_mul(a2-a3,WA(5,i),CH(i,k,6)); - ROTX90(a6); - PMINPLACE(a4,a6); - special_mul(a4+a5,WA(0,i),CH(i,k,1)); - special_mul(a4-a5,WA(4,i),CH(i,k,5)); - special_mul(a6+a7,WA(2,i),CH(i,k,3)); - special_mul(a6-a7,WA(6,i),CH(i,k,7)); - } - } - } - - -#define POCKETFFT_PREP11(idx) \ - T t1 = CC(idx,0,k), t2, t3, t4, t5, t6, t7, t8, t9, t10, t11; \ - PM (t2,t11,CC(idx,1,k),CC(idx,10,k)); \ - PM (t3,t10,CC(idx,2,k),CC(idx, 9,k)); \ - PM (t4,t9 ,CC(idx,3,k),CC(idx, 8,k)); \ - PM (t5,t8 ,CC(idx,4,k),CC(idx, 7,k)); \ - PM (t6,t7 ,CC(idx,5,k),CC(idx, 6,k)); \ - CH(idx,k,0).r=t1.r+t2.r+t3.r+t4.r+t5.r+t6.r; \ - CH(idx,k,0).i=t1.i+t2.i+t3.i+t4.i+t5.i+t6.i; - -#define POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,out1,out2) \ - { \ - T ca = t1 + t2*x1 + t3*x2 + t4*x3 + t5*x4 +t6*x5, \ - cb; \ - cb.i=y1*t11.r y2*t10.r y3*t9.r y4*t8.r y5*t7.r; \ - cb.r=-(y1*t11.i y2*t10.i y3*t9.i y4*t8.i y5*t7.i ); \ - PM(out1,out2,ca,cb); \ - } -#define POCKETFFT_PARTSTEP11a(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5) \ - POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,CH(0,k,u1),CH(0,k,u2)) -#define POCKETFFT_PARTSTEP11(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5) \ - { \ - T da,db; \ - POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,da,db) \ - special_mul(da,WA(u1-1,i),CH(i,k,u1)); \ - special_mul(db,WA(u2-1,i),CH(i,k,u2)); \ - } - -template void pass11 (size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const cmplx * POCKETFFT_RESTRICT wa) const - { - constexpr T0 tw1r= T0(0.8412535328311811688618116489193677L), - tw1i= (fwd ? -1 : 1) * T0(0.5406408174555975821076359543186917L), - tw2r= T0(0.4154150130018864255292741492296232L), - tw2i= (fwd ? -1 : 1) * T0(0.9096319953545183714117153830790285L), - tw3r= T0(-0.1423148382732851404437926686163697L), - tw3i= (fwd ? -1 : 1) * T0(0.9898214418809327323760920377767188L), - tw4r= T0(-0.6548607339452850640569250724662936L), - tw4i= (fwd ? -1 : 1) * T0(0.7557495743542582837740358439723444L), - tw5r= T0(-0.9594929736144973898903680570663277L), - tw5i= (fwd ? -1 : 1) * T0(0.2817325568414296977114179153466169L); - - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+11*c)]; }; - auto WA = [wa, ido](size_t x, size_t i) - { return wa[i-1+x*(ido-1)]; }; - - if (ido==1) - for (size_t k=0; k void passg (size_t ido, size_t ip, - size_t l1, T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const cmplx * POCKETFFT_RESTRICT wa, - const cmplx * POCKETFFT_RESTRICT csarr) const - { - const size_t cdim=ip; - size_t ipph = (ip+1)/2; - size_t idl1 = ido*l1; - - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+cdim*c)]; }; - auto CX = [cc, ido, l1](size_t a, size_t b, size_t c) -> T& - { return cc[a+ido*(b+l1*c)]; }; - auto CX2 = [cc, idl1](size_t a, size_t b) -> T& - { return cc[a+idl1*b]; }; - auto CH2 = [ch, idl1](size_t a, size_t b) -> const T& - { return ch[a+idl1*b]; }; - - arr> wal(ip); - wal[0] = cmplx(1., 0.); - for (size_t i=1; i(csarr[i].r,fwd ? -csarr[i].i : csarr[i].i); - - for (size_t k=0; kip) iwal-=ip; - cmplx xwal=wal[iwal]; - iwal+=l; if (iwal>ip) iwal-=ip; - cmplx xwal2=wal[iwal]; - for (size_t ik=0; ikip) iwal-=ip; - cmplx xwal=wal[iwal]; - for (size_t ik=0; ik(x1,wa[idij],CX(i,k,j)); - idij=(jc-1)*(ido-1)+i-1; - special_mul(x2,wa[idij],CX(i,k,jc)); - } - } - } - } - -template void pass_all(T c[], T0 fct) const - { - if (length==1) { c[0]*=fct; return; } - size_t l1=1; - arr ch(length); - T *p1=c, *p2=ch.data(); - - for(size_t k1=0; k1 (ido, l1, p1, p2, fact[k1].tw); - else if(ip==8) - pass8(ido, l1, p1, p2, fact[k1].tw); - else if(ip==2) - pass2(ido, l1, p1, p2, fact[k1].tw); - else if(ip==3) - pass3 (ido, l1, p1, p2, fact[k1].tw); - else if(ip==5) - pass5 (ido, l1, p1, p2, fact[k1].tw); - else if(ip==7) - pass7 (ido, l1, p1, p2, fact[k1].tw); - else if(ip==11) - pass11 (ido, l1, p1, p2, fact[k1].tw); - else - { - passg(ido, ip, l1, p1, p2, fact[k1].tw, fact[k1].tws); - std::swap(p1,p2); - } - std::swap(p1,p2); - l1=l2; - } - if (p1!=c) - { - if (fct!=1.) - for (size_t i=0; i void exec(T c[], T0 fct, bool fwd) const - { fwd ? pass_all(c, fct) : pass_all(c, fct); } - - private: - POCKETFFT_NOINLINE void factorize() - { - size_t len=length; - while ((len&7)==0) - { add_factor(8); len>>=3; } - while ((len&3)==0) - { add_factor(4); len>>=2; } - if ((len&1)==0) - { - len>>=1; - // factor 2 should be at the front of the factor list - add_factor(2); - std::swap(fact[0].fct, fact.back().fct); - } - for (size_t divisor=3; divisor*divisor<=len; divisor+=2) - while ((len%divisor)==0) - { - add_factor(divisor); - len/=divisor; - } - if (len>1) add_factor(len); - } - - size_t twsize() const - { - size_t twsize=0, l1=1; - for (size_t k=0; k11) - twsize+=ip; - l1*=ip; - } - return twsize; - } - - void comp_twiddle() - { - sincos_2pibyn twiddle(length); - size_t l1=1; - size_t memofs=0; - for (size_t k=0; k11) - { - fact[k].tws=mem.data()+memofs; - memofs+=ip; - for (size_t j=0; j class rfftp - { - private: - struct fctdata - { - size_t fct; - T0 *tw, *tws; - }; - - size_t length; - arr mem; - std::vector fact; - - void add_factor(size_t factor) - { fact.push_back({factor, nullptr, nullptr}); } - -/* (a+ib) = conj(c+id) * (e+if) */ -template inline void MULPM - (T1 &a, T1 &b, T2 c, T2 d, T3 e, T3 f) const - { a=c*e+d*f; b=c*f-d*e; } - -template void radf2 (size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa) const - { - auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; - auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+l1*c)]; }; - auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+2*c)]; }; - - for (size_t k=0; k void radf3(size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa) const - { - constexpr T0 taur=-0.5, taui=T0(0.8660254037844386467637231707529362L); - - auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; - auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+l1*c)]; }; - auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+3*c)]; }; - - for (size_t k=0; k void radf4(size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa) const - { - constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); - - auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; - auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+l1*c)]; }; - auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+4*c)]; }; - - for (size_t k=0; k void radf5(size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa) const - { - constexpr T0 tr11= T0(0.3090169943749474241022934171828191L), - ti11= T0(0.9510565162951535721164393333793821L), - tr12= T0(-0.8090169943749474241022934171828191L), - ti12= T0(0.5877852522924731291687059546390728L); - - auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; - auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+l1*c)]; }; - auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+5*c)]; }; - - for (size_t k=0; k void radfg(size_t ido, size_t ip, size_t l1, - T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa, const T0 * POCKETFFT_RESTRICT csarr) const - { - const size_t cdim=ip; - size_t ipph=(ip+1)/2; - size_t idl1 = ido*l1; - - auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> T& - { return cc[a+ido*(b+cdim*c)]; }; - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> const T& - { return ch[a+ido*(b+l1*c)]; }; - auto C1 = [cc,ido,l1] (size_t a, size_t b, size_t c) -> T& - { return cc[a+ido*(b+l1*c)]; }; - auto C2 = [cc,idl1] (size_t a, size_t b) -> T& - { return cc[a+idl1*b]; }; - auto CH2 = [ch,idl1] (size_t a, size_t b) -> T& - { return ch[a+idl1*b]; }; - - if (ido>1) - { - for (size_t j=1, jc=ip-1; j=ip) iang-=ip; - T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; - iang+=l; if (iang>=ip) iang-=ip; - T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; - iang+=l; if (iang>=ip) iang-=ip; - T0 ar3=csarr[2*iang], ai3=csarr[2*iang+1]; - iang+=l; if (iang>=ip) iang-=ip; - T0 ar4=csarr[2*iang], ai4=csarr[2*iang+1]; - for (size_t ik=0; ik=ip) iang-=ip; - T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; - iang+=l; if (iang>=ip) iang-=ip; - T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; - for (size_t ik=0; ik=ip) iang-=ip; - T0 ar=csarr[2*iang], ai=csarr[2*iang+1]; - for (size_t ik=0; ik void radb2(size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa) const - { - auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+2*c)]; }; - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - - for (size_t k=0; k void radb3(size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa) const - { - constexpr T0 taur=-0.5, taui=T0(0.8660254037844386467637231707529362L); - - auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+3*c)]; }; - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - - for (size_t k=0; k void radb4(size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa) const - { - constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); - - auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+4*c)]; }; - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - - for (size_t k=0; k void radb5(size_t ido, size_t l1, - const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa) const - { - constexpr T0 tr11= T0(0.3090169943749474241022934171828191L), - ti11= T0(0.9510565162951535721164393333793821L), - tr12= T0(-0.8090169943749474241022934171828191L), - ti12= T0(0.5877852522924731291687059546390728L); - - auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; - auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+5*c)]; }; - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - - for (size_t k=0; k void radbg(size_t ido, size_t ip, size_t l1, - T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, - const T0 * POCKETFFT_RESTRICT wa, const T0 * POCKETFFT_RESTRICT csarr) const - { - const size_t cdim=ip; - size_t ipph=(ip+1)/ 2; - size_t idl1 = ido*l1; - - auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+cdim*c)]; }; - auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& - { return ch[a+ido*(b+l1*c)]; }; - auto C1 = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& - { return cc[a+ido*(b+l1*c)]; }; - auto C2 = [cc,idl1](size_t a, size_t b) -> T& - { return cc[a+idl1*b]; }; - auto CH2 = [ch,idl1](size_t a, size_t b) -> T& - { return ch[a+idl1*b]; }; - - for (size_t k=0; kip) iang-=ip; - T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; - iang+=l; if(iang>ip) iang-=ip; - T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; - iang+=l; if(iang>ip) iang-=ip; - T0 ar3=csarr[2*iang], ai3=csarr[2*iang+1]; - iang+=l; if(iang>ip) iang-=ip; - T0 ar4=csarr[2*iang], ai4=csarr[2*iang+1]; - for (size_t ik=0; ikip) iang-=ip; - T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; - iang+=l; if(iang>ip) iang-=ip; - T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; - for (size_t ik=0; ikip) iang-=ip; - T0 war=csarr[2*iang], wai=csarr[2*iang+1]; - for (size_t ik=0; ik void copy_and_norm(T *c, T *p1, T0 fct) const - { - if (p1!=c) - { - if (fct!=1.) - for (size_t i=0; i void exec(T c[], T0 fct, bool r2hc) const - { - if (length==1) { c[0]*=fct; return; } - size_t nf=fact.size(); - arr ch(length); - T *p1=c, *p2=ch.data(); - - if (r2hc) - for(size_t k1=0, l1=length; k1>=2; } - if ((len%2)==0) - { - len>>=1; - // factor 2 should be at the front of the factor list - add_factor(2); - std::swap(fact[0].fct, fact.back().fct); - } - for (size_t divisor=3; divisor*divisor<=len; divisor+=2) - while ((len%divisor)==0) - { - add_factor(divisor); - len/=divisor; - } - if (len>1) add_factor(len); - } - - size_t twsize() const - { - size_t twsz=0, l1=1; - for (size_t k=0; k5) twsz+=2*ip; - l1*=ip; - } - return twsz; - } - - void comp_twiddle() - { - sincos_2pibyn twid(length); - size_t l1=1; - T0 *ptr=mem.data(); - for (size_t k=0; k5) // special factors required by *g functions - { - fact[k].tws=ptr; ptr+=2*ip; - fact[k].tws[0] = 1.; - fact[k].tws[1] = 0.; - for (size_t i=2, ic=2*ip-2; i<=ic; i+=2, ic-=2) - { - fact[k].tws[i ] = twid[i/2*(length/ip)].r; - fact[k].tws[i+1] = twid[i/2*(length/ip)].i; - fact[k].tws[ic] = twid[i/2*(length/ip)].r; - fact[k].tws[ic+1] = -twid[i/2*(length/ip)].i; - } - } - l1*=ip; - } - } - - public: - POCKETFFT_NOINLINE rfftp(size_t length_) - : length(length_) - { - if (length==0) throw std::runtime_error("zero-length FFT requested"); - if (length==1) return; - factorize(); - mem.resize(twsize()); - comp_twiddle(); - } -}; - -// -// complex Bluestein transforms -// - -template class fftblue - { - private: - size_t n, n2; - cfftp plan; - arr> mem; - cmplx *bk, *bkf; - - template void fft(cmplx c[], T0 fct) const - { - arr> akf(n2); - - /* initialize a_k and FFT it */ - for (size_t m=0; m(c[m],bk[m],akf[m]); - auto zero = akf[0]*T0(0); - for (size_t m=n; m(bkf[0]); - for (size_t m=1; m<(n2+1)/2; ++m) - { - akf[m] = akf[m].template special_mul(bkf[m]); - akf[n2-m] = akf[n2-m].template special_mul(bkf[m]); - } - if ((n2&1)==0) - akf[n2/2] = akf[n2/2].template special_mul(bkf[n2/2]); - - /* inverse FFT */ - plan.exec (akf.data(),1.,false); - - /* multiply by b_k */ - for (size_t m=0; m(bk[m])*fct; - } - - public: - POCKETFFT_NOINLINE fftblue(size_t length) - : n(length), n2(util::good_size_cmplx(n*2-1)), plan(n2), mem(n+n2/2+1), - bk(mem.data()), bkf(mem.data()+n) - { - /* initialize b_k */ - sincos_2pibyn tmp(2*n); - bk[0].Set(1, 0); - - size_t coeff=0; - for (size_t m=1; m=2*n) coeff-=2*n; - bk[m] = tmp[coeff]; - } - - /* initialize the zero-padded, Fourier transformed b_k. Add normalisation. */ - arr> tbkf(n2); - T0 xn2 = T0(1)/T0(n2); - tbkf[0] = bk[0]*xn2; - for (size_t m=1; m void exec(cmplx c[], T0 fct, bool fwd) const - { fwd ? fft(c,fct) : fft(c,fct); } - - template void exec_r(T c[], T0 fct, bool fwd) - { - arr> tmp(n); - if (fwd) - { - auto zero = T0(0)*c[0]; - for (size_t m=0; m(tmp.data(),fct); - c[0] = tmp[0].r; - std::copy_n (&tmp[1].r, n-1, &c[1]); - } - else - { - tmp[0].Set(c[0],c[0]*0); - std::copy_n (c+1, n-1, &tmp[1].r); - if ((n&1)==0) tmp[n/2].i=T0(0)*c[0]; - for (size_t m=1; 2*m(tmp.data(),fct); - for (size_t m=0; m class pocketfft_c - { - private: - std::unique_ptr> packplan; - std::unique_ptr> blueplan; - size_t len; - - public: - POCKETFFT_NOINLINE pocketfft_c(size_t length) - : len(length) - { - if (length==0) throw std::runtime_error("zero-length FFT requested"); - size_t tmp = (length<50) ? 0 : util::largest_prime_factor(length); - if (tmp*tmp <= length) - { - packplan=std::unique_ptr>(new cfftp(length)); - return; - } - double comp1 = util::cost_guess(length); - double comp2 = 2*util::cost_guess(util::good_size_cmplx(2*length-1)); - comp2*=1.5; /* fudge factor that appears to give good overall performance */ - if (comp2>(new fftblue(length)); - else - packplan=std::unique_ptr>(new cfftp(length)); - } - - template POCKETFFT_NOINLINE void exec(cmplx c[], T0 fct, bool fwd) const - { packplan ? packplan->exec(c,fct,fwd) : blueplan->exec(c,fct,fwd); } - - size_t length() const { return len; } - }; - -// -// flexible (FFTPACK/Bluestein) real-valued 1D transform -// - -template class pocketfft_r - { - private: - std::unique_ptr> packplan; - std::unique_ptr> blueplan; - size_t len; - - public: - POCKETFFT_NOINLINE pocketfft_r(size_t length) - : len(length) - { - if (length==0) throw std::runtime_error("zero-length FFT requested"); - size_t tmp = (length<50) ? 0 : util::largest_prime_factor(length); - if (tmp*tmp <= length) - { - packplan=std::unique_ptr>(new rfftp(length)); - return; - } - double comp1 = 0.5*util::cost_guess(length); - double comp2 = 2*util::cost_guess(util::good_size_cmplx(2*length-1)); - comp2*=1.5; /* fudge factor that appears to give good overall performance */ - if (comp2>(new fftblue(length)); - else - packplan=std::unique_ptr>(new rfftp(length)); - } - - template POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool fwd) const - { packplan ? packplan->exec(c,fct,fwd) : blueplan->exec_r(c,fct,fwd); } - - size_t length() const { return len; } - }; - - -// -// sine/cosine transforms -// - -template class T_dct1 - { - private: - pocketfft_r fftplan; - - public: - POCKETFFT_NOINLINE T_dct1(size_t length) - : fftplan(2*(length-1)) {} - - template POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool ortho, - int /*type*/, bool /*cosine*/) const - { - constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); - size_t N=fftplan.length(), n=N/2+1; - if (ortho) - { c[0]*=sqrt2; c[n-1]*=sqrt2; } - arr tmp(N); - tmp[0] = c[0]; - for (size_t i=1; i class T_dst1 - { - private: - pocketfft_r fftplan; - - public: - POCKETFFT_NOINLINE T_dst1(size_t length) - : fftplan(2*(length+1)) {} - - template POCKETFFT_NOINLINE void exec(T c[], T0 fct, - bool /*ortho*/, int /*type*/, bool /*cosine*/) const - { - size_t N=fftplan.length(), n=N/2-1; - arr tmp(N); - tmp[0] = tmp[n+1] = c[0]*0; - for (size_t i=0; i class T_dcst23 - { - private: - pocketfft_r fftplan; - std::vector twiddle; - - public: - POCKETFFT_NOINLINE T_dcst23(size_t length) - : fftplan(length), twiddle(length) - { - sincos_2pibyn tw(4*length); - for (size_t i=0; i POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool ortho, - int type, bool cosine) const - { - constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); - size_t N=length(); - size_t NS2 = (N+1)/2; - if (type==2) - { - if (!cosine) - for (size_t k=1; k class T_dcst4 - { - private: - size_t N; - std::unique_ptr> fft; - std::unique_ptr> rfft; - arr> C2; - - public: - POCKETFFT_NOINLINE T_dcst4(size_t length) - : N(length), - fft((N&1) ? nullptr : new pocketfft_c(N/2)), - rfft((N&1)? new pocketfft_r(N) : nullptr), - C2((N&1) ? 0 : N/2) - { - if ((N&1)==0) - { - sincos_2pibyn tw(16*N); - for (size_t i=0; i POCKETFFT_NOINLINE void exec(T c[], T0 fct, - bool /*ortho*/, int /*type*/, bool cosine) const - { - size_t n2 = N/2; - if (!cosine) - for (size_t k=0, kc=N-1; k y(N); - { - size_t i=0, m=n2; - for (; mexec(y.data(), fct, true); - { - auto SGN = [](size_t i) - { - constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); - return (i&2) ? -sqrt2 : sqrt2; - }; - c[n2] = y[0]*SGN(n2+1); - size_t i=0, i1=1, k=1; - for (; k> y(n2); - for(size_t i=0; iexec(y.data(), fct, true); - for(size_t i=0, ic=n2-1; i std::shared_ptr get_plan(size_t length) - { -#if POCKETFFT_CACHE_SIZE==0 - return std::make_shared(length); -#else - constexpr size_t nmax=POCKETFFT_CACHE_SIZE; - static std::array, nmax> cache; - static std::array last_access{{0}}; - static size_t access_counter = 0; - static std::mutex mut; - - auto find_in_cache = [&]() -> std::shared_ptr - { - for (size_t i=0; ilength()==length)) - { - // no need to update if this is already the most recent entry - if (last_access[i]!=access_counter) - { - last_access[i] = ++access_counter; - // Guard against overflow - if (access_counter == 0) - last_access.fill(0); - } - return cache[i]; - } - - return nullptr; - }; - - { - std::lock_guard lock(mut); - auto p = find_in_cache(); - if (p) return p; - } - auto plan = std::make_shared(length); - { - std::lock_guard lock(mut); - auto p = find_in_cache(); - if (p) return p; - - size_t lru = 0; - for (size_t i=1; i class cndarr: public arr_info - { - protected: - const char *d; - - public: - cndarr(const void *data_, const shape_t &shape_, const stride_t &stride_) - : arr_info(shape_, stride_), - d(reinterpret_cast(data_)) {} - const T &operator[](ptrdiff_t ofs) const - { return *reinterpret_cast(d+ofs); } - }; - -template class ndarr: public cndarr - { - public: - ndarr(void *data_, const shape_t &shape_, const stride_t &stride_) - : cndarr::cndarr(const_cast(data_), shape_, stride_) - {} - T &operator[](ptrdiff_t ofs) - { return *reinterpret_cast(const_cast(cndarr::d+ofs)); } - }; - -template class multi_iter - { - private: - shape_t pos; - const arr_info &iarr, &oarr; - ptrdiff_t p_ii, p_i[N], str_i, p_oi, p_o[N], str_o; - size_t idim, rem; - - void advance_i() - { - for (int i_=int(pos.size())-1; i_>=0; --i_) - { - auto i = size_t(i_); - if (i==idim) continue; - p_ii += iarr.stride(i); - p_oi += oarr.stride(i); - if (++pos[i] < iarr.shape(i)) - return; - pos[i] = 0; - p_ii -= ptrdiff_t(iarr.shape(i))*iarr.stride(i); - p_oi -= ptrdiff_t(oarr.shape(i))*oarr.stride(i); - } - } - - public: - multi_iter(const arr_info &iarr_, const arr_info &oarr_, size_t idim_) - : pos(iarr_.ndim(), 0), iarr(iarr_), oarr(oarr_), p_ii(0), - str_i(iarr.stride(idim_)), p_oi(0), str_o(oarr.stride(idim_)), - idim(idim_), rem(iarr.size()/iarr.shape(idim)) - { - auto nshares = threading::num_threads(); - if (nshares==1) return; - if (nshares==0) throw std::runtime_error("can't run with zero threads"); - auto myshare = threading::thread_id(); - if (myshare>=nshares) throw std::runtime_error("impossible share requested"); - size_t nbase = rem/nshares; - size_t additional = rem%nshares; - size_t lo = myshare*nbase + ((myshare=0; --i_) - { - auto i = size_t(i_); - p += arr.stride(i); - if (++pos[i] < arr.shape(i)) - return; - pos[i] = 0; - p -= ptrdiff_t(arr.shape(i))*arr.stride(i); - } - } - ptrdiff_t ofs() const { return p; } - size_t remaining() const { return rem; } - }; - -class rev_iter - { - private: - shape_t pos; - const arr_info &arr; - std::vector rev_axis; - std::vector rev_jump; - size_t last_axis, last_size; - shape_t shp; - ptrdiff_t p, rp; - size_t rem; - - public: - rev_iter(const arr_info &arr_, const shape_t &axes) - : pos(arr_.ndim(), 0), arr(arr_), rev_axis(arr_.ndim(), 0), - rev_jump(arr_.ndim(), 1), p(0), rp(0) - { - for (auto ax: axes) - rev_axis[ax]=1; - last_axis = axes.back(); - last_size = arr.shape(last_axis)/2 + 1; - shp = arr.shape(); - shp[last_axis] = last_size; - rem=1; - for (auto i: shp) - rem *= i; - } - void advance() - { - --rem; - for (int i_=int(pos.size())-1; i_>=0; --i_) - { - auto i = size_t(i_); - p += arr.stride(i); - if (!rev_axis[i]) - rp += arr.stride(i); - else - { - rp -= arr.stride(i); - if (rev_jump[i]) - { - rp += ptrdiff_t(arr.shape(i))*arr.stride(i); - rev_jump[i] = 0; - } - } - if (++pos[i] < shp[i]) - return; - pos[i] = 0; - p -= ptrdiff_t(shp[i])*arr.stride(i); - if (rev_axis[i]) - { - rp -= ptrdiff_t(arr.shape(i)-shp[i])*arr.stride(i); - rev_jump[i] = 1; - } - else - rp -= ptrdiff_t(shp[i])*arr.stride(i); - } - } - ptrdiff_t ofs() const { return p; } - ptrdiff_t rev_ofs() const { return rp; } - size_t remaining() const { return rem; } - }; - -template struct VTYPE {}; -template using vtype_t = typename VTYPE::type; - -#ifndef POCKETFFT_NO_VECTORS -template<> struct VTYPE - { - using type = float __attribute__ ((vector_size (VLEN::val*sizeof(float)))); - }; -template<> struct VTYPE - { - using type = double __attribute__ ((vector_size (VLEN::val*sizeof(double)))); - }; -template<> struct VTYPE - { - using type = long double __attribute__ ((vector_size (VLEN::val*sizeof(long double)))); - }; -#endif - -template arr alloc_tmp(const shape_t &shape, - size_t axsize, size_t elemsize) - { - auto othersize = util::prod(shape)/axsize; - auto tmpsize = axsize*((othersize>=VLEN::val) ? VLEN::val : 1); - return arr(tmpsize*elemsize); - } -template arr alloc_tmp(const shape_t &shape, - const shape_t &axes, size_t elemsize) - { - size_t fullsize=util::prod(shape); - size_t tmpsize=0; - for (size_t i=0; i=VLEN::val) ? VLEN::val : 1); - if (sz>tmpsize) tmpsize=sz; - } - return arr(tmpsize*elemsize); - } - -template void copy_input(const multi_iter &it, - const cndarr> &src, cmplx> *POCKETFFT_RESTRICT dst) - { - for (size_t i=0; i void copy_input(const multi_iter &it, - const cndarr &src, vtype_t *POCKETFFT_RESTRICT dst) - { - for (size_t i=0; i void copy_input(const multi_iter &it, - const cndarr &src, T *POCKETFFT_RESTRICT dst) - { - if (dst == &src[it.iofs(0)]) return; // in-place - for (size_t i=0; i void copy_output(const multi_iter &it, - const cmplx> *POCKETFFT_RESTRICT src, ndarr> &dst) - { - for (size_t i=0; i void copy_output(const multi_iter &it, - const vtype_t *POCKETFFT_RESTRICT src, ndarr &dst) - { - for (size_t i=0; i void copy_output(const multi_iter &it, - const T *POCKETFFT_RESTRICT src, ndarr &dst) - { - if (src == &dst[it.oofs(0)]) return; // in-place - for (size_t i=0; i struct add_vec { using type = vtype_t; }; -template struct add_vec> - { using type = cmplx>; }; -template using add_vec_t = typename add_vec::type; - -template -POCKETFFT_NOINLINE void general_nd(const cndarr &in, ndarr &out, - const shape_t &axes, T0 fct, size_t nthreads, const Exec & exec, - const bool allow_inplace=true) - { - std::shared_ptr plan; - - for (size_t iax=0; iaxlength())) - plan = get_plan(len); - - threading::thread_map( - util::thread_count(nthreads, in.shape(), axes[iax], VLEN::val), - [&] { - constexpr auto vlen = VLEN::val; - auto storage = alloc_tmp(in.shape(), len, sizeof(T)); - const auto &tin(iax==0? in : out); - multi_iter it(tin, out, axes[iax]); -#ifndef POCKETFFT_NO_VECTORS - if (vlen>1) - while (it.remaining()>=vlen) - { - it.advance(vlen); - auto tdatav = reinterpret_cast *>(storage.data()); - exec(it, tin, out, tdatav, *plan, fct); - } -#endif - while (it.remaining()>0) - { - it.advance(1); - auto buf = allow_inplace && it.stride_out() == sizeof(T) ? - &out[it.oofs(0)] : reinterpret_cast(storage.data()); - exec(it, tin, out, buf, *plan, fct); - } - }); // end of parallel region - fct = T0(1); // factor has been applied, use 1 for remaining axes - } - } - -struct ExecC2C - { - bool forward; - - template void operator () ( - const multi_iter &it, const cndarr> &in, - ndarr> &out, T * buf, const pocketfft_c &plan, T0 fct) const - { - copy_input(it, in, buf); - plan.exec(buf, fct, forward); - copy_output(it, buf, out); - } - }; - -template void copy_hartley(const multi_iter &it, - const vtype_t *POCKETFFT_RESTRICT src, ndarr &dst) - { - for (size_t j=0; j void copy_hartley(const multi_iter &it, - const T *POCKETFFT_RESTRICT src, ndarr &dst) - { - dst[it.oofs(0)] = src[0]; - size_t i=1, i1=1, i2=it.length_out()-1; - for (i=1; i void operator () ( - const multi_iter &it, const cndarr &in, ndarr &out, - T * buf, const pocketfft_r &plan, T0 fct) const - { - copy_input(it, in, buf); - plan.exec(buf, fct, true); - copy_hartley(it, buf, out); - } - }; - -struct ExecDcst - { - bool ortho; - int type; - bool cosine; - - template - void operator () (const multi_iter &it, const cndarr &in, - ndarr &out, T * buf, const Tplan &plan, T0 fct) const - { - copy_input(it, in, buf); - plan.exec(buf, fct, ortho, type, cosine); - copy_output(it, buf, out); - } - }; - -template POCKETFFT_NOINLINE void general_r2c( - const cndarr &in, ndarr> &out, size_t axis, bool forward, T fct, - size_t nthreads) - { - auto plan = get_plan>(in.shape(axis)); - size_t len=in.shape(axis); - threading::thread_map( - util::thread_count(nthreads, in.shape(), axis, VLEN::val), - [&] { - constexpr auto vlen = VLEN::val; - auto storage = alloc_tmp(in.shape(), len, sizeof(T)); - multi_iter it(in, out, axis); -#ifndef POCKETFFT_NO_VECTORS - if (vlen>1) - while (it.remaining()>=vlen) - { - it.advance(vlen); - auto tdatav = reinterpret_cast *>(storage.data()); - copy_input(it, in, tdatav); - plan->exec(tdatav, fct, true); - for (size_t j=0; j0) - { - it.advance(1); - auto tdata = reinterpret_cast(storage.data()); - copy_input(it, in, tdata); - plan->exec(tdata, fct, true); - out[it.oofs(0)].Set(tdata[0]); - size_t i=1, ii=1; - if (forward) - for (; i POCKETFFT_NOINLINE void general_c2r( - const cndarr> &in, ndarr &out, size_t axis, bool forward, T fct, - size_t nthreads) - { - auto plan = get_plan>(out.shape(axis)); - size_t len=out.shape(axis); - threading::thread_map( - util::thread_count(nthreads, in.shape(), axis, VLEN::val), - [&] { - constexpr auto vlen = VLEN::val; - auto storage = alloc_tmp(out.shape(), len, sizeof(T)); - multi_iter it(in, out, axis); -#ifndef POCKETFFT_NO_VECTORS - if (vlen>1) - while (it.remaining()>=vlen) - { - it.advance(vlen); - auto tdatav = reinterpret_cast *>(storage.data()); - for (size_t j=0; jexec(tdatav, fct, false); - copy_output(it, tdatav, out); - } -#endif - while (it.remaining()>0) - { - it.advance(1); - auto tdata = reinterpret_cast(storage.data()); - tdata[0]=in[it.iofs(0)].r; - { - size_t i=1, ii=1; - if (forward) - for (; iexec(tdata, fct, false); - copy_output(it, tdata, out); - } - }); // end of parallel region - } - -struct ExecR2R - { - bool r2h, forward; - - template void operator () ( - const multi_iter &it, const cndarr &in, ndarr &out, T * buf, - const pocketfft_r &plan, T0 fct) const - { - copy_input(it, in, buf); - if ((!r2h) && forward) - for (size_t i=2; i void c2c(const shape_t &shape, const stride_t &stride_in, - const stride_t &stride_out, const shape_t &axes, bool forward, - const std::complex *data_in, std::complex *data_out, T fct, - size_t nthreads=1) - { - if (util::prod(shape)==0) return; - util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); - cndarr> ain(data_in, shape, stride_in); - ndarr> aout(data_out, shape, stride_out); - general_nd>(ain, aout, axes, fct, nthreads, ExecC2C{forward}); - } - -template void dct(const shape_t &shape, - const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, - int type, const T *data_in, T *data_out, T fct, bool ortho, size_t nthreads=1) - { - if ((type<1) || (type>4)) throw std::invalid_argument("invalid DCT type"); - if (util::prod(shape)==0) return; - util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); - cndarr ain(data_in, shape, stride_in); - ndarr aout(data_out, shape, stride_out); - const ExecDcst exec{ortho, type, true}; - if (type==1) - general_nd>(ain, aout, axes, fct, nthreads, exec); - else if (type==4) - general_nd>(ain, aout, axes, fct, nthreads, exec); - else - general_nd>(ain, aout, axes, fct, nthreads, exec); - } - -template void dst(const shape_t &shape, - const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, - int type, const T *data_in, T *data_out, T fct, bool ortho, size_t nthreads=1) - { - if ((type<1) || (type>4)) throw std::invalid_argument("invalid DST type"); - if (util::prod(shape)==0) return; - util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); - cndarr ain(data_in, shape, stride_in); - ndarr aout(data_out, shape, stride_out); - const ExecDcst exec{ortho, type, false}; - if (type==1) - general_nd>(ain, aout, axes, fct, nthreads, exec); - else if (type==4) - general_nd>(ain, aout, axes, fct, nthreads, exec); - else - general_nd>(ain, aout, axes, fct, nthreads, exec); - } - -template void r2c(const shape_t &shape_in, - const stride_t &stride_in, const stride_t &stride_out, size_t axis, - bool forward, const T *data_in, std::complex *data_out, T fct, - size_t nthreads=1) - { - if (util::prod(shape_in)==0) return; - util::sanity_check(shape_in, stride_in, stride_out, false, axis); - cndarr ain(data_in, shape_in, stride_in); - shape_t shape_out(shape_in); - shape_out[axis] = shape_in[axis]/2 + 1; - ndarr> aout(data_out, shape_out, stride_out); - general_r2c(ain, aout, axis, forward, fct, nthreads); - } - -template void r2c(const shape_t &shape_in, - const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, - bool forward, const T *data_in, std::complex *data_out, T fct, - size_t nthreads=1) - { - if (util::prod(shape_in)==0) return; - util::sanity_check(shape_in, stride_in, stride_out, false, axes); - r2c(shape_in, stride_in, stride_out, axes.back(), forward, data_in, data_out, - fct, nthreads); - if (axes.size()==1) return; - - shape_t shape_out(shape_in); - shape_out[axes.back()] = shape_in[axes.back()]/2 + 1; - auto newaxes = shape_t{axes.begin(), --axes.end()}; - c2c(shape_out, stride_out, stride_out, newaxes, forward, data_out, data_out, - T(1), nthreads); - } - -template void c2r(const shape_t &shape_out, - const stride_t &stride_in, const stride_t &stride_out, size_t axis, - bool forward, const std::complex *data_in, T *data_out, T fct, - size_t nthreads=1) - { - if (util::prod(shape_out)==0) return; - util::sanity_check(shape_out, stride_in, stride_out, false, axis); - shape_t shape_in(shape_out); - shape_in[axis] = shape_out[axis]/2 + 1; - cndarr> ain(data_in, shape_in, stride_in); - ndarr aout(data_out, shape_out, stride_out); - general_c2r(ain, aout, axis, forward, fct, nthreads); - } - -template void c2r(const shape_t &shape_out, - const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, - bool forward, const std::complex *data_in, T *data_out, T fct, - size_t nthreads=1) - { - if (util::prod(shape_out)==0) return; - if (axes.size()==1) - return c2r(shape_out, stride_in, stride_out, axes[0], forward, - data_in, data_out, fct, nthreads); - util::sanity_check(shape_out, stride_in, stride_out, false, axes); - auto shape_in = shape_out; - shape_in[axes.back()] = shape_out[axes.back()]/2 + 1; - auto nval = util::prod(shape_in); - stride_t stride_inter(shape_in.size()); - stride_inter.back() = sizeof(cmplx); - for (int i=int(shape_in.size())-2; i>=0; --i) - stride_inter[size_t(i)] = - stride_inter[size_t(i+1)]*ptrdiff_t(shape_in[size_t(i+1)]); - arr> tmp(nval); - auto newaxes = shape_t{axes.begin(), --axes.end()}; - c2c(shape_in, stride_in, stride_inter, newaxes, forward, data_in, tmp.data(), - T(1), nthreads); - c2r(shape_out, stride_inter, stride_out, axes.back(), forward, - tmp.data(), data_out, fct, nthreads); - } - -template void r2r_fftpack(const shape_t &shape, - const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, - bool real2hermitian, bool forward, const T *data_in, T *data_out, T fct, - size_t nthreads=1) - { - if (util::prod(shape)==0) return; - util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); - cndarr ain(data_in, shape, stride_in); - ndarr aout(data_out, shape, stride_out); - general_nd>(ain, aout, axes, fct, nthreads, - ExecR2R{real2hermitian, forward}); - } - -template void r2r_separable_hartley(const shape_t &shape, - const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, - const T *data_in, T *data_out, T fct, size_t nthreads=1) - { - if (util::prod(shape)==0) return; - util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); - cndarr ain(data_in, shape, stride_in); - ndarr aout(data_out, shape, stride_out); - general_nd>(ain, aout, axes, fct, nthreads, ExecHartley{}, - false); - } - -template void r2r_genuine_hartley(const shape_t &shape, - const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, - const T *data_in, T *data_out, T fct, size_t nthreads=1) - { - if (util::prod(shape)==0) return; - if (axes.size()==1) - return r2r_separable_hartley(shape, stride_in, stride_out, axes, data_in, - data_out, fct, nthreads); - util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); - shape_t tshp(shape); - tshp[axes.back()] = tshp[axes.back()]/2+1; - arr> tdata(util::prod(tshp)); - stride_t tstride(shape.size()); - tstride.back()=sizeof(std::complex); - for (size_t i=tstride.size()-1; i>0; --i) - tstride[i-1]=tstride[i]*ptrdiff_t(tshp[i]); - r2c(shape, stride_in, tstride, axes, true, data_in, tdata.data(), fct, nthreads); - cndarr> atmp(tdata.data(), tshp, tstride); - ndarr aout(data_out, shape, stride_out); - simple_iter iin(atmp); - rev_iter iout(aout, axes); - while(iin.remaining()>0) - { - auto v = atmp[iin.ofs()]; - aout[iout.ofs()] = v.r+v.i; - aout[iout.rev_ofs()] = v.r-v.i; - iin.advance(); iout.advance(); - } - } - -} // namespace detail - -using detail::FORWARD; -using detail::BACKWARD; -using detail::shape_t; -using detail::stride_t; -using detail::c2c; -using detail::c2r; -using detail::r2c; -using detail::r2r_fftpack; -using detail::r2r_separable_hartley; -using detail::r2r_genuine_hartley; -using detail::dct; -using detail::dst; - -} // namespace pocketfft - -#undef POCKETFFT_NOINLINE -#undef POCKETFFT_RESTRICT - -#endif // POCKETFFT_HDRONLY_H diff --git a/indexer/CMakeLists.txt b/indexer/CMakeLists.txt index fbcb8b0..87ac6c1 100644 --- a/indexer/CMakeLists.txt +++ b/indexer/CMakeLists.txt @@ -12,7 +12,19 @@ FetchContent_Declare( EXCLUDE_FROM_ALL FIND_PACKAGE_ARGS ) +FetchContent_Declare( + pocketfft + GIT_REPOSITORY https://github.com/mreineck/pocketfft + GIT_TAG cpp + CONFIGURE_COMMAND "" + BUILD_COMMAND "" +) FetchContent_MakeAvailable(Eigen3) +FetchContent_MakeAvailable(pocketfft) + +FetchContent_GetProperties(pocketfft) +add_library(pocketfft INTERFACE) +target_include_directories(pocketfft INTERFACE ${pocketfft_SOURCE_DIR}) add_executable(indexer indexer.cc @@ -23,6 +35,7 @@ target_link_libraries(indexer h5read dx2 Eigen3::Eigen + pocketfft argparse standalone CUDA::cudart From f6875deeb0aa8e0657829d8c04a2b2b3b6865e05 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:48:15 +0000 Subject: [PATCH 16/72] Use new panel model from dx2 --- indexer/indexer.cc | 9 ++++++--- indexer/xyz_to_rlp.cc | 9 +++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 0a1a464..a3c5e2a 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -14,7 +14,7 @@ #include "fft3d.cc" #include #include -#include +#include #include #include #include @@ -74,8 +74,11 @@ int main(int argc, char **argv) { std::ofstream goniofile("test_gonio.json"); goniofile << gonio_out.dump(4);*/ - json detector_data = elist_json_obj["detector"][0]; - SimpleDetector detector(detector_data); + json panel_data = elist_json_obj["detector"][0]["panels"][0]; + Panel detector(panel_data); + json det_out = detector.to_json(); + std::ofstream detfile("test_detector.json"); + detfile << det_out.dump(4); //TODO // implement max cell/d_min estimation. - will need annlib if want same result as dials. diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index d6174d8..d05887d 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -11,7 +11,7 @@ using Eigen::Vector3d; std::vector xyz_to_rlp( const std::vector &xyzobs_px, - const SimpleDetector &detector, + const Panel &panel, const MonochromaticBeam &beam, const Scan &scan, const Goniometer &gonio) { @@ -29,20 +29,21 @@ std::vector xyz_to_rlp( Matrix3d setting_rotation_inverse = gonio.get_setting_rotation().inverse(); Matrix3d sample_rotation_inverse = gonio.get_sample_rotation().inverse(); Vector3d rotation_axis = gonio.get_rotation_axis(); + Matrix3d d_matrix = panel.get_d_matrix(); for (int i = 0; i < rlp.size(); ++i) { // first convert detector pixel positions into mm int vec_idx= 3*i; double x1 = xyzobs_px[vec_idx]; double x2 = xyzobs_px[vec_idx+1]; double x3 = xyzobs_px[vec_idx+2]; - std::array xymm = detector.px_to_mm(x1,x2); + std::array xymm = panel.px_to_mm(x1,x2); // convert the image 'z' coordinate to rotation angle based on the scan data double rot_angle = (((x3 + 1 - image_range_start) * osc_width) + osc_start) * DEG2RAD; // calculate the s1 vector using the detector d matrix Vector3d m = {xymm[0], xymm[1], 1.0}; - Vector3d s1_i = detector.d_matrix * m; + Vector3d s1_i = d_matrix * m; s1_i.normalize(); // convert into inverse ansgtroms Vector3d s1_this = s1_i / wl; From ae907727686e61bc94c88ef58ed2d1eaa774d2c4 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:06:27 +0000 Subject: [PATCH 17/72] Start basis vector combination code --- indexer/combinations.cc | 108 ++++++++++++++++++++++++++++++++++++++++ indexer/indexer.cc | 5 ++ 2 files changed, 113 insertions(+) create mode 100644 indexer/combinations.cc diff --git a/indexer/combinations.cc b/indexer/combinations.cc new file mode 100644 index 0000000..b486736 --- /dev/null +++ b/indexer/combinations.cc @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include + +using Eigen::Vector3d; +using Eigen::Vector3i; +using Eigen::Matrix3d; + +bool compare_comb(Vector3i a, Vector3i b){ + return a.squaredNorm() < b.squaredNorm(); // note can't use norm as get int truncation after std::sqrt. +} + +double angle_between_vectors_degrees2(Vector3d v1, Vector3d v2) { + double l1 = v1.norm(); + double l2 = v2.norm(); + double dot = v1.dot(v2); + double normdot = dot / (l1 * l2); + if (std::abs(normdot - 1.0) < 1E-6) { + return 0.0; + } + if (std::abs(normdot + 1.0) < 1E-6) { + return 180.0; + } + double angle = std::acos(normdot) * 180.0 / M_PI; + return angle; +} + +class CandidateOrientationMatrices { +public: + CandidateOrientationMatrices( + const std::vector& basis_vectors, int max_combinations = -1) + : max_combinations(max_combinations), index(0) { + n = basis_vectors.size(); + n = std::min(n, 100); + truncated_basis_vectors = {basis_vectors.begin(), basis_vectors.begin() + n}; + // now get combinations of + combinations.reserve(n*n*n/4); // could work this out exactly... + norms.reserve(n*n*n/4); + for (int i=0;i truncated_basis_vectors{}; + std::vector combinations{}; + std::vector truncated_combinations{}; + std::vector norms{}; + //flex::vec3_double::parts_type parts; + //flex::int_array i, j, k; + int n; + int max_combinations; + size_t index; + double half_pi; + double min_angle; +}; \ No newline at end of file diff --git a/indexer/indexer.cc b/indexer/indexer.cc index a3c5e2a..2333c43 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -12,6 +12,7 @@ #include "flood_fill.cc" #include "sites_to_vecs.cc" #include "fft3d.cc" +#include "combinations.cc" #include #include #include @@ -114,4 +115,8 @@ int main(int argc, char **argv) { std::chrono::duration elapsed_time = t2 - t1; std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; + CandidateOrientationMatrices candidates(candidate_vecs, 1000); + while (candidates.has_next()){ + Vector3i comb = candidates.next(); + } } \ No newline at end of file From 30e86ffa44bd4584c15f4b5f39f37c2c7c93be55 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:57:59 +0000 Subject: [PATCH 18/72] Add basis vector combinations and index assignment --- indexer/assign_indices.h | 122 +++++++++++++++++++++++++++++++++++++++ indexer/combinations.cc | 41 ++++++------- indexer/indexer.cc | 55 ++++++++++++++++-- indexer/sites_to_vecs.cc | 34 +++-------- 4 files changed, 199 insertions(+), 53 deletions(-) create mode 100644 indexer/assign_indices.h diff --git a/indexer/assign_indices.h b/indexer/assign_indices.h new file mode 100644 index 0000000..eff2709 --- /dev/null +++ b/indexer/assign_indices.h @@ -0,0 +1,122 @@ +#include +#include +#include + +using Eigen::Matrix3d; +using Eigen::Vector3d; +using Eigen::Vector3i; + +std::vector assign_indices_global(Matrix3d A, std::vector rlp, std::vector phi, double tolerance = 0.3){ + // Consider only a single lattice. + std::vector miller_indices(rlp.size()); + std::vector crystal_ids(rlp.size()); + std::vector lsq_vector(rlp.size()); + + // map of milleridx to + typedef std::multimap > hklmap; + + hklmap miller_idx_to_iref([](const Vector3i & a, const Vector3i & b)->bool + { + return std::lexicographical_compare( + a.data(),a.data()+a.size(), + b.data(),b.data()+b.size()); + }); + + //hklmap miller_idx_to_iref; + double pi_4 = M_PI / 4; + Vector3i miller_index_zero{{0, 0, 0}}; + Matrix3d A_inv = A.inverse(); + //std::cout << A_inv << std::endl; + double tolsq = std::pow(tolerance, 2); + for (int i=0;i tolsq){ + miller_indices[i] = {0,0,0}; + crystal_ids[i] = -1; + } + else if (miller_indices[i] == miller_index_zero){ + crystal_ids[i] = -1; + } + else { + miller_idx_to_iref.insert({miller_indices[i],i}); + lsq_vector[i] = l_sq; + } + } + // if more than one spot can be assigned the same miller index then + // choose the closest one + /*for (hklmap::iterator it = miller_idx_to_iref.begin(); it != miller_idx_to_iref.end(); it++){ + std::cout << it->first[0] << " " << it->first[1] << " " << it->first[2] << " " << it->second << std::endl; + }*/ + + Vector3i curr_hkl{{0, 0, 0}}; + std::vector i_same_hkl; + for (hklmap::iterator it = miller_idx_to_iref.begin(); it != miller_idx_to_iref.end(); it++){ + if (it->first != curr_hkl){ + if (i_same_hkl.size() > 1) { + for (int i = 0; i < i_same_hkl.size(); i++) { + const std::size_t i_ref = i_same_hkl[i]; + for (int j = i + 1; j < i_same_hkl.size(); j++) { + const std::size_t j_ref = i_same_hkl[j]; + double phi_i = phi[i_ref]; + double phi_j = phi[j_ref]; + if (std::abs(phi_i - phi_j) > pi_4) { + continue; + } + if (lsq_vector[i_ref] < lsq_vector[j_ref]){ + miller_indices[i_ref] = {0,0,0}; + crystal_ids[i_ref] = -1; + } + else { + miller_indices[j_ref] = {0,0,0}; + crystal_ids[j_ref] = -1; + } + } + } + } + curr_hkl = it->first; + i_same_hkl.clear(); + } + i_same_hkl.push_back(it->second); + } + + // Now do the final group! + if (i_same_hkl.size() > 1) { + for (int i = 0; i < i_same_hkl.size(); i++) { + const std::size_t i_ref = i_same_hkl[i]; + for (int j = i + 1; j < i_same_hkl.size(); j++) { + const std::size_t j_ref = i_same_hkl[j]; + double phi_i = phi[i_ref]; + double phi_j = phi[j_ref]; + if (std::abs(phi_i - phi_j) > pi_4) { + continue; + } + if (lsq_vector[i_ref] < lsq_vector[j_ref]){ + miller_indices[i_ref] = {0,0,0}; + crystal_ids[i_ref] = -1; + } + else { + miller_indices[j_ref] = {0,0,0}; + crystal_ids[j_ref] = -1; + } + } + } + } + + + return miller_indices; +} \ No newline at end of file diff --git a/indexer/combinations.cc b/indexer/combinations.cc index b486736..a666a81 100644 --- a/indexer/combinations.cc +++ b/indexer/combinations.cc @@ -3,6 +3,9 @@ #include #include #include +#include +#include "gemmi/unitcell.hpp" +#include using Eigen::Vector3d; using Eigen::Vector3i; @@ -12,21 +15,6 @@ bool compare_comb(Vector3i a, Vector3i b){ return a.squaredNorm() < b.squaredNorm(); // note can't use norm as get int truncation after std::sqrt. } -double angle_between_vectors_degrees2(Vector3d v1, Vector3d v2) { - double l1 = v1.norm(); - double l2 = v2.norm(); - double dot = v1.dot(v2); - double normdot = dot / (l1 * l2); - if (std::abs(normdot - 1.0) < 1E-6) { - return 0.0; - } - if (std::abs(normdot + 1.0) < 1E-6) { - return 180.0; - } - double angle = std::acos(normdot) * 180.0 / M_PI; - return angle; -} - class CandidateOrientationMatrices { public: CandidateOrientationMatrices( @@ -57,15 +45,15 @@ class CandidateOrientationMatrices { return index < truncated_combinations.size(); } - Vector3i next() { + Crystal next() { while (index < truncated_combinations.size()){ Vector3i comb = truncated_combinations[index]; Vector3d v1 = truncated_basis_vectors[comb[0]]; Vector3d v2 = truncated_basis_vectors[comb[1]]; index++; - double gamma = angle_between_vectors_degrees2(v1,v2); + double gamma = angle_between_vectors_degrees(v1,v2); if (gamma < min_angle || (180 - gamma) < min_angle) { - std::cout << "skipping comb " << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; + //std::cout << "skipping comb " << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; continue; } Vector3d crossprod = v1.cross(v2); @@ -74,11 +62,11 @@ class CandidateOrientationMatrices { crossprod = -crossprod; } Vector3d v3 = truncated_basis_vectors[comb[2]]; - if (std::abs(half_pi - angle_between_vectors_degrees2(crossprod, v3)) < min_angle){ - std::cout << "skipping comb " << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; + if (std::abs(half_pi - angle_between_vectors_degrees(crossprod, v3)) < min_angle){ + //std::cout << "skipping comb " << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; continue; } - double alpha = angle_between_vectors_degrees2(v2, v3); + double alpha = angle_between_vectors_degrees(v2, v3); if (alpha < half_pi){ v3 = -v3; } @@ -87,8 +75,15 @@ class CandidateOrientationMatrices { v2 = -v2; v3 = -v3; } - std::cout << "returning comb " << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; - return comb; + //std::cout << "returning comb " << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; + Crystal c{v1,v2,v3}; + c.niggli_reduce(); + gemmi::UnitCell cell = c.get_unit_cell(); + if (cell.volume > (cell.a * cell.b * cell.c / 100.0)){ + std::cout << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; + return c; + } + } throw std::out_of_range("No more combinations available"); } diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 2333c43..9964abc 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -12,12 +12,14 @@ #include "flood_fill.cc" #include "sites_to_vecs.cc" #include "fft3d.cc" +#include "assign_indices.h" #include "combinations.cc" #include #include #include #include #include +#include #include #include #include @@ -25,6 +27,7 @@ using Eigen::Vector3d; using Eigen::Matrix3d; +using Eigen::Vector3i; using json = nlohmann::json; int main(int argc, char **argv) { @@ -35,6 +38,10 @@ int main(int argc, char **argv) { .help("Path to the DIALS expt file"); parser.add_argument("-r", "--refl") .help("Path to the h5 reflection table file containing spotfinding results"); + parser.add_argument("--max-refine") + .help("Maximum number of crystal models to test") + .default_value(50) + .scan<'i', int>(); auto args = parser.parse_args(argc, argv); if (!parser.is_used("--expt")){ @@ -47,6 +54,7 @@ int main(int argc, char **argv) { } std::string imported_expt = parser.get("--expt"); std::string filename = parser.get("--refl"); + int max_refine = parser.get("max-refine"); //std::string imported_expt = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt"; std::ifstream f(imported_expt); @@ -91,6 +99,16 @@ int main(int argc, char **argv) { std::vector rlp = xyz_to_rlp(data, detector, beam, scan, gonio); + // compare against dials proc + /*std::string filename2 = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/index_rot.refl"; + std::string array_name2 = "/dials/processing/group_0/rlp"; + std::vector data2 = read_xyzobs_data(filename2, array_name2); + for (int i=0;i candidate_vecs = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); + CandidateOrientationMatrices candidates(candidate_vecs, 1000); + std::vector miller_indices; + // first extract phis + int image_range_start = scan.get_image_range()[0]; + std::vector phi(rlp.size()); + std::array oscillation = scan.get_oscillation(); + double osc_width = oscillation[1]; + double osc_start = oscillation[0]; + double DEG2RAD = M_PI / 180.0; + for (int i=0;i miller_indices = assign_indices_global(crystal.get_A_matrix(), rlp, phi); + int count = 0; + for (int i=0;i elapsed_time = t2 - t1; std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; - CandidateOrientationMatrices candidates(candidate_vecs, 1000); - while (candidates.has_next()){ - Vector3i comb = candidates.next(); - } } \ No newline at end of file diff --git a/indexer/sites_to_vecs.cc b/indexer/sites_to_vecs.cc index ebdbfa3..bf40937 100644 --- a/indexer/sites_to_vecs.cc +++ b/indexer/sites_to_vecs.cc @@ -5,6 +5,7 @@ #include #include #include +#include using Eigen::Vector3d; @@ -47,33 +48,14 @@ bool compare_site_data_volume(const SiteData& a, const SiteData& b) { return a.volume > b.volume; } -double vector_length(Vector3d v) { - return std::pow(std::pow(v[0], 2) + std::pow(v[1], 2) + std::pow(v[2], 2), 0.5); -} - -double angle_between_vectors_degrees(Vector3d v1, Vector3d v2) { - double l1 = vector_length(v1); - double l2 = vector_length(v2); - double dot = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; - double normdot = dot / (l1 * l2); - if (std::abs(normdot - 1.0) < 1E-6) { - return 0.0; - } - if (std::abs(normdot + 1.0) < 1E-6) { - return 180.0; - } - double angle = std::acos(normdot) * 180.0 / M_PI; - return angle; -} - bool is_approximate_integer_multiple(Vector3d v1, Vector3d v2, double relative_length_tolerance = 0.2, double angular_tolerance = 5.0) { double angle = angle_between_vectors_degrees(v1, v2); if ((angle < angular_tolerance) || (std::abs(180 - angle) < angular_tolerance)) { - double l1 = vector_length(v1); - double l2 = vector_length(v2); + double l1 = v1.norm(); + double l2 = v2.norm(); if (l1 > l2) { std::swap(l1, l2); } @@ -131,7 +113,7 @@ std::vector sites_to_vecs( double length = filtered_data[i].length; for (int j = 0; j < vector_groups.size(); j++) { Vector3d mean_v = vector_groups[j].mean(); - double mean_v_length = vector_length(mean_v); + double mean_v_length = mean_v.norm(); if ((std::abs(mean_v_length - length) / std::max(mean_v_length, length)) < relative_length_tolerance) { double angle = angle_between_vectors_degrees(mean_v, filtered_data[i].site); @@ -157,7 +139,7 @@ std::vector sites_to_vecs( Vector3d site = vector_groups[i].mean(); int max = *std::max_element(vector_groups[i].weights.begin(), vector_groups[i].weights.end()); - SiteData site_data = {site, vector_length(site), max}; + SiteData site_data = {site, site.norm(), max}; grouped_data.push_back(site_data); } std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data_volume); @@ -172,8 +154,8 @@ std::vector sites_to_vecs( for (int j = 0; j < unique_sites.size(); j++) { if (unique_sites[j].volume > grouped_data[i].volume) { if (is_approximate_integer_multiple(unique_sites[j].site, v)) { - std::cout << "rejecting " << vector_length(v) << ": is integer multiple of " - << vector_length(unique_sites[j].site) << std::endl; + std::cout << "rejecting " << v.norm() << ": is integer multiple of " + << unique_sites[j].site.norm() << std::endl; is_unique = false; break; } @@ -183,7 +165,7 @@ std::vector sites_to_vecs( // std::cout << v[0] << " " << v[1] << " " << v[2] << std::endl; // unique_vectors.push_back(v); // unique_volumes.push_back(grouped_data[i].volume); - SiteData site{v, vector_length(v), grouped_data[i].volume}; + SiteData site{v, v.norm(), grouped_data[i].volume}; unique_sites.push_back(site); } } From d2670d4997d36501f4f56e0326d77cf03c408cc2 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:41:57 +0000 Subject: [PATCH 19/72] Update and tidying --- indexer/combinations.cc | 2 +- indexer/indexer.cc | 39 +++++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/indexer/combinations.cc b/indexer/combinations.cc index a666a81..4d0c385 100644 --- a/indexer/combinations.cc +++ b/indexer/combinations.cc @@ -80,7 +80,7 @@ class CandidateOrientationMatrices { c.niggli_reduce(); gemmi::UnitCell cell = c.get_unit_cell(); if (cell.volume > (cell.a * cell.b * cell.c / 100.0)){ - std::cout << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; + std::cout << "Returning combination: " << comb[0] << "," << comb[1] << "," << comb[2] << std::endl; return c; } diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 9964abc..5c8b09f 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -99,16 +99,6 @@ int main(int argc, char **argv) { std::vector rlp = xyz_to_rlp(data, detector, beam, scan, gonio); - // compare against dials proc - /*std::string filename2 = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/index_rot.refl"; - std::string array_name2 = "/dials/processing/group_0/rlp"; - std::vector data2 = read_xyzobs_data(filename2, array_name2); - for (int i=0;i miller_indices; // first extract phis + // need to select on dmin, also only first 360 deg of scan. Do this earlier? int image_range_start = scan.get_image_range()[0]; - std::vector phi(rlp.size()); + std::vector phi_select(rlp.size()); + std::vector rlp_select(rlp.size()); std::array oscillation = scan.get_oscillation(); double osc_width = oscillation[1]; double osc_start = oscillation[0]; double DEG2RAD = M_PI / 180.0; - for (int i=0;i d_min){ + phi_select[selcount] = (((data[i*3+2] +1 - image_range_start)*osc_width) + osc_start) * DEG2RAD; + rlp_select[selcount] = rlp[i]; + selcount++; + } } + rlp_select.resize(selcount); + phi_select.resize(selcount); Vector3i null{{0,0,0}}; int n = 0; while (candidates.has_next() && n < max_refine){ Crystal crystal = candidates.next(); - std::cout << crystal.get_A_matrix() << std::endl; + //std::cout << crystal.get_A_matrix() << std::endl; n++; - std::vector miller_indices = assign_indices_global(crystal.get_A_matrix(), rlp, phi); + std::vector miller_indices = assign_indices_global(crystal.get_A_matrix(), rlp_select, phi_select); int count = 0; for (int i=0;i Date: Tue, 26 Nov 2024 12:12:12 +0000 Subject: [PATCH 20/72] Tidy up, add some extra arguments, output a simple experiment list --- indexer/combinations.cc | 4 +- indexer/indexer.cc | 93 ++++++++++++++++++++++++++++++++--------- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/indexer/combinations.cc b/indexer/combinations.cc index 4d0c385..2a36ddc 100644 --- a/indexer/combinations.cc +++ b/indexer/combinations.cc @@ -5,6 +5,7 @@ #include #include #include "gemmi/unitcell.hpp" +#include "gemmi/symmetry.hpp" #include using Eigen::Vector3d; @@ -76,7 +77,8 @@ class CandidateOrientationMatrices { v3 = -v3; } //std::cout << "returning comb " << comb[0] << " " << comb[1] << " " << comb[2] << std::endl; - Crystal c{v1,v2,v3}; + gemmi::SpaceGroup space_group = *gemmi::find_spacegroup_by_name("P1"); + Crystal c{v1,v2,v3, space_group}; c.niggli_reduce(); gemmi::UnitCell cell = c.get_unit_cell(); if (cell.volume > (cell.a * cell.b * cell.c / 100.0)){ diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 5c8b09f..5a3cf8b 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -30,6 +30,11 @@ using Eigen::Matrix3d; using Eigen::Vector3i; using json = nlohmann::json; +struct score_and_crystal { + double score; + Crystal crystal; +}; + int main(int argc, char **argv) { auto t1 = std::chrono::system_clock::now(); @@ -42,6 +47,13 @@ int main(int argc, char **argv) { .help("Maximum number of crystal models to test") .default_value(50) .scan<'i', int>(); + parser.add_argument("--dmin") + .help("Resolution limit") + .default_value(1.0) + .scan<'f', float>(); + parser.add_argument("--max-cell") + .help("The maxiumu cell length to try during indexing") + .scan<'f', float>(); auto args = parser.parse_args(argc, argv); if (!parser.is_used("--expt")){ @@ -52,9 +64,15 @@ int main(int argc, char **argv) { fmt::print("Error: must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); std::exit(1); } + if (!parser.is_used("--max-cell")){ + fmt::print("Error: must specify --max-cell\n"); + std::exit(1); + } std::string imported_expt = parser.get("--expt"); std::string filename = parser.get("--refl"); int max_refine = parser.get("max-refine"); + double max_cell = parser.get("max-cell"); + double d_min = parser.get("dmin"); //std::string imported_expt = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt"; std::ifstream f(imported_expt); @@ -66,34 +84,21 @@ int main(int argc, char **argv) { std::cerr << "Error: Unable to read " << imported_expt.c_str() << "; json parse error at byte " << ex.byte << std::endl; std::exit(1); } + + // Load the models json beam_data = elist_json_obj["beam"][0]; MonoXrayBeam beam(beam_data); - /*json beam_out = beam.to_json(); - std::ofstream file("test_beam.json"); - file << beam_out.dump(4);*/ json scan_data = elist_json_obj["scan"][0]; Scan scan(scan_data); - /*json scan_out = scan.to_json(); - std::ofstream scanfile("test_scan.json"); - scanfile << scan_out.dump(4);*/ - json gonio_data = elist_json_obj["goniometer"][0]; Goniometer gonio(gonio_data); - /*json gonio_out = gonio.to_json(); - std::ofstream goniofile("test_gonio.json"); - goniofile << gonio_out.dump(4);*/ - json panel_data = elist_json_obj["detector"][0]["panels"][0]; Panel detector(panel_data); - json det_out = detector.to_json(); - std::ofstream detfile("test_detector.json"); - detfile << det_out.dump(4); //TODO // implement max cell/d_min estimation. - will need annlib if want same result as dials. // get processed reflection data from spotfinding - //std::string filename = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl"; std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; std::vector data = read_xyzobs_data(filename, array_name); @@ -101,9 +106,11 @@ int main(int argc, char **argv) { std::cout << "Number of reflections: " << rlp.size() << std::endl; - double d_min = 1.31; + //double d_min = 1.31; + //double d_min = 1.84; double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); - double max_cell = 33.8; + //double max_cell = 33.8; + //double max_cell = 94.4; std::cout << "Setting b_iso =" << b_iso << std::endl; std::vector real_fft(256*256*256, 0.0); @@ -142,12 +149,19 @@ int main(int argc, char **argv) { phi_select.resize(selcount); Vector3i null{{0,0,0}}; int n = 0; + + // iterate over candidates; assign indices, refine, score. + // need a map of scores for candidates: index to score and xtal. What about miller indices? + + std::map results_map; + while (candidates.has_next() && n < max_refine){ Crystal crystal = candidates.next(); - //std::cout << crystal.get_A_matrix() << std::endl; n++; std::vector miller_indices = assign_indices_global(crystal.get_A_matrix(), rlp_select, phi_select); + + // for debugging, let's count the number of nonzero miller indices int count = 0; for (int i=0;i {expt_out}; + elist_out["crystal"] = std::array {cryst_out}; + elist_out["scan"] = std::array {scan.to_json()}; + elist_out["goniometer"] = std::array {gonio.to_json()}; + elist_out["beam"] = std::array {beam.to_json()}; + elist_out["detector"] = std::array {detector.to_json()}; + + std::ofstream efile("elist.json"); + efile << elist_out.dump(4); + auto t2 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time = t2 - t1; From 3006891b0aa04f824238197e86602be467f2d56e Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:49:03 +0000 Subject: [PATCH 21/72] Add some prediction and calculating reflection properties --- indexer/indexer.cc | 102 +++++++++++- indexer/reflection_data.h | 46 ++++++ indexer/refman_filter.cc | 291 +++++++++++++++++++++++++++++++++ indexer/scanstaticpredictor.cc | 158 ++++++++++++++++++ indexer/xyz_to_rlp.cc | 1 + 5 files changed, 595 insertions(+), 3 deletions(-) create mode 100644 indexer/reflection_data.h create mode 100644 indexer/refman_filter.cc create mode 100644 indexer/scanstaticpredictor.cc diff --git a/indexer/indexer.cc b/indexer/indexer.cc index 5a3cf8b..b50ad47 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -13,6 +13,8 @@ #include "sites_to_vecs.cc" #include "fft3d.cc" #include "assign_indices.h" +#include "reflection_data.h" +#include "scanstaticpredictor.cc" #include "combinations.cc" #include #include @@ -24,6 +26,7 @@ #include #include #include +#include "refman_filter.cc" using Eigen::Vector3d; using Eigen::Matrix3d; @@ -100,9 +103,9 @@ int main(int argc, char **argv) { // get processed reflection data from spotfinding std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; - std::vector data = read_xyzobs_data(filename, array_name); + std::vector xyzobs_px = read_xyzobs_data(filename, array_name); - std::vector rlp = xyz_to_rlp(data, detector, beam, scan, gonio); + std::vector rlp = xyz_to_rlp(xyzobs_px, detector, beam, scan, gonio); std::cout << "Number of reflections: " << rlp.size() << std::endl; @@ -140,7 +143,7 @@ int main(int argc, char **argv) { int selcount=0; for (int i=0;i d_min){ - phi_select[selcount] = (((data[i*3+2] +1 - image_range_start)*osc_width) + osc_start) * DEG2RAD; + phi_select[selcount] = (((xyzobs_px[i*3+2] +1 - image_range_start)*osc_width) + osc_start) * DEG2RAD; rlp_select[selcount] = rlp[i]; selcount++; } @@ -171,6 +174,99 @@ int main(int argc, char **argv) { } std::cout << count << " nonzero miller indices" << std::endl; + // Would do some refine here. + // For now, pretend we have refined and calculate the rmsd for the model as an example + + Vector3d s0 = beam.get_s0(); + Vector3d axis = gonio.get_rotation_axis(); + Matrix3d d_matrix = detector.get_d_matrix(); + std::array oscillation = scan.get_oscillation(); + double osc_width = oscillation[1]; + double osc_start = oscillation[0]; + int image_range_start = scan.get_image_range()[0]; + double DEG2RAD = M_PI / 180.0; + + // calculate s1 + std::vector s1(rlp_select.size()); + std::vector xyzobs_mm(rlp_select.size()); + std::vector xyzcal_mm(rlp_select.size()); + for (int i = 0; i < rlp_select.size(); ++i) { + int vec_idx= 3*i; + double x1 = xyzobs_px[vec_idx]; + double x2 = xyzobs_px[vec_idx+1]; + double x3 = xyzobs_px[vec_idx+2]; + std::array xymm = detector.px_to_mm(x1,x2); + double rot_angle = (((x3 + 1 - image_range_start) * osc_width) + osc_start) * DEG2RAD; + Vector3d m = {xymm[0], xymm[1], 1.0}; + s1[i] = d_matrix * m; + xyzobs_mm[i] = {xymm[0], xymm[1], rot_angle}; + } + + // calculate entering array + /*std::vector enterings(rlp_select.size()); + Vector3d vec = s0.cross(axis); + for (int i=0;i flags(rlp_select.size()); + std::vector entering(rlp_select.size()); + //std::vector xyzcal_mm(rlp_select.size()); + reflection_data obs; + obs.miller_indices = miller_indices; + obs.flags = flags; + obs.xyzobs_mm = xyzobs_mm; + obs.xyzcal_mm = xyzcal_mm; + obs.s1 = s1; + obs.entering = entering; + + int n_images = scan.get_image_range()[1] - scan.get_image_range()[0] + 1; + double width = scan.get_oscillation()[0] + (scan.get_oscillation()[1] * n_images); + reflection_data sel_obs = reflection_filter_preevaluation( + obs, gonio, crystal, beam, detector, width, 20 + ); + + // now predict + /*simple_reflection_predictor( + beam, gonio, crystal.get_A_matrix(), detector, + s1, xyzobs_mm, enterings, flags, xyzcal_mm, miller_indices + ); + std::cout << xyzcal_mm[0][0] << " " << xyzcal_mm[0][1] << " " << xyzcal_mm[0][2] < 4.0){ + std::cout << i << " dx " << xyzobs[0] << " " << xyzcal[0] << std::endl; + } + if (dy > 4.0){ + std::cout << i << " dy " << xyzobs[1] << " " << xyzcal[1] << std::endl; + } + xsum += dx; + ysum += dy; + zsum += std::pow(xyzobs[2] - xyzcal[2],2); + } + double rmsdx = std::pow(xsum / sel_obs.xyzcal_mm.size(), 0.5); + double rmsdy = std::pow(ysum / sel_obs.xyzcal_mm.size(), 0.5); + double rmsdz = std::pow(zsum / sel_obs.xyzcal_mm.size(), 0.5); + double xyrmsd = std::pow(std::pow(rmsdx, 2)+std::pow(rmsdy, 2), 0.5); + std::cout << rmsdx << " < +#include +using Eigen::Vector3d; +using Eigen::Vector3i; + +struct reflection_data { + std::vector flags; + std::vector xyzobs_mm; + std::vector xyzcal_mm; + std::vector s1; + std::vector miller_indices; + std::vector entering; +}; + +reflection_data select(reflection_data data, std::vector sel){ + reflection_data selected; + for (int i=0;i sel){ + reflection_data selected; + for (int i=0;i +#include +#include +#include "scanstaticpredictor.cc" +#include +#include +#include +#include +#include +#include "reflection_data.h" +// select on id before here. +using Eigen::Vector3d; +using Eigen::Matrix3d; +using Eigen::Vector3i; + + +std::vector random_selection(int pop_size, int sample_size, int seed=43){ + std::mt19937 mt(seed); + // get random selection up to pop size, then cut down to sample size + // then sort + std::vector result(pop_size); + std::vector::iterator r = result.begin(); + for (size_t i=0;i(mt()) % pop_size; + std::swap(r[i], r[j]); + } + result.resize(sample_size); + std::sort(result.begin(), result.end()); + return result; +} + + + + + +std::vector simple_tukey(std::vector xresid, std::vector yresid, std::vectorphi_resid){ + std::vector sel {};//(xresid.size(), true); + double iqr_multiplier = 3.0; + std::cout << xresid.size() << " xresid size" << std::endl; + std::vector xresid_unsorted(xresid.begin(), xresid.end()); + std::vector yresid_unsorted(yresid.begin(), yresid.end()); + std::vector phi_resid_unsorted(phi_resid.begin(), phi_resid.end()); + std::sort(xresid.begin(), xresid.end()); + std::sort(yresid.begin(), yresid.end()); + std::sort(phi_resid.begin(), phi_resid.end()); + + // this is the way scitbx.math.five_number_summary does iqr + int n_lower=0; + double Q1x=0.0; + double Q1y=0.0; + double Q1p=0.0; + double Q3x=0.0; + double Q3y=0.0; + double Q3p=0.0; + int inc = 0; // an overlap offset if the number of items is odd. + if (xresid.size() % 2){ + n_lower = (xresid.size() / 2) + 1; + inc = -1; + } + else { + n_lower = xresid.size() / 2; + } + if (n_lower % 2){ + // FIXME verify this branch correct + Q1x = xresid[n_lower / 2]; + Q1y = yresid[n_lower / 2]; + Q1p = phi_resid[n_lower / 2]; + Q3x = xresid[n_lower + 1 + (n_lower/ 2)]; + Q3y = yresid[n_lower + 1 + (n_lower/ 2)]; + Q3p = phi_resid[n_lower + 1 + (n_lower/ 2)]; + } + else { + Q1x = (xresid[n_lower / 2] + xresid[(n_lower / 2) -1]) / 2.0; + Q1y = (yresid[n_lower / 2] + yresid[(n_lower / 2) -1]) / 2.0; + Q1p = (phi_resid[n_lower / 2] + phi_resid[(n_lower / 2) -1]) / 2.0; + Q3x = (xresid[n_lower + inc+(n_lower / 2)] + xresid[n_lower +inc+(n_lower / 2) -1]) / 2.0; + Q3y = (yresid[n_lower +inc+(n_lower / 2)] + yresid[n_lower +inc+(n_lower / 2) -1]) / 2.0; + Q3p = (phi_resid[n_lower +inc+(n_lower / 2)] + phi_resid[n_lower +inc+(n_lower / 2) -1]) / 2.0; + } + double iqrx = Q3x - Q1x; + double uppercutx = (iqrx * iqr_multiplier) + Q3x; + double lowercutx = Q1x -(iqrx * iqr_multiplier); + + double iqry = Q3y - Q1y; + double uppercuty = (iqry * iqr_multiplier) + Q3y; + double lowercuty = Q1y -(iqry * iqr_multiplier); + + double iqrp = Q3p - Q1p; + double uppercutp = (iqrp * iqr_multiplier) + Q3p; + double lowercutp = Q1p -(iqrp * iqr_multiplier); + + for (int i=0;i uppercutx){ + sel.push_back(i); + //sel[i] = false; + } + else if (xresid_unsorted[i] < lowercutx){ + sel.push_back(i); + //sel[i] = false; + } + else if (yresid_unsorted[i] > uppercuty){ + sel.push_back(i); + //sel[i] = false; + } + else if (yresid_unsorted[i] < lowercuty){ + sel.push_back(i); + //sel[i] = false; + } + else if (phi_resid_unsorted[i] > uppercutp){ + sel.push_back(i); + //sel[i] = false; + } + else if (phi_resid_unsorted[i] < lowercutp){ + sel.push_back(i); + //sel[i] = false; + } + } + return sel; +} + +reflection_data outlier_filter(reflection_data &reflections){ + // filter on predicted + std::cout << "start outlier filter " << reflections.flags.size() << std::endl; + std::vector flags = reflections.flags; + std::vector xyzobs = reflections.xyzobs_mm; + std::vector xyzcal = reflections.xyzcal_mm; + std::vector x_resid(reflections.flags.size(), 0.0); + std::vector y_resid(reflections.flags.size(), 0.0); + std::vector phi_resid(reflections.flags.size(), 0.0); + std::vector sel {}; + std::cout << x_resid.size() << std::endl; + + size_t predicted_value = (1 << 0); //predicted flag + for (int i=0;i x_resid_sel(sel.size()); + std::vector y_resid_sel(sel.size()); + std::vector phi_resid_sel(sel.size()); + for (int i=0;i outlier_isel = simple_tukey(x_resid_sel, y_resid_sel, phi_resid_sel); + // get indices of good, then loop over good indics from first. + std::vector good_sel(x_resid_sel.size(), true); + for (int i=0;i final_sel {}; + for (int i=0;i subflags = subrefls.flags; + + // unset centroid outlier flag (necessary?) + // set used_in_refinement + size_t used_in_refinement_value = (1 << 3); //used in refinement flag + size_t centroid_outlier_value = (1 << 17); //ucentroid outlier flag + for (int i=0;i flags = reflections.flags; + std::vector hkl = reflections.miller_indices; + std::vector s1 = reflections.s1; + //std::vector id = reflections["id"]; + std::vector xyzobs = reflections.xyzobs_mm; + Vector3d axis = gonio.get_rotation_axis(); + Vector3d s0 = beam.get_s0(); + + // get flags ('overloaded') + size_t overloaded_value = (1 << 10); //overloaded flag + std::vector sel(flags.size(), true); + Vector3i null = {0,0,0}; + for (int i=0;i enterings(subrefls.flags.size(), false); + // calculate the entering column - Move to one of above loops? + std::vector s1_sub = subrefls.s1; + Vector3d vec = s0.cross(axis); + for (int i=0;i sel = random_selection(nrefs, sample_size); + reflection_data sel_obs = select(obs, sel); + return sel_obs; + } + return obs; +} + +reflection_data reflection_filter_preevaluation( + reflection_data &obs, + const Goniometer &gonio, + const Crystal &crystal, + const MonochromaticBeam &beam, + const Panel &panel, + double scan_width_degrees, + int n_ref_per_degree=100, + double close_to_spindle_cutoff=0.02, + int min_sample_size=1000, + int max_sample_size=0 + ){ + reflection_data filter_obs = initial_refman_filter(obs, gonio, beam, close_to_spindle_cutoff); + std::cout << "initital filter " << filter_obs.flags.size() << std::endl; + Matrix3d UB = crystal.get_A_matrix(); + simple_reflection_predictor( + beam, + gonio, + UB, + panel, + filter_obs + ); + filter_obs = outlier_filter(filter_obs); + std::cout << "outlier filter " << filter_obs.flags.size() << std::endl; + reflection_data sel_obs = select_sample(filter_obs, n_ref_per_degree, scan_width_degrees, min_sample_size, max_sample_size); + std::cout << sel_obs.flags.size() << std::endl; + return sel_obs; +} diff --git a/indexer/scanstaticpredictor.cc b/indexer/scanstaticpredictor.cc new file mode 100644 index 0000000..a37363b --- /dev/null +++ b/indexer/scanstaticpredictor.cc @@ -0,0 +1,158 @@ +#ifndef DIALS_STATIC_PREDICTOR +#define DIALS_STATIC_PREDICTOR +#include +#include +#include +#include +#include +#include +#include +#include "reflection_data.h" +const double two_pi = 2 * M_PI; + +using Eigen::Matrix3d; +using Eigen::Vector3d; +using Eigen::Vector3i; + +inline double mod2pi(double angle) { + // E.g. treat 359.9999999 as 360 + if (std::abs(angle - two_pi) <= 1e-7) { + angle = two_pi; + } + return angle - two_pi * floor(angle / two_pi); +} + +Vector3d unit_rotate_around_origin(Vector3d vec, Vector3d unit, double angle){ + double cosang = std::cos(angle); + Vector3d res = vec*cosang + (unit*(unit.dot(vec)) * (1.0-cosang)) + (unit.cross(vec)*std::sin(angle)); + return res; +} + +// actually a repredictor, assumes all successful. +void simple_reflection_predictor( + const MonochromaticBeam beam, + const Goniometer gonio, + //const Vector3d s0,//beam s0 + //const Matrix3d F,//fixed rot + //const Matrix3d S,//setting rot + //const Vector3d R, //get_rotation_axis_datum + const Matrix3d UB, + const Panel &panel, + reflection_data &reflections + //const int image_range_start, + //const double osc_start, + //const double osc_width +){ + std::vector s1 = reflections.s1; + const std::vector xyzobs_mm = reflections.xyzobs_mm; + const std::vector entering = reflections.entering; + std::vector flags = reflections.flags; + std::vector xyzcal_mm = reflections.xyzcal_mm; + const std::vector hkl = reflections.miller_indices; + // these setup bits are the same for all refls. + Vector3d s0 = beam.get_s0(); + Matrix3d F = gonio.get_sample_rotation();//fixed rot + Matrix3d S = gonio.get_setting_rotation();//setting rot + Vector3d R = gonio.get_rotation_axis(); + Vector3d s0_ = S.inverse() * s0; + Matrix3d FUB = F * UB; + Vector3d m2 = R / R.norm(); + //Vector3d m2 = R.normalize();//fixed during refine + Vector3d s0_m2_plane = s0.cross(S * R); + s0_m2_plane.normalize(); + + Vector3d m1 = m2.cross(s0_); + m1.normalize(); //vary with s0 + Vector3d m3 = m1.cross(m2); + m3.normalize(); //vary with s0 + double s0_d_m2 = s0_.dot(m2); + double s0_d_m3 = s0_.dot(m3); + + /*std::vector xyzcalmm = obs["xyzcal.mm"]; + std::vector s1_all = obs["s1"]; + std::vector entering = obs["entering"]; + std::vector xyzobs = obs["xyzobs.mm.value"]; + std::vector hkl = obs["miller_index"]; + std::vector flags = obs["flags"];*/ + size_t predicted_value = (1 << 0); //predicted flag + // now call predict_rays with h and UB for a given refl + for (int i=0;i 4 * s0_.squaredNorm()){ + flags[i] = flags[i] & ~predicted_value; + continue; + } + double pstar0_d_m1 = pstar0.dot(m1); + double pstar0_d_m2 = pstar0.dot(m2); + double pstar0_d_m3 = pstar0.dot(m3); + double pstar_d_m3 = (-(0.5 * pstar0_len_sq) - (pstar0_d_m2 * s0_d_m2)) / s0_d_m3; + double rho_sq = (pstar0_len_sq - (pstar0_d_m2*pstar0_d_m2)); + double psq = pstar_d_m3*pstar_d_m3; + if (rho_sq < psq){ + flags[i] = flags[i] & ~predicted_value; + continue; + } + //DIALS_ASSERT(rho_sq >= sqr(pstar_d_m3)); + double pstar_d_m1 = sqrt(rho_sq - (psq)); + double p1 = pstar_d_m1 * pstar0_d_m1; + double p2 = pstar_d_m3 * pstar0_d_m3; + double p3 = pstar_d_m1 * pstar0_d_m3; + double p4 = pstar_d_m3 * pstar0_d_m1; + + double cosphi1 = p1 + p2; + double sinphi1 = p3 - p4; + double a1 = atan2(sinphi1, cosphi1); + // ASSERT must be in range? is_angle_in_range + + // check each angle + Vector3d pstar = S * unit_rotate_around_origin(pstar0, m2, a1); + Vector3d s1_this = s0_ + pstar; + bool this_entering = s1_this.dot(s0_m2_plane) < 0.; + double angle; + if (this_entering == entering_i){ + // use this s1 and a1 (mod 2pi) + angle = mod2pi(a1); + } + else { + double cosphi2 = -p1 + p2; + double sinphi2 = -p3 - p4; + double a2 = atan2(sinphi2, cosphi2); + pstar = S * unit_rotate_around_origin(pstar0, m2, a2); + s1_this = s0_ + pstar; + this_entering = s1_this.dot(s0_m2_plane) < 0.; + assert(this_entering == entering_i); + angle = mod2pi(a2); + } + + // only need frame if calculating xyzcalpx, but not needed for evaluation + //double frame = image_range_start + ((angle - osc_start) / osc_width) - 1; + //Vector3d v = D * s1; + std::array mm = panel.get_ray_intersection(s1_this); //v = D * s1; v[0]/v[2], v[1]/v[2] + //scitbx::vec2 px = Detector[0].millimeter_to_pixel(mm); // requires call to parallax corr + + // match full turns + double phiobs = xyzobs_mm[i][2]; + // first fmod positive + double val = std::fmod(phiobs, two_pi); + while (val < 0) val += two_pi; + double resid = angle - val; + // second fmod positive + double val2 = std::fmod(resid+M_PI, two_pi); + while (val2 < 0) val2 += two_pi; + val2 -= M_PI; + + xyzcal_mm[i] = {mm[0], mm[1], phiobs + val2}; + //xyzcalpx[i] = {px[0], px[1], frame}; + s1[i] = s1_this; + flags[i] = flags[i] | predicted_value; + } + reflections.flags = flags; + reflections.xyzcal_mm = xyzcal_mm; +} + +#endif // DIALS_STATIC_PREDICTOR \ No newline at end of file diff --git a/indexer/xyz_to_rlp.cc b/indexer/xyz_to_rlp.cc index d05887d..f146a16 100644 --- a/indexer/xyz_to_rlp.cc +++ b/indexer/xyz_to_rlp.cc @@ -5,6 +5,7 @@ #include #include #include +#include using Eigen::Matrix3d; using Eigen::Vector3d; From 511727196004792314e34ee097c9fe5b670a5cde Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Thu, 28 Nov 2024 09:45:20 +0000 Subject: [PATCH 22/72] Add better results output for candidates --- h5read/include/h5read.h | 1 + h5read/src/h5read_processed.cc | 28 ++++++ indexer/indexer.cc | 173 +++++++++++++++++++-------------- indexer/refman_filter.cc | 11 +-- 4 files changed, 128 insertions(+), 85 deletions(-) diff --git a/h5read/include/h5read.h b/h5read/include/h5read.h index 2865ec1..7d33ab4 100644 --- a/h5read/include/h5read.h +++ b/h5read/include/h5read.h @@ -107,6 +107,7 @@ h5read_handle *h5read_parse_standard_args(int argc, char **argv); #include std::vector read_xyzobs_data(std::string filename, std::string array_name); +std::vector read_flags_data(std::string filename, std::string array_name); class Image { private: diff --git a/h5read/src/h5read_processed.cc b/h5read/src/h5read_processed.cc index e8402b1..63bdcbe 100644 --- a/h5read/src/h5read_processed.cc +++ b/h5read/src/h5read_processed.cc @@ -36,3 +36,31 @@ std::vector read_xyzobs_data(string filename, string array_name){ return data_out; } +std::vector read_flags_data(string filename, string array_name){ + auto start_time = std::chrono::high_resolution_clock::now(); + hid_t file = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); + if (file < 0){ + std::cout << "Error: Unable to open " << filename.c_str() << " as a hdf5 reflection table" < data_out(num_elements); + + H5Dread(dataset, datatype, H5S_ALL, space, H5P_DEFAULT, &data_out[0]); + float total_time = + std::chrono::duration_cast>( + std::chrono::high_resolution_clock::now() - start_time) + .count(); + std::cout << "phi READ TIME " << total_time << "s" << std::endl; + + H5Dclose(dataset); + H5Fclose(file); + return data_out; +} + diff --git a/indexer/indexer.cc b/indexer/indexer.cc index b50ad47..b4dd696 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -36,6 +36,8 @@ using json = nlohmann::json; struct score_and_crystal { double score; Crystal crystal; + double num_indexed; + double rmsdxy; }; int main(int argc, char **argv) { @@ -104,9 +106,49 @@ int main(int argc, char **argv) { // get processed reflection data from spotfinding std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; std::vector xyzobs_px = read_xyzobs_data(filename, array_name); + std::string flags_array_name = "/dials/processing/group_0/flags"; + std::vector flags = read_flags_data(filename, flags_array_name); std::vector rlp = xyz_to_rlp(xyzobs_px, detector, beam, scan, gonio); + // some more setup - entering flags and s1 + //self.reflections.centroid_px_to_mm(self.experiments) + //self.reflections.map_centroids_to_reciprocal_space(self.experiments) + //self.reflections.calculate_entering_flags(self.experiments) + Vector3d s0 = beam.get_s0(); + Vector3d axis = gonio.get_rotation_axis(); + Matrix3d d_matrix = detector.get_d_matrix(); + std::array oscillation = scan.get_oscillation(); + double osc_width = oscillation[1]; + double osc_start = oscillation[0]; + int image_range_start = scan.get_image_range()[0]; + double DEG2RAD = M_PI / 180.0; + + // calculate s1 and xyzobsmm + std::vector s1(rlp.size()); + std::vector xyzobs_mm(rlp.size()); + std::vector xyzcal_mm(rlp.size()); + std::vector phi(rlp.size()); + for (int i = 0; i < rlp.size(); ++i) { + int vec_idx= 3*i; + double x1 = xyzobs_px[vec_idx]; + double x2 = xyzobs_px[vec_idx+1]; + double x3 = xyzobs_px[vec_idx+2]; + std::array xymm = detector.px_to_mm(x1,x2); + double rot_angle = (((x3 + 1 - image_range_start) * osc_width) + osc_start) * DEG2RAD; + phi[i] = rot_angle; + Vector3d m = {xymm[0], xymm[1], 1.0}; + s1[i] = d_matrix * m; + xyzobs_mm[i] = {xymm[0], xymm[1], rot_angle}; + } + + // calculate entering array + std::vector enterings(rlp.size()); + Vector3d vec = s0.cross(axis); + for (int i=0;i miller_indices; // first extract phis // need to select on dmin, also only first 360 deg of scan. Do this earlier? - int image_range_start = scan.get_image_range()[0]; + /*int image_range_start = scan.get_image_range()[0]; std::vector phi_select(rlp.size()); std::vector rlp_select(rlp.size()); std::array oscillation = scan.get_oscillation(); double osc_width = oscillation[1]; double osc_start = oscillation[0]; - double DEG2RAD = M_PI / 180.0; + double DEG2RAD = M_PI / 180.0;*/ + + // Fix this inefficient selection with reflection-table-like struct. + std::vector phi_select(rlp.size()); + std::vector rlp_select(rlp.size()); + std::vector flags_select(rlp.size()); + std::vector xyzobs_mm_select(rlp.size()); + std::vector xyzcal_mm_select(rlp.size()); + std::vector s1_select(rlp.size()); + std::vector entering_select(rlp.size()); int selcount=0; + // also select flags, xyzobs/cal, s1 and enterings for (int i=0;i d_min){ - phi_select[selcount] = (((xyzobs_px[i*3+2] +1 - image_range_start)*osc_width) + osc_start) * DEG2RAD; + phi_select[selcount] = phi[i]; rlp_select[selcount] = rlp[i]; + flags_select[selcount] = flags[i]; + xyzobs_mm_select[selcount] = xyzobs_mm[i]; + xyzcal_mm_select[selcount] = xyzcal_mm[i]; + s1_select[selcount] = s1[i]; + entering_select[selcount] = enterings[i]; selcount++; } } rlp_select.resize(selcount); phi_select.resize(selcount); + flags_select.resize(selcount); + xyzobs_mm_select.resize(selcount); + xyzcal_mm_select.resize(selcount); + s1_select.resize(selcount); + entering_select.resize(selcount); Vector3i null{{0,0,0}}; int n = 0; @@ -161,7 +223,6 @@ int main(int argc, char **argv) { while (candidates.has_next() && n < max_refine){ Crystal crystal = candidates.next(); n++; - std::vector miller_indices = assign_indices_global(crystal.get_A_matrix(), rlp_select, phi_select); // for debugging, let's count the number of nonzero miller indices @@ -172,99 +233,47 @@ int main(int argc, char **argv) { //std::cout << i << ": " << miller_indices[i][0] << " " << miller_indices[i][1] << " " << miller_indices[i][2] << std::endl; } } - std::cout << count << " nonzero miller indices" << std::endl; - - // Would do some refine here. - // For now, pretend we have refined and calculate the rmsd for the model as an example - - Vector3d s0 = beam.get_s0(); - Vector3d axis = gonio.get_rotation_axis(); - Matrix3d d_matrix = detector.get_d_matrix(); - std::array oscillation = scan.get_oscillation(); - double osc_width = oscillation[1]; - double osc_start = oscillation[0]; - int image_range_start = scan.get_image_range()[0]; - double DEG2RAD = M_PI / 180.0; + //std::cout << count << " nonzero miller indices" << std::endl; - // calculate s1 - std::vector s1(rlp_select.size()); - std::vector xyzobs_mm(rlp_select.size()); - std::vector xyzcal_mm(rlp_select.size()); - for (int i = 0; i < rlp_select.size(); ++i) { - int vec_idx= 3*i; - double x1 = xyzobs_px[vec_idx]; - double x2 = xyzobs_px[vec_idx+1]; - double x3 = xyzobs_px[vec_idx+2]; - std::array xymm = detector.px_to_mm(x1,x2); - double rot_angle = (((x3 + 1 - image_range_start) * osc_width) + osc_start) * DEG2RAD; - Vector3d m = {xymm[0], xymm[1], 1.0}; - s1[i] = d_matrix * m; - xyzobs_mm[i] = {xymm[0], xymm[1], rot_angle}; - } - - // calculate entering array - /*std::vector enterings(rlp_select.size()); - Vector3d vec = s0.cross(axis); - for (int i=0;i flags(rlp_select.size()); - std::vector entering(rlp_select.size()); - //std::vector xyzcal_mm(rlp_select.size()); + // make a reflection table like object reflection_data obs; obs.miller_indices = miller_indices; - obs.flags = flags; - obs.xyzobs_mm = xyzobs_mm; - obs.xyzcal_mm = xyzcal_mm; - obs.s1 = s1; - obs.entering = entering; + obs.flags = flags_select; + obs.xyzobs_mm = xyzobs_mm_select; + obs.xyzcal_mm = xyzcal_mm_select; + obs.s1 = s1_select; + obs.entering = entering_select; int n_images = scan.get_image_range()[1] - scan.get_image_range()[0] + 1; double width = scan.get_oscillation()[0] + (scan.get_oscillation()[1] * n_images); + + // get a filtered selection for refinement reflection_data sel_obs = reflection_filter_preevaluation( obs, gonio, crystal, beam, detector, width, 20 ); - // now predict - /*simple_reflection_predictor( - beam, gonio, crystal.get_A_matrix(), detector, - s1, xyzobs_mm, enterings, flags, xyzcal_mm, miller_indices - ); - std::cout << xyzcal_mm[0][0] << " " << xyzcal_mm[0][1] << " " << xyzcal_mm[0][2] < 4.0){ - std::cout << i << " dx " << xyzobs[0] << " " << xyzcal[0] << std::endl; - } - if (dy > 4.0){ - std::cout << i << " dy " << xyzobs[1] << " " << xyzcal[1] << std::endl; - } - xsum += dx; - ysum += dy; + xsum += std::pow(xyzobs[0] - xyzcal[0],2); + ysum += std::pow(xyzobs[1] - xyzcal[1],2); zsum += std::pow(xyzobs[2] - xyzcal[2],2); } double rmsdx = std::pow(xsum / sel_obs.xyzcal_mm.size(), 0.5); double rmsdy = std::pow(ysum / sel_obs.xyzcal_mm.size(), 0.5); double rmsdz = std::pow(zsum / sel_obs.xyzcal_mm.size(), 0.5); double xyrmsd = std::pow(std::pow(rmsdx, 2)+std::pow(rmsdy, 2), 0.5); - std::cout << rmsdx << " < random_selection(int pop_size, int sample_size, int see std::vector simple_tukey(std::vector xresid, std::vector yresid, std::vectorphi_resid){ std::vector sel {};//(xresid.size(), true); double iqr_multiplier = 3.0; - std::cout << xresid.size() << " xresid size" << std::endl; std::vector xresid_unsorted(xresid.begin(), xresid.end()); std::vector yresid_unsorted(yresid.begin(), yresid.end()); std::vector phi_resid_unsorted(phi_resid.begin(), phi_resid.end()); @@ -124,7 +123,6 @@ std::vector simple_tukey(std::vector xresid, std::vector reflection_data outlier_filter(reflection_data &reflections){ // filter on predicted - std::cout << "start outlier filter " << reflections.flags.size() << std::endl; std::vector flags = reflections.flags; std::vector xyzobs = reflections.xyzobs_mm; std::vector xyzcal = reflections.xyzcal_mm; @@ -132,8 +130,7 @@ reflection_data outlier_filter(reflection_data &reflections){ std::vector y_resid(reflections.flags.size(), 0.0); std::vector phi_resid(reflections.flags.size(), 0.0); std::vector sel {}; - std::cout << x_resid.size() << std::endl; - + size_t predicted_value = (1 << 0); //predicted flag for (int i=0;i outlier_isel = simple_tukey(x_resid_sel, y_resid_sel, phi_resid_sel); // get indices of good, then loop over good indics from first. std::vector good_sel(x_resid_sel.size(), true); @@ -169,9 +165,7 @@ reflection_data outlier_filter(reflection_data &reflections){ final_sel.push_back(sel[i]); } } - std::cout << "size before outlier select " << flags.size() << std::endl; reflection_data subrefls = select(reflections, final_sel); - std::cout << "size after outlier select " << subrefls.flags.size() << std::endl; std::vector subflags = subrefls.flags; // unset centroid outlier flag (necessary?) @@ -274,7 +268,6 @@ reflection_data reflection_filter_preevaluation( int max_sample_size=0 ){ reflection_data filter_obs = initial_refman_filter(obs, gonio, beam, close_to_spindle_cutoff); - std::cout << "initital filter " << filter_obs.flags.size() << std::endl; Matrix3d UB = crystal.get_A_matrix(); simple_reflection_predictor( beam, @@ -284,8 +277,6 @@ reflection_data reflection_filter_preevaluation( filter_obs ); filter_obs = outlier_filter(filter_obs); - std::cout << "outlier filter " << filter_obs.flags.size() << std::endl; reflection_data sel_obs = select_sample(filter_obs, n_ref_per_degree, scan_width_degrees, min_sample_size, max_sample_size); - std::cout << sel_obs.flags.size() << std::endl; return sel_obs; } From bf2535c2424f94c4a69947f0421157326f7f0530 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:21:29 +0000 Subject: [PATCH 23/72] Template array reading, add candidate vecs output json --- h5read/include/h5read.h | 3 +-- h5read/src/h5read_processed.cc | 39 ++++++---------------------------- indexer/indexer.cc | 17 +++++++++++++-- 3 files changed, 22 insertions(+), 37 deletions(-) diff --git a/h5read/include/h5read.h b/h5read/include/h5read.h index 7d33ab4..813634f 100644 --- a/h5read/include/h5read.h +++ b/h5read/include/h5read.h @@ -106,8 +106,7 @@ h5read_handle *h5read_parse_standard_args(int argc, char **argv); #include #include -std::vector read_xyzobs_data(std::string filename, std::string array_name); -std::vector read_flags_data(std::string filename, std::string array_name); +template std::vector read_array_from_h5_file(std::string filename, std::string array_name); class Image { private: diff --git a/h5read/src/h5read_processed.cc b/h5read/src/h5read_processed.cc index 63bdcbe..2d46c37 100644 --- a/h5read/src/h5read_processed.cc +++ b/h5read/src/h5read_processed.cc @@ -6,9 +6,8 @@ #include #include -using namespace std; - -std::vector read_xyzobs_data(string filename, string array_name){ +template +std::vector read_array_from_h5_file(std::string filename, std::string array_name){ auto start_time = std::chrono::high_resolution_clock::now(); hid_t file = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); if (file < 0){ @@ -22,45 +21,19 @@ std::vector read_xyzobs_data(string filename, string array_name){ hid_t dataspace = H5Dget_space(dataset); size_t num_elements = H5Sget_simple_extent_npoints(dataspace); hid_t space = H5Dget_space(dataset); - std::vector data_out(num_elements); + std::vector data_out(num_elements); H5Dread(dataset, datatype, H5S_ALL, space, H5P_DEFAULT, &data_out[0]); float total_time = std::chrono::duration_cast>( std::chrono::high_resolution_clock::now() - start_time) .count(); - std::cout << "XYZOBS READ TIME " << total_time << "s" << std::endl; - - H5Dclose(dataset); - H5Fclose(file); - return data_out; -} - -std::vector read_flags_data(string filename, string array_name){ - auto start_time = std::chrono::high_resolution_clock::now(); - hid_t file = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); - if (file < 0){ - std::cout << "Error: Unable to open " << filename.c_str() << " as a hdf5 reflection table" < data_out(num_elements); - - H5Dread(dataset, datatype, H5S_ALL, space, H5P_DEFAULT, &data_out[0]); - float total_time = - std::chrono::duration_cast>( - std::chrono::high_resolution_clock::now() - start_time) - .count(); - std::cout << "phi READ TIME " << total_time << "s" << std::endl; - H5Dclose(dataset); H5Fclose(file); return data_out; } +template std::vector read_array_from_h5_file(std::string, std::string); +template std::vector read_array_from_h5_file(std::string, std::string); diff --git a/indexer/indexer.cc b/indexer/indexer.cc index b4dd696..f219d81 100644 --- a/indexer/indexer.cc +++ b/indexer/indexer.cc @@ -105,9 +105,11 @@ int main(int argc, char **argv) { // get processed reflection data from spotfinding std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; - std::vector xyzobs_px = read_xyzobs_data(filename, array_name); + std::vector xyzobs_px = read_array_from_h5_file(filename, array_name); + //read_xyzobs_data(filename, array_name); std::string flags_array_name = "/dials/processing/group_0/flags"; - std::vector flags = read_flags_data(filename, flags_array_name); + std::vector flags = read_array_from_h5_file(filename, flags_array_name); + //read_flags_data(filename, flags_array_name); std::vector rlp = xyz_to_rlp(xyzobs_px, detector, beam, scan, gonio); @@ -171,6 +173,17 @@ int main(int argc, char **argv) { std::vector candidate_vecs = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); + std::string n_vecs = std::to_string(candidate_vecs.size() - 1); + size_t n_zero = n_vecs.length(); + json vecs_out; + for (int i=0;i miller_indices; // first extract phis From 13c358a5b52135473112732811d5a3f8ef272188 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:49:30 +0000 Subject: [PATCH 24/72] Move into baseline_indexer folder, just to candidate vecs for now --- CMakeLists.txt | 2 +- {indexer => baseline_indexer}/CMakeLists.txt | 6 +- {indexer => baseline_indexer}/fft3d.cc | 0 {indexer => baseline_indexer}/flood_fill.cc | 0 baseline_indexer/indexer.cc | 137 +++++++ .../sites_to_vecs.cc | 0 {indexer => baseline_indexer}/xyz_to_rlp.cc | 0 indexer/assign_indices.h | 122 ------ indexer/combinations.cc | 105 ----- indexer/indexer.cc | 359 ------------------ indexer/reflection_data.h | 46 --- indexer/refman_filter.cc | 282 -------------- indexer/scanstaticpredictor.cc | 158 -------- 13 files changed, 141 insertions(+), 1076 deletions(-) rename {indexer => baseline_indexer}/CMakeLists.txt (83%) rename {indexer => baseline_indexer}/fft3d.cc (100%) rename {indexer => baseline_indexer}/flood_fill.cc (100%) create mode 100644 baseline_indexer/indexer.cc rename {indexer => baseline_indexer}/sites_to_vecs.cc (100%) rename {indexer => baseline_indexer}/xyz_to_rlp.cc (100%) delete mode 100644 indexer/assign_indices.h delete mode 100644 indexer/combinations.cc delete mode 100644 indexer/indexer.cc delete mode 100644 indexer/reflection_data.h delete mode 100644 indexer/refman_filter.cc delete mode 100644 indexer/scanstaticpredictor.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index f91185e..dc7cfde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,4 +44,4 @@ add_subdirectory(dx2) add_subdirectory(h5read) add_subdirectory(baseline) add_subdirectory(spotfinder) -add_subdirectory(indexer) +add_subdirectory(baseline_indexer) diff --git a/indexer/CMakeLists.txt b/baseline_indexer/CMakeLists.txt similarity index 83% rename from indexer/CMakeLists.txt rename to baseline_indexer/CMakeLists.txt index 87ac6c1..aa29401 100644 --- a/indexer/CMakeLists.txt +++ b/baseline_indexer/CMakeLists.txt @@ -26,10 +26,10 @@ FetchContent_GetProperties(pocketfft) add_library(pocketfft INTERFACE) target_include_directories(pocketfft INTERFACE ${pocketfft_SOURCE_DIR}) -add_executable(indexer +add_executable(baseline_indexer indexer.cc ) -target_link_libraries(indexer +target_link_libraries(baseline_indexer PRIVATE fmt h5read @@ -43,4 +43,4 @@ target_link_libraries(indexer lodepng nlohmann_json::nlohmann_json ) -target_compile_options(indexer PRIVATE "$<$,$>:-G>") +target_compile_options(baseline_indexer PRIVATE "$<$,$>:-G>") diff --git a/indexer/fft3d.cc b/baseline_indexer/fft3d.cc similarity index 100% rename from indexer/fft3d.cc rename to baseline_indexer/fft3d.cc diff --git a/indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc similarity index 100% rename from indexer/flood_fill.cc rename to baseline_indexer/flood_fill.cc diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc new file mode 100644 index 0000000..b14d28a --- /dev/null +++ b/baseline_indexer/indexer.cc @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include "common.hpp" +#include "cuda_common.hpp" +#include "h5read.h" +#include +#include +#include +#include "xyz_to_rlp.cc" +#include "flood_fill.cc" +#include "sites_to_vecs.cc" +#include "fft3d.cc" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using Eigen::Vector3d; +using Eigen::Matrix3d; +using Eigen::Vector3i; +using json = nlohmann::json; + +struct score_and_crystal { + double score; + Crystal crystal; + double num_indexed; + double rmsdxy; +}; + +int main(int argc, char **argv) { + + auto t1 = std::chrono::system_clock::now(); + auto parser = CUDAArgumentParser(); + parser.add_argument("-e", "--expt") + .help("Path to the DIALS expt file"); + parser.add_argument("-r", "--refl") + .help("Path to the h5 reflection table file containing spotfinding results"); + parser.add_argument("--dmin") + .help("Resolution limit") + .scan<'f', float>(); + parser.add_argument("--max-cell") + .help("The maxiumu cell length to try during indexing") + .scan<'f', float>(); + auto args = parser.parse_args(argc, argv); + + if (!parser.is_used("--expt")){ + fmt::print("Error: must specify experiment list file with --expt\n"); + std::exit(1); + } + if (!parser.is_used("--refl")){ + fmt::print("Error: must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); + std::exit(1); + } + if (!parser.is_used("--max-cell")){ + fmt::print("Error: must specify --max-cell\n"); + std::exit(1); + } + if (!parser.is_used("--dmin")){ + fmt::print("Error: must specify --dmin\n"); + std::exit(1); + } + std::string imported_expt = parser.get("--expt"); + std::string filename = parser.get("--refl"); + double max_cell = parser.get("max-cell"); + double d_min = parser.get("dmin"); + + std::ifstream f(imported_expt); + json elist_json_obj; + try { + elist_json_obj = json::parse(f); + } + catch(json::parse_error& ex){ + std::cerr << "Error: Unable to read " << imported_expt.c_str() << "; json parse error at byte " << ex.byte << std::endl; + std::exit(1); + } + + // Load the models + json beam_data = elist_json_obj["beam"][0]; + MonoXrayBeam beam(beam_data); + json scan_data = elist_json_obj["scan"][0]; + Scan scan(scan_data); + json gonio_data = elist_json_obj["goniometer"][0]; + Goniometer gonio(gonio_data); + json panel_data = elist_json_obj["detector"][0]["panels"][0]; + Panel detector(panel_data); + + //TODO + // implement max cell/d_min estimation. - will need annlib if want same result as dials. + + // get processed reflection data from spotfinding + std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; + std::vector xyzobs_px = read_array_from_h5_file(filename, array_name); + + std::vector rlp = xyz_to_rlp(xyzobs_px, detector, beam, scan, gonio); + + std::cout << "Number of reflections: " << rlp.size() << std::endl; + + double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); + std::cout << "Setting b_iso =" << b_iso << std::endl; + + std::vector real_fft(256*256*256, 0.0); + std::vector used_in_indexing = fft3d(rlp, real_fft, d_min, b_iso); + + std::cout << real_fft[0] << " " << used_in_indexing[0] << std::endl; + + std::vector grid_points_per_void; + std::vector centres_of_mass_frac; + std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft); + std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); + + std::vector candidate_vecs = + sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); + + std::string n_vecs = std::to_string(candidate_vecs.size() - 1); + size_t n_zero = n_vecs.length(); + json vecs_out; + for (int i=0;i elapsed_time = t2 - t1; + std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; + +} \ No newline at end of file diff --git a/indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc similarity index 100% rename from indexer/sites_to_vecs.cc rename to baseline_indexer/sites_to_vecs.cc diff --git a/indexer/xyz_to_rlp.cc b/baseline_indexer/xyz_to_rlp.cc similarity index 100% rename from indexer/xyz_to_rlp.cc rename to baseline_indexer/xyz_to_rlp.cc diff --git a/indexer/assign_indices.h b/indexer/assign_indices.h deleted file mode 100644 index eff2709..0000000 --- a/indexer/assign_indices.h +++ /dev/null @@ -1,122 +0,0 @@ -#include -#include -#include - -using Eigen::Matrix3d; -using Eigen::Vector3d; -using Eigen::Vector3i; - -std::vector assign_indices_global(Matrix3d A, std::vector rlp, std::vector phi, double tolerance = 0.3){ - // Consider only a single lattice. - std::vector miller_indices(rlp.size()); - std::vector crystal_ids(rlp.size()); - std::vector lsq_vector(rlp.size()); - - // map of milleridx to - typedef std::multimap > hklmap; - - hklmap miller_idx_to_iref([](const Vector3i & a, const Vector3i & b)->bool - { - return std::lexicographical_compare( - a.data(),a.data()+a.size(), - b.data(),b.data()+b.size()); - }); - - //hklmap miller_idx_to_iref; - double pi_4 = M_PI / 4; - Vector3i miller_index_zero{{0, 0, 0}}; - Matrix3d A_inv = A.inverse(); - //std::cout << A_inv << std::endl; - double tolsq = std::pow(tolerance, 2); - for (int i=0;i tolsq){ - miller_indices[i] = {0,0,0}; - crystal_ids[i] = -1; - } - else if (miller_indices[i] == miller_index_zero){ - crystal_ids[i] = -1; - } - else { - miller_idx_to_iref.insert({miller_indices[i],i}); - lsq_vector[i] = l_sq; - } - } - // if more than one spot can be assigned the same miller index then - // choose the closest one - /*for (hklmap::iterator it = miller_idx_to_iref.begin(); it != miller_idx_to_iref.end(); it++){ - std::cout << it->first[0] << " " << it->first[1] << " " << it->first[2] << " " << it->second << std::endl; - }*/ - - Vector3i curr_hkl{{0, 0, 0}}; - std::vector i_same_hkl; - for (hklmap::iterator it = miller_idx_to_iref.begin(); it != miller_idx_to_iref.end(); it++){ - if (it->first != curr_hkl){ - if (i_same_hkl.size() > 1) { - for (int i = 0; i < i_same_hkl.size(); i++) { - const std::size_t i_ref = i_same_hkl[i]; - for (int j = i + 1; j < i_same_hkl.size(); j++) { - const std::size_t j_ref = i_same_hkl[j]; - double phi_i = phi[i_ref]; - double phi_j = phi[j_ref]; - if (std::abs(phi_i - phi_j) > pi_4) { - continue; - } - if (lsq_vector[i_ref] < lsq_vector[j_ref]){ - miller_indices[i_ref] = {0,0,0}; - crystal_ids[i_ref] = -1; - } - else { - miller_indices[j_ref] = {0,0,0}; - crystal_ids[j_ref] = -1; - } - } - } - } - curr_hkl = it->first; - i_same_hkl.clear(); - } - i_same_hkl.push_back(it->second); - } - - // Now do the final group! - if (i_same_hkl.size() > 1) { - for (int i = 0; i < i_same_hkl.size(); i++) { - const std::size_t i_ref = i_same_hkl[i]; - for (int j = i + 1; j < i_same_hkl.size(); j++) { - const std::size_t j_ref = i_same_hkl[j]; - double phi_i = phi[i_ref]; - double phi_j = phi[j_ref]; - if (std::abs(phi_i - phi_j) > pi_4) { - continue; - } - if (lsq_vector[i_ref] < lsq_vector[j_ref]){ - miller_indices[i_ref] = {0,0,0}; - crystal_ids[i_ref] = -1; - } - else { - miller_indices[j_ref] = {0,0,0}; - crystal_ids[j_ref] = -1; - } - } - } - } - - - return miller_indices; -} \ No newline at end of file diff --git a/indexer/combinations.cc b/indexer/combinations.cc deleted file mode 100644 index 2a36ddc..0000000 --- a/indexer/combinations.cc +++ /dev/null @@ -1,105 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "gemmi/unitcell.hpp" -#include "gemmi/symmetry.hpp" -#include - -using Eigen::Vector3d; -using Eigen::Vector3i; -using Eigen::Matrix3d; - -bool compare_comb(Vector3i a, Vector3i b){ - return a.squaredNorm() < b.squaredNorm(); // note can't use norm as get int truncation after std::sqrt. -} - -class CandidateOrientationMatrices { -public: - CandidateOrientationMatrices( - const std::vector& basis_vectors, int max_combinations = -1) - : max_combinations(max_combinations), index(0) { - n = basis_vectors.size(); - n = std::min(n, 100); - truncated_basis_vectors = {basis_vectors.begin(), basis_vectors.begin() + n}; - // now get combinations of - combinations.reserve(n*n*n/4); // could work this out exactly... - norms.reserve(n*n*n/4); - for (int i=0;i (cell.a * cell.b * cell.c / 100.0)){ - std::cout << "Returning combination: " << comb[0] << "," << comb[1] << "," << comb[2] << std::endl; - return c; - } - - } - throw std::out_of_range("No more combinations available"); - } - -private: - std::vector truncated_basis_vectors{}; - std::vector combinations{}; - std::vector truncated_combinations{}; - std::vector norms{}; - //flex::vec3_double::parts_type parts; - //flex::int_array i, j, k; - int n; - int max_combinations; - size_t index; - double half_pi; - double min_angle; -}; \ No newline at end of file diff --git a/indexer/indexer.cc b/indexer/indexer.cc deleted file mode 100644 index f219d81..0000000 --- a/indexer/indexer.cc +++ /dev/null @@ -1,359 +0,0 @@ -#include -#include -#include -#include -#include "common.hpp" -#include "cuda_common.hpp" -#include "h5read.h" -#include -#include -#include -#include "xyz_to_rlp.cc" -#include "flood_fill.cc" -#include "sites_to_vecs.cc" -#include "fft3d.cc" -#include "assign_indices.h" -#include "reflection_data.h" -#include "scanstaticpredictor.cc" -#include "combinations.cc" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "refman_filter.cc" - -using Eigen::Vector3d; -using Eigen::Matrix3d; -using Eigen::Vector3i; -using json = nlohmann::json; - -struct score_and_crystal { - double score; - Crystal crystal; - double num_indexed; - double rmsdxy; -}; - -int main(int argc, char **argv) { - - auto t1 = std::chrono::system_clock::now(); - auto parser = CUDAArgumentParser(); - parser.add_argument("-e", "--expt") - .help("Path to the DIALS expt file"); - parser.add_argument("-r", "--refl") - .help("Path to the h5 reflection table file containing spotfinding results"); - parser.add_argument("--max-refine") - .help("Maximum number of crystal models to test") - .default_value(50) - .scan<'i', int>(); - parser.add_argument("--dmin") - .help("Resolution limit") - .default_value(1.0) - .scan<'f', float>(); - parser.add_argument("--max-cell") - .help("The maxiumu cell length to try during indexing") - .scan<'f', float>(); - auto args = parser.parse_args(argc, argv); - - if (!parser.is_used("--expt")){ - fmt::print("Error: must specify experiment list file with --expt\n"); - std::exit(1); - } - if (!parser.is_used("--refl")){ - fmt::print("Error: must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); - std::exit(1); - } - if (!parser.is_used("--max-cell")){ - fmt::print("Error: must specify --max-cell\n"); - std::exit(1); - } - std::string imported_expt = parser.get("--expt"); - std::string filename = parser.get("--refl"); - int max_refine = parser.get("max-refine"); - double max_cell = parser.get("max-cell"); - double d_min = parser.get("dmin"); - - //std::string imported_expt = "/dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt"; - std::ifstream f(imported_expt); - json elist_json_obj; - try { - elist_json_obj = json::parse(f); - } - catch(json::parse_error& ex){ - std::cerr << "Error: Unable to read " << imported_expt.c_str() << "; json parse error at byte " << ex.byte << std::endl; - std::exit(1); - } - - // Load the models - json beam_data = elist_json_obj["beam"][0]; - MonoXrayBeam beam(beam_data); - json scan_data = elist_json_obj["scan"][0]; - Scan scan(scan_data); - json gonio_data = elist_json_obj["goniometer"][0]; - Goniometer gonio(gonio_data); - json panel_data = elist_json_obj["detector"][0]["panels"][0]; - Panel detector(panel_data); - - //TODO - // implement max cell/d_min estimation. - will need annlib if want same result as dials. - - // get processed reflection data from spotfinding - std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; - std::vector xyzobs_px = read_array_from_h5_file(filename, array_name); - //read_xyzobs_data(filename, array_name); - std::string flags_array_name = "/dials/processing/group_0/flags"; - std::vector flags = read_array_from_h5_file(filename, flags_array_name); - //read_flags_data(filename, flags_array_name); - - std::vector rlp = xyz_to_rlp(xyzobs_px, detector, beam, scan, gonio); - - // some more setup - entering flags and s1 - //self.reflections.centroid_px_to_mm(self.experiments) - //self.reflections.map_centroids_to_reciprocal_space(self.experiments) - //self.reflections.calculate_entering_flags(self.experiments) - Vector3d s0 = beam.get_s0(); - Vector3d axis = gonio.get_rotation_axis(); - Matrix3d d_matrix = detector.get_d_matrix(); - std::array oscillation = scan.get_oscillation(); - double osc_width = oscillation[1]; - double osc_start = oscillation[0]; - int image_range_start = scan.get_image_range()[0]; - double DEG2RAD = M_PI / 180.0; - - // calculate s1 and xyzobsmm - std::vector s1(rlp.size()); - std::vector xyzobs_mm(rlp.size()); - std::vector xyzcal_mm(rlp.size()); - std::vector phi(rlp.size()); - for (int i = 0; i < rlp.size(); ++i) { - int vec_idx= 3*i; - double x1 = xyzobs_px[vec_idx]; - double x2 = xyzobs_px[vec_idx+1]; - double x3 = xyzobs_px[vec_idx+2]; - std::array xymm = detector.px_to_mm(x1,x2); - double rot_angle = (((x3 + 1 - image_range_start) * osc_width) + osc_start) * DEG2RAD; - phi[i] = rot_angle; - Vector3d m = {xymm[0], xymm[1], 1.0}; - s1[i] = d_matrix * m; - xyzobs_mm[i] = {xymm[0], xymm[1], rot_angle}; - } - - // calculate entering array - std::vector enterings(rlp.size()); - Vector3d vec = s0.cross(axis); - for (int i=0;i grid_points_per_void; - std::vector centres_of_mass_frac; - std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft); - std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); - - std::vector candidate_vecs = - sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); - - std::string n_vecs = std::to_string(candidate_vecs.size() - 1); - size_t n_zero = n_vecs.length(); - json vecs_out; - for (int i=0;i miller_indices; - // first extract phis - // need to select on dmin, also only first 360 deg of scan. Do this earlier? - /*int image_range_start = scan.get_image_range()[0]; - std::vector phi_select(rlp.size()); - std::vector rlp_select(rlp.size()); - std::array oscillation = scan.get_oscillation(); - double osc_width = oscillation[1]; - double osc_start = oscillation[0]; - double DEG2RAD = M_PI / 180.0;*/ - - // Fix this inefficient selection with reflection-table-like struct. - std::vector phi_select(rlp.size()); - std::vector rlp_select(rlp.size()); - std::vector flags_select(rlp.size()); - std::vector xyzobs_mm_select(rlp.size()); - std::vector xyzcal_mm_select(rlp.size()); - std::vector s1_select(rlp.size()); - std::vector entering_select(rlp.size()); - int selcount=0; - // also select flags, xyzobs/cal, s1 and enterings - for (int i=0;i d_min){ - phi_select[selcount] = phi[i]; - rlp_select[selcount] = rlp[i]; - flags_select[selcount] = flags[i]; - xyzobs_mm_select[selcount] = xyzobs_mm[i]; - xyzcal_mm_select[selcount] = xyzcal_mm[i]; - s1_select[selcount] = s1[i]; - entering_select[selcount] = enterings[i]; - selcount++; - } - } - rlp_select.resize(selcount); - phi_select.resize(selcount); - flags_select.resize(selcount); - xyzobs_mm_select.resize(selcount); - xyzcal_mm_select.resize(selcount); - s1_select.resize(selcount); - entering_select.resize(selcount); - Vector3i null{{0,0,0}}; - int n = 0; - - // iterate over candidates; assign indices, refine, score. - // need a map of scores for candidates: index to score and xtal. What about miller indices? - - std::map results_map; - - while (candidates.has_next() && n < max_refine){ - Crystal crystal = candidates.next(); - n++; - std::vector miller_indices = assign_indices_global(crystal.get_A_matrix(), rlp_select, phi_select); - - // for debugging, let's count the number of nonzero miller indices - int count = 0; - for (int i=0;i {expt_out}; - elist_out["crystal"] = std::array {cryst_out}; - elist_out["scan"] = std::array {scan.to_json()}; - elist_out["goniometer"] = std::array {gonio.to_json()}; - elist_out["beam"] = std::array {beam.to_json()}; - elist_out["detector"] = std::array {detector.to_json()}; - - std::ofstream efile("elist.json"); - efile << elist_out.dump(4); - - - auto t2 = std::chrono::system_clock::now(); - std::chrono::duration elapsed_time = t2 - t1; - std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; - -} \ No newline at end of file diff --git a/indexer/reflection_data.h b/indexer/reflection_data.h deleted file mode 100644 index 3f67a19..0000000 --- a/indexer/reflection_data.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef REFLECTION_DATA -#define REFLECTION_DATA -#include -#include -using Eigen::Vector3d; -using Eigen::Vector3i; - -struct reflection_data { - std::vector flags; - std::vector xyzobs_mm; - std::vector xyzcal_mm; - std::vector s1; - std::vector miller_indices; - std::vector entering; -}; - -reflection_data select(reflection_data data, std::vector sel){ - reflection_data selected; - for (int i=0;i sel){ - reflection_data selected; - for (int i=0;i -#include -#include -#include "scanstaticpredictor.cc" -#include -#include -#include -#include -#include -#include "reflection_data.h" -// select on id before here. -using Eigen::Vector3d; -using Eigen::Matrix3d; -using Eigen::Vector3i; - - -std::vector random_selection(int pop_size, int sample_size, int seed=43){ - std::mt19937 mt(seed); - // get random selection up to pop size, then cut down to sample size - // then sort - std::vector result(pop_size); - std::vector::iterator r = result.begin(); - for (size_t i=0;i(mt()) % pop_size; - std::swap(r[i], r[j]); - } - result.resize(sample_size); - std::sort(result.begin(), result.end()); - return result; -} - - - - - -std::vector simple_tukey(std::vector xresid, std::vector yresid, std::vectorphi_resid){ - std::vector sel {};//(xresid.size(), true); - double iqr_multiplier = 3.0; - std::vector xresid_unsorted(xresid.begin(), xresid.end()); - std::vector yresid_unsorted(yresid.begin(), yresid.end()); - std::vector phi_resid_unsorted(phi_resid.begin(), phi_resid.end()); - std::sort(xresid.begin(), xresid.end()); - std::sort(yresid.begin(), yresid.end()); - std::sort(phi_resid.begin(), phi_resid.end()); - - // this is the way scitbx.math.five_number_summary does iqr - int n_lower=0; - double Q1x=0.0; - double Q1y=0.0; - double Q1p=0.0; - double Q3x=0.0; - double Q3y=0.0; - double Q3p=0.0; - int inc = 0; // an overlap offset if the number of items is odd. - if (xresid.size() % 2){ - n_lower = (xresid.size() / 2) + 1; - inc = -1; - } - else { - n_lower = xresid.size() / 2; - } - if (n_lower % 2){ - // FIXME verify this branch correct - Q1x = xresid[n_lower / 2]; - Q1y = yresid[n_lower / 2]; - Q1p = phi_resid[n_lower / 2]; - Q3x = xresid[n_lower + 1 + (n_lower/ 2)]; - Q3y = yresid[n_lower + 1 + (n_lower/ 2)]; - Q3p = phi_resid[n_lower + 1 + (n_lower/ 2)]; - } - else { - Q1x = (xresid[n_lower / 2] + xresid[(n_lower / 2) -1]) / 2.0; - Q1y = (yresid[n_lower / 2] + yresid[(n_lower / 2) -1]) / 2.0; - Q1p = (phi_resid[n_lower / 2] + phi_resid[(n_lower / 2) -1]) / 2.0; - Q3x = (xresid[n_lower + inc+(n_lower / 2)] + xresid[n_lower +inc+(n_lower / 2) -1]) / 2.0; - Q3y = (yresid[n_lower +inc+(n_lower / 2)] + yresid[n_lower +inc+(n_lower / 2) -1]) / 2.0; - Q3p = (phi_resid[n_lower +inc+(n_lower / 2)] + phi_resid[n_lower +inc+(n_lower / 2) -1]) / 2.0; - } - double iqrx = Q3x - Q1x; - double uppercutx = (iqrx * iqr_multiplier) + Q3x; - double lowercutx = Q1x -(iqrx * iqr_multiplier); - - double iqry = Q3y - Q1y; - double uppercuty = (iqry * iqr_multiplier) + Q3y; - double lowercuty = Q1y -(iqry * iqr_multiplier); - - double iqrp = Q3p - Q1p; - double uppercutp = (iqrp * iqr_multiplier) + Q3p; - double lowercutp = Q1p -(iqrp * iqr_multiplier); - - for (int i=0;i uppercutx){ - sel.push_back(i); - //sel[i] = false; - } - else if (xresid_unsorted[i] < lowercutx){ - sel.push_back(i); - //sel[i] = false; - } - else if (yresid_unsorted[i] > uppercuty){ - sel.push_back(i); - //sel[i] = false; - } - else if (yresid_unsorted[i] < lowercuty){ - sel.push_back(i); - //sel[i] = false; - } - else if (phi_resid_unsorted[i] > uppercutp){ - sel.push_back(i); - //sel[i] = false; - } - else if (phi_resid_unsorted[i] < lowercutp){ - sel.push_back(i); - //sel[i] = false; - } - } - return sel; -} - -reflection_data outlier_filter(reflection_data &reflections){ - // filter on predicted - std::vector flags = reflections.flags; - std::vector xyzobs = reflections.xyzobs_mm; - std::vector xyzcal = reflections.xyzcal_mm; - std::vector x_resid(reflections.flags.size(), 0.0); - std::vector y_resid(reflections.flags.size(), 0.0); - std::vector phi_resid(reflections.flags.size(), 0.0); - std::vector sel {}; - - size_t predicted_value = (1 << 0); //predicted flag - for (int i=0;i x_resid_sel(sel.size()); - std::vector y_resid_sel(sel.size()); - std::vector phi_resid_sel(sel.size()); - for (int i=0;i outlier_isel = simple_tukey(x_resid_sel, y_resid_sel, phi_resid_sel); - // get indices of good, then loop over good indics from first. - std::vector good_sel(x_resid_sel.size(), true); - for (int i=0;i final_sel {}; - for (int i=0;i subflags = subrefls.flags; - - // unset centroid outlier flag (necessary?) - // set used_in_refinement - size_t used_in_refinement_value = (1 << 3); //used in refinement flag - size_t centroid_outlier_value = (1 << 17); //ucentroid outlier flag - for (int i=0;i flags = reflections.flags; - std::vector hkl = reflections.miller_indices; - std::vector s1 = reflections.s1; - //std::vector id = reflections["id"]; - std::vector xyzobs = reflections.xyzobs_mm; - Vector3d axis = gonio.get_rotation_axis(); - Vector3d s0 = beam.get_s0(); - - // get flags ('overloaded') - size_t overloaded_value = (1 << 10); //overloaded flag - std::vector sel(flags.size(), true); - Vector3i null = {0,0,0}; - for (int i=0;i enterings(subrefls.flags.size(), false); - // calculate the entering column - Move to one of above loops? - std::vector s1_sub = subrefls.s1; - Vector3d vec = s0.cross(axis); - for (int i=0;i sel = random_selection(nrefs, sample_size); - reflection_data sel_obs = select(obs, sel); - return sel_obs; - } - return obs; -} - -reflection_data reflection_filter_preevaluation( - reflection_data &obs, - const Goniometer &gonio, - const Crystal &crystal, - const MonochromaticBeam &beam, - const Panel &panel, - double scan_width_degrees, - int n_ref_per_degree=100, - double close_to_spindle_cutoff=0.02, - int min_sample_size=1000, - int max_sample_size=0 - ){ - reflection_data filter_obs = initial_refman_filter(obs, gonio, beam, close_to_spindle_cutoff); - Matrix3d UB = crystal.get_A_matrix(); - simple_reflection_predictor( - beam, - gonio, - UB, - panel, - filter_obs - ); - filter_obs = outlier_filter(filter_obs); - reflection_data sel_obs = select_sample(filter_obs, n_ref_per_degree, scan_width_degrees, min_sample_size, max_sample_size); - return sel_obs; -} diff --git a/indexer/scanstaticpredictor.cc b/indexer/scanstaticpredictor.cc deleted file mode 100644 index a37363b..0000000 --- a/indexer/scanstaticpredictor.cc +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef DIALS_STATIC_PREDICTOR -#define DIALS_STATIC_PREDICTOR -#include -#include -#include -#include -#include -#include -#include -#include "reflection_data.h" -const double two_pi = 2 * M_PI; - -using Eigen::Matrix3d; -using Eigen::Vector3d; -using Eigen::Vector3i; - -inline double mod2pi(double angle) { - // E.g. treat 359.9999999 as 360 - if (std::abs(angle - two_pi) <= 1e-7) { - angle = two_pi; - } - return angle - two_pi * floor(angle / two_pi); -} - -Vector3d unit_rotate_around_origin(Vector3d vec, Vector3d unit, double angle){ - double cosang = std::cos(angle); - Vector3d res = vec*cosang + (unit*(unit.dot(vec)) * (1.0-cosang)) + (unit.cross(vec)*std::sin(angle)); - return res; -} - -// actually a repredictor, assumes all successful. -void simple_reflection_predictor( - const MonochromaticBeam beam, - const Goniometer gonio, - //const Vector3d s0,//beam s0 - //const Matrix3d F,//fixed rot - //const Matrix3d S,//setting rot - //const Vector3d R, //get_rotation_axis_datum - const Matrix3d UB, - const Panel &panel, - reflection_data &reflections - //const int image_range_start, - //const double osc_start, - //const double osc_width -){ - std::vector s1 = reflections.s1; - const std::vector xyzobs_mm = reflections.xyzobs_mm; - const std::vector entering = reflections.entering; - std::vector flags = reflections.flags; - std::vector xyzcal_mm = reflections.xyzcal_mm; - const std::vector hkl = reflections.miller_indices; - // these setup bits are the same for all refls. - Vector3d s0 = beam.get_s0(); - Matrix3d F = gonio.get_sample_rotation();//fixed rot - Matrix3d S = gonio.get_setting_rotation();//setting rot - Vector3d R = gonio.get_rotation_axis(); - Vector3d s0_ = S.inverse() * s0; - Matrix3d FUB = F * UB; - Vector3d m2 = R / R.norm(); - //Vector3d m2 = R.normalize();//fixed during refine - Vector3d s0_m2_plane = s0.cross(S * R); - s0_m2_plane.normalize(); - - Vector3d m1 = m2.cross(s0_); - m1.normalize(); //vary with s0 - Vector3d m3 = m1.cross(m2); - m3.normalize(); //vary with s0 - double s0_d_m2 = s0_.dot(m2); - double s0_d_m3 = s0_.dot(m3); - - /*std::vector xyzcalmm = obs["xyzcal.mm"]; - std::vector s1_all = obs["s1"]; - std::vector entering = obs["entering"]; - std::vector xyzobs = obs["xyzobs.mm.value"]; - std::vector hkl = obs["miller_index"]; - std::vector flags = obs["flags"];*/ - size_t predicted_value = (1 << 0); //predicted flag - // now call predict_rays with h and UB for a given refl - for (int i=0;i 4 * s0_.squaredNorm()){ - flags[i] = flags[i] & ~predicted_value; - continue; - } - double pstar0_d_m1 = pstar0.dot(m1); - double pstar0_d_m2 = pstar0.dot(m2); - double pstar0_d_m3 = pstar0.dot(m3); - double pstar_d_m3 = (-(0.5 * pstar0_len_sq) - (pstar0_d_m2 * s0_d_m2)) / s0_d_m3; - double rho_sq = (pstar0_len_sq - (pstar0_d_m2*pstar0_d_m2)); - double psq = pstar_d_m3*pstar_d_m3; - if (rho_sq < psq){ - flags[i] = flags[i] & ~predicted_value; - continue; - } - //DIALS_ASSERT(rho_sq >= sqr(pstar_d_m3)); - double pstar_d_m1 = sqrt(rho_sq - (psq)); - double p1 = pstar_d_m1 * pstar0_d_m1; - double p2 = pstar_d_m3 * pstar0_d_m3; - double p3 = pstar_d_m1 * pstar0_d_m3; - double p4 = pstar_d_m3 * pstar0_d_m1; - - double cosphi1 = p1 + p2; - double sinphi1 = p3 - p4; - double a1 = atan2(sinphi1, cosphi1); - // ASSERT must be in range? is_angle_in_range - - // check each angle - Vector3d pstar = S * unit_rotate_around_origin(pstar0, m2, a1); - Vector3d s1_this = s0_ + pstar; - bool this_entering = s1_this.dot(s0_m2_plane) < 0.; - double angle; - if (this_entering == entering_i){ - // use this s1 and a1 (mod 2pi) - angle = mod2pi(a1); - } - else { - double cosphi2 = -p1 + p2; - double sinphi2 = -p3 - p4; - double a2 = atan2(sinphi2, cosphi2); - pstar = S * unit_rotate_around_origin(pstar0, m2, a2); - s1_this = s0_ + pstar; - this_entering = s1_this.dot(s0_m2_plane) < 0.; - assert(this_entering == entering_i); - angle = mod2pi(a2); - } - - // only need frame if calculating xyzcalpx, but not needed for evaluation - //double frame = image_range_start + ((angle - osc_start) / osc_width) - 1; - //Vector3d v = D * s1; - std::array mm = panel.get_ray_intersection(s1_this); //v = D * s1; v[0]/v[2], v[1]/v[2] - //scitbx::vec2 px = Detector[0].millimeter_to_pixel(mm); // requires call to parallax corr - - // match full turns - double phiobs = xyzobs_mm[i][2]; - // first fmod positive - double val = std::fmod(phiobs, two_pi); - while (val < 0) val += two_pi; - double resid = angle - val; - // second fmod positive - double val2 = std::fmod(resid+M_PI, two_pi); - while (val2 < 0) val2 += two_pi; - val2 -= M_PI; - - xyzcal_mm[i] = {mm[0], mm[1], phiobs + val2}; - //xyzcalpx[i] = {px[0], px[1], frame}; - s1[i] = s1_this; - flags[i] = flags[i] | predicted_value; - } - reflections.flags = flags; - reflections.xyzcal_mm = xyzcal_mm; -} - -#endif // DIALS_STATIC_PREDICTOR \ No newline at end of file From db0dff09f79407e8bc9b2b2ec757aa3bb5f8f5c1 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:57:05 +0000 Subject: [PATCH 25/72] Output tidying --- baseline_indexer/flood_fill.cc | 10 +--------- baseline_indexer/indexer.cc | 11 +++++------ baseline_indexer/sites_to_vecs.cc | 8 -------- baseline_indexer/xyz_to_rlp.cc | 8 -------- 4 files changed, 6 insertions(+), 31 deletions(-) diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 7043a2e..3cf568e 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -34,14 +34,11 @@ flood_fill(std::vector const& grid, double rmsd = std::pow(sum_delta_sq / grid.size(), 0.5); std::vector grid_binary(n_points * n_points * n_points, 0); double cutoff = rmsd_cutoff * rmsd; - int count = 0; for (int i = 0; i < grid.size(); i++) { if (grid[i] >= cutoff) { grid_binary[i] = 1; - count++; } } - std::cout << "Nonzero grid binary: " << count << std::endl; auto t2 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time = t2 - start; std::cout << "Time for first part of flood fill: " << elapsed_time.count() << "s" << std::endl; @@ -173,12 +170,7 @@ std::tuple, std::vector> flood_fill_filter( int iqr = grid_points_per_void[Q3_index] - grid_points_per_void[Q1_index]; int iqr_multiplier = 5; int cut = (iqr * iqr_multiplier) + grid_points_per_void[Q3_index]; - /*for (int i = grid_points_per_void.size() - 1; i >= 0; i--) { - if (grid_points_per_void_unsorted[i] > cut) { - grid_points_per_void_unsorted.erase(grid_points_per_void_unsorted.begin() + i); - centres_of_mass_frac.erase(centres_of_mass_frac.begin() + i); - } - }*/ + while (grid_points_per_void[grid_points_per_void.size() - 1] > cut) { grid_points_per_void.pop_back(); } diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index b14d28a..8bb8a22 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -104,20 +104,17 @@ int main(int argc, char **argv) { std::cout << "Number of reflections: " << rlp.size() << std::endl; double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); - std::cout << "Setting b_iso =" << b_iso << std::endl; + std::cout << "Setting b_iso = " << b_iso << std::endl; std::vector real_fft(256*256*256, 0.0); std::vector used_in_indexing = fft3d(rlp, real_fft, d_min, b_iso); - - std::cout << real_fft[0] << " " << used_in_indexing[0] << std::endl; std::vector grid_points_per_void; std::vector centres_of_mass_frac; std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft); std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); - std::vector candidate_vecs = - sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); + std::vector candidate_vecs = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); std::string n_vecs = std::to_string(candidate_vecs.size() - 1); size_t n_zero = n_vecs.length(); @@ -127,7 +124,9 @@ int main(int argc, char **argv) { auto pad_s = std::string(n_zero - std::min(n_zero, s.length()), '0') + s; vecs_out[pad_s] = candidate_vecs[i]; } - std::ofstream vecs_file("candidate_vectors.json"); + std::string outfile = "candidate_vectors.json"; + std::cout << "Saving candidate vectors to " << outfile << std::endl; + std::ofstream vecs_file(outfile); vecs_file << vecs_out.dump(4); auto t2 = std::chrono::system_clock::now(); diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index bf40937..14e7857 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -102,9 +101,7 @@ std::vector sites_to_vecs( // need to sort volumes and sites by length for group_vectors, and also filter by max // and min cell - //std::stable_sort(filtered_data.begin(), filtered_data.end(), compare_site_data); - // now 'group vectors' double relative_length_tolerance = 0.1; double angular_tolerance = 5.0; std::vector vector_groups{}; @@ -145,8 +142,6 @@ std::vector sites_to_vecs( std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data_volume); std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data); - // std::vector unique_vectors; - // std::vector unique_volumes; std::vector unique_sites; for (int i = 0; i < grouped_data.size(); i++) { bool is_unique = true; @@ -162,9 +157,6 @@ std::vector sites_to_vecs( } } if (is_unique) { - // std::cout << v[0] << " " << v[1] << " " << v[2] << std::endl; - // unique_vectors.push_back(v); - // unique_volumes.push_back(grouped_data[i].volume); SiteData site{v, v.norm(), grouped_data[i].volume}; unique_sites.push_back(site); } diff --git a/baseline_indexer/xyz_to_rlp.cc b/baseline_indexer/xyz_to_rlp.cc index f146a16..682b827 100644 --- a/baseline_indexer/xyz_to_rlp.cc +++ b/baseline_indexer/xyz_to_rlp.cc @@ -1,11 +1,9 @@ #include -#include #include #include #include #include #include -#include using Eigen::Matrix3d; using Eigen::Vector3d; @@ -16,7 +14,6 @@ std::vector xyz_to_rlp( const MonochromaticBeam &beam, const Scan &scan, const Goniometer &gonio) { - auto start = std::chrono::system_clock::now(); // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method double DEG2RAD = M_PI / 180.0; @@ -63,10 +60,5 @@ std::vector xyz_to_rlp( rlp[i] = sample_rotation_inverse * rlp_this; } - - auto end = std::chrono::system_clock::now(); - std::chrono::duration elapsed_seconds = end - start; - std::cout << "elapsed time for xyz_to_rlp: " << elapsed_seconds.count() << "s" - << std::endl; return rlp; } \ No newline at end of file From 96ba5c8bff9cfcc11f3d7e06ef1d26639e9cf2c9 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:13:42 +0000 Subject: [PATCH 26/72] Use standard argparse for baseline indexer --- baseline_indexer/indexer.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 8bb8a22..da7619b 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -2,8 +2,6 @@ #include #include #include -#include "common.hpp" -#include "cuda_common.hpp" #include "h5read.h" #include #include @@ -22,6 +20,7 @@ #include #include #include +#include using Eigen::Vector3d; using Eigen::Matrix3d; @@ -38,7 +37,7 @@ struct score_and_crystal { int main(int argc, char **argv) { auto t1 = std::chrono::system_clock::now(); - auto parser = CUDAArgumentParser(); + auto parser = argparse::ArgumentParser(); parser.add_argument("-e", "--expt") .help("Path to the DIALS expt file"); parser.add_argument("-r", "--refl") @@ -49,7 +48,7 @@ int main(int argc, char **argv) { parser.add_argument("--max-cell") .help("The maxiumu cell length to try during indexing") .scan<'f', float>(); - auto args = parser.parse_args(argc, argv); + parser.parse_args(argc, argv); if (!parser.is_used("--expt")){ fmt::print("Error: must specify experiment list file with --expt\n"); From abcba8b80fa7af62fa7fc6bf5118722932f8b60d Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:51:58 +0000 Subject: [PATCH 27/72] Updates --- baseline_indexer/CMakeLists.txt | 11 ++--------- baseline_indexer/indexer.cc | 3 +-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/baseline_indexer/CMakeLists.txt b/baseline_indexer/CMakeLists.txt index aa29401..4564640 100644 --- a/baseline_indexer/CMakeLists.txt +++ b/baseline_indexer/CMakeLists.txt @@ -1,6 +1,4 @@ -project(indexer CXX CUDA) - -find_package(CUDAToolkit REQUIRED) +project(indexer CXX) # Automatic Dependencies set(FETCHCONTENT_QUIET OFF) @@ -32,15 +30,10 @@ add_executable(baseline_indexer target_link_libraries(baseline_indexer PRIVATE fmt - h5read dx2 Eigen3::Eigen pocketfft argparse - standalone - CUDA::cudart - CUDA::nppif - lodepng nlohmann_json::nlohmann_json ) -target_compile_options(baseline_indexer PRIVATE "$<$,$>:-G>") +target_compile_options(baseline_indexer PRIVATE) diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index da7619b..b3116be 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -1,8 +1,6 @@ #include -#include #include #include -#include "h5read.h" #include #include #include @@ -17,6 +15,7 @@ #include #include #include +#include #include #include #include From f619de82a767d72ea33f5c05649e41b04adb109d Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:12:04 +0000 Subject: [PATCH 28/72] Add these back in --- baseline_indexer/CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/baseline_indexer/CMakeLists.txt b/baseline_indexer/CMakeLists.txt index 4564640..0ff1ecf 100644 --- a/baseline_indexer/CMakeLists.txt +++ b/baseline_indexer/CMakeLists.txt @@ -1,4 +1,6 @@ -project(indexer CXX) +project(indexer CXX CUDA) + +find_package(CUDAToolkit REQUIRED) # Automatic Dependencies set(FETCHCONTENT_QUIET OFF) @@ -33,7 +35,9 @@ target_link_libraries(baseline_indexer dx2 Eigen3::Eigen pocketfft + CUDA::cudart + CUDA::nppif argparse nlohmann_json::nlohmann_json ) -target_compile_options(baseline_indexer PRIVATE) +target_compile_options(baseline_indexer PRIVATE "$<$,$>:-G>") From 89ea8f0848e774778f7e1858ce86ef3ecfb2d444 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:19:34 +0000 Subject: [PATCH 29/72] Remove code now in dx2 --- h5read/CMakeLists.txt | 2 +- h5read/include/h5read.h | 2 -- h5read/src/h5read_processed.cc | 39 ---------------------------------- 3 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 h5read/src/h5read_processed.cc diff --git a/h5read/CMakeLists.txt b/h5read/CMakeLists.txt index e65d922..a41d971 100644 --- a/h5read/CMakeLists.txt +++ b/h5read/CMakeLists.txt @@ -12,7 +12,7 @@ include(AlwaysColourCompilation) find_package(HDF5) -add_library(h5read src/h5read.c src/h5read.cc src/h5read_processed.cc) +add_library(h5read src/h5read.c src/h5read.cc) target_include_directories(h5read PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) target_link_libraries(h5read PUBLIC $) diff --git a/h5read/include/h5read.h b/h5read/include/h5read.h index 813634f..ec85fad 100644 --- a/h5read/include/h5read.h +++ b/h5read/include/h5read.h @@ -106,8 +106,6 @@ h5read_handle *h5read_parse_standard_args(int argc, char **argv); #include #include -template std::vector read_array_from_h5_file(std::string filename, std::string array_name); - class Image { private: std::shared_ptr _handle; diff --git a/h5read/src/h5read_processed.cc b/h5read/src/h5read_processed.cc deleted file mode 100644 index 2d46c37..0000000 --- a/h5read/src/h5read_processed.cc +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -template -std::vector read_array_from_h5_file(std::string filename, std::string array_name){ - auto start_time = std::chrono::high_resolution_clock::now(); - hid_t file = H5Fopen(filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT); - if (file < 0){ - std::cout << "Error: Unable to open " << filename.c_str() << " as a hdf5 reflection table" < data_out(num_elements); - - H5Dread(dataset, datatype, H5S_ALL, space, H5P_DEFAULT, &data_out[0]); - float total_time = - std::chrono::duration_cast>( - std::chrono::high_resolution_clock::now() - start_time) - .count(); - std::cout << "READ TIME for " << array_name << " : " << total_time << "s" << std::endl; - - H5Dclose(dataset); - H5Fclose(file); - return data_out; -} - -template std::vector read_array_from_h5_file(std::string, std::string); -template std::vector read_array_from_h5_file(std::string, std::string); From d77563e8e0b5b36d376a7861163760ed643df70c Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:22:38 +0000 Subject: [PATCH 30/72] Revert changes to h5read.c --- h5read/src/h5read.c | 80 --------------------------------------------- 1 file changed, 80 deletions(-) diff --git a/h5read/src/h5read.c b/h5read/src/h5read.c index ba654e0..b8780a3 100644 --- a/h5read/src/h5read.c +++ b/h5read/src/h5read.c @@ -50,9 +50,6 @@ struct _h5read_handle { float pixel_size_x, pixel_size_y; float detector_distance; float beam_center_x, beam_center_y; - double module_offsets[3]; - double oscillation_start; - double oscillation_width; }; void h5read_free(h5read_handle *obj) { @@ -404,10 +401,6 @@ float h5read_get_wavelength(h5read_handle *obj) { return obj->wavelength; } -double* h5read_get_module_offsets(h5read_handle *obj){ - return obj->module_offsets; -} - float h5read_get_pixel_size_slow(h5read_handle *obj) { return obj->pixel_size_x; } @@ -423,12 +416,6 @@ float h5read_get_beam_center_x(h5read_handle *obj) { float h5read_get_beam_center_y(h5read_handle *obj) { return obj->beam_center_y; } -double h5read_get_oscillation_start(h5read_handle *obj){ - return obj->oscillation_start; -} -double h5read_get_oscillation_width(h5read_handle *obj){ - return obj->oscillation_width; -} #ifdef HAVE_HDF5 void read_mask(h5read_handle *obj) { @@ -604,28 +591,6 @@ herr_t _read_single_value_image_t_type(hid_t origin, return 0; } -/// Read an attribute of a HDF5 group that is a vector. -herr_t _read_object_attribute_array(hid_t origin, const char *path, const char *attrname, double *destination) { - hid_t dataset = H5Dopen(origin, path, H5P_DEFAULT); - if (dataset < 0) { - return dataset; - } - //hid_t datatype = H5Dget_type(dataset); - hid_t attr = H5Aopen(dataset, attrname, H5P_DEFAULT); - hid_t attrdatatype = H5Aget_type(attr); - if (H5Aread( - attr, attrdatatype, destination) - < 0) { - fprintf(stderr, - "Error: While reading array attribute: Unspecified data reading error.\n", - path); - exit(1); - } - H5Aclose(attr); - H5Dclose(dataset); - return 0; -} - /// Read a single float value out of an HDF5 dataset /// /// If the dataset is present, but contains multiple elements, sets destination @@ -703,47 +668,6 @@ void read_wavelength(h5read_handle *obj) { } } -void read_module_offsets(h5read_handle *obj){ - if (_read_object_attribute_array(obj->master_file, - "/entry/instrument/detector/module/module_offset", - "vector", - *(&obj->module_offsets)) - < 0){ - obj->module_offsets[0] = -1; - obj->module_offsets[1] = -1; - obj->module_offsets[2] = -1; - } -} - -void read_oscillation_start_and_width(h5read_handle *obj){ - char omega_path[] = "/entry/sample/sample_omega/omega"; - - hid_t omega_dataset = H5Dopen(obj->master_file, omega_path, H5P_DEFAULT); - if (omega_dataset < 0){ - //We're allowed no omega scan e.g. grid - obj->oscillation_start=0; - obj->oscillation_width=0; - return; - } - hid_t datatype = H5Dget_type(omega_dataset); - hid_t omega_info = H5Dget_space(omega_dataset); - int size = H5Sget_simple_extent_npoints(omega_info); - if (size<2){ - fprintf(stderr, "Error: While reading oscillation, size<2\n"); - exit(1); - } - double* raw_omega = (double *)malloc(sizeof(double) * size); - void* buffer = (void *)raw_omega; - if (H5Dread(omega_dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer) < 0) { - fprintf(stderr, "Error: While reading oscillation\n"); - exit(1); - } - obj->oscillation_start=raw_omega[0]; - obj->oscillation_width=raw_omega[1] - raw_omega[0]; - free(raw_omega); - H5Dclose(omega_dataset); -} - void read_detector_metadata(h5read_handle *obj) { if (_read_single_value_float(obj->master_file, "/entry/instrument/detector/x_pixel_size", @@ -1000,10 +924,6 @@ h5read_handle *h5read_open(const char *master_filename) { read_detector_metadata(file); - read_module_offsets(file); - - read_oscillation_start_and_width(file); - read_mask(file); setup_data(file); From 3475bdda418332b53e96dab165c74bf70cb51b58 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:27:01 +0000 Subject: [PATCH 31/72] Remove unneeded struct and import --- baseline_indexer/indexer.cc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index b3116be..fefc698 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -26,13 +25,6 @@ using Eigen::Matrix3d; using Eigen::Vector3i; using json = nlohmann::json; -struct score_and_crystal { - double score; - Crystal crystal; - double num_indexed; - double rmsdxy; -}; - int main(int argc, char **argv) { auto t1 = std::chrono::system_clock::now(); @@ -131,4 +123,4 @@ int main(int argc, char **argv) { std::chrono::duration elapsed_time = t2 - t1; std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; -} \ No newline at end of file +} From 0b7144051e4f74b7c5e08db240b68e5f5f02721d Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:29:33 +0000 Subject: [PATCH 32/72] Newlines --- baseline_indexer/fft3d.cc | 3 ++- baseline_indexer/flood_fill.cc | 3 ++- baseline_indexer/sites_to_vecs.cc | 3 ++- baseline_indexer/xyz_to_rlp.cc | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 9ea54ca..76572bd 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -121,4 +121,5 @@ std::vector fft3d( std::cout << "elapsed time for squaring: " << elapsed_square.count() << "s" << std::endl; return used_in_indexing; -} \ No newline at end of file +} + diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 3cf568e..75c948f 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -184,4 +184,5 @@ std::tuple, std::vector> flood_fill_filter( } } return std::make_tuple(grid_points_per_void_unsorted, centres_of_mass_frac); -} \ No newline at end of file +} + diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 14e7857..684a8ec 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -175,4 +175,5 @@ std::vector sites_to_vecs( std::cout << "elapsed time for sites_to_vecs: " << elapsed_seconds.count() << "s" << std::endl; return unique_vectors_sorted; -} \ No newline at end of file +} + diff --git a/baseline_indexer/xyz_to_rlp.cc b/baseline_indexer/xyz_to_rlp.cc index 682b827..0651015 100644 --- a/baseline_indexer/xyz_to_rlp.cc +++ b/baseline_indexer/xyz_to_rlp.cc @@ -61,4 +61,5 @@ std::vector xyz_to_rlp( rlp[i] = sample_rotation_inverse * rlp_this; } return rlp; -} \ No newline at end of file +} + From 450f9bec30e3fa7d9592da21e96852b927a8db4e Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:31:31 +0000 Subject: [PATCH 33/72] Revert changes to h5read.h --- h5read/include/h5read.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/h5read/include/h5read.h b/h5read/include/h5read.h index ec85fad..bc3b28c 100644 --- a/h5read/include/h5read.h +++ b/h5read/include/h5read.h @@ -50,12 +50,9 @@ size_t h5read_get_image_fast(h5read_handle *obj); void h5read_get_trusted_range(h5read_handle *obj, image_t_type *min, image_t_type *max); /// Get the wavelength for this dataset float h5read_get_wavelength(h5read_handle *obj); -double* h5read_get_module_offsets(h5read_handle *obj); /// Get the pixel size for this dataset float h5read_get_pixel_size_slow(h5read_handle *obj); float h5read_get_pixel_size_fast(h5read_handle *obj); -double h5read_get_oscillation_start(h5read_handle *obj); -double h5read_get_oscillation_width(h5read_handle *obj); float h5read_get_detector_distance(h5read_handle *obj); float h5read_get_beam_center_x(h5read_handle *obj); float h5read_get_beam_center_y(h5read_handle *obj); @@ -161,7 +158,6 @@ class Reader { virtual size_t get_number_of_images() const = 0; virtual std::array get_trusted_range() const = 0; virtual std::array image_shape() const = 0; - //virtual std::array get_module_offsets() const = 0; virtual std::optional> get_mask() const = 0; virtual std::optional get_wavelength() const = 0; virtual std::optional> get_pixel_size() @@ -264,15 +260,6 @@ class H5Read : public Reader { } } - std::array get_module_offsets() const { - double* vals = h5read_get_module_offsets(_handle.get()); - return {vals[0], vals[1], vals[2]}; - } - std::array get_oscillation() const { - return {{h5read_get_oscillation_start(_handle.get()), - h5read_get_oscillation_width(_handle.get())}}; - } - virtual std::optional> get_pixel_size() const { return {{h5read_get_pixel_size_slow(_handle.get()), h5read_get_pixel_size_fast(_handle.get())}}; From 43858cd2e15026df0a82120c6c3107b4dc1e2daa Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:32:30 +0000 Subject: [PATCH 34/72] Output an example experiment list, add a test as an example of how to run --- baseline_indexer/indexer.cc | 43 ++++++++++ baseline_indexer/test_baseline_indexer.sh | 95 +++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100755 baseline_indexer/test_baseline_indexer.sh diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index fefc698..acbf209 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -14,11 +14,13 @@ #include #include #include +#include #include #include #include #include #include +#include "gemmi/symmetry.hpp" using Eigen::Vector3d; using Eigen::Matrix3d; @@ -106,6 +108,15 @@ int main(int argc, char **argv) { std::vector candidate_vecs = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); + // at this point, we will test combinations of the candidate vectors, use those to index the spots, do some + // refinement of the candidates and choose the best one. Then we will do some more refinement including extra + // model parameters. At then end, we will have a list of refined experiment models (including a crystal) + + // For now, let's just write out the candidate vectors and write out the unrefined experiment models with the + // first combination of candidate vectors as an example crystal, to demonstrate an example experiment list data + // structure. + + // dump the candidate vectors to json std::string n_vecs = std::to_string(candidate_vecs.size() - 1); size_t n_zero = n_vecs.length(); json vecs_out; @@ -118,6 +129,38 @@ int main(int argc, char **argv) { std::cout << "Saving candidate vectors to " << outfile << std::endl; std::ofstream vecs_file(outfile); vecs_file << vecs_out.dump(4); + + // Now make a crystal and save an experiment list with the models. + if (candidate_vecs.size() < 3){ + std::cout << "Insufficient number of candidate vectors to make a crystal model." << std::endl; + } + else { + gemmi::SpaceGroup space_group = *gemmi::find_spacegroup_by_name("P1"); + Crystal best_xtal {candidate_vecs[0], candidate_vecs[1], candidate_vecs[2], space_group}; + json cryst_out = best_xtal.to_json(); + + // save an example experiment list + json elist_out; // a list of potentially multiple experiments + elist_out["__id__"] = "ExperimentList"; + json expt_out; // our single experiment + // no imageset (for now?). + expt_out["__id__"] = "Experiment"; + expt_out["identifier"] = "test"; + expt_out["beam"] = 0; // the indices of the models that will correspond to our experiment + expt_out["detector"] = 0; + expt_out["goniometer"] = 0; + expt_out["scan"] = 0; + expt_out["crystal"] = 0; + elist_out["experiment"] = std::array {expt_out}; + elist_out["crystal"] = std::array {cryst_out}; // add the the actual models + elist_out["scan"] = std::array {scan.to_json()}; + elist_out["goniometer"] = std::array {gonio.to_json()}; + elist_out["beam"] = std::array {beam.to_json()}; + elist_out["detector"] = std::array {detector.to_json()}; + + std::ofstream efile("elist.json"); + efile << elist_out.dump(4); + } auto t2 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time = t2 - t1; diff --git a/baseline_indexer/test_baseline_indexer.sh b/baseline_indexer/test_baseline_indexer.sh new file mode 100755 index 0000000..b1fd101 --- /dev/null +++ b/baseline_indexer/test_baseline_indexer.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# Run this file from within the build folder i.e. +# ../baseline_indexer/test_baseline_indexer.sh + +# To run the indexer, we need: +# a strong reflection table (in DIALS new H5 format) +# an experiment list (standard dials format) +# a max cell (i.e. upper limit on basis vector length) +# a dmin (resolution limit of spots to use in fourier transform) +if test -f "candidate_vectors.json"; then + rm candidate_vectors.json +fi + +./bin/baseline_indexer \ + -r /dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl \ + -e /dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt \ + --max-cell 100 \ + --dmin 1.81 + +# Read the output from the candidate vecs, which is all we have for now. +output=$(cat candidate_vectors.json) + +expected_output='{ + "00": [ + 0.3828846032802875, + 16.046345646564774, + -2.9586537526204055 + ], + "01": [ + 41.00043348644091, + -6.374347624571426, + -53.04086788840915 + ], + "02": [ + 25.233528614044186, + 61.114115715026855, + -14.959117174148561 + ], + "03": [ + 15.894061997532845, + -63.236873000860214, + -38.00999879837036 + ], + "04": [ + 0.45249998569488525, + 13.574999570846558, + -8.144999742507935 + ], + "05": [ + 18.778749406337738, + 87.10624724626541, + 90.95249712467194 + ], + "06": [ + 75.11499762535095, + 18.09999942779541, + 0.0 + ], + "07": [ + 8.144999742507935, + 90.04749715328217, + 25.339999198913574 + ], + "08": [ + 59.72999811172485, + 82.35499739646912, + 37.33124881982803 + ], + "09": [ + 0.0, + 1.809999942779541, + -9.954999685287476 + ], + "10": [ + 1.809999942779541, + 26.244999170303345, + 36.19999885559082 + ] +}' + +# Compare the output with the expected output +if [ "$output" == "$expected_output" ]; then + echo "#############################################" + echo "# #" + echo "# SUCCESS!!! #" + echo "# #" + echo "#############################################" +else + echo "*********************************************" + echo "* *" + echo "* FAILURE!!! *" + echo "* *" + echo "*********************************************" +fi \ No newline at end of file From 41efadc7ede18e3c8b6fff5917bcf8af2314669b Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:47:27 +0000 Subject: [PATCH 35/72] Allow setting of number of fft grid points --- baseline_indexer/fft3d.cc | 31 ++++++++++++++----------------- baseline_indexer/indexer.cc | 13 +++++++++---- baseline_indexer/sites_to_vecs.cc | 4 ++-- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 76572bd..2e16b12 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -21,8 +21,8 @@ void map_centroids_to_reciprocal_space_grid_cpp( std::vector> &data_in, std::vector &selection, double d_min, - double b_iso = 0) { - const int n_points = 256; + double b_iso = 0, + uint32_t n_points = 256) { const double rlgrid = 2 / (d_min * n_points); const double one_over_rlgrid = 1 / rlgrid; const int half_n_points = n_points / 2; @@ -50,7 +50,7 @@ void map_centroids_to_reciprocal_space_grid_cpp( } else { T = 1; } - size_t index = coord[2] + (256 * coord[1]) + (256 * 256 * coord[0]); + size_t index = coord[2] + (n_points * coord[1]) + (n_points * n_points * coord[0]); if (!data_in[index].real()){ count++; } @@ -63,28 +63,25 @@ std::vector fft3d( std::vector const& reciprocal_space_vectors, std::vector &real_out, double d_min, - double b_iso = 0) { + double b_iso = 0, + uint32_t n_points = 256) { auto start = std::chrono::system_clock::now(); - std::vector> complex_data_in(256 * 256 * 256); - std::vector> data_out(256 * 256 * 256); + std::vector> complex_data_in(n_points * n_points * n_points); + std::vector> data_out(n_points * n_points * n_points); std::vector used_in_indexing(reciprocal_space_vectors.size(), true); auto t1 = std::chrono::system_clock::now(); - map_centroids_to_reciprocal_space_grid_cpp(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso); + map_centroids_to_reciprocal_space_grid_cpp(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso, n_points); auto t2 = std::chrono::system_clock::now(); - - shape_t shape_in{256, 256, 256}; - stride_t stride_in{sizeof(std::complex), - sizeof(std::complex) * 256, - sizeof(std::complex) * 256 - * 256}; // must have the size of each element. Must have + shape_t shape_in{n_points, n_points, n_points}; + int stride_x = sizeof(std::complex); + int stride_y = static_cast(sizeof(std::complex) * n_points); + int stride_z = static_cast(sizeof(std::complex) * n_points * n_points); + stride_t stride_in{stride_x, stride_y, stride_z}; // must have the size of each element. Must have // size() equal to shape_in.size() - stride_t stride_out{sizeof(std::complex), - sizeof(std::complex) * 256, - sizeof(std::complex) * 256 - * 256}; // must have the size of each element. Must + stride_t stride_out{stride_x, stride_y, stride_z}; // must have the size of each element. Must // have size() equal to shape_in.size() shape_t axes{0, 1, 2}; // 0 to shape.size()-1 inclusive bool forward{FORWARD}; diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index acbf209..31038a4 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -41,6 +41,10 @@ int main(int argc, char **argv) { parser.add_argument("--max-cell") .help("The maxiumu cell length to try during indexing") .scan<'f', float>(); + parser.add_argument("--fft-npoints") + .help("The number of grid points to use for the fft. Powers of two are most efficient.") + .default_value(256) + .scan<'u', uint32_t>(); parser.parse_args(argc, argv); if (!parser.is_used("--expt")){ @@ -96,17 +100,18 @@ int main(int argc, char **argv) { std::cout << "Number of reflections: " << rlp.size() << std::endl; double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); + uint32_t n_points = parser.get("--fft-npoints"); std::cout << "Setting b_iso = " << b_iso << std::endl; - std::vector real_fft(256*256*256, 0.0); - std::vector used_in_indexing = fft3d(rlp, real_fft, d_min, b_iso); + std::vector real_fft(n_points*n_points*n_points, 0.0); + std::vector used_in_indexing = fft3d(rlp, real_fft, d_min, b_iso, n_points); std::vector grid_points_per_void; std::vector centres_of_mass_frac; - std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft); + std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft, 15.0, n_points); std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); - std::vector candidate_vecs = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell); + std::vector candidate_vecs = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell, n_points); // at this point, we will test combinations of the candidate vectors, use those to index the spots, do some // refinement of the candidates and choose the best one. Then we will do some more refinement including extra diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 684a8ec..97d7952 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -71,10 +71,10 @@ std::vector sites_to_vecs( std::vector grid_points_per_void, double d_min, double min_cell = 3.0, - double max_cell = 92.3) { + double max_cell = 92.3, + uint32_t n_points = 256) { auto start = std::chrono::system_clock::now(); - int n_points = 256; double fft_cell_length = n_points * d_min / 2.0; // sites_mod_short and convert to cartesian for (int i = 0; i < centres_of_mass_frac.size(); i++) { From c1e148b8ba650cde3407564a0b9d114219ee4943 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:46:59 +0000 Subject: [PATCH 36/72] Add more comments into code, function docstrings. --- baseline_indexer/fft3d.cc | 41 +++++++++++++++- baseline_indexer/flood_fill.cc | 35 ++++++++++++-- baseline_indexer/indexer.cc | 77 +++++++++++++++++++++++-------- baseline_indexer/sites_to_vecs.cc | 29 +++++++++--- baseline_indexer/xyz_to_rlp.cc | 20 +++++++- 5 files changed, 167 insertions(+), 35 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 2e16b12..fc516e1 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -6,6 +6,7 @@ #include #include #include +#include using Eigen::Matrix3d; using Eigen::Vector3d; @@ -16,13 +17,25 @@ using Eigen::Vector3i; using namespace pocketfft; -void map_centroids_to_reciprocal_space_grid_cpp( +/** + * @brief map reciprocal space vectors onto a grid of size n_points^3. + * @param reciprocal_space_vectors Reciprocal space vectors to be mapped. + * @param data_in The vector (grid) which the data will be mapped to. + * @param selection The vector of the selection of points mapped to the grid. + * @param d_min A resolution limit for mapping to the grid. + * @param b_iso The isotropic B-factor used to weight the points as a function of resolution. + * @param n_points The size of each dimension of the FFT grid. + */ +void map_centroids_to_reciprocal_space_grid( std::vector const& reciprocal_space_vectors, std::vector> &data_in, std::vector &selection, double d_min, double b_iso = 0, uint32_t n_points = 256) { + assert(data_in.size() == n_points * n_points * n_points); + // Determine the resolution span of the grid so we know how to map + // each coordinate to the grid. const double rlgrid = 2 / (d_min * n_points); const double one_over_rlgrid = 1 / rlgrid; const int half_n_points = n_points / 2; @@ -37,6 +50,7 @@ void map_centroids_to_reciprocal_space_grid_cpp( continue; } Vector3i coord; + // map to the nearest point in each dimension. for (int j = 0; j < 3; j++) { coord[j] = ((int)round(v[j] * one_over_rlgrid)) + half_n_points; } @@ -44,12 +58,14 @@ void map_centroids_to_reciprocal_space_grid_cpp( selection[i] = false; continue; } + // Use the b_iso to determine the weight for each coordinate. double T; if (b_iso != 0) { T = std::exp(-b_iso * v_length * v_length / 4.0); } else { T = 1; } + // unravel to the 1d index and write the complex value. size_t index = coord[2] + (n_points * coord[1]) + (n_points * n_points * coord[0]); if (!data_in[index].real()){ count++; @@ -59,6 +75,15 @@ void map_centroids_to_reciprocal_space_grid_cpp( std::cout << "Number of centroids used: " << count << std::endl; } +/** + * @brief Perform a 3D FFT of the reciprocal space coordinates (spots). + * @param reciprocal_space_vectors The input vector of reciprocal space coordinates. + * @param real_out The (real) array that the FFT result will be written to. + * @param d_min Cut the data at this resolution limit for the FFT + * @param b_iso The isotropic B-factor used to weight the points as a function of resolution. + * @param n_points The size of each dimension of the FFT grid. + * @returns A boolean array indicating which coordinates were used for the FFT. + */ std::vector fft3d( std::vector const& reciprocal_space_vectors, std::vector &real_out, @@ -66,15 +91,25 @@ std::vector fft3d( double b_iso = 0, uint32_t n_points = 256) { auto start = std::chrono::system_clock::now(); + assert(real_out.size() == n_points * n_points * n_points); + // We want to write out the real part of the FFT, but the pocketfft functions require + // complex vectors (we are using c2c i.e. complex to complex), so initialise these vectors. + // Note we should be able to use c2r rather than c2c, but I couldn't get this to work with + // the output ordering in c2r (JBE). std::vector> complex_data_in(n_points * n_points * n_points); std::vector> data_out(n_points * n_points * n_points); + // A boolean array of whether the vectors were used for the FFT. std::vector used_in_indexing(reciprocal_space_vectors.size(), true); auto t1 = std::chrono::system_clock::now(); - map_centroids_to_reciprocal_space_grid_cpp(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso, n_points); + // Map the vectors onto a discrete grid. The values of the grid points are weights + // determined by b_iso. + map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso, n_points); auto t2 = std::chrono::system_clock::now(); + + // Prepare the required objects for the FFT. shape_t shape_in{n_points, n_points, n_points}; int stride_x = sizeof(std::complex); int stride_y = static_cast(sizeof(std::complex) * n_points); @@ -89,6 +124,7 @@ std::vector fft3d( double fct{1.0f}; size_t nthreads = 20; // use all threads available - is this working? + // Do the FFT. c2c(shape_in, stride_in, stride_out, @@ -100,6 +136,7 @@ std::vector fft3d( nthreads); auto t3 = std::chrono::system_clock::now(); + // Take the square of the real part as the output. for (int i = 0; i < real_out.size(); ++i) { real_out[i] = std::pow(data_out[i].real(), 2); } diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 75c948f..7e3a41c 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -16,12 +16,20 @@ int modulo(int i, int n) { return (i % n + n) % n; } +/** + * @brief Perform a flood fill algorithm on a grid of data to determine connected areas of signal. + * @param grid The input array (grid) of data + * @param rmsd_cutoff Filter out grid points below this cutoff value + * @param n_points The size of each dimension of the FFT grid. + * @returns A tuple of grid points per peak and centres of mass of the peaks in fractional coordinates. + */ std::tuple,std::vector> flood_fill(std::vector const& grid, double rmsd_cutoff = 15.0, int n_points = 256) { auto start = std::chrono::system_clock::now(); - // First calc rmsd and use this to create a binary grid + assert(grid.size() == n_points * n_points * n_points); + // First calculate the rmsd and use this to create a binary grid double sumg = 0.0; for (int i = 0; i < grid.size(); ++i) { sumg += grid[i]; @@ -43,7 +51,9 @@ flood_fill(std::vector const& grid, std::chrono::duration elapsed_time = t2 - start; std::cout << "Time for first part of flood fill: " << elapsed_time.count() << "s" << std::endl; - // Now do flood fill. Wrap around the edge in all three dimensions. + // Now do the flood fill. + // Wrap around the edge in all three dimensions to replicate the DIALS + // results exactly. int n_voids = 0; std::stack stack; std::vector> accumulators; @@ -51,6 +61,8 @@ flood_fill(std::vector const& grid, int replacement = 2; std::vector grid_points_per_void; int accumulator_index = 0; + + // precalculate a few constants int total = n_points * n_points * n_points; int n_sq = n_points * n_points; int n_sq_minus_n = n_points * (n_points - 1); @@ -136,7 +148,7 @@ flood_fill(std::vector const& grid, std::chrono::duration elapsed_time2 = t3 - t2; std::cout << "Time for second part of flood fill: " << elapsed_time2.count() << "s" << std::endl; - // Now calculate the unweighted centres of mass of each group. + // Now calculate the unweighted centres of mass of each group, in fractional coordinates. std::vector centres_of_mass_frac(n_voids); for (int i = 0; i < accumulators.size(); i++) { std::vector values = accumulators[i]; @@ -158,24 +170,37 @@ flood_fill(std::vector const& grid, return std::make_tuple(grid_points_per_void, centres_of_mass_frac); } +/** + * @brief Perform a filter on the flood fill results. + * @param grid_points_per_void The number of grid points in each peak + * @param centres_of_mass_frac The centres of mass of each peak, in fractional coordinates + * @param peak_volume_cutoff The minimum fractional threshold for peaks to be included. + * @returns A tuple of grid points per peak and centres of mass of the peaks in fractional coordinates. + */ std::tuple, std::vector> flood_fill_filter( std::vector grid_points_per_void, std::vector centres_of_mass_frac, double peak_volume_cutoff = 0.15){ - // now filter out based on iqr range and peak_volume_cutoff + // Filter out based on iqr range and peak_volume_cutoff std::vector grid_points_per_void_unsorted(grid_points_per_void); std::sort(grid_points_per_void.begin(), grid_points_per_void.end()); + // The peak around the origin of the FFT can be very large in volume, + // so use the IQR range to filter high-volume peaks out that are not + // from the typical distribution of points, before applying the peak + // volume cutoff based on a fraction of the max volume in the remaining + // array. int Q3_index = grid_points_per_void.size() * 3 / 4; int Q1_index = grid_points_per_void.size() / 4; int iqr = grid_points_per_void[Q3_index] - grid_points_per_void[Q1_index]; int iqr_multiplier = 5; int cut = (iqr * iqr_multiplier) + grid_points_per_void[Q3_index]; + // Remove abnormally high volumes while (grid_points_per_void[grid_points_per_void.size() - 1] > cut) { grid_points_per_void.pop_back(); } int max_val = grid_points_per_void[grid_points_per_void.size() - 1]; - + // Cut based on a fraction of the max volume. int peak_cutoff = (int)(peak_volume_cutoff * max_val); for (int i = grid_points_per_void_unsorted.size() - 1; i >= 0; i--) { if (grid_points_per_void_unsorted[i] <= peak_cutoff) { diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 31038a4..4952c2a 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -28,7 +28,13 @@ using Eigen::Vector3i; using json = nlohmann::json; int main(int argc, char **argv) { - + // The purpose of an indexer is to determine the lattice model that best + // explains the positions of the strong spots found during spot-finding. + // The lattice model is a set of three vectors that define the crystal + // lattice translations. + // The experiment models (beam, detector) can also be refined during the + // indexing process. The output is a set of models - a new crystal model that + // describes the crystal lattice and an updated set of experiment models. auto t1 = std::chrono::system_clock::now(); auto parser = argparse::ArgumentParser(); parser.add_argument("-e", "--expt") @@ -36,12 +42,12 @@ int main(int argc, char **argv) { parser.add_argument("-r", "--refl") .help("Path to the h5 reflection table file containing spotfinding results"); parser.add_argument("--dmin") - .help("Resolution limit") + .help("The resolution limit of spots to use in the indexing process.") .scan<'f', float>(); parser.add_argument("--max-cell") - .help("The maxiumu cell length to try during indexing") + .help("The maximum possible cell length to consider during indexing") .scan<'f', float>(); - parser.add_argument("--fft-npoints") + parser.add_argument("--fft-npoints") // mainly for testing, likely would always want to keep it as 256. .help("The number of grid points to use for the fft. Powers of two are most efficient.") .default_value(256) .scan<'u', uint32_t>(); @@ -55,19 +61,26 @@ int main(int argc, char **argv) { fmt::print("Error: must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); std::exit(1); } + // In DIALS, the max cell is automatically determined through a nearest + // neighbour analysis that requires the annlib package. For now, + // let's make this a required argument to help with testing/comparison + // to DIALS. if (!parser.is_used("--max-cell")){ fmt::print("Error: must specify --max-cell\n"); std::exit(1); } + // FIXME use highest resolution by default to remove this requirement. if (!parser.is_used("--dmin")){ fmt::print("Error: must specify --dmin\n"); std::exit(1); } std::string imported_expt = parser.get("--expt"); - std::string filename = parser.get("--refl"); + std::string filename = parser.get("--refl"); double max_cell = parser.get("max-cell"); double d_min = parser.get("dmin"); + // Parse the experiment list (a json file) and load the models. + // Will be moved to dx2. std::ifstream f(imported_expt); json elist_json_obj; try { @@ -88,30 +101,54 @@ int main(int argc, char **argv) { json panel_data = elist_json_obj["detector"][0]["panels"][0]; Panel detector(panel_data); - //TODO - // implement max cell/d_min estimation. - will need annlib if want same result as dials. - - // get processed reflection data from spotfinding + // Read data from a reflection table. Again, this should be moved to + // dx2 and only require the data array name (xyzobs.px.value) with some + // logic to step through the directory structure std::string array_name = "/dials/processing/group_0/xyzobs.px.value"; + // Note, xyzobs_px is the flattened, on-disk representation of the array + // i.e. if there are 100 spots, the length of xyzobs_px is 300, and + // contains the elements [x0, y0, z0, x1, y1, z1, ..., x99, y99, z99] std::vector xyzobs_px = read_array_from_h5_file(filename, array_name); + // The diffraction spots form a lattice in reciprocal space (if the experimental + // geometry is accurate). So use the experimental models to transform the spot + // coordinates on the detector into reciprocal space. std::vector rlp = xyz_to_rlp(xyzobs_px, detector, beam, scan, gonio); - std::cout << "Number of reflections: " << rlp.size() << std::endl; + // b_iso is an isotropic b-factor used to weight the points when doing the fft. + // i.e. high resolution (weaker) spots are downweighted by the expected + // intensity fall-off as as function of resolution. double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); uint32_t n_points = parser.get("--fft-npoints"); std::cout << "Setting b_iso = " << b_iso << std::endl; - std::vector real_fft(n_points*n_points*n_points, 0.0); - std::vector used_in_indexing = fft3d(rlp, real_fft, d_min, b_iso, n_points); - + // Create an array to store the fft result. This is a 3D grid of points, typically 256^3. + std::vector real_fft_result(n_points*n_points*n_points, 0.0); + + // Do the fft of the reciprocal lattice coordinates. + // the used in indexing array denotes whether a coordinate was used for the + // fft (might not be if dmin filter was used for example). The used_in_indexing array + // is sometimes used onwards in the dials indexing algorithms, so keep for now. + std::vector used_in_indexing = fft3d(rlp, real_fft_result, d_min, b_iso, n_points); + + // The fft result is noisy. We want to extract the peaks, which may be spread over several + // points on the fft grid. So we use a flood fill algorithm (https://en.wikipedia.org/wiki/Flood_fill) + // to determine the connected regions in 3D. This is how it is done in DIALS, but I note that + // perhaps this could be done with connected components analysis. + // So do the flood fill, and extract the centres of mass of the peaks and the number of grid points + // that contribute to each peak. std::vector grid_points_per_void; std::vector centres_of_mass_frac; - std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft, 15.0, n_points); + // 15.0 is the DIALS 'rmsd_cutoff' parameter to filter out weak peaks. + std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft_result, 15.0, n_points); + // Do some further filtering, 0.15 is the DIALS peak_volume_cutoff parameter. std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); - std::vector candidate_vecs = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell, n_points); + // Convert the peak centres from the fft grid into vectors in reciprocal space. These are our candidate + // lattice vectors. + // 3.0 is the min cell parameter. + std::vector candidate_lattice_vectors = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell, n_points); // at this point, we will test combinations of the candidate vectors, use those to index the spots, do some // refinement of the candidates and choose the best one. Then we will do some more refinement including extra @@ -122,13 +159,13 @@ int main(int argc, char **argv) { // structure. // dump the candidate vectors to json - std::string n_vecs = std::to_string(candidate_vecs.size() - 1); + std::string n_vecs = std::to_string(candidate_lattice_vectors.size() - 1); size_t n_zero = n_vecs.length(); json vecs_out; - for (int i=0;i + +// Define a few simple data structures to help with the calculations below. class VectorGroup { public: void add(Vector3d vec, int weight) { @@ -66,6 +68,16 @@ bool is_approximate_integer_multiple(Vector3d v1, return false; } +/** + * @brief Rescale the fractional coordinates on the grid to vectors (in reciprocal space). + * @param centres_of_mass_frac The fractional centres of mass of FFT peaks. + * @param grid_points_per_void The number of FFT grid points corresponding to each peak. + * @param d_min The resolution limit that was applied to the FFT. + * @param min_cell Don't consider vectors below this length + * @param max_cell Don't consider vectors above this length + * @param n_points The size of each dimension of the FFT grid. + * @returns Unique vectors, sorted by volume, that give describe the FFT peaks. + */ std::vector sites_to_vecs( std::vector centres_of_mass_frac, std::vector grid_points_per_void, @@ -74,9 +86,9 @@ std::vector sites_to_vecs( double max_cell = 92.3, uint32_t n_points = 256) { auto start = std::chrono::system_clock::now(); - + // Calculate the scaling between the FFT grid and reciprocal space. double fft_cell_length = n_points * d_min / 2.0; - // sites_mod_short and convert to cartesian + // Use 'sites_mod_short' and convert to cartesian (but keep in same array) for (int i = 0; i < centres_of_mass_frac.size(); i++) { for (size_t j = 0; j < 3; j++) { if (centres_of_mass_frac[i][j] > 0.5) { @@ -86,7 +98,7 @@ std::vector sites_to_vecs( } } - // now do some filtering + // now do some filtering based on the min and max cell std::vector filtered_data; for (int i = 0; i < centres_of_mass_frac.size(); i++) { auto v = centres_of_mass_frac[i]; @@ -97,11 +109,9 @@ std::vector sites_to_vecs( filtered_data.push_back(site_data); } } - // now sort filtered data - - // need to sort volumes and sites by length for group_vectors, and also filter by max - // and min cell + // Now sort the filtered data. Ggroup together those + // with similar angles and lengths (e.g. inverse pairs from the FFT). double relative_length_tolerance = 0.1; double angular_tolerance = 5.0; std::vector vector_groups{}; @@ -125,12 +135,14 @@ std::vector sites_to_vecs( } } } + // If it didn't match any existing group, create a new one. if (!matched_group) { VectorGroup group = VectorGroup(); group.add(filtered_data[i].site, filtered_data[i].volume); vector_groups.push_back(group); } } + // Create 'site's based on the data from the groups. std::vector grouped_data; for (int i = 0; i < vector_groups.size(); i++) { Vector3d site = vector_groups[i].mean(); @@ -139,9 +151,12 @@ std::vector sites_to_vecs( SiteData site_data = {site, site.norm(), max}; grouped_data.push_back(site_data); } + + // Sort by volume, then by length. std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data_volume); std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data); + // Now check if any sites are integer multiples of other sites. std::vector unique_sites; for (int i = 0; i < grouped_data.size(); i++) { bool is_unique = true; diff --git a/baseline_indexer/xyz_to_rlp.cc b/baseline_indexer/xyz_to_rlp.cc index 0651015..a12fdc8 100644 --- a/baseline_indexer/xyz_to_rlp.cc +++ b/baseline_indexer/xyz_to_rlp.cc @@ -8,16 +8,33 @@ using Eigen::Matrix3d; using Eigen::Vector3d; +/** + * @brief Transform detector pixel coordinates into reciprocal space coordinates. + * @param xyzobs_px A 1D array of detector pixel coordinates from a single panel. + * @param panel A dx2 Panel object defining the corresponding detector panel. + * @param beam A dx2 MonochromaticBeam object. + * @param scan A dx2 Scan object. + * @param gonio A dx2 Goniometer object. + * @returns A vector of reciprocal space coordinates. + */ std::vector xyz_to_rlp( const std::vector &xyzobs_px, const Panel &panel, const MonochromaticBeam &beam, const Scan &scan, const Goniometer &gonio) { + // Use the experimental models to perform a coordinate transformation from + // pixel coordinates in detector space to reciprocal space, in units of + // inverse angstrom. // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method double DEG2RAD = M_PI / 180.0; + + // xyzobs_px is a flattened array, we want to return a vector of Vector3ds, + // so the size is divided by 3. std::vector rlp(xyzobs_px.size() / 3); + + // Extract the quantities from the models that are needed for the calculation. Vector3d s0 = beam.get_s0(); double wl = beam.get_wavelength(); std::array oscillation = scan.get_oscillation(); @@ -28,6 +45,7 @@ std::vector xyz_to_rlp( Matrix3d sample_rotation_inverse = gonio.get_sample_rotation().inverse(); Vector3d rotation_axis = gonio.get_rotation_axis(); Matrix3d d_matrix = panel.get_d_matrix(); + for (int i = 0; i < rlp.size(); ++i) { // first convert detector pixel positions into mm int vec_idx= 3*i; @@ -52,12 +70,12 @@ std::vector xyz_to_rlp( Vector3d S = setting_rotation_inverse * (s1_this - s0); double cos = std::cos(-1.0 * rot_angle); double sin = std::sin(-1.0 * rot_angle); + // The DIALS equivalent to the code below is // rlp_this = S.rotate_around_origin(gonio.rotation_axis, -1.0 * rot_angle); Vector3d rlp_this = (S * cos) + (rotation_axis * rotation_axis.dot(S) * (1 - cos)) + (sin * rotation_axis.cross(S)); - rlp[i] = sample_rotation_inverse * rlp_this; } return rlp; From 913a702269e9921ca554af5a2b50be04131166e1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:51:23 +0000 Subject: [PATCH 37/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/fft3d.cc | 245 +++++++++++---------- baseline_indexer/flood_fill.cc | 347 +++++++++++++++--------------- baseline_indexer/indexer.cc | 160 +++++++------- baseline_indexer/sites_to_vecs.cc | 288 +++++++++++++------------ baseline_indexer/xyz_to_rlp.cc | 125 ++++++----- 5 files changed, 599 insertions(+), 566 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index fc516e1..469025d 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -1,12 +1,13 @@ +#include +#include #include -#include -#include + +#include #include #include +#include +#include #include -#include -#include -#include using Eigen::Matrix3d; using Eigen::Vector3d; @@ -27,52 +28,53 @@ using namespace pocketfft; * @param n_points The size of each dimension of the FFT grid. */ void map_centroids_to_reciprocal_space_grid( - std::vector const& reciprocal_space_vectors, + std::vector const &reciprocal_space_vectors, std::vector> &data_in, std::vector &selection, double d_min, double b_iso = 0, uint32_t n_points = 256) { - assert(data_in.size() == n_points * n_points * n_points); - // Determine the resolution span of the grid so we know how to map - // each coordinate to the grid. - const double rlgrid = 2 / (d_min * n_points); - const double one_over_rlgrid = 1 / rlgrid; - const int half_n_points = n_points / 2; - int count = 0; - - for (int i = 0; i < reciprocal_space_vectors.size(); i++) { - const Vector3d v = reciprocal_space_vectors[i]; - const double v_length = v.norm(); - const double d_spacing = 1 / v_length; - if (d_spacing < d_min) { - selection[i] = false; - continue; - } - Vector3i coord; - // map to the nearest point in each dimension. - for (int j = 0; j < 3; j++) { - coord[j] = ((int)round(v[j] * one_over_rlgrid)) + half_n_points; - } - if ((coord.maxCoeff() >= n_points) || coord.minCoeff() < 0) { - selection[i] = false; - continue; - } - // Use the b_iso to determine the weight for each coordinate. - double T; - if (b_iso != 0) { - T = std::exp(-b_iso * v_length * v_length / 4.0); - } else { - T = 1; - } - // unravel to the 1d index and write the complex value. - size_t index = coord[2] + (n_points * coord[1]) + (n_points * n_points * coord[0]); - if (!data_in[index].real()){ - count++; + assert(data_in.size() == n_points * n_points * n_points); + // Determine the resolution span of the grid so we know how to map + // each coordinate to the grid. + const double rlgrid = 2 / (d_min * n_points); + const double one_over_rlgrid = 1 / rlgrid; + const int half_n_points = n_points / 2; + int count = 0; + + for (int i = 0; i < reciprocal_space_vectors.size(); i++) { + const Vector3d v = reciprocal_space_vectors[i]; + const double v_length = v.norm(); + const double d_spacing = 1 / v_length; + if (d_spacing < d_min) { + selection[i] = false; + continue; + } + Vector3i coord; + // map to the nearest point in each dimension. + for (int j = 0; j < 3; j++) { + coord[j] = ((int)round(v[j] * one_over_rlgrid)) + half_n_points; + } + if ((coord.maxCoeff() >= n_points) || coord.minCoeff() < 0) { + selection[i] = false; + continue; + } + // Use the b_iso to determine the weight for each coordinate. + double T; + if (b_iso != 0) { + T = std::exp(-b_iso * v_length * v_length / 4.0); + } else { + T = 1; + } + // unravel to the 1d index and write the complex value. + size_t index = + coord[2] + (n_points * coord[1]) + (n_points * n_points * coord[0]); + if (!data_in[index].real()) { + count++; + } + data_in[index] = {T, 0.0}; } - data_in[index] = {T, 0.0}; - } - std::cout << "Number of centroids used: " << count << std::endl; + std::cout << "Number of centroids used: " << count << std::endl; } /** @@ -84,76 +86,85 @@ void map_centroids_to_reciprocal_space_grid( * @param n_points The size of each dimension of the FFT grid. * @returns A boolean array indicating which coordinates were used for the FFT. */ -std::vector fft3d( - std::vector const& reciprocal_space_vectors, - std::vector &real_out, - double d_min, - double b_iso = 0, - uint32_t n_points = 256) { - auto start = std::chrono::system_clock::now(); - assert(real_out.size() == n_points * n_points * n_points); - - // We want to write out the real part of the FFT, but the pocketfft functions require - // complex vectors (we are using c2c i.e. complex to complex), so initialise these vectors. - // Note we should be able to use c2r rather than c2c, but I couldn't get this to work with - // the output ordering in c2r (JBE). - std::vector> complex_data_in(n_points * n_points * n_points); - std::vector> data_out(n_points * n_points * n_points); - - // A boolean array of whether the vectors were used for the FFT. - std::vector used_in_indexing(reciprocal_space_vectors.size(), true); - auto t1 = std::chrono::system_clock::now(); - - // Map the vectors onto a discrete grid. The values of the grid points are weights - // determined by b_iso. - map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso, n_points); - auto t2 = std::chrono::system_clock::now(); - - // Prepare the required objects for the FFT. - shape_t shape_in{n_points, n_points, n_points}; - int stride_x = sizeof(std::complex); - int stride_y = static_cast(sizeof(std::complex) * n_points); - int stride_z = static_cast(sizeof(std::complex) * n_points * n_points); - stride_t stride_in{stride_x, stride_y, stride_z}; // must have the size of each element. Must have - // size() equal to shape_in.size() - stride_t stride_out{stride_x, stride_y, stride_z}; // must have the size of each element. Must - // have size() equal to shape_in.size() - shape_t axes{0, 1, 2}; // 0 to shape.size()-1 inclusive - bool forward{FORWARD}; - - double fct{1.0f}; - size_t nthreads = 20; // use all threads available - is this working? - - // Do the FFT. - c2c(shape_in, - stride_in, - stride_out, - axes, - forward, - complex_data_in.data(), - data_out.data(), - fct, - nthreads); - auto t3 = std::chrono::system_clock::now(); - - // Take the square of the real part as the output. - for (int i = 0; i < real_out.size(); ++i) { - real_out[i] = std::pow(data_out[i].real(), 2); - } - auto t4 = std::chrono::system_clock::now(); - - std::chrono::duration elapsed_seconds = t4 - start; - std::chrono::duration elapsed_map = t2 - t1; - std::chrono::duration elapsed_make_arrays = t1 - start; - std::chrono::duration elapsed_c2c = t3 - t2; - std::chrono::duration elapsed_square = t4 - t3; - std::cout << "Total time for fft3d: " << elapsed_seconds.count() << "s" << std::endl; - - std::cout << "elapsed time for making data arrays: " << elapsed_make_arrays.count() << "s" << std::endl; - std::cout << "elapsed time for map_to_recip: " << elapsed_map.count() << "s" << std::endl; - std::cout << "elapsed time for c2c: " << elapsed_c2c.count() << "s" << std::endl; - std::cout << "elapsed time for squaring: " << elapsed_square.count() << "s" << std::endl; - - return used_in_indexing; -} +std::vector fft3d(std::vector const &reciprocal_space_vectors, + std::vector &real_out, + double d_min, + double b_iso = 0, + uint32_t n_points = 256) { + auto start = std::chrono::system_clock::now(); + assert(real_out.size() == n_points * n_points * n_points); + + // We want to write out the real part of the FFT, but the pocketfft functions require + // complex vectors (we are using c2c i.e. complex to complex), so initialise these vectors. + // Note we should be able to use c2r rather than c2c, but I couldn't get this to work with + // the output ordering in c2r (JBE). + std::vector> complex_data_in(n_points * n_points * n_points); + std::vector> data_out(n_points * n_points * n_points); + + // A boolean array of whether the vectors were used for the FFT. + std::vector used_in_indexing(reciprocal_space_vectors.size(), true); + auto t1 = std::chrono::system_clock::now(); + + // Map the vectors onto a discrete grid. The values of the grid points are weights + // determined by b_iso. + map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, + complex_data_in, + used_in_indexing, + d_min, + b_iso, + n_points); + auto t2 = std::chrono::system_clock::now(); + + // Prepare the required objects for the FFT. + shape_t shape_in{n_points, n_points, n_points}; + int stride_x = sizeof(std::complex); + int stride_y = static_cast(sizeof(std::complex) * n_points); + int stride_z = static_cast(sizeof(std::complex) * n_points * n_points); + stride_t stride_in{ + stride_x, stride_y, stride_z}; // must have the size of each element. Must have + // size() equal to shape_in.size() + stride_t stride_out{ + stride_x, stride_y, stride_z}; // must have the size of each element. Must + // have size() equal to shape_in.size() + shape_t axes{0, 1, 2}; // 0 to shape.size()-1 inclusive + bool forward{FORWARD}; + double fct{1.0f}; + size_t nthreads = 20; // use all threads available - is this working? + + // Do the FFT. + c2c(shape_in, + stride_in, + stride_out, + axes, + forward, + complex_data_in.data(), + data_out.data(), + fct, + nthreads); + auto t3 = std::chrono::system_clock::now(); + + // Take the square of the real part as the output. + for (int i = 0; i < real_out.size(); ++i) { + real_out[i] = std::pow(data_out[i].real(), 2); + } + auto t4 = std::chrono::system_clock::now(); + + std::chrono::duration elapsed_seconds = t4 - start; + std::chrono::duration elapsed_map = t2 - t1; + std::chrono::duration elapsed_make_arrays = t1 - start; + std::chrono::duration elapsed_c2c = t3 - t2; + std::chrono::duration elapsed_square = t4 - t3; + std::cout << "Total time for fft3d: " << elapsed_seconds.count() << "s" + << std::endl; + + std::cout << "elapsed time for making data arrays: " << elapsed_make_arrays.count() + << "s" << std::endl; + std::cout << "elapsed time for map_to_recip: " << elapsed_map.count() << "s" + << std::endl; + std::cout << "elapsed time for c2c: " << elapsed_c2c.count() << "s" << std::endl; + std::cout << "elapsed time for squaring: " << elapsed_square.count() << "s" + << std::endl; + + return used_in_indexing; +} diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 7e3a41c..762fb4b 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -1,10 +1,11 @@ -#include -#include +#include + +#include #include #include +#include +#include #include -#include -#include #define _USE_MATH_DEFINES #include @@ -13,7 +14,7 @@ using Eigen::Vector3i; // Define a modulo function that returns python style modulo for negative numbers. int modulo(int i, int n) { - return (i % n + n) % n; + return (i % n + n) % n; } /** @@ -23,151 +24,153 @@ int modulo(int i, int n) { * @param n_points The size of each dimension of the FFT grid. * @returns A tuple of grid points per peak and centres of mass of the peaks in fractional coordinates. */ -std::tuple,std::vector> -flood_fill(std::vector const& grid, - double rmsd_cutoff = 15.0, - int n_points = 256) { - auto start = std::chrono::system_clock::now(); - assert(grid.size() == n_points * n_points * n_points); - // First calculate the rmsd and use this to create a binary grid - double sumg = 0.0; - for (int i = 0; i < grid.size(); ++i) { - sumg += grid[i]; - } - double meang = sumg / grid.size(); - double sum_delta_sq = 0.0; - for (int i = 0; i < grid.size(); ++i) { - sum_delta_sq += std::pow(grid[i] - meang, 2); - } - double rmsd = std::pow(sum_delta_sq / grid.size(), 0.5); - std::vector grid_binary(n_points * n_points * n_points, 0); - double cutoff = rmsd_cutoff * rmsd; - for (int i = 0; i < grid.size(); i++) { - if (grid[i] >= cutoff) { - grid_binary[i] = 1; +std::tuple, std::vector> flood_fill( + std::vector const& grid, + double rmsd_cutoff = 15.0, + int n_points = 256) { + auto start = std::chrono::system_clock::now(); + assert(grid.size() == n_points * n_points * n_points); + // First calculate the rmsd and use this to create a binary grid + double sumg = 0.0; + for (int i = 0; i < grid.size(); ++i) { + sumg += grid[i]; } - } - auto t2 = std::chrono::system_clock::now(); - std::chrono::duration elapsed_time = t2 - start; - std::cout << "Time for first part of flood fill: " << elapsed_time.count() << "s" << std::endl; - - // Now do the flood fill. - // Wrap around the edge in all three dimensions to replicate the DIALS - // results exactly. - int n_voids = 0; - std::stack stack; - std::vector> accumulators; - int target = 1; - int replacement = 2; - std::vector grid_points_per_void; - int accumulator_index = 0; - - // precalculate a few constants - int total = n_points * n_points * n_points; - int n_sq = n_points * n_points; - int n_sq_minus_n = n_points * (n_points - 1); - int nn_sq_minus_n = n_points * n_points * (n_points - 1); - - for (int i = 0; i < grid_binary.size(); i++) { - if (grid_binary[i] == target) { - // Convert the array index into xyz coordinates. - // Store xyz coordinates on the stack, but index the array with 1D index. - int x = i % n_points; - int y = (i % n_sq) / n_points; - int z = i / n_sq; - Vector3i xyz = {x, y, z}; - stack.push(xyz); - grid_binary[i] = replacement; - std::vector this_accumulator; - accumulators.push_back(this_accumulator); - n_voids++; - grid_points_per_void.push_back(0); - - while (!stack.empty()) { - Vector3i this_xyz = stack.top(); - stack.pop(); - accumulators[accumulator_index].push_back(this_xyz); - grid_points_per_void[accumulator_index]++; - - int x_plus = this_xyz[0] + 1; - int modx = modulo(this_xyz[0], n_points); - int mody = modulo(this_xyz[1], n_points) * n_points; - int modz = modulo(this_xyz[2], n_points) * n_sq; - - // For x,y,z, check locations +-1 on the grid and add to stack if match. - - int array_index = modulo(x_plus, n_points) + mody + modz; - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {x_plus, this_xyz[1], this_xyz[2]}; - stack.push(new_xyz); - } - int x_minus = this_xyz[0] - 1; - array_index = modulo(x_minus, n_points) + mody + modz; - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {x_minus, this_xyz[1], this_xyz[2]}; - stack.push(new_xyz); + double meang = sumg / grid.size(); + double sum_delta_sq = 0.0; + for (int i = 0; i < grid.size(); ++i) { + sum_delta_sq += std::pow(grid[i] - meang, 2); + } + double rmsd = std::pow(sum_delta_sq / grid.size(), 0.5); + std::vector grid_binary(n_points * n_points * n_points, 0); + double cutoff = rmsd_cutoff * rmsd; + for (int i = 0; i < grid.size(); i++) { + if (grid[i] >= cutoff) { + grid_binary[i] = 1; } + } + auto t2 = std::chrono::system_clock::now(); + std::chrono::duration elapsed_time = t2 - start; + std::cout << "Time for first part of flood fill: " << elapsed_time.count() << "s" + << std::endl; - int y_plus = this_xyz[1] + 1; - array_index = modx + (modulo(y_plus, n_points) * n_points) + modz; - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {this_xyz[0], y_plus, this_xyz[2]}; - stack.push(new_xyz); - } - int y_minus = this_xyz[1] - 1; - array_index = modx + (modulo(y_minus, n_points) * n_points) + modz; - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {this_xyz[0], y_minus, this_xyz[2]}; - stack.push(new_xyz); - } + // Now do the flood fill. + // Wrap around the edge in all three dimensions to replicate the DIALS + // results exactly. + int n_voids = 0; + std::stack stack; + std::vector> accumulators; + int target = 1; + int replacement = 2; + std::vector grid_points_per_void; + int accumulator_index = 0; - int z_plus = this_xyz[2] + 1; - array_index = modx + mody + (modulo(z_plus, n_points) * n_sq); - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {this_xyz[0], this_xyz[1], z_plus}; - stack.push(new_xyz); - } - int z_minus = this_xyz[2] - 1; - array_index = modx + mody + (modulo(z_minus, n_points) * n_sq); - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {this_xyz[0], this_xyz[1], z_minus}; - stack.push(new_xyz); + // precalculate a few constants + int total = n_points * n_points * n_points; + int n_sq = n_points * n_points; + int n_sq_minus_n = n_points * (n_points - 1); + int nn_sq_minus_n = n_points * n_points * (n_points - 1); + + for (int i = 0; i < grid_binary.size(); i++) { + if (grid_binary[i] == target) { + // Convert the array index into xyz coordinates. + // Store xyz coordinates on the stack, but index the array with 1D index. + int x = i % n_points; + int y = (i % n_sq) / n_points; + int z = i / n_sq; + Vector3i xyz = {x, y, z}; + stack.push(xyz); + grid_binary[i] = replacement; + std::vector this_accumulator; + accumulators.push_back(this_accumulator); + n_voids++; + grid_points_per_void.push_back(0); + + while (!stack.empty()) { + Vector3i this_xyz = stack.top(); + stack.pop(); + accumulators[accumulator_index].push_back(this_xyz); + grid_points_per_void[accumulator_index]++; + + int x_plus = this_xyz[0] + 1; + int modx = modulo(this_xyz[0], n_points); + int mody = modulo(this_xyz[1], n_points) * n_points; + int modz = modulo(this_xyz[2], n_points) * n_sq; + + // For x,y,z, check locations +-1 on the grid and add to stack if match. + + int array_index = modulo(x_plus, n_points) + mody + modz; + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {x_plus, this_xyz[1], this_xyz[2]}; + stack.push(new_xyz); + } + int x_minus = this_xyz[0] - 1; + array_index = modulo(x_minus, n_points) + mody + modz; + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {x_minus, this_xyz[1], this_xyz[2]}; + stack.push(new_xyz); + } + + int y_plus = this_xyz[1] + 1; + array_index = modx + (modulo(y_plus, n_points) * n_points) + modz; + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {this_xyz[0], y_plus, this_xyz[2]}; + stack.push(new_xyz); + } + int y_minus = this_xyz[1] - 1; + array_index = modx + (modulo(y_minus, n_points) * n_points) + modz; + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {this_xyz[0], y_minus, this_xyz[2]}; + stack.push(new_xyz); + } + + int z_plus = this_xyz[2] + 1; + array_index = modx + mody + (modulo(z_plus, n_points) * n_sq); + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {this_xyz[0], this_xyz[1], z_plus}; + stack.push(new_xyz); + } + int z_minus = this_xyz[2] - 1; + array_index = modx + mody + (modulo(z_minus, n_points) * n_sq); + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + Vector3i new_xyz = {this_xyz[0], this_xyz[1], z_minus}; + stack.push(new_xyz); + } + } + replacement++; + accumulator_index++; } - } - replacement++; - accumulator_index++; } - } - auto t3 = std::chrono::system_clock::now(); - std::chrono::duration elapsed_time2 = t3 - t2; - std::cout << "Time for second part of flood fill: " << elapsed_time2.count() << "s" << std::endl; - - // Now calculate the unweighted centres of mass of each group, in fractional coordinates. - std::vector centres_of_mass_frac(n_voids); - for (int i = 0; i < accumulators.size(); i++) { - std::vector values = accumulators[i]; - int n = values.size(); - int divisor = n * n_points; - double x = 0.0; - double y = 0.0; - double z = 0.0; - for (int j = 0; j < n; j++) { - x += values[j][0]; - y += values[j][1]; - z += values[j][2]; + auto t3 = std::chrono::system_clock::now(); + std::chrono::duration elapsed_time2 = t3 - t2; + std::cout << "Time for second part of flood fill: " << elapsed_time2.count() << "s" + << std::endl; + + // Now calculate the unweighted centres of mass of each group, in fractional coordinates. + std::vector centres_of_mass_frac(n_voids); + for (int i = 0; i < accumulators.size(); i++) { + std::vector values = accumulators[i]; + int n = values.size(); + int divisor = n * n_points; + double x = 0.0; + double y = 0.0; + double z = 0.0; + for (int j = 0; j < n; j++) { + x += values[j][0]; + y += values[j][1]; + z += values[j][2]; + } + x /= divisor; + y /= divisor; + z /= divisor; + centres_of_mass_frac[i] = {z, y, x}; } - x /= divisor; - y /= divisor; - z /= divisor; - centres_of_mass_frac[i] = {z, y, x}; - } - return std::make_tuple(grid_points_per_void, centres_of_mass_frac); + return std::make_tuple(grid_points_per_void, centres_of_mass_frac); } /** @@ -180,34 +183,34 @@ flood_fill(std::vector const& grid, std::tuple, std::vector> flood_fill_filter( std::vector grid_points_per_void, std::vector centres_of_mass_frac, - double peak_volume_cutoff = 0.15){ - // Filter out based on iqr range and peak_volume_cutoff - std::vector grid_points_per_void_unsorted(grid_points_per_void); - std::sort(grid_points_per_void.begin(), grid_points_per_void.end()); - // The peak around the origin of the FFT can be very large in volume, - // so use the IQR range to filter high-volume peaks out that are not - // from the typical distribution of points, before applying the peak - // volume cutoff based on a fraction of the max volume in the remaining - // array. - int Q3_index = grid_points_per_void.size() * 3 / 4; - int Q1_index = grid_points_per_void.size() / 4; - int iqr = grid_points_per_void[Q3_index] - grid_points_per_void[Q1_index]; - int iqr_multiplier = 5; - int cut = (iqr * iqr_multiplier) + grid_points_per_void[Q3_index]; - - // Remove abnormally high volumes - while (grid_points_per_void[grid_points_per_void.size() - 1] > cut) { - grid_points_per_void.pop_back(); - } - int max_val = grid_points_per_void[grid_points_per_void.size() - 1]; - // Cut based on a fraction of the max volume. - int peak_cutoff = (int)(peak_volume_cutoff * max_val); - for (int i = grid_points_per_void_unsorted.size() - 1; i >= 0; i--) { - if (grid_points_per_void_unsorted[i] <= peak_cutoff) { - grid_points_per_void_unsorted.erase(grid_points_per_void_unsorted.begin() + i); - centres_of_mass_frac.erase(centres_of_mass_frac.begin() + i); + double peak_volume_cutoff = 0.15) { + // Filter out based on iqr range and peak_volume_cutoff + std::vector grid_points_per_void_unsorted(grid_points_per_void); + std::sort(grid_points_per_void.begin(), grid_points_per_void.end()); + // The peak around the origin of the FFT can be very large in volume, + // so use the IQR range to filter high-volume peaks out that are not + // from the typical distribution of points, before applying the peak + // volume cutoff based on a fraction of the max volume in the remaining + // array. + int Q3_index = grid_points_per_void.size() * 3 / 4; + int Q1_index = grid_points_per_void.size() / 4; + int iqr = grid_points_per_void[Q3_index] - grid_points_per_void[Q1_index]; + int iqr_multiplier = 5; + int cut = (iqr * iqr_multiplier) + grid_points_per_void[Q3_index]; + + // Remove abnormally high volumes + while (grid_points_per_void[grid_points_per_void.size() - 1] > cut) { + grid_points_per_void.pop_back(); } - } - return std::make_tuple(grid_points_per_void_unsorted, centres_of_mass_frac); + int max_val = grid_points_per_void[grid_points_per_void.size() - 1]; + // Cut based on a fraction of the max volume. + int peak_cutoff = (int)(peak_volume_cutoff * max_val); + for (int i = grid_points_per_void_unsorted.size() - 1; i >= 0; i--) { + if (grid_points_per_void_unsorted[i] <= peak_cutoff) { + grid_points_per_void_unsorted.erase(grid_points_per_void_unsorted.begin() + + i); + centres_of_mass_frac.erase(centres_of_mass_frac.begin() + i); + } + } + return std::make_tuple(grid_points_per_void_unsorted, centres_of_mass_frac); } - diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 4952c2a..36dd06d 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -1,33 +1,35 @@ -#include -#include -#include -#include -#include -#include -#include "xyz_to_rlp.cc" -#include "flood_fill.cc" -#include "sites_to_vecs.cc" -#include "fft3d.cc" -#include -#include -#include #include -#include -#include #include +#include +#include #include +#include #include #include #include + +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include "fft3d.cc" +#include "flood_fill.cc" #include "gemmi/symmetry.hpp" +#include "sites_to_vecs.cc" +#include "xyz_to_rlp.cc" -using Eigen::Vector3d; using Eigen::Matrix3d; +using Eigen::Vector3d; using Eigen::Vector3i; using json = nlohmann::json; -int main(int argc, char **argv) { +int main(int argc, char** argv) { // The purpose of an indexer is to determine the lattice model that best // explains the positions of the strong spots found during spot-finding. // The lattice model is a set of three vectors that define the crystal @@ -37,8 +39,7 @@ int main(int argc, char **argv) { // describes the crystal lattice and an updated set of experiment models. auto t1 = std::chrono::system_clock::now(); auto parser = argparse::ArgumentParser(); - parser.add_argument("-e", "--expt") - .help("Path to the DIALS expt file"); + parser.add_argument("-e", "--expt").help("Path to the DIALS expt file"); parser.add_argument("-r", "--refl") .help("Path to the h5 reflection table file containing spotfinding results"); parser.add_argument("--dmin") @@ -47,47 +48,53 @@ int main(int argc, char **argv) { parser.add_argument("--max-cell") .help("The maximum possible cell length to consider during indexing") .scan<'f', float>(); - parser.add_argument("--fft-npoints") // mainly for testing, likely would always want to keep it as 256. - .help("The number of grid points to use for the fft. Powers of two are most efficient.") + parser + .add_argument( + "--fft-npoints") // mainly for testing, likely would always want to keep it as 256. + .help( + "The number of grid points to use for the fft. Powers of two are most " + "efficient.") .default_value(256) .scan<'u', uint32_t>(); parser.parse_args(argc, argv); - if (!parser.is_used("--expt")){ + if (!parser.is_used("--expt")) { fmt::print("Error: must specify experiment list file with --expt\n"); std::exit(1); } - if (!parser.is_used("--refl")){ - fmt::print("Error: must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); + if (!parser.is_used("--refl")) { + fmt::print( + "Error: must specify spotfinding results file (in DIALS HDF5 format) with " + "--refl\n"); std::exit(1); } // In DIALS, the max cell is automatically determined through a nearest // neighbour analysis that requires the annlib package. For now, // let's make this a required argument to help with testing/comparison // to DIALS. - if (!parser.is_used("--max-cell")){ + if (!parser.is_used("--max-cell")) { fmt::print("Error: must specify --max-cell\n"); std::exit(1); } // FIXME use highest resolution by default to remove this requirement. - if (!parser.is_used("--dmin")){ + if (!parser.is_used("--dmin")) { fmt::print("Error: must specify --dmin\n"); std::exit(1); } std::string imported_expt = parser.get("--expt"); - std::string filename = parser.get("--refl"); + std::string filename = parser.get("--refl"); double max_cell = parser.get("max-cell"); double d_min = parser.get("dmin"); - + // Parse the experiment list (a json file) and load the models. // Will be moved to dx2. std::ifstream f(imported_expt); json elist_json_obj; try { elist_json_obj = json::parse(f); - } - catch(json::parse_error& ex){ - std::cerr << "Error: Unable to read " << imported_expt.c_str() << "; json parse error at byte " << ex.byte << std::endl; + } catch (json::parse_error& ex) { + std::cerr << "Error: Unable to read " << imported_expt.c_str() + << "; json parse error at byte " << ex.byte << std::endl; std::exit(1); } @@ -108,29 +115,31 @@ int main(int argc, char **argv) { // Note, xyzobs_px is the flattened, on-disk representation of the array // i.e. if there are 100 spots, the length of xyzobs_px is 300, and // contains the elements [x0, y0, z0, x1, y1, z1, ..., x99, y99, z99] - std::vector xyzobs_px = read_array_from_h5_file(filename, array_name); + std::vector xyzobs_px = + read_array_from_h5_file(filename, array_name); // The diffraction spots form a lattice in reciprocal space (if the experimental // geometry is accurate). So use the experimental models to transform the spot // coordinates on the detector into reciprocal space. std::vector rlp = xyz_to_rlp(xyzobs_px, detector, beam, scan, gonio); std::cout << "Number of reflections: " << rlp.size() << std::endl; - + // b_iso is an isotropic b-factor used to weight the points when doing the fft. // i.e. high resolution (weaker) spots are downweighted by the expected // intensity fall-off as as function of resolution. double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); uint32_t n_points = parser.get("--fft-npoints"); std::cout << "Setting b_iso = " << b_iso << std::endl; - + // Create an array to store the fft result. This is a 3D grid of points, typically 256^3. - std::vector real_fft_result(n_points*n_points*n_points, 0.0); + std::vector real_fft_result(n_points * n_points * n_points, 0.0); // Do the fft of the reciprocal lattice coordinates. // the used in indexing array denotes whether a coordinate was used for the // fft (might not be if dmin filter was used for example). The used_in_indexing array // is sometimes used onwards in the dials indexing algorithms, so keep for now. - std::vector used_in_indexing = fft3d(rlp, real_fft_result, d_min, b_iso, n_points); + std::vector used_in_indexing = + fft3d(rlp, real_fft_result, d_min, b_iso, n_points); // The fft result is noisy. We want to extract the peaks, which may be spread over several // points on the fft grid. So we use a flood fill algorithm (https://en.wikipedia.org/wiki/Flood_fill) @@ -141,14 +150,17 @@ int main(int argc, char **argv) { std::vector grid_points_per_void; std::vector centres_of_mass_frac; // 15.0 is the DIALS 'rmsd_cutoff' parameter to filter out weak peaks. - std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(real_fft_result, 15.0, n_points); + std::tie(grid_points_per_void, centres_of_mass_frac) = + flood_fill(real_fft_result, 15.0, n_points); // Do some further filtering, 0.15 is the DIALS peak_volume_cutoff parameter. - std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); - + std::tie(grid_points_per_void, centres_of_mass_frac) = + flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); + // Convert the peak centres from the fft grid into vectors in reciprocal space. These are our candidate // lattice vectors. // 3.0 is the min cell parameter. - std::vector candidate_lattice_vectors = sites_to_vecs(centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell, n_points); + std::vector candidate_lattice_vectors = sites_to_vecs( + centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell, n_points); // at this point, we will test combinations of the candidate vectors, use those to index the spots, do some // refinement of the candidates and choose the best one. Then we will do some more refinement including extra @@ -162,7 +174,7 @@ int main(int argc, char **argv) { std::string n_vecs = std::to_string(candidate_lattice_vectors.size() - 1); size_t n_zero = n_vecs.length(); json vecs_out; - for (int i=0;i {expt_out}; - elist_out["crystal"] = std::array {cryst_out}; // add the the actual models - elist_out["scan"] = std::array {scan.to_json()}; - elist_out["goniometer"] = std::array {gonio.to_json()}; - elist_out["beam"] = std::array {beam.to_json()}; - elist_out["detector"] = std::array {detector.to_json()}; - - std::ofstream efile("elist.json"); - efile << elist_out.dump(4); + if (candidate_lattice_vectors.size() < 3) { + std::cout << "Insufficient number of candidate vectors to make a crystal model." + << std::endl; + } else { + gemmi::SpaceGroup space_group = *gemmi::find_spacegroup_by_name("P1"); + Crystal best_xtal{candidate_lattice_vectors[0], + candidate_lattice_vectors[1], + candidate_lattice_vectors[2], + space_group}; + json cryst_out = best_xtal.to_json(); + + // save an example experiment list + json elist_out; // a list of potentially multiple experiments + elist_out["__id__"] = "ExperimentList"; + json expt_out; // our single experiment + // no imageset (for now?). + expt_out["__id__"] = "Experiment"; + expt_out["identifier"] = "test"; + expt_out["beam"] = + 0; // the indices of the models that will correspond to our experiment + expt_out["detector"] = 0; + expt_out["goniometer"] = 0; + expt_out["scan"] = 0; + expt_out["crystal"] = 0; + elist_out["experiment"] = std::array{expt_out}; + elist_out["crystal"] = + std::array{cryst_out}; // add the the actual models + elist_out["scan"] = std::array{scan.to_json()}; + elist_out["goniometer"] = std::array{gonio.to_json()}; + elist_out["beam"] = std::array{beam.to_json()}; + elist_out["detector"] = std::array{detector.to_json()}; + + std::ofstream efile("elist.json"); + efile << elist_out.dump(4); } - + auto t2 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time = t2 - t1; std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; - } diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 46baee5..6ad9592 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -1,71 +1,70 @@ -#include +#include +#include + #include #include -#include -#include #include -#include +#include +#include using Eigen::Vector3d; - #define _USE_MATH_DEFINES #include - // Define a few simple data structures to help with the calculations below. class VectorGroup { -public: - void add(Vector3d vec, int weight) { - vectors.push_back(vec); - weights.push_back(weight); - } - Vector3d mean() { - int n = vectors.size(); - double sum_x = 0.0; - double sum_y = 0.0; - double sum_z = 0.0; - for (const Vector3d& i : vectors) { - sum_x += i[0]; - sum_y += i[1]; - sum_z += i[2]; + public: + void add(Vector3d vec, int weight) { + vectors.push_back(vec); + weights.push_back(weight); } - Vector3d m = {sum_x / n, sum_y / n, sum_z / n}; - return m; - } - std::vector vectors{}; - std::vector weights{}; + Vector3d mean() { + int n = vectors.size(); + double sum_x = 0.0; + double sum_y = 0.0; + double sum_z = 0.0; + for (const Vector3d& i : vectors) { + sum_x += i[0]; + sum_y += i[1]; + sum_z += i[2]; + } + Vector3d m = {sum_x / n, sum_y / n, sum_z / n}; + return m; + } + std::vector vectors{}; + std::vector weights{}; }; struct SiteData { - Vector3d site; - double length; - int volume; + Vector3d site; + double length; + int volume; }; bool compare_site_data(const SiteData& a, const SiteData& b) { - return a.length < b.length; + return a.length < b.length; } bool compare_site_data_volume(const SiteData& a, const SiteData& b) { - return a.volume > b.volume; + return a.volume > b.volume; } bool is_approximate_integer_multiple(Vector3d v1, Vector3d v2, double relative_length_tolerance = 0.2, double angular_tolerance = 5.0) { - double angle = angle_between_vectors_degrees(v1, v2); - if ((angle < angular_tolerance) || (std::abs(180 - angle) < angular_tolerance)) { - double l1 = v1.norm(); - double l2 = v2.norm(); - if (l1 > l2) { - std::swap(l1, l2); - } - double n = l2 / l1; - if (std::abs(std::round(n) - n) < relative_length_tolerance) { - return true; + double angle = angle_between_vectors_degrees(v1, v2); + if ((angle < angular_tolerance) || (std::abs(180 - angle) < angular_tolerance)) { + double l1 = v1.norm(); + double l2 = v2.norm(); + if (l1 > l2) { + std::swap(l1, l2); + } + double n = l2 / l1; + if (std::abs(std::round(n) - n) < relative_length_tolerance) { + return true; + } } - } - return false; + return false; } /** @@ -78,117 +77,122 @@ bool is_approximate_integer_multiple(Vector3d v1, * @param n_points The size of each dimension of the FFT grid. * @returns Unique vectors, sorted by volume, that give describe the FFT peaks. */ -std::vector sites_to_vecs( - std::vector centres_of_mass_frac, - std::vector grid_points_per_void, - double d_min, - double min_cell = 3.0, - double max_cell = 92.3, - uint32_t n_points = 256) { - auto start = std::chrono::system_clock::now(); - // Calculate the scaling between the FFT grid and reciprocal space. - double fft_cell_length = n_points * d_min / 2.0; - // Use 'sites_mod_short' and convert to cartesian (but keep in same array) - for (int i = 0; i < centres_of_mass_frac.size(); i++) { - for (size_t j = 0; j < 3; j++) { - if (centres_of_mass_frac[i][j] > 0.5) { - centres_of_mass_frac[i][j]--; - } - centres_of_mass_frac[i][j] *= fft_cell_length; +std::vector sites_to_vecs(std::vector centres_of_mass_frac, + std::vector grid_points_per_void, + double d_min, + double min_cell = 3.0, + double max_cell = 92.3, + uint32_t n_points = 256) { + auto start = std::chrono::system_clock::now(); + // Calculate the scaling between the FFT grid and reciprocal space. + double fft_cell_length = n_points * d_min / 2.0; + // Use 'sites_mod_short' and convert to cartesian (but keep in same array) + for (int i = 0; i < centres_of_mass_frac.size(); i++) { + for (size_t j = 0; j < 3; j++) { + if (centres_of_mass_frac[i][j] > 0.5) { + centres_of_mass_frac[i][j]--; + } + centres_of_mass_frac[i][j] *= fft_cell_length; + } } - } - // now do some filtering based on the min and max cell - std::vector filtered_data; - for (int i = 0; i < centres_of_mass_frac.size(); i++) { - auto v = centres_of_mass_frac[i]; - double length = - std::pow(std::pow(v[0], 2) + std::pow(v[1], 2) + std::pow(v[2], 2), 0.5); - if ((length > min_cell) && (length < 2 * max_cell)) { - SiteData site_data = {centres_of_mass_frac[i], length, grid_points_per_void[i]}; - filtered_data.push_back(site_data); + // now do some filtering based on the min and max cell + std::vector filtered_data; + for (int i = 0; i < centres_of_mass_frac.size(); i++) { + auto v = centres_of_mass_frac[i]; + double length = + std::pow(std::pow(v[0], 2) + std::pow(v[1], 2) + std::pow(v[2], 2), 0.5); + if ((length > min_cell) && (length < 2 * max_cell)) { + SiteData site_data = { + centres_of_mass_frac[i], length, grid_points_per_void[i]}; + filtered_data.push_back(site_data); + } } - } - // Now sort the filtered data. Ggroup together those - // with similar angles and lengths (e.g. inverse pairs from the FFT). - double relative_length_tolerance = 0.1; - double angular_tolerance = 5.0; - std::vector vector_groups{}; - for (int i = 0; i < filtered_data.size(); i++) { - bool matched_group = false; - double length = filtered_data[i].length; - for (int j = 0; j < vector_groups.size(); j++) { - Vector3d mean_v = vector_groups[j].mean(); - double mean_v_length = mean_v.norm(); - if ((std::abs(mean_v_length - length) / std::max(mean_v_length, length)) - < relative_length_tolerance) { - double angle = angle_between_vectors_degrees(mean_v, filtered_data[i].site); - if (angle < angular_tolerance) { - vector_groups[j].add(filtered_data[i].site, filtered_data[i].volume); - matched_group = true; - break; - } else if (std::abs(180 - angle) < angular_tolerance) { - vector_groups[j].add(-1.0 * filtered_data[i].site, filtered_data[i].volume); - matched_group = true; - break; + // Now sort the filtered data. Ggroup together those + // with similar angles and lengths (e.g. inverse pairs from the FFT). + double relative_length_tolerance = 0.1; + double angular_tolerance = 5.0; + std::vector vector_groups{}; + for (int i = 0; i < filtered_data.size(); i++) { + bool matched_group = false; + double length = filtered_data[i].length; + for (int j = 0; j < vector_groups.size(); j++) { + Vector3d mean_v = vector_groups[j].mean(); + double mean_v_length = mean_v.norm(); + if ((std::abs(mean_v_length - length) / std::max(mean_v_length, length)) + < relative_length_tolerance) { + double angle = + angle_between_vectors_degrees(mean_v, filtered_data[i].site); + if (angle < angular_tolerance) { + vector_groups[j].add(filtered_data[i].site, + filtered_data[i].volume); + matched_group = true; + break; + } else if (std::abs(180 - angle) < angular_tolerance) { + vector_groups[j].add(-1.0 * filtered_data[i].site, + filtered_data[i].volume); + matched_group = true; + break; + } + } + } + // If it didn't match any existing group, create a new one. + if (!matched_group) { + VectorGroup group = VectorGroup(); + group.add(filtered_data[i].site, filtered_data[i].volume); + vector_groups.push_back(group); } - } } - // If it didn't match any existing group, create a new one. - if (!matched_group) { - VectorGroup group = VectorGroup(); - group.add(filtered_data[i].site, filtered_data[i].volume); - vector_groups.push_back(group); + // Create 'site's based on the data from the groups. + std::vector grouped_data; + for (int i = 0; i < vector_groups.size(); i++) { + Vector3d site = vector_groups[i].mean(); + int max = *std::max_element(vector_groups[i].weights.begin(), + vector_groups[i].weights.end()); + SiteData site_data = {site, site.norm(), max}; + grouped_data.push_back(site_data); } - } - // Create 'site's based on the data from the groups. - std::vector grouped_data; - for (int i = 0; i < vector_groups.size(); i++) { - Vector3d site = vector_groups[i].mean(); - int max = *std::max_element(vector_groups[i].weights.begin(), - vector_groups[i].weights.end()); - SiteData site_data = {site, site.norm(), max}; - grouped_data.push_back(site_data); - } - // Sort by volume, then by length. - std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data_volume); - std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data); + // Sort by volume, then by length. + std::stable_sort( + grouped_data.begin(), grouped_data.end(), compare_site_data_volume); + std::stable_sort(grouped_data.begin(), grouped_data.end(), compare_site_data); - // Now check if any sites are integer multiples of other sites. - std::vector unique_sites; - for (int i = 0; i < grouped_data.size(); i++) { - bool is_unique = true; - Vector3d v = grouped_data[i].site; - for (int j = 0; j < unique_sites.size(); j++) { - if (unique_sites[j].volume > grouped_data[i].volume) { - if (is_approximate_integer_multiple(unique_sites[j].site, v)) { - std::cout << "rejecting " << v.norm() << ": is integer multiple of " - << unique_sites[j].site.norm() << std::endl; - is_unique = false; - break; + // Now check if any sites are integer multiples of other sites. + std::vector unique_sites; + for (int i = 0; i < grouped_data.size(); i++) { + bool is_unique = true; + Vector3d v = grouped_data[i].site; + for (int j = 0; j < unique_sites.size(); j++) { + if (unique_sites[j].volume > grouped_data[i].volume) { + if (is_approximate_integer_multiple(unique_sites[j].site, v)) { + std::cout << "rejecting " << v.norm() << ": is integer multiple of " + << unique_sites[j].site.norm() << std::endl; + is_unique = false; + break; + } + } + } + if (is_unique) { + SiteData site{v, v.norm(), grouped_data[i].volume}; + unique_sites.push_back(site); } - } } - if (is_unique) { - SiteData site{v, v.norm(), grouped_data[i].volume}; - unique_sites.push_back(site); + // now sort by peak volume again + std::stable_sort( + unique_sites.begin(), unique_sites.end(), compare_site_data_volume); + std::vector unique_vectors_sorted; + std::cout << "Candidate basis vectors: " << std::endl; + for (int i = 0; i < unique_sites.size(); i++) { + unique_vectors_sorted.push_back(unique_sites[i].site); + std::cout << i << " " << unique_sites[i].length << " " << unique_sites[i].volume + << std::endl; } - } - // now sort by peak volume again - std::stable_sort(unique_sites.begin(), unique_sites.end(), compare_site_data_volume); - std::vector unique_vectors_sorted; - std::cout << "Candidate basis vectors: " << std::endl; - for (int i = 0; i < unique_sites.size(); i++) { - unique_vectors_sorted.push_back(unique_sites[i].site); - std::cout << i << " " << unique_sites[i].length << " " << unique_sites[i].volume << std::endl; - } - auto end = std::chrono::system_clock::now(); - std::chrono::duration elapsed_seconds = end - start; - std::cout << "elapsed time for sites_to_vecs: " << elapsed_seconds.count() << "s" - << std::endl; - return unique_vectors_sorted; + auto end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + std::cout << "elapsed time for sites_to_vecs: " << elapsed_seconds.count() << "s" + << std::endl; + return unique_vectors_sorted; } - diff --git a/baseline_indexer/xyz_to_rlp.cc b/baseline_indexer/xyz_to_rlp.cc index a12fdc8..0eeca46 100644 --- a/baseline_indexer/xyz_to_rlp.cc +++ b/baseline_indexer/xyz_to_rlp.cc @@ -1,9 +1,10 @@ -#include -#include -#include #include -#include +#include #include +#include +#include + +#include using Eigen::Matrix3d; using Eigen::Vector3d; @@ -17,67 +18,65 @@ using Eigen::Vector3d; * @param gonio A dx2 Goniometer object. * @returns A vector of reciprocal space coordinates. */ -std::vector xyz_to_rlp( - const std::vector &xyzobs_px, - const Panel &panel, - const MonochromaticBeam &beam, - const Scan &scan, - const Goniometer &gonio) { - // Use the experimental models to perform a coordinate transformation from - // pixel coordinates in detector space to reciprocal space, in units of - // inverse angstrom. - // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method +std::vector xyz_to_rlp(const std::vector &xyzobs_px, + const Panel &panel, + const MonochromaticBeam &beam, + const Scan &scan, + const Goniometer &gonio) { + // Use the experimental models to perform a coordinate transformation from + // pixel coordinates in detector space to reciprocal space, in units of + // inverse angstrom. + // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method - double DEG2RAD = M_PI / 180.0; + double DEG2RAD = M_PI / 180.0; - // xyzobs_px is a flattened array, we want to return a vector of Vector3ds, - // so the size is divided by 3. - std::vector rlp(xyzobs_px.size() / 3); + // xyzobs_px is a flattened array, we want to return a vector of Vector3ds, + // so the size is divided by 3. + std::vector rlp(xyzobs_px.size() / 3); - // Extract the quantities from the models that are needed for the calculation. - Vector3d s0 = beam.get_s0(); - double wl = beam.get_wavelength(); - std::array oscillation = scan.get_oscillation(); - double osc_width = oscillation[1]; - double osc_start = oscillation[0]; - int image_range_start = scan.get_image_range()[0]; - Matrix3d setting_rotation_inverse = gonio.get_setting_rotation().inverse(); - Matrix3d sample_rotation_inverse = gonio.get_sample_rotation().inverse(); - Vector3d rotation_axis = gonio.get_rotation_axis(); - Matrix3d d_matrix = panel.get_d_matrix(); + // Extract the quantities from the models that are needed for the calculation. + Vector3d s0 = beam.get_s0(); + double wl = beam.get_wavelength(); + std::array oscillation = scan.get_oscillation(); + double osc_width = oscillation[1]; + double osc_start = oscillation[0]; + int image_range_start = scan.get_image_range()[0]; + Matrix3d setting_rotation_inverse = gonio.get_setting_rotation().inverse(); + Matrix3d sample_rotation_inverse = gonio.get_sample_rotation().inverse(); + Vector3d rotation_axis = gonio.get_rotation_axis(); + Matrix3d d_matrix = panel.get_d_matrix(); - for (int i = 0; i < rlp.size(); ++i) { - // first convert detector pixel positions into mm - int vec_idx= 3*i; - double x1 = xyzobs_px[vec_idx]; - double x2 = xyzobs_px[vec_idx+1]; - double x3 = xyzobs_px[vec_idx+2]; - std::array xymm = panel.px_to_mm(x1,x2); - // convert the image 'z' coordinate to rotation angle based on the scan data - double rot_angle = - (((x3 + 1 - image_range_start) * osc_width) + osc_start) * DEG2RAD; - - // calculate the s1 vector using the detector d matrix - Vector3d m = {xymm[0], xymm[1], 1.0}; - Vector3d s1_i = d_matrix * m; - s1_i.normalize(); - // convert into inverse ansgtroms - Vector3d s1_this = s1_i / wl; - - // now apply the goniometer matrices - // see https://dials.github.io/documentation/conventions.html for full conventions - // rlp = F^-1 * R'^-1 * S^-1 * (s1-s0) - Vector3d S = setting_rotation_inverse * (s1_this - s0); - double cos = std::cos(-1.0 * rot_angle); - double sin = std::sin(-1.0 * rot_angle); - // The DIALS equivalent to the code below is - // rlp_this = S.rotate_around_origin(gonio.rotation_axis, -1.0 * rot_angle); - Vector3d rlp_this = (S * cos) - + (rotation_axis * rotation_axis.dot(S) * (1 - cos)) - + (sin * rotation_axis.cross(S)); - - rlp[i] = sample_rotation_inverse * rlp_this; - } - return rlp; -} + for (int i = 0; i < rlp.size(); ++i) { + // first convert detector pixel positions into mm + int vec_idx = 3 * i; + double x1 = xyzobs_px[vec_idx]; + double x2 = xyzobs_px[vec_idx + 1]; + double x3 = xyzobs_px[vec_idx + 2]; + std::array xymm = panel.px_to_mm(x1, x2); + // convert the image 'z' coordinate to rotation angle based on the scan data + double rot_angle = + (((x3 + 1 - image_range_start) * osc_width) + osc_start) * DEG2RAD; + // calculate the s1 vector using the detector d matrix + Vector3d m = {xymm[0], xymm[1], 1.0}; + Vector3d s1_i = d_matrix * m; + s1_i.normalize(); + // convert into inverse ansgtroms + Vector3d s1_this = s1_i / wl; + + // now apply the goniometer matrices + // see https://dials.github.io/documentation/conventions.html for full conventions + // rlp = F^-1 * R'^-1 * S^-1 * (s1-s0) + Vector3d S = setting_rotation_inverse * (s1_this - s0); + double cos = std::cos(-1.0 * rot_angle); + double sin = std::sin(-1.0 * rot_angle); + // The DIALS equivalent to the code below is + // rlp_this = S.rotate_around_origin(gonio.rotation_axis, -1.0 * rot_angle); + Vector3d rlp_this = (S * cos) + + (rotation_axis * rotation_axis.dot(S) * (1 - cos)) + + (sin * rotation_axis.cross(S)); + + rlp[i] = sample_rotation_inverse * rlp_this; + } + return rlp; +} From 33f15af302a4f9ca6f45748f38dfa39a267c9c5f Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:34:17 +0000 Subject: [PATCH 38/72] Apply suggestions from code review - small changes/one-liners Co-authored-by: Dimitri Vlachos --- baseline_indexer/fft3d.cc | 2 +- baseline_indexer/flood_fill.cc | 4 ++-- baseline_indexer/sites_to_vecs.cc | 26 +++++++------------------- baseline_indexer/xyz_to_rlp.cc | 2 +- 4 files changed, 11 insertions(+), 23 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 469025d..3516854 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -53,7 +53,7 @@ void map_centroids_to_reciprocal_space_grid( Vector3i coord; // map to the nearest point in each dimension. for (int j = 0; j < 3; j++) { - coord[j] = ((int)round(v[j] * one_over_rlgrid)) + half_n_points; + coord[j] = static_cast(round(v[j] * one_over_rlgrid)) + half_n_points; } if ((coord.maxCoeff() >= n_points) || coord.minCoeff() < 0) { selection[i] = false; diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 762fb4b..c408bb4 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -195,7 +195,7 @@ std::tuple, std::vector> flood_fill_filter( int Q3_index = grid_points_per_void.size() * 3 / 4; int Q1_index = grid_points_per_void.size() / 4; int iqr = grid_points_per_void[Q3_index] - grid_points_per_void[Q1_index]; - int iqr_multiplier = 5; + constexpr int iqr_multiplier = 5; int cut = (iqr * iqr_multiplier) + grid_points_per_void[Q3_index]; // Remove abnormally high volumes @@ -204,7 +204,7 @@ std::tuple, std::vector> flood_fill_filter( } int max_val = grid_points_per_void[grid_points_per_void.size() - 1]; // Cut based on a fraction of the max volume. - int peak_cutoff = (int)(peak_volume_cutoff * max_val); + int peak_cutoff = static_cast(peak_volume_cutoff * max_val); for (int i = grid_points_per_void_unsorted.size() - 1; i >= 0; i--) { if (grid_points_per_void_unsorted[i] <= peak_cutoff) { grid_points_per_void_unsorted.erase(grid_points_per_void_unsorted.begin() diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 6ad9592..dbafd15 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -20,17 +20,9 @@ class VectorGroup { weights.push_back(weight); } Vector3d mean() { - int n = vectors.size(); - double sum_x = 0.0; - double sum_y = 0.0; - double sum_z = 0.0; - for (const Vector3d& i : vectors) { - sum_x += i[0]; - sum_y += i[1]; - sum_z += i[2]; - } - Vector3d m = {sum_x / n, sum_y / n, sum_z / n}; - return m; + Vector3d sum = + std::accumulate(vectors.begin(), vectors.end(), Vector3d{0, 0, 0}); + return sum / static_cast(vectors.size()); } std::vector vectors{}; std::vector weights{}; @@ -98,14 +90,10 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, // now do some filtering based on the min and max cell std::vector filtered_data; - for (int i = 0; i < centres_of_mass_frac.size(); i++) { - auto v = centres_of_mass_frac[i]; - double length = - std::pow(std::pow(v[0], 2) + std::pow(v[1], 2) + std::pow(v[2], 2), 0.5); - if ((length > min_cell) && (length < 2 * max_cell)) { - SiteData site_data = { - centres_of_mass_frac[i], length, grid_points_per_void[i]}; - filtered_data.push_back(site_data); + for (int i = 0; i < centres_of_mass_frac.size(); ++i) { + double length = centres_of_mass_frac[i].norm(); + if (length > min_cell && length < 2 * max_cell) { + filtered_data.push_back({centres_of_mass_frac[i], length, grid_points_per_void[i]}); } } diff --git a/baseline_indexer/xyz_to_rlp.cc b/baseline_indexer/xyz_to_rlp.cc index 0eeca46..017666e 100644 --- a/baseline_indexer/xyz_to_rlp.cc +++ b/baseline_indexer/xyz_to_rlp.cc @@ -28,7 +28,7 @@ std::vector xyz_to_rlp(const std::vector &xyzobs_px, // inverse angstrom. // An equivalent to dials flex_ext.map_centroids_to_reciprocal_space method - double DEG2RAD = M_PI / 180.0; + constexpr double DEG2RAD = M_PI / 180.0; // xyzobs_px is a flattened array, we want to return a vector of Vector3ds, // so the size is divided by 3. From 722b64c60509824dd50f0731a48e0cc29618fa74 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:34:52 +0000 Subject: [PATCH 39/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/sites_to_vecs.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index dbafd15..953d7e3 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -93,7 +93,8 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, for (int i = 0; i < centres_of_mass_frac.size(); ++i) { double length = centres_of_mass_frac[i].norm(); if (length > min_cell && length < 2 * max_cell) { - filtered_data.push_back({centres_of_mass_frac[i], length, grid_points_per_void[i]}); + filtered_data.push_back( + {centres_of_mass_frac[i], length, grid_points_per_void[i]}); } } From d438591fa33135fdff1881e97657fd2b1c5632de Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 09:24:37 +0000 Subject: [PATCH 40/72] Add more suggestions from code review. Co-authored-by: Dimitri Vlachos --- baseline_indexer/sites_to_vecs.cc | 62 +++++++++++++++---------------- baseline_indexer/xyz_to_rlp.cc | 3 +- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 953d7e3..050942e 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -79,13 +79,14 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, // Calculate the scaling between the FFT grid and reciprocal space. double fft_cell_length = n_points * d_min / 2.0; // Use 'sites_mod_short' and convert to cartesian (but keep in same array) - for (int i = 0; i < centres_of_mass_frac.size(); i++) { - for (size_t j = 0; j < 3; j++) { - if (centres_of_mass_frac[i][j] > 0.5) { - centres_of_mass_frac[i][j]--; + for (Vector3d& vec: centres_of_mass_frac){ + // Apply the scaling across the vector + std::transform(vec.begin(), vec.end(), vec.begin(), + [fft_cell_length](double& val){ + if (val > 0.5) val -= 1.0; + return val * fft_cell_length; } - centres_of_mass_frac[i][j] *= fft_cell_length; - } + ); } // now do some filtering based on the min and max cell @@ -103,24 +104,20 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, double relative_length_tolerance = 0.1; double angular_tolerance = 5.0; std::vector vector_groups{}; - for (int i = 0; i < filtered_data.size(); i++) { + for (const SiteData& data : filtered_data){ bool matched_group = false; - double length = filtered_data[i].length; - for (int j = 0; j < vector_groups.size(); j++) { - Vector3d mean_v = vector_groups[j].mean(); + for (VectorGroup& group : vector_groups){ + Vector3d mean_v = group.mean(); double mean_v_length = mean_v.norm(); - if ((std::abs(mean_v_length - length) / std::max(mean_v_length, length)) + if ((std::abs(mean_v_length - data.length) / std::max(mean_v_length, data.length)) < relative_length_tolerance) { - double angle = - angle_between_vectors_degrees(mean_v, filtered_data[i].site); + double angle = angle_between_vectors_degrees(mean_v, data.site); if (angle < angular_tolerance) { - vector_groups[j].add(filtered_data[i].site, - filtered_data[i].volume); + group.add(data.site, data.volume); matched_group = true; break; } else if (std::abs(180 - angle) < angular_tolerance) { - vector_groups[j].add(-1.0 * filtered_data[i].site, - filtered_data[i].volume); + group.add(-1.0 * data.site, data.volume); matched_group = true; break; } @@ -128,11 +125,12 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, } // If it didn't match any existing group, create a new one. if (!matched_group) { - VectorGroup group = VectorGroup(); - group.add(filtered_data[i].site, filtered_data[i].volume); + VectorGroup group; + group.add(data.site, data.volume); vector_groups.push_back(group); } } + // Create 'site's based on the data from the groups. std::vector grouped_data; for (int i = 0; i < vector_groups.size(); i++) { @@ -150,22 +148,24 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, // Now check if any sites are integer multiples of other sites. std::vector unique_sites; - for (int i = 0; i < grouped_data.size(); i++) { + for (const SiteData& data: grouped_data){ bool is_unique = true; - Vector3d v = grouped_data[i].site; - for (int j = 0; j < unique_sites.size(); j++) { - if (unique_sites[j].volume > grouped_data[i].volume) { - if (is_approximate_integer_multiple(unique_sites[j].site, v)) { - std::cout << "rejecting " << v.norm() << ": is integer multiple of " - << unique_sites[j].site.norm() << std::endl; - is_unique = false; - break; - } + const Vector3d& v = data.site; + for (const SiteData& unique_site : unique_sites) { + // If the volume of the unique site is less than the current site, skip + if (unique_site.volume <= data.volume) { + continue; + } + // If the current site is an integer multiple of the unique site, exit + if (is_approximate_integer_multiple(unique_site.site, v)) { + std::cout << "rejecting " << v.norm() << ": is integer multiple of " + << unique_site.site.norm() << std::endl; + is_unique = false; + break; } } if (is_unique) { - SiteData site{v, v.norm(), grouped_data[i].volume}; - unique_sites.push_back(site); + unique_sites.push_back({v, v.norm(), data.volume}); } } // now sort by peak volume again diff --git a/baseline_indexer/xyz_to_rlp.cc b/baseline_indexer/xyz_to_rlp.cc index 017666e..6e55fb1 100644 --- a/baseline_indexer/xyz_to_rlp.cc +++ b/baseline_indexer/xyz_to_rlp.cc @@ -38,8 +38,7 @@ std::vector xyz_to_rlp(const std::vector &xyzobs_px, Vector3d s0 = beam.get_s0(); double wl = beam.get_wavelength(); std::array oscillation = scan.get_oscillation(); - double osc_width = oscillation[1]; - double osc_start = oscillation[0]; + const auto [osc_start, osc_width] = scan.get_oscillation(); int image_range_start = scan.get_image_range()[0]; Matrix3d setting_rotation_inverse = gonio.get_setting_rotation().inverse(); Matrix3d sample_rotation_inverse = gonio.get_sample_rotation().inverse(); From a4b0ba940ceeb5350531617537af2b19ed2a6944 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 09:25:24 +0000 Subject: [PATCH 41/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/sites_to_vecs.cc | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 050942e..4c98366 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -79,14 +79,13 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, // Calculate the scaling between the FFT grid and reciprocal space. double fft_cell_length = n_points * d_min / 2.0; // Use 'sites_mod_short' and convert to cartesian (but keep in same array) - for (Vector3d& vec: centres_of_mass_frac){ + for (Vector3d& vec : centres_of_mass_frac) { // Apply the scaling across the vector - std::transform(vec.begin(), vec.end(), vec.begin(), - [fft_cell_length](double& val){ - if (val > 0.5) val -= 1.0; - return val * fft_cell_length; - } - ); + std::transform( + vec.begin(), vec.end(), vec.begin(), [fft_cell_length](double& val) { + if (val > 0.5) val -= 1.0; + return val * fft_cell_length; + }); } // now do some filtering based on the min and max cell @@ -104,12 +103,13 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, double relative_length_tolerance = 0.1; double angular_tolerance = 5.0; std::vector vector_groups{}; - for (const SiteData& data : filtered_data){ + for (const SiteData& data : filtered_data) { bool matched_group = false; - for (VectorGroup& group : vector_groups){ + for (VectorGroup& group : vector_groups) { Vector3d mean_v = group.mean(); double mean_v_length = mean_v.norm(); - if ((std::abs(mean_v_length - data.length) / std::max(mean_v_length, data.length)) + if ((std::abs(mean_v_length - data.length) + / std::max(mean_v_length, data.length)) < relative_length_tolerance) { double angle = angle_between_vectors_degrees(mean_v, data.site); if (angle < angular_tolerance) { @@ -148,7 +148,7 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, // Now check if any sites are integer multiples of other sites. std::vector unique_sites; - for (const SiteData& data: grouped_data){ + for (const SiteData& data : grouped_data) { bool is_unique = true; const Vector3d& v = data.site; for (const SiteData& unique_site : unique_sites) { @@ -159,7 +159,7 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, // If the current site is an integer multiple of the unique site, exit if (is_approximate_integer_multiple(unique_site.site, v)) { std::cout << "rejecting " << v.norm() << ": is integer multiple of " - << unique_site.site.norm() << std::endl; + << unique_site.site.norm() << std::endl; is_unique = false; break; } From 8230a133021d8b9d9634c2ae9648e60ab0422fa2 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:04:08 +0000 Subject: [PATCH 42/72] More suggested changes. Co-authored-by: Dimitri Vlachos --- baseline_indexer/fft3d.cc | 1 + baseline_indexer/flood_fill.cc | 66 ++++++++++--------------------- baseline_indexer/sites_to_vecs.cc | 1 + 3 files changed, 23 insertions(+), 45 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 3516854..73c3762 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -8,6 +8,7 @@ #include #include #include +#include using Eigen::Matrix3d; using Eigen::Vector3d; diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index c408bb4..8463bc4 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -8,6 +8,7 @@ #include #define _USE_MATH_DEFINES #include +#include using Eigen::Vector3d; using Eigen::Vector3i; @@ -91,55 +92,30 @@ std::tuple, std::vector> flood_fill( accumulators[accumulator_index].push_back(this_xyz); grid_points_per_void[accumulator_index]++; - int x_plus = this_xyz[0] + 1; + // Predefined neighbor offsets for 6-connected neighbors + static const std::array neighbors = { + Vector3i{1, 0, 0}, Vector3i{-1, 0, 0}, + Vector3i{0, 1, 0}, Vector3i{0, -1, 0}, + Vector3i{0, 0, 1}, Vector3i{0, 0, -1} + }; + int modx = modulo(this_xyz[0], n_points); int mody = modulo(this_xyz[1], n_points) * n_points; int modz = modulo(this_xyz[2], n_points) * n_sq; - // For x,y,z, check locations +-1 on the grid and add to stack if match. - - int array_index = modulo(x_plus, n_points) + mody + modz; - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {x_plus, this_xyz[1], this_xyz[2]}; - stack.push(new_xyz); - } - int x_minus = this_xyz[0] - 1; - array_index = modulo(x_minus, n_points) + mody + modz; - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {x_minus, this_xyz[1], this_xyz[2]}; - stack.push(new_xyz); - } - - int y_plus = this_xyz[1] + 1; - array_index = modx + (modulo(y_plus, n_points) * n_points) + modz; - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {this_xyz[0], y_plus, this_xyz[2]}; - stack.push(new_xyz); - } - int y_minus = this_xyz[1] - 1; - array_index = modx + (modulo(y_minus, n_points) * n_points) + modz; - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {this_xyz[0], y_minus, this_xyz[2]}; - stack.push(new_xyz); - } - - int z_plus = this_xyz[2] + 1; - array_index = modx + mody + (modulo(z_plus, n_points) * n_sq); - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {this_xyz[0], this_xyz[1], z_plus}; - stack.push(new_xyz); - } - int z_minus = this_xyz[2] - 1; - array_index = modx + mody + (modulo(z_minus, n_points) * n_sq); - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - Vector3i new_xyz = {this_xyz[0], this_xyz[1], z_minus}; - stack.push(new_xyz); + for (const Vector3i& offset : neighbors) { + // Compute the neighbor position + Vector3i neighbor = this_xyz + offset; + // Compute the flattened 1D array index for the neighbor + int array_index = (offset[0] ? modulo(neighbor[0], n_points) : modx) + // x + (offset[1] ? (modulo(neighbor[1], n_points) * n_points) : mody) + // y + (offset[2] ? (modulo(neighbor[2], n_points) * n_sq) : modz); // z + + // Check if the neighbor matches the target and push to the stack + if (grid_binary[array_index] == target) { + grid_binary[array_index] = replacement; + stack.push(neighbor); + } } } replacement++; diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 4c98366..2be19e9 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -6,6 +6,7 @@ #include #include #include +#include using Eigen::Vector3d; From 8287cd003ede060e602e30573f3974552121d806 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:05:26 +0000 Subject: [PATCH 43/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/fft3d.cc | 2 +- baseline_indexer/flood_fill.cc | 19 +++++++++++-------- baseline_indexer/sites_to_vecs.cc | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 73c3762..8d02756 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -5,10 +5,10 @@ #include #include #include +#include #include #include #include -#include using Eigen::Matrix3d; using Eigen::Vector3d; diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 8463bc4..19aee41 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -93,11 +93,12 @@ std::tuple, std::vector> flood_fill( grid_points_per_void[accumulator_index]++; // Predefined neighbor offsets for 6-connected neighbors - static const std::array neighbors = { - Vector3i{1, 0, 0}, Vector3i{-1, 0, 0}, - Vector3i{0, 1, 0}, Vector3i{0, -1, 0}, - Vector3i{0, 0, 1}, Vector3i{0, 0, -1} - }; + static const std::array neighbors = {Vector3i{1, 0, 0}, + Vector3i{-1, 0, 0}, + Vector3i{0, 1, 0}, + Vector3i{0, -1, 0}, + Vector3i{0, 0, 1}, + Vector3i{0, 0, -1}}; int modx = modulo(this_xyz[0], n_points); int mody = modulo(this_xyz[1], n_points) * n_points; @@ -107,9 +108,11 @@ std::tuple, std::vector> flood_fill( // Compute the neighbor position Vector3i neighbor = this_xyz + offset; // Compute the flattened 1D array index for the neighbor - int array_index = (offset[0] ? modulo(neighbor[0], n_points) : modx) + // x - (offset[1] ? (modulo(neighbor[1], n_points) * n_points) : mody) + // y - (offset[2] ? (modulo(neighbor[2], n_points) * n_sq) : modz); // z + int array_index = + (offset[0] ? modulo(neighbor[0], n_points) : modx) + // x + (offset[1] ? (modulo(neighbor[1], n_points) * n_points) : mody) + + // y + (offset[2] ? (modulo(neighbor[2], n_points) * n_sq) : modz); // z // Check if the neighbor matches the target and push to the stack if (grid_binary[array_index] == target) { diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 2be19e9..f526dd6 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -5,8 +5,8 @@ #include #include #include -#include #include +#include using Eigen::Vector3d; From 99bc0f086d1e43ab2789427e76ce0e0cd73b3e66 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:38:17 +0000 Subject: [PATCH 44/72] Some tidying --- baseline_indexer/flood_fill.cc | 30 +++++++++--------------------- baseline_indexer/sites_to_vecs.cc | 15 +++++++-------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 19aee41..d599ab2 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -9,6 +9,7 @@ #define _USE_MATH_DEFINES #include #include +#include using Eigen::Vector3d; using Eigen::Vector3i; @@ -32,15 +33,11 @@ std::tuple, std::vector> flood_fill( auto start = std::chrono::system_clock::now(); assert(grid.size() == n_points * n_points * n_points); // First calculate the rmsd and use this to create a binary grid - double sumg = 0.0; - for (int i = 0; i < grid.size(); ++i) { - sumg += grid[i]; - } + double sumg = std::accumulate(grid.begin(), grid.end(), 0.0); double meang = sumg / grid.size(); - double sum_delta_sq = 0.0; - for (int i = 0; i < grid.size(); ++i) { - sum_delta_sq += std::pow(grid[i] - meang, 2); - } + double sum_delta_sq = std::accumulate(grid.begin(), grid.end(), 0.0, [meang](double total, const double& val){ + return total + std::pow(val - meang, 2); + }); double rmsd = std::pow(sum_delta_sq / grid.size(), 0.5); std::vector grid_binary(n_points * n_points * n_points, 0); double cutoff = rmsd_cutoff * rmsd; @@ -135,19 +132,10 @@ std::tuple, std::vector> flood_fill( for (int i = 0; i < accumulators.size(); i++) { std::vector values = accumulators[i]; int n = values.size(); - int divisor = n * n_points; - double x = 0.0; - double y = 0.0; - double z = 0.0; - for (int j = 0; j < n; j++) { - x += values[j][0]; - y += values[j][1]; - z += values[j][2]; - } - x /= divisor; - y /= divisor; - z /= divisor; - centres_of_mass_frac[i] = {z, y, x}; + double divisor = static_cast(n * n_points); + Vector3i sum = std::accumulate( + values.begin(), values.end(), Vector3i{0, 0, 0}); + centres_of_mass_frac[i] = {sum[2] / divisor, sum[1]/ divisor, sum[0]/ divisor}; //z,y,x } return std::make_tuple(grid_points_per_void, centres_of_mass_frac); } diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index f526dd6..9262ac6 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -20,7 +20,7 @@ class VectorGroup { vectors.push_back(vec); weights.push_back(weight); } - Vector3d mean() { + Vector3d mean() const { Vector3d sum = std::accumulate(vectors.begin(), vectors.end(), Vector3d{0, 0, 0}); return sum / static_cast(vectors.size()); @@ -132,14 +132,13 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, } } - // Create 'site's based on the data from the groups. + // Create "site"s based on the data from the groups. std::vector grouped_data; - for (int i = 0; i < vector_groups.size(); i++) { - Vector3d site = vector_groups[i].mean(); - int max = *std::max_element(vector_groups[i].weights.begin(), - vector_groups[i].weights.end()); - SiteData site_data = {site, site.norm(), max}; - grouped_data.push_back(site_data); + for (const VectorGroup& group: vector_groups){ + Vector3d site = group.mean(); + int max = *std::max_element(group.weights.begin(), + group.weights.end()); + grouped_data.push_back({site, site.norm(), max}); } // Sort by volume, then by length. From fe5113ae4064ba4476d8d237f8000536b92afe44 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:38:37 +0000 Subject: [PATCH 45/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/flood_fill.cc | 13 +++++++------ baseline_indexer/sites_to_vecs.cc | 5 ++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index d599ab2..19cae88 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -35,9 +35,10 @@ std::tuple, std::vector> flood_fill( // First calculate the rmsd and use this to create a binary grid double sumg = std::accumulate(grid.begin(), grid.end(), 0.0); double meang = sumg / grid.size(); - double sum_delta_sq = std::accumulate(grid.begin(), grid.end(), 0.0, [meang](double total, const double& val){ - return total + std::pow(val - meang, 2); - }); + double sum_delta_sq = std::accumulate( + grid.begin(), grid.end(), 0.0, [meang](double total, const double& val) { + return total + std::pow(val - meang, 2); + }); double rmsd = std::pow(sum_delta_sq / grid.size(), 0.5); std::vector grid_binary(n_points * n_points * n_points, 0); double cutoff = rmsd_cutoff * rmsd; @@ -133,9 +134,9 @@ std::tuple, std::vector> flood_fill( std::vector values = accumulators[i]; int n = values.size(); double divisor = static_cast(n * n_points); - Vector3i sum = std::accumulate( - values.begin(), values.end(), Vector3i{0, 0, 0}); - centres_of_mass_frac[i] = {sum[2] / divisor, sum[1]/ divisor, sum[0]/ divisor}; //z,y,x + Vector3i sum = std::accumulate(values.begin(), values.end(), Vector3i{0, 0, 0}); + centres_of_mass_frac[i] = { + sum[2] / divisor, sum[1] / divisor, sum[0] / divisor}; //z,y,x } return std::make_tuple(grid_points_per_void, centres_of_mass_frac); } diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 9262ac6..49a5514 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -134,10 +134,9 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, // Create "site"s based on the data from the groups. std::vector grouped_data; - for (const VectorGroup& group: vector_groups){ + for (const VectorGroup& group : vector_groups) { Vector3d site = group.mean(); - int max = *std::max_element(group.weights.begin(), - group.weights.end()); + int max = *std::max_element(group.weights.begin(), group.weights.end()); grouped_data.push_back({site, site.norm(), max}); } From c2a75487892eb4dc3876c8bf7ecf071391d4cb9c Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:08:59 +0000 Subject: [PATCH 46/72] Add dx2 as a dependency --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c1c8333..c7076ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,13 +41,19 @@ FetchContent_Declare( GIT_TAG f362c4647e7b4bbfef8320040409560b5f90e9e0 ) FetchContent_MakeAvailable(argparse) +FetchContent_Declare( + dx2 + GIT_REPOSITORY https://github.com/dials/dx2 + GIT_TAG 431850825377a0b6a7e9c1f9b0b285b985bb2a8c +) +FetchContent_MakeAvailable(dx2) # Make a small library that we can link to to get the version project(version CXX) configure_file(version.cc.in version.cc @ONLY) add_library(version STATIC version.cc) -add_subdirectory(dx2) +#add_subdirectory(dx2) // for development add_subdirectory(h5read) add_subdirectory(baseline) add_subdirectory(spotfinder) From 32dce33e1e0e16d9a6f1677c6ba319e057bf5767 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:59:17 +0000 Subject: [PATCH 47/72] Use the new experiment class --- CMakeLists.txt | 4 ++-- baseline_indexer/indexer.cc | 45 ++++++++++--------------------------- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7076ac..d89666b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ FetchContent_MakeAvailable(argparse) FetchContent_Declare( dx2 GIT_REPOSITORY https://github.com/dials/dx2 - GIT_TAG 431850825377a0b6a7e9c1f9b0b285b985bb2a8c + GIT_TAG 3898dc078a1e7bb5a4a83d69ee83282c4f51fcc8 ) FetchContent_MakeAvailable(dx2) @@ -53,7 +53,7 @@ project(version CXX) configure_file(version.cc.in version.cc @ONLY) add_library(version STATIC version.cc) -#add_subdirectory(dx2) // for development +#add_subdirectory(dx2) #for development add_subdirectory(h5read) add_subdirectory(baseline) add_subdirectory(spotfinder) diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 36dd06d..07dc71e 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -1,7 +1,9 @@ +#include #include #include #include #include +#include #include #include #include @@ -98,15 +100,13 @@ int main(int argc, char** argv) { std::exit(1); } - // Load the models - json beam_data = elist_json_obj["beam"][0]; - MonoXrayBeam beam(beam_data); - json scan_data = elist_json_obj["scan"][0]; - Scan scan(scan_data); - json gonio_data = elist_json_obj["goniometer"][0]; - Goniometer gonio(gonio_data); - json panel_data = elist_json_obj["detector"][0]["panels"][0]; - Panel detector(panel_data); + Experiment expt(elist_json_obj); + Scan scan = expt.scan(); + MonochromaticBeam beam = expt.beam(); + Goniometer gonio = expt.goniometer(); + Detector detector = expt.detector(); + assert(detector.panels().size() == 1); // only considering single panel detectors initially. + Panel panel = detector.panels()[0]; // Read data from a reflection table. Again, this should be moved to // dx2 and only require the data array name (xyzobs.px.value) with some @@ -121,7 +121,7 @@ int main(int argc, char** argv) { // The diffraction spots form a lattice in reciprocal space (if the experimental // geometry is accurate). So use the experimental models to transform the spot // coordinates on the detector into reciprocal space. - std::vector rlp = xyz_to_rlp(xyzobs_px, detector, beam, scan, gonio); + std::vector rlp = xyz_to_rlp(xyzobs_px, panel, beam, scan, gonio); std::cout << "Number of reflections: " << rlp.size() << std::endl; // b_iso is an isotropic b-factor used to weight the points when doing the fft. @@ -194,29 +194,8 @@ int main(int argc, char** argv) { candidate_lattice_vectors[1], candidate_lattice_vectors[2], space_group}; - json cryst_out = best_xtal.to_json(); - - // save an example experiment list - json elist_out; // a list of potentially multiple experiments - elist_out["__id__"] = "ExperimentList"; - json expt_out; // our single experiment - // no imageset (for now?). - expt_out["__id__"] = "Experiment"; - expt_out["identifier"] = "test"; - expt_out["beam"] = - 0; // the indices of the models that will correspond to our experiment - expt_out["detector"] = 0; - expt_out["goniometer"] = 0; - expt_out["scan"] = 0; - expt_out["crystal"] = 0; - elist_out["experiment"] = std::array{expt_out}; - elist_out["crystal"] = - std::array{cryst_out}; // add the the actual models - elist_out["scan"] = std::array{scan.to_json()}; - elist_out["goniometer"] = std::array{gonio.to_json()}; - elist_out["beam"] = std::array{beam.to_json()}; - elist_out["detector"] = std::array{detector.to_json()}; - + expt.set_crystal(best_xtal); + json elist_out = expt.to_json(); std::ofstream efile("elist.json"); efile << elist_out.dump(4); } From bed6bb19f4e41badc70e243af63e9988705975cd Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:06:36 +0000 Subject: [PATCH 48/72] Add autodiscovery and settable nthreads for fft --- baseline_indexer/fft3d.cc | 8 +++++--- baseline_indexer/indexer.cc | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 8d02756..fdef6cc 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -91,7 +91,8 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, std::vector &real_out, double d_min, double b_iso = 0, - uint32_t n_points = 256) { + uint32_t n_points = 256, + size_t nthreads=1) { auto start = std::chrono::system_clock::now(); assert(real_out.size() == n_points * n_points * n_points); @@ -131,8 +132,9 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, bool forward{FORWARD}; double fct{1.0f}; - size_t nthreads = 20; // use all threads available - is this working? - + // note, threads can be higher than the number of hardware threads. + // It is not clear what the best value is for this. + std::cout << "Performing FFT with nthreads=" << nthreads << std::endl; // Do the FFT. c2c(shape_in, stride_in, diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 07dc71e..7e6dea3 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include "fft3d.cc" #include "flood_fill.cc" @@ -58,6 +59,14 @@ int main(int argc, char** argv) { "efficient.") .default_value(256) .scan<'u', uint32_t>(); + parser.add_argument("--nthreads") // mainly for testing. + .help( + "The number of threads to use for the fft calculation." + "Defaults to the value of std::thread::hardware_concurrency." + "Better performance can typically be obtained with a higher number" + "of threads than this.") + .default_value(0) + .scan<'u', size_t>(); parser.parse_args(argc, argv); if (!parser.is_used("--expt")) { @@ -85,6 +94,7 @@ int main(int argc, char** argv) { } std::string imported_expt = parser.get("--expt"); std::string filename = parser.get("--refl"); + size_t nthreads = parser.get("--nthreads"); double max_cell = parser.get("max-cell"); double d_min = parser.get("dmin"); @@ -138,8 +148,13 @@ int main(int argc, char** argv) { // the used in indexing array denotes whether a coordinate was used for the // fft (might not be if dmin filter was used for example). The used_in_indexing array // is sometimes used onwards in the dials indexing algorithms, so keep for now. + if (nthreads == 0){ // i.e. user has not specified. + nthreads = std::thread::hardware_concurrency(); // Get max number of threads + } + if (nthreads == 0) nthreads = 1; // Set to default number of threads + std::vector used_in_indexing = - fft3d(rlp, real_fft_result, d_min, b_iso, n_points); + fft3d(rlp, real_fft_result, d_min, b_iso, n_points, nthreads); // The fft result is noisy. We want to extract the peaks, which may be spread over several // points on the fft grid. So we use a flood fill algorithm (https://en.wikipedia.org/wiki/Flood_fill) From 1813a2d50b8a773ec5dc56633ee9b5b356b35e9f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:09:19 +0000 Subject: [PATCH 49/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/fft3d.cc | 2 +- baseline_indexer/indexer.cc | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index fdef6cc..a0ed6aa 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -92,7 +92,7 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, double d_min, double b_iso = 0, uint32_t n_points = 256, - size_t nthreads=1) { + size_t nthreads = 1) { auto start = std::chrono::system_clock::now(); assert(real_out.size() == n_points * n_points * n_points); diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 7e6dea3..005827b 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -2,8 +2,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -18,8 +18,8 @@ #include #include #include -#include #include +#include #include "fft3d.cc" #include "flood_fill.cc" @@ -59,7 +59,8 @@ int main(int argc, char** argv) { "efficient.") .default_value(256) .scan<'u', uint32_t>(); - parser.add_argument("--nthreads") // mainly for testing. + parser + .add_argument("--nthreads") // mainly for testing. .help( "The number of threads to use for the fft calculation." "Defaults to the value of std::thread::hardware_concurrency." @@ -115,7 +116,8 @@ int main(int argc, char** argv) { MonochromaticBeam beam = expt.beam(); Goniometer gonio = expt.goniometer(); Detector detector = expt.detector(); - assert(detector.panels().size() == 1); // only considering single panel detectors initially. + assert(detector.panels().size() + == 1); // only considering single panel detectors initially. Panel panel = detector.panels()[0]; // Read data from a reflection table. Again, this should be moved to @@ -148,10 +150,10 @@ int main(int argc, char** argv) { // the used in indexing array denotes whether a coordinate was used for the // fft (might not be if dmin filter was used for example). The used_in_indexing array // is sometimes used onwards in the dials indexing algorithms, so keep for now. - if (nthreads == 0){ // i.e. user has not specified. - nthreads = std::thread::hardware_concurrency(); // Get max number of threads + if (nthreads == 0) { // i.e. user has not specified. + nthreads = std::thread::hardware_concurrency(); // Get max number of threads } - if (nthreads == 0) nthreads = 1; // Set to default number of threads + if (nthreads == 0) nthreads = 1; // Set to default number of threads std::vector used_in_indexing = fft3d(rlp, real_fft_result, d_min, b_iso, n_points, nthreads); From a2a17ab22f5cb9ae00229244a88e3b54a53713ea Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:43:22 +0000 Subject: [PATCH 50/72] Use spdlog to log to console and file with suitable log levels --- baseline_indexer/CMakeLists.txt | 1 + baseline_indexer/fft3d.cc | 21 +++++++--------- baseline_indexer/flood_fill.cc | 8 +++---- baseline_indexer/indexer.cc | 40 ++++++++++++++++++------------- baseline_indexer/sites_to_vecs.cc | 16 +++++-------- 5 files changed, 42 insertions(+), 44 deletions(-) diff --git a/baseline_indexer/CMakeLists.txt b/baseline_indexer/CMakeLists.txt index 0ff1ecf..0aa1e6a 100644 --- a/baseline_indexer/CMakeLists.txt +++ b/baseline_indexer/CMakeLists.txt @@ -1,6 +1,7 @@ project(indexer CXX CUDA) find_package(CUDAToolkit REQUIRED) +find_package(spdlog) # Automatic Dependencies set(FETCHCONTENT_QUIET OFF) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index a0ed6aa..2a6a00a 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -5,10 +5,10 @@ #include #include #include -#include #include #include #include +#include using Eigen::Matrix3d; using Eigen::Vector3d; @@ -75,7 +75,7 @@ void map_centroids_to_reciprocal_space_grid( } data_in[index] = {T, 0.0}; } - std::cout << "Number of centroids used: " << count << std::endl; + spdlog::info("Number of centroids used: {0}", count); } /** @@ -134,7 +134,7 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, double fct{1.0f}; // note, threads can be higher than the number of hardware threads. // It is not clear what the best value is for this. - std::cout << "Performing FFT with nthreads=" << nthreads << std::endl; + spdlog::info("Performing FFT with nthreads={0}", nthreads); // Do the FFT. c2c(shape_in, stride_in, @@ -158,16 +158,11 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, std::chrono::duration elapsed_make_arrays = t1 - start; std::chrono::duration elapsed_c2c = t3 - t2; std::chrono::duration elapsed_square = t4 - t3; - std::cout << "Total time for fft3d: " << elapsed_seconds.count() << "s" - << std::endl; - - std::cout << "elapsed time for making data arrays: " << elapsed_make_arrays.count() - << "s" << std::endl; - std::cout << "elapsed time for map_to_recip: " << elapsed_map.count() << "s" - << std::endl; - std::cout << "elapsed time for c2c: " << elapsed_c2c.count() << "s" << std::endl; - std::cout << "elapsed time for squaring: " << elapsed_square.count() << "s" - << std::endl; + spdlog::debug("Total time for fft3d: {0:.5f}s", elapsed_seconds.count()); + spdlog::debug("elapsed time for making data arrays: {0:.5f}s", elapsed_make_arrays.count()); + spdlog::debug("elapsed time for map_to_recip: {0:.5f}s", elapsed_map.count()); + spdlog::debug("elapsed time for c2c: {0:.5f}s", elapsed_c2c.count()); + spdlog::debug("elapsed time for squaring: {0:.5f}s", elapsed_square.count()); return used_in_indexing; } diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 19cae88..c78fe4a 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -8,8 +8,8 @@ #include #define _USE_MATH_DEFINES #include -#include #include +#include using Eigen::Vector3d; using Eigen::Vector3i; @@ -49,8 +49,7 @@ std::tuple, std::vector> flood_fill( } auto t2 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time = t2 - start; - std::cout << "Time for first part of flood fill: " << elapsed_time.count() << "s" - << std::endl; + spdlog::debug("Time for first part of flood fill: {0:.5f}s", elapsed_time.count()); // Now do the flood fill. // Wrap around the edge in all three dimensions to replicate the DIALS @@ -125,8 +124,7 @@ std::tuple, std::vector> flood_fill( } auto t3 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time2 = t3 - t2; - std::cout << "Time for second part of flood fill: " << elapsed_time2.count() << "s" - << std::endl; + spdlog::debug("Time for second part of flood fill: {0:.5f}s", elapsed_time2.count()); // Now calculate the unweighted centres of mass of each group, in fractional coordinates. std::vector centres_of_mass_frac(n_voids); diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 005827b..9ec2645 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -15,11 +15,13 @@ #include #include #include -#include #include #include #include #include +#include +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" #include "fft3d.cc" #include "flood_fill.cc" @@ -70,14 +72,20 @@ int main(int argc, char** argv) { .scan<'u', size_t>(); parser.parse_args(argc, argv); + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::info); + auto file_sink = std::make_shared("ffs_index.log", true); + file_sink->set_level(spdlog::level::debug); + spdlog::logger logger("ffs_index", {console_sink, file_sink}); + spdlog::set_default_logger(std::make_shared("ffs_index", spdlog::sinks_init_list({console_sink, file_sink}))); + spdlog::set_level(spdlog::level::debug); // Will output debug messages, but only to the file log. + if (!parser.is_used("--expt")) { - fmt::print("Error: must specify experiment list file with --expt\n"); + logger.error("Must specify experiment list file with --expt\n"); std::exit(1); } if (!parser.is_used("--refl")) { - fmt::print( - "Error: must specify spotfinding results file (in DIALS HDF5 format) with " - "--refl\n"); + logger.error("Must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); std::exit(1); } // In DIALS, the max cell is automatically determined through a nearest @@ -85,12 +93,12 @@ int main(int argc, char** argv) { // let's make this a required argument to help with testing/comparison // to DIALS. if (!parser.is_used("--max-cell")) { - fmt::print("Error: must specify --max-cell\n"); + logger.error("Must specify --max-cell\n"); std::exit(1); } // FIXME use highest resolution by default to remove this requirement. if (!parser.is_used("--dmin")) { - fmt::print("Error: must specify --dmin\n"); + logger.error("Must specify --dmin\n"); std::exit(1); } std::string imported_expt = parser.get("--expt"); @@ -106,8 +114,7 @@ int main(int argc, char** argv) { try { elist_json_obj = json::parse(f); } catch (json::parse_error& ex) { - std::cerr << "Error: Unable to read " << imported_expt.c_str() - << "; json parse error at byte " << ex.byte << std::endl; + logger.error("Unable to read {0}; json parse error at byte {1}", imported_expt.c_str(), ex.byte); std::exit(1); } @@ -134,14 +141,14 @@ int main(int argc, char** argv) { // geometry is accurate). So use the experimental models to transform the spot // coordinates on the detector into reciprocal space. std::vector rlp = xyz_to_rlp(xyzobs_px, panel, beam, scan, gonio); - std::cout << "Number of reflections: " << rlp.size() << std::endl; + logger.info("Number of reflections: {0}", rlp.size()); // b_iso is an isotropic b-factor used to weight the points when doing the fft. // i.e. high resolution (weaker) spots are downweighted by the expected // intensity fall-off as as function of resolution. double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); uint32_t n_points = parser.get("--fft-npoints"); - std::cout << "Setting b_iso = " << b_iso << std::endl; + logger.info("Setting b_iso = {0:.3f}", b_iso); // Create an array to store the fft result. This is a 3D grid of points, typically 256^3. std::vector real_fft_result(n_points * n_points * n_points, 0.0); @@ -197,14 +204,13 @@ int main(int argc, char** argv) { vecs_out[pad_s] = candidate_lattice_vectors[i]; } std::string outfile = "candidate_vectors.json"; - std::cout << "Saving candidate vectors to " << outfile << std::endl; std::ofstream vecs_file(outfile); vecs_file << vecs_out.dump(4); + logger.info("Saved candidate vectors to {0}", outfile); // Now make a crystal and save an experiment list with the models. if (candidate_lattice_vectors.size() < 3) { - std::cout << "Insufficient number of candidate vectors to make a crystal model." - << std::endl; + logger.info("Insufficient number of candidate vectors to make a crystal model."); } else { gemmi::SpaceGroup space_group = *gemmi::find_spacegroup_by_name("P1"); Crystal best_xtal{candidate_lattice_vectors[0], @@ -213,11 +219,13 @@ int main(int argc, char** argv) { space_group}; expt.set_crystal(best_xtal); json elist_out = expt.to_json(); - std::ofstream efile("elist.json"); + std::string efile_name = "elist.json"; + std::ofstream efile(efile_name); efile << elist_out.dump(4); + logger.info("Saved experiment list to {0}", efile_name); } auto t2 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time = t2 - t1; - std::cout << "Total time for indexer: " << elapsed_time.count() << "s" << std::endl; + logger.info("Total time for indexer: {0:.4f}s", elapsed_time.count()); } diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 49a5514..999f21e 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include #include @@ -157,8 +157,7 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, } // If the current site is an integer multiple of the unique site, exit if (is_approximate_integer_multiple(unique_site.site, v)) { - std::cout << "rejecting " << v.norm() << ": is integer multiple of " - << unique_site.site.norm() << std::endl; + spdlog::info("rejecting {0} : is integer multiple of {1}", v.norm(), unique_site.site.norm()); is_unique = false; break; } @@ -171,16 +170,13 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, std::stable_sort( unique_sites.begin(), unique_sites.end(), compare_site_data_volume); std::vector unique_vectors_sorted; - std::cout << "Candidate basis vectors: " << std::endl; + spdlog::info("Candidate basis vectors:"); for (int i = 0; i < unique_sites.size(); i++) { unique_vectors_sorted.push_back(unique_sites[i].site); - std::cout << i << " " << unique_sites[i].length << " " << unique_sites[i].volume - << std::endl; + spdlog::info("{0} {1:.5f} {2}", i, unique_sites[i].length, unique_sites[i].volume); } - auto end = std::chrono::system_clock::now(); - std::chrono::duration elapsed_seconds = end - start; - std::cout << "elapsed time for sites_to_vecs: " << elapsed_seconds.count() << "s" - << std::endl; + std::chrono::duration elapsed_seconds = std::chrono::system_clock::now() - start; + spdlog::debug("elapsed time for sites_to_vecs: {0:.5f}s", elapsed_seconds.count()); return unique_vectors_sorted; } From 2badc8d33280e45fef0a2b66413e5de8e43d9044 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:43:54 +0000 Subject: [PATCH 51/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/fft3d.cc | 5 +++-- baseline_indexer/flood_fill.cc | 6 ++++-- baseline_indexer/indexer.cc | 25 ++++++++++++++++--------- baseline_indexer/sites_to_vecs.cc | 12 ++++++++---- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 2a6a00a..ad2009d 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include using Eigen::Matrix3d; using Eigen::Vector3d; @@ -159,7 +159,8 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, std::chrono::duration elapsed_c2c = t3 - t2; std::chrono::duration elapsed_square = t4 - t3; spdlog::debug("Total time for fft3d: {0:.5f}s", elapsed_seconds.count()); - spdlog::debug("elapsed time for making data arrays: {0:.5f}s", elapsed_make_arrays.count()); + spdlog::debug("elapsed time for making data arrays: {0:.5f}s", + elapsed_make_arrays.count()); spdlog::debug("elapsed time for map_to_recip: {0:.5f}s", elapsed_map.count()); spdlog::debug("elapsed time for c2c: {0:.5f}s", elapsed_c2c.count()); spdlog::debug("elapsed time for squaring: {0:.5f}s", elapsed_square.count()); diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index c78fe4a..b84c86a 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -7,9 +7,10 @@ #include #include #define _USE_MATH_DEFINES +#include + #include #include -#include using Eigen::Vector3d; using Eigen::Vector3i; @@ -124,7 +125,8 @@ std::tuple, std::vector> flood_fill( } auto t3 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time2 = t3 - t2; - spdlog::debug("Time for second part of flood fill: {0:.5f}s", elapsed_time2.count()); + spdlog::debug("Time for second part of flood fill: {0:.5f}s", + elapsed_time2.count()); // Now calculate the unweighted centres of mass of each group, in fractional coordinates. std::vector centres_of_mass_frac(n_voids); diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 9ec2645..f8b290a 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -19,14 +20,13 @@ #include #include #include -#include -#include "spdlog/sinks/basic_file_sink.h" -#include "spdlog/sinks/stdout_color_sinks.h" #include "fft3d.cc" #include "flood_fill.cc" #include "gemmi/symmetry.hpp" #include "sites_to_vecs.cc" +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" #include "xyz_to_rlp.cc" using Eigen::Matrix3d; @@ -74,18 +74,22 @@ int main(int argc, char** argv) { auto console_sink = std::make_shared(); console_sink->set_level(spdlog::level::info); - auto file_sink = std::make_shared("ffs_index.log", true); + auto file_sink = + std::make_shared("ffs_index.log", true); file_sink->set_level(spdlog::level::debug); spdlog::logger logger("ffs_index", {console_sink, file_sink}); - spdlog::set_default_logger(std::make_shared("ffs_index", spdlog::sinks_init_list({console_sink, file_sink}))); - spdlog::set_level(spdlog::level::debug); // Will output debug messages, but only to the file log. + spdlog::set_default_logger(std::make_shared( + "ffs_index", spdlog::sinks_init_list({console_sink, file_sink}))); + spdlog::set_level( + spdlog::level::debug); // Will output debug messages, but only to the file log. if (!parser.is_used("--expt")) { logger.error("Must specify experiment list file with --expt\n"); std::exit(1); } if (!parser.is_used("--refl")) { - logger.error("Must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); + logger.error( + "Must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); std::exit(1); } // In DIALS, the max cell is automatically determined through a nearest @@ -114,7 +118,9 @@ int main(int argc, char** argv) { try { elist_json_obj = json::parse(f); } catch (json::parse_error& ex) { - logger.error("Unable to read {0}; json parse error at byte {1}", imported_expt.c_str(), ex.byte); + logger.error("Unable to read {0}; json parse error at byte {1}", + imported_expt.c_str(), + ex.byte); std::exit(1); } @@ -210,7 +216,8 @@ int main(int argc, char** argv) { // Now make a crystal and save an experiment list with the models. if (candidate_lattice_vectors.size() < 3) { - logger.info("Insufficient number of candidate vectors to make a crystal model."); + logger.info( + "Insufficient number of candidate vectors to make a crystal model."); } else { gemmi::SpaceGroup space_group = *gemmi::find_spacegroup_by_name("P1"); Crystal best_xtal{candidate_lattice_vectors[0], diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 999f21e..487e99b 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -1,9 +1,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -157,7 +157,9 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, } // If the current site is an integer multiple of the unique site, exit if (is_approximate_integer_multiple(unique_site.site, v)) { - spdlog::info("rejecting {0} : is integer multiple of {1}", v.norm(), unique_site.site.norm()); + spdlog::info("rejecting {0} : is integer multiple of {1}", + v.norm(), + unique_site.site.norm()); is_unique = false; break; } @@ -173,10 +175,12 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, spdlog::info("Candidate basis vectors:"); for (int i = 0; i < unique_sites.size(); i++) { unique_vectors_sorted.push_back(unique_sites[i].site); - spdlog::info("{0} {1:.5f} {2}", i, unique_sites[i].length, unique_sites[i].volume); + spdlog::info( + "{0} {1:.5f} {2}", i, unique_sites[i].length, unique_sites[i].volume); } - std::chrono::duration elapsed_seconds = std::chrono::system_clock::now() - start; + std::chrono::duration elapsed_seconds = + std::chrono::system_clock::now() - start; spdlog::debug("elapsed time for sites_to_vecs: {0:.5f}s", elapsed_seconds.count()); return unique_vectors_sorted; } From 86cb07173bda805c3990e91bd046463ed832c37b Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:48:50 +0000 Subject: [PATCH 52/72] Use parser.is_used for nthreads parameter --- baseline_indexer/indexer.cc | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index f8b290a..b6da3b5 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -68,7 +68,6 @@ int main(int argc, char** argv) { "Defaults to the value of std::thread::hardware_concurrency." "Better performance can typically be obtained with a higher number" "of threads than this.") - .default_value(0) .scan<'u', size_t>(); parser.parse_args(argc, argv); @@ -83,11 +82,11 @@ int main(int argc, char** argv) { spdlog::set_level( spdlog::level::debug); // Will output debug messages, but only to the file log. - if (!parser.is_used("--expt")) { + if (!parser.is_used("expt")) { logger.error("Must specify experiment list file with --expt\n"); std::exit(1); } - if (!parser.is_used("--refl")) { + if (!parser.is_used("refl")) { logger.error( "Must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); std::exit(1); @@ -96,18 +95,17 @@ int main(int argc, char** argv) { // neighbour analysis that requires the annlib package. For now, // let's make this a required argument to help with testing/comparison // to DIALS. - if (!parser.is_used("--max-cell")) { + if (!parser.is_used("max-cell")) { logger.error("Must specify --max-cell\n"); std::exit(1); } // FIXME use highest resolution by default to remove this requirement. - if (!parser.is_used("--dmin")) { + if (!parser.is_used("dmin")) { logger.error("Must specify --dmin\n"); std::exit(1); } - std::string imported_expt = parser.get("--expt"); - std::string filename = parser.get("--refl"); - size_t nthreads = parser.get("--nthreads"); + std::string imported_expt = parser.get("expt"); + std::string filename = parser.get("refl"); double max_cell = parser.get("max-cell"); double d_min = parser.get("dmin"); @@ -153,7 +151,7 @@ int main(int argc, char** argv) { // i.e. high resolution (weaker) spots are downweighted by the expected // intensity fall-off as as function of resolution. double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); - uint32_t n_points = parser.get("--fft-npoints"); + uint32_t n_points = parser.get("fft-npoints"); logger.info("Setting b_iso = {0:.3f}", b_iso); // Create an array to store the fft result. This is a 3D grid of points, typically 256^3. @@ -163,10 +161,14 @@ int main(int argc, char** argv) { // the used in indexing array denotes whether a coordinate was used for the // fft (might not be if dmin filter was used for example). The used_in_indexing array // is sometimes used onwards in the dials indexing algorithms, so keep for now. - if (nthreads == 0) { // i.e. user has not specified. - nthreads = std::thread::hardware_concurrency(); // Get max number of threads + size_t nthreads; + if (parser.is_used("nthreads")) { + nthreads = parser.get("nthreads"); + } + else { + size_t max_threads = std::thread::hardware_concurrency(); + nthreads = max_threads ? max_threads : 1; } - if (nthreads == 0) nthreads = 1; // Set to default number of threads std::vector used_in_indexing = fft3d(rlp, real_fft_result, d_min, b_iso, n_points, nthreads); From 4102681664f0f5164d1c5b74957e7f031443055d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:52:04 +0000 Subject: [PATCH 53/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/indexer.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index b6da3b5..2f3413d 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -164,8 +164,7 @@ int main(int argc, char** argv) { size_t nthreads; if (parser.is_used("nthreads")) { nthreads = parser.get("nthreads"); - } - else { + } else { size_t max_threads = std::thread::hardware_concurrency(); nthreads = max_threads ? max_threads : 1; } From 24e79122048f172f24dbacb1a10f239d1d44cb5a Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:36:43 +0000 Subject: [PATCH 54/72] Remove CUDA requirements from CMakelists and fix cmake warnings --- baseline_indexer/CMakeLists.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/baseline_indexer/CMakeLists.txt b/baseline_indexer/CMakeLists.txt index 0aa1e6a..9d87a05 100644 --- a/baseline_indexer/CMakeLists.txt +++ b/baseline_indexer/CMakeLists.txt @@ -1,7 +1,9 @@ -project(indexer CXX CUDA) +project(indexer CXX) + +set(CMAKE_CXX_STANDARD 20) -find_package(CUDAToolkit REQUIRED) find_package(spdlog) +find_package (Threads) # Automatic Dependencies set(FETCHCONTENT_QUIET OFF) @@ -17,8 +19,6 @@ FetchContent_Declare( pocketfft GIT_REPOSITORY https://github.com/mreineck/pocketfft GIT_TAG cpp - CONFIGURE_COMMAND "" - BUILD_COMMAND "" ) FetchContent_MakeAvailable(Eigen3) FetchContent_MakeAvailable(pocketfft) @@ -36,9 +36,7 @@ target_link_libraries(baseline_indexer dx2 Eigen3::Eigen pocketfft - CUDA::cudart - CUDA::nppif argparse nlohmann_json::nlohmann_json + ${CMAKE_THREAD_LIBS_INIT} ) -target_compile_options(baseline_indexer PRIVATE "$<$,$>:-G>") From 8e047562ba298f943c1dc3424f651bd2cd19314e Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:52:41 +0000 Subject: [PATCH 55/72] Add a dev flag for cmake to switch between dx2 as a subdirectory or the git repo. --- CMakeLists.txt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d89666b..d599ad3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,19 +41,22 @@ FetchContent_Declare( GIT_TAG f362c4647e7b4bbfef8320040409560b5f90e9e0 ) FetchContent_MakeAvailable(argparse) -FetchContent_Declare( - dx2 - GIT_REPOSITORY https://github.com/dials/dx2 - GIT_TAG 3898dc078a1e7bb5a4a83d69ee83282c4f51fcc8 -) -FetchContent_MakeAvailable(dx2) +if(dev) #flag for for development + add_subdirectory(dx2) +else() + FetchContent_Declare( + dx2 + GIT_REPOSITORY https://github.com/dials/dx2 + GIT_TAG 3898dc078a1e7bb5a4a83d69ee83282c4f51fcc8 + ) + FetchContent_MakeAvailable(dx2) +endif() # Make a small library that we can link to to get the version project(version CXX) configure_file(version.cc.in version.cc @ONLY) add_library(version STATIC version.cc) -#add_subdirectory(dx2) #for development add_subdirectory(h5read) add_subdirectory(baseline) add_subdirectory(spotfinder) From 8fadfa7c4f7180112cc5086dec47ec0c718d4c23 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:37:09 +0000 Subject: [PATCH 56/72] Pin to the merged dx2 commit. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d599ad3..96b875b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ else() FetchContent_Declare( dx2 GIT_REPOSITORY https://github.com/dials/dx2 - GIT_TAG 3898dc078a1e7bb5a4a83d69ee83282c4f51fcc8 + GIT_TAG 2654929bc73e0d699dfdfcc2d924c3454e434ccf ) FetchContent_MakeAvailable(dx2) endif() From a0b93b310b1c78072f5761999672475460109011 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:25:23 +0000 Subject: [PATCH 57/72] Update file paths in test, tidying --- baseline_indexer/sites_to_vecs.cc | 2 +- baseline_indexer/test_baseline_indexer.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 487e99b..75c1572 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -157,7 +157,7 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, } // If the current site is an integer multiple of the unique site, exit if (is_approximate_integer_multiple(unique_site.site, v)) { - spdlog::info("rejecting {0} : is integer multiple of {1}", + spdlog::info("rejecting {0:.5f} : is integer multiple of {1:.5f}", v.norm(), unique_site.site.norm()); is_unique = false; diff --git a/baseline_indexer/test_baseline_indexer.sh b/baseline_indexer/test_baseline_indexer.sh index b1fd101..a589569 100755 --- a/baseline_indexer/test_baseline_indexer.sh +++ b/baseline_indexer/test_baseline_indexer.sh @@ -13,8 +13,8 @@ if test -f "candidate_vectors.json"; then fi ./bin/baseline_indexer \ - -r /dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/strong.refl \ - -e /dls/mx-scratch/jbe/test_cuda_spotfinder/cm37235-2_ins_14_24_rot/imported.expt \ + -r /dls/i03/data/2024/cm37235-2/processing/JBE/ins_14_24_rot/strong.refl \ + -e /dls/i03/data/2024/cm37235-2/processing/JBE/ins_14_24_rot/imported.expt \ --max-cell 100 \ --dmin 1.81 From 1b9639f0d995de4d1bbf38358eb635b99fa537d8 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 22 Jan 2025 09:13:33 +0000 Subject: [PATCH 58/72] Update dx2 source discovery --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 96b875b..ee8847f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,9 +41,14 @@ FetchContent_Declare( GIT_TAG f362c4647e7b4bbfef8320040409560b5f90e9e0 ) FetchContent_MakeAvailable(argparse) -if(dev) #flag for for development +if(EXISTS ${CMAKE_SOURCE_DIR}/dx2) #a local copy e.g. for development + message("-- Using local dx2 subdirectory") add_subdirectory(dx2) +elseif(dx2_source) + message("-- Using dx2 located at ${dx2_source}") + add_subdirectory(${dx2_source}) else() + message("-- Fetching dx2 from GitHub") FetchContent_Declare( dx2 GIT_REPOSITORY https://github.com/dials/dx2 From fae88d0f6b7eecda3325bc9597377aadcef87a8f Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:21:20 +0000 Subject: [PATCH 59/72] Update CMakeLists.txt Co-authored-by: Nicholas Devenish --- CMakeLists.txt | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee8847f..7e2092e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,21 +41,32 @@ FetchContent_Declare( GIT_TAG f362c4647e7b4bbfef8320040409560b5f90e9e0 ) FetchContent_MakeAvailable(argparse) -if(EXISTS ${CMAKE_SOURCE_DIR}/dx2) #a local copy e.g. for development - message("-- Using local dx2 subdirectory") + +# Handle inclusion of dx2 dependency in a choice of several ways +set(dx2_SOURCE "" CACHE FILEPATH "Location of dx2 source directory") +set(_dx2_already_populated "${dx2_SOURCE_DIR}") +if(dx2_SOURCE) + # Explicitly told where to find dx2 + add_subdirectory(${dx2_SOURCE} dx2) + set(dx2_SOURCE_DIR "${dx2_SOURCE}") +elseif(EXISTS ${CMAKE_SOURCE_DIR}/dx2) + # dx2 exists as a local checkout add_subdirectory(dx2) -elseif(dx2_source) - message("-- Using dx2 located at ${dx2_source}") - add_subdirectory(${dx2_source}) + set(dx2_SOURCE_DIR "${CMAKE_SOURCE_DIR}/dx2") else() - message("-- Fetching dx2 from GitHub") + # Try finding an installed dx2, or fetch FetchContent_Declare( dx2 GIT_REPOSITORY https://github.com/dials/dx2 GIT_TAG 2654929bc73e0d699dfdfcc2d924c3454e434ccf + FIND_PACKAGE_ARGS ) FetchContent_MakeAvailable(dx2) endif() +# Only print a message when as a new action +if (NOT dx2_SOURCE_DIR STREQUAL _dx2_already_populated) + message(STATUS "Found dx2: ${dx2_SOURCE_DIR}") +endif() # Make a small library that we can link to to get the version project(version CXX) From 70a7df36aa37601d8a63398e6e08beb04108bc33 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:12:19 +0000 Subject: [PATCH 60/72] Use an unordered map rather than array --- baseline_indexer/fft3d.cc | 6 +++--- baseline_indexer/flood_fill.cc | 27 ++++++++++++++++++++------- baseline_indexer/indexer.cc | 2 +- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index ad2009d..bc9dc10 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -101,7 +101,7 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, // Note we should be able to use c2r rather than c2c, but I couldn't get this to work with // the output ordering in c2r (JBE). std::vector> complex_data_in(n_points * n_points * n_points); - std::vector> data_out(n_points * n_points * n_points); + //std::vector> data_out(n_points * n_points * n_points); // A boolean array of whether the vectors were used for the FFT. std::vector used_in_indexing(reciprocal_space_vectors.size(), true); @@ -142,14 +142,14 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, axes, forward, complex_data_in.data(), - data_out.data(), + complex_data_in.data(), fct, nthreads); auto t3 = std::chrono::system_clock::now(); // Take the square of the real part as the output. for (int i = 0; i < real_out.size(); ++i) { - real_out[i] = std::pow(data_out[i].real(), 2); + real_out[i] = std::pow(complex_data_in[i].real(), 2); } auto t4 = std::chrono::system_clock::now(); diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index b84c86a..45db606 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -8,6 +8,7 @@ #include #define _USE_MATH_DEFINES #include +#include #include #include @@ -41,7 +42,10 @@ std::tuple, std::vector> flood_fill( return total + std::pow(val - meang, 2); }); double rmsd = std::pow(sum_delta_sq / grid.size(), 0.5); - std::vector grid_binary(n_points * n_points * n_points, 0); + + // Most of the binary grid will be zero, so use an + // unordered map rather than vector. + std::unordered_map grid_binary; double cutoff = rmsd_cutoff * rmsd; for (int i = 0; i < grid.size(); i++) { if (grid[i] >= cutoff) { @@ -68,11 +72,13 @@ std::tuple, std::vector> flood_fill( int n_sq = n_points * n_points; int n_sq_minus_n = n_points * (n_points - 1); int nn_sq_minus_n = n_points * n_points * (n_points - 1); - - for (int i = 0; i < grid_binary.size(); i++) { - if (grid_binary[i] == target) { + for (auto& it: grid_binary){ + if (it.second == target){ + //for (int i = 0; i < grid_binary.size(); i++) { + //if (grid_binary[i] == target) { // Convert the array index into xyz coordinates. // Store xyz coordinates on the stack, but index the array with 1D index. + int i = it.first; int x = i % n_points; int y = (i % n_sq) / n_points; int z = i / n_sq; @@ -113,10 +119,17 @@ std::tuple, std::vector> flood_fill( (offset[2] ? (modulo(neighbor[2], n_points) * n_sq) : modz); // z // Check if the neighbor matches the target and push to the stack - if (grid_binary[array_index] == target) { - grid_binary[array_index] = replacement; - stack.push(neighbor); + std::unordered_map::const_iterator found = grid_binary.find(array_index); + if (found != grid_binary.end()){ + if (found->second == target){ + grid_binary[array_index] = replacement; + stack.push(neighbor); + } } + //if (grid_binary[array_index] == target) { + // grid_binary[array_index] = replacement; + // stack.push(neighbor); + //} } } replacement++; diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 2f3413d..4ce2d3f 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -155,7 +155,7 @@ int main(int argc, char** argv) { logger.info("Setting b_iso = {0:.3f}", b_iso); // Create an array to store the fft result. This is a 3D grid of points, typically 256^3. - std::vector real_fft_result(n_points * n_points * n_points, 0.0); + std::vector real_fft_result(n_points * n_points * n_points); // Do the fft of the reciprocal lattice coordinates. // the used in indexing array denotes whether a coordinate was used for the From a5a0811b1b3a5899752102c5fe7f233308f1265b Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:39:54 +0000 Subject: [PATCH 61/72] Update test due to sign changes --- baseline_indexer/fft3d.cc | 3 +- baseline_indexer/test_baseline_indexer.sh | 52 +++++++++++------------ 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index bc9dc10..5ca6b63 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -101,7 +101,6 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, // Note we should be able to use c2r rather than c2c, but I couldn't get this to work with // the output ordering in c2r (JBE). std::vector> complex_data_in(n_points * n_points * n_points); - //std::vector> data_out(n_points * n_points * n_points); // A boolean array of whether the vectors were used for the FFT. std::vector used_in_indexing(reciprocal_space_vectors.size(), true); @@ -142,7 +141,7 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, axes, forward, complex_data_in.data(), - complex_data_in.data(), + complex_data_in.data(), // this is the output array, we are going to overwrite the input as we don't need it fct, nthreads); auto t3 = std::chrono::system_clock::now(); diff --git a/baseline_indexer/test_baseline_indexer.sh b/baseline_indexer/test_baseline_indexer.sh index a589569..5f4bea6 100755 --- a/baseline_indexer/test_baseline_indexer.sh +++ b/baseline_indexer/test_baseline_indexer.sh @@ -23,49 +23,49 @@ output=$(cat candidate_vectors.json) expected_output='{ "00": [ - 0.3828846032802875, - 16.046345646564774, - -2.9586537526204055 + -0.38288460328028456, + -16.046345646564774, + 2.9586537526204055 ], "01": [ - 41.00043348644091, - -6.374347624571426, - -53.04086788840915 + -41.00043348644091, + 6.374347624571426, + 53.04086788840915 ], "02": [ - 25.233528614044186, - 61.114115715026855, - -14.959117174148561 + -25.233528614044186, + -61.114115715026855, + 14.959117174148561 ], "03": [ - 15.894061997532845, - -63.236873000860214, - -38.00999879837036 + -15.894061997532845, + 63.236873000860214, + 38.00999879837036 ], "04": [ - 0.45249998569488525, - 13.574999570846558, - -8.144999742507935 + -0.45249998569488525, + -13.574999570846558, + 8.144999742507935 ], "05": [ - 18.778749406337738, - 87.10624724626541, - 90.95249712467194 + -18.778749406337738, + -87.10624724626541, + -90.95249712467194 ], "06": [ - 75.11499762535095, - 18.09999942779541, + -75.11499762535095, + -18.09999942779541, 0.0 ], "07": [ - 8.144999742507935, - 90.04749715328217, - 25.339999198913574 + -8.144999742507935, + -90.04749715328217, + -25.339999198913574 ], "08": [ - 59.72999811172485, - 82.35499739646912, - 37.33124881982803 + -59.72999811172485, + -82.35499739646912, + -37.33124881982803 ], "09": [ 0.0, From 9f8767dcdc7fee6e8b26f51f978c1b015006b8c7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:40:32 +0000 Subject: [PATCH 62/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/fft3d.cc | 20 +++++++++++--------- baseline_indexer/flood_fill.cc | 13 +++++++------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 5ca6b63..4d00796 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -135,15 +135,17 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, // It is not clear what the best value is for this. spdlog::info("Performing FFT with nthreads={0}", nthreads); // Do the FFT. - c2c(shape_in, - stride_in, - stride_out, - axes, - forward, - complex_data_in.data(), - complex_data_in.data(), // this is the output array, we are going to overwrite the input as we don't need it - fct, - nthreads); + c2c( + shape_in, + stride_in, + stride_out, + axes, + forward, + complex_data_in.data(), + complex_data_in + .data(), // this is the output array, we are going to overwrite the input as we don't need it + fct, + nthreads); auto t3 = std::chrono::system_clock::now(); // Take the square of the real part as the output. diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 45db606..3ffe130 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -8,10 +8,10 @@ #include #define _USE_MATH_DEFINES #include -#include #include #include +#include using Eigen::Vector3d; using Eigen::Vector3i; @@ -72,8 +72,8 @@ std::tuple, std::vector> flood_fill( int n_sq = n_points * n_points; int n_sq_minus_n = n_points * (n_points - 1); int nn_sq_minus_n = n_points * n_points * (n_points - 1); - for (auto& it: grid_binary){ - if (it.second == target){ + for (auto& it : grid_binary) { + if (it.second == target) { //for (int i = 0; i < grid_binary.size(); i++) { //if (grid_binary[i] == target) { // Convert the array index into xyz coordinates. @@ -119,9 +119,10 @@ std::tuple, std::vector> flood_fill( (offset[2] ? (modulo(neighbor[2], n_points) * n_sq) : modz); // z // Check if the neighbor matches the target and push to the stack - std::unordered_map::const_iterator found = grid_binary.find(array_index); - if (found != grid_binary.end()){ - if (found->second == target){ + std::unordered_map::const_iterator found = + grid_binary.find(array_index); + if (found != grid_binary.end()) { + if (found->second == target) { grid_binary[array_index] = replacement; stack.push(neighbor); } From 589029f37b124bdbe900a0c87b2ae82ceb365121 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:02:42 +0000 Subject: [PATCH 63/72] Use common logger --- baseline_indexer/CMakeLists.txt | 2 +- baseline_indexer/fft3d.cc | 16 ++++++------- baseline_indexer/flood_fill.cc | 6 ++--- baseline_indexer/indexer.cc | 37 ++++++++++--------------------- baseline_indexer/sites_to_vecs.cc | 12 +++++----- 5 files changed, 30 insertions(+), 43 deletions(-) diff --git a/baseline_indexer/CMakeLists.txt b/baseline_indexer/CMakeLists.txt index 9d87a05..c2144f8 100644 --- a/baseline_indexer/CMakeLists.txt +++ b/baseline_indexer/CMakeLists.txt @@ -2,7 +2,6 @@ project(indexer CXX) set(CMAKE_CXX_STANDARD 20) -find_package(spdlog) find_package (Threads) # Automatic Dependencies @@ -38,5 +37,6 @@ target_link_libraries(baseline_indexer pocketfft argparse nlohmann_json::nlohmann_json + spdlog::spdlog ${CMAKE_THREAD_LIBS_INIT} ) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index 4d00796..c74df60 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -1,7 +1,7 @@ #include #include #include -#include +#include "common.hpp" #include #include @@ -75,7 +75,7 @@ void map_centroids_to_reciprocal_space_grid( } data_in[index] = {T, 0.0}; } - spdlog::info("Number of centroids used: {0}", count); + logger->info("Number of centroids used: {}", count); } /** @@ -133,7 +133,7 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, double fct{1.0f}; // note, threads can be higher than the number of hardware threads. // It is not clear what the best value is for this. - spdlog::info("Performing FFT with nthreads={0}", nthreads); + logger->info("Performing FFT with nthreads={}", nthreads); // Do the FFT. c2c( shape_in, @@ -159,12 +159,12 @@ std::vector fft3d(std::vector const &reciprocal_space_vectors, std::chrono::duration elapsed_make_arrays = t1 - start; std::chrono::duration elapsed_c2c = t3 - t2; std::chrono::duration elapsed_square = t4 - t3; - spdlog::debug("Total time for fft3d: {0:.5f}s", elapsed_seconds.count()); - spdlog::debug("elapsed time for making data arrays: {0:.5f}s", + logger->debug("Total time for fft3d: {:.5f}s", elapsed_seconds.count()); + logger->debug("elapsed time for making data arrays: {:.5f}s", elapsed_make_arrays.count()); - spdlog::debug("elapsed time for map_to_recip: {0:.5f}s", elapsed_map.count()); - spdlog::debug("elapsed time for c2c: {0:.5f}s", elapsed_c2c.count()); - spdlog::debug("elapsed time for squaring: {0:.5f}s", elapsed_square.count()); + logger->debug("elapsed time for map_to_recip: {:.5f}s", elapsed_map.count()); + logger->debug("elapsed time for c2c: {:.5f}s", elapsed_c2c.count()); + logger->debug("elapsed time for squaring: {:.5f}s", elapsed_square.count()); return used_in_indexing; } diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 3ffe130..5c894ca 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -7,7 +7,7 @@ #include #include #define _USE_MATH_DEFINES -#include +#include "common.hpp" #include #include @@ -54,7 +54,7 @@ std::tuple, std::vector> flood_fill( } auto t2 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time = t2 - start; - spdlog::debug("Time for first part of flood fill: {0:.5f}s", elapsed_time.count()); + logger->debug("Time for first part of flood fill: {:.5f}s", elapsed_time.count()); // Now do the flood fill. // Wrap around the edge in all three dimensions to replicate the DIALS @@ -139,7 +139,7 @@ std::tuple, std::vector> flood_fill( } auto t3 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time2 = t3 - t2; - spdlog::debug("Time for second part of flood fill: {0:.5f}s", + logger->debug("Time for second part of flood fill: {:.5f}s", elapsed_time2.count()); // Now calculate the unweighted centres of mass of each group, in fractional coordinates. diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index 4ce2d3f..b55d08d 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -21,12 +20,11 @@ #include #include +#include "common.hpp" #include "fft3d.cc" #include "flood_fill.cc" #include "gemmi/symmetry.hpp" #include "sites_to_vecs.cc" -#include "spdlog/sinks/basic_file_sink.h" -#include "spdlog/sinks/stdout_color_sinks.h" #include "xyz_to_rlp.cc" using Eigen::Matrix3d; @@ -71,23 +69,12 @@ int main(int argc, char** argv) { .scan<'u', size_t>(); parser.parse_args(argc, argv); - auto console_sink = std::make_shared(); - console_sink->set_level(spdlog::level::info); - auto file_sink = - std::make_shared("ffs_index.log", true); - file_sink->set_level(spdlog::level::debug); - spdlog::logger logger("ffs_index", {console_sink, file_sink}); - spdlog::set_default_logger(std::make_shared( - "ffs_index", spdlog::sinks_init_list({console_sink, file_sink}))); - spdlog::set_level( - spdlog::level::debug); // Will output debug messages, but only to the file log. - if (!parser.is_used("expt")) { - logger.error("Must specify experiment list file with --expt\n"); + logger->error("Must specify experiment list file with --expt\n"); std::exit(1); } if (!parser.is_used("refl")) { - logger.error( + logger->error( "Must specify spotfinding results file (in DIALS HDF5 format) with --refl\n"); std::exit(1); } @@ -96,12 +83,12 @@ int main(int argc, char** argv) { // let's make this a required argument to help with testing/comparison // to DIALS. if (!parser.is_used("max-cell")) { - logger.error("Must specify --max-cell\n"); + logger->error("Must specify --max-cell\n"); std::exit(1); } // FIXME use highest resolution by default to remove this requirement. if (!parser.is_used("dmin")) { - logger.error("Must specify --dmin\n"); + logger->error("Must specify --dmin\n"); std::exit(1); } std::string imported_expt = parser.get("expt"); @@ -116,7 +103,7 @@ int main(int argc, char** argv) { try { elist_json_obj = json::parse(f); } catch (json::parse_error& ex) { - logger.error("Unable to read {0}; json parse error at byte {1}", + logger->error("Unable to read {}; json parse error at byte {}", imported_expt.c_str(), ex.byte); std::exit(1); @@ -145,14 +132,14 @@ int main(int argc, char** argv) { // geometry is accurate). So use the experimental models to transform the spot // coordinates on the detector into reciprocal space. std::vector rlp = xyz_to_rlp(xyzobs_px, panel, beam, scan, gonio); - logger.info("Number of reflections: {0}", rlp.size()); + logger->info("Number of reflections: {}", rlp.size()); // b_iso is an isotropic b-factor used to weight the points when doing the fft. // i.e. high resolution (weaker) spots are downweighted by the expected // intensity fall-off as as function of resolution. double b_iso = -4.0 * std::pow(d_min, 2) * log(0.05); uint32_t n_points = parser.get("fft-npoints"); - logger.info("Setting b_iso = {0:.3f}", b_iso); + logger->info("Setting b_iso = {:.3f}", b_iso); // Create an array to store the fft result. This is a 3D grid of points, typically 256^3. std::vector real_fft_result(n_points * n_points * n_points); @@ -213,11 +200,11 @@ int main(int argc, char** argv) { std::string outfile = "candidate_vectors.json"; std::ofstream vecs_file(outfile); vecs_file << vecs_out.dump(4); - logger.info("Saved candidate vectors to {0}", outfile); + logger->info("Saved candidate vectors to {}", outfile); // Now make a crystal and save an experiment list with the models. if (candidate_lattice_vectors.size() < 3) { - logger.info( + logger->info( "Insufficient number of candidate vectors to make a crystal model."); } else { gemmi::SpaceGroup space_group = *gemmi::find_spacegroup_by_name("P1"); @@ -230,10 +217,10 @@ int main(int argc, char** argv) { std::string efile_name = "elist.json"; std::ofstream efile(efile_name); efile << elist_out.dump(4); - logger.info("Saved experiment list to {0}", efile_name); + logger->info("Saved experiment list to {}", efile_name); } auto t2 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time = t2 - t1; - logger.info("Total time for indexer: {0:.4f}s", elapsed_time.count()); + logger->info("Total time for indexer: {:.4f}s", elapsed_time.count()); } diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 75c1572..5050d23 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -1,6 +1,6 @@ #include #include -#include +#include "common.hpp" #include #include @@ -157,7 +157,7 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, } // If the current site is an integer multiple of the unique site, exit if (is_approximate_integer_multiple(unique_site.site, v)) { - spdlog::info("rejecting {0:.5f} : is integer multiple of {1:.5f}", + logger->info("rejecting {:.5f} : is integer multiple of {:.5f}", v.norm(), unique_site.site.norm()); is_unique = false; @@ -172,15 +172,15 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, std::stable_sort( unique_sites.begin(), unique_sites.end(), compare_site_data_volume); std::vector unique_vectors_sorted; - spdlog::info("Candidate basis vectors:"); + logger->info("Candidate basis vectors:"); for (int i = 0; i < unique_sites.size(); i++) { unique_vectors_sorted.push_back(unique_sites[i].site); - spdlog::info( - "{0} {1:.5f} {2}", i, unique_sites[i].length, unique_sites[i].volume); + logger->info( + "{} {:.5f} {}", i, unique_sites[i].length, unique_sites[i].volume); } std::chrono::duration elapsed_seconds = std::chrono::system_clock::now() - start; - spdlog::debug("elapsed time for sites_to_vecs: {0:.5f}s", elapsed_seconds.count()); + logger->debug("elapsed time for sites_to_vecs: {:.5f}s", elapsed_seconds.count()); return unique_vectors_sorted; } From 984d8df7fd37625c062fc88301635574675ebaa2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:03:12 +0000 Subject: [PATCH 64/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/fft3d.cc | 3 ++- baseline_indexer/flood_fill.cc | 7 +++---- baseline_indexer/indexer.cc | 4 ++-- baseline_indexer/sites_to_vecs.cc | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index c74df60..a1e61c3 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -1,7 +1,6 @@ #include #include #include -#include "common.hpp" #include #include @@ -10,6 +9,8 @@ #include #include +#include "common.hpp" + using Eigen::Matrix3d; using Eigen::Vector3d; using Eigen::Vector3i; diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index 5c894ca..b0bcb17 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -7,12 +7,12 @@ #include #include #define _USE_MATH_DEFINES -#include "common.hpp" - #include #include #include +#include "common.hpp" + using Eigen::Vector3d; using Eigen::Vector3i; @@ -139,8 +139,7 @@ std::tuple, std::vector> flood_fill( } auto t3 = std::chrono::system_clock::now(); std::chrono::duration elapsed_time2 = t3 - t2; - logger->debug("Time for second part of flood fill: {:.5f}s", - elapsed_time2.count()); + logger->debug("Time for second part of flood fill: {:.5f}s", elapsed_time2.count()); // Now calculate the unweighted centres of mass of each group, in fractional coordinates. std::vector centres_of_mass_frac(n_voids); diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index b55d08d..a0da59c 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -104,8 +104,8 @@ int main(int argc, char** argv) { elist_json_obj = json::parse(f); } catch (json::parse_error& ex) { logger->error("Unable to read {}; json parse error at byte {}", - imported_expt.c_str(), - ex.byte); + imported_expt.c_str(), + ex.byte); std::exit(1); } diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/sites_to_vecs.cc index 5050d23..510ee1e 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/sites_to_vecs.cc @@ -1,6 +1,5 @@ #include #include -#include "common.hpp" #include #include @@ -8,6 +7,8 @@ #include #include +#include "common.hpp" + using Eigen::Vector3d; #define _USE_MATH_DEFINES @@ -175,8 +176,7 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, logger->info("Candidate basis vectors:"); for (int i = 0; i < unique_sites.size(); i++) { unique_vectors_sorted.push_back(unique_sites[i].site); - logger->info( - "{} {:.5f} {}", i, unique_sites[i].length, unique_sites[i].volume); + logger->info("{} {:.5f} {}", i, unique_sites[i].length, unique_sites[i].volume); } std::chrono::duration elapsed_seconds = From 72833c8d4b03ddfe45304080d523b3a5672f6c3a Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:11:13 +0000 Subject: [PATCH 65/72] Add unit test for xyz_to_rlp --- baseline_indexer/test_xyz_to_rlp.cc | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 baseline_indexer/test_xyz_to_rlp.cc diff --git a/baseline_indexer/test_xyz_to_rlp.cc b/baseline_indexer/test_xyz_to_rlp.cc new file mode 100644 index 0000000..699ff52 --- /dev/null +++ b/baseline_indexer/test_xyz_to_rlp.cc @@ -0,0 +1,47 @@ +#include "xyz_to_rlp.cc" +#include + +#include +#include +#include +#include +#include +#include +#include + +using Eigen::Matrix3d; +using Eigen::Vector3d; +using json = nlohmann::json; + +TEST(BaselineIndexer, XyztoRlptest) { + std::vector xyzobs_px{{10.1,10.1,50.2, 20.1,20.1,70.2}}; + Scan scan{{1,100}, {0.0,0.1}}; //image range and oscillation. + Goniometer gonio{{{1.0,0.0,0.0}}, {{0.0}}, {{"phi"}}, 0}; //axes, angles, names, scan-axis. + json panel_data; + panel_data["fast_axis"] = {1.0, 0.0, 0.0}; + panel_data["slow_axis"] = {0.0, -1.0, 0.0}; + panel_data["origin"] = {-150,162,-200}; + panel_data["pixel_size"] = {0.075,0.075}; + panel_data["image_size"] = {4148,4362}; + panel_data["trusted_range"] = {0.0,46051}; + panel_data["type"] = std::string("SENSOR_PAD"); + panel_data["name"] = std::string("test"); + panel_data["raw_image_offset"] = {0,0}; + panel_data["thickness"] = 0.45; + panel_data["material"] = "Si"; + panel_data["mu"] = 3.92; + panel_data["gain"] = 1.0; + panel_data["pedestal"] = 0.0; + json pxdata; + pxdata["type"] = std::string("ParallaxCorrectedPxMmStrategy"); + panel_data["px_mm_strategy"] = pxdata; + Panel panel{panel_data}; // use defaults + MonochromaticBeam beam{1.0}; //wavelength + std::vector rlp = xyz_to_rlp( + xyzobs_px, panel, beam, scan, gonio + ); + Vector3d expected_0{{-0.5021752936083477, 0.5690514955867707, 0.27788051106787137}}; + Vector3d expected_1{{-0.5009709068399325, 0.5770958485799975, 0.2562207980973077}}; + EXPECT_EQ(rlp[0], expected_0); + EXPECT_EQ(rlp[1], expected_1); +} \ No newline at end of file From 490da03b580078f792111f5d0a9854aab90244d4 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:52:00 +0000 Subject: [PATCH 66/72] Add test for fft3d file --- baseline_indexer/CMakeLists.txt | 14 +++++++++ baseline_indexer/test_fft3d.cc | 45 +++++++++++++++++++++++++++++ baseline_indexer/test_xyz_to_rlp.cc | 10 +++++-- 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 baseline_indexer/test_fft3d.cc diff --git a/baseline_indexer/CMakeLists.txt b/baseline_indexer/CMakeLists.txt index c2144f8..181ac36 100644 --- a/baseline_indexer/CMakeLists.txt +++ b/baseline_indexer/CMakeLists.txt @@ -26,6 +26,12 @@ FetchContent_GetProperties(pocketfft) add_library(pocketfft INTERFACE) target_include_directories(pocketfft INTERFACE ${pocketfft_SOURCE_DIR}) +# GTest could have been made available under a different name +if(NOT TARGET GTest::gtest_main) + FetchContent_MakeAvailable(GTest) +endif() +enable_testing() + add_executable(baseline_indexer indexer.cc ) @@ -40,3 +46,11 @@ target_link_libraries(baseline_indexer spdlog::spdlog ${CMAKE_THREAD_LIBS_INIT} ) +add_executable(test_xyz_to_rlp test_xyz_to_rlp.cc) +target_link_libraries( + test_xyz_to_rlp GTest::gtest_main dx2 Eigen3::Eigen nlohmann_json::nlohmann_json) +add_executable(test_fft3d test_fft3d.cc) +target_link_libraries( + test_fft3d GTest::gtest_main fmt Eigen3::Eigen pocketfft spdlog::spdlog) +include(GoogleTest) +gtest_discover_tests(test_xyz_to_rlp test_fft3d PROPERTIES LABELS baseline-indexer-tests) \ No newline at end of file diff --git a/baseline_indexer/test_fft3d.cc b/baseline_indexer/test_fft3d.cc new file mode 100644 index 0000000..905f8ba --- /dev/null +++ b/baseline_indexer/test_fft3d.cc @@ -0,0 +1,45 @@ +#include "fft3d.cc" +#include +#include +#include +#include +#include "common.hpp" +using Eigen::Matrix3d; +using Eigen::Vector3d; + +TEST(BaselineIndexer, map_centroids_to_reciprocal_space_test) { + std::vector reciprocal_space_vectors{}; + reciprocal_space_vectors.push_back({-0.2,0.2,0.25}); + reciprocal_space_vectors.push_back({-0.2,0.1,0.10}); + uint32_t n_points = 64; + std::vector> complex_data_in(n_points * n_points * n_points); + std::vector used_in_indexing(reciprocal_space_vectors.size(), true); + double d_min = 2.0; + // First test with no biso; + double b_iso=0.0; + map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso, n_points); + // expect these map to data at indices 80294 and 80752 + EXPECT_DOUBLE_EQ(complex_data_in[80294].real(), 1.0); + EXPECT_DOUBLE_EQ(complex_data_in[80752].real(), 1.0); + // check no other values have been written + EXPECT_DOUBLE_EQ(std::accumulate(complex_data_in.begin(), complex_data_in.end(), std::complex{0.0,0.0}).real(), 2.0); + + // Now set a biso; + double b_iso_2=10.0; + std::vector> complex_data_in_2(n_points * n_points * n_points); + map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, complex_data_in_2, used_in_indexing, d_min, b_iso_2, n_points); + EXPECT_DOUBLE_EQ(complex_data_in_2[80294].real(), 0.86070797642505781); + EXPECT_DOUBLE_EQ(complex_data_in_2[80752].real(), 0.70029752396813894); + //check no other values have been written + EXPECT_DOUBLE_EQ(std::accumulate(complex_data_in_2.begin(), complex_data_in_2.end(), std::complex{0.0,0.0}).real(), 1.5610055003931969); + + // Now set a d_min, which filters out one of the points; + double dmin_2=4.0; + std::vector> complex_data_in_3(n_points * n_points * n_points); + map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, complex_data_in_3, used_in_indexing, dmin_2, b_iso_2, n_points); + // The index changes as reciprocal space is rescaled to cover the resolution range. + // now expect a single value at index 27501 + EXPECT_DOUBLE_EQ(complex_data_in_3[27501].real(), 0.86070797642505781); + // check no other values have been written + EXPECT_DOUBLE_EQ(std::accumulate(complex_data_in_3.begin(), complex_data_in_3.end(), std::complex{0.0,0.0}).real(), 0.86070797642505781); +} \ No newline at end of file diff --git a/baseline_indexer/test_xyz_to_rlp.cc b/baseline_indexer/test_xyz_to_rlp.cc index 699ff52..89cfe4f 100644 --- a/baseline_indexer/test_xyz_to_rlp.cc +++ b/baseline_indexer/test_xyz_to_rlp.cc @@ -14,6 +14,9 @@ using Eigen::Vector3d; using json = nlohmann::json; TEST(BaselineIndexer, XyztoRlptest) { + // Test the xyz_to_rlp function. Compare output to the dials + // equivalent on the same data: centroid_px_to_mm plus + // map_centroids_to_reciprocal_space std::vector xyzobs_px{{10.1,10.1,50.2, 20.1,20.1,70.2}}; Scan scan{{1,100}, {0.0,0.1}}; //image range and oscillation. Goniometer gonio{{{1.0,0.0,0.0}}, {{0.0}}, {{"phi"}}, 0}; //axes, angles, names, scan-axis. @@ -40,8 +43,11 @@ TEST(BaselineIndexer, XyztoRlptest) { std::vector rlp = xyz_to_rlp( xyzobs_px, panel, beam, scan, gonio ); + // Check against the equivalent results from the dials calculation Vector3d expected_0{{-0.5021752936083477, 0.5690514955867707, 0.27788051106787137}}; Vector3d expected_1{{-0.5009709068399325, 0.5770958485799975, 0.2562207980973077}}; - EXPECT_EQ(rlp[0], expected_0); - EXPECT_EQ(rlp[1], expected_1); + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(rlp[0][i], expected_0[i]); + EXPECT_DOUBLE_EQ(rlp[1][i], expected_1[i]); + } } \ No newline at end of file From 806d18c8f7a12b44e8bc80ac06023d7113e643f4 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 4 Feb 2025 13:36:18 +0000 Subject: [PATCH 67/72] Add flood-fill test, move to tests subdirectory --- CMakeLists.txt | 2 + baseline_indexer/CMakeLists.txt | 10 +-- baseline_indexer/fft3d.cc | 6 ++ baseline_indexer/flood_fill.cc | 9 +-- baseline_indexer/tests/CMakeLists.txt | 17 ++++ .../{ => tests}/test_baseline_indexer.sh | 0 baseline_indexer/{ => tests}/test_fft3d.cc | 0 baseline_indexer/tests/test_flood_fill.cc | 81 +++++++++++++++++++ .../{ => tests}/test_xyz_to_rlp.cc | 0 9 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 baseline_indexer/tests/CMakeLists.txt rename baseline_indexer/{ => tests}/test_baseline_indexer.sh (100%) rename baseline_indexer/{ => tests}/test_fft3d.cc (100%) create mode 100644 baseline_indexer/tests/test_flood_fill.cc rename baseline_indexer/{ => tests}/test_xyz_to_rlp.cc (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff5c497..2913b41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,8 @@ project(version CXX) configure_file(version.cc.in version.cc @ONLY) add_library(version STATIC version.cc) +enable_testing() + add_subdirectory(h5read) add_subdirectory(baseline) add_subdirectory(spotfinder) diff --git a/baseline_indexer/CMakeLists.txt b/baseline_indexer/CMakeLists.txt index 181ac36..e6f6656 100644 --- a/baseline_indexer/CMakeLists.txt +++ b/baseline_indexer/CMakeLists.txt @@ -30,8 +30,8 @@ target_include_directories(pocketfft INTERFACE ${pocketfft_SOURCE_DIR}) if(NOT TARGET GTest::gtest_main) FetchContent_MakeAvailable(GTest) endif() -enable_testing() +add_subdirectory(tests) add_executable(baseline_indexer indexer.cc ) @@ -46,11 +46,3 @@ target_link_libraries(baseline_indexer spdlog::spdlog ${CMAKE_THREAD_LIBS_INIT} ) -add_executable(test_xyz_to_rlp test_xyz_to_rlp.cc) -target_link_libraries( - test_xyz_to_rlp GTest::gtest_main dx2 Eigen3::Eigen nlohmann_json::nlohmann_json) -add_executable(test_fft3d test_fft3d.cc) -target_link_libraries( - test_fft3d GTest::gtest_main fmt Eigen3::Eigen pocketfft spdlog::spdlog) -include(GoogleTest) -gtest_discover_tests(test_xyz_to_rlp test_fft3d PROPERTIES LABELS baseline-indexer-tests) \ No newline at end of file diff --git a/baseline_indexer/fft3d.cc b/baseline_indexer/fft3d.cc index a1e61c3..0303daf 100644 --- a/baseline_indexer/fft3d.cc +++ b/baseline_indexer/fft3d.cc @@ -39,6 +39,11 @@ void map_centroids_to_reciprocal_space_grid( assert(data_in.size() == n_points * n_points * n_points); // Determine the resolution span of the grid so we know how to map // each coordinate to the grid. + // -dmin to +dmin spans the coordinate range from 0 to N (in each dimension), + // but the discrete FFT requires input coefficients 0 to N-1, and so our + // grid goes from 0 .. N-1 and we don't use data that maps to grid point N + // (which is equivalent to grid point 0) + const double rlgrid = 2 / (d_min * n_points); const double one_over_rlgrid = 1 / rlgrid; const int half_n_points = n_points / 2; @@ -58,6 +63,7 @@ void map_centroids_to_reciprocal_space_grid( coord[j] = static_cast(round(v[j] * one_over_rlgrid)) + half_n_points; } if ((coord.maxCoeff() >= n_points) || coord.minCoeff() < 0) { + // We can get maxcoeff == n_points if we anear +dmin, see comment above. selection[i] = false; continue; } diff --git a/baseline_indexer/flood_fill.cc b/baseline_indexer/flood_fill.cc index b0bcb17..ba1a7d4 100644 --- a/baseline_indexer/flood_fill.cc +++ b/baseline_indexer/flood_fill.cc @@ -74,8 +74,6 @@ std::tuple, std::vector> flood_fill( int nn_sq_minus_n = n_points * n_points * (n_points - 1); for (auto& it : grid_binary) { if (it.second == target) { - //for (int i = 0; i < grid_binary.size(); i++) { - //if (grid_binary[i] == target) { // Convert the array index into xyz coordinates. // Store xyz coordinates on the stack, but index the array with 1D index. int i = it.first; @@ -127,10 +125,6 @@ std::tuple, std::vector> flood_fill( stack.push(neighbor); } } - //if (grid_binary[array_index] == target) { - // grid_binary[array_index] = replacement; - // stack.push(neighbor); - //} } } replacement++; @@ -167,12 +161,13 @@ std::tuple, std::vector> flood_fill_filter( double peak_volume_cutoff = 0.15) { // Filter out based on iqr range and peak_volume_cutoff std::vector grid_points_per_void_unsorted(grid_points_per_void); + // Acting on a copy of the input data. std::sort(grid_points_per_void.begin(), grid_points_per_void.end()); // The peak around the origin of the FFT can be very large in volume, // so use the IQR range to filter high-volume peaks out that are not // from the typical distribution of points, before applying the peak // volume cutoff based on a fraction of the max volume in the remaining - // array. + // array. But the large volume peaks are still contained in the output. int Q3_index = grid_points_per_void.size() * 3 / 4; int Q1_index = grid_points_per_void.size() / 4; int iqr = grid_points_per_void[Q3_index] - grid_points_per_void[Q1_index]; diff --git a/baseline_indexer/tests/CMakeLists.txt b/baseline_indexer/tests/CMakeLists.txt new file mode 100644 index 0000000..9da2917 --- /dev/null +++ b/baseline_indexer/tests/CMakeLists.txt @@ -0,0 +1,17 @@ +include(GoogleTest) + +add_executable(test_xyz_to_rlp test_xyz_to_rlp.cc) +target_include_directories(test_xyz_to_rlp PRIVATE "${PROJECT_SOURCE_DIR}") +target_link_libraries(test_xyz_to_rlp GTest::gtest_main dx2 Eigen3::Eigen nlohmann_json::nlohmann_json) + +add_executable(test_fft3d test_fft3d.cc) +target_include_directories(test_fft3d PRIVATE "${PROJECT_SOURCE_DIR}") +target_link_libraries(test_fft3d GTest::gtest_main fmt Eigen3::Eigen pocketfft spdlog::spdlog) + +add_executable(test_flood_fill test_flood_fill.cc) +target_include_directories(test_flood_fill PRIVATE "${PROJECT_SOURCE_DIR}") +target_link_libraries(test_flood_fill GTest::gtest_main fmt Eigen3::Eigen spdlog::spdlog) + +gtest_discover_tests(test_xyz_to_rlp PROPERTIES LABELS baseline-indexer-tests) +gtest_discover_tests(test_flood_fill PROPERTIES LABELS baseline-indexer-tests) +gtest_discover_tests(test_fft3d PROPERTIES LABELS baseline-indexer-tests) diff --git a/baseline_indexer/test_baseline_indexer.sh b/baseline_indexer/tests/test_baseline_indexer.sh similarity index 100% rename from baseline_indexer/test_baseline_indexer.sh rename to baseline_indexer/tests/test_baseline_indexer.sh diff --git a/baseline_indexer/test_fft3d.cc b/baseline_indexer/tests/test_fft3d.cc similarity index 100% rename from baseline_indexer/test_fft3d.cc rename to baseline_indexer/tests/test_fft3d.cc diff --git a/baseline_indexer/tests/test_flood_fill.cc b/baseline_indexer/tests/test_flood_fill.cc new file mode 100644 index 0000000..46c50db --- /dev/null +++ b/baseline_indexer/tests/test_flood_fill.cc @@ -0,0 +1,81 @@ +// Test the flood fill algorithm with a small grid e.g. 6^3. + +// both with rmsd cutoff and without + +// Test the filter + +#include "flood_fill.cc" +#include +#include +#include +#include +#include "common.hpp" +using Eigen::Matrix3d; +using Eigen::Vector3d; + +TEST(BaselineIndexer, flood_fill_test) { + // Modify the test values (for channel) from cctbx masks flood_fill + int n_points = 5; + std::vector grid(n_points * n_points * n_points, 0.0); + std::vector corner_cube{{0,4,20,24,100,104,120,124}}; // cube across all 8 corners + // i.e. at fractional coords (0,0,0), (0,0.8,0), (0,0,0.8), ... (0.8,0.8,0.8) + std::vector channel{{12,37,38,39,42,43,62,63,67,112}}; // a channel with a break + // channel: fractional coords along z: 1 at 0, 5 at 0.2, 3 at 0.4, 1 at 0.8 (==-0.2) + for (auto& i : corner_cube){ + grid[i] = 1; + } + for (auto& i : channel){ + grid[i] = 1; + } + std::vector grid_points_per_void; + std::vector centres_of_mass_frac; + std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(grid, 0.0001, n_points); + + EXPECT_EQ(grid_points_per_void[0], 10); // channel + EXPECT_EQ(grid_points_per_void[1], 8); // corner cube + // we return z,y,x. + EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][0], 1.2); // z (== 0.2) + EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][1], 0.46); // y + EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][2], 0.5); // x + // points over the boundary are equivalent modulo 1.0 (centre of space is at 0.5,0.5,0.5) + EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][0], 0.9); // corner cube + EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][1], -0.1); // corner cube (==0.9) + EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][2], 0.9); // corner cube +} + +TEST(BaselineIndexer, flood_fill_filter_test) { + std::vector grid_points_per_void{{1,3,1,2,80,5,3,4,2}}; + std::vector centres_of_mass_frac; + for (int i=0;i(i+1) / 10.0; + centres_of_mass_frac.push_back({frac,frac,frac}); + } + std::vector grid_points_per_void_out; + std::vector centres_of_mass_frac_out; + // The value 80 should be internally filtered out based on IQR + // but solely for the purposes of the peak_volume_cutoff check. + // i.e. Then max value is 5, so with a peak_volume_cutoff of 0.2, + // only the ones should be removed. + std::tie(grid_points_per_void_out, centres_of_mass_frac_out) = flood_fill_filter( + grid_points_per_void, centres_of_mass_frac, 0.2); + + EXPECT_EQ(grid_points_per_void_out.size(), 7); + EXPECT_EQ(centres_of_mass_frac_out.size(), 7); + EXPECT_EQ(grid_points_per_void.size(), 9); // it was unmodified. + EXPECT_EQ(centres_of_mass_frac.size(), 9); // it was unmodified. + std::vector expected_grid_points_per_void{{3,2,80, 5,3,4,2}}; + std::vector expected_centres_of_mass; + expected_centres_of_mass.push_back({0.2,0.2,0.2}); + expected_centres_of_mass.push_back({0.4,0.4,0.4}); + expected_centres_of_mass.push_back({0.5,0.5,0.5}); + expected_centres_of_mass.push_back({0.6,0.6,0.6}); + expected_centres_of_mass.push_back({0.7,0.7,0.7}); + expected_centres_of_mass.push_back({0.8,0.8,0.8}); + expected_centres_of_mass.push_back({0.9,0.9,0.9}); + for (int i=0;i Date: Tue, 4 Feb 2025 13:54:58 +0000 Subject: [PATCH 68/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/tests/test_fft3d.cc | 57 ++++++++++++----- baseline_indexer/tests/test_flood_fill.cc | 76 ++++++++++++----------- baseline_indexer/tests/test_xyz_to_rlp.cc | 36 +++++------ 3 files changed, 102 insertions(+), 67 deletions(-) diff --git a/baseline_indexer/tests/test_fft3d.cc b/baseline_indexer/tests/test_fft3d.cc index 905f8ba..36d07d1 100644 --- a/baseline_indexer/tests/test_fft3d.cc +++ b/baseline_indexer/tests/test_fft3d.cc @@ -1,45 +1,74 @@ -#include "fft3d.cc" #include -#include #include + #include +#include + #include "common.hpp" +#include "fft3d.cc" using Eigen::Matrix3d; using Eigen::Vector3d; TEST(BaselineIndexer, map_centroids_to_reciprocal_space_test) { std::vector reciprocal_space_vectors{}; - reciprocal_space_vectors.push_back({-0.2,0.2,0.25}); - reciprocal_space_vectors.push_back({-0.2,0.1,0.10}); + reciprocal_space_vectors.push_back({-0.2, 0.2, 0.25}); + reciprocal_space_vectors.push_back({-0.2, 0.1, 0.10}); uint32_t n_points = 64; std::vector> complex_data_in(n_points * n_points * n_points); std::vector used_in_indexing(reciprocal_space_vectors.size(), true); double d_min = 2.0; // First test with no biso; - double b_iso=0.0; - map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, complex_data_in, used_in_indexing, d_min, b_iso, n_points); + double b_iso = 0.0; + map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, + complex_data_in, + used_in_indexing, + d_min, + b_iso, + n_points); // expect these map to data at indices 80294 and 80752 EXPECT_DOUBLE_EQ(complex_data_in[80294].real(), 1.0); EXPECT_DOUBLE_EQ(complex_data_in[80752].real(), 1.0); // check no other values have been written - EXPECT_DOUBLE_EQ(std::accumulate(complex_data_in.begin(), complex_data_in.end(), std::complex{0.0,0.0}).real(), 2.0); - + EXPECT_DOUBLE_EQ( + std::accumulate( + complex_data_in.begin(), complex_data_in.end(), std::complex{0.0, 0.0}) + .real(), + 2.0); + // Now set a biso; - double b_iso_2=10.0; + double b_iso_2 = 10.0; std::vector> complex_data_in_2(n_points * n_points * n_points); - map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, complex_data_in_2, used_in_indexing, d_min, b_iso_2, n_points); + map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, + complex_data_in_2, + used_in_indexing, + d_min, + b_iso_2, + n_points); EXPECT_DOUBLE_EQ(complex_data_in_2[80294].real(), 0.86070797642505781); EXPECT_DOUBLE_EQ(complex_data_in_2[80752].real(), 0.70029752396813894); //check no other values have been written - EXPECT_DOUBLE_EQ(std::accumulate(complex_data_in_2.begin(), complex_data_in_2.end(), std::complex{0.0,0.0}).real(), 1.5610055003931969); + EXPECT_DOUBLE_EQ( + std::accumulate( + complex_data_in_2.begin(), complex_data_in_2.end(), std::complex{0.0, 0.0}) + .real(), + 1.5610055003931969); // Now set a d_min, which filters out one of the points; - double dmin_2=4.0; + double dmin_2 = 4.0; std::vector> complex_data_in_3(n_points * n_points * n_points); - map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, complex_data_in_3, used_in_indexing, dmin_2, b_iso_2, n_points); + map_centroids_to_reciprocal_space_grid(reciprocal_space_vectors, + complex_data_in_3, + used_in_indexing, + dmin_2, + b_iso_2, + n_points); // The index changes as reciprocal space is rescaled to cover the resolution range. // now expect a single value at index 27501 EXPECT_DOUBLE_EQ(complex_data_in_3[27501].real(), 0.86070797642505781); // check no other values have been written - EXPECT_DOUBLE_EQ(std::accumulate(complex_data_in_3.begin(), complex_data_in_3.end(), std::complex{0.0,0.0}).real(), 0.86070797642505781); + EXPECT_DOUBLE_EQ( + std::accumulate( + complex_data_in_3.begin(), complex_data_in_3.end(), std::complex{0.0, 0.0}) + .real(), + 0.86070797642505781); } \ No newline at end of file diff --git a/baseline_indexer/tests/test_flood_fill.cc b/baseline_indexer/tests/test_flood_fill.cc index 46c50db..d79563c 100644 --- a/baseline_indexer/tests/test_flood_fill.cc +++ b/baseline_indexer/tests/test_flood_fill.cc @@ -4,12 +4,14 @@ // Test the filter -#include "flood_fill.cc" #include -#include #include + #include +#include + #include "common.hpp" +#include "flood_fill.cc" using Eigen::Matrix3d; using Eigen::Vector3d; @@ -17,38 +19,41 @@ TEST(BaselineIndexer, flood_fill_test) { // Modify the test values (for channel) from cctbx masks flood_fill int n_points = 5; std::vector grid(n_points * n_points * n_points, 0.0); - std::vector corner_cube{{0,4,20,24,100,104,120,124}}; // cube across all 8 corners + std::vector corner_cube{ + {0, 4, 20, 24, 100, 104, 120, 124}}; // cube across all 8 corners // i.e. at fractional coords (0,0,0), (0,0.8,0), (0,0,0.8), ... (0.8,0.8,0.8) - std::vector channel{{12,37,38,39,42,43,62,63,67,112}}; // a channel with a break + std::vector channel{ + {12, 37, 38, 39, 42, 43, 62, 63, 67, 112}}; // a channel with a break // channel: fractional coords along z: 1 at 0, 5 at 0.2, 3 at 0.4, 1 at 0.8 (==-0.2) - for (auto& i : corner_cube){ + for (auto& i : corner_cube) { grid[i] = 1; } - for (auto& i : channel){ + for (auto& i : channel) { grid[i] = 1; } std::vector grid_points_per_void; std::vector centres_of_mass_frac; - std::tie(grid_points_per_void, centres_of_mass_frac) = flood_fill(grid, 0.0001, n_points); + std::tie(grid_points_per_void, centres_of_mass_frac) = + flood_fill(grid, 0.0001, n_points); - EXPECT_EQ(grid_points_per_void[0], 10); // channel - EXPECT_EQ(grid_points_per_void[1], 8); // corner cube + EXPECT_EQ(grid_points_per_void[0], 10); // channel + EXPECT_EQ(grid_points_per_void[1], 8); // corner cube // we return z,y,x. - EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][0], 1.2); // z (== 0.2) - EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][1], 0.46); // y - EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][2], 0.5); // x + EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][0], 1.2); // z (== 0.2) + EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][1], 0.46); // y + EXPECT_DOUBLE_EQ(centres_of_mass_frac[0][2], 0.5); // x // points over the boundary are equivalent modulo 1.0 (centre of space is at 0.5,0.5,0.5) - EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][0], 0.9); // corner cube - EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][1], -0.1); // corner cube (==0.9) - EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][2], 0.9); // corner cube + EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][0], 0.9); // corner cube + EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][1], -0.1); // corner cube (==0.9) + EXPECT_DOUBLE_EQ(centres_of_mass_frac[1][2], 0.9); // corner cube } TEST(BaselineIndexer, flood_fill_filter_test) { - std::vector grid_points_per_void{{1,3,1,2,80,5,3,4,2}}; + std::vector grid_points_per_void{{1, 3, 1, 2, 80, 5, 3, 4, 2}}; std::vector centres_of_mass_frac; - for (int i=0;i(i+1) / 10.0; - centres_of_mass_frac.push_back({frac,frac,frac}); + for (int i = 0; i < grid_points_per_void.size(); i++) { + double frac = static_cast(i + 1) / 10.0; + centres_of_mass_frac.push_back({frac, frac, frac}); } std::vector grid_points_per_void_out; std::vector centres_of_mass_frac_out; @@ -56,26 +61,27 @@ TEST(BaselineIndexer, flood_fill_filter_test) { // but solely for the purposes of the peak_volume_cutoff check. // i.e. Then max value is 5, so with a peak_volume_cutoff of 0.2, // only the ones should be removed. - std::tie(grid_points_per_void_out, centres_of_mass_frac_out) = flood_fill_filter( - grid_points_per_void, centres_of_mass_frac, 0.2); - + std::tie(grid_points_per_void_out, centres_of_mass_frac_out) = + flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.2); + EXPECT_EQ(grid_points_per_void_out.size(), 7); EXPECT_EQ(centres_of_mass_frac_out.size(), 7); - EXPECT_EQ(grid_points_per_void.size(), 9); // it was unmodified. - EXPECT_EQ(centres_of_mass_frac.size(), 9); // it was unmodified. - std::vector expected_grid_points_per_void{{3,2,80, 5,3,4,2}}; + EXPECT_EQ(grid_points_per_void.size(), 9); // it was unmodified. + EXPECT_EQ(centres_of_mass_frac.size(), 9); // it was unmodified. + std::vector expected_grid_points_per_void{{3, 2, 80, 5, 3, 4, 2}}; std::vector expected_centres_of_mass; - expected_centres_of_mass.push_back({0.2,0.2,0.2}); - expected_centres_of_mass.push_back({0.4,0.4,0.4}); - expected_centres_of_mass.push_back({0.5,0.5,0.5}); - expected_centres_of_mass.push_back({0.6,0.6,0.6}); - expected_centres_of_mass.push_back({0.7,0.7,0.7}); - expected_centres_of_mass.push_back({0.8,0.8,0.8}); - expected_centres_of_mass.push_back({0.9,0.9,0.9}); - for (int i=0;i - #include #include #include #include +#include #include -#include + #include +#include + +#include "xyz_to_rlp.cc" using Eigen::Matrix3d; using Eigen::Vector3d; @@ -17,19 +18,20 @@ TEST(BaselineIndexer, XyztoRlptest) { // Test the xyz_to_rlp function. Compare output to the dials // equivalent on the same data: centroid_px_to_mm plus // map_centroids_to_reciprocal_space - std::vector xyzobs_px{{10.1,10.1,50.2, 20.1,20.1,70.2}}; - Scan scan{{1,100}, {0.0,0.1}}; //image range and oscillation. - Goniometer gonio{{{1.0,0.0,0.0}}, {{0.0}}, {{"phi"}}, 0}; //axes, angles, names, scan-axis. + std::vector xyzobs_px{{10.1, 10.1, 50.2, 20.1, 20.1, 70.2}}; + Scan scan{{1, 100}, {0.0, 0.1}}; //image range and oscillation. + Goniometer gonio{ + {{1.0, 0.0, 0.0}}, {{0.0}}, {{"phi"}}, 0}; //axes, angles, names, scan-axis. json panel_data; panel_data["fast_axis"] = {1.0, 0.0, 0.0}; panel_data["slow_axis"] = {0.0, -1.0, 0.0}; - panel_data["origin"] = {-150,162,-200}; - panel_data["pixel_size"] = {0.075,0.075}; - panel_data["image_size"] = {4148,4362}; - panel_data["trusted_range"] = {0.0,46051}; + panel_data["origin"] = {-150, 162, -200}; + panel_data["pixel_size"] = {0.075, 0.075}; + panel_data["image_size"] = {4148, 4362}; + panel_data["trusted_range"] = {0.0, 46051}; panel_data["type"] = std::string("SENSOR_PAD"); panel_data["name"] = std::string("test"); - panel_data["raw_image_offset"] = {0,0}; + panel_data["raw_image_offset"] = {0, 0}; panel_data["thickness"] = 0.45; panel_data["material"] = "Si"; panel_data["mu"] = 3.92; @@ -38,15 +40,13 @@ TEST(BaselineIndexer, XyztoRlptest) { json pxdata; pxdata["type"] = std::string("ParallaxCorrectedPxMmStrategy"); panel_data["px_mm_strategy"] = pxdata; - Panel panel{panel_data}; // use defaults - MonochromaticBeam beam{1.0}; //wavelength - std::vector rlp = xyz_to_rlp( - xyzobs_px, panel, beam, scan, gonio - ); + Panel panel{panel_data}; // use defaults + MonochromaticBeam beam{1.0}; //wavelength + std::vector rlp = xyz_to_rlp(xyzobs_px, panel, beam, scan, gonio); // Check against the equivalent results from the dials calculation Vector3d expected_0{{-0.5021752936083477, 0.5690514955867707, 0.27788051106787137}}; Vector3d expected_1{{-0.5009709068399325, 0.5770958485799975, 0.2562207980973077}}; - for (int i=0;i<3;i++){ + for (int i = 0; i < 3; i++) { EXPECT_DOUBLE_EQ(rlp[0][i], expected_0[i]); EXPECT_DOUBLE_EQ(rlp[1][i], expected_1[i]); } From a56cc95b166236b036aab10153ea114a773720e7 Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:25:17 +0000 Subject: [PATCH 69/72] Update test to test the rmsd cutoff filter --- baseline_indexer/tests/test_flood_fill.cc | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/baseline_indexer/tests/test_flood_fill.cc b/baseline_indexer/tests/test_flood_fill.cc index d79563c..069126b 100644 --- a/baseline_indexer/tests/test_flood_fill.cc +++ b/baseline_indexer/tests/test_flood_fill.cc @@ -1,9 +1,3 @@ -// Test the flood fill algorithm with a small grid e.g. 6^3. - -// both with rmsd cutoff and without - -// Test the filter - #include #include @@ -26,15 +20,17 @@ TEST(BaselineIndexer, flood_fill_test) { {12, 37, 38, 39, 42, 43, 62, 63, 67, 112}}; // a channel with a break // channel: fractional coords along z: 1 at 0, 5 at 0.2, 3 at 0.4, 1 at 0.8 (==-0.2) for (auto& i : corner_cube) { - grid[i] = 1; + grid[i] = 100; } for (auto& i : channel) { - grid[i] = 1; + grid[i] = 100; } + // now add a weak point (next to the corner), which should get filtered out by the rmsd cutoff filter. + grid[1] = 1; // the RMSD is approx 35, so anything below this is filtered out. std::vector grid_points_per_void; std::vector centres_of_mass_frac; std::tie(grid_points_per_void, centres_of_mass_frac) = - flood_fill(grid, 0.0001, n_points); + flood_fill(grid, 1.0, n_points); EXPECT_EQ(grid_points_per_void[0], 10); // channel EXPECT_EQ(grid_points_per_void[1], 8); // corner cube From 81abd600973979f2f54336464570aca75efa24f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:25:42 +0000 Subject: [PATCH 70/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/tests/test_flood_fill.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseline_indexer/tests/test_flood_fill.cc b/baseline_indexer/tests/test_flood_fill.cc index 069126b..04c10d4 100644 --- a/baseline_indexer/tests/test_flood_fill.cc +++ b/baseline_indexer/tests/test_flood_fill.cc @@ -26,7 +26,7 @@ TEST(BaselineIndexer, flood_fill_test) { grid[i] = 100; } // now add a weak point (next to the corner), which should get filtered out by the rmsd cutoff filter. - grid[1] = 1; // the RMSD is approx 35, so anything below this is filtered out. + grid[1] = 1; // the RMSD is approx 35, so anything below this is filtered out. std::vector grid_points_per_void; std::vector centres_of_mass_frac; std::tie(grid_points_per_void, centres_of_mass_frac) = From 77c7b3b8a4a7bd88eaae37ea7d372c391c8728bc Mon Sep 17 00:00:00 2001 From: James Beilsten-Edmands <30625594+jbeilstenedmands@users.noreply.github.com> Date: Thu, 6 Feb 2025 10:25:26 +0000 Subject: [PATCH 71/72] Rename sites_to_vecs to peaks_to_rlvs, add test --- baseline_indexer/indexer.cc | 16 ++-- .../{sites_to_vecs.cc => peaks_to_rlvs.cc} | 4 +- baseline_indexer/tests/CMakeLists.txt | 5 ++ baseline_indexer/tests/test_peaks_to_rlvs.cc | 86 +++++++++++++++++++ 4 files changed, 101 insertions(+), 10 deletions(-) rename baseline_indexer/{sites_to_vecs.cc => peaks_to_rlvs.cc} (98%) create mode 100644 baseline_indexer/tests/test_peaks_to_rlvs.cc diff --git a/baseline_indexer/indexer.cc b/baseline_indexer/indexer.cc index a0da59c..f238114 100644 --- a/baseline_indexer/indexer.cc +++ b/baseline_indexer/indexer.cc @@ -24,7 +24,7 @@ #include "fft3d.cc" #include "flood_fill.cc" #include "gemmi/symmetry.hpp" -#include "sites_to_vecs.cc" +#include "peaks_to_rlvs.cc" #include "xyz_to_rlp.cc" using Eigen::Matrix3d; @@ -165,20 +165,20 @@ int main(int argc, char** argv) { // perhaps this could be done with connected components analysis. // So do the flood fill, and extract the centres of mass of the peaks and the number of grid points // that contribute to each peak. - std::vector grid_points_per_void; - std::vector centres_of_mass_frac; + std::vector grid_points_per_peak; + std::vector fractional_centres_of_mass; // 15.0 is the DIALS 'rmsd_cutoff' parameter to filter out weak peaks. - std::tie(grid_points_per_void, centres_of_mass_frac) = + std::tie(grid_points_per_peak, fractional_centres_of_mass) = flood_fill(real_fft_result, 15.0, n_points); // Do some further filtering, 0.15 is the DIALS peak_volume_cutoff parameter. - std::tie(grid_points_per_void, centres_of_mass_frac) = - flood_fill_filter(grid_points_per_void, centres_of_mass_frac, 0.15); + std::tie(grid_points_per_peak, fractional_centres_of_mass) = + flood_fill_filter(grid_points_per_peak, fractional_centres_of_mass, 0.15); // Convert the peak centres from the fft grid into vectors in reciprocal space. These are our candidate // lattice vectors. // 3.0 is the min cell parameter. - std::vector candidate_lattice_vectors = sites_to_vecs( - centres_of_mass_frac, grid_points_per_void, d_min, 3.0, max_cell, n_points); + std::vector candidate_lattice_vectors = peaks_to_rlvs( + fractional_centres_of_mass, grid_points_per_peak, d_min, 3.0, max_cell, n_points); // at this point, we will test combinations of the candidate vectors, use those to index the spots, do some // refinement of the candidates and choose the best one. Then we will do some more refinement including extra diff --git a/baseline_indexer/sites_to_vecs.cc b/baseline_indexer/peaks_to_rlvs.cc similarity index 98% rename from baseline_indexer/sites_to_vecs.cc rename to baseline_indexer/peaks_to_rlvs.cc index 510ee1e..5e5b0e6 100644 --- a/baseline_indexer/sites_to_vecs.cc +++ b/baseline_indexer/peaks_to_rlvs.cc @@ -71,7 +71,7 @@ bool is_approximate_integer_multiple(Vector3d v1, * @param n_points The size of each dimension of the FFT grid. * @returns Unique vectors, sorted by volume, that give describe the FFT peaks. */ -std::vector sites_to_vecs(std::vector centres_of_mass_frac, +std::vector peaks_to_rlvs(std::vector centres_of_mass_frac, std::vector grid_points_per_void, double d_min, double min_cell = 3.0, @@ -181,6 +181,6 @@ std::vector sites_to_vecs(std::vector centres_of_mass_frac, std::chrono::duration elapsed_seconds = std::chrono::system_clock::now() - start; - logger->debug("elapsed time for sites_to_vecs: {:.5f}s", elapsed_seconds.count()); + logger->debug("elapsed time for peaks_to_rlvs: {:.5f}s", elapsed_seconds.count()); return unique_vectors_sorted; } diff --git a/baseline_indexer/tests/CMakeLists.txt b/baseline_indexer/tests/CMakeLists.txt index 9da2917..c305d24 100644 --- a/baseline_indexer/tests/CMakeLists.txt +++ b/baseline_indexer/tests/CMakeLists.txt @@ -12,6 +12,11 @@ add_executable(test_flood_fill test_flood_fill.cc) target_include_directories(test_flood_fill PRIVATE "${PROJECT_SOURCE_DIR}") target_link_libraries(test_flood_fill GTest::gtest_main fmt Eigen3::Eigen spdlog::spdlog) +add_executable(test_peaks_to_rlvs test_peaks_to_rlvs.cc) +target_include_directories(test_peaks_to_rlvs PRIVATE "${PROJECT_SOURCE_DIR}") +target_link_libraries(test_peaks_to_rlvs GTest::gtest_main fmt dx2 Eigen3::Eigen spdlog::spdlog) + gtest_discover_tests(test_xyz_to_rlp PROPERTIES LABELS baseline-indexer-tests) gtest_discover_tests(test_flood_fill PROPERTIES LABELS baseline-indexer-tests) gtest_discover_tests(test_fft3d PROPERTIES LABELS baseline-indexer-tests) +gtest_discover_tests(test_peaks_to_rlvs PROPERTIES LABELS baseline-indexer-tests) diff --git a/baseline_indexer/tests/test_peaks_to_rlvs.cc b/baseline_indexer/tests/test_peaks_to_rlvs.cc new file mode 100644 index 0000000..b51ac81 --- /dev/null +++ b/baseline_indexer/tests/test_peaks_to_rlvs.cc @@ -0,0 +1,86 @@ +#include +#include + +#include +#include +#include "common.hpp" +#include "peaks_to_rlvs.cc" + +using Eigen::Matrix3d; +using Eigen::Vector3d; + +TEST(BaselineIndexer, peaks_to_rlvs_test) { + // peaks_to_rlvs transforms the fractional centres of mass on the FFT grid + // back into basis vectors in reciprocal space. + // The origin of space is at fractional coordinate (0.5, 0.5, 0.5). + std::vector grid_points_per_void; + std::vector centres_of_mass_frac; + grid_points_per_void.push_back(8); + grid_points_per_void.push_back(10); + grid_points_per_void.push_back(10); + centres_of_mass_frac.push_back({0.75,0.75,0.75}); // rlp = (64.0, 64.0, 64.0) = 110.85A + centres_of_mass_frac.push_back({0.1,0.1,0.1}); // rlp = (25.6, 25.6,25.6) = 44.34A + centres_of_mass_frac.push_back({0.4,0.4,0.4}); // rlp = (102.4, 102.4,102.4) = 177.36A + double dmin = 2.0; + double min_cell = 3.0; + double max_cell = 100.0; + uint32_t n_points = 256; + std::vector unique = peaks_to_rlvs( + centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points + ); + // Results should be sorted by grid points per void in descending order + // Equivalent multiples are not filtered if the grid points per void are equal. + EXPECT_EQ(unique.size(), 3); + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(unique[0][i], 25.6); // point at 0.1,0.1,0.1 + } + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(unique[1][i], 102.4); // point at 0.4,0.4,0.4 + } + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(unique[2][i], -64.0); // point at 0.75,0.75,0.75 + } + + grid_points_per_void[1] = 11; // This causes the third to + // get filtered as an equivalent to the second + std::vector unique2 = peaks_to_rlvs( + centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points + ); + EXPECT_EQ(unique2.size(), 2); + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(unique2[0][i], 25.6); // point at 0.1,0.1,0.1 + } + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(unique2[1][i], -64.0); // point at 0.75,0.75,0.75 + } + + // now check grouping based on length and angle - second and third should be combined + centres_of_mass_frac[1] = {0.6,0.6,0.6}; // i.e. inverse pair to index 2 + centres_of_mass_frac[2] = {0.405,0.405,0.405}; // similar but not exactly equal to index 1 + grid_points_per_void[1] = 10; // i.e. wouldn't get filtered out by approx multiple filter + std::vector unique3 = peaks_to_rlvs( + centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points + ); + EXPECT_EQ(unique3.size(), 2); + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(unique3[0][i], -103.04); // mean of point at 0.6,0.6,0.6 and 0.405,0.405,0.405 + } + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(unique3[1][i], -64.0); // point at 0.75,0.75,0.75 + } + + // check min and max cell filters + // the three points are at d-values of 44.34, 177.36 and 110.85 + // set min_cell to 50 and max_cell to 80, to just leave the 110.85 solution (yes the filter in the + // dials code is cell < 2 * max_cell...). This could be changed here to make sense. + double min_cell2 = 50.0; + double max_cell2 = 80.0; + centres_of_mass_frac[2] = {0.4,0.4,0.4}; + std::vector unique4 = peaks_to_rlvs( + centres_of_mass_frac, grid_points_per_void, dmin, min_cell2, max_cell2, n_points + ); + EXPECT_EQ(unique4.size(), 1); + for (int i=0;i<3;i++){ + EXPECT_DOUBLE_EQ(unique4[0][i], -64.0); // rlp = (64.0, 64.0, 64.0) = 110.85A + } +} \ No newline at end of file From 2b53b09b175c32e81b5ea8f8dd49ffd2fbdd467f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 10:27:02 +0000 Subject: [PATCH 72/72] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- baseline_indexer/tests/test_peaks_to_rlvs.cc | 68 +++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/baseline_indexer/tests/test_peaks_to_rlvs.cc b/baseline_indexer/tests/test_peaks_to_rlvs.cc index b51ac81..a2dccef 100644 --- a/baseline_indexer/tests/test_peaks_to_rlvs.cc +++ b/baseline_indexer/tests/test_peaks_to_rlvs.cc @@ -3,6 +3,7 @@ #include #include + #include "common.hpp" #include "peaks_to_rlvs.cc" @@ -18,55 +19,59 @@ TEST(BaselineIndexer, peaks_to_rlvs_test) { grid_points_per_void.push_back(8); grid_points_per_void.push_back(10); grid_points_per_void.push_back(10); - centres_of_mass_frac.push_back({0.75,0.75,0.75}); // rlp = (64.0, 64.0, 64.0) = 110.85A - centres_of_mass_frac.push_back({0.1,0.1,0.1}); // rlp = (25.6, 25.6,25.6) = 44.34A - centres_of_mass_frac.push_back({0.4,0.4,0.4}); // rlp = (102.4, 102.4,102.4) = 177.36A + centres_of_mass_frac.push_back( + {0.75, 0.75, 0.75}); // rlp = (64.0, 64.0, 64.0) = 110.85A + centres_of_mass_frac.push_back( + {0.1, 0.1, 0.1}); // rlp = (25.6, 25.6,25.6) = 44.34A + centres_of_mass_frac.push_back( + {0.4, 0.4, 0.4}); // rlp = (102.4, 102.4,102.4) = 177.36A double dmin = 2.0; double min_cell = 3.0; double max_cell = 100.0; uint32_t n_points = 256; std::vector unique = peaks_to_rlvs( - centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points - ); + centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points); // Results should be sorted by grid points per void in descending order // Equivalent multiples are not filtered if the grid points per void are equal. EXPECT_EQ(unique.size(), 3); - for (int i=0;i<3;i++){ - EXPECT_DOUBLE_EQ(unique[0][i], 25.6); // point at 0.1,0.1,0.1 + for (int i = 0; i < 3; i++) { + EXPECT_DOUBLE_EQ(unique[0][i], 25.6); // point at 0.1,0.1,0.1 } - for (int i=0;i<3;i++){ - EXPECT_DOUBLE_EQ(unique[1][i], 102.4); // point at 0.4,0.4,0.4 + for (int i = 0; i < 3; i++) { + EXPECT_DOUBLE_EQ(unique[1][i], 102.4); // point at 0.4,0.4,0.4 } - for (int i=0;i<3;i++){ - EXPECT_DOUBLE_EQ(unique[2][i], -64.0); // point at 0.75,0.75,0.75 + for (int i = 0; i < 3; i++) { + EXPECT_DOUBLE_EQ(unique[2][i], -64.0); // point at 0.75,0.75,0.75 } - grid_points_per_void[1] = 11; // This causes the third to + grid_points_per_void[1] = 11; // This causes the third to // get filtered as an equivalent to the second std::vector unique2 = peaks_to_rlvs( - centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points - ); + centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points); EXPECT_EQ(unique2.size(), 2); - for (int i=0;i<3;i++){ - EXPECT_DOUBLE_EQ(unique2[0][i], 25.6); // point at 0.1,0.1,0.1 + for (int i = 0; i < 3; i++) { + EXPECT_DOUBLE_EQ(unique2[0][i], 25.6); // point at 0.1,0.1,0.1 } - for (int i=0;i<3;i++){ - EXPECT_DOUBLE_EQ(unique2[1][i], -64.0); // point at 0.75,0.75,0.75 + for (int i = 0; i < 3; i++) { + EXPECT_DOUBLE_EQ(unique2[1][i], -64.0); // point at 0.75,0.75,0.75 } // now check grouping based on length and angle - second and third should be combined - centres_of_mass_frac[1] = {0.6,0.6,0.6}; // i.e. inverse pair to index 2 - centres_of_mass_frac[2] = {0.405,0.405,0.405}; // similar but not exactly equal to index 1 - grid_points_per_void[1] = 10; // i.e. wouldn't get filtered out by approx multiple filter + centres_of_mass_frac[1] = {0.6, 0.6, 0.6}; // i.e. inverse pair to index 2 + centres_of_mass_frac[2] = { + 0.405, 0.405, 0.405}; // similar but not exactly equal to index 1 + grid_points_per_void[1] = + 10; // i.e. wouldn't get filtered out by approx multiple filter std::vector unique3 = peaks_to_rlvs( - centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points - ); + centres_of_mass_frac, grid_points_per_void, dmin, min_cell, max_cell, n_points); EXPECT_EQ(unique3.size(), 2); - for (int i=0;i<3;i++){ - EXPECT_DOUBLE_EQ(unique3[0][i], -103.04); // mean of point at 0.6,0.6,0.6 and 0.405,0.405,0.405 + for (int i = 0; i < 3; i++) { + EXPECT_DOUBLE_EQ( + unique3[0][i], + -103.04); // mean of point at 0.6,0.6,0.6 and 0.405,0.405,0.405 } - for (int i=0;i<3;i++){ - EXPECT_DOUBLE_EQ(unique3[1][i], -64.0); // point at 0.75,0.75,0.75 + for (int i = 0; i < 3; i++) { + EXPECT_DOUBLE_EQ(unique3[1][i], -64.0); // point at 0.75,0.75,0.75 } // check min and max cell filters @@ -75,12 +80,11 @@ TEST(BaselineIndexer, peaks_to_rlvs_test) { // dials code is cell < 2 * max_cell...). This could be changed here to make sense. double min_cell2 = 50.0; double max_cell2 = 80.0; - centres_of_mass_frac[2] = {0.4,0.4,0.4}; + centres_of_mass_frac[2] = {0.4, 0.4, 0.4}; std::vector unique4 = peaks_to_rlvs( - centres_of_mass_frac, grid_points_per_void, dmin, min_cell2, max_cell2, n_points - ); + centres_of_mass_frac, grid_points_per_void, dmin, min_cell2, max_cell2, n_points); EXPECT_EQ(unique4.size(), 1); - for (int i=0;i<3;i++){ - EXPECT_DOUBLE_EQ(unique4[0][i], -64.0); // rlp = (64.0, 64.0, 64.0) = 110.85A + for (int i = 0; i < 3; i++) { + EXPECT_DOUBLE_EQ(unique4[0][i], -64.0); // rlp = (64.0, 64.0, 64.0) = 110.85A } } \ No newline at end of file