├── .gitignore ├── resource.rc ├── .clangd ├── Images ├── Fig1.png ├── Fig2.png ├── Logo.png ├── AppIcon.ico ├── Banner.png ├── Test3_g.png └── joss-logo.png ├── ui ├── fonts │ ├── FiraCode-Bold.ttf │ └── FiraCode-Regular.ttf ├── ToolTip.slint ├── InputGroup.slint ├── Theme.slint └── AppWindow.slint ├── correlation.desktop ├── src ├── main.cpp ├── AppBackend.cpp ├── FileWriter.cpp ├── AppController.cpp ├── Cell.cpp └── StructureAnalyzer.cpp ├── include ├── AppController.hpp ├── AppBackend.hpp ├── FileIO.hpp ├── Trajectory.hpp ├── FileWriter.hpp ├── StructureAnalyzer.hpp ├── Atom.hpp ├── DistributionFunctions.hpp ├── Cell.hpp ├── Smoothing.hpp └── LinearAlgebra.hpp ├── AUTHORS ├── LICENSE ├── tests ├── Atom_test.cpp ├── Angle_Reproduction_tests.cpp ├── Cell_test.cpp ├── FileWriter_test.cpp ├── StructureAnalyzer_test.cpp ├── FileIO_test.cpp ├── Distribution_Functions_tests.cpp └── PAD_tests.cpp ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── ChangeLog ├── CMakeLists.txt ├── aps.csl ├── README.md ├── .clang-format ├── paper.bib ├── paper.md └── INSTALL /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .vs 3 | compile_commands.json 4 | build 5 | -------------------------------------------------------------------------------- /resource.rc: -------------------------------------------------------------------------------- 1 | // resource.rc 2 | IDI_ICON1 ICON "Images/AppIcon.ico" 3 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [-I./build/] 3 | FileExtensions: 4 | .h: cpp 5 | -------------------------------------------------------------------------------- /Images/Fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/Images/Fig1.png -------------------------------------------------------------------------------- /Images/Fig2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/Images/Fig2.png -------------------------------------------------------------------------------- /Images/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/Images/Logo.png -------------------------------------------------------------------------------- /Images/AppIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/Images/AppIcon.ico -------------------------------------------------------------------------------- /Images/Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/Images/Banner.png -------------------------------------------------------------------------------- /Images/Test3_g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/Images/Test3_g.png -------------------------------------------------------------------------------- /Images/joss-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/Images/joss-logo.png -------------------------------------------------------------------------------- /ui/fonts/FiraCode-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/ui/fonts/FiraCode-Bold.ttf -------------------------------------------------------------------------------- /ui/fonts/FiraCode-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isurwars/Correlation/HEAD/ui/fonts/FiraCode-Regular.ttf -------------------------------------------------------------------------------- /correlation.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.4 3 | Name=Correlation Analysis Tool 4 | Comment=Analysis tool for liquid and amorphous solid structures. 5 | Exec=/usr/local/bin/correlation %F 6 | Icon=correlation 7 | Terminal=false 8 | Type=Application 9 | Categories=Science;Engineering; 10 | StartupNotify=true 11 | Keywords=physics;chemistry;materials;simulation;rdf;sq; 12 | -------------------------------------------------------------------------------- /ui/ToolTip.slint: -------------------------------------------------------------------------------- 1 | import { Theme } from "Theme.slint"; 2 | 3 | export component ToolTip inherits Window { 4 | in property text <=> text_element.text; 5 | in property user_is_hovering; 6 | 7 | always-on-top: true; 8 | z: 100; 9 | visible: user_is_hovering; 10 | Rectangle { 11 | visible: user_is_hovering; 12 | opacity: user_is_hovering ? 1.0 : 0.0; 13 | animate opacity { 14 | duration: 175ms; 15 | delay: 1000ms; 16 | } 17 | background: Theme.background-hover; 18 | border-radius: Theme.radius-small; 19 | border-width: 1px; 20 | border-color: Theme.background-selected-hover; 21 | 22 | text_element := Text { 23 | width: parent.width - Theme.spacing-medium; 24 | height: parent.height - Theme.spacing-medium; 25 | color: Theme.foreground-hover; 26 | font-size: root.default-font-size * 0.9; 27 | horizontal-alignment: left; 28 | vertical-alignment: top; 29 | wrap: word-wrap; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #if defined(_WIN32) 7 | #define NOMINMAX 8 | #define IDI_ICON1 101 9 | #include 10 | #endif 11 | 12 | #include "../include/AppBackend.hpp" 13 | #include "../include/AppController.hpp" 14 | 15 | #include "AppWindow.h" 16 | 17 | int main() { 18 | 19 | #if defined(_WIN32) 20 | // Load the icon from the application's resource file 21 | HICON hIcon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_ICON1)); 22 | #endif 23 | 24 | auto ui = AppWindow::create(); 25 | AppBackend backend; 26 | 27 | // Instantiate the controller, which sets up all callbacks 28 | AppController controller(*ui, backend); 29 | 30 | ui->run(); 31 | 32 | return 0; 33 | } 34 | 35 | #if _WIN32 36 | int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 37 | LPSTR lpCmdLine, int nCmdShow) { 38 | return main(); 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /include/AppController.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "AppBackend.hpp" 11 | #include "AppWindow.h" 12 | #include "PortableFileDialogs.hpp" 13 | 14 | class AppController { 15 | public: 16 | // Constructor to set up the UI and backend references 17 | AppController(AppWindow &ui, AppBackend &backend); 18 | 19 | private: 20 | // Pointers to the UI and backend objects 21 | AppWindow &ui_; 22 | AppBackend &backend_; 23 | 24 | // Handle to the file dialog, moved here from main.cpp 25 | std::unique_ptr current_file_dialog_; 26 | 27 | // functions to handle options 28 | void handleOptionstoUI(AppWindow &ui); 29 | ProgramOptions handleOptionsfromUI(AppWindow &ui); 30 | 31 | // functios to handle Bond_Cutoffs_Sq_ 32 | void setBondCutoffs(AppWindow &ui); 33 | std::vector> getBondCutoffs(AppWindow &ui); 34 | 35 | // Member functions for the UI callbacks 36 | void handleRunAnalysis(); 37 | void handleBrowseFile(); 38 | void handleCheckFileDialogStatus(); 39 | }; 40 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Correlation was originally created in late 2010 by Prof. Valladares Group 2 | at Materials Research Institute, National Autonomous University of Mexico. 3 | 4 | Developers of Correlation (2021): 5 | 6 | Isaías Rodríguez 7 | Salvador Villareal 8 | Renela M. Valladares 9 | Alexander Valladares 10 | David Hinojosa-Romero 11 | Ulises Santiago 12 | Ariel A. Valladares 13 | 14 | Special thanks to the editor and reviewers from Journal of Open Source Software: 15 | 16 | Jeff Gostick (jgostick) 17 | Sebastian Ehlert (awvwgk) 18 | Carsten Fortmann-Grote (CFGrote) 19 | 20 | 21 | Here is an incomplete list of MUCH-APPRECIATED CONTRIBUTORS to Correlation, 22 | people who have submitted patches, reported bugs, added translations, tested, 23 | benchmarked and helped answer questions: 24 | 25 | 26 | Adrián Ortiz 27 | Alejandro De Leon 28 | Alejandro Valenzuela 29 | Andrés Díaz 30 | Brandon E. Dévora 31 | Cristina Romero 32 | Edgar Saynes 33 | Flor B. Quiroga 34 | Gerardo Antonio 35 | Joel Vásquez 36 | Jonathan Galván 37 | Jóse F. Frey 38 | María A. Carrillo 39 | Martín Mejía 40 | Oscar A. Ibañez 41 | Salvador Villarreal 42 | Sammuel González 43 | Sebastian Calderón 44 | Sebastian P. Tamariz 45 | Sebastian Vilchis 46 | Zaahel Mata 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /*============================================================================== 2 | * Correlation: Analysis Tool for Liquids and Amorphous Solids 3 | * Copyright © 2013-2025 Isaías Rodríguez 4 | * 5 | * SPDX-License-Identifier: MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | * 25 | * Main repository: https://github.com/Isurwars/Correlation 26 | *============================================================================*/ 27 | -------------------------------------------------------------------------------- /include/AppBackend.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "Cell.hpp" 11 | #include "DistributionFunctions.hpp" 12 | 13 | // Encapsulates all command-line configurable options for the application. 14 | struct ProgramOptions { 15 | std::string input_file; 16 | std::string output_file_base; 17 | bool normalize = true; 18 | bool smoothing = false; 19 | double r_max = 20.0; 20 | double r_bin_width = 0.02; 21 | double q_max = 20.0; 22 | double q_bin_width = 0.02; 23 | double r_int_max = 10.0; 24 | double angle_max = 180.0; 25 | double angle_bin_width = 1.0; 26 | double bond_factor = 1.2; 27 | double smoothing_sigma = 0.1; 28 | KernelType smoothing_kernel = KernelType::Gaussian; 29 | std::vector> bond_cutoffs_sq_; 30 | }; 31 | 32 | class AppBackend { 33 | public: 34 | // Constructor to set up the UI and backend references 35 | AppBackend(); 36 | 37 | // Accesors 38 | void setOptions(ProgramOptions opt) { options_ = opt; } 39 | ProgramOptions options() { return options_; } 40 | 41 | // Member functions 42 | std::string load_file(const std::string &path); 43 | void run_analysis(); 44 | 45 | private: 46 | // Member functions 47 | void analysis_thread_func(); 48 | 49 | // Pointers to the Cell and DF 50 | std::unique_ptr cell_; 51 | std::unique_ptr df_; 52 | 53 | ProgramOptions options_; 54 | }; 55 | -------------------------------------------------------------------------------- /include/FileIO.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "Cell.hpp" 11 | 12 | // The FileIO namespace encapsulates all functionality related to reading and 13 | // writing structure files. 14 | namespace FileIO { 15 | 16 | // A type-safe enum to specify the format of a structure file. 17 | // This is more robust than using raw string extensions. 18 | enum class FileType { Car, Cell, Cif, OnetepDat, Unknown }; 19 | 20 | // --- Main Public Interface --- 21 | 22 | /** 23 | * @brief Reads an atomic structure from a file. 24 | * 25 | * This function serves as a single entry point for reading all supported file 26 | * formats. It dispatches to the appropriate specialized reader based on the 27 | * provided FileType. 28 | * 29 | * @param filename The path to the structure file. 30 | * @param type The format of the file. 31 | * @return A Cell object containing the parsed atomic structure. 32 | * @throws std::runtime_error if the file cannot be opened or is malformed. 33 | */ 34 | Cell readStructure(const std::string &filename, FileType type); 35 | 36 | /** 37 | * @brief Determines the FileType from a given filename extension. 38 | * @param filename The full path of the file. 39 | * @return The corresponding FileType enum value. 40 | * @throws std::runtime_error if the file has no extension or an unsupported 41 | * one. 42 | */ 43 | FileType determineFileType(const std::string &filename); 44 | 45 | } // namespace FileIO 46 | -------------------------------------------------------------------------------- /include/Trajectory.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "../include/Cell.hpp" 11 | 12 | class Trajectory { 13 | /* -------------------------------------------------------------------------- 14 | * This class stores a series of snapshots of a system, ensuring that the 15 | * number of atoms and their identities are consistent across all frames. It 16 | * provides methods to analyze the dynamic properties of the system, such as 17 | * velocity distributions. 18 | * -------------------------------------------------------------------------- 19 | */ 20 | 21 | private: 22 | void validateFrame(const Cell &new_frame) const; 23 | std::vector frames_; 24 | double time_step_; // Time step between frames 25 | 26 | public: 27 | //-------------------------------------------------------------------------// 28 | //----------------------------- Constructors ------------------------------// 29 | //-------------------------------------------------------------------------// 30 | Trajectory(); 31 | Trajectory(std::vector frames, double time_step); 32 | void addFrame(const Cell &frame); 33 | 34 | //-------------------------------------------------------------------------// 35 | //------------------------------- Accessors -------------------------------// 36 | //-------------------------------------------------------------------------// 37 | const std::vector &getFrames() const { return frames_; } 38 | size_t getFrameCount() const { return frames_.size(); } 39 | double getTimeStep() const { return time_step_; } 40 | }; 41 | -------------------------------------------------------------------------------- /ui/InputGroup.slint: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | import { LineEdit } from "std-widgets.slint"; 7 | import { Theme } from "Theme.slint"; 8 | 9 | 10 | export component InputGroup inherits TouchArea { 11 | in-out property value_text <=> edit.text; 12 | in property label_text; 13 | in property unit_text: ""; 14 | in property enabled_input: true; 15 | 16 | property is_focused: false; 17 | property is_label_floating: is_focused || !value_text.is-empty; 18 | 19 | min-width: Theme.size-medium; 20 | min-height: Theme.size-regular; 21 | Rectangle { 22 | min-height: Theme.size-regular; 23 | 24 | edit := LineEdit { 25 | x: Theme.spacing-medium; 26 | y: Theme.spacing-medium; 27 | width: parent.width - 2 * Theme.spacing-medium; 28 | height: Theme.size-small; 29 | enabled: enabled_input; 30 | 31 | changed has-focus => { 32 | is_focused = self.has-focus; 33 | } 34 | } 35 | 36 | Text { 37 | text: label_text + (unit_text != "" ? " (" + unit_text + ")" : ""); 38 | color: is_label_floating ? Theme.foreground-pressed : Theme.foreground; 39 | font-size: is_label_floating ? Theme.font-size-regular : Theme.font-size-medium; 40 | x: Theme.spacing-medium; 41 | y: is_label_floating ? -Theme.spacing-regular : Theme.spacing-medium; 42 | animate y { duration: Theme.duration-regular; } 43 | animate font-size { duration: Theme.duration-regular; } 44 | animate color { duration: Theme.duration-regular; } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /include/FileWriter.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "DistributionFunctions.hpp" 11 | 12 | /** 13 | * @class FileWriter 14 | * @brief Manages the writing of distribution function data to CSV files. 15 | * 16 | * This class takes a DistributionFunctions object and provides methods to 17 | * write the calculated histograms (both raw and smoothed) to disk in a 18 | * well-formatted, data-driven way. 19 | */ 20 | class FileWriter { 21 | public: 22 | /** 23 | * @brief Constructs a FileWriter linked to a DistributionFunctions object. 24 | * @param df The DistributionFunctions object containing the data to be 25 | * written. 26 | */ 27 | explicit FileWriter(const DistributionFunctions &df); 28 | 29 | /** 30 | * @brief Writes all available histograms to appropriately named CSV files. 31 | * 32 | * For each histogram in the DistributionFunctions object (e.g., "g(r)"), 33 | * this method will create a corresponding CSV file (e.g., "base_path_g.csv"). 34 | * 35 | * @param base_path The base name for the output files (e.g., 36 | * "output/my_sample"). 37 | * @param write_smoothed If true, also writes smoothed data to separate files 38 | * (e.g., "base_path_g_smoothed.csv"). 39 | */ 40 | void writeAllCSVs(const std::string &base_path, 41 | bool write_smoothed = false) const; 42 | 43 | private: 44 | /** 45 | * @brief The core implementation for writing a single histogram 46 | * and it's smoothed histograms to a single CSV file. 47 | * @param filename The full path of the file to write. 48 | * @param hist The Histogram data structure to write. 49 | */ 50 | void writeHistogramToCSV(const std::string &filename, 51 | const Histogram &hist) const; 52 | 53 | const DistributionFunctions &df_; 54 | }; 55 | -------------------------------------------------------------------------------- /ui/Theme.slint: -------------------------------------------------------------------------------- 1 | import "fonts/FiraCode-Bold.ttf"; 2 | import "fonts/FiraCode-Bold.ttf"; 3 | 4 | export global Theme { 5 | // brushes 6 | out property window-background: #0B0B0B; 7 | out property background-regular: #0B0B0B; 8 | out property background-hover: root.background-regular.brighter(0.2); 9 | out property background-pressed: root.background-regular.brighter(0.4); 10 | out property background-selected: root.foreground; 11 | out property background-selected-hover: root.background-selected.brighter(0.2); 12 | out property background-selected-pressed: root.background-selected.brighter(0.4); 13 | out property foreground: #FFFFFF; 14 | out property foreground-hover: root.foreground.darker(0.2); 15 | out property foreground-pressed: root.foreground.darker(0.4); 16 | out property foreground-selected: root.background-regular; 17 | out property foreground-selected-hover: root.foreground-selected.darker(0.2); 18 | out property foreground-selected-pressed: root.foreground-selected.darker(0.4); 19 | 20 | // durations 21 | out property duration-fast: 100ms; 22 | out property duration-regular: 250ms; 23 | 24 | // radius 25 | out property radius-small: 4px; 26 | out property radius-regular: 8px; 27 | 28 | // sizes 29 | out property size-small: 24px; 30 | out property size-regular: 48px; 31 | out property size-medium: 128px; 32 | out property size-big: 170px; 33 | 34 | // spacings 35 | out property spacing-regular: 4px; 36 | out property spacing-medium: 8px; 37 | out property spacing-large: 16px; 38 | 39 | // typo 40 | out property font-family: "FiraCode"; 41 | out property font-size-regular: 12px; 42 | out property font-size-medium: 14px; 43 | out property font-size-large: 20px; 44 | out property font-weight-regular: 400; 45 | out property font-weight-bold: 900; 46 | } 47 | -------------------------------------------------------------------------------- /src/AppBackend.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include "../include/AppBackend.hpp" 7 | 8 | #include 9 | 10 | #include "../include/FileIO.hpp" 11 | #include "../include/FileWriter.hpp" 12 | 13 | //---------------------------------------------------------------------------// 14 | //------------------------------- Constructors ------------------------------// 15 | //---------------------------------------------------------------------------// 16 | 17 | AppBackend::AppBackend() { ProgramOptions options_; } 18 | 19 | //---------------------------------------------------------------------------// 20 | //--------------------------------- Methods ---------------------------------// 21 | //---------------------------------------------------------------------------// 22 | 23 | std::string AppBackend::load_file(const std::string &path) { 24 | std::string display_path = path; 25 | std::replace(display_path.begin(), display_path.end(), '\\', '/'); 26 | FileIO::FileType type = FileIO::determineFileType(path); 27 | cell_ = std::make_unique(FileIO::readStructure(path, type)); 28 | options_.input_file = path; 29 | options_.output_file_base = path; 30 | return "Loaded " + std::to_string(cell_->atomCount()) + " atoms from:\n" + 31 | display_path; 32 | } 33 | 34 | void AppBackend::run_analysis() { 35 | if (!cell_) { 36 | return; 37 | } 38 | 39 | try { 40 | // Create the DistributionFunctions object 41 | df_ = std::make_unique(*cell_, options_.r_max, 42 | options_.bond_factor); 43 | 44 | // --- Run calculations sequentially and report progress --- 45 | df_->calculateCoordinationNumber(); 46 | df_->calculateRDF(options_.r_max, options_.r_bin_width); 47 | df_->calculatePAD(options_.angle_max, options_.angle_bin_width); 48 | df_->calculateSQ(options_.q_max, options_.q_bin_width, options_.r_int_max); 49 | if (options_.smoothing) { 50 | df_->smoothAll(options_.smoothing_sigma, options_.smoothing_kernel); 51 | } 52 | // --- Write results --- 53 | FileWriter writer(*df_); 54 | writer.writeAllCSVs(options_.output_file_base, options_.smoothing); 55 | 56 | } catch (const std::exception &e) { 57 | std::cerr << "Error during analysis: " << e.what() << std::endl; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/FileWriter.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include "../include/FileWriter.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | FileWriter::FileWriter(const DistributionFunctions &df) : df_(df) {} 18 | 19 | void FileWriter::writeHistogramToCSV(const std::string &filename, 20 | const Histogram &hist) const { 21 | if (hist.partials.empty() || hist.bins.empty()) { 22 | return; 23 | } 24 | 25 | std::ofstream file(filename); 26 | if (!file) { 27 | throw std::runtime_error("Failed to open file for writing: " + filename); 28 | } 29 | 30 | // Get sorted keys for both raw and smoothed data 31 | std::vector raw_keys; 32 | for (const auto &[key, val] : hist.partials) { 33 | raw_keys.push_back(key); 34 | } 35 | std::sort(raw_keys.begin(), raw_keys.end()); 36 | 37 | std::vector smoothed_keys; 38 | for (const auto &[key, val] : hist.smoothed_partials) { 39 | smoothed_keys.push_back(key); 40 | } 41 | std::sort(smoothed_keys.begin(), smoothed_keys.end()); 42 | 43 | // --- Write Header --- 44 | file << hist.bin_label; 45 | // Write headers for raw data 46 | for (const auto &key : raw_keys) { 47 | file << "," << key; 48 | } 49 | // Write headers for smoothed data 50 | for (const auto &key : smoothed_keys) { 51 | file << "," << key << "_smoothed"; 52 | } 53 | file << '\n'; 54 | 55 | // --- Write Data Rows --- 56 | const size_t num_rows = hist.bins.size(); 57 | for (size_t i = 0; i < num_rows; ++i) { 58 | file << std::fixed << std::setprecision(5) << hist.bins[i]; 59 | 60 | // Write raw data 61 | for (const auto &key : raw_keys) { 62 | file << "," << hist.partials.at(key)[i]; 63 | } 64 | 65 | // Write smoothed data 66 | for (const auto &key : smoothed_keys) { 67 | file << "," << hist.smoothed_partials.at(key)[i]; 68 | } 69 | file << '\n'; 70 | } 71 | } 72 | 73 | void FileWriter::writeAllCSVs(const std::string &base_path, 74 | bool write_smoothed) const { 75 | const std::map file_map = { 76 | {"g(r)", "_g.csv"}, {"J(r)", "_J.csv"}, {"G(r)", "__G.csv"}, 77 | {"f(theta)", "_PAD.csv"}, {"S(Q)", "_S.csv"}, {"XRD", "_XRD.csv"}, 78 | {"CN", "_CN.csv"}}; 79 | 80 | for (const auto &[name, suffix] : file_map) { 81 | try { 82 | const auto &hist = df_.getHistogram(name); 83 | 84 | std::string filename = base_path + suffix; 85 | writeHistogramToCSV(filename, hist); 86 | 87 | } catch (const std::out_of_range &) { 88 | // This is not an error; it just means the histogram wasn't calculated. 89 | // We can silently skip it. 90 | } catch (const std::exception &e) { 91 | std::cerr << "Error writing file for '" << name << "': " << e.what() 92 | << std::endl; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/Atom_test.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright (c) 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include 7 | 8 | #include "../include/Atom.hpp" 9 | #include "../include/PhysicalData.hpp" 10 | 11 | // Test fixture for the Atom class and related free functions. 12 | class AtomTest : public ::testing::Test {}; 13 | 14 | TEST_F(AtomTest, DefaultConstructorInitializesCorrectly) { 15 | // Arrange & Act 16 | const Atom atom{}; 17 | 18 | // Assert 19 | EXPECT_EQ(atom.id().value, 0); 20 | EXPECT_EQ(atom.element().symbol, ""); 21 | EXPECT_EQ(atom.element().id.value, -1); 22 | EXPECT_DOUBLE_EQ(linalg::norm(atom.position()), 0.0); 23 | } 24 | 25 | TEST_F(AtomTest, ParameterizedConstructorSetsProperties) { 26 | // Arrange 27 | const Element element = {"O", {1}}; 28 | const linalg::Vector3 expected_pos = {1.0, 2.5, -3.0}; 29 | const AtomID expected_id = {123}; 30 | 31 | // Act 32 | const Atom atom(element, expected_pos, expected_id); 33 | 34 | // Assert 35 | EXPECT_EQ(atom.element().symbol, "O"); 36 | EXPECT_EQ(atom.element().id.value, 1); 37 | EXPECT_EQ(atom.id().value, 123); 38 | EXPECT_NEAR(atom.position().x(), expected_pos.x(), 1e-9); 39 | EXPECT_NEAR(atom.position().y(), expected_pos.y(), 1e-9); 40 | EXPECT_NEAR(atom.position().z(), expected_pos.z(), 1e-9); 41 | } 42 | 43 | TEST_F(AtomTest, DistanceFunctionCalculatesCorrectly) { 44 | // Arrange: A classic 3-4-5 right triangle. 45 | const Element element = {"H", {0}}; 46 | const Atom atom1(element, {0.0, 0.0, 0.0}, {0}); 47 | const Atom atom2(element, {3.0, 4.0, 0.0}, {1}); 48 | 49 | // Act 50 | const double d = distance(atom1, atom2); 51 | 52 | // Assert 53 | EXPECT_NEAR(d, 5.0, 1e-9); 54 | } 55 | 56 | TEST_F(AtomTest, AngleFunctionCalculatesNinetyDegrees) { 57 | // Arrange 58 | const Element element_c = {"C", {0}}; 59 | const Element element_h = {"H", {1}}; 60 | const Atom center(element_c, {0.0, 0.0, 0.0}, {0}); 61 | const Atom neighbor_a(element_h, {1.0, 0.0, 0.0}, {1}); // Along X-axis 62 | const Atom neighbor_b(element_h, {0.0, 1.0, 0.0}, {2}); // Along Y-axis 63 | 64 | // Act 65 | const double calculated_angle = angle(center, neighbor_a, neighbor_b); 66 | 67 | // Assert 68 | EXPECT_NEAR(calculated_angle, constants::pi / 2.0, 1e-9); 69 | } 70 | 71 | TEST_F(AtomTest, AngleFunctionHandlesCollinearAtoms) { 72 | // Arrange 73 | const Element element = {"O", {0}}; 74 | const Atom center(element, {0.0, 0.0, 0.0}, {0}); 75 | const Atom neighbor_a(element, {1.0, 0.0, 0.0}, {1}); 76 | const Atom neighbor_b(element, {-1.0, 0.0, 0.0}, {2}); // 180 degrees 77 | 78 | // Act 79 | const double calculated_angle = angle(center, neighbor_a, neighbor_b); 80 | 81 | // Assert 82 | EXPECT_NEAR(calculated_angle, constants::pi, 1e-9); 83 | } 84 | 85 | TEST_F(AtomTest, AngleFunctionHandlesCoincidentAtoms) { 86 | // Arrange: All atoms at the same position. 87 | const Element element = {"C", {0}}; 88 | const Atom center(element, {0.0, 0.0, 0.0}, {0}); 89 | const Atom neighbor_a(element, {0.0, 0.0, 0.0}, {1}); 90 | 91 | // Act 92 | const double calculated_angle = angle(center, neighbor_a, center); 93 | 94 | // Assert: Angle with a zero-length vector should gracefully return 0. 95 | EXPECT_DOUBLE_EQ(calculated_angle, 0.0); 96 | } 97 | -------------------------------------------------------------------------------- /include/StructureAnalyzer.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "Cell.hpp" 11 | 12 | /** 13 | * @class StructureAnalyzer 14 | * @brief Computes and stores pairwise distances and bond angles in tensors 15 | * indexed by element type. 16 | * 17 | * This class efficiently finds all atom pairs within a cutoff radius and all 18 | * unique bond angles, storing the results in multi-dimensional vectors 19 | * (tensors) suitable for calculating partial distribution functions. 20 | * The distance and angle calculation loops are parallelized with OpenMP. 21 | */ 22 | class StructureAnalyzer { 23 | public: 24 | using NeighborTensor = std::vector>; 25 | using DistanceTensor = std::vector>>; 26 | using AngleTensor = 27 | std::vector>>>; 28 | 29 | // A helper struct to hold the thread-local results 30 | struct ThreadLocalResults { 31 | DistanceTensor distance_tensor_local; 32 | NeighborTensor neighbor_tensor_local; 33 | AngleTensor angle_tensor_local; 34 | 35 | // Constructor to initialize the tensors with the correct dimensions 36 | ThreadLocalResults(size_t num_elements, size_t atom_count) 37 | : distance_tensor_local(num_elements, 38 | std::vector>(num_elements)), 39 | neighbor_tensor_local(atom_count), 40 | angle_tensor_local(num_elements, 41 | std::vector>>( 42 | num_elements, std::vector>( 43 | num_elements))) {} 44 | }; 45 | 46 | /** 47 | * @brief Computes all distances and angles within the cutoff radius. 48 | * @param cell The cell containing the atoms and lattice information. 49 | * @param cutoff The maximum distance to search for neighbors. 50 | * @param bond_factor A factor to multiply with the sum of covalent radii to 51 | * determine if two atoms are bonded. 52 | * @param ignore_periodic_self_interactions If true the periodic self 53 | * interactions will be ignored. 54 | */ 55 | explicit StructureAnalyzer(Cell &cell, double cutoff = 20.0, 56 | bool ignore_periodic_self_interactions = true); 57 | 58 | // --- Accessors for the computed data tensors --- 59 | const DistanceTensor &distances() const { return distance_tensor_; } 60 | const AngleTensor &angles() const { return angle_tensor_; } 61 | const NeighborTensor &neighbors() const { return neighbor_tensor_; } 62 | 63 | private: 64 | Cell &cell_; 65 | double cutoff_sq_; 66 | 67 | bool ignore_periodic_self_interactions_; 68 | 69 | NeighborTensor neighbor_tensor_; 70 | DistanceTensor distance_tensor_; 71 | AngleTensor angle_tensor_; 72 | 73 | /** 74 | * @brief Computes pairwise distances and identifies bonded neighbors. 75 | * This method is parallelized using OpenMP. 76 | * @return A list of all neighbors within the cutoff for each atom, used for 77 | * angle calculations. 78 | */ 79 | void computeDistances(); 80 | 81 | /** 82 | * @brief Computes all unique bond angles. 83 | * This method is parallelized using OpenMP. 84 | */ 85 | void computeAngles(); 86 | }; 87 | -------------------------------------------------------------------------------- /include/Atom.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "LinearAlgebra.hpp" 13 | 14 | struct AtomID { 15 | std::uint32_t value; 16 | linalg::Vector3 offset = {0, 0, 0}; 17 | // Allow comparison and use as a key in maps if needed. 18 | constexpr bool operator==(const AtomID other) const { 19 | return value == other.value; 20 | }; 21 | }; 22 | 23 | struct ElementID { 24 | int value; 25 | constexpr bool operator==(const ElementID other) const { 26 | return value == other.value; 27 | }; 28 | }; 29 | 30 | struct Element { 31 | std::string symbol; 32 | ElementID id{-1}; 33 | }; 34 | 35 | struct Neighbor { 36 | AtomID index; 37 | double distance; 38 | linalg::Vector3 r_ij; 39 | }; 40 | 41 | class Atom { 42 | public: 43 | //-------------------------------------------------------------------------// 44 | //----------------------------- Constructors ------------------------------// 45 | //-------------------------------------------------------------------------// 46 | explicit Atom() = default; 47 | 48 | explicit Atom(Element element, linalg::Vector3 pos, 49 | AtomID id) noexcept 50 | : element_(std::move(element)), position_(pos), id_(id) {} 51 | 52 | //-------------------------------------------------------------------------// 53 | //------------------------------- Accessors -------------------------------// 54 | //-------------------------------------------------------------------------// 55 | 56 | AtomID id() const noexcept { return id_; } 57 | void setID(std::uint32_t num) { id_.value = num; } 58 | 59 | const linalg::Vector3 &position() const noexcept { return position_; } 60 | void setPosition(linalg::Vector3 pos) { position_ = pos; } 61 | 62 | const Element &element() const { return element_; } 63 | void setElement(const Element &ele) { element_ = ele; } 64 | 65 | int element_id() const { return element_.id.value; } 66 | 67 | private: 68 | AtomID id_; 69 | linalg::Vector3 position_; 70 | Element element_; 71 | }; 72 | 73 | /** 74 | * @brief Calculates the Euclidean distance between two atoms. 75 | */ 76 | inline double distance(const Atom &a, const Atom &b) noexcept { 77 | return linalg::norm(a.position() - b.position()); 78 | } 79 | 80 | /** 81 | * @brief Calculates the angle (in radians) formed by three atoms. 82 | * @param center The atom at the vertex of the angle. 83 | * @param a One of the outer atoms. 84 | * @param b The other outer atom. 85 | * @return The angle in radians, or 0.0 if vectors are collinear or zero. 86 | */ 87 | inline double angle(const Atom ¢er, const Atom &a, const Atom &b) noexcept { 88 | const linalg::Vector3 vA = a.position() - center.position(); 89 | const linalg::Vector3 vB = b.position() - center.position(); 90 | 91 | const double norm_sq_A = linalg::dot(vA, vA); 92 | const double norm_sq_B = linalg::dot(vB, vB); 93 | 94 | if (norm_sq_A == 0.0 || norm_sq_B == 0.0) { 95 | return 0.0; 96 | } 97 | 98 | double cos_theta = linalg::dot(vA, vB) / std::sqrt(norm_sq_A * norm_sq_B); 99 | 100 | // Clamp for numerical stability 101 | cos_theta = std::clamp(cos_theta, -1.0, 1.0); 102 | 103 | return std::acos(cos_theta); 104 | } 105 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [![Version]()]() [![License](https://img.shields.io/badge/license-MIT-brightgreen)](https://img.shields.io/badge/license-MIT-brightgreen)[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](code_of_conduct.md) 2 | 3 | # Correlation: Contributing guidelines 4 | 5 | We want to make contributing to this project easy and transparent, whether it's: 6 | 7 | - Reporting a bug 8 | - Submitting a fix 9 | - Proposing new features 10 | - Discussing the current state of the code 11 | 12 | ## Code of Conduct 13 | 14 | This project and everyone participating in it are governed by our [**code of conduct**](https://github.com/Isurwars/Correlation/blob/main/CODE_OF_CONDUCT.md) guidelines. By participating, you are expected to uphold this code. Please report unacceptable behavior. 15 | 16 | ## How to contribute to **Correlation** 17 | 18 | ### **Reporting a bug:** 19 | 20 | - **Please, make sure the bug has not been previously reported** by searching on GitHub under [Issues](https://github.com/Isurwars/Correlation/issues). 21 | 22 | - **If you're unable to find an open issue addressing the problem**, [open a new one](https://github.com/Isurwars/Correlation/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible: the **structure used**, as well as the **parameters**. In case the program does not match the expected results, please include a sample of the expected behavior that is not occurring. 23 | 24 | ### **Submitting a fix:** 25 | 26 | In case you have created a patch for an existing bug in **Correlation**, follow these steps: 27 | 28 | - Open a new GitHub **pull request (PR)** with the patch. 29 | - Ensure the **PR** description includes the following : 30 | - Clearly describes the **bug** and your solution. 31 | - Include the relevant issue number if applicable. 32 | - Include the changes to the [**Changelog**](https://github.com/Isurwars/Correlation/blob/main/ChangeLog) and your contribution to the [**AUTHORS**](https://github.com/Isurwars/Correlation/blob/main/AUTHORS) file. 33 | 34 | ### **Proposing new features** 35 | 36 | - **Please, make sure the feature is not currently been developed** by searching on the GitHub [**branches**](https://github.com/Isurwars/Correlation/branches). 37 | - If the feature is currently under development, **you are heartily welcome** to contribute within the corresponding branch. 38 | 39 | - If the feature is not under development, but is included in the planned future features in the [**README**](https://github.com/Isurwars/Correlation/blob/main/README.md), you are encouraged to create a new branch. 40 | 41 | - If the intended feature is not included in the planned future features in the [**README**](https://github.com/Isurwars/Correlation/blob/main/README.md), please contact the developers listed in the [**AUTHORS**](https://github.com/Isurwars/Correlation/blob/main/AUTHORS) file. 42 | 43 | ### **Discussing the current state of the code:** 44 | 45 | - For any questions about **Correlation** source code, please contact the developers listed in the [**AUTHORS**](https://github.com/Isurwars/Correlation/blob/main/AUTHORS) file. 46 | 47 | ## License 48 | 49 | By contributing to **Correlation**, you agree that your contributions will be licensed under the MIT License. 50 | 51 | ## Attribution 52 | 53 | This Contributing guidelines is adapted from the [Ruby on Rails](https://rubyonrails.org/) contributing guidelines available at 54 | [CONTRIBUTING.md](https://github.com/rails/rails/blob/main/CONTRIBUTING.md). 55 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # **Correlation** Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as contributors and maintainers pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual identity 10 | and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to a positive environment for our 15 | community includes: 16 | 17 | - Demonstrating empathy and kindness toward other people 18 | - Being respectful of differing opinions, viewpoints, and experiences 19 | - Giving and gracefully accepting constructive feedback 20 | - Accepting responsibility and apologizing to those affected by our mistakes, 21 | and learning from the experience 22 | - Focusing on what is best not just for us as individuals, but for the 23 | overall community 24 | 25 | Examples of unacceptable behavior include: 26 | 27 | - The use of sexualized language or imagery, and sexual attention or 28 | advances of any kind 29 | - Trolling, insulting or derogatory comments, and personal or political attacks 30 | - Public or private harassment 31 | - Publishing other's private information, such as a physical or email 32 | address, without their explicit permission 33 | - Other conduct which could reasonably be considered inappropriate in a 34 | professional setting 35 | 36 | All **Correlation** forums and spaces are meant for professional interactions, 37 | and any behavior which could reasonably be considered inappropriate in a 38 | professional setting is unacceptable. 39 | 40 | ## Our Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Reporting Violations 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to Isaías Rodríguez at or Ariel A. Valladares at 64 | . 65 | 66 | The Project Steward will determine whether the Code of Conduct was violated, 67 | and will issue an appropriate sanction, possibly including a written warning or 68 | expulsion from the project, project sponsored spaces, or project forums. 69 | 70 | We ask that you make a good-faith effort to resolve your conflict via the 71 | conflict resolution policy before submitting a report. 72 | 73 | ## Enforcement 74 | 75 | If the Project Stewards receive a report alleging a violation of this Code of 76 | Conduct, the Project Stewards will notify the accused of the report, and provide 77 | them an opportunity to discuss the report before a sanction is issued. 78 | The Project Stewards will do their utmost to keep the reporter anonymous. 79 | 80 | ## Attribution 81 | 82 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 83 | version 2.0, available at 84 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 85 | -------------------------------------------------------------------------------- /include/DistributionFunctions.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "Cell.hpp" 14 | #include "Smoothing.hpp" 15 | #include "StructureAnalyzer.hpp" 16 | 17 | // A structure to hold all data related to a single histogram. 18 | struct Histogram { 19 | std::vector bins; 20 | std::string bin_label; 21 | // Maps a partial key (e.g., "Si-O" or "Total") to its histogram values 22 | std::map> partials; 23 | std::map> smoothed_partials; 24 | }; 25 | 26 | class DistributionFunctions { 27 | public: 28 | //-------------------------------------------------------------------------// 29 | //----------------------------- Constructors ------------------------------// 30 | //-------------------------------------------------------------------------// 31 | 32 | explicit DistributionFunctions(Cell &, double, double); 33 | 34 | // Move constructor 35 | DistributionFunctions(DistributionFunctions &&other) noexcept; 36 | 37 | // Move assignment operator 38 | DistributionFunctions &operator=(DistributionFunctions &&other) noexcept; 39 | 40 | //-------------------------------------------------------------------------// 41 | //------------------------------- Accessors -------------------------------// 42 | //-------------------------------------------------------------------------// 43 | const Cell &cell() const { return cell_; } 44 | 45 | /** 46 | * @brief Access a specific calculated histogram by name. 47 | * @param name The name of the distribution function (e.g., "g(r)", "S(Q)"). 48 | * @return A const reference to the Histogram data. 49 | * @throws std::out_of_range if the name is not found. 50 | */ 51 | const Histogram &getHistogram(const std::string &name) const; 52 | 53 | const std::map &getAllHistograms() const { 54 | return histograms_; 55 | } 56 | 57 | std::vector getAvailableHistograms() const; 58 | //-------------------------------------------------------------------------// 59 | //--------------------------- Calculation Methods -------------------------// 60 | //-------------------------------------------------------------------------// 61 | 62 | void calculateCoordinationNumber(); 63 | /** 64 | * @brief Calculates the radial distribution function g(r) 65 | * @param r_max Maximum radius to calculate up to 66 | * @param r_bin_width Width of each bin in Angstroms 67 | * @throws std::invalid_argument if parameters are invalid 68 | * @throws std::logic_error if cell volume is invalid 69 | */ 70 | void calculateRDF(double r_max = 20.0, double bin_width = 0.05); 71 | 72 | void calculatePAD(double theta_max = 180.0, double bin_width = 1.0); 73 | 74 | void calculateSQ(double q_max = 25.0, double q_bin_width = 0.05, 75 | double r_integration_max = 8.0); 76 | 77 | void calculateXRD(double lambda = 1.5406, double theta_min = 5.0, 78 | double theta_max = 90.0, double bin_width = 1.0); 79 | 80 | void smooth(const std::string &name, double sigma, 81 | KernelType kernel = KernelType::Gaussian); 82 | void smoothAll(double sigma, KernelType kernel = KernelType::Gaussian); 83 | 84 | private: 85 | void ensureNeighborsComputed(double r_cut); 86 | std::string getPartialKey(int type1, int type2) const; 87 | std::string getInversePartialKey(int type1, int type2) const; 88 | void calculateAshcroftWeights(); 89 | 90 | Cell &cell_; 91 | std::unique_ptr neighbors_; 92 | double current_cutoff_{-1.0}; 93 | 94 | std::map histograms_; 95 | std::map ashcroft_weights_; 96 | }; 97 | -------------------------------------------------------------------------------- /tests/Angle_Reproduction_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/Cell.hpp" 3 | #include "../include/StructureAnalyzer.hpp" 4 | #include "../include/DistributionFunctions.hpp" 5 | #include 6 | 7 | class AngleReproductionTest : public ::testing::Test { 8 | protected: 9 | void SetUp() override { 10 | // Simple cubic cell 11 | cell_ = Cell({10.0, 10.0, 10.0, 90.0, 90.0, 90.0}); 12 | } 13 | Cell cell_; 14 | }; 15 | 16 | TEST_F(AngleReproductionTest, MissingAnglesWhenCutoffIsTooSmall) { 17 | // A-B-C angle. 18 | // B is at (5,5,5) 19 | // A is at (4,5,5) -> dist 1.0 20 | // C is at (5,6,5) -> dist 1.0 21 | // Angle should be 90 degrees. 22 | 23 | cell_.addAtom("O", {4.0, 5.0, 5.0}); 24 | cell_.addAtom("Si", {5.0, 5.0, 5.0}); 25 | cell_.addAtom("O", {5.0, 6.0, 5.0}); 26 | 27 | // Bond cutoff for Si-O is likely around 1.6 * 1.2 = 1.92 or similar. 28 | // Distance is 1.0. 29 | 30 | // If we set analyzer cutoff to 0.9, we should find nothing. 31 | { 32 | StructureAnalyzer analyzer(cell_, 0.9); 33 | const auto& angles = analyzer.angles(); 34 | // angles[type][center][type] 35 | // We expect empty because cutoff < distance 36 | // But wait, StructureAnalyzer might throw if cutoff is too small? No. 37 | 38 | // Check if any angles are found 39 | bool found = false; 40 | for(const auto& t1 : angles) { 41 | for(const auto& center : t1) { 42 | for(const auto& t2 : center) { 43 | if(!t2.empty()) found = true; 44 | } 45 | } 46 | } 47 | EXPECT_TRUE(found) << "Should find angles even if cutoff is smaller than bond distance (auto-correction)"; 48 | 49 | } 50 | 51 | // If we set analyzer cutoff to 1.1, we should find the angle. 52 | { 53 | StructureAnalyzer analyzer(cell_, 1.1); 54 | const auto& angles = analyzer.angles(); 55 | 56 | // Find Si (center) 57 | // We need to know element IDs. 58 | // Si is usually heavier, maybe index 1? 59 | // Let's just iterate and find 90 degrees. 60 | 61 | bool found = false; 62 | for(const auto& t1 : angles) { 63 | for(const auto& center : t1) { 64 | for(const auto& t2 : center) { 65 | for(double angle : t2) { 66 | if(std::abs(angle * 180.0 / M_PI - 90.0) < 1.0) { 67 | found = true; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | EXPECT_TRUE(found) << "Should find 90 degree angle with sufficient cutoff"; 74 | } 75 | } 76 | 77 | TEST_F(AngleReproductionTest, PBCAngleDetection) { 78 | // Atom A at 0.1 79 | // Atom B (center) at 9.9 (wrapped) -> effectively -0.1 80 | // Atom C at 9.9 (wrapped) but different axis? 81 | 82 | // Let's put Center at 0.5, 0.5, 0.5 83 | // Neighbor 1 at 9.6, 0.5, 0.5 (distance 0.9 across boundary) 84 | // Neighbor 2 at 0.5, 9.6, 0.5 (distance 0.9 across boundary) 85 | // Angle should be 90 degrees. 86 | 87 | cell_.addAtom("Si", {0.5, 0.5, 0.5}); 88 | cell_.addAtom("O", {9.6, 0.5, 0.5}); 89 | cell_.addAtom("O", {0.5, 9.6, 0.5}); 90 | 91 | StructureAnalyzer analyzer(cell_, 1.5); // Cutoff > 0.9 92 | 93 | bool found = false; 94 | const auto& angles = analyzer.angles(); 95 | for(const auto& t1 : angles) { 96 | for(const auto& center : t1) { 97 | for(const auto& t2 : center) { 98 | for(double angle : t2) { 99 | if(std::abs(angle * 180.0 / M_PI - 90.0) < 1.0) { 100 | found = true; 101 | } 102 | } 103 | } 104 | } 105 | } 106 | EXPECT_TRUE(found) << "Should find 90 degree angle across PBC"; 107 | } 108 | -------------------------------------------------------------------------------- /tests/Cell_test.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright (c) 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include 7 | #include 8 | 9 | #include "../include/Cell.hpp" 10 | #include "../include/PhysicalData.hpp" 11 | 12 | // Test fixture for the Cell class. 13 | class CellTest : public ::testing::Test { 14 | protected: 15 | // A standard 10x10x10 orthogonal cell created once for all tests in this 16 | // fixture. 17 | Cell orthogonal_cell_{{10.0, 10.0, 10.0, 90.0, 90.0, 90.0}}; 18 | }; 19 | 20 | // --- Constructor and Lattice Tests --- 21 | 22 | TEST_F(CellTest, ParameterConstructorForCubicCell) { 23 | // Arrange 24 | const std::array params = {4.0, 4.0, 4.0, 90.0, 90.0, 90.0}; 25 | 26 | // Act 27 | Cell cell(params); 28 | const auto &vectors = cell.latticeVectors(); 29 | 30 | // Assert 31 | EXPECT_NEAR(linalg::norm(vectors[0]), 4.0, 1e-9); 32 | EXPECT_NEAR(linalg::norm(vectors[1]), 4.0, 1e-9); 33 | EXPECT_NEAR(linalg::norm(vectors[2]), 4.0, 1e-9); 34 | EXPECT_NEAR(cell.volume(), 64.0, 1e-9); 35 | } 36 | 37 | TEST_F(CellTest, VectorConstructorCalculatesParameters) { 38 | // Arrange & Act 39 | Cell cell({2.0, 0.0, 0.0}, {0.0, 3.0, 0.0}, {0.0, 0.0, 4.0}); 40 | const auto ¶ms = cell.lattice_parameters(); 41 | 42 | // Assert 43 | EXPECT_NEAR(params[0], 2.0, 1e-9); // a 44 | EXPECT_NEAR(params[1], 3.0, 1e-9); // b 45 | EXPECT_NEAR(params[2], 4.0, 1e-9); // c 46 | EXPECT_NEAR(params[3], 90.0, 1e-9); // alpha 47 | EXPECT_NEAR(params[4], 90.0, 1e-9); // beta 48 | EXPECT_NEAR(params[5], 90.0, 1e-9); // gamma 49 | EXPECT_NEAR(cell.volume(), 24.0, 1e-9); 50 | } 51 | 52 | TEST_F(CellTest, VolumeForNonOrthogonalCellIsCorrect) { 53 | // Arrange 54 | const std::array params = {5.0, 6.0, 7.0, 80.0, 90.0, 100.0}; 55 | Cell cell(params); 56 | 57 | // Act: Expected volume from standard formula 58 | const double cos_a = std::cos(80.0 * constants::deg2rad); 59 | const double cos_b = std::cos(90.0 * constants::deg2rad); 60 | const double cos_g = std::cos(100.0 * constants::deg2rad); 61 | const double vol_sqrt = 1.0 - cos_a * cos_a - cos_b * cos_b - cos_g * cos_g + 62 | 2 * cos_a * cos_b * cos_g; 63 | const double expected_volume = 5.0 * 6.0 * 7.0 * std::sqrt(vol_sqrt); 64 | 65 | // Assert 66 | EXPECT_NEAR(cell.volume(), expected_volume, 1e-9); 67 | } 68 | 69 | TEST_F(CellTest, ThrowsOnInvalidLatticeParameters) { 70 | EXPECT_THROW(Cell({-5.0, 1.0, 1.0, 90.0, 90.0, 90.0}), std::invalid_argument); 71 | EXPECT_THROW(Cell({0.0, 1.0, 1.0, 90.0, 90.0, 90.0}), std::invalid_argument); 72 | } 73 | 74 | // --- Atom & Element Management Tests --- 75 | 76 | TEST_F(CellTest, AddAtomManagesElementsAndAtomsCorrectly) { 77 | // Arrange 78 | Cell cell; 79 | 80 | // Act 81 | cell.addAtom("Si", {0.0, 0.0, 0.0}); 82 | cell.addAtom("O", {1.0, 1.0, 1.0}); 83 | cell.addAtom("Si", {2.0, 2.0, 2.0}); 84 | 85 | // Assert 86 | const auto &elements = cell.elements(); 87 | const auto &atoms = cell.atoms(); 88 | 89 | ASSERT_EQ(elements.size(), 2); 90 | EXPECT_EQ(elements[0].symbol, "Si"); 91 | EXPECT_EQ(elements[0].id.value, 0); 92 | EXPECT_EQ(elements[1].symbol, "O"); 93 | EXPECT_EQ(elements[1].id.value, 1); 94 | 95 | ASSERT_EQ(atoms.size(), 3); 96 | EXPECT_EQ(atoms[0].element().symbol, "Si"); 97 | EXPECT_EQ(atoms[0].element().id.value, 0); 98 | EXPECT_EQ(atoms[1].element().symbol, "O"); 99 | EXPECT_EQ(atoms[1].element().id.value, 1); 100 | EXPECT_EQ(atoms[2].element().symbol, "Si"); 101 | EXPECT_EQ(atoms[2].element().id.value, 0); 102 | } 103 | 104 | // --- Position Manipulation Tests --- 105 | 106 | TEST_F(CellTest, WrapPositionsWrapsAtomIntoCell) { 107 | // Arrange: Uses orthogonal_cell_ from fixture 108 | orthogonal_cell_.addAtom("H", {12.0, -3.0, 5.0}); 109 | 110 | // Act 111 | orthogonal_cell_.wrapPositions(); 112 | const auto &final_pos = orthogonal_cell_.atoms().front().position(); 113 | 114 | // Assert 115 | EXPECT_NEAR(final_pos.x(), 2.0, 1e-9); // 12.0 mod 10.0 116 | EXPECT_NEAR(final_pos.y(), 7.0, 1e-9); // -3.0 mod 10.0 117 | EXPECT_NEAR(final_pos.z(), 5.0, 1e-9); // 5.0 mod 10.0 118 | } 119 | -------------------------------------------------------------------------------- /tests/FileWriter_test.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright (c) 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include // For std::max_element 7 | #include // For std::remove 8 | #include 9 | #include 10 | #include // For std::distance 11 | 12 | #include "../include/Cell.hpp" 13 | #include "../include/DistributionFunctions.hpp" 14 | #include "../include/FileIO.hpp" 15 | #include "../include/FileWriter.hpp" 16 | 17 | // Test fixture for FileWriter integration tests. 18 | class FileWriterTest : public ::testing::Test { 19 | protected: 20 | void SetUp() override { 21 | // Create a temporary CAR file for an 8-atom Silicon crystal. 22 | std::ofstream car_file("si_crystal.car"); 23 | ASSERT_TRUE(car_file.is_open()); 24 | car_file << "!BIOSYM archive 3\n"; 25 | car_file << "PBC=ON\n"; 26 | car_file << "PBC 5.431 5.431 5.431 90.0 90.0 90.0\n"; 27 | car_file << "Si1 0.0000 0.0000 0.0000 XXXX 1 xx Si 0.000\n"; 28 | car_file << "Si2 0.0000 2.7155 2.7155 XXXX 1 xx Si 0.000\n"; 29 | car_file << "Si3 2.7155 0.0000 2.7155 XXXX 1 xx Si 0.000\n"; 30 | car_file << "Si4 2.7155 2.7155 0.0000 XXXX 1 xx Si 0.000\n"; 31 | car_file << "Si5 1.3578 1.3578 1.3578 XXXX 1 xx Si 0.000\n"; 32 | car_file << "Si6 1.3578 4.0733 4.0733 XXXX 1 xx Si 0.000\n"; 33 | car_file << "Si7 4.0733 1.3578 4.0733 XXXX 1 xx Si 0.000\n"; 34 | car_file << "Si8 4.0733 4.0733 1.3578 XXXX 1 xx Si 0.000\n"; 35 | car_file << "end\n"; 36 | car_file << "end\n"; 37 | car_file.close(); 38 | } 39 | 40 | void TearDown() override { 41 | // Clean up all generated files. 42 | std::remove("si_crystal.car"); 43 | std::remove("test_si_g.csv"); 44 | std::remove("test_si_J.csv"); 45 | std::remove("test_si_G.csv"); 46 | std::remove("test_si_PAD.csv"); 47 | std::remove("test_si_g_smoothed.csv"); 48 | std::remove("test_si_J_smoothed.csv"); 49 | std::remove("test_si_G_smoothed.csv"); 50 | std::remove("test_si_PAD_smoothed.csv"); 51 | } 52 | 53 | // Helper to check if a file exists and is not empty. 54 | bool fileExistsAndIsNotEmpty(const std::string &name) { 55 | if (std::ifstream f(name); f.good()) { 56 | return f.peek() != std::ifstream::traits_type::eof(); 57 | } 58 | return false; 59 | } 60 | }; 61 | 62 | TEST_F(FileWriterTest, CalculatesAndWritesSiliconDistributions) { 63 | // Arrange 64 | FileIO::FileType type = FileIO::determineFileType("si_crystal.car"); 65 | Cell si_cell = FileIO::readStructure("si_crystal.car", type); 66 | DistributionFunctions df(si_cell, 20.0, 1.2); 67 | 68 | // Act 69 | const double rdf_bin = 0.05; 70 | const double pad_bin = 1.0; 71 | df.calculateRDF(20.0, rdf_bin); 72 | df.calculatePAD(180.0, pad_bin); 73 | df.smoothAll(0.1); 74 | 75 | FileWriter writer(df); 76 | writer.writeAllCSVs("test_si", true); 77 | 78 | // Assert: Part 1 - Validate content of the calculated g(r) histogram. 79 | 80 | const auto &rdf_hist = df.getHistogram("g(r)"); 81 | const auto &bins = rdf_hist.bins; 82 | const auto &si_si_rdf = rdf_hist.partials.at("Si-Si"); 83 | 84 | // Helper to find the index of the maximum value in a given range. 85 | auto find_peak_idx = [&](size_t start, size_t end) { 86 | auto it = 87 | std::max_element(si_si_rdf.begin() + start, si_si_rdf.begin() + end); 88 | return std::distance(si_si_rdf.begin(), it); 89 | }; 90 | 91 | // Find peaks in expected regions for crystalline silicon. 92 | // 1st neighbor shell: ~2.35 Å. Search from 2.0 to 3.0 Å. 93 | size_t first_peak_idx = find_peak_idx(2.0 / rdf_bin, 3.0 / rdf_bin); 94 | // 2nd neighbor shell: ~3.84 Å. Search from 3.5 to 4.2 Å. 95 | size_t second_peak_idx = find_peak_idx(3.5 / rdf_bin, 4.2 / rdf_bin); 96 | 97 | EXPECT_NEAR(bins[first_peak_idx], 2.35, rdf_bin * 2); 98 | EXPECT_NEAR(bins[second_peak_idx], 3.84, rdf_bin * 2); 99 | 100 | // Assert: Part 2 - Validate content of the calculated f(theta) histogram. 101 | 102 | const auto &pad_hist = df.getHistogram("f(theta)"); 103 | const auto &pad_bins = pad_hist.bins; 104 | const auto &si_si_si_pad = pad_hist.partials.at("Si-Si-Si"); 105 | 106 | auto max_it = std::max_element(si_si_si_pad.begin(), si_si_si_pad.end()); 107 | size_t peak_index = std::distance(si_si_si_pad.begin(), max_it); 108 | 109 | // The dominant peak angle should be the tetrahedral angle in silicon. 110 | EXPECT_NEAR(pad_bins[peak_index], 109.5, pad_bin * 2.0); 111 | 112 | // Assert: Part 3 - Check that all expected files were created 113 | EXPECT_TRUE(fileExistsAndIsNotEmpty("test_si_g.csv")); 114 | EXPECT_TRUE(fileExistsAndIsNotEmpty("test_si_J.csv")); 115 | EXPECT_TRUE(fileExistsAndIsNotEmpty("test_si__G.csv")); 116 | EXPECT_TRUE(fileExistsAndIsNotEmpty("test_si_PAD.csv")); 117 | } 118 | -------------------------------------------------------------------------------- /include/Cell.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "Atom.hpp" 14 | #include "LinearAlgebra.hpp" 15 | 16 | class Cell { 17 | 18 | public: 19 | //-------------------------------------------------------------------------// 20 | //----------------------------- Constructors ------------------------------// 21 | //-------------------------------------------------------------------------// 22 | explicit Cell() = default; 23 | 24 | /** 25 | * @brief Constructs a Cell from three lattice vectors. 26 | * @param a The first lattice vector. 27 | * @param b The second lattice vector. 28 | * @param c The third lattice vector. 29 | */ 30 | explicit Cell(const linalg::Vector3 &a, 31 | const linalg::Vector3 &b, 32 | const linalg::Vector3 &c); 33 | 34 | /** 35 | * @brief Constructs a Cell from lattice parameters {a, b, c, alpha, beta, 36 | * gamma}. 37 | * @param params An array containing the six lattice parameters. 38 | */ 39 | explicit Cell(const std::array ¶ms); 40 | 41 | // Move constructor 42 | Cell(Cell &&other) noexcept; 43 | 44 | // Move assignment operator 45 | Cell &operator=(Cell &&other) noexcept; 46 | 47 | // The copy constructor and assignment operator can be explicitly defaulted, 48 | // or implicitly generated if no other special member functions are declared. 49 | Cell(const Cell &) = default; 50 | Cell &operator=(const Cell &) = default; 51 | 52 | //-------------------------------------------------------------------------// 53 | //------------------------------- Accessors -------------------------------// 54 | //-------------------------------------------------------------------------// 55 | 56 | // Lattice Parameters 57 | const std::array &lattice_parameters() const { 58 | return lattice_parameters_; 59 | } 60 | void setLatticeParameters(std::array); 61 | // Lattice Vectors 62 | const linalg::Matrix3 &latticeVectors() const noexcept { 63 | return lattice_vectors_; 64 | } 65 | const linalg::Matrix3 &inverseLatticeVectors() const noexcept { 66 | return inverse_lattice_vectors_; 67 | } 68 | // Volume 69 | const double &volume() const noexcept { return volume_; } 70 | // Atoms 71 | const std::vector &atoms() const noexcept { return atoms_; } 72 | // Elements 73 | const std::vector &elements() const { return elements_; } 74 | // Bond Factor 75 | void setBondFactor(double b) { bond_factor_ = b; } 76 | double getBondFactor() { return bond_factor_; } 77 | 78 | size_t atomCount() const noexcept { return atoms_.size(); } 79 | bool isEmpty() const noexcept { return atoms_.empty(); } 80 | 81 | /** 82 | * @brief Finds the Element properties for a given element symbol. 83 | * @param symbol The element symbol (e.g., "Si"). 84 | * @return An optional containing the Element struct if found, otherwise 85 | * std::nullopt. 86 | */ 87 | std::optional findElement(const std::string &symbol) const; 88 | 89 | /** 90 | * @brief get a bond cut off for two given elements. 91 | *@param elementID 92 | *@param elementID 93 | **/ 94 | double getBondCutoff(int, int); 95 | 96 | //-------------------------------------------------------------------------// 97 | //-------------------------------- Methods --------------------------------// 98 | //-------------------------------------------------------------------------// 99 | 100 | /** 101 | * @brief Pre-calculates the squared bond cutoff distances for every pair of 102 | * element types. 103 | */ 104 | void precomputeBondCutoffs(); 105 | 106 | /** 107 | * @brief Adds a new atom to the cell. 108 | * The atom's element type is automatically registered. 109 | * @param symbol The element symbol of the atom. 110 | * @param position The Cartesian position of the atom. 111 | * @return The newly created Atom object. 112 | */ 113 | Atom &addAtom(const std::string &symbol, 114 | const linalg::Vector3 &position); 115 | 116 | /** 117 | * @brief Applies periodic boundary conditions to all atom positions. 118 | * Wraps all atoms back into the primary simulation cell [0, 1) in fractional 119 | * coordinates. 120 | */ 121 | void wrapPositions(); 122 | 123 | private: 124 | void updateLattice(const linalg::Matrix3 &new_lattice); 125 | void updateLatticeParametersFromVectors(); 126 | ElementID getOrRegisterElement(const std::string &symbol); 127 | 128 | linalg::Matrix3 lattice_vectors_; 129 | linalg::Matrix3 inverse_lattice_vectors_; 130 | std::array lattice_parameters_; 131 | double volume_{0.0}; 132 | double bond_factor_{1.2}; 133 | std::vector atoms_; 134 | std::vector elements_; 135 | std::vector> bond_cutoffs_sq_; 136 | }; 137 | -------------------------------------------------------------------------------- /include/Smoothing.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../include/PhysicalData.hpp" 15 | 16 | // Enum class for type-safe selection of kernel types. 17 | // This prevents passing invalid integer values. 18 | enum class KernelType { Gaussian, Bump, Triweight }; 19 | 20 | /** 21 | * @brief Applies kernel smoothing to a data series. 22 | * 23 | * This function convolves a given kernel with the input data `y` to produce a 24 | * smoothed version. 25 | * 26 | * @param r The independent variable values (x-axis). 27 | * @param y The dependent variable values (y-axis) to be smoothed. 28 | * @param sigma The bandwidth (standard deviation for Gaussian) of the kernel, 29 | * in the same units as the 'r' bins (e.g., Å or degrees). 30 | * @param type The type of kernel to use for smoothing. 31 | * @return A vector containing the smoothed data. 32 | */ 33 | 34 | inline std::vector generateKernel(size_t size, double dx, double sigma, 35 | KernelType type) { 36 | std::vector kernel(size); 37 | // Center of the discrete kernel in bin index units. 38 | const double center = static_cast(size - 1) / 2.0; 39 | 40 | if (type == KernelType::Gaussian) { 41 | // 1 / (sigma * sqrt(2*pi)) 42 | const double a = 1.0 / (std::sqrt(2 * constants::pi) * sigma); 43 | const double b = -1.0 / (2.0 * sigma * sigma); 44 | for (size_t i = 0; i < size; ++i) { 45 | // Calculate x in distance units (r units) 46 | const double x = (static_cast(i) - center) * dx; 47 | kernel[i] = a * std::exp(b * x * x); 48 | } 49 | } else if (type == KernelType::Triweight) { 50 | constexpr double factor = 35.0 / 32.0; 51 | for (size_t i = 0; i < size; ++i) { 52 | // Calculate u (distance normalized by sigma) 53 | const double u = ((static_cast(i) - center) * dx) / sigma; 54 | if (std::abs(u) <= 1.0) { 55 | const double u2 = u * u; 56 | kernel[i] = factor * std::pow(1 - u2, 3); 57 | } else { 58 | kernel[i] = 0.0; 59 | } 60 | } 61 | } else if (type == KernelType::Bump) { 62 | for (size_t i = 0; i < size; ++i) { 63 | // Calculate u (distance normalized by sigma) 64 | const double u = ((static_cast(i) - center) * dx) / sigma; 65 | if (std::abs(u) < 1.0) { 66 | const double u2 = u * u; 67 | kernel[i] = std::exp(-1.0 / (1.0 - u2)); 68 | } else { 69 | kernel[i] = 0.0; 70 | } 71 | } 72 | } else { 73 | throw std::invalid_argument("Unsupported kernel type for smoothing."); 74 | } 75 | 76 | // Normalize the discrete kernel to ensure the sum of its elements is 1. 77 | const double sum = std::accumulate(kernel.begin(), kernel.end(), 0.0); 78 | if (sum > 1e-9) { 79 | for (double &val : kernel) { 80 | val /= sum; 81 | } 82 | } 83 | return kernel; 84 | } 85 | 86 | inline std::vector KernelSmoothing(const std::vector &r, 87 | const std::vector &y, 88 | double sigma, KernelType type) { 89 | if (r.size() != y.size() || r.empty()) { 90 | return {}; // Return empty vector for invalid input 91 | } 92 | if (sigma <= 0.0) { 93 | throw std::invalid_argument("Smoothing sigma must be positive."); 94 | } 95 | 96 | const size_t n = r.size(); 97 | const double dx = (n > 1) ? (r[1] - r[0]) : 0; 98 | if (dx <= 0) { 99 | throw std::invalid_argument( 100 | "Input 'r' must be uniformly increasing for smoothing."); 101 | } 102 | 103 | // Calculate the kernel radius in bins by dividing the physical sigma by dx. 104 | // We use 4.0 * sigma to cover approximately 99.99% of the Gaussian area. 105 | size_t kernel_radius_bins = static_cast(4.0 * sigma / dx); 106 | 107 | // Clamp the radius to prevent the kernel from exceeding half the data size. 108 | const size_t max_kernel_radius = n / 2; 109 | size_t kernel_radius = std::min(kernel_radius_bins, max_kernel_radius); 110 | 111 | // Ensure a minimum kernel size (3 bins: -1, 0, +1) if sigma is very small. 112 | if (kernel_radius == 0) { 113 | kernel_radius = 1; 114 | } 115 | 116 | const size_t kernel_size = 2 * kernel_radius + 1; 117 | 118 | // Pass the original physical sigma (in distance units) to the kernel 119 | // generator. 120 | const auto kernel = generateKernel(kernel_size, dx, sigma, type); 121 | std::vector smoothed(n, 0.0); 122 | 123 | // Apply the convolution. 124 | for (size_t i = 0; i < n; ++i) { 125 | for (size_t j = 0; j < kernel_size; ++j) { 126 | const long long data_idx = static_cast(i) + 127 | static_cast(j) - 128 | static_cast(kernel_radius); 129 | 130 | // Handle boundary conditions: clamp the index to [0, n-1]. 131 | // This ensures safe access and uses the boundary values for convolution 132 | // with kernel points outside the data range. 133 | const long long clamped_idx = std::clamp(data_idx, 0, n - 1); 134 | 135 | smoothed[i] += y[clamped_idx] * kernel[j]; 136 | } 137 | } 138 | 139 | return smoothed; 140 | } 141 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [1.3] 2025-12-04 5 | 6 | ### Added 7 | - Angle reproduction tests and example structure file. 8 | - FiraCode fonts support. 9 | - Feedback mechanism from backend to App. 10 | - LabeledEdit component and InputGroup to AppWindow. 11 | - Theme support. 12 | - ToolTips for most parameters in the GUI. 13 | - Application icon for Windows and Linux. 14 | - Logo to window component. 15 | - All parameters are now available in the GUI. 16 | - Move semantics for DistributionFunction and Cell objects. 17 | - Portable file dialogs support. 18 | 19 | ### Changed 20 | - Refined UI layout and responsiveness. 21 | - Moved App logic to a new AppController object. 22 | - Moved Bond Distance matrix to the Cell object. 23 | - Reimplemented Bump kernel in Smoothing header. 24 | - Moved constants to PhysicalData header. 25 | - Reduced ProgramOptions to a single header file. 26 | - Updated UI and Backend initialization. 27 | 28 | ### Fixed 29 | - Multiple fixes for Windows compilation and CMake configuration (MSVC 2022). 30 | - Corrections for Path handling in Windows. 31 | - Fixes to Smoothing for PAD, CN, and when bin width/sigma were problematic. 32 | - Fixes to g(r), J(r), G(r), and S(Q) calculations. 33 | - Fixed Total PAD not being calculated. 34 | - Fixed global path to app_window.h. 35 | 36 | ### Removed 37 | - Removed code for writing histograms. 38 | - Removed min Q for S(Q) calculation and CSV writers. 39 | - Removed redundant code and old constants header references. 40 | 41 | ## [1.2] 2025-09-15 42 | - Several improvements were made to the code in order to modernize 43 | and incorporate new features. 44 | - Added GUI for easy of use. 45 | 46 | 47 | ## [1.0.4] 2021-12-21 48 | - Added support for self interactions, important for small crystals. 49 | - Switch to std::filesystem to generate paths. 50 | - Added Smoothing option: Kernel Smoothing with Gaussian Kernel. 51 | - Added BumpKernel as second option, more kernels will be added. 52 | - Added Total correlation functions (previously only partials were reported). 53 | - Added initial support for Voronoi and Voronoi index. (Road to 1.1) 54 | 55 | ## [1.0.3] 2021-10-12 56 | - Major refactor of the code in preparation for MultiThreading. 57 | - The code now comply with google c++ style guidelines. 58 | - Modify the name for the Coordination Number (Z). 59 | - Modify the labels in the coordination number file (Z)to comply 60 | with the standard atom A surrounded by atoms B: A by B. 61 | 62 | ## [1.0.2] 2021-09-17 63 | - Minor style corrections to paper.md and README.md. 64 | - Updated DOI for JOSS paper. 65 | 66 | ## [1.0.1] 2021-09-11 67 | - Minor fix to paper.md to correct a compile error from whedon. 68 | 69 | ## [1.0.0] 2021-09-06 70 | - First official release. 71 | 72 | ## [1.0.0 rc9] 2021-09-06 73 | - Added test 8, an structure of amorphous Palladium-Gold-Hydrogen, 74 | the structure was provided by Alejandro de Leon and Salvador Villareal. 75 | This new test should take at least 1 hour and require more RAM. 76 | - Added new option '-n'/"--normalize", this parameter change between 77 | weighted partials (default), or normalized partials. 78 | 79 | ## [1.0.0 rc8] - 2021-09-02 80 | - Temporal Fix to S(Q) weird behavior for small Q. (0<=Q<1) 81 | Because of the abnormal results for small Q, and in the process of 82 | getting better results for small Q, we decided to remove the unreliable 83 | information for this segment, now we report for Q above 1.0. 84 | - Added -q/--q_bin_width parameter in order to further personalize the 85 | structure factor options. 86 | - Updated the "aPdH.cell" file, in order to correctly reflect the 87 | structure of the article. 88 | - Added arguments to tests in the Makefile: 89 | -a in test 1, 90 | -b in test 2, 91 | -q in test 3, 92 | -i was already in test 4, 93 | -w in test 5, 94 | -r in test 6. 95 | - Fixed padding in output files (purely cosmetic). 96 | - Fixed Tests to match new padding and options (see above). 97 | - Update to documentation: README.md, paper.md and command line help 98 | to reflect current functionality. 99 | 100 | ## [1.0.0 rc7] - 2021-08-01 101 | - Fixed a bug in reduced Pair Distribution Function (rPDF) (G(r)) 102 | in alloys (pure element structures unaffected). 103 | - Added weighing to the calculated properties, related to the above bug. 104 | - Added units to Comma Separated Values (CSV) files. 105 | - Actualized the test archives to match the new headings with units. 106 | 107 | 108 | ## [1.0.0 rc6] - 2021-07-28 109 | - Added Contributing guidelines. 110 | - Added Code of Conduct. 111 | - Fixed several typos in the README and paper. 112 | - Fixed a bug in Bin Width option (-w, --bin_width). 113 | - Fixed the command "make clean-all" to match the README. 114 | 115 | ## [1.0.0 rc5] - 2021-07-23 116 | - Several Typos in the project were corrected. 117 | - Added units to the output files. 118 | - Corrected paper.md to comply with reviewers corrections. 119 | 120 | ## [1.0.0 rc4] - 2021-04-11 121 | - Major revision to README file 122 | - Minor bugs corrections in main.cpp and Makefile.am 123 | 124 | ## [1.0.0 rc3] - 2021-03-04 125 | 126 | ### Added 127 | - Tests now check validity of the results. 128 | - Test 7 (make test7), also known as an stress test has been incorporated. 129 | This test should take several minutes and a large amount of RAM. 130 | - Added a new option to clean the test directory (make clean-test) 131 | 132 | ### Modify 133 | - clean-local option now leaves a working Makefile and the Binary. 134 | This modification should help with testing, while keeping a clean 135 | directory. 136 | - cleanall option now replace clean-local 137 | 138 | ## [1.0.0 rc2] - 2021-02-28 139 | 140 | ### Added 141 | - configure and Makefile.in, generated with GNU autotools. 142 | - AUTHORS and Changelog files now included. 143 | 144 | ### Modify 145 | - Old Makefile is included as Makefile.old 146 | -------------------------------------------------------------------------------- /tests/StructureAnalyzer_test.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright (c) 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include 7 | 8 | #include "../include/Atom.hpp" 9 | #include "../include/Cell.hpp" 10 | #include "../include/PhysicalData.hpp" 11 | #include "../include/StructureAnalyzer.hpp" 12 | 13 | // A test fixture for StructureAnalyzer tests. 14 | class StructureAnalyzerTest : public ::testing::Test {}; 15 | 16 | TEST_F(StructureAnalyzerTest, FindsCorrectNeighborsForSilicon) { 17 | // Arrange: Create an 8-atom conventional unit cell of Silicon. 18 | // The diamond lattice structure is a robust test for neighbor finding. 19 | const double lattice_const = 5.43; // Angstroms 20 | Cell si_cell({lattice_const, lattice_const, lattice_const, 90.0, 90.0, 90.0}); 21 | 22 | // Fractional coordinates for the 8 atoms in a diamond cubic cell 23 | std::vector> fractional_coords = { 24 | {0.0, 0.0, 0.0}, {0.5, 0.5, 0.0}, {0.5, 0.0, 0.5}, 25 | {0.0, 0.5, 0.5}, {0.25, 0.25, 0.25}, {0.75, 0.75, 0.25}, 26 | {0.75, 0.25, 0.75}, {0.25, 0.75, 0.75}}; 27 | 28 | for (const auto &frac_pos : fractional_coords) { 29 | // Convert fractional to Cartesian coordinates before adding to the cell 30 | si_cell.addAtom("Si", si_cell.latticeVectors() * frac_pos); 31 | } 32 | 33 | // Act: Calculate neighbors with a cutoff just beyond the first neighbor 34 | // shell. The first nearest neighbor distance in Si is (sqrt(3)/4)*a 35 | // approx 2.35 Å. 36 | StructureAnalyzer neighbors(si_cell, 3.0); 37 | const auto &neighborMatrix = neighbors.neighbors(); 38 | const auto &atoms = si_cell.atoms(); 39 | 40 | // Assert 41 | ASSERT_EQ(neighborMatrix.size(), 8); 42 | const double expected_distance = 2.3512; // More precise value 43 | 44 | // Every Si atom in a diamond lattice must have exactly 4 nearest neighbors. 45 | for (const auto neighbors : neighborMatrix) { 46 | ASSERT_EQ(neighbors.size(), 4); 47 | // Check that the distance to each of these neighbors is correct. 48 | for (const auto neighbor : neighbors) { 49 | EXPECT_NEAR(neighbor.distance, expected_distance, 1e-4); 50 | } 51 | } 52 | } 53 | 54 | TEST_F(StructureAnalyzerTest, CalculatesCorrectAnglesForWater) { 55 | // Arrange: Create a single water molecule with a known bond angle. 56 | Cell water_cell({20.0, 20.0, 20.0, 90.0, 90.0, 90.0}); 57 | const double bond_length = 0.957; // Angstroms 58 | const double bond_angle_deg = 104.5; // Degrees 59 | const double bond_angle_rad = bond_angle_deg * constants::deg2rad; 60 | 61 | water_cell.addAtom("O", {10.0, 10.0, 10.0}); 62 | water_cell.addAtom("H", {10.0 + bond_length, 10.0, 10.0}); 63 | water_cell.addAtom("H", 64 | {10.0 + bond_length * std::cos(bond_angle_rad), 65 | 10.0 + bond_length * std::sin(bond_angle_rad), 10.0}); 66 | 67 | // Act: Calculate neighbors and angles. 68 | StructureAnalyzer neighbors(water_cell, 69 | 3.0); // Cutoff to include only H-O bonds 70 | const auto &angles = neighbors.angles(); 71 | 72 | // Assert: We need to get the element IDs to index the angle tensor correctly. 73 | const int h_id = water_cell.findElement("H")->id.value; 74 | const int o_id = water_cell.findElement("O")->id.value; 75 | ASSERT_EQ(h_id, 1); 76 | ASSERT_EQ(o_id, 0); 77 | 78 | // The angle tensor is indexed by [type1][central_type][type2]. 79 | ASSERT_TRUE(angles.size() > h_id && angles[h_id].size() > o_id && 80 | angles[h_id][o_id].size() > h_id); 81 | 82 | const auto &hoh_angles = angles[h_id][o_id][h_id]; 83 | 84 | // There should be exactly one H-O-H angle in this system. 85 | ASSERT_EQ(hoh_angles.size(), 1); 86 | // The calculated angle should match the known value. 87 | EXPECT_NEAR(hoh_angles[0] * constants::rad2deg, bond_angle_deg, 1e-4); 88 | } 89 | 90 | TEST_F(StructureAnalyzerTest, CalculatesCorrectAngleWithPBC) { 91 | // Arrange: Setup a system where the angle calculation requires the Minimum 92 | // Image Convention. Central atom B (index 1) at (0.5, 0.5, 0.5). Neighbor A 93 | // (index 0) at (3.5, 0.5, 0.5) -> PBC vector B->A is (-1.0, 0.0, 0.0). 94 | // Neighbor C (index 2) at (0.5, 3.5, 0.5) -> PBC vector B->C is (0.0, -1.0, 95 | // 0.0). The resulting angle A-B-C must be 90 degrees (pi/2 radians). 96 | 97 | const double side_length = 10.0; 98 | const double cutoff = 2.0; 99 | // Calculate pi/2 explicitly using acos(-1.0) = pi. 100 | const double expected_angle_rad = std::acos(-1.0) / 2.0; 101 | 102 | Cell pbc_cell({side_length, side_length, side_length, 90.0, 90.0, 90.0}); 103 | 104 | // B (Central) at (0.5, 0.5, 0.5) 105 | // A at (9.0, 0.5, 0.5) -> Wrapped dist to B is 1.5 106 | // C at (0.5, 9.0, 0.5) -> Wrapped dist to B is 1.5 107 | // Dist A-C is sqrt(1.5^2 + 1.5^2) = 2.12 > bond_cutoff (~1.85) 108 | 109 | pbc_cell.addAtom("C", {9.0, 0.5, 0.5}); // Atom A 110 | pbc_cell.addAtom("C", {0.5, 0.5, 0.5}); // Atom B (Central) 111 | pbc_cell.addAtom("O", {0.5, 9.0, 0.5}); // Atom C 112 | 113 | // Act: Calculate neighbors and angles. 114 | StructureAnalyzer analyzer(pbc_cell, cutoff); 115 | const auto &angles = analyzer.angles(); 116 | 117 | // Assert 118 | // Retrieve the element ID for indexing (should be 0). 119 | const int c_id = pbc_cell.findElement("C")->id.value; 120 | ASSERT_EQ(c_id, 0); 121 | 122 | // Retrieve the element ID for indexing (should be 0). 123 | const int o_id = pbc_cell.findElement("O")->id.value; 124 | ASSERT_EQ(o_id, 1); 125 | 126 | // The angle is stored in the [C][C][O] slot. 127 | const auto &cco_angles = angles[c_id][c_id][o_id]; 128 | 129 | // There must be exactly one angle calculated (A-B-C). 130 | ASSERT_EQ(cco_angles.size(), 1); 131 | 132 | // The calculated angle should be pi/2 radians. 133 | EXPECT_NEAR(cco_angles[0], expected_angle_rad, 1e-6); 134 | } 135 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(Correlation VERSION 1.3 LANGUAGES CXX) 3 | 4 | # Set the C++ standard as a target property, not a global variable. 5 | set(CXX_STANDARD 23) 6 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 7 | 8 | # Set default install path 9 | set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "Installation directory") 10 | 11 | # ----------------------------------------------------------- 12 | # Dependencies 13 | # ----------------------------------------------------------- 14 | # Find dependencies 15 | find_package(TBB QUIET COMPONENTS tbb tbbmalloc) 16 | if (NOT TBB_FOUND) 17 | message("TBB could not be located. Downloading it from GitHub and building it locally.") 18 | include(FetchContent) 19 | FetchContent_Declare( 20 | TBB 21 | GIT_REPOSITORY https://github.com/uxlfoundation/oneTBB.git 22 | GIT_TAG v2022.2.0 23 | ) 24 | FetchContent_MakeAvailable(TBB) 25 | endif() 26 | 27 | find_package(Slint QUIET) 28 | if (NOT Slint_FOUND) 29 | message("Slint could not be located in the CMake module search path. Downloading it from Git and building it locally") 30 | include(FetchContent) 31 | FetchContent_Declare( 32 | Slint 33 | GIT_REPOSITORY https://github.com/slint-ui/slint.git 34 | GIT_TAG v1.13.1 35 | SOURCE_SUBDIR api/cpp 36 | ) 37 | FetchContent_MakeAvailable(Slint) 38 | endif (NOT Slint_FOUND) 39 | 40 | 41 | # ----------------------------------------------------------- 42 | # Helper Function for Bundling DLLs on Windows 43 | # ----------------------------------------------------------- 44 | function(bundle_windows_dlls target_name) 45 | if(WIN32) 46 | add_custom_command( 47 | TARGET ${target_name} POST_BUILD 48 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 49 | $ 50 | $ 51 | COMMAND_EXPAND_LISTS 52 | COMMENT "Copying runtime DLLs for ${target_name}" 53 | ) 54 | endif() 55 | endfunction() 56 | 57 | 58 | #----------------------------------------------------------- 59 | # Library setup 60 | #----------------------------------------------------------- 61 | 62 | # Add sources to library 63 | add_library(correlation_lib STATIC 64 | src/Cell.cpp 65 | src/DistributionFunctions.cpp 66 | src/FileIO.cpp 67 | src/FileWriter.cpp 68 | src/StructureAnalyzer.cpp 69 | ) 70 | 71 | # Include directories 72 | target_include_directories(correlation_lib PUBLIC 73 | $ 74 | $ 75 | ) 76 | 77 | target_compile_features(correlation_lib PUBLIC cxx_std_${CXX_STANDARD}) 78 | target_compile_definitions(correlation_lib PUBLIC _PSTL_PAR_BACKEND_TBB) 79 | target_link_libraries(correlation_lib PRIVATE TBB::tbb TBB::tbbmalloc) 80 | 81 | #------------------------------------------------------------------ 82 | # Executable Target (correlation) 83 | #------------------------------------------------------------------ 84 | set(CORRELATION_SOURCES 85 | src/main.cpp 86 | src/AppBackend.cpp 87 | src/AppController.cpp 88 | ) 89 | 90 | if (WIN32) 91 | list(APPEND CORRELATION_SOURCES resource.rc) 92 | add_executable(correlation WIN32 ${CORRELATION_SOURCES}) 93 | else() 94 | add_executable(correlation ${CORRELATION_SOURCES}) 95 | endif() 96 | 97 | 98 | # Link correlation_lib to correlation 99 | target_link_libraries(correlation 100 | correlation_lib 101 | Slint::Slint 102 | ) 103 | 104 | # Add the include directory for the generated slint header 105 | target_include_directories(correlation PRIVATE 106 | ${CMAKE_CURRENT_BINARY_DIR} 107 | ) 108 | 109 | slint_target_sources(correlation 110 | ui/AppWindow.slint 111 | ) 112 | 113 | # Call the helper function to bundle DLLs on Windows 114 | bundle_windows_dlls(correlation) 115 | 116 | #----------------------------------------------------------- 117 | # Testing setup (using GoogleTest) 118 | #----------------------------------------------------------- 119 | enable_testing() 120 | 121 | # Modern GoogleTest fetch 122 | include(FetchContent) 123 | FetchContent_Declare( 124 | googletest 125 | GIT_REPOSITORY https://github.com/google/googletest.git 126 | GIT_TAG v1.16.0 127 | ) 128 | FetchContent_MakeAvailable(googletest) 129 | 130 | # Explicitly list test sources. 131 | add_executable(correlation_tests 132 | tests/Atom_test.cpp 133 | tests/Cell_test.cpp 134 | tests/Distribution_Functions_tests.cpp 135 | tests/FileIO_test.cpp 136 | tests/FileWriter_test.cpp 137 | tests/StructureAnalyzer_test.cpp 138 | tests/Angle_Reproduction_tests.cpp 139 | tests/PAD_tests.cpp 140 | 141 | 142 | ) 143 | 144 | # The test executable also links to the library and inherits its properties. 145 | target_link_libraries(correlation_tests PRIVATE 146 | correlation_lib 147 | gtest_main 148 | ) 149 | 150 | # Call the helper function to bundle DLLs on Windows 151 | bundle_windows_dlls(correlation_tests) 152 | 153 | include(GoogleTest) 154 | gtest_discover_tests(correlation_tests DISCOVERY_TIMEOUT 60) 155 | 156 | #----------------------------------------------------------- 157 | # Install setup 158 | #----------------------------------------------------------- 159 | 160 | # Install the executable and the library. 161 | install(TARGETS correlation 162 | RUNTIME DESTINATION bin 163 | ) 164 | 165 | # ----------------------------------------------------------- 166 | # Install setup for Linux Desktop Integration 167 | # ----------------------------------------------------------- 168 | if (NOT WIN32) 169 | # 1. Install the Icon (adjust path if you use a different icon directory) 170 | install(FILES "${PROJECT_SOURCE_DIR}/Images/Logo.png" 171 | DESTINATION share/icons/hicolor/scalable/apps 172 | RENAME correlation.png) 173 | 174 | # 2. Configure and Install the .desktop file 175 | configure_file("${PROJECT_SOURCE_DIR}/correlation.desktop" 176 | "${PROJECT_BINARY_DIR}/correlation.desktop" 177 | COPYONLY) 178 | install(FILES "${PROJECT_BINARY_DIR}/correlation.desktop" 179 | DESTINATION share/applications) 180 | endif() 181 | -------------------------------------------------------------------------------- /src/AppController.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #if defined(_WIN32) 7 | #define NOMINMAX 8 | #include 9 | #endif 10 | 11 | #include "../include/AppController.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | //---------------------------------------------------------------------------// 18 | //------------------------------- Constructors ------------------------------// 19 | //---------------------------------------------------------------------------// 20 | 21 | AppController::AppController(AppWindow &ui, AppBackend &backend) 22 | : ui_(ui), backend_(backend) { 23 | 24 | // default options to UI 25 | handleOptionstoUI(ui_); 26 | 27 | // Connect the UI signals to the controller's member functions 28 | ui_.on_run_analysis([this]() { handleRunAnalysis(); }); 29 | ui_.on_browse_file([this]() { handleBrowseFile(); }); 30 | ui_.on_check_file_dialog_status([this]() { handleCheckFileDialogStatus(); }); 31 | } 32 | 33 | //---------------------------------------------------------------------------// 34 | //--------------------------------- Helpers ---------------------------------// 35 | //---------------------------------------------------------------------------// 36 | 37 | // Safe conversion helper 38 | float safe_stof(const slint::SharedString &s, float default_value) { 39 | try { 40 | return std::stof(s.data()); 41 | } catch (const std::exception &e) { 42 | // Optionally, log the error or update a UI status message 43 | return default_value; 44 | } 45 | } 46 | 47 | void AppController::handleOptionstoUI(AppWindow &ui) { 48 | ProgramOptions opt = backend_.options(); 49 | ui.set_in_file_text(slint::SharedString(opt.input_file)); 50 | ui.set_normalize(opt.normalize); 51 | ui.set_smoothing(opt.smoothing); 52 | ui.set_r_max(slint::SharedString(std::format("{:.2f}", opt.r_max))); 53 | ui.set_r_bin_width( 54 | slint::SharedString(std::format("{:.2f}", opt.r_bin_width))); 55 | ui.set_q_max(slint::SharedString(std::format("{:.2f}", opt.q_max))); 56 | ui.set_q_bin_width( 57 | slint::SharedString(std::format("{:.2f}", opt.q_bin_width))); 58 | ui.set_r_int_max(slint::SharedString(std::format("{:.2f}", opt.r_int_max))); 59 | ui.set_angle_max(slint::SharedString(std::format("{:.2f}", opt.angle_max))); 60 | ui.set_angle_bin_width( 61 | slint::SharedString(std::format("{:.2f}", opt.angle_bin_width))); 62 | ui.set_bond_factor( 63 | slint::SharedString(std::format("{:.2f}", opt.bond_factor))); 64 | ui.set_smoothing_sigma( 65 | slint::SharedString(std::format("{:.2f}", opt.smoothing_sigma))); 66 | ui.set_smoothing_kernel(static_cast(opt.smoothing_kernel)); 67 | }; 68 | 69 | ProgramOptions AppController::handleOptionsfromUI(AppWindow &ui) { 70 | ProgramOptions opt; 71 | const std::string input_path_str = ui_.get_in_file_text().data(); 72 | std::filesystem::path full_path(input_path_str); 73 | const std::string output_path_base = 74 | full_path.parent_path().string() + "/" + full_path.stem().string(); 75 | opt.input_file = input_path_str; 76 | opt.output_file_base = output_path_base; 77 | opt.normalize = ui_.get_normalize(); 78 | opt.smoothing = ui_.get_smoothing(); 79 | opt.r_max = safe_stof(ui_.get_r_max(), opt.r_max); 80 | opt.r_bin_width = safe_stof(ui_.get_r_bin_width(), opt.r_bin_width); 81 | opt.q_max = safe_stof(ui_.get_q_max(), opt.q_max); 82 | opt.q_bin_width = safe_stof(ui_.get_q_bin_width(), opt.q_bin_width); 83 | opt.r_int_max = safe_stof(ui_.get_r_int_max(), opt.r_int_max); 84 | opt.angle_max = safe_stof(ui_.get_angle_max(), opt.angle_max); 85 | opt.angle_bin_width = 86 | safe_stof(ui_.get_angle_bin_width(), opt.angle_bin_width); 87 | opt.bond_factor = safe_stof(ui_.get_bond_factor(), opt.bond_factor); 88 | opt.smoothing_sigma = 89 | safe_stof(ui_.get_smoothing_sigma(), opt.smoothing_sigma); 90 | opt.smoothing_kernel = static_cast(ui_.get_smoothing_kernel()); 91 | 92 | return opt; 93 | }; 94 | 95 | //---------------------------------------------------------------------------// 96 | //--------------------------------- Methods ---------------------------------// 97 | //---------------------------------------------------------------------------// 98 | 99 | void AppController::handleRunAnalysis() { 100 | // Create a ProgramOptions object from the UI properties 101 | backend_.setOptions(handleOptionsfromUI(ui_)); 102 | 103 | // run analysis 104 | backend_.run_analysis(); 105 | ui_.set_analysis_status_text("Analysis ended."); 106 | } 107 | 108 | void AppController::handleBrowseFile() { 109 | std::vector filters = {"Supported Structure Files", 110 | "*.car *.cell *.cif *.dat", 111 | "Materials Studio CAR", 112 | "*.car", 113 | "CASTEP CELL", 114 | "*.cell", 115 | "CIF files", 116 | "*.cif", 117 | "ONETEP DAT", 118 | "*.dat", 119 | "All Files", 120 | "*"}; 121 | 122 | current_file_dialog_ = std::make_unique( 123 | "Select a structure file", "", filters, pfd::opt::multiselect); 124 | 125 | ui_.set_timer_running(true); 126 | ui_.set_text_opacity(true); 127 | } 128 | 129 | void AppController::handleCheckFileDialogStatus() { 130 | std::string message = "File selection cancelled."; 131 | if (current_file_dialog_ && current_file_dialog_->ready(0)) { 132 | auto selection = current_file_dialog_->result(); 133 | if (!selection.empty()) { 134 | ui_.set_in_file_text(slint::SharedString(selection[0])); 135 | try { 136 | message = backend_.load_file(selection[0]); 137 | } catch (const std::exception &e) { 138 | message = "Error loading file: " + std::string(e.what()); 139 | } 140 | } 141 | ui_.set_file_status_text(slint::SharedString(message)); 142 | ui_.set_timer_running(false); 143 | current_file_dialog_.reset(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /aps.csl: -------------------------------------------------------------------------------- 1 | 2 | 158 | -------------------------------------------------------------------------------- /tests/FileIO_test.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright (c) 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include 7 | #include 8 | 9 | #include "../include/Cell.hpp" 10 | #include "../include/FileIO.hpp" 11 | #include "../include/LinearAlgebra.hpp" 12 | 13 | // A single test fixture for all File I/O related tests. 14 | // This fixture handles the creation and cleanup of temporary files needed for 15 | // tests, ensuring that tests are self-contained and do not rely on external 16 | // data files. 17 | class FileIOTest : public ::testing::Test { 18 | protected: 19 | // This function runs before each test to create temporary files. 20 | void SetUp() override { 21 | // Create a temporary CAR file 22 | std::ofstream car_file("test.car"); 23 | ASSERT_TRUE(car_file.is_open()); 24 | car_file << "!BIOSYM archive 3\n"; 25 | car_file << "PBC=ON\n"; 26 | car_file << "PBC 10.5 11.5 12.5 90.0 90.0 90.0\n"; 27 | car_file << "C1 1.00 2.00 3.00 XXXX 1 xx C 0.000\n"; 28 | car_file << "Si2 4.50 5.50 6.50 XXXX 1 xx Si 0.000\n"; 29 | car_file << "end\n"; 30 | car_file << "end\n"; 31 | car_file.close(); 32 | 33 | // Create a temporary CELL file 34 | std::ofstream cell_file("test.cell"); 35 | ASSERT_TRUE(cell_file.is_open()); 36 | cell_file << "%BLOCK LATTICE_ABC\n"; 37 | cell_file << " 15.0 15.0 20.0\n"; 38 | cell_file << " 90.0 90.0 120.0\n"; 39 | cell_file << "%ENDBLOCK LATTICE_ABC\n\n"; 40 | cell_file << "%BLOCK POSITIONS_ABS\n"; 41 | cell_file << " C 1.1 2.2 3.3\n"; 42 | cell_file << " O 4.4 5.5 6.6\n"; 43 | cell_file << "%ENDBLOCK POSITIONS_ABS\n"; 44 | cell_file.close(); 45 | 46 | // Create a temporary CIF file for a simple rock-salt structure 47 | std::ofstream cif_file("test.cif"); 48 | ASSERT_TRUE(cif_file.is_open()); 49 | cif_file << "data_NaCl\n"; 50 | cif_file 51 | << "_cell_length_a 5.64\n_cell_length_b 5.64\n_cell_length_c 5.64\n"; 52 | cif_file 53 | << "_cell_angle_alpha 90\n_cell_angle_beta 90\n_cell_angle_gamma 90\n"; 54 | cif_file << "loop_\n_atom_site_type_symbol\n_atom_site_fract_x\n_atom_site_" 55 | "fract_y\n_atom_site_fract_z\n"; 56 | cif_file << " Na 0.0 0.0 0.0\n Cl 0.5 0.5 0.5\n"; 57 | cif_file << "loop_\n_symmetry_equiv_pos_as_xyz\n 'x, y, z'\n"; 58 | cif_file << "loop_\n"; 59 | cif_file.close(); 60 | } 61 | 62 | // This function runs after each test to clean up temporary files. 63 | void TearDown() override { 64 | remove("test.car"); 65 | remove("test.cell"); 66 | remove("test.cif"); 67 | } 68 | }; 69 | 70 | //----------------------------------------------------------------------------// 71 | //--------------------------------- Test Cases -------------------------------// 72 | //----------------------------------------------------------------------------// 73 | 74 | TEST_F(FileIOTest, ReadCarFileCorrectly) { 75 | // Arrange & Act 76 | FileIO::FileType type = FileIO::determineFileType("test.car"); 77 | Cell result_cell = FileIO::readStructure("test.car", type); 78 | 79 | // Assert: Check lattice parameters 80 | const auto ¶ms = result_cell.lattice_parameters(); 81 | EXPECT_DOUBLE_EQ(params[0], 10.5); 82 | EXPECT_DOUBLE_EQ(params[1], 11.5); 83 | EXPECT_DOUBLE_EQ(params[2], 12.5); 84 | EXPECT_DOUBLE_EQ(params[3], 90.0); 85 | EXPECT_DOUBLE_EQ(params[4], 90.0); 86 | EXPECT_DOUBLE_EQ(params[5], 90.0); 87 | 88 | // Assert: Check atoms 89 | const auto &atoms = result_cell.atoms(); 90 | ASSERT_EQ(atoms.size(), 2); 91 | 92 | EXPECT_EQ(atoms[0].element().symbol, "C"); 93 | EXPECT_DOUBLE_EQ(atoms[0].position().x(), 1.0); 94 | EXPECT_DOUBLE_EQ(atoms[0].position().y(), 2.0); 95 | EXPECT_DOUBLE_EQ(atoms[0].position().z(), 3.0); 96 | 97 | EXPECT_EQ(atoms[1].element().symbol, "Si"); 98 | EXPECT_DOUBLE_EQ(atoms[1].position().x(), 4.5); 99 | EXPECT_DOUBLE_EQ(atoms[1].position().y(), 5.5); 100 | EXPECT_DOUBLE_EQ(atoms[1].position().z(), 6.5); 101 | } 102 | 103 | TEST_F(FileIOTest, ReadCellFileCorrectly) { 104 | // Arrange & Act 105 | FileIO::FileType type = FileIO::determineFileType("test.cell"); 106 | Cell result_cell = FileIO::readStructure("test.cell", type); 107 | 108 | // Assert: Check lattice parameters 109 | const auto ¶ms = result_cell.lattice_parameters(); 110 | EXPECT_DOUBLE_EQ(params[0], 15.0); // a 111 | EXPECT_DOUBLE_EQ(params[1], 15.0); // b 112 | EXPECT_DOUBLE_EQ(params[2], 20.0); // c 113 | EXPECT_DOUBLE_EQ(params[3], 90.0); // alpha 114 | EXPECT_DOUBLE_EQ(params[4], 90.0); // beta 115 | EXPECT_DOUBLE_EQ(params[5], 120.0); // gamma 116 | 117 | // Assert: Check atoms 118 | const auto &atoms = result_cell.atoms(); 119 | ASSERT_EQ(atoms.size(), 2); 120 | 121 | EXPECT_EQ(atoms[0].element().symbol, "C"); 122 | EXPECT_DOUBLE_EQ(atoms[0].position().x(), 1.1); 123 | EXPECT_DOUBLE_EQ(atoms[0].position().y(), 2.2); 124 | EXPECT_DOUBLE_EQ(atoms[0].position().z(), 3.3); 125 | 126 | EXPECT_EQ(atoms[1].element().symbol, "O"); 127 | EXPECT_DOUBLE_EQ(atoms[1].position().x(), 4.4); 128 | EXPECT_DOUBLE_EQ(atoms[1].position().y(), 5.5); 129 | EXPECT_DOUBLE_EQ(atoms[1].position().z(), 6.6); 130 | } 131 | 132 | TEST_F(FileIOTest, ReadCifFileCorrectly) { 133 | // Arrange & Act 134 | FileIO::FileType type = FileIO::determineFileType("test.cif"); 135 | Cell result_cell = FileIO::readStructure("test.cif", type); 136 | 137 | // Assert: Check lattice parameters 138 | const auto ¶ms = result_cell.lattice_parameters(); 139 | EXPECT_DOUBLE_EQ(params[0], 5.64); // a 140 | EXPECT_DOUBLE_EQ(params[1], 5.64); // b 141 | EXPECT_DOUBLE_EQ(params[2], 5.64); // c 142 | EXPECT_DOUBLE_EQ(params[5], 90.0); // gamma 143 | 144 | // Assert: Check total atom count (2 unique atoms * 1 symm ops = 2 atoms) 145 | const auto &atoms = result_cell.atoms(); 146 | ASSERT_EQ(atoms.size(), 2); 147 | 148 | // Assert: Check that specific, expected atoms were generated by symmetry 149 | bool na_origin_found = false; 150 | bool cl_center_found = false; 151 | for (const auto &atom : atoms) { 152 | if (atom.element().symbol == "Na" && linalg::norm(atom.position()) < 1e-4) { 153 | na_origin_found = true; 154 | } 155 | linalg::Vector3 expected_cl_pos = {2.82, 2.82, 2.82}; 156 | if (atom.element().symbol == "Cl" && 157 | linalg::norm(atom.position() - expected_cl_pos) < 1e-4) { 158 | cl_center_found = true; 159 | } 160 | } 161 | EXPECT_TRUE(na_origin_found) 162 | << "Did not find the original Na atom at (0,0,0)"; 163 | EXPECT_TRUE(cl_center_found) 164 | << "Did not find the original Cl atom at the cell center"; 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](Images/Banner.png) 2 | 3 | # `Correlation`: An Analysis Tool for Liquids and for Amorphous Solids 4 | 5 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5514113.svg)](https://doi.org/10.5281/zenodo.5514113) [![Version](https://img.shields.io/badge/version-1.0.4-green)](https://img.shields.io/badge/version-1.0.4-green) [![License](https://img.shields.io/badge/license-MIT-brightgreen)](https://img.shields.io/badge/license-MIT-brightgreen) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](code_of_conduct.md) [![DOI](https://joss.theoj.org/papers/10.21105/joss.02976/status.svg)](https://doi.org/10.21105/joss.02976) 6 | 7 | `Correlation` is a high-performance, user-friendly tool for calculating and analyzing the structural properties of materials. It is designed for researchers working with atomistic simulations of liquids, amorphous solids, and crystalline structures. 8 | 9 | The software computes key correlation functions from atomic coordinate files and exports the results in clean, ready-to-plot CSV files, making it easy to integrate into scientific workflows. 10 | 11 | ## Table of Contents 12 | - [Key Features](#key-features) 13 | - [Quick Start: Installation](#quick-start-installation) 14 | - [Prerequisites](#prerequisites) 15 | - [Windows](#windows) 16 | - [Linux (Debian/Ubuntu)](#linux-debian-ubuntu) 17 | - [Linux (Arch/Manjaro)](#linux-arch-manjaro) 18 | - [MacOS](#macos) 19 | - [Build Instructions](#build-instructions) 20 | - [Usage](#usage) 21 | - [Command-Line Options](#command-line-options) 22 | - [License](#license) 23 | - [Authors](#authors) 24 | - [Acknowledgments](#acknowledgments) 25 | 26 | ## Key Features 27 | 28 | Comprehensive Analysis: Calculates a full suite of standard correlation 29 | functions: 30 | 31 | - Radial Distribution Function (J(r)) 32 | - Pair Distribution Function (g(r)) 33 | - Reduced Pair Distribution Function (G(r)) 34 | - Coordination Number (CN) 35 | - Plane-Angle Distribution (PAD) 36 | - Structure Factor (S(Q)) 37 | 38 | Supports structure files from: 39 | - DMoL3 (`.CAR`) 40 | - CASTEP (`.CELL`) 41 | - ONETEP (`.DAT`) 42 | - LAMMPS (`.XYZ`) 43 | 44 | High Performance: The core calculation loops are parallelized using modern C++ techniques, enabling the analysis of systems with hundreds of thousands of atoms. 45 | 46 | Data Smoothing: Includes built-in kernel smoothing (Gaussian, Triweight) to clean up noisy data for better presentation and analysis. 47 | 48 | ## Quick Start: Installation 49 | 50 | ### Prerequisites 51 | 52 | - A modern C++ compiler (c++20 support required) 53 | - CMake (version 3.20+) 54 | - git 55 | - Intel TBB (for parallelization) 56 | - Slint (for GUI) 57 | 58 | ### Windows 59 | 60 | Installing MSVC: 61 | ``` 62 | https://visualstudio.microsoft.com/downloads/ 63 | ``` 64 | 65 | Installing Rust: 66 | ``` 67 | https://www.rust-lang.org/tools/install 68 | ``` 69 | 70 | ### Linux (Debian/Ubuntu): 71 | 72 | ```bash 73 | sudo apt update 74 | sudo apt install build-essential cmake tbb git rust 75 | ``` 76 | 77 | ### Linux (Arch/Manjaro): 78 | 79 | ```bash 80 | sudo pacman -Syu 81 | sudo pacman base-devel cmake tbb git rust 82 | ``` 83 | 84 | ### MacOS: 85 | 86 | ```bash 87 | xcode-select --install 88 | brew install cmake tbb git rustup 89 | ``` 90 | 91 | ### Build Instructions 92 | 93 | #### Clone the repository: 94 | 95 | ```bash 96 | git clone https://github.com/isurwars/correlation.git 97 | cd correlation 98 | ``` 99 | 100 | #### Build the project: 101 | 102 | ```bash 103 | rm -rf build && mkdir build && cd build 104 | cmake .. 105 | cmake --build . 106 | ``` 107 | 108 | #### Run tests: 109 | 110 | ```bash 111 | ctest -V 112 | ``` 113 | 114 | 115 | #### (OPTIONAL) Install system-wide: 116 | 117 | ```bash 118 | sudo cmake --install . 119 | ``` 120 | 121 | 122 | 123 | ## Usage 124 | 125 | The program is run from the command line, with the only required argument being the input structure file. 126 | 127 | ### Basic Command 128 | 129 | ```bash 130 | ./build/correlation 131 | ``` 132 | ### For example, to analyze a silicon structure: 133 | 134 | ```bash 135 | ./build/correlation silicon.car 136 | ``` 137 | This will run the analysis with default parameters and create output files (e.g., silicon_g.csv, silicon_PAD.csv) in the same directory as the input file. 138 | 139 | ### Example with Options 140 | 141 | This will run the analysis with default parameters and create output files (e.g., silicon_g.csv, silicon_PAD.csv) in the same directory as the input file. 142 | ```bash 143 | ./build/correlation -R 10.0 -r 0.02 -S -K 0.05 -o si_run_1 si_crystal.car 144 | ``` 145 | This command will: 146 | 147 | - Set the radial cutoff (-R) to 10.0 Å. 148 | - Set the RDF bin width (-r) to 0.02 Å. 149 | - Enable smoothing (-S). 150 | - Set the smoothing kernel width (-K) to 0.05. 151 | - Set the output file base name (-o) to si_run_1. 152 | 153 | ## Command-Line Options 154 | 155 | | Option | Long Option | Argument | Description | Default | 156 | |--------|-------------|----------|-------------|---------| 157 | | -h | --help | - | Display the help text and exit. | - | 158 | | -o | --out_file | | The base name for the output files.| Input filename | 159 | | -r | --r_bin_width | | Width of the histogram bins for RDFs (in Angstroms). | 0.05 | 160 | | -R | --r_cut | | Cutoff radius for RDF calculations (in Angstroms). | 20.0 | 161 | | -a | --angle_bin_width | | Width of the histogram bins for PAD (in degrees). | 1.0 | 162 | | -b | --bond_parameter | | Bond parameter factor. (determines bond by distance) | 1.2 | 163 | | -S | --smoothing | - | Enable kernel smoothing on all calculated distributions. | Disabled | 164 | | -k | --kernel_sigma | | Width (sigma) of the smoothing kernel. | 0.081 | 165 | | -K | --kernel | | Smoothing kernel type: 1=Gaussian, 2=Bump, 3=Triweight. | 1 (Gaussian) | 166 | 167 | 168 | ## Built with 169 | 170 | - [emacs](https://www.gnu.org/software/emacs/) - An extensible, customizable, free/libre text editor — and more. 171 | - [MSYS2](https://www.msys2.org/) - Software Distribution and Building Platform for Windows 172 | 173 | ## Authors 174 | 175 | - **Isaías Rodríguez** - _Corresponding Author_ - [Isurwars](https://github.com/Isurwars) 176 | - **Salvador Villareal Lopez Rebuelta** 177 | - **Renela M. Valladares** 178 | - **Alexander Valladares** 179 | - **David Hinojosa-Romero** 180 | - **Ulises Santiago** 181 | - **Ariel A. Valladares** 182 | 183 | ## License 184 | 185 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 186 | 187 | ## Acknowledgments 188 | 189 | I.R. acknowledge PAPIIT, DGAPA-UNAM for his postdoctoral fellowship. 190 | D.H.R. acknowledge Consejo Nacional de Ciencia y Tecnología (CONACyT) for supporting his graduate studies. 191 | A.A.V., R.M.V., and A.V. thank DGAPA-UNAM for continued financial support to carry out research projects under Grant No. IN104617, IN116520 and IIN118223. 192 | M. T. Vázquez and O. Jiménez provided the information requested. 193 | A. López and A. Pompa helped with the maintenance and support of the supercomputer in IIM-UNAM. 194 | Simulations were partially carried out in the Supercomputing Center of DGTIC-UNAM. 195 | I.R. would like to express his gratitude to F. B. Quiroga, M. A. Carrillo, R. S. Vilchis, S. Villareal and A. de Leon, for their time invested in testing the code, as well as the structures provided for benchmarks and tests. 196 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveAssignments: 8 | Enabled: false 9 | AcrossEmptyLines: false 10 | AcrossComments: false 11 | AlignCompound: false 12 | PadOperators: true 13 | AlignConsecutiveBitFields: 14 | Enabled: false 15 | AcrossEmptyLines: false 16 | AcrossComments: false 17 | AlignCompound: false 18 | PadOperators: false 19 | AlignConsecutiveDeclarations: 20 | Enabled: false 21 | AcrossEmptyLines: false 22 | AcrossComments: false 23 | AlignCompound: false 24 | PadOperators: false 25 | AlignConsecutiveMacros: 26 | Enabled: false 27 | AcrossEmptyLines: false 28 | AcrossComments: false 29 | AlignCompound: false 30 | PadOperators: false 31 | AlignConsecutiveShortCaseStatements: 32 | Enabled: false 33 | AcrossEmptyLines: false 34 | AcrossComments: false 35 | AlignCaseColons: false 36 | AlignEscapedNewlines: Right 37 | AlignOperands: Align 38 | AlignTrailingComments: 39 | Kind: Always 40 | OverEmptyLines: 0 41 | AllowAllArgumentsOnNextLine: true 42 | AllowAllParametersOfDeclarationOnNextLine: true 43 | AllowShortBlocksOnASingleLine: Never 44 | AllowShortCaseLabelsOnASingleLine: false 45 | AllowShortEnumsOnASingleLine: true 46 | AllowShortFunctionsOnASingleLine: All 47 | AllowShortIfStatementsOnASingleLine: Never 48 | AllowShortLambdasOnASingleLine: All 49 | AllowShortLoopsOnASingleLine: false 50 | AlwaysBreakAfterDefinitionReturnType: None 51 | AlwaysBreakAfterReturnType: None 52 | AlwaysBreakBeforeMultilineStrings: false 53 | AlwaysBreakTemplateDeclarations: MultiLine 54 | AttributeMacros: 55 | - __capability 56 | BinPackArguments: true 57 | BinPackParameters: true 58 | BitFieldColonSpacing: Both 59 | BraceWrapping: 60 | AfterCaseLabel: false 61 | AfterClass: false 62 | AfterControlStatement: Never 63 | AfterEnum: false 64 | AfterExternBlock: false 65 | AfterFunction: false 66 | AfterNamespace: false 67 | AfterObjCDeclaration: false 68 | AfterStruct: false 69 | AfterUnion: false 70 | BeforeCatch: false 71 | BeforeElse: false 72 | BeforeLambdaBody: false 73 | BeforeWhile: false 74 | IndentBraces: false 75 | SplitEmptyFunction: true 76 | SplitEmptyRecord: true 77 | SplitEmptyNamespace: true 78 | BreakAfterAttributes: Never 79 | BreakAfterJavaFieldAnnotations: false 80 | BreakArrays: true 81 | BreakBeforeBinaryOperators: None 82 | BreakBeforeConceptDeclarations: Always 83 | BreakBeforeBraces: Attach 84 | BreakBeforeInlineASMColon: OnlyMultiline 85 | BreakBeforeTernaryOperators: true 86 | BreakConstructorInitializers: BeforeColon 87 | BreakInheritanceList: BeforeColon 88 | BreakStringLiterals: true 89 | ColumnLimit: 80 90 | CommentPragmas: '^ IWYU pragma:' 91 | CompactNamespaces: false 92 | ConstructorInitializerIndentWidth: 4 93 | ContinuationIndentWidth: 4 94 | Cpp11BracedListStyle: true 95 | DerivePointerAlignment: false 96 | DisableFormat: false 97 | EmptyLineAfterAccessModifier: Never 98 | EmptyLineBeforeAccessModifier: LogicalBlock 99 | ExperimentalAutoDetectBinPacking: false 100 | FixNamespaceComments: true 101 | ForEachMacros: 102 | - foreach 103 | - Q_FOREACH 104 | - BOOST_FOREACH 105 | IfMacros: 106 | - KJ_IF_MAYBE 107 | IncludeBlocks: Preserve 108 | IncludeCategories: 109 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 110 | Priority: 2 111 | SortPriority: 0 112 | CaseSensitive: false 113 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 114 | Priority: 3 115 | SortPriority: 0 116 | CaseSensitive: false 117 | - Regex: '.*' 118 | Priority: 1 119 | SortPriority: 0 120 | CaseSensitive: false 121 | IncludeIsMainRegex: '(Test)?$' 122 | IncludeIsMainSourceRegex: '' 123 | IndentAccessModifiers: false 124 | IndentCaseBlocks: false 125 | IndentCaseLabels: false 126 | IndentExternBlock: AfterExternBlock 127 | IndentGotoLabels: true 128 | IndentPPDirectives: None 129 | IndentRequiresClause: true 130 | IndentWidth: 2 131 | IndentWrappedFunctionNames: false 132 | InsertBraces: false 133 | InsertNewlineAtEOF: false 134 | InsertTrailingCommas: None 135 | IntegerLiteralSeparator: 136 | Binary: 0 137 | BinaryMinDigits: 0 138 | Decimal: 0 139 | DecimalMinDigits: 0 140 | Hex: 0 141 | HexMinDigits: 0 142 | JavaScriptQuotes: Leave 143 | JavaScriptWrapImports: true 144 | KeepEmptyLinesAtTheStartOfBlocks: true 145 | KeepEmptyLinesAtEOF: false 146 | LambdaBodyIndentation: Signature 147 | LineEnding: DeriveLF 148 | MacroBlockBegin: '' 149 | MacroBlockEnd: '' 150 | MaxEmptyLinesToKeep: 1 151 | NamespaceIndentation: None 152 | ObjCBinPackProtocolList: Auto 153 | ObjCBlockIndentWidth: 2 154 | ObjCBreakBeforeNestedBlockParam: true 155 | ObjCSpaceAfterProperty: false 156 | ObjCSpaceBeforeProtocolList: true 157 | PackConstructorInitializers: BinPack 158 | PenaltyBreakAssignment: 2 159 | PenaltyBreakBeforeFirstCallParameter: 19 160 | PenaltyBreakComment: 300 161 | PenaltyBreakFirstLessLess: 120 162 | PenaltyBreakOpenParenthesis: 0 163 | PenaltyBreakString: 1000 164 | PenaltyBreakTemplateDeclaration: 10 165 | PenaltyExcessCharacter: 1000000 166 | PenaltyIndentedWhitespace: 0 167 | PenaltyReturnTypeOnItsOwnLine: 60 168 | PointerAlignment: Right 169 | PPIndentWidth: -1 170 | QualifierAlignment: Leave 171 | ReferenceAlignment: Pointer 172 | ReflowComments: true 173 | RemoveBracesLLVM: false 174 | RemoveParentheses: Leave 175 | RemoveSemicolon: false 176 | RequiresClausePosition: OwnLine 177 | RequiresExpressionIndentation: OuterScope 178 | SeparateDefinitionBlocks: Leave 179 | ShortNamespaceLines: 1 180 | SortIncludes: CaseSensitive 181 | SortJavaStaticImport: Before 182 | SortUsingDeclarations: LexicographicNumeric 183 | SpaceAfterCStyleCast: false 184 | SpaceAfterLogicalNot: false 185 | SpaceAfterTemplateKeyword: true 186 | SpaceAroundPointerQualifiers: Default 187 | SpaceBeforeAssignmentOperators: true 188 | SpaceBeforeCaseColon: false 189 | SpaceBeforeCpp11BracedList: false 190 | SpaceBeforeCtorInitializerColon: true 191 | SpaceBeforeInheritanceColon: true 192 | SpaceBeforeJsonColon: false 193 | SpaceBeforeParens: ControlStatements 194 | SpaceBeforeParensOptions: 195 | AfterControlStatements: true 196 | AfterForeachMacros: true 197 | AfterFunctionDefinitionName: false 198 | AfterFunctionDeclarationName: false 199 | AfterIfMacros: true 200 | AfterOverloadedOperator: false 201 | AfterRequiresInClause: false 202 | AfterRequiresInExpression: false 203 | BeforeNonEmptyParentheses: false 204 | SpaceBeforeRangeBasedForLoopColon: true 205 | SpaceBeforeSquareBrackets: false 206 | SpaceInEmptyBlock: false 207 | SpacesBeforeTrailingComments: 1 208 | SpacesInAngles: Never 209 | SpacesInContainerLiterals: true 210 | SpacesInLineCommentPrefix: 211 | Minimum: 1 212 | Maximum: -1 213 | SpacesInParens: Never 214 | SpacesInParensOptions: 215 | InCStyleCasts: false 216 | InConditionalStatements: false 217 | InEmptyParentheses: false 218 | Other: false 219 | SpacesInSquareBrackets: false 220 | Standard: Latest 221 | StatementAttributeLikeMacros: 222 | - Q_EMIT 223 | StatementMacros: 224 | - Q_UNUSED 225 | - QT_REQUIRE_VERSION 226 | TabWidth: 8 227 | UseTab: Never 228 | VerilogBreakBetweenInstancePorts: true 229 | WhitespaceSensitiveMacros: 230 | - BOOST_PP_STRINGIZE 231 | - CF_SWIFT_NAME 232 | - NS_SWIFT_NAME 233 | - PP_STRINGIZE 234 | - STRINGIZE 235 | ... 236 | -------------------------------------------------------------------------------- /src/Cell.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | #include "../include/Cell.hpp" 6 | 7 | #include 8 | #include 9 | 10 | #include "../include/Atom.hpp" 11 | #include "../include/LinearAlgebra.hpp" 12 | #include "../include/PhysicalData.hpp" 13 | 14 | //---------------------------------------------------------------------------// 15 | //------------------------------- Constructors ------------------------------// 16 | //---------------------------------------------------------------------------// 17 | Cell::Cell(const linalg::Vector3 &a, const linalg::Vector3 &b, 18 | const linalg::Vector3 &c) { 19 | updateLattice(linalg::Matrix3(a, b, c)); 20 | } 21 | 22 | Cell::Cell(const std::array ¶ms) { 23 | setLatticeParameters(params); 24 | } 25 | 26 | // Move Constructor 27 | Cell::Cell(Cell &&other) noexcept 28 | : lattice_vectors_(std::move(other.lattice_vectors_)), 29 | inverse_lattice_vectors_(std::move(other.inverse_lattice_vectors_)), 30 | lattice_parameters_(std::move(other.lattice_parameters_)), 31 | volume_(std::move(other.volume_)), atoms_(std::move(other.atoms_)), 32 | elements_(std::move(other.elements_)) {} 33 | 34 | // Move Assignment Operator 35 | Cell &Cell::operator=(Cell &&other) noexcept { 36 | if (this != &other) { 37 | lattice_vectors_ = std::move(other.lattice_vectors_); 38 | inverse_lattice_vectors_ = std::move(other.inverse_lattice_vectors_); 39 | lattice_parameters_ = std::move(other.lattice_parameters_); 40 | volume_ = std::move(other.volume_); 41 | atoms_ = std::move(other.atoms_); 42 | elements_ = std::move(other.elements_); 43 | } 44 | return *this; 45 | } 46 | 47 | //---------------------------------------------------------------------------// 48 | //-------------------------------- Accessors --------------------------------// 49 | //---------------------------------------------------------------------------// 50 | 51 | void Cell::setLatticeParameters(std::array params) { 52 | lattice_parameters_ = params; 53 | const double a = params[0], b = params[1], c = params[2]; 54 | const double alpha = params[3] * constants::deg2rad; 55 | const double beta = params[4] * constants::deg2rad; 56 | const double gamma = params[5] * constants::deg2rad; 57 | 58 | if (a <= 0 || b <= 0 || c <= 0) { 59 | throw std::invalid_argument("Lattice parameters a, b, c must be positive."); 60 | } 61 | 62 | const double cos_g = std::cos(gamma); 63 | const double sin_g = std::sin(gamma); 64 | 65 | // Standard conversion from lattice parameters to vectors 66 | linalg::Vector3 v_a = {a, 0.0, 0.0}; 67 | linalg::Vector3 v_b = {b * cos_g, b * sin_g, 0.0}; 68 | linalg::Vector3 v_c = { 69 | c * std::cos(beta), 70 | c * (std::cos(alpha) - std::cos(beta) * cos_g) / sin_g, 71 | 0.0 // z-component is calculated from volume 72 | }; 73 | // Re-calculate z-component of v_c from volume to ensure orthogonality 74 | const double volume = 75 | a * b * c * 76 | std::sqrt(1.0 - std::pow(std::cos(alpha), 2) - 77 | std::pow(std::cos(beta), 2) - std::pow(std::cos(gamma), 2) + 78 | 2.0 * std::cos(alpha) * std::cos(beta) * std::cos(gamma)); 79 | v_c.z() = volume / (a * b * sin_g); 80 | updateLattice(linalg::Matrix3(v_a, v_b, v_c)); 81 | } 82 | 83 | void Cell::updateLattice(const linalg::Matrix3 &new_lattice) { 84 | lattice_vectors_ = new_lattice; 85 | volume_ = linalg::determinant(lattice_vectors_); 86 | if (volume_ <= 1e-9) { 87 | throw std::logic_error("Cell volume must be positive."); 88 | } 89 | inverse_lattice_vectors_ = 90 | linalg::transpose(linalg::invert(lattice_vectors_)); 91 | updateLatticeParametersFromVectors(); 92 | } 93 | 94 | void Cell::updateLatticeParametersFromVectors() { 95 | const auto &a_vec = lattice_vectors_[0]; 96 | const auto &b_vec = lattice_vectors_[1]; 97 | const auto &c_vec = lattice_vectors_[2]; 98 | 99 | const double a = linalg::norm(a_vec); 100 | const double b = linalg::norm(b_vec); 101 | const double c = linalg::norm(c_vec); 102 | 103 | if (a < 1e-9 || b < 1e-9 || c < 1e-9) { 104 | // Handle case of zero-length vectors, though updateLattice would likely 105 | // throw first. 106 | lattice_parameters_ = {0, 0, 0, 0, 0, 0}; 107 | return; 108 | } 109 | 110 | const double alpha_rad = std::acos(linalg::dot(b_vec, c_vec) / (b * c)); 111 | const double beta_rad = std::acos(linalg::dot(a_vec, c_vec) / (a * c)); 112 | const double gamma_rad = std::acos(linalg::dot(a_vec, b_vec) / (a * b)); 113 | 114 | lattice_parameters_ = {a, 115 | b, 116 | c, 117 | alpha_rad * constants::rad2deg, 118 | beta_rad * constants::rad2deg, 119 | gamma_rad * constants::rad2deg}; 120 | } 121 | 122 | //---------------------------------------------------------------------------// 123 | //--------------------------------- Methods ---------------------------------// 124 | //---------------------------------------------------------------------------// 125 | 126 | void Cell::precomputeBondCutoffs() { 127 | const size_t num_elements = elements_.size(); 128 | std::vector placeholder = std::vector(num_elements); 129 | bond_cutoffs_sq_.resize(num_elements, placeholder); 130 | 131 | for (size_t i = 0; i < num_elements; ++i) { 132 | const double radius_A = CovalentRadii::get(elements_[i].symbol); 133 | for (size_t j = i; j < num_elements; ++j) { 134 | const double radius_B = CovalentRadii::get(elements_[j].symbol); 135 | const double max_bond_dist = (radius_A + radius_B) * bond_factor_; 136 | const double max_bond_dist_sq = max_bond_dist * max_bond_dist; 137 | bond_cutoffs_sq_[i][j] = max_bond_dist_sq; 138 | bond_cutoffs_sq_[j][i] = max_bond_dist_sq; 139 | } 140 | } 141 | } 142 | 143 | std::optional Cell::findElement(const std::string &symbol) const { 144 | auto it = std::find_if(elements_.begin(), elements_.end(), 145 | [&](const Element &e) { return e.symbol == symbol; }); 146 | if (it != elements_.end()) { 147 | return *it; 148 | } 149 | return std::nullopt; 150 | } 151 | 152 | double Cell::getBondCutoff(int Type_A, int Type_B) { 153 | if (bond_cutoffs_sq_.empty()) 154 | precomputeBondCutoffs(); 155 | return sqrt(bond_cutoffs_sq_[Type_A][Type_B]); 156 | } 157 | 158 | ElementID Cell::getOrRegisterElement(const std::string &symbol) { 159 | if (auto existing_element = findElement(symbol)) { 160 | return existing_element->id; 161 | } 162 | // Register the new element 163 | ElementID new_id{static_cast(elements_.size())}; 164 | elements_.push_back({symbol, new_id}); 165 | return new_id; 166 | } 167 | 168 | Atom &Cell::addAtom(const std::string &symbol, 169 | const linalg::Vector3 &position) { 170 | ElementID element_id = getOrRegisterElement(symbol); 171 | auto element_it = 172 | std::find_if(elements_.begin(), elements_.end(), [&](const Element &e) { 173 | return e.id.value == element_id.value; 174 | }); 175 | 176 | AtomID new_atom_id{static_cast(atoms_.size())}; 177 | atoms_.emplace_back(*element_it, position, new_atom_id); 178 | return atoms_.back(); 179 | } 180 | 181 | void Cell::wrapPositions() { 182 | for (Atom &atom : atoms_) { 183 | linalg::Vector3 frac_pos = 184 | inverse_lattice_vectors_ * atom.position(); 185 | frac_pos.x() -= std::floor(frac_pos.x()); 186 | frac_pos.y() -= std::floor(frac_pos.y()); 187 | frac_pos.z() -= std::floor(frac_pos.z()); 188 | atom.setPosition(lattice_vectors_ * frac_pos); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /include/LinearAlgebra.hpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace linalg { 13 | 14 | // ----------------------------------------------------------------------------- 15 | // Vector3 – lightweight, constexpr, stack-based 16 | // ----------------------------------------------------------------------------- 17 | template class Vector3 { 18 | public: 19 | using value_type = T; 20 | 21 | constexpr Vector3() noexcept : data_{0, 0, 0} {} 22 | constexpr Vector3(T x, T y, T z) noexcept : data_{x, y, z} {} 23 | constexpr explicit Vector3(const std::array &a) noexcept : data_{a} {} 24 | 25 | // Access by index 26 | constexpr T operator[](std::size_t i) const noexcept { return data_[i]; } 27 | constexpr T &operator[](std::size_t i) noexcept { return data_[i]; } 28 | 29 | // Named component accessors 30 | constexpr T x() const noexcept { return data_[0]; } 31 | constexpr T &x() noexcept { return data_[0]; } 32 | constexpr T y() const noexcept { return data_[1]; } 33 | constexpr T &y() noexcept { return data_[1]; } 34 | constexpr T z() const noexcept { return data_[2]; } 35 | constexpr T &z() noexcept { return data_[2]; } 36 | 37 | // True if every component is exactly zero 38 | constexpr bool empty() const noexcept { 39 | return data_[0] == T{} && data_[1] == T{} && data_[2] == T{}; 40 | } 41 | 42 | constexpr const T *begin() const noexcept { return data_.data(); } 43 | constexpr const T *end() const noexcept { return data_.data() + 3; } 44 | constexpr T *begin() noexcept { return data_.data(); } 45 | constexpr T *end() noexcept { return data_.data() + 3; } 46 | 47 | // arithmetic 48 | constexpr Vector3 operator+(const Vector3 &rhs) const noexcept { 49 | return {data_[0] + rhs[0], data_[1] + rhs[1], data_[2] + rhs[2]}; 50 | } 51 | constexpr Vector3 operator-(const Vector3 &rhs) const noexcept { 52 | return {data_[0] - rhs[0], data_[1] - rhs[1], data_[2] - rhs[2]}; 53 | } 54 | constexpr Vector3 operator*(T s) const noexcept { 55 | return {data_[0] * s, data_[1] * s, data_[2] * s}; 56 | } 57 | constexpr Vector3 operator/(T s) const noexcept { 58 | return {data_[0] / s, data_[1] / s, data_[2] / s}; 59 | } 60 | 61 | // compound 62 | constexpr Vector3 &operator+=(const Vector3 &rhs) noexcept { 63 | data_[0] += rhs[0]; 64 | data_[1] += rhs[1]; 65 | data_[2] += rhs[2]; 66 | return *this; 67 | } 68 | constexpr Vector3 &operator-=(const Vector3 &rhs) noexcept { 69 | data_[0] -= rhs[0]; 70 | data_[1] -= rhs[1]; 71 | data_[2] -= rhs[2]; 72 | return *this; 73 | } 74 | 75 | // dot product 76 | constexpr T operator*(const Vector3 &rhs) const noexcept { 77 | return data_[0] * rhs[0] + data_[1] * rhs[1] + data_[2] * rhs[2]; 78 | } 79 | 80 | constexpr std::array array() const noexcept { return data_; } 81 | 82 | private: 83 | std::array data_; 84 | }; 85 | 86 | // free scalar * vector 87 | template 88 | constexpr std::enable_if_t::value, Vector3> 89 | operator*(Scalar s, const Vector3 &v) noexcept { 90 | return v * static_cast(s); 91 | } 92 | 93 | // ----------------------------------------------------------------------------- 94 | // Matrix3 – column-major storage 95 | // ----------------------------------------------------------------------------- 96 | template class Matrix3 { 97 | public: 98 | using value_type = T; 99 | // Constructors 100 | constexpr Matrix3() noexcept { data_.fill(Vector3{0, 0, 0}); } 101 | constexpr Matrix3(const Vector3 &c0, const Vector3 &c1, 102 | const Vector3 &c2) noexcept 103 | : data_{c0, c1, c2} {} 104 | // Column access 105 | constexpr const Vector3 &operator[](std::size_t c) const noexcept { 106 | return data_[c]; 107 | } 108 | constexpr Vector3 &operator[](std::size_t c) noexcept { return data_[c]; } 109 | 110 | // Element access (row, col) 111 | constexpr T operator()(std::size_t r, std::size_t c) const noexcept { 112 | return data_[c][r]; 113 | } 114 | constexpr T &operator()(std::size_t r, std::size_t c) noexcept { 115 | return data_[c][r]; 116 | } 117 | 118 | constexpr std::array, 3> array() const noexcept { 119 | return {{{data_[0][0], data_[1][0], data_[2][0]}, 120 | {data_[0][1], data_[1][1], data_[2][1]}, 121 | {data_[0][2], data_[1][2], data_[2][2]}}}; 122 | } 123 | constexpr Vector3 operator*(const Vector3 v) const noexcept { 124 | return {data_[0][0] * v.x() + data_[0][1] * v.y() + data_[0][2] * v.z(), 125 | data_[1][0] * v.x() + data_[1][1] * v.y() + data_[1][2] * v.z(), 126 | data_[2][0] * v.x() + data_[2][1] * v.y() + data_[2][2] * v.z()}; 127 | } 128 | 129 | private: 130 | std::array, 3> data_; 131 | }; 132 | 133 | // ----------------------------------------------------------------------------- 134 | // Functions 135 | // ----------------------------------------------------------------------------- 136 | 137 | // Vector dot product 138 | template 139 | constexpr T dot(const Vector3 &a, const Vector3 &b) noexcept { 140 | return a * b; 141 | } 142 | // Vector cross product 143 | template 144 | constexpr Vector3 cross(const Vector3 &a, const Vector3 &b) noexcept { 145 | return {a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], 146 | a[0] * b[1] - a[1] * b[0]}; 147 | } 148 | // Vector norm_sq 149 | template constexpr T norm_sq(const Vector3 &v) noexcept { 150 | return v * v; 151 | } 152 | // Vector norm 153 | template constexpr T norm(const Vector3 &v) noexcept { 154 | return std::sqrt(v * v); 155 | } 156 | 157 | // Matrix determinant 158 | template constexpr T determinant(const Matrix3 &m) noexcept { 159 | return m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - 160 | m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + 161 | m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); 162 | } 163 | // Matrix invert 164 | template constexpr Matrix3 invert(const Matrix3 &m) { 165 | T det = determinant(m); 166 | if (det == T{0}) 167 | throw std::runtime_error("singular matrix"); 168 | T inv = T{1} / det; 169 | 170 | Vector3 c0{m[1][1] * m[2][2] - m[1][2] * m[2][1], 171 | m[0][2] * m[2][1] - m[0][1] * m[2][2], 172 | m[0][1] * m[1][2] - m[0][2] * m[1][1]}; 173 | 174 | Vector3 c1{m[1][2] * m[2][0] - m[1][0] * m[2][2], 175 | m[0][0] * m[2][2] - m[0][2] * m[2][0], 176 | m[0][2] * m[1][0] - m[0][0] * m[1][2]}; 177 | 178 | Vector3 c2{m[1][0] * m[2][1] - m[1][1] * m[2][0], 179 | m[0][1] * m[2][0] - m[0][0] * m[2][1], 180 | m[0][0] * m[1][1] - m[0][1] * m[1][0]}; 181 | 182 | return Matrix3(c0 * inv, c1 * inv, c2 * inv); 183 | } 184 | 185 | // Matrix transpose 186 | template 187 | constexpr Matrix3 transpose(const Matrix3 &m) noexcept { 188 | return Matrix3({m(0, 0), m(1, 0), m(2, 0)}, // New col 0 is old row 0 189 | {m(0, 1), m(1, 1), m(2, 1)}, // New col 1 is old row 1 190 | {m(0, 2), m(1, 2), m(2, 2)} // New col 2 is old row 2 191 | ); 192 | } 193 | 194 | // Matrix-Vector product 195 | template 196 | constexpr Vector3 operator*(const Matrix3 &m, 197 | const Vector3 &v) noexcept { 198 | // For column-major matrix: Result = col0*v.x + col1*v.y + col2*v.z 199 | Vector3 res; 200 | res += m[0] * v.x(); 201 | res += m[1] * v.y(); 202 | res += m[2] * v.z(); 203 | return res; 204 | } 205 | 206 | // Matrix-Matrix product 207 | template 208 | constexpr Matrix3 operator*(const Matrix3 &a, 209 | const Matrix3 &b) noexcept { 210 | // Result column 'j' is the product of matrix 'a' and column 'j' of matrix 'b' 211 | return Matrix3(a * b[0], a * b[1], a * b[2]); 212 | } 213 | 214 | } // namespace linalg 215 | -------------------------------------------------------------------------------- /tests/Distribution_Functions_tests.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright (c) 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../include/Cell.hpp" 13 | #include "../include/DistributionFunctions.hpp" 14 | 15 | namespace { 16 | // Helper function to print a histogram's contents for debugging purposes. 17 | void print_histogram(const std::string &title, const std::vector &bins, 18 | const std::vector &values) { 19 | std::cout << "\n--- Histogram: " << title << " ---" << std::endl; 20 | std::cout << std::fixed << std::setprecision(4); 21 | for (size_t i = 0; i < bins.size(); ++i) { 22 | // Only print bins with non-zero values to keep the output concise. 23 | if (values.size() > i && std::abs(values[i]) > 1e-9) { 24 | std::cout << "Bin: " << std::setw(8) << bins[i] 25 | << " | Value: " << values[i] << std::endl; 26 | } 27 | } 28 | std::cout << "--------------------------------------\n" << std::endl; 29 | } 30 | } // namespace 31 | 32 | // Test fixture for DistributionFunctions tests. 33 | // Provides a pre-configured Cell object to reduce boilerplate in test cases. 34 | class DistributionFunctionsTest : public ::testing::Test { 35 | protected: 36 | void SetUp() override { 37 | // A simple cubic cell containing two atoms, 1.5 Angstroms apart. 38 | // This is a common setup for testing pair-based calculations. 39 | cell_ = Cell({10.0, 10.0, 10.0, 90.0, 90.0, 90.0}); 40 | cell_.addAtom("Ar", {5.0, 5.0, 5.0}); 41 | cell_.addAtom("Ar", {6.5, 5.0, 5.0}); 42 | } 43 | 44 | Cell cell_{}; 45 | }; 46 | 47 | TEST_F(DistributionFunctionsTest, CalculateRDFThrowsOnInvalidParameters) { 48 | // Arrange 49 | DistributionFunctions df(cell_, 5.0, 1.2); 50 | 51 | // Act & Assert 52 | EXPECT_THROW(df.calculateRDF(5.0, 0.0), std::invalid_argument); 53 | EXPECT_THROW(df.calculateRDF(0.0, 0.1), std::invalid_argument); 54 | } 55 | 56 | TEST_F(DistributionFunctionsTest, RDFPeakPositionIsCorrect) { 57 | // Arrange 58 | DistributionFunctions df(cell_, 5.0, 1.2); 59 | const double bin_width = 0.1; 60 | const double expected_distance = 1.5; 61 | 62 | // Act 63 | df.calculateRDF(5.0, bin_width); 64 | const auto &rdf_hist = df.getHistogram("g(r)"); 65 | const auto &total_rdf = rdf_hist.partials.at("Ar-Ar"); 66 | 67 | // Assert: Find the peak of the RDF and verify its position. 68 | auto max_it = std::max_element(total_rdf.begin(), total_rdf.end()); 69 | size_t peak_index = std::distance(total_rdf.begin(), max_it); 70 | 71 | double peak_position = rdf_hist.bins[peak_index]; 72 | 73 | EXPECT_NEAR(peak_position, expected_distance, bin_width); 74 | } 75 | 76 | TEST_F(DistributionFunctionsTest, PADPeakPositionIsCorrectForWater) { 77 | // Arrange: A water-like structure with a known ~109.5 degree angle. 78 | Cell water_cell({10.0, 10.0, 10.0, 90.0, 90.0, 90.0}); 79 | water_cell.addAtom("O", {0.0, 0.0, 0.0}); 80 | water_cell.addAtom("H", {1.0, 0.0, 0.0}); 81 | water_cell.addAtom("H", 82 | {std::cos(1.916), std::sin(1.916), 0.0}); // ~109.5 deg 83 | DistributionFunctions df(water_cell, 5.0, 1.2); 84 | const double bin_width = 1.0; // 1-degree bins 85 | 86 | // Act 87 | df.calculatePAD(180.0, bin_width); 88 | const auto &pad_hist = df.getHistogram("f(theta)"); 89 | const auto &hoh_pad = pad_hist.partials.at("H-O-H"); 90 | 91 | // --- DEBUGGING STEP --- 92 | // This will print the contents of the H-O-H partial to the console. 93 | print_histogram("H-O-H Partial", pad_hist.bins, hoh_pad); 94 | // --- END DEBUGGING STEP --- 95 | 96 | // Assert 97 | auto max_it = std::max_element(hoh_pad.begin(), hoh_pad.end()); 98 | size_t peak_index = std::distance(hoh_pad.begin(), max_it); 99 | double peak_angle = pad_hist.bins[peak_index]; 100 | 101 | EXPECT_NEAR(peak_angle, 109.5, bin_width * 2.0); // Allow for binning error 102 | } 103 | 104 | TEST_F(DistributionFunctionsTest, SmoothAllUpdatesSmoothedPartials) { 105 | // Arrange 106 | DistributionFunctions df(cell_, 5.0, 1.2); 107 | df.calculateRDF(5.0, 0.1); 108 | 109 | // Act 110 | df.smoothAll(0.2); 111 | const auto &rdf_hist = df.getHistogram("g(r)"); 112 | 113 | // Assert 114 | ASSERT_FALSE(rdf_hist.smoothed_partials.empty()); 115 | ASSERT_TRUE(rdf_hist.smoothed_partials.count("Ar-Ar")); 116 | 117 | const auto &raw_data = rdf_hist.partials.at("Ar-Ar"); 118 | const auto &smoothed_data = rdf_hist.smoothed_partials.at("Ar-Ar"); 119 | 120 | ASSERT_EQ(raw_data.size(), smoothed_data.size()); 121 | 122 | // A simple check: the peak of the smoothed data should be lower than the raw 123 | // data's peak. 124 | double raw_max = *std::max_element(raw_data.begin(), raw_data.end()); 125 | double smoothed_max = 126 | *std::max_element(smoothed_data.begin(), smoothed_data.end()); 127 | 128 | EXPECT_LE(smoothed_max, raw_max); 129 | } 130 | 131 | TEST_F(DistributionFunctionsTest, CoordinationNumberDistributionIsCorrect) { 132 | // Arrange: Create a structure with a known, simple coordination environment. 133 | Cell test_cell({20.0, 20.0, 20.0, 90.0, 90.0, 90.0}); 134 | // A central "C" atom 135 | test_cell.addAtom("C", {10.0, 10.0, 10.0}); 136 | // Four "H" atoms tetrahedrally coordinated around the "C" 137 | test_cell.addAtom("H", {11.0, 10.0, 10.0}); 138 | test_cell.addAtom("H", {10.0, 11.0, 10.0}); 139 | test_cell.addAtom("H", {10.0, 10.0, 11.0}); 140 | test_cell.addAtom("H", {10.5, 10.5, 10.5}); // Not a perfect tetrahedron 141 | // A lone "O" atom, not bonded to anything 142 | test_cell.addAtom("O", {15.0, 15.0, 15.0}); 143 | 144 | // A cutoff that includes the C-H bonds but excludes everything else. 145 | // The C-H distance is 1.0, C-O is ~8.6 146 | DistributionFunctions df(test_cell, 3.0, 1.2); 147 | 148 | // Act 149 | df.calculateCoordinationNumber(); 150 | const auto &cn_hist = df.getHistogram("CN"); 151 | 152 | // Assert C-H coordination 153 | const auto &c_h_cn = cn_hist.partials.at("C-H"); 154 | // There is 1 Carbon atom, and it has 4 Hydrogen neighbors. 155 | // So, the histogram should have a value of 1 at bin 4. 156 | EXPECT_EQ(c_h_cn.size(), 7); // Bins for CN=0,1,2,3,4,5,6 157 | EXPECT_EQ(c_h_cn[4], 1); 158 | EXPECT_EQ(c_h_cn[0] + c_h_cn[1] + c_h_cn[2] + c_h_cn[3], 0); 159 | 160 | // Assert H-C coordination 161 | const auto &h_c_cn = cn_hist.partials.at("H-C"); 162 | // There are 4 Hydrogen atoms, and each has 1 Carbon neighbor. 163 | // So, the histogram should have a value of 4 at bin 1. 164 | EXPECT_EQ(h_c_cn.size(), 7); // Must be padded to the max CN+2 165 | EXPECT_EQ(h_c_cn[1], 4); 166 | EXPECT_EQ(h_c_cn[0] + h_c_cn[2] + h_c_cn[3] + h_c_cn[4], 0); 167 | 168 | // Assert that non-bonded pairs do not appear or are empty. 169 | EXPECT_EQ(cn_hist.partials.count("C-O"), 0); 170 | } 171 | 172 | TEST_F(DistributionFunctionsTest, SQPeakPositionIsCorrect) { 173 | // Arrange 174 | // Using the fixture's cell with two Ar atoms 1.5 Å apart. 175 | DistributionFunctions df(cell_, 10.0, 1.2); 176 | const double q_bin_width = 0.01; 177 | const double expected_peak_q = 0.7; // 2.0 * constants::pi / 1.5; // ~4.18 178 | 179 | // Act 180 | // S(Q) depends on g(r), so we must calculate it first. 181 | df.calculateRDF(10.0, 0.01); 182 | // df.smooth("g(r)", 0.1, KernelType::Gaussian); 183 | df.calculateSQ(10.0, q_bin_width, 8.0); 184 | const auto &g_hist = df.getHistogram("g(r)"); 185 | const auto &total_g = g_hist.partials.at("Total"); 186 | 187 | const auto &sq_hist = df.getHistogram("S(Q)"); 188 | const auto &total_sq = sq_hist.partials.at("Total"); 189 | 190 | // Assert: Find the peak of S(Q) and verify its position. 191 | // We'll ignore the very first few bins as S(Q) can be noisy at Q->0. 192 | auto search_start = total_sq.begin() + 5; 193 | auto max_it = std::max_element(search_start, total_sq.end()); 194 | size_t peak_index = std::distance(total_sq.begin(), max_it); 195 | 196 | double peak_position = sq_hist.bins[peak_index]; 197 | 198 | // The peak should be near 2*pi/r 199 | EXPECT_NEAR(peak_position, expected_peak_q, 200 | q_bin_width * 5.0); // Allow a generous tolerance 201 | } 202 | -------------------------------------------------------------------------------- /tests/PAD_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../include/Cell.hpp" 5 | #include "../include/StructureAnalyzer.hpp" 6 | #include "../include/DistributionFunctions.hpp" 7 | 8 | // Helper to sum a partial histogram 9 | double sumHistogram(const std::vector& hist) { 10 | return std::accumulate(hist.begin(), hist.end(), 0.0); 11 | } 12 | 13 | class PADTest : public ::testing::Test { 14 | protected: 15 | void SetUp() override { 16 | // Large box to avoid PBC issues by default 17 | cell_ = Cell({20.0, 20.0, 20.0, 90.0, 90.0, 90.0}); 18 | cell_.setBondFactor(1.3); // Standard bond factor 19 | } 20 | Cell cell_; 21 | }; 22 | 23 | // 1. Trivial Cases 24 | TEST_F(PADTest, EmptyCellThrows) { 25 | // Current implementation throws explicitly if atoms are empty in calculateAshcroftWeights 26 | // or implicitly via other checks. 27 | EXPECT_THROW({ 28 | DistributionFunctions df(cell_, 5.0, 1.3); 29 | }, std::invalid_argument); 30 | } 31 | 32 | TEST_F(PADTest, SingleAtomNoAngles) { 33 | cell_.addAtom("Si", {10.0, 10.0, 10.0}); 34 | DistributionFunctions df(cell_, 5.0, 1.3); 35 | df.calculatePAD(180.0, 1.0); 36 | // Might have partials created but empty, or just no "f(theta)" if logic handles it. 37 | // Actually implementation might create partials if atoms exist but no angles found. 38 | // Let's check total counts. 39 | if(df.getAllHistograms().count("f(theta)")) { 40 | const auto& hist = df.getHistogram("f(theta)"); 41 | if (!hist.partials.empty()) { 42 | if (hist.partials.count("Total")) { 43 | EXPECT_DOUBLE_EQ(sumHistogram(hist.partials.at("Total")), 0.0); 44 | } 45 | } 46 | } 47 | } 48 | 49 | // 2. Geometry Verification 50 | TEST_F(PADTest, LinearGeometry180) { 51 | // A-B-C line 52 | cell_.addAtom("O", {9.0, 10.0, 10.0}); 53 | auto& si = cell_.addAtom("Si", {10.0, 10.0, 10.0}); // Center 54 | cell_.addAtom("O", {11.0, 10.0, 10.0}); 55 | 56 | // Debug: Check bond cutoff is sufficient 57 | // We need element IDs. 58 | // Assuming 0=O, 1=Si (based on insertion order? No, map order). 59 | // Let's rely on cell internals or just trust if getBondCutoff works. 60 | int id_O = cell_.findElement("O")->id.value; 61 | int id_Si = cell_.findElement("Si")->id.value; 62 | double cutoff = cell_.getBondCutoff(id_O, id_Si); 63 | // O(0.73) + Si(1.11) = 1.84 * 1.3 = 2.392. 64 | EXPECT_GT(cutoff, 1.1) << "Bond cutoff must be larger than bond distance 1.0"; 65 | 66 | // Verify StructureAnalyzer finds neighbors 67 | StructureAnalyzer analyzer(cell_, 1.5); 68 | const auto& neighbors = analyzer.neighbors(); 69 | // Si is atom index 1 (0-based) 70 | ASSERT_GT(neighbors.size(), 1); 71 | EXPECT_EQ(neighbors[1].size(), 2) << "Si should have 2 neighbors (O atoms)"; 72 | 73 | // Bond length 1.0. Cutoff needs to be > 1.0 74 | DistributionFunctions df(cell_, 1.5, 1.3); 75 | // Use 180.0 now that we fixed the binning logic 76 | df.calculatePAD(180.0, 1.0); 77 | 78 | const auto& hist = df.getHistogram("f(theta)"); 79 | // Should have O-Si-O peak at 180 80 | ASSERT_EQ(hist.partials.count("O-Si-O"), 1); 81 | const auto& partial = hist.partials.at("O-Si-O"); 82 | 83 | // Bin for 180 degrees. 84 | // If n_bins = 180.1/1 = 180. 85 | 86 | double total_prob = sumHistogram(partial); 87 | EXPECT_NEAR(total_prob, 1.0, 1e-5) << "Should be normalized to 1 angle (normalized by counts * bin_width)"; 88 | 89 | // Check peak location 90 | double peak_val = 0; 91 | int peak_bin = -1; 92 | for(size_t i=0; i peak_val) { 94 | peak_val = partial[i]; 95 | peak_bin = i; 96 | } 97 | } 98 | 99 | if (peak_bin >= 0) { 100 | double peak_angle = hist.bins[peak_bin]; 101 | EXPECT_NEAR(peak_angle, 179.5, 1.0); 102 | } else { 103 | FAIL() << "No peak found in partial distribution"; 104 | } 105 | } 106 | 107 | TEST_F(PADTest, RightAngle90) { 108 | cell_.addAtom("O", {10.0, 9.0, 10.0}); 109 | cell_.addAtom("Si", {10.0, 10.0, 10.0}); // Center 110 | cell_.addAtom("O", {11.0, 10.0, 10.0}); 111 | 112 | DistributionFunctions df(cell_, 1.5, 1.3); 113 | df.calculatePAD(180.0, 1.0); 114 | 115 | const auto& hist = df.getHistogram("f(theta)"); 116 | ASSERT_EQ(hist.partials.count("O-Si-O"), 1); 117 | 118 | // Find peak 119 | const auto& partial = hist.partials.at("O-Si-O"); 120 | double peak_val = 0; 121 | int peak_bin = -1; 122 | for(size_t i=0; i peak_val) { 124 | peak_val = partial[i]; 125 | peak_bin = i; 126 | } 127 | } 128 | double peak_angle = hist.bins[peak_bin]; 129 | EXPECT_NEAR(peak_angle, 90.0, 1.0); 130 | } 131 | 132 | TEST_F(PADTest, EquilateralTriangle60) { 133 | // Si at (0,0,0) 134 | // O at (1,0,0) 135 | // O at (0.5, sqrt(3)/2, 0) 136 | cell_.addAtom("Si", {10.0, 10.0, 10.0}); 137 | cell_.addAtom("O", {11.0, 10.0, 10.0}); 138 | cell_.addAtom("O", {10.5, 10.0 + std::sqrt(3.0)/2.0, 10.0}); 139 | 140 | DistributionFunctions df(cell_, 1.5, 1.3); 141 | df.calculatePAD(180.0, 1.0); 142 | 143 | const auto& hist = df.getHistogram("f(theta)"); 144 | // Should have O-Si-O 145 | const auto& partial = hist.partials.at("O-Si-O"); 146 | 147 | // Find peak near 60 148 | double val_at_60 = 0; 149 | // index for 60 deg is 60 or 59 depending on binning. 150 | // 59.5 (idx 59) -> [59, 60) 151 | // 60.5 (idx 60) -> [60, 61) 152 | // Exact 60 might land in 60. 153 | 154 | // Search max around 60 155 | size_t bin_60 = 60; 156 | EXPECT_GT(partial[bin_60] + partial[bin_60-1], 0.1) << "Should have peak near 60 degrees"; 157 | } 158 | 159 | TEST_F(PADTest, TetrahedralAngle) { 160 | // Si at center 161 | // 4 Neighbors at tetrahedral positions. 162 | // For simplicity, just check one angle 109.47 163 | cell_.addAtom("Si", {10.0, 10.0, 10.0}); 164 | // Vector 1: (1,1,1) normalized 165 | // Vector 2: (1,-1,-1) normalized 166 | // Dot product = (1-1-1)/3 = -1/3. acos(-1/3) = 109.47 deg 167 | 168 | double L = 1.0 / std::sqrt(3.0); 169 | cell_.addAtom("O", {10.0 + L, 10.0 + L, 10.0 + L}); 170 | cell_.addAtom("O", {10.0 + L, 10.0 - L, 10.0 - L}); 171 | 172 | DistributionFunctions df(cell_, 1.5, 1.3); // Distance is 1.0 173 | df.calculatePAD(180.0, 0.5); // Finer bins 174 | 175 | const auto& hist = df.getHistogram("f(theta)"); 176 | const auto& partial = hist.partials.at("O-Si-O"); 177 | 178 | // Expected ~109.5 179 | double peak_val = 0; 180 | double peak_angle = 0; 181 | for(size_t i=0; i peak_val) { 183 | peak_val = partial[i]; 184 | peak_angle = hist.bins[i]; 185 | } 186 | } 187 | EXPECT_NEAR(peak_angle, 109.5, 1.0); 188 | } 189 | 190 | 191 | // 3. Symmetry & Multi-Species 192 | TEST_F(PADTest, SymmetryAndSorting) { 193 | // A-B-C should partial B-A-C (or A-B-C sorted) or similar? 194 | // Implementation uses "Type1-Center-Type2" key. 195 | // If we have O-Si-N, is it stored as N-Si-O or O-Si-N? 196 | // It should be canonical. 197 | 198 | cell_.addAtom("Si", {10.0, 10.0, 10.0}); // Center 199 | cell_.addAtom("O", {11.0, 10.0, 10.0}); 200 | // Add Nitrogen 201 | cell_.addAtom("N", {10.0, 11.0, 10.0}); // 90 degrees 202 | 203 | DistributionFunctions df(cell_, 1.5, 1.3); 204 | df.calculatePAD(180.0, 1.0); 205 | 206 | const auto& hist = df.getHistogram("f(theta)"); 207 | 208 | // Check if we have O-Si-N or N-Si-O 209 | bool found = false; 210 | if (hist.partials.count("O-Si-N")) found = true; 211 | if (hist.partials.count("N-Si-O")) found = true; 212 | 213 | EXPECT_TRUE(found) << "Should have mixed species angle distribution"; 214 | } 215 | 216 | // 4. Normalization 217 | TEST_F(PADTest, FullNormalizationCheck) { 218 | // 1 Si, 4 O neighbors (tetrahedron) 219 | // 4 neighbors -> 4*3/2 = 6 angles. 220 | // All 6 angles are 109.47 221 | 222 | cell_.addAtom("Si", {10.0, 10.0, 10.0}); 223 | double L = 1.0 / std::sqrt(3.0); 224 | 225 | // Tetrahedral vertices 226 | cell_.addAtom("O", {10.0 + L, 10.0 + L, 10.0 + L}); 227 | cell_.addAtom("O", {10.0 + L, 10.0 - L, 10.0 - L}); 228 | cell_.addAtom("O", {10.0 - L, 10.0 + L, 10.0 - L}); 229 | cell_.addAtom("O", {10.0 - L, 10.0 - L, 10.0 + L}); 230 | 231 | DistributionFunctions df(cell_, 1.5, 1.1); 232 | df.calculatePAD(180.0, 1.0); 233 | 234 | const auto& hist = df.getHistogram("f(theta)"); 235 | 236 | double sum_partial = 0; 237 | double sum_total = 0; 238 | double bin_width = 1.0; 239 | 240 | 241 | 242 | if (hist.partials.count("O-Si-O")) { 243 | const auto& partial = hist.partials.at("O-Si-O"); 244 | for(double v : partial) sum_partial += v * bin_width; 245 | } 246 | 247 | if (hist.partials.count("Total")) { 248 | const auto& total = hist.partials.at("Total"); 249 | for(double v : total) sum_total += v * bin_width; 250 | } 251 | 252 | EXPECT_NEAR(sum_partial, 1.0, 0.05); // Relaxed checking 0.05 due to binning effects 253 | EXPECT_NEAR(sum_total, 1.0, 0.05); 254 | } 255 | -------------------------------------------------------------------------------- /src/StructureAnalyzer.cpp: -------------------------------------------------------------------------------- 1 | // Correlation - Liquid and Amorphous Solid Analysis Tool 2 | // Copyright © 2013-2025 Isaías Rodríguez (isurwars@gmail.com) 3 | // SPDX-License-Identifier: MIT 4 | // Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE 5 | 6 | #include "../include/StructureAnalyzer.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | //---------------------------------------------------------------------------// 17 | //------------------------------- Constructors ------------------------------// 18 | //---------------------------------------------------------------------------// 19 | StructureAnalyzer::StructureAnalyzer(Cell &cell, double cutoff, 20 | bool ignore_periodic_self_interactions) 21 | // Use the member initializer list for all members for correctness and 22 | // efficiency. 23 | : cell_(cell), cutoff_sq_(cutoff * cutoff), 24 | ignore_periodic_self_interactions_(ignore_periodic_self_interactions) { 25 | if (cutoff <= 0) { 26 | throw std::invalid_argument("Cutoff distance must be positive."); 27 | } 28 | 29 | // Ensure cutoff covers the largest bond distance 30 | const auto &elements = cell.elements(); 31 | double max_bond_dist = 0.0; 32 | for (size_t i = 0; i < elements.size(); ++i) { 33 | for (size_t j = i; j < elements.size(); ++j) { 34 | max_bond_dist = std::max(max_bond_dist, 35 | cell.getBondCutoff(elements[i].id.value, 36 | elements[j].id.value)); 37 | } 38 | } 39 | if (cutoff < max_bond_dist) { 40 | cutoff = max_bond_dist; 41 | cutoff_sq_ = cutoff * cutoff; 42 | } 43 | 44 | if (cell.isEmpty()) { 45 | return; // Nothing to compute for an empty cell 46 | } 47 | 48 | // Initialize the tensors and bond list with the correct dimensions 49 | const size_t num_elements = cell.elements().size(); 50 | distance_tensor_.resize(num_elements, 51 | std::vector>(num_elements)); 52 | angle_tensor_.resize( 53 | num_elements, 54 | std::vector>>( 55 | num_elements, std::vector>( 56 | num_elements, std::vector()))); 57 | neighbor_tensor_.resize(cell.atomCount()); 58 | 59 | // The constructor orchestrates the computation 60 | computeDistances(); 61 | computeAngles(); 62 | } 63 | 64 | //---------------------------------------------------------------------------// 65 | //--------------------------------- Methods ---------------------------------// 66 | //---------------------------------------------------------------------------// 67 | 68 | void StructureAnalyzer::computeDistances() { 69 | const auto &atoms = cell_.atoms(); 70 | const size_t atom_count = atoms.size(); 71 | const size_t num_elements = cell_.elements().size(); 72 | const auto &lattice = cell_.latticeVectors(); 73 | 74 | linalg::Vector3 box_sidelengths = {linalg::norm(lattice[0]), 75 | linalg::norm(lattice[1]), 76 | linalg::norm(lattice[2])}; 77 | int nx = 78 | static_cast(std::ceil(std::sqrt(cutoff_sq_) / box_sidelengths.x())); 79 | int ny = 80 | static_cast(std::ceil(std::sqrt(cutoff_sq_) / box_sidelengths.y())); 81 | int nz = 82 | static_cast(std::ceil(std::sqrt(cutoff_sq_) / box_sidelengths.z())); 83 | 84 | if (nx + ny + nz > 8) { 85 | ignore_periodic_self_interactions_ = false; 86 | } 87 | 88 | std::vector> displacements; 89 | for (int i = -nx; i <= nx; ++i) { 90 | for (int j = -ny; j <= ny; ++j) { 91 | for (int k = -nz; k <= nz; ++k) { 92 | displacements.push_back(lattice[0] * i + lattice[1] * j + 93 | lattice[2] * k); 94 | } 95 | } 96 | } 97 | 98 | std::vector atom_indices(atom_count); 99 | std::iota(atom_indices.begin(), atom_indices.end(), 0); 100 | 101 | // lock-free calculation phase. 102 | tbb::enumerable_thread_specific ets(num_elements, 103 | atom_count); 104 | 105 | tbb::parallel_for_each( 106 | atom_indices.begin(), atom_indices.end(), [&](size_t i) { 107 | // Each thread gets its own private copy of the results structure. 108 | ThreadLocalResults &local_results = ets.local(); 109 | auto &distance_local = local_results.distance_tensor_local; 110 | auto &neighbor_local = local_results.neighbor_tensor_local; 111 | 112 | const auto &atom_A = atoms[i]; 113 | const int type_A = atom_A.element_id(); 114 | 115 | for (size_t j = i; j < atom_count; ++j) { 116 | if (i == j && ignore_periodic_self_interactions_) { 117 | continue; 118 | } 119 | const auto &atom_B = atoms[j]; 120 | const int type_B = atom_B.element_id(); 121 | const double max_bond_dist = cell_.getBondCutoff(type_A, type_B); 122 | const double max_bond_dist_sq = max_bond_dist * max_bond_dist; 123 | for (const auto &disp : displacements) { 124 | if (i == j && linalg::norm_sq(disp) < 1e-9) { 125 | continue; 126 | } 127 | 128 | linalg::Vector3 r_ij = 129 | atom_B.position() + disp - atom_A.position(); 130 | double d_sq = linalg::norm_sq(r_ij); 131 | 132 | if (d_sq < cutoff_sq_) { 133 | double dist = std::sqrt(d_sq); 134 | distance_local[type_A][type_B].push_back(dist); 135 | if (i != j && type_A != type_B) { 136 | distance_local[type_B][type_A].push_back(dist); 137 | } 138 | 139 | if (d_sq <= max_bond_dist_sq) { 140 | neighbor_local[i].push_back({atom_B.id(), dist, r_ij}); 141 | if (i != j) { 142 | neighbor_local[j].push_back({atom_A.id(), dist, -1.0 * r_ij}); 143 | } 144 | } 145 | } 146 | } 147 | } 148 | }); 149 | 150 | for (auto &local_results : ets) { 151 | for (size_t i = 0; i < num_elements; ++i) { 152 | for (size_t j = 0; j < num_elements; ++j) { 153 | distance_tensor_[i][j].insert( 154 | distance_tensor_[i][j].end(), 155 | local_results.distance_tensor_local[i][j].begin(), 156 | local_results.distance_tensor_local[i][j].end()); 157 | } 158 | } 159 | for (size_t i = 0; i < atom_count; ++i) { 160 | neighbor_tensor_[i].insert(neighbor_tensor_[i].end(), 161 | local_results.neighbor_tensor_local[i].begin(), 162 | local_results.neighbor_tensor_local[i].end()); 163 | } 164 | } 165 | } 166 | 167 | void StructureAnalyzer::computeAngles() { 168 | const auto &atoms = cell_.atoms(); 169 | const size_t atom_count = atoms.size(); 170 | 171 | const size_t num_elements = cell_.elements().size(); 172 | using AngleTensor = 173 | std::vector>>>; 174 | 175 | // Initialize thread-local storage 176 | tbb::enumerable_thread_specific ets([&]() { 177 | return AngleTensor(num_elements, 178 | std::vector>>( 179 | num_elements, std::vector>( 180 | num_elements))); 181 | }); 182 | 183 | tbb::parallel_for( 184 | tbb::blocked_range(0, atom_count), 185 | [&](const tbb::blocked_range &r) { 186 | auto &local_tensor = ets.local(); 187 | for (size_t i = r.begin(); i != r.end(); ++i) { 188 | const auto ¢ral_atom_neighbors = neighbor_tensor_[i]; 189 | 190 | // Skip if the central atom has fewer than two neighbors to form an 191 | // angle 192 | if (central_atom_neighbors.size() < 2) 193 | continue; 194 | 195 | const int type_central = atoms[i].element_id(); 196 | 197 | // Loop over all unique pairs of neighbors (j, k) 198 | for (size_t j = 0; j < central_atom_neighbors.size(); ++j) { 199 | for (size_t k = j + 1; k < central_atom_neighbors.size(); ++k) { 200 | const auto &neighbor1 = central_atom_neighbors[j]; 201 | const auto &neighbor2 = central_atom_neighbors[k]; 202 | 203 | const int type1 = atoms[neighbor1.index.value].element_id(); 204 | const int type2 = atoms[neighbor2.index.value].element_id(); 205 | 206 | const auto &v1 = neighbor1.r_ij; 207 | const auto &v2 = neighbor2.r_ij; 208 | 209 | // Calculate angle 210 | double cos_theta = linalg::dot(v1, v2) / 211 | (neighbor1.distance * neighbor2.distance); 212 | cos_theta = std::clamp(cos_theta, -1.0, 1.0); 213 | double angle_rad = std::acos(cos_theta); 214 | local_tensor[type1][type_central][type2].push_back(angle_rad); 215 | if (type1 != type2) { 216 | local_tensor[type2][type_central][type1].push_back(angle_rad); 217 | } 218 | } 219 | } 220 | } 221 | }); 222 | 223 | // Merge results 224 | for (const auto &local_tensor : ets) { 225 | for (size_t i = 0; i < num_elements; ++i) { 226 | for (size_t j = 0; j < num_elements; ++j) { 227 | for (size_t k = 0; k < num_elements; ++k) { 228 | angle_tensor_[i][j][k].insert(angle_tensor_[i][j][k].end(), 229 | local_tensor[i][j][k].begin(), 230 | local_tensor[i][j][k].end()); 231 | } 232 | } 233 | } 234 | } 235 | } 236 | 237 | -------------------------------------------------------------------------------- /paper.bib: -------------------------------------------------------------------------------- 1 | 2 | @article{rodriguez_emergence_2019, 3 | title = {Emergence of magnetism in bulk amorphous palladium}, 4 | volume = {100}, 5 | copyright = {All rights reserved}, 6 | issn = {2469-9950, 2469-9969}, 7 | url = {https://link.aps.org/doi/10.1103/PhysRevB.100.024422}, 8 | doi = {10.1103/PhysRevB.100.024422}, 9 | language = {en}, 10 | urldate = {2020-05-11}, 11 | journal = {Phys. Rev. B}, 12 | author = {Rodríguez, Isaías and Valladares, Renela M. and Hinojosa-Romero, David and Valladares, Alexander and Valladares, Ariel A.}, 13 | month = jul, 14 | year = {2019}, 15 | pages = {024422} 16 | } 17 | 18 | @article{pyykko_molecular_2009, 19 | title = {Molecular {Single}-{Bond} {Covalent} {Radii} for {Elements} 1-118}, 20 | volume = {15}, 21 | issn = {09476539, 15213765}, 22 | url = {http://doi.wiley.com/10.1002/chem.200800987}, 23 | doi = {10.1002/chem.200800987}, 24 | language = {en}, 25 | urldate = {2020-05-26}, 26 | journal = {Chem. Eur. J.}, 27 | author = {Pyykkö, Pekka and Atsumi, Michiko}, 28 | month = jan, 29 | year = {2009}, 30 | pages = {186} 31 | } 32 | 33 | @article{valladares_new_2011, 34 | title = {New {Approaches} to the {Computer} {Simulation} of {Amorphous} {Alloys}: {A} {Review}}, 35 | volume = {4}, 36 | issn = {1996-1944}, 37 | shorttitle = {New {Approaches} to the {Computer} {Simulation} of {Amorphous} {Alloys}}, 38 | url = {http://www.mdpi.com/1996-1944/4/4/716}, 39 | doi = {10.3390/ma4040716}, 40 | language = {en}, 41 | urldate = {2020-11-30}, 42 | journal = {Materials}, 43 | author = {Valladares, Ariel A. and Díaz-Celaya, Juan A. and Galván-Colín, Jonathan and Mejía-Mendoza, Luis M. and Reyes-Retana, José A. and Valladares, Renela M. and Valladares, Alexander and Alvarez-Ramirez, Fernando and Qu, Dongdong and Shen, Jun}, 44 | month = apr, 45 | year = {2011}, 46 | pages = {716} 47 | } 48 | 49 | @book{elliott_physics_1986, 50 | title = {Physics of {Amorphous} {Materials}}, 51 | isbn = {978-0-470-20472-6}, 52 | url = {https://books.google.com.mx/books?id=WJ5LPgAACAAJ}, 53 | publisher = {John Wiley \& Sons, Incorporated}, 54 | author = {Elliott, S.R.}, 55 | year = {1986} 56 | } 57 | 58 | @book{miller_bulk_2007, 59 | series = {{SpringerLink} : {Bücher}}, 60 | title = {Bulk {Metallic} {Glasses}: {An} {Overview}}, 61 | isbn = {978-0-387-48921-6}, 62 | url = {https://books.google.com.mx/books?id=_pYa4uSuDUcC}, 63 | publisher = {Springer US}, 64 | author = {Miller, M. and Liaw, P.}, 65 | year = {2007} 66 | } 67 | 68 | @article{galvan-colin_short-range_2015, 69 | title = {Short-range order in \textit{ab initio} computer generated amorphous and liquid {Cu}–{Zr} alloys: {A} new approach}, 70 | volume = {475}, 71 | issn = {09214526}, 72 | shorttitle = {Short-range order in \textit{ab initio} computer generated amorphous and liquid {Cu}–{Zr} alloys}, 73 | url = {https://linkinghub.elsevier.com/retrieve/pii/S0921452615301435}, 74 | doi = {10.1016/j.physb.2015.07.027}, 75 | language = {en}, 76 | urldate = {2020-11-30}, 77 | journal = {Physica B: Condensed Matter}, 78 | author = {Galván-Colín, Jonathan and Valladares, Ariel A. and Valladares, Renela M. and Valladares, Alexander}, 79 | month = oct, 80 | year = {2015}, 81 | pages = {140} 82 | } 83 | 84 | @article{bernal_attempt_1937, 85 | title = {An attempt at a molecular theory of liquid structure}, 86 | volume = {33}, 87 | issn = {0014-7672}, 88 | url = {http://xlink.rsc.org/?DOI=tf9373300027}, 89 | doi = {10.1039/tf9373300027}, 90 | language = {en}, 91 | urldate = {2020-12-01}, 92 | journal = {Trans. Faraday Soc.}, 93 | author = {Bernal, J. D.}, 94 | year = {1937}, 95 | pages = {27} 96 | } 97 | 98 | @article{hafner_triplet_1982, 99 | title = {Triplet correlation functions in metallic glasses}, 100 | volume = {12}, 101 | issn = {0305-4608}, 102 | url = {https://iopscience.iop.org/article/10.1088/0305-4608/12/11/001}, 103 | doi = {10.1088/0305-4608/12/11/001}, 104 | urldate = {2020-12-04}, 105 | journal = {J. Phys. F: Met. Phys.}, 106 | author = {Hafner, J}, 107 | month = nov, 108 | year = {1982}, 109 | pages = {L205} 110 | } 111 | 112 | @article{bernal_bakerian_1964, 113 | title = {The {Bakerian} {Lecture}, 1962 {The} structure of liquids}, 114 | volume = {280}, 115 | issn = {0080-4630, 2053-9169}, 116 | url = {https://royalsocietypublishing.org/doi/10.1098/rspa.1964.0147}, 117 | doi = {10.1098/rspa.1964.0147}, 118 | abstract = {A satisfactory picture of the structure of liquids has lagged far behind that of other states of matter. Ever since the time of Euler in the eighteenth century or, in a more precise form, since that of Maxwell in the nineteenth, we have had a convincing qualitative and quantitative picture of the chaos that is represented by the movements of the ideal gas molecules. The notion of a crystal or a solid in general as an arrangement of molecules ‘ in rank and file’, as Newton put it, is, in fact, older than Newton yet its quantitative statement was made possible only through the work of Born and others in our own century. But it is admitted even by those who work most in the field that the study of the structure of liquids or any exposition of their properties in atomic terms is still largely to be sought. This is not for want of trying. A vast number of researches have been devoted to attempts to analyze the structure of liquids, either directly by the diffraction methods which have proved so successful in crystalline solids, or, indirectly, through the construction of models and their thermodynamic testing. But we still lack either an adequate picture of the arrangement of molecules in a liquid or the necessary quantitative theory to explain their thermal and other properties.}, 119 | language = {en}, 120 | urldate = {2020-12-04}, 121 | journal = {Proc. R. Soc. Lond. A}, 122 | author = {Bernal, J. D.}, 123 | month = jul, 124 | year = {1964}, 125 | pages = {299} 126 | } 127 | 128 | @inproceedings{galvan-colin_ab_2016, 129 | address = {Sciforum.net}, 130 | title = {\textit{Ab initio} generation of binary alloy foams: the case of amorphous {Cu}$_{64}${Zr}$_{36}$}, 131 | url = {http://sciforum.net/conference/ecm-2/paper/3392}, 132 | doi = {10.3390/ecm-2-B001}, 133 | language = {en}, 134 | urldate = {2020-12-04}, 135 | booktitle = {Proceedings of 2nd {International} {Electronic} {Conference} on {Materials}}, 136 | publisher = {MDPI}, 137 | author = {Galvan-Colin, Jonathan and Valladares, Ariel and Valladares, Renela and Valladares, Alexander}, 138 | month = apr, 139 | year = {2016}, 140 | pages = {B001} 141 | } 142 | 143 | @article{mata-pinzon_superconductivity_2016, 144 | title = {Superconductivity in {Bismuth}. {A} {New} {Look} at an {Old} {Problem}}, 145 | volume = {11}, 146 | issn = {1932-6203}, 147 | url = {https://dx.plos.org/10.1371/journal.pone.0147645}, 148 | doi = {10.1371/journal.pone.0147645}, 149 | language = {en}, 150 | urldate = {2020-12-04}, 151 | journal = {PLoS ONE}, 152 | author = {Mata-Pinzón, Zaahel and Valladares, Ariel A. and Valladares, Renela M. and Valladares, Alexander}, 153 | editor = {He, Ruihua}, 154 | month = jan, 155 | year = {2016}, 156 | pages = {e0147645} 157 | } 158 | 159 | @phdthesis{romero-rangel_simulaciones_2014, 160 | address = {CDMX, México}, 161 | type = {{PhD} {Thesis}}, 162 | title = {{Simulaciones} {Computacionales} de {Materiales} {Nanoporosos}. {El} {Caso} de {Carbono}}, 163 | url = {http://alephv23.cichcu.unam.mx/ptd2014/junio/0714308/Index.html}, 164 | language = {Spanish}, 165 | urldate = {2020-12-04}, 166 | school = {Universidad Nacional Autónoma de México}, 167 | author = {Romero-Rangel, Cristina}, 168 | month = jun, 169 | year = {2014} 170 | } 171 | 172 | @phdthesis{mejia-mendoza_estudio_2014, 173 | address = {CDMX, México}, 174 | type = {{PhD} {Thesis}}, 175 | title = {Estudio {Computacional} de {Aleaciones} {Amorfas} {Basadas} en {Silicio}-{Carbono} y {Silicio}-{Germanio}}, 176 | url = {http://alephv23.cichcu.unam.mx/ptd2014/junio/0714309/Index.html}, 177 | language = {Spanish}, 178 | urldate = {2020-12-04}, 179 | school = {Universidad Nacional Autónoma de México}, 180 | author = {Mejía-Mendoza, Luis M.}, 181 | month = jun, 182 | year = {2014} 183 | } 184 | 185 | @phdthesis{santiago_simulacion_2011, 186 | address = {CDMX, México}, 187 | type = {{PhD} {Thesis}}, 188 | title = {Simulación de sistemas metálicos amorfos y porosos de elementos nobles}, 189 | url = {http://alephv23.cichcu.unam.mx/ptd2012/marzo/0677980/Index.html}, 190 | language = {Spanish}, 191 | urldate = {2020-12-04}, 192 | school = {Universidad Nacional Autónoma de México}, 193 | author = {Santiago, Ulises}, 194 | month = dec, 195 | year = {2011} 196 | } 197 | 198 | @phdthesis{galvan-colin_atomic_2016, 199 | address = {CDMX, México}, 200 | type = {{PhD} {Thesis}}, 201 | title = {Atomic structure and properties of amorphous, liquid and amorphous porous {Cu}-{Zr} alloys by \textit{ab initio} simulations}, 202 | url = {http://alephv23.cichcu.unam.mx/ptd2016/noviembre/0752730/Index.html}, 203 | language = {English}, 204 | urldate = {2020-12-04}, 205 | school = {Universidad Nacional Autónoma de México}, 206 | author = {Galvan-Colin, Jonathan}, 207 | month = nov, 208 | year = {2016} 209 | } 210 | 211 | @phdthesis{mata-pinzon_propiedades_2016, 212 | address = {CDMX, México}, 213 | type = {{PhD} {Thesis}}, 214 | title = {Propiedades electrónicas y vibracionales y su influencia en la superconductividad del bismuto amorfo y sus aleaciones con plomo, talio y antimonio}, 215 | url = {http://alephv23.cichcu.unam.mx/ptd2016/noviembre/0753741/Index.html}, 216 | language = {Spanish}, 217 | urldate = {2020-12-04}, 218 | school = {Universidad Nacional Autónoma de México}, 219 | author = {Mata-Pinzón, Zaahel}, 220 | month = nov, 221 | year = {2016} 222 | } 223 | 224 | @phdthesis{rodriguez_calculo_2019, 225 | address = {CDMX, México}, 226 | type = {{PhD} {Thesis}}, 227 | title = {Cálculo \textit{ab initio} de propiedades estructurales, electrónicas y vibracionales en aleaciones de paladio}, 228 | url = {http://alephv23.cichcu.unam.mx/ptd2019/agosto/0793234/Index.html}, 229 | language = {Spanish}, 230 | urldate = {2020-12-04}, 231 | school = {Universidad Nacional Autónoma de México}, 232 | author = {Rodríguez, Isaías}, 233 | month = jun, 234 | year = {2019} 235 | } 236 | 237 | @book{waseda_structure_1980, 238 | address = {New York ; London}, 239 | title = {The structure of non-crystalline materials: liquids and amorphous solids}, 240 | isbn = {978-0-07-068426-3}, 241 | shorttitle = {The structure of non-crystalline materials}, 242 | publisher = {McGraw-Hill International Book Co}, 243 | author = {Waseda, Yoshio}, 244 | year = {1980}, 245 | doi = {10.1016/0025-5416(82)90073-8}, 246 | keywords = {Amorphous substances, Liquid metals} 247 | } 248 | 249 | @book{dassault_systemes_biovia_materials_2016, 250 | title = {Materials {Studio} 2016: {Forcite}, {DMol3} and {CASTEP}}, 251 | publisher = {Dassault Systèmes BIOVIA}, 252 | author = {{Dassault Systèmes BIOVIA}}, 253 | year = {2016} 254 | } 255 | 256 | @misc{materials_2016, 257 | title = {Materials {Studio} 2016: {Forcite}, {CASTEP}}, 258 | publisher = {Dassault Systèmes BIOVIA}, 259 | year = {2016} 260 | } 261 | 262 | @book{ziman_models_1979, 263 | address = {Cambridge [Eng.] ; New York}, 264 | title = {Models of disorder: the theoretical physics of homogeneously disordered systems}, 265 | isbn = {978-0-521-21784-2 978-0-521-29280-1}, 266 | shorttitle = {Models of disorder}, 267 | doi = {10.1063/1.2914122}, 268 | publisher = {Cambridge University Press}, 269 | author = {Ziman, J. M.}, 270 | year = {1979}, 271 | keywords = {Order-disorder models}, 272 | annote = {Includes index}, 273 | } 274 | 275 | @article{roe_ptraj_2013, 276 | title = {{PTRAJ} and {CPPTRAJ}: {Software} for {Processing} and {Analysis} of {Molecular} {Dynamics} {Trajectory} {Data}}, 277 | volume = {9}, 278 | issn = {1549-9618, 1549-9626}, 279 | shorttitle = {{PTRAJ} and {CPPTRAJ}}, 280 | url = {https://pubs.acs.org/doi/10.1021/ct400341p}, 281 | doi = {10.1021/ct400341p}, 282 | language = {en}, 283 | number = {7}, 284 | urldate = {2021-07-24}, 285 | journal = {J. Chem. Theory Comput.}, 286 | author = {Roe, Daniel R. and Cheatham, Thomas E.}, 287 | month = jul, 288 | year = {2013}, 289 | pages = {3084--3095} 290 | } 291 | 292 | @article{VASPKIT-2021, 293 | title = {VASPKIT: A user-friendly interface facilitating high-throughput computing and analysis using VASP code}, 294 | journal = {Computer Physics Communications}, 295 | volume = {267}, 296 | pages = {108033}, 297 | year = {2021}, 298 | doi = {10.1016/j.cpc.2021.108033}, 299 | author = {Vei Wang and Nan Xu and Jin-Cheng Liu and Gang Tang and Wen-Tong Geng}, 300 | } 301 | -------------------------------------------------------------------------------- /paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Correlation: An Analysis Tool for Liquids and for Amorphous Solids" 3 | tags: 4 | - C++ 5 | - Materials 6 | - Molecular dynamics 7 | - Pair Correlation Function 8 | - Plane Angle Distribution 9 | authors: 10 | - name: Isaías Rodríguez 11 | orcid: 0000-0002-4359-2742 12 | affiliation: 1 13 | - name: Renela M. Valladares 14 | orcid: 0000-0002-3819-2226 15 | affiliation: 2 16 | - name: Alexander Valladares 17 | affiliation: 2 18 | - name: David Hinojosa-Romero 19 | orcid: 0000-0002-8686-965X 20 | affiliation: 1 21 | - name: Ulises Santiago 22 | orcid: 0000-0003-2259-5984 23 | affiliation: 3 24 | - name: Ariel A. Valladares 25 | orcid: 0000-0003-1505-2244 26 | affiliation: 1 27 | affiliations: 28 | - name: Departamento de Materia Condensada, Instituto de Investigaciones en Materiales, Universidad Nacional Autónoma de México, Coyoacán, Ciudad de México, México. 29 | index: 1 30 | - name: Departamento de Física, Facultad de Ciencias, Universidad Nacional Autónoma de México, Coyoacán, Ciudad de México, México. 31 | index: 2 32 | - name: Department of Computational and Systems Biology, University of Pittsburgh School of Medicine, Pittsburgh, Pennsylvania, USA. 33 | index: 3 34 | date: 09 September 2021 35 | bibliography: paper.bib 36 | csl: aps.csl 37 | --- 38 | 39 | # Summary 40 | 41 | For almost a century, since Bernal's attempts at a molecular theory of liquid structure [@bernal_attempt_1937], correlation functions have been the bridge to compare theoretical calculations with experimental measurements in the study of disordered materials. 42 | 43 | Pair Distribution Functions ($g(r)$), Radial Distribution Functions ($J(r)$), Plane Angle Distributions ($g(\theta)$) and Coordination Numbers ($n_c$) have been widely used to characterize amorphous and liquid materials [@waseda_structure_1980; @elliott_physics_1986; @valladares_new_2011] and, in particular Bulk Metallic Glasses [@miller_bulk_2007; @galvan-colin_short-range_2015]. 44 | 45 | `Correlation` is an Open-Source software designed to analyze liquid structures and amorphous solids; the software is user-friendly, the modular design makes it easy to integrate in High-Throughput Computing (HTC) to process structures with a large number of constituents in a standardized fashion. `Correlation` is ready to be used in Windows, Linux and Mac. Currently, we support DMol3 (CAR), CASTEP (CELL), ONETEP (DAT) and VASP (POSCAR) structure files. The code can handle up to 100,000 atoms, so it can be used to analyze both classical and first-principles simulations. At the end, the output of every single correlation function is exported to the corresponding comma-separated value file (CSV), to further analyze the results. 46 | 47 | # Statement of Need 48 | 49 | As time goes by, the number of atoms in theoretical calculations has grown from a few dozens to hundreds of thousands of atoms, and with this increment the complexity to calculate correlation functions that represent the structure of materials has steadily increased. 50 | To answer this need, there have been several tools developed to calculate some of the more used correlation functions like: pair distribution functions, radial distribution functions, and plane angle distributions. Here, we present an incomplete list of these tools: 51 | 52 | - **Forcite Plus**: Forcite Plus is part of the Materials Studio suite [@materials_2016], the program includes analyzing tools to compute structure properties like RDF and PAD. 53 | 54 | - **PTRAJ/CPPTRAJ**: A tool designed to analyze Amber Molecular Dynamics trajectories and related properties including RDF and time correlation functions, included in the AmbarTools suite [@roe_ptraj_2013]. 55 | 56 | - **VASPKIT**: A post-processing tool for VASP calculated data [@VASPKIT-2021], the code includes tools to analyze structural properties and dynamics trajectories. 57 | 58 | - [**rdfpy**](https://github.com/by256/rdfpy): An open Python library for fast computation of 2D and 3D radial distribution functions of crystalline structures in the Crystallographic Information File (CIF). 59 | 60 | - [**RadialDistributionFunction **](https://github.com/RaulPPelaez/RadialDistributionFunction): Computes the Radial Distribution Function (RDF) of a group of positions in a file, averages it for all snapshots in the file, atom positions must be in a custom format. 61 | 62 | However, the use of these tools has been limited, either by a prohibiting cost (**Forcite Plus**), or has been restricted to private academic groups, or geopolitical limitations introduced by the licensing (**CASTEP** postprocessing tools), or by being specially designed to specific software for material simulation (**PTRAJ/CPPTRAJ**, **VASPKIT**), or by having a narrow scope of input formats and correlation functions calculated (**rdfpy**, **RadialDistributionFunction**). 63 | With these limitations in mind, we decided to develop software that could calculate several correlation functions of materials, as well as the more interesting properties derived from these functions, while making the software accessible to as many people as possible. 64 | 65 | # Mathematical Background 66 | 67 | ## Pair Distribution Functions 68 | 69 | The structure factor is one of the most useful tools in analyzing the scattering patterns obtained from X-ray, electron and neutron diffraction experiments of crystalline, disordered and amorphous materials. 70 | The Fourier transform of the scattering intensity given by the structure factor $S(Q)$, yields the pair distribution function (PDF) $g(r)$ defined by: 71 | 72 | \begin{equation}\label{eq:PDF} 73 | g(r) - 1= \frac{1}{2 \pi^2 \rho*0 r}\int*{0}^{\infty} Q[S(Q)-1]\sin(Qr)dQ, 74 | \end{equation} 75 | 76 | The pair distribution function could also be seen like a distance map inside the material, the $g(r)$ function gives how feasible is finding two atoms separated by the distance ($r$) as can be seen in \autoref{fig:RDF} [@ziman_models_1979]. 77 | 78 | The PDF is normalized so that, as $r \to \infty$, $g(r) \to 1$, Also, as $r \to 0$ (for $r$ shorter than the distance of the closest approach of pair of atoms), $g(r)$ becomes zero. The main advantage of the PDF and the related functions, is that they emphasize the short-range order of a material. 79 | 80 | ### Reduced pair distribution function $G(r)$ 81 | 82 | One of the most widely used pair correlation function is the reduced pair distribution function (rPDF), also known in the literature as differential pair distribution function ($D(r)$). This function is defined as $G(r) = 4\pi \rho_0 r (g(r)-1)$. From this definition, and the previously discussed tendency at large $r$ of the PDF, it's clear that the reduced pair distribution function (rPDF) oscillates around zero as $r \to \infty$. It also becomes evident that as $r \to 0$ (for $r$ smaller than the closest pair of atoms), the rPDF behaves like $-4\pi \rho_0$. 83 | 84 | ![Schematic depiction of the first and second neighbor’s coordination spheres for an amorphous metallic alloy and the corresponding pair distribution function. Design inspired by J. M. Ziman, Models of disorder (Cambridge University Press, 1979). \label{fig:RDF}](./Images/Fig1.png){ width=75% } 85 | 86 | While the PDF ($g(r)$) has an intuitive geometric definition, the rPDF ($G(r)$) can be directly obtained by a Fourier transform of the structure factor ($S(Q)$) as can be seen in \autoref{eq:PDF}; this close relation with the experimental data explains the popularity that this function has. Also, it has another advantage over other correlation functions [like PDF ($g(r)$)] as the numerical density $\rho_0 = N/V$ needs to be estimated in order to normalize the functions. This is not necessary in rPDF ($G(r)$); where this information is already contained in $G(r)$ as the slope of the function when $r \to 0$. 87 | 88 | ### Radial distribution function $J(r)$ 89 | 90 | The last correlation function we shall discuss is also one of the most physically intuitive, the RDF, $J(r)$, which is related to the Pair Distribution Function (PDF) by: 91 | 92 | \begin{equation}\label{eq:RDF} 93 | J(r) = 4\pi r^{2} \rho_0 g(r). 94 | \end{equation} 95 | 96 | The RDF has the useful property that the quantity $J(r)dr$ gives the number of atoms in a spherical shell with inner radius $r$ and thickness $dr$ around every atom as depicted in \autoref{fig:RDF}. For example, the coordination number, or the number of neighbors ($n_c$), is given by: 97 | 98 | \begin{equation}\label{eq:CN} 99 | n*c = \int*{r_1}^{r_2} J(r) dr, 100 | \end{equation} 101 | 102 | where $r_1$ and $r_2$ define the RDF peak corresponding to the coordination shells in question. 103 | 104 | ## Plane Angle Distribution 105 | 106 | The use of higher order correlation functions to analyze the structure of liquids and amorphous solids has been proposed in the literature [@hafner_triplet_1982; @galvan-colin_ab_2016], trying to reproduce the success obtained by Bernal in the analysis of the structure of liquids [@bernal_bakerian_1964]. 107 | 108 | In particular, the Plane Angle Distribution, also known as the Bond Angle Distribution $f(\theta)$ has been used to characterize the short-range order of amorphous and liquid structures [@galvan-colin_short-range_2015; @galvan-colin_ab_2016; @mata-pinzon_superconductivity_2016]. Here we propose to substitute the term “Bond Angle Distribution” (BAD), by the term “Plane Angle Distribution” (PAD), also frequently used, since in condensed matter, proximity does not necessarily imply bonding. 109 | 110 | # Benchmarks 111 | 112 | In order to assess the performance of ´Correlation´, we calculated the PDF and PAD for two well known structures (Crystalline Silicon and a Graphene Layer), and compared the results with the commercially available software Forcite included in the Materials Studio suite [@materials_2016]; to test `Correlation` in amorphous and liquid materials we selected amorphous palladium [@rodriguez_emergence_2019], amorphous palladium hydride [@rodriguez_calculo_2019] and liquid bismuth. Because of the complexity to calculate PAD of amorphous and liquids in Forcite, we chose to compare them with the code developed by U. Santiago within our group. [@santiago_simulacion_2011]. 113 | 114 | The results of these benchmarks are shown in \autoref{fig:RDF-PAD}, and the structures used to calculate these figures are included in the code as tests 1 to 5. The last structure included as test 6 is a 2x2x2 supercell of amorphous palladium hydride included in test 4, to benchmark memory and CPU performance in a structure with thousands of atoms. 115 | 116 | ![Pair Distribution Functions $g(r)$ on the left, Plane Angle Distributions on the right for: crystalline silicon, graphene Layer, amorphous palladium, amorphous palladium hydride and liquid bismuth. Correlation in gray, Forcite in black, Plane Angles in red. Similarity is remarkable between `Correlation` and Forcite as can be seen in all PDFs. Figures (a) to (d) indicate that the coincidence in the two results overlap completely. \label{fig:RDF-PAD}](./Images/Fig2.png){ width=75% } 117 | 118 | # Conclusion & Perspective 119 | 120 | `Correlation` is a lightweight, modular software that can be used in HTC and adapted to analyze the main correlation functions used to characterize crystalline and amorphous solids, as well as liquids. 121 | 122 | `Correlation` has been used in previously published work [@galvan-colin_short-range_2015; @galvan-colin_ab_2016; @mata-pinzon_superconductivity_2016] as well as several Ph.D. Theses [@santiago_simulacion_2011; @romero-rangel_simulaciones_2014; @mejia-mendoza_estudio_2014; @galvan-colin_atomic_2016; @mata-pinzon_propiedades_2016; @rodriguez_calculo_2019] done in our group. 123 | We will continue to support and enrich the software in the foreseeable future. Here we list the features planned to be added in the future: 124 | 125 | - Support for additional output files, like hdf5 standard. 126 | 127 | - Inclusion of other correlation functions like Velocity Correlation Functions, to further improve the analysis of liquids and phase transitions. 128 | 129 | - Inclusion of structure factors and x-ray diffraction, to facilitate the comparison with experimental results. 130 | 131 | - Parallelization of the main loop, to further improve the code by switching to a ‘divide-and-conquer paradigm. 132 | 133 | We are open to receive suggestions that would further improve the functionality of the software. Address all comments and observations to the GitHub: [Correlation](https://github.com/Isurwars/Correlation), or to the email of the first author, I.R: [isurwars@ciencias.unam.mx](isurwars@ciencias.unam.mx) 134 | 135 | # Acknowledgements 136 | 137 | I.R. acknowledges PAPIIT, DGAPA-UNAM for his posdoctoral fellowship. 138 | D.H.R. acknowledges Consejo Nacional de Ciencia y Tecnología (CONACyT) for supporting his graduate studies. 139 | A.A.V., R.M.V., and A.V. thank DGAPA-UNAM for continued financial support to carry out research projects under grants No. IN104617 and IN116520. 140 | M. T. Vázquez and O. Jiménez provided the information requested. 141 | A. López and A. Pompa helped with the maintenance and support of the supercomputer in IIM-UNAM. 142 | Simulations were partially carried out at the Supercomputing Center of DGTIC-UNAM. 143 | We would like to express our gratitude to F. B. Quiroga, M. A. Carrillo, R. S. Vilchis, S. Villarreal and A. de León, for the time invested in testing the code, as well as the structures provided for benchmarks and tests. 144 | 145 | # References 146 | -------------------------------------------------------------------------------- /ui/AppWindow.slint: -------------------------------------------------------------------------------- 1 | import { VerticalBox, HorizontalBox, Button, LineEdit, CheckBox, GroupBox, ComboBox } from "std-widgets.slint"; 2 | import {InputGroup} from "InputGroup.slint"; 3 | import { ToolTip } from "ToolTip.slint"; 4 | import { Theme } from "Theme.slint"; 5 | 6 | 7 | 8 | export component AppWindow inherits Window { 9 | // Callback that the UI invokes on the C++ side 10 | callback run_analysis(); 11 | callback browse_file(); 12 | callback check_file_dialog_status(); 13 | 14 | // Properties that C++ updates to change the UI 15 | in-out property file_status_text: "Please load a structure file."; 16 | in-out property analysis_status_text: ""; 17 | in-out property progress: 0.0; 18 | in-out property analysis_running: false; 19 | in-out property timer_running: false; 20 | in-out property text_opacity: true; 21 | 22 | // User-configurable options, now exposed to the UI 23 | in-out property in_file_text; 24 | in-out property normalize; 25 | in-out property smoothing; 26 | in-out property r_max; 27 | in-out property r_bin_width; 28 | in-out property q_max; 29 | in-out property q_bin_width; 30 | in-out property r_int_max; 31 | in-out property angle_max; 32 | in-out property angle_bin_width; 33 | in-out property bond_factor; 34 | in-out property smoothing_sigma; 35 | in-out property smoothing_kernel; 36 | 37 | // Theme 38 | min-width: 500px; 39 | min-height: 500px; 40 | preferred-width: 600px; 41 | preferred-height: 600px; 42 | background: Theme.window-background; 43 | default-font-size: Theme.font-size-regular; 44 | default-font-family: Theme.font-family; 45 | default-font-weight: Theme.font-weight-regular; 46 | property logo-icon: @image-url("../Images/Logo.png"); 47 | icon: logo-icon; 48 | title: "Correlation Analysis Tool"; 49 | 50 | timer := Timer { 51 | interval: 200ms; 52 | running: timer_running; 53 | triggered() => { 54 | check_file_dialog_status(); 55 | } 56 | } 57 | 58 | window_area := TouchArea { 59 | VerticalBox { 60 | GridLayout { 61 | padding: Theme.spacing-regular; 62 | spacing: Theme.spacing-regular; 63 | preferred-width: 600px; 64 | preferred-height: 600px; 65 | Row { 66 | Text { 67 | preferred-width: 550px; 68 | text: "1. Input File"; 69 | color: Theme.foreground; 70 | colspan: 3; 71 | } 72 | } 73 | 74 | Row { 75 | file_area := TouchArea { 76 | colspan: 3; 77 | preferred-width: 550px; 78 | preferred-height: 150px; 79 | max-height: 150px; 80 | Button { 81 | height: parent.height; 82 | width: parent.width; 83 | text: "Load a structure file"; 84 | clicked => { 85 | browse_file(); 86 | } 87 | } 88 | } 89 | } 90 | 91 | Row { 92 | Text { 93 | preferred-width: 550px; 94 | text: file_status_text; 95 | color: Theme.foreground; 96 | colspan: 3; 97 | } 98 | } 99 | 100 | Row { 101 | Text { 102 | min-height: Theme.size-regular; 103 | text: "2. Options"; 104 | color: Theme.foreground; 105 | } 106 | } 107 | 108 | Row { 109 | r_max_area := InputGroup { 110 | min-height: Theme.size-regular; 111 | label_text: "RDF Max r"; 112 | unit_text: "Å"; 113 | value_text <=> r_max; 114 | } 115 | 116 | r_bin_area := InputGroup { 117 | min-height: Theme.size-regular; 118 | label_text: "RDF Bin Width"; 119 | unit_text: "Å"; 120 | value_text <=> r_bin_width; 121 | } 122 | 123 | bond_area := InputGroup { 124 | min-height: Theme.size-regular; 125 | label_text: "Bond Parameter"; 126 | value_text <=> bond_factor; 127 | } 128 | } 129 | 130 | Row { 131 | theta_max_area := InputGroup { 132 | min-height: Theme.size-regular; 133 | label_text: "PAD Max Angle"; 134 | unit_text: "°"; 135 | value_text <=> angle_max; 136 | } 137 | 138 | theta_bin_area := InputGroup { 139 | min-height: Theme.size-regular; 140 | label_text: "PAD Bin Width"; 141 | unit_text: "°"; 142 | value_text <=> angle_bin_width; 143 | } 144 | } 145 | 146 | Row { 147 | s_max_area := InputGroup { 148 | min-height: Theme.size-regular; 149 | label_text: "Max Q"; 150 | unit_text: "Å⁻¹"; 151 | value_text <=> q_max; 152 | } 153 | 154 | s_bin_area := InputGroup { 155 | min-height: Theme.size-regular; 156 | label_text: "S(Q) Bin Width"; 157 | unit_text: "Å⁻¹"; 158 | value_text: q_bin_width; 159 | } 160 | 161 | r_int_area := InputGroup { 162 | min-height: Theme.size-regular; 163 | label_text: "Max r for the FFT"; 164 | unit_text: "Å"; 165 | value_text: r_int_max; 166 | } 167 | } 168 | 169 | Row { 170 | smooth_area := TouchArea { 171 | min-height: Theme.size-regular; 172 | VerticalBox { 173 | CheckBox { 174 | text: "Enable Smoothing"; 175 | checked: smoothing; 176 | toggled => { 177 | smoothing = !smoothing; 178 | } 179 | } 180 | } 181 | } 182 | 183 | sigma_area := InputGroup { 184 | min-height: Theme.size-regular; 185 | label_text: "Smoothing Sigma"; 186 | enabled_input: smoothing; 187 | value_text <=> smoothing_sigma; 188 | } 189 | 190 | kernel_area := TouchArea { 191 | min-height: Theme.size-regular; 192 | 193 | ComboBox { 194 | enabled: smoothing; 195 | model: ["Gaussian", "Bump", "Triweight"]; 196 | current-index: smoothing_kernel; 197 | selected(i) => { 198 | smoothing_kernel = i.to-float(); 199 | } 200 | } 201 | } 202 | } 203 | 204 | Row { 205 | Text { 206 | preferred-width: 550px; 207 | text: "3. Run Analysis"; 208 | color: Theme.foreground; 209 | colspan: 3; 210 | } 211 | } 212 | 213 | Row { 214 | 215 | analysis_area := TouchArea { 216 | colspan: 3; 217 | preferred-width: 550px; 218 | preferred-height: 150px; 219 | max-height: 150px; 220 | Button { 221 | height: parent.height; 222 | width: parent.width; 223 | text: analysis_running ? "Analysis in Progress..." : "Run Analysis"; 224 | enabled: !analysis_running; 225 | clicked => { 226 | analysis_status_text = "Running Analysis..."; 227 | run_analysis(); 228 | analysis_status_text = "Analysis ended."; 229 | } 230 | } 231 | } 232 | } 233 | 234 | Row { 235 | Text { 236 | preferred-width: 550px; 237 | text: analysis_status_text; 238 | color: Theme.foreground; 239 | colspan: 3; 240 | } 241 | } 242 | } 243 | } 244 | } 245 | 246 | file_tip := ToolTip { 247 | x: window_area.mouse_x + 20px; 248 | y: window_area.mouse_y + 20px; 249 | text: "Load a structure file"; 250 | user_is_hovering: file_area.has-hover; 251 | width: 135px; 252 | height: 25px; 253 | } 254 | 255 | r_max_tip := ToolTip { 256 | x: window_area.mouse_x + 20px; 257 | y: window_area.mouse_y + 20px; 258 | text: "Maximum radius to calculate Radial Distribution Functions (g(r), J(r), G(r)).\nThe recommended max radius is 20 Å."; 259 | user_is_hovering: r_max_area.has-hover; 260 | width: 260px; 261 | height: 55px; 262 | } 263 | 264 | r_bin_tip := ToolTip { 265 | x: window_area.mouse_x + 20px; 266 | y: window_area.mouse_y + 20px; 267 | text: "Bin width used to calculate the histograms for the Radial Distribution \nFunctions (g(r), J(r), G(r)).\nThe recommended bin is 0.02 Å."; 268 | user_is_hovering: r_bin_area.has-hover; 269 | width: 250px; 270 | height: 70px; 271 | } 272 | 273 | bond_tip := ToolTip { 274 | x: window_area.mouse_x - 235px; 275 | y: window_area.mouse_y + 20px; 276 | text: "Bond factor used to determine if two atoms are bonded. The bond parameter is calculated as follows:\nb = (max_bond_distance)/\n (sum_of_covalent_radii).\nThe default bond parameter is 1.2."; 277 | user_is_hovering: bond_area.has-hover; 278 | width: 225px; 279 | height: 100px; 280 | } 281 | 282 | theta_max_tip := ToolTip { 283 | x: window_area.mouse_x + 20px; 284 | y: window_area.mouse_y + 20px; 285 | text: "Maximum angle to calculate the Plane-Angle Distribution (F(θ)).\nThe recommended max angle is 180°."; 286 | user_is_hovering: theta_max_area.has-hover; 287 | width: 230px; 288 | height: 55px; 289 | } 290 | 291 | theta_bin_tip := ToolTip { 292 | x: window_area.mouse_x - 190px; 293 | y: window_area.mouse_y + 20px; 294 | text: "Bin width used to calculate the histograms for the Plane-Angle Distribution.\nThe recommended bin is 1.0°."; 295 | user_is_hovering: theta_bin_area.has-hover; 296 | width: 180px; 297 | height: 70px; 298 | } 299 | 300 | s_max_tip := ToolTip { 301 | x: window_area.mouse_x + 20px; 302 | y: window_area.mouse_y + 20px; 303 | text: "Maximum scattering vector (Q) to calculate the Structure Factor (S(Q)).\nThe recommended max scattering vector is 20.0 Å⁻¹"; 304 | user_is_hovering: s_max_area.has-hover; 305 | width: 245px; 306 | height: 70px; 307 | } 308 | 309 | s_bin_tip := ToolTip { 310 | x: window_area.mouse_x + 20px; 311 | y: window_area.mouse_y + 20px; 312 | text: "Bin width used to calculate the histograms for the Structure Factor.\nThe recommended bin is 0.02 Å⁻¹."; 313 | user_is_hovering: s_bin_area.has-hover; 314 | width: 225px; 315 | height: 55px; 316 | } 317 | 318 | r_int_tip := ToolTip { 319 | x: window_area.mouse_x - 215px; 320 | y: window_area.mouse_y + 20px; 321 | text: "Maximum radius for the Fourier Transform of the g(r) used to\ncalculate the Structure Factor.\nThe recommended max radius is 10.0 Å"; 322 | user_is_hovering: r_int_area.has-hover; 323 | width: 225px; 324 | height: 70px; 325 | } 326 | 327 | smooth_tip := ToolTip { 328 | x: window_area.mouse_x + 20px; 329 | y: window_area.mouse_y + 20px; 330 | text: "Enable the smoothing of the\ndistribution functions."; 331 | user_is_hovering: smooth_area.has-hover; 332 | width: 175px; 333 | height: 40px; 334 | } 335 | 336 | sigma_tip := ToolTip { 337 | x: window_area.mouse_x + 20px; 338 | y: window_area.mouse_y + 20px; 339 | text: "Sigma to be used for the smoothing\nof the distribution functions.\nThe recommended sigma is 0.01."; 340 | user_is_hovering: sigma_area.has-hover; 341 | width: 220px; 342 | height: 55px; 343 | } 344 | 345 | kernel_tip := ToolTip { 346 | x: window_area.mouse_x - 270px; 347 | y: window_area.mouse_y + 20px; 348 | text: "The kernel used by the kernel smoother.\nThe recommended kernel is the Gaussian kernel."; 349 | user_is_hovering: kernel_area.has-hover; 350 | width: 260px; 351 | height: 55px; 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installation Instructions 2 | ************************* 3 | 4 | Copyright (C) 1994-1996, 1999-2002, 2004-2016 Free Software 5 | Foundation, Inc. 6 | 7 | Copying and distribution of this file, with or without modification, 8 | are permitted in any medium without royalty provided the copyright 9 | notice and this notice are preserved. This file is offered as-is, 10 | without warranty of any kind. 11 | 12 | Basic Installation 13 | ================== 14 | 15 | Briefly, the shell command './configure && make && make install' 16 | should configure, build, and install this package. The following 17 | more-detailed instructions are generic; see the 'README' file for 18 | instructions specific to this package. Some packages provide this 19 | 'INSTALL' file but do not implement all of the features documented 20 | below. The lack of an optional feature in a given package is not 21 | necessarily a bug. More recommendations for GNU packages can be found 22 | in *note Makefile Conventions: (standards)Makefile Conventions. 23 | 24 | The 'configure' shell script attempts to guess correct values for 25 | various system-dependent variables used during compilation. It uses 26 | those values to create a 'Makefile' in each directory of the package. 27 | It may also create one or more '.h' files containing system-dependent 28 | definitions. Finally, it creates a shell script 'config.status' that 29 | you can run in the future to recreate the current configuration, and a 30 | file 'config.log' containing compiler output (useful mainly for 31 | debugging 'configure'). 32 | 33 | It can also use an optional file (typically called 'config.cache' and 34 | enabled with '--cache-file=config.cache' or simply '-C') that saves the 35 | results of its tests to speed up reconfiguring. Caching is disabled by 36 | default to prevent problems with accidental use of stale cache files. 37 | 38 | If you need to do unusual things to compile the package, please try 39 | to figure out how 'configure' could check whether to do them, and mail 40 | diffs or instructions to the address given in the 'README' so they can 41 | be considered for the next release. If you are using the cache, and at 42 | some point 'config.cache' contains results you don't want to keep, you 43 | may remove or edit it. 44 | 45 | The file 'configure.ac' (or 'configure.in') is used to create 46 | 'configure' by a program called 'autoconf'. You need 'configure.ac' if 47 | you want to change it or regenerate 'configure' using a newer version of 48 | 'autoconf'. 49 | 50 | The simplest way to compile this package is: 51 | 52 | 1. 'cd' to the directory containing the package's source code and type 53 | './configure' to configure the package for your system. 54 | 55 | Running 'configure' might take a while. While running, it prints 56 | some messages telling which features it is checking for. 57 | 58 | 2. Type 'make' to compile the package. 59 | 60 | 3. Optionally, type 'make check' to run any self-tests that come with 61 | the package, generally using the just-built uninstalled binaries. 62 | 63 | 4. Type 'make install' to install the programs and any data files and 64 | documentation. When installing into a prefix owned by root, it is 65 | recommended that the package be configured and built as a regular 66 | user, and only the 'make install' phase executed with root 67 | privileges. 68 | 69 | 5. Optionally, type 'make installcheck' to repeat any self-tests, but 70 | this time using the binaries in their final installed location. 71 | This target does not install anything. Running this target as a 72 | regular user, particularly if the prior 'make install' required 73 | root privileges, verifies that the installation completed 74 | correctly. 75 | 76 | 6. You can remove the program binaries and object files from the 77 | source code directory by typing 'make clean'. To also remove the 78 | files that 'configure' created (so you can compile the package for 79 | a different kind of computer), type 'make distclean'. There is 80 | also a 'make maintainer-clean' target, but that is intended mainly 81 | for the package's developers. If you use it, you may have to get 82 | all sorts of other programs in order to regenerate files that came 83 | with the distribution. 84 | 85 | 7. Often, you can also type 'make uninstall' to remove the installed 86 | files again. In practice, not all packages have tested that 87 | uninstallation works correctly, even though it is required by the 88 | GNU Coding Standards. 89 | 90 | 8. Some packages, particularly those that use Automake, provide 'make 91 | distcheck', which can by used by developers to test that all other 92 | targets like 'make install' and 'make uninstall' work correctly. 93 | This target is generally not run by end users. 94 | 95 | Compilers and Options 96 | ===================== 97 | 98 | Some systems require unusual options for compilation or linking that 99 | the 'configure' script does not know about. Run './configure --help' 100 | for details on some of the pertinent environment variables. 101 | 102 | You can give 'configure' initial values for configuration parameters 103 | by setting variables in the command line or in the environment. Here is 104 | an example: 105 | 106 | ./configure CC=c99 CFLAGS=-g LIBS=-lposix 107 | 108 | *Note Defining Variables::, for more details. 109 | 110 | Compiling For Multiple Architectures 111 | ==================================== 112 | 113 | You can compile the package for more than one kind of computer at the 114 | same time, by placing the object files for each architecture in their 115 | own directory. To do this, you can use GNU 'make'. 'cd' to the 116 | directory where you want the object files and executables to go and run 117 | the 'configure' script. 'configure' automatically checks for the source 118 | code in the directory that 'configure' is in and in '..'. This is known 119 | as a "VPATH" build. 120 | 121 | With a non-GNU 'make', it is safer to compile the package for one 122 | architecture at a time in the source code directory. After you have 123 | installed the package for one architecture, use 'make distclean' before 124 | reconfiguring for another architecture. 125 | 126 | On MacOS X 10.5 and later systems, you can create libraries and 127 | executables that work on multiple system types--known as "fat" or 128 | "universal" binaries--by specifying multiple '-arch' options to the 129 | compiler but only a single '-arch' option to the preprocessor. Like 130 | this: 131 | 132 | ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ 133 | CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ 134 | CPP="gcc -E" CXXCPP="g++ -E" 135 | 136 | This is not guaranteed to produce working output in all cases, you 137 | may have to build one architecture at a time and combine the results 138 | using the 'lipo' tool if you have problems. 139 | 140 | Installation Names 141 | ================== 142 | 143 | By default, 'make install' installs the package's commands under 144 | '/usr/local/bin', include files under '/usr/local/include', etc. You 145 | can specify an installation prefix other than '/usr/local' by giving 146 | 'configure' the option '--prefix=PREFIX', where PREFIX must be an 147 | absolute file name. 148 | 149 | You can specify separate installation prefixes for 150 | architecture-specific files and architecture-independent files. If you 151 | pass the option '--exec-prefix=PREFIX' to 'configure', the package uses 152 | PREFIX as the prefix for installing programs and libraries. 153 | Documentation and other data files still use the regular prefix. 154 | 155 | In addition, if you use an unusual directory layout you can give 156 | options like '--bindir=DIR' to specify different values for particular 157 | kinds of files. Run 'configure --help' for a list of the directories 158 | you can set and what kinds of files go in them. In general, the default 159 | for these options is expressed in terms of '${prefix}', so that 160 | specifying just '--prefix' will affect all of the other directory 161 | specifications that were not explicitly provided. 162 | 163 | The most portable way to affect installation locations is to pass the 164 | correct locations to 'configure'; however, many packages provide one or 165 | both of the following shortcuts of passing variable assignments to the 166 | 'make install' command line to change installation locations without 167 | having to reconfigure or recompile. 168 | 169 | The first method involves providing an override variable for each 170 | affected directory. For example, 'make install 171 | prefix=/alternate/directory' will choose an alternate location for all 172 | directory configuration variables that were expressed in terms of 173 | '${prefix}'. Any directories that were specified during 'configure', 174 | but not in terms of '${prefix}', must each be overridden at install time 175 | for the entire installation to be relocated. The approach of makefile 176 | variable overrides for each directory variable is required by the GNU 177 | Coding Standards, and ideally causes no recompilation. However, some 178 | platforms have known limitations with the semantics of shared libraries 179 | that end up requiring recompilation when using this method, particularly 180 | noticeable in packages that use GNU Libtool. 181 | 182 | The second method involves providing the 'DESTDIR' variable. For 183 | example, 'make install DESTDIR=/alternate/directory' will prepend 184 | '/alternate/directory' before all installation names. The approach of 185 | 'DESTDIR' overrides is not required by the GNU Coding Standards, and 186 | does not work on platforms that have drive letters. On the other hand, 187 | it does better at avoiding recompilation issues, and works well even 188 | when some directory options were not specified in terms of '${prefix}' 189 | at 'configure' time. 190 | 191 | Optional Features 192 | ================= 193 | 194 | If the package supports it, you can cause programs to be installed 195 | with an extra prefix or suffix on their names by giving 'configure' the 196 | option '--program-prefix=PREFIX' or '--program-suffix=SUFFIX'. 197 | 198 | Some packages pay attention to '--enable-FEATURE' options to 199 | 'configure', where FEATURE indicates an optional part of the package. 200 | They may also pay attention to '--with-PACKAGE' options, where PACKAGE 201 | is something like 'gnu-as' or 'x' (for the X Window System). The 202 | 'README' should mention any '--enable-' and '--with-' options that the 203 | package recognizes. 204 | 205 | For packages that use the X Window System, 'configure' can usually 206 | find the X include and library files automatically, but if it doesn't, 207 | you can use the 'configure' options '--x-includes=DIR' and 208 | '--x-libraries=DIR' to specify their locations. 209 | 210 | Some packages offer the ability to configure how verbose the 211 | execution of 'make' will be. For these packages, running './configure 212 | --enable-silent-rules' sets the default to minimal output, which can be 213 | overridden with 'make V=1'; while running './configure 214 | --disable-silent-rules' sets the default to verbose, which can be 215 | overridden with 'make V=0'. 216 | 217 | Particular systems 218 | ================== 219 | 220 | On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC 221 | is not installed, it is recommended to use the following options in 222 | order to use an ANSI C compiler: 223 | 224 | ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" 225 | 226 | and if that doesn't work, install pre-built binaries of GCC for HP-UX. 227 | 228 | HP-UX 'make' updates targets which have the same time stamps as their 229 | prerequisites, which makes it generally unusable when shipped generated 230 | files such as 'configure' are involved. Use GNU 'make' instead. 231 | 232 | On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot 233 | parse its '' header file. The option '-nodtk' can be used as a 234 | workaround. If GNU CC is not installed, it is therefore recommended to 235 | try 236 | 237 | ./configure CC="cc" 238 | 239 | and if that doesn't work, try 240 | 241 | ./configure CC="cc -nodtk" 242 | 243 | On Solaris, don't put '/usr/ucb' early in your 'PATH'. This 244 | directory contains several dysfunctional programs; working variants of 245 | these programs are available in '/usr/bin'. So, if you need '/usr/ucb' 246 | in your 'PATH', put it _after_ '/usr/bin'. 247 | 248 | On Haiku, software installed for all users goes in '/boot/common', 249 | not '/usr/local'. It is recommended to use the following options: 250 | 251 | ./configure --prefix=/boot/common 252 | 253 | Specifying the System Type 254 | ========================== 255 | 256 | There may be some features 'configure' cannot figure out 257 | automatically, but needs to determine by the type of machine the package 258 | will run on. Usually, assuming the package is built to be run on the 259 | _same_ architectures, 'configure' can figure that out, but if it prints 260 | a message saying it cannot guess the machine type, give it the 261 | '--build=TYPE' option. TYPE can either be a short name for the system 262 | type, such as 'sun4', or a canonical name which has the form: 263 | 264 | CPU-COMPANY-SYSTEM 265 | 266 | where SYSTEM can have one of these forms: 267 | 268 | OS 269 | KERNEL-OS 270 | 271 | See the file 'config.sub' for the possible values of each field. If 272 | 'config.sub' isn't included in this package, then this package doesn't 273 | need to know the machine type. 274 | 275 | If you are _building_ compiler tools for cross-compiling, you should 276 | use the option '--target=TYPE' to select the type of system they will 277 | produce code for. 278 | 279 | If you want to _use_ a cross compiler, that generates code for a 280 | platform different from the build platform, you should specify the 281 | "host" platform (i.e., that on which the generated programs will 282 | eventually be run) with '--host=TYPE'. 283 | 284 | Sharing Defaults 285 | ================ 286 | 287 | If you want to set default values for 'configure' scripts to share, 288 | you can create a site shell script called 'config.site' that gives 289 | default values for variables like 'CC', 'cache_file', and 'prefix'. 290 | 'configure' looks for 'PREFIX/share/config.site' if it exists, then 291 | 'PREFIX/etc/config.site' if it exists. Or, you can set the 292 | 'CONFIG_SITE' environment variable to the location of the site script. 293 | A warning: not all 'configure' scripts look for a site script. 294 | 295 | Defining Variables 296 | ================== 297 | 298 | Variables not defined in a site shell script can be set in the 299 | environment passed to 'configure'. However, some packages may run 300 | configure again during the build, and the customized values of these 301 | variables may be lost. In order to avoid this problem, you should set 302 | them in the 'configure' command line, using 'VAR=value'. For example: 303 | 304 | ./configure CC=/usr/local2/bin/gcc 305 | 306 | causes the specified 'gcc' to be used as the C compiler (unless it is 307 | overridden in the site shell script). 308 | 309 | Unfortunately, this technique does not work for 'CONFIG_SHELL' due to an 310 | Autoconf limitation. Until the limitation is lifted, you can use this 311 | workaround: 312 | 313 | CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash 314 | 315 | 'configure' Invocation 316 | ====================== 317 | 318 | 'configure' recognizes the following options to control how it 319 | operates. 320 | 321 | '--help' 322 | '-h' 323 | Print a summary of all of the options to 'configure', and exit. 324 | 325 | '--help=short' 326 | '--help=recursive' 327 | Print a summary of the options unique to this package's 328 | 'configure', and exit. The 'short' variant lists options used only 329 | in the top level, while the 'recursive' variant lists options also 330 | present in any nested packages. 331 | 332 | '--version' 333 | '-V' 334 | Print the version of Autoconf used to generate the 'configure' 335 | script, and exit. 336 | 337 | '--cache-file=FILE' 338 | Enable the cache: use and save the results of the tests in FILE, 339 | traditionally 'config.cache'. FILE defaults to '/dev/null' to 340 | disable caching. 341 | 342 | '--config-cache' 343 | '-C' 344 | Alias for '--cache-file=config.cache'. 345 | 346 | '--quiet' 347 | '--silent' 348 | '-q' 349 | Do not print messages saying which checks are being made. To 350 | suppress all normal output, redirect it to '/dev/null' (any error 351 | messages will still be shown). 352 | 353 | '--srcdir=DIR' 354 | Look for the package's source code in directory DIR. Usually 355 | 'configure' can determine that directory automatically. 356 | 357 | '--prefix=DIR' 358 | Use DIR as the installation prefix. *note Installation Names:: for 359 | more details, including other options available for fine-tuning the 360 | installation locations. 361 | 362 | '--no-create' 363 | '-n' 364 | Run the configure checks, but stop before creating any output 365 | files. 366 | 367 | 'configure' also accepts some other, not widely useful, options. Run 368 | 'configure --help' for more details. 369 | --------------------------------------------------------------------------------