├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── docs ├── Code_Compatibility.rst ├── Contributing.rst ├── Installation.rst ├── Makefile ├── Tips.rst ├── Tutorials.rst ├── conf.py ├── distance.png ├── graphic.png ├── index.rst ├── lammps_tutorial_link.ipynb ├── logo.png ├── logo_v1.png ├── minimal_tutorial_link.ipynb ├── non_3d_tutorial_link.ipynb ├── pdyna.analysis.rst ├── pdyna.basis.rst ├── pdyna.core.rst ├── pdyna.io.rst ├── pdyna.rst ├── pdyna.structural.rst ├── requirements.txt ├── system_tutorial_link.ipynb └── tutorial.ipynb ├── examples ├── 2D_perovskite │ ├── 2d.ipynb │ ├── connectivity_2D.png │ └── trajetory_2D.extxyz ├── lammps │ ├── lammps.ipynb │ └── lammps_example_short.out ├── minimal │ ├── INCAR_mapi_example │ ├── POSCAR_mapi_example │ ├── XDATCAR_mapi_example │ └── minimal.ipynb └── system │ ├── define_system.ipynb │ ├── init.extxyz │ ├── system_schematic.png │ └── trajetory_1D.extxyz ├── pdyna ├── __init__.py ├── _version.py ├── analysis.py ├── basis │ ├── octahedron_basis.json │ ├── octahedron_basis_analytical.json │ └── octahedron_basis_full.json ├── core.py ├── io.py └── structural.py ├── pyproject.toml └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # gitignore comments have to be on their own line 2 | 3 | # Distribution/packagaing shit 4 | *egg-info/ 5 | 6 | # No PyPi stuff 7 | dist/ 8 | 9 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for detail 4 | 5 | # Required 6 | version: 2 7 | 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.8" 12 | 13 | # Build from the docs/ directory with Sphinx 14 | sphinx: 15 | configuration: docs/conf.py 16 | 17 | # Explicitly set the version of Python and its requirements 18 | python: 19 | install: 20 | - requirements: docs/requirements.txt 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Xia Liang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![Documentation Status](https://readthedocs.org/projects/pdyna/badge/?version=latest&style=flat)](https://pdyna.readthedocs.io/en/latest/) 5 | [![DOI](https://zenodo.org/badge/568782890.svg)](https://zenodo.org/badge/latestdoi/568782890) 6 | 7 | 8 | 9 | **P**erovskite **Dyn**amics **A**nalysis (**PDynA**) is a Python package for analysis of perovskite structural dynamics. 10 | 11 | The Python documentation of **PDynA** can be found at https://pdyna.readthedocs.io/en/latest/. 12 | 13 | Code features 14 | -------- 15 | - The input to the code is molecular dynamics (MD) trajectories, currently readable formats are VASP-XDATCAR, XYZ, EXTXYZ, PDB (proteindatabank), ASE-Trajectory and LAMMPS dump files. The core class of PDynA is the `Trajectory` class. 16 | 17 | - The structure recognition functions will automatically detect the constituent octahedral network and organic A-site molecules, and process the analysis. 18 | 19 | - The output is a selected set of the following properties: (pseudo-cubic) lattice parameter, octahedral distortion and tilting, time-averaged structure, A-site molecular orientation, A-site spatial displacement, radial distribution functions. 20 | 21 | - The octahedral distortion and tilting calculation is the core feature of this package, which can quantitatively examine the dynamic behaviour of perovskite in terms of how octahedra tilt and distort, as well as the spatial correlation of these properties (for example equivalent to Glazer notation in the 3C corner-sharing polytype). 22 | 23 | - The octahedral distortion and tilting calculation can be accelerated with parallelization by joblib, this can be tuned with the input parameter `multi_thread`. 24 | 25 |

26 | 27 |

28 | 29 | List of modules 30 | ------- 31 | 32 | * **pdyna** library containing: 33 | * **core.py**: Contains the dataclass Trajectory and Frame as well as their related functions. 34 | * **structural.py**: Handles structure recognition and property calculations. 35 | * **analysis.py**: A collection of tools for computing and visualizing the output. 36 | * **io.py**: The IO to input data files. 37 | 38 | 39 | Requirements 40 | ------------ 41 | 42 | The main language is Python 3 and has been tested using Python 3.8+, with the following dependencies: 43 | - Numpy 44 | - Matplotlib 45 | - Pymatgen 46 | - Scipy 47 | - ASE 48 | 49 | Installation 50 | ------------ 51 | **1. From GitHub** 52 | 53 | **PDynA** can be installed with the following commands: 54 | 55 | Clone the repository (or download manually) 56 | 57 | git clone https://github.com/WMD-group/PDynA.git 58 | 59 | cd to PDynA directory with the `setup.py` file 60 | 61 | cd pdyna 62 | 63 | Install the package with pip 64 | 65 | pip install . 66 | 67 | **2. From PyPI** 68 | 69 | Alternatively, **PDynA** can be installed directly from PyPI, with: 70 | 71 | pip install pdyna 72 | 73 | Pre-definition of the structure 74 | ------------ 75 | 76 | To start using the code, the user must define the B and X site elements in the BX6 octahedra. This can be done by defining the B and X sites species and their relative distances with the additional entry `system_overwrite` in the `dynamics`, for example, 77 | 78 | traj.dynamics(..., system_overwrite=user_system)` 79 | 80 | The input `user_system` is a dict with four entries: 81 | 82 | {'B-sites': ['Sn'], 'X-sites': ['I','Br'], 'fpg_val_BB': [[a,b], [c,d]], 'fpg_val_BX': [[a,b], [c,d]]} 83 | 84 | The four numbers in B-B (`fpg_val_BB`) and B-X (`fpg_val_BX`) connectivity are: 85 | 86 | c: NN1 distance of the connected pair 87 | d: NN2 distance of the connected pair 88 | a: lower bound of a range of distance that covers both and only NN1 and NN2 89 | b: upper bound of a range of distance that covers both and only NN1 and NN2 90 | 91 | If you are not sure about the relative distances in your system, you can run the `system_test` function with one of your trajectories by calling: 92 | 93 | traj = Trajectory(filetype,(file_path, MDtup)) 94 | traj.system_test(B_sites=['Sn'],X_sites=['I','Br']) # with Sn(I/Br) system as an example 95 | 96 | This will give you two plots as follows: 97 | 98 |

99 | 100 |

101 | 102 | An alternative way is to directly define the same quantities at the beginning of the `Trajectory` class if you are working with only one system throughout. 103 | 104 | _Xsite_species = ['Cl','Br','I'] 105 | _Bsite_species = ['Pb'] 106 | _fpg_val_BB = [[5,13.5], [8.2,12]] # the values for above plot 107 | _fpg_val_BX = [[1,8], [3,6.8]] # the values for above plot 108 | 109 | 110 | Usage 111 | ------------ 112 | 113 | Two molecular dynamics trajectories are given in the `examples` folder. 114 | 115 | We encourage users to run orthogonal (the vectors connecting adjacent B-atoms are parallel to the principal axes) perovskite structures with corner-sharing connectivity to get the best performance of structure matching, when possible. 116 | 117 | **1. VASP-XDATCAR trajectory** 118 | 119 | The minimal example is based on a VASP-XDATCAR format trajectory, containing about 100 atoms. 120 | 121 | Copy all the files under `examples/minimal` to your working directory and run the script `pdyna_example.py`. 122 | 123 | 124 | **2. LAMMPS trajectory** 125 | 126 | The second example is a larger LAMMPS trajectory (about 20,000 atoms), covering more functions in **PDynA**. 127 | 128 | Copy all the files under `examples/lammps` to your working directory, unzip `lammps_example_mapbbr3.out.gz` and run the script `example_lammps.py`. 129 | 130 | For your own LAMMPS trajectory, please use a custom dump style with this command: `dump dump_name all custom dump_frequency ./dump.out id element x y z` 131 | 132 | 133 | **3. XYZ/PDB/ASE trajectory** 134 | 135 | Reading of these file types can be done similarly by running `traj = Trajectory(filetype,(file_path, MDtup))`, where `filetype` is one of "xyz", "pdb" or "ase-traj", the MD settings `MDtup = (Ti, Tf, timestep, nblock)` (`timestep` is MD time step in femtosecond, `nblock` is the frequency of frame saving). 136 | 137 | 138 | **4. Parameters explained** 139 | 140 | A full list of all the parameters is shown in the `examples/full.py`. 141 | 142 | 143 | Computed Properties 144 | ------------ 145 | 146 | The computed dynamic properties are all stored in the Trajectory object and can all be accessed easily by calling `traj.Tilting`, `traj.Tilting_Corr`, `traj.Distortion`, etc, which are octahedral tilting, octahedral tilting NN1 correlations (effective Glazor notation), octahedral distortions, respectively. The dimension of these arrays are (N_frames,N_octahedra,property_dimension). 147 | 148 | 149 | Citation 150 | ------------ 151 | ["Structural dynamics descriptors for metal halide perovskites" Journal of Physical Chemistry C (2023)](https://pubs.acs.org/doi/full/10.1021/acs.jpcc.3c03377) 152 | 153 | ``` 154 | @article{pdyna, 155 | title={Structural dynamics descriptors for metal halide perovskites}, 156 | author={Liang, Xia and Klarbring, Johan and Baldwin, William J and Li, Zhenzhu and Cs{\'a}nyi, G{\'a}bor and Walsh, Aron}, 157 | journal={J. Phys. Chem. C}, 158 | volume={127}, 159 | number={38}, 160 | pages={19141--19151}, 161 | year={2023} 162 | } 163 | ``` 164 | 165 | Used in 166 | ------------ 167 | ["Phase transitions, dielectric response, and nonlinear optical properties of aziridinium lead halide perovskites" Chemistry of Materials (2023)](https://pubs.acs.org/doi/10.1021/acs.chemmater.3c02200) 168 | 169 | License and attribution 170 | ----------------------- 171 | 172 | Python code and original data tables are licensed under the MIT License. 173 | 174 | 175 | Development notes 176 | ----------------- 177 | 178 | ### Bugs, features and questions 179 | Please use the [Issue Tracker](https://github.com/WMD-group/PDynA/issues) to report bugs or request features in the first instance. For other queries about any aspect of the code, please contact Xia Liang by e-mail: xia.liang16[at]imperial.ac.uk. 180 | 181 | ### Lead developer 182 | - Xia Liang (Department of Materials, Imperial College London) 183 | -------------------------------------------------------------------------------- /docs/Code_Compatibility.rst: -------------------------------------------------------------------------------- 1 | Code Compatibility 2 | ======================== 3 | 4 | :code:`PDynA` is built with mostly internalized functions and attributes, and only uses :code:`pymatgen` and 5 | :code:`ASE` for their structure objects as output for user to save and use in other platforms. 6 | This is the same for the other dependencies. If you are receiving dependency-related errors when using 7 | :code:`PDynA`, please raise an Issue in GitHub. 8 | -------------------------------------------------------------------------------- /docs/Contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ======================================= 3 | 4 | Bugs reports, feature requests and questions 5 | --------------------------------------------- 6 | 7 | Please use the `Issue Tracker `_ to report bugs or 8 | request new features. Contributions to extend this package are very welcome! 9 | -------------------------------------------------------------------------------- /docs/Installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | Installation 4 | ============== 5 | 6 | Download and Install 7 | -------------------- 8 | 9 | ``PDynA`` can be installed with the following commands: 10 | 11 | .. code-block:: bash 12 | 13 | git clone https://github.com/WMD-group/PDynA.git # Clone the repository (or download manually) 14 | cd pdyna # cd to PDynA directory with the setup.py file 15 | pip install . # Install the package with pip 16 | 17 | Note that if you already have all the dependencies installed in your environment (namely ``numpy``, ``scipy``, 18 | ``pymatgen``, ``matplotlib``, and ``ASE``), you can also install ``PDynA`` without updating these dependencies 19 | as it only requires their fundamental functionality. For example, instead do: 20 | 21 | .. code-block:: bash 22 | 23 | pip install --no-deps . # Install the package with pip, and without changing its dependency packages 24 | 25 | Install from PyPI 26 | -------------------- 27 | 28 | ``PDynA`` can be installed directly from PyPI: 29 | 30 | .. code-block:: bash 31 | 32 | pip install pdyna 33 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/Tips.rst: -------------------------------------------------------------------------------- 1 | Tips & Tricks 2 | ============================ 3 | 4 | -------------------------------------------------------------------------------- /docs/Tutorials.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | Tutorials 4 | =========================================================== 5 | 6 | The generalized workflow using ``PDynA`` is exemplified through these tutorials: 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | minimal_tutorial_link 12 | lammps_tutorial_link 13 | system_tutorial_link 14 | non_3d_tutorial_link 15 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | from recommonmark.transform import AutoStructify 17 | 18 | sys.path.insert(0, os.path.abspath('..')) 19 | 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = 'PDynA' 24 | author = 'Xia Liang' 25 | 26 | # The full version, including alpha/beta/rc tags 27 | release = '1.1.0' 28 | 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [ 36 | 'sphinx.ext.autodoc', # for automatic documentation 37 | 'sphinx.ext.coverage', 38 | 'sphinx.ext.napoleon', 39 | 'sphinx.ext.mathjax', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.autosectionlabel', 42 | 'sphinx_click', 43 | 'sphinx_design', 44 | 'myst_nb', # for jupyter notebooks 45 | 'nbsphinx', 46 | ] 47 | 48 | # Make sure the target is unique 49 | autosectionlabel_prefix_document = True 50 | 51 | source_suffix = { 52 | '.rst': 'restructuredtext', 53 | '.ipynb': 'myst-nb', 54 | } 55 | 56 | # Add any paths that contain templates here, relative to this directory. 57 | templates_path = ['_templates'] 58 | 59 | # List of patterns, relative to source directory, that match files and 60 | # directories to ignore when looking for source files. 61 | # This pattern also affects html_static_path and html_extra_path. 62 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 63 | 64 | myst_enable_extensions = [ 65 | "html_admonition", 66 | "html_image", # to parse html syntax to insert images 67 | "dollarmath", #"amsmath", # to parse Latex-style math 68 | ] 69 | 70 | # -- Options for HTML output ------------------------------------------------- 71 | 72 | # The theme to use for HTML and HTML Help pages. See the documentation for 73 | # a list of builtin themes. 74 | # 75 | html_theme = 'renku' # 'sphinx_book_theme' 76 | 77 | # The name of an image file (relative to this directory) to place at the top 78 | # of the sidebar. 79 | html_logo = "logo_v1.png" #"logo.png" 80 | html_title = "PDynA" 81 | 82 | # If true, SmartyPants will be used to convert quotes and dashes to 83 | # typographically correct entities. 84 | html_use_smartypants = True 85 | 86 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 87 | # html_show_sphinx = True 88 | 89 | html_theme_options = { 90 | "repository_url": "https://github.com/WMD-group/PDynA/", 91 | "github_repo": "https://github.com/WMD-group/PDynA/", # renku 92 | "github_button": True, 93 | # "github_user": "nukenadal", # Username 94 | "description": "Tools for calculating the solar energy conversion limits of inorganic crystals.", 95 | "repository_branch": "main", 96 | "path_to_docs": "docs", 97 | "use_repository_button": True, 98 | "home_page_in_toc": True, 99 | "launch_buttons": { 100 | "binderhub_url": "https://mybinder.org", 101 | "colab_url": "https://colab.research.google.com", 102 | }, 103 | } 104 | 105 | # Adding “Edit Source” links on your Sphinx theme 106 | #html_context = { 107 | # "display_github": True, # Integrate GitHub 108 | # "github_user": "", # Username 109 | # "github_repo": "", # Repo name 110 | # "github_version": "main", # Version 111 | # "conf_py_path": "/docs/", # Path in the checkout to the docs root 112 | #} 113 | 114 | # -- Options for intersphinx extension --------------------------------------- 115 | 116 | # Example configuration for intersphinx: refer to the Python standard library. 117 | intersphinx_mapping = { 118 | "python": ("https://docs.python.org/3.8", None), 119 | "numpy": ("http://docs.scipy.org/doc/numpy/", None), 120 | "pymatgen": ("http://pymatgen.org/", None), 121 | "matplotlib": ("http://matplotlib.org", None), 122 | } 123 | 124 | # -- Options for autodoc ----------------------------------------------------- 125 | autoclass_content="both" 126 | 127 | # -- Options for nb extension ----------------------------------------------- 128 | nb_execution_mode = "off" 129 | # nb_render_image_options = {"height": "300",} # Reduce plots size 130 | #myst_render_markdown_format = "gfm" 131 | myst_heading_anchors = 2 132 | github_doc_root = 'https://github.com/executablebooks/MyST-Parser/tree/master/docs/' 133 | def setup(app): 134 | app.add_config_value('myst_parser_config', { 135 | 'url_resolver': lambda url: github_doc_root + url, 136 | 'auto_toc_tree_section': 'Contents', 137 | }, True) 138 | app.add_transform(AutoStructify) 139 | -------------------------------------------------------------------------------- /docs/distance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WMD-group/PDynA/e94f12fcea87a280046e522aa9cb51d37abd33dd/docs/distance.png -------------------------------------------------------------------------------- /docs/graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WMD-group/PDynA/e94f12fcea87a280046e522aa9cb51d37abd33dd/docs/graphic.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | PDynA 3 | ======================================= 4 | 5 | ``PDynA`` is an open-source Python package for computing structural properties of perovskites from molecular dynamics output. 6 | 7 | Key Features 8 | ============ 9 | All features and functionality are fully-customisable: 10 | 11 | - **Processing of MD Trajectory**: Read from most of the common MD software outputs. 12 | - **Structural Analysis**: Compute structural properties of the perovskite structure including (local) lattice parameters, time-averaged structure, molecular motion, site displacements, etc. 13 | - **Octahedral deformation**: Compute octahedral tilting and distortion, giving direct information on phase transformation and dynamic ordering. 14 | - **Spatial and Time Correlations**: Calculate the correlation of various properties in space and time, with customizable functions. 15 | - **Vectorization and Parallelization**: Most of the functions are vectorized and/or can be parallelized with multi-threading, under an almost perfect scaling. 16 | - **Plotting**: Generate plots of computed outputs, providing visual intuition on the key properties, and can also be easily tuned for your needs. 17 | - ``Python`` **Interface**: Customisable and modular ``Python`` API. 18 | 19 | .. image:: ./graphic.png 20 | :width: 500 21 | :target: https://github.com/WMD-group/PDynA 22 | 23 | Citation 24 | ======== 25 | 26 | If you use ``PDynA`` in your research, please cite: 27 | 28 | - Xia Liang et al. `Structural Dynamics Descriptors for Metal Halide Perovskites `__. *The Journal of Physical Chemistry C* 127 (38), 19141-19151, **2023** 29 | 30 | .. toctree:: 31 | :hidden: 32 | :caption: Usage 33 | :maxdepth: 4 34 | 35 | Installation 36 | Python API 37 | Tutorials 38 | Tips 39 | 40 | .. toctree:: 41 | :hidden: 42 | :caption: Information 43 | :maxdepth: 1 44 | 45 | Contributing 46 | Code_Compatibility 47 | PDynA on GitHub 48 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WMD-group/PDynA/e94f12fcea87a280046e522aa9cb51d37abd33dd/docs/logo.png -------------------------------------------------------------------------------- /docs/logo_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WMD-group/PDynA/e94f12fcea87a280046e522aa9cb51d37abd33dd/docs/logo_v1.png -------------------------------------------------------------------------------- /docs/pdyna.analysis.rst: -------------------------------------------------------------------------------- 1 | pdyna.analysis module 2 | ================================================= 3 | .. automodule:: pdyna.analysis 4 | :members: 5 | :undoc-members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/pdyna.basis.rst: -------------------------------------------------------------------------------- 1 | pdyna.basis module 2 | ================================================= 3 | 4 | ``pdyna.basis`` is the pre-calculated distortion irreps for mapping octahedral distortions and should not be modified in principle. It will be automatically read by the ``pdyna.structural`` module. 5 | 6 | .. automodule:: pdyna.basis 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | -------------------------------------------------------------------------------- /docs/pdyna.core.rst: -------------------------------------------------------------------------------- 1 | pdyna.core module 2 | ================================================= 3 | .. automodule:: pdyna.core 4 | :members: 5 | :undoc-members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/pdyna.io.rst: -------------------------------------------------------------------------------- 1 | pdyna.io module 2 | ================================================= 3 | .. automodule:: pdyna.io 4 | :members: 5 | :undoc-members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/pdyna.rst: -------------------------------------------------------------------------------- 1 | PDynA 2 | ============= 3 | 4 | Module contents 5 | --------------- 6 | .. automodule:: pdyna 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | Submodules 12 | ----------- 13 | 14 | .. toctree:: 15 | :maxdepth: 3 16 | 17 | pdyna.core 18 | pdyna.structural 19 | pdyna.analysis 20 | pdyna.io 21 | pdyna.basis 22 | -------------------------------------------------------------------------------- /docs/pdyna.structural.rst: -------------------------------------------------------------------------------- 1 | pdyna.structural module 2 | ================================================= 3 | .. automodule:: pdyna.structural 4 | :members: 5 | :undoc-members: 6 | :show-inheritance: 7 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx-rtd-theme 3 | recommonmark 4 | sphinx_click 5 | sphinx_design 6 | renku-sphinx-theme 7 | myst_nb 8 | nbsphinx 9 | 10 | # required dependencies: 11 | numpy 12 | pymatgen 13 | ase 14 | matplotlib 15 | scipy 16 | git+https://github.com/WMD-group/PDynA.git@main 17 | -------------------------------------------------------------------------------- /docs/tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "# test" 10 | ] 11 | } 12 | ], 13 | "metadata": { 14 | "language_info": { 15 | "name": "python" 16 | } 17 | }, 18 | "nbformat": 4, 19 | "nbformat_minor": 2 20 | } 21 | -------------------------------------------------------------------------------- /examples/2D_perovskite/connectivity_2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WMD-group/PDynA/e94f12fcea87a280046e522aa9cb51d37abd33dd/examples/2D_perovskite/connectivity_2D.png -------------------------------------------------------------------------------- /examples/minimal/INCAR_mapi_example: -------------------------------------------------------------------------------- 1 | NBLOCK = 50 2 | TEBEG = 100 3 | #TEEND = 100 4 | NSW = 100000 5 | POTIM = 0.5 -------------------------------------------------------------------------------- /examples/minimal/POSCAR_mapi_example: -------------------------------------------------------------------------------- 1 | H48 Pb8 C8 I24 N8 2 | 1.0 3 | 12.800000 0.000000 0.000000 4 | 0.000000 12.800000 0.000000 5 | 0.000000 0.000000 12.200000 6 | H Pb C I N 7 | 48 8 8 24 8 8 | direct 9 | 0.902132 0.920216 0.886435 H 10 | 0.901956 0.762694 0.386435 H 11 | 0.901956 0.262694 0.886435 H 12 | 0.902132 0.420216 0.386436 H 13 | 0.401956 0.762694 0.886435 H 14 | 0.402132 0.920216 0.386436 H 15 | 0.402132 0.420216 0.886435 H 16 | 0.401956 0.262694 0.386435 H 17 | 0.330111 0.933573 0.263339 H 18 | 0.473977 0.749337 0.763339 H 19 | 0.473977 0.249337 0.263339 H 20 | 0.330111 0.433573 0.763339 H 21 | 0.973977 0.749337 0.263339 H 22 | 0.830111 0.933573 0.763339 H 23 | 0.830111 0.433573 0.263339 H 24 | 0.973977 0.249337 0.763339 H 25 | 0.830357 0.749235 0.263112 H 26 | 0.973731 0.933675 0.763112 H 27 | 0.973731 0.433675 0.263112 H 28 | 0.830357 0.249235 0.763112 H 29 | 0.473731 0.933675 0.263112 H 30 | 0.330357 0.749235 0.763112 H 31 | 0.330357 0.249235 0.263112 H 32 | 0.473731 0.433675 0.763112 H 33 | 0.334852 0.248676 0.820236 H 34 | 0.469236 0.434234 0.320236 H 35 | 0.469236 0.934234 0.820236 H 36 | 0.334852 0.748676 0.320236 H 37 | 0.969236 0.434234 0.820236 H 38 | 0.834852 0.248676 0.320236 H 39 | 0.834852 0.748676 0.820236 H 40 | 0.969236 0.934234 0.320236 H 41 | 0.834630 0.434136 0.820054 H 42 | 0.969458 0.248774 0.320054 H 43 | 0.969458 0.748774 0.820054 H 44 | 0.834630 0.934136 0.320054 H 45 | 0.469458 0.248774 0.820054 H 46 | 0.334630 0.434136 0.320054 H 47 | 0.334630 0.934136 0.820054 H 48 | 0.469458 0.748774 0.320054 H 49 | 0.402100 0.421446 0.206627 H 50 | 0.401988 0.261464 0.706627 H 51 | 0.401988 0.761464 0.206627 H 52 | 0.402100 0.921446 0.706627 H 53 | 0.901988 0.261464 0.206627 H 54 | 0.902100 0.421446 0.706626 H 55 | 0.902100 0.921446 0.206627 H 56 | 0.901988 0.761464 0.706627 H 57 | 0.151954 0.612735 0.061555 Pb 58 | 0.151782 0.615250 0.561829 Pb 59 | 0.152603 0.114298 0.061460 Pb 60 | 0.151989 0.114107 0.560623 Pb 61 | 0.651410 0.614695 0.061230 Pb 62 | 0.652534 0.615492 0.561440 Pb 63 | 0.650819 0.114389 0.062520 Pb 64 | 0.652697 0.114114 0.561183 Pb 65 | 0.902007 0.901080 0.801136 C 66 | 0.902081 0.781830 0.301136 C 67 | 0.902081 0.281830 0.801136 C 68 | 0.902007 0.401080 0.301136 C 69 | 0.402081 0.781830 0.801136 C 70 | 0.402007 0.901080 0.301136 C 71 | 0.402007 0.401080 0.801136 C 72 | 0.402081 0.281830 0.301136 C 73 | 0.208630 0.665270 0.811259 I 74 | 0.097337 0.563686 0.311921 I 75 | 0.103611 0.059069 0.810810 I 76 | 0.201442 0.170994 0.310780 I 77 | 0.602883 0.558901 0.811503 I 78 | 0.702159 0.670974 0.311511 I 79 | 0.707097 0.163204 0.811320 I 80 | 0.593624 0.064816 0.311337 I 81 | 0.406250 0.688454 0.104236 I 82 | 0.406982 0.689076 0.518946 I 83 | 0.397197 0.038879 0.018036 I 84 | 0.397443 0.040459 0.602786 I 85 | 0.897992 0.541533 0.017954 I 86 | 0.898348 0.540068 0.605209 I 87 | 0.906278 0.188980 0.102847 I 88 | 0.906513 0.189773 0.518760 I 89 | 0.077647 0.859675 0.105202 I 90 | 0.076527 0.859984 0.517037 I 91 | 0.228135 0.368455 0.016804 I 92 | 0.224824 0.368466 0.605229 I 93 | 0.724525 0.868865 0.017394 I 94 | 0.728929 0.869258 0.602616 I 95 | 0.577746 0.360785 0.103681 I 96 | 0.579319 0.360301 0.518957 I 97 | 0.902075 0.284179 0.286864 N 98 | 0.902013 0.398731 0.786864 N 99 | 0.902013 0.898731 0.286864 N 100 | 0.902075 0.784179 0.786864 N 101 | 0.402013 0.398731 0.286864 N 102 | 0.402075 0.284179 0.786864 N 103 | 0.402075 0.784179 0.286864 N 104 | 0.402013 0.898731 0.786864 N 105 | -------------------------------------------------------------------------------- /examples/system/init.extxyz: -------------------------------------------------------------------------------- 1 | 640 2 | Lattice="26.7200584412 0.0 0.0 -13.3600323018 23.1402476216 0.0 0.0 0.0 22.9405765534" Properties=species:S:1:pos:R:3 pbc="T T T" 3 | Ba 3.34001033 1.92835196 1.43378603 4 | Ba 3.34001033 1.92835196 7.16893017 5 | Ba 0.00000215 7.71341404 1.43378603 6 | Ba 0.00000215 7.71341404 7.16893017 7 | Ba 10.02002494 1.92835196 1.43378603 8 | Ba 10.02002494 1.92835196 7.16893017 9 | Ba 6.68001676 7.71341404 1.43378603 10 | Ba 6.68001676 7.71341404 7.16893017 11 | Ba -0.00000389 3.85670977 4.30135810 12 | Ba -0.00000389 3.85670977 10.03650224 13 | Ba -3.34001197 9.64177168 4.30135810 14 | Ba -3.34001197 9.64177168 10.03650224 15 | Ba 6.68001092 3.85670977 4.30135810 16 | Ba 6.68001092 3.85670977 10.03650224 17 | Ba 3.34000284 9.64177168 4.30135810 18 | Ba 3.34000284 9.64177168 10.03650224 19 | Ti 0.00000000 0.00000000 2.86757207 20 | Ti 0.00000000 0.00000000 8.60271621 21 | Ti -3.34000808 5.78506191 2.86757207 22 | Ti -3.34000808 5.78506191 8.60271621 23 | Ti 6.68001461 0.00000000 2.86757207 24 | Ti 6.68001461 0.00000000 8.60271621 25 | Ti 3.34000653 5.78506191 2.86757207 26 | Ti 3.34000653 5.78506191 8.60271621 27 | Ti 0.00000000 0.00000000 0.00000000 28 | Ti 0.00000000 0.00000000 5.73514414 29 | Ti -3.34000808 5.78506191 0.00000000 30 | Ti -3.34000808 5.78506191 5.73514414 31 | Ti 6.68001461 0.00000000 0.00000000 32 | Ti 6.68001461 0.00000000 5.73514414 33 | Ti 3.34000653 5.78506191 0.00000000 34 | Ti 3.34000653 5.78506191 5.73514414 35 | S -0.00000026 1.94216651 1.43378603 36 | S -0.00000026 1.94216651 7.16893017 37 | S -3.34000853 7.72722877 1.43378603 38 | S -3.34000853 7.72722877 7.16893017 39 | S 6.68001455 1.94216651 1.43378603 40 | S 6.68001455 1.94216651 7.16893017 41 | S 3.34000628 7.72722877 1.43378603 42 | S 3.34000628 7.72722877 7.16893017 43 | S 4.99804901 0.97108326 4.30135810 44 | S 4.99804901 0.97108326 10.03650224 45 | S 1.65804084 6.75614534 4.30135810 46 | S 1.65804084 6.75614534 10.03650224 47 | S 11.67806322 0.97108326 4.30135810 48 | S 11.67806322 0.97108326 10.03650224 49 | S 8.33805505 6.75614534 4.30135810 50 | S 8.33805505 6.75614534 10.03650224 51 | S 1.68196554 0.97108326 4.30135810 52 | S 1.68196554 0.97108326 10.03650224 53 | S -1.65804264 6.75614534 4.30135810 54 | S -1.65804264 6.75614534 10.03650224 55 | S 8.36198055 0.97108326 4.30135810 56 | S 8.36198055 0.97108326 10.03650224 57 | S 5.02197237 6.75614534 4.30135810 58 | S 5.02197237 6.75614534 10.03650224 59 | S 1.65804090 4.81397882 1.43378603 60 | S 1.65804090 4.81397882 7.16893017 61 | S -1.68196698 10.59904038 1.43378603 62 | S -1.68196698 10.59904038 7.16893017 63 | S 8.33805511 4.81397882 1.43378603 64 | S 8.33805511 4.81397882 7.16893017 65 | S 4.99804723 10.59904038 1.43378603 66 | S 4.99804723 10.59904038 7.16893017 67 | S 3.34000699 3.84289539 4.30135810 68 | S 3.34000699 3.84289539 10.03650224 69 | S -0.00000088 9.62795695 4.30135810 70 | S -0.00000088 9.62795695 10.03650224 71 | S 10.02002120 3.84289539 4.30135810 72 | S 10.02002120 3.84289539 10.03650224 73 | S 6.68001333 9.62795695 4.30135810 74 | S 6.68001333 9.62795695 10.03650224 75 | S -1.65804238 4.81397882 1.43378603 76 | S -1.65804238 4.81397882 7.16893017 77 | S -4.99805025 10.59904038 1.43378603 78 | S -4.99805025 10.59904038 7.16893017 79 | S 5.02197243 4.81397882 1.43378603 80 | S 5.02197243 4.81397882 7.16893017 81 | S 1.68196456 10.59904038 1.43378603 82 | S 1.68196456 10.59904038 7.16893017 83 | Ba 3.34001033 1.92835196 12.90407431 84 | Ba 3.34001033 1.92835196 18.63921845 85 | Ba 0.00000215 7.71341404 12.90407431 86 | Ba 0.00000215 7.71341404 18.63921845 87 | Ba 10.02002494 1.92835196 12.90407431 88 | Ba 10.02002494 1.92835196 18.63921845 89 | Ba 6.68001676 7.71341404 12.90407431 90 | Ba 6.68001676 7.71341404 18.63921845 91 | Ba -0.00000389 3.85670977 15.77164638 92 | Ba -0.00000389 3.85670977 21.50679052 93 | Ba -3.34001197 9.64177168 15.77164638 94 | Ba -3.34001197 9.64177168 21.50679052 95 | Ba 6.68001092 3.85670977 15.77164638 96 | Ba 6.68001092 3.85670977 21.50679052 97 | Ba 3.34000284 9.64177168 15.77164638 98 | Ba 3.34000284 9.64177168 21.50679052 99 | Ti 0.00000000 0.00000000 14.33786035 100 | Ti 0.00000000 0.00000000 20.07300448 101 | Ti -3.34000808 5.78506191 14.33786035 102 | Ti -3.34000808 5.78506191 20.07300448 103 | Ti 6.68001461 0.00000000 14.33786035 104 | Ti 6.68001461 0.00000000 20.07300448 105 | Ti 3.34000653 5.78506191 14.33786035 106 | Ti 3.34000653 5.78506191 20.07300448 107 | Ti 0.00000000 0.00000000 11.47028828 108 | Ti 0.00000000 0.00000000 17.20543242 109 | Ti -3.34000808 5.78506191 11.47028828 110 | Ti -3.34000808 5.78506191 17.20543242 111 | Ti 6.68001461 0.00000000 11.47028828 112 | Ti 6.68001461 0.00000000 17.20543242 113 | Ti 3.34000653 5.78506191 11.47028828 114 | Ti 3.34000653 5.78506191 17.20543242 115 | S -0.00000026 1.94216651 12.90407431 116 | S -0.00000026 1.94216651 18.63921845 117 | S -3.34000853 7.72722877 12.90407431 118 | S -3.34000853 7.72722877 18.63921845 119 | S 6.68001455 1.94216651 12.90407431 120 | S 6.68001455 1.94216651 18.63921845 121 | S 3.34000628 7.72722877 12.90407431 122 | S 3.34000628 7.72722877 18.63921845 123 | S 4.99804901 0.97108326 15.77164638 124 | S 4.99804901 0.97108326 21.50679052 125 | S 1.65804084 6.75614534 15.77164638 126 | S 1.65804084 6.75614534 21.50679052 127 | S 11.67806322 0.97108326 15.77164638 128 | S 11.67806322 0.97108326 21.50679052 129 | S 8.33805505 6.75614534 15.77164638 130 | S 8.33805505 6.75614534 21.50679052 131 | S 1.68196554 0.97108326 15.77164638 132 | S 1.68196554 0.97108326 21.50679052 133 | S -1.65804264 6.75614534 15.77164638 134 | S -1.65804264 6.75614534 21.50679052 135 | S 8.36198055 0.97108326 15.77164638 136 | S 8.36198055 0.97108326 21.50679052 137 | S 5.02197237 6.75614534 15.77164638 138 | S 5.02197237 6.75614534 21.50679052 139 | S 1.65804090 4.81397882 12.90407431 140 | S 1.65804090 4.81397882 18.63921845 141 | S -1.68196698 10.59904038 12.90407431 142 | S -1.68196698 10.59904038 18.63921845 143 | S 8.33805511 4.81397882 12.90407431 144 | S 8.33805511 4.81397882 18.63921845 145 | S 4.99804723 10.59904038 12.90407431 146 | S 4.99804723 10.59904038 18.63921845 147 | S 3.34000699 3.84289539 15.77164638 148 | S 3.34000699 3.84289539 21.50679052 149 | S -0.00000088 9.62795695 15.77164638 150 | S -0.00000088 9.62795695 21.50679052 151 | S 10.02002120 3.84289539 15.77164638 152 | S 10.02002120 3.84289539 21.50679052 153 | S 6.68001333 9.62795695 15.77164638 154 | S 6.68001333 9.62795695 21.50679052 155 | S -1.65804238 4.81397882 12.90407431 156 | S -1.65804238 4.81397882 18.63921845 157 | S -4.99805025 10.59904038 12.90407431 158 | S -4.99805025 10.59904038 18.63921845 159 | S 5.02197243 4.81397882 12.90407431 160 | S 5.02197243 4.81397882 18.63921845 161 | S 1.68196456 10.59904038 12.90407431 162 | S 1.68196456 10.59904038 18.63921845 163 | Ba -3.34000582 13.49847577 1.43378603 164 | Ba -3.34000582 13.49847577 7.16893017 165 | Ba -6.68001400 19.28353785 1.43378603 166 | Ba -6.68001400 19.28353785 7.16893017 167 | Ba 3.34000879 13.49847577 1.43378603 168 | Ba 3.34000879 13.49847577 7.16893017 169 | Ba 0.00000061 19.28353785 1.43378603 170 | Ba 0.00000061 19.28353785 7.16893017 171 | Ba -6.68002004 15.42683358 4.30135810 172 | Ba -6.68002004 15.42683358 10.03650224 173 | Ba -10.02002812 21.21189549 4.30135810 174 | Ba -10.02002812 21.21189549 10.03650224 175 | Ba -0.00000523 15.42683358 4.30135810 176 | Ba -0.00000523 15.42683358 10.03650224 177 | Ba -3.34001331 21.21189549 4.30135810 178 | Ba -3.34001331 21.21189549 10.03650224 179 | Ti -6.68001615 11.57012381 2.86757207 180 | Ti -6.68001615 11.57012381 8.60271621 181 | Ti -10.02002423 17.35518572 2.86757207 182 | Ti -10.02002423 17.35518572 8.60271621 183 | Ti -0.00000154 11.57012381 2.86757207 184 | Ti -0.00000154 11.57012381 8.60271621 185 | Ti -3.34000962 17.35518572 2.86757207 186 | Ti -3.34000962 17.35518572 8.60271621 187 | Ti -6.68001615 11.57012381 0.00000000 188 | Ti -6.68001615 11.57012381 5.73514414 189 | Ti -10.02002423 17.35518572 0.00000000 190 | Ti -10.02002423 17.35518572 5.73514414 191 | Ti -0.00000154 11.57012381 0.00000000 192 | Ti -0.00000154 11.57012381 5.73514414 193 | Ti -3.34000962 17.35518572 0.00000000 194 | Ti -3.34000962 17.35518572 5.73514414 195 | S -6.68001641 13.51229032 1.43378603 196 | S -6.68001641 13.51229032 7.16893017 197 | S -10.02002469 19.29735258 1.43378603 198 | S -10.02002469 19.29735258 7.16893017 199 | S -0.00000160 13.51229032 1.43378603 200 | S -0.00000160 13.51229032 7.16893017 201 | S -3.34000987 19.29735258 1.43378603 202 | S -3.34000987 19.29735258 7.16893017 203 | S -1.68196714 12.54120707 4.30135810 204 | S -1.68196714 12.54120707 10.03650224 205 | S -5.02197531 18.32626915 4.30135810 206 | S -5.02197531 18.32626915 10.03650224 207 | S 4.99804707 12.54120707 4.30135810 208 | S 4.99804707 12.54120707 10.03650224 209 | S 1.65803890 18.32626915 4.30135810 210 | S 1.65803890 18.32626915 10.03650224 211 | S -4.99805061 12.54120707 4.30135810 212 | S -4.99805061 12.54120707 10.03650224 213 | S -8.33805879 18.32626915 4.30135810 214 | S -8.33805879 18.32626915 10.03650224 215 | S 1.68196440 12.54120707 4.30135810 216 | S 1.68196440 12.54120707 10.03650224 217 | S -1.65804378 18.32626915 4.30135810 218 | S -1.65804378 18.32626915 10.03650224 219 | S -5.02197526 16.38410263 1.43378603 220 | S -5.02197526 16.38410263 7.16893017 221 | S -8.36198313 22.16916419 1.43378603 222 | S -8.36198313 22.16916419 7.16893017 223 | S 1.65803895 16.38410263 1.43378603 224 | S 1.65803895 16.38410263 7.16893017 225 | S -1.68196892 22.16916419 1.43378603 226 | S -1.68196892 22.16916419 7.16893017 227 | S -3.34000916 15.41301920 4.30135810 228 | S -3.34000916 15.41301920 10.03650224 229 | S -6.68001703 21.19808076 4.30135810 230 | S -6.68001703 21.19808076 10.03650224 231 | S 3.34000505 15.41301920 4.30135810 232 | S 3.34000505 15.41301920 10.03650224 233 | S -0.00000282 21.19808076 4.30135810 234 | S -0.00000282 21.19808076 10.03650224 235 | S -8.33805853 16.38410263 1.43378603 236 | S -8.33805853 16.38410263 7.16893017 237 | S -11.67806640 22.16916419 1.43378603 238 | S -11.67806640 22.16916419 7.16893017 239 | S -1.65804372 16.38410263 1.43378603 240 | S -1.65804372 16.38410263 7.16893017 241 | S -4.99805159 22.16916419 1.43378603 242 | S -4.99805159 22.16916419 7.16893017 243 | Ba -3.34000582 13.49847577 12.90407431 244 | Ba -3.34000582 13.49847577 18.63921845 245 | Ba -6.68001400 19.28353785 12.90407431 246 | Ba -6.68001400 19.28353785 18.63921845 247 | Ba 3.34000879 13.49847577 12.90407431 248 | Ba 3.34000879 13.49847577 18.63921845 249 | Ba 0.00000061 19.28353785 12.90407431 250 | Ba 0.00000061 19.28353785 18.63921845 251 | Ba -6.68002004 15.42683358 15.77164638 252 | Ba -6.68002004 15.42683358 21.50679052 253 | Ba -10.02002812 21.21189549 15.77164638 254 | Ba -10.02002812 21.21189549 21.50679052 255 | Ba -0.00000523 15.42683358 15.77164638 256 | Ba -0.00000523 15.42683358 21.50679052 257 | Ba -3.34001331 21.21189549 15.77164638 258 | Ba -3.34001331 21.21189549 21.50679052 259 | Ti -6.68001615 11.57012381 14.33786035 260 | Ti -6.68001615 11.57012381 20.07300448 261 | Ti -10.02002423 17.35518572 14.33786035 262 | Ti -10.02002423 17.35518572 20.07300448 263 | Ti -0.00000154 11.57012381 14.33786035 264 | Ti -0.00000154 11.57012381 20.07300448 265 | Ti -3.34000962 17.35518572 14.33786035 266 | Ti -3.34000962 17.35518572 20.07300448 267 | Ti -6.68001615 11.57012381 11.47028828 268 | Ti -6.68001615 11.57012381 17.20543242 269 | Ti -10.02002423 17.35518572 11.47028828 270 | Ti -10.02002423 17.35518572 17.20543242 271 | Ti -0.00000154 11.57012381 11.47028828 272 | Ti -0.00000154 11.57012381 17.20543242 273 | Ti -3.34000962 17.35518572 11.47028828 274 | Ti -3.34000962 17.35518572 17.20543242 275 | S -6.68001641 13.51229032 12.90407431 276 | S -6.68001641 13.51229032 18.63921845 277 | S -10.02002469 19.29735258 12.90407431 278 | S -10.02002469 19.29735258 18.63921845 279 | S -0.00000160 13.51229032 12.90407431 280 | S -0.00000160 13.51229032 18.63921845 281 | S -3.34000987 19.29735258 12.90407431 282 | S -3.34000987 19.29735258 18.63921845 283 | S -1.68196714 12.54120707 15.77164638 284 | S -1.68196714 12.54120707 21.50679052 285 | S -5.02197531 18.32626915 15.77164638 286 | S -5.02197531 18.32626915 21.50679052 287 | S 4.99804707 12.54120707 15.77164638 288 | S 4.99804707 12.54120707 21.50679052 289 | S 1.65803890 18.32626915 15.77164638 290 | S 1.65803890 18.32626915 21.50679052 291 | S -4.99805061 12.54120707 15.77164638 292 | S -4.99805061 12.54120707 21.50679052 293 | S -8.33805879 18.32626915 15.77164638 294 | S -8.33805879 18.32626915 21.50679052 295 | S 1.68196440 12.54120707 15.77164638 296 | S 1.68196440 12.54120707 21.50679052 297 | S -1.65804378 18.32626915 15.77164638 298 | S -1.65804378 18.32626915 21.50679052 299 | S -5.02197526 16.38410263 12.90407431 300 | S -5.02197526 16.38410263 18.63921845 301 | S -8.36198313 22.16916419 12.90407431 302 | S -8.36198313 22.16916419 18.63921845 303 | S 1.65803895 16.38410263 12.90407431 304 | S 1.65803895 16.38410263 18.63921845 305 | S -1.68196892 22.16916419 12.90407431 306 | S -1.68196892 22.16916419 18.63921845 307 | S -3.34000916 15.41301920 15.77164638 308 | S -3.34000916 15.41301920 21.50679052 309 | S -6.68001703 21.19808076 15.77164638 310 | S -6.68001703 21.19808076 21.50679052 311 | S 3.34000505 15.41301920 15.77164638 312 | S 3.34000505 15.41301920 21.50679052 313 | S -0.00000282 21.19808076 15.77164638 314 | S -0.00000282 21.19808076 21.50679052 315 | S -8.33805853 16.38410263 12.90407431 316 | S -8.33805853 16.38410263 18.63921845 317 | S -11.67806640 22.16916419 12.90407431 318 | S -11.67806640 22.16916419 18.63921845 319 | S -1.65804372 16.38410263 12.90407431 320 | S -1.65804372 16.38410263 18.63921845 321 | S -4.99805159 22.16916419 12.90407431 322 | S -4.99805159 22.16916419 18.63921845 323 | Ba 16.70003955 1.92835196 1.43378603 324 | Ba 16.70003955 1.92835196 7.16893017 325 | Ba 13.36003137 7.71341404 1.43378603 326 | Ba 13.36003137 7.71341404 7.16893017 327 | Ba 23.38005416 1.92835196 1.43378603 328 | Ba 23.38005416 1.92835196 7.16893017 329 | Ba 20.04004598 7.71341404 1.43378603 330 | Ba 20.04004598 7.71341404 7.16893017 331 | Ba 13.36002533 3.85670977 4.30135810 332 | Ba 13.36002533 3.85670977 10.03650224 333 | Ba 10.02001725 9.64177168 4.30135810 334 | Ba 10.02001725 9.64177168 10.03650224 335 | Ba 20.04004014 3.85670977 4.30135810 336 | Ba 20.04004014 3.85670977 10.03650224 337 | Ba 16.70003206 9.64177168 4.30135810 338 | Ba 16.70003206 9.64177168 10.03650224 339 | Ti 13.36002922 0.00000000 2.86757207 340 | Ti 13.36002922 0.00000000 8.60271621 341 | Ti 10.02002115 5.78506191 2.86757207 342 | Ti 10.02002115 5.78506191 8.60271621 343 | Ti 20.04004383 0.00000000 2.86757207 344 | Ti 20.04004383 0.00000000 8.60271621 345 | Ti 16.70003576 5.78506191 2.86757207 346 | Ti 16.70003576 5.78506191 8.60271621 347 | Ti 13.36002922 0.00000000 0.00000000 348 | Ti 13.36002922 0.00000000 5.73514414 349 | Ti 10.02002115 5.78506191 0.00000000 350 | Ti 10.02002115 5.78506191 5.73514414 351 | Ti 20.04004383 0.00000000 0.00000000 352 | Ti 20.04004383 0.00000000 5.73514414 353 | Ti 16.70003576 5.78506191 0.00000000 354 | Ti 16.70003576 5.78506191 5.73514414 355 | S 13.36002896 1.94216651 1.43378603 356 | S 13.36002896 1.94216651 7.16893017 357 | S 10.02002069 7.72722877 1.43378603 358 | S 10.02002069 7.72722877 7.16893017 359 | S 20.04004377 1.94216651 1.43378603 360 | S 20.04004377 1.94216651 7.16893017 361 | S 16.70003550 7.72722877 1.43378603 362 | S 16.70003550 7.72722877 7.16893017 363 | S 18.35807823 0.97108326 4.30135810 364 | S 18.35807823 0.97108326 10.03650224 365 | S 15.01807006 6.75614534 4.30135810 366 | S 15.01807006 6.75614534 10.03650224 367 | S 25.03809244 0.97108326 4.30135810 368 | S 25.03809244 0.97108326 10.03650224 369 | S 21.69808427 6.75614534 4.30135810 370 | S 21.69808427 6.75614534 10.03650224 371 | S 15.04199476 0.97108326 4.30135810 372 | S 15.04199476 0.97108326 10.03650224 373 | S 11.70198658 6.75614534 4.30135810 374 | S 11.70198658 6.75614534 10.03650224 375 | S 21.72200977 0.97108326 4.30135810 376 | S 21.72200977 0.97108326 10.03650224 377 | S 18.38200160 6.75614534 4.30135810 378 | S 18.38200160 6.75614534 10.03650224 379 | S 15.01807012 4.81397882 1.43378603 380 | S 15.01807012 4.81397882 7.16893017 381 | S 11.67806224 10.59904038 1.43378603 382 | S 11.67806224 10.59904038 7.16893017 383 | S 21.69808433 4.81397882 1.43378603 384 | S 21.69808433 4.81397882 7.16893017 385 | S 18.35807645 10.59904038 1.43378603 386 | S 18.35807645 10.59904038 7.16893017 387 | S 16.70003621 3.84289539 4.30135810 388 | S 16.70003621 3.84289539 10.03650224 389 | S 13.36002834 9.62795695 4.30135810 390 | S 13.36002834 9.62795695 10.03650224 391 | S 23.38005042 3.84289539 4.30135810 392 | S 23.38005042 3.84289539 10.03650224 393 | S 20.04004255 9.62795695 4.30135810 394 | S 20.04004255 9.62795695 10.03650224 395 | S 11.70198684 4.81397882 1.43378603 396 | S 11.70198684 4.81397882 7.16893017 397 | S 8.36197897 10.59904038 1.43378603 398 | S 8.36197897 10.59904038 7.16893017 399 | S 18.38200165 4.81397882 1.43378603 400 | S 18.38200165 4.81397882 7.16893017 401 | S 15.04199378 10.59904038 1.43378603 402 | S 15.04199378 10.59904038 7.16893017 403 | Ba 16.70003955 1.92835196 12.90407431 404 | Ba 16.70003955 1.92835196 18.63921845 405 | Ba 13.36003137 7.71341404 12.90407431 406 | Ba 13.36003137 7.71341404 18.63921845 407 | Ba 23.38005416 1.92835196 12.90407431 408 | Ba 23.38005416 1.92835196 18.63921845 409 | Ba 20.04004598 7.71341404 12.90407431 410 | Ba 20.04004598 7.71341404 18.63921845 411 | Ba 13.36002533 3.85670977 15.77164638 412 | Ba 13.36002533 3.85670977 21.50679052 413 | Ba 10.02001725 9.64177168 15.77164638 414 | Ba 10.02001725 9.64177168 21.50679052 415 | Ba 20.04004014 3.85670977 15.77164638 416 | Ba 20.04004014 3.85670977 21.50679052 417 | Ba 16.70003206 9.64177168 15.77164638 418 | Ba 16.70003206 9.64177168 21.50679052 419 | Ti 13.36002922 0.00000000 14.33786035 420 | Ti 13.36002922 0.00000000 20.07300448 421 | Ti 10.02002115 5.78506191 14.33786035 422 | Ti 10.02002115 5.78506191 20.07300448 423 | Ti 20.04004383 0.00000000 14.33786035 424 | Ti 20.04004383 0.00000000 20.07300448 425 | Ti 16.70003576 5.78506191 14.33786035 426 | Ti 16.70003576 5.78506191 20.07300448 427 | Ti 13.36002922 0.00000000 11.47028828 428 | Ti 13.36002922 0.00000000 17.20543242 429 | Ti 10.02002115 5.78506191 11.47028828 430 | Ti 10.02002115 5.78506191 17.20543242 431 | Ti 20.04004383 0.00000000 11.47028828 432 | Ti 20.04004383 0.00000000 17.20543242 433 | Ti 16.70003576 5.78506191 11.47028828 434 | Ti 16.70003576 5.78506191 17.20543242 435 | S 13.36002896 1.94216651 12.90407431 436 | S 13.36002896 1.94216651 18.63921845 437 | S 10.02002069 7.72722877 12.90407431 438 | S 10.02002069 7.72722877 18.63921845 439 | S 20.04004377 1.94216651 12.90407431 440 | S 20.04004377 1.94216651 18.63921845 441 | S 16.70003550 7.72722877 12.90407431 442 | S 16.70003550 7.72722877 18.63921845 443 | S 18.35807823 0.97108326 15.77164638 444 | S 18.35807823 0.97108326 21.50679052 445 | S 15.01807006 6.75614534 15.77164638 446 | S 15.01807006 6.75614534 21.50679052 447 | S 25.03809244 0.97108326 15.77164638 448 | S 25.03809244 0.97108326 21.50679052 449 | S 21.69808427 6.75614534 15.77164638 450 | S 21.69808427 6.75614534 21.50679052 451 | S 15.04199476 0.97108326 15.77164638 452 | S 15.04199476 0.97108326 21.50679052 453 | S 11.70198658 6.75614534 15.77164638 454 | S 11.70198658 6.75614534 21.50679052 455 | S 21.72200977 0.97108326 15.77164638 456 | S 21.72200977 0.97108326 21.50679052 457 | S 18.38200160 6.75614534 15.77164638 458 | S 18.38200160 6.75614534 21.50679052 459 | S 15.01807012 4.81397882 12.90407431 460 | S 15.01807012 4.81397882 18.63921845 461 | S 11.67806224 10.59904038 12.90407431 462 | S 11.67806224 10.59904038 18.63921845 463 | S 21.69808433 4.81397882 12.90407431 464 | S 21.69808433 4.81397882 18.63921845 465 | S 18.35807645 10.59904038 12.90407431 466 | S 18.35807645 10.59904038 18.63921845 467 | S 16.70003621 3.84289539 15.77164638 468 | S 16.70003621 3.84289539 21.50679052 469 | S 13.36002834 9.62795695 15.77164638 470 | S 13.36002834 9.62795695 21.50679052 471 | S 23.38005042 3.84289539 15.77164638 472 | S 23.38005042 3.84289539 21.50679052 473 | S 20.04004255 9.62795695 15.77164638 474 | S 20.04004255 9.62795695 21.50679052 475 | S 11.70198684 4.81397882 12.90407431 476 | S 11.70198684 4.81397882 18.63921845 477 | S 8.36197897 10.59904038 12.90407431 478 | S 8.36197897 10.59904038 18.63921845 479 | S 18.38200165 4.81397882 12.90407431 480 | S 18.38200165 4.81397882 18.63921845 481 | S 15.04199378 10.59904038 12.90407431 482 | S 15.04199378 10.59904038 18.63921845 483 | Ba 10.02002340 13.49847577 1.43378603 484 | Ba 10.02002340 13.49847577 7.16893017 485 | Ba 6.68001522 19.28353785 1.43378603 486 | Ba 6.68001522 19.28353785 7.16893017 487 | Ba 16.70003801 13.49847577 1.43378603 488 | Ba 16.70003801 13.49847577 7.16893017 489 | Ba 13.36002983 19.28353785 1.43378603 490 | Ba 13.36002983 19.28353785 7.16893017 491 | Ba 6.68000918 15.42683358 4.30135810 492 | Ba 6.68000918 15.42683358 10.03650224 493 | Ba 3.34000110 21.21189549 4.30135810 494 | Ba 3.34000110 21.21189549 10.03650224 495 | Ba 13.36002399 15.42683358 4.30135810 496 | Ba 13.36002399 15.42683358 10.03650224 497 | Ba 10.02001591 21.21189549 4.30135810 498 | Ba 10.02001591 21.21189549 10.03650224 499 | Ti 6.68001307 11.57012381 2.86757207 500 | Ti 6.68001307 11.57012381 8.60271621 501 | Ti 3.34000499 17.35518572 2.86757207 502 | Ti 3.34000499 17.35518572 8.60271621 503 | Ti 13.36002768 11.57012381 2.86757207 504 | Ti 13.36002768 11.57012381 8.60271621 505 | Ti 10.02001960 17.35518572 2.86757207 506 | Ti 10.02001960 17.35518572 8.60271621 507 | Ti 6.68001307 11.57012381 0.00000000 508 | Ti 6.68001307 11.57012381 5.73514414 509 | Ti 3.34000499 17.35518572 0.00000000 510 | Ti 3.34000499 17.35518572 5.73514414 511 | Ti 13.36002768 11.57012381 0.00000000 512 | Ti 13.36002768 11.57012381 5.73514414 513 | Ti 10.02001960 17.35518572 0.00000000 514 | Ti 10.02001960 17.35518572 5.73514414 515 | S 6.68001281 13.51229032 1.43378603 516 | S 6.68001281 13.51229032 7.16893017 517 | S 3.34000454 19.29735258 1.43378603 518 | S 3.34000454 19.29735258 7.16893017 519 | S 13.36002762 13.51229032 1.43378603 520 | S 13.36002762 13.51229032 7.16893017 521 | S 10.02001935 19.29735258 1.43378603 522 | S 10.02001935 19.29735258 7.16893017 523 | S 11.67806208 12.54120707 4.30135810 524 | S 11.67806208 12.54120707 10.03650224 525 | S 8.33805391 18.32626915 4.30135810 526 | S 8.33805391 18.32626915 10.03650224 527 | S 18.35807629 12.54120707 4.30135810 528 | S 18.35807629 12.54120707 10.03650224 529 | S 15.01806812 18.32626915 4.30135810 530 | S 15.01806812 18.32626915 10.03650224 531 | S 8.36197861 12.54120707 4.30135810 532 | S 8.36197861 12.54120707 10.03650224 533 | S 5.02197043 18.32626915 4.30135810 534 | S 5.02197043 18.32626915 10.03650224 535 | S 15.04199362 12.54120707 4.30135810 536 | S 15.04199362 12.54120707 10.03650224 537 | S 11.70198544 18.32626915 4.30135810 538 | S 11.70198544 18.32626915 10.03650224 539 | S 8.33805397 16.38410263 1.43378603 540 | S 8.33805397 16.38410263 7.16893017 541 | S 4.99804609 22.16916419 1.43378603 542 | S 4.99804609 22.16916419 7.16893017 543 | S 15.01806817 16.38410263 1.43378603 544 | S 15.01806817 16.38410263 7.16893017 545 | S 11.67806030 22.16916419 1.43378603 546 | S 11.67806030 22.16916419 7.16893017 547 | S 10.02002006 15.41301920 4.30135810 548 | S 10.02002006 15.41301920 10.03650224 549 | S 6.68001219 21.19808076 4.30135810 550 | S 6.68001219 21.19808076 10.03650224 551 | S 16.70003427 15.41301920 4.30135810 552 | S 16.70003427 15.41301920 10.03650224 553 | S 13.36002640 21.19808076 4.30135810 554 | S 13.36002640 21.19808076 10.03650224 555 | S 5.02197069 16.38410263 1.43378603 556 | S 5.02197069 16.38410263 7.16893017 557 | S 1.68196282 22.16916419 1.43378603 558 | S 1.68196282 22.16916419 7.16893017 559 | S 11.70198550 16.38410263 1.43378603 560 | S 11.70198550 16.38410263 7.16893017 561 | S 8.36197763 22.16916419 1.43378603 562 | S 8.36197763 22.16916419 7.16893017 563 | Ba 10.02002340 13.49847577 12.90407431 564 | Ba 10.02002340 13.49847577 18.63921845 565 | Ba 6.68001522 19.28353785 12.90407431 566 | Ba 6.68001522 19.28353785 18.63921845 567 | Ba 16.70003801 13.49847577 12.90407431 568 | Ba 16.70003801 13.49847577 18.63921845 569 | Ba 13.36002983 19.28353785 12.90407431 570 | Ba 13.36002983 19.28353785 18.63921845 571 | Ba 6.68000918 15.42683358 15.77164638 572 | Ba 6.68000918 15.42683358 21.50679052 573 | Ba 3.34000110 21.21189549 15.77164638 574 | Ba 3.34000110 21.21189549 21.50679052 575 | Ba 13.36002399 15.42683358 15.77164638 576 | Ba 13.36002399 15.42683358 21.50679052 577 | Ba 10.02001591 21.21189549 15.77164638 578 | Ba 10.02001591 21.21189549 21.50679052 579 | Ti 6.68001307 11.57012381 14.33786035 580 | Ti 6.68001307 11.57012381 20.07300448 581 | Ti 3.34000499 17.35518572 14.33786035 582 | Ti 3.34000499 17.35518572 20.07300448 583 | Ti 13.36002768 11.57012381 14.33786035 584 | Ti 13.36002768 11.57012381 20.07300448 585 | Ti 10.02001960 17.35518572 14.33786035 586 | Ti 10.02001960 17.35518572 20.07300448 587 | Ti 6.68001307 11.57012381 11.47028828 588 | Ti 6.68001307 11.57012381 17.20543242 589 | Ti 3.34000499 17.35518572 11.47028828 590 | Ti 3.34000499 17.35518572 17.20543242 591 | Ti 13.36002768 11.57012381 11.47028828 592 | Ti 13.36002768 11.57012381 17.20543242 593 | Ti 10.02001960 17.35518572 11.47028828 594 | Ti 10.02001960 17.35518572 17.20543242 595 | S 6.68001281 13.51229032 12.90407431 596 | S 6.68001281 13.51229032 18.63921845 597 | S 3.34000454 19.29735258 12.90407431 598 | S 3.34000454 19.29735258 18.63921845 599 | S 13.36002762 13.51229032 12.90407431 600 | S 13.36002762 13.51229032 18.63921845 601 | S 10.02001935 19.29735258 12.90407431 602 | S 10.02001935 19.29735258 18.63921845 603 | S 11.67806208 12.54120707 15.77164638 604 | S 11.67806208 12.54120707 21.50679052 605 | S 8.33805391 18.32626915 15.77164638 606 | S 8.33805391 18.32626915 21.50679052 607 | S 18.35807629 12.54120707 15.77164638 608 | S 18.35807629 12.54120707 21.50679052 609 | S 15.01806812 18.32626915 15.77164638 610 | S 15.01806812 18.32626915 21.50679052 611 | S 8.36197861 12.54120707 15.77164638 612 | S 8.36197861 12.54120707 21.50679052 613 | S 5.02197043 18.32626915 15.77164638 614 | S 5.02197043 18.32626915 21.50679052 615 | S 15.04199362 12.54120707 15.77164638 616 | S 15.04199362 12.54120707 21.50679052 617 | S 11.70198544 18.32626915 15.77164638 618 | S 11.70198544 18.32626915 21.50679052 619 | S 8.33805397 16.38410263 12.90407431 620 | S 8.33805397 16.38410263 18.63921845 621 | S 4.99804609 22.16916419 12.90407431 622 | S 4.99804609 22.16916419 18.63921845 623 | S 15.01806817 16.38410263 12.90407431 624 | S 15.01806817 16.38410263 18.63921845 625 | S 11.67806030 22.16916419 12.90407431 626 | S 11.67806030 22.16916419 18.63921845 627 | S 10.02002006 15.41301920 15.77164638 628 | S 10.02002006 15.41301920 21.50679052 629 | S 6.68001219 21.19808076 15.77164638 630 | S 6.68001219 21.19808076 21.50679052 631 | S 16.70003427 15.41301920 15.77164638 632 | S 16.70003427 15.41301920 21.50679052 633 | S 13.36002640 21.19808076 15.77164638 634 | S 13.36002640 21.19808076 21.50679052 635 | S 5.02197069 16.38410263 12.90407431 636 | S 5.02197069 16.38410263 18.63921845 637 | S 1.68196282 22.16916419 12.90407431 638 | S 1.68196282 22.16916419 18.63921845 639 | S 11.70198550 16.38410263 12.90407431 640 | S 11.70198550 16.38410263 18.63921845 641 | S 8.36197763 22.16916419 12.90407431 642 | S 8.36197763 22.16916419 18.63921845 643 | -------------------------------------------------------------------------------- /examples/system/system_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WMD-group/PDynA/e94f12fcea87a280046e522aa9cb51d37abd33dd/examples/system/system_schematic.png -------------------------------------------------------------------------------- /pdyna/__init__.py: -------------------------------------------------------------------------------- 1 | """PDyna is a package for analysing dynamic properties of perovskite.""" 2 | 3 | from pdyna._version import __version__ 4 | from pdyna.core import Trajectory 5 | -------------------------------------------------------------------------------- /pdyna/_version.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import DistributionNotFound, get_distribution 2 | 3 | try: 4 | __version__ = get_distribution("pdyna").version 5 | except DistributionNotFound: 6 | # package is not installed 7 | __version__ = "" 8 | -------------------------------------------------------------------------------- /pdyna/basis/octahedron_basis.json: -------------------------------------------------------------------------------- 1 | {"rotationT1g": [[0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0], [0.0, 0.0, 0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [0.0, -0.5, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.5, 0.0]], "translationT1u": [[0.40824829046386296, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0], [0.0, 0.4082482904638631, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.4082482904638631, 0.0], [0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631]], "A1g": [[0.4082482904638631, 0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, -0.4082482904638631, 0.0, -0.4082482904638631, 0.0, -0.4082482904638631, 0.0, 0.0]], "Eg": [[0.5773502691896258, 0.0, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.2886751345948129, 0.0, 0.2886751345948129, 0.0, -0.5773502691896258, 0.0, 0.0], [7.401486830834377e-17, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, -0.5, 0.0, -7.401486830834377e-17, 0.0, 0.0]], "T2g": [[0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, -0.5, 0.0], [0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0]], "T1u": [[-0.577350269189626, 0.0, 0.0, 0.2886751345948128, 0.0, 0.0, 0.2886751345948128, 0.0, 0.0, 0.2886751345948128, 0.0, 0.0, 0.2886751345948128, 0.0, 0.0, -0.577350269189626, 0.0, 0.0], [0.0, -0.2886751345948129, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, -0.2886751345948129, 0.0], [0.0, 0.0, -0.2886751345948129, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, -0.2886751345948129]], "T2u": [[0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0], [0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5], [0.0, 0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0]]} 2 | -------------------------------------------------------------------------------- /pdyna/basis/octahedron_basis_analytical.json: -------------------------------------------------------------------------------- 1 | {"rotationT1g": [[0.0, 0.5, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, -0.5, 0.0], [0.0, 0.0, 0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0]], "translationT1u": [[0.408248290463863, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.408248290463863, 0.0, 0.0], [0.0, 0.4082482904638631, 0.0, 0.0, 0.408248290463863, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.408248290463863, 0.0, 0.0, 0.4082482904638631, 0.0], [0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.408248290463863, 0.0, 0.0, 0.408248290463863, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631]], "A1g": [[0.4082482904638631, 0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, -0.4082482904638631, 0.0, -0.4082482904638631, 0.0, -0.4082482904638631, 0.0, 0.0]], "Eg": [[0.5773502691896258, 0.0, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.2886751345948129, 0.0, 0.2886751345948129, 0.0, -0.5773502691896258, 0.0, 0.0], [7.401486830834377e-17, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, -0.5, 0.0, -7.401486830834377e-17, 0.0, 0.0]], "T2g": [[0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, -0.5, 0.0], [0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0]], "T1u": [[-0.5773502691896258, 0.0, 0.0, 0.2886751345948129, 0.0, 0.0, 0.2886751345948129, 0.0, 0.0, 0.2886751345948129, 0.0, 0.0, 0.2886751345948129, 0.0, 0.0, -0.5773502691896258, 0.0, 0.0], [0.0, 0.2886751345948129, 0.0, 0.0, -0.5773502691896258, 0.0, 0.0, 0.2886751345948129, 0.0, 0.0, 0.2886751345948129, 0.0, 0.0, -0.5773502691896258, 0.0, 0.0, 0.2886751345948129, 0.0], [0.0, 0.0, 0.2886751345948129, 0.0, 0.0, 0.2886751345948129, 0.0, 0.0, -0.5773502691896258, 0.0, 0.0, -0.5773502691896258, 0.0, 0.0, 0.2886751345948129, 0.0, 0.0, 0.2886751345948129]], "T2u": [[0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0], [0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5], [0.0, 0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0]]} 2 | -------------------------------------------------------------------------------- /pdyna/basis/octahedron_basis_full.json: -------------------------------------------------------------------------------- 1 | {"rotationT1g": [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.5, 0.0]], "translationT1u": [[0.0, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.4082482904638631, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.40824829046386296, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.4082482904638631]], "A1g": [[0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, 0.0, 0.4082482904638631, 0.0, 0.0, -0.4082482904638631, 0.0, -0.4082482904638631, 0.0, -0.4082482904638631, 0.0, 0.0]], "Eg": [[0.0, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.2886751345948129, 0.0, 0.2886751345948129, 0.0, -0.5773502691896258, 0.0, 0.0], [0.0, 0.0, 0.0, 7.401486830834377e-17, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, -0.5, 0.0, -7.401486830834377e-17, 0.0, 0.0]], "T2g": [[0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, -0.5, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0]], "T1u": [[0.0, 0.0, 0.0, -0.577350269189626, 0.0, 0.0, 0.2886751345948128, 0.0, 0.0, 0.2886751345948128, 0.0, 0.0, 0.2886751345948128, 0.0, 0.0, 0.2886751345948128, 0.0, 0.0, -0.577350269189626, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, -0.2886751345948129, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, 0.5773502691896258, 0.0, 0.0, -0.2886751345948129, 0.0, 0.0, -0.2886751345948129]], "T2u": [[0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, -0.5, 0.0, 0.0, -0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0]], "B100": [[0.9258200997725514, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0], [0.0, 0.9258200997725514, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0], [0.0, 0.0, 0.9258200997725514, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919, 0.0, 0.0, -0.1543033499620919]], "B110": [[0.6546536707079772, 0.6546536707079772, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0], [0.6546536707079772, 0.0, 0.6546536707079772, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962], [0.0, 0.6546536707079772, 0.6546536707079772, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962, 0.0, -0.1091089451179962, -0.1091089451179962]], "B111": [[0.5345224838248488, 0.5345224838248488, 0.5345224838248488, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748, -0.0890870806374748], [-0.5345224838248488, 0.5345224838248488, 0.5345224838248488, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748], [0.5345224838248488, -0.5345224838248488, 0.5345224838248488, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748, -0.0890870806374748, 0.0890870806374748, -0.0890870806374748], [-0.5345224838248488, -0.5345224838248488, 0.5345224838248488, 0.0890870806374748, 0.0890870806374748, -0.0890870806374748, 0.0890870806374748, 0.0890870806374748, -0.0890870806374748, 0.0890870806374748, 0.0890870806374748, -0.0890870806374748, 0.0890870806374748, 0.0890870806374748, -0.0890870806374748, 0.0890870806374748, 0.0890870806374748, -0.0890870806374748, 0.0890870806374748, 0.0890870806374748, -0.0890870806374748]]} -------------------------------------------------------------------------------- /pdyna/io.py: -------------------------------------------------------------------------------- 1 | """ 2 | pdyna.io: The collection of I/O functions to various input formats. 3 | 4 | """ 5 | 6 | import re 7 | import numpy as np 8 | from ase.atoms import Atoms 9 | from ase.calculators.lammps import convert 10 | 11 | 12 | def read_lammps_dir(fdir,allow_multi=False): 13 | """ 14 | Read the LAMMPS input files in the directory and extract the simulation settings. 15 | 16 | Args: 17 | fdir (str): The directory path. 18 | allow_multi (bool): Allow multiple LAMMPS input files in the directory. 19 | 20 | Returns: 21 | dict or list of dict: The simulation settings. 22 | """ 23 | 24 | from glob import glob 25 | filelist = glob(fdir+"*.in") 26 | if len(filelist) == 0: 27 | raise FileNotFoundError("Can't find any LAMMPS .in file in the directory.") 28 | 29 | if not allow_multi: 30 | if len(filelist) > 1: 31 | raise FileExistsError("There are more than one LAMMPS .in file in the directory.") 32 | return read_lammps_settings(filelist[0]) 33 | else: 34 | return [read_lammps_settings(f) for f in filelist] 35 | 36 | 37 | def read_lammps_settings(infile): # internal use 38 | """ 39 | Read the LAMMPS input file and extract the simulation settings. 40 | 41 | Args: 42 | infile (str): The LAMMPS input file. 43 | 44 | Returns: 45 | dict: The simulation settings 46 | """ 47 | 48 | with open(infile,"r") as fp: 49 | lines = fp.readlines() 50 | tvelo = 0 51 | ti = None 52 | tf = None 53 | tstep = None 54 | nsw = 0 55 | for line in lines: 56 | if line.startswith("velocity"): 57 | tvelo = float(line.split()[3]) 58 | if line.startswith("fix"): 59 | ti = float(line.split()[5]) 60 | tf = float(line.split()[6]) 61 | if line.startswith("timestep"): 62 | tstep = float(line.split()[1])*1000 63 | if line.startswith("run"): 64 | nsw = float(line.split()[1]) 65 | 66 | #if tvelo != ti: 67 | # print("!Lammps in file: the thermalization temperature is different from the initial temperature.") 68 | 69 | return {"Ti":ti, "Tf":tf, "tstep":tstep, "nsw":nsw} 70 | 71 | 72 | def read_ase_md_settings(fdir): # internal use 73 | """ 74 | Read the ASE MD input file and extract the simulation settings. 75 | 76 | Args: 77 | fdir (str): The directory path. 78 | 79 | Returns: 80 | tuple: The simulation settings 81 | """ 82 | 83 | from glob import glob 84 | 85 | filelist = glob(fdir+"*.py") 86 | if len(filelist) == 0: 87 | raise FileNotFoundError("Can't find any ASE MD .py file in the directory.") 88 | if len(filelist) > 1: 89 | raise FileExistsError("There are more than one ASE MD .py file in the directory.") 90 | 91 | infile = filelist[0] 92 | 93 | with open(infile,"r") as fp: 94 | lines = fp.readlines() 95 | 96 | ti = None 97 | tstep = None 98 | nsw = None 99 | nblock = None 100 | for line in lines: 101 | if line.startswith("dyn = NPT"): 102 | sets = line.split(',') 103 | for s in sets: 104 | if "temperature_K" in s: 105 | ti = int(s.split("=")[1]) 106 | tstep=float(sets[1].rstrip("*units.fs")) 107 | if line.startswith("dyn.run"): 108 | nsw = int(re.search(r'\d+', line).group()) 109 | if line.startswith("dyn.attach") and "write_frame" in line: 110 | nblock = int(re.search(r'\d+', line).group()) 111 | 112 | return ti, tstep, nsw, nblock 113 | 114 | 115 | def process_lat(m): 116 | """ 117 | Convert lattice matrix to abc and three angles. 118 | 119 | Args: 120 | m (numpy.ndarray): The lattice matrix. 121 | 122 | Returns: 123 | numpy.ndarray: The abc lattice vectors and three angles. 124 | """ 125 | 126 | abc = np.sqrt(np.sum(m**2, axis=1)) 127 | angles = np.zeros(3) 128 | for i in range(3): 129 | j = (i + 1) % 3 130 | k = (i + 2) % 3 131 | angles[i] = np.clip(np.dot(m[j], m[k])/(abc[j] * abc[k]),-1,1) 132 | angles = np.arccos(angles) * 180.0 / np.pi 133 | return np.concatenate((abc,angles)).reshape(1,6) 134 | 135 | 136 | def process_lat_reverse(cellpar): 137 | """ 138 | Convert abc and three angles to lattice matrix. 139 | Modified from ASE functions. 140 | 141 | Args: 142 | cellpar (numpy.ndarray): The abc lattice vectors and three angles. 143 | 144 | Returns: 145 | numpy.ndarray: The lattice matrix. 146 | """ 147 | 148 | X = np.array([1., 0., 0.]) 149 | Y = np.array([0., 1., 0.]) 150 | Z = np.array([0., 0., 1.]) 151 | 152 | # Express va, vb and vc in the X,Y,Z-system 153 | alpha, beta, gamma = 90., 90., 90. 154 | if isinstance(cellpar, (int, float)): 155 | a = b = c = cellpar 156 | elif len(cellpar) == 1: 157 | a = b = c = cellpar[0] 158 | elif len(cellpar) == 3: 159 | a, b, c = cellpar 160 | else: 161 | a, b, c, alpha, beta, gamma = cellpar 162 | 163 | # Handle orthorhombic cells separately to avoid rounding errors 164 | eps = 2 * np.spacing(90.0, dtype=np.float64) # around 1.4e-14 165 | # alpha 166 | if abs(abs(alpha) - 90) < eps: 167 | cos_alpha = 0.0 168 | else: 169 | cos_alpha = np.cos(alpha * np.pi / 180.0) 170 | # beta 171 | if abs(abs(beta) - 90) < eps: 172 | cos_beta = 0.0 173 | else: 174 | cos_beta = np.cos(beta * np.pi / 180.0) 175 | # gamma 176 | if abs(gamma - 90) < eps: 177 | cos_gamma = 0.0 178 | sin_gamma = 1.0 179 | elif abs(gamma + 90) < eps: 180 | cos_gamma = 0.0 181 | sin_gamma = -1.0 182 | else: 183 | cos_gamma = np.cos(gamma * np.pi / 180.0) 184 | sin_gamma = np.sin(gamma * np.pi / 180.0) 185 | 186 | # Build the cell vectors 187 | va = a * np.array([1, 0, 0]) 188 | vb = b * np.array([cos_gamma, sin_gamma, 0]) 189 | cx = cos_beta 190 | cy = (cos_alpha - cos_beta * cos_gamma) / sin_gamma 191 | cz_sqr = 1. - cx * cx - cy * cy 192 | assert cz_sqr >= 0 193 | cz = np.sqrt(cz_sqr) 194 | vc = c * np.array([cx, cy, cz]) 195 | 196 | # Convert to the Cartesian x,y,z-system 197 | abc = np.vstack((va, vb, vc)) 198 | T = np.vstack((X, Y, Z)) 199 | cell = np.dot(abc, T) 200 | 201 | return cell 202 | 203 | 204 | def read_xdatcar(filename,natom): 205 | """ 206 | Read the VASP XDATCAR file and extract the atomic symbols, lattices, and atomic positions. 207 | 208 | Args: 209 | filename (str): The XDATCAR file. 210 | natom (int): The number of atoms. 211 | 212 | Returns: 213 | tuple: The atomic symbols, lattices, and atomic positions. 214 | """ 215 | 216 | import warnings 217 | from monty.io import zopen 218 | from pymatgen.util.io_utils import clean_lines 219 | from pymatgen.core.periodic_table import Element 220 | 221 | from pdyna.structural import get_cart_from_frac, get_frac_from_cart 222 | 223 | def from_string(data): 224 | """ 225 | Modified from the Pymatgen function Poscar.from_string 226 | 227 | Args: 228 | data (str): String representation of VASP structure. 229 | 230 | Returns: 231 | tuple: The atomic symbols, lattices, and atomic positions. 232 | """ 233 | # "^\s*$" doesn't match lines with no whitespace 234 | chunks = re.split(r"\n\s*\n", data.rstrip(), flags=re.MULTILINE) 235 | try: 236 | if chunks[0] == "": 237 | chunks.pop(0) 238 | chunks[0] = "\n" + chunks[0] 239 | except IndexError: 240 | raise ValueError("Empty structure") 241 | 242 | # Parse positions 243 | lines = tuple(clean_lines(chunks[0].split("\n"), False)) 244 | scale = float(lines[1]) 245 | lattice = np.array([[float(i) for i in line.split()] for line in lines[2:5]]) 246 | if scale < 0: 247 | # In vasp, a negative scale factor is treated as a volume. We need 248 | # to translate this to a proper lattice vector scaling. 249 | vol = abs(np.linalg.det(lattice)) 250 | lattice *= (-scale / vol) ** (1 / 3) 251 | else: 252 | lattice *= scale 253 | 254 | vasp5_symbols = False 255 | try: 256 | natoms = [int(i) for i in lines[5].split()] 257 | ipos = 6 258 | except ValueError: 259 | vasp5_symbols = True 260 | symbols = lines[5].split() 261 | 262 | nlines_symbols = 1 263 | for nlines_symbols in range(1, 11): 264 | try: 265 | int(lines[5 + nlines_symbols].split()[0]) 266 | break 267 | except ValueError: 268 | pass 269 | for iline_symbols in range(6, 5 + nlines_symbols): 270 | symbols.extend(lines[iline_symbols].split()) 271 | natoms = [] 272 | iline_natoms_start = 5 + nlines_symbols 273 | for iline_natoms in range(iline_natoms_start, iline_natoms_start + nlines_symbols): 274 | natoms.extend([int(i) for i in lines[iline_natoms].split()]) 275 | atomic_symbols = [] 276 | for i, nat in enumerate(natoms): 277 | atomic_symbols.extend([symbols[i]] * nat) 278 | ipos = 5 + 2 * nlines_symbols 279 | 280 | pos_type = lines[ipos].split()[0] 281 | 282 | has_selective_dynamics = False 283 | # Selective dynamics 284 | if pos_type[0] in "sS": 285 | has_selective_dynamics = True 286 | ipos += 1 287 | pos_type = lines[ipos].split()[0] 288 | 289 | cart = pos_type[0] in "cCkK" 290 | n_sites = sum(natoms) 291 | 292 | if not vasp5_symbols: 293 | ind = 3 if not has_selective_dynamics else 6 294 | try: 295 | # Check if names are appended at the end of the coordinates. 296 | atomic_symbols = [l.split()[ind] for l in lines[ipos + 1 : ipos + 1 + n_sites]] 297 | # Ensure symbols are valid elements 298 | if not all(Element.is_valid_symbol(sym) for sym in atomic_symbols): 299 | raise ValueError("Non-valid symbols detected.") 300 | vasp5_symbols = True 301 | except (ValueError, IndexError): 302 | # Defaulting to false names. 303 | atomic_symbols = [] 304 | for i, nat in enumerate(natoms): 305 | sym = Element.from_Z(i + 1).symbol 306 | atomic_symbols.extend([sym] * nat) 307 | warnings.warn(f"Elements in POSCAR cannot be determined. Defaulting to false names {atomic_symbols}.") 308 | 309 | # read the atomic coordinates 310 | coords = [] 311 | selective_dynamics = [] if has_selective_dynamics else None 312 | for i in range(n_sites): 313 | toks = lines[ipos + 1 + i].split() 314 | crd_scale = scale if cart else 1 315 | coords.append([float(j) * crd_scale for j in toks[:3]]) 316 | if has_selective_dynamics: 317 | selective_dynamics.append([tok.upper()[0] == "T" for tok in toks[3:6]]) 318 | 319 | 320 | l6 = process_lat(lattice) 321 | atomic_symbols, 322 | lattice, 323 | coords = np.array(coords) 324 | if cart: 325 | cart_coords = coords 326 | frac_coords = get_frac_from_cart(coords, lattice) 327 | else: 328 | frac_coords = coords 329 | cart_coords = get_cart_from_frac(coords, lattice) 330 | 331 | return atomic_symbols, lattice, l6, frac_coords, cart_coords 332 | 333 | preamble = None 334 | coords_str = [] 335 | preamble_done = False 336 | 337 | f = zopen(filename, "rt") 338 | fcount = 0 339 | Allpos = np.empty((0,natom,3)) 340 | lattice = np.empty((0,6)) 341 | latmat = np.empty((0,3,3)) 342 | 343 | for l in f: 344 | new_frame = False 345 | l = l.strip() 346 | if preamble is None: 347 | preamble = [l] 348 | title = l 349 | elif title == l: 350 | preamble_done = False 351 | p = "\n".join(preamble + ["Direct"] + coords_str) 352 | new_frame = True #texts.append(p) 353 | coords_str = [] 354 | preamble = [l] 355 | elif not preamble_done: 356 | if l == "" or "Direct configuration=" in l: 357 | preamble_done = True 358 | tmp_preamble = [preamble[0]] 359 | for i in range(1, len(preamble)): 360 | if preamble[0] != preamble[i]: 361 | tmp_preamble.append(preamble[i]) 362 | else: 363 | break 364 | preamble = tmp_preamble 365 | else: 366 | preamble.append(l) 367 | elif l == "" or "Direct configuration=" in l: 368 | p = "\n".join(preamble + ["Direct"] + coords_str) 369 | new_frame = True #texts.append(p) 370 | coords_str = [] 371 | else: 372 | coords_str.append(l) 373 | if new_frame: 374 | fcount += 1 375 | atomic_symbols, lat, l6, frac_coords, cart_coords = from_string(p) 376 | Allpos = np.concatenate((Allpos,cart_coords[np.newaxis,:]),axis=0) 377 | lattice = np.concatenate((lattice,l6),axis=0) 378 | latmat = np.concatenate((latmat,lat[np.newaxis,:]),axis=0) 379 | 380 | p = "\n".join(preamble + ["Direct"] + coords_str) 381 | fcount += 1 382 | atomic_symbols, lat, l6, frac_coords, cart_coords = from_string(p) 383 | Allpos = np.concatenate((Allpos,cart_coords[np.newaxis,:]),axis=0) 384 | lattice = np.concatenate((lattice,l6),axis=0) 385 | latmat = np.concatenate((latmat,lat[np.newaxis,:]),axis=0) 386 | 387 | assert Allpos.shape[0] == fcount 388 | assert lattice.shape[0] == fcount 389 | assert latmat.shape[0] == fcount 390 | 391 | return atomic_symbols, lattice, latmat, Allpos 392 | 393 | 394 | def read_lammps_dump(filepath,specorder=None): 395 | """ 396 | Read the LAMMPS trajectory file and extract the atomic symbols, lattices, and atomic positions. 397 | Modified from ASE LAMMPS reading functions 398 | 399 | Args: 400 | filepath (str): The LAMMPS trajectory file. 401 | specorder (list): The order of atomic species. 402 | 403 | Returns: 404 | tuple: The atomic symbols, lattices, and atomic positions. 405 | """ 406 | from collections import deque 407 | from pymatgen.io.ase import AseAtomsAdaptor as aaa 408 | # Load all dumped timesteps into memory simultaneously 409 | fp = open(filepath,"r") 410 | lines = deque(fp.readlines()) 411 | index_end = -1 412 | 413 | n_atoms = 0 414 | framenums = [] 415 | Allpos_list = [] 416 | lattice = np.empty((0,6)) 417 | latmat = np.empty((0,3,3)) 418 | asymb = 0 419 | 420 | # avoid references before assignment in case of incorrect file structure 421 | cell, celldisp = None, None 422 | 423 | while len(lines) > n_atoms: 424 | line = lines.popleft() 425 | 426 | if "ITEM: TIMESTEP" in line: 427 | n_atoms = 0 428 | line = lines.popleft() 429 | stepnum = int(line.split()[0]) 430 | 431 | if "ITEM: NUMBER OF ATOMS" in line: 432 | line = lines.popleft() 433 | n_atoms = int(line.split()[0]) 434 | 435 | if "ITEM: BOX BOUNDS" in line: 436 | tilt_items = line.split()[3:] 437 | celldatarows = [lines.popleft() for _ in range(3)] 438 | celldata = np.loadtxt(celldatarows) 439 | diagdisp = celldata[:,:2].reshape(6, 1).flatten() 440 | 441 | # determine cell tilt (triclinic case) 442 | if len(celldata[0]) > 2: 443 | offdiag = celldata[:, 2] 444 | if len(tilt_items) >= 3: 445 | sort_index = [tilt_items.index(i) 446 | for i in ["xy", "xz", "yz"]] 447 | offdiag = offdiag[sort_index] 448 | else: 449 | offdiag = (0.0,) * 3 450 | 451 | cell, celldisp = construct_cell(diagdisp, offdiag) 452 | 453 | if "ITEM: ATOMS" in line: 454 | colnames = line.split()[2:] 455 | datarows = [lines.popleft() for _ in range(n_atoms)] 456 | data = np.loadtxt(datarows, dtype=str) 457 | atomic_symbols, lm, l6, frac_coords, cart_coords = process_lammps_data(data,colnames,cell,celldisp,specorder=specorder) 458 | if asymb == 0: 459 | asymb = atomic_symbols 460 | framenums.append(stepnum) 461 | 462 | Allpos_list.append(cart_coords) 463 | lattice = np.concatenate((lattice,l6),axis=0) 464 | latmat = np.concatenate((latmat,lm[np.newaxis,:]),axis=0) 465 | 466 | if lattice.shape[0] > index_end >= 0: 467 | break 468 | 469 | if Allpos_list[0].shape != Allpos_list[-1].shape: # the last block is incomplete 470 | for istop in range(len(Allpos_list)): 471 | nafr = Allpos_list[istop].shape 472 | if nafr != Allpos_list[0].shape: break 473 | Allpos = np.array(Allpos_list[:istop]) 474 | # isolate the first frame which is the initial structure as st0 475 | pos0 = Allpos[0,:] 476 | cell = latmat[0,:] 477 | Allpos = Allpos[1:istop,:] 478 | lattice = lattice[1:istop,:] 479 | latmat = latmat[1:istop,:] 480 | else: 481 | Allpos = np.array(Allpos_list) 482 | # isolate the first frame which is the initial structure as st0 483 | pos0 = Allpos[0,:] 484 | cell = latmat[0,:] 485 | Allpos = Allpos[1:,:] 486 | lattice = lattice[1:,:] 487 | latmat = latmat[1:,:] 488 | 489 | assert Allpos.shape[0] == lattice.shape[0] == latmat.shape[0] 490 | 491 | out_atoms = Atoms(symbols=np.array(asymb),positions=pos0,pbc=[True,True,True],celldisp=celldisp,cell=cell) 492 | st0 = aaa.get_structure(out_atoms) 493 | 494 | maxframe = framenums[-1] 495 | if framenums[1]-framenums[0] > framenums[-1]-framenums[-2]: 496 | maxframe -= (framenums[1]-framenums[0]) 497 | 498 | return asymb, lattice, latmat, Allpos, st0, maxframe, framenums[-1]-framenums[-2] 499 | 500 | 501 | def read_xyz(filepath): 502 | """ 503 | Read the XYZ format trajectory file and extract the atomic symbols, lattices, and atomic positions. 504 | Modified from Pymatgen xyz reading functions 505 | 506 | Args: 507 | filepath (str): The XYZ format file. 508 | 509 | Returns: 510 | tuple: The atomic symbols, lattices, and atomic positions. 511 | """ 512 | 513 | from pymatgen.io.ase import AseAtomsAdaptor as aaa 514 | 515 | def read_xyz_block(bloc): 516 | """ 517 | Read the XYZ frame block and extract the atomic symbols, lattices, and atomic positions. 518 | 519 | Args: 520 | bloc (list): The XYZ frame block. 521 | 522 | Returns: 523 | tuple: The atomic symbols, lattices, and atomic positions. 524 | """ 525 | 526 | num_sites = int(bloc[0]) 527 | if len(bloc) != num_sites+2: 528 | raise SyntaxError("The XYZ format should be line 1: N-atoms; line 2: cell dimension: [a b c alpha beta gamma]; line 3-end: species and atomic coordinates. ") 529 | cellstr = bloc[1] 530 | try: 531 | cell = [float(entry) for entry in cellstr.split()] 532 | except ValueError: 533 | raise SyntaxError("The XYZ format should be line 1: N-atoms; line 2: cell dimension: [a b c alpha beta gamma]; line 3-end: species and atomic coordinates. ") 534 | 535 | coords = [] 536 | sp = [] 537 | coord_patt = re.compile(r"(\w+)\s+([0-9\-\+\.*^eEdD]+)\s+([0-9\-\+\.*^eEdD]+)\s+([0-9\-\+\.*^eEdD]+)") 538 | for i in range(2, 2 + num_sites): 539 | m = coord_patt.search(bloc[i]) 540 | if m: 541 | sp.append(m.group(1)) # this is 1-indexed 542 | xyz = [val.lower().replace("d", "e").replace("*^", "e") for val in m.groups()[1:4]] 543 | coords.append([float(val) for val in xyz]) 544 | return sp, np.array(coords), np.array(cell) 545 | 546 | contents = open(filepath, "rt").read() 547 | lines = re.split("\n", contents) 548 | blockheads = [i for i,s in enumerate(lines) if bool(re.match("\s*\d+\s*$", s))] 549 | if len(blockheads) < 2: 550 | raise ValueError("The frames can be read correctly, please check the file integrity.") 551 | frames = [] 552 | cart = [] 553 | celldim = [] 554 | cellmat = [] 555 | for i,j in enumerate(blockheads): 556 | if j != blockheads[-1]: 557 | bloc = lines[j:blockheads[i+1]] 558 | else: 559 | bloc = lines[j:] 560 | while len(bloc[-1]) == 0: 561 | bloc.pop() 562 | 563 | asymb, ci, celli = read_xyz_block(bloc) 564 | cart.append(ci) 565 | celldim.append(celli) 566 | cellmat.append(process_lat_reverse(celli)) 567 | 568 | Allpos = np.array(cart) 569 | lattice = np.array(celldim) 570 | latmat = np.array(cellmat) 571 | 572 | assert Allpos.shape[0] == lattice.shape[0] == latmat.shape[0] 573 | 574 | # isolate the first frame which is the initial structure as st0 575 | Allpos = Allpos[1:,:] 576 | lattice = lattice[1:,:] 577 | latmat = latmat[1:,:] 578 | 579 | out_atoms = Atoms(symbols=np.array(asymb),positions=Allpos[0,:],pbc=[True,True,True],celldisp=np.array([0., 0., 0.]),cell=latmat[0,:]) 580 | st0 = aaa.get_structure(out_atoms) 581 | 582 | return asymb, lattice, latmat, Allpos, st0, latmat.shape[0] 583 | 584 | 585 | def read_pdb(filepath): 586 | """ 587 | Read the protein data bank (PDB) trajectory file and extract the atomic symbols, lattices, and atomic positions. 588 | Modified from Pymatgen xyz reading functions 589 | 590 | Args: 591 | filepath (str): The PDB file. 592 | 593 | Returns: 594 | tuple: The atomic symbols, lattices, and atomic positions. 595 | """ 596 | from pymatgen.io.ase import AseAtomsAdaptor as aaa 597 | 598 | def read_pdb_block(bloc): 599 | """ 600 | Read the PDB frame block and extract the atomic symbols, lattices, and atomic positions. 601 | 602 | Args: 603 | bloc (list): The PDB frame block. 604 | 605 | Returns: 606 | tuple: The atomic symbols, lattices, and atomic positions. 607 | """ 608 | 609 | coords = [] 610 | sp = [] 611 | for line in bloc: 612 | if line.startswith("CRYST1"): 613 | str1 = line.lstrip("CRYST1").split() 614 | cell = np.array([float(e) for e in str1]) 615 | if line.startswith("ATOM"): 616 | coords.append(np.array([float(line[30:38]),float(line[38:46]),float(line[46:54])], dtype=np.float64)) 617 | sp.append(line[76:78].strip()) 618 | 619 | return sp, np.array(coords), cell 620 | 621 | contents = open(filepath, "rt").read() 622 | lines = re.split("\n", contents) 623 | b0 = [] 624 | b1 = [] 625 | for i,s in enumerate(lines): 626 | if s.startswith("REMARK"): b0.append(i) 627 | elif s.startswith("END"): b1.append(i) 628 | 629 | if len(b0) != len(b1): 630 | raise ValueError("The PDB file format is not recognized, please check file integrity. ") 631 | if len(b0) < 2: 632 | raise ValueError("The PDB file format is not recognized, please check file integrity. ") 633 | 634 | cart = [] 635 | celldim = [] 636 | cellmat = [] 637 | for i in range(len(b0)): 638 | bloc = lines[b0[i]:b1[i]] 639 | 640 | asymb, ci, celli = read_pdb_block(bloc) 641 | cart.append(ci) 642 | celldim.append(celli) 643 | cellmat.append(process_lat_reverse(celli)) 644 | 645 | Allpos = np.array(cart) 646 | lattice = np.array(celldim) 647 | latmat = np.array(cellmat) 648 | 649 | assert Allpos.shape[0] == lattice.shape[0] == latmat.shape[0] 650 | 651 | # isolate the first frame which is the initial structure as st0 652 | pos0 = Allpos[0,:] 653 | lat0 = latmat[0,:] 654 | Allpos = Allpos[1:,:] 655 | lattice = lattice[1:,:] 656 | latmat = latmat[1:,:] 657 | 658 | out_atoms = Atoms(symbols=np.array(asymb),positions=pos0,pbc=[True,True,True],celldisp=np.array([0., 0., 0.]),cell=lat0) 659 | st0 = aaa.get_structure(out_atoms) 660 | 661 | return asymb, lattice, latmat, Allpos, st0, latmat.shape[0] 662 | 663 | 664 | def read_ase_traj(filepath): 665 | """ 666 | Read the ASE internal trajectory file and extract the atomic symbols, lattices, and atomic positions. 667 | Modified from ASE original trajectory reading functions 668 | 669 | Args: 670 | filepath (str): The ASE trajectory file. 671 | 672 | Returns: 673 | tuple: The atomic symbols, lattices, and atomic positions 674 | """ 675 | 676 | from ase.io import Trajectory 677 | from pymatgen.io.ase import AseAtomsAdaptor as aaa 678 | 679 | contents = Trajectory(filepath) 680 | 681 | cart = [] 682 | celldim = [] 683 | cellmat = [] 684 | for bloc in contents: 685 | ci = bloc.positions 686 | cmat = bloc.cell.array 687 | 688 | cart.append(ci) 689 | celldim.append(process_lat(cmat)[0,:]) 690 | cellmat.append(cmat) 691 | 692 | Allpos = np.array(cart) 693 | lattice = np.array(celldim) 694 | latmat = np.array(cellmat) 695 | 696 | assert Allpos.shape[0] == lattice.shape[0] == latmat.shape[0] 697 | 698 | # isolate the first frame which is the initial structure as st0 699 | Allpos = Allpos[1:,:] 700 | lattice = lattice[1:,:] 701 | latmat = latmat[1:,:] 702 | 703 | st0 = aaa.get_structure(contents[0]) 704 | if not np.array_equal(np.array(st0.atomic_numbers),np.array(contents[0].numbers)): 705 | raise TypeError("Fatal: the converted Pymatgen structure does not match with the ASE Atoms. ") 706 | asymb = [] 707 | for s in st0.species: 708 | asymb.append(s.name) 709 | 710 | return asymb, lattice, latmat, Allpos, st0, latmat.shape[0] 711 | 712 | 713 | def process_lammps_data( 714 | data, 715 | colnames, 716 | cell, 717 | celldisp, 718 | order=True, 719 | specorder=None, 720 | units="metal",): 721 | """ 722 | Extract positions and other per-atom parameters from block of LAMMPS data. 723 | Modified from ASE functions 724 | 725 | Args: 726 | data (numpy.ndarray): The LAMMPS data block. 727 | colnames (list): The column names. 728 | cell (numpy.ndarray): The lattice matrix. 729 | celldisp (numpy.ndarray): The lattice displacement. 730 | order (bool): Order the atoms. 731 | specorder (list): The order of atomic species. 732 | units (str): The LAMMPS units. 733 | 734 | Returns: 735 | tuple: The atomic symbols, lattices, and atomic positions. 736 | """ 737 | 738 | from pdyna.structural import get_frac_from_cart, get_cart_from_frac 739 | 740 | # read IDs if given and order if needed 741 | if "id" in colnames: 742 | ids = data[:, colnames.index("id")].astype(int) 743 | if order: 744 | sort_order = np.argsort(ids) 745 | data = data[sort_order, :] 746 | 747 | # determine the elements 748 | if "element" in colnames: 749 | elements = data[:, colnames.index("element")] 750 | elif "type" in colnames: 751 | elements = data[:, colnames.index("type")].astype(int) 752 | if specorder: 753 | elements = [specorder[t - 1] for t in elements] 754 | else: 755 | raise ValueError("Cannot determine atom types form LAMMPS dump file") 756 | 757 | def get_quantity(labels, quantity=None): 758 | """ 759 | Extract the quantity from the LAMMPS data block. 760 | """ 761 | try: 762 | cols = [colnames.index(label) for label in labels] 763 | if quantity: 764 | return convert(data[:, cols].astype(float), quantity, 765 | units, "ASE") 766 | 767 | return data[:, cols].astype(float) 768 | except ValueError: 769 | return None 770 | 771 | # Positions 772 | positions = None 773 | scaled_positions = None 774 | if "x" in colnames: 775 | # doc: x, y, z = unscaled atom coordinates 776 | positions = get_quantity(["x", "y", "z"], "distance") 777 | elif "xs" in colnames: 778 | # doc: xs,ys,zs = scaled atom coordinates 779 | scaled_positions = get_quantity(["xs", "ys", "zs"]) 780 | elif "xu" in colnames: 781 | # doc: xu,yu,zu = unwrapped atom coordinates 782 | positions = get_quantity(["xu", "yu", "zu"], "distance") 783 | elif "xsu" in colnames: 784 | # xsu,ysu,zsu = scaled unwrapped atom coordinates 785 | scaled_positions = get_quantity(["xsu", "ysu", "zsu"]) 786 | else: 787 | raise ValueError("No atomic positions found in LAMMPS output") 788 | 789 | # convert cell 790 | cell = convert(cell, "distance", units, "ASE") 791 | celldisp = convert(celldisp, "distance", units, "ASE") 792 | 793 | l6 = process_lat(cell) 794 | 795 | if positions is not None: 796 | cart_coords = positions - celldisp 797 | frac_coords = get_frac_from_cart(cart_coords,cell) 798 | else: 799 | frac_coords = scaled_positions 800 | cart_coords = get_cart_from_frac(frac_coords, cell) - celldisp 801 | 802 | return list(elements), cell, l6, frac_coords, cart_coords 803 | 804 | 805 | def lammps_data_to_ase_atoms( # deprecated 806 | data, 807 | colnames, 808 | cell, 809 | celldisp, 810 | pbc=False, 811 | atomsobj=Atoms, 812 | order=True, 813 | specorder=None, 814 | prismobj=None, 815 | units="metal", 816 | ): 817 | """ 818 | Extract positions and other per-atom parameters and create Atoms 819 | Taken directly from ASE 820 | 821 | Args: 822 | data (numpy.ndarray): The LAMMPS data block. 823 | colnames (list): The column names. 824 | cell (numpy.ndarray): The lattice matrix. 825 | celldisp (numpy.ndarray): The lattice displacement. 826 | pbc (bool): The periodic boundary conditions. 827 | atomsobj (class): The atoms object class. 828 | order (bool): Order the atoms. 829 | specorder (list): The order of atomic species. 830 | prismobj (class): The prism object. 831 | 832 | Returns: 833 | ase.Atoms: The atomic structure in ASE.Atoms format. 834 | """ 835 | 836 | from ase.calculators.singlepoint import SinglePointCalculator 837 | 838 | # read IDs if given and order if needed 839 | if "id" in colnames: 840 | ids = data[:, colnames.index("id")].astype(int) 841 | if order: 842 | sort_order = np.argsort(ids) 843 | data = data[sort_order, :] 844 | 845 | # determine the elements 846 | if "element" in colnames: 847 | # priority to elements written in file 848 | elements = data[:, colnames.index("element")] 849 | elif "type" in colnames: 850 | # fall back to `types` otherwise 851 | elements = data[:, colnames.index("type")].astype(int) 852 | 853 | # reconstruct types from given specorder 854 | if specorder: 855 | elements = [specorder[t - 1] for t in elements] 856 | else: 857 | # todo: what if specorder give but no types? 858 | # in principle the masses could work for atoms, but that needs 859 | # lots of cases and new code I guess 860 | raise ValueError("Cannot determine atom types form LAMMPS dump file") 861 | 862 | def get_quantity(labels, quantity=None): 863 | """ 864 | Extract the quantity from the LAMMPS data block. 865 | """ 866 | try: 867 | cols = [colnames.index(label) for label in labels] 868 | if quantity: 869 | return convert(data[:, cols].astype(float), quantity, 870 | units, "ASE") 871 | 872 | return data[:, cols].astype(float) 873 | except ValueError: 874 | return None 875 | 876 | # Positions 877 | positions = None 878 | scaled_positions = None 879 | if "x" in colnames: 880 | # doc: x, y, z = unscaled atom coordinates 881 | positions = get_quantity(["x", "y", "z"], "distance") 882 | elif "xs" in colnames: 883 | # doc: xs,ys,zs = scaled atom coordinates 884 | scaled_positions = get_quantity(["xs", "ys", "zs"]) 885 | elif "xu" in colnames: 886 | # doc: xu,yu,zu = unwrapped atom coordinates 887 | positions = get_quantity(["xu", "yu", "zu"], "distance") 888 | elif "xsu" in colnames: 889 | # xsu,ysu,zsu = scaled unwrapped atom coordinates 890 | scaled_positions = get_quantity(["xsu", "ysu", "zsu"]) 891 | else: 892 | raise ValueError("No atomic positions found in LAMMPS output") 893 | 894 | velocities = get_quantity(["vx", "vy", "vz"], "velocity") 895 | charges = get_quantity(["q"], "charge") 896 | forces = get_quantity(["fx", "fy", "fz"], "force") 897 | quaternions = get_quantity(["c_q[1]", "c_q[2]", "c_q[3]", "c_q[4]"]) # not tested 898 | 899 | # convert cell 900 | cell = convert(cell, "distance", units, "ASE") 901 | celldisp = convert(celldisp, "distance", units, "ASE") 902 | if prismobj: 903 | celldisp = prismobj.vector_to_ase(celldisp) 904 | cell = prismobj.update_cell(cell) 905 | 906 | if quaternions: 907 | from ase.quaternions import Quaternions 908 | out_atoms = Quaternions( 909 | symbols=elements, 910 | positions=positions, 911 | cell=cell, 912 | celldisp=celldisp, 913 | pbc=pbc, 914 | quaternions=quaternions, 915 | ) 916 | elif positions is not None: 917 | # reverse coordinations transform to lammps system 918 | # (for all vectors = pos, vel, force) 919 | if prismobj: 920 | positions = prismobj.vector_to_ase(positions, wrap=True) 921 | 922 | out_atoms = atomsobj( 923 | symbols=elements, 924 | positions=positions, 925 | pbc=pbc, 926 | celldisp=celldisp, 927 | cell=cell 928 | ) 929 | elif scaled_positions is not None: 930 | out_atoms = atomsobj( 931 | symbols=elements, 932 | scaled_positions=scaled_positions, 933 | pbc=pbc, 934 | celldisp=celldisp, 935 | cell=cell, 936 | ) 937 | 938 | if velocities is not None: 939 | if prismobj: 940 | velocities = prismobj.vector_to_ase(velocities) 941 | out_atoms.set_velocities(velocities) 942 | if charges is not None: 943 | out_atoms.set_initial_charges(charges) 944 | if forces is not None: 945 | if prismobj: 946 | forces = prismobj.vector_to_ase(forces) 947 | # !TODO: use another calculator if available (or move forces 948 | # to atoms.property) (other problem: synchronizing 949 | # parallel runs) 950 | calculator = SinglePointCalculator(out_atoms, energy=0.0, 951 | forces=forces) 952 | out_atoms.calc = calculator 953 | 954 | # process the extra columns of fixes, variables and computes 955 | # that can be dumped, add as additional arrays to atoms object 956 | for colname in colnames: 957 | # determine if it is a compute or fix (but not the quaternian) 958 | if (colname.startswith('f_') or colname.startswith('v_') or 959 | (colname.startswith('c_') and not colname.startswith('c_q['))): 960 | out_atoms.new_array(colname, get_quantity([colname]), 961 | dtype='float') 962 | 963 | return out_atoms 964 | 965 | def get_max_index(index): 966 | """Get the maximum index from a slice object or integer.""" 967 | if np.isscalar(index): 968 | return index 969 | elif isinstance(index, slice): 970 | return index.stop if (index.stop is not None) else float("inf") 971 | 972 | def construct_cell(diagdisp, offdiag): 973 | """ 974 | Help function to create an ASE-cell with displacement vector from the lammps coordination system parameters. 975 | 976 | Args: 977 | diagdisp (tuple): The diagonal displacement vector. 978 | offdiag (tuple): The off-diagonal displacement vector. 979 | 980 | Returns: 981 | tuple: The cell matrix and the displacement vector. 982 | """ 983 | xlo, xhi, ylo, yhi, zlo, zhi = diagdisp 984 | xy, xz, yz = offdiag 985 | 986 | # create ase-cell from lammps-box 987 | xhilo = (xhi - xlo) - abs(xy) - abs(xz) 988 | yhilo = (yhi - ylo) - abs(yz) 989 | zhilo = zhi - zlo 990 | celldispx = xlo - min(0, xy) - min(0, xz) 991 | celldispy = ylo - min(0, yz) 992 | celldispz = zlo 993 | cell = np.array([[xhilo, 0, 0], [xy, yhilo, 0], [xz, yz, zhilo]]) 994 | celldisp = np.array([celldispx, celldispy, celldispz]) 995 | 996 | return cell, celldisp 997 | 998 | 999 | def chemical_from_formula(struct): 1000 | """ 1001 | Get the chemical formula from the structure object. 1002 | Only recorded a limited range of structures. 1003 | 1004 | Args: 1005 | struct (Structure): The structure object. 1006 | 1007 | Returns: 1008 | str: The chemical formula. 1009 | """ 1010 | chem_lib = {'CsPbI3': r'CsPbI$_{3}$', 1011 | 'CsPbBr3': r'CsPbBr$_{3}$', 1012 | 'H5PbCI3N2': r'FAPbI$_{3}$', 1013 | 'H5PbCBr3N2': r'FAPbBr$_{3}$', 1014 | 'H6PbCI3N': r'MAPbI$_{3}$', 1015 | 'H6PbCBr3N': r'MAPbBr$_{3}$'} 1016 | 1017 | if struct.composition.reduced_formula in chem_lib: 1018 | return chem_lib[struct.composition.reduced_formula] 1019 | else: 1020 | return struct.composition.reduced_formula 1021 | 1022 | 1023 | def print_time(times): 1024 | """ 1025 | Print the elapsed time for each step in the calculation. 1026 | """ 1027 | import time 1028 | def time_format(secs): 1029 | """ Format the time in hours, minutes, and seconds. """ 1030 | return time.strftime("%H:%M:%S", time.gmtime(secs)) 1031 | 1032 | time_quantities = {'env_resolve': "Structure Resolving: {}", 1033 | 'lattice': "Lattice Parameter: {}", 1034 | 'tavg': "Time-averaging: {}", 1035 | 'tilt_distort': "Tilting & Distortion: {}", 1036 | 'MO': "Molecular Orientation: {}", 1037 | 'RDF': "Radial Distribution: {}", 1038 | 'site_disp': "A-site Displacement: {}", 1039 | 'property_processing': "Property processing: {}", 1040 | } 1041 | 1042 | print("--Elapsed Time") 1043 | print("Data Reading: {}".format(time_format(round(times['reading'])))) 1044 | for printkey, printstr in time_quantities.items(): 1045 | if printkey in times: 1046 | print(printstr.format(time_format(round(times[printkey])))) 1047 | print("Total: {}".format(time_format(round(times['total'])))) 1048 | 1049 | 1050 | def display_A_sites(A_sites): 1051 | """ 1052 | Display the A-site occupancy in the structure. 1053 | """ 1054 | prstr = [] 1055 | for key in A_sites: 1056 | sites = len(A_sites[key]) 1057 | if sites > 0: 1058 | prstr.append(key+": "+str(sites)) 1059 | print("A-sites are ->",", ".join(prstr)) 1060 | -------------------------------------------------------------------------------- /pdyna/structural.py: -------------------------------------------------------------------------------- 1 | """ 2 | pdyna.structural: The collection of structural processing functions. 3 | 4 | """ 5 | import pdyna 6 | import contextlib 7 | import numpy as np 8 | import os, json, sys 9 | import matplotlib.pyplot as plt 10 | from pymatgen.core.operations import SymmOp 11 | from pymatgen.core.structure import Molecule 12 | from pymatgen.core.periodic_table import Element 13 | from pymatgen.analysis.molecule_matcher import HungarianOrderMatcher 14 | 15 | 16 | # loading the octahedral basis files 17 | filename_basis = os.path.join(os.path.abspath(pdyna.__file__).rstrip('__init__.py'),'basis/octahedron_basis_full.json') 18 | try: 19 | with open(filename_basis, 'r') as f: 20 | dict_basis = json.load(f) 21 | dist_dim = len(dict_basis)-3 22 | except IOError: 23 | sys.stderr.write('IOError: failed reading from {}.'.format(filename_basis)) 24 | sys.exit(1) 25 | 26 | @contextlib.contextmanager 27 | def tqdm_joblib(tqdm_object): 28 | """ 29 | Supportive function for adding progress bar within joblib parallelized computation. 30 | """ 31 | import joblib 32 | class TqdmBatchCompletionCallback(joblib.parallel.BatchCompletionCallBack): 33 | def __call__(self, *args, **kwargs): 34 | tqdm_object.update(n=self.batch_size) 35 | return super().__call__(*args, **kwargs) 36 | 37 | old_batch_callback = joblib.parallel.BatchCompletionCallBack 38 | joblib.parallel.BatchCompletionCallBack = TqdmBatchCompletionCallback 39 | try: 40 | yield tqdm_object 41 | finally: 42 | joblib.parallel.BatchCompletionCallBack = old_batch_callback 43 | tqdm_object.close() 44 | 45 | 46 | def traj_time_average(Allpos,latmat,MDTimestep,twindow): 47 | """ 48 | Compute the time-averaged structure from coordinates. 49 | 50 | Args: 51 | Allpos (numpy.ndarray): Coordinate array of all atoms. 52 | latmat (numpy.ndarray): Lattice matrix of all frames, with a shape of (N, 3, 3). 53 | MDTimestep (float): Time step of the MD simulation. 54 | twindow (float): Time window for averaging. 55 | 56 | Returns: 57 | numpy.ndarray: Time-averaged coordinates. 58 | numpy.ndarray: Time-averaged lattice matrix. 59 | int: Number of atoms in the time-averaged structure. 60 | """ 61 | avgfr = round(twindow/MDTimestep) 62 | fracs = get_frac_from_cart(Allpos,latmat) 63 | ftavg = np.empty((fracs.shape[0]-avgfr+1,fracs.shape[1],3)) 64 | for i in range(fracs.shape[1]): 65 | for j in range(3): 66 | ftavg[:,i,j] = np.convolve(fracs[:,i,j],np.ones(avgfr)/avgfr,'valid') 67 | lmat = latmat.copy() 68 | lavg = np.empty((fracs.shape[0]-avgfr+1,3,3)) 69 | for i in range(3): 70 | for j in range(3): 71 | lavg[:,i,j] = np.convolve(lmat[:,i,j],np.ones(avgfr)/avgfr,'valid') 72 | 73 | # store into object attr 74 | ctavg = get_cart_from_frac(ftavg,lavg) 75 | timeori = np.linspace(0,fracs.shape[0]-1,fracs.shape[0])*MDTimestep 76 | timeavg = np.convolve(timeori,np.ones(avgfr)/avgfr,'valid') 77 | 78 | fig,ax = plt.subplots() 79 | plt.plot(timeori,Allpos[:,0,0],alpha=0.8,label='raw') 80 | plt.plot(timeavg,ctavg[:,0,0],alpha=0.8,label='t-averaged') 81 | plt.title("Time averaging: x-coords of atom 0") 82 | plt.xlabel("time (ps)") 83 | plt.ylabel("X coordinate (Angstrom)") 84 | plt.legend() 85 | plt.show() 86 | 87 | return ctavg, lavg, ctavg.shape[0] 88 | 89 | 90 | def resolve_octahedra(Bpos,Xpos,readfr,at0,enable_refit,multi_thread,latmat,fpg_val_BX,neigh_list,orthogonal_frame,structure_type,complex_pbc,ref_initial=None,rtr=None): 91 | """ 92 | Compute octahedral tilting and distortion from the trajectory coordinates and octahedral connectivity. 93 | 94 | Args: 95 | Bpos (numpy.ndarray): Coordinate array of B-sites. 96 | Xpos (numpy.ndarray): Coordinate array of X-sites. 97 | readfr (list): List of frame numbers to be computed. 98 | at0 (ASE Atoms): Atoms object of the initial frame. 99 | enable_refit (bool): Enabling of structure refitting during computation if a large distortion is found. 100 | multi_thread (bool): Enable multi-threading for computation. 101 | latmat (numpy.ndarray): Lattice matrix of all frames, with a shape of (N, 3, 3). 102 | fpg_val_BX (list): Defined B-X bond information at the beginning of the class. 103 | neigh_list (numpy.ndarray): The B-X connectivity matrix of octahedra with shape (N, 6). 104 | orthogonal_frame (bool): If the structure is 3D perovskite and aligned in the orthogonal directions. 105 | structure_type (int): Structure type indicating orientation and connectivity of octahedra. 106 | complex_pbc (bool): If the cell has strong tilts. 107 | ref_initial (numpy.ndarray, optional): For non-orthogonal structure, the individual reference for each octahedron. 108 | rtr (numpy.ndarray, optional): A (3, 3) rotation matrix from orthogonal directions if specified. 109 | 110 | Returns: 111 | numpy.ndarray: Computed array of octahedral distortion with a shape of (N, dist_dim). 112 | numpy.ndarray: Computed array of octahedral tilting with a shape of (N, 3). 113 | numpy.ndarray: Record of structural refitting if parameter enable_refit is True, shape is (N, 2), giving a frame number and a bool indicating if refit is performed. 114 | """ 115 | from tqdm import tqdm 116 | from scipy.spatial.transform import Rotation as sstr 117 | 118 | def frame_wise(fr): 119 | """ 120 | Compute octahedral tilting and distortion for a single frame, can be parallelized with multi-threading. 121 | """ 122 | mymat=latmat[fr,:] 123 | 124 | disto = np.empty((0,dist_dim)) 125 | Rmat = np.zeros((Bcount,3,3)) 126 | Rmsd = np.zeros((Bcount,1)) 127 | for B_site in range(Bcount): # for each B-site atom 128 | if np.isnan(neigh_list[B_site,:]).any(): 129 | a = np.empty((1,dist_dim)) 130 | a[:] = np.nan 131 | Rmat[B_site,:] = np.nan 132 | Rmsd[B_site] = np.nan 133 | disto = np.concatenate((disto,a),axis = 0) 134 | else: 135 | raw = Xpos[fr,neigh_list[B_site,:].astype(int),:] - Bpos[fr,B_site,:] 136 | bx = octahedra_coords_into_bond_vectors(raw,mymat) 137 | if not orthogonal_frame: 138 | bx = np.matmul(bx,ref_initial[B_site,:]) 139 | if not rtr is None: 140 | bx = np.matmul(bx,rtr) 141 | 142 | dist_val,rotmat,rmsd = calc_distortions_from_bond_vectors_full(bx) # _full 143 | 144 | Rmat[B_site,:] = rotmat 145 | Rmsd[B_site] = rmsd 146 | disto = np.concatenate((disto,dist_val.reshape(1,dist_dim)),axis = 0) 147 | 148 | tilts = np.zeros((Bcount,3)) 149 | for i in range(Rmat.shape[0]): 150 | if np.isnan(neigh_list[i,:]).any(): 151 | tilts[i,:] = np.nan 152 | else: 153 | tilts[i,:] = sstr.from_matrix(Rmat[i,:]).as_euler('xyz', degrees=True) 154 | 155 | #tilts = periodicity_fold(tilts) 156 | return (disto.reshape(1,Bcount,dist_dim),tilts.reshape(1,Bcount,3),Rmsd) 157 | 158 | if (not ref_initial is None) and (not rtr is None): 159 | raise TypeError("individual reference of octahedra and orthogonal lattice alignment are simulaneously enabled. Check structure_type and rotation_from_orthogonal parameters. ") 160 | 161 | Bcount = Bpos.shape[1] 162 | refits = np.empty((0,2)) 163 | 164 | #if not rotation_from_orthogonal is None: 165 | # rtr = np.linalg.inv(rotation_from_orthogonal) 166 | 167 | if multi_thread == 1: # multi-threading disabled and can do in-situ refit 168 | 169 | Di = np.empty((len(readfr),Bcount,dist_dim)) 170 | T = np.empty((len(readfr),Bcount,3)) 171 | 172 | for subfr in tqdm(range(len(readfr))): 173 | fr = readfr[subfr] 174 | mymat=latmat[fr,:] 175 | 176 | disto = np.empty((0,dist_dim)) 177 | Rmat = np.zeros((Bcount,3,3)) 178 | Rmsd = np.zeros((Bcount,1)) 179 | for B_site in range(Bcount): # for each B-site atom 180 | if np.isnan(neigh_list[B_site,:]).any(): 181 | a = np.empty((1,dist_dim)) 182 | a[:] = np.nan 183 | Rmat[B_site,:] = np.nan 184 | Rmsd[B_site] = np.nan 185 | disto = np.concatenate((disto,a),axis = 0) 186 | else: 187 | raw = Xpos[fr,neigh_list[B_site,:].astype(int),:] - Bpos[fr,B_site,:] 188 | bx = octahedra_coords_into_bond_vectors(raw,mymat) 189 | if not orthogonal_frame: 190 | bx = np.matmul(bx,ref_initial[B_site,:]) 191 | if not rtr is None: 192 | bx = np.matmul(bx,rtr) 193 | 194 | dist_val,rotmat,rmsd = calc_distortions_from_bond_vectors_full(bx) 195 | 196 | Rmat[B_site,:] = rotmat 197 | Rmsd[B_site] = rmsd 198 | disto = np.concatenate((disto,dist_val.reshape(1,dist_dim)),axis = 0) 199 | 200 | if enable_refit and fr > 0 and max(np.amax(disto)) > 1: # re-fit neigh_list if large distortion value is found 201 | disto_prev = np.nanmean(disto,axis=0) 202 | neigh_list_prev = neigh_list 203 | 204 | Bpos_frame = Bpos[fr,:] 205 | Xpos_frame = Xpos[fr,:] 206 | rt = distance_matrix_handler(Bpos_frame,Xpos_frame,latmat[fr,:],at0.cell,at0.pbc,complex_pbc) 207 | if orthogonal_frame: 208 | if rtr is None: 209 | neigh_list = fit_octahedral_network_defect_tol(Bpos_frame,Xpos_frame,rt,mymat,fpg_val_BX,structure_type) 210 | else: # non-orthogonal 211 | neigh_list = fit_octahedral_network_defect_tol_non_orthogonal(Bpos_frame,Xpos_frame,rt,mymat,fpg_val_BX,structure_type,np.linalg.inv(rtr)) 212 | else: 213 | neigh_list, ref_initial = fit_octahedral_network_defect_tol(Bpos_frame,Xpos_frame,rt,mymat,fpg_val_BX,structure_type) 214 | 215 | mymat=latmat[fr,:] 216 | 217 | # re-calculate distortion values 218 | disto = np.empty((0,dist_dim)) 219 | Rmat = np.zeros((Bcount,3,3)) 220 | Rmsd = np.zeros((Bcount,1)) 221 | for B_site in range(Bcount): # for each B-site atom 222 | 223 | raw = Xpos[fr,neigh_list[B_site,:].astype(int),:] - Bpos[fr,B_site,:] 224 | bx = octahedra_coords_into_bond_vectors(raw,mymat) 225 | if not orthogonal_frame: 226 | bx = np.matmul(bx,ref_initial[B_site,:]) 227 | if not rtr is None: 228 | bx = np.matmul(bx,rtr) 229 | 230 | dist_val,rotmat,rmsd = calc_distortions_from_bond_vectors_full(bx) 231 | 232 | Rmat[B_site,:] = rotmat 233 | Rmsd[B_site] = rmsd 234 | disto = np.concatenate((disto,dist_val.reshape(1,dist_dim)),axis = 0) 235 | if np.array_equal(np.nanmean(disto,axis=0),disto_prev) and np.array_equal(neigh_list,neigh_list_prev): 236 | refits = np.concatenate((refits,np.array([[fr,0]])),axis=0) 237 | else: 238 | refits = np.concatenate((refits,np.array([[fr,1]])),axis=0) 239 | 240 | Di[subfr,:,:] = disto.reshape(1,Bcount,dist_dim) 241 | 242 | tilts = np.zeros((Bcount,3)) 243 | for i in range(Rmat.shape[0]): 244 | tilts[i,:] = sstr.from_matrix(Rmat[i,:]).as_euler('xyz', degrees=True) 245 | #tilts = periodicity_fold(tilts) 246 | T[subfr,:,:] = tilts.reshape(1,Bcount,3) 247 | 248 | 249 | elif multi_thread > 1: # multi-threading enabled and cannot refit 250 | from joblib import Parallel, delayed 251 | Di = np.empty((len(readfr),Bcount,dist_dim)) 252 | T = np.empty((len(readfr),Bcount,3)) 253 | 254 | with tqdm_joblib(tqdm(desc="Progress", total=len(readfr))) as progress_bar: 255 | results = Parallel(n_jobs=multi_thread)(delayed(frame_wise)(fr) for fr in readfr) 256 | 257 | # unpack the result from multi-threading calculation 258 | assert len(results) == len(readfr) 259 | for fr, each in enumerate(results): 260 | Di[fr,:,:] = each[0] 261 | T[fr,:,:] = each[1] 262 | 263 | else: 264 | raise ValueError("The input multi-threading count must be a positive integer.") 265 | 266 | 267 | return Di, T, refits 268 | 269 | 270 | def fit_octahedral_network_frame(Bpos_frame,Xpos_frame,r,mymat,fpg_val_BX,rotated,rotmat): 271 | """ 272 | Resolve the octahedral connectivity. 273 | 274 | Args: 275 | Bpos_frame (numpy.ndarray): Coordinate array of B-sites. 276 | Xpos_frame (numpy.ndarray): Coordinate array of X-sites. 277 | r (numpy.ndarray): Distance matrix of B-X sites. 278 | mymat (numpy.ndarray): Lattice matrix of the frame. 279 | fpg_val_BX (list): Defined B-X bond information at the beginning of the class. 280 | rotated (bool): If the structure is rotated. 281 | rotmat (numpy.ndarray): Rotation matrix of the structure to align with orthogonal directions. 282 | 283 | Returns: 284 | numpy.ndarray: The B-X connectivity matrix of octahedra with shape (N, 6). 285 | """ 286 | 287 | max_BX_distance = find_population_gap(r, fpg_val_BX[0], fpg_val_BX[1]) 288 | 289 | neigh_list = np.zeros((Bpos_frame.shape[0],6)) 290 | for B_site, X_list in enumerate(r): # for each B-site atom 291 | X_idx = [i for i in range(len(X_list)) if X_list[i] < max_BX_distance] 292 | if len(X_idx) != 6: 293 | raise ValueError(f"The number of X site atoms connected to B site atom no.{B_site} is not 6 but {len(X_idx)}. \n") 294 | 295 | bx_raw = Xpos_frame[X_idx,:] - Bpos_frame[B_site,:] 296 | bx = octahedra_coords_into_bond_vectors(bx_raw,mymat) 297 | 298 | if rotated: 299 | order1 = match_bx_orthogonal_rotated(bx,rotmat) 300 | else: 301 | order1 = match_bx_orthogonal(bx) 302 | neigh_list[B_site,:] = np.array(X_idx)[order1] 303 | 304 | 305 | neigh_list = neigh_list.astype(int) 306 | 307 | return neigh_list 308 | 309 | 310 | 311 | def fit_octahedral_network_defect_tol(Bpos_frame,Xpos_frame,r,mymat,fpg_val_BX,structure_type): 312 | """ 313 | Resolve the octahedral connectivity with a defect tolerance. 314 | 315 | Args: 316 | Bpos_frame (numpy.ndarray): Coordinate array of B-sites. 317 | Xpos_frame (numpy.ndarray): Coordinate array of X-sites. 318 | r (numpy.ndarray): Distance matrix of B-X sites. 319 | mymat (numpy.ndarray): Lattice matrix of the frame. 320 | fpg_val_BX (list): Defined B-X bond information at the beginning of the class. 321 | structure_type (int): Structure type indicating orientation and connectivity of octahedra. 322 | 323 | Returns: 324 | numpy.ndarray: The B-X connectivity matrix of octahedra with shape (N, 6). 325 | """ 326 | 327 | try: 328 | max_BX_distance = find_population_gap(r, fpg_val_BX[0], fpg_val_BX[1], tol=5) # tol is set for extrame cases with defects 329 | 330 | bxs = [] 331 | bxc = [] 332 | for B_site, X_list in enumerate(r): # for each B-site atom 333 | X_idx = [i for i in range(len(X_list)) if X_list[i] < max_BX_distance] 334 | bxc.append(len(X_idx)) 335 | bxs.append(X_idx) 336 | bxc = np.array(bxc) 337 | 338 | ndef = None 339 | if np.amax(bxc) != 6 or np.amin(bxc) != 6: 340 | ndef = np.sum(bxc!=6) 341 | if np.amax(bxc) < 8 and np.amin(bxc) > 4: # benign defects 342 | print(f"!Structure Resolving: the initial structure contains {ndef} out of {len(bxc)} octahedra with defects. The algorithm has screened them out of the population. ") 343 | else: # bad defects 344 | raise TypeError("!Structure Resolving: the initial structure contains octahedra with complex defect configuration, leading to unresolvable connectivity. ") 345 | 346 | except (ValueError,TypeError): 347 | closebx = np.argsort(r, axis=0)[:2, :] 348 | binx, bcount = np.unique(closebx, return_counts=True) 349 | if (not np.array_equal(binx,np.arange(0,r.shape[0]))) or (not np.array_equal(bcount,np.ones_like(bcount)*6)): 350 | raise ValueError("!Structure resolving: Can't separate the different neighbours, please check the fpg_val values are correct according to the guidance at the beginning of the Trajectory class, or check if initial structure is defected or too distorted. ") 351 | #print("!Sructure Resolving: Detected relative high deviation in the B-X connectivity, used a more aggresive fit, please monitor the outputs carefully. ") 352 | bxs = convert_xb_to_bx(closebx) 353 | 354 | neigh_list = np.zeros((Bpos_frame.shape[0],6)) 355 | ref_initial = np.zeros((Bpos_frame.shape[0],3,3)) 356 | #bxfit_rmsd = [] 357 | for B_site, bxcom in enumerate(bxs): # for each B-site atom 358 | if len(bxcom) == 6: 359 | #rmsd = None 360 | try: # remove this try for strict octahedral match 361 | bx_raw = Xpos_frame[bxcom,:] - Bpos_frame[B_site,:] 362 | bx = octahedra_coords_into_bond_vectors(bx_raw,mymat) 363 | 364 | if structure_type == 1: 365 | order1 = match_bx_orthogonal(bx) 366 | neigh_list[B_site,:] = np.array(bxcom)[order1].astype(int) 367 | elif structure_type == 2: 368 | order1, _, rmsd = match_bx_arbitrary(bx) 369 | neigh_list[B_site,:] = np.array(bxcom)[order1].astype(int) 370 | elif structure_type == 3: 371 | order1, ref1, rmsd = match_bx_arbitrary(bx) 372 | neigh_list[B_site,:] = np.array(bxcom)[order1].astype(int) 373 | ref_initial[B_site,:] = ref1 374 | #bxfit_rmsd.append(rmsd) 375 | 376 | except ValueError: 377 | if structure_type in (1,2): 378 | neigh_list[B_site,:] = np.nan 379 | else: 380 | neigh_list[B_site,:] = np.nan 381 | ref_initial[B_site,:] = np.nan # til here 382 | 383 | else: 384 | if structure_type in (1,2): 385 | neigh_list[B_site,:] = np.nan 386 | else: 387 | neigh_list[B_site,:] = np.nan 388 | ref_initial[B_site,:] = np.nan 389 | 390 | if np.sum(np.isnan(neigh_list[:,0])) > neigh_list.shape[0]*0.1 and np.sum(np.isnan(neigh_list[:,0]))>1: 391 | raise ValueError(f"There are {np.sum(np.isnan(neigh_list[:,0]))} out of {neigh_list.shape[0]} octahedra contains unsolvable B-X connections.") 392 | #elif np.sum(np.isnan(neigh_list[:,0])) != 0: 393 | # print(f"!Structural Resolving: There are {np.sum(np.isnan(neigh_list[:,0]))} out of {neigh_list.shape[0]} octahedra contains unsolvable B-X connections.") 394 | 395 | if structure_type in (1,2): 396 | return neigh_list 397 | else: 398 | return neigh_list, ref_initial 399 | 400 | 401 | def fit_octahedral_network_defect_tol_non_orthogonal(Bpos_frame,Xpos_frame,r,mymat,fpg_val_BX,structure_type,rotmat): 402 | """ 403 | Resolve the octahedral connectivity with a defect tolerance. 404 | 405 | Args: 406 | Bpos_frame (numpy.ndarray): Coordinate array of B-sites. 407 | Xpos_frame (numpy.ndarray): Coordinate array of X-sites. 408 | r (numpy.ndarray): Distance matrix of B-X sites. 409 | mymat (numpy.ndarray): Lattice matrix of the frame. 410 | fpg_val_BX (list): Defined B-X bond information at the beginning of the class. 411 | structure_type (int): Structure type indicating orientation and connectivity of octahedra. 412 | rotmat (numpy.ndarray): Rotation matrix of the structure to align with orthogonal directions. 413 | 414 | Returns: 415 | numpy.ndarray: The B-X connectivity matrix of octahedra with shape (N, 6). 416 | """ 417 | 418 | #if structure_type != 1: 419 | # raise TypeError("The non-orthogonal initial structure can only be analysed under the 3C (corner-sharing) connectivity. ") 420 | #rotmat = np.linalg.inv(rotmat) 421 | 422 | try: 423 | max_BX_distance = find_population_gap(r, fpg_val_BX[0], fpg_val_BX[1], tol=5) 424 | 425 | bxs = [] 426 | bxc = [] 427 | for B_site, X_list in enumerate(r): # for each B-site atom 428 | X_idx = [i for i in range(len(X_list)) if X_list[i] < max_BX_distance] 429 | bxc.append(len(X_idx)) 430 | bxs.append(X_idx) 431 | bxc = np.array(bxc) 432 | 433 | ndef = None 434 | if np.amax(bxc) != 6 or np.amin(bxc) != 6: 435 | ndef = np.sum(bxc!=6) 436 | if np.amax(bxc) == 7 and np.amin(bxc) == 5: # benign defects 437 | print(f"!Structure Resolving: the initial structure contains {ndef} out of {len(bxc)} octahedra with defects. The algorithm has screened them out of the population. ") 438 | else: # bad defects 439 | raise TypeError("!Structure Resolving: the initial structure contains octahedra with complex defect configuration, leading to unresolvable connectivity. ") 440 | 441 | except (ValueError,TypeError): 442 | closebx = np.argsort(r, axis=0)[:2, :] 443 | binx, bcount = np.unique(closebx, return_counts=True) 444 | if (not np.array_equal(binx,np.arange(0,r.shape[0]))) or (not np.array_equal(bcount,np.ones_like(bcount)*6)): 445 | raise ValueError("!Structure resolving: Can't separate the different neighbours, please check the fpg_val values are correct according to the guidance at the beginning of the Trajectory class, or check if initial structure is defected or too distorted. ") 446 | print("!Sructure Resolving: Detected relatively high deviation in the B-X connectivity, used a more aggresive fit, please monitor the outputs carefully. ") 447 | bxs = convert_xb_to_bx(closebx) 448 | 449 | neigh_list = np.zeros((Bpos_frame.shape[0],6)) 450 | ref_initial = np.zeros((Bpos_frame.shape[0],3,3)) 451 | for B_site, bxcom in enumerate(bxs): # for each B-site atom 452 | if len(bxcom) == 6: 453 | bx_raw = Xpos_frame[bxcom,:] - Bpos_frame[B_site,:] 454 | bx = octahedra_coords_into_bond_vectors(bx_raw,mymat) 455 | 456 | order1 = match_bx_orthogonal_rotated(bx,rotmat) 457 | neigh_list[B_site,:] = np.array(bxcom)[order1].astype(int) 458 | 459 | else: 460 | neigh_list[B_site,:] = np.nan 461 | 462 | return neigh_list 463 | 464 | 465 | def convert_xb_to_bx(xb): 466 | """ 467 | Convert the X-to-B connectivity matrix to the ordinary B-to-X connectivity matrix. 468 | 469 | Args: 470 | xb (numpy.ndarray): The X-to-B connectivity matrix of octahedra. 471 | 472 | Returns: 473 | numpy.ndarray: The B-X connectivity matrix with shape (N, 6). 474 | """ 475 | 476 | bx = [[] for i in range(int(xb.shape[1]/3))] 477 | for i in range(xb.shape[1]): 478 | bx[xb[0,i]].append(i) 479 | bx[xb[1,i]].append(i) 480 | bx = np.array(bx) 481 | assert bx.shape==(int(xb.shape[1]/3),6) 482 | 483 | return bx 484 | 485 | 486 | def find_polytype_network(Bpos_frame,Xpos_frame,r,mymat,neigh_list): 487 | """ 488 | Resolve the octahedral connectivity and output the polytype information. 489 | 490 | Args: 491 | Bpos_frame (numpy.ndarray): Coordinate array of B-sites. 492 | Xpos_frame (numpy.ndarray): Coordinate array of X-sites. 493 | r (numpy.ndarray): Distance matrix of B-X sites. 494 | mymat (numpy.ndarray): Lattice matrix of the frame. 495 | neigh_list (numpy.ndarray): The B-X connectivity matrix of octahedra with shape (N, 6). 496 | 497 | Returns: 498 | list: List of strings indicating the local polytype connecivity of each octahedron. 499 | list: List of arrays indicating the shared X-sites with neighbouring B-sites. 500 | dict: Dictionary of octahedra categories. 501 | """ 502 | 503 | if np.isnan(neigh_list).any(): 504 | raise TypeError("Polytype recognition is not compatible with initial structure with defects at the moment.") 505 | 506 | def category_register(cat,typestr,b): 507 | """ 508 | Register the octahedra into categories dict. 509 | """ 510 | if not typestr in cat: 511 | cat[typestr] = [] 512 | cat[typestr].append(b) 513 | return cat 514 | 515 | BBsearch = 10.0 # 8.0 516 | 517 | connectivity = [] 518 | conntypeStr = [] 519 | conntypeStrAll = [] 520 | conn_category = {} 521 | #plt.hist(dm.reshape(-1,),bins=100,range=[0,15]) 522 | for B0, B1_list in enumerate(r): # for each B-site atom 523 | B1 = [i for i in range(len(B1_list)) if (B1_list[i] < BBsearch and i != B0)] 524 | if len(B1) == 0: 525 | raise ValueError(f"Can't find any other B-site around B-site number {B0} within a search radius of {BBsearch} angstrom. ") 526 | 527 | conn = np.empty((0,2)) 528 | for b1 in B1: 529 | intersect = list(set(neigh_list[B0,:]).intersection(neigh_list[b1,:])) 530 | if len(intersect) > 0: 531 | if len(intersect) == 2: #consider special case PBC effect for case 'intersect 2' 532 | bond_raw = Xpos_frame[intersect,:] - Bpos_frame[B0,:] 533 | bond0 = apply_pbc_cart_vecs(bond_raw, mymat) 534 | bond_raw = Xpos_frame[intersect,:] - Bpos_frame[b1,:] 535 | bond1 = apply_pbc_cart_vecs(bond_raw, mymat) 536 | dots = np.sum((bond0/np.linalg.norm(bond0,axis=1).reshape(2,1))*(bond1/np.linalg.norm(bond1,axis=1).reshape(2,1)), axis=1) 537 | if np.amax(dots) < -0.8: # effectively cornor-sharing 538 | intersect = [intersect[0]] 539 | conn = np.concatenate((conn,np.array([[b1,len(intersect)]]))).astype(int) 540 | 541 | if conn.shape[0] == 0: 542 | conntypeStr.append("isolated") 543 | conntypeStrAll.append("isolated") 544 | connectivity.append(conn) 545 | conn_category = category_register(conn_category, "isolated", B0) 546 | 547 | else: 548 | conntype = set(list(conn[:,1])) 549 | if len(conntype-{1,2,3}) != 0: 550 | raise TypeError(f"Found an unexpected connectivity type {conntype}. ") 551 | 552 | if len(conntype) == 1: # having only one connectivity type 553 | if conntype == {1}: 554 | conntypeStr.append("corner") 555 | conntypeStrAll.append("corner") 556 | conn_category = category_register(conn_category, "corner", B0) 557 | elif conntype == {2}: 558 | conntypeStr.append("edge") 559 | conntypeStrAll.append("edge") 560 | conn_category = category_register(conn_category, "edge", B0) 561 | elif conntype == {3}: 562 | conntypeStr.append("face") 563 | conntypeStrAll.append("face") 564 | conn_category = category_register(conn_category, "face", B0) 565 | else: 566 | strt = [] 567 | for ct in conntype: 568 | if ct == 1: 569 | strt.append("corner") 570 | conntypeStrAll.append("corner") 571 | elif ct == 2: 572 | strt.append("edge") 573 | conntypeStrAll.append("edge") 574 | elif ct == 3: 575 | strt.append("face") 576 | conntypeStrAll.append("face") 577 | strt = "+".join(strt) 578 | conntypeStr.append(strt) 579 | conn_category = category_register(conn_category, strt, B0) 580 | 581 | connectivity.append(conn) 582 | 583 | if len(set(conntypeStr)) == 1: 584 | print(f"Octahedral connectivity: {list(set(conntypeStr))[0]}-sharing") 585 | else: 586 | conntypestr = "+".join(list(set(conntypeStrAll))) 587 | print(f"Octahedral connectivity: mixed - {conntypestr}") 588 | 589 | return conntypeStr, connectivity, conn_category 590 | 591 | 592 | def pseudocubic_lat(traj, # the main class instance 593 | allow_equil = 0, # take the first x fraction of the trajectory as equilibration 594 | zdrc = 2, # zdrc: the z direction for pseudo-cubic lattice paramter reading. a,b,c = 0,1,2 595 | lattice_tilt = None, # if primary B-B bond is not along orthogonal directions, in degrees 596 | ): 597 | 598 | """ 599 | Compute the pseudo-cubic lattice parameters. 600 | 601 | Args: 602 | traj (Trajectory): The main class instance. 603 | allow_equil (float): The fraction of the trajectory to be considered as equilibration. 604 | zdrc (int): The z direction for pseudo-cubic lattice paramter reading. a,b,c = 0,1,2. 605 | lattice_tilt (numpy.ndarray): If the primary B-B bond is not along orthogonal directions, in degrees. 606 | 607 | Returns: 608 | numpy.ndarray: The pseudo-cubic lattice parameters. 609 | """ 610 | 611 | from scipy.stats import norm 612 | from scipy.stats import binned_statistic_dd as binstat 613 | 614 | def run_pcl(zi,): 615 | 616 | dims = [0,1,2] 617 | dims.remove(zi) 618 | grided = bin_indices[dims,:].T 619 | 620 | ma = np.array([[[0,1,1],[0,1,-1],[2,0,0]], 621 | [[-1,0,1],[1,0,1],[0,2,0]], 622 | [[-1,1,0],[1,1,0],[0,0,2]]]) # setting of pseuso-cubic lattice parameter vectors 623 | 624 | mappings = BBdist*ma 625 | maps = mappings[zi,:,:] # select out of x,y,z 626 | maps_frac = ma[zi,:,:] 627 | 628 | if not lattice_tilt is None: 629 | maps = np.dot(maps,lattice_tilt) 630 | 631 | match_tol = 1.5 # tolerance of the coordinate difference is set to 1.5 angstroms 632 | 633 | if len(blist) <= 8: 634 | Im = np.empty((0,3)) 635 | for i in range(-1,2): # create images of all surrounding cells 636 | for j in range(-1,2): 637 | for k in range(-1,2): 638 | Im = np.concatenate((Im,np.array([[i,j,k]])),axis=0) 639 | 640 | Bfc = st0.frac_coords[blist,:] # frac_coords of B site atoms 641 | 642 | Bnet = np.zeros((Bfc.shape[0],3,2)) # find the corresponding atoms (with image) for lattice parameter calc. 643 | # dim0: for each B atom, dim1: a,b,c vector of that atom, dim2: [B atom number, image] 644 | Bnet[:] = np.nan 645 | for B1 in range(Bfc.shape[0]): 646 | for im in range(Im.shape[0]): 647 | for B2 in range(Bfc.shape[0]): 648 | vec = st0.lattice.get_cartesian_coords(Im[im,:] + Bfc[B1,:] - Bfc[B2,:]) 649 | for i in range(3): 650 | if np.linalg.norm(vec-maps[i,:]) < match_tol: 651 | 652 | if np.isnan(Bnet[B1,i,0]) and np.isnan(Bnet[B1,i,1]): 653 | Bnet[B1,i,0] = B2 654 | Bnet[B1,i,1] = im 655 | else: 656 | raise ValueError("A second fitted neighbour is written to the site. Decrease match_tol") 657 | 658 | if np.isnan(Bnet).any(): 659 | raise TypeError("Some of the neightbours are not found (still nan). Increase match_tol") 660 | Bnet = Bnet.astype(int) 661 | 662 | # find the correct a and b vectors 663 | #code = np.remainder(np.sum(grided,axis=1),2) 664 | code = np.remainder(grided[:,1],2) 665 | 666 | Bfrac = get_frac_from_cart(traj.Allpos[:,traj.Bindex,:],traj.latmat) 667 | lats = traj.latmat 668 | 669 | Lati_on = np.zeros((len(range(round(traj.nframe*allow_equil),traj.nframe)),Bfc.shape[0],3)) 670 | 671 | for ite, fr in enumerate(range(round(traj.nframe*allow_equil),traj.nframe)): 672 | Bfc = Bfrac[fr,:,:] 673 | templat = np.empty((Bfc.shape[0],3)) 674 | for B1 in range(Bfc.shape[0]): 675 | for i in range(3): 676 | if i == 2: # identified z-axis 677 | templat[B1,i] = np.linalg.norm(np.dot((Im[Bnet[B1,i,1],:] + Bfc[B1,:] - Bfc[Bnet[B1,i,0]]),lats[fr,:,:]))/2 678 | else: # x- and y-axis 679 | temp = np.linalg.norm(np.dot((Im[Bnet[B1,i,1],:] + Bfc[B1,:] - Bfc[Bnet[B1,i,0]]),lats[fr,:,:]))/np.sqrt(2) 680 | templat[B1,i] = temp 681 | Lati_on[ite,:] = templat 682 | 683 | if zi != 2: 684 | l0 = np.expand_dims(Lati_on[:,:,0], axis=2) 685 | l1 = np.expand_dims(Lati_on[:,:,1], axis=2) 686 | l2 = np.expand_dims(Lati_on[:,:,2], axis=2) 687 | if zi == 0: 688 | Lati_on = np.concatenate((l2,l1,l0),axis=2) 689 | if zi == 1: 690 | Lati_on = np.concatenate((l0,l2,l1),axis=2) 691 | 692 | Lati_off = np.zeros((len(range(round(traj.nframe*allow_equil),traj.nframe)),Bfc.shape[0],3)) 693 | 694 | for ite, fr in enumerate(range(round(traj.nframe*allow_equil),traj.nframe)): 695 | Bfc = Bfrac[fr,:,:] 696 | templat = np.empty((Bfc.shape[0],3)) 697 | for B1 in range(Bfc.shape[0]): 698 | for i in range(3): 699 | if i == 2: # identified z-axis 700 | templat[B1,i] = np.linalg.norm(np.dot((Im[Bnet[B1,i,1],:] + Bfc[B1,:] - Bfc[Bnet[B1,i,0]]),lats[fr,:,:]))/2 701 | else: # x- and y-axis 702 | temp = np.linalg.norm(np.dot((Im[Bnet[B1,i,1],:] + Bfc[B1,:] - Bfc[Bnet[B1,i,0]]),lats[fr,:,:]))/np.sqrt(2) 703 | if code[B1] == 1: 704 | i = (i+1)%2 705 | templat[B1,i] = temp 706 | Lati_off[ite,:] = templat 707 | 708 | if zi != 2: 709 | l0 = np.expand_dims(Lati_off[:,:,0], axis=2) 710 | l1 = np.expand_dims(Lati_off[:,:,1], axis=2) 711 | l2 = np.expand_dims(Lati_off[:,:,2], axis=2) 712 | if zi == 0: 713 | Lati_off = np.concatenate((l2,l1,l0),axis=2) 714 | if zi == 1: 715 | Lati_off = np.concatenate((l0,l2,l1),axis=2) 716 | 717 | std_off = norm.fit(Lati_off.reshape(-1,3)[:,0])[1]+norm.fit(Lati_off.reshape(-1,3)[:,1])[1] 718 | std_on = norm.fit(Lati_on.reshape(-1,3)[:,0])[1]+norm.fit(Lati_on.reshape(-1,3)[:,1])[1] 719 | 720 | if std_off > std_on: 721 | return Lati_on 722 | else: 723 | return Lati_off 724 | 725 | else: # cells larger than 222 supercell 726 | 727 | #Bcart = st0.cart_coords[blist,:] 728 | Bnet = np.zeros((len(blist),3)) 729 | 730 | for B1 in range(len(blist)): 731 | for v in range(3): 732 | pos1 = bin_indices[:,B1]+maps_frac[v,:] 733 | pos1[pos1>tss] = pos1[pos1>tss]-tss 734 | pos1[pos1<1] = pos1[pos1<1]+tss 735 | Bnet[B1,v] = np.where(~(bin_indices-pos1[:,np.newaxis]).any(axis=0))[0][0] 736 | Bnet = Bnet.astype(int) 737 | 738 | # find the correct a and b vectors 739 | #code = np.remainder(np.sum(grided,axis=1),2) 740 | code = np.remainder(grided[:,1],2) 741 | trajnum = list(range(round(traj.nframe*allow_equil),traj.nframe,traj.read_every)) 742 | lats = traj.latmat[trajnum,:] 743 | Bpos = traj.Allpos[trajnum,:,:][:,traj.Bindex,:] 744 | 745 | # filtering direction 1 746 | Lati_off = np.empty((len(trajnum),len(blist),3)) 747 | for B1 in range(len(blist)): 748 | for i in range(3): 749 | if i == 2: # identified z-axis 750 | Lati_off[:,B1,i] = (np.linalg.norm(apply_pbc_cart_vecs((Bpos[:,B1,:]-Bpos[:,Bnet[B1,i],:])[:,np.newaxis,:],lats),axis=2)/2).reshape(-1,) 751 | else: # x- and y-axis 752 | temp = (np.linalg.norm(apply_pbc_cart_vecs((Bpos[:,B1,:]-Bpos[:,Bnet[B1,i],:])[:,np.newaxis,:],lats),axis=2)/np.sqrt(2)).reshape(-1,) 753 | if code[B1] == 1: 754 | i = (i+1)%2 755 | Lati_off[:,B1,i] = temp 756 | 757 | 758 | if zi != 2: 759 | l0 = np.expand_dims(Lati_off[:,:,0], axis=2) 760 | l1 = np.expand_dims(Lati_off[:,:,1], axis=2) 761 | l2 = np.expand_dims(Lati_off[:,:,2], axis=2) 762 | if zi == 0: 763 | Lati_off = np.concatenate((l2,l1,l0),axis=2) 764 | if zi == 1: 765 | Lati_off = np.concatenate((l0,l2,l1),axis=2) 766 | 767 | # filtering direction 2 768 | Lati_on = np.empty((len(trajnum),len(blist),3)) 769 | for B1 in range(len(blist)): 770 | for i in range(3): 771 | if i == 2: # identified z-axis 772 | Lati_on[:,B1,i] = (np.linalg.norm(apply_pbc_cart_vecs((Bpos[:,B1,:]-Bpos[:,Bnet[B1,i],:])[:,np.newaxis,:],lats),axis=2)/2).reshape(-1,) 773 | else: # x- and y-axis 774 | temp = (np.linalg.norm(apply_pbc_cart_vecs((Bpos[:,B1,:]-Bpos[:,Bnet[B1,i],:])[:,np.newaxis,:],lats),axis=2)/np.sqrt(2)).reshape(-1,) 775 | Lati_on[:,B1,i] = temp 776 | 777 | if zi != 2: 778 | l0 = np.expand_dims(Lati_on[:,:,0], axis=2) 779 | l1 = np.expand_dims(Lati_on[:,:,1], axis=2) 780 | l2 = np.expand_dims(Lati_on[:,:,2], axis=2) 781 | if zi == 0: 782 | Lati_on = np.concatenate((l2,l1,l0),axis=2) 783 | if zi == 1: 784 | Lati_on = np.concatenate((l0,l2,l1),axis=2) 785 | 786 | std_off = norm.fit(Lati_off.reshape(-1,3)[:,0])[1]+norm.fit(Lati_off.reshape(-1,3)[:,1])[1] 787 | std_on = norm.fit(Lati_on.reshape(-1,3)[:,0])[1]+norm.fit(Lati_on.reshape(-1,3)[:,1])[1] 788 | 789 | if std_off > std_on: 790 | return Lati_on 791 | else: 792 | return Lati_off 793 | 794 | # begin of main function 795 | st0 = traj.st0 796 | Bpos = st0.cart_coords[traj.Bindex,:] 797 | blist = traj.Bindex 798 | if len(blist) == 0: 799 | raise TypeError("Detected zero B site atoms, check B_list") 800 | 801 | #dm = distance_matrix_handler(Bpos,Bpos,st0.lattice.matrix,traj.at0.cell,traj.at0.pbc,traj.complex_pbc) 802 | #dm = dm.reshape((dm.shape[0]**2,1)) 803 | BBdist = traj.default_BB_dist #np.mean(dm[np.logical_and(dm>0.1,dm<7)]) 804 | 805 | #if not traj._non_orthogonal: 806 | celldim = np.cbrt(len(traj.Bindex)) 807 | if not celldim.is_integer(): 808 | raise ValueError("The cell is not in cubic shape, please use lat_method = 1. ") 809 | 810 | tss = traj.supercell_size 811 | cc = st0.frac_coords[blist] 812 | 813 | for i in range(3): 814 | if np.amax(cc[:,i])>(1-1/tss/4) and np.amin(cc[:,i])<1/tss/4: 815 | addit = np.zeros((1,3)) 816 | addit[0,i] = 1/tss/2 817 | cc = cc+addit 818 | cc[cc>1] = cc[cc>1]-1 819 | cc[cc<0] = cc[cc<0]+1 820 | 821 | clims = np.array([[(np.quantile(cc[:,0],1/(tss**2))+np.amin(cc[:,0]))/2,(np.quantile(cc[:,0],1-1/(tss**2))+np.amax(cc[:,0]))/2], 822 | [(np.quantile(cc[:,1],1/(tss**2))+np.amin(cc[:,1]))/2,(np.quantile(cc[:,1],1-1/(tss**2))+np.amax(cc[:,1]))/2], 823 | [(np.quantile(cc[:,2],1/(tss**2))+np.amin(cc[:,2]))/2,(np.quantile(cc[:,2],1-1/(tss**2))+np.amax(cc[:,2]))/2]]) 824 | 825 | bin_indices = binstat(cc, None, 'count', bins=[tss,tss,tss], 826 | range=[[clims[0,0]-0.5*(1/tss), 827 | clims[0,1]+0.5*(1/tss)], 828 | [clims[1,0]-0.5*(1/tss), 829 | clims[1,1]+0.5*(1/tss)], 830 | [clims[2,0]-0.5*(1/tss), 831 | clims[2,1]+0.5*(1/tss)]], 832 | expand_binnumbers=True).binnumber 833 | # validate the binning 834 | atom_indices = np.array([bin_indices[0,i]+(bin_indices[1,i]-1)*tss+(bin_indices[2,i]-1)*tss**2 for i in range(bin_indices.shape[1])]) 835 | bincount = np.unique(atom_indices, return_counts=True)[1] 836 | if len(bincount) != tss**3: 837 | raise TypeError("Incorrect number of bins. ") 838 | if max(bincount) != min(bincount): 839 | raise ValueError("Not all bins contain exactly the same number of atoms (1). ") 840 | 841 | if zdrc == -1: 842 | Lat = [] 843 | for zi in [0,1,2]: 844 | Lat.append(run_pcl(zi)) 845 | return Lat 846 | else: 847 | return run_pcl(zdrc) 848 | 849 | 850 | def structure_time_average_ase(traj, start_ratio = 0.5, end_ratio = 0.98, cif_save_path = None, force_periodicity = False): 851 | """ 852 | Compute the time-averaged structure through the ASE interface. 853 | 854 | Args: 855 | traj (Trajectory): The main class instance. 856 | start_ratio (float): The starting ratio of the trajectory to be considered. 857 | end_ratio (float): The ending ratio of the trajectory to be considered. 858 | cif_save_path (str): The path to save the CIF file. 859 | force_periodicity (bool): If the periodic images are considered. 860 | 861 | Returns: 862 | pymatgen.Structure: The time-averaged Pymatgen structure object 863 | """ 864 | import pymatgen.io.ase 865 | # current problem: can't average lattice parameters and angles; can't deal with organic A-site 866 | 867 | if force_periodicity: 868 | lat_param = np.mean(traj.latmat[round(traj.nframe*start_ratio):round(traj.nframe*end_ratio),:],axis=0) 869 | ap = traj.Allpos[round(traj.nframe*start_ratio):round(traj.nframe*end_ratio),:] 870 | 871 | dispp = ap-ap[-1,:,:] 872 | dispp = apply_pbc_cart_vecs(dispp,traj.latmat[round(traj.nframe*start_ratio):round(traj.nframe*end_ratio),:]) 873 | 874 | carts = np.mean(dispp,axis=0)+ap[-1,:,:] 875 | 876 | else: 877 | frac = get_frac_from_cart(traj.Allpos, traj.latmat) 878 | lat_param = np.mean(traj.latmat[round(traj.nframe*start_ratio):round(traj.nframe*end_ratio),:],axis=0) 879 | CC = np.mean(frac[round(traj.nframe*start_ratio):round(traj.nframe*end_ratio),:],axis=0) 880 | carts = np.dot(CC,lat_param) 881 | 882 | 883 | at=pymatgen.io.ase.AseAtomsAdaptor.get_atoms(traj.st0) 884 | at.positions = carts 885 | 886 | at.set_cell(lat_param) 887 | struct = pymatgen.io.ase.AseAtomsAdaptor.get_structure(at) 888 | if cif_save_path: 889 | struct.to(filename = cif_save_path,fmt="cif") 890 | 891 | return struct 892 | 893 | 894 | def structure_time_average_ase_organic(traj, tavgspan, start_ratio = 0.5, end_ratio = 0.98, cif_save_path = None): 895 | """ 896 | Compute the time-averaged structure, with the organic A-site treated differently. 897 | 898 | Args: 899 | traj (Trajectory): The main class instance. 900 | tavgspan (int): The time span for averaging. 901 | start_ratio (float): The starting ratio of the trajectory to be considered. 902 | end_ratio (float): The ending ratio of the trajectory to be considered. 903 | cif_save_path (str): The path to save the CIF file. 904 | 905 | Returns: 906 | pymatgen.Structure: The time-averaged Pymatgen structure object 907 | """ 908 | import pymatgen.io.ase 909 | # current problem: can't average lattice parameters and angles; can't deal with organic A-site 910 | 911 | frac = get_frac_from_cart(traj.Allpos, traj.latmat) 912 | org_index = traj.Cindex+traj.Hindex+traj.Nindex 913 | 914 | endind = min(round(traj.nframe*end_ratio),round(traj.nframe*start_ratio)+tavgspan) 915 | 916 | # time averaging 917 | lat_param = np.mean(traj.latmat[round(traj.nframe*start_ratio):round(traj.nframe*end_ratio),:],axis=0) 918 | CC = np.mean(frac[round(traj.nframe*start_ratio):round(traj.nframe*end_ratio),:],axis=0) 919 | orgs = np.mean(frac[round(traj.nframe*start_ratio):endind,org_index,:],axis=0) 920 | CC[org_index,:] = orgs 921 | 922 | carts = np.dot(CC,lat_param) 923 | 924 | at=pymatgen.io.ase.AseAtomsAdaptor.get_atoms(traj.st0) 925 | at.positions = carts 926 | 927 | at.set_cell(lat_param) 928 | struct = pymatgen.io.ase.AseAtomsAdaptor.get_structure(at) 929 | if cif_save_path: 930 | struct.to(filename = cif_save_path,fmt="cif") 931 | 932 | return struct 933 | 934 | 935 | def simply_calc_distortion(traj): 936 | """ 937 | Compute the octahedral distortion of the time-averaged structure. 938 | 939 | Args: 940 | traj (Trajectory): The main class instance. 941 | 942 | Returns: 943 | numpy.ndarray: The octahedral distortion. 944 | numpy.ndarray: The standard deviations of octahedral distortion. 945 | """ 946 | 947 | struct = traj.tavg_struct 948 | neigh_list = traj.octahedra 949 | 950 | assert struct.atomic_numbers == traj.st0.atomic_numbers 951 | 952 | Bpos = struct.cart_coords[traj.Bindex,:] 953 | Xpos = struct.cart_coords[traj.Xindex,:] 954 | 955 | mymat = struct.lattice.matrix 956 | 957 | disto = np.empty((0,dist_dim)) 958 | Rmat = np.zeros((len(traj.Bindex),3,3)) 959 | Rmsd = np.zeros((len(traj.Bindex),1)) 960 | for B_site in range(len(traj.Bindex)): # for each B-site atom 961 | if np.isnan(neigh_list[B_site,:]).any(): 962 | a = np.empty((1,dist_dim)) 963 | a[:] = np.nan 964 | Rmat[B_site,:] = np.nan 965 | Rmsd[B_site] = np.nan 966 | disto = np.concatenate((disto,a),axis = 0) 967 | else: 968 | raw = Xpos[neigh_list[B_site,:].astype(int),:] - Bpos[B_site,:] 969 | bx = octahedra_coords_into_bond_vectors(raw,mymat) 970 | dist_val,rotmat,rmsd = calc_distortions_from_bond_vectors_full(bx) 971 | Rmat[B_site,:] = rotmat 972 | Rmsd[B_site] = rmsd 973 | disto = np.concatenate((disto,dist_val.reshape(1,dist_dim)),axis = 0) 974 | 975 | temp_var = np.var(disto,axis = 0) 976 | temp_std = np.divide(np.sqrt(temp_var),np.nanmean(disto, axis=0)) 977 | temp_dist = np.nanmean(disto,axis=0) 978 | 979 | # set all values under tol to be zero. 980 | tol = 1e-15 981 | for i in range(len(temp_dist)): 982 | if temp_dist[i] 0.1: 1151 | raise ValueError(f"The RMSD of fitting related to non-orthogonal frame mapping is too large (RMSD = {round(rmsd,4)})") 1152 | 1153 | rots = sstr.from_matrix(rotmat).as_euler('xyz', degrees=True) 1154 | rots = periodicity_fold(rots) 1155 | 1156 | for i in range(len(rots)): 1157 | if abs(rots[i]) < 0.01: 1158 | rots[i] = 0 1159 | 1160 | rotmat = sstr.from_rotvec(rots/180*np.pi).as_matrix() 1161 | 1162 | return rots, rotmat 1163 | 1164 | 1165 | def calc_distortion_from_order_manual(bx,ideal_coords,dict_basis): 1166 | """ 1167 | Compute the rotation of six bond vectors with arbitrary order, used in non-ortho structure. 1168 | 1169 | Args: 1170 | bx (numpy.ndarray): The six B-X bond vectors. 1171 | ideal_coords (numpy.ndarray): The ideal coordinates. 1172 | dict_basis (dict): The basis dictionary imported. 1173 | 1174 | Returns: 1175 | numpy.ndarray: The distortion amplitudes. 1176 | numpy.ndarray: The rotation matrix. 1177 | float: The RMSD of the fitting. 1178 | """ 1179 | from scipy.spatial.transform import Rotation as sstr 1180 | 1181 | irrep_distortions = [] 1182 | for irrep in dict_basis.keys(): 1183 | for elem in dict_basis[irrep]: 1184 | irrep_distortions.append(elem) 1185 | 1186 | # define "molecules" 1187 | pymatgen_molecule = Molecule( 1188 | species=[Element("Pb"),Element("H"),Element("He"),Element("Li"),Element("Be"),Element("B"),Element("I")], 1189 | coords=np.concatenate((np.zeros((1, 3)), bx), axis=0)) 1190 | 1191 | pymatgen_molecule_ideal = Molecule( 1192 | species=pymatgen_molecule.species, 1193 | coords=np.concatenate((np.zeros((1, 3)), ideal_coords), axis=0)) 1194 | 1195 | # transform 1196 | pymatgen_molecule,rotmat,rmsd = match_molecules_extra( 1197 | pymatgen_molecule, pymatgen_molecule_ideal) 1198 | 1199 | # project 1200 | distortion_amplitudes = calc_displacement_full( 1201 | pymatgen_molecule, pymatgen_molecule_ideal, irrep_distortions 1202 | ) 1203 | 1204 | # average 1205 | distortion_amplitudes = distortion_amplitudes * distortion_amplitudes 1206 | temp_list = [] 1207 | count = 0 1208 | for irrep in dict_basis: 1209 | dim = len(dict_basis[irrep]) 1210 | temp_list.append(np.sum(distortion_amplitudes[count:count + dim])) 1211 | count += dim 1212 | distortion_amplitudes = np.sqrt(temp_list) 1213 | 1214 | return distortion_amplitudes,rotmat,rmsd 1215 | 1216 | 1217 | def quick_match_octahedron(bx): 1218 | """ 1219 | Compute the rotation status of an octahedron with a reference. 1220 | Used in connectivity type 3. 1221 | 1222 | Args: 1223 | bx (numpy.ndarray): The six B-X bond vectors. 1224 | 1225 | Returns: 1226 | numpy.ndarray: The rotation matrix. 1227 | numpy.ndarray: The rotated coordinates. 1228 | float: The RMSD of the fitting. 1229 | """ 1230 | 1231 | def calc_match(bx): 1232 | # define "molecules" 1233 | pymatgen_molecule = Molecule( 1234 | species=[Element("Pb"),Element("I"),Element("I"),Element("I"),Element("I"),Element("I"),Element("I")], 1235 | coords=np.concatenate((np.zeros((1, 3)), bx), axis=0)) 1236 | 1237 | pymatgen_molecule_ideal = Molecule( 1238 | species=pymatgen_molecule.species, 1239 | coords=np.concatenate((np.zeros((1, 3)), ideal_coords), axis=0)) 1240 | 1241 | # transform 1242 | new_molecule,rotmat,rmsd = match_molecules_extra(pymatgen_molecule_ideal, pymatgen_molecule) 1243 | 1244 | return new_molecule,rotmat,rmsd 1245 | 1246 | # constants 1247 | ideal_coords = [[-1, 0, 0], 1248 | [0, -1, 0], 1249 | [0, 0, -1], 1250 | [0, 0, 1], 1251 | [0, 1, 0], 1252 | [1, 0, 0]] 1253 | 1254 | irrep_distortions = [] 1255 | for irrep in dict_basis.keys(): 1256 | for elem in dict_basis[irrep]: 1257 | irrep_distortions.append(elem) 1258 | 1259 | new_molecule,rotmat,rmsd = calc_match(bx) 1260 | 1261 | if rmsd > 0.1: # in case of very rare mis-match, shuffle the entry to try again 1262 | fitting_tol = 0.3 1263 | order = [] 1264 | for ix in range(6): 1265 | fits = np.dot(bx,np.array(ideal_coords)[ix,:]) 1266 | if not (fits[fits.argsort()[-1]]-fits[fits.argsort()[-2]] > fitting_tol): 1267 | #print(bx,fits) 1268 | raise ValueError(f"The fitting of initial octahedron config to ideal coords is not successful. The initial structure may be too tilted or distorted. The fitting confidence is {round(fits[fits.argsort()[-1]]-fits[fits.argsort()[-2]],3)}, if this is not too far from the tolerance {fitting_tol}, consider lower the fitting_tol in this function. ") 1269 | order.append(np.argmax(fits)) 1270 | new_molecule,rotmat,rmsd = calc_match(bx[order,:]) 1271 | if rmsd > 0.1: 1272 | print(bx[order,:]) 1273 | #raise RuntimeError("The fitting of initial octahedron config to ideal coords is not successful.") 1274 | 1275 | new_coords = np.matmul(ideal_coords,rotmat) 1276 | #new_coords = new_molecule.cart_coords[1:,:] 1277 | 1278 | return np.linalg.inv(rotmat), new_coords, rmsd 1279 | 1280 | 1281 | def match_bx_orthogonal(bx): 1282 | """ 1283 | Find the order of atoms in octahedron through matching with the reference. 1284 | Used in structure_type 1. 1285 | 1286 | Args: 1287 | bx (numpy.ndarray): The six B-X bond vectors. 1288 | 1289 | Returns: 1290 | list: The order of atoms matching to the reference. 1291 | """ 1292 | fitting_tol = 0.3 1293 | ideal_coords = np.array([[-1, 0, 0], 1294 | [0, -1, 0], 1295 | [0, 0, -1], 1296 | [0, 0, 1], 1297 | [0, 1, 0], 1298 | [1, 0, 0]]) 1299 | order = [] 1300 | for ix in range(6): 1301 | fits = np.dot(bx,ideal_coords[ix,:]) 1302 | if not (fits[fits.argsort()[-1]]-fits[fits.argsort()[-2]] > fitting_tol): 1303 | #print(bx,fits) 1304 | raise ValueError(f"The fitting of initial octahedron config to ideal coords is not successful. The initial structure may be too tilted or distorted. The fitting confidence is {round(fits[fits.argsort()[-1]]-fits[fits.argsort()[-2]],3)}, if this is not too far from the tolerance {fitting_tol}, consider lower the fitting_tol in this function. ") 1305 | order.append(np.argmax(fits)) 1306 | if len(set(order)) != 6: raise ValueError # double-check sanity 1307 | return order 1308 | 1309 | 1310 | def match_bx_orthogonal_rotated(bx,rotmat): 1311 | """ 1312 | Find the order of atoms in octahedron through matching with the reference. 1313 | Used in structure_type 2. 1314 | 1315 | Args: 1316 | bx (numpy.ndarray): The six B-X bond vectors. 1317 | rotmat (numpy.ndarray): The rotation matrix for alignment. 1318 | 1319 | Returns: 1320 | list: The order of atoms matching to the reference. 1321 | """ 1322 | fitting_tol = 0.3 1323 | ideal_coords = np.array([[-1, 0, 0], 1324 | [0, -1, 0], 1325 | [0, 0, -1], 1326 | [0, 0, 1], 1327 | [0, 1, 0], 1328 | [1, 0, 0]]) 1329 | ideal_coords = np.matmul(ideal_coords,rotmat) 1330 | order = [] 1331 | for ix in range(6): 1332 | fits = np.dot(bx,ideal_coords[ix,:]) 1333 | if not (fits[fits.argsort()[-1]]-fits[fits.argsort()[-2]]) > fitting_tol: 1334 | print(bx,fits) 1335 | raise ValueError(f"The fitting of initial octahedron config to ideal coords is not successful. The initial structure may be too tilted or distorted. The fitting confidence is {round(fits[fits.argsort()[-1]]-fits[fits.argsort()[-2]],3)}, if this is not too far from the tolerance {fitting_tol}, consider lower the fitting_tol in this function. ") 1336 | order.append(np.argmax(fits)) 1337 | assert len(set(order)) == 6 # double-check sanity 1338 | return order 1339 | 1340 | 1341 | def match_bx_arbitrary(bx): 1342 | """ 1343 | Find the order of atoms in octahedron through matching with the reference. 1344 | Used in structure_type 3. 1345 | 1346 | Args: 1347 | bx (numpy.ndarray): The six B-X bond vectors. 1348 | 1349 | Returns: 1350 | list: The order of atoms matching to the reference. 1351 | numpy.ndarray: rotation matrices for alignment. 1352 | float: The RMSD of the fitting. 1353 | """ 1354 | fitting_tol = 0.4 1355 | #confidence_bound = [0.8,0.2] 1356 | ref_rot, ideal_coords, rmsd = quick_match_octahedron(bx) 1357 | 1358 | order = [] 1359 | for ix in range(6): 1360 | fits = np.dot(bx,ideal_coords[ix,:]) 1361 | #if not (fits[fits.argsort()[-1]] > confidence_bound[0] and fits[fits.argsort()[-2]] < confidence_bound[1]): 1362 | if not (fits[fits.argsort()[-1]]-fits[fits.argsort()[-2]]) > fitting_tol: 1363 | print(bx,fits) 1364 | raise ValueError("The fitting of initial octahedron config to rotated reference is not successful. This may happen if the initial configuration is too distorted. ") 1365 | order.append(np.argmax(fits)) 1366 | assert len(set(order)) == 6 # double-check sanity 1367 | return order, ref_rot, rmsd 1368 | 1369 | 1370 | def match_molecules_extra(molecule_transform, molecule_reference): 1371 | """ 1372 | Hungarian matching to output the transformation matrix 1373 | 1374 | Args: 1375 | molecule_transform (pymatgen.Molecule): The molecule to be transformed. 1376 | molecule_reference (pymatgen.Molecule): The reference molecule. 1377 | 1378 | Returns: 1379 | pymatgen.Molecule: The transformed molecule. 1380 | numpy.ndarray: The rotation matrix. 1381 | float: The RMSD of the fitting. 1382 | """ 1383 | 1384 | # match molecules 1385 | (inds, u, v, rmsd) = HungarianOrderMatcher( 1386 | molecule_reference).match(molecule_transform) 1387 | 1388 | # affine transform 1389 | molecule_transform.apply_operation(SymmOp( 1390 | np.concatenate(( 1391 | np.concatenate((u.T, v.reshape(3, 1)), axis=1), 1392 | [np.zeros(4)]), axis=0 1393 | ))) 1394 | molecule_transform._sites = np.array( 1395 | molecule_transform._sites)[inds].tolist() 1396 | 1397 | return molecule_transform,u,rmsd 1398 | 1399 | 1400 | def calc_displacement(pymatgen_molecule, pymatgen_molecule_ideal, irrep_distortions): 1401 | """ 1402 | Compute the displacement of the atoms in the molecule for matching. 1403 | 1404 | Args: 1405 | pymatgen_molecule (pymatgen.Molecule): The molecule to be transformed. 1406 | pymatgen_molecule_ideal (pymatgen.Molecule): The reference molecule. 1407 | irrep_distortions (numpy.ndarray): The basis irreps for matching. 1408 | 1409 | Returns: 1410 | numpy.ndarray: The processed displacement of the atoms. 1411 | """ 1412 | 1413 | return np.tensordot(irrep_distortions, 1414 | (pymatgen_molecule.cart_coords - pymatgen_molecule_ideal.cart_coords).ravel()[3:], 1415 | axes=1) 1416 | 1417 | def calc_displacement_full(pymatgen_molecule, pymatgen_molecule_ideal, irrep_distortions): 1418 | """ 1419 | Compute the displacement of the atoms in the molecule for matching. 1420 | 1421 | Args: 1422 | pymatgen_molecule (pymatgen.Molecule): The molecule to be transformed. 1423 | pymatgen_molecule_ideal (pymatgen.Molecule): The reference molecule. 1424 | irrep_distortions (numpy.ndarray): The basis irreps for matching. 1425 | 1426 | Returns: 1427 | numpy.ndarray: The processed displacement of the atoms. 1428 | """ 1429 | 1430 | return np.tensordot(irrep_distortions, 1431 | (pymatgen_molecule.cart_coords - pymatgen_molecule_ideal.cart_coords).ravel(), 1432 | axes=1) 1433 | 1434 | 1435 | def match_mixed_halide_octa_dot(bx,hals): 1436 | """ 1437 | Find the configuration class in binary-mixed halide octahedron. 1438 | 1439 | Args: 1440 | bx (numpy.ndarray): The six B-X bond vectors. 1441 | hals (list): The list of symbols of halide species. 1442 | 1443 | Returns: 1444 | tuple: The configuration class and the configuration 1445 | """ 1446 | 1447 | distinct_thresh = 20 1448 | 1449 | ideal_coords = [ 1450 | [-1, 0, 0], 1451 | [0, -1, 0], 1452 | [0, 0, -1], 1453 | [0, 0, 1], 1454 | [0, 1, 0], 1455 | [1, 0, 0]] 1456 | 1457 | brnum = hals.count('Br') 1458 | 1459 | if brnum == 0: 1460 | return (0,0),0 1461 | 1462 | elif brnum == 1: 1463 | return (1,0),1 1464 | 1465 | elif brnum == 2: 1466 | brs = [] 1467 | for i, h in enumerate(hals): 1468 | if h=="Br": 1469 | brs.append(i) 1470 | 1471 | ang = np.dot(bx[brs[0],:],bx[brs[1],:]) 1472 | 1473 | if ang < -0.7: # planar form 1474 | return (2,1),3 # planar form 1475 | elif abs(ang) < 0.3: # right-angle form 1476 | return (2,0),2 # right-angle form 1477 | else: 1478 | print(ang) 1479 | raise ValueError("Can't distinguish the local halide environment. Angular term printed above. ") 1480 | 1481 | elif brnum == 3: 1482 | brs = [] 1483 | for i, h in enumerate(hals): 1484 | if h=="Br": 1485 | brs.append(i) 1486 | 1487 | ang = [np.dot(bx[brs[0],:],bx[brs[1],:]),np.dot(bx[brs[0],:],bx[brs[2],:]),np.dot(bx[brs[1],:],bx[brs[2],:])] 1488 | 1489 | if min(ang) < -0.7: # planar form 1490 | return (3,1),5 # planar form 1491 | elif abs(min(ang)) < 0.3: # right-angle form 1492 | return (3,0),4 # right-angle form 1493 | else: 1494 | print(ang) 1495 | raise ValueError("Can't distinguish the local halide environment. Angular terms printed above.") 1496 | 1497 | elif brnum == 4: 1498 | ios = [] 1499 | for i, h in enumerate(hals): 1500 | if h=="I": 1501 | ios.append(i) 1502 | 1503 | ang = np.dot(bx[ios[0],:],bx[ios[1],:]) 1504 | 1505 | if ang < -0.7: # planar form 1506 | return (4,1),7 # planar form 1507 | elif abs(ang) < 0.3: # right-angle form 1508 | return (4,0),6 # right-angle form 1509 | else: 1510 | print(ang) 1511 | raise ValueError("Can't distinguish the local halide environment. Angular term printed above. ") 1512 | 1513 | elif brnum == 5: 1514 | return (5,0),8 1515 | 1516 | elif brnum == 6: 1517 | return (6,0),9 1518 | 1519 | 1520 | def centmass_organic(st0pos,latmat,env): 1521 | """ 1522 | Find the center of mass of organic A-site. 1523 | 1524 | Args: 1525 | st0pos (numpy.ndarray): The atomic positions. 1526 | latmat (numpy.ndarray): The lattice matrix. 1527 | env (list): The environment of the A-site. 1528 | 1529 | Returns: 1530 | numpy.ndarray: The center of mass positions. 1531 | """ 1532 | 1533 | c = env[0] 1534 | n = env[1] 1535 | h = env[2] 1536 | 1537 | refc = st0pos[[c[0]],:] 1538 | cs = apply_pbc_cart_vecs(st0pos[c,:] - refc,latmat) 1539 | ns = apply_pbc_cart_vecs(st0pos[n,:] - refc,latmat) 1540 | hs = apply_pbc_cart_vecs(st0pos[h,:] - refc,latmat) 1541 | 1542 | mass = refc+(np.sum(cs,axis=0)*12+np.sum(ns,axis=0)*14+np.sum(hs,axis=0)*0)/(12*len(c)+14*len(n)+1*len(h)) 1543 | 1544 | return mass 1545 | 1546 | 1547 | def centmass_organic_vec(pos,latmat,env): 1548 | """ 1549 | Find the center of mass of organic A-site. (vectorised version) 1550 | 1551 | Args: 1552 | pos (numpy.ndarray): The atomic positions. 1553 | latmat (numpy.ndarray): The lattice matrix. 1554 | env (list): The environment of the A-site. 1555 | 1556 | Returns: 1557 | numpy.ndarray: The center of mass positions. 1558 | """ 1559 | 1560 | c = env[0] 1561 | n = env[1] 1562 | h = env[2] 1563 | 1564 | refc = pos[:,[c[0]],:] 1565 | cs = apply_pbc_cart_vecs(pos[:,c,:] - refc,latmat) 1566 | ns = apply_pbc_cart_vecs(pos[:,n,:] - refc,latmat) 1567 | hs = apply_pbc_cart_vecs(pos[:,h,:] - refc,latmat) 1568 | 1569 | mass = refc[:,0,:]+(np.sum(cs,axis=1)*12+np.sum(ns,axis=1)*14+np.sum(hs,axis=1)*1)/(12*len(c)+14*len(n)+1*len(h)) 1570 | 1571 | return mass 1572 | 1573 | 1574 | def find_B_cage_and_disp(pos,mymat,cent,Bs): 1575 | """ 1576 | Find the displacement of A-site with respect to the capsulating B-X cage. 1577 | 1578 | Args: 1579 | pos (numpy.ndarray): The atomic positions. 1580 | mymat (numpy.ndarray): The lattice matrix. 1581 | cent (numpy.ndarray): The center of mass of A-site. 1582 | Bs (list): The indices of the B-site atoms. 1583 | 1584 | Returns: 1585 | numpy.ndarray: The relative displacement of A-site to its surrounding. 1586 | """ 1587 | 1588 | B8pos = pos[:,Bs,:] 1589 | BX = B8pos - np.expand_dims(cent, axis=1) 1590 | 1591 | BX = apply_pbc_cart_vecs(BX, mymat) 1592 | 1593 | Bs = BX + np.expand_dims(cent, axis=1) 1594 | Xcent = np.mean(Bs, axis=1) 1595 | 1596 | disp = cent - Xcent 1597 | 1598 | return disp 1599 | #return np.expand_dims(disp, axis=1) 1600 | 1601 | 1602 | def periodicity_fold(arrin,n_fold=4): 1603 | """ 1604 | Handle abnormal tilting numbers. 1605 | 1606 | Args: 1607 | arrin (numpy.ndarray): The input array of tilting angles. 1608 | n_fold (int): nfold of angle range. 1609 | 1610 | Returns: 1611 | numpy.ndarray: The folded tilting angles. 1612 | """ 1613 | from copy import deepcopy 1614 | arr = deepcopy(arrin) 1615 | if n_fold == 4: 1616 | arr[arr<-45] = arr[arr<-45]+90 1617 | arr[arr<-45] = arr[arr<-45]+90 1618 | arr[arr>45] = arr[arr>45]-90 1619 | arr[arr>45] = arr[arr>45]-90 1620 | elif n_fold == 2: 1621 | arr[arr<-90] = arr[arr<-90]+180 1622 | arr[arr>90] = arr[arr>90]-180 1623 | elif n_fold == 8: 1624 | arr[arr<-45] = arr[arr<-45]+90 1625 | arr[arr<-45] = arr[arr<-45]+90 1626 | arr[arr>45] = arr[arr>45]-90 1627 | arr[arr>45] = arr[arr>45]-90 1628 | arr = np.abs(arr) 1629 | return arr 1630 | 1631 | 1632 | def get_cart_from_frac(frac,latmat): 1633 | """ 1634 | Get cartesian coordinates from fractional coordinates. 1635 | 1636 | Args: 1637 | frac (numpy.ndarray): The fractional coordinates. 1638 | latmat (numpy.ndarray): The lattice matrix. 1639 | 1640 | Returns: 1641 | numpy.ndarray: The corresponding cartesian coordinates. 1642 | """ 1643 | if frac.ndim != latmat.ndim: 1644 | raise ValueError("The dimension of the input arrays do not match. ") 1645 | if frac.shape[-1] != 3 or latmat.shape[-2:] != (3,3): 1646 | raise TypeError("Must be 3D vectors. ") 1647 | 1648 | if frac.ndim == 2: 1649 | pass 1650 | elif frac.ndim == 3: 1651 | if frac.shape[0] != latmat.shape[0]: 1652 | raise ValueError("The frame number of input arrays do not match. ") 1653 | else: 1654 | raise TypeError("Can only deal with 2 or 3D arrays. ") 1655 | 1656 | return np.matmul(frac,latmat) 1657 | 1658 | 1659 | def get_frac_from_cart(cart,latmat): 1660 | """ 1661 | Get fractional coordinates from cartesian coordinates. 1662 | 1663 | Args: 1664 | cart (numpy.ndarray): The cartesian coordinates. 1665 | latmat (numpy.ndarray): The lattice matrix. 1666 | 1667 | Returns: 1668 | numpy.ndarray: The corresponding fractional coordinates. 1669 | """ 1670 | if cart.ndim != latmat.ndim: 1671 | raise ValueError("The dimension of the input arrays do not match. ") 1672 | if cart.shape[-1] != 3 or latmat.shape[-2:] != (3,3): 1673 | raise TypeError("Must be 3D vectors. ") 1674 | 1675 | if cart.ndim == 2: 1676 | pass 1677 | elif cart.ndim == 3: 1678 | if cart.shape[0] != latmat.shape[0]: 1679 | raise ValueError("The frame number of input arrays do not match. ") 1680 | else: 1681 | raise TypeError("Can only deal with 2 or 3D arrays. ") 1682 | 1683 | return np.matmul(cart,np.linalg.inv(latmat)) 1684 | 1685 | 1686 | def apply_pbc_cart_vecs(vecs, mymat): 1687 | """ 1688 | Apply PBC to a set of vectors wrt. lattice matrix. (vectorised version) 1689 | 1690 | Args: 1691 | vecs (numpy.ndarray): The vectors. 1692 | mymat (numpy.ndarray): The lattice matrix. 1693 | 1694 | Returns: 1695 | numpy.ndarray: The corresponding vectors with PBC applied. 1696 | """ 1697 | vecs_frac = get_frac_from_cart(vecs, mymat) 1698 | vecs_pbc = get_cart_from_frac(vecs_frac-np.round(vecs_frac), mymat) 1699 | return vecs_pbc 1700 | 1701 | def apply_pbc_cart_vecs_single_frame(vecs, mymat): 1702 | """ 1703 | Apply PBC to a set of vectors wrt. lattice matrix. 1704 | 1705 | Args: 1706 | vecs (numpy.ndarray): The vectors. 1707 | mymat (numpy.ndarray): The lattice matrix. 1708 | 1709 | Returns: 1710 | numpy.ndarray: The corresponding vectors with PBC applied. 1711 | """ 1712 | vecs_frac = np.matmul(vecs,np.linalg.inv(mymat)) 1713 | vecs_pbc = np.matmul(vecs_frac-np.round(vecs_frac), mymat) 1714 | return vecs_pbc 1715 | 1716 | 1717 | def find_population_gap(r,find_range,init,tol=0): 1718 | """ 1719 | Find the 1D classifier value to separate two populations. 1720 | 1721 | Args: 1722 | r (numpy.ndarray): The input distance matrix array. 1723 | find_range (list): The distance range to find the classifier. 1724 | init (numpy.ndarray): The initial guess of the classifier. 1725 | tol (float): The tolerance of the classifier. 1726 | 1727 | Returns: 1728 | float: The classifier value. 1729 | """ 1730 | from scipy.cluster.vq import kmeans 1731 | 1732 | scan = r.reshape(-1,) 1733 | scan = scan[np.logical_and(scanfind_range[0])] 1734 | centers = kmeans(scan, k_or_guess=init, iter=20, thresh=1e-05)[0] 1735 | p = np.mean(centers) 1736 | 1737 | y,binEdges=np.histogram(scan,bins=50) 1738 | bincenters = 0.5*(binEdges[1:]+binEdges[:-1]) 1739 | 1740 | if y[(np.abs(bincenters - p)).argmin()] > tol: 1741 | p1 = centers[0]+(centers[1]-centers[0])/(centers[1]+centers[0])*centers[0] 1742 | p = p1 1743 | if y[(np.abs(bincenters - p)).argmin()] > tol: 1744 | plt.hist(r.reshape(-1,),bins=100,range=[1,10]) 1745 | raise ValueError("!Structure resolving: Can't separate the different neighbours, please check the fpg_val values are correct according to the guidance at the beginning of the Trajectory class, or check if initial structure is defected or too distorted. ") 1746 | 1747 | return p 1748 | 1749 | 1750 | def get_volume(lattice): 1751 | 1752 | """ 1753 | Calculate the volume of the lattice. 1754 | 1755 | Args: 1756 | lattice (numpy.ndarray): The lattice matrix. 1757 | 1758 | Returns: 1759 | float: The volume of the lattice. 1760 | """ 1761 | 1762 | 1763 | if lattice.shape == (6,): 1764 | A,B,C,a,b,c = lattice 1765 | a=a/180*np.pi 1766 | b=b/180*np.pi 1767 | c=c/180*np.pi 1768 | Vol = A*B*C*(1-np.cos(a)**2-np.cos(b)**2-np.cos(c)**2+2*np.cos(a)*np.cos(b)*np.cos(c))**0.5 1769 | elif (lattice.ndim == 2 and lattice.shape[1] == 6): 1770 | Vol = [] 1771 | for i in range(lattice.shape[0]): 1772 | A,B,C,a,b,c = lattice[i,:] 1773 | a=a/180*np.pi 1774 | b=b/180*np.pi 1775 | c=c/180*np.pi 1776 | vol = A*B*C*(1-np.cos(a)**2-np.cos(b)**2-np.cos(c)**2+2*np.cos(a)*np.cos(b)*np.cos(c))**0.5 1777 | Vol.append(vol) 1778 | Vol = np.array(Vol) 1779 | else: 1780 | raise TypeError("The input must with shape (6,) or (N,6). ") 1781 | 1782 | return Vol 1783 | 1784 | 1785 | def distance_matrix(v1,v2,latmat,get_vec=False): 1786 | """ 1787 | Compute the distance matrix between two sets of vectors, using the apparent matrix multiplication method. 1788 | Should only be used for the case with alpha, beta, gamma angles all close to 90 degrees. 1789 | 1790 | Args: 1791 | v1 (numpy.ndarray): The first set of coordinate vectors. 1792 | v2 (numpy.ndarray): The second set of coordinate vectors. 1793 | latmat (numpy.ndarray): The lattice matrix. 1794 | get_vec (bool): Whether to return the distance vectors. 1795 | 1796 | Returns: 1797 | numpy.ndarray: The distance matrix. 1798 | """ 1799 | 1800 | dprec = np.float32 1801 | 1802 | f1 = get_frac_from_cart(v1,latmat)[:,np.newaxis,:].astype(dprec) 1803 | f2 = get_frac_from_cart(v2,latmat)[np.newaxis,:,:].astype(dprec) 1804 | 1805 | df = np.repeat(f1,f2.shape[1],axis=1)-np.repeat(f2,f1.shape[0],axis=0) 1806 | df = df-np.round(df) 1807 | df = np.matmul(df,latmat.astype(dprec)) 1808 | 1809 | if get_vec: 1810 | return df 1811 | else: 1812 | return np.linalg.norm(df,axis=2) 1813 | 1814 | 1815 | def distance_matrix_ase(v1,v2,asecell,pbc,get_vec=False): 1816 | """ 1817 | Compute the distance matrix between two sets of vectors, using the algorithm implemented in ASE. 1818 | 1819 | Args: 1820 | v1 (numpy.ndarray): The first set of coordinate vectors. 1821 | v2 (numpy.ndarray): The second set of coordinate vectors. 1822 | asecell (ase.Atoms): The Atoms object of the system. 1823 | pbc (list of bool): Periodic boudary condition in three directions as defined in ASE. 1824 | get_vec (bool): Whether to return the distance vectors. 1825 | 1826 | Returns: 1827 | numpy.ndarray: The distance matrix. 1828 | """ 1829 | 1830 | from ase.geometry import get_distances 1831 | 1832 | D, D_len = get_distances(v1,v2,cell=asecell, pbc=pbc) 1833 | 1834 | if get_vec: 1835 | return D 1836 | else: 1837 | return D_len 1838 | 1839 | 1840 | def distance_matrix_ase_replace(v1,v2,asecell,newcell,pbc,get_vec=False): 1841 | """ 1842 | Compute the distance matrix between two sets of vectors, using the algorithm implemented in ASE, with a new cell. 1843 | Uses not the cell object in the Atoms object, but a new cell matrix. 1844 | 1845 | Args: 1846 | v1 (numpy.ndarray): The first set of coordinate vectors. 1847 | v2 (numpy.ndarray): The second set of coordinate vectors. 1848 | asecell (ase.Atoms): The Atoms object of the system. 1849 | newcell (numpy.ndarray): The new cell matrix. 1850 | pbc (list of bool): Periodic boudary condition in three directions as defined in ASE. 1851 | get_vec (bool): Whether to return the distance vectors. 1852 | 1853 | Returns: 1854 | numpy.ndarray: The distance matrix. 1855 | """ 1856 | 1857 | from ase.geometry import get_distances 1858 | 1859 | celltemp = asecell.copy() 1860 | celltemp.array = newcell 1861 | 1862 | D, D_len = get_distances(v1,v2,cell=celltemp, pbc=pbc) 1863 | 1864 | if get_vec: 1865 | return D 1866 | else: 1867 | return D_len 1868 | 1869 | 1870 | def distance_matrix_handler(v1,v2,latmat,asecell=None,pbc=None,complex_pbc=False,replace=True,get_vec=False): 1871 | """ 1872 | Compute the distance matrix between two sets of vectors, with different methods depending on requirement. 1873 | 1874 | Args: 1875 | v1 (numpy.ndarray): The first set of coordinate vectors. 1876 | v2 (numpy.ndarray): The second set of coordinate vectors. 1877 | latmat (numpy.ndarray): The lattice matrix. 1878 | asecell (ase.Atoms): The Atoms object of the system. 1879 | pbc (list of bool): Periodic boudary condition in three directions as defined in ASE. 1880 | complex_pbc (bool): Whether to use the ASE algorithm. 1881 | replace (bool): Whether to use a new cell matrix. 1882 | get_vec (bool): Whether to return the distance vectors. 1883 | 1884 | Returns: 1885 | numpy.ndarray: The distance matrix. 1886 | """ 1887 | 1888 | if v1.shape == (3,): 1889 | v1 = v1[np.newaxis,:] 1890 | if v2.shape == (3,): 1891 | v2 = v2[np.newaxis,:] 1892 | if v1.ndim != 2 or v1.shape[1] != 3 or v2.ndim != 2 or v2.shape[1] != 3: 1893 | raise TypeError("The input arrays must be in shape (N*3) or (3,). ") 1894 | 1895 | if complex_pbc is False: 1896 | r = distance_matrix(v1,v2,latmat,get_vec=get_vec) 1897 | else: 1898 | if replace is False: 1899 | r = distance_matrix_ase(v1,v2,asecell,pbc,get_vec=get_vec) 1900 | else: 1901 | r = distance_matrix_ase_replace(v1,v2,asecell,latmat,pbc,get_vec=get_vec) 1902 | 1903 | return r 1904 | 1905 | 1906 | 1907 | 1908 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pdyna" 7 | version = "1.1.0" 8 | description = "Perovskite dynamics analysis package" 9 | authors = [{name = "Xia Liang", email = "xia.liang16@imperial.ac.uk"}] 10 | readme = "README.md" 11 | license = {file = "LICENSE"} 12 | classifiers = [ 13 | "License :: OSI Approved :: MIT License", 14 | "Programming Language :: Python :: 3", 15 | "Intended Audience :: Science/Research", 16 | "Operating System :: OS Independent", 17 | "Topic :: Scientific/Engineering :: Information Analysis", 18 | "Topic :: Software Development :: Libraries :: Python Modules", 19 | ] 20 | requires-python = ">=3.8" 21 | dependencies = [ 22 | "numpy", 23 | "matplotlib", 24 | "pymatgen", 25 | "ase", 26 | "scipy", 27 | ] 28 | 29 | [project.optional-dependencies] 30 | datasets = ["pymlff"] 31 | docs = ["sphinx", "sphinx-book-theme", "sphinx_design"] 32 | pdf = ["pycairo"] 33 | 34 | [project.urls] 35 | Homepage = "https://pdyna.readthedocs.io/en/latest/" 36 | Documentation = "https://pdyna.readthedocs.io/en/latest/" 37 | Package = "https://pypi.org/project/pdyna/" 38 | Repository = "https://github.com/WMD-group/PDynA" 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import find_packages, setup 3 | 4 | with open("README.md") as file: 5 | long_description = file.read() 6 | 7 | setup( 8 | name="pdyna", 9 | version="1.1.1", 10 | description="Perovskite dynamics analysis package", 11 | url="https://github.com/WMD-group/PDynA", 12 | author="Xia Liang", 13 | author_email="xia.liang16@imperial.ac.uk", 14 | long_description=long_description, 15 | long_description_content_type="text/markdown", 16 | license="MIT", 17 | classifiers=[ 18 | "Development Status :: 3 - Alpha", 19 | "Intended Audience :: Science/Research", 20 | "License :: OSI Approved :: MIT License", 21 | "Natural Language :: English", 22 | "Programming Language :: Python :: 3 :: Only", 23 | "Topic :: Scientific/Engineering", 24 | "Operating System :: OS Independent", 25 | ], 26 | keywords="perovskite dynamics analysis", 27 | test_suite="nose.collector", 28 | packages=find_packages(), 29 | # Specify any non-python files to be distributed with the package 30 | package_data={'pdyna': ['basis/*.json']}, # include json files in the basis directory 31 | #package_data={ 32 | # "pdyna": ["pdyna/basis/*"], 33 | #}, 34 | include_package_data=True, 35 | install_requires=[ 36 | "scipy", 37 | "numpy", 38 | "matplotlib", 39 | "pymatgen", 40 | "ase", 41 | ], 42 | data_files=["LICENSE"], 43 | ) 44 | 45 | --------------------------------------------------------------------------------