{"id":21765179,"url":"https://github.com/yesint/molar","last_synced_at":"2025-04-13T11:09:53.211Z","repository":{"id":161993920,"uuid":"579386505","full_name":"yesint/molar","owner":"yesint","description":"Repository for molar crate and its dependencies","archived":false,"fork":false,"pushed_at":"2025-04-13T07:23:36.000Z","size":88556,"stargazers_count":25,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-13T11:09:36.740Z","etag":null,"topics":["molecular-dynamics","molecular-dynamics-simulation","molecular-modeling","trajectory-analysis"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/molar","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"artistic-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yesint.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-17T14:22:47.000Z","updated_at":"2025-04-13T07:23:39.000Z","dependencies_parsed_at":"2024-04-20T16:46:29.712Z","dependency_job_id":"be07247b-6166-4fe5-b789-b8fe6cec76ac","html_url":"https://github.com/yesint/molar","commit_stats":{"total_commits":174,"total_committers":1,"mean_commits":174.0,"dds":0.0,"last_synced_commit":"9b7575cbe1d4ea185a3d72cdf61d823b8d5a149c"},"previous_names":["yesint/pteros_rust"],"tags_count":45,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesint%2Fmolar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesint%2Fmolar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesint%2Fmolar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yesint%2Fmolar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yesint","download_url":"https://codeload.github.com/yesint/molar/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248703199,"owners_count":21148118,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["molecular-dynamics","molecular-dynamics-simulation","molecular-modeling","trajectory-analysis"],"created_at":"2024-11-26T13:10:59.565Z","updated_at":"2025-04-13T11:09:53.198Z","avatar_url":"https://github.com/yesint.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"**MolAR** is a **Mol**ecular **A**nalysis and modeling library for **R**ust.\n\n# Table of contents\n- [What is MolAR?](#what-is-molar)\n- [Features](#features)\n- [Current status](#current-status)\n- [Design and performance](#design-and-performance)\n- [Installation](#installation)\n- [Tutorial](#tutorial)\n\n# What is molar?\nMolAR is a library for molecular modeling and analysis written in Rust with an emphasis on memory safety and performance. \n\nMolar is designed to simplify the analysis of molecular dynamics trajectories and to implement new analysis algorithms. Molar is intended to provide facilities, which are routinely used in all molecular analysis programs, namely input/output of popular file formats, powerful and flexible atom selections, geometry transformations, RMSD fitting and alignment, etc.\n\nMolAR is a logical successor of [Pteros](https://github.com/yesint/pteros) molecular modeling library, which is written in C++ and become hard to develop and maintain due to all C++ idiosyncrasies.\n\n# Features\n* Reading and writing PDB, GRO, XYZ, XTC, TPR files\n    * Recognizes any VMD molfile plugins. \n    * Reading and writing Gromacs XTC format with random access.\n    * Reading Gromacs TPR files if Gromacs is installed.\n* Selections using the syntax similar to VMD and Pteros.\n    * Memory-safe selections for serial and parallel analysis tasks.\n    * Powerful subselections and selection splitting.\n* SASA calculations with the fastest PowerSasa method.\n* RMSD fitting and alignment.\n* Basic algorithm (center of mass, center of geometry, etc.).\n* Seamless PBC treatment.\n\n# Design and Performance\nPlease refer to the [MolAR paper](https://onlinelibrary.wiley.com/doi/10.1002/jcc.27536).\n\n# Current status\nMolar is close to be feature complete and usable in useful projects. Documentation is still rudimentary.\n\n# Installation\nMolar requires Rust 1.80 or above and a C/C++ compiler for compiling third-party libraries. Any sufficiently modern gcc or clang compiler should work.\n\nTo add MolAR to your Rust project just use `cargo add molar`.\n\n## Linking to Gromacs\nIn order to be able to read Gromacs TPR files MolAR should link to locally installed Gromacs. Unfortunately, modern versions of Gromacs do not expose all needed functionality in the public API, so MolAR has to hack into the internals and thus requires an access to the whole Gromacs source and build directories. This means that you have to _compile_ Gromacs on your local machine from source.\n\nIn order to link with Gromacs create a `.cargo/config.toml` file in the root directory of your project with the following content:\n```toml\n[env]\n# Location of Gromacs source tree\nGROMACS_SOURCE_DIR = \"\u003cpath-to-gromacs-source\u003e/gromacs-2023\"\n# Location of Gromacs *build* directory (for generated headers)\nGROMACS_BUILD_DIR = \"\u003cpath-to-gromacs-source\u003e/gromacs-2023\u003e/build\"\n# Location of installed gromacs libraries (where libgromacs.so is located)\nGROMACS_LIB_DIR = \"\u003cpath-to-installed-gromacs\u003e/lib64\"\n```\nYou may use a template: `mv config.toml.template config.toml`.\n\n# Tutorial\nWe will write an example program that reads a file of some molecular system containing TIP3P water molecules, convert all water to TIP4P and saves this as a new file. TIP3P water has 3 particles (oxygen and two hydrogens), while TIP4P [has 4]((http://www.sklogwiki.org/SklogWiki/index.php/TIP4P/2005_model_of_water)) (oxygen, two hydrogens and a dummy particle). Our goal is to add these dummy particles to each water molecule.\n\n## Preparing the stage\nFirst, let's create a new Rust project called `tip3to4`: \n```shell\ncargo new tip3to4\n```\n\nThen, let's add dependencies: the MolAR itself and anyhow crate for easy error reporting:\n```shell\ncargo add molar anyhow\n```\n\nIn `src/main.rs` add needed boilerplate:\n```rust,ignore\n// For processing command line arguments\nuse std::env;\n// Import all basic things from molar\nuse molar::prelude::*;\n// For error handling\nuse anyhow::Result;\n\nfn main() -\u003e Result\u003c()\u003e {\n    // Get the command line arguments\n    let args: Vec\u003cString\u003e = env::args().collect();\n\n    // Here our program is going to be written\n\n    // Report successful completion of the program\n    Ok(())\n}\n```\n\nNow we can start writing our program.\n\n## Reading an input file\nThe simples way of loading the molecular system in MolAR is to use a `Source` - an object that holds a `Topology` and `State` of the system and is used to create atom selections for manipulating this data:\n\n```rust,ignore\n// Load the source file from the first command line argument\nlet src = Source::serial_from_file(\u0026args[0])?;\n```\n\nUnlike other molecular analysis libraries, there are four kinds of sources and atom selections in MolAR: `serial`, `serial builder`, `parallel mutable` and `parallel immutable`. This is required to enforce memory safety and to guarantee the absense of data races in paralell programs. For now we will just work with the simplest `serial` kind of sources and selections, which behave in the most intuitive way similar to what you see in other analysis libraries. `Source::serial_from_file()` creates such serial `Source` by reading a file specified in the first command line argument.\n\nThe file type (PDB, GRO, etc) is automatically recognized by its extention.\n\nIf reading the file fails for whatever reason the `?` operator will return an error, which will be nicely printed by `anyhow` crate.\n\n## Making selections\nNow we need to select all waters that are going to be converted to TIP4. We also need to select all non-water part of the system to keep it as is.\n\n```rust,ignore\nlet water = src.select(\"resname TIP3\")?;\nlet non_water = src.select(\"not resname TIP3\")?;\n```\n\nSelections are created with the syntax that is very similar to one used in VMD Pteros and Gromacs. Here we select water and non-water by residue name.\n\nIn MolAR empty selections are not permitted, so if no atoms are selected (or if anything else goes wrong) the error will be reported.\n\n## Looping over indiviudual water molecules\nWe selected all water molecules as a single selection but we need to loop over individual water molecules to add an additional dummy particle to each of them. In order to do this we are splitting a selection to fragments by the residue index:\n\n```rust,ignore\n// Go over water molecules one by one                   \nfor mol in water.split_resindex_into_iter() {\n    // Do something with mol\n}\n```\n\nThe method `split_resindex_into_iter()` returns a Rust iterator, which produces contigous selections containig distinct residue index each. There are many other ways of splitting selections into parts using arbitrary logic in MolAR, but this simplest one is what we need now. \n\n## Working with coordinates\nNow we need to get the coordinates of atoms for current water molecules and compute a position of the dummy atom.\n\n```rust,ignore\n// Go over water molecules one by one                   \nfor mol in water.split_resindex_into_iter() {\n    // TIP3 is arranged as O-\u003eH-\u003eH\n    // so atom 0 is O, atoms 1 and 2 are H\n    // Get cooridnates\n    let o_pos = mol.nth_pos(0).unwrap();\n    let h1_pos = mol.nth_pos(1).unwrap();\n    let h2_pos = mol.nth_pos(2).unwrap();\n    // Get center of masses of H\n    let hc = 0.5*(h1_pos.coords + h2_pos.coords);\n    // Unit vector from o to hc\n    let v = (hc-o_pos.coords).normalize();\t\n    // Position of the M dummy particle in TIP4\n    let m_pos = o_pos + v*0.01546;\n    // Dummy atom M\n    let m_at = Atom {   \n        resname: \"TIP4\".into(),\n        name: \"M\".into(),\n        ..mol.first_particle().atom.clone()\n    };\n    println!(\"{:?} {:?}\",m_at,m_pos);\n}\n```\n\nFirst, we are getting the coordinates of oxigen and two hydrogens. `nth_pos(n)` returns the position of n-th atom in selection. Since n may potentially be out of range, it returns an `Option\u003c\u0026Pos\u003e`. We are sure that there are just 3 atoms in water molecule, so we just unwrapping an option.\n\nThen we are computing the position of the dummy atom, which is on the bissection of H-O-H angle at the distance of 0.01546 from the oxygen.\n\nFinally, we are constructing a new `Atom` with name 'M', residue name 'TIP4' and all other fields (resid,resindex, etc) the same as in our water molecule.\n\nWe are printing our new dummy atom and its position just to be sure that everything works as intended.\n\n## Constructing output system\nAll this is fine, but we still have no system to write our converted water molecules to. Let's fix this and modify the beginning of our main funtion like this:\n\n```rust,ignore\n// Load the source file from the first command line argument\nlet src = Source::serial_from_file(\u0026args[0])?;\n\n// Make empty output system\nlet out = Source::empty_builder();\n```\n\nHere we are creating new empty `Source` of kind `builder`. This means that we will be able to add and delete the atoms to this source. Conventional `serial` source can access and alter existing atoms, but can't add or delete them. Such a distinction is dictated by performance and memory safety reasons - `builder` sources and selections require additional range checks, which make them a tiny bit slower, so it only makes sense to use them when you actually need to add or delete the atoms.\n\nThe first thing that we add to out new empty system is all non-water atoms:\n```rust,ignore\n// Add non-water atoms to the output\nout.append(\u0026non_water);\n```\n\nNow, at the end of our loop over water molecules, we can add new dummy atoms properly to the new system:\n```rust,ignore\n// Add new converted water molecule\n// We assume that the dummy is the last atom.\nout.append_atoms(\n    mol.iter_atoms().cloned().chain(std::iter::once(m_at)),\n    mol.iter_pos().cloned().chain(std::iter::once(m_pos)),\n);\n```\n\nThis code snippet may look a bit puzzling for non-rustaceans, so let's go through it.\n- `append_atoms()` method accepts two iterators: the first yielding atoms and the second yielding their corresponding coordinates. \n- Our selected water molecule `mol` has methods `iter_atoms()` and `iter_pos()` for getting these iterators. \n- `cloned()` adaptor is used to get copies of existing atoms and coordinates instead of references to them. \n- We add our new dummy atom at the end of water molecule by \"chaining\" another iterator at the end of the current one. `std::iter::once(value)` returns an iterator yielding a single value and allows us to add newly constructed `m_at` and `m_pos` to the corrsponding iterators.\n\n## Writing the output file\nOut output system is now fully constructed but it still lacks an important element - the periodic box description. Most molecular systems originating from MD are periodic and the information about the periodic box has to be copied to our newly constructed system:\n\n```rust,ignore\n// Transfer the box from original file\nout.set_box_from(\u0026src);\n```\n\nHere we provide a reference to the input system, so the box is cloned from it to the output system.\n\nFinally we are ready to write the output file:\n\n```rust,ignore\n// Write out new system\nout.save(\u0026args[1])?;\n```\n\nAgain, file format will be determined by extension. The file name is provided by the second command line argument.\n\n## The final result\nThe complete program looks like this:\n```rust,no_run\n// For processing command line arguments\nuse std::env;\n// Import all baic things from molar\nuse molar::prelude::*;\n// For error handling\nuse anyhow::Result;\n\nfn main() -\u003e Result\u003c()\u003e {\n    // Get the command line arguments\n    let args: Vec\u003cString\u003e = env::args().collect();\n\n    // Load the source file from the first command line argument\n    let src = Source::serial_from_file(\u0026args[0])?;\n\n    // Make empty output system\n    let out = Source::empty_builder();\n\n    let water = src.select(\"resname TIP3\")?;\n    let non_water = src.select(\"not resname TIP3\")?;\n\n    // Add non-water atoms to the output\n    out.append(\u0026non_water);\n\n    // Go over water molecules one by one                   \n    for mol in water.split_resindex_into_iter() {\n        // TIP3 is arranged as O-\u003eH-\u003eH\n        // so atom 0 is O, atoms 1 and 2 are H\n\t    // Get cooridnates\n        let o_pos = mol.nth_pos(0).unwrap();\n        let h1_pos = mol.nth_pos(1).unwrap();\n        let h2_pos = mol.nth_pos(2).unwrap();\n\t    // Get center of masses of H\n\t    let hc = 0.5*(h1_pos.coords + h2_pos.coords);\n\t    // Unit vector from o to hc\n\t    let v = (hc-o_pos.coords).normalize();\t\n\t    // Position of the M dummy particle in TIP4\n\t    let m_pos = o_pos + v*0.01546;\n        // Dummy atom M\n        let m_at = Atom {   \n            resname: \"TIP4\".into(),\n            name: \"M\".into(),\n            ..mol.first_particle().atom.clone()\n        };\n\n        // Add new converted water molecule\n        // We assume that the dummy is the last atom.\n        out.append_atoms(\n            mol.iter_atoms().cloned().chain(std::iter::once(m_at)),\n            mol.iter_pos().cloned().chain(std::iter::once(m_pos)),\n        );\n\n    }\n\n    // Transfer the box\n    out.set_box_from(\u0026src);\n\n    // Write out new system\n    out.save(\u0026args[1])?;\n\n    // Report successful completion of the program\n    Ok(())\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyesint%2Fmolar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyesint%2Fmolar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyesint%2Fmolar/lists"}