├── extlib └── .gitignore ├── .gitignore ├── .gitmodules ├── test ├── CMakeLists.txt ├── test_aabb.cpp ├── test_collision.cpp └── test_read_as_angstrom.cpp ├── src ├── CMakeLists.txt ├── simulator.cpp └── afmize.cpp ├── include └── afmize │ ├── read_number.hpp │ ├── noise.hpp │ ├── simulator_base.hpp │ ├── parameter.hpp │ ├── system.hpp │ ├── colormap.hpp │ ├── stage.hpp │ ├── input_utility.hpp │ ├── reader_base.hpp │ ├── image.hpp │ ├── shapes.hpp │ ├── mask.hpp │ ├── progress_bar.hpp │ ├── collision.hpp │ ├── xyz_reader.hpp │ ├── output_utility.hpp │ ├── pdb_reader.hpp │ ├── score.hpp │ ├── observe.hpp │ ├── cell_list.hpp │ ├── scanning_simulator.hpp │ └── annealing_simulator.hpp ├── example └── actin │ ├── gen_image.toml │ ├── scan.toml │ └── README.md ├── LICENSE ├── .travis.yml ├── CMakeLists.txt └── README.md /extlib/.gitignore: -------------------------------------------------------------------------------- 1 | boost_1_67_0.tar.bz2 2 | boost_1_67_0/* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | build/* 3 | data/* 4 | extlib/boost_1_72_0/* 5 | extlib/*.tar.gz 6 | *csv 7 | *json 8 | *ppm 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extlib/pnm"] 2 | path = extlib/pnm 3 | url = https://github.com/ToruNiina/pnm.git 4 | [submodule "extlib/mave"] 5 | path = extlib/mave 6 | url = https://github.com/ToruNiina/mave.git 7 | [submodule "extlib/toml11"] 8 | path = extlib/toml11 9 | url = https://github.com/ToruNiina/toml11.git 10 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TEST_NAMES 2 | test_collision 3 | test_read_as_angstrom 4 | test_aabb 5 | ) 6 | 7 | foreach(TEST_NAME ${TEST_NAMES}) 8 | add_executable(${TEST_NAME} ${TEST_NAME}.cpp) 9 | set_target_properties(${TEST_NAME} PROPERTIES 10 | COMPILE_FLAGS "-std=c++14 -O2") 11 | add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME} 12 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/test") 13 | endforeach(TEST_NAME) 14 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(afmize afmize.cpp) 2 | set_target_properties(afmize PROPERTIES 3 | COMPILE_FLAGS "-std=c++14 -O2 -DMAVE_NO_SIMD" 4 | RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin") 5 | 6 | add_executable(simulator simulator.cpp) 7 | set_target_properties(simulator PROPERTIES 8 | COMPILE_FLAGS "-std=c++14 -O2 -DMAVE_NO_SIMD" 9 | RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin") 10 | 11 | install(TARGETS afmize RUNTIME DESTINATION "bin") 12 | -------------------------------------------------------------------------------- /include/afmize/read_number.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_READ_NUMBER_HPP 2 | #define AFMIZE_READ_NUMBER_HPP 3 | #include 4 | 5 | namespace afmize 6 | { 7 | 8 | template 9 | T read_number(const std::string&); 10 | 11 | template<> 12 | inline double read_number(const std::string& str) 13 | { 14 | return std::stod(str); 15 | } 16 | 17 | template<> 18 | float read_number(const std::string& str) 19 | { 20 | return std::stof(str); 21 | } 22 | 23 | } // afmize 24 | #endif// AFMIZE_READ_NUMBER_HPP 25 | -------------------------------------------------------------------------------- /example/actin/gen_image.toml: -------------------------------------------------------------------------------- 1 | file.input = "actin_filament_reference.pdb" 2 | file.output.basename = "actin_filament_2nmpx" 3 | file.output.formats = ["tsv", "svg"] 4 | probe.size = {radius = "2.0nm", angle = 10.0} 5 | resolution.x = "2.0nm" 6 | resolution.y = "2.0nm" 7 | resolution.z = "0.64angstrom" 8 | range.x = ["0.0nm", "80.0nm"] 9 | range.y = ["0.0nm", "80.0nm"] 10 | scale_bar.length = "5.0nm" 11 | stage.align = true 12 | stage.position = 0.0 13 | noise = "0.3nm" 14 | -------------------------------------------------------------------------------- /include/afmize/noise.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_NOISE_HPP 2 | #define AFMIZE_NOISE_HPP 3 | #include "image.hpp" 4 | #include 5 | 6 | namespace afmize 7 | { 8 | 9 | template 10 | void apply_noise(image& img, RNG& rng, const Real sigma = 3.0 /*angstrom*/, 11 | const Real stage_position = 0.0) 12 | { 13 | std::normal_distribution nrm(0.0, sigma); 14 | for(auto& pxl : img) 15 | { 16 | pxl = pxl + nrm(rng); 17 | } 18 | return ; 19 | } 20 | 21 | } // afmize 22 | #endif// AFMIZE_NOISE_HPP 23 | -------------------------------------------------------------------------------- /include/afmize/simulator_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_SIMULATOR_BASE_HPP 2 | #define AFMIZE_SIMULATOR_BASE_HPP 3 | #include "system.hpp" 4 | #include "image.hpp" 5 | 6 | namespace afmize 7 | { 8 | 9 | template 10 | struct SimulatorBase 11 | { 12 | virtual ~SimulatorBase() = default; 13 | virtual void run() = 0; 14 | virtual bool run(const std::size_t) = 0; 15 | virtual bool step() = 0; 16 | virtual std::size_t total_step() const noexcept = 0; 17 | virtual std::size_t current_step() const noexcept = 0; 18 | 19 | virtual system const& current_state() const noexcept = 0; 20 | virtual system& current_state() noexcept = 0; 21 | virtual image const& current_image() const noexcept = 0; 22 | }; 23 | 24 | } // afmize 25 | #endif// AFMIZE_SIMULATOR_HPP 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Toru Niina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/actin/scan.toml: -------------------------------------------------------------------------------- 1 | # reference image info 2 | [image] 3 | pixels.x = 40 4 | pixels.y = 40 5 | reference = "actin_filament_2nmpx.tsv" 6 | lower.x = "0.0nm" # default 7 | lower.y = "0.0nm" # default 8 | 9 | # pseudo AFM image generation procedure 10 | [stage] 11 | pixels.x = 40 # should be the same as the reference image ... 12 | pixels.y = 40 13 | resolution.x = "2.0nm" # ditto 14 | resolution.y = "2.0nm" 15 | # resolution.z = "0.64angstrom" # if this is set, heights in the pseudo AFM image are discritized. 16 | 17 | # probe shape to be used while pseudo-AFM image generation 18 | [probe] 19 | radius = "2.0nm" 20 | angle = 20.0 21 | 22 | # input structure 23 | [initial] 24 | input = "6BNO_A15.pdb" 25 | 26 | # score function 27 | [score] 28 | method = "CosineSimilarity" 29 | k = 1.0 30 | mask = "none" 31 | use_zero_pixel_in_model = true 32 | 33 | [simulator] 34 | output = "actin_filament_fitting_2.0nm_20.0deg_x36_CosineSimilarity_2nmpx_A15" 35 | method = "Scanning" 36 | num_division = 36 # delta theta = 360 / 36 = 10 degree 37 | save = 100 # top 100 structures will be written 38 | dz = "0.64angstrom" # z-direction search 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: cpp 3 | 4 | matrix: 5 | include: 6 | - os: linux 7 | language: cpp 8 | compiler: gcc 9 | env: COMPILER="g++-7" CXX_STANDARD=14 10 | addons: 11 | apt: 12 | sources: 13 | - ubuntu-toolchain-r-test 14 | - sourceline: 'ppa:mhier/libboost-latest' 15 | packages: 16 | - g++-7 17 | - boost1.70 18 | - os: linux 19 | language: cpp 20 | compiler: clang 21 | env: COMPILER="clang++-6.0" CXX_STANDARD=14 22 | addons: 23 | apt: 24 | sources: 25 | - ubuntu-toolchain-r-test 26 | - sourceline: 'ppa:mhier/libboost-latest' 27 | - llvm-toolchain-trusty-6.0 28 | packages: 29 | - clang-6.0 30 | - boost1.70 31 | - os: osx 32 | language: cpp 33 | compiler: clang 34 | env: COMPILER="clang++" CXX_STANDARD=14 35 | 36 | script: 37 | - | 38 | if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 39 | mkdir -p cmake 40 | travis_retry wget "https://cmake.org/files/v3.11/cmake-3.11.2-Linux-x86_64.tar.gz" 41 | tar xf cmake-3.11.2-Linux-x86_64.tar.gz -C cmake --strip-components=1 42 | export PATH=${TRAVIS_BUILD_DIR}/cmake/bin:${PATH} 43 | fi 44 | - cmake --version 45 | - mkdir build 46 | - cd build 47 | - cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_CXX_STANDARD=$CXX_STANDARD -DFIND_BOOST=ON .. 48 | - make 49 | - ctest --output-on-failure 50 | -------------------------------------------------------------------------------- /include/afmize/parameter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_PARAMETER_HPP 2 | #define AFMIZE_PARAMETER_HPP 3 | #include 4 | #include 5 | 6 | namespace afmize 7 | { 8 | 9 | template 10 | struct parameter 11 | { 12 | static std::map radius_atom; 13 | static std::map> radius_residue; 14 | }; 15 | 16 | // Van der Waals radii. unit is Angstrom. overwritable from config file. 17 | // references: 18 | // A. Bondi (1964). "van der Waals Volumes and Radii". 19 | // J. Phys. Chem. 68: 441. doi:10.1021/j100785a001. 20 | // M. Mantina; A.C. Chamberlin; R. Valero; C.J. Cramer; D.G. Truhlar (2009). 21 | // "Consistent van der Waals Radii for the Whole Main Group". 22 | // J. Phys. Chem. A. 113 (19): 5806–12. doi:10.1021/jp8111556. 23 | template 24 | std::map parameter::radius_atom = { 25 | { "H", 1.20}, 26 | {"HE", 1.40}, 27 | { "B", 1.92}, 28 | { "C", 1.70}, 29 | { "N", 1.55}, 30 | { "O", 1.52}, 31 | { "F", 1.47}, 32 | {"NE", 1.54}, 33 | {"NA", 2.27}, 34 | {"MG", 1.73}, 35 | {"AL", 1.84}, 36 | {"SI", 2.10}, 37 | { "P", 1.80}, 38 | { "S", 1.80}, 39 | {"CL", 1.75}, 40 | {"AR", 1.88}, 41 | { "K", 2.75}, 42 | {"CA", 2.31}, 43 | }; 44 | 45 | template 46 | std::map> 47 | parameter::radius_residue; 48 | 49 | } // afmize 50 | #endif// AFMIZE_PARAMETER_HPP 51 | -------------------------------------------------------------------------------- /include/afmize/system.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_SYSTEM_HPP 2 | #define AFMIZE_SYSTEM_HPP 3 | #include "cell_list.hpp" 4 | #include "shapes.hpp" 5 | #include "stage.hpp" 6 | #include 7 | 8 | namespace afmize 9 | { 10 | 11 | template 12 | struct system 13 | { 14 | explicit system(std::pair, std::vector>> mol, 15 | stage stg) 16 | : max_radius(0), particles(std::move(mol.second)), names(std::move(mol.first)), 17 | stage_info(std::move(stg)) 18 | { 19 | max_radius = particles.front().radius; 20 | this->bounding_box = make_aabb(particles.front()); 21 | for(std::size_t i=1; ibounding_box = 25 | merge_aabb(this->bounding_box, make_aabb(particles[i])); 26 | } 27 | // since the molecule is rigid-body, this radius never change. 28 | bounding_radius = make_bounding_sphere_centered_at_geometric_center( 29 | particles).radius; 30 | } 31 | ~system() = default; 32 | 33 | Real max_radius; 34 | Real bounding_radius; 35 | aabb bounding_box; 36 | std::vector> particles; 37 | std::vector names; 38 | stage stage_info; 39 | cell_list cells; 40 | }; 41 | 42 | } // afmize 43 | #endif // AFMIZE_SYSTEM_HPP 44 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | enable_testing() 3 | project(afmize) 4 | 5 | execute_process(COMMAND git submodule update --init --recursive 6 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) 7 | include_directories(${PROJECT_SOURCE_DIR}/extlib) 8 | include_directories(${PROJECT_SOURCE_DIR}/include) 9 | 10 | 11 | find_package(Boost 1.69.0) 12 | if(Boost_FOUND) 13 | include_directories(${Boost_INCLUDE_DIRS}) 14 | else() 15 | if(EXISTS "${PROJECT_SOURCE_DIR}/extlib/boost_1_72_0/boost/version.hpp") 16 | message(STATUS "boost 1.72.0 exists.") 17 | include_directories(${PROJECT_SOURCE_DIR}/extlib/boost_1_72_0) 18 | else() 19 | message(STATUS "Boost library not found. Donwloading...") 20 | 21 | file(DOWNLOAD https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.gz 22 | ${PROJECT_SOURCE_DIR}/extlib/boost_1_77_0.tar.gz 23 | EXPECTED_HASH SHA256=5347464af5b14ac54bb945dc68f1dd7c56f0dad7262816b956138fc53bcc0131 24 | STATUS AFMIZE_DOWNLOAD_BOOST_STATUS) 25 | 26 | # check status 27 | list(GET AFMIZE_DOWNLOAD_BOOST_STATUS 0 AFMIZE_DOWNLOAD_BOOST_RESULT) 28 | if(NOT ${AFMIZE_DOWNLOAD_BOOST_RESULT} EQUAL 0) 29 | list(GET AFMIZE_DOWNLOAD_BOOST_STATUS 1 AFMIZE_DOWNLOAD_BOOST_ERROR) 30 | message(FATAL_ERROR "failed to download Boost library." 31 | ${AFMIZE_DOWNLOAD_BOOST_ERROR}) 32 | endif() 33 | 34 | message(STATUS "downloading completed. Unpacking...") 35 | 36 | execute_process(COMMAND tar xf boost_1_77_0.tar.gz 37 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/extlib" 38 | OUTPUT_QUIET ERROR_QUIET) 39 | 40 | include_directories(${PROJECT_SOURCE_DIR}/extlib/boost_1_77_0) 41 | message(STATUS "done.") 42 | endif() 43 | endif() 44 | 45 | add_subdirectory(src) 46 | add_subdirectory(test) 47 | -------------------------------------------------------------------------------- /include/afmize/colormap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_COLOR_MAP_HPP 2 | #define AFMIZE_COLOR_MAP_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace afmize 9 | { 10 | 11 | // this implementation is made based on a image found in an issue #6032 of 12 | // github:matplotlib/matplotlib repository which explains how the color changes 13 | // when value changes. 14 | // URL: https://github.com/matplotlib/matplotlib/issues/6032 15 | template 16 | Pixel color_afmhot(const Real value, const Real min, const Real max) 17 | { 18 | // assuming Pixel(R, G, B) and R, G, B are 8-bit integer 19 | assert(min <= value && value <= max); 20 | const Real sqrt2 = std::sqrt(Real(2.0)); 21 | const Real sqrt2_plus_1 = sqrt2 + Real(1.0); 22 | 23 | const Real v = ((value - min) / (max - min)) * sqrt2_plus_1; 24 | 25 | if(v < Real(0.5)) 26 | { 27 | const std::uint8_t R = std::floor(v * 256); 28 | return Pixel(R, 0, 0); 29 | } 30 | else if(v < Real(0.5) * sqrt2_plus_1) 31 | { 32 | const Real u = (v - Real(0.5)) / sqrt2; 33 | if (u < Real(0.0)) {return Pixel(127, 0, 0);} 34 | else if(Real(0.5) <= u) {return Pixel(255, 127, 0);} 35 | 36 | const std::uint8_t R = std::floor((Real(0.5) + u) * 256); 37 | const std::uint8_t G = std::floor(u * 256); 38 | return Pixel(R, G, 0); 39 | } 40 | else if(v < Real(0.5) + sqrt2) 41 | { 42 | const Real u = (v - Real(0.5) * sqrt2_plus_1) / sqrt2; 43 | if (u < Real(0.0)) {return Pixel(255, 127, 0);} 44 | else if(Real(0.5) <= u) {return Pixel(255, 255, 127);} 45 | const std::uint8_t G = std::floor((Real(0.5) + u) * 256); 46 | const std::uint8_t B = std::floor(u * 256); 47 | return Pixel(255, G, B); 48 | } 49 | else 50 | { 51 | const Real u = v - Real(0.5) - sqrt2; 52 | if (u < Real(0.0)) {return Pixel(255, 255, 127);} 53 | else if(Real(0.5) < u) {return Pixel(255, 255, 255);} 54 | const std::uint8_t B = std::floor((Real(0.5) + u) * 256); 55 | return Pixel(255, 255, B); 56 | } 57 | } 58 | 59 | 60 | } // afmize 61 | #endif// AFMIZE_COLOR_MAP_HPP 62 | -------------------------------------------------------------------------------- /include/afmize/stage.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_STAGE_HPP 2 | #define AFMIZE_STAGE_HPP 3 | #include 4 | #include "image.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace afmize 10 | { 11 | 12 | // 13 | // stage information such as resolution and range 14 | // 15 | template 16 | struct stage 17 | { 18 | using container = std::vector; 19 | using iterator = typename container::iterator; 20 | using const_iterator = typename container::const_iterator; 21 | 22 | stage(Real x_res, Real y_res, Real z_res, 23 | std::pair x_range, std::pair y_range) 24 | : x_reso_(x_res), y_reso_(y_res), z_reso_(z_res), 25 | x_rreso_(1.0 / x_res), y_rreso_(1.0 / y_res), 26 | x_lower_(x_range.first), x_upper_(x_range.second), 27 | y_lower_(y_range.first), y_upper_(y_range.second), 28 | x_pixels_(std::floor((x_range.second - x_range.first) / x_reso_)), 29 | y_pixels_(std::floor((y_range.second - y_range.first) / y_reso_)) 30 | {} 31 | 32 | // for z, returns current height (at first it's zero) 33 | mave::vector position_at(std::size_t x, std::size_t y) const noexcept 34 | { 35 | return mave::vector{x_lower_ + x_reso_ * Real(x + 0.5), 36 | y_lower_ + y_reso_ * Real(y + 0.5), 0.0}; 37 | } 38 | 39 | std::pair 40 | pixel_at(const mave::vector& pos) const noexcept 41 | { 42 | return std::make_pair( 43 | static_cast(std::floor((pos[0] - x_lower_) * x_rreso_)), 44 | static_cast(std::floor((pos[1] - y_lower_) * y_rreso_))); 45 | } 46 | 47 | image create_image() const 48 | { 49 | return image(x_pixels_, y_pixels_); 50 | } 51 | 52 | Real x_resolution() const noexcept {return this->x_reso_;} 53 | Real y_resolution() const noexcept {return this->y_reso_;} 54 | Real z_resolution() const noexcept {return this->z_reso_;} 55 | 56 | std::pair x_range() const noexcept {return std::make_pair(x_lower_, x_upper_);} 57 | std::pair y_range() const noexcept {return std::make_pair(y_lower_, y_upper_);} 58 | 59 | std::size_t x_pixel() const noexcept {return x_pixels_;} 60 | std::size_t y_pixel() const noexcept {return y_pixels_;} 61 | 62 | private: 63 | 64 | Real x_reso_, y_reso_, z_reso_; 65 | Real x_rreso_, y_rreso_; 66 | Real x_lower_, x_upper_, y_lower_, y_upper_; 67 | std::size_t x_pixels_, y_pixels_; 68 | }; 69 | 70 | } // afmize 71 | #endif// AFMIZE_STAGE_HPP 72 | -------------------------------------------------------------------------------- /include/afmize/input_utility.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_INPUT_UTILITY_HPP 2 | #define AFMIZE_INPUT_UTILITY_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace afmize 9 | { 10 | 11 | template 12 | std::enable_if_t::value, T> 13 | read_as_angstrom(const toml::value& v) 14 | { 15 | using namespace std::literals::string_literals; 16 | 17 | const std::vector hints{ // for error message. 18 | "\"1.0nm\" : 1.0 nano meter.", 19 | "\"1.0\xC3\x85\", \"1.0\xE2\x84\xAB\", or \"1.0angstrom\": 1.0 angstrom.", 20 | "\"1.0pm\" : 1.0 pico meter.", 21 | "1.0 (floating) : 1.0 angstrom.", 22 | "1 (integer) : 1.0 angstrom." 23 | }; 24 | 25 | if(v.is_floating()) {return v.as_floating();} 26 | if(v.is_integer()) {return v.as_integer();} 27 | if(!v.is_string()) 28 | { 29 | throw std::runtime_error(toml::format_error("input has invalid type " + 30 | toml::stringize(v.type()) + ", none of string, floating, or integer", 31 | v, "here, only the following inputs are allowed", hints)); 32 | } 33 | 34 | const std::string str = v.as_string(); 35 | toml::detail::location loc("internal region", str); 36 | const auto result = toml::detail::parse_floating(loc); 37 | if(!result) 38 | { 39 | throw std::runtime_error(toml::format_error("[error] `"s + str + 40 | "` is not a valid floating-unit pair."s, 41 | v, "here, only the following inputs are allowed", hints)); 42 | } 43 | const auto val = result.unwrap().first; 44 | const std::string unit(loc.iter(), loc.end()); 45 | 46 | if(unit == "pm") 47 | { 48 | return static_cast(val * 0.01); 49 | } 50 | if(unit == "angst" || unit == "angstrom" || unit == "\xC3\x85" || unit == "\xE2\x84\xAB") 51 | { 52 | // left is U+00C5 "latin capical letter A with ring above". 53 | // right is U+212B "angstrom", 54 | return static_cast(val); 55 | } 56 | else if(unit == "nm") 57 | { 58 | return static_cast(val * 10.0); 59 | } 60 | else if(unit == "um" || unit == u8"μm") 61 | { 62 | return static_cast(val * 10'000.0); 63 | } 64 | else if(unit == "mm") 65 | { 66 | return static_cast(val * 10'000'000.0); 67 | } 68 | 69 | throw std::runtime_error(toml::format_error("[error] unknown length unit `"s 70 | + unit + "` appeared."s, 71 | v, "here, only the following inputs are allowed", hints)); 72 | 73 | } 74 | 75 | } // afmize 76 | #endif// AFMIZE_UTILITY_HPP 77 | -------------------------------------------------------------------------------- /include/afmize/reader_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_READER_BASE_HPP 2 | #define AFMIZE_READER_BASE_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace afmize 11 | { 12 | template 13 | class reader_base 14 | { 15 | public: 16 | // pairof {name, shape} 17 | using snapshot_type = std::pair, std::vector>>; 18 | using trajectory_type = std::vector; 19 | 20 | struct no_more_model{}; 21 | 22 | reader_base(std::string nm): filename(std::move(nm)){} 23 | virtual ~reader_base() = default; 24 | 25 | virtual bool is_eof() = 0; 26 | 27 | virtual trajectory_type read_trajectory() = 0; 28 | virtual snapshot_type read_snapshot() = 0; 29 | 30 | virtual std::size_t size() const noexcept = 0; 31 | 32 | protected: 33 | 34 | void highlight_columns(std::ostream& os, const std::string& line, 35 | const std::size_t padding, const std::size_t width) 36 | { 37 | os << "> " << line << '\n'; 38 | os << " "; 39 | for(std::size_t i=0; i 3 | #include 4 | #include 5 | 6 | BOOST_AUTO_TEST_CASE(aabb_sphere) 7 | { 8 | using sphere = afmize::sphere; 9 | using aabb = afmize::aabb; 10 | using point = mave::vector; 11 | 12 | std::mt19937 mt(123456789); 13 | std::uniform_real_distribution uni(-100.0, 100.0); 14 | 15 | for(std::size_t i=0; i<1000; ++i) 16 | { 17 | const sphere s{uni(mt), point{uni(mt), uni(mt), uni(mt)}}; 18 | const aabb box = afmize::make_aabb(s); 19 | 20 | BOOST_TEST(box.lower[0] == s.center[0] - s.radius, 21 | boost::test_tools::tolerance(1e-6)); 22 | BOOST_TEST(box.lower[1] == s.center[1] - s.radius, 23 | boost::test_tools::tolerance(1e-6)); 24 | BOOST_TEST(box.lower[2] == s.center[2] - s.radius, 25 | boost::test_tools::tolerance(1e-6)); 26 | 27 | BOOST_TEST(box.upper[0] == s.center[0] + s.radius, 28 | boost::test_tools::tolerance(1e-6)); 29 | BOOST_TEST(box.upper[1] == s.center[1] + s.radius, 30 | boost::test_tools::tolerance(1e-6)); 31 | BOOST_TEST(box.upper[2] == s.center[2] + s.radius, 32 | boost::test_tools::tolerance(1e-6)); 33 | } 34 | } 35 | 36 | BOOST_AUTO_TEST_CASE(merge_aabb) 37 | { 38 | using sphere = afmize::sphere; 39 | using aabb = afmize::aabb; 40 | using point = mave::vector; 41 | 42 | std::mt19937 mt(123456789); 43 | std::uniform_real_distribution uni(-100.0, 100.0); 44 | 45 | for(std::size_t i=0; i<1000; ++i) 46 | { 47 | const aabb box1{point{uni(mt), uni(mt), uni(mt)}, 48 | point{uni(mt), uni(mt), uni(mt)}}; 49 | const aabb box2{point{uni(mt), uni(mt), uni(mt)}, 50 | point{uni(mt), uni(mt), uni(mt)}}; 51 | 52 | const aabb box3 = afmize::merge_aabb(box1, box2); 53 | 54 | BOOST_TEST(box3.lower[0] == std::min(box1.lower[0], box2.lower[0]), 55 | boost::test_tools::tolerance(1e-6)); 56 | BOOST_TEST(box3.lower[1] == std::min(box1.lower[1], box2.lower[1]), 57 | boost::test_tools::tolerance(1e-6)); 58 | BOOST_TEST(box3.lower[2] == std::min(box1.lower[2], box2.lower[2]), 59 | boost::test_tools::tolerance(1e-6)); 60 | 61 | BOOST_TEST(box3.upper[0] == std::max(box1.upper[0], box2.upper[0]), 62 | boost::test_tools::tolerance(1e-6)); 63 | BOOST_TEST(box3.upper[1] == std::max(box1.upper[1], box2.upper[1]), 64 | boost::test_tools::tolerance(1e-6)); 65 | BOOST_TEST(box3.upper[2] == std::max(box1.upper[2], box2.upper[2]), 66 | boost::test_tools::tolerance(1e-6)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /include/afmize/image.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_IMAGE_HPP 2 | #define AFMIZE_IMAGE_HPP 3 | #include 4 | #include 5 | 6 | namespace afmize 7 | { 8 | 9 | template 10 | struct image 11 | { 12 | using container = std::vector; 13 | using iterator = typename container::iterator; 14 | using const_iterator = typename container::const_iterator; 15 | 16 | image(): x_pixels_(0), y_pixels_(0) {} 17 | image(const std::size_t x, const std::size_t y) 18 | : x_pixels_(x), y_pixels_(y), heights(x_pixels_ * y_pixels_, 0) 19 | {} 20 | image(const image&) = default; 21 | image(image&&) = default; 22 | image& operator=(const image&) = default; 23 | image& operator=(image&&) = default; 24 | ~image() = default; 25 | 26 | Real& operator[](std::size_t i) noexcept {return heights[i];} 27 | Real operator[](std::size_t i) const noexcept {return heights[i];} 28 | Real& at(std::size_t i) {return heights.at(i);} 29 | Real at(std::size_t i) const {return heights.at(i);} 30 | 31 | Real& operator()(std::size_t x, std::size_t y) noexcept 32 | { 33 | return heights[y * x_pixels_ + x]; 34 | } 35 | Real operator()(std::size_t x, std::size_t y) const noexcept 36 | { 37 | return heights[y * x_pixels_ + x]; 38 | } 39 | Real& operator()(std::pair xy) 40 | { 41 | return this->operator()(xy.first, xy.second); 42 | } 43 | Real operator()(std::pair xy) const 44 | { 45 | return this->operator()(xy.first, xy.second); 46 | } 47 | 48 | Real& at(std::size_t x, std::size_t y) 49 | { 50 | return heights.at(y * x_pixels_ + x); 51 | } 52 | Real at(std::size_t x, std::size_t y) const 53 | { 54 | return heights.at(y * x_pixels_ + x); 55 | } 56 | 57 | Real& at(std::pair xy) 58 | { 59 | return this->at(xy.first, xy.second); 60 | } 61 | Real at(std::pair xy) const 62 | { 63 | return this->at(xy.first, xy.second); 64 | } 65 | 66 | iterator begin() noexcept {return heights.begin();} 67 | iterator end() noexcept {return heights.end();} 68 | const_iterator begin() const noexcept {return heights.begin();} 69 | const_iterator end() const noexcept {return heights.end();} 70 | const_iterator cbegin() const noexcept {return heights.cbegin();} 71 | const_iterator cend() const noexcept {return heights.cend();} 72 | 73 | void resize(const std::size_t x, const std::size_t y) 74 | { 75 | x_pixels_ = x; 76 | y_pixels_ = y; 77 | heights.resize(x * y, 0.0); 78 | return; 79 | } 80 | 81 | std::size_t x_pixel() const noexcept {return x_pixels_;} 82 | std::size_t y_pixel() const noexcept {return y_pixels_;} 83 | 84 | std::vector const& get_container() const noexcept {return heights;} 85 | 86 | private: 87 | 88 | std::size_t x_pixels_, y_pixels_; 89 | container heights; 90 | }; 91 | 92 | 93 | } // afmize 94 | #endif// AFMIZE_IMAGE_HPP 95 | -------------------------------------------------------------------------------- /include/afmize/shapes.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_SHAPES_HPP 2 | #define AFMIZE_SHAPES_HPP 3 | #include 4 | #include 5 | 6 | namespace afmize 7 | { 8 | 9 | template 10 | struct sphere 11 | { 12 | Real radius; 13 | mave::vector center; 14 | }; 15 | 16 | // angle 17 | // \ | / 18 | // \ | / 19 | // \_____|/ 20 | // '--' 21 | // radius 22 | // 23 | template 24 | struct circular_frustum 25 | { 26 | Real angle; 27 | Real radius; 28 | mave::vector apex; 29 | }; 30 | 31 | // default probe shape is sphere + frustum. 32 | // at the apex of frustum, a sphere is attached and their radii are same. 33 | template 34 | struct default_probe 35 | { 36 | Real angle; // radian 37 | Real radius; 38 | mave::vector apex; 39 | }; 40 | 41 | // ---------------------------------------------------------------------------- 42 | // bounding box 43 | 44 | template 45 | struct aabb 46 | { 47 | mave::vector upper; 48 | mave::vector lower; 49 | }; 50 | 51 | template 52 | aabb make_aabb(const sphere& sph) noexcept 53 | { 54 | return aabb{ 55 | sph.center + mave::vector{sph.radius, sph.radius, sph.radius}, 56 | sph.center - mave::vector{sph.radius, sph.radius, sph.radius} 57 | }; 58 | } 59 | 60 | template 61 | aabb merge_aabb(const aabb& lhs, const aabb& rhs) noexcept 62 | { 63 | return aabb{ 64 | mave::max(lhs.upper, rhs.upper), mave::min(lhs.lower, rhs.lower) 65 | }; 66 | } 67 | 68 | template 69 | aabb make_bounding_box( 70 | const std::vector, Alloc>& particles) 71 | { 72 | if(particles.empty()) 73 | { 74 | return aabb{mave::vector{0.0, 0.0, 0.0}, 75 | mave::vector{0.0, 0.0, 0.0}}; 76 | } 77 | 78 | aabb bb = make_aabb(particles.front()); 79 | for(const auto& p : particles) 80 | { 81 | bb = merge_aabb(bb, make_aabb(p)); 82 | } 83 | return bb; 84 | } 85 | 86 | // ---------------------------------------------------------------------------- 87 | // "bounding" sphere. 88 | // XXX Note that the center is fixed at the geometric center. It might decrease 89 | // the runtime efficiency, but makes the calculation simpler. It is easy to 90 | // calculate the geometric center from a configuration. 91 | 92 | template 93 | sphere make_bounding_sphere_centered_at_geometric_center( 94 | const std::vector, Alloc>& particles) 95 | { 96 | sphere sph; 97 | sph.center = mave::vector(0,0,0); 98 | for(const auto& p : particles) 99 | { 100 | sph.center += p.center; 101 | } 102 | sph.center /= static_cast(particles.size()); 103 | 104 | Real max_distance = 0.0; 105 | for(const auto& p : particles) 106 | { 107 | const auto dist = mave::length(p.center - sph.center) + p.radius; 108 | max_distance = std::max(max_distance, dist); 109 | } 110 | sph.radius = max_distance; 111 | return sph; 112 | } 113 | 114 | } // afmize 115 | #endif // AFMIZE_SHAPES_HPP 116 | -------------------------------------------------------------------------------- /example/actin/README.md: -------------------------------------------------------------------------------- 1 | # Actin-filament fitting 2 | 3 | This example file includes the following. 4 | 5 | - input files to generate a pseudo-AFM image from a pdb file 6 | - input files to fit molecular structure into a pseudo-AFM image 7 | 8 | ## Building 9 | 10 | To build the software, run the following commands. 11 | 12 | ```console 13 | $ git clone https://github.com/ToruNiina/afmize.git 14 | $ cd afmize 15 | $ git submodule update --init 16 | $ mkdir build 17 | $ cd build 18 | $ cmake .. 19 | $ make 20 | ``` 21 | 22 | Please make sure that you have `afmize` and `simulator` in the `afmize/bin/` directory. 23 | 24 | ## pseudo-AFM image generation procedure 25 | 26 | Here, you will use `gen_image.toml` and `actin_filament_reference.pdb`. 27 | 28 | The `gen_image.toml` file has the following values. 29 | 30 | ```toml 31 | file.input = "actin_filament_reference.pdb" # structure file used to generate an AFM image 32 | file.output.basename = "actin_filament_2nmpx" # output file name 33 | file.output.formats = ["tsv", "svg"] # output file format 34 | probe.size = {radius = "2.0nm", angle = 10.0} # probe shape used to generate an AFM image 35 | resolution.x = "2.0nm" # X resolution (width of a pixel in X direction) 36 | resolution.y = "2.0nm" # Y resolution (width of a pixel in Y direction) 37 | resolution.z = "0.64angstrom" # Z resolution (height discritization) 38 | range.x = ["0.0nm", "80.0nm"] # region to be used to generate an AFM image 39 | range.y = ["0.0nm", "80.0nm"] # (this value corresponds to the coordinate in the pdb file) 40 | scale_bar.length = "5.0nm" # scale bar written in the image file. 41 | stage.align = true # a flag to align the input structure on to Z == 0. 42 | stage.position = 0.0 # stage position (if you don't have any special reason, set this to 0) 43 | noise = "0.3nm" # noise intensity. 44 | ``` 45 | 46 | When you pass this file to `bin/afmize`, you will get `actin_filament_2nmpx.tsv` and `actin_filament_2nmpx.svg`. 47 | It would take some seconds. 48 | 49 | ```console 50 | $ pwd 51 | /path/to/afmize/sample/actin 52 | $ ls 53 | 6BNO_A15.pdb actin_filament_reference.pdb gen_image.toml README.md scan.toml 54 | $ ../../bin/afmize gen_image.toml 55 | ``` 56 | 57 | `.tsv` file contains the height at each pixel (from (x0, y0), (x1, y0), ..., (xN, y0), (x0, y1), ... (xN, yN)) in angstrom. 58 | `.svg` file represents the AFM image. 59 | The `.svg` file is for quick look, and using some visualization software is recommended to visualize the result. 60 | 61 | ## scanning-based structure fitting procedure 62 | 63 | Here, you will use `scan.toml`, `6BNO_A15.pdb`, and `actin_filament_2nmpx.tsv` that is generated in the previous step. 64 | You can find the description about input parameters as comments in the `scan.toml`. 65 | 66 | When you pass `scan.toml` to `bin/simulator`, you will get `actin_filament_fitting_2.0nm_20.0deg_x36_CosineSimilarity_2nmpx_A15.*` files. 67 | It will take some tens of minutes. It is because we are using large pdb file (~80k atoms) and using normal orientation resolution (Δθ = 10°). 68 | If you use small pdb file or a large Δθ, the time required for scanning decreases. 69 | 70 | ```console 71 | $ pwd 72 | /path/to/afmize/sample/actin 73 | $ ../../bin/simulator scan.toml 74 | ``` 75 | -------------------------------------------------------------------------------- /test/test_collision.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE "test_collision" 2 | #include 3 | #include 4 | 5 | BOOST_AUTO_TEST_CASE(collision_sphere_sphere) 6 | { 7 | using sphere = afmize::sphere; 8 | using point = mave::vector; 9 | 10 | { 11 | sphere probe {1.0, point{0.0, 0.0, 10.0}}; 12 | sphere target{1.0, point{0.0, 0.0, 0.0}}; 13 | 14 | const auto t = afmize::collision_z(probe, target); 15 | BOOST_TEST(!std::isnan(t)); 16 | BOOST_TEST(t == -8.0, boost::test_tools::tolerance(1e-6)); 17 | } 18 | { 19 | sphere probe {1.0, point{0.0, 0.0, 10.0}}; 20 | sphere target{1.0, point{0.0, 0.0, 20.0}}; 21 | 22 | const auto t = afmize::collision_z(probe, target); 23 | BOOST_TEST(!std::isnan(t)); 24 | BOOST_TEST(t == 8.0, boost::test_tools::tolerance(1e-6)); 25 | } 26 | 27 | { 28 | sphere probe {1.0, point{0.0, 0.0, 10.0}}; 29 | sphere target{1.0, point{1.0, 1.0, 0.0}}; 30 | const double expect = std::sqrt(2.0) - 10.0; 31 | 32 | const auto t = afmize::collision_z(probe, target); 33 | 34 | BOOST_TEST(!std::isnan(t)); 35 | BOOST_TEST(t == expect, boost::test_tools::tolerance(1e-6)); 36 | } 37 | { 38 | sphere probe {1.0, point{0.0, 0.0, 10.0}}; 39 | sphere target{1.0, point{1.0, 1.0, 20.0}}; 40 | const double expect = 10.0 - std::sqrt(2.0); 41 | 42 | const auto t = afmize::collision_z(probe, target); 43 | BOOST_TEST(!std::isnan(t)); 44 | BOOST_TEST(t == expect, boost::test_tools::tolerance(1e-6)); 45 | } 46 | 47 | { 48 | sphere probe {1.0, point{ 0.0, 0.0, 10.0}}; 49 | sphere target{1.0, point{10.0, 10.0, 0.0}}; 50 | 51 | const auto t = afmize::collision_z(probe, target); 52 | BOOST_TEST(std::isnan(t)); 53 | } 54 | } 55 | 56 | BOOST_AUTO_TEST_CASE(collision_frustum_sphere) 57 | { 58 | using sphere = afmize::sphere; 59 | using frustum = afmize::circular_frustum; 60 | using point = mave::vector; 61 | 62 | { 63 | frustum probe {0.0, 1.0, point{0.0, 0.0, 10.0}}; // just a cylinder 64 | sphere target{ 1.0, point{0.0, 0.0, 0.0}}; 65 | 66 | const auto t = afmize::collision_z(probe, target); 67 | BOOST_TEST(!std::isnan(t)); 68 | BOOST_TEST(t == -9.0, boost::test_tools::tolerance(1e-6)); 69 | } 70 | { 71 | frustum probe {0.0, 1.0, point{0.0, 0.0, 10.0}}; 72 | sphere target{ 1.0, point{0.0, 0.0, 20.0}}; 73 | 74 | const auto t = afmize::collision_z(probe, target); 75 | BOOST_TEST(!std::isnan(t)); 76 | BOOST_TEST(t == 11.0, boost::test_tools::tolerance(1e-6)); 77 | } 78 | 79 | { 80 | frustum probe {0.0, 1.0, point{0.0, 0.0, 10.0}}; 81 | sphere target{ 1.0, point{1.5, 0.0, 0.0}}; 82 | const double expect = std::sqrt(3.0) * 0.5 - 10.0; 83 | 84 | const auto t = afmize::collision_z(probe, target); 85 | 86 | BOOST_TEST(!std::isnan(t)); 87 | BOOST_TEST(t == expect, boost::test_tools::tolerance(1e-6)); 88 | } 89 | { 90 | frustum probe {0.0, 1.0, point{0.0, 0.0, 10.0}}; 91 | sphere target{ 1.0, point{0.0, 1.5, 0.0}}; 92 | const double expect = std::sqrt(3.0) * 0.5 - 10.0; 93 | 94 | const auto t = afmize::collision_z(probe, target); 95 | 96 | BOOST_TEST(!std::isnan(t)); 97 | BOOST_TEST(t == expect, boost::test_tools::tolerance(1e-6)); 98 | } 99 | 100 | { 101 | sphere probe {1.0, point{ 0.0, 0.0, 10.0}}; 102 | sphere target{1.0, point{10.0, 10.0, 0.0}}; 103 | 104 | const auto t = afmize::collision_z(probe, target); 105 | BOOST_TEST(std::isnan(t)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/test_read_as_angstrom.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE "test_read_as_angstrom" 2 | #include 3 | // don't include this first to override BOOST_MPL_LIMIT_LIST_SIZE 4 | #include 5 | 6 | BOOST_AUTO_TEST_CASE(read_as_angstrom_default) 7 | { 8 | { 9 | const toml::value t(10); 10 | const double v = afmize::read_as_angstrom(t); 11 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 12 | } 13 | { 14 | const toml::value t(10.0); 15 | const double v = afmize::read_as_angstrom(t); 16 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 17 | } 18 | } 19 | 20 | BOOST_AUTO_TEST_CASE(read_as_angstrom_pm) 21 | { 22 | { 23 | const toml::value t("100.0pm"); 24 | const double v = afmize::read_as_angstrom(t); 25 | BOOST_TEST(v == 1.0, boost::test_tools::tolerance(1e-6)); 26 | } 27 | { 28 | const toml::value t("1_000.000_000pm"); 29 | const double v = afmize::read_as_angstrom(t); 30 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 31 | } 32 | } 33 | 34 | BOOST_AUTO_TEST_CASE(read_as_angstrom_angstrom) 35 | { 36 | { 37 | const toml::value t("1.0angstrom"); 38 | const double v = afmize::read_as_angstrom(t); 39 | BOOST_TEST(v == 1.0, boost::test_tools::tolerance(1e-6)); 40 | } 41 | { 42 | const toml::value t("1_0.000_000angstrom"); 43 | const double v = afmize::read_as_angstrom(t); 44 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 45 | } 46 | 47 | { 48 | const toml::value t("1.0Å"); 49 | const double v = afmize::read_as_angstrom(t); 50 | BOOST_TEST(v == 1.0, boost::test_tools::tolerance(1e-6)); 51 | } 52 | { 53 | const toml::value t("1_0.000_000Å"); 54 | const double v = afmize::read_as_angstrom(t); 55 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 56 | } 57 | } 58 | 59 | BOOST_AUTO_TEST_CASE(read_as_angstrom_nm) 60 | { 61 | { 62 | const toml::value t("1.0nm"); 63 | const double v = afmize::read_as_angstrom(t); 64 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 65 | } 66 | { 67 | const toml::value t("1_0.000_000nm"); 68 | const double v = afmize::read_as_angstrom(t); 69 | BOOST_TEST(v == 100.0, boost::test_tools::tolerance(1e-6)); 70 | } 71 | } 72 | 73 | BOOST_AUTO_TEST_CASE(read_as_angstrom_um) 74 | { 75 | { 76 | const toml::value t("1e-3um"); 77 | const double v = afmize::read_as_angstrom(t); 78 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 79 | } 80 | { 81 | const toml::value t("0.000_1um"); 82 | const double v = afmize::read_as_angstrom(t); 83 | BOOST_TEST(v == 1.0, boost::test_tools::tolerance(1e-6)); 84 | } 85 | 86 | { 87 | const toml::value t("1e-3μm"); 88 | const double v = afmize::read_as_angstrom(t); 89 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 90 | } 91 | { 92 | const toml::value t("0.000_1μm"); 93 | const double v = afmize::read_as_angstrom(t); 94 | BOOST_TEST(v == 1.0, boost::test_tools::tolerance(1e-6)); 95 | } 96 | } 97 | 98 | BOOST_AUTO_TEST_CASE(read_as_angstrom_mm) 99 | { 100 | { 101 | const toml::value t("1e-6mm"); 102 | const double v = afmize::read_as_angstrom(t); 103 | BOOST_TEST(v == 10.0, boost::test_tools::tolerance(1e-6)); 104 | } 105 | { 106 | const toml::value t("0.000_000_1mm"); 107 | const double v = afmize::read_as_angstrom(t); 108 | BOOST_TEST(v == 1.0, boost::test_tools::tolerance(1e-6)); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /include/afmize/mask.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_MASK_HPP 2 | #define AFMIZE_MASK_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // mask for image (stage). return true if masked. 11 | namespace afmize 12 | { 13 | 14 | template 15 | struct mask_nothing 16 | { 17 | mask_nothing(const system& sys, 18 | const std::size_t, const std::size_t, 19 | const std::size_t, const std::size_t) 20 | : pixel_x_(sys.stage_info.x_pixel()), pixel_y_(sys.stage_info.y_pixel()) 21 | {} 22 | 23 | explicit mask_nothing(const image& img) 24 | : pixel_x_(img.x_pixel()), pixel_y_(img.y_pixel()) 25 | {} 26 | 27 | Real operator()(const image& img, 28 | const std::size_t x, const std::size_t y) const noexcept 29 | { 30 | return img(x, y); 31 | } 32 | 33 | std::size_t size() const noexcept {return pixel_x_ * pixel_y_;} 34 | 35 | std::size_t pixel_x() const noexcept {return pixel_x_;} 36 | std::size_t pixel_y() const noexcept {return pixel_y_;} 37 | 38 | std::size_t lower_bounding_x() const noexcept {return 0;} 39 | std::size_t lower_bounding_y() const noexcept {return 0;} 40 | std::size_t upper_bounding_x() const noexcept {return pixel_x_;} 41 | std::size_t upper_bounding_y() const noexcept {return pixel_y_;} 42 | 43 | private: 44 | 45 | std::size_t pixel_x_; 46 | std::size_t pixel_y_; 47 | }; 48 | 49 | template 50 | struct mask_by_rectangle 51 | { 52 | explicit mask_by_rectangle(const image& img) // nonzero 53 | { 54 | x_lower_ = std::numeric_limits::max(); 55 | x_upper_ = 0; 56 | y_lower_ = std::numeric_limits::max(); 57 | y_upper_ = 0; 58 | for(std::size_t j=0; j&, 77 | const std::size_t x_lower, const std::size_t x_size, 78 | const std::size_t y_lower, const std::size_t y_size) 79 | : pixel_x_(x_size), pixel_y_(y_size), 80 | x_lower_(x_lower), y_lower_(y_lower), 81 | x_upper_(x_lower + x_size), y_upper_(y_lower + y_size) 82 | {} 83 | 84 | std::size_t size() const noexcept {return pixel_x_ * pixel_y_;} 85 | 86 | Real operator()(const image& img, 87 | const std::size_t x, const std::size_t y) const 88 | { 89 | if(x_upper_ <= x + x_lower_) 90 | { 91 | throw std::out_of_range("afmize::mask: condition x + x_lower (" + 92 | std::to_string(x) + " + " + std::to_string(x_lower_) + ") <= x_upper (" + 93 | std::to_string(x_upper_) + ") is not satisfied."); 94 | } 95 | if(y_upper_ <= y + y_lower_) 96 | { 97 | throw std::out_of_range("afmize::mask: condition y + y_lower (" + 98 | std::to_string(y) + " + " + std::to_string(y_lower_) + ") <= y_upper (" + 99 | std::to_string(y_upper_) + ") is not satisfied."); 100 | } 101 | return img(x + x_lower_, y + y_lower_); 102 | } 103 | 104 | std::size_t pixel_x() const noexcept {return pixel_x_;} 105 | std::size_t pixel_y() const noexcept {return pixel_y_;} 106 | 107 | std::size_t lower_bounding_x() const noexcept {return x_lower_;} 108 | std::size_t lower_bounding_y() const noexcept {return y_lower_;} 109 | std::size_t upper_bounding_x() const noexcept {return x_upper_;} 110 | std::size_t upper_bounding_y() const noexcept {return y_upper_;} 111 | 112 | private: 113 | 114 | std::size_t pixel_x_; 115 | std::size_t pixel_y_; 116 | std::size_t x_lower_; 117 | std::size_t y_lower_; 118 | std::size_t x_upper_; 119 | std::size_t y_upper_; 120 | }; 121 | } // afmize 122 | #endif// AFMIZE_MASK_HPP 123 | -------------------------------------------------------------------------------- /include/afmize/progress_bar.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_PROGRESS_BAR_HPP 2 | #define AFMIZE_PROGRESS_BAR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* This code is originally implemented for other project named Mjolnir, * 14 | * by the same author, Toru Niina (c) 2018. destributed under the MIT License.*/ 15 | 16 | namespace afmize 17 | { 18 | 19 | template 20 | class progress_bar 21 | { 22 | //XXX: requires UTF-8. TODO: consider setting locale 23 | static constexpr auto full = u8"█"; // U+2588 Full block 24 | static constexpr auto l7 = u8"▉"; // U+2589 Left seven eighths block 25 | static constexpr auto l6 = u8"▊"; // U+258A Left three quarters block 26 | static constexpr auto l5 = u8"▋"; // U+258B Left five eighths block 27 | static constexpr auto l4 = u8"▌"; // U+258C Left half block 28 | static constexpr auto l3 = u8"▍"; // U+258D Left three eighths block 29 | static constexpr auto l2 = u8"▎"; // U+258E Left one quarter block 30 | static constexpr auto l1 = u8"▏"; // U+258F Left one eighth block 31 | 32 | public: 33 | 34 | explicit progress_bar(std::size_t tot) 35 | : total_(tot), r_total_(1.0 / tot), start_(std::chrono::system_clock::now()) 36 | {} 37 | ~progress_bar() = default; 38 | progress_bar(progress_bar const&) = default; 39 | progress_bar(progress_bar &&) = default; 40 | progress_bar& operator=(progress_bar const&) = default; 41 | progress_bar& operator=(progress_bar &&) = default; 42 | 43 | std::string format(std::size_t count) 44 | { 45 | if(count == 0){start_ = std::chrono::system_clock::now();} 46 | std::ostringstream os; 47 | 48 | //XXX: requires UTF-8. 49 | constexpr auto full = u8"█"; // U+2588 Full block 50 | constexpr auto l7 = u8"▉"; // U+2589 Left seven eighths block 51 | constexpr auto l6 = u8"▊"; // U+258A Left three quarters block 52 | constexpr auto l5 = u8"▋"; // U+258B Left five eighths block 53 | constexpr auto l4 = u8"▌"; // U+258C Left half block 54 | constexpr auto l3 = u8"▍"; // U+258D Left three eighths block 55 | constexpr auto l2 = u8"▎"; // U+258E Left one quarter block 56 | constexpr auto l1 = u8"▏"; // U+258F Left one eighth block 57 | 58 | const double ratio = (count == total_) ? 1.0 : 59 | std::max(0.0, std::min(1.0, count * this->r_total_)); 60 | 61 | std::array buf; 62 | buf.fill('\0'); 63 | std::snprintf(buf.data(), 8, "%5.1f%%|", ratio * 100.0); 64 | os << '\r' << buf.data(); 65 | 66 | const std::size_t filled = std::floor(ratio*Width); 67 | for(std::size_t i=0; i filled) 72 | { 73 | switch(static_cast(ratio * Width * 8) % 8) 74 | { 75 | case 0: {os << ' '; break;} 76 | case 1: {os << l1; break;} 77 | case 2: {os << l2; break;} 78 | case 3: {os << l3; break;} 79 | case 4: {os << l4; break;} 80 | case 5: {os << l5; break;} 81 | case 6: {os << l6; break;} 82 | case 7: {os << l7; break;} 83 | } 84 | for(std::size_t i=1; i( 99 | (current - start_) * (1.0 - ratio) / ratio).count() * 0.001; 100 | 101 | buf.fill('\0'); 102 | if(residual < 60.0) 103 | { 104 | std::snprintf(buf.data(), 6, "%5.1f", residual); 105 | os << buf.data() << " seconds remaining "; 106 | } 107 | else if(residual < 60.0 * 60.0) 108 | { 109 | std::snprintf(buf.data(), 6, "%5.1f", residual * 0.0167); 110 | os << buf.data() << " minutes remaining "; 111 | } 112 | else if(residual < 60.0 * 60.0 * 24.0) 113 | { 114 | std::snprintf(buf.data(), 6, "%5.1f", residual * 0.0167 * 0.0167); 115 | os << buf.data() << " hours remaining "; 116 | } 117 | else if(residual < 60.0 * 60.0 * 24.0 * 99.0) 118 | { 119 | std::snprintf(buf.data(), 6, "%5.1f", residual * 0.0167 * 0.0167 * 0.0417); 120 | os << buf.data() << " days remaining "; 121 | } 122 | else 123 | { 124 | os << " over 100 days remaining"; 125 | } 126 | return os.str(); 127 | } 128 | 129 | void reset_total(std::size_t t) 130 | { 131 | this->total_ = t; 132 | this->r_total_ = 1.0 / t; 133 | this->start_ = std::chrono::system_clock::now(); 134 | } 135 | 136 | std::size_t total() const noexcept {return total_;} 137 | 138 | private: 139 | 140 | std::size_t total_; 141 | double r_total_; 142 | std::chrono::system_clock::time_point start_; 143 | }; 144 | 145 | 146 | } // afmize 147 | #endif// AFMIZE_PROGRESS_BAR_HPP 148 | -------------------------------------------------------------------------------- /include/afmize/collision.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_COLLISION_HPP 2 | #define AFMIZE_COLLISION_HPP 3 | #include 4 | #include 5 | 6 | namespace afmize 7 | { 8 | 9 | // if collides, returns distance in Z axis. 10 | // otherwise, returns NaN. 11 | template 12 | Real collision_z(const sphere& probe, const sphere& target) noexcept 13 | { 14 | const auto dr = probe.center - target.center; 15 | const auto rr = probe.radius + target.radius; 16 | const auto zz = probe.center[2] - target.center[2]; 17 | const auto D = zz * zz - mave::length_sq(dr) + rr * rr; 18 | if(D < 0) {return std::numeric_limits::quiet_NaN();} 19 | 20 | return -zz + std::copysign(std::sqrt(D), zz); 21 | } 22 | 23 | template 24 | Real collision_z(const circular_frustum& probe, 25 | const sphere& target) noexcept 26 | { 27 | const auto square = [](const Real x) noexcept -> Real {return x * x;}; 28 | const auto dist_xy = std::sqrt(square(probe.apex[0] - target.center[0]) + 29 | square(probe.apex[1] - target.center[1])); 30 | 31 | const auto cos_theta = std::cos(probe.angle); 32 | const auto threshold = probe.radius + cos_theta * target.radius; 33 | if(threshold < dist_xy) 34 | { 35 | if(probe.angle == 0.0) {return std::numeric_limits::quiet_NaN();} 36 | // collides at the lateral surface of circular frustum 37 | return target.center[2] - 38 | ((dist_xy - probe.radius - target.radius * cos_theta) / 39 | std::tan(probe.angle) - target.radius * std::sin(probe.angle)) 40 | - probe.apex[2]; 41 | } 42 | else if(probe.radius < dist_xy) 43 | { 44 | // collides at the edge of circular frustum 45 | return target.center[2] + std::sqrt( 46 | square(target.radius) - square(dist_xy - probe.radius)) - 47 | probe.apex[2]; 48 | } 49 | else // trivial case. 50 | { 51 | return target.center[2] + target.radius - probe.apex[2]; 52 | } 53 | } 54 | 55 | // check collision 56 | 57 | template 58 | bool collides_with(const sphere& probe, const sphere& target) noexcept 59 | { 60 | const auto square = [](const Real x) noexcept -> Real {return x * x;}; 61 | return mave::length_sq(probe.center - target.center) < 62 | square(probe.radius + target.radius); 63 | } 64 | 65 | template 66 | bool collides_with(const circular_frustum& probe, 67 | const sphere& target) noexcept 68 | { 69 | const auto square = [](const Real x) noexcept -> Real {return x * x;}; 70 | const auto dist_xy = std::sqrt(square(probe.apex[0] - target.center[0]) + 71 | square(probe.apex[1] - target.center[1])); 72 | const auto dz = probe.apex[2] - target.center[2]; 73 | 74 | const auto cos_theta = std::cos(probe.angle); 75 | const auto threshold = probe.radius + cos_theta * target.radius; 76 | if(threshold < dist_xy) 77 | { 78 | // collides at the lateral surface of circular frustum 79 | return cos_theta * (dist_xy - probe.radius + dz * std::tan(probe.angle)) 80 | < target.radius; 81 | } 82 | else if(probe.radius < dist_xy) 83 | { 84 | // collides at the top of circular frustum 85 | if(dz < 0) {return true;} 86 | return (dz*dz + square(dist_xy - probe.radius)) < square(target.radius); 87 | } 88 | else // trivial case 89 | { 90 | if(dz < 0) {return true;} 91 | return dz < target.radius; 92 | } 93 | } 94 | 95 | // circular_frustum is always aligned to z axis. 96 | template 97 | bool collides_with(const circular_frustum& probe, const aabb& box) 98 | { 99 | if(box.upper[2] < probe.apex[2]) 100 | { 101 | return false; 102 | } 103 | 104 | const auto c = mave::vector(probe.apex[0], probe.apex[1], box.upper[2]); 105 | const auto r = probe.radius + std::tan(probe.angle) * (box.upper[2] - probe.apex[2]); 106 | 107 | const auto dx = std::max(box.lower[0] - c[0], std::max(Real(0), c[0] - box.upper[0])); 108 | const auto dy = std::max(box.lower[1] - c[1], std::max(Real(0), c[1] - box.upper[1])); 109 | 110 | return (dx * dx + dy * dy) < (r * r); 111 | } 112 | 113 | template 114 | bool collides_with(const sphere& probe, const aabb& box) 115 | { 116 | const auto du = probe.center - box.upper; 117 | const auto dl = box.lower - probe.center; 118 | 119 | const auto dx = std::max(du[0], std::max(Real(0), dl[0])); 120 | const auto dy = std::max(du[1], std::max(Real(0), dl[1])); 121 | const auto dz = std::max(du[2], std::max(Real(0), dl[2])); 122 | 123 | return (dx * dx + dy * dy + dz * dz) < (probe.radius * probe.radius); 124 | } 125 | 126 | template 127 | bool collides_with(const aabb& lhs, const aabb& rhs) 128 | { 129 | return (lhs.lower[0] <= rhs.upper[0] && rhs.lower[0] <= lhs.upper[0]) && 130 | (lhs.lower[1] <= rhs.upper[1] && rhs.lower[1] <= lhs.upper[1]) && 131 | (lhs.lower[2] <= rhs.upper[2] && rhs.lower[2] <= lhs.upper[2]); 132 | } 133 | 134 | template 135 | bool collides_with(const std::vector>& lhs, 136 | const std::vector>& rhs) 137 | { 138 | const auto aabb1 = make_bounding_box(lhs); 139 | const auto aabb2 = make_bounding_box(rhs); 140 | if( ! collides_with(lhs, rhs)) 141 | { 142 | return false; 143 | } 144 | 145 | for(const auto& p1 : lhs) 146 | { 147 | if( ! collides_with(p1, aabb2)) 148 | { 149 | continue; 150 | } 151 | for(const auto& p2 : rhs) 152 | { 153 | if(collides_with(p1, p2)) 154 | { 155 | return true; 156 | } 157 | } 158 | } 159 | return false; 160 | } 161 | 162 | } // afmize 163 | #endif// AFMIZE_COLLISION_HPP 164 | -------------------------------------------------------------------------------- /include/afmize/xyz_reader.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_READ_XYZ_HPP 2 | #define AFMIZE_READ_XYZ_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace afmize 12 | { 13 | 14 | template 15 | class xyz_reader final : public reader_base 16 | { 17 | public: 18 | 19 | using base_type = reader_base; 20 | using snapshot_type = typename base_type::snapshot_type; 21 | using trajectory_type = typename base_type::trajectory_type; 22 | 23 | xyz_reader(const std::string& fname) 24 | : base_type(fname), size_(0), ln(0), xyz(fname) 25 | { 26 | if(!xyz.good()) {throw std::runtime_error("file open error: " + fname);} 27 | 28 | std::string line; 29 | std::getline(xyz, line); 30 | size_++; 31 | const std::size_t snapshot = std::stoull(line) + 2; 32 | 33 | while(std::getline(xyz, line)) 34 | { 35 | size_++; 36 | } 37 | xyz.clear(); // clear the EOF flag 38 | xyz.seekg(0, std::ios_base::beg); 39 | 40 | if(this->size_ % snapshot != 0) 41 | { 42 | std::cerr << "file has invalid size: " << snapshot 43 | << " lines per snapshot, but there are " << this->size_ 44 | << " lines (" << this->size_ << " % " << snapshot 45 | << " = " << this->size_ % snapshot << "). " 46 | << "this may cause error."<< std::endl; 47 | } 48 | this->size_ /= snapshot; 49 | } 50 | ~xyz_reader() override = default; 51 | 52 | bool is_eof() override {xyz.peek(); return xyz.eof();} 53 | std::size_t size() const noexcept override {return this->size_;} 54 | 55 | trajectory_type read_trajectory() override 56 | { 57 | using namespace std::literals::string_literals; 58 | 59 | this->xyz.seekg(0, std::ios_base::beg); 60 | this->ln = 0; 61 | 62 | trajectory_type traj; 63 | while(this->is_eof()) 64 | { 65 | try 66 | { 67 | traj.push_back(this->read_snapshot()); 68 | xyz.peek(); 69 | } 70 | catch(typename base_type::no_more_model) 71 | { 72 | break; 73 | } 74 | } 75 | return traj; 76 | } 77 | 78 | snapshot_type read_snapshot() override 79 | { 80 | if(this->is_eof()) 81 | { 82 | throw typename base_type::no_more_model{}; 83 | } 84 | 85 | const auto n_elem = [this] { 86 | std::string l; 87 | std::getline(this->xyz, l); 88 | this->ln += 1; 89 | return l; 90 | }(); 91 | std::size_t N = 0; 92 | try 93 | { 94 | N = std::stoull(n_elem); 95 | } 96 | catch(std::invalid_argument) 97 | { 98 | std::cerr << "\ninvalid format in number of element at line " 99 | << ln << ".\n"; 100 | std::cerr << "> " << n_elem << '\n'; 101 | std::exit(EXIT_FAILURE); 102 | } 103 | catch(std::out_of_range) 104 | { 105 | std::cerr << "\ntoo many atoms in one snapshot at line" 106 | << ln << ".\n"; 107 | std::cerr << "> " << n_elem << '\n'; 108 | std::exit(EXIT_FAILURE); 109 | } 110 | 111 | { 112 | std::string comment; 113 | std::getline(this->xyz, comment); 114 | } 115 | 116 | snapshot_type snapshot; 117 | snapshot.first .reserve(N); 118 | snapshot.second.reserve(N); 119 | while(!this->is_eof() && snapshot.first.size() < N) 120 | { 121 | std::string line; 122 | std::getline(this->xyz, line); 123 | const auto name_pos = this->read_atom(line); 124 | snapshot.first .push_back(name_pos.first); 125 | snapshot.second.push_back(name_pos.second); 126 | } 127 | if(snapshot.first.size() != N || snapshot.second.size() != N) 128 | { 129 | throw std::runtime_error("a snapshot in the xyz file (" + 130 | this->filename + std::string(") lacks particle")); 131 | } 132 | return snapshot; 133 | } 134 | 135 | private: 136 | 137 | std::pair> read_atom(const std::string& line) 138 | { 139 | std::istringstream iss(line); 140 | std::string name, x, y, z; 141 | iss >> name >> x >> y >> z; 142 | 143 | sphere particle; 144 | 145 | try 146 | { 147 | particle.radius = parameter::radius_atom.at(name); 148 | } 149 | catch(std::out_of_range) 150 | { 151 | std::cerr << "\nunknown atom name found at line " << ln << ".\n"; 152 | this->highlight_columns(std::cerr, line, 0, name.size()); 153 | std::exit(EXIT_FAILURE); 154 | } 155 | 156 | try 157 | { 158 | particle.center[0] = read_number(x); 159 | } 160 | catch(std::invalid_argument) 161 | { 162 | std::cerr << "\ninvalid format at line " << ln << ".\n"; 163 | std::cerr << "> " << line << '\n'; 164 | std::cerr << base_type::mes_float_format_err; 165 | std::exit(EXIT_FAILURE); 166 | } 167 | catch(std::out_of_range) 168 | { 169 | std::cerr << "\ninvalid value at line" << ln << ".\n"; 170 | std::cerr << "> " << line << '\n'; 171 | std::cerr << base_type::mes_float_range_err; 172 | std::exit(EXIT_FAILURE); 173 | } 174 | 175 | try 176 | { 177 | particle.center[1] = read_number(y); 178 | } 179 | catch(std::invalid_argument) 180 | { 181 | std::cerr << "\ninvalid format at line " << ln << ".\n"; 182 | std::cerr << "> " << line << '\n'; 183 | std::cerr << base_type::mes_float_format_err; 184 | std::exit(EXIT_FAILURE); 185 | } 186 | catch(std::out_of_range) 187 | { 188 | std::cerr << "\ninvalid value at line" << ln << ".\n"; 189 | std::cerr << "> " << line << '\n'; 190 | std::cerr << base_type::mes_float_range_err; 191 | std::exit(EXIT_FAILURE); 192 | } 193 | 194 | try 195 | { 196 | particle.center[2] = read_number(z); 197 | } 198 | catch(std::invalid_argument) 199 | { 200 | std::cerr << "\ninvalid format at line " << ln << ".\n"; 201 | std::cerr << "> " << line << '\n'; 202 | std::cerr << base_type::mes_float_format_err; 203 | std::exit(EXIT_FAILURE); 204 | } 205 | catch(std::out_of_range) 206 | { 207 | std::cerr << "\ninvalid value at line" << ln << ".\n"; 208 | std::cerr << "> " << line << '\n'; 209 | std::cerr << base_type::mes_float_range_err; 210 | std::exit(EXIT_FAILURE); 211 | } 212 | 213 | return std::make_pair(name, particle); 214 | } 215 | 216 | private: 217 | 218 | std::size_t size_; 219 | std::size_t ln; 220 | std::ifstream xyz; 221 | }; 222 | 223 | } // afmize 224 | #endif // AFMIZE_READ_xyz_HPP 225 | -------------------------------------------------------------------------------- /include/afmize/output_utility.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_OUTPUT_UTILITY_HPP 2 | #define AFMIZE_OUTPUT_UTILITY_HPP 3 | #include "system.hpp" 4 | #include "image.hpp" 5 | #include "colormap.hpp" 6 | #include 7 | #include 8 | #include 9 | 10 | namespace afmize 11 | { 12 | 13 | template 14 | void write_xyz(const std::string& basename, const system& sys) 15 | { 16 | std::ofstream ofs(basename + ".xyz", std::ios_base::app); 17 | ofs << sys.particles.size() << "\n\n"; 18 | for(std::size_t i=0; i 31 | void write_tsv(const std::string& basename, const image& img) 32 | { 33 | using namespace std::literals::string_literals; 34 | std::ofstream ofs(basename + ".tsv"s); 35 | 36 | for(std::size_t y=0; y 48 | void write_ppm(const std::string& out, const image& img, 49 | const std::pair height_range) 50 | { 51 | using namespace std::literals::string_literals; 52 | const auto min_elem = height_range.first; 53 | const auto max_elem = height_range.second; 54 | 55 | pnm::image ppm(img.x_pixel(), img.y_pixel()); 56 | for(std::size_t i=0; i(img[i], min_elem, max_elem); 60 | } 61 | 62 | // origin of the image is upper left, but the physical origin is lower left. 63 | // to make it consistent, it simply reverses the image. 64 | pnm::image reversed(ppm.x_size(), ppm.y_size()); 65 | for(std::size_t i = 0; i 75 | void write_ppm(const std::string& out, const image& img) 76 | { 77 | const auto minmax = std::minmax_element(img.begin(), img.end()); 78 | const auto min_elem = *minmax.first; 79 | const auto max_elem = *minmax.second; 80 | 81 | write_ppm(out, img, std::make_pair(min_elem, max_elem)); 82 | return; 83 | } 84 | 85 | template 86 | void write_json(const std::string& out, const image& img, const system& sys) 87 | { 88 | using namespace std::literals::string_literals; 89 | 90 | std::ofstream ofs(out + ".json"); 91 | ofs << "{\n\t\"resolution\":{\"x\":" << sys.stage_info.x_resolution() 92 | << ", \"y\":" << sys.stage_info.y_resolution() 93 | << ", \"z\":" << sys.stage_info.z_resolution() << "},\n"; 94 | ofs << "\t\"height\":[\n"; 95 | for(std::size_t j=0; j 113 | void write_csv(const std::string& out, const image& img) 114 | { 115 | using namespace std::literals::string_literals; 116 | 117 | std::ofstream ofs(out + ".csv"); 118 | for(std::size_t j=0; j 131 | void write_svg(const std::string& out, const image& img, const system& sys, 132 | const Real scale_bar, const std::pair height_range) 133 | { 134 | using namespace std::literals::string_literals; 135 | std::ofstream svg(out + ".svg"); 136 | if(!svg.good()) 137 | { 138 | throw std::runtime_error("file open error: " + out); 139 | } 140 | 141 | const auto img_width = img.x_pixel() * sys.stage_info.x_resolution(); 142 | const auto img_height = img.y_pixel() * sys.stage_info.y_resolution(); 143 | 144 | svg << "\n"; 145 | 146 | const auto minv = height_range.first; 147 | const auto maxv = height_range.second; 148 | 149 | for(std::size_t yi=0; yi(img.at(xi, yi), minv, maxv); 158 | svg << "(color.red) << ',' 162 | << static_cast(color.green) << ',' 163 | << static_cast(color.blue) 164 | << ");stroke:none\"/>\n"; 165 | } 166 | } 167 | 168 | if(scale_bar != Real(0.0)) 169 | { 170 | const auto sb_width = scale_bar; 171 | const auto sb_height = std::max(sys.stage_info.y_resolution() * 0.5, img_height * 0.01); 172 | 173 | const auto buf_x = img_width * 0.05; 174 | const auto buf_y = img_height * 0.05; 175 | 176 | const auto sb_left = img_width - buf_x - sb_width; 177 | const auto sb_up = img_height - buf_y - sb_height; 178 | 179 | // scale bar 180 | svg << "\n"; 183 | } 184 | svg << "\n"; 185 | return; 186 | } 187 | 188 | template 189 | void write_svg(const std::string& out, const image& img, const system& sys, 190 | const Real scale_bar) 191 | { 192 | const auto minmax = std::minmax_element(img.begin(), img.end()); 193 | const auto minv = *minmax.first; 194 | const auto maxv = *minmax.second; 195 | write_svg(out, img, sys, scale_bar, std::make_pair(minv, maxv)); 196 | return; 197 | } 198 | 199 | 200 | 201 | } // afmize 202 | #endif// AFMIZE_OUTPUT_UTILITY_HPP 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | afmize 2 | ==== 3 | [![Build Status](https://www.travis-ci.com/ToruNiina/afmize.svg?branch=master)](https://www.travis-ci.com/ToruNiina/afmize) 4 | [![version](https://img.shields.io/github/release/ToruNiina/afmize.svg?style=flat)](https://github.com/ToruNiina/afmize/releases) 5 | [![license](https://img.shields.io/github/license/ToruNiina/afmize.svg?style=flat)](https://github.com/ToruNiina/afmize/blob/master/LICENSE) 6 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3779937.svg)](https://doi.org/10.5281/zenodo.3779937) 7 | 8 | Make pseudo AFM images from a structure file. 9 | 10 | ## Usage 11 | 12 | All the configurations are written in `.toml` file. 13 | No command-line option is available. 14 | 15 | ```sh 16 | $ afmize example.toml 17 | ``` 18 | 19 | See also: [`example/actin/README.md`](example/actin) 20 | 21 | ## Configuration 22 | 23 | It uses [toml format](https://github.com/toml-lang/toml) as a config file format. 24 | For more information about the format, see [the spec of toml-v1.0.0](https://toml.io/en/v1.0.0). 25 | 26 | ### Example file 27 | 28 | ```toml 29 | file.input = "example.pdb" 30 | file.output.basename = "output" 31 | file.output.formats = ["tsv", "json", "ppm", "svg"] 32 | probe.size = {radius = "1.0nm", angle = 10.0} 33 | resolution.x = "1.0nm" 34 | resolution.y = "1.0nm" 35 | resolution.z = "0.64angstrom" 36 | range.x = ["0.0nm", "100.0nm"] 37 | range.y = ["0.0nm", "100.0nm"] 38 | scale_bar.length = "5.0nm" 39 | noise = "0.3nm" 40 | ``` 41 | 42 | ### Reference 43 | 44 | Length unit is angstrom by default. 45 | You can explicitly specify `"nm"`, `"pm"`, and `"angstrom"`. 46 | 47 | #### `file` table 48 | 49 | - `file.input`: String 50 | - An input structure. `.pdb` or `.xyz` are available. 51 | - note that it may fail reading pdb files that does not conform [wwPDB 3.3](http://www.wwpdb.org/documentation/file-format-content/format33/sect9.html#ATOM). 52 | - `file.output.basename`: String 53 | - An output filename. An extension will be added. 54 | - `file.output.formats`: Array of Strings 55 | - An output format. One or more formats can be selected from `["csv", "json", "ppm", "svg"]`. 56 | 57 | #### `method` 58 | 59 | - `method`: String, `"rigid"` or `"smooth"`. 60 | - By default, `"rigid"`. 61 | - `"rigid"` is a collision-detection based method. 62 | - `"smooth"` is a method introduced in the [paper](https://pubs.acs.org/doi/10.1021/acs.jctc.9b00991) 63 | 64 | #### `probe` table 65 | 66 | In the `"rigid"` method, the AFM cantilever tip will be modeled as a hemisphere on top of truncated cone. 67 | If you specify `"smooth"` method, the value will be ignored. 68 | 69 | - `probe.size.radius`: String or Floating 70 | - The radius of the probe. 71 | - `probe.size.angle`: Floating 72 | - The angle of the cone. The unit is degree, not radian. 73 | 74 | #### `resolution` table 75 | 76 | - `resolution.x`: String or Floating 77 | - The resolution in x direction. The same as the pixel width. 78 | - `resolution.y`: String or Floating 79 | - The resolution in y direction. The same as the pixel height. 80 | - `resolution.z`: String or Floating 81 | - The resolution in z direction. The output height will be rounded using this. 82 | 83 | #### `range` table 84 | 85 | - `range.x`: Array of Strings or Floatings 86 | - The minimum and maximum coordinate of scanning range in x direction. 87 | - Atoms that exceed this boundary will not be scanned. 88 | - `range.y`: Array of Strings or Floatings 89 | - The minimum and maximum coordinate of scanning range in y direction. 90 | - Atoms that exceed this boundary will not be scanned. 91 | 92 | #### `stage` table 93 | 94 | - `stage.position`: String or Floating 95 | - The stage position in Z direction. 96 | - By default, `0`. 97 | - `stage.align`: Boolean 98 | - If `true`, the position in Z axis of the structure will be aligned to `0`. 99 | - Otherwise, the position will be kept intact. 100 | - By default, `true`. 101 | 102 | #### `radii` table 103 | 104 | You can change the atom radius in this table. 105 | By defualt, only a few number of atoms are supported. 106 | If you got some error like `unknown atom`, specify its radius in this table. 107 | 108 | - `radii.atom.[name-of-atom]`: String or Floating 109 | - Replace `[name-of-atom]` by your atom name. 110 | - The atom `[name-of-atom]` will have the radius you specified here. 111 | - `radii.residue.[name-of-residue]`: String or Floating 112 | - Replace `[name-of-residue]` by your residue name. 113 | - All the atoms in the residue `[name-of-residue]` will have the same radius you specified here. 114 | 115 | #### `scale_bar` table 116 | 117 | Scale bar option is only available with `.svg` output format. 118 | If you specify other format, the value will be ignored. 119 | 120 | - `length`: String or Floating 121 | - The length of the scale bar. 122 | 123 | #### `sigma` and `gamma` 124 | 125 | Parameters `sigma` and `gamma` that are used in the `"smooth"` method. 126 | If you specify `"rigid"` method, the value will be ignored. 127 | 128 | - `sigma`: String or Floating 129 | - The value of `sigma` in the formula described in the paper. 130 | - `gamma`: String or Floating 131 | - The value of `gamma` in the formula described in the paper. 132 | 133 | ## Installation 134 | 135 | First, clone this repository via `git`. Do not download auto-generated zip or tar 136 | file that does not include submodule information. 137 | 138 | ### Prerequisites 139 | 140 | Use your favorite package managers (e.g. `apt`) to install them. 141 | 142 | - CMake (> 3.2) 143 | - to generate Makefile. 144 | - git 145 | - to download submodules. 146 | - C++14 compliant compiler. tested with ... 147 | - g++-7 or later 148 | - clang++-6 or later 149 | - or others that fully support C++14 ISO standard. 150 | 151 | All other dependencies are managed by CMake script and git submodule. 152 | 153 | ### Building 154 | 155 | ```sh 156 | $ git clone https://github.com/ToruNiina/afmize.git 157 | $ cd afmize 158 | $ git submodule update --init 159 | $ mkdir build 160 | $ cd build 161 | $ cmake .. 162 | $ make 163 | $ make test # optional 164 | ``` 165 | 166 | After this, you will find the executable at `bin/` directory. 167 | 168 | ## Citation 169 | 170 | Please cite the following. 171 | 172 | - [Toru Niina, Yasuhiro Matsunaga, & Shoji Takada (2021). Rigid-Body Fitting to Atomic Force Microscopy Images for Inferring Probe Shape and Biomolecular Structure. bioRxiv. doi:10.1101/2021.02.21.432132](https://www.biorxiv.org/content/10.1101/2021.02.21.432132v1) 173 | - [Toru Niina, Sotaro Fuchigami, & Shoji Takada (2020). Flexible Fitting of Biomolecular Structures to Atomic Force Microscopy Images via Biased Molecular Simulations. JCTC. doi:10.1021/acs.jctc.9b00991](https://doi.org/10.1021/acs.jctc.9b00991) 174 | - [Toru Niina, & Suguru Kato. (2019, August 7). ToruNiina/afmize: version 1.1.0 (Version v1.1.0). Zenodo. doi:10.5281/zenodo.2556444](https://doi.org/10.5281/zenodo.2556444) 175 | 176 | ## Contact 177 | 178 | If you have any question, please feel free to make an [issue](https://github.com/ToruNiina/afmize/issues) to this repository. 179 | 180 | Issues are public. Everyone can share information about problems and save time. 181 | 182 | If you want to share a sensitive data with the repository owner to solve the problem, 183 | you can e-mail to [me](https://github.com/ToruNiina). 184 | 185 | ## Licensing Terms 186 | 187 | This product is licensed under the terms of the [MIT License](LICENSE). 188 | 189 | - Copyright (c) 2018-2021 Toru Niina 190 | 191 | All rights reserved. 192 | -------------------------------------------------------------------------------- /include/afmize/pdb_reader.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_READ_PDB_HPP 2 | #define AFMIZE_READ_PDB_HPP 3 | #include 4 | #include 5 | #include 6 | 7 | namespace afmize 8 | { 9 | 10 | template 11 | class pdb_reader final : public reader_base 12 | { 13 | public: 14 | 15 | using base_type = reader_base; 16 | using snapshot_type = typename base_type::snapshot_type; 17 | using trajectory_type = typename base_type::trajectory_type; 18 | 19 | pdb_reader(const std::string& fname, const bool read_hetatms = false) 20 | : base_type(fname), model_found(false), read_HETATMs(read_hetatms), 21 | size_(0), ln(0), pdb(fname) 22 | { 23 | if(!pdb.good()) {throw std::runtime_error("file open error: " + fname);} 24 | 25 | while(!pdb.eof()) 26 | { 27 | std::string line; 28 | std::getline(this->pdb, line); 29 | try 30 | { 31 | if(line.substr(0, 5) == "MODEL") 32 | { 33 | this->model_found = true; 34 | // if there are `MODEL` line, there should be several models. 35 | // count number of `MODEL` and replace this->size_ 36 | this->size_++; 37 | } 38 | } 39 | catch(...) {/* do nothing */} 40 | this->pdb.peek(); 41 | } 42 | this->pdb.clear(); // clear the EOF flag 43 | this->pdb.seekg(0, std::ios_base::beg); 44 | 45 | if(!this->model_found) {this->size_ = 1;} 46 | } 47 | ~pdb_reader() override = default; 48 | 49 | bool is_eof() override {pdb.peek(); return pdb.eof();} 50 | std::size_t size() const noexcept override {return this->size_;} 51 | 52 | trajectory_type read_trajectory() override 53 | { 54 | using namespace std::literals::string_literals; 55 | 56 | this->pdb.seekg(0, std::ios_base::beg); 57 | this->ln = 0; 58 | 59 | std::vector models; 60 | while(!pdb.eof()) 61 | { 62 | try 63 | { 64 | models.push_back(this->read_snapshot()); 65 | pdb.peek(); 66 | } 67 | catch(typename base_type::no_more_model) 68 | { 69 | break; 70 | } 71 | } 72 | return models; 73 | } 74 | 75 | snapshot_type read_snapshot() override 76 | { 77 | if(model_found) // seek line after `MODEL` 78 | { 79 | while(!pdb.eof()) 80 | { 81 | const auto line = [this] { 82 | std::string l; 83 | std::getline(this->pdb, l); 84 | this->ln += 1; 85 | return l; 86 | }(); 87 | 88 | const auto header = this->get_substr(line, ln, 0, 5); 89 | if(header == "MODEL") 90 | { 91 | pdb.peek(); 92 | break; 93 | } 94 | } 95 | if(pdb.eof()) 96 | { 97 | throw typename base_type::no_more_model{}; 98 | } 99 | } 100 | 101 | snapshot_type snap; 102 | while(!pdb.eof()) 103 | { 104 | const auto line = [this] { 105 | std::string l; 106 | std::getline(this->pdb, l); 107 | this->ln += 1; 108 | return l; 109 | }(); 110 | 111 | const auto header = this->get_substr(line, ln, 0, 6); 112 | if(model_found && (header == "ENDMDL" || header == "MODEL ")) 113 | { 114 | break; 115 | } 116 | if(header == "ATOM " || (this->read_HETATMs && header == "HETATM")) 117 | { 118 | const auto name_xyz = read_atom(line); 119 | snap.first .push_back(name_xyz.first); 120 | snap.second.push_back(name_xyz.second); 121 | } 122 | this->pdb.peek(); 123 | } 124 | 125 | pdb.peek(); 126 | return snap; 127 | } 128 | 129 | private: 130 | 131 | std::pair> read_atom(const std::string& line) 132 | { 133 | const auto header = line.substr(0, 6); 134 | if(!(header == "ATOM " || (this->read_HETATMs && header == "HETATM"))) 135 | { 136 | throw std::invalid_argument("internal error: invalid line: " + line); 137 | } 138 | 139 | sphere particle; 140 | particle.radius = this->get_radius(line); 141 | 142 | try 143 | { 144 | particle.center[0] = 145 | read_number(this->get_substr(line, ln, 30, 8)); 146 | } 147 | catch(std::invalid_argument) 148 | { 149 | std::cerr << "\ninvalid format at line " << ln << ".\n"; 150 | this->highlight_columns(std::cerr, line, 30, 8); 151 | std::cerr << base_type::mes_float_format_err; 152 | std::exit(EXIT_FAILURE); 153 | } 154 | catch(std::out_of_range) 155 | { 156 | std::cerr << "\ninvalid value at line" << ln << ".\n"; 157 | this->highlight_columns(std::cerr, line, 30, 8); 158 | std::cerr << base_type::mes_float_range_err; 159 | std::exit(EXIT_FAILURE); 160 | } 161 | 162 | try 163 | { 164 | particle.center[1] = 165 | read_number(this->get_substr(line, ln, 38, 8)); 166 | } 167 | catch(std::invalid_argument) 168 | { 169 | std::cerr << "\ninvalid format at line " << ln << ".\n"; 170 | this->highlight_columns(std::cerr, line, 38, 8); 171 | std::cerr << base_type::mes_float_format_err; 172 | std::exit(EXIT_FAILURE); 173 | } 174 | catch(std::out_of_range) 175 | { 176 | std::cerr << "\ninvalid value at line" << ln << ".\n"; 177 | this->highlight_columns(std::cerr, line, 38, 8); 178 | std::cerr << base_type::mes_float_range_err; 179 | std::exit(EXIT_FAILURE); 180 | } 181 | 182 | try 183 | { 184 | particle.center[2] = 185 | read_number(this->get_substr(line, ln, 46, 8)); 186 | } 187 | catch(std::invalid_argument) 188 | { 189 | std::cerr << "\ninvalid format at line " << ln << ".\n"; 190 | this->highlight_columns(std::cerr, line, 46, 8); 191 | std::cerr << base_type::mes_float_format_err; 192 | std::exit(EXIT_FAILURE); 193 | } 194 | catch(std::out_of_range) 195 | { 196 | std::cerr << "\ninvalid value at line" << ln << ".\n"; 197 | this->highlight_columns(std::cerr, line, 46, 8); 198 | std::cerr << base_type::mes_float_range_err; 199 | std::exit(EXIT_FAILURE); 200 | } 201 | 202 | const auto atm = (!std::isupper(line.at(12))) ? 203 | this->get_substr(line, ln, 13, 1) : 204 | this->get_substr(line, ln, 12, 2); 205 | 206 | return std::make_pair(atm, particle); 207 | } 208 | 209 | realT get_radius(const std::string& line) 210 | { 211 | // according to wwPDB 3.3 212 | //> Alignment of one-letter atom name such as C starts at column 14, 213 | //> while two-letter atom name such as FE starts at column 13. 214 | try 215 | { 216 | const auto tmp = this->get_substr(line, ln, 13, 1); 217 | std::string res; 218 | std::copy_if(tmp.begin(), tmp.end(), res.begin(), [](char c) { 219 | return !std::isspace(c); 220 | }); 221 | 222 | const auto atm = (!std::isupper(line.at(12))) ? 223 | this->get_substr(line, ln, 13, 1) : 224 | this->get_substr(line, ln, 12, 2); 225 | 226 | if(parameter::radius_residue.count(res) == 1) 227 | { 228 | const auto& rs = parameter::radius_residue.at(res); 229 | if(rs.count(atm) == 1) 230 | { 231 | return rs.at(atm); 232 | } 233 | } 234 | 235 | return parameter::radius_atom.at(atm); 236 | } 237 | catch(std::out_of_range) 238 | { 239 | std::cerr << "\nunknown atom name found at line " << ln << ".\n"; 240 | this->highlight_columns(std::cerr, line, 12, 4); 241 | std::cerr << "according to wwPDB 3.3,\n> one-letter atom name " 242 | "such as C starts at column 14,\n> while two-letter " 243 | "atom name such as FE starts at column 13.\n"; 244 | std::cerr << "see also:\nhttp://www.wwpdb.org/documentation/" 245 | "file-format-content/format33/sect9.html#ATOM\n"; 246 | if(line.at(12) == 'H') 247 | { 248 | std::cerr << "WARNING: It seems to be a hydrogen. " 249 | << "continue reading...\n\n"; 250 | return parameter::radius_atom.at("H"); 251 | } 252 | else 253 | { 254 | std::exit(EXIT_FAILURE); 255 | } 256 | } 257 | } 258 | 259 | 260 | private: 261 | 262 | 263 | bool model_found; 264 | bool read_HETATMs; 265 | std::size_t size_; 266 | std::size_t ln; // line number 267 | std::ifstream pdb; 268 | }; 269 | 270 | } // afmize 271 | #endif // AFMIZE_READ_PDB_HPP 272 | -------------------------------------------------------------------------------- /include/afmize/score.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_SCORE_HPP 2 | #define AFMIZE_SCORE_HPP 3 | #include "image.hpp" 4 | #include "mask.hpp" 5 | 6 | namespace afmize 7 | { 8 | 9 | template 10 | struct ScoreBase 11 | { 12 | virtual ~ScoreBase() = default; 13 | virtual Real calc(const system& sys, 14 | const image& img, const Mask& mask, 15 | const image& target, const Mask& target_mask) const = 0; 16 | }; 17 | 18 | template 19 | struct NegativeCosineSimilarity: public ScoreBase 20 | { 21 | Real k; // modulating coefficient 22 | 23 | explicit NegativeCosineSimilarity(const Real k_): k(k_) {} 24 | ~NegativeCosineSimilarity() override = default; 25 | 26 | Real calc(const system& sys, 27 | const image& img, const Mask& mask, 28 | const image& target, const Mask& target_mask) const override 29 | { 30 | assert(mask.size() == target_mask.size()); 31 | 32 | Real numer = 0; 33 | Real denom1 = 0; 34 | Real denom2 = 0; 35 | 36 | for(std::size_t y=0; y < mask.pixel_y(); ++y) 37 | { 38 | for(std::size_t x=0; x < mask.pixel_x(); ++x) 39 | { 40 | if( ! UseZeroPixel && mask(img, x, y) == 0) 41 | { 42 | continue; 43 | } 44 | const auto l = mask(img, x, y); 45 | const auto r = target_mask(target, x, y); 46 | 47 | numer += l * r; 48 | denom1 += l * l; 49 | denom2 += r * r; 50 | } 51 | } 52 | if(denom1 == 0 || denom2 == 0) 53 | { 54 | return std::numeric_limits::infinity(); 55 | } 56 | return k * (1.0 - numer / std::sqrt(denom1 * denom2)); 57 | } 58 | }; 59 | 60 | template 61 | struct NegativeCorrelation: public ScoreBase 62 | { 63 | Real k; // modulating coefficient 64 | 65 | explicit NegativeCorrelation(const Real k_): k(k_) {} 66 | ~NegativeCorrelation() override = default; 67 | 68 | Real calc(const system& sys, 69 | const image& img, const Mask& mask, 70 | const image& target, const Mask& target_mask) const override 71 | { 72 | assert(mask.size() == target_mask.size()); 73 | 74 | std::size_t N = 0; 75 | Real mean1 = 0; 76 | Real mean2 = 0; 77 | for(std::size_t y=0; y < mask.pixel_y(); ++y) 78 | { 79 | for(std::size_t x=0; x < mask.pixel_x(); ++x) 80 | { 81 | if( ! UseZeroPixel && mask(img, x, y) == 0) 82 | { 83 | continue; 84 | } 85 | const auto l = mask(img, x, y); 86 | const auto r = target_mask(target, x, y); 87 | 88 | mean1 += l; 89 | mean2 += r; 90 | N += 1; 91 | } 92 | } 93 | if(N == 0 || mean1 == 0.0 || mean2 == 0.0) 94 | { 95 | return std::numeric_limits::infinity(); 96 | } 97 | mean1 /= static_cast(N); 98 | mean2 /= static_cast(N); 99 | 100 | Real numer = 0; 101 | Real denom1 = 0; 102 | Real denom2 = 0; 103 | 104 | for(std::size_t y=0; y < mask.pixel_y(); ++y) 105 | { 106 | for(std::size_t x=0; x < mask.pixel_x(); ++x) 107 | { 108 | if( ! UseZeroPixel && mask(img, x, y) == 0) 109 | { 110 | continue; 111 | } 112 | const auto l = mask(img, x, y); 113 | const auto r = target_mask(target, x, y); 114 | 115 | numer += (l - mean1) * (r - mean2); 116 | denom1 += (l - mean1) * (l - mean1); 117 | denom2 += (r - mean2) * (r - mean2); 118 | } 119 | } 120 | return k * (1.0 - numer / std::sqrt(denom1 * denom2)); 121 | } 122 | }; 123 | 124 | 125 | template 126 | struct RootMeanSquareDeviation: public ScoreBase 127 | { 128 | Real k; // modulating coefficient 129 | 130 | explicit RootMeanSquareDeviation(const Real k_): k(k_) {} 131 | ~RootMeanSquareDeviation() override = default; 132 | 133 | Real calc(const system& sys, 134 | const image& img, const Mask& mask, 135 | const image& target, const Mask& target_mask) const override 136 | { 137 | assert(mask.size() == target_mask.size()); 138 | 139 | std::uint64_t N = 0; 140 | Real sd = 0.0; 141 | 142 | for(std::size_t y=0; y < mask.pixel_y(); ++y) 143 | { 144 | for(std::size_t x=0; x < mask.pixel_x(); ++x) 145 | { 146 | if( ! UseZeroPixel && mask(img, x, y) == 0) 147 | { 148 | continue; 149 | } 150 | const auto l = mask(img, x, y); 151 | const auto r = target_mask(target, x, y); 152 | 153 | sd += (l - r) * (l - r); 154 | N += 1; 155 | } 156 | } 157 | if(N == 0) 158 | { 159 | return std::numeric_limits::infinity(); 160 | } 161 | return k * std::sqrt(sd / static_cast(N)); 162 | } 163 | }; 164 | 165 | template 166 | struct SumOfDifference: public ScoreBase 167 | { 168 | Real k; // modulating coefficient 169 | 170 | explicit SumOfDifference(const Real k_): k(k_) {} 171 | ~SumOfDifference() override = default; 172 | 173 | Real calc(const system& sys, 174 | const image& img, const Mask& mask, 175 | const image& target, const Mask& target_mask) const override 176 | { 177 | assert(mask.size() == target_mask.size()); 178 | 179 | Real s = 0.0; 180 | 181 | for(std::size_t y=0; y < mask.pixel_y(); ++y) 182 | { 183 | for(std::size_t x=0; x < mask.pixel_x(); ++x) 184 | { 185 | if( ! UseZeroPixel && mask(img, x, y) == 0) 186 | { 187 | continue; 188 | } 189 | 190 | const auto l = mask(img, x, y); 191 | const auto r = target_mask(target, x, y); 192 | 193 | s += std::abs(l - r); 194 | } 195 | } 196 | return k * s; 197 | } 198 | }; 199 | 200 | template 201 | struct TopographicalPenalty: public ScoreBase 202 | { 203 | Real penalty; // penalty per particle in the forbidden region 204 | Real reward; // reward per particle in the favorable region 205 | Real thickness; // thickness of the favorable region 206 | 207 | TopographicalPenalty(const Real p, const Real r, const Real th) 208 | : penalty(p), reward(r), thickness(th) 209 | {} 210 | ~TopographicalPenalty() override = default; 211 | 212 | Real calc(const system& sys, 213 | const image& img, const Mask& mask, 214 | const image& target, const Mask& target_mask) const override 215 | { 216 | assert(mask.size() == target_mask.size()); 217 | 218 | Real score = 0.0; 219 | for(std::size_t y=0; y < mask.pixel_y(); ++y) 220 | { 221 | for(std::size_t x=0; x < mask.pixel_x(); ++x) 222 | { 223 | const auto threshold_penalty = target_mask(target, x, y); 224 | const auto threshold_reward = target_mask(target, x, y) - thickness; 225 | 226 | for(const auto& p : sys.particles) // speedup 227 | { 228 | if(threshold_penalty < p.center[2] + p.radius) 229 | { 230 | // higher energy 231 | score += penalty; 232 | } 233 | else if(threshold_reward < p.center[2] + p.radius) 234 | { 235 | // lower energy 236 | score -= reward; 237 | } 238 | } 239 | } 240 | } 241 | return score; 242 | } 243 | }; 244 | 245 | template 246 | struct PixelPenalty: public ScoreBase 247 | { 248 | Real penalty; // penalty per pixel in the forbidden region 249 | Real reward; // reward per pixel in the favorable region 250 | Real thickness; // thickness of the favorable region 251 | 252 | PixelPenalty(const Real p, const Real r, const Real th) 253 | : penalty(p), reward(r), thickness(th) 254 | {} 255 | ~PixelPenalty() override = default; 256 | 257 | Real calc(const system& sys, 258 | const image& img, const Mask& mask, 259 | const image& target, const Mask& target_mask) const override 260 | { 261 | assert(mask.size() == target_mask.size()); 262 | 263 | Real score = 0.0; 264 | for(std::size_t y=0; y < mask.pixel_y(); ++y) 265 | { 266 | for(std::size_t x=0; x < mask.pixel_x(); ++x) 267 | { 268 | const auto threshold_penalty = target_mask(target, x, y); 269 | const auto threshold_reward = target_mask(target, x, y) - thickness; 270 | 271 | if(threshold_penalty < mask(img, x, y)) 272 | { 273 | // higher energy 274 | score += penalty; 275 | } 276 | else if(threshold_reward < mask(img, x, y)) 277 | { 278 | // lower energy 279 | score -= reward; 280 | } 281 | } 282 | } 283 | return score; 284 | } 285 | }; 286 | 287 | } // afmize 288 | #endif// AFMIZE_MASK_HPP 289 | -------------------------------------------------------------------------------- /include/afmize/observe.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_OBSERVE_HPP 2 | #define AFMIZE_OBSERVE_HPP 3 | #include "cell_list.hpp" 4 | #include "collision.hpp" 5 | #include "shapes.hpp" 6 | #include "system.hpp" 7 | #include "stage.hpp" 8 | #include 9 | 10 | namespace afmize 11 | { 12 | 13 | template 14 | Real discretize(const Real x, const Real resolution, const Real min_val) 15 | { 16 | return std::round((x - min_val) / resolution) * resolution + min_val; 17 | } 18 | 19 | template 20 | Real collide_at(const system& sys, const default_probe& probe, 21 | const Real bottom) 22 | { 23 | // the point at which the probe collides with the stage 24 | Real height = bottom + probe.radius; 25 | for(const auto& sph : sys.particles) 26 | { 27 | const Real dz_sph = collision_z(sphere{ 28 | probe.radius, probe.apex 29 | }, sph); 30 | const Real dz_frs = collision_z(circular_frustum{ 31 | probe.angle, probe.radius, probe.apex 32 | }, sph); 33 | if(!std::isnan(dz_frs)) 34 | { 35 | height = std::max(height, probe.apex[2] + dz_frs); 36 | } 37 | if(!std::isnan(dz_sph)) 38 | { 39 | height = std::max(height, probe.apex[2] + dz_sph); 40 | } 41 | } 42 | // subtract probe radius to set the ground as 0 43 | return height - probe.radius; 44 | } 45 | 46 | template 47 | Real smooth_at(const system& sys, 48 | const mave::vector& pos, const Real bottom, 49 | const Real gamma, const Real sigma_x, const Real sigma_y) 50 | { 51 | const Real rgamma = 1.0 / gamma; 52 | const Real rsigma_x = 1.0 / sigma_x; 53 | const Real rsigma_x_sq = rsigma_x * rsigma_x; 54 | const Real rsigma_y = 1.0 / sigma_y; 55 | const Real rsigma_y_sq = rsigma_y * rsigma_y; 56 | 57 | Real expsum = std::exp(-bottom * rgamma); 58 | for(const auto& p : sys.particles) 59 | { 60 | const auto dr = p.center - pos; 61 | 62 | const auto dx_sq = dr[0] * dr[0]; 63 | const auto dy_sq = dr[1] * dr[1]; 64 | 65 | expsum += std::exp(-(dx_sq * rsigma_x_sq + dy_sq * rsigma_y_sq) + 66 | (p.radius + p.center[2] - bottom) * rgamma); 67 | } 68 | return gamma * std::log(expsum); 69 | } 70 | 71 | template 72 | struct ObserverBase 73 | { 74 | virtual ~ObserverBase() = default; 75 | virtual image const& observe(const system&) = 0; 76 | virtual bool update_probe(const std::map&) = 0; 77 | virtual std::map get_probe() const = 0; 78 | virtual void print_probe(std::ostream&) const = 0; 79 | virtual image const& get_image() const = 0; 80 | }; 81 | 82 | template 83 | struct RigidObserver: public ObserverBase 84 | { 85 | explicit RigidObserver(default_probe p) 86 | : probe(std::move(p)) 87 | {} 88 | ~RigidObserver() override = default; 89 | 90 | // here we assume the stage locates z == 0. 91 | image const& observe(const system& sys) override 92 | { 93 | img_.resize(sys.stage_info.x_pixel(), sys.stage_info.y_pixel()); 94 | 95 | // to skip pixels, we first calculate aabb of the probe at the 96 | const Real max_frustum_radius = probe.radius + 97 | std::tan(probe.angle) * (sys.bounding_box.upper[2] - probe.radius); 98 | 99 | const Real initial_z = sys.bounding_box.upper[2] + probe.radius; 100 | for(std::size_t j=0; j probe_aabb; 108 | probe_aabb.upper[0] = probe.apex[0] + max_frustum_radius; 109 | probe_aabb.upper[1] = probe.apex[1] + max_frustum_radius; 110 | probe_aabb.upper[2] = sys.bounding_box.upper[2]; 111 | 112 | probe_aabb.lower[0] = probe.apex[0] - max_frustum_radius; 113 | probe_aabb.lower[1] = probe.apex[1] - max_frustum_radius; 114 | probe_aabb.lower[2] = 0.0; 115 | 116 | if( ! collides_with(probe_aabb, sys.bounding_box)) 117 | { 118 | img_(i, j) = 0.0; 119 | continue; 120 | } 121 | 122 | // const auto min_height = collide_at(sys, probe, 0.0); 123 | 124 | 125 | // screen particles and get initial guess of the (minimum) height 126 | Real min_height = 0.0; 127 | for(const auto& elem : sys.cells.cell_at(probe.apex)) 128 | { 129 | const auto& particle = sys.particles.at(elem.particle_idx); 130 | const auto dz_sph = collision_z( 131 | sphere{probe.radius, probe.apex}, particle); 132 | if(!std::isnan(dz_sph)) 133 | { 134 | min_height = std::max(min_height, probe.apex[2] + dz_sph - probe.radius); 135 | break; 136 | } 137 | } 138 | // Now we have initial guess. Next we check additional collision. 139 | probe.apex[2] = min_height + probe.radius; 140 | 141 | // additional collision with circular frustum 142 | sys.cells.overwrapping_cells( 143 | circular_frustum{probe.angle, probe.radius, probe.apex}, 144 | this->index_buffer_); 145 | 146 | for(const auto cell_idx : index_buffer_) 147 | { 148 | for(const auto& elem : sys.cells.cell_at(cell_idx)) 149 | { 150 | if(elem.z_coordinate + sys.max_radius < min_height) 151 | { 152 | // elements are sorted by its z coordinate. 153 | break; 154 | } 155 | const auto& particle = sys.particles.at(elem.particle_idx); 156 | 157 | const auto dz = collision_z( 158 | circular_frustum{probe.angle, probe.radius, probe.apex}, 159 | particle); 160 | 161 | if(!std::isnan(dz)) // optional requires C++17 and it's 14... 162 | { 163 | // at the tip, we always have a (half) sphere. 164 | // the top of the tip is always lower than the apex of 165 | // circular frustum in probe.radius. 166 | min_height = std::max(min_height, probe.apex[2] + dz - probe.radius); 167 | } 168 | } 169 | } 170 | 171 | // additional collision with tip sphere 172 | probe.apex[2] = initial_z; 173 | sys.cells.overwrapping_cells(sphere{probe.radius, probe.apex}, 174 | this->index_buffer_); 175 | for(const auto cell_idx : index_buffer_) 176 | { 177 | for(const auto& elem : sys.cells.cell_at(cell_idx)) 178 | { 179 | if(elem.z_coordinate + sys.max_radius < min_height) 180 | { 181 | break; // elements are sorted by its z coordinate. 182 | } 183 | const auto& particle = sys.particles.at(elem.particle_idx); 184 | 185 | const auto dz_sph = collision_z( 186 | sphere{probe.radius, probe.apex}, particle); 187 | 188 | if(!std::isnan(dz_sph)) 189 | { 190 | min_height = std::max(min_height, probe.apex[2] + dz_sph - probe.radius); 191 | } 192 | } 193 | } 194 | 195 | if (Descritize) 196 | { 197 | img_(i, j) = afmize::discretize(min_height, sys.stage_info.z_resolution(), Real(0)); 198 | } 199 | else 200 | { 201 | img_(i, j) = min_height; 202 | } 203 | } 204 | } 205 | return img_; 206 | } 207 | 208 | bool update_probe(const std::map& attr) override 209 | { 210 | bool is_updated = false; 211 | if(0.0 < attr.at("radius")) 212 | { 213 | probe.radius = attr.at("radius"); 214 | is_updated = true; 215 | } 216 | if(0.0 < attr.at("angle") && attr.at("angle") < 3.1416 * 0.5) 217 | { 218 | probe.angle = attr.at("angle"); 219 | is_updated = true; 220 | } 221 | return is_updated; 222 | } 223 | 224 | void print_probe(std::ostream& os) const override 225 | { 226 | os << probe.radius * 0.1 << " [nm] " << probe.angle * 180.0 / 3.1416 << " [rad]"; 227 | } 228 | 229 | std::map get_probe() const override 230 | { 231 | return std::map{ 232 | {"radius", probe.radius}, 233 | {"angle", probe.angle } 234 | }; 235 | } 236 | 237 | image const& get_image() const override {return img_;} 238 | 239 | default_probe probe; 240 | 241 | private: 242 | image img_; 243 | std::vector index_buffer_; // avoid allocation 244 | }; 245 | 246 | template 247 | struct SmoothObserver: public ObserverBase 248 | { 249 | explicit SmoothObserver(const Real sigma, 250 | const Real gamma = 1.0 /*angst.*/, const Real cutoff_sigma = 5.0) 251 | : sigma_(sigma), gamma_(gamma), rgamma_(1.0 / gamma), cutoff_(cutoff_sigma) 252 | {} 253 | ~SmoothObserver() override = default; 254 | 255 | // here we assume the stage locates z == 0. 256 | image const& observe(const system& sys) override 257 | { 258 | img_.resize(sys.stage_info.x_pixel(), sys.stage_info.y_pixel()); 259 | 260 | const auto rsigma = 1.0 / sigma_; 261 | const auto rsigma_sq = rsigma * rsigma; 262 | const auto cutoff_sq = (sigma_ * cutoff_) * (sigma_ * cutoff_); 263 | 264 | std::vector cells; 265 | cells.reserve(25); 266 | 267 | for(std::size_t j=0; j& attr) override 307 | { 308 | bool is_updated = false; 309 | if(0.0 < attr.at("sigma")) 310 | { 311 | sigma_ = attr.at("sigma"); 312 | is_updated = true; 313 | } 314 | return is_updated; 315 | } 316 | void print_probe(std::ostream& os) const override 317 | { 318 | os << sigma_ * 0.1 << " [nm]"; 319 | } 320 | std::map get_probe() const override 321 | { 322 | return std::map{ 323 | {"sigma", sigma_}, 324 | }; 325 | } 326 | 327 | image const& get_image() const override {return img_;} 328 | 329 | private: 330 | 331 | Real sigma_; 332 | Real gamma_; 333 | Real rgamma_; 334 | Real cutoff_; 335 | 336 | image img_; 337 | std::vector index_buffer_; // avoid allocation 338 | }; 339 | 340 | 341 | } // afmize 342 | #endif// AFMIZE_OBSERVE_HPP 343 | -------------------------------------------------------------------------------- /include/afmize/cell_list.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_CELL_LIST_HPP 2 | #define AFMIZE_CELL_LIST_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace afmize 11 | { 12 | 13 | template 14 | struct range 15 | { 16 | range(Iterator f, Iterator l): first(f), last(l) {} 17 | 18 | Iterator begin() const noexcept {return first;} 19 | Iterator end() const noexcept {return last;} 20 | 21 | bool empty() {return first == last;} 22 | 23 | private: 24 | Iterator first, last; 25 | }; 26 | 27 | template 28 | struct cell_list 29 | { 30 | struct list_elem 31 | { 32 | std::size_t particle_idx; 33 | std::size_t cell_idx; 34 | Real z_coordinate; 35 | }; 36 | 37 | cell_list() : is_dirty_(true) {} 38 | 39 | void initialize(const Real stage_reso_x, const Real stage_reso_y, 40 | const std::vector>& mol, 41 | const std::size_t fine_factor = 2) 42 | { 43 | if( ! this->is_dirty_) 44 | { 45 | throw std::runtime_error("initialized twice"); 46 | } 47 | 48 | this->is_dirty_ = false; 49 | this->max_radius_ = 0.0; 50 | for(const auto& sph : mol) 51 | { 52 | max_radius_ = std::max(max_radius_, sph.radius); 53 | } 54 | 55 | this->stage_reso_x_ = stage_reso_x; 56 | this->stage_reso_y_ = stage_reso_y; 57 | // Since the size of typical protein is in the order of 1~10 nm, most 58 | // of the pixels in an AFM image does not contain any particle. Thus 59 | // constructing a cell list that has the same grid size as the AFM 60 | // image might not be so efficient. 61 | // Here we construct a minimal cell list that can contain molecule 62 | // regardless of the relative configuration (but we consider the 63 | // molecule is rigid-body). 64 | 65 | const auto bounding_sphere = 66 | make_bounding_sphere_centered_at_geometric_center(mol); 67 | 68 | // a square of which edge length is `bounding_sphere.radius * 2` is 69 | // enough large to containe the whole molecule. 70 | // The number of grids that can wrap the square is, 71 | 72 | const std::size_t grids_x = 2 * std::ceil(bounding_sphere.radius / stage_reso_x_); 73 | const std::size_t grids_y = 2 * std::ceil(bounding_sphere.radius / stage_reso_y_); 74 | 75 | // make grid a bit fine-grained to speed up more 76 | 77 | this->x_size_ = grids_x * fine_factor; // number of cells in x 78 | this->y_size_ = grids_y * fine_factor; 79 | this->cell_width_x_ = bounding_sphere.radius * 2 / x_size_; 80 | this->cell_width_y_ = bounding_sphere.radius * 2 / y_size_; 81 | this->rcw_x_ = 1.0 / cell_width_x_; 82 | this->rcw_y_ = 1.0 / cell_width_y_; 83 | this->region_x_ = bounding_sphere.radius * 2; 84 | this->region_y_ = bounding_sphere.radius * 2; 85 | 86 | // construct data container 87 | // 88 | // .--------- N+1 elems ---------. 89 | // 0 1 2, 3 N, N+1 (N-th element is always empty) 90 | // | | |--' |---' 91 | // v v v v 92 | // |i1|i2|i3|i4|i5|i6|i7|i8| ... | 93 | // '--------'-----'--------'...--' 94 | // cell 0 1 | 3 ... 95 | // +- 2nd cell is empty 96 | // 97 | // We construct an offset list that indicates which range corresponds to 98 | // the cell. Here, we add an always-empty cell as the last element to 99 | // represent out-of-bound region. So here we need to allocate N+1 +1 100 | // offsets. 101 | this->offsets_.resize(x_size_ * y_size_ + 2, 0); 102 | this->list_.resize(mol.size()); 103 | } 104 | 105 | // construct cell list. 106 | // system must be the same (the orientation and position can change). 107 | void construct(const std::vector>& mol, 108 | const aabb& bounding_box /* of mol */) 109 | { 110 | assert(!is_dirty_); 111 | assert(list_.size() == mol.size()); 112 | 113 | // XXX here it assumes system::bounding_box is up to date. 114 | // It wraps a bit larger region, 115 | this->lower_ = bounding_box.lower; 116 | this->upper_[0] = this->lower_[0] + this->region_x_; 117 | this->upper_[1] = this->lower_[1] + this->region_y_; 118 | this->upper_[2] = bounding_box.upper[2]; 119 | 120 | // calc cell indices 121 | for(std::size_t i=0; icalc_index(pos), pos[2]}; 125 | } 126 | 127 | // cell_idx: 0 -> N 128 | // z_coord: large -> small 129 | std::sort(list_.begin(), list_.end(), 130 | [](const auto& lhs, const auto& rhs) -> bool { 131 | return (lhs.cell_idx == rhs.cell_idx) ? lhs.z_coordinate > rhs.z_coordinate : 132 | lhs.cell_idx < rhs.cell_idx; 133 | }); 134 | 135 | // 0 1 2,3 N,N+1 136 | // | | | | 137 | // v v v v 138 | // |i1|i2|i3|i4|i5|i6|i7|i8| ... | 139 | // '--------'-----'--------'...--' 140 | // cell 0 1 3 ... 141 | assert(offsets_.size() == x_size_ * y_size_ + 2); 142 | offsets_.at(0) = 0; // starting point 143 | offsets_.at(offsets_.size() - 1) = list_.size(); // empty N+1-th cell 144 | 145 | std::size_t current_offset = 0; 146 | std::size_t current_cell = 0; 147 | for(std::size_t i=0; i::const_iterator> 174 | cell_at(const mave::vector& xy) const 175 | { 176 | const auto x = static_cast(std::floor((xy[0] - lower_[0]) * rcw_x_)); 177 | const auto y = static_cast(std::floor((xy[1] - lower_[1]) * rcw_y_)); 178 | return cell_at(check_idx(x, y)); 179 | } 180 | range::const_iterator> 181 | cell_at(const std::size_t idx) const 182 | { 183 | return range::const_iterator>{ 184 | list_.begin() + offsets_.at(idx), 185 | list_.begin() + offsets_.at(idx + 1) 186 | }; 187 | } 188 | 189 | // here, we don't care about the z coordinate of the probe. 190 | // sphere is not "open". It might collides with the particles above 191 | // while observation. 192 | void overwrapping_cells(const sphere& probe, std::vector& out) const 193 | { 194 | out.clear(); 195 | const std::int32_t ctr_x = std::floor((probe.center[0] - lower_[0]) * rcw_x_); 196 | const std::int32_t ctr_y = std::floor((probe.center[1] - lower_[1]) * rcw_y_); 197 | const std::int32_t ofs_x = (probe.radius + max_radius_) * rcw_x_; 198 | const std::int32_t ofs_y = (probe.radius + max_radius_) * rcw_y_; 199 | 200 | const auto nil = x_size_ * y_size_; 201 | for(std::int32_t y = ctr_y - ofs_y; y <= ctr_y + ofs_y; ++y) 202 | { 203 | for(std::int32_t x = ctr_x - ofs_x; x <= ctr_x + ofs_x; ++x) 204 | { 205 | const auto idx = check_idx(x, y); 206 | if(idx != nil) 207 | { 208 | out.push_back(idx); 209 | } 210 | } 211 | } 212 | return; 213 | } 214 | 215 | void overwrapping_cells(const circular_frustum& probe, 216 | std::vector& out) const 217 | { 218 | out.clear(); 219 | 220 | if(this->upper_[2] < probe.apex[2]) 221 | { 222 | return; 223 | } 224 | const auto r = probe.radius + max_radius_ + 225 | std::tan(probe.angle) * (this->upper_[2] - probe.apex[2]); 226 | 227 | const std::int32_t ctr_x = std::floor((probe.apex[0] - lower_[0]) * rcw_x_); 228 | const std::int32_t ctr_y = std::floor((probe.apex[1] - lower_[1]) * rcw_y_); 229 | const std::int32_t ofs_x = r * rcw_x_; 230 | const std::int32_t ofs_y = r * rcw_y_; 231 | 232 | const auto nil = x_size_ * y_size_; 233 | for(std::int32_t y = ctr_y - ofs_y; y <= ctr_y + ofs_y; ++y) 234 | { 235 | for(std::int32_t x = ctr_x - ofs_x; x <= ctr_x + ofs_x; ++x) 236 | { 237 | const auto idx = check_idx(x, y); 238 | if(idx != nil) 239 | { 240 | out.push_back(idx); 241 | } 242 | } 243 | } 244 | return; 245 | } 246 | 247 | // returns cells that forms a rectangle that overlaps with a circle 248 | void overwrapping_cells(const Real width_x, const Real width_y, 249 | const std::size_t x, const std::size_t y, std::vector& out) 250 | { 251 | out.clear(); 252 | 253 | const std::int32_t ctr_x = x; 254 | const std::int32_t ctr_y = y; 255 | const std::int32_t ofs_x = width_x * rcw_x_; 256 | const std::int32_t ofs_y = width_y * rcw_y_; 257 | 258 | for(std::int32_t y = std::max(0, ctr_y - ofs_y); y <= std::min(y_size_, ctr_y + ofs_y); ++y) 259 | { 260 | for(std::int32_t x = std::max(0, ctr_x - ofs_x); x <= std::min(x_size_, ctr_x + ofs_x); ++x) 261 | { 262 | out.push_back(y * x_size_ + x); 263 | } 264 | } 265 | } 266 | 267 | void diagnosis(const std::vector>& mol) const 268 | { 269 | for(std::int32_t y=0; y bool { 276 | return lhs.z_coordinate > rhs.z_coordinate; 277 | })) 278 | { 279 | throw std::runtime_error("z coord is not sorted"); 280 | } 281 | for(const auto& elem : elems) 282 | { 283 | const auto p = mol.at(elem.particle_idx); 284 | 285 | const auto x_ = std::floor((p.center[0] - lower_[0]) * rcw_x_); 286 | const auto y_ = std::floor((p.center[1] - lower_[1]) * rcw_y_); 287 | 288 | if(x != x_ || y != y_) 289 | { 290 | throw std::runtime_error("particle position inconsistent:" 291 | " cell = (" + std::to_string(x) + ", " + 292 | std::to_string(y) + ")," + 293 | " particle = (" + std::to_string(x_) + ", " + 294 | std::to_string(y_) + ")."); 295 | } 296 | } 297 | } 298 | } 299 | return ; 300 | } 301 | 302 | private: 303 | 304 | std::size_t check_idx(std::int32_t x, std::int32_t y) const 305 | { 306 | if(x < 0 || std::int32_t(x_size_) <= x || 307 | y < 0 || std::int32_t(y_size_) <= y) 308 | { 309 | return x_size_ * y_size_; // the last one is always empty 310 | } 311 | return y * x_size_ + x; 312 | } 313 | 314 | std::size_t calc_index(const mave::vector& v) const noexcept 315 | { 316 | const auto xi = static_cast(std::floor((v[0] - lower_[0]) * rcw_x_)); 317 | const auto yi = static_cast(std::floor((v[1] - lower_[1]) * rcw_y_)); 318 | assert(0 <= xi && xi < x_size_ && 0 <= yi && yi < y_size_); 319 | return yi * x_size_ + xi; 320 | } 321 | 322 | private: 323 | 324 | bool is_dirty_; 325 | Real max_radius_; // in system 326 | Real stage_reso_x_, stage_reso_y_; // width of a image pixel 327 | Real cell_width_x_, cell_width_y_; // width of a cell 328 | Real rcw_x_, rcw_y_; // reciprocal cell width 329 | Real region_x_, region_y_; // width of the whole region (fixed) 330 | std::size_t x_size_, y_size_; 331 | 332 | mave::vector lower_, upper_; 333 | std::vector offsets_; 334 | std::vector list_; 335 | }; 336 | 337 | } // afmize 338 | #endif // AFMIZE_CELL_LIST_HPP 339 | -------------------------------------------------------------------------------- /src/simulator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace afmize 14 | { 15 | 16 | template 17 | std::unique_ptr> 18 | open_file(const std::string& fname) 19 | { 20 | if(fname.substr(fname.size() - 4, 4) == ".pdb") 21 | { 22 | return std::make_unique>(fname); 23 | } 24 | else if(fname.substr(fname.size() - 6, 6) == ".movie") 25 | { 26 | return std::make_unique>(fname); 27 | } 28 | else if(fname.substr(fname.size() - 4, 4) == ".xyz") 29 | { 30 | return std::make_unique>(fname); 31 | } 32 | else 33 | { 34 | throw std::runtime_error("afmize supports only pdb or xyz"); 35 | } 36 | } 37 | 38 | template 39 | std::unique_ptr> read_observation_method(const toml::value& config) 40 | { 41 | constexpr Real pi = Real(3.1415926535897932384626); 42 | constexpr Real deg_to_rad = pi / Real(180.0); 43 | 44 | const auto& p = toml::find(config, "probe"); 45 | default_probe probe; 46 | probe.angle = toml::find(p, "angle") * deg_to_rad; 47 | probe.radius = read_as_angstrom(toml::find(p, "radius")); 48 | 49 | // If z resolution is set, that means height is descritized in an image. 50 | const auto z_descritized = config.at("stage").at("resolution").contains("z"); 51 | if(z_descritized) 52 | { 53 | return std::make_unique>(std::move(probe)); 54 | } 55 | else 56 | { 57 | return std::make_unique>(std::move(probe)); 58 | } 59 | } 60 | 61 | template 62 | std::unique_ptr> 63 | read_score_function(const toml::value& config) 64 | { 65 | const auto& score = config.at("score"); 66 | 67 | auto method = toml::find(score, "method"); 68 | std::transform(method.begin(), method.end(), method.begin(), 69 | [](const char c) -> char {return std::tolower(c);}); 70 | 71 | if(method == "cosine similarity" || method == "cosinesimilarity") 72 | { 73 | const auto use_zero = toml::find(score, "use_zero_pixel_in_model"); 74 | const auto k = toml::find(score, "k"); 75 | if(use_zero) 76 | { 77 | return std::make_unique>(k); 78 | } 79 | else 80 | { 81 | return std::make_unique>(k); 82 | } 83 | } 84 | else if(method == "correlation") 85 | { 86 | const auto use_zero = toml::find(score, "use_zero_pixel_in_model"); 87 | const auto k = toml::find(score, "k"); 88 | if(use_zero) 89 | { 90 | return std::make_unique>(k); 91 | } 92 | else 93 | { 94 | return std::make_unique>(k); 95 | } 96 | } 97 | else if(method == "rmsd") 98 | { 99 | const auto use_zero = toml::find(score, "use_zero_pixel_in_model"); 100 | const auto k = toml::find(score, "k"); 101 | if(use_zero) 102 | { 103 | return std::make_unique>(k); 104 | } 105 | else 106 | { 107 | return std::make_unique>(k); 108 | } 109 | } 110 | else if(method == "topographical penalty") 111 | { 112 | const auto penalty = toml::find(score, "penalty"); 113 | const auto reward = toml::find(score, "reward"); 114 | const auto thickness = read_as_angstrom(toml::find(score, "thickness")); 115 | return std::make_unique>( 116 | penalty, reward, thickness); 117 | } 118 | else if(method == "pixel penalty") 119 | { 120 | const auto penalty = toml::find(score, "penalty"); 121 | const auto reward = toml::find(score, "reward"); 122 | const auto thickness = read_as_angstrom(toml::find(score, "thickness")); 123 | return std::make_unique>(penalty, reward, thickness); 124 | } 125 | else 126 | { 127 | throw std::runtime_error("unknown score function: " + method); 128 | } 129 | } 130 | 131 | template 132 | std::unique_ptr> 133 | read_temperature_schedule(const toml::value& config) 134 | { 135 | const auto& sim = toml::find(config, "simulator"); 136 | const auto method = toml::find(sim, "schedule", "method"); 137 | if(method == "linear") 138 | { 139 | const auto total_t = toml::find(sim, "steps"); 140 | const auto init_v = toml::find(sim, "schedule", "initial"); 141 | Real initial = -1.0; 142 | if(init_v.is_floating()) 143 | { 144 | initial = init_v.as_floating(); 145 | } 146 | const auto final = toml::find(sim, "schedule", "final"); 147 | 148 | return std::make_unique>(initial, final, total_t); 149 | } 150 | else if(method == "exponential") 151 | { 152 | const auto total_t = toml::find(sim, "steps"); 153 | const auto init_v = toml::find(sim, "schedule", "initial"); 154 | Real initial = -1.0; 155 | if(init_v.is_floating()) 156 | { 157 | initial = init_v.as_floating(); 158 | } 159 | const auto final = toml::find(sim, "schedule", "final"); 160 | 161 | return std::make_unique>(initial, final, total_t); 162 | } 163 | else 164 | { 165 | throw std::runtime_error("unknown schedule method: " + method); 166 | } 167 | } 168 | 169 | template 170 | image read_reference_image(const toml::value& config, const stage& stg) 171 | { 172 | const auto refname = toml::find(config, "image", "reference"); 173 | if(refname.substr(refname.size() - 4, 4) == ".tsv") 174 | { 175 | const auto x = toml::find(config, "image", "pixels", "x"); 176 | const auto y = toml::find(config, "image", "pixels", "y"); 177 | image img(x, y); 178 | 179 | std::ifstream ifs(refname); 180 | if(!ifs.good()) 181 | { 182 | throw std::runtime_error("file open error: " + refname); 183 | } 184 | for(std::size_t y=0; y> img.at(x, y); 189 | } 190 | } 191 | return img; 192 | } 193 | else 194 | { 195 | throw std::runtime_error("[error] image format not supported: " + refname); 196 | } 197 | } 198 | 199 | template 200 | system read_system(const toml::value& config) 201 | { 202 | const auto reader = open_file(toml::find(config, "initial", "input")); 203 | 204 | // image lower coordinate 205 | const auto lower_x = read_as_angstrom(toml::find(config, "image", "lower", "x")); 206 | const auto lower_y = read_as_angstrom(toml::find(config, "image", "lower", "y")); 207 | 208 | // read stage setting 209 | // [stage] 210 | // pixels.x = 80 211 | // pixels.y = 80 212 | // resolution.x = "1.0nm" 213 | // resolution.y = "1.0nm" 214 | const auto pixel_x = toml::find(config, "stage", "pixels", "x"); 215 | const auto pixel_y = toml::find(config, "stage", "pixels", "y"); 216 | 217 | const auto& resolution = toml::find(config, "stage", "resolution"); 218 | const auto reso_x = read_as_angstrom(toml::find(resolution, "x")); 219 | const auto reso_y = read_as_angstrom(toml::find(resolution, "y")); 220 | const auto reso_z = read_as_angstrom( 221 | toml::find_or(resolution, "z", toml::value(0.0))); 222 | 223 | stage stg(reso_x, reso_y, reso_z, 224 | std::make_pair(lower_x, lower_x + reso_x * pixel_x), 225 | std::make_pair(lower_y, lower_y + reso_y * pixel_y)); 226 | 227 | return system(reader->read_snapshot(), std::move(stg)); 228 | } 229 | 230 | template 231 | std::unique_ptr> 232 | read_simulated_annealing_simulator(const toml::value& config, system init) 233 | { 234 | constexpr Real pi = Real(3.1415926535897932384626); 235 | constexpr Real deg_to_rad = pi / Real(180.0); 236 | const auto& sim = toml::find(config, "simulator"); 237 | const auto out = toml::find(sim, "output"); 238 | 239 | const auto seed = toml::find(sim, "seed"); 240 | const auto steps = toml::find(sim, "steps"); 241 | const auto save = toml::find(sim, "save"); 242 | 243 | const auto sx = afmize::read_as_angstrom(toml::find(sim, "sigma_x")); 244 | const auto sy = afmize::read_as_angstrom(toml::find(sim, "sigma_y")); 245 | const auto sz = afmize::read_as_angstrom(toml::find(sim, "sigma_z")); 246 | const auto rx = toml::find(sim, "maxrot_x") * deg_to_rad; 247 | const auto ry = toml::find(sim, "maxrot_y") * deg_to_rad; 248 | const auto rz = toml::find(sim, "maxrot_z") * deg_to_rad; 249 | 250 | std::map dprobe; 251 | if(sim.at("dprobe").contains("max_dradius")) 252 | { 253 | dprobe["radius"] = afmize::read_as_angstrom(toml::find(sim, "dprobe", "max_dradius")); 254 | } 255 | if(sim.at("dprobe").contains("max_dangle")) 256 | { 257 | dprobe["angle"] = toml::find(sim, "dprobe", "max_dangle") * deg_to_rad; 258 | } 259 | if(sim.at("dprobe").contains("max_dsigma")) 260 | { 261 | dprobe["sigma"] = afmize::read_as_angstrom(toml::find(sim, "dprobe", "max_dsigma")); 262 | } 263 | 264 | auto ref = read_reference_image(config, init.stage_info); 265 | 266 | return std::make_unique>( 267 | steps, save, seed, sx, sy, sz, rx, ry, rz, dprobe, 268 | std::move(ref), 269 | std::move(init), 270 | read_observation_method(config), 271 | read_score_function(config), 272 | read_temperature_schedule(config), 273 | out); 274 | } 275 | 276 | template 277 | std::unique_ptr> 278 | read_scanning_simulator(const toml::value& config, system init) 279 | { 280 | const auto out = toml::find(config, "simulator", "output"); 281 | 282 | const auto& sim = toml::find(config, "simulator"); 283 | const auto num_div = toml::find(sim, "num_division"); 284 | const auto num_save = toml::find(sim, "save"); 285 | const auto dz = read_as_angstrom(toml::find(sim, "dz")); 286 | 287 | auto ref = read_reference_image(config, init.stage_info); 288 | 289 | return std::make_unique>( 290 | num_div, num_save, dz, 291 | std::move(ref), 292 | std::move(init), 293 | read_observation_method(config), 294 | read_score_function(config), 295 | out); 296 | } 297 | 298 | template 299 | std::unique_ptr> 300 | read_simulator(const toml::value& config, system init) 301 | { 302 | const auto& sim = toml::find(config, "simulator"); 303 | const auto algo = toml::find(sim, "method"); 304 | if(algo == "SA" || algo == "SimulatedAnnealing") 305 | { 306 | const auto mask = toml::find(config, "score", "mask"); 307 | if(mask == "rectangular") 308 | { 309 | return read_simulated_annealing_simulator>( 310 | config, std::move(init)); 311 | } 312 | else if(mask == "none") 313 | { 314 | return read_simulated_annealing_simulator>( 315 | config, std::move(init)); 316 | } 317 | else 318 | { 319 | throw std::runtime_error("unknown mask: " + mask); 320 | } 321 | } 322 | else if(algo == "Scanning") 323 | { 324 | return read_scanning_simulator>( 325 | config, std::move(init)); 326 | } 327 | else 328 | { 329 | throw std::runtime_error("unknown simulation algorhtm: " + algo); 330 | } 331 | } 332 | 333 | } // afmize 334 | 335 | int main(int argc, char** argv) 336 | { 337 | using Real = double; 338 | 339 | if(argc != 2) 340 | { 341 | std::cerr << "usage: afmize \n"; 342 | return 1; 343 | } 344 | 345 | const std::string config_file(argv[1]); 346 | if(config_file.substr(config_file.size() - 5, 5) != ".toml") 347 | { 348 | std::cerr << "afmize requires toml file as an input\n"; 349 | return 1; 350 | } 351 | 352 | const auto config = toml::parse(config_file); 353 | 354 | // ----------------------------------------------------------------------- 355 | // update global parameter, radii 356 | 357 | if(config.contains("radii")) 358 | { 359 | const auto& radii = toml::find(config, "radii"); 360 | for(const auto& kv : toml::find_or(radii, "atom", toml::table{})) 361 | { 362 | afmize::parameter::radius_atom[kv.first] = 363 | afmize::read_as_angstrom(kv.second); 364 | } 365 | for(const auto& res : toml::find_or(radii, "residue", toml::table{})) 366 | { 367 | for(const auto& atm : res.second.as_table()) 368 | { 369 | afmize::parameter::radius_residue[res.first][atm.first] = 370 | afmize::read_as_angstrom(atm.second); 371 | } 372 | } 373 | } 374 | 375 | for(const auto& kv : afmize::parameter::radius_atom) 376 | { 377 | std::cerr << "-- radius of ATOM:" << std::setw(4) << kv.first 378 | << " = " << kv.second << '\n'; 379 | } 380 | for(const auto& kv1 : afmize::parameter::radius_residue) 381 | { 382 | for(const auto& kv2 : kv1.second) 383 | { 384 | std::cerr << "-- radius of ATOM:" << std::setw(4) << kv2.first 385 | << " in RES:" << std::setw(5) << kv1.first 386 | << " = " << kv2.second << '\n'; 387 | } 388 | } 389 | 390 | std::cerr << "reading system..." << std::endl; 391 | afmize::system sys = afmize::read_system(config); 392 | 393 | // align the bottom to z == 0 394 | if(sys.bounding_box.lower[2] != 0) 395 | { 396 | const mave::vector offset{0, 0, sys.bounding_box.lower[2]}; 397 | for(auto& p : sys.particles) 398 | { 399 | p.center -= offset; 400 | } 401 | sys.bounding_box.lower -= offset; 402 | sys.bounding_box.upper -= offset; 403 | } 404 | std::cerr << "done." << std::endl; 405 | std::cerr << "reading simulator ..." << std::endl; 406 | 407 | auto sim = afmize::read_simulator(config, std::move(sys)); 408 | 409 | std::cerr << "done." << std::endl; 410 | 411 | sim->run(); 412 | 413 | return 0; 414 | } 415 | -------------------------------------------------------------------------------- /src/afmize.cpp: -------------------------------------------------------------------------------- 1 | #define TOML11_COLORIZE_ERROR_MESSAGE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace afmize 16 | { 17 | 18 | template 19 | std::unique_ptr> 20 | open_file(const std::string& fname) 21 | { 22 | if(fname.substr(fname.size() - 4, 4) == ".pdb") 23 | { 24 | return std::make_unique>(fname); 25 | } 26 | else if(fname.substr(fname.size() - 6, 6) == ".movie") 27 | { 28 | return std::make_unique>(fname); 29 | } 30 | else if(fname.substr(fname.size() - 4, 4) == ".xyz") 31 | { 32 | return std::make_unique>(fname); 33 | } 34 | else 35 | { 36 | throw std::runtime_error("afmize supports only pdb or xyz"); 37 | } 38 | } 39 | 40 | struct output_format_flags 41 | { 42 | output_format_flags() : csv(false), tsv(false), json(false), ppm(false), svg(false) {} 43 | 44 | static output_format_flags 45 | from_inputs(std::vector inputs) 46 | { 47 | output_format_flags self; 48 | for(const auto& format : inputs) 49 | { 50 | if(format == "csv") 51 | { 52 | set_flag(self.csv, format); 53 | } 54 | if(format == "tsv") 55 | { 56 | set_flag(self.tsv, format); 57 | } 58 | else if(format == "json") 59 | { 60 | set_flag(self.json, format); 61 | } 62 | else if(format == "ppm") 63 | { 64 | set_flag(self.ppm, format); 65 | } 66 | else if(format == "svg") 67 | { 68 | set_flag(self.svg, format); 69 | } 70 | else 71 | { 72 | std::cerr << "warning: file.output.formats contains an invalid flag \"" 73 | << format << "\"\n"; 74 | } 75 | } 76 | return self; 77 | } 78 | 79 | static void 80 | set_flag(bool& flag, const std::string& input) 81 | { 82 | if(flag) 83 | { 84 | std::cerr << "warning: file.output.formats contains duplicated flags \"" 85 | << input << "\"\n"; 86 | } 87 | else 88 | { 89 | flag = true; 90 | } 91 | } 92 | 93 | bool csv; 94 | bool tsv; 95 | bool json; 96 | bool ppm; 97 | bool svg; 98 | }; 99 | 100 | 101 | } // afmize 102 | 103 | int main(int argc, char** argv) 104 | { 105 | using namespace std::literals::string_literals; 106 | 107 | using Real = double; 108 | constexpr Real pi = Real(3.1415926535897932384626); 109 | constexpr Real deg_to_rad = pi / Real(180.0); 110 | 111 | if(argc != 2) 112 | { 113 | std::cerr << "usage: afmize \n"; 114 | return 1; 115 | } 116 | 117 | const std::string config_file(argv[1]); 118 | if(config_file.substr(config_file.size() - 5, 5) != ".toml") 119 | { 120 | std::cerr << "afmize requires toml file as an input\n"; 121 | return 1; 122 | } 123 | 124 | std::cerr << "reading config file..." << std::endl; 125 | 126 | const auto config = toml::parse(config_file); 127 | 128 | if(config.as_table().count("radii") == 1) 129 | { 130 | const auto& radii = toml::find(config, "radii"); 131 | for(const auto& kv : toml::find_or(radii, "atom", toml::table{})) 132 | { 133 | afmize::parameter::radius_atom[kv.first] = 134 | afmize::read_as_angstrom(kv.second); 135 | } 136 | for(const auto& res : toml::find_or(radii, "residue", toml::table{})) 137 | { 138 | for(const auto& atm : res.second.as_table()) 139 | { 140 | afmize::parameter::radius_residue[res.first][atm.first] = 141 | afmize::read_as_angstrom(atm.second); 142 | } 143 | } 144 | } 145 | 146 | for(const auto& kv : afmize::parameter::radius_atom) 147 | { 148 | std::cerr << "-- radius of ATOM:" << std::setw(4) << kv.first 149 | << " = " << kv.second << '\n'; 150 | } 151 | for(const auto& kv1 : afmize::parameter::radius_residue) 152 | { 153 | for(const auto& kv2 : kv1.second) 154 | { 155 | std::cerr << "-- radius of ATOM:" << std::setw(4) << kv2.first 156 | << " in RES:" << std::setw(5) << kv1.first 157 | << " = " << kv2.second << '\n'; 158 | } 159 | } 160 | 161 | // read input output files 162 | const auto& file = toml::find(config, "file"); 163 | const auto reader = afmize::open_file( 164 | toml::find(file, "input") 165 | ); 166 | const auto& output = toml::find(file, "output"); 167 | const auto output_basename = toml::find(output, "basename"); 168 | const auto output_formats = afmize::output_format_flags::from_inputs( 169 | toml::find>(output, "formats")); 170 | 171 | std::cerr << "-- " << reader->size() << " snapshots are found\n"; 172 | 173 | // image size information 174 | const auto& resolution = toml::find(config, "resolution"); 175 | const auto& range = toml::find(config, "range"); 176 | const auto range_x = toml::find>(range, "x"); 177 | const auto range_y = toml::find>(range, "y"); 178 | afmize::stage stg( 179 | afmize::read_as_angstrom(toml::find(resolution, "x")), 180 | afmize::read_as_angstrom(toml::find(resolution, "y")), 181 | afmize::read_as_angstrom(toml::find(resolution, "z")), 182 | std::make_pair(afmize::read_as_angstrom(range_x[0]), 183 | afmize::read_as_angstrom(range_x[1])), 184 | std::make_pair(afmize::read_as_angstrom(range_y[0]), 185 | afmize::read_as_angstrom(range_y[1])) 186 | ); 187 | 188 | const auto scale_bar_length = afmize::read_as_angstrom( 189 | toml::find_or(config, "scale_bar", toml::value{{"length", toml::value(0.0)}}).at("length")); 190 | 191 | // probe size information 192 | const auto& probe_tab = toml::find(config, "probe"); 193 | const auto& probe_size = toml::find(probe_tab, "size"); 194 | afmize::default_probe probe{ 195 | toml::find(probe_size, "angle") * deg_to_rad, 196 | afmize::read_as_angstrom(toml::find(probe_size, "radius")), 197 | mave::vector{0, 0, 0} 198 | }; 199 | 200 | // stage information ... 201 | const toml::value nan_v(std::numeric_limits::quiet_NaN()); 202 | const toml::value stage_tab = toml::find_or(config, "stage", toml::value(toml::table{})); 203 | const bool stage_align = toml::find_or(stage_tab, "align", false); 204 | const Real stage_position = [&]() -> Real { 205 | if(stage_tab.contains("position")) 206 | { 207 | return afmize::read_as_angstrom(toml::find(stage_tab, "position")); 208 | } 209 | else 210 | { 211 | return std::numeric_limits::quiet_NaN(); 212 | } 213 | }(); 214 | 215 | if(stage_align && std::isnan(stage_position)) 216 | { 217 | std::cerr << toml::format_error("[error] " 218 | "`stage.align` requires `stage.position`.", stage_tab, 219 | "in this table", {"In order to align system on the stage, " 220 | "you need to input the z-coordinate position of your stage." 221 | }) << std::endl; 222 | return EXIT_FAILURE; 223 | } 224 | 225 | const Real noise_sigma = [&]() -> Real { 226 | if(config.contains("noise")) 227 | { 228 | return afmize::read_as_angstrom(toml::find(config, "noise")); 229 | } 230 | else 231 | { 232 | return 0.0; 233 | } 234 | }(); 235 | std::random_device dev; 236 | std::mt19937 rng(dev()); 237 | 238 | // color range information ... 239 | const auto& cmap = toml::find_or(config, "colormap", toml::value{}); 240 | const auto cmap_min = afmize::read_as_angstrom( 241 | toml::find_or(cmap, "min", nan_v)); 242 | const auto cmap_max = afmize::read_as_angstrom( 243 | toml::find_or(cmap, "max", nan_v)); 244 | 245 | // image generation method 246 | const auto method = toml::find_or(config, "method", "rigid"); 247 | if(method != "rigid" && method != "smooth") 248 | { 249 | std::cerr << toml::format_error("[error] unknown method: " + method, 250 | config, "here", {"expected one of the followings.", 251 | "- \"rigid\" : well-known rigid body collidion based method (default)", 252 | "- \"smooth\": smooth function approximation [T. Niina et al., JCTC (2020)]" 253 | }) << std::endl; 254 | 255 | return EXIT_FAILURE; 256 | } 257 | 258 | const auto sigma_x_v = 259 | config.contains("sigma_x") ? toml::find(config, "sigma_x") : 260 | config.contains("sigma") ? toml::find(config, "sigma") : nan_v; 261 | const auto sigma_y_v = 262 | config.contains("sigma_y") ? toml::find(config, "sigma_y") : 263 | config.contains("sigma") ? toml::find(config, "sigma") : nan_v; 264 | 265 | if(config.contains("sigma") && config.contains("sigma_x")) 266 | { 267 | std::cout << toml::format_error( 268 | "when \"sigma_x\" is defined, \"sigma\" will be ignored.", 269 | config.at("sigma_x"), "sigma for x is defined here", 270 | config.at("sigma"), "the default sigma will be ignored"); 271 | } 272 | if(config.contains("sigma") && config.contains("sigma_y")) 273 | { 274 | std::cout << toml::format_error( 275 | "when \"sigma_y\" is defined, \"sigma\" will be ignored.", 276 | config.at("sigma_y"), "sigma for y is defined here", 277 | config.at("sigma"), "the default sigma will be ignored"); 278 | } 279 | 280 | const auto sigma_x = afmize::read_as_angstrom(sigma_x_v); 281 | const auto sigma_y = afmize::read_as_angstrom(sigma_y_v); 282 | const auto gamma = afmize::read_as_angstrom( 283 | toml::find_or(config, "gamma", nan_v)); 284 | 285 | afmize::progress_bar<70> bar( 286 | (reader->size() == 1) ? stg.x_pixel() * stg.y_pixel() : reader->size() 287 | ); 288 | 289 | std::cerr << "done. creating image..." << std::endl; 290 | auto img = stg.create_image(); 291 | try 292 | { 293 | std::size_t index = 0; 294 | while(!reader->is_eof()) 295 | { 296 | if(reader->size() != 1) 297 | { 298 | std::cerr << bar.format(index); 299 | } 300 | 301 | const auto sys = [=](auto sys) -> afmize::system { 302 | if(stage_align) // align the lower edge of bounding box to stage 303 | { 304 | assert(!std::isnan(stage_position)); 305 | const auto dz = sys.bounding_box.lower[2] - stage_position; 306 | for(auto& p : sys.particles) 307 | { 308 | p.center[2] -= dz; 309 | } 310 | } 311 | return sys; 312 | }(afmize::system(reader->read_snapshot(), stg)); 313 | 314 | const Real bottom = std::isnan(stage_position) ? 315 | sys.bounding_box.lower[2] : stage_position; 316 | 317 | const Real initial_z = sys.bounding_box.upper[2] + probe.radius; 318 | 319 | for(std::size_t j=0; jsize() == 1) 351 | { 352 | std::cerr << bar.format(j * stg.x_pixel() + i); 353 | } 354 | } 355 | } 356 | 357 | if(noise_sigma != 0.0) 358 | { 359 | afmize::apply_noise(img, rng, noise_sigma, stage_position); 360 | } 361 | 362 | if(reader->size() == 1) 363 | { 364 | std::cerr << bar.format(stg.y_pixel() * stg.x_pixel()); 365 | } 366 | 367 | std::string outname(output_basename); 368 | if(reader->size() != 1) 369 | { 370 | std::ostringstream oss; 371 | oss << '_' << std::setfill('0') 372 | << std::setw(std::to_string(reader->size()).size()) << index; 373 | outname += oss.str(); 374 | } 375 | 376 | if(output_formats.csv) 377 | { 378 | afmize::write_csv (outname, img); 379 | } 380 | if(output_formats.tsv) 381 | { 382 | afmize::write_tsv (outname, img); 383 | } 384 | 385 | if(output_formats.json) 386 | { 387 | afmize::write_json(outname, img, sys); 388 | } 389 | 390 | if(std::isnan(cmap_min) || std::isnan(cmap_max)) 391 | { 392 | if(output_formats.ppm) 393 | { 394 | afmize::write_ppm (outname, img); 395 | } 396 | if(output_formats.svg) 397 | { 398 | afmize::write_svg (outname, img, sys, scale_bar_length); 399 | } 400 | } 401 | else 402 | { 403 | if(output_formats.ppm) 404 | { 405 | afmize::write_ppm(outname, img, std::make_pair(cmap_min, cmap_max)); 406 | } 407 | if(output_formats.svg) 408 | { 409 | afmize::write_svg(outname, img, sys, scale_bar_length, 410 | std::make_pair(cmap_min, cmap_max)); 411 | } 412 | } 413 | ++index; 414 | } 415 | } 416 | catch(afmize::reader_base::no_more_model) 417 | { 418 | ; // do nothing 419 | } 420 | std::cerr << "done." << std::endl; 421 | return 0; 422 | } 423 | -------------------------------------------------------------------------------- /include/afmize/scanning_simulator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_SCANNING_SIMULATOR_HPP 2 | #define AFMIZE_SCANNING_SIMULATOR_HPP 3 | 4 | #include "simulator_base.hpp" 5 | #include "progress_bar.hpp" 6 | #include "observe.hpp" 7 | #include "shapes.hpp" 8 | #include "system.hpp" 9 | #include "stage.hpp" 10 | #include "score.hpp" 11 | #include "output_utility.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace afmize 19 | { 20 | 21 | template 22 | struct ScanningSimulator : public SimulatorBase 23 | { 24 | constexpr static Real pi = 3.14159265; 25 | using mask_type = mask_by_rectangle; 26 | 27 | struct location 28 | { 29 | mave::matrix rot; 30 | mave::vector trans; 31 | }; 32 | 33 | ScanningSimulator(const std::size_t num_div, const std::size_t num_save, 34 | Real dz_, 35 | image ref, system sys, 36 | std::unique_ptr> obs, 37 | std::unique_ptr> score, 38 | std::string out) 39 | : step_(0), num_save_(num_save), next_output_(0.0), doutput_percent_(1.0), 40 | num_div_(num_div), 41 | dtheta_(2 * 3.14159265 / num_div), 42 | dz_(dz_), 43 | max_height_(*std::max_element(ref.begin(), ref.end())), 44 | img_(ref.x_pixel(), ref.y_pixel()), 45 | reference_(std::move(ref)), 46 | sys_(sys), 47 | init_(sys), 48 | obs_(std::move(obs)), 49 | score_(std::move(score)), 50 | bar_(0), 51 | output_basename_(std::move(out)) 52 | { 53 | sys_.cells.initialize(sys.stage_info.x_resolution(), 54 | sys.stage_info.y_resolution(), 55 | sys.particles); 56 | sys_.cells.construct(sys_.particles, sys_.bounding_box); 57 | init_.cells.initialize(sys.stage_info.x_resolution(), 58 | sys.stage_info.y_resolution(), 59 | sys.particles); 60 | init_.cells.construct(init_.particles, init_.bounding_box); 61 | this->center_[0] = 0.0; 62 | this->center_[1] = 0.0; 63 | this->center_[2] = 0.0; 64 | for(const auto& p : this->sys_.particles) 65 | { 66 | this->center_ += p.center; 67 | } 68 | this->center_ /= static_cast(sys_.particles.size()); 69 | 70 | // -------------------------------------------------------------------- 71 | // cache scanning angles 72 | if(num_div_ % 4 != 0) 73 | { 74 | throw std::runtime_error("Scanning: number of divisions should be " 75 | "a multiple of 4."); 76 | } 77 | const auto drot_y = 2 * pi / num_div_; 78 | 79 | // +z 80 | this->axes_rot_.push_back(mave::matrix( 1.0, 0.0, 0.0, 81 | 0.0, 1.0, 0.0, 82 | 0.0, 0.0, 1.0)); 83 | // upper halves 84 | for(std::size_t i=1; i rot_y1( cos_y, 0.0, sin_y, 89 | 0.0, 1.0, 0.0, 90 | -sin_y, 0.0, cos_y); 91 | const auto drot_z = (2 * pi) / (i * 4); 92 | for(std::size_t j=0; j rot_z(cos_z, -sin_z, 0.0, 98 | sin_z, cos_z, 0.0, 99 | 0.0, 0.0, 1.0); 100 | axes_rot_.push_back(rot_z * rot_y1); 101 | } 102 | } 103 | // on Equator 104 | { 105 | const auto cos_y = 0.0; 106 | const auto sin_y = 1.0; 107 | const mave::matrix rot_y( cos_y, 0.0, sin_y, 108 | 0.0, 1.0, 0.0, 109 | -sin_y, 0.0, cos_y); 110 | const auto drot_z = 2 * pi / num_div_; 111 | for(std::size_t j=0; j rot_z(cos_z, -sin_z, 0.0, 117 | sin_z, cos_z, 0.0, 118 | 0.0, 0.0, 1.0); 119 | axes_rot_.push_back(rot_z * rot_y); 120 | } 121 | } 122 | // -z 123 | this->axes_rot_.push_back(mave::matrix(-1.0, 0.0, 0.0, 124 | 0.0, -1.0, 0.0, 125 | 0.0, 0.0, -1.0)); 126 | // lower halves 127 | for(std::size_t i=1; i rot_y2(-cos_y, 0.0, sin_y, 132 | 0.0, 1.0, 0.0, 133 | -sin_y, 0.0, -cos_y); 134 | 135 | const auto drot_z = (2 * pi) / (i * 4); 136 | for(std::size_t j=0; j rot_z(cos_z, -sin_z, 0.0, 142 | sin_z, cos_z, 0.0, 143 | 0.0, 0.0, 1.0); 144 | 145 | axes_rot_.push_back(rot_z * rot_y2); 146 | } 147 | } 148 | 149 | if(axes_rot_.size() > 10000) 150 | { 151 | doutput_percent_ = 0.1; 152 | } 153 | else 154 | { 155 | doutput_percent_ = 1.0; 156 | } 157 | 158 | std::cout << axes_rot_.size() << " rotational configuration will be searched" << std::endl; 159 | 160 | // setup progress bar 161 | bar_.reset_total(axes_rot_.size()); 162 | } 163 | ~ScanningSimulator() override = default; 164 | 165 | void run() override 166 | { 167 | while(this->step()) {} 168 | } 169 | bool run(const std::size_t steps) override 170 | { 171 | const auto until = std::min(this->step_ + steps, this->axes_rot_.size()); 172 | while(step_ < until) 173 | { 174 | this->step(); 175 | } 176 | return step_ < this->axes_rot_.size(); 177 | } 178 | 179 | bool step() override 180 | { 181 | std::cerr << bar_.format(this->step_); 182 | if(this->step_ == this->axes_rot_.size()) 183 | { 184 | this->output_status(); 185 | return false; 186 | } 187 | 188 | mave::vector vtx1(0.0, 0.0, 1.0); 189 | mave::vector vtx2(0.0, 0.0, 0.0); 190 | mave::vector vtx3(1.0, 0.0, 0.0); 191 | 192 | const auto n = axes_rot_.at(this->step_) * 193 | mave::vector(0.0, 0.0, 1.0); 194 | const auto dtheta = 2 * pi / num_div_; 195 | for(std::size_t i=0; i rot; 202 | rot.zero(); 203 | rot(0, 0) = n[0] * n[0] * (1.0 - cos_t) + cos_t; 204 | rot(0, 1) = n[0] * n[1] * (1.0 - cos_t) - n[2] * sin_t; 205 | rot(0, 2) = n[0] * n[2] * (1.0 - cos_t) + n[1] * sin_t; 206 | 207 | rot(1, 0) = n[1] * n[0] * (1.0 - cos_t) + n[2] * sin_t; 208 | rot(1, 1) = n[1] * n[1] * (1.0 - cos_t) + cos_t; 209 | rot(1, 2) = n[1] * n[2] * (1.0 - cos_t) - n[0] * sin_t; 210 | 211 | rot(2, 0) = n[2] * n[0] * (1.0 - cos_t) - n[1] * sin_t; 212 | rot(2, 1) = n[2] * n[1] * (1.0 - cos_t) + n[0] * sin_t; 213 | rot(2, 2) = n[2] * n[2] * (1.0 - cos_t) + cos_t; 214 | 215 | const auto mat = rot * axes_rot_.at(this->step_); 216 | 217 | for(auto& p : this->sys_.particles) 218 | { 219 | p.center -= this->center_; 220 | p.center = mat * p.center; 221 | p.center += this->center_; 222 | } 223 | 224 | sys_.bounding_box = make_bounding_box(sys_.particles); 225 | // align the bottom to the xy plane (z=0.0) 226 | for(auto& p : this->sys_.particles) 227 | { 228 | p.center[2] -= sys_.bounding_box.lower[2]; 229 | } 230 | sys_.bounding_box.upper[2] -= sys_.bounding_box.lower[2]; 231 | sys_.bounding_box.lower[2] = 0.0; 232 | 233 | sys_.cells.construct(sys_.particles, sys_.bounding_box); 234 | 235 | const auto v1 = mat * vtx1; 236 | const auto v2 = mat * vtx2; 237 | const auto v3 = mat * vtx3; 238 | 239 | this->scan_translation(location{mat, mave::vector(0.0, 0.0, 0.0)}); 240 | } 241 | this->step_ += 1; 242 | return true; 243 | } 244 | 245 | system const& current_state() const noexcept override {return sys_;} 246 | system& current_state() noexcept override {return sys_;} 247 | 248 | image const& current_image() const noexcept override 249 | { 250 | return img_; 251 | } 252 | 253 | std::unique_ptr>& observer() noexcept {return obs_;} 254 | 255 | std::size_t total_step() const noexcept override {return this->axes_rot_.size();} 256 | std::size_t current_step() const noexcept override {return step_;} 257 | 258 | private: 259 | 260 | void scan_translation(location loc) 261 | { 262 | const std::size_t z_len = 1 + std::ceil( 263 | std::max(0.0, this->max_height_ - sys_.bounding_box.upper[2]) / 264 | this->dz_); 265 | 266 | for(std::size_t z_ofs=0; z_ofs < z_len; ++z_ofs) 267 | { 268 | loc.trans[2] = this->dz_ * z_ofs; 269 | auto img = obs_->observe(sys_); 270 | 271 | const mask_type mask(img); 272 | const std::size_t x_rem = reference_.x_pixel() - mask.pixel_x(); 273 | const std::size_t y_rem = reference_.y_pixel() - mask.pixel_y(); 274 | 275 | for(std::size_t y_ofs=0; y_ofs < y_rem; ++y_ofs) 276 | { 277 | const std::int64_t y_offset(y_ofs); 278 | const std::int64_t y_lowerb(mask.lower_bounding_y()); 279 | 280 | loc.trans[1] = (y_offset - y_lowerb) * sys_.stage_info.y_resolution(); 281 | for(std::size_t x_ofs=0; x_ofs < x_rem; ++x_ofs) 282 | { 283 | const std::int64_t x_offset(x_ofs); 284 | const std::int64_t x_lowerb(mask.lower_bounding_x()); 285 | loc.trans[0] = (x_offset - x_lowerb) * sys_.stage_info.x_resolution(); 286 | 287 | const mask_type target_mask(sys_, x_ofs, mask.pixel_x(), 288 | y_ofs, mask.pixel_y()); 289 | const auto penalty = this->score_->calc(sys_, img, mask, reference_, target_mask); 290 | 291 | const auto found = std::lower_bound(high_score_.begin(), high_score_.end(), 292 | penalty, [](const auto& lhs, const Real& p) {return lhs.second < p;}); 293 | 294 | if(high_score_.size() < num_save_ || found != high_score_.end()) 295 | { 296 | high_score_.insert(found, std::make_pair(loc, penalty)); 297 | if(num_save_ < high_score_.size()) 298 | { 299 | // when we add one configuration, we remove the 300 | // worst one to keep the total number same. 301 | high_score_.pop_back(); 302 | } 303 | } 304 | } 305 | } 306 | for(auto& p : sys_.particles) 307 | { 308 | p.center[2] += this->dz_; 309 | } 310 | } 311 | return; 312 | } 313 | void output_status() 314 | { 315 | std::cerr << "\nwriting best " << high_score_.size() << " conformations ... "; 316 | std::ofstream trj(output_basename_ + ".xyz"); 317 | std::ofstream ene(output_basename_ + ".log"); 318 | std::ofstream rtr(output_basename_ + "_rot_trans.dat"); 319 | ene << "# idx energy\n"; 320 | 321 | const auto width = std::to_string(high_score_.size()).size(); 322 | 323 | std::size_t idx = 0; 324 | for(const auto& best : high_score_) 325 | { 326 | sys_ = init_; 327 | 328 | const auto& loc = best.first; 329 | const auto& rot = loc.rot; 330 | 331 | for(auto& p : this->sys_.particles) 332 | { 333 | p.center -= this->center_; 334 | p.center = rot * p.center; 335 | p.center += this->center_; 336 | } 337 | sys_.bounding_box = make_bounding_box(sys_.particles); 338 | // align the bottom to the xy plane (z=0.0) 339 | for(auto& p : this->sys_.particles) 340 | { 341 | p.center[2] -= sys_.bounding_box.lower[2]; 342 | } 343 | sys_.bounding_box.upper[2] -= sys_.bounding_box.lower[2]; 344 | sys_.bounding_box.lower[2] = 0.0; 345 | 346 | // align the bottom to the xy plane (z=0.0) 347 | for(auto& p : this->sys_.particles) 348 | { 349 | p.center += loc.trans; 350 | } 351 | sys_.bounding_box.upper += loc.trans; 352 | sys_.bounding_box.lower += loc.trans; 353 | 354 | sys_.cells.construct(sys_.particles, sys_.bounding_box); 355 | 356 | const auto& img = obs_->observe(sys_); 357 | std::ostringstream oss; 358 | oss << output_basename_ << "_" << std::setw(width) << std::setfill('0') << idx; 359 | 360 | afmize::write_svg(oss.str(), img, sys_, 0.0); 361 | afmize::write_tsv(oss.str(), img); 362 | afmize::write_xyz(output_basename_, this->sys_); 363 | 364 | ene << idx++ << ' ' << best.second << '\n'; 365 | 366 | rtr << loc.rot(0,0) << ' ' << loc.rot(0,1) << ' ' << loc.rot(0,2) << ' '; 367 | rtr << loc.rot(1,0) << ' ' << loc.rot(1,1) << ' ' << loc.rot(1,2) << ' '; 368 | rtr << loc.rot(2,0) << ' ' << loc.rot(2,1) << ' ' << loc.rot(2,2) << ' '; 369 | rtr << loc.trans[0] << ' ' << loc.trans[1] << ' ' << loc.trans[2] << '\n'; 370 | } 371 | std::cerr << "done.\n"; 372 | } 373 | 374 | private: 375 | std::size_t step_; 376 | std::size_t num_save_; 377 | Real next_output_; 378 | Real doutput_percent_; 379 | 380 | std::size_t num_div_; 381 | Real dtheta_; 382 | std::vector> axes_rot_; 383 | mave::vector center_; 384 | 385 | Real dz_; 386 | Real max_height_; 387 | 388 | // pairof{pairof{axis rotation, rotation around axis}, score} 389 | std::vector> high_score_; 390 | 391 | image img_; 392 | image reference_; 393 | system sys_; 394 | system init_; 395 | std::unique_ptr> obs_; 396 | std::unique_ptr> score_; 397 | afmize::progress_bar<70> bar_; 398 | std::string output_basename_; 399 | }; 400 | 401 | } // afmize 402 | #endif// AFMIZE_SIMULATOR_HPP 403 | -------------------------------------------------------------------------------- /include/afmize/annealing_simulator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef AFMIZE_ANNEALING_SIMULATOR_HPP 2 | #define AFMIZE_ANNEALING_SIMULATOR_HPP 3 | #include "simulator_base.hpp" 4 | #include "progress_bar.hpp" 5 | #include "output_utility.hpp" 6 | #include "observe.hpp" 7 | #include "shapes.hpp" 8 | #include "system.hpp" 9 | #include "stage.hpp" 10 | #include "score.hpp" 11 | #include 12 | #include 13 | #include 14 | 15 | namespace afmize 16 | { 17 | 18 | template 19 | struct ScheduleBase 20 | { 21 | virtual ~ScheduleBase() = default; 22 | virtual Real temperature(const std::size_t tstep) const noexcept = 0; 23 | virtual Real init_temperature() const noexcept = 0; 24 | virtual Real last_temperature() const noexcept = 0; 25 | virtual void set_init_temperature(const Real) = 0; 26 | virtual void set_last_temperature(const Real) = 0; 27 | }; 28 | 29 | template 30 | struct LinearSchedule final: public ScheduleBase 31 | { 32 | LinearSchedule( 33 | const Real init, const Real last, const std::size_t total_step) 34 | : init_(init), last_(last), total_step_(total_step) 35 | {} 36 | ~LinearSchedule() override = default; 37 | 38 | Real temperature(const std::size_t tstep) const noexcept override 39 | { 40 | const auto t = static_cast(tstep) / static_cast(total_step_); 41 | return t * (last_ - init_) + init_; 42 | } 43 | 44 | Real init_temperature() const noexcept override {return init_;} 45 | Real last_temperature() const noexcept override {return last_;} 46 | 47 | void set_init_temperature(const Real init) override 48 | { 49 | this->init_ = init; 50 | } 51 | void set_last_temperature(const Real last) override 52 | { 53 | this->last_ = last; 54 | } 55 | 56 | private: 57 | Real init_; 58 | Real last_; 59 | std::size_t total_step_; 60 | }; 61 | 62 | template 63 | struct ExponentialSchedule final: public ScheduleBase 64 | { 65 | ExponentialSchedule( 66 | const Real init, const Real last, const std::size_t total_step) 67 | : init_(init), last_(last), coef_(std::log(last / init)), 68 | total_step_(total_step) 69 | { 70 | if(last_ <= 0.0) 71 | { 72 | last_ = 1e-12; 73 | } 74 | } 75 | ~ExponentialSchedule() override = default; 76 | 77 | Real temperature(const std::size_t tstep) const noexcept override 78 | { 79 | const auto t = static_cast(tstep) / static_cast(total_step_); 80 | return init_ * std::exp(coef_ * t); 81 | } 82 | 83 | Real init_temperature() const noexcept override {return init_;} 84 | Real last_temperature() const noexcept override {return last_;} 85 | 86 | void set_init_temperature(const Real init) override 87 | { 88 | this->init_ = init; 89 | if(this->init_ <= 0) 90 | { 91 | this->init_ = 1e-12; 92 | } 93 | coef_ = std::log(last_ / init_); 94 | } 95 | void set_last_temperature(const Real last) override 96 | { 97 | this->last_ = last; 98 | if(this->last_ <= 0) 99 | { 100 | this->last_ = 1e-12; 101 | } 102 | coef_ = std::log(last_ / init_); 103 | } 104 | 105 | private: 106 | Real init_; 107 | Real last_; 108 | Real coef_; 109 | std::size_t total_step_; 110 | }; 111 | 112 | // Brownian Movement under Simulated Annealing with given score function and mask. 113 | template 114 | struct SimulatedAnnealingSimulator : public SimulatorBase 115 | { 116 | SimulatedAnnealingSimulator(const std::size_t total_step, 117 | const std::size_t save, 118 | const std::uint32_t seed, 119 | const Real sigma_x, const Real sigma_y, const Real sigma_z, 120 | const Real max_rotx, const Real max_roty, const Real max_rotz, 121 | const std::map& max_dprobe, 122 | image ref, 123 | system sys, 124 | std::unique_ptr> obs, 125 | std::unique_ptr> score, 126 | std::unique_ptr> schedule, 127 | const std::string& out) 128 | : step_(0), 129 | total_step_(total_step), 130 | save_step_(save), 131 | sigma_dx_(sigma_x), 132 | sigma_dy_(sigma_y), 133 | sigma_dz_(sigma_z), 134 | max_drotx_(max_rotx), 135 | max_droty_(max_roty), 136 | max_drotz_(max_rotz), 137 | dprobe_(max_dprobe), 138 | rng_(seed), 139 | nrm_(0.0, 1.0), 140 | reference_(std::move(ref)), 141 | sys_(sys), 142 | next_(sys), 143 | obs_(std::move(obs)), 144 | score_(std::move(score)), 145 | schedule_(std::move(schedule)), 146 | bar_(total_step), 147 | output_basename_(out) 148 | { 149 | sys_.cells.initialize(sys.stage_info.x_resolution(), 150 | sys.stage_info.y_resolution(), 151 | sys.particles); 152 | sys_.cells.construct(sys_.particles, sys_.bounding_box); 153 | this->img_ = obs_->observe(sys_); 154 | current_energy_ = score_->calc(sys_, img_, Mask(img_), reference_, Mask(img_)); 155 | 156 | // clear output content 157 | { 158 | std::ofstream trj(output_basename_ + ".xyz"); 159 | } 160 | { 161 | std::ofstream ene(output_basename_ + ".log"); 162 | ene << "# step energy probe_shape\n"; 163 | } 164 | 165 | if(schedule_->init_temperature() < 0.0) 166 | { 167 | this->warm_up(); 168 | } 169 | } 170 | ~SimulatedAnnealingSimulator() override = default; 171 | 172 | void run() override 173 | { 174 | while(this->step()) {} 175 | return ; 176 | } 177 | 178 | bool run(const std::size_t steps) override 179 | { 180 | const auto until = std::min(this->step_ + steps, this->total_step_); 181 | while(step_ < until) 182 | { 183 | this->step(); 184 | } 185 | return step_ < total_step_; 186 | } 187 | 188 | bool step() override 189 | { 190 | const auto temperature = schedule_->temperature(step_); 191 | const auto beta = 1.0 / temperature; 192 | 193 | if(this->step_ >= total_step_) 194 | { 195 | // -------------------------------------------------------------------- 196 | // output final result 197 | 198 | afmize::write_xyz(output_basename_, this->current_state()); 199 | afmize::write_ppm(output_basename_ + "_result", this->current_image()); 200 | afmize::write_tsv(output_basename_ + "_result", this->current_image()); 201 | 202 | std::ofstream ene(output_basename_ + ".log", std::ios::app); 203 | ene << this->step_ << " " << this->current_energy_ << " "; 204 | obs_->print_probe(ene); 205 | ene << " " << temperature << "\n"; 206 | std::cerr << bar_.format(this->step_); 207 | 208 | return false; 209 | } 210 | 211 | if(this->step_ % save_step_ == 0) 212 | { 213 | const auto fname = output_basename_ + "_" + std::to_string(this->current_step()); 214 | afmize::write_xyz(output_basename_, this->current_state()); 215 | afmize::write_ppm(fname, this->current_image()); 216 | afmize::write_tsv(fname, this->current_image()); 217 | 218 | std::ofstream ene(output_basename_ + ".log", std::ios::app); 219 | ene << this->step_ << " " << this->current_energy_ << " "; 220 | obs_->print_probe(ene); 221 | ene << " " << temperature << "\n"; 222 | 223 | std::cerr << bar_.format(this->step_); 224 | } 225 | 226 | // translation 227 | 228 | const auto dx = nrm_(rng_) * sigma_dx_; 229 | const auto dy = nrm_(rng_) * sigma_dy_; 230 | // const auto dz = nrm_(rng_) * sigma_dz_; 231 | // std::cerr << "dxy = " << dx << ", " << dy << std::endl; 232 | this->try_translation(mave::vector(dx, dy, 0.0), beta); 233 | 234 | // rotation 235 | mave::matrix mat; 236 | 237 | // around x 238 | const auto rot_x = (this->generate_01() * 2.0 - 1.0) * max_drotx_; 239 | const auto cos_x = std::cos(rot_x); 240 | const auto sin_x = std::sin(rot_x); 241 | // std::cerr << "x rotation = " << rot_x << std::endl; 242 | mat.zero(); 243 | mat(0, 0) = 1.0; 244 | mat(1, 1) = cos_x; 245 | mat(1, 2) = -sin_x; 246 | mat(2, 1) = sin_x; 247 | mat(2, 2) = cos_x; 248 | this->try_rotation(mat, beta); 249 | 250 | // rot around y 251 | const auto rot_y = (this->generate_01() * 2.0 - 1.0) * max_droty_; 252 | const auto cos_y = std::cos(rot_y); 253 | const auto sin_y = std::sin(rot_y); 254 | // std::cerr << "y rotation = " << rot_y << std::endl; 255 | mat.zero(); 256 | mat(0, 0) = cos_y; 257 | mat(0, 2) = sin_y; 258 | mat(1, 1) = 1.0; 259 | mat(2, 0) = -sin_y; 260 | mat(2, 2) = cos_y; 261 | this->try_rotation(mat, beta); 262 | 263 | // rot around z 264 | const auto rot_z = (this->generate_01() * 2.0 - 1.0) * max_drotz_; 265 | const auto cos_z = std::cos(rot_z); 266 | const auto sin_z = std::sin(rot_z); 267 | // std::cerr << "z rotation = " << rot_z << std::endl; 268 | mat.zero(); 269 | mat(0, 0) = cos_z; 270 | mat(0, 1) = -sin_z; 271 | mat(1, 0) = sin_z; 272 | mat(1, 1) = cos_z; 273 | mat(2, 2) = 1.0; 274 | this->try_rotation(mat, beta); 275 | 276 | // try to change probe 277 | this->try_probe_change(beta); 278 | 279 | this->step_ += 1; 280 | return true; 281 | } 282 | 283 | system const& current_state() const noexcept override {return sys_;} 284 | system& current_state() noexcept override {return sys_;} 285 | 286 | image const& current_image() const noexcept override 287 | { 288 | return img_; 289 | } 290 | 291 | std::unique_ptr>& observer() noexcept {return obs_;} 292 | 293 | std::size_t total_step() const noexcept override {return total_step_;} 294 | std::size_t current_step() const noexcept override {return step_;} 295 | 296 | Real& sigma_dx() noexcept {return sigma_dx_;} 297 | Real sigma_dx() const noexcept {return sigma_dx_;} 298 | 299 | Real& sigma_dy() noexcept {return sigma_dy_;} 300 | Real sigma_dy() const noexcept {return sigma_dy_;} 301 | 302 | Real& sigma_dz() noexcept {return sigma_dz_;} 303 | Real sigma_dz() const noexcept {return sigma_dz_;} 304 | 305 | Real& max_drotx() noexcept {return max_drotx_;} 306 | Real max_drotx() const noexcept {return max_drotx_;} 307 | 308 | Real& max_droty() noexcept {return max_droty_;} 309 | Real max_droty() const noexcept {return max_droty_;} 310 | 311 | Real& max_drotz() noexcept {return max_drotz_;} 312 | Real max_drotz() const noexcept {return max_drotz_;} 313 | 314 | private: 315 | 316 | void try_translation(mave::vector dr, const Real beta) 317 | { 318 | next_ = sys_; 319 | 320 | this->translate(dr, next_); 321 | 322 | // avoid particle from escaping observation stage. 323 | // To fix the number of pixels when calculating score, it does not 324 | // allow particle sticks out of the stage region. 325 | if(next_.bounding_box.lower[0] < sys_.stage_info.x_range().first || 326 | next_.bounding_box.lower[1] < sys_.stage_info.y_range().first || 327 | sys_.stage_info.x_range().second < next_.bounding_box.upper[0] || 328 | sys_.stage_info.y_range().second < next_.bounding_box.upper[1]) 329 | { 330 | return ; 331 | } 332 | 333 | // calculate score depending on score function 334 | const auto& next_img = obs_->observe(next_); 335 | const auto energy = score_->calc(next_, obs_->get_image(), Mask(next_img), reference_, Mask(next_img)); 336 | const auto deltaE = energy - current_energy_; 337 | 338 | if(deltaE <= 0.0 || this->generate_01() < std::exp(-deltaE * beta)) 339 | { 340 | sys_ = next_; 341 | current_energy_ = energy; 342 | img_ = obs_->get_image(); 343 | } 344 | return; 345 | } 346 | 347 | void try_rotation(const mave::matrix& rot, const Real beta) 348 | { 349 | next_ = sys_; 350 | 351 | this->rotate(rot, next_); 352 | 353 | // avoid particle from escaping observation stage 354 | if(next_.bounding_box.lower[0] < sys_.stage_info.x_range().first || 355 | next_.bounding_box.lower[1] < sys_.stage_info.y_range().first || 356 | sys_.stage_info.x_range().second < next_.bounding_box.upper[0] || 357 | sys_.stage_info.y_range().second < next_.bounding_box.upper[1]) 358 | { 359 | return ; 360 | } 361 | 362 | // calculate score depending on score function 363 | const auto& next_img = obs_->observe(next_); 364 | const auto energy = score_->calc(next_, obs_->get_image(), Mask(next_img), 365 | reference_, Mask(next_img)); 366 | const auto deltaE = energy - current_energy_; 367 | 368 | if(deltaE <= 0.0 || this->generate_01() < std::exp(-deltaE * beta)) 369 | { 370 | sys_ = next_; 371 | current_energy_ = energy; 372 | img_ = obs_->get_image(); 373 | } 374 | return; 375 | } 376 | 377 | void try_probe_change(const Real beta) 378 | { 379 | const auto prev_state = obs_->get_probe(); 380 | auto probe_state = prev_state; 381 | for(auto& kv : probe_state) 382 | { 383 | kv.second += (this->generate_01() * 2.0 - 1.0) * dprobe_.at(kv.first); 384 | } 385 | if( ! obs_->update_probe(probe_state)) 386 | { 387 | return ; 388 | } 389 | 390 | // calculate score depending on score function 391 | const auto& next_img = obs_->observe(next_); 392 | const auto energy = score_->calc(next_, obs_->get_image(), Mask(next_img), reference_, Mask(next_img)); 393 | const auto deltaE = energy - current_energy_; 394 | 395 | // std::cerr << "beta = " << beta << ", dE = " << deltaE 396 | // << ", Enext = " << energy << ", Ecurr = " << current_energy_ 397 | // << ", prob = " << std::exp(-deltaE * beta) << std::endl; 398 | 399 | if(deltaE <= 0.0 || this->generate_01() < std::exp(-deltaE * beta)) 400 | { 401 | // system is not updated 402 | current_energy_ = energy; 403 | img_ = obs_->get_image(); 404 | } 405 | else 406 | { 407 | // if energy increases, go back to the original probe 408 | obs_->update_probe(probe_state); 409 | } 410 | return; 411 | } 412 | 413 | Real generate_01() noexcept 414 | { 415 | return std::generate_canonical::digits>(rng_); 416 | } 417 | 418 | void translate(mave::vector dr, system& target) 419 | { 420 | // translation does not change the shape of bounding box. 421 | target.bounding_box.upper += dr; 422 | target.bounding_box.lower += dr; 423 | 424 | if(target.bounding_box.lower[2] < 0.0) 425 | { 426 | const auto offset = target.bounding_box.lower[2]; 427 | dr[2] -= offset; 428 | target.bounding_box.upper[2] -= offset; 429 | target.bounding_box.lower[2] -= offset; 430 | } 431 | 432 | // apply the movement 433 | for(auto& p : target.particles) 434 | { 435 | p.center += dr; 436 | } 437 | 438 | // update cell list 439 | target.cells.construct(target.particles, target.bounding_box); 440 | return ; 441 | } 442 | void rotate(const mave::matrix& rot, system& target) 443 | { 444 | // 1. move center to origin 445 | // 2. apply rotation matrix 446 | // 3. move back to the original center 447 | 448 | mave::vector com(0.0, 0.0, 0.0); 449 | for(const auto& p : target.particles) 450 | { 451 | com += p.center; 452 | } 453 | com /= static_cast(target.particles.size()); 454 | 455 | mave::matrix translation1; 456 | translation1.zero(); 457 | translation1(0, 0) = 1.0; 458 | translation1(1, 1) = 1.0; 459 | translation1(2, 2) = 1.0; 460 | translation1(3, 3) = 1.0; 461 | translation1(0, 3) = -com[0]; 462 | translation1(1, 3) = -com[1]; 463 | translation1(2, 3) = -com[2]; 464 | 465 | mave::matrix rotation; 466 | rotation.zero(); 467 | rotation(0, 0) = rot(0, 0); 468 | rotation(0, 1) = rot(0, 1); 469 | rotation(0, 2) = rot(0, 2); 470 | rotation(1, 0) = rot(1, 0); 471 | rotation(1, 1) = rot(1, 1); 472 | rotation(1, 2) = rot(1, 2); 473 | rotation(2, 0) = rot(2, 0); 474 | rotation(2, 1) = rot(2, 1); 475 | rotation(2, 2) = rot(2, 2); 476 | rotation(3, 3) = 1.0; 477 | 478 | mave::matrix translation2; 479 | translation2.zero(); 480 | translation2(0, 0) = 1.0; 481 | translation2(1, 1) = 1.0; 482 | translation2(2, 2) = 1.0; 483 | translation2(3, 3) = 1.0; 484 | translation2(0, 3) = com[0]; 485 | translation2(1, 3) = com[1]; 486 | translation2(2, 3) = com[2]; 487 | 488 | const mave::matrix matrix = translation2 * rotation * translation1; 489 | for(auto& p : target.particles) 490 | { 491 | mave::vector r(p.center[0], p.center[1], p.center[2], 1.0); 492 | r = matrix * r; 493 | p.center[0] = r[0]; 494 | p.center[1] = r[1]; 495 | p.center[2] = r[2]; 496 | } 497 | target.bounding_box = make_bounding_box(target.particles); 498 | 499 | if(target.bounding_box.lower[2] < 0.0) 500 | { 501 | // align the bottom to the xy plane (z=0.0) 502 | for(auto& p : target.particles) 503 | { 504 | p.center[2] -= target.bounding_box.lower[2]; 505 | } 506 | target.bounding_box.upper[2] -= target.bounding_box.lower[2]; 507 | target.bounding_box.lower[2] = 0.0; 508 | } 509 | // update cell list 510 | target.cells.construct(target.particles, target.bounding_box); 511 | return; 512 | } 513 | 514 | void warm_up() 515 | { 516 | constexpr std::size_t N = 100; 517 | std::vector energy_differences; 518 | energy_differences.reserve(N); 519 | 520 | const auto initial_configuration = sys_; 521 | 522 | Real current_energy = this->current_energy_; 523 | std::bernoulli_distribution half_half(0.5); 524 | 525 | std::cerr << "collecting warm up information ..." << std::endl; 526 | afmize::progress_bar<70> bar(N); 527 | for(std::size_t i=0; i(dx, dy, 0.0), next_); 536 | 537 | // rot around x 538 | const auto rot_x = (this->generate_01() * 2.0 - 1.0) * max_drotx_; 539 | const auto cos_x = std::cos(rot_x); 540 | const auto sin_x = std::sin(rot_x); 541 | mave::matrix mat_x; 542 | mat_x.zero(); 543 | mat_x(0, 0) = 1.0; 544 | mat_x(1, 1) = cos_x; 545 | mat_x(1, 2) = -sin_x; 546 | mat_x(2, 1) = sin_x; 547 | mat_x(2, 2) = cos_x; 548 | 549 | // rot around y 550 | const auto rot_y = (this->generate_01() * 2.0 - 1.0) * max_droty_; 551 | const auto cos_y = std::cos(rot_y); 552 | const auto sin_y = std::sin(rot_y); 553 | mave::matrix mat_y; 554 | mat_y.zero(); 555 | mat_y(0, 0) = cos_y; 556 | mat_y(0, 2) = sin_y; 557 | mat_y(1, 1) = 1.0; 558 | mat_y(2, 0) = -sin_y; 559 | mat_y(2, 2) = cos_y; 560 | 561 | // rot around z 562 | const auto rot_z = (this->generate_01() * 2.0 - 1.0) * max_drotz_; 563 | const auto cos_z = std::cos(rot_z); 564 | const auto sin_z = std::sin(rot_z); 565 | mave::matrix mat_z; 566 | mat_z.zero(); 567 | mat_z(0, 0) = cos_z; 568 | mat_z(0, 1) = -sin_z; 569 | mat_z(1, 0) = sin_z; 570 | mat_z(1, 1) = cos_z; 571 | mat_z(2, 2) = 1.0; 572 | 573 | rotate(mat_z * mat_y * mat_x, next_); 574 | 575 | 576 | const auto& next_img = obs_->observe(next_); 577 | const auto energy = score_->calc(next_, next_img , Mask(next_img), 578 | reference_, Mask(next_img)); 579 | const auto deltaE = energy - current_energy; 580 | 581 | if(0.0 < deltaE) 582 | { 583 | energy_differences.push_back(deltaE); 584 | } 585 | 586 | if(50 <= N && energy_differences.size() <= 10) 587 | { 588 | if(half_half(rng_)) 589 | { 590 | sys_ = next_; 591 | current_energy = energy; 592 | } 593 | } 594 | } 595 | std::cerr << bar.format(N) << std::endl; 596 | 597 | this->sys_ = initial_configuration; 598 | 599 | std::sort(energy_differences.begin(), energy_differences.end()); 600 | if(N / 2 < energy_differences.size()) 601 | { 602 | const auto initial_temperature = energy_differences.at(energy_differences.size() - N / 2) / std::log(2.0); 603 | this->schedule_->set_init_temperature(initial_temperature); 604 | } 605 | else 606 | { 607 | const auto initial_temperature = energy_differences.at(energy_differences.size() / 5) / std::log(2.0); 608 | this->schedule_->set_init_temperature(initial_temperature); 609 | } 610 | return ; 611 | } 612 | 613 | private: 614 | std::size_t step_; 615 | std::size_t save_step_; 616 | std::size_t total_step_; 617 | Real current_energy_; 618 | 619 | Real sigma_dx_; 620 | Real sigma_dy_; 621 | Real sigma_dz_; 622 | Real max_drotx_; 623 | Real max_droty_; 624 | Real max_drotz_; 625 | 626 | std::map dprobe_; 627 | 628 | std::mt19937 rng_; 629 | std::normal_distribution nrm_; 630 | image img_; 631 | image reference_; 632 | system sys_; 633 | system next_; 634 | std::unique_ptr> obs_; 635 | std::unique_ptr> score_; 636 | std::unique_ptr> schedule_; 637 | afmize::progress_bar<70> bar_; 638 | std::string output_basename_; 639 | }; 640 | 641 | } // afmize 642 | #endif// AFMIZE_SIMULATOR_HPP 643 | --------------------------------------------------------------------------------