├── .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 | [](https://www.python.org/)
3 | [](https://opensource.org/licenses/MIT)
4 | [](https://pdyna.readthedocs.io/en/latest/)
5 | [](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 |
--------------------------------------------------------------------------------