├── .bumpversion.cfg ├── .ci_support └── environment.yml ├── .coverage ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── deploy.yml │ └── testing.yml ├── .gitignore ├── .readthedocs.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── README.md ├── Welcome.md ├── _config.yml ├── _toc.yml ├── binder ├── apt.txt ├── environment.yml ├── jupyterlab-workspace.json ├── postBuild └── start ├── docs ├── .nojekyll ├── _static │ ├── img_time_memory.png │ ├── img_time_neighbor.png │ ├── pyscal_logo1.png │ └── pyscal_logo2.png ├── _templates │ ├── download.html │ └── sidebarhelp.html ├── epilogue.md ├── examples.md ├── gettingstarted.md ├── index.rst ├── intro.md ├── license.md ├── methods │ ├── 01_neighbors.md │ ├── 02_steinhardt.md │ ├── 03_solidliquid.md │ ├── 04_disorder.md │ ├── 05_angular.md │ ├── 06_voronoi.md │ ├── 07_centrosymmetry.md │ └── 08_entropy.md ├── methods_landing.md ├── methodsandexamples.rst ├── methodsindex.rst ├── modules.rst ├── prologue │ ├── acknowledgements.md │ ├── api.md │ ├── citing.md │ ├── download.md │ ├── extending.md │ ├── gettingstarted.md │ ├── helpandsupport.md │ ├── license.md │ ├── news.md │ ├── projects.md │ └── publications.md ├── pubsandprojects.rst ├── pyscal.rst └── requirements.txt ├── environment-docs.yml ├── examples ├── 01_getting_started.ipynb ├── 02_creating_structures.ipynb ├── 03_atoms_in_more_detail.ipynb ├── 04_creating_grain_boundaries.ipynb ├── 05_finding_neighbors.ipynb ├── 06_steinhardt_parameters.ipynb ├── 07_disorder_parameters.ipynb ├── 08_angular_parameters.ipynb ├── 09_distinguishing_solid_liquid.ipynb ├── 10_voronoi_tessellation.ipynb ├── 11_chi_params.ipynb ├── 12_centrosymmetry_parameter.ipynb ├── 13_short_range_order.ipynb ├── 14_common_neighbor_analysis.ipynb ├── 15_trajectory_module.ipynb ├── 16_entropy_parameter.ipynb ├── Al.eam.fs ├── Mo.set ├── cluster.dump ├── conf.bcc ├── conf.bcc.dump ├── conf.dump ├── conf.fcc ├── conf.fcc.Al.dump ├── conf.fcc.dump ├── conf.hcp ├── conf.hcp.dump ├── conf.lqd ├── conf.lqd.Al.dump ├── conf0.bcc ├── conf0.fcc ├── conf0.hcp ├── conf8k.dump ├── prototype_files.ipynb ├── system1.png ├── system2.png ├── system3.png ├── system4.png └── traj.light ├── lib └── voro++ │ ├── Doxyfile │ ├── Makefile │ ├── Makefile.dep │ ├── README │ ├── c_loops.cc │ ├── c_loops.hh │ ├── cell.cc │ ├── cell.hh │ ├── cmd_line.cc │ ├── common.cc │ ├── common.hh │ ├── config.hh │ ├── container.cc │ ├── container.hh │ ├── container_prd.cc │ ├── container_prd.hh │ ├── pre_container.cc │ ├── pre_container.hh │ ├── rad_option.hh │ ├── test.cpp │ ├── unitcell.cc │ ├── unitcell.hh │ ├── v_base.cc │ ├── v_base.hh │ ├── v_base_wl.cc │ ├── v_compute.cc │ ├── v_compute.hh │ ├── voro++.cc │ ├── voro++.hh │ ├── wall.cc │ ├── wall.hh │ ├── worklist.hh │ └── worklist_gen.pl ├── more_atom_manipulation.ipynb ├── neighbor_data.ipynb ├── new_features.ipynb ├── pyproject.toml ├── requirements.txt ├── requirements_dev.txt ├── setup.py ├── src └── pyscal3 │ ├── __init__.py │ ├── ase.py │ ├── atoms.py │ ├── attributes.py │ ├── centrosymmetry.cpp │ ├── cna.cpp │ ├── core.py │ ├── csl.py │ ├── data │ ├── annotations.yaml │ ├── element_data.yaml │ └── structure_data.yaml │ ├── entropy.cpp │ ├── formats │ ├── __init__.py │ ├── ase.py │ ├── lammps.py │ ├── mdtraj.py │ └── vasp.py │ ├── grain_boundary.py │ ├── misc.py │ ├── neighbor.cpp │ ├── operations │ ├── __init__.py │ ├── calculations.py │ ├── centrosymmetry.py │ ├── chemical.py │ ├── cna.py │ ├── entropy.py │ ├── identify.py │ ├── input.py │ ├── neighbor.py │ ├── operations.py │ ├── serialize.py │ ├── symmetry.py │ ├── visualize.py │ └── voronoi.py │ ├── routines.py │ ├── sh.cpp │ ├── solids.cpp │ ├── structure_creator.py │ ├── system.h │ ├── system_binding.cpp │ ├── traj_process.py │ ├── trajectory.py │ ├── visualization.py │ └── voronoi.cpp ├── test.ipynb ├── tests ├── files │ ├── POSCAR │ ├── cluster.dump │ ├── conf.bcc.scaled.dump │ ├── conf.dump │ ├── conf.dump.gz │ ├── conf.fcc.Al.dump │ ├── conf.fcc.dump │ └── conf.lqd.dump ├── test_angular.py ├── test_ase.py ├── test_centro.py ├── test_chiparams.py ├── test_cna.py ├── test_crystal_structures.py ├── test_delete.py ├── test_disorder.py ├── test_entropy.py ├── test_masking.py ├── test_neighbors.py ├── test_nucsize.py ├── test_q12.py ├── test_q3.py ├── test_q_system.py ├── test_rdf.py ├── test_read_file.py ├── test_sro.py ├── test_symmetry.py ├── test_system.py ├── test_traj_process.py ├── test_trajectory.py └── test_voronoi.py └── update_xtals.ipynb /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 3.3.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | -------------------------------------------------------------------------------- /.ci_support/environment.yml: -------------------------------------------------------------------------------- 1 | name: pyscal-test 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - numpy 6 | - matplotlib 7 | - ase 8 | - plotly 9 | - ipywidgets 10 | - pytest 11 | - pytest-cov 12 | - pybind11 13 | - scipy 14 | - spglib 15 | - pyyaml 16 | -------------------------------------------------------------------------------- /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscal/pyscal3/ae76867ab0a315c39151666a5fda1121f0c4a899/.coverage -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload to PyPI 2 | 3 | # Build on every branch push, tag push, and pull request change: 4 | #on: [push, pull_request] 5 | # Alternatively, to publish when a (published) GitHub Release is created, use the following: 6 | on: 7 | release: 8 | types: 9 | - published 10 | 11 | jobs: 12 | build_wheels: 13 | name: Build wheels on ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-13] 18 | python-version: ['3.10', '3.11'] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Install dependencies 23 | run: >- 24 | python -m pip install --user --upgrade setuptools wheel pybind11 25 | - name: Build wheels 26 | uses: pypa/cibuildwheel@v2.12.3 27 | 28 | - uses: actions/upload-artifact@v3 29 | with: 30 | path: ./wheelhouse/*.whl 31 | 32 | build_sdist: 33 | name: Build source distribution 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v3 37 | - name: Install dependencies 38 | run: >- 39 | python -m pip install --user --upgrade setuptools wheel pybind11 40 | - name: Build sdist 41 | run: | 42 | pip install pybind11 43 | python setup.py sdist 44 | 45 | - uses: actions/upload-artifact@v3 46 | with: 47 | path: dist/*.tar.gz 48 | 49 | upload_pypi: 50 | needs: [build_wheels, build_sdist] 51 | runs-on: ubuntu-latest 52 | # upload to PyPI on every tag starting with 'v' 53 | #if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 54 | # alternatively, to publish when a GitHub Release is created, use the following rule: 55 | #if: github.event_name == 'release' && github.event.action == 'published' 56 | steps: 57 | - uses: actions/download-artifact@v4 58 | with: 59 | # unpacks default artifact into dist/ 60 | # if `name: artifact` is omitted, the action will create extra parent dir 61 | name: artifact 62 | path: dist 63 | 64 | - uses: pypa/gh-action-pypi-publish@v1.5.0 65 | with: 66 | user: __token__ 67 | password: ${{ secrets.PYPI_API_TOKEN }} 68 | #repository_url: https://test.pypi.org/legacy/ 69 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: pyscal-testing 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-13, windows-latest] 14 | python-version: ['3.10', '3.11'] 15 | 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setup Mambaforge 20 | uses: conda-incubator/setup-miniconda@v3 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | miniforge-version: latest 24 | channels: conda-forge 25 | environment-file: .ci_support/environment.yml 26 | - name: run tests 27 | shell: bash -l {0} 28 | run: | 29 | pip install --no-deps --no-build-isolation -e . 30 | pytest --cov-report=xml --cov=pyscal tests/ 31 | 32 | - name: Upload coverage to Codecov 33 | uses: codecov/codecov-action@v1 34 | with: 35 | file: coverage.xml 36 | name: codecov-umbrella 37 | fail_ci_if_error: false 38 | 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | *# 4 | *.nfs* 5 | __pycache__/ 6 | build/ 7 | dist/ 8 | *.egg* 9 | .cache 10 | docs/html/* 11 | docs/doctrees/* 12 | docs/source/examples 13 | .ipynb_check* 14 | *.dump 15 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | #python: "mambaforge-4.10" 7 | python: "3.11" 8 | 9 | jobs: 10 | pre_build: 11 | # Generate the Sphinx configuration for this Jupyter Book so it builds. 12 | - "jupyter-book config sphinx ." 13 | 14 | #conda: 15 | # environment: environment-docs.yml 16 | 17 | python: 18 | install: 19 | - requirements: requirements.txt 20 | - method: pip 21 | path: . 22 | 23 | sphinx: 24 | builder: html 25 | fail_on_warning: false 26 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at sarath.menon@ruhr-uni-bochum.de. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Extending and contributing 2 | ========================== 3 | 4 | **pyscal** welcomes contribution and extension to the module. Rather than local modifications, we request that the modifications be submitted through a pull request. **pyscal** follows the `pep8 `_ style. The quality of the code can be checked by `pylint `_ library by running ``pylint yourscript.py``. Rather than the style of code, what is more important is the documentation. We request that all improvements are documented in detail. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Sarath Menon 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/pyscal3/data/*.yaml 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # pyscal - python Structural Environment Calculator 3 | 4 | Complete documentation with examples available [here](https://pyscal.org/). 5 | 6 | pyscal3, a completely new pyscal which is faster and can handle a large number of atoms, with a much more user-friendly and intuitive interface. Adds more features such as more structure creation including grain boundaries, selection, and deletion of atoms 7 | 8 | 9 | ## Installation 10 | 11 | pyscal3 can be installed directly using conda by the following statement- 12 | 13 | ``` 14 | conda install -c conda-forge pyscal3 15 | ``` 16 | 17 | **From repository** 18 | 19 | pyscal can be built from the repository by- 20 | 21 | ``` 22 | git clone https://github.com/pyscal/pyscal3.git 23 | cd pyscal3 24 | pip install . 25 | ``` 26 | 27 | ## Citing the work 28 | 29 | If you use pyscal3 in your work, the citation of the [following article](https://joss.theoj.org/papers/10.21105/joss.01824) will be greatly appreciated: 30 | 31 | Sarath Menon, Grisell Díaz Leines and Jutta Rogal (2019). pyscal: A python module for structural analysis of atomic environments. Journal of Open Source Software, 4(43), 1824, https://doi.org/10.21105/joss.01824 32 | 33 | ## Works using pyscal 34 | 35 | For a complete list of publications which used pyscal, see [here](https://scholar.google.com/scholar?oi=bibs&hl=en&cites=315020929885190486&as_sdt=5). 36 | 37 | -------------------------------------------------------------------------------- /Welcome.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
pyscal

pyscal is a python module for the calculation of local atomic structural environments including Steinhardt's bond orientational order parameters during post-processing of atomistic simulation data. The core functionality of pyscal is written in C++ with python wrappers using pybind11 which allows for fast calculations with possibilities for easy expansion in python.

12 | 13 | ## **Examples** 14 | 15 | 1. [Getting started](examples/01_getting_started.ipynb) : Explore basic features of pyscal and how to get started with pyscal. 16 | 2. [Finding neighbors](examples/02_finding_neighbors.ipynb) : Various methods of finding the local neighborhood of an atom in pyscal, including cutoff and Voronoi methods. 17 | 3. [Bond orientational order parameters](examples/03_steinhardt_parameters.ipynb) : Calculation of bond orientational parameters, averaged and Voronoi weighted versions, and distinction of structures. 18 | 4. [Disorder parameters](examples/04_disorder_parameters.ipynb) : Disorder parameters based on Steinhardts parameters. 19 | 5. [Distinguishing solid and liquid atoms](examples/05_distinguishing_solid_liquid.ipynb) : Use Steinhardts parameters to distinguish between solid and liquid, and how to cluster solid atoms based on a property. 20 | 6. [Voronoi tessellation](examples/06_voronoi_tessellation.ipynb) : Identifying Voronoi polyhedra and calculation of associated volume. 21 | 7. [Angular parameters](examples/07_angular_parameters.ipynb) : Angular parameters for identification of tetrahedral ordering. 22 | 8. [$\chi$ parameters](examples/08_chi_params.ipynb) : Angular parameters for structural identification. 23 | 9. [Centrosymmetry parameter](examples/09_centrosymmetry_parameter.ipynb) : Parameters for identification of defects in crystals. 24 | 10. [Short range order](examples/10_short_range_order.ipynb) : Calculating ordering in binary systems. 25 | 11. [Entropy parameter](examples/11_entropy_parameter.ipynb) : Use a measure of entropy to distinguish solid and liquid. 26 | 12. [Calculation of energy](examples/12_calculating_energy.ipynb) : Use pyscal in combination with LAMMPS to calculate energy of atoms. 27 | 13. [Structural identification using entropy and enthalpy](examples/13_combining_energy_enthalpy.ipynb) : Combine entropy and energy methods to identify crystal structures. 28 | 14. [Working with lammps trajectories](examples/03_01_Steinhardts_parameters_for_lammps.ipynb) : Reading in a lammps trajectory, calculating Steinhardt's parameter for slices and various cluster properties. 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Book settings 2 | # Learn more at https://jupyterbook.org/customize/config.html 3 | 4 | title: "pyscal3" 5 | #author: The Jupyter Book Community 6 | logo: docs/_static/pyscal_logo1.png 7 | 8 | # Force re-execution of notebooks on each build. 9 | # See https://jupyterbook.org/content/execute.html 10 | execute: 11 | execute_notebooks: 'auto' 12 | 13 | only_build_toc_files: true 14 | 15 | # Define the name of the latex output file for PDF builds 16 | latex: 17 | latex_documents: 18 | targetname: book.tex 19 | 20 | # Information about where the book exists on the web 21 | repository: 22 | url: https://github.com/pyiron/DGM_workshop 23 | path_to_book: book 24 | branch: main 25 | 26 | notebook_interface : "notebook" 27 | 28 | # Add GitHub buttons to your book 29 | # See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository 30 | html: 31 | use_issues_button: false 32 | use_repository_button: true 33 | 34 | parse: 35 | myst_enable_extensions: 36 | # don't forget to list any other extensions you want enabled, 37 | # including those that are enabled by default! 38 | - html_image 39 | - amsmath 40 | - dollarmath 41 | - linkify 42 | - substitution 43 | - colon_fence 44 | - html_admonition 45 | 46 | sphinx: 47 | config: 48 | mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js 49 | html_theme: pydata_sphinx_theme 50 | html_js_files: 51 | - https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js 52 | html_sidebars: 53 | "**": [] 54 | -------------------------------------------------------------------------------- /_toc.yml: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | # Learn more at https://jupyterbook.org/customize/toc.html 3 | 4 | format: jb-book 5 | root: docs/intro 6 | chapters: 7 | - file: docs/gettingstarted.md 8 | - file: docs/methods_landing.md 9 | sections: 10 | - file: docs/methods/01_neighbors.md 11 | - file: docs/methods/02_steinhardt.md 12 | - file: docs/methods/03_solidliquid.md 13 | - file: docs/methods/04_disorder.md 14 | - file: docs/methods/05_angular.md 15 | - file: docs/methods/06_voronoi.md 16 | - file: docs/methods/07_centrosymmetry.md 17 | - file: docs/methods/08_entropy.md 18 | - file: docs/examples.md 19 | sections: 20 | - file: examples/01_getting_started 21 | - file: examples/02_creating_structures 22 | - file: examples/03_atoms_in_more_detail 23 | - file: examples/04_creating_grain_boundaries 24 | - file: examples/05_finding_neighbors 25 | - file: examples/06_steinhardt_parameters 26 | - file: examples/07_disorder_parameters 27 | - file: examples/08_angular_parameters 28 | - file: examples/09_distinguishing_solid_liquid 29 | - file: examples/10_voronoi_tessellation 30 | - file: examples/11_chi_params 31 | - file: examples/12_centrosymmetry_parameter 32 | - file: examples/13_short_range_order 33 | - file: examples/14_common_neighbor_analysis 34 | - file: examples/15_trajectory_module 35 | - file: examples/16_entropy_parameter 36 | - file: docs/epilogue.md 37 | 38 | 39 | -------------------------------------------------------------------------------- /binder/apt.txt: -------------------------------------------------------------------------------- 1 | cmake 2 | -------------------------------------------------------------------------------- /binder/environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | dependencies: 4 | - python 5 | - jupyterlab 6 | - nodejs 7 | - notebook 8 | - numpy 9 | - matplotlib 10 | - lammps 11 | - ase 12 | - ipywidgets 13 | - plotly 14 | - h5py 15 | -------------------------------------------------------------------------------- /binder/jupyterlab-workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "file-browser-filebrowser:cwd": { 4 | "path": "" 5 | }, 6 | "dask-dashboard-launcher:individual-progress": { 7 | "data": { 8 | "route": "individual-progress", 9 | "label": "Progress" 10 | } 11 | }, 12 | "dask-dashboard-launcher:individual-task-stream": { 13 | "data": { 14 | "route": "individual-task-stream", 15 | "label": "Task Stream" 16 | } 17 | }, 18 | "layout-restorer:data": { 19 | "main": { 20 | "dock": { 21 | "type": "split-area", 22 | "orientation": "horizontal", 23 | "sizes": [ 24 | 0.5, 25 | 0.5 26 | ], 27 | "children": [ 28 | { 29 | "type": "tab-area", 30 | "currentIndex": 0, 31 | "widgets": [ 32 | "markdownviewer-widget:Welcome.md" 33 | ] 34 | }, 35 | { 36 | "type": "split-area", 37 | "orientation": "vertical", 38 | "sizes": [ 39 | 0.67, 40 | 0.33 41 | ], 42 | "children": [ 43 | { 44 | "type": "tab-area", 45 | "currentIndex": 0, 46 | "widgets": [ 47 | "dask-dashboard-launcher:individual-task-stream" 48 | ] 49 | }, 50 | { 51 | "type": "tab-area", 52 | "currentIndex": 0, 53 | "widgets": [ 54 | "dask-dashboard-launcher:individual-progress" 55 | ] 56 | } 57 | ] 58 | } 59 | ] 60 | }, 61 | "mode": "multiple-document", 62 | "current": "markdownviewer-widget:Welcome.md" 63 | }, 64 | "left": { 65 | "collapsed": false, 66 | "current": "filebrowser", 67 | "widgets": [ 68 | "filebrowser", 69 | "running-sessions", 70 | "dask-dashboard-launcher", 71 | "command-palette", 72 | "tab-manager" 73 | ] 74 | }, 75 | "right": { 76 | "collapsed": true, 77 | "widgets": [] 78 | } 79 | }, 80 | "markdownviewer-widget:Welcome.md": { 81 | "data": { 82 | "path": "Welcome.md", 83 | "factory": "Markdown Preview" 84 | } 85 | }, 86 | "dask-dashboard-launcher": { 87 | "url": "DASK_DASHBOARD_URL", 88 | "cluster": "" 89 | } 90 | }, 91 | "metadata": { 92 | "id": "/lab" 93 | } 94 | } -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | pip install . 2 | jupyter labextension install jupyterlab-plotly@4.11.0 3 | jupyter labextension install @jupyter-widgets/jupyterlab-manager plotlywidget@4.11.0 -------------------------------------------------------------------------------- /binder/start: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | 4 | # Import the workspace 5 | jupyter lab workspaces import binder/jupyterlab-workspace.json 6 | 7 | exec "$@" -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscal/pyscal3/ae76867ab0a315c39151666a5fda1121f0c4a899/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_static/img_time_memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscal/pyscal3/ae76867ab0a315c39151666a5fda1121f0c4a899/docs/_static/img_time_memory.png -------------------------------------------------------------------------------- /docs/_static/img_time_neighbor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscal/pyscal3/ae76867ab0a315c39151666a5fda1121f0c4a899/docs/_static/img_time_neighbor.png -------------------------------------------------------------------------------- /docs/_static/pyscal_logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscal/pyscal3/ae76867ab0a315c39151666a5fda1121f0c4a899/docs/_static/pyscal_logo1.png -------------------------------------------------------------------------------- /docs/_static/pyscal_logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscal/pyscal3/ae76867ab0a315c39151666a5fda1121f0c4a899/docs/_static/pyscal_logo2.png -------------------------------------------------------------------------------- /docs/_templates/download.html: -------------------------------------------------------------------------------- 1 |

Download this documentation

2 | 3 |

4 | Documentation is available in the following formats
5 | PDF
6 | HTML
7 | Epub
8 |

9 | -------------------------------------------------------------------------------- /docs/_templates/sidebarhelp.html: -------------------------------------------------------------------------------- 1 |

Need help?

2 | 3 |

4 | Raise an issue at the 5 | Github repository 6 | or send an 7 | email. 8 |

9 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | The gallery of examples below cover different ways in which pyscal can be used. 4 | 5 | ::::{grid} 1 1 2 3 6 | :class-container: text-center 7 | :gutter: 3 8 | 9 | :::{grid-item-card} 10 | :link: ../examples/01_getting_started 11 | :link-type: doc 12 | :class-header: bg-light 13 | Getting started with pyscal 14 | ^^^ 15 | Learn the very basis, including the concepts of `System` and `Atoms`. 16 | ::: 17 | 18 | :::{grid-item-card} 19 | :link: ../examples/02_creating_structures 20 | :link-type: doc 21 | :class-header: bg-light 22 | Creating structures 23 | ^^^ 24 | Create common atomic structures like bcc, fcc; and custom ones. Save and read from files. 25 | ::: 26 | 27 | :::{grid-item-card} 28 | :link: ../examples/03_atoms_in_more_detail 29 | :link-type: doc 30 | :class-header: bg-light 31 | More atom manipulation 32 | ^^^ 33 | Work in progress 34 | ::: 35 | 36 | :::{grid-item-card} 37 | :link: ../examples/04_creating_grain_boundaries 38 | :link-type: doc 39 | :class-header: bg-light 40 | Creating defects 41 | ^^^ 42 | Create Grain boundaries and view them. 43 | ::: 44 | 45 | :::{grid-item-card} 46 | :link: ../examples/05_finding_neighbors 47 | :link-type: doc 48 | :class-header: bg-light 49 | Finding neighbors 50 | ^^^ 51 | Methods such as `cutoff` and its variations, and `voronoi` for finding neighbors. 52 | ::: 53 | 54 | :::{grid-item-card} 55 | :link: ../examples/06_steinhardt_parameters 56 | :link-type: doc 57 | :class-header: bg-light 58 | Calculating Steinhardt parameters 59 | ^^^ 60 | Combine neighbor calculation with Steinhardt parameters. The original function for which pyscal was developed for. 61 | ::: 62 | 63 | :::{grid-item-card} 64 | :link: ../examples/07_disorder_parameters 65 | :link-type: doc 66 | :class-header: bg-light 67 | Disorder parameter 68 | ^^^ 69 | Identify disorder in crystals using Steinhardt parameters. 70 | ::: 71 | 72 | :::{grid-item-card} 73 | :link: ../examples/08_angular_parameters 74 | :link-type: doc 75 | :class-header: bg-light 76 | Angular parameters 77 | ^^^ 78 | Parameters to quantify the angle around an atom, useful for detecting diamond structures. 79 | ::: 80 | 81 | :::{grid-item-card} 82 | :link: ../examples/09_distinguishing_solid_liquid 83 | :link-type: doc 84 | :class-header: bg-light 85 | Distinguishing solid and liquid 86 | ^^^ 87 | Steinhardt parameter based methods to distinguish solid atoms in liquid. Clustering methods to cluster atoms based on any property. 88 | ::: 89 | 90 | :::{grid-item-card} 91 | :link: ../examples/10_voronoi_tessellation 92 | :link-type: doc 93 | :class-header: bg-light 94 | Voronoi tessellation 95 | ^^^ 96 | Voronoi tessellation to calculate structural vector and Voronoi volume. 97 | ::: 98 | 99 | :::{grid-item-card} 100 | :link: ../examples/11_chi_params 101 | :link-type: doc 102 | :class-header: bg-light 103 | Chi params 104 | ^^^ 105 | Angle-based Chi params for structural identification. 106 | ::: 107 | 108 | :::{grid-item-card} 109 | :link: ../examples/12_centrosymmetry_parameter 110 | :link-type: doc 111 | :class-header: bg-light 112 | Centrosymmetry parameter 113 | ^^^ 114 | Useful parameters for finding breaks in the ordered crystal. 115 | ::: 116 | 117 | :::{grid-item-card} 118 | :link: ../examples/13_short_range_order 119 | :link-type: doc 120 | :class-header: bg-light 121 | Chemical short range order 122 | ^^^ 123 | Calculate multi-component chemical short range order in alloys. 124 | ::: 125 | 126 | :::{grid-item-card} 127 | :link: ../examples/14_common_neighbor_analysis 128 | :link-type: doc 129 | :class-header: bg-light 130 | Common neighbor analysis 131 | ^^^ 132 | CNA, adaptive CNA to identify bcc, fcc, and hcp. Extension of CNA to identify various flavors of diamond lattice. 133 | ::: 134 | 135 | :::{grid-item-card} 136 | :link: ../examples/15_trajectory_module 137 | :link-type: doc 138 | :class-header: bg-light 139 | pyscal Trajectory 140 | ^^^ 141 | Trajectory module enables fast and efficient analysis of LAMMPS dump trajectories. 142 | ::: 143 | 144 | :::{grid-item-card} 145 | :link: ../examples/16_entropy_parameter 146 | :link-type: doc 147 | :class-header: bg-light 148 | Entropy parameters 149 | ^^^ 150 | Entropy parameter can be used for distinguishing crystal structures. 151 | ::: 152 | :::: 153 | 154 | 155 | -------------------------------------------------------------------------------- /docs/gettingstarted.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | `pyscal3` can be installed on Linux and Mac OS based systems. On Windows systems, it is recommended to use [Windows subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install). The following instructions will help install `pyscal3`: 4 | 5 | ````{tab-set} 6 | ```{tab-item} pip 7 | `pip install pyscal3` 8 | ``` 9 | 10 | ```{tab-item} conda 11 | `conda install -c conda-forge pyscal3` 12 | ``` 13 | 14 | ```{tab-item} from source 15 | We strongly recommend creating a conda environment for the installation. To see how you can install conda see [here](https://docs.conda.io/projects/conda/en/latest/user-guide/install/). 16 | 17 | Once a conda distribution is available, the following steps will help set up an environment to use `pyscal3`. First step is to clone the repository. 18 | 19 | `git clone https://github.com/pyscal/pyscal3.git` 20 | 21 | After cloning, an environment can be created from the included file- 22 | 23 | `cd pyscal3` 24 | `conda env create -f .ci_support/environment.yml` 25 | 26 | This will install the necessary packages and create an environment called rdf. It can be activated by, 27 | 28 | `conda activate pyscal-test` 29 | 30 | then, install `pyscal3` using, 31 | 32 | `pip install .` 33 | ``` 34 | ```` 35 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyscal documentation master file, created by 2 | sphinx-quickstart on Wed Apr 24 14:11:20 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | pyscal 7 | ====== 8 | 9 | .. image:: https://dev.azure.com/sarathrmenon/pyscal/_apis/build/status/srmnitc.pyscal?branchName=master 10 | :target: https://dev.azure.com/sarathrmenon/pyscal/_build/latest?definitionId=1&branchName=master 11 | :width: 20% 12 | 13 | .. image:: https://codecov.io/gh/srmnitc/pyscal/branch/master/graph/badge.svg 14 | :target: https://codecov.io/gh/srmnitc/pyscal 15 | :width: 15 % 16 | 17 | .. image:: https://mybinder.org/badge_logo.svg 18 | :target: https://mybinder.org/v2/gh/srmnitc/pybop/master?filepath=examples%2F 19 | :width: 15 % 20 | 21 | .. image:: https://anaconda.org/pyscal/pyscal/badges/installer/conda.svg 22 | :target: https://anaconda.org/conda-forge/pyscal 23 | :width: 13 % 24 | 25 | .. image:: https://img.shields.io/conda/dn/conda-forge/pyscal.svg 26 | :target: https://conda.anaconda.org/pyscal 27 | :width: 13 % 28 | 29 | .. image:: https://joss.theoj.org/papers/168eca482155601dc517523899527a4e/status.svg 30 | :target: https://joss.theoj.org/papers/168eca482155601dc517523899527a4e 31 | :width: 20 % 32 | 33 | .. image:: https://img.shields.io/conda/pn/conda-forge/pyscal.svg 34 | :target: https://anaconda.org/conda-forge/pyscal 35 | :width: 20 % 36 | 37 | 38 | **pyscal** is a python module for the calculation of local atomic 39 | structural environments including `Steinhardt's bond orientational order 40 | parameters `__ 41 | during post-processing of atomistic simulation data. The core 42 | functionality of pyscal is written in C++ with python wrappers using 43 | `pybind11 `__ 44 | which allows for fast calculations with possibilities for easy expansion 45 | in python. 46 | 47 | Steinhardt's order parameters are widely used for `identification of 48 | crystal 49 | structures `__. 50 | They are also used to identify if an atom is `solid or 51 | liquid `__. pyscal is 52 | inspired by 53 | `BondOrderAnalysis `__ 54 | code, but has since incorporated many additions and modifications. 55 | 56 | 57 | .. toctree:: 58 | :hidden: 59 | :maxdepth: 2 60 | 61 | Installation 62 | Why version 3? 63 | Examples 64 | prologue/extending 65 | prologue/helpandsupport 66 | prologue/citing 67 | prologue/acknowledgements 68 | prologue/license 69 | 70 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | 2 | # pyscal3 - python Structural Environment Calculator 3 | 4 | 5 | **pyscal3** is a completely new version of [pyscal](https://docs.pyscal.org/en/latest/), a python module for the calculation of local atomic structural environments including [Steinhardt's bond orientational order parameters](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.28.784) during post-processing of atomistic simulation data. `pyscal3` is faster and can handle a large number of atoms, with a much more user-friendly and intuitive interface. 6 | 7 | Features of `pyscal3` includes: 8 | 9 | - fast and efficient calculations using C++ and expansion using python. 10 | - more structure creation routines, including defects such as grain boundaries. 11 | - calculation of [Steinhardt's order parameters](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.28.784) and their [averaged version](https://aip.scitation.org/doi/full/10.1063/1.2977970) and [disorder parameters](https://doi.org/10.1063/1.3656762). 12 | - links with [Voro++](http://math.lbl.gov/voro++/)> code, for calculation of [Steinhardt parameters weighted using face area of Voronoi polyhedra](https://aip.scitation.org/doi/full/10.1063/1.4774084). 13 | - classification of atoms as [solid or liquid](https://link.springer.com/chapter/10.1007/b99429). 14 | - clustering of particles based on a user defined property. 15 | - methods for calculating radial distribution function, voronoi volumeof particles, number of vertices and face area of voronoi polyhedra and coordination number. 16 | - calculation of angular parameters such as [for identification of diamond structure](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.47.15717) and [Ackland-Jones](https://doi.org/10.1103/PhysRevB.73.054104) angular parameters. 17 | - [Centrosymmetry parameter](https://doi.org/10.1103/PhysRevB.58.11085) for identification of defects. 18 | - [Adaptive common neighbor analysis](https://iopscience.iop.org/article/10.1088/0965-0393/20/4/045021) for identification of crystal structures. 19 | - [Cowley short-range](https://doi.org/10.1103/PhysRev.120.1648) order parameters. 20 | 21 | 22 | ## Why version 3? 23 | 24 | pyscal v3 is a new version with mostly updated codebase and breaking changes. Anybody who has working pyscal code will need to update it to get it working with this new version. Therefore, it is necessary to discuss why this new version was needed and the benefits of updating. 25 | 26 | ### Version 3 is much faster 27 | 28 | In the plot below, the time needed to calculate neighbors with the 'cutoff' method for systems with varying number of atoms with versions 2.10.15 and 3.0 is shown. 29 | 30 | 31 | 32 | v3 is faster for all system sizes. At a system size of about 50,000 atoms, v3 is about 4x faster. 33 | 34 | ### Version 3 uses less memory 35 | 36 | A major issue with pyscal v2.x series was that it not useful for large system sizes due to the large amount of memory needed. In the plot below, the memory usage of both versions for the same calculation above is shown. 37 | 38 | 39 | 40 | v3 uses less memory, for a system size of 50,000 atoms, v3 uses 14x less memory. A more interesting feature is the slope of the data, or how much the memory scales with the system size. For v3 it is only 0.008, while for v2 it is .12! For a system of 1 million atoms, v2 would use 117 GB of memory while v3 would need only 8 GB, making larger calculations accessible (these numbers will be updated after real use-case tests). 41 | 42 | ### What are reasons for these benefits? 43 | 44 | - The older C++ atoms class is deprecated. Instead, it is store as python dictionary. Therefore the copying between python and C++ sides is avoided. 45 | - The atoms python dictionary is directly exposed to the C++ side. The dictionary is passed by reference, which allows in-place modification directly. 46 | 47 | ### What are the other feature updates? 48 | 49 | The new version includes a number of new features and quality of life improvements. Please check the examples for details. 50 | 51 | 52 | ## Citing the work 53 | 54 | If you use pyscal in your work, the citation of the [following article](https://joss.theoj.org/papers/10.21105/joss.01824) will be greatly appreciated: 55 | 56 | Sarath Menon, Grisell Díaz Leines and Jutta Rogal (2019). pyscal: A python module for structural analysis of atomic environments. Journal of Open Source Software, 4(43), 1824, https://doi.org/10.21105/joss.01824 -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | pyscal3 4 | 5 | BSD 3-Clause License 6 | 7 | Copyright (c) 2024, Sarath Menon $^1$ 8 | $^1$: Max Planck Institut für Eisenforschung, Dusseldorf, Germany 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions are met: 13 | 14 | 1. Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 17 | 2. Redistributions in binary form must reproduce the above copyright notice, 18 | this list of conditions and the following disclaimer in the documentation 19 | and/or other materials provided with the distribution. 20 | 21 | 3. Neither the name of the copyright holder nor the names of its 22 | contributors may be used to endorse or promote products derived from 23 | this software without specific prior written permission. 24 | 25 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 26 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 28 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 29 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 31 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | 36 | For more information contact: 37 | sarath.menon@pyscal.org -------------------------------------------------------------------------------- /docs/methods/01_neighbors.md: -------------------------------------------------------------------------------- 1 | # Methods to calculate neighbors of a particle 2 | 3 | pyscal3 includes different methods to explore the local environment of a particle that rely on the calculation of nearest neighbors. Various approaches to compute the neighbors of particles are discussed here. 4 | 5 | ## Fixed cutoff method 6 | 7 | The most common method to calculate the nearest neighbors of an atom is using a cutoff radius. Commonly, a cutoff is selected as the first minimum of the radial distribution functions. Once a cutoff is selected, the neighbors of an atom are those that fall within this selected radius. The following code snippet will use the cutoff method to calculate neighbors. In this example, `conf.dump` is assumed to 8 | be the input configuration of the system. A cutoff radius of 3 is assumed for calculation of neighbors. 9 | 10 | ``` python 11 | from pyscal3 import System 12 | sys = pc.System() 13 | sys.read.file('conf.dump') 14 | sys.find.neighbors(method='cutoff', cutoff=3) 15 | ``` 16 | 17 | ## Adaptive cutoff methods 18 | 19 | A fixed cutoff radius can introduce limitations to explore the local environment of the particle in some cases: 20 | 21 | - At finite temperatures, when thermal fluctuations take place, the selection of a fixed cutoff may result in an inaccurate description of the local environment. 22 | - If there is more than one structure present in the system, for example, bcc and fcc, the selection of cutoff such that it includes the first shell of both structures can be difficult. 23 | 24 | In order to achieve a more accurate description of the local environment, various adaptive approaches have been proposed. Two of the methods implemented in the module are discussed below. 25 | 26 | ### Solid angle based nearest neighbor algorithm (SANN) 27 | 28 | SANN algorithm [1] determines the cutoff radius by counting the solid angles around an atom and equating it to $4\pi$. The algorithm solves the following equation iteratively. 29 | 30 | $$ 31 | R_i^{(m)} = \frac{\sum_{j=1}^m r_{i,j}}{m-2} < r_{i, m+1} 32 | $$ 33 | 34 | where $i$ is the host atom, $j$ are its neighbors with $r_{ij}$ is the distance between atoms $i$ and $j$. $R_i$ is the cutoff radius for each particle $i$ which is found by increasing the neighbor of neighbors $m$ iteratively. For a description of the algorithm and more details, please check the reference [1]. SANN algorithm can be used to find the neighbors by, 35 | 36 | ``` python 37 | from pyscal3 import System 38 | sys = pc.System() 39 | sys.read.file('conf.dump') 40 | sys.find.neighbors(method='cutoff', cutoff='sann') 41 | ``` 42 | 43 | Since SANN algorithm involves sorting, a sufficiently large cutoff is used in the beginning to reduce the number entries to be sorted. This parameter is calculated by, 44 | 45 | $$ 46 | r_{initial} = \mathrm{threshold} \times \bigg(\frac{\mathrm{Simulation~box~volume}}{\mathrm{Number~of~particles}}\bigg)^{\frac{1}{3}} 47 | $$ 48 | 49 | a tunable `threshold` parameter can be set through function arguments. 50 | 51 | ### Adaptive cutoff method 52 | 53 | An adaptive cutoff specific for each atom can also be found using an algorithm similar to adaptive common neighbor analysis [2]. This adaptive cutoff is calculated by first making a list of all neighbor 54 | distances for each atom similar to SANN method. Once this list is available, then the cutoff is calculated from, 55 | 56 | $$ 57 | r_{cut}(i) = \mathrm{padding}\times \bigg(\frac{1}{\mathrm{nlimit}} \sum_{j=1}^{\mathrm{nlimit}} r_{ij} \bigg) 58 | $$ 59 | 60 | This method can be chosen by, 61 | 62 | ``` python 63 | from pyscal3 import System 64 | sys = pc.System() 65 | sys.read.file('conf.dump') 66 | sys.find.neighbors(method='cutoff', cutoff='adaptive') 67 | ``` 68 | 69 | The `padding` and `nlimit` parameters in the above equation can be tuned using the respective keywords. 70 | 71 | Either of the adaptive method can be used to find neighbors, which can then be used to calculate Steinhardt\'s parameters or their averaged version. 72 | 73 | ## Voronoi tessellation 74 | 75 | [Voronoi tessellation](https://en.wikipedia.org/wiki/Voronoi_diagram) provides a completely parameter free geometric approach for calculation of neighbors. [Voro++](http://math.lbl.gov/voro++/) code is used for Voronoi tessellation. Neighbors can be calculated using this method by, 76 | 77 | ``` python 78 | from pyscal3 import System 79 | sys = pc.System() 80 | sys.read.file('conf.dump') 81 | sys.find.neighbors(method='voronoi') 82 | ``` 83 | 84 | Finding neighbors using Voronoi tessellation also calculates a weight for each neighbor. The weight of a neighbor $j$ towards a host atom $i$ is given by, 85 | 86 | $$ 87 | W_{ij} = \frac{A_{ij}}{\sum_{j=1}^N A_{ij}} 88 | $$ 89 | 90 | where $A_{ij}$ is the area of Voronoi facet between atom $i$ and $j$, $N$ are all the neighbors identified through Voronoi tessellation. This weight can be used later for calculation of weighted Steinhardt's 91 | parameters. Optionally, it is possible to choose the exponent for this weight. Option `voroexp` is used to set this option. For example if `voroexp=2`, the weight would be calculated as, 92 | 93 | $$ 94 | W_{ij} = \frac{A_{ij}^2}{\sum_{j=1}^N A_{ij}^2} 95 | $$ 96 | 97 | ## References 98 | 99 | 1. van Meel, J. A., Filion, L., Valeriani, C. & Frenkel, D. A parameter-free, solid-angle based, nearest- neighbor algorithm. J Chem Phys 234107, (2012). 100 | 2. Stukowski, A. Structure identification methods for atomistic simulations of crystalline materials. Modelling and Simulation in Materials Science and Engineering 20, (2012). 101 | -------------------------------------------------------------------------------- /docs/methods/02_steinhardt.md: -------------------------------------------------------------------------------- 1 | # Steinhardt's parameters 2 | 3 | Steinhardt's bond orientational order parameters [1] are a set of parameters based on [spherical harmonics](https://en.wikipedia.org/wiki/Spherical_harmonics) to explore the local atomic environment. These parameters have been used extensively for various uses such as distinction of crystal structures, identification of solid and liquid atoms and identification of defects. 4 | 5 | These parameters, which are rotationally and translationally invariant are defined by, 6 | 7 | $$ 8 | q_l (i) = \Big( \frac{4\pi}{2l+1} \sum_{m=-l}^l | q_{lm}(i) |^2 \Big )^{\frac{1}{2}} 9 | $$ 10 | 11 | where, 12 | 13 | $$ 14 | q_{lm} (i) = \frac{1}{N(i)} \sum_{j=1}^{N(i)} Y_{lm}(\pmb{r}_{ij}) 15 | $$ 16 | 17 | in which $Y_{lm}$ are the spherical harmonics and $N(i)$ is the number of neighbours of particle $i$, $\pmb{r}_{ij}$ is the vector connecting particles $i$ and $j$, and $l$ and $m$ are both intergers with $m \in [-l,+l]$. Various parameters have found specific uses, such as $q_2$ and $q_6$ for identification of crystallinity, $q_6$ for identification of solidity, and $q_4$ and $q_6$ for distinction of crystal structures [2]. Commonly this method uses a cutoff radius to identify the neighbors of an atom. 18 | Once the cutoff is chosen and neighbors are calculated, the calculation of Steinhardt's parameters is straightforward. 19 | 20 | ``` python 21 | q = sys.calculate.steinhardt_parameter([4,6]) 22 | ``` 23 | 24 | ## Averaged Steinhardt's parameters 25 | 26 | At high temperatures, thermal vibrations affect the atomic positions. This in turn leads to overlapping distributions of $q_l$ parameters, which makes the identification of crystal structures difficult. To address this problem, the averaged version $\bar{q}_l$ of Steinhardt's parameters was introduced by Lechner and Dellago [3]. $\bar{q}_l$ is given by, 27 | 28 | $$ 29 | \bar{q}_l (i) = \Big( \frac{4\pi}{2l+1} \sum_{m=-l}^l \Big| \frac{1}{\tilde{N}(i)} \sum_{k=0}^{\tilde{N}(i)} q_{lm}(k) \Big|^2 \Big )^{\frac{1}{2}} 30 | $$ 31 | 32 | where the sum from $k=0$ to $\tilde{N}(i)$ is over all the neighbors and the particle itself. The averaged parameters takes into account the first neighbor shell and also information from the neighboring atoms and thus reduces the overlap between the distributions. Commonly $\bar{q}_4$ and $\bar{q}_6$ are used in identification of crystal structures. 33 | Averaged versions can be calculated by setting the keyword `averaged=True` as follows. 34 | 35 | ``` python 36 | aq = sys.calculate.steinhardt_parameter([4,6], averaged=True) 37 | ``` 38 | 39 | ## Voronoi weighted Steinhardt's parameters 40 | 41 | In order to improve the resolution of crystal structures Mickel et al [2] proposed weighting the contribution of each neighbor to the Steinhardt parameters by the ratio of the area of the Voronoi facet shared between the neighbor and host atom. The weighted parameters are given by, 42 | 43 | $$ 44 | q_{lm} (i) = \frac{1}{N(i)} \sum_{j=1}^{N(i)} \frac{A_{ij}}{A} Y_{lm}(\pmb{r}_{ij}) 45 | $$ 46 | 47 | where $A_{ij}$ is the area of the Voronoi facet between atoms $i$ and $j$ and $A$ is the sum of the face areas of atom $i$. In pyscal, the area weights are already assigned during the neighbor calculation phase when the Voronoi method is used to calculate neighbors. The Voronoi weighted Steinhardt's parameters can be calculated as follows, 48 | 49 | ``` python 50 | sys.find.neighbors(method='voronoi') 51 | q = sys.calculate.steinhardt_parameter([4,6]) 52 | ``` 53 | 54 | The weighted Steinhardt's parameters can also be averaged as described above. Once again, the keyword `averaged=True` can be used for this purpose. 55 | 56 | ``` python 57 | sys.find_neighbors(method='voronoi') 58 | q = sys.calculate.steinhardt_parameter([4,6], averaged=True) 59 | ``` 60 | 61 | It was also proposed that higher powers of the weight [4] $\frac{A_{ij}^{\alpha}}{A(\alpha)}$ where $\alpha = 2, 3$ can also be used, where $A(\alpha) = \sum_{j=1}^{N(i)} A_{ij}^{\alpha}$ The value of this can be set using the keyword `voroexp` during the neighbor calculation phase. 62 | 63 | ``` python 64 | sys.find.neighbors(method='voronoi', voroexp=2) 65 | ``` 66 | 67 | If the value of `voroexp` is set to 0, the neighbors would be found using Voronoi method, but the calculated Steinhardt's parameters will not be weighted. 68 | 69 | ## References 70 | 71 | 1. Steinhardt, P. J., Nelson, D. R. & Ronchetti, M. Bond-orientational order in liquids and glasses. Physical Review B 28, 784–805 (1983). 72 | 2. Mickel, W., Kapfer, S. C., Schröder-Turk, G. E. & Mecke, K. Shortcomings of the bond orientational order parameters for the analysis of disordered particulate matter. Journal of Chemical Physics 138, (2013). 73 | 3. Lechner, W. & Dellago, C. Accurate determination of crystal structures based on averaged local bond order parameters. Journal of Chemical Physics 129, (2008). 74 | 4. Haeberle, J., Sperl, M. & Born, P. Distinguishing noisy crystalline structures using bond orientational order parameters. Eur. Phys. J. E 42, 149 (2019). 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/methods/03_solidliquid.md: -------------------------------------------------------------------------------- 1 | # Classification of atoms as solid or liquid 2 | 3 | 4 | pyscal can also be used to distinguish solid and liquid atoms. The classification is based on Steinhardt's parameters, 5 | specifically $q_6$. The method defines two neighboring atoms $i$ and $j$ as having solid bonds if a parameter $s_{ij}$ [1], 6 | 7 | $$ 8 | s_{ij} = \sum_{m=-6}^6 q_{6m}(i) q_{6m}^*(j) \geq \mathrm{threshold} 9 | $$ 10 | 11 | Additionally, a second order parameter is used to improve the distinction in solid-liquid boundaries [2]. This is defined by the criteria, 12 | 13 | $$ 14 | \langle s_{ij} \rangle > \mathrm{avgthreshold} 15 | $$ 16 | 17 | If a particle has $n$ number of bonds with $s_{ij} \geq \mathrm{threshold}$ and the above condition is also satisfied, it is considered as a solid. The solid atoms can be clustered to find the largest solid cluster of atoms. 18 | 19 | Finding solid atoms in liquid start with reading in a file and calculation of neighbors. 20 | 21 | ``` python 22 | from pyscal3 import System 23 | sys = System('conf.dump') 24 | sys.find.neighbors(method='cutoff', cutoff=4) 25 | ``` 26 | 27 | Once again, there are various methods for finding neighbors. Once the neighbors are calculated, solid atoms can be found directly by, 28 | 29 | ``` python 30 | sys.find.solids(bonds=6, threshold=0.5, avgthreshold=0.6, cluster=True) 31 | ``` 32 | 33 | `bonds` set the number of minimum bonds a particle should have (as defined above), `threshold` and `avgthreshold` are the same quantities that appear in the equations above. Setting the keyword `cluster` to True returns the size of the largest solid cluster. It is also possible to check if each atom is solid or not. 34 | 35 | ``` python 36 | sys.atoms.solid 37 | ``` 38 | 39 | ## References 40 | 41 | 1. Auer, S. & Frenkel, D. Numerical Simulation of Crystal Nucleation in Colloids. in Advanced Computer Simulation: Approaches for Soft Matter Sciences I (eds. Dr. Holm, C. & Prof. Dr. Kremer, K.) 149–208 (Springer Berlin Heidelberg, Berlin, Heidelberg, 2005). doi:10.1007/b99429. 42 | 2. Bokeloh, J., Wilde, G., Rozas, R. E., Benjamin, R. & Horbach, J. Nucleation barriers for the liquid-to-crystal transition in simple metals: Experiment vs. simulation. European Physical Journal: Special Topics 223, 511–526 (2014). 43 | 44 | -------------------------------------------------------------------------------- /docs/methods/04_disorder.md: -------------------------------------------------------------------------------- 1 | # Disorder parameter 2 | 3 | Kawasaki and Onuki [1] proposed a disorder variable based on Steinhardt's order paramaters which can be used to distinguish between ordered and disordered structures 4 | 5 | The disorder variable for an atom is defined as, 6 | 7 | $$ 8 | D_j = \frac{1}{n_b^j} \sum_{k \in neighbors } [S_{jj} + S_{kk} - 2S_{jk}] 9 | $$ 10 | 11 | where S is given by, 12 | 13 | $$ 14 | S_{jk} = \sum_{-l \leq m \leq l} q_{lm}^j (q_{lm}^k)^* 15 | $$ 16 | 17 | l = 6 was used in the original publication as it is a good indicator of crystallinity. However, l = 4 can also be used for treating bcc structures. An averaged disorder parameter for each atom can also be calculated in pyscal, 18 | 19 | $$ 20 | \bar{D}_j = \frac{1}{n_b^j} \sum_{k \in neighbors } D_j 21 | $$ 22 | 23 | In pyscal, disorder parameter can be calculated by the following code-block, 24 | 25 | ``` python 26 | from pyscal3 import System 27 | sys = System('conf.dump') 28 | sys.find.neighbors(method='cutoff', cutoff=0) 29 | q = fcc.calculate.steinhardt_parameter(6) 30 | sys.calculate.disorder(averaged=True, q=6) 31 | ``` 32 | 33 | The value of q can be replaced with whichever. The calculated values can be accessed by, `sys.atoms.steinhardt.disorder` 34 | 35 | ## References 36 | 37 | 1. Kawasaki, T. & Onuki, A. Construction of a disorder variable from Steinhardt order parameters in binary mixtures at high densities in three dimensions. Journal of Chemical Physics 135, (2011). 38 | -------------------------------------------------------------------------------- /docs/methods/05_angular.md: -------------------------------------------------------------------------------- 1 | # Angular parameters 2 | 3 | ## Angular criteria for identification of diamond structure 4 | 5 | Angular parameter introduced by Uttormark et al is used to measure the tetrahedrality of local atomic structure. An atom belonging to diamond structure has four nearest neighbors which gives rise to six three body angles around the atom. The angular parameter $A$ is then defined as, 6 | 7 | $$ 8 | A = \sum_{i=1}^6 (\cos(\theta_i)+\frac{1}{3})^2 9 | $$ 10 | 11 | An atom belonging to diamond structure would show the value of angular params close to 0. Angular parameter can be calculated in pyscal using the following method - 12 | 13 | ``` python 14 | from pyscal3 import System 15 | sys = System('conf.dump') 16 | sys.find.neighbors(method='cutoff', cutoff='adaptive') 17 | sys.calculate.angular_criteria() 18 | ``` 19 | 20 | The calculated angular criteria value can be accessed for each atom using `sys.atoms.angular_parameters.diamond_angle`. 21 | 22 | ## $\chi$ parameters for structural identification 23 | 24 | $\chi$ parameters introduced by Ackland and Jones [1] measures all local angles created by an atom with its neighbors and creates a histogram of these angles to produce vector which can be used to identify structures. After finding the neighbors of an atom, $\cos \theta_{ijk}$ for atoms j and k which are neighbors of i is calculated for all combinations of i, j and k. The set of all calculated cosine values are then added to a histogram with the following bins - \[-1.0, -0.945, -0.915, -0.755, -0.705, -0.195, 0.195, 0.245, 0.795, 1.0\]. Compared to $\chi$ parameters from $\chi_0$ to $\chi_7$ in the associated publication, the vector calculated in pyscal contains values from $\chi_0$ to $\chi_8$ which is due to an additional $\chi$ parameter which measures the number of neighbors between cosines -0.705 to -0.195. The $\chi$ vector is characteristic of the local atomic environment and can be used to identify crystal structures, details of which can be found in the publication[1]. 25 | 26 | $\chi$ parameters can be calculated in pyscal using, 27 | 28 | ``` python 29 | import pyscal.core as pc 30 | from pyscal3 import System 31 | sys = System('conf.dump') 32 | sys.find.neighbors(method='cutoff', cutoff='adaptive') 33 | sys.calculate.chi_params() 34 | ``` 35 | 36 | The calculated values for each atom can be accessed using `sys.atoms.angular_parameters.chi_params`. 37 | 38 | ## References 39 | 40 | 1. Ackland, G. J. & Jones, A. P. Applications of local crystal structure measures in experiment and simulation. Physical Review B - Condensed Matter and Materials Physics 73, 1–7 (2006). 41 | -------------------------------------------------------------------------------- /docs/methods/06_voronoi.md: -------------------------------------------------------------------------------- 1 | # Voronoi tessellation to identify local structures 2 | 3 | Voronoi tessellation can be used for identification of local structure by counting the number of faces of the Voronoi polyhedra of an atom [1,2]. For each atom a vector $\langle n_3~n_4~n_5~n_6 \rangle$ can be calculated where $n_3$ is the number of Voronoi faces of the associated Voronoi polyhedron with three vertices, $n_4$ is with four vertices and so on. Each perfect crystal structure such as a signature vector, for example, bcc can be identified by $\langle 0~6~0~8 \rangle$ 4 | and fcc can be identified using $\langle 0~12~0~0 \rangle$. It is also a useful tool for identifying icosahedral structure which has the fingerprint $\langle 0~0~12~0 \rangle$. In pyscal, the voronoi vector can be calculated using, 5 | 6 | ``` python 7 | from pyscal3 import System 8 | sys = System('conf.dump') 9 | sys.find.neighbors(method='voronoi') 10 | sys.calculate.voronoi_vector() 11 | ``` 12 | 13 | The vector for each atom can be accessed using `sys.atoms.voronoi.vector`. Furthermore, the associated Voronoi volume of the polyhedron, which may be indicative of the local structure, is also automatically calculated 14 | when finding neighbors. This value for each atom can be accessed by `sys.atoms.voronoi.volume`. 15 | 16 | ## References 17 | 18 | 1. Finney, J. L. Random Packings and the Structure of Simple Liquids. I. The Geometry of Random Close Packing. Proceedings of the Royal Society A: Mathematical, Physical and Engineering Sciences 319, 479–493 (1970). 19 | 2. Tanemura, M. et al. Geometrical Analysis of Crystallization of the Soft-Core Model. Progress of Theoretical Physics 58, 1079–1095 (1977). 20 | -------------------------------------------------------------------------------- /docs/methods/07_centrosymmetry.md: -------------------------------------------------------------------------------- 1 | # Centrosymmetry parameter 2 | 3 | Centrosymmetry parameter (CSP) was introduced by Kelchner et al. [1] to identify defects in crystals. The parameter measures the loss of local symmetry. For an atom with $N$ nearest neighbors, the parameter is given by, 4 | 5 | $$ 6 | \mathrm{CSP} = \sum_{i=1}^{N/2} \big | \textbf{r}_i + \textbf{r}_{i+N/2} \big |^2 7 | $$ 8 | 9 | $\textbf{r}_i$ and $\textbf{r}_{i+N/2}$ are vectors from the central atom to two opposite pairs of neighbors. There are two main methods to identify the opposite pairs of neighbors as described in [this publication](https://arxiv.org/abs/2003.08879). The first of the approaches is called Greedy Edge Selection (GES) [2] 10 | and is implemented in [LAMMPS](https://lammps.sandia.gov/) and [Ovito](https://www.ovito.org/). GES algorithm calculates a weight $w_{ij} = |\textbf{r}_i + \textbf{r}_j|$ for all combinations of neighbors around an atom and calculates CSP over the smallest $N/2$ weights. 11 | 12 | A centrosymmetry parameter calculation using GES algorithm can be carried out as follows- 13 | 14 | ``` python 15 | from pyscal3 import System 16 | sys = System('conf.dump') 17 | csm = sys.calculate.centrosymmetry(nmax = 12) 18 | ``` 19 | 20 | `nmax` parameter specifies the number of nearest neighbors to be considered for the calculation of CSP. 21 | 22 | ## References 23 | 24 | 1. Kelchner, C. L., Plimpton, S. J. & Hamilton, J. C. Dislocation nucleation and defect structure during surface indentation. Phys. Rev. B 58, 11085–11088 (1998). 25 | 2. Stukowski, A. Structure identification methods for atomistic simulations of crystalline materials. Modelling and Simulation in Materials Science and Engineering 20, (2012). 26 | 27 | -------------------------------------------------------------------------------- /docs/methods/08_entropy.md: -------------------------------------------------------------------------------- 1 | # Entropy parameter 2 | 3 | The entropy parameter was introduced by Piaggi et al [1] for identification of defects and distinction between solid and liquid. The entropy paramater $s_s^i$ is defined as, 4 | 5 | $$ 6 | s_s^i = -2\pi\rho k_B \int_0^{r_m} [g_m^i(r)\ln g_m^i(r) - g_m^i(r) + 1] r^2 dr 7 | $$ 8 | 9 | where $r_m$ is the upper bound of integration and $g_m^i$ is radial distribution function centered on atom $i$, 10 | 11 | $$ 12 | g_m^i(r) = \frac{1}{4\pi\rho r^2} \sum_j \frac{1}{\sqrt{2\pi\sigma^2}} \exp{-(r-r_{ij})^2/(2\sigma^2)} 13 | $$ 14 | 15 | $r_{ij}$ is the interatomic distance between atom $i$ and its neighbors $j$ and $\sigma$ is a broadening parameter. 16 | 17 | The averaged version of entropy parameters $\bar{s}_s^i$ can be calculated by using a simple averaging over the neighbors given by, 18 | 19 | $$ 20 | \bar{s}_s^i = \frac{\sum_j s_s^j + s_s^i}{N + 1} 21 | $$ 22 | 23 | Entropy parameters can be calculated in pyscal using the following code, 24 | 25 | ``` python 26 | from pyscal3 import System 27 | sys = System('conf.dump') 28 | sys.find.neighbors(method="cutoff", cutoff=0) 29 | lattice_constant=4.00 30 | avg_entropy = sys.calculate.entropy(1.4*lattice_constant, averaged=True) 31 | ``` 32 | 33 | The value of $r_m$ is provided in units of lattice constant. Further parameters shown above, such as $\sigma$ can be specified using the various keyword arguments. 34 | 35 | In pyscal, a slightly different version of $s_s^i$ is calculated. This is given by, 36 | 37 | $$ 38 | s_s^i = -\rho \int_0^{r_m} [g_m^i(r)\ln g_m^i(r) - g_m^i(r) + 1] r^2 dr 39 | $$ 40 | 41 | The prefactor $2\pi k_B$ is dropped in the entropy values calculated in pyscal. 42 | 43 | ## References 44 | 45 | 1. Piaggi, P. M. & Parrinello, M. Entropy based fingerprint for local crystalline order. Journal of Chemical Physics 147, (2017). 46 | -------------------------------------------------------------------------------- /docs/methods_landing.md: -------------------------------------------------------------------------------- 1 | # Descriptors 2 | 3 | pyscal can calculate the following descriptors: 4 | 5 | | | | | 6 | | -------- | ------- | ------- | 7 | | Isolating local environment: *Various approaches including fixed cutoff, adaptive cutoff, SANN, and Voronoi* | [Method](methods/01_neighbors) | [Example](../examples/05_finding_neighbors) | 8 | | Bond-orientational order parameters: *For structure identification* | [Method](methods/02_steinhardt) | [Example](../examples/06_steinhardt_parameters) | 9 | | Disorder parameters: *identify regions of disorder in crystalline materials* | [Method](methods/04_disorder) | [Example](../examples/07_disorder_parameters) | 10 | | Angular parameters: *parameters to quantify the angle around an atom, useful for detecting diamond structures.* | [Method](methods/05_angular) | [Example](../examples/08_angular_parameters) | 11 | | Solid identification: *distinguish solid atoms in liquid. Clustering methods to cluster atoms based on any property.* | [Method](methods/03_solidliquid) | [Example](../examples/09_distinguishing_solid_liquid) | 12 | | Voronoi tessellation: *to calculate structural vector and Voronoi volume.* | [Method](methods/06_voronoi) | [Example](../examples/10_voronoi_tessellation) | 13 | | Chi parameters: *angle-based params for structural identification.* | [Method](methods/05_angular) | [Example](../examples/11_chi_params) | 14 | | Centrosymmetry parameter: *for finding breaks in the ordered crystal.* | [Method](methods/07_centrosymmetry) | [Example](../examples/12_centrosymmetry_parameter) | 15 | | Chemical short range order: *multi-component chemical short range order in alloys.* | | [Example](../examples/13_short_range_order) | 16 | | Common neighbor analysis: *CNA, adaptive CNA to identify bcc, fcc, and hcp. Extension of CNA to identify various flavors of diamond lattice.* | | [Example](../examples/14_common_neighbor_analysis) | 17 | | Entropy parameter: *for distinguishing crystal structures.* | [Method](methods/08_entropy) | [Example](../examples/16_entropy_parameter) | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/methodsandexamples.rst: -------------------------------------------------------------------------------- 1 | Methods and examples 2 | -------------------- 3 | 4 | .. toctree:: 5 | 6 | methodsindex 7 | examplesindex -------------------------------------------------------------------------------- /docs/methodsindex.rst: -------------------------------------------------------------------------------- 1 | Methods 2 | ------- 3 | 4 | .. toctree:: 5 | 6 | methods/01_neighbors 7 | methods/02_steinhardt 8 | methods/03_solidliquid 9 | methods/04_disorder 10 | methods/05_angular 11 | methods/06_voronoi 12 | methods/07_centrosymmetry 13 | methods/08_entropy 14 | 15 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | pyscal reference 2 | ================ 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | pyscal 8 | -------------------------------------------------------------------------------- /docs/prologue/acknowledgements.md: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | 3 | ## Developer 4 | 5 | - [Sarath 6 | Menon](http://sarathmenon.me) 7 | sarath.menon@pyscal.org 8 | 9 | ## Contributers 10 | 11 | - [Jan Janßen](https://jan-janssen.com/) - developing and 12 | maintaining a [conda-forge](https://conda-forge.org/) recipe. 13 | 14 | - [Pedro Antonio Santos Flórez](https://github.com/pedroantoniosantosf) - addition of the pairwise multicomponent short range order parameter. 15 | 16 | ## Acknowledgements 17 | 18 | We acknowledge [Bond order 19 | analysis](https://github.com/WolfgangLechner/StructureAnalysis) code for the 20 | inspiration and the base for what later grew to be `pyscal`. We are also 21 | thankful to the developers of [Voro++](math.lbl.gov/voro++/) and 22 | [pybind11](https://pybind11.readthedocs.io/en/stable/) for developing 23 | the great tools that we could use in `pyscal`. We are grateful for the help and support received during the [E-CAM High Throughput Computing ESDW](https://www.e-cam2020.eu/event/4424/?instance_id=71) held in 24 | [Turin](https://www.polito.it/?lang=en) in 2018 and 2019. This module was developed at the [Interdisciplinary Centre for Advanced 25 | Materials Simulation](http://www.icams.de/content), at the [Ruhr 26 | University Bochum](https://www.ruhr-uni-bochum.de/en), Germany. 27 | 28 | 29 | In addition, the following people are acknowledged: 30 | 31 | - Grisell Díaz Leines 32 | - Jutta Rogal 33 | - Alberto Ferrari 34 | - Abril Azócar Guzmán 35 | - Matteo Rinaldi 36 | - Yanyan Liang 37 | - David W.H. Swenson 38 | - Alan O'Cais 39 | -------------------------------------------------------------------------------- /docs/prologue/api.md: -------------------------------------------------------------------------------- 1 | # API reference 2 | 3 | for pyscal API reference, see [here](https://docs.pyscal.org). -------------------------------------------------------------------------------- /docs/prologue/citing.md: -------------------------------------------------------------------------------- 1 | 2 | # Citing the code 3 | 4 | If you use pyscal in your work, the citation of the [following 5 | article](https://joss.theoj.org/papers/10.21105/joss.01824) will be 6 | greatly appreciated: 7 | 8 | Sarath Menon, Grisell Díaz Leines and Jutta Rogal (2019). pyscal: A 9 | python module for structural analysis of atomic environments. Journal of 10 | Open Source Software, 4(43), 1824, Makefile.dep 20 | 21 | include Makefile.dep 22 | 23 | libvoro++.a: $(objs) 24 | rm -f libvoro++.a 25 | ar rs libvoro++.a $^ 26 | 27 | voro++: libvoro++.a cmd_line.cc 28 | $(CXX) $(CFLAGS) -L. -o voro++ cmd_line.cc -lvoro++ 29 | 30 | %.o: %.cc 31 | $(CXX) $(CFLAGS) -c $< 32 | 33 | help: Doxyfile $(SOURCE) 34 | doxygen Doxyfile 35 | 36 | clean: 37 | rm -f $(objs) voro++ libvoro++.a 38 | 39 | .PHONY: all help execs depend 40 | -------------------------------------------------------------------------------- /lib/voro++/Makefile.dep: -------------------------------------------------------------------------------- 1 | cell.o: cell.cc config.hh common.hh cell.hh 2 | common.o: common.cc common.hh config.hh 3 | container.o: container.cc container.hh config.hh common.hh v_base.hh \ 4 | worklist.hh cell.hh c_loops.hh v_compute.hh rad_option.hh 5 | unitcell.o: unitcell.cc unitcell.hh config.hh cell.hh common.hh 6 | v_compute.o: v_compute.cc worklist.hh v_compute.hh config.hh cell.hh \ 7 | common.hh rad_option.hh container.hh v_base.hh c_loops.hh \ 8 | container_prd.hh unitcell.hh 9 | c_loops.o: c_loops.cc c_loops.hh config.hh 10 | v_base.o: v_base.cc v_base.hh worklist.hh config.hh v_base_wl.cc 11 | wall.o: wall.cc wall.hh cell.hh config.hh common.hh container.hh \ 12 | v_base.hh worklist.hh c_loops.hh v_compute.hh rad_option.hh 13 | pre_container.o: pre_container.cc config.hh pre_container.hh c_loops.hh \ 14 | container.hh common.hh v_base.hh worklist.hh cell.hh v_compute.hh \ 15 | rad_option.hh 16 | container_prd.o: container_prd.cc container_prd.hh config.hh common.hh \ 17 | v_base.hh worklist.hh cell.hh c_loops.hh v_compute.hh unitcell.hh \ 18 | rad_option.hh 19 | -------------------------------------------------------------------------------- /lib/voro++/README: -------------------------------------------------------------------------------- 1 | Voro++ library source files 2 | =========================== 3 | This directory contains the source code for the library. For full details of 4 | each file, see the files section of the online reference manual at 5 | http://math.lbl.gov/voro++/doc/refman/ 6 | 7 | Several other files are present: 8 | 9 | Makefile - the GNU Makefile controlling the compilation. 10 | 11 | Makefile.dep - a file containing all the dependencies of the source files, 12 | automatically generated by the GNU compiler. 13 | 14 | cmd_line.cc - source file for creating the command-line utility that makes use 15 | of the library. 16 | 17 | Doxyfile - configuration file for Doxygen, used to automatically generate 18 | documentation based on the source code comments. 19 | 20 | worklist_gen.pl - perl script for automatically generating the worklist.hh and 21 | v_base_wl.cc files. 22 | -------------------------------------------------------------------------------- /lib/voro++/common.cc: -------------------------------------------------------------------------------- 1 | // Voro++, a 3D cell-based Voronoi library 2 | // 3 | // Author : Chris H. Rycroft (LBL / UC Berkeley) 4 | // Email : chr@alum.mit.edu 5 | // Date : August 30th 2011 6 | 7 | /** \file common.cc 8 | * \brief Implementations of the small helper functions. */ 9 | 10 | #include "common.hh" 11 | 12 | namespace voro { 13 | 14 | /** \brief Prints a vector of integers. 15 | * 16 | * Prints a vector of integers. 17 | * \param[in] v the vector to print. 18 | * \param[in] fp the file stream to print to. */ 19 | void voro_print_vector(std::vector &v,FILE *fp) { 20 | int k=0,s=v.size(); 21 | while(k+4 &v,FILE *fp) { 40 | int k=0,s=v.size(); 41 | while(k+4 &v,FILE *fp) { 63 | int j,k=0,l; 64 | if(v.size()>0) { 65 | l=v[k++]; 66 | if(l<=1) { 67 | if(l==1) fprintf(fp,"(%d)",v[k++]); 68 | else fputs("()",fp); 69 | } else { 70 | j=k+l; 71 | fprintf(fp,"(%d",v[k++]); 72 | while(k 14 | #include 15 | #include 16 | 17 | #include "config.hh" 18 | 19 | namespace voro { 20 | 21 | /** \brief Function for printing fatal error messages and exiting. 22 | * 23 | * Function for printing fatal error messages and exiting. 24 | * \param[in] p a pointer to the message to print. 25 | * \param[in] status the status code to return with. */ 26 | inline void voro_fatal_error(const char *p,int status) { 27 | fprintf(stderr,"voro++: %s\n",p); 28 | exit(status); 29 | } 30 | 31 | /** \brief Prints a vector of positions. 32 | * 33 | * Prints a vector of positions as bracketed triplets. 34 | * \param[in] v the vector to print. 35 | * \param[in] fp the file stream to print to. */ 36 | inline void voro_print_positions(std::vector &v,FILE *fp=stdout) { 37 | if(v.size()>0) { 38 | fprintf(fp,"(%g,%g,%g)",v[0],v[1],v[2]); 39 | for(int k=3;(unsigned int) k &v,FILE *fp=stdout); 62 | void voro_print_vector(std::vector &v,FILE *fp=stdout); 63 | void voro_print_face_vertices(std::vector &v,FILE *fp=stdout); 64 | 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /lib/voro++/config.hh: -------------------------------------------------------------------------------- 1 | // Voro++, a 3D cell-based Voronoi library 2 | // 3 | // Author : Chris H. Rycroft (LBL / UC Berkeley) 4 | // Email : chr@alum.mit.edu 5 | // Date : August 30th 2011 6 | 7 | /** \file config.hh 8 | * \brief Master configuration file for setting various compile-time options. */ 9 | 10 | #ifndef VOROPP_CONFIG_HH 11 | #define VOROPP_CONFIG_HH 12 | 13 | namespace voro { 14 | 15 | // These constants set the initial memory allocation for the Voronoi cell 16 | /** The initial memory allocation for the number of vertices. */ 17 | const int init_vertices=256; 18 | /** The initial memory allocation for the maximum vertex order. */ 19 | const int init_vertex_order=64; 20 | /** The initial memory allocation for the number of regular vertices of order 21 | * 3. */ 22 | const int init_3_vertices=256; 23 | /** The initial memory allocation for the number of vertices of higher order. 24 | */ 25 | const int init_n_vertices=8; 26 | /** The initial buffer size for marginal cases used by the suretest class. */ 27 | const int init_marginal=64; 28 | /** The initial size for the delete stack. */ 29 | const int init_delete_size=256; 30 | /** The initial size for the auxiliary delete stack. */ 31 | const int init_delete2_size=256; 32 | /** The initial size for the wall pointer array. */ 33 | const int init_wall_size=32; 34 | /** The default initial size for the ordering class. */ 35 | const int init_ordering_size=4096; 36 | /** The initial size of the pre_container chunk index. */ 37 | const int init_chunk_size=256; 38 | 39 | // If the initial memory is too small, the program dynamically allocates more. 40 | // However, if the limits below are reached, then the program bails out. 41 | /** The maximum memory allocation for the number of vertices. */ 42 | const int max_vertices=16777216; 43 | /** The maximum memory allocation for the maximum vertex order. */ 44 | const int max_vertex_order=2048; 45 | /** The maximum memory allocation for the any particular order of vertex. */ 46 | const int max_n_vertices=16777216; 47 | /** The maximum buffer size for marginal cases used by the suretest class. */ 48 | const int max_marginal=16777216; 49 | /** The maximum size for the delete stack. */ 50 | const int max_delete_size=16777216; 51 | /** The maximum size for the auxiliary delete stack. */ 52 | const int max_delete2_size=16777216; 53 | /** The maximum amount of particle memory allocated for a single region. */ 54 | const int max_particle_memory=16777216; 55 | /** The maximum size for the wall pointer array. */ 56 | const int max_wall_size=2048; 57 | /** The maximum size for the ordering class. */ 58 | const int max_ordering_size=67108864; 59 | /** The maximum size for the pre_container chunk index. */ 60 | const int max_chunk_size=65536; 61 | 62 | /** The chunk size in the pre_container classes. */ 63 | const int pre_container_chunk_size=1024; 64 | 65 | #ifndef VOROPP_VERBOSE 66 | /** Voro++ can print a number of different status and debugging messages to 67 | * notify the user of special behavior, and this macro sets the amount which 68 | * are displayed. At level 0, no messages are printed. At level 1, messages 69 | * about unusual cases during cell construction are printed, such as when the 70 | * plane routine bails out due to floating point problems. At level 2, general 71 | * messages about memory expansion are printed. At level 3, technical details 72 | * about memory management are printed. */ 73 | #define VOROPP_VERBOSE 0 74 | #endif 75 | 76 | /** If a point is within this distance of a cutting plane, then the code 77 | * assumes that point exactly lies on the plane. */ 78 | const double tolerance=1e-11; 79 | 80 | /** If a point is within this distance of a cutting plane, then the code stores 81 | * whether this point is inside, outside, or exactly on the cutting plane in 82 | * the marginal cases buffer, to prevent the test giving a different result on 83 | * a subsequent evaluation due to floating point rounding errors. */ 84 | const double tolerance2=2e-11; 85 | 86 | /** The square of the tolerance, used when deciding whether some squared 87 | * quantities are large enough to be used. */ 88 | const double tolerance_sq=tolerance*tolerance; 89 | 90 | /** A large number that is used in the computation. */ 91 | const double large_number=1e30; 92 | 93 | /** A radius to use as a placeholder when no other information is available. */ 94 | const double default_radius=0.5; 95 | 96 | /** The maximum number of shells of periodic images to test over. */ 97 | const int max_unit_voro_shells=10; 98 | 99 | /** A guess for the optimal number of particles per block, used to set up the 100 | * container grid. */ 101 | const double optimal_particles=5.6; 102 | 103 | /** If this is set to 1, then the code reports any instances of particles being 104 | * put outside of the container geometry. */ 105 | #define VOROPP_REPORT_OUT_OF_BOUNDS 0 106 | 107 | /** Voro++ returns this status code if there is a file-related error, such as 108 | * not being able to open file. */ 109 | #define VOROPP_FILE_ERROR 1 110 | 111 | /** Voro++ returns this status code if there is a memory allocation error, if 112 | * one of the safe memory limits is exceeded. */ 113 | #define VOROPP_MEMORY_ERROR 2 114 | 115 | /** Voro++ returns this status code if there is any type of internal error, if 116 | * it detects that representation of the Voronoi cell is inconsistent. This 117 | * status code will generally indicate a bug, and the developer should be 118 | * contacted. */ 119 | #define VOROPP_INTERNAL_ERROR 3 120 | 121 | /** Voro++ returns this status code if it could not interpret the command line 122 | * arguments passed to the command line utility. */ 123 | #define VOROPP_CMD_LINE_ERROR 4 124 | 125 | } 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /lib/voro++/test.cpp: -------------------------------------------------------------------------------- 1 | #include "voro++.hh" 2 | #include 3 | using namespace voro; 4 | using namespace std; 5 | const int particles=20; 6 | 7 | double rnd() {return double(rand())/RAND_MAX;} 8 | 9 | int main(){ 10 | 11 | int i; 12 | int id,nx,ny,nz; 13 | double x,y,z,r,rx,ry,rz; 14 | vector neigh,f_vert; 15 | vector face; 16 | voronoicell_neighbor c; 17 | // Create a container with the geometry given above, and make it 18 | // non-periodic in each of the three coordinates. Allocate space for 19 | // eight particles within each computational block 20 | pre_container pcon(0,1,0,1,0,1,true,true,true,8); 21 | container con(0,1,0,1,0,1,5,5,5,true,true,true,8); 22 | 23 | // Randomly add particles into the container 24 | for(i=0;i 14 | 15 | #include "config.hh" 16 | #include "cell.hh" 17 | 18 | namespace voro { 19 | 20 | /** \brief Class for computation of the unit Voronoi cell associated with 21 | * a 3D non-rectangular periodic domain. */ 22 | class unitcell { 23 | public: 24 | /** The x coordinate of the first vector defining the periodic 25 | * domain. */ 26 | const double bx; 27 | /** The x coordinate of the second vector defining the periodic 28 | * domain. */ 29 | const double bxy; 30 | /** The y coordinate of the second vector defining the periodic 31 | * domain. */ 32 | const double by; 33 | /** The x coordinate of the third vector defining the periodic 34 | * domain. */ 35 | const double bxz; 36 | /** The y coordinate of the third vector defining the periodic 37 | * domain. */ 38 | const double byz; 39 | /** The z coordinate of the third vector defining the periodic 40 | * domain. */ 41 | const double bz; 42 | /** The computed unit Voronoi cell corresponding the given 43 | * 3D non-rectangular periodic domain geometry. */ 44 | voronoicell unit_voro; 45 | unitcell(double bx_,double bxy_,double by_,double bxz_,double byz_,double bz_); 46 | /** Draws an outline of the domain in Gnuplot format. 47 | * \param[in] filename the filename to write to. */ 48 | inline void draw_domain_gnuplot(const char* filename) { 49 | FILE *fp(safe_fopen(filename,"w")); 50 | draw_domain_gnuplot(fp); 51 | fclose(fp); 52 | } 53 | void draw_domain_gnuplot(FILE *fp=stdout); 54 | /** Draws an outline of the domain in Gnuplot format. 55 | * \param[in] filename the filename to write to. */ 56 | inline void draw_domain_pov(const char* filename) { 57 | FILE *fp(safe_fopen(filename,"w")); 58 | draw_domain_pov(fp); 59 | fclose(fp); 60 | } 61 | void draw_domain_pov(FILE *fp=stdout); 62 | bool intersects_image(double dx,double dy,double dz,double &vol); 63 | void images(std::vector &vi,std::vector &vd); 64 | protected: 65 | /** The maximum y-coordinate that could possibly cut the 66 | * computed unit Voronoi cell. */ 67 | double max_uv_y; 68 | /** The maximum z-coordinate that could possibly cut the 69 | * computed unit Voronoi cell. */ 70 | double max_uv_z; 71 | private: 72 | inline void unit_voro_apply(int i,int j,int k); 73 | bool unit_voro_intersect(int l); 74 | inline bool unit_voro_test(int i,int j,int k); 75 | }; 76 | 77 | } 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /lib/voro++/v_base.cc: -------------------------------------------------------------------------------- 1 | // Voro++, a 3D cell-based Voronoi library 2 | // 3 | // Author : Chris H. Rycroft (LBL / UC Berkeley) 4 | // Email : chr@alum.mit.edu 5 | // Date : August 30th 2011 6 | 7 | /** \file v_base.cc 8 | * \brief Function implementations for the base Voronoi container class. */ 9 | 10 | #include "v_base.hh" 11 | #include "config.hh" 12 | 13 | namespace voro { 14 | 15 | /** This function is called during container construction. The routine scans 16 | * all of the worklists in the wl[] array. For a given worklist of blocks 17 | * labeled \f$w_1\f$ to \f$w_n\f$, it computes a sequence \f$r_0\f$ to 18 | * \f$r_n\f$ so that $r_i$ is the minimum distance to all the blocks 19 | * \f$w_{j}\f$ where \f$j>i\f$ and all blocks outside the worklist. The values 20 | * of \f$r_n\f$ is calculated first, as the minimum distance to any block in 21 | * the shell surrounding the worklist. The \f$r_i\f$ are then computed in 22 | * reverse order by considering the distance to \f$w_{i+1}\f$. */ 23 | voro_base::voro_base(int nx_,int ny_,int nz_,double boxx_,double boxy_,double boxz_) : 24 | nx(nx_), ny(ny_), nz(nz_), nxy(nx_*ny_), nxyz(nxy*nz_), boxx(boxx_), boxy(boxy_), boxz(boxz_), 25 | xsp(1/boxx_), ysp(1/boxy_), zsp(1/boxz_), mrad(new double[wl_hgridcu*wl_seq_length]) { 26 | const unsigned int b1=1<<21,b2=1<<22,b3=1<<24,b4=1<<25,b5=1<<27,b6=1<<28; 27 | const double xstep=boxx/wl_fgrid,ystep=boxy/wl_fgrid,zstep=boxz/wl_fgrid; 28 | int i,j,k,lx,ly,lz,q; 29 | unsigned int f,*e=const_cast (wl); 30 | double xlo,ylo,zlo,xhi,yhi,zhi,minr,*radp=mrad; 31 | for(zlo=0,zhi=zstep,lz=0;lz>7&127)-64; 39 | k=(f>>14&127)-64; 40 | if((f&b2)==b2) { 41 | compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i-1,j,k); 42 | if((f&b1)==0) compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i+1,j,k); 43 | } else if((f&b1)==b1) compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i+1,j,k); 44 | if((f&b4)==b4) { 45 | compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i,j-1,k); 46 | if((f&b3)==0) compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i,j+1,k); 47 | } else if((f&b3)==b3) compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i,j+1,k); 48 | if((f&b6)==b6) { 49 | compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i,j,k-1); 50 | if((f&b5)==0) compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i,j,k+1); 51 | } else if((f&b5)==b5) compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i,j,k+1); 52 | } 53 | q--; 54 | while(q>0) { 55 | radp[q]=minr; 56 | f=e[q]; 57 | i=(f&127)-64; 58 | j=(f>>7&127)-64; 59 | k=(f>>14&127)-64; 60 | compute_minimum(minr,xlo,xhi,ylo,yhi,zlo,zhi,i,j,k); 61 | q--; 62 | } 63 | *radp=minr; 64 | e+=wl_seq_length; 65 | radp+=wl_seq_length; 66 | } 67 | } 68 | } 69 | } 70 | 71 | /** Computes the minimum distance from a subregion to a given block. If this distance 72 | * is smaller than the value of minr, then it passes 73 | * \param[in,out] minr a pointer to the current minimum distance. If the distance 74 | * computed in this function is smaller, then this distance is 75 | * set to the new one. 76 | * \param[out] (xlo,ylo,zlo) the lower coordinates of the subregion being 77 | * considered. 78 | * \param[out] (xhi,yhi,zhi) the upper coordinates of the subregion being 79 | * considered. 80 | * \param[in] (ti,tj,tk) the coordinates of the block. */ 81 | void voro_base::compute_minimum(double &minr,double &xlo,double &xhi,double &ylo,double &yhi,double &zlo,double &zhi,int ti,int tj,int tk) { 82 | double radsq,temp; 83 | if(ti>0) {temp=boxx*ti-xhi;radsq=temp*temp;} 84 | else if(ti<0) {temp=xlo-boxx*(1+ti);radsq=temp*temp;} 85 | else radsq=0; 86 | 87 | if(tj>0) {temp=boxy*tj-yhi;radsq+=temp*temp;} 88 | else if(tj<0) {temp=ylo-boxy*(1+tj);radsq+=temp*temp;} 89 | 90 | if(tk>0) {temp=boxz*tk-zhi;radsq+=temp*temp;} 91 | else if(tk<0) {temp=zlo-boxz*(1+tk);radsq+=temp*temp;} 92 | 93 | if(radsq(format)); 102 | 103 | // Check to see if "%n" appears in the format sequence 104 | while(*fmp!=0) { 105 | if(*fmp=='%') { 106 | fmp++; 107 | if(*fmp=='n') return true; 108 | else if(*fmp==0) return false; 109 | } 110 | fmp++; 111 | } 112 | 113 | return false; 114 | } 115 | 116 | #include "v_base_wl.cc" 117 | 118 | } 119 | -------------------------------------------------------------------------------- /lib/voro++/v_base.hh: -------------------------------------------------------------------------------- 1 | // Voro++, a 3D cell-based Voronoi library 2 | // 3 | // Author : Chris H. Rycroft (LBL / UC Berkeley) 4 | // Email : chr@alum.mit.edu 5 | // Date : August 30th 2011 6 | 7 | /** \file v_base.hh 8 | * \brief Header file for the base Voronoi container class. */ 9 | 10 | #ifndef VOROPP_V_BASE_HH 11 | #define VOROPP_V_BASE_HH 12 | 13 | #include "worklist.hh" 14 | 15 | namespace voro { 16 | 17 | /** \brief Class containing data structures common across all particle container classes. 18 | * 19 | * This class contains constants and data structures that are common across all 20 | * particle container classes. It contains constants setting the size of the 21 | * underlying subgrid of blocks that forms the basis of the Voronoi cell 22 | * computations. It also constructs bound tables that are used in the Voronoi 23 | * cell computation, and contains a number of routines that are common across 24 | * all container classes. */ 25 | class voro_base { 26 | public: 27 | /** The number of blocks in the x direction. */ 28 | const int nx; 29 | /** The number of blocks in the y direction. */ 30 | const int ny; 31 | /** The number of blocks in the z direction. */ 32 | const int nz; 33 | /** A constant, set to the value of nx multiplied by ny, which 34 | * is used in the routines that step through blocks in 35 | * sequence. */ 36 | const int nxy; 37 | /** A constant, set to the value of nx*ny*nz, which is used in 38 | * the routines that step through blocks in sequence. */ 39 | const int nxyz; 40 | /** The size of a computational block in the x direction. */ 41 | const double boxx; 42 | /** The size of a computational block in the y direction. */ 43 | const double boxy; 44 | /** The size of a computational block in the z direction. */ 45 | const double boxz; 46 | /** The inverse box length in the x direction. */ 47 | const double xsp; 48 | /** The inverse box length in the y direction. */ 49 | const double ysp; 50 | /** The inverse box length in the z direction. */ 51 | const double zsp; 52 | /** An array to hold the minimum distances associated with the 53 | * worklists. This array is initialized during container 54 | * construction, by the initialize_radii() routine. */ 55 | double *mrad; 56 | /** The pre-computed block worklists. */ 57 | static const unsigned int wl[wl_seq_length*wl_hgridcu]; 58 | bool contains_neighbor(const char* format); 59 | voro_base(int nx_,int ny_,int nz_,double boxx_,double boxy_,double boxz_); 60 | ~voro_base() {delete [] mrad;} 61 | protected: 62 | /** A custom int function that returns consistent stepping 63 | * for negative numbers, so that (-1.5, -0.5, 0.5, 1.5) maps 64 | * to (-2,-1,0,1). 65 | * \param[in] a the number to consider. 66 | * \return The value of the custom int operation. */ 67 | inline int step_int(double a) {return a<0?int(a)-1:int(a);} 68 | /** A custom modulo function that returns consistent stepping 69 | * for negative numbers. For example, (-2,-1,0,1,2) step_mod 2 70 | * is (0,1,0,1,0). 71 | * \param[in] (a,b) the input integers. 72 | * \return The value of a modulo b, consistent for negative 73 | * numbers. */ 74 | inline int step_mod(int a,int b) {return a>=0?a%b:b-1-(b-1-a)%b;} 75 | /** A custom integer division function that returns consistent 76 | * stepping for negative numbers. For example, (-2,-1,0,1,2) 77 | * step_div 2 is (-1,-1,0,0,1). 78 | * \param[in] (a,b) the input integers. 79 | * \return The value of a div b, consistent for negative 80 | * numbers. */ 81 | inline int step_div(int a,int b) {return a>=0?a/b:-1+(a+1)/b;} 82 | private: 83 | void compute_minimum(double &minr,double &xlo,double &xhi,double &ylo,double &yhi,double &zlo,double &zhi,int ti,int tj,int tk); 84 | }; 85 | 86 | } 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /lib/voro++/voro++.cc: -------------------------------------------------------------------------------- 1 | // Voro++, a 3D cell-based Voronoi library 2 | // 3 | // Author : Chris H. Rycroft (LBL / UC Berkeley) 4 | // Email : chr@alum.mit.edu 5 | // Date : August 30th 2011 6 | 7 | /** \file voro++.cc 8 | * \brief A file that loads all of the function implementation files. */ 9 | 10 | #include "cell.cc" 11 | #include "common.cc" 12 | #include "v_base.cc" 13 | #include "container.cc" 14 | #include "unitcell.cc" 15 | #include "container_prd.cc" 16 | #include "pre_container.cc" 17 | #include "v_compute.cc" 18 | #include "c_loops.cc" 19 | #include "wall.cc" 20 | -------------------------------------------------------------------------------- /lib/voro++/wall.hh: -------------------------------------------------------------------------------- 1 | // Voro++, a 3D cell-based Voronoi library 2 | // 3 | // Author : Chris H. Rycroft (LBL / UC Berkeley) 4 | // Email : chr@alum.mit.edu 5 | // Date : August 30th 2011 6 | 7 | /** \file wall.hh 8 | * \brief Header file for the derived wall classes. */ 9 | 10 | #ifndef VOROPP_WALL_HH 11 | #define VOROPP_WALL_HH 12 | 13 | #include "cell.hh" 14 | #include "container.hh" 15 | 16 | namespace voro { 17 | 18 | /** \brief A class representing a spherical wall object. 19 | * 20 | * This class represents a spherical wall object. */ 21 | struct wall_sphere : public wall { 22 | public: 23 | /** Constructs a spherical wall object. 24 | * \param[in] w_id_ an ID number to associate with the wall for 25 | * neighbor tracking. 26 | * \param[in] (xc_,yc_,zc_) a position vector for the sphere's 27 | * center. 28 | * \param[in] rc_ the radius of the sphere. */ 29 | wall_sphere(double xc_,double yc_,double zc_,double rc_,int w_id_=-99) 30 | : w_id(w_id_), xc(xc_), yc(yc_), zc(zc_), rc(rc_) {} 31 | bool point_inside(double x,double y,double z); 32 | template 33 | bool cut_cell_base(v_cell &c,double x,double y,double z); 34 | bool cut_cell(voronoicell &c,double x,double y,double z) {return cut_cell_base(c,x,y,z);} 35 | bool cut_cell(voronoicell_neighbor &c,double x,double y,double z) {return cut_cell_base(c,x,y,z);} 36 | private: 37 | const int w_id; 38 | const double xc,yc,zc,rc; 39 | }; 40 | 41 | /** \brief A class representing a plane wall object. 42 | * 43 | * This class represents a single plane wall object. */ 44 | struct wall_plane : public wall { 45 | public: 46 | /** Constructs a plane wall object. 47 | * \param[in] (xc_,yc_,zc_) a normal vector to the plane. 48 | * \param[in] ac_ a displacement along the normal vector. 49 | * \param[in] w_id_ an ID number to associate with the wall for 50 | * neighbor tracking. */ 51 | wall_plane(double xc_,double yc_,double zc_,double ac_,int w_id_=-99) 52 | : w_id(w_id_), xc(xc_), yc(yc_), zc(zc_), ac(ac_) {} 53 | bool point_inside(double x,double y,double z); 54 | template 55 | bool cut_cell_base(v_cell &c,double x,double y,double z); 56 | bool cut_cell(voronoicell &c,double x,double y,double z) {return cut_cell_base(c,x,y,z);} 57 | bool cut_cell(voronoicell_neighbor &c,double x,double y,double z) {return cut_cell_base(c,x,y,z);} 58 | private: 59 | const int w_id; 60 | const double xc,yc,zc,ac; 61 | }; 62 | 63 | /** \brief A class representing a cylindrical wall object. 64 | * 65 | * This class represents a open cylinder wall object. */ 66 | struct wall_cylinder : public wall { 67 | public: 68 | /** Constructs a cylinder wall object. 69 | * \param[in] (xc_,yc_,zc_) a point on the axis of the 70 | * cylinder. 71 | * \param[in] (xa_,ya_,za_) a vector pointing along the 72 | * direction of the cylinder. 73 | * \param[in] rc_ the radius of the cylinder 74 | * \param[in] w_id_ an ID number to associate with the wall for 75 | * neighbor tracking. */ 76 | wall_cylinder(double xc_,double yc_,double zc_,double xa_,double ya_,double za_,double rc_,int w_id_=-99) 77 | : w_id(w_id_), xc(xc_), yc(yc_), zc(zc_), xa(xa_), ya(ya_), za(za_), 78 | asi(1/(xa_*xa_+ya_*ya_+za_*za_)), rc(rc_) {} 79 | bool point_inside(double x,double y,double z); 80 | template 81 | bool cut_cell_base(v_cell &c,double x,double y,double z); 82 | bool cut_cell(voronoicell &c,double x,double y,double z) {return cut_cell_base(c,x,y,z);} 83 | bool cut_cell(voronoicell_neighbor &c,double x,double y,double z) {return cut_cell_base(c,x,y,z);} 84 | private: 85 | const int w_id; 86 | const double xc,yc,zc,xa,ya,za,asi,rc; 87 | }; 88 | 89 | 90 | /** \brief A class representing a conical wall object. 91 | * 92 | * This class represents a cone wall object. */ 93 | struct wall_cone : public wall { 94 | public: 95 | /** Constructs a cone wall object. 96 | * \param[in] (xc_,yc_,zc_) the apex of the cone. 97 | * \param[in] (xa_,ya_,za_) a vector pointing along the axis of 98 | * the cone. 99 | * \param[in] ang the angle (in radians) of the cone, measured 100 | * from the axis. 101 | * \param[in] w_id_ an ID number to associate with the wall for 102 | * neighbor tracking. */ 103 | wall_cone(double xc_,double yc_,double zc_,double xa_,double ya_,double za_,double ang,int w_id_=-99) 104 | : w_id(w_id_), xc(xc_), yc(yc_), zc(zc_), xa(xa_), ya(ya_), za(za_), 105 | asi(1/(xa_*xa_+ya_*ya_+za_*za_)), 106 | gra(tan(ang)), sang(sin(ang)), cang(cos(ang)) {} 107 | bool point_inside(double x,double y,double z); 108 | template 109 | bool cut_cell_base(v_cell &c,double x,double y,double z); 110 | bool cut_cell(voronoicell &c,double x,double y,double z) {return cut_cell_base(c,x,y,z);} 111 | bool cut_cell(voronoicell_neighbor &c,double x,double y,double z) {return cut_cell_base(c,x,y,z);} 112 | private: 113 | const int w_id; 114 | const double xc,yc,zc,xa,ya,za,asi,gra,sang,cang; 115 | }; 116 | 117 | } 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /lib/voro++/worklist.hh: -------------------------------------------------------------------------------- 1 | // Voro++, a 3D cell-based Voronoi library 2 | // 3 | // Author : Chris H. Rycroft (LBL / UC Berkeley) 4 | // Email : chr@alum.mit.edu 5 | // Date : August 30th 2011 6 | 7 | /** \file worklist.hh 8 | * \brief Header file for setting constants used in the block worklists that are 9 | * used during cell computation. 10 | * 11 | * This file is automatically generated by worklist_gen.pl and it is not 12 | * intended to be edited by hand. */ 13 | 14 | #ifndef VOROPP_WORKLIST_HH 15 | #define VOROPP_WORKLIST_HH 16 | 17 | namespace voro { 18 | 19 | /** Each region is divided into a grid of subregions, and a worklist is 20 | # constructed for each. This parameter sets is set to half the number of 21 | # subregions that the block is divided into. */ 22 | const int wl_hgrid=4; 23 | /** The number of subregions that a block is subdivided into, which is twice 24 | the value of hgrid. */ 25 | const int wl_fgrid=8; 26 | /** The total number of worklists, set to the cube of hgrid. */ 27 | const int wl_hgridcu=64; 28 | /** The number of elements in each worklist. */ 29 | const int wl_seq_length=64; 30 | 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "wheel", 5 | "pybind11", 6 | ] 7 | 8 | [tool.cibuildwheel] 9 | skip = ["cp36-*", "cp37-*"] 10 | 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | ase 4 | plotly 5 | ipywidgets 6 | pybind11 7 | scipy 8 | spglib 9 | pyyaml 10 | jupyter-book 11 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip 2 | pytest 3 | pytest-cov 4 | pytest-benchmark 5 | codecov 6 | h5py 7 | ipywidgets=7.5 8 | plotly=4.11.0 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pybind11.setup_helpers import Pybind11Extension, build_ext 2 | from setuptools import setup, find_packages 3 | 4 | 5 | with open('README.md') as readme_file: 6 | readme = readme_file.read() 7 | 8 | setup( 9 | name='pyscal3', 10 | version='3.3.0', 11 | author='Sarath Menon', 12 | author_email='sarath.menon@pyscal.org', 13 | description='Python library written in C++ for calculation of local atomic structural environment', 14 | long_description=readme, 15 | long_description_content_type = "text/markdown", 16 | # tell setuptools to look for any packages under 'src' 17 | packages=find_packages('src'), 18 | # tell setuptools that all packages will be under the 'src' directory 19 | # and nowhere else 20 | package_dir={'':'src'}, 21 | headers=["src/pyscal3/system.h"], 22 | ext_modules=[ 23 | Pybind11Extension( 24 | "pyscal3.csystem", 25 | ["src/pyscal3/neighbor.cpp", "src/pyscal3/sh.cpp", 26 | "src/pyscal3/solids.cpp", "src/pyscal3/voronoi.cpp", 27 | "src/pyscal3/cna.cpp", "src/pyscal3/centrosymmetry.cpp", 28 | "src/pyscal3/entropy.cpp", 29 | "src/pyscal3/system_binding.cpp", "lib/voro++/voro++.cc"], 30 | language='c++', 31 | include_dirs=['lib/voro++'], 32 | extra_compile_args=['-O3'], 33 | ), 34 | ], 35 | # add custom build_ext command 36 | cmdclass={"build_ext": build_ext}, 37 | zip_safe=False, 38 | download_url = 'https://anaconda.org/conda-forge/pyscal', 39 | url = 'https://pyscal.org', 40 | install_requires=['pybind11', 'numpy', 'ase', 'pyyaml'], 41 | classifiers=[ 42 | 'Programming Language :: Python :: 3' 43 | ], 44 | include_package_data=True, 45 | ) 46 | -------------------------------------------------------------------------------- /src/pyscal3/__init__.py: -------------------------------------------------------------------------------- 1 | from pyscal3.core import System 2 | from pyscal3.atoms import Atoms 3 | from pyscal3.trajectory import Trajectory -------------------------------------------------------------------------------- /src/pyscal3/ase.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from ase.atoms import Atoms 4 | 5 | import pyscal3.core as pc 6 | from pyscal3.operations.centrosymmetry import calculate_centrosymmetry as _calculate_centrosymmetry 7 | from pyscal3.operations.calculations import ( 8 | calculate_chiparams as _calculate_chiparams, 9 | calculate_q as _calculate_q, 10 | calculate_rdf as _calculate_rdf, 11 | ) 12 | from pyscal3.operations.cna import ( 13 | calculate_cna as _calculate_cna, 14 | identify_diamond as _identify_diamond, 15 | ) 16 | from pyscal3.operations.identify import find_neighbors 17 | from pyscal3.operations.symmetry import get_symmetry as _get_symmetry 18 | 19 | 20 | def _get_voronoi_volume(system: pc.System) -> pc.System: 21 | """ 22 | Calculate the Voronoi volume of atoms 23 | 24 | Parameters 25 | ---------- 26 | system: System object 27 | 28 | Returns 29 | ------- 30 | np.ndarray: Array of Voronoi volumes for each atom. 31 | """ 32 | system.find.neighbors(method="voronoi") 33 | return system 34 | 35 | 36 | def _get_structure(ase_atoms: Atoms) -> pc.System: 37 | return pc.System(ase_atoms, format='ase') 38 | 39 | 40 | def _get_updated_signature(signature: inspect.Signature, add_find_neighbors: bool = False) -> inspect.Signature: 41 | parameters = signature.parameters.copy() 42 | del parameters["system"] 43 | parameters["ase_atoms"] = inspect.Parameter( 44 | name="ase_atoms", 45 | kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, 46 | annotation=Atoms, 47 | ) 48 | if add_find_neighbors: 49 | for k, v in inspect.signature(find_neighbors).parameters.items(): 50 | if k != "system": 51 | parameters[k] = v 52 | parameters.move_to_end("ase_atoms", last = False) 53 | return signature.replace(parameters=parameters.values()) 54 | 55 | 56 | def _wrap_function(funct: callable, add_find_neighbors: bool = False, return_system: bool = False, system_attribute: str = "") -> callable: 57 | sig_updated = _get_updated_signature( 58 | signature=inspect.signature(funct), 59 | add_find_neighbors=add_find_neighbors, 60 | ) 61 | 62 | def ase_compatibility_wrapper(*args, **kwargs): 63 | input_dict = sig_updated.bind(*args, **kwargs).arguments 64 | pc_system = _get_structure(input_dict.pop("ase_atoms")) 65 | if add_find_neighbors: 66 | find_neighbor_keys = inspect.signature(find_neighbors).parameters.keys() 67 | find_neighbors_kwargs = { 68 | k: input_dict.pop(k) 69 | for k in find_neighbor_keys 70 | if k != "system" and k in input_dict 71 | } 72 | pc_system.find.neighbors(**find_neighbors_kwargs) 73 | input_dict["system"] = pc_system 74 | if not return_system: 75 | return funct(**input_dict) 76 | elif system_attribute != "": 77 | funct(**input_dict) 78 | obj = pc_system 79 | for attr in system_attribute.split("."): 80 | obj = getattr(obj, attr) 81 | return obj 82 | else: 83 | return pc_system 84 | 85 | ase_compatibility_wrapper.__name__ = funct.__name__ 86 | ase_compatibility_wrapper.__signature__ = sig_updated 87 | ase_compatibility_wrapper.__doc__ = funct.__doc__.replace( 88 | "system: System object", "ase_atoms: ase.atoms.Atoms object" 89 | ) 90 | return ase_compatibility_wrapper 91 | 92 | 93 | calculate_centrosymmetry = _wrap_function( 94 | funct=_calculate_centrosymmetry, 95 | add_find_neighbors=False, 96 | return_system=False, 97 | system_attribute="", 98 | ) 99 | calculate_chiparams = _wrap_function( 100 | funct=_calculate_chiparams, 101 | add_find_neighbors=True, 102 | return_system=True, 103 | system_attribute="atoms.angular_parameters.chi_params", 104 | ) 105 | calculate_cna = _wrap_function( 106 | funct=_calculate_cna, 107 | add_find_neighbors=False, 108 | return_system=False, 109 | system_attribute="", 110 | ) 111 | calculate_diamond_structure = _wrap_function( 112 | funct=_identify_diamond, 113 | add_find_neighbors=False, 114 | return_system=False, 115 | system_attribute="", 116 | ) 117 | calculate_radial_distribution_function = _wrap_function( 118 | funct=_calculate_rdf, 119 | add_find_neighbors=False, 120 | return_system=False, 121 | system_attribute="", 122 | ) 123 | calculate_steinhardt_parameter = _wrap_function( 124 | funct=_calculate_q, 125 | add_find_neighbors=True, 126 | return_system=False, 127 | system_attribute="", 128 | ) 129 | calculate_voronoi_volume = _wrap_function( 130 | funct=_get_voronoi_volume, 131 | add_find_neighbors=False, 132 | return_system=True, 133 | system_attribute="atoms.voronoi.volume", 134 | ) 135 | get_symmetry = _wrap_function( 136 | funct=_get_symmetry, 137 | add_find_neighbors=False, 138 | return_system=False, 139 | system_attribute="", 140 | ) -------------------------------------------------------------------------------- /src/pyscal3/centrosymmetry.cpp: -------------------------------------------------------------------------------- 1 | #include "system.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "string.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | void calculate_centrosymmetry(py::dict& atoms, 19 | const int nmax){ 20 | 21 | double dx, dy, dz, weight; 22 | vector temp; 23 | 24 | vector> neighbors = atoms[py::str("neighbors")].cast>>();; 25 | vector>> diff = atoms[py::str("diff")].cast>>>();; 26 | 27 | int nop = neighbors.size(); 28 | vector centrosymmetry(nop); 29 | 30 | for (int ti=0; ti 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "string.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | double gmr(double r, 20 | double sigma, 21 | double rho, 22 | int n_neighbors, 23 | vector &neighbordist){ 24 | 25 | double g = 0.00; 26 | double rij,r2; 27 | double sigma2 = sigma*sigma; 28 | double frho = 4.00*PI*rho*r*r; 29 | double fsigma = sqrt(2.00*PI*sigma2); 30 | double factor = (1.00/frho)*(1.00/fsigma); 31 | 32 | for(int i=0; i &neighbordist){ 47 | 48 | double g = gmr(r, sigma, rho, n_neighbors, neighbordist); 49 | return ((g*log(g)-g +1.00)*r*r); 50 | } 51 | 52 | 53 | double trapezoid_integration(const double rstart, 54 | const double rstop, 55 | const double h, 56 | double sigma, 57 | double rho, 58 | int n_neighbors, 59 | vector &neighbordist, 60 | double kb){ 61 | 62 | int nsteps = (rstop - rstart)/h; 63 | double summ=0.00; 64 | double xstart, xend; 65 | 66 | double rloop; 67 | double integral; 68 | 69 | xstart = entropy_integrand(rstart, sigma, rho, n_neighbors, neighbordist); 70 | 71 | for(int j=1; j> neighbors = atoms[py::str("neighbors")].cast>>(); 94 | vector cutoff = atoms[py::str("cutoff")].cast>(); 95 | vector> neighbordist = atoms[py::str("neighbordist")].cast>>(); 96 | int nop = neighbors.size(); 97 | 98 | vector entropy(nop); 99 | 100 | for(int ti=0; ti entropy = atoms[py::str("entropy")].cast>(); 113 | vector> neighbors = atoms[py::str("neighbors")].cast>>(); 114 | int nop = neighbors.size(); 115 | 116 | vector avg_entropy(nop); 117 | 118 | for(int ti=0; ti>> atoms, box = read_poscar('POSCAR') 36 | >>> atoms, box = read_poscar('POSCAR.gz') 37 | >>> atoms, box = read_poscar('POSCAR.dat', compressed=True) 38 | 39 | """ 40 | aseobj = read(infile, format="vasp") 41 | atoms, box = ptase.read_snap(aseobj) 42 | return atoms, box 43 | 44 | 45 | def write_snap(sys, outfile, comments="pyscal", species=None): 46 | """ 47 | Function to read a POSCAR format. 48 | 49 | Parameters 50 | ---------- 51 | outfile : string 52 | name of the input file 53 | 54 | 55 | """ 56 | if species is None: 57 | warnings.warn("Using legacy poscar writer, to use ASE backend specify species") 58 | write_poscar(sys, outfile, comments=comments) 59 | else: 60 | aseobj = ptase.convert_snap(sys, species=species) 61 | write(outfile, aseobj, format="vasp") 62 | 63 | 64 | def split_snaps(**kwargs): 65 | raise NotImplementedError("split method for mdtraj is not implemented") 66 | 67 | def convert_snap(**kwargs): 68 | raise NotImplementedError("convert method for mdtraj is not implemented") 69 | 70 | def write_poscar(sys, outfile, comments="pyscal"): 71 | """ 72 | Function to read a POSCAR format. 73 | Parameters 74 | ---------- 75 | outfile : string 76 | name of the input file 77 | """ 78 | #get element strings 79 | if 'species' not in sys.atoms.keys(): 80 | sys.atoms["species"] = [None for x in range(sys.atoms.ntotal)] 81 | 82 | if sys.atoms.species[0] is None: 83 | if species is None: 84 | raise ValueError("Species was not known! To convert to ase, species need to be provided using the species keyword") 85 | #otherwise we know the species 86 | types = sys.atoms.types 87 | unique_types = np.unique(types) 88 | if not (len(unique_types) == len(species)): 89 | raise ValueError("Length of species and number of types found in system are different. Maybe you specified \"Au\" instead of [\"Au\"]") 90 | #now assign the species to custom 91 | atomspecies = [] 92 | for cc, typ in enumerate(types): 93 | atomspecies.append(species[int(typ-1)]) 94 | else: 95 | atomspecies = sys.atoms.species 96 | 97 | 98 | fout = open(outfile, 'w') 99 | 100 | fout.write(comments+"\n") 101 | fout.write(" 1.00000000000000\n") 102 | 103 | #write box 104 | vecs = sys.box 105 | fout.write(" %1.14f %1.14f %1.14f\n"%(vecs[0][0], vecs[0][1], vecs[0][2])) 106 | fout.write(" %1.14f %1.14f %1.14f\n"%(vecs[1][0], vecs[1][1], vecs[1][2])) 107 | fout.write(" %1.14f %1.14f %1.14f\n"%(vecs[2][0], vecs[2][1], vecs[2][2])) 108 | 109 | tt, cc = np.unique(atomspecies, return_counts=True) 110 | 111 | atomgroups = [[] for x in range(len(tt))] 112 | 113 | for count, t in enumerate(tt): 114 | for ccount, pos in enumerate(sys.atoms.positions): 115 | if atomspecies[ccount] == t: 116 | atomgroups[count].append(pos) 117 | 118 | fout.write(" ") 119 | for c in cc: 120 | fout.write("%d "%int(c)) 121 | fout.write("\n") 122 | 123 | fout.write("Cartesian\n") 124 | 125 | for i in range(len(atomgroups)): 126 | for pos in atomgroups[i]: 127 | fout.write(" %1.14f %1.14f %1.14f\n"%(pos[0], pos[1], pos[2])) 128 | 129 | fout.close() 130 | -------------------------------------------------------------------------------- /src/pyscal3/operations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscal/pyscal3/ae76867ab0a315c39151666a5fda1121f0c4a899/src/pyscal3/operations/__init__.py -------------------------------------------------------------------------------- /src/pyscal3/operations/centrosymmetry.py: -------------------------------------------------------------------------------- 1 | import pyscal3.csystem as pc 2 | 3 | def calculate_centrosymmetry(system, nmax=12): 4 | """ 5 | Calculate the centrosymmetry parameter 6 | 7 | Parameters 8 | ---------- 9 | system: System object 10 | 11 | nmax : int, optional 12 | number of neighbors to be considered for centrosymmetry 13 | parameters. Has to be a positive, even integer. Default 12 14 | 15 | Returns 16 | ------- 17 | None 18 | 19 | Notes 20 | ----- 21 | Calculate the centrosymmetry parameter for each atom which can be accessed by 22 | :attr:`~pyscal.catom.centrosymmetry` attribute. It calculates the degree of inversion 23 | symmetry of an atomic environment. Centrosymmetry recalculates the neighbor using 24 | the number method as specified in :func:`¬pyscal.core.System.find_neighbors` method. This 25 | is the ensure that the required number of neighbors are found for calculation of the parameter. 26 | 27 | The Greedy Edge Selection (GES) [1] as specified in [2] in used in this method. 28 | GES algorithm is implemented in LAMMPS and Ovito. Please see [2] for 29 | a detailed description of the algorithms. 30 | References 31 | ---------- 32 | .. [1] Stukowski, A, Model Simul Mater SC 20, 2012 33 | .. [2] Larsen, arXiv:2003.08879v1, 2020 34 | 35 | """ 36 | if not nmax>0: 37 | raise ValueError("nmax cannot be negative") 38 | 39 | if not nmax%2 == 0: 40 | raise ValueError("nmax has to even integer") 41 | 42 | system.atoms.create_attribute('centrosymmetry', fill_with = 0) 43 | system.find.neighbors(method='number', nmax=nmax, assign_neighbor=True) 44 | pc.calculate_centrosymmetry(system.atoms, nmax) 45 | 46 | return system.atoms.centrosymmetry 47 | -------------------------------------------------------------------------------- /src/pyscal3/operations/chemical.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def calculate_sro(system, reference_type=1, compare_type=2, average=True): 4 | """ 5 | Calculate short range order 6 | 7 | Parameters 8 | ---------- 9 | reference_type: int, optional 10 | type of the atom to be used a reference. default 1 11 | 12 | compare_type: int, optional 13 | type of the atom to be compared to. default 2 14 | 15 | average: bool, optional 16 | if True, average over all atoms of the reference type in the system. 17 | default True. 18 | 19 | Returns 20 | ------- 21 | vec: list of float 22 | The short range order averaged over the whole system for atom of 23 | the reference type. Only returned if `average` is True. 24 | 25 | Notes 26 | ----- 27 | Calculates the short range order for an AB alloy using the approach by 28 | Cowley [1]. Short range order is calculated as, 29 | 30 | .. math:: 31 | 32 | \\alpha_i = 1 - \\frac{n_i}{m_A c_i} 33 | 34 | where n_i is the number of atoms of the non reference type among the c_i atoms 35 | in the ith shell. m_A is the concentration of the non reference atom. Please 36 | note that the value is calculated for shells 1 and 2 by default. In order for 37 | this to be possible, neighbors have to be found first using the :func:`~pyscal.core.System.find_neighbors` 38 | method. The selected neighbor method should include the second shell as well. For this 39 | purpose `method=cutoff` can be chosen with a cutoff long enough to include the second 40 | shell. In order to estimate this cutoff, one can use the :func:`~pyscal.core.System.calculate_rdf` 41 | method. 42 | 43 | The method also works for alloying elements greater than 2 [2]. For this, you can choose the reference type. 44 | 45 | References 46 | ---------- 47 | .. [1] Cowley J. M., PR 77(5), 1950. 48 | .. [2] de Fountaine D., J. Appl. Cryst. 4(15), 1971. 49 | 50 | """ 51 | 52 | def _get_dict(val, cdict): 53 | cx, cxc = np.unique(val, return_counts=True) 54 | cx = list(cx) 55 | cxc = list(cxc) 56 | for key, val in cdict.items(): 57 | if key not in cx: 58 | cx.append(key) 59 | cxc.append(0) 60 | d = dict([(x, cxc[c]/np.sum(cxc)) for c, x in enumerate(cx)]) 61 | return d 62 | 63 | system._check_neighbors() 64 | 65 | cdict = system.atoms.composition_ints 66 | 67 | neighbortypes = system.atoms["types"][system.atoms['neighbors']] 68 | comp_dicts = [_get_dict(neighbortype, cdict) for neighbortype in neighbortypes] 69 | local_comp = np.array([d[reference_type] for d in comp_dicts]) 70 | global_comp = cdict[reference_type] 71 | 72 | if reference_type == compare_type: 73 | sro = (local_comp - global_comp)/(1 - global_comp) 74 | else: 75 | sro = 1 - (local_comp/global_comp) 76 | 77 | system.atoms["sro"]= sro 78 | mapdict = {} 79 | mapdict['chemical'] = {} 80 | mapdict['chemical']['short_range_order'] = "sro" 81 | system.atoms._add_attribute(mapdict) 82 | 83 | if average: 84 | np.mean(sro) 85 | -------------------------------------------------------------------------------- /src/pyscal3/operations/cna.py: -------------------------------------------------------------------------------- 1 | 2 | import pyscal3.csystem as pc 3 | 4 | def calculate_cna(system, lattice_constant=None): 5 | """ 6 | Calculate the Common Neighbor Analysis indices 7 | 8 | Parameters 9 | ---------- 10 | system: System object 11 | 12 | lattice_constant : float, optional 13 | lattice constant to calculate CNA. If not specified, 14 | adaptive CNA will be used 15 | 16 | Returns 17 | ------- 18 | resdict: dict 19 | dictionary containing the calculated results 20 | 21 | Notes 22 | ----- 23 | Performs the common neighbor analysis [1][2] or the adaptive common neighbor 24 | analysis [2] and assigns a structure to each atom. 25 | 26 | If `lattice_constant` is specified, a convential common neighbor analysis is 27 | used. If `lattice_constant` is not specified, adaptive common neighbor analysis is used. 28 | The assigned structures can be accessed by :attr:`~pyscal.catom.Atom.structure`. 29 | The values assigned for stucture are 0 Unknown, 1 fcc, 2 hcp, 3 bcc, 4 icosahedral. 30 | 31 | References 32 | ---------- 33 | .. [1] Faken, Jonsson, CMS 2, 1994 34 | .. [2] Honeycutt, Andersen, JPC 91, 1987 35 | .. [3] Stukowski, A, Model Simul Mater SC 20, 2012 36 | 37 | """ 38 | system.atoms.create_attribute('structure', fill_with = 0) 39 | #run to calculate temp neighbors 40 | system.find.neighbors(method='number', nmax=14, assign_neighbor=False) 41 | 42 | if lattice_constant is None: 43 | #we need adaptive calculation 44 | pc.get_acna_neighbors_cn12(system.atoms, system.triclinic, 45 | system.rot, system.rotinv, system.boxdims) 46 | pc.identify_cn12(system.atoms, system.triclinic, 47 | system.rot, system.rotinv, system.boxdims) 48 | 49 | pc.get_acna_neighbors_cn14(system.atoms, system.triclinic, 50 | system.rot, system.rotinv, system.boxdims) 51 | pc.identify_cn14(system.atoms, system.triclinic, 52 | system.rot, system.rotinv, system.boxdims) 53 | 54 | else: 55 | system.lattice_constant = lattice_constant 56 | pc.get_cna_neighbors(system.atoms, system.triclinic, 57 | system.rot, system.rotinv, system.boxdims, 58 | lattice_constant, 1) 59 | pc.identify_cn12(system.atoms, system.triclinic, 60 | system.rot, system.rotinv, system.boxdims) 61 | 62 | pc.get_cna_neighbors(system.atoms, system.triclinic, 63 | system.rot, system.rotinv, system.boxdims, 64 | lattice_constant, 2) 65 | pc.identify_cn14(system.atoms, system.triclinic, 66 | system.rot, system.rotinv, system.boxdims) 67 | 68 | 69 | res_dict = { 70 | "others": len([x for x in system.atoms.structure if x==0]), 71 | "fcc": len([x for x in system.atoms.structure if x==1]), 72 | "hcp": len([x for x in system.atoms.structure if x==2]), 73 | "bcc": len([x for x in system.atoms.structure if x==3]), 74 | "ico": len([x for x in system.atoms.structure if x==4]), 75 | } 76 | return res_dict 77 | 78 | def identify_diamond(system): 79 | """ 80 | Identify diamond structure 81 | 82 | Parameters 83 | ---------- 84 | system: System object 85 | 86 | Returns 87 | ------- 88 | diamondstructure : dict 89 | dict of structure signature 90 | 91 | Notes 92 | ----- 93 | Identify diamond structure using the algorithm mentioned in [1]. It is an 94 | extended CNA method. The integers 1, 2, 3, 4, 5 and 6 are assigned to the 95 | structure variable of the atom. 1 stands for cubic diamond, 2 stands for first 96 | nearest neighbors of cubic diamond and 3 stands for second nearest neighbors 97 | of cubic diamond. 4 signifies hexagonal diamond, the first nearest neighbors 98 | are marked with 5 and second nearest neighbors with 6. 99 | 100 | References 101 | ---------- 102 | .. [1] Maras et al, CPC 205, 2016 103 | """ 104 | system.atoms.create_attribute('structure', fill_with = 0) 105 | #run to calculate temp neighbors 106 | system.find.neighbors(method='number', nmax=4, assign_neighbor=False) 107 | 108 | pc.identify_diamond_cna(system.atoms, system.triclinic, 109 | system.rot, system.rotinv, system.boxdims) 110 | 111 | res_dict = { 112 | "others": len([x for x in system.atoms.structure if x==0]), 113 | "cubic diamond": len([x for x in system.atoms.structure if x==1]), 114 | "cubic diamond 1NN": len([x for x in system.atoms.structure if x==2]), 115 | "cubic diamond 2NN": len([x for x in system.atoms.structure if x==3]), 116 | "hex diamond": len([x for x in system.atoms.structure if x==4]), 117 | "hex diamond 1NN": len([x for x in system.atoms.structure if x==5]), 118 | "hex diamond 2NN": len([x for x in system.atoms.structure if x==6]), 119 | } 120 | return res_dict -------------------------------------------------------------------------------- /src/pyscal3/operations/entropy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pyscal3.csystem as pc 3 | 4 | 5 | def calculate_entropy(system, rm, sigma=0.2, 6 | rstart=0.001, h=0.001, local=False, average=False): 7 | """ 8 | Calculate the entropy parameter for each atom 9 | 10 | Parameters 11 | ---------- 12 | rm : float 13 | cutoff distance for integration of entropy parameter in distance units 14 | 15 | sigma : float 16 | broadening parameter 17 | 18 | rstart : float, optional 19 | minimum limit for integration, default 0.00001 20 | 21 | h : float, optional 22 | width for trapezoidal integration, default 0.0001 23 | 24 | local : bool, optional 25 | if True, use the local density instead of global density 26 | default False 27 | 28 | average : bool, optional 29 | if True find the averaged entropy parameters 30 | default False 31 | Returns 32 | ------- 33 | None 34 | """ 35 | kb = 1 36 | if local: 37 | rho = 0 38 | else: 39 | rho = system.natoms/system.volume 40 | 41 | pc.calculate_entropy(system.atoms, sigma, rho, rstart, rm, h, kb) 42 | 43 | mapdict = {} 44 | mapdict["entropy"] = {} 45 | mapdict["entropy"]["norm"] = "entropy" 46 | 47 | if average: 48 | pc.calculate_average_entropy(system.atoms) 49 | mapdict["entropy"]["average"] = "average_entropy" 50 | system.atoms._add_attribute(mapdict) 51 | return system.atoms.entropy.average 52 | else: 53 | system.atoms._add_attribute(mapdict) 54 | return system.atoms.entropy.norm 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/pyscal3/operations/neighbor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pyscal3.csystem as pc 3 | 4 | 5 | def get_distance(system, pos1, pos2, vector=False): 6 | """ 7 | Get the distance between two atoms. 8 | 9 | Parameters 10 | ---------- 11 | pos1 : list 12 | first atom position 13 | pos2 : list 14 | second atom position 15 | vector: bool, optional 16 | If True, return the vector between two atoms 17 | 18 | Returns 19 | ------- 20 | distance : double 21 | distance between the first and second atom. 22 | 23 | Notes 24 | ----- 25 | Periodic boundary conditions are assumed by default. 26 | """ 27 | diff = pc.get_distance_vector(pos1, pos2, system.triclinic, 28 | system.rot, system.rotinv, system.boxdims) 29 | dist = np.linalg.norm(diff) 30 | 31 | if vector: 32 | return dist, diff 33 | else: 34 | return dist 35 | 36 | def reset_neighbors(system): 37 | """ 38 | Reset the neighbors of all atoms in the system. 39 | 40 | Parameters 41 | ---------- 42 | None 43 | 44 | Returns 45 | ------- 46 | None 47 | 48 | Notes 49 | ----- 50 | It is used automatically when neighbors are recalculated. 51 | 52 | """ 53 | system.atoms["neighbors"] = [] 54 | system.atoms["neighbordist"] = [] 55 | system.atoms["temp_neighbors"] = [] 56 | system.atoms["temp_neighbordist"] = [] 57 | system.atoms["neighborweight"] = [] 58 | system.atoms["diff"] = [] 59 | system.atoms["r"] = [] 60 | system.atoms["theta"] = [] 61 | system.atoms["phi"] = [] 62 | system.atoms["cutoff"] = [] 63 | system.neighbors_found = False 64 | 65 | 66 | mapdict = {} 67 | mapdict["neighbors"] = {} 68 | mapdict["neighbors"]["index"] = "neighbors" 69 | mapdict["neighbors"]["distance"] = "neighbordist" 70 | mapdict["neighbors"]["weight"] = "neighborweight" 71 | mapdict["neighbors"]["displacement"] = "diff" 72 | mapdict["neighbors"]["cutoff"] = "cutoff" 73 | 74 | mapdict["neighbors"]["angle"] = {} 75 | mapdict["neighbors"]["angle"]["polar"] = "theta" 76 | mapdict["neighbors"]["angle"]["azimuthal"] = "phi" 77 | 78 | mapdict["neighbors"]["temporary"] = {} 79 | mapdict["neighbors"]["temporary"]["index"] = "temp_neighbors" 80 | mapdict["neighbors"]["temporary"]["distance"] = "temp_neighbordist" 81 | 82 | system.atoms._add_attribute(mapdict) 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/pyscal3/operations/serialize.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create an annotated representation of the the system using pyscal_rdf 3 | """ 4 | 5 | def serialize(system, return_type='dict', outputfile=None): 6 | """ 7 | Serialize a system into a validated and annotated json representation. 8 | Needs pyscal-rdf module installed. 9 | 10 | Parameters 11 | ---------- 12 | return_type: Optional, {'dict', 'json', 'model', 'file'} 13 | if dict: a dictionary is returned, 14 | if json: a json dump is returned, 15 | if model: a pydantic class is returned, 16 | if file: output is written as a json file, outputfile needs 17 | to be specified 18 | 19 | outputfile: filename in which the system is to be serialised to 20 | 21 | Returns 22 | ------- 23 | schema: output, type depends on `return_type`, see above. 24 | 25 | Note 26 | ---- 27 | Needs `pyscal_rdf`>=0.0.19 installation. 28 | Atom positions, types, and other properties are not serialised 29 | currently. 30 | """ 31 | try: 32 | from pyscal_rdf.schema import Sample 33 | import pyscal_rdf.properties as prp 34 | except ImportError: 35 | print("serialization needs pyscal_rdf module") 36 | 37 | #create sample 38 | sample = Sample() 39 | sample.material.element_ratio = system.composition 40 | 41 | if system._structure_dict is not None: 42 | sample.material.crystal_structure.altname = system.atoms._lattice 43 | spg = prp.get_space_group(system) 44 | sample.material.crystal_structure.spacegroup_symbol = spg[0] 45 | sample.material.crystal_structure.spacegroup_number = spg[1] 46 | sample.material.crystal_structure.unit_cell.bravais_lattice = prp.get_bravais_lattice(system) 47 | sample.material.crystal_structure.unit_cell.lattice_parameter = system.atoms._lattice_constant 48 | sample.material.crystal_structure.unit_cell.angle = list([prp.get_angle(system.box[0], system.box[1]), 49 | prp.get_angle(system.box[1], system.box[2]), 50 | prp.get_angle(system.box[2], system.box[0])]) 51 | 52 | sample.simulation_cell.volume = system.volume 53 | sample.simulation_cell.number_of_atoms = system.natoms 54 | sample.simulation_cell.length = list(system.box_dimensions) 55 | sample.simulation_cell.vector = [list(x) for x in system.box] 56 | sample.simulation_cell.angle = list([prp.get_angle(system.box[0], system.box[1]), 57 | prp.get_angle(system.box[1], system.box[2]), 58 | prp.get_angle(system.box[2], system.box[0])]) 59 | 60 | if return_type=='dict': 61 | return sample.dict() 62 | elif return_type=='json': 63 | return sample.model_dump_json() 64 | elif return_type=='model': 65 | return sample 66 | else: 67 | if outputfile is not None: 68 | with open(outputfile, 'w') as fout: 69 | fout.write(sample.model_dump_json()) 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/pyscal3/operations/symmetry.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | #from pyscal3.attributes import DocumentedKeywords 3 | import spglib 4 | 5 | def get_symmetry(system, angle_tolerance=-1.0, 6 | symprec=1e-5, hall_number=0): 7 | """ 8 | Calculate space group number and other symmetry properties 9 | 10 | Parameters 11 | ---------- 12 | system: pyscal System object 13 | the input system 14 | 15 | angle_tolerance: float, optional 16 | An experimental argument that controls angle tolerance between basis vectors. 17 | Normally it is not recommended to use this argument. Default -1.0 18 | https://spglib.github.io/spglib/variable.html#angle-tolerance 19 | 20 | symprec: float, optional 21 | Distance tolerance in Cartesian coordinates to find crystal symmetry. 22 | Default 1e-5 23 | https://spglib.github.io/spglib/variable.html#symprec 24 | 25 | hall_number: int, optional 26 | The argument to constrain the space-group-type search only for the Hall symbol corresponding to it. 27 | Default 0 28 | https://spglib.github.io/spglib/dataset.html#hall-number 29 | 30 | Returns 31 | ------- 32 | results: dict 33 | dict containing results. The keys are explained below: 34 | international_space_group_number: 35 | The space group type number defined in International Tables for Crystallography (ITA). 36 | https://it.iucr.org/Ac/contents/ 37 | international_symbol: 38 | The (full) Hermann–Mauguin notation of space group type 39 | https://www.chemeurope.com/en/encyclopedia/Hermann-Mauguin_notation.html 40 | point_group: 41 | Symbol of the crystallographic point group in the Hermann–Mauguin notation 42 | https://www.chemeurope.com/en/encyclopedia/Hermann-Mauguin_notation.html 43 | 44 | """ 45 | res = spglib.get_symmetry_dataset((system.box, 46 | system.direct_coordinates, system.atoms.types)) 47 | 48 | results = {} 49 | results['international_space_group_number'] = res["number"] 50 | results['international_symbol'] = res["international"] 51 | results['point_group'] = res["pointgroup"] 52 | return results 53 | -------------------------------------------------------------------------------- /src/pyscal3/operations/voronoi.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def calculate_vorovector(system, edge_cutoff=0.05, area_cutoff=0.01, edge_length=False): 4 | """ 5 | get the voronoi structure identification vector. 6 | 7 | Parameters 8 | ---------- 9 | 10 | edge_cutoff : float, optional 11 | cutoff for edge length. Default 0.05. 12 | 13 | 14 | area_cutoff : float, optional 15 | cutoff for face area. Default 0.01. 16 | 17 | edge_length : bool, optional 18 | if True, a list of unrefined edge lengths are returned. Default false. 19 | 20 | Returns 21 | ------- 22 | vorovector : array like, int 23 | array of the form (n3, n4, n5, n6) 24 | 25 | Notes 26 | ----- 27 | Returns a vector of the form `(n3, n4, n5, n6)`, where `n3` is the number 28 | of faces with 3 vertices, `n4` is the number of faces with 4 29 | vertices and so on. This can be used to identify structures [1] [2]. 30 | 31 | The keywords `edge_cutoff` and `area_cutoff` can be used to tune the values to minimise 32 | the effect of thermal distortions. Edges are only considered in the analysis if the 33 | `edge_length/sum(edge_lengths)` is at least `edge_cutoff`. Similarly, faces are only 34 | considered in the analysis if the `face_area/sum(face_areas)` is at least `face_cutoff`. 35 | 36 | References 37 | ---------- 38 | .. [1] Finney, JL, Proc. Royal Soc. Lond. A 319, 1970 39 | .. [2] Tanemura, M, Hiwatari, Y, Matsuda, H,Ogawa, T, Ogita, N, Ueda, A. Prog. Theor. Phys. 58, 1977 40 | 41 | """ 42 | complete_edge_lengths = [] 43 | vorovectors = [] 44 | 45 | for x in range(len(system.atoms['positions'])): 46 | st = 1 47 | refined_edges = [] 48 | edge_lengths = [] 49 | for vno in system.atoms['face_vertices'][x]: 50 | vphase = system.atoms['vertex_numbers'][x][st:st+vno] 51 | edgecount = 0 52 | dummy_edge_lengths = [] 53 | #now calculate the length f each edge 54 | for i in range(-1, len(vphase)-1): 55 | #get pairs of indices 56 | #verts are i, i+1 57 | ipos = system.atoms['vertex_vectors'][x][vphase[i]*3:vphase[i]*3+3] 58 | jpos = system.atoms['vertex_vectors'][x][vphase[i+1]*3:vphase[i+1]*3+3] 59 | 60 | #now calculate edge length 61 | edgeln = np.sqrt((ipos[0]-jpos[0])**2 + (ipos[1]-jpos[1])**2 + (ipos[2]-jpos[2])**2) 62 | dummy_edge_lengths.append(edgeln) 63 | 64 | edge_lengths.append(dummy_edge_lengths) 65 | st += (vno+1) 66 | 67 | #now all the edge lengths are saved 68 | for c, ed in enumerate(edge_lengths): 69 | #normalise the edge lengths 70 | norm = (ed/np.sum(ed)) 71 | #apply face area cutoff 72 | if (system.atoms['neighborweight'][x][c] > area_cutoff): 73 | #check for edge length cutoff 74 | edgecount = len([cc for cc,x in enumerate(norm) if x > edge_cutoff]) 75 | refined_edges.append(edgecount) 76 | 77 | #now loop over refined edges and collect n3, n4, n5, n6 78 | vorovector = [0, 0, 0, 0] 79 | 80 | for ed in refined_edges: 81 | if ed == 3: 82 | vorovector[0] += 1 83 | elif ed == 4: 84 | vorovector[1] += 1 85 | elif ed == 5: 86 | vorovector[2] += 1 87 | elif ed == 6: 88 | vorovector[3] += 1 89 | 90 | complete_edge_lengths.append(edge_lengths) 91 | vorovectors.append(vorovector) 92 | 93 | system.atoms["edge_lengths"] = np.array(complete_edge_lengths) 94 | system.atoms["vorovector"] = np.array(vorovectors) 95 | 96 | mapdict = {} 97 | mapdict["voronoi"] ={} 98 | mapdict["voronoi"]["vector"] = "vorovector" 99 | mapdict["voronoi"]["face"] = {} 100 | mapdict["voronoi"]["face"]["edge_lengths"] = "edge_lengths" 101 | system.atoms._add_attribute(mapdict) 102 | 103 | -------------------------------------------------------------------------------- /src/pyscal3/routines.py: -------------------------------------------------------------------------------- 1 | """ 2 | The pyscal routines module. This will contain slightly bigger routines which needs to be used. 3 | Most of them will not be used at the user level. User level longer routines will go to misc 4 | module. 5 | 6 | Imports might need specific modules! 7 | """ 8 | 9 | import numpy as np 10 | import os 11 | 12 | 13 | def get_energy_atom(outfile, species=None, pair_style=None, pair_coeff=None, mass=1.0): 14 | """ 15 | Get energy per atom using a LAMMPS calculator 16 | """ 17 | 18 | #file is written 19 | #now lammps part 20 | try: 21 | from lammps import lammps 22 | except ImportError: 23 | raise ModuleNotFoundError("energy method needs lammps compiled as a library. Either install with conda - conda install -c conda-forge lammps,\ 24 | or see here - https://lammps.sandia.gov/doc/Howto_pylammps.html") 25 | 26 | #start routine 27 | lmp = lammps() 28 | lmp.command("echo log") 29 | lmp.command("units metal") 30 | lmp.command("atom_style atomic") 31 | lmp.command("boundary p p p") 32 | 33 | if (pair_style is None): 34 | raise ValueError("pair style has to be provided") 35 | 36 | if (pair_coeff is None): 37 | raise ValueError("pair coeff has to be provided") 38 | 39 | lmp.command('read_data %s'%outfile) 40 | lmp.command('pair_style %s'%pair_style) 41 | lmp.command('pair_coeff %s'%pair_coeff) 42 | lmp.command("mass * %f"%mass) 43 | 44 | lmp.command("compute 1 all pe/atom") 45 | lmp.command("run 0") 46 | eng = lmp.extract_compute("1",1,1) 47 | ids = lmp.extract_atom("id",0) 48 | 49 | natoms = lmp.get_natoms() 50 | eng = [eng[x] for x in range(natoms)] 51 | ids = [ids[x] for x in range(natoms)] 52 | 53 | iddict = dict(zip(np.array(ids).astype(str), eng)) 54 | return iddict 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/pyscal3/system_binding.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "system.h" 8 | #include 9 | #include 10 | #include 11 | 12 | namespace py = pybind11; 13 | using namespace std; 14 | 15 | 16 | PYBIND11_MODULE(csystem, m) { 17 | py::options options; 18 | options.disable_function_signatures(); 19 | m.def("get_abs_distance", &get_abs_distance); 20 | m.def("get_distance_vector", &get_distance_vector); 21 | m.def("remap_atom_into_box", &remap_atom_into_box); 22 | m.def("remap_and_displace_atom", &remap_and_displace_atom); 23 | m.def("reset_all_neighbors", &reset_all_neighbors); 24 | m.def("get_all_neighbors_normal", &get_all_neighbors_normal); 25 | m.def("get_all_neighbors_shell_normal", &get_all_neighbors_shell_normal); 26 | m.def("get_all_neighbors_cells", &get_all_neighbors_cells); 27 | m.def("get_all_neighbors_shell_cells", &get_all_neighbors_shell_cells); 28 | m.def("get_all_neighbors_bynumber", &get_all_neighbors_bynumber); 29 | m.def("get_all_neighbors_sann", &get_all_neighbors_sann); 30 | m.def("get_all_neighbors_adaptive", &get_all_neighbors_adaptive); 31 | m.def("calculate_q", &calculate_q); 32 | m.def("calculate_q_atom", &calculate_q_atom); 33 | m.def("calculate_q_single", &calculate_q_single); 34 | m.def("calculate_aq_single", &calculate_aq_single); 35 | m.def("calculate_disorder", &calculate_disorder); 36 | m.def("calculate_bonds", &calculate_bonds); 37 | m.def("find_clusters", &find_clusters); 38 | m.def("get_all_neighbors_voronoi", &get_all_neighbors_voronoi); 39 | m.def("clean_voronoi_vertices", &clean_voronoi_vertices); 40 | m.def("get_cna_neighbors", &get_cna_neighbors); 41 | m.def("get_acna_neighbors_cn12", &get_acna_neighbors_cn12); 42 | m.def("get_acna_neighbors_cn14", &get_acna_neighbors_cn14); 43 | m.def("get_common_neighbors", &get_common_neighbors); 44 | m.def("get_common_bonds", &get_common_bonds); 45 | m.def("identify_cn12", &identify_cn12); 46 | m.def("identify_cn14", &identify_cn14); 47 | m.def("identify_diamond_cna", &identify_diamond_cna); 48 | m.def("calculate_centrosymmetry", &calculate_centrosymmetry); 49 | m.def("calculate_entropy", &calculate_entropy); 50 | m.def("calculate_average_entropy", &calculate_average_entropy); 51 | 52 | #ifdef VERSION_INFO 53 | m.attr("__version__") = VERSION_INFO; 54 | #else 55 | m.attr("__version__") = "dev"; 56 | #endif 57 | } -------------------------------------------------------------------------------- /tests/files/POSCAR: -------------------------------------------------------------------------------- 1 | Sigma 5 (310)[100] bcc Fe - substitution 2 | 2.83746000000000 3 | 6.6146000000000003 0.0000000000000000 0.0000000000000000 4 | 0.0000000000000000 3.1622800000000000 0.0000000000000000 5 | 0.0000000000000000 0.0000000000000000 1.0000000000000000 6 | Fe C 7 | 38 4 8 | Direct 9 | 0.0010863331828145 -0.0083273568915470 0.0002106906580257 Fe 10 | 0.5010863331828143 0.0083273568915470 -0.0002106906580257 Fe 11 | 0.4970680982182295 0.4754465771634786 0.4996606231861733 Fe 12 | -0.0029319017817708 0.5245534228365212 0.5003393768138266 Fe 13 | 0.3452818673793536 0.0915051963676188 0.9996813604900742 Fe 14 | 0.2005534874462380 0.1923318705125774 0.9999629437457686 Fe 15 | 0.3928590793985325 0.3968586720034397 0.9996394347404414 Fe 16 | 0.2499917129969630 0.4937135049526343 0.9998942667658617 Fe 17 | 0.1057971132665034 0.5912806014586985 0.9998931934711517 Fe 18 | 0.4421534119006636 0.6908683523054653 0.9994041964173341 Fe 19 | 0.2999646754898488 0.7955279563623594 0.9998433273327710 Fe 20 | 0.1555067387970006 0.8965135650524934 0.9998868775449921 Fe 21 | 0.2504467744632241 -0.0060593817149507 0.4996579171498540 Fe 22 | 0.1012225357233653 0.0938495719274259 0.5000859229831427 Fe 23 | 0.4370377151993814 0.1978946126267057 0.4996577384844778 Fe 24 | 0.2966159201849374 0.2932724389161597 0.4997494269563136 Fe 25 | 0.1517525484520206 0.3930635457981305 0.4996995827982813 Fe 26 | 0.3474596127595615 0.5954261176968162 0.4996492982936289 Fe 27 | 0.2026204444684257 0.6926474398129446 0.4998863038796597 Fe 28 | 0.0631969783181307 0.7926722417505505 0.5000284393440730 Fe 29 | 0.4005095905281749 0.8951343565323178 0.4996626491568862 Fe 30 | 0.6555067387970006 0.1034864349475070 0.0001131224550085 Fe 31 | 0.7999646754898486 0.2044720436376406 0.0001566726672291 Fe 32 | 0.9421534119006632 0.3091316476945350 0.0005958035826660 Fe 33 | 0.6057971132665038 0.4087193985413015 0.0001068065288483 Fe 34 | 0.7499917129969632 0.5062864950473660 0.0001057332341379 Fe 35 | 0.8928590793985325 0.6031413279965604 0.0003605652595584 Fe 36 | 0.7005534874462376 0.8076681294874225 0.0000370562542317 Fe 37 | 0.8452818673793534 0.9084948036323812 0.0003186395099262 Fe 38 | 0.7504467744632244 0.0060593817149507 0.5003420828501460 Fe 39 | 0.9005095905281749 0.1048656434676822 0.5003373508431138 Fe 40 | 0.5631971783181329 0.2073277582494500 0.4999715606559268 Fe 41 | 0.7026204444684256 0.3073525601870553 0.5001136961203403 Fe 42 | 0.8474596127595614 0.4045738823031838 0.5003507017063713 Fe 43 | 0.6517525484520204 0.6069364542018695 0.5003004172017192 Fe 44 | 0.7966159201849369 0.7067275610838403 0.5002505730436870 Fe 45 | 0.9370377151993815 0.8021053873732947 0.5003422615155225 Fe 46 | 0.6012225357233651 0.9061504280725740 0.4999140770168577 Fe 47 | 0.0593724205650713 0.2954569414730223 0.9995321993609536 C 48 | 0.5593726205650734 0.7045430585269776 0.0004678006390462 C 49 | 0.499503 0.3023286775005862 -0.0002178286240014 C 50 | -0.000497459 0.6976713224994133 0.0002178286240014 C 51 | -------------------------------------------------------------------------------- /tests/files/conf.bcc.scaled.dump: -------------------------------------------------------------------------------- 1 | ITEM: TIMESTEP 2 | 0 3 | ITEM: NUMBER OF ATOMS 4 | 2 5 | ITEM: BOX BOUNDS 6 | 0.000000 2.000000 7 | 0.000000 2.000000 8 | 0.000000 2.000000 9 | ITEM: ATOMS id type xs ys zs 10 | 1 1 0.000000 0.000000 0.000000 11 | 2 1 0.500000 0.500000 0.500000 12 | -------------------------------------------------------------------------------- /tests/files/conf.dump.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyscal/pyscal3/ae76867ab0a315c39151666a5fda1121f0c4a899/tests/files/conf.dump.gz -------------------------------------------------------------------------------- /tests/test_angular.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os,sys,inspect 3 | import numpy as np 4 | import pyscal3.core as pc 5 | 6 | def test_angular(): 7 | sys = pc.System.create.lattice.diamond(repetitions = [4, 4, 4]) 8 | sys.find.neighbors(method = 'cutoff', cutoff=0) 9 | sys.calculate.angular_criteria() 10 | 11 | assert np.round(np.mean(np.array(sys.atoms.angular_parameters.diamond_angle)), decimals=2) == 0.00 12 | -------------------------------------------------------------------------------- /tests/test_ase.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os,sys,inspect 3 | import numpy as np 4 | import pyscal3.core as pc 5 | import pyscal3.ase as psa 6 | from ase.build import bulk 7 | 8 | 9 | def test_calculate_centrosymmetry(): 10 | ti_hcp = bulk("Ti", orthorhombic=True) 11 | q = psa.calculate_centrosymmetry(ti_hcp, nmax=12) 12 | assert np.round(np.mean(np.array(q)), decimals=2) == 8.7 13 | 14 | 15 | def test_calculate_cna(): 16 | al_fcc = bulk("Al") 17 | fe_bcc = bulk("Fe") 18 | ti_hcp = bulk("Ti") 19 | 20 | cna = psa.calculate_cna(al_fcc) 21 | assert cna["fcc"] == 1 22 | 23 | cna = psa.calculate_cna(fe_bcc) 24 | assert cna["bcc"] == 1 25 | 26 | cna = psa.calculate_cna(ti_hcp) 27 | assert cna["hcp"] == 2 28 | 29 | 30 | def test_calculate_steinhardt_parameter(): 31 | w_bcc = bulk("W", a=1.0, cubic=True).repeat([4, 4, 4]) 32 | q = psa.calculate_steinhardt_parameter( 33 | ase_atoms=w_bcc, 34 | q=[4, 6], 35 | nmax=12, 36 | method='cutoff', 37 | cutoff=0.9, 38 | ) 39 | assert np.round(np.mean(np.array(q[0])), decimals=2) == 0.51, "Calculated q4 value is wrong!" 40 | assert np.round(np.mean(np.array(q[1])), decimals=2) == 0.63, "Calculated q4 value is wrong!" 41 | 42 | w_bcc = bulk("W", a=1.0, cubic=True).repeat([4, 4, 4]) 43 | q = psa.calculate_steinhardt_parameter( 44 | ase_atoms=w_bcc, 45 | q=[4, 6], 46 | nmax=12, 47 | method='cutoff', 48 | cutoff=0.9, 49 | averaged=True, 50 | ) 51 | assert np.round(np.mean(np.array(q[0])), decimals=2) == 0.51, "Calculated q4 value is wrong!" 52 | assert np.round(np.mean(np.array(q[1])), decimals=2) == 0.63, "Calculated q4 value is wrong!" 53 | 54 | 55 | def test_calculate_diamond_structure(): 56 | # the ASE diamond structure is wrong so we use pyscal to generate the structure 57 | c_diamond = pc.System.create.lattice.diamond( 58 | element="C", 59 | repetitions=(2,2,2), 60 | lattice_constant=4.00, 61 | ).convert_to.ase() 62 | diamond_dict = psa.calculate_diamond_structure(c_diamond) 63 | assert diamond_dict['cubic diamond'] == len(c_diamond) 64 | 65 | 66 | def test_calculate_radial_distribution_function(): 67 | w_bcc = bulk("W", a=1.00).repeat([10, 10, 10]) 68 | al_fcc = bulk("Al", a=1.00).repeat([10, 10, 10]) 69 | 70 | rdf, r = psa.calculate_radial_distribution_function(w_bcc, rmax=2) 71 | args = np.argsort(rdf)[::-1] 72 | assert(r[args[0]] - 0.86 < 1E-5) 73 | 74 | rdf, r = psa.calculate_radial_distribution_function(al_fcc, rmax=2) 75 | args = np.argsort(rdf)[::-1] 76 | assert(r[args[0]] - 0.70 < 1E-5) 77 | 78 | 79 | def test_get_symmetry(): 80 | w_bcc = bulk("W", cubic=True) 81 | al_fcc = bulk("Al", cubic=True) 82 | assert psa.get_symmetry(w_bcc)["international_symbol"] == "Im-3m" 83 | assert psa.get_symmetry(al_fcc)["international_symbol"] == "Fm-3m" 84 | 85 | 86 | def test_calculate_chiparams(): 87 | w_bcc = bulk("W", a=4, cubic=True).repeat([3, 3, 3]) 88 | chi_params = psa.calculate_chiparams(w_bcc, method='cutoff', cutoff=0) 89 | chip2 = [3, 0, 0, 0, 36, 12, 0, 36, 0] 90 | assert np.sum(np.array(chi_params)-np.array(chip2)) == 0 91 | 92 | 93 | def test_calculate_voronoi_volume(): 94 | w_bcc = bulk("W", a=4, cubic=True).repeat([3, 3, 3]) 95 | voronoi_volume_lst = psa.calculate_voronoi_volume(w_bcc) 96 | assert all(np.isclose(voronoi_volume_lst, 32)) 97 | -------------------------------------------------------------------------------- /tests/test_centro.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os,sys,inspect 3 | import numpy as np 4 | import pyscal3.core as pc 5 | from ase.build import bulk 6 | 7 | def test_cs_ges(): 8 | sys = pc.System.create.lattice.bcc(repetitions = [7, 7, 7], lattice_constant=4.00) 9 | q = sys.calculate.centrosymmetry(nmax=8) 10 | assert np.round(np.mean(np.array(q)), decimals=2) == 0.00 11 | 12 | q = sys.calculate.centrosymmetry(nmax=4) 13 | assert np.round(np.mean(np.array(q)), decimals=2) > 0.00 14 | 15 | def test_hcp(): 16 | ti_hcp = bulk("Ti", orthorhombic=True) 17 | sys = pc.System(ti_hcp, format='ase') 18 | q = sys.calculate.centrosymmetry(nmax=12) 19 | assert np.round(np.mean(np.array(q)), decimals=2) == 8.7 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/test_chiparams.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import pyscal3.core as pc 4 | 5 | def test_chiparamsbcc(): 6 | sys = pc.System.create.lattice.bcc(repetitions = [3, 3, 3], lattice_constant=4) 7 | sys.find.neighbors(method='cutoff', cutoff=0) 8 | sys.calculate.chi_params() 9 | chip2 = [3, 0, 0, 0, 36, 12, 0, 36, 0] 10 | assert np.sum(np.array(sys.atoms.angular_parameters.chi_params[2])-np.array(chip2)) == 0 11 | 12 | 13 | def test_chiparamsfcc(): 14 | sys = pc.System.create.lattice.fcc(repetitions = [5, 5, 5], lattice_constant=4) 15 | sys.find.neighbors(method='cutoff', cutoff=0) 16 | sys.calculate.chi_params() 17 | chip2 = [6, 0, 0, 0, 24, 12, 0, 24, 0] 18 | assert np.sum(np.array(sys.atoms.angular_parameters.chi_params[2])-np.array(chip2)) == 0 19 | 20 | def test_chiparamsdia(): 21 | sys = pc.System.create.lattice.diamond(repetitions = [3, 3, 3], lattice_constant=4) 22 | sys.find.neighbors(method='cutoff', cutoff=0) 23 | sys.calculate.chi_params() 24 | chip2 = [0, 0, 0, 0, 6, 0, 0, 0, 0] 25 | assert np.sum(np.array(sys.atoms.angular_parameters.chi_params[2])-np.array(chip2)) == 0 26 | -------------------------------------------------------------------------------- /tests/test_cna.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os,sys,inspect 3 | import numpy as np 4 | import pyscal3.core as pc 5 | from ase.build import bulk 6 | 7 | #test conventional cna for fcc 8 | def test_cna_cutoff(): 9 | sys = pc.System.create.lattice.fcc(repetitions=(7,7,7), lattice_constant=4.00) 10 | res = sys.calculate.common_neighbor_analysis(lattice_constant=4.00) 11 | assert res["fcc"] == 7*7*7*4 12 | 13 | def test_cna_a1(): 14 | sys = pc.System.create.lattice.fcc(repetitions=(7,7,7), lattice_constant=4.00) 15 | res = sys.calculate.common_neighbor_analysis(lattice_constant=None) 16 | assert res["fcc"] == 7*7*7*4 17 | 18 | #now test adaptive 19 | def test_cna_adaptive(): 20 | sys = pc.System.create.lattice.fcc(repetitions=(7,7,7), lattice_constant=4.00) 21 | sys.calculate.common_neighbor_analysis(lattice_constant=None) 22 | assert sys.atoms.structure[0] == 1 23 | 24 | sys = pc.System.create.lattice.hcp(repetitions=(7,7,7), lattice_constant=4.00) 25 | sys.calculate.common_neighbor_analysis(lattice_constant=None) 26 | assert sys.atoms.structure[0] == 2 27 | 28 | sys = pc.System.create.lattice.bcc(repetitions=(7,7,7), lattice_constant=4.00) 29 | sys.calculate.common_neighbor_analysis(lattice_constant=None) 30 | assert sys.atoms.structure[0] == 3 31 | 32 | 33 | def test_ase_bulks(): 34 | 35 | al_fcc = bulk("Al") 36 | fe_bcc = bulk("Fe") 37 | ti_hcp = bulk("Ti") 38 | 39 | sys = pc.System() 40 | sys.read.ase(al_fcc) 41 | cna = sys.calculate.common_neighbor_analysis() 42 | assert cna["fcc"] == 1 43 | 44 | sys = pc.System() 45 | sys.read.ase(fe_bcc) 46 | cna = sys.calculate.common_neighbor_analysis() 47 | assert cna["bcc"] == 1 48 | 49 | sys = pc.System() 50 | sys.read.ase(ti_hcp) 51 | cna = sys.calculate.common_neighbor_analysis() 52 | assert cna["hcp"] == 2 53 | 54 | def test_cna_diamond(): 55 | sys = pc.System.create.lattice.diamond(repetitions=(7,7,7), lattice_constant=4.00) 56 | sys.calculate.diamond_structure() 57 | assert sys.atoms.structure[0] == 1 -------------------------------------------------------------------------------- /tests/test_crystal_structures.py: -------------------------------------------------------------------------------- 1 | 2 | import pyscal3.structure_creator as pcs 3 | import os 4 | 5 | def test_create_structure(): 6 | structure = ['bcc', 'fcc', 'hcp', 'diamond', 'a15', 'l12', 'b2'] 7 | natoms = [2, 4, 4, 8, 8, 4, 2] 8 | 9 | for count, struct in enumerate(structure): 10 | #dumpfile = os.path.join(os.getcwd(), "test.dat") 11 | atoms, boxdims = pcs.make_crystal(struct) 12 | assert len(atoms["positions"]) == natoms[count] 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/test_delete.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os,sys,inspect 3 | import numpy as np 4 | import pyscal3.core as pc 5 | 6 | def test_delete(): 7 | sys = pc.System.create.element.Fe() 8 | assert sys.natoms == 2 9 | 10 | sys.delete(ids=[1]) 11 | assert sys.natoms == 1 12 | 13 | sys = pc.System.create.element.Fe() 14 | sys.delete(indices=[0]) 15 | assert sys.natoms == 1 16 | 17 | def condition(atom): 18 | if atom.ids[0] == 1: 19 | return True 20 | 21 | sys = pc.System.create.element.Fe() 22 | sys.delete(condition=condition) 23 | assert sys.natoms == 1 24 | 25 | sys = pc.System.create.element.Fe() 26 | sys.apply_selection(indices=[0]) 27 | sys.delete(selection=True) 28 | assert sys.natoms == 1 29 | 30 | -------------------------------------------------------------------------------- /tests/test_disorder.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import pyscal3.core as pc 4 | 5 | def test_ordered_disorder(): 6 | sys = pc.System('tests/files/conf.fcc.dump') 7 | sys.find.neighbors(method='cutoff', cutoff=0) 8 | sys.calculate.steinhardt_parameter(6) 9 | sys.calculate.disorder(averaged=True) 10 | assert np.mean(sys.atoms.steinhardt.disorder.norm) < 0.50 11 | assert np.mean(sys.atoms.steinhardt.disorder.average) < 0.50 12 | 13 | def test_disordered_disorder(): 14 | sys = pc.System('tests/files/conf.lqd.dump') 15 | sys.find.neighbors(method='cutoff', cutoff=0) 16 | sys.calculate.steinhardt_parameter(6) 17 | sys.calculate.disorder(averaged=True) 18 | assert np.mean(sys.atoms.steinhardt.disorder.norm) > 1.00 19 | assert np.mean(sys.atoms.steinhardt.disorder.average) > 1.00 20 | -------------------------------------------------------------------------------- /tests/test_entropy.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import numpy as np 4 | import pyscal3.core as pc 5 | 6 | 7 | def test_entropy(): 8 | sys = pc.System("tests/files/conf.fcc.Al.dump") 9 | sys.find.neighbors(method="cutoff", cutoff=0) 10 | 11 | lat = (sys.box[0][0]/5) 12 | 13 | assert np.mean(sys.calculate.entropy(1.4*lat, average=True, local=True))+4.1782399110084985 < 0.001 -------------------------------------------------------------------------------- /tests/test_masking.py: -------------------------------------------------------------------------------- 1 | import pyscal3.core as pc 2 | import os 3 | import numpy as np 4 | 5 | def test_masking(): 6 | pass 7 | #atoms, box = pcs.make_crystal(structure='b2', repetitions=(6,6,6), lattice_constant=3.127) 8 | #sys = pc.System() 9 | #sys.box = box 10 | #sys.atoms = atoms 11 | 12 | #def _condition1(atom): 13 | # if atom.species[0] == 1: 14 | # return True 15 | # return False 16 | 17 | #def _condition2(atom): 18 | # if atom.species[0] == 1: 19 | # return False 20 | # return True 21 | 22 | #sys.apply_mask(condition=_condition1, mask_type="secondary") 23 | #sys.apply_mask(condition=_condition2, mask_type="primary") 24 | #sys.find_neighbors(method="cutoff", cutoff=3.6) 25 | #assert len(sys.atoms.neighbors.index[0]) == 0 26 | #sys.remove_mask(mask_type="all") 27 | 28 | #sys.apply_mask(condition=_condition2, mask_type="primary") 29 | #sys.find_neighbors(method="cutoff", cutoff=3.6) 30 | #assert len(sys.atoms.neighbors.index[1]) == 7 31 | #sys.remove_mask(mask_type="all") 32 | 33 | #sys.find_neighbors(method="cutoff", cutoff=3.6) 34 | #assert len(sys.atoms.neighbors.index[0]) == 14 35 | #assert len(sys.atoms.neighbors.index[1]) == 14 36 | -------------------------------------------------------------------------------- /tests/test_neighbors.py: -------------------------------------------------------------------------------- 1 | import pyscal3.core as pc 2 | import os 3 | import numpy as np 4 | from ase.build import bulk 5 | 6 | def test_system_init(): 7 | sys = pc.System.create.lattice.bcc(repetitions = [10,10,10], lattice_constant=3.127) 8 | sys.find.neighbors(method="cutoff", cutoff=3.6) 9 | a1 = np.array(sys.atoms.neighbors.distance) 10 | a2 = np.array([2.708061437633939, 11 | 3.127, 12 | 3.126999999999999, 13 | 2.7080614376339383, 14 | 3.127, 15 | 3.126999999999999, 16 | 2.7080614376339383, 17 | 2.708061437633937, 18 | 3.127, 19 | 3.126999999999999, 20 | 2.7080614376339383, 21 | 2.708061437633937, 22 | 2.708061437633937, 23 | 2.7080614376339356]) 24 | assert np.sum(a1-a2) < 1E-5 25 | assert np.sum(sys.atoms.neighbors.distance[0]-a2) < 1E-5 26 | 27 | sys.find.neighbors(method="cutoff", cutoff=3.6) 28 | a1 = np.array(sys.atoms.neighbors.distance[0]) 29 | a2 = np.array([2.7080614376339356, 30 | 2.708061437633937, 31 | 2.708061437633937, 32 | 2.708061437633937, 33 | 2.7080614376339383, 34 | 2.7080614376339383, 35 | 2.7080614376339383, 36 | 2.708061437633939, 37 | 3.126999999999999, 38 | 3.126999999999999, 39 | 3.126999999999999, 40 | 3.127, 41 | 3.127, 42 | 3.127]) 43 | assert np.sum(a1-a2) < 1E-5 44 | 45 | sys.find.neighbors(method="cutoff", cutoff='sann') 46 | a1 = np.array(sys.atoms.neighbors.distance[0]) 47 | a2 = np.array([2.7080614376339356, 48 | 2.708061437633937, 49 | 2.708061437633937, 50 | 2.7080614376339383, 51 | 2.7080614376339383, 52 | 2.7080614376339383, 53 | 2.708061437633939, 54 | 3.126999999999999, 55 | 3.126999999999999, 56 | 3.126999999999999, 57 | 3.127, 58 | 3.127, 59 | 3.127, 60 | 4.422245809540667]) 61 | assert np.sum(a1-a2) < 1E-5 62 | 63 | sys.find.neighbors(method="number", nmax=8) 64 | a1 = np.array(sys.atoms.neighbors.distance[0]) 65 | a2 = np.array([2.7080614376339356, 66 | 2.708061437633937, 67 | 2.708061437633937, 68 | 2.708061437633937, 69 | 2.7080614376339383, 70 | 2.7080614376339383, 71 | 2.7080614376339383, 72 | 2.708061437633939]) 73 | assert np.sum(a1-a2) < 1E-5 74 | 75 | 76 | def test_neighbor_shell(): 77 | sys = pc.System.create.element.Cu(repetitions=(5,5,5)) 78 | sys.find.neighbors(method='cutoff', cutoff=3, shell_thickness=1, cells=False) 79 | assert len(sys.atoms.neighbors.distance[0]) == 6 80 | assert np.abs(sys.atoms.neighbors.distance[0][0]-3.61) <= 1E-3 81 | 82 | sys = pc.System.create.element.Cu(repetitions=(5,5,5)) 83 | sys.find.neighbors(method='cutoff', cutoff=3, shell_thickness=1, cells=True) 84 | assert len(sys.atoms.neighbors.distance[0]) == 6 85 | assert np.abs(sys.atoms.neighbors.distance[0][0]-3.61) <= 1E-3 -------------------------------------------------------------------------------- /tests/test_nucsize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import numpy as np 4 | import pyscal3.core as pc 5 | 6 | def test_complex_system(): 7 | sys = pc.System('tests/files/cluster.dump') 8 | sys.find.neighbors(method='cutoff', cutoff=3.63) 9 | assert 176 == sys.find.solids(bonds=6, threshold=0.5, avgthreshold=0.6, cluster=True) 10 | 11 | def test_cluster(): 12 | sys = pc.System('tests/files/cluster.dump') 13 | sys.find.neighbors(method='cutoff', cutoff=3.63) 14 | sys.find.solids(cluster=False) 15 | val = sys.find.clusters(sys.atoms.steinhardt.order.sij.solid, largest = True) 16 | assert 176 == val 17 | 18 | def test_cluster_cutoff(): 19 | sys = pc.System('tests/files/cluster.dump') 20 | sys.find.neighbors(method='cutoff', cutoff=3.63) 21 | sys.find.solids(cluster=False) 22 | val = sys.find.clusters(sys.atoms.steinhardt.order.sij.solid, largest = True, cutoff=3.63) 23 | assert 176 == val 24 | 25 | def test_system_nucsize_fraction(): 26 | #create some atoms 27 | sys = pc.System.create.lattice.bcc(repetitions = [2,2,2], lattice_constant=3.20) 28 | 29 | #test that atoms are set properly 30 | assert sys.natoms == 16 31 | 32 | #now calculate nucsize 33 | sys.find.neighbors(method='cutoff', cutoff=3.63) 34 | assert 16 == sys.find.solids(bonds=0.8, threshold=0.5, avgthreshold=0.6, cluster=True) -------------------------------------------------------------------------------- /tests/test_q12.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import numpy as np 4 | import pyscal3.core as pc 5 | 6 | 7 | def test_q_12(): 8 | sys = pc.System.create.lattice.fcc(repetitions = [4, 4, 4]) 9 | 10 | #sys.get_neighbors(method = 'voronoi') 11 | sys.find.neighbors(method = 'cutoff', cutoff=0.9) 12 | q = sys.calculate.steinhardt_parameter(12) 13 | assert np.round(np.mean(np.array(q)), decimals=2) == 0.60 , "Calculated q4 value is wrong!" 14 | 15 | q = sys.calculate.steinhardt_parameter(12, averaged=True) 16 | assert np.round(np.mean(np.array(q)), decimals=2) == 0.60 , "Calculated q4 value is wrong!" 17 | 18 | """ 19 | def test_q_12_voro(): 20 | atoms, boxdims = pcs.make_crystal('fcc', repetitions = [4, 4, 4]) 21 | sys = pc.System() 22 | sys.box = boxdims 23 | sys.atoms = atoms 24 | 25 | sys.find_neighbors(method = 'voronoi') 26 | q = sys.calculate_q(12) 27 | assert np.round(np.mean(np.array(q)), decimals=2) == 0.60 , "Calculated q4 value is wrong!" 28 | 29 | q = sys.calculate_q(12, averaged=True) 30 | assert np.round(np.mean(np.array(q)), decimals=2) == 0.60 , "Calculated q4 value is wrong!" 31 | """ -------------------------------------------------------------------------------- /tests/test_q3.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import numpy as np 4 | import pyscal3.core as pc 5 | 6 | 7 | def test_q_3(): 8 | sys = pc.System.create.lattice.bcc(repetitions = [4, 4, 4]) 9 | 10 | #sys.get_neighbors(method = 'voronoi') 11 | sys.find.neighbors(method = 'cutoff', cutoff=0.9) 12 | 13 | q = sys.calculate.steinhardt_parameter(3, averaged=True) 14 | assert np.round(np.mean(np.array(q)), decimals=2) == 0.00 15 | -------------------------------------------------------------------------------- /tests/test_q_system.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import numpy as np 4 | import pyscal3.core as pc 5 | 6 | def test_q_4(): 7 | sys = pc.System.create.lattice.bcc(repetitions = [4, 4, 4]) 8 | 9 | #sys.get_neighbors(method = 'voronoi') 10 | sys.find.neighbors(method = 'cutoff', cutoff=0.9) 11 | 12 | q = sys.calculate.steinhardt_parameter([4, 6]) 13 | assert np.round(np.mean(np.array(q[0])), decimals=2) == 0.51 , "Calculated q4 value is wrong!" 14 | assert np.round(np.mean(np.array(q[1])), decimals=2) == 0.63 , "Calculated q4 value is wrong!" 15 | 16 | q = sys.calculate.steinhardt_parameter([4, 6], averaged=True) 17 | assert np.round(np.mean(np.array(q[0])), decimals=2) == 0.51 , "Calculated q4 value is wrong!" 18 | assert np.round(np.mean(np.array(q[1])), decimals=2) == 0.63 , "Calculated q4 value is wrong!" 19 | 20 | -------------------------------------------------------------------------------- /tests/test_rdf.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os,sys,inspect 3 | import numpy as np 4 | import pyscal3.core as pc 5 | 6 | 7 | def test_rdf_bcc(): 8 | sys = pc.System.create.lattice.bcc(repetitions = [10, 10, 10]) 9 | rdf, r = sys.calculate.radial_distribution_function(rmax=2) 10 | 11 | args = np.argsort(rdf)[::-1] 12 | assert(r[args[0]]-0.86 < 1E-5) 13 | 14 | def test_rdf_fcc(): 15 | sys = pc.System.create.lattice.fcc(repetitions = [10, 10, 10]) 16 | rdf, r = sys.calculate.radial_distribution_function(rmax=2) 17 | 18 | args = np.argsort(rdf)[::-1] 19 | assert(r[args[0]]-0.70 < 1E-5) 20 | -------------------------------------------------------------------------------- /tests/test_read_file.py: -------------------------------------------------------------------------------- 1 | import pyscal3.core as pc 2 | import os 3 | import numpy as np 4 | from ase.build import bulk 5 | 6 | def test_system_init(): 7 | sys = pc.System("tests/files/conf.dump", customkeys=["vx", "vy"]) 8 | assert sys.natoms == 500 9 | assert sys.atoms.vx[0] == '0.0394436' 10 | 11 | sys.read.file("tests/files/conf.dump.gz") 12 | assert sys.natoms == 500 13 | 14 | sys.read.file("tests/files/conf.bcc.scaled.dump") 15 | assert sys.natoms == 2 16 | 17 | sys.read.file("tests/files/POSCAR", format="poscar") 18 | assert sys.natoms == 42 19 | 20 | sys = pc.System("tests/files/conf.dump", customkeys=["vx", "vy"]) 21 | sys.write.file("test.dump", customkeys=["vx"]) 22 | 23 | sys = pc.System("test.dump", customkeys=["vx"]) 24 | assert sys.natoms == 500 25 | assert sys.atoms.vx[0] == '0.0394436' 26 | 27 | sys = pc.System("tests/files/conf.dump", customkeys=["vx", "vy"]) 28 | aseobj = sys.write.ase(species=["Au"]) 29 | assert np.sum(sys.atoms.positions[0]-aseobj.positions[0]) < 1E-5 -------------------------------------------------------------------------------- /tests/test_sro.py: -------------------------------------------------------------------------------- 1 | from pyscal3.core import System 2 | import numpy as np 3 | 4 | 5 | def test_sro(): 6 | sys = System.create.lattice.l12(lattice_constant=4, repetitions=(2,2,2)) 7 | sys.find.neighbors(method='cutoff') 8 | sys.chemical.short_range_order() 9 | assert sys.atoms.chemical.short_range_order[0] == 1 10 | assert np.isclose(sys.atoms.chemical.short_range_order[1], -0.33333333) -------------------------------------------------------------------------------- /tests/test_symmetry.py: -------------------------------------------------------------------------------- 1 | from pyscal3.operations.symmetry import get_symmetry 2 | from pyscal3.core import System 3 | 4 | def test_bcc(): 5 | sys = System.create.element.Fe() 6 | sym = get_symmetry(sys) 7 | assert sym["international_symbol"] == 'Im-3m' 8 | 9 | def test_fcc(): 10 | sys = System.create.element.Cu() 11 | sym = get_symmetry(sys) 12 | assert sym["international_symbol"] == 'Fm-3m' 13 | 14 | def test_hcp(): 15 | sys = System.create.element.Mg() 16 | sym = get_symmetry(sys) 17 | assert sym["international_symbol"] == 'P6_3/mmc' 18 | 19 | def test_diamond(): 20 | sys = System.create.element.Si() 21 | sym = get_symmetry(sys) 22 | assert sym["international_symbol"] == 'Fd-3m' -------------------------------------------------------------------------------- /tests/test_system.py: -------------------------------------------------------------------------------- 1 | import pyscal3.core as pc 2 | import os 3 | import numpy as np 4 | from ase.build import bulk 5 | from pyscal3.atoms import Atoms 6 | 7 | 8 | def test_system_init(): 9 | sys = pc.System.create.lattice.bcc(repetitions = [10,10,10], lattice_constant=3.127) 10 | assert len(sys.atoms["positions"]) == 10*10*10*2 11 | assert sys.triclinic == 0 12 | assert np.abs(sys.boxdims[0] - sys.box[0][0]) < 1E-5 13 | 14 | 15 | def test_system_triclinic(): 16 | struct = bulk('Cu').repeat(10) 17 | sys = pc.System() 18 | sys.box = np.array(struct.cell) 19 | atoms = {} 20 | atoms["positions"] = struct.positions 21 | sys.atoms = atoms 22 | assert sys.triclinic == 1 23 | tb = (sys.rot== np.array(struct.cell).T) 24 | assert np.prod(tb) == 1 25 | tb = (sys.rotinv==np.linalg.inv(np.array(struct.cell).T)) 26 | assert np.prod(tb) == 1 27 | 28 | def test_nop(): 29 | sys = pc.System.create.lattice.bcc(repetitions = [2, 2, 2], lattice_constant=3.127) 30 | 31 | assert sys.natoms == 16 32 | assert len(sys.atoms['positions']) == 128 33 | 34 | for a in sys.iter_atoms(): 35 | assert np.sum(a["positions"]) == 0 36 | break 37 | 38 | natoms = {'positions':[[0,0,0]]} 39 | sys = sys.add_atoms(natoms) 40 | assert sys.natoms == 17 41 | 42 | def test_embed(): 43 | cu = bulk('Cu') 44 | sys = pc.System() 45 | sys.box = np.array(cu.cell) 46 | sys.atoms = Atoms({"positions": cu.positions}) 47 | sys.modify.embed_in_cubic_box() 48 | assert np.abs(sys.box[0][0] - 5.105310960166873) < 1E-5 49 | 50 | def test_distance(): 51 | sys = pc.System.create.lattice.bcc(repetitions = [2, 2, 2], lattice_constant=3.127) 52 | dist = sys.calculate.distance([0.0, 0.0, 0.0], [1.5635, 1.5635, 1.5635]) 53 | assert np.abs(dist - 2.708061437633939) < 1E-5 54 | 55 | def test_composition(): 56 | sys = pc.System.create.lattice.l12(repetitions = [2, 2, 2], lattice_constant=3.127) 57 | c = sys.concentration 58 | assert c[1] == 0.25 59 | assert c[2] == 0.75 60 | 61 | c = sys.composition 62 | assert c[1] == 0.25 63 | assert c[2] == 0.75 64 | 65 | def test_volume(): 66 | sys = pc.System.create.lattice.fcc(repetitions = [10, 10, 10]) 67 | assert sys.volume == 1000 68 | 69 | sys_a = pc.System.create.element.Al() 70 | vol_a = sys_a.volume/sys_a.natoms 71 | sys_b = pc.System.create.element.Al(primitive=True) 72 | vol_b = sys_b.volume/sys_b.natoms 73 | assert np.abs(vol_a-vol_b) < 1E-5 74 | 75 | def test_system_init(): 76 | sys = pc.System.create.lattice.custom([[0, 0, 0], [0.5, 0.5, 0.5]], [1, 2], 77 | [[1,0,0], [0,1,0], [0,0,1]]) 78 | assert sys.natoms == 2 -------------------------------------------------------------------------------- /tests/test_traj_process.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import numpy as np 4 | import pyscal3.core as pc 5 | import pyscal3.structure_creator as pcs 6 | import pyscal3.traj_process as ptp 7 | 8 | def test_create_multislice_dump(): 9 | """ 10 | Create a multitest dump file and test it 11 | """ 12 | sys = pc.System.create.lattice.bcc(repetitions = [6, 6, 6]) 13 | ptp.write_file(sys, "tests/bcc1.dump") 14 | 15 | atoms2, boxdims2 = pcs.make_crystal('bcc', repetitions=[6,6,6]) 16 | #modify the coordinates of one atom 17 | x = atoms2["positions"][0] 18 | x[0] += 0.01 19 | atoms2["positions"][0] = x 20 | assert len(atoms2["positions"]) == 432 21 | #write it out 22 | sys2 = pc.System() 23 | sys2.box = boxdims2 24 | sys2.atoms = atoms2 25 | 26 | ptp.write_file(sys2, "tests/bcc2.dump") 27 | 28 | #now cleanup 29 | if os.path.exists("tests/bcc1.dat"): 30 | os.remove("tests/bcc1.dat") 31 | if os.path.exists("tests/bcc2.dat"): 32 | os.remove("tests/bcc2.dat") 33 | 34 | def test_customvals_dump(): 35 | """ 36 | Test writing customvals 37 | """ 38 | sys = pc.System.create.lattice.bcc(repetitions = [1, 1, 1]) 39 | 40 | 41 | #test for multiple customvals 42 | customks = ['one'] 43 | customvs = [[1],[1]] 44 | ptp.write_file(sys, "tests/bcc4.dump", customkeys=customks, customvals=customvs) 45 | 46 | #now read this file 47 | lines = [] 48 | for line in open("tests/bcc4.dump", 'r'): 49 | lines.append(line) 50 | 51 | #now check the atoms 52 | last1line = lines[-1].strip().split() 53 | last2line = lines[-2].strip().split() 54 | last3line = lines[-3].strip().split() 55 | 56 | #now verify 57 | assert last1line[-1] == '1' 58 | assert last2line[-1] == '1' 59 | assert last3line[-1] == 'one' 60 | 61 | #clean up 62 | if os.path.exists("tests/bcc4.dat"): 63 | os.remove("tests/bcc4.dat") 64 | 65 | #test for multiple customvals 66 | customks = ['one', 'two'] 67 | customvs = [[1,1], [2,2]] 68 | ptp.write_file(sys, "tests/bcc4.dump", customkeys=customks, customvals=customvs) 69 | 70 | #now read this file 71 | lines = [] 72 | for line in open("tests/bcc4.dump", 'r'): 73 | lines.append(line) 74 | 75 | #now check the atoms 76 | last1line = lines[-1].strip().split() 77 | last2line = lines[-2].strip().split() 78 | last3line = lines[-3].strip().split() 79 | 80 | #now verify 81 | assert last1line[-1] == '2' 82 | assert last1line[-2] == '2' 83 | assert last2line[-1] == '1' 84 | assert last2line[-2] == '1' 85 | assert last3line[-1] == 'two' 86 | assert last3line[-2] == 'one' 87 | 88 | #clean up 89 | if os.path.exists("tests/bcc4.dat"): 90 | os.remove("tests/bcc4.dat") 91 | -------------------------------------------------------------------------------- /tests/test_trajectory.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import numpy as np 4 | import pyscal3.core as pc 5 | #from pyscal.trajectory import Trajectory, hdf_to_dump 6 | from pyscal3.trajectory import Trajectory 7 | 8 | def test_traj(): 9 | traj = Trajectory("examples/traj.light") 10 | assert traj.nblocks == 10 11 | 12 | assert len(traj.get_block(0)) == 509 13 | 14 | traj.load(0) 15 | data = traj.data[0] 16 | assert data["box"][0][0] == -7.34762 17 | assert data["atoms"]["x"][0] == -4.72745 18 | traj.unload(0) 19 | 20 | def test_timeslice(): 21 | traj = Trajectory("examples/traj.light") 22 | assert traj.nblocks == 10 23 | sys = traj[0].to_system() 24 | assert sys[0].box[0][0] == 18.21922 25 | 26 | aseobj = traj[0].to_ase(species=["Au"]) 27 | assert aseobj[0].positions[0][0] == -4.72745 28 | 29 | od = traj[0].to_dict() 30 | assert od[0]["box"][0][0] == -7.34762 31 | 32 | traj[0].to_file("test.out") 33 | assert os.path.exists("test.out") == True 34 | 35 | #traj[0].to_hdf("test.hdf") 36 | #assert os.path.exists("test.hdf") == True 37 | 38 | #hdf_to_dump("test.hdf", "test.dat") 39 | #assert os.path.exists("test.dat") == True -------------------------------------------------------------------------------- /tests/test_voronoi.py: -------------------------------------------------------------------------------- 1 | import pyscal3.core as pc 2 | import os 3 | import numpy as np 4 | from ase.build import bulk 5 | 6 | def test_voronoi_props(): 7 | nx = 5 8 | sys = pc.System.create.lattice.bcc(repetitions = [nx, nx, nx], lattice_constant=3.127) 9 | sys.find.neighbors(method="voronoi") 10 | 11 | assert sys.atoms.voronoi.vertex.numbers[0][0] == 6 12 | assert (sys.atoms.voronoi.vertex.positions[0][0][0]+1.5635 < 1E-4) 13 | assert (sys.atoms.voronoi.volume[0]-15.288104691499992 < 1E-5) 14 | assert (sys.atoms.voronoi.face.perimeters[0][0]-6.6333687143110005 < 1E-5) 15 | assert sys.atoms.voronoi.face.vertices[0][0] == 6 16 | 17 | def test_voronoi_vector(): 18 | sys = pc.System.create.lattice.fcc(repetitions=(4,4,4)) 19 | sys.find.neighbors(method='voronoi') 20 | sys.calculate.voronoi_vector() 21 | assert sys.atoms.voronoi.vector[0][1] == 12 22 | 23 | #def test_voronoi_vertices(): 24 | # nx = np.random.randint(1, 10) 25 | # nverts = (nx**3*12) 26 | # sys = Structure().lattice.bcc(repetitions = [nx, nx, nx]) 27 | # sys.find_neighbors(method='voronoi', cutoff=0.1) 28 | # assert len(sys.atoms.voronoi.vertex.unique_positions) == nverts --------------------------------------------------------------------------------