├── .github └── workflows │ ├── test.yml │ └── wheels.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── .gitignore ├── README.md ├── assets │ ├── archiving.py │ ├── clustering.xcf │ ├── conventional_cell_comparison.py │ ├── sbc.svg │ └── stacked.py ├── babel.config.js ├── docs │ ├── about.md │ ├── get-started │ │ ├── _category_.json │ │ ├── installation.md │ │ └── quick-start.md │ ├── learn │ │ ├── _category_.json │ │ ├── dimensionality.mdx │ │ ├── symmetry-analysis.mdx │ │ └── symmetry-based-clustering.mdx │ └── reference │ │ ├── _category_.json │ │ ├── cluster.md │ │ ├── geometry.md │ │ ├── sbc.md │ │ └── symmetryanalyzer.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── pydoc-markdown.yml ├── sidebars.js ├── src │ ├── components │ │ ├── Figure.js │ │ └── HomepageFeatures │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ └── index.module.css ├── static │ ├── .nojekyll │ ├── img │ │ ├── all_cropped.png │ │ ├── conventional.svg │ │ ├── cu_cropped.png │ │ ├── docusaurus-social-card.jpg │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── graphene_cropped.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── mos2_cropped.png │ │ ├── sbc.png │ │ ├── tsa.jpg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg │ └── snippets │ │ ├── clathrate.xyz │ │ ├── conventional_cell.py │ │ ├── dimensionality.py │ │ ├── material_id.py │ │ ├── readme.py │ │ ├── sbc.py │ │ ├── system.xyz │ │ └── wyckoff_sets.py └── yarn.lock ├── examples ├── classification1.py ├── classification2.py ├── data │ ├── C26H24N4O2.xyz │ ├── C32Mo32+CO2.xyz │ ├── C49+N.xyz │ ├── H2O.xyz │ ├── Mg61O62+CH4Ni.xyz │ ├── Ru.xyz │ └── system.xyz ├── summary.py ├── surface.py └── symmetry.py ├── matid ├── __init__.py ├── classification │ ├── __init__.py │ ├── classifications.py │ └── classifier.py ├── clustering │ ├── __init__.py │ ├── cluster.py │ └── sbc.py ├── core │ ├── __init__.py │ ├── distances.py │ ├── lattice.py │ ├── linkedunits.py │ ├── periodicfinder.py │ └── system.py ├── data │ ├── __init__.py │ ├── alphabet_data.py │ ├── constants.py │ ├── element_data.py │ └── symmetry_data.py ├── ext │ ├── celllist.cpp │ ├── celllist.h │ ├── ext.cpp │ ├── geometry.cpp │ └── geometry.h ├── geometry │ ├── __init__.py │ └── geometry.py ├── symmetry │ ├── __init__.py │ ├── symmetryanalyzer.py │ └── wyckoffset.py └── utils │ ├── __init__.py │ ├── exceptions.py │ ├── segfault_protect.py │ ├── surfacegenerator.py │ └── symmetry_info_generation │ ├── generate_symmetry_info.py │ ├── query_continuous_translations.py │ ├── query_conventional_transform_info.py │ ├── query_normalizers.py │ ├── query_space_group_info.py │ └── query_wyckoff_sets.py ├── pyproject.toml ├── reports └── coverage │ └── coverage-badge.svg ├── requirements.txt ├── scripts ├── doc_deploy.sh ├── doc_reference.sh ├── format.sh ├── lint.sh ├── release.sh ├── setup.sh └── test.sh ├── setup.py └── tests ├── classificationtests.py ├── clustering └── test_sbc.py ├── conftest.py ├── data ├── R6JuJXj20goPQ0vv6aAVYpNyuwGgN+P_PaYo5EiiPChgUe9B6JnTX6BcOwt.xyz ├── RDtJ5cTyLBPt4PA182VbCzoCxf5Js+P8Wnwz4dfyea6UAD0WEBadXv83wyf.xyz ├── RDtJ5cTyLBPt4PA182VbCzoCxf5Js+PEzXqLISX8Pam-HlJMxeLc86lcKgf.xyz ├── RDtJ5cTyLBPt4PA182VbCzoCxf5Js+PFw_-OtcPJ5og8XMItaAAFYhQUaY6.xyz ├── RJv-r5Vwf6ypWElBSq_hTCOaxEU89+PDLFIM7Xvy9JaEqwS72kDtDr_Szhp.xyz ├── RJv-r5Vwf6ypWElBSq_hTCOaxEU89+PKPif9Fqbl30oVX-710UwCHGMd83y.xyz ├── RJv-r5Vwf6ypWElBSq_hTCOaxEU89+PgZTqAjcn_4hHS3fozZkAI0Jxtdas.xyz ├── ROHGEranIWm-gnS6jhQaLZRORWDKx+Pbco91p05ftuJQ38__Y0_TDg9tNIy.xyz ├── ROHGEranIWm-gnS6jhQaLZRORWDKx+Pbsl6Hlb_C1aXadFiJ58UCUek5a8x.xyz ├── ROJiORHNwL4q0WTvNUy0mW5s2Buuq+PSX9X4dQR2r1cjQ9kBtuC-wI6MO8B.xyz ├── Rhn-EWQQN8Z-lbmZwoWPyrGiM9Isx+PbYDgCBSwbq3nxONqWaq03HYUn8_V.xyz ├── RloVGNkMhI83gtwzF5DmftT6fM31d+P9ZCykgTQkZ7aIFmr-vje9gq8p6fc.xyz ├── RloVGNkMhI83gtwzF5DmftT6fM31d+PKxGoPkNrvdpZrlLS-V14MszJ-57L.xyz ├── RmlNIfj-YIQ14UBYjtAHtXcAEXZif+PIkKcrxeOf997qnQ_hWRXLdMsmpAf.xyz ├── RmlNIfj-YIQ14UBYjtAHtXcAEXZif+PYu3zrqdlNhhs9tII2lnvJ3Gj7tai.xyz ├── RmlNIfj-YIQ14UBYjtAHtXcAEXZif+Pkl2CiGU9KP0uluTY8M3PeGEb4OS_.xyz ├── RmlNIfj-YIQ14UBYjtAHtXcAEXZif+PmZsb-Uf3AIGQyTBZDg4ZgxXaq5UB.xyz ├── Rq0LUBXa6rZ-mddbQUZJXOIVAIg-J+Pm73-Kx5CWtuIHzLTr5R-Nir2el0i.xyz ├── RscdVKibS4pD0O_Yo1CSwkznfiL1c+PCvflj-qTkfRcUaCISfn8fm-2oaVW.xyz ├── RzQh5XijWuXsNZiRSxeOlPFUY_9Gl+PY5NRLMRYyQXsYmBN9hMcT-FftquP.xyz ├── cu55.xyz ├── system-AlOPbSeSiC_PbSe.xyz ├── system-BNPbSeBN.xyz └── system-CVC.extxyz ├── performance ├── benchmark_cpu.sh ├── benchmark_memory.sh ├── performance.py └── plot.sh ├── symmetry └── test_symmetry.py ├── symmetrytests.py ├── test_geometry.py └── testrunner.py /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a 2 | # variety of Python versions For more information see: 3 | # https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 4 | 5 | name: test 6 | 7 | on: 8 | push: 9 | branches: [ "main"] 10 | pull_request: 11 | branches: [ "main"] 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | cd ${GITHUB_WORKSPACE}/scripts && ./setup.sh 30 | - name: format 31 | if: always() 32 | run: | 33 | cd ${GITHUB_WORKSPACE}/scripts && ./format.sh 34 | - name: lint 35 | if: always() 36 | run: | 37 | cd ${GITHUB_WORKSPACE}/scripts && ./lint.sh 38 | - name: Run unit tests 39 | run: | 40 | cd ${GITHUB_WORKSPACE}/scripts && ./test.sh -------------------------------------------------------------------------------- /.github/workflows/wheels.yml: -------------------------------------------------------------------------------- 1 | name: wheels 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | build_wheels: 7 | name: Build wheels on ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, windows-latest, macos-13, macos-14] 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Build wheels 17 | uses: pypa/cibuildwheel@v2.20.0 18 | env: 19 | # All PyPy builds are currently failing, CPython 3.6 and 3.13 are also failing 20 | CIBW_SKIP: cp36-* cp313* pp* 21 | 22 | - uses: actions/upload-artifact@v4 23 | with: 24 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 25 | path: ./wheelhouse/*.whl -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://github.com/github/gitignore/blob/main/Global/macOS.gitignore 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # https://github.com/github/gitignore/blob/main/Python.gitignore 31 | 32 | # Byte-compiled / optimized / DLL files 33 | __pycache__/ 34 | *.py[cod] 35 | *$py.class 36 | 37 | # C extensions 38 | *.so 39 | 40 | # Distribution / packaging 41 | .Python 42 | build/ 43 | develop-eggs/ 44 | dist/ 45 | downloads/ 46 | eggs/ 47 | .eggs/ 48 | lib/ 49 | lib64/ 50 | parts/ 51 | sdist/ 52 | var/ 53 | wheels/ 54 | share/python-wheels/ 55 | *.egg-info/ 56 | .installed.cfg 57 | *.egg 58 | MANIFEST 59 | 60 | # PyInstaller 61 | # Usually these files are written by a python script from a template 62 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 63 | *.manifest 64 | *.spec 65 | 66 | # Installer logs 67 | pip-log.txt 68 | pip-delete-this-directory.txt 69 | 70 | # Unit test / coverage reports 71 | htmlcov/ 72 | .tox/ 73 | .nox/ 74 | .coverage 75 | .coverage.* 76 | .cache 77 | nosetests.xml 78 | coverage.xml 79 | *.cover 80 | *.py,cover 81 | .hypothesis/ 82 | .pytest_cache/ 83 | cover/ 84 | 85 | # Translations 86 | *.mo 87 | *.pot 88 | 89 | # Django stuff: 90 | *.log 91 | local_settings.py 92 | db.sqlite3 93 | db.sqlite3-journal 94 | 95 | # Flask stuff: 96 | instance/ 97 | .webassets-cache 98 | 99 | # Scrapy stuff: 100 | .scrapy 101 | 102 | # Sphinx documentation 103 | docs/_build/ 104 | 105 | # PyBuilder 106 | .pybuilder/ 107 | target/ 108 | 109 | # Jupyter Notebook 110 | .ipynb_checkpoints 111 | 112 | # IPython 113 | profile_default/ 114 | ipython_config.py 115 | 116 | # pyenv 117 | # For a library or package, you might want to ignore these files since the code is 118 | # intended to run in multiple environments; otherwise, check them in: 119 | # .python-version 120 | 121 | # pipenv 122 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 123 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 124 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 125 | # install all needed dependencies. 126 | #Pipfile.lock 127 | 128 | # poetry 129 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 130 | # This is especially recommended for binary packages to ensure reproducibility, and is more 131 | # commonly ignored for libraries. 132 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 133 | #poetry.lock 134 | 135 | # pdm 136 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 137 | #pdm.lock 138 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 139 | # in version control. 140 | # https://pdm.fming.dev/#use-with-ide 141 | .pdm.toml 142 | 143 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 144 | __pypackages__/ 145 | 146 | # Celery stuff 147 | celerybeat-schedule 148 | celerybeat.pid 149 | 150 | # SageMath parsed files 151 | *.sage.py 152 | 153 | # Environments 154 | .env 155 | .venv 156 | env/ 157 | venv/ 158 | ENV/ 159 | env.bak/ 160 | venv.bak/ 161 | 162 | # Spyder project settings 163 | .spyderproject 164 | .spyproject 165 | 166 | # Rope project settings 167 | .ropeproject 168 | 169 | # mkdocs documentation 170 | /site 171 | 172 | # mypy 173 | .mypy_cache/ 174 | .dmypy.json 175 | dmypy.json 176 | 177 | # Pyre type checker 178 | .pyre/ 179 | 180 | # pytype static type analyzer 181 | .pytype/ 182 | 183 | # Cython debug symbols 184 | cython_debug/ 185 | 186 | # PyCharm 187 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 188 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 189 | # and can be added to the global gitignore or merged into this file. For a more nuclear 190 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 191 | #.idea/ 192 | 193 | # VSCode 194 | .vscode/* 195 | 196 | # Coverage report 197 | coverage 198 | tests/.coverage 199 | 200 | # performance results 201 | tests/performance/results 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include requirements.txt 3 | recursive-include matid/ext *.h 4 | recursive-exclude tests * 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![Build status](https://github.com/nomad-coe/matid/actions/workflows/test.yml/badge.svg) 4 | ![Coverage Status](./reports/coverage/coverage-badge.svg) 5 | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) 6 | 7 | MatID is a Python package for identifying and analyzing atomistic systems based 8 | on their structure. 9 | 10 | # Documentation 11 | For more details and tutorials, visit the documentation at: 12 | [https://nomad-coe.github.io/matid/](https://nomad-coe.github.io/matid/) 13 | 14 | You can find even more details in the following open-access articles: 15 | 16 | - [Automated identification of bulk structures, two-dimensional materials, and interfaces using symmetry-based clustering]() 17 | - [Materials structure genealogy and high-throughput topological classification of surfaces and 2D materials]() 18 | 19 | ## Example: Surface detection and analysis 20 | 21 | ```python 22 | import ase.io 23 | from ase.visualize import view 24 | 25 | from matid.clustering import SBC 26 | from matid.symmetry import SymmetryAnalyzer 27 | 28 | # Load structure from a file 29 | system = ase.io.read('data/system.xyz') 30 | 31 | # Find interesting substructures using Symmetry-based Clustering (SBC) 32 | sbc = SBC() 33 | clusters = sbc.get_clusters(system) 34 | 35 | # Analyze each found cluster printing out the indices of the atoms belonging to 36 | # this cluster and visualizing the conventional cell from which the cluster was 37 | # built from. 38 | for cluster in clusters: 39 | 40 | # Get the indices of the atoms belonging to this cluster 41 | indices = cluster.indices 42 | print(indices) 43 | 44 | # Get the dimensionality of the cluster 45 | dimensionality = cluster.get_dimensionality() 46 | print(dimensionality) 47 | 48 | # Get the cell from which the cluster is constructed from. The periodicity 49 | # of this cell indicates in which directions the unit cell has been found to 50 | # be repeated in (at least once, possibly infinitely). 51 | cell = cluster.get_cell() 52 | n_repeated_directions = sum(cell.get_pbc()) 53 | print(n_repeated_directions) 54 | 55 | # Analyze some symmetry properties of the underlying cell to better identify 56 | # the material from which the cluster has been constructed from. 57 | analyzer = SymmetryAnalyzer(cell, symmetry_tol=0.5) 58 | conv_sys = analyzer.get_conventional_system() 59 | view(conv_sys) 60 | ``` 61 | 62 | # Installation 63 | 64 | ## pip 65 | ```sh 66 | pip install matid 67 | ``` 68 | 69 | ## From source 70 | ```sh 71 | git clone https://github.com/nomad-coe/matid.git 72 | cd matid 73 | pip install . 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/assets/archiving.py: -------------------------------------------------------------------------------- 1 | from ase.io import read 2 | from nomad.datamodel import EntryArchive 3 | from nomad.datamodel.metainfo.simulation.run import Run, Program 4 | from nomad.datamodel.metainfo.simulation.system import System, Atoms as NOMADAtoms 5 | from nomad.datamodel.metainfo.simulation.method import ( 6 | Method, 7 | BasisSetContainer, 8 | BasisSet, 9 | Electronic, 10 | DFT, 11 | XCFunctional, 12 | Functional, 13 | Electronic, 14 | ) 15 | 16 | from nomad.datamodel.metainfo.simulation.calculation import ( 17 | Calculation, 18 | Energy, 19 | EnergyEntry, 20 | BandEnergies, 21 | ) 22 | from nomad.datamodel.metainfo.simulation.workflow import GeometryOptimization 23 | from nomad.normalizing import normalizers 24 | 25 | 26 | atoms = read("system.xyz") 27 | template = EntryArchive() 28 | run = template.m_create(Run) 29 | run.program = Program(name="VASP", version="4.6.35") 30 | method = run.m_create(Method) 31 | method.electrons_representation = [ 32 | BasisSetContainer( 33 | type="plane waves", 34 | scope=["wavefunction"], 35 | basis_set=[ 36 | BasisSet( 37 | type="plane waves", 38 | scope=["valence"], 39 | ) 40 | ], 41 | ) 42 | ] 43 | method.electronic = Electronic(method="DFT") 44 | xc_functional = XCFunctional(exchange=[Functional(name="GGA_X_PBE")]) 45 | method.dft = DFT(xc_functional=xc_functional) 46 | system = run.m_create(System) 47 | system.atoms = NOMADAtoms( 48 | lattice_vectors=[ 49 | [5.76372622e-10, 0.0, 0.0], 50 | [0.0, 5.76372622e-10, 0.0], 51 | [0.0, 0.0, 4.0755698899999997e-10], 52 | ], 53 | positions=[ 54 | [2.88186311e-10, 0.0, 2.0377849449999999e-10], 55 | [0.0, 2.88186311e-10, 2.0377849449999999e-10], 56 | [0.0, 0.0, 0.0], 57 | [2.88186311e-10, 2.88186311e-10, 0.0], 58 | ], 59 | labels=["Br", "K", "Si", "Si"], 60 | periodic=[True, True, True], 61 | ) 62 | scc = run.m_create(Calculation) 63 | scc.system_ref = system 64 | scc.method_ref = method 65 | scc.energy = Energy( 66 | free=EnergyEntry(value=-1.5936767191492225e-18), 67 | total=EnergyEntry(value=-1.5935696296699573e-18), 68 | total_t0=EnergyEntry(value=-3.2126683561907e-22), 69 | ) 70 | template.workflow2 = GeometryOptimization() 71 | 72 | template.run[0].calculation[0].system_ref = None 73 | template.run[0].calculation[0].eigenvalues.append(BandEnergies()) 74 | template.run[0].calculation[0].eigenvalues[0].kpoints = [[0, 0, 0]] 75 | template.run[0].system = None 76 | 77 | system = System() 78 | system.atoms = NOMADAtoms( 79 | positions=atoms.get_positions() * 1e-10, 80 | labels=atoms.get_chemical_symbols(), 81 | atomic_numbers=atoms.get_atomic_numbers(), 82 | lattice_vectors=atoms.get_cell() * 1e-10, 83 | periodic=atoms.get_pbc(), 84 | ) 85 | 86 | template.run[0].m_add_sub_section(Run.system, system) 87 | for normalizer_class in normalizers: 88 | normalizer = normalizer_class(template) 89 | normalizer.normalize() 90 | 91 | archive = template.m_to_json() 92 | with open("system.archive.json", "w") as fout: 93 | fout.write(archive) 94 | -------------------------------------------------------------------------------- /docs/assets/clustering.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/assets/clustering.xcf -------------------------------------------------------------------------------- /docs/assets/conventional_cell_comparison.py: -------------------------------------------------------------------------------- 1 | from matid import SymmetryAnalyzer 2 | from ase.spacegroup import crystal 3 | from ase.io import write 4 | from ase import Atoms 5 | from ase.visualize import view 6 | import spglib 7 | 8 | def get_spglib_conventional(system): 9 | cell = ( 10 | system.get_cell(), 11 | system.get_scaled_positions(), 12 | system.get_atomic_numbers(), 13 | ) 14 | dataset = spglib.get_symmetry_dataset(cell) 15 | 16 | return Atoms( 17 | symbols=dataset.std_types, 18 | scaled_positions=dataset.std_positions, 19 | cell=dataset.std_lattice, 20 | ) 21 | 22 | # Lets define two variants of NaCl in rocksalt structure 23 | a = 5.64 24 | nacl1 = crystal( 25 | ['Na', 'Cl'], 26 | [(0, 0, 0), (0.5, 0.5, 0.5)], 27 | spacegroup=225, 28 | cellpar=[a, a, a, 90, 90, 90] 29 | ) 30 | write(f"original1.eps", nacl1, rotation="60y,20x,0y", show_unit_cell=2, scale=50, maxwidth=5000) 31 | a = 5.64 32 | nacl2 = crystal( 33 | ['Cl', 'Na'], 34 | [(0, 0, 0), (0.5, 0.5, 0.5)], 35 | spacegroup=225, 36 | cellpar=[a, a, a, 90, 90, 90] 37 | ) 38 | write(f"original2.eps", nacl2, rotation="60y,20x,0y", show_unit_cell=2, scale=50, maxwidth=5000) 39 | 40 | # Lets retrieve the conventional cell using spglib 41 | conventional1_spglib = get_spglib_conventional(nacl1) 42 | conventional2_spglib = get_spglib_conventional(nacl2) 43 | write(f"spglib1.eps", conventional1_spglib, rotation="60y,20x,0y", show_unit_cell=2, scale=50, maxwidth=5000) 44 | write(f"spglib2.eps", conventional2_spglib, rotation="60y,20x,0y", show_unit_cell=2, scale=50, maxwidth=5000) 45 | 46 | # Let's retrieve the conventional cell as ASE Atoms using MatID 47 | analyzer = SymmetryAnalyzer(nacl1) 48 | conventional1_matid = analyzer.get_conventional_system() 49 | print(analyzer.get_wyckoff_sets_conventional()) 50 | analyzer = SymmetryAnalyzer(nacl2) 51 | print(analyzer.get_wyckoff_sets_conventional()) 52 | conventional2_matid = analyzer.get_conventional_system() 53 | write(f"matid1.eps", conventional1_matid, rotation="60y,20x,0y", show_unit_cell=2, scale=50, maxwidth=5000) 54 | write(f"matid2.eps", conventional2_matid, rotation="60y,20x,0y", show_unit_cell=2, scale=50, maxwidth=5000) 55 | 56 | -------------------------------------------------------------------------------- /docs/assets/stacked.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ase import Atoms 3 | from ase.io import write 4 | from ase.build import fcc111, mx2 5 | from ase.visualize import view 6 | 7 | from matid import SBC, SymmetryAnalyzer 8 | 9 | # Copper surface 10 | surface = fcc111("Cu", size=(6, 6, 3), vacuum=0) 11 | surface.rotate(v=[0, 0, 1], a=-60, rotate_cell=True) 12 | surface.wrap() 13 | 14 | # MoS2 15 | mos2 = mx2(formula="MoS2", kind="2H", a=3.18, thickness=3.19, size=(5, 5, 1)) 16 | 17 | # Graphene sheet 18 | graphene = Atoms( 19 | symbols=["C", "C"], 20 | cell=np.array( 21 | ( 22 | [2.4595121467478055, 0.0, 0.0], 23 | [-1.2297560733739028, 2.13, 0.0], 24 | [0.0, 0.0, 20.0], 25 | ) 26 | ), 27 | scaled_positions=np.array(([1 / 3, 2 / 3, 0.5], [2 / 3, 1 / 3, 0.5])), 28 | pbc=[True, True, False], 29 | ) * [6, 6, 1] 30 | 31 | # Center structures in the horizontal plane 32 | surface_com = surface.get_center_of_mass() 33 | cell_center = 1 / 2 * surface.get_cell().sum(axis=0) 34 | mos2_com = mos2.get_center_of_mass() 35 | mos2.translate(cell_center - mos2_com) 36 | graphene_com = graphene.get_center_of_mass() 37 | graphene.translate(cell_center - graphene_com) 38 | 39 | # Stack structures vertically 40 | surface_top = np.max(surface.get_positions()[:, 2], axis=0) 41 | mos2_bottom = np.min(mos2.get_positions()[:, 2], axis=0) 42 | distance_surface_mos2 = 2 43 | mos2.translate([0, 0, surface_top - mos2_bottom + distance_surface_mos2]) 44 | mos2_top = np.max(mos2.get_positions()[:, 2], axis=0) 45 | graphene_bottom = np.max(graphene.get_positions()[:, 2], axis=0) 46 | distance_mos2_graphene = 2 47 | graphene.translate([0, 0, mos2_top - graphene_bottom + distance_mos2_graphene]) 48 | system = surface + mos2 + graphene 49 | 50 | # Fit in cell and set periodicity 51 | scaled_positions = system.get_scaled_positions() 52 | system.set_cell(3 * system.get_cell()) 53 | system.center() 54 | system.set_pbc(False) 55 | write("original.eps", system, rotation="30z,-60x,0y", show_unit_cell=0, maxwidth=5000) 56 | 57 | # Peform clustering and view results 58 | sbc = SBC() 59 | clusters = sbc.get_clusters(system) 60 | rotations = [ 61 | "240z,-70x,0y", 62 | "-30z,-70x,0y", 63 | "240z,-70x,0y", 64 | ] 65 | 66 | for i, cluster in enumerate(clusters): 67 | write(f"cluster{i}.eps", system[cluster.indices], rotation="30z,-60x,0y", show_unit_cell=0, maxwidth=3000) 68 | unit_cell = cluster.get_cell() 69 | analyzer = SymmetryAnalyzer(unit_cell) 70 | conv_system = analyzer.get_conventional_system() 71 | cell = conv_system.get_cell() 72 | cell[~conv_system.get_pbc(), :] = 0 73 | conv_system.set_cell(cell) 74 | write(f"cluster{i}_unit_cell.eps", conv_system, rotation=rotations[i], show_unit_cell=2, maxwidth=3000) 75 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # About 6 | 7 | ## Citing 8 | 9 | When using versions < 2.0.0, please consider citing the article: 10 | 11 | ```bibtex 12 | @article{matid, 13 | author = {Himanen, Lauri and Rinke, Patrick and Foster, Adam Stuart}, 14 | title = {{Materials structure genealogy and high-throughput topological classification of surfaces and 2D materials}}, 15 | journal = {npj Computational Materials}, 16 | volume = {4}, 17 | number = {52}, 18 | year = {2018} 19 | publisher = {Springer US}, 20 | doi = {10.1038/s41524-018-0107-6}, 21 | url = {https://www.nature.com/articles/s41524-018-0107-6}, 22 | } 23 | ``` 24 | 25 | When using versions >= 2.0.0, please additionally cite: 26 | 27 | ```bibtex 28 | @article{matidv2, 29 | author = {Denell, Thea and Himanen, Lauri and Scheidgen, Markus and Draxl, Claudia}, 30 | title = {{Automated identification of bulk structures, two-dimensional materials, and interfaces using symmetry-based clustering}}, 31 | journal = {npj Computational Materials}, 32 | volume = {11}, 33 | number = {25}, 34 | year = {2025} 35 | publisher = {Springer US}, 36 | doi = {10.1038/s41524-024-01498-x}, 37 | url = {https://www.nature.com/articles/s41524-024-01498-x}, 38 | } 39 | ``` 40 | 41 | ## License 42 | The matid source code is released under the Apache-2.0 license. 43 | 44 | ## Funding 45 | This project has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 676580 with The Novel Materials Discovery (NOMAD) Laboratory, a European Center of Excellence and from the Jenny and Antti Wihuri Foundation. This work was furthermore supported by the Academy of Finland through its Centres of Excellence Programme 2015-2017 under project number 284621, as well as its Key Project Funding scheme under project number 305632. Additionally this work has been funded by the German Research Foundation (DFG) through the project FAIRmat (DFG project no. 460197019, https://www.fairmat-nfdi.eu/) within the German National Research Data Infrastructure (NFDI, https://www.nfdi.de/) 46 | 47 | ## Contact 48 | If you encounter issues with the software please submit them directly as GitHub issues. For general discussion and possible improvements/additions use the GitHub discussion forum. This ensures that all issues/discussion are publicly available and archived. -------------------------------------------------------------------------------- /docs/docs/get-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Get started", 3 | "position": 1 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/get-started/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Installation 6 | 7 | ## pip 8 | 9 | The package is available through pip: 10 | 11 | ```sh 12 | pip install matid 13 | ``` 14 | 15 | ## From source 16 | 17 | To install the latest development version from source, clone the source code 18 | from github and install with pip from local file: 19 | 20 | ```sh 21 | git clone https://github.com/nomad-coe/matid.git 22 | cd matid 23 | pip install . 24 | ``` -------------------------------------------------------------------------------- /docs/docs/get-started/quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | import CodeBlock from '@theme/CodeBlock' 6 | import ReadmeSource from '!!raw-loader!@site/static/snippets/readme.py'; 7 | 8 | # Quick Start 9 | 10 | MatID is a Python package for identifying and analyzing atomistic systems based 11 | on their structure. MatID is designed to help researchers in the automated 12 | analysis and labeling of atomistic data. 13 | 14 | The package comes with different tools that can be connected together to form a 15 | workflow for analyzing structures. The **Learn** section contains documentation 16 | that helps you get to know the basic functionality of the package, most 17 | importantly: 18 | 19 | - [Symmetry-based Clustering](/docs/learn/symmetry-based-clustering): Learn how to identify and report repeating structures in 20 | atomistic system. Works both with finite and periodic systems. 21 | - [Symmetry Analysis](/docs/learn/symmetry-analysis): Routines for analyzing the symmetry of structures. Can be used to return a highly unique conventional cell, detailed Wyckoff position information etc. 22 | - [Dimensionality Analysis](/docs/learn/dimensionality): Used to determine the intended dimensionality of a system taking periodic boundary conditions into account. 23 | 24 | As a user, you are free to choose one or many of these tools for your analysis 25 | purposes. A small example that combines several of the provided tools for 26 | analysing an existing structure file is shown below 27 | 28 | {ReadmeSource} 29 | 30 | To learn more about the exact call signature of classes and functions, visit the 31 | documents in the **Reference** section. It contains autogenerated documentation 32 | for the key classes and modules. 33 | -------------------------------------------------------------------------------- /docs/docs/learn/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Learn", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/learn/dimensionality.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | import Image from '@theme/IdealImage' 6 | import tsa from '@site/static/img/tsa.jpg' 7 | import CodeBlock from '@theme/CodeBlock' 8 | import DimensionalitySource from '!!raw-loader!@site/static/snippets/dimensionality.py'; 9 | 10 | # Dimensionality Analysis 11 | MatID can be used to find out the dimensionality of an atomistic geometry. It 12 | should be noted that MatID does not consider the shape or size of a structure 13 | when assigning dimensionality, but instead looks at __the number of dimensions 14 | in which the structure is connected to itself through periodic boundary 15 | conditions__. This definition is often relevant for atomistic simulations with 16 | periodic boundary conditions, but may not be useful in other contexts where e.g. 17 | the shape is the deciding factor. 18 | 19 | 20 | 21 | To determine the dimensionality of a system, MatID uses a modified version of the 22 | topological scaling algorithm (TSA)[^1]. The algorithm is based on analyzing 23 | the size scaling of atomic clusters when going from the original system to a 24 | bigger supercell of the same system. With TSA, the dimensionality $D$ is 25 | given by 26 | 27 | $$ 28 | D=\begin{cases} 29 | n_\text{pbc}-\log_n (N_{n}) \text{, when}~n_\text{pbc} \neq 0 \\ 30 | 0\text{, when}~n_\text{pbc} = 0 31 | \end{cases} 32 | $$ 33 | 34 | where $N_n$ is the number of clusters in a supercell that is repeated 35 | $n$ times in each periodic direction and $n_\mathrm{pbc}$ is the 36 | number of periodic dimensions. For the clustering we use the Density-Based 37 | Spatial Clustering of Applications with Noise (DBSCAN)[^2] data clustering algorithm. 38 | The advantage of this algorithm is that it does not require an initial guess 39 | for the number of clusters and it can find arbitrarily shaped clusters. The 40 | clustering requires that we define a metric for the distance between atoms. We 41 | use the following metric: 42 | 43 | $$ 44 | d_{ij} = \lvert \vec{R}_i - \vec{R}_j \rvert^{\text{MIC}} - r_i - r_j 45 | $$ 46 | 47 | where $\vec{R}_i$ and $\vec{R}_j$ are the cartesian positions of atom $i$ and 48 | $j$, respectively, and $r_i$ and $r_j$ are their radii. The radii definition can 49 | be changed and defaults to covalent radii[^3]. It is important to notice that in 50 | this metric the distances always follow the minimum image convention (MIC), i.e. 51 | the distance is calculated between two closest periodic neighbours. By using the 52 | distance to the closest periodic neighbour we obtain the correct clusters 53 | regardless of what shape of cell is used in the original simulation. 54 | 55 | The clustering uses two parameters: the minimum cluster size 56 | $n_\mathrm{min}$ and the neighbourhood radius $\epsilon$. We set 57 | $n_\mathrm{min}$ to 1 to allow clusters consisting of even single atoms 58 | and $\epsilon$ defaults to 3.5 Å. At present, a system, in which there is 59 | more than one cluster in the original non-repeated system ($N_1 \gt 1$), 60 | is classified as unknown. Such a case corresponds to systems with multiple 61 | components that are spatially separated, such as a molecule far above a 62 | surface, low density gases, widely spaced clusters in vacuum, etc. 63 | 64 | The following code illustrates how dimensionality detection can be performed for 65 | any atomistic structure with MatID. 66 | 67 | {DimensionalitySource} 68 | 69 | If you wish to get the dimensionality of a [`Cluster`](/docs/reference/cluster) object, then you can 70 | directly use the [`Cluster.get_dimensionality()`](/docs/reference/cluster#get_dimensionality) method, which is essentially a 71 | shortcut to the [`matid.geometry.get_dimensionality()`](/docs/reference/geometry#get_dimensionality) function. 72 | 73 | [^1]: Ashton, M., Paul, J., Sinnott, S. B. & Hennig, R. G. Topology-scaling identification of layered solids and stable exfoliated 2d materials. Phys. Rev. Lett. 118, 106101 (2017) 74 | [^2]: Ester, M., Kriegel, H.-P., Sander, J. & Xu, X. A density-based algorithm for discovering clusters in large spatial databases with noise. KDD’96 Proceedings of the Second International Conference on Knowledge Discovery and Data Mining 226–231 (1996). 75 | [^3]: Cordero, B. et al. Covalent radii revisited. Dalton Trans. 2832–2838 (2008) -------------------------------------------------------------------------------- /docs/docs/learn/symmetry-analysis.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | import Figure from '@site/src/components/Figure' 6 | import CodeBlock from '@theme/CodeBlock' 7 | import ConventionalCellSource from '!!raw-loader!@site/static/snippets/conventional_cell.py'; 8 | import MaterialIDSource from '!!raw-loader!@site/static/snippets/material_id.py'; 9 | import WyckoffSetsSource from '!!raw-loader!@site/static/snippets/wyckoff_sets.py'; 10 | 11 | # Symmetry Analysis 12 | 13 | The [`SymmetryAnalyzer`](/docs/reference/symmetryanalyzer) class in MatID contains several symmetry routines for 14 | analyzing 3D structures. The basic features are based on the excellent `spglib` 15 | library, but with an extended feature set that makes MatID unique. The following 16 | subsections showcase some of its most interesting features and you can learn 17 | more in the API reference. 18 | 19 | ## Conventional Cell 20 | 21 | For each space group there is a conventional cell that has been standardized by 22 | in [Volume A of International Tables for Crystallography 23 | (ITA)](https://it.iucr.org/A/). This conventional cell can be retrieved by using 24 | the 25 | [`get_conventional_system()`](/docs/reference/symmetryanalyzer#get_conventional_system) 26 | method, for example: 27 | 28 | {ConventionalCellSource} 29 | 30 | You could also use `spglib` to retrieve the conventional cell, but MatID adds an 31 | additional step of normalization to make the structure even more standardized. 32 | Although the shape and symmetries of the cell are already well-defined for a 33 | conventional cell reported by `spglib`, the exact location of atoms in the cell 34 | can vary. The same structure can have multiple representations, which all look 35 | different. If you are only interested in the symmetry properties of the system, 36 | this may not be relevant. But if instead you wish to generate a more unique 37 | structure for the conventional cell for visualization or identification 38 | purposes, then additional processing is required. 39 | 40 | Below is an example of the same rock salt structure (space group 225) in two 41 | different configurations, and the result of spglib conventional cell and the 42 | MatID conventional cell. 43 | 44 |
45 | 46 | The only difference in the two original structures is the way the Wyckoff 47 | positions have been filled: structure A has has Na in Wyckoff position "a" and 48 | Cl in Wyckoff position "b" while in the second one these positions are reversed. 49 | The conventional cell reported by MatID makes an additional step of searching 50 | for the most unique representation based on chirality-preserving Euclidean 51 | Normalizers. These are reported e.g. by the [Bilbao Crystal 52 | database](https://www.cryst.ehu.es/). Each normalizer is essentially a transform 53 | that can be applied to the structure to gain a new form of the conventional cell 54 | without modifying the underlying material. MatID iterates over these different 55 | representations and selects one based on the population of different Wyckoff 56 | sites which differs among these representations. 57 | 58 | It should be noted that certain Wyckoff sites in certain space groups are 59 | parametrized. MatID does not perform any selection between these parameters, so 60 | in these cases the exact visual representation will still differ. By calling 61 | [`get_has_free_wyckoff_parameters()`](/docs/reference/symmetryanalyzer#get_has_free_wyckoff_parameters) 62 | you can query whether the structure contains parametrized wyckoff positions. 63 | 64 | ## Wyckoff sets 65 | 66 | You can use 67 | [`get_wyckoff_sets_conventional()`](/docs/reference/symmetryanalyzer#get_wyckoff_sets_conventional) 68 | to query a detailed breakdown of the occupied Wyckoff sets in a material. The 69 | following example shows an example of how you could query the Wyckoff sets in a 70 | fairly complicated clathrate material: 71 | 72 | {WyckoffSetsSource} 73 | 74 | The output of this script is: 75 | 76 | ``` 77 | Set 0 78 | Letter: c 79 | Element: Si 80 | Indices: [40, 41, 42, 43, 44, 45] 81 | Multiplicity: 6 82 | Repr.: ['1/4', '0', '1/2'] 83 | x: None 84 | y: None 85 | z: None 86 | Set 1 87 | Letter: i 88 | Element: Si 89 | Indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] 90 | Multiplicity: 16 91 | Repr.: ['x', 'x', 'x'] 92 | x: 0.6837 93 | y: None 94 | z: None 95 | Set 2 96 | Letter: k 97 | Element: Si 98 | Indices: [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39] 99 | Multiplicity: 24 100 | Repr.: ['0', 'y', 'z'] 101 | x: None 102 | y: 0.6922999999999999 103 | z: 0.8827999999999999 104 | 105 | ``` 106 | 107 | ## Material ID 108 | 109 | You can use 110 | [`get_material_id()`](/docs/reference/symmetryanalyzer#get_material_id) to 111 | report a 28-digit character sequence for a given structure. This is essentially 112 | a hash-digest which is seeded using information about the space group, 113 | dimensionality and the Wyckoff sites occupied in the conventional cell. Due to 114 | the way MatID generates the conventional cell, this identifier is fairly unique 115 | and can be used in many cases to identify structures 3D and 2D materials with 116 | "enough" symmetry. 117 | 118 | The material id can act as a convenient handle for finding materials with very 119 | specific structure. As a simple example one could imagine trying to find 120 | [skutterudite](https://en.wikipedia.org/wiki/Skutterudite). The filters required for this would include: 121 | 122 | - Space group number 123 | - Wyckoff position occupation (assuming a normalized one is available) 124 | 125 | Often this information is not directly reported and if it is, it can be a 126 | daunting task for the user to formulate the queries precisely. This is where the 127 | material id can offer a shortcut: you can use MatID to retrieve it for a 128 | structure and use it in queries, provided that a database has it stored. For 129 | example, if you have an atomic structure, you could use the following script to 130 | retrieve the material id for it: 131 | 132 | {MaterialIDSource} 133 | 134 | This material id can then be used e.g. in the [NOMAD](https://nomad-lab.eu/) platform to query for structures 135 | containing skutterudite (`material id = zI9HC39lOUK_w2Clv8XUHb43tYVV`). 136 | 137 | The material id is not guaranteed to be unique, but can be highly useful in 138 | practice. In some sense it has a similar value proposal to SMILES for molecules. 139 | There are some of the cases in which it will fail: 140 | 141 | - Parametrized Wyckoff sites 142 | - If isotropic scaling of cell should affect the material identification 143 | - If variable angles for the conventional cell are a problem 144 | -------------------------------------------------------------------------------- /docs/docs/learn/symmetry-based-clustering.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | import Image from '@theme/IdealImage' 6 | import sbc from '@site/static/img/sbc.png' 7 | 8 | # Symmetry-based Clustering (SBC) 9 | 10 | There are several tools for building atomistic systems from components, but the 11 | reverse process of identifying these components from an existing system is much 12 | harder. MatID utilizes a custom clustering algorithm, called Symmetry-based 13 | Clustering (SBC), which can cluster atoms based on local translational symmetry. 14 | Essentially this means that atoms which are built from the same underlying unit 15 | cell, will get clustered together. 16 | 17 | 18 | 19 | Unlike clustering methods that work solely on the basis of atomic distances or 20 | chemical identities, SBC can produce meaningful clustering results even for 21 | structures like grain boundaries and defected structures, where other tools 22 | would struggle. SBC works for both finite and periodic structures, and can deal 23 | with noise and curvature in the structures. SBC currently returns clusters where 24 | the underlying unit cell is repeated either in two or three distinct directions 25 | (one-dimensional clusters, such as polymers are currently not handled). 26 | 27 | Clustering is performed using the [`SBC`](/docs/reference/sbc) class. The basic syntax is relatively 28 | simple: you have to initialize the SBC class with parameters that are suitable 29 | for your use case (sensible defaults are provided), and then call the 30 | [`get_clusters`](/docs/reference/sbc#get_clusters)-method. The following demonstrates this on a existing structure 31 | file: 32 | 33 | import CodeBlock from '@theme/CodeBlock' 34 | import SBCSource from '!!raw-loader!@site/static/snippets/sbc.py'; 35 | 36 | {`${SBCSource.split('\n').slice(0, 8).join('\n')}`} 37 | 38 | The return value is a list of the found [`Cluster`](/docs/reference/cluster) instances. You can perform 39 | further analysis on these found clusters by using methods and attributes of this 40 | class. E.g. to print out the indices of atoms belonging to the cluster and to 41 | visualize the units cells of the found clusters, we could do the following: 42 | 43 | {`${SBCSource.split('\n').slice(9, 12).join('\n')}`} -------------------------------------------------------------------------------- /docs/docs/reference/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Reference", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/reference/cluster.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | sidebar_label: Cluster Class 4 | --- 5 | # Cluster Class 6 | 7 | Contains information about a cluster, i.e. a part of some bigger original 8 | atomistic system. 9 | 10 | This class is a simple data container where every attribute should be set 11 | only once, but does not have to be set in one go and can be insted built 12 | gradually. 13 | 14 | ## \_\_init\_\_ 15 | 16 | ```python 17 | def __init__(indices=None, 18 | species=None, 19 | region=None, 20 | dimensionality=None, 21 | cell=None, 22 | system=None, 23 | distances=None, 24 | radii=None, 25 | bond_threshold=None) 26 | ``` 27 | 28 | **Arguments**: 29 | 30 | - `indices(Iterable)` - Contains the indices of atoms belonging to this 31 | cluster. 32 | - `species(set)` - Contains the species of atoms belonging to this 33 | cluster. Each unique species should be include only once. 34 | - `region(Region)` - The Region instance from which this cluster was 35 | exracted from. 36 | - `dimensionality(int)` - The dimensionality of the cluster. Can be set 37 | initially here or calculated through the get_dimensionality-function. 38 | - `cell(ase.Atoms)` - The unit cell from which this cluster is 39 | constructed from. 40 | - `system(ase.Atoms)` - Reference to the original system which this 41 | cluster is a part of. 42 | - `distances(Distances)` - Contains cached distance information about 43 | this cluster. 44 | - `radii(ndarray)` - Contains the radii for each atom in the cluster as 45 | floating point numbers. 46 | 47 | ## get\_cell 48 | 49 | ```python 50 | def get_cell() -> Atoms 51 | ``` 52 | 53 | Used to fetch the prototypical cell for this cluster if one exists. 54 | 55 | ## get\_atoms 56 | 57 | ```python 58 | def get_atoms() -> Atoms 59 | ``` 60 | 61 | Returns the ase.Atoms object for this cluster. 62 | 63 | ## get\_dimensionality 64 | 65 | ```python 66 | def get_dimensionality() -> int 67 | ``` 68 | 69 | Shortcut for fetching the dimensionality of the cluster using 70 | matid.geometry.get_dimensionality and the radii + bond thresholds that 71 | were used during the clustering. 72 | -------------------------------------------------------------------------------- /docs/docs/reference/sbc.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | sidebar_label: SBC Class 4 | --- 5 | # SBC Class 6 | 7 | Class for performing Symmetry-based clustering (SBC). 8 | 9 | You can apply this class for partitioning a larger material into grains, a 10 | heterostructure into it's component etc. The clustering is based on finding 11 | periodically repeating motifs, and as such it is not suitable for e.g. 12 | finding molecules. Any atoms that do not have enough periodic repetitions 13 | will be returned as isolated clusters. 14 | 15 | ## get\_clusters 16 | 17 | ```python 18 | def get_clusters(system, 19 | angle_tol=20, 20 | max_cell_size=6, 21 | pos_tol=0.7, 22 | merge_threshold=0.5, 23 | merge_radius=1, 24 | bond_threshold=0.65, 25 | overlap_threshold=-0.1, 26 | radii="covalent", 27 | seed=7) 28 | ``` 29 | 30 | Used to detect and return structurally separate clusters within the 31 | given system. 32 | 33 | **Arguments**: 34 | 35 | - `system` _ase.Atoms_ - The structure to partition. 36 | - `angle_tol` _float_ - angle_tol parameter for PeriodicFinder 37 | - `max_cell_size` _float_ - max_cell_size parameter for PeriodicFinder.get_region 38 | - `pos_tol` _float_ - pos_tol parameter for PeriodicFinder.get_region 39 | - `merge_threshold` _float_ - A threshold for merging two clusters 40 | together. Give as a fraction of shared atoms. Value of 1 would 41 | mean that clusters are never merged, value of 0 means that they 42 | are merged always when at least one atom is shared. 43 | - `merge_radius` _float_ - Radius for finding nearby atoms when deciding 44 | which cluster is closest. The atomic radii are subtracted from 45 | distances. Given in angstroms. 46 | - `bond_threshold(float)` - Used to control the connectivity threshold 47 | for defining a chemical connection between atoms. Controls e.g. 48 | what types of unit cells are accepted and how outliers are 49 | removed from clusters. 50 | - `overlap_threshold(float)` - Used to exclude non-physical cells by 51 | checking overlap of atoms. Overlap between two atoms is 52 | calculated by subtracting atomic radii from the distance between 53 | the atoms. 54 | - `radii(str|np.ndarray)` - The radii to use for atoms. Use either a preset 55 | or a custom list of atomic radii for each atom. The available presets are: 56 | 57 | - covalent: Covalent radii from DOI:10.1039/B801115J 58 | - vdw: van Der Waals radii from DOI:10.1039/C3DT50599E 59 | - vdw_covalent: preferably van Der Waals radii, covalent if vdw 60 | not defined. 61 | - `seed(int)` - The seed that is used for random number generation. 62 | 63 | 64 | **Returns**: 65 | 66 | A list of Clusters. 67 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | import remarkMath from 'remark-math'; 4 | import rehypeKatex from 'rehype-katex'; 5 | 6 | const lightCodeTheme = require('prism-react-renderer').themes.github; 7 | const darkCodeTheme = require('prism-react-renderer').themes.dracula; 8 | 9 | /** @type {import('@docusaurus/types').Config} */ 10 | const config = { 11 | title: 'MatID', 12 | tagline: 'MatID is a Python package for identifying and analyzing atomistic systems based on their structure.', 13 | favicon: 'img/favicon.ico', 14 | 15 | stylesheets: [ 16 | { 17 | href: 'https://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css', 18 | type: 'text/css', 19 | integrity: 20 | 'sha384-odtC+0UGzzFL/6PNoE8rX/SPcQDXBJ+uRepguP4QkPCm2LBxH3FA3y+fKSiJ+AmM', 21 | crossorigin: 'anonymous', 22 | }, 23 | ], 24 | 25 | plugins: [ 26 | [ 27 | '@docusaurus/plugin-ideal-image', 28 | { 29 | quality: 70, 30 | max: 1030, // max resized image's size. 31 | min: 640, // min resized image's size. if original is lower, use that size. 32 | steps: 2, // the max number of images generated between min and max (inclusive) 33 | disableInDev: false, 34 | }, 35 | ], 36 | ], 37 | 38 | // Set the production url of your site here 39 | url: 'https://nomad-coe.github.io', 40 | // Set the // pathname under which your site is served 41 | // For GitHub pages deployment, it is often '//' 42 | baseUrl: '/matid', 43 | deploymentBranch: 'gh-pages', 44 | // GitHub pages deployment config. 45 | // If you aren't using GitHub pages, you don't need these. 46 | organizationName: 'nomad-coe', // Usually your GitHub org/user name. 47 | projectName: 'matid', // Usually your repo name. 48 | 49 | onBrokenLinks: 'throw', 50 | onBrokenMarkdownLinks: 'warn', 51 | 52 | // Even if you don't use internalization, you can use this field to set useful 53 | // metadata like html lang. For example, if your site is Chinese, you may want 54 | // to replace "en" with "zh-Hans". 55 | i18n: { 56 | defaultLocale: 'en', 57 | locales: ['en'], 58 | }, 59 | 60 | presets: [ 61 | [ 62 | 'classic', 63 | /** @type {import('@docusaurus/preset-classic').Options} */ 64 | ({ 65 | docs: { 66 | //routeBasePath: '/', // Serve the docs at the site's root 67 | remarkPlugins: [remarkMath], 68 | rehypePlugins: [rehypeKatex], 69 | sidebarPath: require.resolve('./sidebars.js'), 70 | sidebarCollapsible: false, 71 | }, 72 | blog: false, 73 | theme: { 74 | customCss: require.resolve('./src/css/custom.css'), 75 | }, 76 | }), 77 | ], 78 | ], 79 | themeConfig: 80 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 81 | ({ 82 | // Replace with your project's social card 83 | image: 'img/docusaurus-social-card.jpg', 84 | navbar: { 85 | title: '', 86 | logo: { 87 | alt: 'MatID logo', 88 | src: 'img/logo.png', 89 | }, 90 | items: [ 91 | { 92 | type: 'docSidebar', 93 | sidebarId: 'docsSidebar', 94 | position: 'left', 95 | label: 'Docs', 96 | }, 97 | { 98 | href: 'https://github.com/nomad-coe/matid', 99 | label: 'GitHub', 100 | position: 'right', 101 | }, 102 | ], 103 | }, 104 | colorMode: { 105 | defaultMode: 'light', 106 | disableSwitch: true, 107 | respectPrefersColorScheme: false, 108 | }, 109 | footer: { 110 | links: [ 111 | { 112 | title: 'Docs', 113 | items: [ 114 | { 115 | label: 'Quick Start', 116 | to: '/docs/get-started/quick-start', 117 | }, 118 | { 119 | label: 'Symmetry-based Clustering', 120 | to: '/docs/learn/symmetry-based-clustering', 121 | }, 122 | { 123 | label: 'Symmetry Analysis', 124 | to: '/docs/learn/symmetry-analysis', 125 | }, 126 | ], 127 | }, 128 | { 129 | title: 'Links', 130 | items: [ 131 | { 132 | label: 'GitHub', 133 | href: 'https://github.com/nomad-coe/matid', 134 | }, 135 | { 136 | label: 'FAIRmat', 137 | href: 'https://www.fairmat-nfdi.eu/fairmat', 138 | }, 139 | { 140 | label: 'NOMAD', 141 | href: 'https://nomad-lab.eu/nomad-lab/', 142 | }, 143 | ], 144 | }, 145 | ], 146 | }, 147 | prism: { 148 | theme: lightCodeTheme, 149 | darkTheme: darkCodeTheme, 150 | }, 151 | }), 152 | }; 153 | 154 | module.exports = config; 155 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^3.4.0", 18 | "@docusaurus/plugin-ideal-image": "^3.4.0", 19 | "@docusaurus/preset-classic": "^3.4.0", 20 | "@mdx-js/react": "^3.0.0", 21 | "clsx": "^1.2.1", 22 | "hast-util-is-element": "^1.1.0", 23 | "prism-react-renderer": "^2.1.0", 24 | "raw-loader": "^4.0.2", 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0", 27 | "rehype-katex": "^7.0.0", 28 | "remark-math": "^6.0.0" 29 | }, 30 | "devDependencies": { 31 | "@docusaurus/module-type-aliases": "^3.4.0", 32 | "@docusaurus/types": "^3.4.0" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.5%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "engines": { 47 | "node": ">=18.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/pydoc-markdown.yml: -------------------------------------------------------------------------------- 1 | loaders: 2 | - type: python 3 | search_path: [../] 4 | processors: 5 | - type: filter 6 | skip_empty_modules: true 7 | - type: google 8 | - type: crossref 9 | renderer: 10 | type: markdown 11 | render_page_title: false 12 | render_module_header: false 13 | use_fixed_header_levels: true 14 | insert_header_anchors: false 15 | header_level_by_type: 16 | Module: 1 17 | Class: 1 18 | Method: 2 19 | Function: 2 20 | Data: 4 21 | descriptive_class_title: '$ Class' 22 | classdef_code_block: false 23 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | docsSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | }; 19 | 20 | module.exports = sidebars; 21 | -------------------------------------------------------------------------------- /docs/src/components/Figure.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import useBaseUrl from '@docusaurus/useBaseUrl' 3 | 4 | export default function Figure({ src, caption }) { 5 | return ( 6 |
7 | {caption} 8 |
{`Figure: ${caption}`}
9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from '@docusaurus/Link' 3 | import clsx from 'clsx'; 4 | import styles from './styles.module.css'; 5 | 6 | const FeatureList = [ 7 | { 8 | title: 'Symmetry-based Clustering', 9 | link: '/docs/learn/symmetry-based-clustering', 10 | description: ( 11 | <> 12 | MatID contains a novel clustering approach called Symmetry-based 13 | Clustering (SBC) that can be used to identify meaningful components from 14 | atomistic systems. 15 | 16 | ), 17 | }, 18 | { 19 | title: 'Symmetry Analysis', 20 | link: '/docs/learn/symmetry-analysis', 21 | description: ( 22 | <> 23 | MatID contains several symmetry routines for analyzing structures. The 24 | basic features are based on the excellent {spglib library}, but with an 26 | extended feature set that makes MatID unique. 27 | 28 | ), 29 | }, 30 | { 31 | title: 'Powering identification in NOMAD', 32 | link: 'https://nomad-lab.eu/nomad-lab/', 33 | linktext: 'Visit NOMAD', 34 | description: ( 35 | <> 36 | MatID is being developed at FAIRmat and is powering the automated 37 | analysis of structures at scale in the NOMAD platform. 38 | 39 | ), 40 | }, 41 | ]; 42 | 43 | function Feature({title, description, link, linktext}) { 44 | return ( 45 |
46 |
47 |

{title}

48 |

{description}

49 | 52 | {linktext || 'Learn more'} 53 | 54 |
55 |
56 | ); 57 | } 58 | 59 | export default function HomepageFeatures() { 60 | return ( 61 |
62 |
63 |
64 | {FeatureList.map((props, idx) => ( 65 | 66 | ))} 67 |
68 |
69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .feature { 9 | margin-bottom: 3rem; 10 | } 11 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #3e852eff; 10 | --ifm-color-primary-dark: #387829; 11 | --ifm-color-primary-darker: #357127; 12 | --ifm-color-primary-darkest: #2b5d20; 13 | --ifm-color-primary-light: #449233; 14 | --ifm-color-primary-lighter: #479935; 15 | --ifm-color-primary-lightest: #51ad3c; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | --ifm-footer-background-color: #444; 19 | --ifm-footer-color: rgb(227, 227, 227); 20 | --ifm-footer-link-color: rgb(227, 227, 227); 21 | --ifm-hero-background-color: #ffffff; 22 | } 23 | 24 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 25 | [data-theme='dark'] { 26 | --ifm-color-primary: #5dca44; 27 | --ifm-color-primary-dark: #4fbd36; 28 | --ifm-color-primary-darker: #4bb333; 29 | --ifm-color-primary-darkest: #3d932a; 30 | --ifm-color-primary-light: #6fd059; 31 | --ifm-color-primary-lighter: #78d364; 32 | --ifm-color-primary-lightest: #94dc83; 33 | --ifm-footer-background-color: #444; 34 | --ifm-footer-color: rgb(227, 227, 227); 35 | --ifm-footer-link-color: rgb(227, 227, 227); 36 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 37 | } 38 | -------------------------------------------------------------------------------- /docs/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react' 2 | import clsx from 'clsx' 3 | import Link from '@docusaurus/Link' 4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext' 5 | import Layout from '@theme/Layout' 6 | import HomepageFeatures from '@site/src/components/HomepageFeatures' 7 | 8 | import styles from './index.module.css' 9 | import logo from '@site/static/img/logo.png' 10 | import all from '@site/static/img/all_cropped.png' 11 | import cu from '@site/static/img/cu_cropped.png' 12 | import graphene from '@site/static/img/graphene_cropped.png' 13 | import mos2 from '@site/static/img/mos2_cropped.png' 14 | 15 | function HomepageHeader() { 16 | const {siteConfig} = useDocusaurusContext() 17 | return ( 18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 | ); 31 | } 32 | 33 | /** 34 | * Displays logo, motivational message and ed link. 35 | */ 36 | function GetStarted() { 37 | const {siteConfig} = useDocusaurusContext() 38 | 39 | return
40 | 41 |

{siteConfig.tagline}

42 |
43 | 46 | Get started 47 | 48 |
49 |
50 | } 51 | 52 | /** 53 | * Displays an interative image of a clustered system. 54 | */ 55 | function Stack() { 56 | const [stack, setStack] = useState('all') 57 | 58 | const handleMouseMove = useCallback((event) => { 59 | const imageHeight = event.target.height 60 | const y = event.nativeEvent.offsetY 61 | const fraction = y / imageHeight 62 | let stack 63 | if (fraction < 0.25) { 64 | stack = 'graphene' 65 | } else if (fraction < 0.57) { 66 | stack = 'mos2' 67 | } else { 68 | stack = 'cu' 69 | } 70 | setStack(stack) 71 | }, []) 72 | 73 | const handleMouseLeave = useCallback(() => setStack('all'), []) 74 | 75 | return
80 | 85 | 90 | 95 | 100 |
101 | } 102 | 103 | export default function Home() { 104 | const {siteConfig} = useDocusaurusContext() 105 | return ( 106 | 109 | 110 |
111 | 112 |
113 |
114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 2rem; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | color: rgb(28, 30, 33); 12 | background-color: #ffffff; 13 | } 14 | 15 | .buttons { 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | } 20 | 21 | .logo { 22 | width: 15rem; 23 | margin-bottom: 0.8rem; 24 | } 25 | 26 | .column { 27 | width: 100%; 28 | height: 100%; 29 | display: flex; 30 | flex-direction: column; 31 | align-items: center; 32 | justify-content: center; 33 | } 34 | 35 | .stackContainer { 36 | margin-left: auto; 37 | margin-right: auto; 38 | width: 17rem; 39 | height: 17rem; 40 | position: relative; 41 | } 42 | 43 | .stack { 44 | top: 0; 45 | bottom: 0; 46 | left: 0; 47 | right: 0; 48 | position: absolute; 49 | } 50 | 51 | .row { 52 | display: flex; 53 | flex-wrap: wrap; 54 | } 55 | 56 | .rightCol { 57 | flex-basis: 20rem; 58 | } 59 | 60 | .leftCol { 61 | flex-basis: 60%; 62 | margin-bottom: 1rem; 63 | } 64 | 65 | @media screen and (max-width: 996px) { 66 | .rightCol { 67 | flex-basis: 100%; 68 | } 69 | .leftCol { 70 | flex-basis: 100%; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/all_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/all_cropped.png -------------------------------------------------------------------------------- /docs/static/img/cu_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/cu_cropped.png -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/graphene_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/graphene_cropped.png -------------------------------------------------------------------------------- /docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/logo.png -------------------------------------------------------------------------------- /docs/static/img/mos2_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/mos2_cropped.png -------------------------------------------------------------------------------- /docs/static/img/sbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/sbc.png -------------------------------------------------------------------------------- /docs/static/img/tsa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/docs/static/img/tsa.jpg -------------------------------------------------------------------------------- /docs/static/snippets/clathrate.xyz: -------------------------------------------------------------------------------- 1 | 46 2 | Lattice="10.355 0.0 0.0 0.0 10.355 0.0 0.0 0.0 10.355" Properties=species:S:1:pos:R:3 pbc="T T T" 3 | Si 1.90221350 1.90221350 1.90221350 4 | Si 1.90221350 1.90221350 8.45278650 5 | Si 1.90221350 8.45278650 1.90221350 6 | Si 1.90221350 8.45278650 8.45278650 7 | Si 3.27528650 3.27528650 3.27528650 8 | Si 3.27528650 3.27528650 7.07971350 9 | Si 3.27528650 7.07971350 3.27528650 10 | Si 3.27528650 7.07971350 7.07971350 11 | Si 7.07971350 3.27528650 3.27528650 12 | Si 7.07971350 3.27528650 7.07971350 13 | Si 7.07971350 7.07971350 3.27528650 14 | Si 7.07971350 7.07971350 7.07971350 15 | Si 8.45278650 1.90221350 1.90221350 16 | Si 8.45278650 1.90221350 8.45278650 17 | Si 8.45278650 8.45278650 1.90221350 18 | Si 8.45278650 8.45278650 8.45278650 19 | Si 0.00000000 1.21360600 3.18623350 20 | Si 0.00000000 1.21360600 7.16876650 21 | Si 0.00000000 9.14139400 3.18623350 22 | Si 0.00000000 9.14139400 7.16876650 23 | Si 1.21360600 3.18623350 0.00000000 24 | Si 1.21360600 7.16876650 0.00000000 25 | Si 1.99126650 3.96389400 5.17750000 26 | Si 1.99126650 6.39110600 5.17750000 27 | Si 3.18623350 0.00000000 1.21360600 28 | Si 3.18623350 0.00000000 9.14139400 29 | Si 3.96389400 5.17750000 1.99126650 30 | Si 3.96389400 5.17750000 8.36373350 31 | Si 5.17750000 1.99126650 3.96389400 32 | Si 5.17750000 1.99126650 6.39110600 33 | Si 5.17750000 8.36373350 3.96389400 34 | Si 5.17750000 8.36373350 6.39110600 35 | Si 6.39110600 5.17750000 1.99126650 36 | Si 6.39110600 5.17750000 8.36373350 37 | Si 7.16876650 0.00000000 1.21360600 38 | Si 7.16876650 0.00000000 9.14139400 39 | Si 8.36373350 3.96389400 5.17750000 40 | Si 8.36373350 6.39110600 5.17750000 41 | Si 9.14139400 3.18623350 0.00000000 42 | Si 9.14139400 7.16876650 0.00000000 43 | Si 0.00000000 2.58875000 5.17750000 44 | Si 0.00000000 7.76625000 5.17750000 45 | Si 2.58875000 5.17750000 0.00000000 46 | Si 5.17750000 0.00000000 2.58875000 47 | Si 5.17750000 0.00000000 7.76625000 48 | Si 7.76625000 5.17750000 0.00000000 49 | -------------------------------------------------------------------------------- /docs/static/snippets/conventional_cell.py: -------------------------------------------------------------------------------- 1 | from matid import SymmetryAnalyzer 2 | from ase.spacegroup import crystal 3 | from ase.visualize import view 4 | 5 | # Create an initial system with ASE 6 | a = 5.64 7 | nacl = crystal( 8 | ['Na', 'Cl'], 9 | [(0, 0, 0), (0.5, 0.5, 0.5)], 10 | spacegroup=225, 11 | cellpar=[a, a, a, 90, 90, 90] 12 | ) * [3, 2, 1] 13 | view(nacl) 14 | 15 | # Let's retrieve the conventional cell as ASE Atoms 16 | analyzer = SymmetryAnalyzer(nacl) 17 | conventional_cell = analyzer.get_conventional_system() 18 | view(conventional_cell) -------------------------------------------------------------------------------- /docs/static/snippets/dimensionality.py: -------------------------------------------------------------------------------- 1 | from matid.geometry import get_dimensionality 2 | 3 | from ase.build import molecule 4 | from ase.build import nanotube 5 | from ase.build import mx2 6 | from ase.build import bulk 7 | 8 | # Here we create one example of each dimensionality class 9 | zero_d = molecule("H2O", vacuum=5) 10 | one_d = nanotube(6, 0, length=4, vacuum=5) 11 | two_d = mx2(vacuum=5) 12 | three_d = bulk("NaCl", "rocksalt", a=5.64) 13 | 14 | # In order to make the dimensionality detection interesting, we add periodic 15 | # boundary conditions. This is more realistic as not that many electronic 16 | # structure codes support anything else than full periodic boundary conditions, 17 | # and thus the dimensionality information is typically not available. 18 | zero_d.set_pbc(True) 19 | one_d.set_pbc(True) 20 | two_d.set_pbc(True) 21 | three_d.set_pbc(True) 22 | 23 | # Here we perform the dimensionality detection with clustering threshold epsilon 24 | epsilon = 3.5 25 | dim0 = get_dimensionality(zero_d, epsilon) 26 | dim1 = get_dimensionality(one_d, epsilon) 27 | dim2 = get_dimensionality(two_d, epsilon) 28 | dim3 = get_dimensionality(three_d, epsilon) 29 | 30 | # Printing out the results 31 | print(dim0) 32 | print(dim1) 33 | print(dim2) 34 | print(dim3) 35 | -------------------------------------------------------------------------------- /docs/static/snippets/material_id.py: -------------------------------------------------------------------------------- 1 | from matid import SymmetryAnalyzer 2 | from ase.spacegroup import crystal 3 | from ase.visualize import view 4 | 5 | # Create an initial system with ASE 6 | a = 9.04 7 | skutterudite = crystal( 8 | ('Co', 'Sb'), 9 | basis=[(0.25, 0.25, 0.25), (0.0, 0.335, 0.158)], 10 | spacegroup=204, 11 | cellpar=[a, a, a, 90, 90, 90] 12 | ) 13 | view(skutterudite) 14 | 15 | # Let's retrieve the material id 16 | analyzer = SymmetryAnalyzer(skutterudite) 17 | material_id = analyzer.get_material_id() 18 | print(material_id) 19 | -------------------------------------------------------------------------------- /docs/static/snippets/readme.py: -------------------------------------------------------------------------------- 1 | import ase.io 2 | from ase.visualize import view 3 | 4 | from matid.clustering import SBC 5 | from matid.symmetry import SymmetryAnalyzer 6 | 7 | # Load structure from a file 8 | system = ase.io.read('data/system.xyz') 9 | 10 | # Find interesting substructures using Symmetry-based Clustering (SBC) 11 | sbc = SBC() 12 | clusters = sbc.get_clusters(system) 13 | 14 | # Analyze each found cluster printing out the indices of the atoms belonging to 15 | # this cluster and visualizing the conventional cell from which the cluster was 16 | # built from. 17 | for cluster in clusters: 18 | 19 | # Get the indices of the atoms belonging to this cluster 20 | indices = cluster.indices 21 | print(indices) 22 | 23 | # Get the dimensionality of the cluster 24 | dimensionality = cluster.get_dimensionality() 25 | print(dimensionality) 26 | 27 | # Get the cell from which the cluster is constructed from. The periodicity 28 | # of this cell indicates in which directions the unit cell has been found to 29 | # be repeated in (at least once, possibly infinitely). 30 | cell = cluster.get_cell() 31 | n_repeated_directions = sum(cell.get_pbc()) 32 | print(n_repeated_directions) 33 | 34 | # Analyze some symmetry properties of the underlying cell to better identify 35 | # the material from which the cluster has been constructed from. 36 | analyzer = SymmetryAnalyzer(cell, symmetry_tol=0.5) 37 | conv_sys = analyzer.get_conventional_system() 38 | view(conv_sys) 39 | -------------------------------------------------------------------------------- /docs/static/snippets/sbc.py: -------------------------------------------------------------------------------- 1 | from matid.clustering import SBC 2 | import ase.io 3 | from ase.visualize import view 4 | 5 | system = ase.io.read('system.xyz') 6 | 7 | sbc = SBC() 8 | clusters = sbc.get_clusters(system) 9 | 10 | for cluster in clusters: 11 | print(cluster.indices) 12 | view(cluster.get_cell()) 13 | -------------------------------------------------------------------------------- /docs/static/snippets/wyckoff_sets.py: -------------------------------------------------------------------------------- 1 | from matid import SymmetryAnalyzer 2 | from ase.io import read 3 | 4 | # Load clathrate structure from file 5 | clathrate = read('clathrate.xyz') 6 | 7 | # Print the wyckoff sets 8 | symm = SymmetryAnalyzer(clathrate) 9 | wyckoff_sets_conv = symm.get_wyckoff_sets_conventional() 10 | 11 | for i_group, group in enumerate(wyckoff_sets_conv): 12 | print("Set {}".format(i_group)) 13 | print(" Letter: {}".format(group.wyckoff_letter)) 14 | print(" Element: {}".format(group.element)) 15 | print(" Indices: {}".format(group.indices)) 16 | print(" Multiplicity: {}".format(group.multiplicity)) 17 | print(" Repr.: {}".format(group.representative)) 18 | print(" x: {}".format(group.x)) 19 | print(" y: {}".format(group.y)) 20 | print(" z: {}".format(group.z)) 21 | -------------------------------------------------------------------------------- /examples/classification1.py: -------------------------------------------------------------------------------- 1 | from matid import Classifier 2 | import ase.io 3 | 4 | # Read an extended XYZ file containin an atomic geometry. Extended XYZ files 5 | # will also include the unit cell and periodic boundary conditions. 6 | system = ase.io.read("structure.xyz") 7 | 8 | # Define the classifier 9 | classifier = Classifier() 10 | 11 | # Perform classification 12 | classification = classifier.classify(system) 13 | 14 | # Investigate result 15 | print(classification) 16 | -------------------------------------------------------------------------------- /examples/classification2.py: -------------------------------------------------------------------------------- 1 | from matid import Classifier 2 | from ase import Atoms 3 | 4 | # Define the structure as an ASE Atoms object 5 | system = Atoms( 6 | positions=[ 7 | [5.0, 5.0, 5.2981545], 8 | [5.0, 5.763239, 4.7018455], 9 | [5.0, 4.236761, 4.7018455], 10 | ], 11 | symbols=["O", "H", "H"], 12 | cell=[10, 10, 10], 13 | pbc=[True, True, True], 14 | ) 15 | 16 | # Define the classifier 17 | classifier = Classifier() 18 | 19 | # Perform classification 20 | classification = classifier.classify(system) 21 | 22 | # Investigate result 23 | print(classification) 24 | -------------------------------------------------------------------------------- /examples/data/C26H24N4O2.xyz: -------------------------------------------------------------------------------- 1 | 56 2 | Lattice="6.050002408539566 0.0 0.0 0.0 7.800003105227187 0.0 0.0 0.0 24.871328901490003" Properties=species:S:1:pos:R:3 pbc="T T T" 3 | C 5.09482356 3.94598862 18.49431433 4 | C 5.03305347 5.31275985 16.08613360 5 | C 4.84648847 1.20165102 23.21732331 6 | C 4.82335313 1.88149436 22.01961841 7 | C 3.90022943 4.48314958 18.02848284 8 | C 3.87022506 5.16289135 16.82505151 9 | C 6.00343892 2.05156297 21.29662256 10 | C 6.04162617 0.67672869 23.69079719 11 | C 0.21773714 4.10930393 17.75981855 12 | C 0.17835718 4.78601731 16.55875539 13 | C 0.40901395 0.80823772 1.07526662 14 | C 0.84522386 1.26288979 16.82505151 15 | C 0.87522822 0.58314803 18.02848284 16 | C 1.14366921 1.50894012 21.76985956 17 | C 1.16664155 0.82644768 22.96962025 18 | C 1.79835192 5.78149591 22.01961841 19 | C 1.82148726 5.10165257 23.21732331 20 | C 2.06982236 0.04598707 18.49431433 21 | C 2.00805227 1.41275830 16.08613360 22 | C 2.97843385 5.95156452 21.29662256 23 | C 3.01662327 4.57673024 23.69079719 24 | C 3.24273835 0.20930238 17.75981855 25 | C 3.20335839 0.88601576 16.55875539 26 | C 3.43401516 4.70823927 1.07526662 27 | C 4.16867042 5.40894168 21.76985956 28 | C 4.19164275 4.72644923 22.96962025 29 | H 5.01164446 5.85391857 15.13360594 30 | H 3.94189390 1.08905697 23.82749066 31 | H 3.90386305 2.32717599 21.62466380 32 | H 3.00371090 4.35238958 18.64640364 33 | H 2.93247919 5.60400498 16.47072311 34 | H 5.72786133 1.62379877 1.25404857 35 | H 6.02871210 0.45238803 18.64640364 36 | H 5.95748039 1.70400342 16.47072311 37 | H 4.42541409 5.16718280 0.88238570 38 | H 5.06864760 5.53344810 21.15582519 39 | H 5.11310195 4.27123035 23.34931173 40 | H 0.46486182 0.16621427 1.96830837 41 | H 0.87886184 6.22717754 21.62466380 42 | H 0.91689270 4.98905852 23.82749066 43 | H 1.09502132 4.89482310 15.96663657 44 | H 1.14207237 3.66485799 18.14510446 45 | H 1.40041289 1.26718125 0.88238570 46 | H 1.98664326 1.95391701 15.13360594 47 | H 2.04364639 1.63344655 21.15582519 48 | H 2.08810074 0.37122880 23.34931173 49 | H 2.70286012 5.52380032 1.25404857 50 | H 3.48986302 4.06621582 1.96830837 51 | H 4.12002252 0.99482155 15.96663657 52 | H 4.16707358 7.56485955 18.14510446 53 | N 0.04442369 2.76489020 20.10172880 54 | N 1.98360877 7.13653273 19.69397304 55 | N 3.06942078 6.66489175 20.10172880 56 | N 5.00860997 3.23653118 19.69397304 57 | O 0.00000000 0.00000000 0.00000000 58 | O 3.02500121 3.90000155 0.00000000 59 | -------------------------------------------------------------------------------- /examples/data/C32Mo32+CO2.xyz: -------------------------------------------------------------------------------- 1 | 67 2 | Lattice="8.760012 0.0 0.0 0.0 8.760012 0.0 0.0 0.0 18.760012" Properties=species:S:1:pos:R:3 pbc="T T T" 3 | Mo 6.78999500 4.38000600 6.41574000 4 | Mo 6.64447900 0.00000000 6.45966700 5 | Mo 1.97001700 4.38000600 6.41574000 6 | Mo 2.11553200 0.00000000 6.45966700 7 | Mo 4.38000600 6.79870600 6.56015800 8 | Mo 4.38000600 1.96130600 6.56015800 9 | Mo 0.00000000 6.58765000 6.42585200 10 | Mo 0.00000000 2.17236200 6.42585200 11 | Mo 6.48828300 6.56200100 4.45945800 12 | Mo 6.48828300 2.19801000 4.45945800 13 | Mo 2.27172900 6.56200100 4.45945800 14 | Mo 2.27172900 2.19801000 4.45945800 15 | Mo 4.38000600 4.38000600 4.49499700 16 | Mo 4.38000600 0.00000000 4.44845900 17 | Mo 0.00000000 4.38000600 4.37074800 18 | Mo 0.00000000 0.00000000 4.41167700 19 | Mo 6.57000900 4.38000600 2.19000300 20 | Mo 6.57000900 0.00000000 2.19000300 21 | Mo 2.19000300 4.38000600 2.19000300 22 | Mo 2.19000300 0.00000000 2.19000300 23 | Mo 4.38000600 6.57000900 2.19000300 24 | Mo 4.38000600 2.19000300 2.19000300 25 | Mo 0.00000000 6.57000900 2.19000300 26 | Mo 0.00000000 2.19000300 2.19000300 27 | Mo 6.57000900 6.57000900 0.00000000 28 | Mo 6.57000900 2.19000300 0.00000000 29 | Mo 2.19000300 6.57000900 0.00000000 30 | Mo 2.19000300 2.19000300 0.00000000 31 | Mo 4.38000600 4.38000600 0.00000000 32 | Mo 4.38000600 0.00000000 0.00000000 33 | Mo 0.00000000 4.38000600 0.00000000 34 | Mo 0.00000000 0.00000000 0.00000000 35 | C 6.56958400 6.54378100 6.68847700 36 | C 6.56958400 2.21623000 6.68847700 37 | C 2.19042700 6.54378100 6.68847700 38 | C 2.19042700 2.21623000 6.68847700 39 | C 4.38000600 4.38000600 6.54080900 40 | C 4.38000600 0.00000000 6.86074100 41 | C 0.00000000 4.38000600 6.74872800 42 | C 0.00000000 0.00000000 6.73365600 43 | C 6.58861800 4.38000600 4.41290600 44 | C 6.57551700 0.00000000 4.45338000 45 | C 2.17139400 4.38000600 4.41290600 46 | C 2.18449500 0.00000000 4.45338000 47 | C 4.38000600 6.58207800 4.48015600 48 | C 4.38000600 2.17793300 4.48015600 49 | C 0.00000000 6.57435000 4.42915700 50 | C 0.00000000 2.18566200 4.42915700 51 | C 6.57000900 6.57000900 2.19000300 52 | C 6.57000900 2.19000300 2.19000300 53 | C 2.19000300 6.57000900 2.19000300 54 | C 2.19000300 2.19000300 2.19000300 55 | C 4.38000600 4.38000600 2.19000300 56 | C 4.38000600 0.00000000 2.19000300 57 | C 0.00000000 4.38000600 2.19000300 58 | C 0.00000000 0.00000000 2.19000300 59 | C 6.57000900 4.38000600 0.00000000 60 | C 6.57000900 0.00000000 0.00000000 61 | C 2.19000300 4.38000600 0.00000000 62 | C 2.19000300 0.00000000 0.00000000 63 | C 4.38000600 6.57000900 0.00000000 64 | C 4.38000600 2.19000300 0.00000000 65 | C 0.00000000 6.57000900 0.00000000 66 | C 0.00000000 2.19000300 0.00000000 67 | C 4.38000600 4.38000600 8.00540000 68 | O 4.38000600 3.21624800 8.56331000 69 | O 4.38000600 5.54376400 8.56331000 70 | -------------------------------------------------------------------------------- /examples/data/C49+N.xyz: -------------------------------------------------------------------------------- 1 | 50 2 | Lattice="10.650003758837872 -6.148782545663814 0.0 0.0 12.297565091327629 0.0 0.0 0.0 20.00000394583286" Properties=species:S:1:pos:R:3 pbc="T T T" 3 | C 9.94222900 -4.92288071 0.00000000 4 | C 9.94222900 4.92288071 0.00000000 5 | C 1.41554951 -0.00000000 0.00000000 6 | C 2.12069797 11.07001661 0.00000000 7 | C 0.00273918 2.45035133 0.00000000 8 | C 8.52656767 4.92597908 0.00000000 9 | C 0.00273918 9.84721376 0.00000000 10 | C 8.52656767 7.37158601 0.00000000 11 | C 2.12069797 1.22754725 0.00000000 12 | C 3.54369077 -1.22547573 0.00000000 13 | C 9.93945148 -2.46711845 0.00000000 14 | C 7.81686526 3.69259417 0.00000000 15 | C 9.93945148 2.46711845 0.00000000 16 | C 7.81686526 -3.69259417 0.00000000 17 | C 3.54369077 1.22547573 0.00000000 18 | C 4.25855736 -2.45764010 0.00000000 19 | C 10.64910383 -1.23194486 0.00000000 20 | C 6.39234739 3.68958312 0.00000000 21 | C 10.64910383 1.23194363 0.00000000 22 | C 6.39234739 -3.68958435 0.00000000 23 | C 4.25855736 2.45763887 0.00000000 24 | C 5.67997822 -2.45955422 0.00000000 25 | C 9.94005108 -0.00000000 0.00000000 26 | C 5.67997822 2.45955422 0.00000000 27 | C 1.42237510 2.46362471 0.00000000 28 | C 7.80525463 6.14878193 0.00000000 29 | C 1.42237510 9.83393915 0.00000000 30 | C 4.25988116 -0.00000061 0.00000000 31 | C 8.52006371 -2.45961693 0.00000000 32 | C 8.52006371 2.45961693 0.00000000 33 | C 2.12899859 3.68753373 0.00000000 34 | C 6.39200659 6.14878255 0.00000000 35 | C 2.12899965 8.61003075 0.00000000 36 | C 5.68096015 -0.00000061 0.00000000 37 | C 7.80952422 -1.22892765 0.00000000 38 | C 7.80952422 1.22892765 0.00000000 39 | C 6.39014177 -1.22951609 0.00000000 40 | C 8.51972504 0.00000061 0.00000000 41 | C 6.39014177 1.22951486 0.00000000 42 | C 2.12947890 6.14878255 0.00000000 43 | C 4.26026243 4.91857410 0.00000000 44 | C 4.26026243 7.37899099 0.00000000 45 | C 1.41843140 4.91973561 0.00000000 46 | C 5.68017205 4.91731114 0.00000000 47 | C 3.55140137 8.60930027 0.00000000 48 | C 5.68017205 7.38025395 0.00000000 49 | C 3.55140137 3.68826482 0.00000000 50 | C 1.41843140 7.37782826 0.00000000 51 | C 3.55000196 6.14878255 0.00000000 52 | N 0.00000000 0.00000000 0.00000000 53 | -------------------------------------------------------------------------------- /examples/data/H2O.xyz: -------------------------------------------------------------------------------- 1 | 3 2 | Lattice="7.0 0.0 0.0 0.0 7.0 0.0 0.0 0.0 7.0" Properties=species:S:1:pos:R:3 pbc="F F F" 3 | O 3.50000000 3.50000000 3.79815450 4 | H 3.50000000 4.26323900 3.20184550 5 | H 3.50000000 2.73676100 3.20184550 6 | -------------------------------------------------------------------------------- /examples/data/Ru.xyz: -------------------------------------------------------------------------------- 1 | 1 2 | Lattice="10.0 0.0 0.0 0.0 10.0 0.0 0.0 0.0 10.0" Properties=species:S:1:pos:R:3 pbc="T T T" 3 | Ru 5.00000000 5.00000000 5.00000000 4 | -------------------------------------------------------------------------------- /examples/summary.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | import ase.io 5 | 6 | from matid import Classifier 7 | from matid import SymmetryAnalyzer 8 | from matid.classifications import Class3D, Material2D, Surface 9 | 10 | # This is a folder containing 10 different extended XYZ files. 11 | inpath = "./structures" 12 | 13 | # Lets find all XYZ files and read the geometries as ASE.Atoms objects in to a 14 | # list 15 | geometries = [] 16 | for root, dirs, files in os.walk(inpath): 17 | for i_file in files: 18 | # if i_file.endswith("C96Si96+C54H16.xyz"): 19 | if i_file.endswith("xyz"): 20 | i_atoms = ase.io.read("{}/{}".format(root, i_file)) 21 | # view(i_atoms) 22 | geometries.append((i_file, i_atoms)) 23 | 24 | # Create a Classifier instance. The default settings are used here 25 | classifier = Classifier() 26 | 27 | # Get a classification result for each geometry 28 | classifications = [] 29 | for i_file, i_geom in geometries: 30 | print("Classifying") 31 | i_cls = classifier.classify(i_geom) 32 | print("Done") 33 | classifications.append(i_cls) 34 | 35 | # Create a summary of the geometries 36 | summary = {} 37 | for (i_file, i_geom), i_cls in zip(geometries, classifications): 38 | i_type = type(i_cls) 39 | i_atoms = i_cls.atoms 40 | 41 | i_data = { 42 | "system_type": str(i_cls), 43 | } 44 | 45 | # Get symmetry information 46 | blk_cell = None 47 | if i_type == Class3D: 48 | blk_cell = i_atoms 49 | elif i_type == Surface: 50 | blk_cell = i_cls.prototype_cell 51 | if blk_cell is not None: 52 | symm_analyzer = SymmetryAnalyzer(blk_cell) 53 | formula = i_atoms.get_chemical_formula() 54 | crystal_system = symm_analyzer.get_crystal_system() 55 | bravais_lattice = symm_analyzer.get_bravais_lattice() 56 | space_group = symm_analyzer.get_space_group_number() 57 | i_data["space_group_number"] = space_group 58 | i_data["crystal_system"] = crystal_system 59 | i_data["bravais_lattice"] = bravais_lattice 60 | 61 | # Get the outlier information from two-dimensional systems 62 | if i_type == Surface or i_type == Material2D: 63 | outlier_indices = i_cls.outliers 64 | outlier_formula = i_atoms[outlier_indices].get_chemical_formula() 65 | i_data["outlier_indices"] = outlier_indices.tolist() 66 | i_data["outlier_formula"] = outlier_formula 67 | 68 | summary[i_file] = i_data 69 | 70 | # Write a summary of the results 71 | with open("summary.json", "w") as fout: 72 | fout.write(json.dumps(summary, indent=2, sort_keys=True)) 73 | -------------------------------------------------------------------------------- /examples/surface.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ase.visualize import view 3 | from ase.build import bcc100, molecule 4 | from matid import Classifier, SymmetryAnalyzer 5 | import ase.io 6 | 7 | # Generating a surface adsorption geometry with ASE. 8 | adsorbent = bcc100("Fe", size=(3, 3, 4), vacuum=8) 9 | ase.io.write("structure.xyz", adsorbent) 10 | 11 | adsorbate = molecule("H2O") 12 | adsorbate.rotate(180, [1, 0, 0]) 13 | adsorbate.translate([4.3, 4.3, 13.5]) 14 | system = adsorbent + adsorbate 15 | system.set_pbc([True, True, True]) 16 | 17 | # Add noise and defects to the structure 18 | positions = system.get_positions() 19 | positions += 0.25 * np.random.rand(*positions.shape) 20 | system.set_positions(positions) 21 | del system[31] 22 | 23 | # Visualize the final system 24 | view(system) 25 | 26 | # Run the classification 27 | classifier = Classifier(pos_tol=1.0, max_cell_size=6) 28 | classification = classifier.classify(system) 29 | 30 | # Print classification 31 | print("Structure classified as: {}".format(classification)) 32 | 33 | # Print found outliers 34 | outliers = classification.outliers 35 | print("Outlier atoms indices: {}".format(outliers)) 36 | 37 | # Visualize the cell that was found by matid 38 | prototype_cell = classification.prototype_cell 39 | view(prototype_cell) 40 | 41 | # Visualize the corresponding conventional cell 42 | analyzer = SymmetryAnalyzer(prototype_cell, symmetry_tol=0.5) 43 | conv_sys = analyzer.get_conventional_system() 44 | view(conv_sys) 45 | 46 | # Visualize the corresponding primitive cell 47 | prim_sys = analyzer.get_primitive_system() 48 | view(prim_sys) 49 | 50 | # Print space group number 51 | spg_number = analyzer.get_space_group_number() 52 | print("Space group number: {}".format(spg_number)) 53 | -------------------------------------------------------------------------------- /examples/symmetry.py: -------------------------------------------------------------------------------- 1 | from matid import SymmetryAnalyzer 2 | from ase.build import bulk 3 | from ase.visualize import view 4 | from ase import Atoms 5 | 6 | # Prepare a geometry to be analyzed 7 | nacl = bulk("NaCl", "rocksalt", a=5.64).repeat([2, 2, 2]) 8 | view(nacl) 9 | 10 | # Setup the symmetry analyzer 11 | symm = SymmetryAnalyzer(nacl, symmetry_tol=0.1) 12 | 13 | # Get the conventional system as an ase.Atoms-object. 14 | conv = symm.get_conventional_system() 15 | view(conv) 16 | 17 | # Get the primitive system as an ase.Atoms-object 18 | prim = symm.get_primitive_system() 19 | view(prim) 20 | 21 | # Get symmetry related information. Some properties depenc on which system is 22 | # meant: the original, primitive or conventional system. To avoid ambiquity, 23 | # the method names include for which system these properties are reported. 24 | space_group_number = symm.get_space_group_number() 25 | space_group_symbol = symm.get_space_group_international_short() 26 | is_chiral = symm.get_is_chiral() 27 | hall_number = symm.get_hall_number() 28 | hall_symbol = symm.get_hall_symbol() 29 | crystal_system = symm.get_crystal_system() 30 | bravais_lattice = symm.get_bravais_lattice() 31 | point_group = symm.get_point_group() 32 | wyckoff_letters_orig = symm.get_wyckoff_letters_original() 33 | wyckoff_letters_prim = symm.get_wyckoff_letters_primitive() 34 | wyckoff_letters_conv = symm.get_wyckoff_letters_conventional() 35 | equivalent_atoms_orig = symm.get_equivalent_atoms_original() 36 | equivalent_atoms_prim = symm.get_equivalent_atoms_primitive() 37 | equivalent_atoms_conv = symm.get_equivalent_atoms_conventional() 38 | 39 | print("Space group number: {}".format(space_group_number)) 40 | print("Space group international short symbol: {}".format(space_group_symbol)) 41 | print("Is chiral: {}".format(is_chiral)) 42 | print("Hall number: {}".format(hall_number)) 43 | print("Hall symbol: {}".format(hall_symbol)) 44 | print("Crystal system: {}".format(crystal_system)) 45 | print("Bravais lattice: {}".format(bravais_lattice)) 46 | print("Point group: {}".format(point_group)) 47 | print("Wyckoff letters original: {}".format(wyckoff_letters_orig)) 48 | print("Wyckoff letters primitive: {}".format(wyckoff_letters_prim)) 49 | print("Wyckoff letters conventional: {}".format(wyckoff_letters_conv)) 50 | 51 | # Print out details of the Wyckoff sets contained in a more complex silicon 52 | # clathrate structure. 53 | scaled_positions = [ 54 | [0.18370000000000, 0.18370000000000, 0.18370000000000], 55 | [0.18370000000000, 0.18370000000000, 0.81630000000000], 56 | [0.18370000000000, 0.81630000000000, 0.18370000000000], 57 | [0.18370000000000, 0.81630000000000, 0.81630000000000], 58 | [0.31630000000000, 0.31630000000000, 0.31630000000000], 59 | [0.31630000000000, 0.31630000000000, 0.68370000000000], 60 | [0.31630000000000, 0.68370000000000, 0.31630000000000], 61 | [0.31630000000000, 0.68370000000000, 0.68370000000000], 62 | [0.68370000000000, 0.31630000000000, 0.31630000000000], 63 | [0.68370000000000, 0.31630000000000, 0.68370000000000], 64 | [0.68370000000000, 0.68370000000000, 0.31630000000000], 65 | [0.68370000000000, 0.68370000000000, 0.68370000000000], 66 | [0.81630000000000, 0.18370000000000, 0.18370000000000], 67 | [0.81630000000000, 0.18370000000000, 0.81630000000000], 68 | [0.81630000000000, 0.81630000000000, 0.18370000000000], 69 | [0.81630000000000, 0.81630000000000, 0.81630000000000], 70 | [0.00000000000000, 0.11720000000000, 0.30770000000000], 71 | [0.00000000000000, 0.11720000000000, 0.69230000000000], 72 | [0.00000000000000, 0.88280000000000, 0.30770000000000], 73 | [0.00000000000000, 0.88280000000000, 0.69230000000000], 74 | [0.11720000000000, 0.30770000000000, 0.00000000000000], 75 | [0.11720000000000, 0.69230000000000, 0.00000000000000], 76 | [0.19230000000000, 0.38280000000000, 0.50000000000000], 77 | [0.19230000000000, 0.61720000000000, 0.50000000000000], 78 | [0.30770000000000, 0.00000000000000, 0.11720000000000], 79 | [0.30770000000000, 0.00000000000000, 0.88280000000000], 80 | [0.38280000000000, 0.50000000000000, 0.19230000000000], 81 | [0.38280000000000, 0.50000000000000, 0.80770000000000], 82 | [0.50000000000000, 0.19230000000000, 0.38280000000000], 83 | [0.50000000000000, 0.19230000000000, 0.61720000000000], 84 | [0.50000000000000, 0.80770000000000, 0.38280000000000], 85 | [0.50000000000000, 0.80770000000000, 0.61720000000000], 86 | [0.61720000000000, 0.50000000000000, 0.19230000000000], 87 | [0.61720000000000, 0.50000000000000, 0.80770000000000], 88 | [0.69230000000000, 0.00000000000000, 0.11720000000000], 89 | [0.69230000000000, 0.00000000000000, 0.88280000000000], 90 | [0.80770000000000, 0.38280000000000, 0.50000000000000], 91 | [0.80770000000000, 0.61720000000000, 0.50000000000000], 92 | [0.88280000000000, 0.30770000000000, 0.00000000000000], 93 | [0.88280000000000, 0.69230000000000, 0.00000000000000], 94 | [0.00000000000000, 0.25000000000000, 0.50000000000000], 95 | [0.00000000000000, 0.75000000000000, 0.50000000000000], 96 | [0.25000000000000, 0.50000000000000, 0.00000000000000], 97 | [0.50000000000000, 0.00000000000000, 0.25000000000000], 98 | [0.50000000000000, 0.00000000000000, 0.75000000000000], 99 | [0.75000000000000, 0.50000000000000, 0.00000000000000], 100 | ] 101 | cell = [ 102 | [10.35500000000000, 0.00000000000000, 0.00000000000000], 103 | [0.00000000000000, 10.35500000000000, 0.00000000000000], 104 | [0.00000000000000, 0.00000000000000, 10.35500000000000], 105 | ] 106 | labels = ["Si"] * 46 107 | clathrate = Atoms(labels, scaled_positions=scaled_positions, cell=cell, pbc=True) 108 | 109 | # Setup the symmetry analyzer 110 | symm = SymmetryAnalyzer(clathrate, symmetry_tol=0.1) 111 | has_free_param = symm.get_has_free_wyckoff_parameters() 112 | wyckoff_sets_conv = symm.get_wyckoff_sets_conventional() 113 | 114 | for i_group, group in enumerate(wyckoff_sets_conv): 115 | print("Set {}".format(i_group)) 116 | print(" Letter: {}".format(group.wyckoff_letter)) 117 | print(" Element: {}".format(group.element)) 118 | print(" Indices: {}".format(group.indices)) 119 | print(" Multiplicity: {}".format(group.multiplicity)) 120 | print(" Repr.: {}".format(group.representative)) 121 | print(" x: {}".format(group.x)) 122 | print(" y: {}".format(group.y)) 123 | print(" z: {}".format(group.z)) 124 | -------------------------------------------------------------------------------- /matid/__init__.py: -------------------------------------------------------------------------------- 1 | from matid.core.periodicfinder import PeriodicFinder 2 | from matid.classification.classifier import Classifier 3 | from matid.symmetry.symmetryanalyzer import SymmetryAnalyzer 4 | from matid.clustering.sbc import SBC 5 | -------------------------------------------------------------------------------- /matid/classification/__init__.py: -------------------------------------------------------------------------------- 1 | from matid.classification.classifier import Classifier 2 | from matid.classification.classifications import * 3 | -------------------------------------------------------------------------------- /matid/classification/classifications.py: -------------------------------------------------------------------------------- 1 | class Classification: 2 | def __init__(self, atoms): 3 | self.atoms = atoms 4 | 5 | 6 | # =============================================================================== 7 | # 0D Structures 8 | class Class0D(Classification): 9 | """Structures that have a structure that is isolated in all directions by a 10 | vacuum gap. 11 | """ 12 | 13 | 14 | class Atom(Class0D): 15 | """ """ 16 | 17 | 18 | # =============================================================================== 19 | # 1D Structures 20 | class Class1D(Classification): 21 | """All structures that are roughly 1-dimensional, meaning that one 22 | dimension is much larger than the two others. 23 | """ 24 | 25 | 26 | # =============================================================================== 27 | # 2D Structures 28 | class Class2D(Classification): 29 | """Base class for all structures that are roughly 2-dimensional, meaning that two of the 30 | dimensions are much larger than the remaining one. 31 | """ 32 | 33 | 34 | class Class2DWithCell(Class2D): 35 | """Two dimensional structures from which a periodic unit cell has been 36 | identified. 37 | """ 38 | 39 | def __init__( 40 | self, 41 | atoms, 42 | region, 43 | ): 44 | super().__init__(atoms) 45 | self.region = region 46 | 47 | @property 48 | def basis_indices(self): 49 | return self.region.get_basis_indices() 50 | 51 | @property 52 | def outliers(self): 53 | all = set(list(range(len(self.atoms)))) 54 | region = self.region.get_basis_indices() 55 | return list(all - region) 56 | 57 | @property 58 | def prototype_cell(self): 59 | return self.region.cell 60 | 61 | 62 | class Surface(Class2DWithCell): 63 | """ """ 64 | 65 | 66 | class Material2D(Class2DWithCell): 67 | """ """ 68 | 69 | 70 | # =============================================================================== 71 | # 3D Structures 72 | class Class3D(Classification): 73 | """All structures that periodically extend infinitely without vacuum gaps.""" 74 | 75 | 76 | # =============================================================================== 77 | # Unknown structures 78 | class Unknown(Classification): 79 | """ """ 80 | -------------------------------------------------------------------------------- /matid/clustering/__init__.py: -------------------------------------------------------------------------------- 1 | from matid.clustering.sbc import SBC 2 | from matid.clustering.cluster import Cluster 3 | -------------------------------------------------------------------------------- /matid/clustering/cluster.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ase import Atoms 3 | 4 | import matid.geometry 5 | 6 | 7 | class Cluster: 8 | """ 9 | Contains information about a cluster, i.e. a part of some bigger original 10 | atomistic system. 11 | 12 | This class is a simple data container where every attribute should be set 13 | only once, but does not have to be set in one go and can be insted built 14 | gradually. 15 | """ 16 | 17 | def __init__( 18 | self, 19 | indices=None, 20 | species=None, 21 | region=None, 22 | dimensionality=None, 23 | cell=None, 24 | system=None, 25 | distances=None, 26 | radii=None, 27 | bond_threshold=None, 28 | ): 29 | """ 30 | Args: 31 | indices(Iterable): Contains the indices of atoms belonging to this 32 | cluster. 33 | species(set): Contains the species of atoms belonging to this 34 | cluster. Each unique species should be include only once. 35 | region(Region): The Region instance from which this cluster was 36 | exracted from. 37 | dimensionality(int): The dimensionality of the cluster. Can be set 38 | initially here or calculated through the get_dimensionality-function. 39 | cell(ase.Atoms): The unit cell from which this cluster is 40 | constructed from. 41 | system(ase.Atoms): Reference to the original system which this 42 | cluster is a part of. 43 | distances(Distances): Contains cached distance information about 44 | this cluster. 45 | radii(ndarray): Contains the radii for each atom in the cluster as 46 | floating point numbers. 47 | """ 48 | if isinstance(indices, list): 49 | self.indices = indices 50 | else: 51 | self.indices = list(indices) 52 | self.species = species 53 | 54 | self._region = region 55 | self._dimensionality = dimensionality 56 | self._cell = cell 57 | self._system = system 58 | self._distances = distances 59 | self._radii = radii 60 | self._merged = False 61 | self._bond_threshold = bond_threshold 62 | self._distance_matrix_radii_mic = None 63 | 64 | def __len__(self): 65 | return len(self.indices) 66 | 67 | def _get_distance_matrix_radii_mic(self) -> np.ndarray: 68 | """Retrieves the distance matrix with subtracted radii for this cluster.""" 69 | if self._distance_matrix_radii_mic is None: 70 | self._distance_matrix_radii_mic = self._distances.dist_matrix_radii_mic[ 71 | np.ix_(self.indices, self.indices) 72 | ] 73 | return self._distance_matrix_radii_mic 74 | 75 | def get_cell(self) -> Atoms: 76 | """Used to fetch the prototypical cell for this cluster if one exists.""" 77 | if self._cell: 78 | return self._cell 79 | if self._region: 80 | return self._region.cell 81 | return None 82 | 83 | def get_atoms(self) -> Atoms: 84 | """Returns the ase.Atoms object for this cluster.""" 85 | return self._system[self.indices] 86 | 87 | def get_dimensionality(self) -> int: 88 | """Shortcut for fetching the dimensionality of the cluster using 89 | matid.geometry.get_dimensionality and the radii + bond thresholds that 90 | were used during the clustering. 91 | """ 92 | if self._dimensionality is None: 93 | self._dimensionality = matid.geometry.get_dimensionality( 94 | self.get_atoms(), 95 | self._bond_threshold, 96 | dist_matrix_radii_mic_1x=self._get_distance_matrix_radii_mic(), 97 | ) 98 | return self._dimensionality 99 | -------------------------------------------------------------------------------- /matid/core/__init__.py: -------------------------------------------------------------------------------- 1 | from matid.core.periodicfinder import PeriodicFinder 2 | -------------------------------------------------------------------------------- /matid/core/distances.py: -------------------------------------------------------------------------------- 1 | class Distances: 2 | """Container for all distance information that has been extracted from a 3 | system. 4 | """ 5 | 6 | def __init__( 7 | self, 8 | disp_tensor_mic, 9 | disp_factors, 10 | dist_matrix_mic, 11 | dist_matrix_radii_mic, 12 | cell_list=None, 13 | ): 14 | self.disp_tensor_mic = disp_tensor_mic 15 | self.disp_factors = disp_factors 16 | self.dist_matrix_mic = dist_matrix_mic 17 | self.dist_matrix_radii_mic = dist_matrix_radii_mic 18 | self.cell_list = cell_list 19 | -------------------------------------------------------------------------------- /matid/core/lattice.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class Lattice(object): 5 | """ 6 | A lattice object. Essentially a matrix with conversion matrices. In 7 | general, it is assumed that length units are in Angstroms and angles are in 8 | degrees unless otherwise stated. 9 | """ 10 | 11 | def __init__(self, matrix): 12 | """ 13 | Create a lattice from any sequence of 9 numbers. Note that the sequence 14 | is assumed to be read one row at a time. Each row represents one 15 | lattice vector. 16 | 17 | Args: 18 | matrix: Sequence of numbers in any form. Examples of acceptable 19 | input. 20 | i) An actual numpy array. 21 | ii) [[1, 0, 0], [0, 1, 0], [0, 0, 1]] 22 | iii) [1, 0, 0 , 0, 1, 0, 0, 0, 1] 23 | iv) (1, 0, 0, 0, 1, 0, 0, 0, 1) 24 | Each row should correspond to a lattice vector. 25 | E.g., [[10, 0, 0], [20, 10, 0], [0, 0, 30]] specifies a lattice 26 | with lattice vectors [10, 0, 0], [20, 10, 0] and [0, 0, 30]. 27 | """ 28 | m = np.array(matrix, dtype=np.float64).reshape((3, 3)) 29 | lengths = np.sqrt(np.sum(m**2, axis=1)) 30 | self._lengths = lengths 31 | self._matrix = m 32 | self._angles = None 33 | self._inv_matrix = None 34 | 35 | @property 36 | def matrix(self): 37 | """Copy of matrix representing the Lattice""" 38 | return np.copy(self._matrix) 39 | 40 | @property 41 | def inv_matrix(self): 42 | """ 43 | Inverse of lattice matrix. 44 | """ 45 | if self._inv_matrix is None: 46 | self._inv_matrix = np.linalg.inv(self._matrix) 47 | return self._inv_matrix 48 | 49 | def get_cartesian_coords(self, fractional_coords): 50 | """ 51 | Returns the cartesian coordinates given fractional coordinates. 52 | 53 | Args: 54 | fractional_coords (3x1 array): Fractional coords. 55 | 56 | Returns: 57 | Cartesian coordinates 58 | """ 59 | return np.dot(fractional_coords, self._matrix) 60 | 61 | def get_fractional_coords(self, cart_coords): 62 | """ 63 | Returns the fractional coordinates given cartesian coordinates. 64 | 65 | Args: 66 | cart_coords (3x1 array): Cartesian coords. 67 | 68 | Returns: 69 | Fractional coordinates. 70 | """ 71 | return np.dot(cart_coords, self.inv_matrix) 72 | 73 | @property 74 | def lengths(self): 75 | if self._lengths is None: 76 | lengths = np.linalg.norm(self._matrix, axis=1) 77 | self._lengths = lengths 78 | return self._lengths 79 | 80 | @property 81 | def angles(self): 82 | """ 83 | Returns the angles (alpha, beta, gamma) of the lattice. 84 | """ 85 | if self._angles is None: 86 | # Angles 87 | angles = np.zeros(3) 88 | for i in range(3): 89 | j = (i + 1) % 3 90 | k = (i + 2) % 3 91 | angles[i] = np.dot(self._matrix[j], self._matrix[k]) / ( 92 | self.lengths[j] * self.lengths[k] 93 | ) 94 | angles = np.clip(angles, -1.0, 1.0) 95 | self._angles = np.arccos(angles) * 180.0 / np.pi 96 | return self._angles 97 | 98 | @property 99 | def abc(self): 100 | """ 101 | Lengths of the lattice vectors, i.e. (a, b, c) 102 | """ 103 | return tuple(self.lengths) 104 | 105 | @property 106 | def alpha(self): 107 | """ 108 | Angle alpha of lattice in degrees. 109 | """ 110 | return self._angles[0] 111 | 112 | @property 113 | def beta(self): 114 | """ 115 | Angle beta of lattice in degrees. 116 | """ 117 | return self._angles[1] 118 | 119 | @property 120 | def gamma(self): 121 | """ 122 | Angle gamma of lattice in degrees. 123 | """ 124 | return self._angles[2] 125 | 126 | @property 127 | def volume(self): 128 | """ 129 | Volume of the unit cell. 130 | """ 131 | m = self._matrix 132 | return abs(np.dot(np.cross(m[0], m[1]), m[2])) 133 | 134 | @property 135 | def lengths_and_angles(self): 136 | """ 137 | Returns (lattice lengths, lattice angles). 138 | """ 139 | return tuple(self.lengths), tuple(self.angles) 140 | 141 | def get_reciprocal_lattice(self): 142 | """ 143 | Return the reciprocal lattice. Note that this is the standard 144 | reciprocal lattice used for solid state physics with a factor of 2 * 145 | pi. If you are looking for the crystallographic reciprocal lattice, 146 | use the reciprocal_lattice_crystallographic property. 147 | The property is lazily generated for efficiency. 148 | """ 149 | try: 150 | return self._reciprocal_lattice 151 | except AttributeError: 152 | v = self.inv_matrix.T 153 | self._reciprocal_lattice = Lattice(v * 2 * np.pi) 154 | return self._reciprocal_lattice 155 | 156 | def get_reciprocal_lattice_crystallographic(self): 157 | """ 158 | Returns the *crystallographic* reciprocal lattice, i.e., no factor of 159 | 2 * pi. 160 | """ 161 | return Lattice(self.get_reciprocal_lattice().matrix / (2 * np.pi)) 162 | -------------------------------------------------------------------------------- /matid/core/linkedunits.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from ase import Atoms 4 | 5 | from matid.data import constants 6 | 7 | import networkx as nx 8 | 9 | 10 | class LinkedUnitCollection(dict): 11 | """Represents a collection of similar cells that are connected in 3D space 12 | to form a structure, e.g. a surface. 13 | 14 | Essentially this is a special flavor of a regular dictionary: the keys can 15 | only be a sequence of three integers, and the values should be LinkedUnits. 16 | """ 17 | 18 | def __init__( 19 | self, 20 | system, 21 | cell, 22 | is_2d, 23 | ): 24 | """ 25 | Args: 26 | system(ase.Atoms): A reference to the system from which this 27 | LinkedUniCollection is gathered. 28 | cell(ase.Atoms): The prototype cell that is used in finding this 29 | region. 30 | is_2d(boolean): Whether this system represents a 2D-material or not. 31 | """ 32 | self.system = system 33 | self.cell = cell 34 | self.is_2d = is_2d 35 | self._search_graph = nx.MultiDiGraph() 36 | self._index_cell_map = {} 37 | self._used_points = set() 38 | self._basis_indices = None 39 | self._pos_tol = None 40 | dict.__init__(self) 41 | 42 | def __setitem__(self, key, value): 43 | # Transform key to tuple, check length 44 | try: 45 | key = tuple(key) 46 | except Exception: 47 | raise TypeError( 48 | "Could not transform the given key '{}' into tuple.".format(key) 49 | ) 50 | if len(key) != 3: 51 | raise ValueError( 52 | "The given coordinate '{}' does not have three components.".format(key) 53 | ) 54 | 55 | # Check that old unit is not overwritten 56 | if key in dict.keys(self): 57 | raise ValueError("Overriding existing units is not supported.") 58 | 59 | dict.__setitem__(self, key, value) 60 | 61 | def recreate_valid(self): 62 | """Used to recreate a new Atoms object, where each atom is created from 63 | a single unit cell. Atoms that were found not to belong to the periodic 64 | unit cell are not included. 65 | """ 66 | recreated_system = Atoms( 67 | cell=self.system.get_cell(), 68 | pbc=self.system.get_pbc(), 69 | ) 70 | for unit in self.values(): 71 | i_valid_indices = np.array([x for x in unit.basis_indices if x is not None]) 72 | if len(i_valid_indices) != 0: 73 | i_atoms = self.system[i_valid_indices] 74 | recreated_system += i_atoms 75 | 76 | return recreated_system 77 | 78 | def get_basis_indices(self): 79 | """Returns the indices of the atoms that were found to belong to a unit 80 | cell basis in the LinkedUnits in this collection as a single list. 81 | 82 | Returns: 83 | np.ndarray: Indices of the atoms in the original system that belong 84 | to this collection of LinkedUnits. 85 | """ 86 | if self._basis_indices is None: 87 | indices = set() 88 | for unit in self.values(): 89 | for index in unit.basis_indices: 90 | if index is not None: 91 | indices.add(index) 92 | self._basis_indices = indices 93 | 94 | return self._basis_indices 95 | 96 | def get_connected_directions(self): 97 | """During the tracking of the region the information about searches 98 | that matched an atom twice but with a negated multiplier are stored. 99 | """ 100 | G = self._search_graph 101 | dir_vectors = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) 102 | directions = set([0, 1, 2]) 103 | for node in G.nodes(): 104 | node_edges = G.in_edges(node, data=True) 105 | dir_to_remove = set() 106 | for direction in directions: 107 | dir_vector = dir_vectors[direction] 108 | positive = False 109 | negative = False 110 | for edge in node_edges: 111 | multiplier = edge[2]["multiplier"] 112 | if np.array_equal(multiplier, dir_vector): 113 | positive = True 114 | if np.array_equal(multiplier, -dir_vector): 115 | negative = True 116 | if positive and negative: 117 | break 118 | if positive and negative: 119 | dir_to_remove.add(direction) 120 | directions -= dir_to_remove 121 | 122 | connected_directions = np.array([True, True, True]) 123 | connected_directions[list(directions)] = False 124 | return connected_directions 125 | 126 | 127 | class LinkedUnit: 128 | """Represents a cell that is connected to others in 3D space to form a 129 | structure, e.g. a surface. 130 | """ 131 | 132 | def __init__( 133 | self, 134 | index, 135 | seed_index, 136 | seed_coordinate, 137 | cell, 138 | basis_indices, 139 | substitutions, 140 | vacancies, 141 | ): 142 | """ 143 | Args: 144 | index(tuple of three ints): 145 | seed_index(int): 146 | seed_coordinate(): 147 | cell(np.ndarray): Cell for this unit. Can change from unit to unit. 148 | basis_indices(sequence of ints and Nones): A sequence where there 149 | is an index or None for each atom that is supposed to be in the 150 | basis 151 | substitute_indices(sequence of ints and Nones): If basis atom is 152 | replaced by a foreign atom, the index of the substitutional atom is 153 | here. 154 | """ 155 | self.index = index 156 | self.seed_index = seed_index 157 | self.seed_coordinate = seed_coordinate 158 | self.cell = cell 159 | self.basis_indices = basis_indices 160 | self.substitutions = substitutions 161 | self.vacancies = vacancies 162 | 163 | 164 | class Substitution: 165 | """Represents a substitutional point defect.""" 166 | 167 | def __init__(self, index, position, original_element, substitutional_element): 168 | """ 169 | Args: 170 | index (int): Index of the subtitutional defect in the original 171 | system. 172 | original (ase.Atom): The original atom that was substituted 173 | substitution (ase.Atom): The substitutional atom 174 | 175 | """ 176 | self.index = index 177 | self.original_element = original_element 178 | self.substitutional_element = substitutional_element 179 | -------------------------------------------------------------------------------- /matid/core/system.py: -------------------------------------------------------------------------------- 1 | from ase import Atoms 2 | import matid.geometry 3 | import numpy as np 4 | 5 | 6 | class System(Atoms): 7 | def __init__( 8 | self, 9 | symbols=None, 10 | positions=None, 11 | numbers=None, 12 | tags=None, 13 | momenta=None, 14 | masses=None, 15 | magmoms=None, 16 | charges=None, 17 | scaled_positions=None, 18 | cell=None, 19 | pbc=None, 20 | celldisp=None, 21 | constraint=None, 22 | calculator=None, 23 | info=None, 24 | wyckoff_letters=None, 25 | equivalent_atoms=None, 26 | ): 27 | super(System, self).__init__( 28 | symbols, 29 | positions, 30 | numbers, 31 | tags, 32 | momenta, 33 | masses, 34 | magmoms, 35 | charges, 36 | scaled_positions, 37 | cell, 38 | pbc, 39 | celldisp, 40 | constraint, 41 | calculator, 42 | info, 43 | ) 44 | 45 | self.wyckoff_letters = wyckoff_letters 46 | self.equivalent_atoms = equivalent_atoms 47 | 48 | @staticmethod 49 | def from_atoms(atoms): 50 | """Creates a System object from ASE.Atoms object.""" 51 | system = System( 52 | positions=atoms.get_positions(), 53 | symbols=atoms.get_chemical_symbols(), 54 | cell=atoms.get_cell(), 55 | pbc=atoms.get_pbc(), 56 | ) 57 | return system 58 | 59 | def to_scaled(self, positions, wrap=False): 60 | """Used to transform a set of positions to the basis defined by the 61 | cell of this system. 62 | 63 | Args: 64 | positions (numpy.ndarray): The positions to scale 65 | wrap (numpy.ndarray): Whether the positions should be wrapped 66 | inside the cell. 67 | 68 | Returns: 69 | numpy.ndarray: The scaled positions 70 | """ 71 | return matid.geometry.to_scaled( 72 | self.get_cell(), positions, wrap, self.get_pbc() 73 | ) 74 | 75 | def to_cartesian(self, scaled_positions, wrap=False): 76 | """Used to transofrm a set of relative positions to the cartesian basis 77 | defined by the cell of this system. 78 | 79 | Args: 80 | positions (numpy.ndarray): The positions to scale 81 | wrap (numpy.ndarray): Whether the positions should be wrapped 82 | inside the cell. 83 | 84 | Returns: 85 | numpy.ndarray: The cartesian positions 86 | """ 87 | return matid.geometry.to_cartesian( 88 | self.get_cell(), scaled_positions, wrap, self.get_pbc() 89 | ) 90 | 91 | def translate(self, translation, relative=False): 92 | """Translates the positions by the given translation. 93 | 94 | Args: 95 | translation (1x3 numpy.array): The translation to apply. 96 | relative (bool): True if given translation is relative to cell 97 | vectors. 98 | """ 99 | matid.geometry.translate(self, translation, relative) 100 | 101 | def get_wyckoff_letters(self): 102 | """Returns a list of Wyckoff letters for the atoms in the system. This 103 | information is only available is explicitly set. 104 | 105 | Returns: 106 | np.ndarray: Wyckoff letters as a list of strings. 107 | """ 108 | return np.array(self.wyckoff_letters) 109 | 110 | def set_wyckoff_letters(self, wyckoff_letters): 111 | """Used to set the Wyckoff letters of for the atoms in this system. 112 | 113 | Args: 114 | wyckoff_letters(sequence of str): The Wyckoff letters for the atoms 115 | in this system. 116 | """ 117 | self.wyckoff_letters = np.array(wyckoff_letters) 118 | 119 | def get_equivalent_atoms(self): 120 | """Returns a list of indices marking the equivalence for the atoms in 121 | the system. This information is only available is explicitly set. 122 | 123 | Returns: 124 | np.ndarray: The equivalence information as a list of integers, 125 | where the same integer means equivalence and an integer is given 126 | for each atom. 127 | """ 128 | return np.array(self.equivalent_atoms) 129 | 130 | def set_equivalent_atoms(self, equivalent_atoms): 131 | """Used to set the list of indices marking the equivalence for the 132 | atoms for the atoms in this system. 133 | 134 | Args: 135 | equivalent_atoms(sequence of int): list of indices marking the 136 | equivalence for the atoms the atoms in this system. 137 | """ 138 | self.equivalent_atoms = np.array(equivalent_atoms) 139 | -------------------------------------------------------------------------------- /matid/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/matid/data/__init__.py -------------------------------------------------------------------------------- /matid/data/alphabet_data.py: -------------------------------------------------------------------------------- 1 | ALPHABET_POSITIONS = { 2 | "a": 1, 3 | "b": 2, 4 | "c": 3, 5 | "d": 4, 6 | "e": 5, 7 | "f": 6, 8 | "g": 7, 9 | "h": 8, 10 | "i": 9, 11 | "j": 10, 12 | "k": 11, 13 | "l": 12, 14 | "m": 13, 15 | "n": 14, 16 | "o": 15, 17 | "p": 16, 18 | "q": 17, 19 | "r": 18, 20 | "s": 19, 21 | "t": 20, 22 | "u": 21, 23 | "v": 22, 24 | "w": 23, 25 | "x": 24, 26 | "y": 25, 27 | "z": 26, 28 | "A": 27, 29 | } 30 | -------------------------------------------------------------------------------- /matid/data/constants.py: -------------------------------------------------------------------------------- 1 | # The variable SYMMETRY_TOL controls the precision used by spglib in order to 2 | # find symmetries 3 | SYMMETRY_TOL = 0.4 # unit: angstrom 4 | 5 | # The threshold for a system to be considered "flat". Used e.g. when 6 | # determining if a 2D structure is purely 2-dimensional to allow extra rigid 7 | # transformations that are improper in 3D but proper in 2D. 8 | FLAT_DIM_THRESHOLD = 0.1 9 | 10 | # An ordered list of Wyckoff letters 11 | WYCKOFF_LETTERS = list("abcdefghijklmnopqrstuvwxyzA") 12 | WYCKOFF_LETTER_POSITIONS = { 13 | letter: positions for positions, letter in enumerate(WYCKOFF_LETTERS) 14 | } 15 | 16 | # =============================================================================== 17 | # Constants for classification 18 | MAX_SINGLE_CELL_SIZE = 5 19 | MAX_2D_CELL_HEIGHT = 5 20 | MAX_CELL_SIZE = [12] 21 | REL_POS_TOL = [ 22 | 0.25, 23 | 0.75, 24 | ] # Position tolerances. Given relative to minimum distance between two atoms in the system. 25 | ANGLE_TOL = 20 26 | CLUSTER_THRESHOLD = 3.5 27 | CRYSTALLINITY_THRESHOLD = 0.25 28 | DELAUNAY_THRESHOLD = 1.5 29 | BOND_THRESHOLD = 0.75 30 | CHEM_SIMILARITY_THRESHOLD = 0.4 31 | CELL_SIZE_TOL = 0.25 32 | MIN_COVERAGE = 0.5 33 | -------------------------------------------------------------------------------- /matid/ext/celllist.h: -------------------------------------------------------------------------------- 1 | /*Copyright 2019 DScribe developers 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #ifndef CELLLIST_H 17 | #define CELLLIST_H 18 | 19 | #include 20 | #include 21 | 22 | namespace py = pybind11; 23 | using namespace std; 24 | 25 | struct CellListResult { 26 | vector indices; 27 | vector distances; 28 | vector distances_squared; 29 | vector> displacements; 30 | vector indices_original; 31 | vector> factors; 32 | }; 33 | 34 | /** 35 | * For calculating pairwise distances using a cell lists: 36 | * https://en.wikipedia.org/wiki/Cell_lists. 37 | */ 38 | class CellList { 39 | public: 40 | /** 41 | * Constructor 42 | * 43 | * @param positions Atomic positions in cartesian coordinates. 44 | * @param atomicNumbers Atomic numbers. 45 | */ 46 | CellList(py::array_t positions, py::array_t indices, py::array_t factors, double cutoff); 47 | /** 48 | * Get the indices of atoms within the radial cutoff distance from the 49 | * given position. 50 | * 51 | * @param x Cartesian x-coordinate. 52 | * @param y Cartesian y-coordinate. 53 | * @param z Cartesian z-coordinate. 54 | */ 55 | CellListResult get_neighbours_for_position(const double x, const double y, const double z); 56 | /** 57 | * Get the indices of atoms within the radial cutoff distance from the 58 | * given atomic index. The given index is not included in the returned 59 | * values. 60 | * 61 | * @param i Index of the atom for which neighbours are queried for. 62 | */ 63 | CellListResult get_neighbours_for_index(const int i); 64 | /** 65 | * Used to calculate the full displacement tensor in an efficient 66 | * manner. 67 | */ 68 | void get_displacement_tensor( 69 | py::array_t displacements, 70 | py::array_t distances, 71 | py::array_t factors, 72 | py::array_t original_indices, 73 | int n_atoms 74 | ); 75 | py::array_t indices_py; 76 | 77 | private: 78 | /** 79 | * Used to initialize the cell list. Querying for distances is only 80 | * possible after this initialization. 81 | */ 82 | void init(); 83 | 84 | // const py::detail::unchecked_reference positions; 85 | // const py::detail::unchecked_reference indices; 86 | // const py::detail::unchecked_reference factors; 87 | vector> positions; 88 | vector indices; 89 | vector> factors; 90 | const double cutoff; 91 | const double cutoffSquared; 92 | double xmin; 93 | double xmax; 94 | double ymin; 95 | double ymax; 96 | double zmin; 97 | double zmax; 98 | double dx; 99 | double dy; 100 | double dz; 101 | int nx; 102 | int ny; 103 | int nz; 104 | vector>>> bins; 105 | }; 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /matid/ext/ext.cpp: -------------------------------------------------------------------------------- 1 | /*Copyright 2019 DScribe developers 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #include 17 | #include // Enables easy access to numpy arrays 18 | #include // Enables automatic type conversion from C++ containers to python 19 | #include "celllist.h" 20 | #include "geometry.h" 21 | 22 | namespace py = pybind11; 23 | using namespace std; 24 | 25 | template 26 | using overload_cast_ = pybind11::detail::overload_cast_impl; 27 | 28 | // Notice that the name of the first argument to the module macro needs to 29 | // correspond to the file name! 30 | PYBIND11_MODULE(ext, m) { 31 | // Geometry 32 | m.def("extend_system", &extend_system, "Create a periodically extended system."); 33 | py::class_(m, "ExtendedSystem", py::module_local()) 34 | .def(py::init<>()) 35 | .def_readonly("positions", &ExtendedSystem::positions) 36 | .def_readonly("atomic_numbers", &ExtendedSystem::atomic_numbers) 37 | .def_readonly("indices", &ExtendedSystem::indices) 38 | .def_readonly("factors", &ExtendedSystem::factors); 39 | m.def("get_cell_list", &get_cell_list, "Get cell list for system."); 40 | m.def("get_displacement_tensor", &get_displacement_tensor, "Get displacement vectors respecting minimum image convention."); 41 | 42 | // CellList 43 | py::class_(m, "CellList", py::module_local()) 44 | .def(py::init, py::array_t, py::array_t, double>()) 45 | .def("get_neighbours_for_index", &CellList::get_neighbours_for_index) 46 | .def("get_neighbours_for_position", &CellList::get_neighbours_for_position); 47 | py::class_(m, "CellListResult", py::module_local()) 48 | .def(py::init<>()) 49 | .def_readonly("indices", &CellListResult::indices) 50 | .def_readonly("indices_original", &CellListResult::indices_original) 51 | .def_readonly("distances", &CellListResult::distances) 52 | .def_readonly("distances_squared", &CellListResult::distances_squared) 53 | .def_readonly("displacements", &CellListResult::displacements) 54 | .def_readonly("factors", &CellListResult::factors); 55 | } 56 | -------------------------------------------------------------------------------- /matid/ext/geometry.h: -------------------------------------------------------------------------------- 1 | /*Copyright 2019 DScribe developers 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | #ifndef GEOMETRY_H 17 | #define GEOMETRY_H 18 | 19 | #include 20 | #include 21 | #include 22 | #include "celllist.h" 23 | 24 | namespace py = pybind11; 25 | using namespace std; 26 | 27 | struct ExtendedSystem { 28 | py::array_t positions; 29 | py::array_t atomic_numbers; 30 | py::array_t indices; 31 | py::array_t factors; 32 | }; 33 | 34 | inline vector cross(const vector& a, const vector& b); 35 | inline double dot(const vector& a, const vector& b); 36 | inline double norm(const vector& a); 37 | 38 | /** 39 | * Used to periodically extend an atomic system in order to take into account 40 | * periodic copies beyond the given unit cell. 41 | * 42 | * @param positions Cartesian positions of the original system. 43 | * @param atomic_numbers Atomic numbers of the original system. 44 | * @param cell Unit cell of the original system. 45 | * @param pbc Periodic boundary conditions (array of three booleans) of the original system. 46 | * @param cutoff Radial cutoff value for determining extension size. 47 | * 48 | * @return Instance of ExtendedSystem. 49 | */ 50 | ExtendedSystem extend_system( 51 | py::array_t positions, 52 | py::array_t atomic_numbers, 53 | py::array_t cell, 54 | py::array_t pbc, 55 | double cutoff 56 | ); 57 | 58 | /** 59 | * Returns a CellList instance for the given atomic system. 60 | * 61 | * Note that you should control how large extension should be performed to take 62 | * into account the periodic boundary conditions, as the CellList internally 63 | * works with plain cartesian coordinates. 64 | */ 65 | CellList get_cell_list( 66 | py::array_t positions, 67 | py::array_t cell, 68 | py::array_t pbc, 69 | double extension, 70 | double cutoff 71 | ); 72 | 73 | /** 74 | * Calculates a pairwise displacement tensor (distance vectors) with a given 75 | * cutoff. 76 | */ 77 | void get_displacement_tensor( 78 | py::array_t displacements, 79 | py::array_t distances, 80 | py::array_t factors, 81 | py::array_t positions, 82 | py::array_t cell, 83 | py::array_t pbc, 84 | double cutoff, 85 | bool return_factors, 86 | bool return_distances 87 | ); 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /matid/geometry/__init__.py: -------------------------------------------------------------------------------- 1 | from matid.geometry.geometry import * 2 | -------------------------------------------------------------------------------- /matid/symmetry/__init__.py: -------------------------------------------------------------------------------- 1 | from matid.symmetry.wyckoffset import WyckoffSet 2 | from matid.symmetry.symmetryanalyzer import SymmetryAnalyzer 3 | -------------------------------------------------------------------------------- /matid/symmetry/wyckoffset.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class WyckoffSet: 5 | """Represents a group of atoms in a certain Wyckoff position, for a certain 6 | space group. 7 | """ 8 | 9 | def __init__( 10 | self, 11 | wyckoff_letter=None, 12 | atomic_number=None, 13 | element=None, 14 | indices=None, 15 | x=None, 16 | y=None, 17 | z=None, 18 | space_group=None, 19 | representative=None, 20 | multiplicity=None, 21 | ): 22 | """ 23 | Args: 24 | wyckoff_letter (str): The letter for the Wyckoff position. 25 | atomic_number (int): Atomic number stored in this Wyckoff position. 26 | element (str): The chemical symbol of the element. 27 | indices (list): Indices corresponding to atoms in this Wyckoff 28 | group in a structure. 29 | x (float): The free parameter for the a-vector. 30 | y (float): The free parameter for the b-vector. 31 | z (float): The free parameter for the c-vector. 32 | space_group (int): The space group number for this set. 33 | representative (str): Algebraic expression that represents this set: all 34 | other positions are generated from symmetry. 35 | multiplicity (int): The multiplicity of the set: how many atoms are 36 | in this set. 37 | """ 38 | self.wyckoff_letter = wyckoff_letter 39 | self.atomic_number = atomic_number 40 | self.element = element 41 | self.space_group = space_group 42 | self.indices = indices 43 | self.multiplicity = multiplicity 44 | self.representative = representative 45 | self.x = x 46 | self.y = y 47 | self.z = z 48 | 49 | def __repr__(self): 50 | msg = [] 51 | msg.append( 52 | "Wyckoff letter: {}, element: {}, multiplicity: {}, space group: {}".format( 53 | self.wyckoff_letter, self.element, self.multiplicity, self.space_group 54 | ) 55 | ) 56 | if self.x is not None: 57 | msg.append(", x: {}".format(self.x)) 58 | if self.y is not None: 59 | msg.append(", y: {}".format(self.y)) 60 | if self.z is not None: 61 | msg.append(", z: {}".format(self.z)) 62 | return "".join(msg) 63 | 64 | def __str__(self): 65 | return self.__repr__() 66 | 67 | def __eq__(self, other): 68 | if self.wyckoff_letter != other.wyckoff_letter: 69 | return False 70 | if self.atomic_number != other.atomic_number: 71 | return False 72 | if self.element != other.element: 73 | return False 74 | if self.space_group != other.space_group: 75 | return False 76 | if self.multiplicity != other.multiplicity: 77 | return False 78 | if self.x is not None and other.x is not None: 79 | if not np.isclose(self.x, other.x): 80 | return False 81 | else: 82 | if self.x != other.x: 83 | return False 84 | if self.y is not None and other.y is not None: 85 | if not np.isclose(self.y, other.y): 86 | return False 87 | else: 88 | if self.y != other.y: 89 | return False 90 | if self.z is not None and other.z is not None: 91 | if not np.isclose(self.z, other.z): 92 | return False 93 | else: 94 | if self.z != other.z: 95 | return False 96 | 97 | return True 98 | -------------------------------------------------------------------------------- /matid/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-coe/matid/642d3caa8bf6c048ee223454ab6b6b076946a069/matid/utils/__init__.py -------------------------------------------------------------------------------- /matid/utils/exceptions.py: -------------------------------------------------------------------------------- 1 | class MatIDError(Exception): 2 | def __init__(self, message, value=None): 3 | self.value = value 4 | Exception.__init__(self, message) 5 | 6 | 7 | class ClassificationError(MatIDError): 8 | """Indicates that there was an error in finding a surface.""" 9 | 10 | pass 11 | 12 | 13 | class CellNormalizationError(MatIDError): 14 | """For errors in finding the normalized cell.""" 15 | 16 | pass 17 | -------------------------------------------------------------------------------- /matid/utils/segfault_protect.py: -------------------------------------------------------------------------------- 1 | """Utility functions to protect main process against segfaults""" 2 | 3 | import signal 4 | 5 | 6 | def sig_handler(signum, frame): 7 | raise RuntimeError("SIGSEGV encountered.") 8 | 9 | 10 | def segfault_protect(function, *args, **kwargs): 11 | """Used to run a function in a separate process to catch RuntimeErrors such 12 | as segfaults. 13 | """ 14 | signal.signal(signal.SIGSEGV, sig_handler) 15 | result = function(*args) 16 | return result 17 | -------------------------------------------------------------------------------- /matid/utils/surfacegenerator.py: -------------------------------------------------------------------------------- 1 | import ase.build 2 | 3 | 4 | class SurfaceGenerator: 5 | """Used to generate different kind of cuts from a given crystal cell. 6 | 7 | This class uses the function ase.build.surface to construct a surface from 8 | an arbitrary unit cell given the surface direction as Miller indices. 9 | Discussion about this method and the theory behind this construction 10 | algorithm can be found at 11 | https://wiki.fysik.dtu.dk/ase/ase/build/surface.html#create-specific-non-common-surfaces 12 | """ 13 | 14 | def __init__(self): 15 | """ 16 | Args: 17 | atoms(ase.Atoms): The crystal system from which the surface is 18 | construced. 19 | """ 20 | 21 | def generate(self, atoms, miller_indices, layers, vacuum): 22 | """Given an arbitrary crystal lattice, transforms it to a conventional 23 | representation and from this conventional representation constructs a 24 | surface with the given Miller indices. 25 | 26 | Args: 27 | miller_indices(sequence of three ints): The surface normal 28 | corresponding to Miller indices (ijk). 29 | layers(int): The number of equivalent layers in the surface. Notice 30 | that this direction does not necessarily have to be orthogonal to 31 | the surface. 32 | vacuum(float): The amount of vacuum on both sides of the slab. 33 | """ 34 | # Build surface with ASE 35 | surface = ase.build.surface(atoms, miller_indices, layers, vacuum) 36 | 37 | return surface 38 | -------------------------------------------------------------------------------- /matid/utils/symmetry_info_generation/generate_symmetry_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script combines multiple pickle files into one python source file 3 | containing the symmetry information. 4 | """ 5 | 6 | import os 7 | import pickle 8 | import pprint 9 | import numpy as np 10 | 11 | 12 | def print_dict(d, level): 13 | out = [] 14 | out.append("{\n") 15 | for key in sorted(d.keys()): 16 | value = d[key] 17 | if isinstance(value, np.ndarray): 18 | value = ( 19 | "array(" 20 | + np.array2string( 21 | value, 22 | threshold=np.inf, 23 | max_line_width=np.inf, 24 | separator=",", 25 | sign=" ", 26 | prefix="", 27 | suffix="", 28 | ).replace("\n", "") 29 | + ")" 30 | ) 31 | elif isinstance(value, dict): 32 | value = print_dict(value, level + 1) 33 | else: 34 | value = repr(value) 35 | if isinstance(key, str): 36 | key = '"' + key + '"' 37 | out.append(" " * level + "{}: {},\n".format(key, value)) 38 | out.append(" " * (level - 1) + "}") 39 | return "".join(out) 40 | 41 | 42 | _directory = os.path.dirname(os.path.abspath(__file__)) 43 | 44 | CHIRALITY_PRESERVING_EUCLIDEAN_NORMALIZERS = {} 45 | batches = [0, 100, 200, 230] 46 | for i in range(len(batches) - 1): 47 | start = batches[i] + 1 48 | end = batches[i + 1] 49 | with open( 50 | f"chirality_preserving_euclidean_normalizers_{start}_{end}.pickle", "rb" 51 | ) as fin: 52 | CHIRALITY_PRESERVING_EUCLIDEAN_NORMALIZERS.update(pickle.load(fin)) 53 | 54 | # _space_group_info_filename = os.path.join(_directory, "space_group_info.pickle") 55 | # with open(_space_group_info_filename, "rb") as fin: 56 | # SPACE_GROUP_INFO = pickle.load(fin) 57 | 58 | # _translations_continuous_filename = os.path.join(_directory, "free_wyckoff_positions.pickle") 59 | # with open(_translations_continuous_filename, "rb") as fin: 60 | # WYCKOFF_POSITIONS = pickle.load(fin) 61 | 62 | # wyckoff_sets_path = os.path.join(_directory, "wyckoff_sets.pickle") 63 | # with open(wyckoff_sets_path, "rb") as fin: 64 | # wyckoff_sets = pickle.load(fin) 65 | 66 | with open("symmetry_data_new.py", "w") as fout: 67 | # header += "from numpy import array\n\n" 68 | # fout.write(header) 69 | # header_sgi = "SPACE_GROUP_INFO = " 70 | # fout.write(header_sgi + pprint.pformat(SPACE_GROUP_INFO, indent=4) + "\n\n") 71 | # header_tc = "WYCKOFF_POSITIONS = " 72 | # fout.write(header_tc + pprint.pformat(WYCKOFF_POSITIONS, indent=4) + "\n\n") 73 | header_norm = "CHIRALITY_PRESERVING_EUCLIDEAN_NORMALIZERS = " 74 | fout.write( 75 | header_norm 76 | + pprint.pformat(CHIRALITY_PRESERVING_EUCLIDEAN_NORMALIZERS, indent=4) 77 | + "\n\n" 78 | ) 79 | # header_wyckoff_sets = "WYCKOFF_SETS = " 80 | # fout.write(header_wyckoff_sets + print_dict(wyckoff_sets, 1)) 81 | -------------------------------------------------------------------------------- /matid/utils/symmetry_info_generation/query_continuous_translations.py: -------------------------------------------------------------------------------- 1 | """ 2 | For each spacegroup returns the lattice directions in which all the atoms can 3 | be moved without breaking the symmetry. 4 | """ 5 | 6 | import re 7 | import json 8 | import urllib.request 9 | from bs4 import BeautifulSoup 10 | 11 | 12 | regex = re.compile("([xyzrst\+,]+)") 13 | try: 14 | # Fetch data for each space group 15 | n_groups = 230 16 | groups = range(1, n_groups + 1) 17 | continuous_translation_dict = {} 18 | for space_group in groups: 19 | # Do a http get request for the data 20 | url = "http://www.cryst.ehu.es/cgi-bin/cryst/programs/nph-normsets?from=wycksets&gnum={}".format( 21 | space_group 22 | ) 23 | html_raw = urllib.request.urlopen(url).read() 24 | 25 | # Create the soup :) 26 | soup = BeautifulSoup(html_raw, "html.parser") 27 | center = soup.body.center 28 | tables = center.find_all("table", align="center") 29 | 30 | if len(tables) <= 2: 31 | continue 32 | table = tables[2] 33 | all_rows = table.find_all("tr", recursive=False) 34 | 35 | # See if there are continuous translations 36 | header = all_rows[0] 37 | titles = header("th") 38 | found = False 39 | for i, title in enumerate(titles): 40 | title_text = title.text 41 | if title_text == "Continuous Translations": 42 | found = True 43 | break 44 | 45 | if found: 46 | cont_trans = {"a": False, "b": False, "c": False} 47 | rows = all_rows[1:] 48 | for row in rows: 49 | tds = row.find_all("td", recursive=False) 50 | interpretation = tds[1] 51 | content = interpretation.text 52 | match = regex.match(content) 53 | if match: 54 | coords = match.groups()[0] 55 | components = coords.split(",") 56 | if len(components[0]) != 1: 57 | cont_trans["a"] = True 58 | if len(components[1]) != 1: 59 | cont_trans["b"] = True 60 | if len(components[2]) != 1: 61 | cont_trans["c"] = True 62 | 63 | continuous_translation_dict[space_group] = cont_trans 64 | 65 | print( 66 | "Space group {} has continuous translations: {}".format( 67 | space_group, cont_trans 68 | ) 69 | ) 70 | 71 | # Write the results as a pickle file 72 | with open("translations_continuous.json", "w") as fout: 73 | fout.write(json.dump(continuous_translation_dict)) 74 | 75 | except Exception: 76 | print(space_group) 77 | raise 78 | -------------------------------------------------------------------------------- /matid/utils/symmetry_info_generation/query_conventional_transform_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script is used to determine the transform from the system specififed by 3 | first Hall number in each space group, into the system that correponds to the 4 | conventional settings for that space group. 5 | 6 | This information is needed because the normalizers and Wyckoff positions in 7 | Bilbao Crystallographic Server are given in the conventional setttings only by 8 | default. 9 | """ 10 | 11 | import spglib 12 | from collections import defaultdict 13 | 14 | space_hall_map = defaultdict(list) 15 | 16 | for hall_number in range(1, 531): 17 | dataset = spglib.get_spacegroup_type(hall_number) 18 | number = dataset.number 19 | space_hall_map[number].append(hall_number) 20 | 21 | degenerate_spgs = [] 22 | for key, value in space_hall_map.items(): 23 | if len(value) == 1: 24 | continue 25 | 26 | degenerate_spgs.append(key) 27 | first_hall = value[0] 28 | dataset = spglib.get_spacegroup_type(first_hall) 29 | choice = dataset.choice 30 | 31 | # try: 32 | # origin = int(choice) 33 | # except ValueError as e: 34 | # if choice != "H": 35 | # print(choice) 36 | 37 | if choice == "": 38 | print(spglib.get_spacegroup_type(value[0])["choice"]) 39 | -------------------------------------------------------------------------------- /matid/utils/symmetry_info_generation/query_space_group_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | Goes through the spglib database for different Hall numbers and extracts space 3 | group specific intormation. The results are then written to a python file for 4 | later use. 5 | """ 6 | 7 | import spglib 8 | import pickle 9 | 10 | space_groups = {} 11 | space_group_database = {} 12 | 13 | for hall_number in range(1, 531): 14 | dataset = spglib.get_spacegroup_type(hall_number) 15 | number = dataset.number 16 | international_short = dataset.international_short 17 | 18 | # Check that the spglib data has no two different international symbols for 19 | # the same space group number 20 | old = space_groups.get(number) 21 | if old is not None: 22 | if old != international_short: 23 | raise LookupError("Two spacegroups have different point groups!") 24 | else: 25 | if number not in space_group_database: 26 | space_group_database[number] = {} 27 | 28 | # Point group. There actually seeems to be a bug in spglib 1.9.4, where 29 | # the Hermann-Mauguin point group symbol is in the plalce of Schonflies 30 | # data and vice versa. 31 | pointgroup = dataset.pointgroup_schoenflies 32 | space_group_database[number]["pointgroup"] = pointgroup 33 | 34 | # Crystal system 35 | crystal_systems = ( 36 | ("triclinic", 1, 2), 37 | ("monoclinic", 3, 15), 38 | ("orthorhombic", 16, 74), 39 | ("tetragonal", 75, 142), 40 | ("trigonal", 143, 167), 41 | ("hexagonal", 168, 194), 42 | ("cubic", 195, 230), 43 | ) 44 | crystal_system = None 45 | for system in crystal_systems: 46 | min_number = system[1] 47 | max_number = system[2] 48 | if number >= min_number and number <= max_number: 49 | crystal_system = system[0] 50 | break 51 | space_group_database[number]["crystal_system"] = crystal_system 52 | 53 | # Bravais lattice. 54 | lattice_type = international_short[0] 55 | crystal_system_map = { 56 | "triclinic": "a", 57 | "monoclinic": "m", 58 | "orthorhombic": "o", 59 | "tetragonal": "t", 60 | "trigonal": "h", 61 | "hexagonal": "h", 62 | "cubic": "c", 63 | } 64 | crystal_system_letter = crystal_system_map.get(crystal_system) 65 | space_group_database[number]["bravais_lattice"] = ( 66 | crystal_system_letter + lattice_type 67 | ) 68 | 69 | # Write the results as a pickle file 70 | with open("space_group_info.pickle", "wb") as fout: 71 | pickle.dump(space_group_database, fout) 72 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "pybind11~=2.13.6"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = 'matid' 7 | version = '2.1.5' 8 | description = 'MatID is a Python package for identifying and analyzing atomistic systems based on their structure.' 9 | readme = "README.md" 10 | authors = [{ name = "Lauri Himanen" }] 11 | license = { file = "LICENSE" } 12 | requires-python = ">=3.8" 13 | dependencies = [ 14 | "numpy", 15 | "ase", 16 | "spglib>=2.0.0", 17 | "scikit-learn", 18 | "networkx>=2.4", 19 | ] 20 | classifiers = [ 21 | "Development Status :: 5 - Production/Stable", 22 | "Intended Audience :: Developers", 23 | "Topic :: Scientific/Engineering :: Physics", 24 | "License :: OSI Approved :: Apache Software License", 25 | "Programming Language :: Python", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3 :: Only", 28 | "Programming Language :: Python :: 3.8", 29 | "Programming Language :: Python :: 3.9", 30 | "Programming Language :: Python :: 3.10", 31 | "Programming Language :: Python :: 3.11", 32 | "Programming Language :: Python :: 3.12", 33 | ] 34 | keywords = ['atoms', 'structure', 'materials', 'science', 'crystal', 'symmetry'] 35 | 36 | [project.urls] 37 | Documentation = 'https://nomad-coe.github.io/matid/' 38 | Source = "https://github.com/nomad-coe/matid" 39 | 40 | [project.optional-dependencies] 41 | dev = [ 42 | "coverage", 43 | "pytest", 44 | "ruff", 45 | "pydoc-markdown", 46 | "genbadge[coverage]", 47 | ] 48 | 49 | [tool.setuptools.packages.find] 50 | include = ["matid*"] 51 | exclude = ["tests*"] 52 | 53 | [tool.ruff] 54 | include = ["matid/*.py", "tests/*.py"] 55 | 56 | # Same as Black. 57 | line-length = 88 58 | indent-width = 4 59 | 60 | [tool.ruff.lint] 61 | select = [ 62 | "E", # pycodestyle 63 | "W", # pycodestyle 64 | "PL", # pylint 65 | ] 66 | ignore = [ 67 | "E501", # Line too long ({width} > {limit} characters) 68 | "E701", # Multiple statements on one line (colon) 69 | "E731", # Do not assign a lambda expression, use a def 70 | "E402", # Module level import not at top of file 71 | "W605", # Invalid escape sequence 72 | "PLR0911", # Too many return statements 73 | "PLR0912", # Too many branches 74 | "PLR0913", # Too many arguments in function definition 75 | "PLR0915", # Too many statements 76 | "PLR2004", # Magic value used instead of constant 77 | "PLW0603", # Using the global statement 78 | "PLW2901", # redefined-loop-name 79 | "PLR1714", # consider-using-in 80 | "PLR5501", # else-if-used 81 | ] 82 | fixable = ["ALL"] 83 | 84 | [tool.ruff.format] 85 | # indent with spaces, rather than tabs. 86 | indent-style = "space" 87 | 88 | # Like Black, respect magic trailing commas. 89 | skip-magic-trailing-comma = false 90 | 91 | # Like Black, automatically detect the appropriate line ending. 92 | line-ending = "auto" 93 | -------------------------------------------------------------------------------- /reports/coverage/coverage-badge.svg: -------------------------------------------------------------------------------- 1 | coverage: 85.95%coverage85.95% -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | . 2 | -------------------------------------------------------------------------------- /scripts/doc_deploy.sh: -------------------------------------------------------------------------------- 1 | # Deploys the documentation to the gh-pages branch of the repo 2 | cd ../docs/ 3 | USE_SSH=true yarn deploy -------------------------------------------------------------------------------- /scripts/doc_reference.sh: -------------------------------------------------------------------------------- 1 | # Used to build the documentation using PyDoc: 2 | # https://niklasrosenstein.github.io/pydoc-markdown/ 3 | 4 | # SBC Class 5 | cd .. 6 | pydoc-markdown -m matid.clustering.sbc docs/pydoc-markdown.yml > docs/docs/reference/sbc.md 7 | echo "--- 8 | sidebar_position: 1 9 | sidebar_label: SBC Class 10 | --- 11 | $(cat docs/docs/reference/sbc.md)" > docs/docs/reference/sbc.md 12 | 13 | # Cluster Class 14 | pydoc-markdown -m matid.clustering.cluster docs/pydoc-markdown.yml > docs/docs/reference/cluster.md 15 | echo "--- 16 | sidebar_position: 2 17 | sidebar_label: Cluster Class 18 | --- 19 | $(cat docs/docs/reference/cluster.md)" > docs/docs/reference/cluster.md 20 | 21 | # SymmetryAnalyzer Class 22 | pydoc-markdown -m matid.symmetry.symmetryanalyzer docs/pydoc-markdown.yml > docs/docs/reference/symmetryanalyzer.md 23 | echo "--- 24 | sidebar_position: 3 25 | sidebar_label: SymmetryAnalyzer Class 26 | --- 27 | $(cat docs/docs/reference/symmetryanalyzer.md)" > docs/docs/reference/symmetryanalyzer.md 28 | 29 | # Geometry Module 30 | pydoc-markdown -m matid.geometry.geometry docs/pydoc-markdown.yml > docs/docs/reference/geometry.md 31 | echo "--- 32 | sidebar_position: 4 33 | sidebar_label: Geometry Module 34 | --- 35 | # Geometry Module 36 | $(cat docs/docs/reference/geometry.md)" > docs/docs/reference/geometry.md 37 | -------------------------------------------------------------------------------- /scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd .. 3 | ruff format --check -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd .. 3 | ruff check 4 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | cd .. 2 | rm -rf dist 3 | pipx run build --sdist 4 | # Before uploading with twine, fetch the wheels from github action arfifact and 5 | # put them inside the 'dist' folder 6 | # pipx run twine upload dist/* 7 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | cd .. 2 | python -m pip install --upgrade pip 3 | pip install .[dev] 4 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd ../tests 3 | export COVERAGE_FILE="../reports/coverage/.coverage" 4 | coverage run --source="matid" testrunner.py 5 | unittest=$? 6 | coverage run -m --source="matid" --append pytest 7 | pytest=$? 8 | if [ "$unittest" != 0 ] || [ "$pytest" != 0 ]; then 9 | exit 1 10 | fi 11 | cd ../reports/coverage 12 | export COVERAGE_FILE=".coverage" 13 | coverage xml -o coverage.xml 14 | genbadge coverage -i coverage.xml -o coverage-badge.svg 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import platform 4 | from distutils.ccompiler import new_compiler 5 | from distutils.sysconfig import customize_compiler 6 | from setuptools import setup, Extension 7 | from subprocess import getoutput 8 | 9 | 10 | def using_clang(): 11 | """Will we be using a clang compiler?""" 12 | compiler = new_compiler() 13 | customize_compiler(compiler) 14 | compiler_ver = getoutput("{0} -v".format(compiler.compiler[0])) 15 | return "clang" in compiler_ver 16 | 17 | 18 | class get_pybind_include(object): 19 | """Helper class to determine the pybind11 include path 20 | The purpose of this class is to postpone importing pybind11 21 | until it is actually installed, so that the ``get_include()`` 22 | method can be invoked.""" 23 | 24 | def __init__(self, user=False): 25 | self.user = user 26 | 27 | def __str__(self): 28 | import pybind11 29 | 30 | return pybind11.get_include(self.user) 31 | 32 | 33 | cpp_extra_link_args = [] 34 | cpp_extra_compile_args = [ 35 | "-std=c++11", # C++11 36 | "-O3", # O3 optimizations 37 | ] 38 | 39 | # Needed to specify C++ runtime library on OSX. This solution is replicated 40 | # from the setup.py of mdanalysis 41 | if platform.system() == "Darwin" and using_clang(): 42 | cpp_extra_compile_args.append("-stdlib=libc++") 43 | cpp_extra_compile_args.append("-mmacosx-version-min=10.9") 44 | cpp_extra_link_args.append("-stdlib=libc++") 45 | cpp_extra_link_args.append("-mmacosx-version-min=10.7") 46 | 47 | extensions = [ 48 | Extension( 49 | "matid.ext", 50 | [ 51 | "matid/ext/ext.cpp", 52 | "matid/ext/geometry.cpp", 53 | "matid/ext/celllist.cpp", 54 | ], 55 | include_dirs=[ 56 | # Path to pybind11 headers 57 | "matid/ext", 58 | get_pybind_include(), 59 | get_pybind_include(user=True), 60 | ], 61 | language="c++", 62 | extra_compile_args=cpp_extra_compile_args 63 | + ["-fvisibility=hidden"], # the -fvisibility flag is needed by pybind11 64 | extra_link_args=cpp_extra_link_args, 65 | ) 66 | ] 67 | 68 | if __name__ == "__main__": 69 | setup(ext_modules=extensions) 70 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.random import default_rng 3 | from ase import Atoms 4 | from ase.build import surface as ase_surface 5 | import ase.build 6 | 7 | 8 | def create_graphene(): 9 | system = Atoms( 10 | symbols=["C", "C"], 11 | cell=np.array( 12 | ( 13 | [2.4595121467478055, 0.0, 0.0], 14 | [-1.2297560733739028, 2.13, 0.0], 15 | [0.0, 0.0, 20.0], 16 | ) 17 | ), 18 | scaled_positions=np.array(([1 / 3, 2 / 3, 0.5], [2 / 3, 1 / 3, 0.5])), 19 | pbc=[True, True, False], 20 | ) 21 | return system 22 | 23 | 24 | def create_mos2(): 25 | system = ase.build.mx2( 26 | formula="MoS2", kind="2H", a=3.18, thickness=3.19, size=(1, 1, 1), vacuum=0 27 | ) 28 | return system 29 | 30 | 31 | def create_si(cubic=True): 32 | system = ase.build.bulk( 33 | "Si", 34 | crystalstructure="diamond", 35 | a=5.430710, 36 | cubic=cubic, 37 | ) 38 | return system 39 | 40 | 41 | def create_fe(cubic=True): 42 | system = ase.build.bulk( 43 | "Fe", 44 | crystalstructure="bcc", 45 | a=2.834, 46 | cubic=cubic, 47 | ) 48 | return system 49 | 50 | 51 | def create_sic(cubic=True): 52 | system = ase.build.bulk( 53 | "SiC", 54 | crystalstructure="zincblende", 55 | a=4.329, 56 | cubic=cubic, 57 | ) 58 | return system 59 | 60 | 61 | def rattle(atoms, displacement=0.08): 62 | rng = default_rng(seed=7) 63 | noise = rng.random((len(atoms), 3)) - 0.5 64 | lengths = np.linalg.norm(noise, axis=1) 65 | noise /= np.expand_dims(lengths, 1) 66 | noise *= displacement 67 | atoms_copy = atoms.copy() 68 | atoms_copy.set_positions(atoms_copy.get_positions() + noise) 69 | return atoms_copy 70 | 71 | 72 | def surface(conv_cell, indices, layers=[3, 3, 2], vacuum=10): 73 | surface = ase_surface(conv_cell, indices, layers[2], vacuum=vacuum, periodic=True) 74 | surface *= [layers[0], layers[1], 1] 75 | return surface 76 | 77 | 78 | def stack(a, b, axis=2, distance=3, vacuum=10): 79 | a_pos = a.get_positions()[:, axis] 80 | a_max = np.max(a_pos) 81 | a_min = np.min(a_pos) 82 | b_pos = b.get_positions()[:, axis] 83 | b_max = np.max(b_pos) 84 | b_min = np.min(b_pos) 85 | a_shift = np.zeros((len(a), 3)) 86 | a_shift[:, axis] += -a_min 87 | b_shift = np.zeros((len(b), 3)) 88 | b_shift[:, axis] += -b_min + (a_max - a_min) + distance 89 | a.translate(a_shift) 90 | b.translate(b_shift) 91 | stacked = a + b 92 | cell = a.get_cell() 93 | axis_new = cell[axis, :] 94 | axis_norm = np.linalg.norm(axis_new) 95 | axis_new = axis_new / axis_norm * (a_max - a_min + b_max - b_min + distance) 96 | cell[axis, :] = axis_new 97 | stacked.set_cell(cell) 98 | ase.build.add_vacuum(stacked, vacuum) 99 | return stacked 100 | 101 | 102 | def assert_topology(results, expected): 103 | # Check that correct clusters are found 104 | assert len(expected) == len(results) 105 | cluster_map = {tuple(sorted(x.indices)): x for x in results} 106 | for cluster_expected in expected: 107 | cluster = cluster_map[tuple(sorted(cluster_expected.indices))] 108 | assert cluster.get_dimensionality() == cluster_expected.get_dimensionality() 109 | -------------------------------------------------------------------------------- /tests/data/R6JuJXj20goPQ0vv6aAVYpNyuwGgN+P_PaYo5EiiPChgUe9B6JnTX6BcOwt.xyz: -------------------------------------------------------------------------------- 1 | 56 2 | Lattice="6.050002408539566 0.0 0.0 0.0 7.800003105227187 0.0 0.0 0.0 24.871328901490003" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | C 5.09482356 3.94598862 18.49431433 6 4 | C 5.03305347 5.31275985 16.08613360 6 5 | C 4.84648847 1.20165102 23.21732331 6 6 | C 4.82335313 1.88149436 22.01961841 6 7 | C 3.90022943 4.48314958 18.02848284 6 8 | C 3.87022506 5.16289135 16.82505151 6 9 | C 6.00343892 2.05156297 21.29662256 6 10 | C 6.04162617 0.67672869 23.69079719 6 11 | C 0.21773714 4.10930393 17.75981855 6 12 | C 0.17835718 4.78601731 16.55875539 6 13 | C 0.40901395 0.80823772 1.07526662 6 14 | C 0.84522386 1.26288979 16.82505151 6 15 | C 0.87522822 0.58314803 18.02848284 6 16 | C 1.14366921 1.50894012 21.76985956 6 17 | C 1.16664155 0.82644768 22.96962025 6 18 | C 1.79835192 5.78149591 22.01961841 6 19 | C 1.82148726 5.10165257 23.21732331 6 20 | C 2.06982236 0.04598707 18.49431433 6 21 | C 2.00805227 1.41275830 16.08613360 6 22 | C 2.97843385 5.95156452 21.29662256 6 23 | C 3.01662327 4.57673024 23.69079719 6 24 | C 3.24273835 0.20930238 17.75981855 6 25 | C 3.20335839 0.88601576 16.55875539 6 26 | C 3.43401516 4.70823927 1.07526662 6 27 | C 4.16867042 5.40894168 21.76985956 6 28 | C 4.19164275 4.72644923 22.96962025 6 29 | H 5.01164446 5.85391857 15.13360594 1 30 | H 3.94189390 1.08905697 23.82749066 1 31 | H 3.90386305 2.32717599 21.62466380 1 32 | H 3.00371090 4.35238958 18.64640364 1 33 | H 2.93247919 5.60400498 16.47072311 1 34 | H 5.72786133 1.62379877 1.25404857 1 35 | H 6.02871210 0.45238803 18.64640364 1 36 | H 5.95748039 1.70400342 16.47072311 1 37 | H 4.42541409 5.16718280 0.88238570 1 38 | H 5.06864760 5.53344810 21.15582519 1 39 | H 5.11310195 4.27123035 23.34931173 1 40 | H 0.46486182 0.16621427 1.96830837 1 41 | H 0.87886184 6.22717754 21.62466380 1 42 | H 0.91689270 4.98905852 23.82749066 1 43 | H 1.09502132 4.89482310 15.96663657 1 44 | H 1.14207237 3.66485799 18.14510446 1 45 | H 1.40041289 1.26718125 0.88238570 1 46 | H 1.98664326 1.95391701 15.13360594 1 47 | H 2.04364639 1.63344655 21.15582519 1 48 | H 2.08810074 0.37122880 23.34931173 1 49 | H 2.70286012 5.52380032 1.25404857 1 50 | H 3.48986302 4.06621582 1.96830837 1 51 | H 4.12002252 0.99482155 15.96663657 1 52 | H 4.16707358 7.56485955 18.14510446 1 53 | N 0.04442369 2.76489020 20.10172880 7 54 | N 1.98360877 7.13653273 19.69397304 7 55 | N 3.06942078 6.66489175 20.10172880 7 56 | N 5.00860997 3.23653118 19.69397304 7 57 | O 0.00000000 0.00000000 0.00000000 8 58 | O 3.02500121 3.90000155 0.00000000 8 59 | -------------------------------------------------------------------------------- /tests/data/RDtJ5cTyLBPt4PA182VbCzoCxf5Js+P8Wnwz4dfyea6UAD0WEBadXv83wyf.xyz: -------------------------------------------------------------------------------- 1 | 49 2 | Lattice="4.7077 -2.718 0.0 0.0 8.15225 0.0 0.0 0.0 50.0" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | H 2.96075900 2.46751200 13.53021700 1 4 | C 2.02010900 1.97524400 13.25604000 6 5 | H 1.19497800 2.51999600 13.73531200 1 6 | H 1.20509800 -0.07045600 13.51277700 1 7 | H 2.98080000 0.02035500 13.47088500 1 8 | C 2.07106900 0.54855100 13.78514400 6 9 | H 2.09396600 0.59802800 14.89011600 1 10 | C 2.40831900 4.23903000 12.14254600 6 11 | H 2.47661700 4.42711900 13.22088300 1 12 | H -0.01737600 -0.02420600 11.72421400 1 13 | H -0.03764100 2.68613200 11.81726700 1 14 | H 0.04931700 5.49023000 11.77128100 1 15 | H 2.38739800 -1.41274200 11.72356200 1 16 | Ru 1.58952600 -0.02486900 10.67460800 44 17 | Ru 1.67353900 2.64366000 11.11846200 44 18 | Ru 1.60918100 5.39771800 10.75330500 44 19 | Ru 3.95756700 -1.32942700 10.71463500 44 20 | Ru 3.91181800 1.31380300 10.68182000 44 21 | Ru 3.89202600 4.05535500 10.78709000 44 22 | Ru 0.80471900 1.37238500 8.59329000 44 23 | Ru 0.78789100 4.07927700 8.58795800 44 24 | Ru 0.80087600 6.82770700 8.54834700 44 25 | Ru 3.14970000 0.00463800 8.61390100 44 26 | Ru 3.12183000 2.72641400 8.61972300 44 27 | Ru 3.14238900 5.42538400 8.59940800 44 28 | Ru 3.92480000 1.35864000 6.45326000 44 29 | Ru 1.56909000 0.00199000 6.45326000 44 30 | Ru 1.56909000 2.71871000 6.45326000 44 31 | Ru 1.56909000 5.43543000 6.45326000 44 32 | Ru 3.92480000 -1.35807000 6.45326000 44 33 | Ru 3.92480000 4.07536000 6.45326000 44 34 | Ru 3.13863000 2.71485000 4.30282000 44 35 | Ru 0.78292000 1.35820000 4.30282000 44 36 | Ru 0.78292000 4.07492000 4.30282000 44 37 | Ru 0.78292000 6.79163000 4.30282000 44 38 | Ru 3.13863000 -0.00187000 4.30282000 44 39 | Ru 3.13863000 5.43156000 4.30282000 44 40 | Ru 1.56909000 0.00199000 2.15044000 44 41 | Ru 1.56909000 2.71871000 2.15044000 44 42 | Ru 1.56909000 5.43543000 2.15044000 44 43 | Ru 3.92480000 -1.35807000 2.15044000 44 44 | Ru 3.92480000 1.35864000 2.15044000 44 45 | Ru 3.92480000 4.07536000 2.15044000 44 46 | Ru 0.78292000 1.35820000 0.00000000 44 47 | Ru 0.78292000 4.07492000 0.00000000 44 48 | Ru 0.78292000 6.79163000 0.00000000 44 49 | Ru 3.13863000 -0.00187000 0.00000000 44 50 | Ru 3.13863000 2.71485000 0.00000000 44 51 | Ru 3.13863000 5.43156000 0.00000000 44 52 | -------------------------------------------------------------------------------- /tests/data/RDtJ5cTyLBPt4PA182VbCzoCxf5Js+PEzXqLISX8Pam-HlJMxeLc86lcKgf.xyz: -------------------------------------------------------------------------------- 1 | 50 2 | Lattice="4.7077 -2.718 0.0 0.0 8.15225 0.0 0.0 0.0 50.0" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | H 3.08102700 1.86839800 13.34796300 1 4 | C 2.33222600 2.58103400 13.74452600 6 5 | H 2.78002900 3.07043400 14.62578500 1 6 | H 0.16939000 2.23933100 14.36472800 1 7 | H 0.98642400 0.79484400 13.71135000 1 8 | C 1.14696600 1.72614600 14.28703300 6 9 | H 1.42805900 1.42376500 15.31423600 1 10 | C 2.21257500 3.71856800 12.68517200 6 11 | H 1.60500200 4.57360900 13.02298500 1 12 | H 3.25818300 4.17269900 12.66397000 1 13 | H 0.04023100 0.12437200 11.71618700 1 14 | H 0.01045100 2.77230600 11.75508900 1 15 | H -0.00955800 5.48288500 11.70405300 1 16 | H 2.42167900 -1.23093900 11.74632800 1 17 | Ru 1.58015100 0.11475900 10.69087700 44 18 | Ru 1.59154200 2.86718700 10.79246200 44 19 | Ru 1.55224700 5.55089900 10.67439000 44 20 | Ru 3.93533600 -1.24524300 10.71308300 44 21 | Ru 3.91161000 1.44503200 10.67895100 44 22 | Ru 3.90534600 4.14764800 10.71363800 44 23 | Ru 0.78331700 1.40373100 8.56067200 44 24 | Ru 0.78992300 4.10656700 8.63054700 44 25 | Ru 0.78221800 6.85482300 8.64034900 44 26 | Ru 3.13791100 0.04906200 8.58109000 44 27 | Ru 3.11739200 2.76867000 8.60344600 44 28 | Ru 3.13223100 5.49139800 8.57947600 44 29 | Ru 3.92480000 1.35864000 6.45326000 44 30 | Ru 1.56909000 0.00199000 6.45326000 44 31 | Ru 1.56909000 2.71871000 6.45326000 44 32 | Ru 1.56909000 5.43543000 6.45326000 44 33 | Ru 3.92480000 -1.35807000 6.45326000 44 34 | Ru 3.92480000 4.07536000 6.45326000 44 35 | Ru 3.13863000 2.71485000 4.30282000 44 36 | Ru 0.78292000 1.35820000 4.30282000 44 37 | Ru 0.78292000 4.07492000 4.30282000 44 38 | Ru 0.78292000 6.79163000 4.30282000 44 39 | Ru 3.13863000 -0.00187000 4.30282000 44 40 | Ru 3.13863000 5.43156000 4.30282000 44 41 | Ru 1.56909000 0.00199000 2.15044000 44 42 | Ru 1.56909000 2.71871000 2.15044000 44 43 | Ru 1.56909000 5.43543000 2.15044000 44 44 | Ru 3.92480000 -1.35807000 2.15044000 44 45 | Ru 3.92480000 1.35864000 2.15044000 44 46 | Ru 3.92480000 4.07536000 2.15044000 44 47 | Ru 0.78292000 1.35820000 0.00000000 44 48 | Ru 0.78292000 4.07492000 0.00000000 44 49 | Ru 0.78292000 6.79163000 0.00000000 44 50 | Ru 3.13863000 -0.00187000 0.00000000 44 51 | Ru 3.13863000 2.71485000 0.00000000 44 52 | Ru 3.13863000 5.43156000 0.00000000 44 53 | -------------------------------------------------------------------------------- /tests/data/RDtJ5cTyLBPt4PA182VbCzoCxf5Js+PFw_-OtcPJ5og8XMItaAAFYhQUaY6.xyz: -------------------------------------------------------------------------------- 1 | 42 2 | Lattice="4.7077 -2.718 0.0 0.0 8.15225 0.0 0.0 0.0 50.0" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | H 2.17112900 1.60947100 12.88270500 1 4 | H 3.06151900 2.52156900 14.10246700 1 5 | C 2.25402300 2.61532900 13.35414500 6 6 | H 1.30602900 2.79048900 13.88885600 1 7 | C 2.53537900 3.73761000 12.33849500 6 8 | H 2.37033800 4.72319800 12.83866700 1 9 | Ru 1.54761200 -0.04645900 10.65377100 44 10 | Ru 1.54246900 2.75059400 10.70572400 44 11 | Ru 1.57593400 5.40037200 10.69366900 44 12 | Ru 3.94474100 -1.36131400 10.66818700 44 13 | Ru 3.91960600 1.28806400 10.66223000 44 14 | Ru 3.87100900 4.01563700 10.77115100 44 15 | Ru 0.77446800 1.32185000 8.58130700 44 16 | Ru 0.79052100 4.05742600 8.62541600 44 17 | Ru 0.79528000 6.77981800 8.60189500 44 18 | Ru 3.14055700 -0.02358500 8.61473200 44 19 | Ru 3.14729600 2.70151100 8.60401900 44 20 | Ru 3.15880100 5.40687300 8.62157400 44 21 | Ru 3.92480000 1.35864000 6.45326000 44 22 | Ru 1.56909000 0.00199000 6.45326000 44 23 | Ru 1.56909000 2.71871000 6.45326000 44 24 | Ru 1.56909000 5.43543000 6.45326000 44 25 | Ru 3.92480000 -1.35807000 6.45326000 44 26 | Ru 3.92480000 4.07536000 6.45326000 44 27 | Ru 3.13863000 2.71485000 4.30282000 44 28 | Ru 0.78292000 1.35820000 4.30282000 44 29 | Ru 0.78292000 4.07492000 4.30282000 44 30 | Ru 0.78292000 6.79163000 4.30282000 44 31 | Ru 3.13863000 -0.00187000 4.30282000 44 32 | Ru 3.13863000 5.43156000 4.30282000 44 33 | Ru 1.56909000 0.00199000 2.15044000 44 34 | Ru 1.56909000 2.71871000 2.15044000 44 35 | Ru 1.56909000 5.43543000 2.15044000 44 36 | Ru 3.92480000 -1.35807000 2.15044000 44 37 | Ru 3.92480000 1.35864000 2.15044000 44 38 | Ru 3.92480000 4.07536000 2.15044000 44 39 | Ru 0.78292000 1.35820000 0.00000000 44 40 | Ru 0.78292000 4.07492000 0.00000000 44 41 | Ru 0.78292000 6.79163000 0.00000000 44 42 | Ru 3.13863000 -0.00187000 0.00000000 44 43 | Ru 3.13863000 2.71485000 0.00000000 44 44 | Ru 3.13863000 5.43156000 0.00000000 44 45 | -------------------------------------------------------------------------------- /tests/data/RJv-r5Vwf6ypWElBSq_hTCOaxEU89+PDLFIM7Xvy9JaEqwS72kDtDr_Szhp.xyz: -------------------------------------------------------------------------------- 1 | 12 2 | Lattice="4.359520614662661 0.0 0.0 0.0 2.5169784848307883 0.0 0.0 0.0 18.521202373450002" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | C 0.00000567 2.51683989 1.42370948 6 4 | C 2.90625407 2.51693829 1.92277952 6 5 | C 0.72649064 1.25848135 1.92275394 6 6 | C 2.17977582 1.25837043 1.42372279 6 7 | F 4.35936862 0.00016301 0.04996878 9 8 | F 2.90602892 0.00015887 3.29639978 9 9 | F 0.72624497 1.25881168 3.29637662 9 10 | F 2.17966488 1.25881665 0.04998014 9 11 | B 1.45310438 0.00004079 6.47146305 5 12 | B 3.63287142 1.25853165 6.47146305 5 13 | N 0.00008247 2.51694202 6.47146305 7 14 | N 2.17984891 1.25845414 6.47146305 7 15 | -------------------------------------------------------------------------------- /tests/data/RJv-r5Vwf6ypWElBSq_hTCOaxEU89+PKPif9Fqbl30oVX-710UwCHGMd83y.xyz: -------------------------------------------------------------------------------- 1 | 8 2 | Lattice="4.359520614662661 0.0 0.0 0.0 2.5169784848307883 0.0 0.0 0.0 18.521202373450002" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | C 0.00000567 -0.00013860 7.45632968 6 4 | C 2.90625407 -0.00004019 7.95539972 6 5 | C 0.72649064 1.25848135 7.95537414 6 6 | C 2.17977582 1.25837043 7.45634300 6 7 | F -0.00015199 0.00016301 6.08258898 9 8 | F 2.90602892 0.00015887 9.32901998 9 9 | F 0.72624497 1.25881168 9.32899682 9 10 | F 2.17966488 1.25881665 6.08260034 9 11 | -------------------------------------------------------------------------------- /tests/data/RJv-r5Vwf6ypWElBSq_hTCOaxEU89+PgZTqAjcn_4hHS3fozZkAI0Jxtdas.xyz: -------------------------------------------------------------------------------- 1 | 2 2 | Lattice="2.516984993710479 0.0 0.0 -1.2584924968552396 2.1797729440831213 0.0 0.0 0.0 10.5835442134" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | B 0.00000000 0.00000000 0.00000000 5 4 | N -0.00000001 1.45318197 0.00000000 7 5 | -------------------------------------------------------------------------------- /tests/data/ROHGEranIWm-gnS6jhQaLZRORWDKx+Pbco91p05ftuJQ38__Y0_TDg9tNIy.xyz: -------------------------------------------------------------------------------- 1 | 36 2 | Lattice="3.011582 0.0 0.0 0.0 12.04344 0.0 0.0 0.0 19.61734" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | H 0.64082300 7.72556700 8.98577100 1 4 | H 1.59175000 6.43338800 8.73761900 1 5 | H 2.55989000 4.56751800 8.89534800 1 6 | H 2.79736100 5.80478400 7.33271900 1 7 | O 3.01047200 0.00046400 6.38483600 8 8 | O 2.99672800 2.92816600 6.36746800 8 9 | O 0.10357200 6.10014200 6.41982000 8 10 | O 0.00799700 9.11661600 6.36932800 8 11 | Mg 1.50012300 1.45298500 6.33066100 12 12 | Mg 1.46663700 4.31038300 6.50045200 12 13 | Mg 1.51857600 7.71041200 6.49836300 12 14 | Mg 1.50913000 10.57887700 6.33123800 12 15 | Mg 3.01049600 12.04097600 4.22708000 12 16 | Mg 3.00547700 3.00420900 4.25891200 12 17 | Mg 3.00905800 6.02188200 4.27904900 12 18 | Mg 0.00364500 9.03424800 4.26013300 12 19 | O 1.50231400 1.49802900 4.22972200 8 20 | O 1.50386200 4.52150600 4.31027100 8 21 | O 1.50898300 7.51862000 4.29752800 8 22 | O 1.50736100 10.54525900 4.22858200 8 23 | O 0.00000000 0.00000000 2.12912900 8 24 | O 0.00000000 3.00997800 2.12912900 8 25 | O 0.00000000 6.01995700 2.12912900 8 26 | O 0.00000000 9.02993500 2.12912900 8 27 | Mg 1.50543000 1.50981300 2.12912900 12 28 | Mg 1.50543000 4.51496800 2.12912900 12 29 | Mg 1.50543000 7.52494600 2.12912900 12 30 | Mg 1.50543000 10.53492400 2.12912900 12 31 | Mg 0.00000000 0.00000000 0.00000000 12 32 | Mg 0.00000000 3.00997800 0.00000000 12 33 | Mg 0.00000000 6.01995700 0.00000000 12 34 | Mg 0.00000000 9.02993500 0.00000000 12 35 | O 1.50543000 1.50981300 0.00000000 8 36 | O 1.50543000 4.51496800 0.00000000 8 37 | O 1.50543000 7.52494600 0.00000000 8 38 | O 1.50543000 10.53492400 0.00000000 8 39 | -------------------------------------------------------------------------------- /tests/data/ROJiORHNwL4q0WTvNUy0mW5s2Buuq+PSX9X4dQR2r1cjQ9kBtuC-wI6MO8B.xyz: -------------------------------------------------------------------------------- 1 | 4 2 | Lattice="5.837174 0.0 0.0 0.0 5.837174 0.0 2.918587 2.918587 1.4178500000000003" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Be 5.83717400 4.37788000 0.35446300 4 4 | Be 2.91858700 4.37788000 1.06338800 4 5 | O 5.83717400 7.29646800 1.06338800 8 6 | O 2.91858700 1.45929400 0.35446300 8 7 | -------------------------------------------------------------------------------- /tests/data/Rhn-EWQQN8Z-lbmZwoWPyrGiM9Isx+PbYDgCBSwbq3nxONqWaq03HYUn8_V.xyz: -------------------------------------------------------------------------------- 1 | 68 2 | Lattice="12.671902 0.0 0.0 0.00026 9.252205 0.0 0.0 0.0 200.0" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Ba 10.82179700 4.14101300 1.80426100 56 4 | Ba 6.73584600 4.38063000 4.12669300 56 5 | Ba 3.99543800 1.85182900 1.44850100 56 6 | Ba 0.09282000 1.88766600 4.25774000 56 7 | Si 7.00385400 2.76618500 1.24205100 14 8 | Si 3.20062500 4.43321700 4.06982200 14 9 | Si 10.21832200 4.43708900 5.89146100 14 10 | O 11.83655200 1.85829400 2.02106400 8 11 | O 6.47553600 1.96462800 2.54031200 8 12 | O 3.74139700 4.12370800 2.56022700 8 13 | O 11.03789900 4.16646000 4.53649100 8 14 | O 3.61763200 1.42139800 4.31576100 8 15 | O 8.61397800 4.16058000 5.88165500 8 16 | O 5.98835700 2.71991500 0.03774500 8 17 | O 1.65024400 4.18215900 4.37162600 8 18 | O 4.19877300 3.74058300 5.22702400 8 19 | O 8.21847600 3.74002100 1.49354900 8 20 | Ba 1.80481500 4.20061500 7.31583600 56 21 | Ba 10.57488500 4.16518700 10.12546200 56 22 | Ba 7.83442900 2.06795800 7.44669000 56 23 | Ba 3.74858400 1.82800600 9.76937300 56 24 | Si 4.35178800 2.12372300 5.68242900 14 25 | Si 11.36945700 2.12039300 7.50378500 14 26 | Si 7.56634300 5.07932400 10.33207700 14 27 | O 3.53217100 1.85285200 7.03731100 8 28 | O 10.82869400 1.81087500 9.01334300 8 29 | O 8.09513500 4.27859700 9.03347100 8 30 | O 2.73257000 4.17105700 9.55272200 8 31 | O 8.58328600 0.41066900 11.53604800 8 32 | O 5.95614200 1.84736700 5.69218800 8 33 | O 10.95277500 3.73460900 7.25788900 8 34 | O 6.35097200 6.05230300 10.08101400 8 35 | O 10.37145700 1.42767800 6.34647200 8 36 | O 0.24792600 1.86932200 7.20194100 8 37 | Ba 10.82147100 8.76708900 1.80420600 56 38 | Ba 6.73582000 9.00656800 4.12717400 56 39 | Ba 3.99488300 6.47813400 1.44853300 56 40 | Ba 0.09322000 6.51390500 4.25762500 56 41 | Si 7.00318200 7.39305400 1.24181200 14 42 | Si 3.20063600 9.05952100 4.06997700 14 43 | Si 10.21835100 9.06288900 5.89155300 14 44 | O 11.83702400 6.48470800 2.02082400 8 45 | O 6.47530200 6.59098500 2.53993200 8 46 | O 3.74105300 8.74983200 2.56031000 8 47 | O 11.03760900 8.79165700 4.53652500 8 48 | O 3.61724500 6.04747700 4.31581500 8 49 | O 8.61391700 8.78680100 5.88227100 8 50 | O 5.98733200 7.34713800 0.03776600 8 51 | O 1.65024000 8.80882900 4.37201300 8 52 | O 4.19861400 8.36657700 5.22714700 8 53 | O 8.21803400 8.36663700 1.49316300 8 54 | Ba 1.80549200 8.82696500 7.31614400 56 55 | Ba 10.57536000 8.79154700 10.12554200 56 56 | Ba 7.83402100 6.69395100 7.44727200 56 57 | Ba 3.74791100 6.45354400 9.76969500 56 58 | Si 4.35170100 6.74967000 5.68243000 14 59 | Si 11.36963800 6.74661700 7.50408000 14 60 | Si 7.56714100 0.45550000 10.33221700 14 61 | O 3.53227200 6.47869500 7.03740700 8 62 | O 10.82968300 6.43708700 9.01393200 8 63 | O 8.09461200 8.90408000 9.03481400 8 64 | O 2.73367700 8.79767200 9.55285300 8 65 | O 8.58214900 5.03350900 11.53616400 8 66 | O 5.95610600 6.47346300 5.69202000 8 67 | O 10.95264100 8.36080600 7.25829000 8 68 | O 6.35285600 1.42964400 10.08006900 8 69 | O 10.37107800 6.05384500 6.34729700 8 70 | O 0.24800800 6.49566900 7.20156000 8 71 | -------------------------------------------------------------------------------- /tests/data/RloVGNkMhI83gtwzF5DmftT6fM31d+P9ZCykgTQkZ7aIFmr-vje9gq8p6fc.xyz: -------------------------------------------------------------------------------- 1 | 24 2 | Lattice="12.300000045968947 0.0 0.0 6.1500000256303595 10.65211250922573 0.0 0.0 0.0 10.000000039911455" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | C 9.92473173 8.11541912 5.08228944 6 4 | C 9.93522029 9.50790761 5.08043315 6 5 | C 11.13680054 10.19486504 5.07331155 6 6 | C 12.33289922 9.49394847 5.06827948 6 7 | C 12.32287412 8.10200264 5.06970983 6 8 | C 11.12877806 7.41001022 5.07674423 6 9 | C 4.98978637 4.29043558 5.06828382 6 10 | C 4.99879590 5.68237102 5.07110287 6 11 | C 6.19229445 6.37521990 5.07751677 6 12 | C 7.38743918 4.27821443 5.07761584 6 13 | C 6.18620802 3.59030370 5.07088043 6 14 | C 7.39691610 5.67072126 5.08178805 6 15 | H 6.18251200 2.49224023 5.06530031 1 16 | H 8.35422024 3.75996693 5.07784450 1 17 | H 4.04849715 6.23115340 5.06615930 1 18 | H 4.03531731 3.74997206 5.06194898 1 19 | H 6.23116682 7.47103911 5.07785316 1 20 | H 13.28766022 10.03383497 5.06184131 1 21 | H 13.27285859 7.55272129 5.06357898 1 22 | H 4.99147448 0.64088159 5.07040984 1 23 | H 8.96891493 10.02697656 5.08246675 1 24 | H 11.08914212 6.31421864 5.07532982 1 25 | N 8.66393239 6.26513863 5.08518521 7 26 | N 8.65733676 7.52164840 5.08508983 7 27 | -------------------------------------------------------------------------------- /tests/data/RloVGNkMhI83gtwzF5DmftT6fM31d+PKxGoPkNrvdpZrlLS-V14MszJ-57L.xyz: -------------------------------------------------------------------------------- 1 | 74 2 | Lattice="12.300000045968947 0.0 0.0 6.1500000256303595 10.65211250922573 0.0 0.0 0.0 15.000000057221294" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | C 6.15114351 10.64988332 6.01430331 6 4 | C 6.15119453 6.38917843 5.98167211 6 5 | C 7.38163706 8.51873603 6.00105759 6 6 | C 11.07078996 10.64942328 5.98578321 6 7 | C 6.15084300 2.12899200 5.99167008 6 8 | C 8.61042916 6.38959696 5.98191728 6 9 | C 9.84097242 8.51945966 5.97911039 6 10 | C 13.52996190 10.64976423 5.99823817 6 11 | C 8.61015203 2.12931317 6.00710978 6 12 | C 9.84001135 4.26014321 6.00068030 6 13 | C 11.07027079 6.39044454 5.98377738 6 14 | C 12.29997342 8.51949716 5.97513483 6 15 | C 15.99078008 10.65043363 6.01421941 6 16 | C 11.07075009 2.12877834 6.01302972 6 17 | C 4.92156854 4.25958765 5.98159302 6 18 | C 12.30013737 4.25962150 6.00963971 6 19 | C 15.99089114 9.22966682 6.01267165 6 20 | C 7.38054909 4.25987104 5.98104468 6 21 | C 3.69172835 2.12871030 6.00374967 6 22 | C 14.76076871 7.09941068 6.00178439 6 23 | C 4.92133142 8.51902552 6.00899734 6 24 | C 3.69166483 4.96910364 5.99220092 6 25 | C 4.92144808 7.09887611 5.99313743 6 26 | C 6.15173673 9.22914327 6.01197039 6 27 | C 3.69153613 0.70792168 5.99983566 6 28 | C 4.92135017 2.83942564 5.98959784 6 29 | C 6.15090682 4.96916177 5.97502870 6 30 | C 7.38106082 7.09909752 5.98589156 6 31 | C 8.61138123 9.22903095 5.99580803 6 32 | C 6.15011471 0.70795308 5.99230968 6 33 | C 8.61159767 10.64933301 6.00670347 6 34 | C 8.61017811 4.96984540 5.98565937 6 35 | C 9.84052606 7.09961801 5.97941026 6 36 | C 11.07070520 9.22946300 5.97522006 6 37 | C 8.61026086 0.70844246 6.01010939 6 38 | C 7.38040877 2.83967123 5.99216644 6 39 | C 11.07006193 4.97026763 6.00005241 6 40 | C 3.69164317 6.38939644 5.99560373 6 41 | C 9.84060530 2.83947851 6.00966696 6 42 | C 1.23081605 2.12855144 6.01490587 6 43 | C 13.52983228 6.39017765 5.99768389 6 44 | C 14.76004950 8.52006409 6.00346328 6 45 | C 1.23133624 0.70785052 6.01397350 6 46 | C 2.46135440 4.25966764 6.00793041 6 47 | C 13.53061552 4.96950544 6.00855766 6 48 | C 12.30067965 2.83888900 6.01380004 6 49 | C 11.07107380 0.70823993 6.01431871 6 50 | C 13.52956889 9.22939667 5.99021509 6 51 | C 12.29993679 7.09997678 5.98090002 6 52 | C 2.46105512 2.83870863 6.01219973 6 53 | C 9.94498847 8.12882898 9.32644602 6 54 | C 9.95096831 9.53378252 9.32847020 6 55 | C 11.16240094 10.22812936 9.32072353 6 56 | C 12.37239694 9.52410556 9.31571579 6 57 | C 12.36585569 8.11862857 9.31315478 6 58 | C 11.16228866 7.41596551 9.31651935 6 59 | C 4.94921026 4.26538193 9.31345316 6 60 | C 4.95077755 5.67091284 9.31581519 6 61 | C 6.15206974 6.37687309 9.31643726 6 62 | C 7.37002140 4.26267172 9.31526143 6 63 | C 6.16125154 3.56450348 9.31494889 6 64 | C 7.37125533 5.66783898 9.31700529 6 65 | H 6.16736877 2.47324229 9.30196151 1 66 | H 8.33145343 3.74314407 9.30395380 1 67 | H 4.00203316 6.21389528 9.30233671 1 68 | H 4.00020562 3.72142239 9.29743125 1 69 | H 6.17419130 7.46830227 9.30664949 1 70 | H 13.32335956 10.06384509 9.30147392 1 71 | H 13.31272975 7.57158327 9.29474445 1 72 | H 5.00895150 0.66758226 9.30744971 1 73 | H 8.99094230 10.05556074 9.32427564 1 74 | H 11.13914198 6.32452259 9.30526571 1 75 | N 8.66047269 6.26834247 9.30898599 7 76 | N 8.65217290 7.53294077 9.32821429 7 77 | -------------------------------------------------------------------------------- /tests/data/RmlNIfj-YIQ14UBYjtAHtXcAEXZif+PIkKcrxeOf997qnQ_hWRXLdMsmpAf.xyz: -------------------------------------------------------------------------------- 1 | 27 2 | Lattice="6.068708 0.0 0.0 -3.034354 10.818808 0.0 0.0 0.0 13.606626000000004" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Mo 3.03435400 1.32856900 0.99307600 42 4 | Mo 0.00000000 1.32856900 0.99307600 42 5 | Mo 1.51717700 6.67711000 1.02698000 42 6 | Mo -1.51717700 6.67711000 1.02698000 42 7 | Mo 1.51717700 9.59861700 1.27301800 42 8 | Mo -1.51717700 9.59861700 1.27301800 42 9 | Mo 3.03435400 4.12835000 1.30692200 42 10 | Mo 0.00000000 4.12835000 1.30692200 42 11 | Mo -0.07135300 7.87529300 3.25962700 42 12 | Mo 3.08456300 7.95317400 3.26456200 42 13 | Mo 4.55342700 2.56049600 3.22441100 42 14 | Mo 1.52763700 2.61106000 3.24652700 42 15 | Mo 1.52370900 5.52203200 3.34037000 42 16 | Mo -1.50152000 5.52492700 3.26466000 42 17 | Mo -2.97573900 10.76937300 3.39620400 42 18 | Mo -0.00739400 10.74145200 3.41919800 42 19 | C 1.51717700 4.79263400 0.00000000 6 20 | C 4.55153100 4.79263400 0.00000000 6 21 | C -0.00000000 8.13786300 1.14985200 6 22 | C 3.03435400 8.13786300 1.14985200 6 23 | C 1.54373800 0.74066400 2.21085500 6 24 | C 4.52911000 0.73075700 2.19353300 6 25 | C 3.04739100 4.02912500 3.51671500 6 26 | C 0.00514300 4.02296900 3.51222600 6 27 | C -1.65218200 9.29343900 4.20234100 6 28 | O -0.49903700 9.21979700 4.91035800 8 29 | O 1.55184700 7.25870700 4.40258000 8 30 | -------------------------------------------------------------------------------- /tests/data/RmlNIfj-YIQ14UBYjtAHtXcAEXZif+PYu3zrqdlNhhs9tII2lnvJ3Gj7tai.xyz: -------------------------------------------------------------------------------- 1 | 27 2 | Lattice="6.068708 0.0 0.0 -3.034354 10.818808 0.0 0.0 0.0 13.606626000000004" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Mo 3.03435400 1.32856900 0.99307600 42 4 | Mo 0.00000000 1.32856900 0.99307600 42 5 | Mo 1.51717700 6.67711000 1.02698000 42 6 | Mo -1.51717700 6.67711000 1.02698000 42 7 | Mo 1.51717700 9.59861700 1.27301800 42 8 | Mo -1.51717700 9.59861700 1.27301800 42 9 | Mo 3.03435400 4.12835000 1.30692200 42 10 | Mo 0.00000000 4.12835000 1.30692200 42 11 | Mo -0.09163200 8.07216400 3.20469600 42 12 | Mo 3.12786200 8.07026800 3.20405600 42 13 | Mo 4.55163600 2.63806200 3.22564700 42 14 | Mo 1.51762900 2.65415100 3.21127000 42 15 | Mo 1.51697500 5.62210100 3.28863800 42 16 | Mo -1.51674600 5.59924500 3.29722500 42 17 | Mo 3.01735600 10.70028500 3.39985500 42 18 | Mo 0.01567900 10.70154900 3.40078700 42 19 | C 1.51717700 4.79263400 0.00000000 6 20 | C 4.55153100 4.79263400 0.00000000 6 21 | C -0.00000000 8.13786300 1.14985200 6 22 | C 3.03435400 8.13786300 1.14985200 6 23 | C 1.51704500 0.73972600 2.24750400 6 24 | C 4.55089800 0.75184500 2.22256400 6 25 | C 3.04027800 4.11094500 3.51597300 6 26 | C -0.00564400 4.10969000 3.51728400 6 27 | C 1.31466800 7.55149600 7.24945800 6 28 | O 0.47493700 8.37601000 7.27541300 8 29 | O 2.15497900 6.72805200 7.22829200 8 30 | -------------------------------------------------------------------------------- /tests/data/RmlNIfj-YIQ14UBYjtAHtXcAEXZif+Pkl2CiGU9KP0uluTY8M3PeGEb4OS_.xyz: -------------------------------------------------------------------------------- 1 | 27 2 | Lattice="6.068708 0.0 0.0 -3.034354 10.818808 0.0 0.0 0.0 13.606626000000004" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Mo 3.03435400 1.32856900 0.99307600 42 4 | Mo 0.00000000 1.32856900 0.99307600 42 5 | Mo 1.51717700 6.67711000 1.02698000 42 6 | Mo -1.51717700 6.67711000 1.02698000 42 7 | Mo 1.51717700 9.59861700 1.27301800 42 8 | Mo -1.51717700 9.59861700 1.27301800 42 9 | Mo 3.03435400 4.12835000 1.30692200 42 10 | Mo 0.00000000 4.12835000 1.30692200 42 11 | Mo 0.00342700 8.14803000 3.20149700 42 12 | Mo 3.03099400 8.04184800 3.17635200 42 13 | Mo 4.62905000 2.58960500 3.25547300 42 14 | Mo 1.43596700 2.58866200 3.25438100 42 15 | Mo 1.51113200 5.62433300 3.25769100 42 16 | Mo -1.51291200 5.62518300 3.25759100 42 17 | Mo 3.03418000 10.67153200 3.40441800 42 18 | Mo -0.00105100 10.69604600 3.43032100 42 19 | C 1.51717700 4.79263400 0.00000000 6 20 | C 4.55153100 4.79263400 0.00000000 6 21 | C -0.00000000 8.13786300 1.14985200 6 22 | C 3.03435400 8.13786300 1.14985200 6 23 | C 1.50699200 0.72885500 2.21747600 6 24 | C 4.56133900 0.72872800 2.21838900 6 25 | C 3.03263000 4.11411800 3.78056800 6 26 | C -0.00114600 4.08916700 3.51224100 6 27 | C 3.01857700 3.38804600 5.08955100 6 28 | O 2.99656100 3.73312900 6.24582400 8 29 | O 3.03650900 1.95094500 4.70098200 8 30 | -------------------------------------------------------------------------------- /tests/data/RmlNIfj-YIQ14UBYjtAHtXcAEXZif+PmZsb-Uf3AIGQyTBZDg4ZgxXaq5UB.xyz: -------------------------------------------------------------------------------- 1 | 27 2 | Lattice="6.068708 0.0 0.0 -3.034354 10.818808 0.0 0.0 0.0 13.606626000000004" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Mo 3.03435400 1.32856900 0.99307600 42 4 | Mo 0.00000000 1.32856900 0.99307600 42 5 | Mo 1.51717700 6.67711000 1.02698000 42 6 | Mo -1.51717700 6.67711000 1.02698000 42 7 | Mo 1.51717700 9.59861700 1.27301800 42 8 | Mo -1.51717700 9.59861700 1.27301800 42 9 | Mo 3.03435400 4.12835000 1.30692200 42 10 | Mo 0.00000000 4.12835000 1.30692200 42 11 | Mo 0.04018500 7.99870800 3.24944700 42 12 | Mo 2.99945700 8.11809600 3.22893000 42 13 | Mo 4.58556900 2.60379700 3.23255500 42 14 | Mo 1.54411000 2.60958800 3.21513700 42 15 | Mo 1.53905900 5.52603200 3.39517300 42 16 | Mo -1.48596100 5.53705800 3.22344600 42 17 | Mo 3.02690500 10.73423900 3.38930300 42 18 | Mo 0.00555200 10.71742700 3.38803800 42 19 | C 1.51717700 4.79263400 0.00000000 6 20 | C 4.55153100 4.79263400 0.00000000 6 21 | C -0.00000000 8.13786300 1.14985200 6 22 | C 3.03435400 8.13786300 1.14985200 6 23 | C 1.52117600 0.71856100 2.22912700 6 24 | C 4.54620400 0.73830500 2.21174800 6 25 | C 3.06314100 4.03096500 3.51273700 6 26 | C 0.03410800 4.05211600 3.51969300 6 27 | C 1.31597600 7.41330300 4.79571100 6 28 | O 0.49225900 7.94947200 5.59109000 8 29 | O 2.51121500 6.86029600 5.01440400 8 30 | -------------------------------------------------------------------------------- /tests/data/Rq0LUBXa6rZ-mddbQUZJXOIVAIg-J+Pm73-Kx5CWtuIHzLTr5R-Nir2el0i.xyz: -------------------------------------------------------------------------------- 1 | 27 2 | Lattice="6.068708 0.0 0.0 -3.034354 5.264879 0.0 0.0 0.0 18.304884" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Mo 3.03490200 3.43447400 7.16862700 42 4 | Mo -0.00009600 3.62599100 7.01703800 42 5 | Mo 4.49353000 0.76191700 7.11786300 42 6 | Mo 1.57509800 0.76290300 7.12099000 42 7 | Mo -1.51519800 4.42173900 4.76600000 42 8 | Mo 1.51600400 4.42292700 4.76708800 42 9 | Mo 3.03475300 1.86853900 4.65823000 42 10 | Mo 0.00079700 1.74579900 4.78182400 42 11 | Mo 3.03435400 3.45077200 2.43263700 42 12 | Mo -0.00000000 3.45077200 2.43263700 42 13 | Mo 4.55153100 0.81833300 2.36296800 42 14 | Mo 1.51717700 0.81833300 2.36296800 42 15 | Mo 1.51717700 4.44654600 0.06980400 42 16 | Mo -1.51717700 4.44654600 0.06980400 42 17 | Mo 3.03435400 1.81410700 0.00013600 42 18 | Mo 0.00000000 1.81410700 0.00013600 42 19 | C -0.00140200 3.82852900 9.04255500 6 20 | C -0.00007500 0.08450900 8.03113300 6 21 | C 1.53337400 2.60188600 5.98853100 6 22 | C 4.53621200 2.60155500 5.98815000 6 23 | C 3.03435400 0.00000000 3.57921900 6 24 | C 0.00000000 0.00000000 3.57921900 6 25 | C -1.51717700 2.63244000 1.21638600 6 26 | C 1.51717700 2.63244000 1.21638600 6 27 | C 3.03150900 1.69524600 8.48471200 6 28 | O -0.00268700 3.87915600 10.20354200 8 29 | O 3.04274400 1.76144900 9.69679500 8 30 | -------------------------------------------------------------------------------- /tests/data/RscdVKibS4pD0O_Yo1CSwkznfiL1c+PCvflj-qTkfRcUaCISfn8fm-2oaVW.xyz: -------------------------------------------------------------------------------- 1 | 92 2 | Lattice="8.121775 0.0 0.0 0.0 11.485924000000002 0.0 0.0 0.0 250.00000000000003" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Ba 0.00000000 0.00000000 5.74296200 56 4 | Ba 0.00000000 5.74296200 5.74296200 56 5 | Ba 4.05961100 0.00000000 5.74296200 56 6 | Ba 4.05961100 5.74296200 5.74296200 56 7 | Ti 2.02980600 8.61444300 5.74296200 22 8 | Ti 2.02980600 2.87148100 5.74296200 22 9 | Ti 6.09196900 8.61444300 5.74296200 22 10 | Ti 6.09196900 2.87148100 5.74296200 22 11 | O 0.00000000 8.61444300 5.74296200 8 12 | O 0.00000000 2.87148100 5.74296200 8 13 | O 4.05961100 8.61444300 5.74296200 8 14 | O 4.05961100 2.87148100 5.74296200 8 15 | O 2.02980600 10.05018400 4.30722100 8 16 | O 2.02980600 4.30722100 4.30722100 8 17 | O 6.09196900 10.05018400 4.30722100 8 18 | O 6.09196900 4.30722100 4.30722100 8 19 | O 2.02980600 1.43574000 4.30722100 8 20 | O 2.02980600 7.17870300 4.30722100 8 21 | O 6.09196900 1.43574000 4.30722100 8 22 | O 6.09196900 7.17870300 4.30722100 8 23 | Ba 0.00000000 8.61444300 2.87148100 56 24 | Ba 0.00000000 2.87148100 2.87148100 56 25 | Ba 4.05961100 8.61444300 2.87148100 56 26 | Ba 4.05961100 2.87148100 2.87148100 56 27 | Ti 2.02980600 0.00000000 2.87148100 22 28 | Ti 2.02980600 5.74296200 2.87148100 22 29 | Ti 6.09196900 0.00000000 2.87148100 22 30 | Ti 6.09196900 5.74296200 2.87148100 22 31 | O 0.00000000 0.00000000 2.87148100 8 32 | O 0.00000000 5.74296200 2.87148100 8 33 | O 4.05961100 0.00000000 2.87148100 8 34 | O 4.05961100 5.74296200 2.87148100 8 35 | O 2.02980600 1.43574000 1.43574000 8 36 | O 2.02980600 7.17870300 1.43574000 8 37 | O 6.09196900 1.43574000 1.43574000 8 38 | O 6.09196900 7.17870300 1.43574000 8 39 | O 2.02980600 10.05018400 1.43574000 8 40 | O 2.02980600 4.30722100 1.43574000 8 41 | O 6.09196900 10.05018400 1.43574000 8 42 | O 6.09196900 4.30722100 1.43574000 8 43 | Ba 0.00000000 0.00000000 0.00000000 56 44 | Ba 0.00000000 5.74296200 0.00000000 56 45 | Ba 4.05961100 0.00000000 0.00000000 56 46 | Ba 4.05961100 5.74296200 0.00000000 56 47 | Ti 2.02980600 2.87148100 0.00000000 22 48 | Ti 2.02980600 8.61444300 0.00000000 22 49 | Ti 6.09196900 2.87148100 0.00000000 22 50 | Ti 6.09196900 8.61444300 0.00000000 22 51 | O 0.00000000 2.87148100 0.00000000 8 52 | O 0.00000000 8.61444300 0.00000000 8 53 | O 4.05961100 2.87148100 0.00000000 8 54 | O 4.05961100 8.61444300 0.00000000 8 55 | O 2.02980600 10.05018400 248.56426000 8 56 | O 2.02980600 4.30722100 248.56426000 8 57 | O 6.09196900 10.05018400 248.56426000 8 58 | O 6.09196900 4.30722100 248.56426000 8 59 | O 2.02980600 1.43574000 248.56426000 8 60 | O 2.02980600 7.17870300 248.56426000 8 61 | O 6.09196900 1.43574000 248.56426000 8 62 | O 6.09196900 7.17870300 248.56426000 8 63 | Ba 0.00000000 2.87148100 247.12851900 56 64 | Ba 0.00000000 8.61444300 247.12851900 56 65 | Ba 4.05961100 2.87148100 247.12851900 56 66 | Ba 4.05961100 8.61444300 247.12851900 56 67 | Ti 2.02980600 0.00000000 247.12851900 22 68 | Ti 2.02980600 5.74296200 247.12851900 22 69 | Ti 6.09196900 0.00000000 247.12851900 22 70 | Ti 6.09196900 5.74296200 247.12851900 22 71 | O 0.00000000 0.00000000 247.12851900 8 72 | O 0.00000000 5.74296200 247.12851900 8 73 | O 4.05961100 0.00000000 247.12851900 8 74 | O 4.05961100 5.74296200 247.12851900 8 75 | O 2.02980600 1.43574000 245.69277900 8 76 | O 2.02980600 7.17870300 245.69277900 8 77 | O 6.09196900 1.43574000 245.69277900 8 78 | O 6.09196900 7.17870300 245.69277900 8 79 | O 2.02980600 10.05018400 245.69277900 8 80 | O 2.02980600 4.30722100 245.69277900 8 81 | O 6.09196900 10.05018400 245.69277900 8 82 | O 6.09196900 4.30722100 245.69277900 8 83 | Ba 0.00000000 0.00000000 244.25703800 56 84 | Ba 0.00000000 5.74296200 244.25703800 56 85 | Ba 4.05961100 0.00000000 244.25703800 56 86 | Ba 4.05961100 5.74296200 244.25703800 56 87 | Ti 2.02980600 2.87148100 244.25703800 22 88 | Ti 2.02980600 8.61444300 244.25703800 22 89 | Ti 6.09196900 2.87148100 244.25703800 22 90 | Ti 6.09196900 8.61444300 244.25703800 22 91 | O 0.00000000 2.87148100 244.25703800 8 92 | O 0.00000000 8.61444300 244.25703800 8 93 | O 4.05961100 2.87148100 244.25703800 8 94 | O 4.05961100 8.61444300 244.25703800 8 95 | -------------------------------------------------------------------------------- /tests/data/RzQh5XijWuXsNZiRSxeOlPFUY_9Gl+PY5NRLMRYyQXsYmBN9hMcT-FftquP.xyz: -------------------------------------------------------------------------------- 1 | 44 2 | Lattice="11.165000000000003 0.0 0.0 -5.582 9.669 0.0 0.0 0.0 250.00000000000003" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T" 3 | Ti 0.08576100 3.31297800 2.48399500 22 4 | Ti -3.03385100 8.11818200 2.49853900 22 5 | Ti 6.06024900 3.01578000 2.62715000 22 6 | Ti 2.65119300 8.06083000 2.58026400 22 7 | Sr 8.25569200 1.68914400 1.42520000 38 8 | Sr 5.87981300 6.59810600 1.42899100 38 9 | Sr 2.57105400 1.37696900 1.27949700 38 10 | Sr 0.16074000 6.23416400 1.27656700 38 11 | O -1.48584800 4.07955500 1.11571000 8 12 | O -4.26109700 8.91033000 1.12956700 8 13 | O 3.97349700 4.16493500 1.09983100 8 14 | O 1.30286700 8.92195900 1.12432300 8 15 | O 7.05441700 4.07453700 1.12179000 8 16 | O 4.30588800 8.93801000 1.10281500 8 17 | O 1.46607600 4.07961000 1.12548200 8 18 | O -1.28503800 8.92510400 1.11531000 8 19 | O -0.00516700 1.51693200 1.09184200 8 20 | O -2.78564000 6.33971000 1.09973100 8 21 | O 5.58353700 1.51537700 1.10686600 8 22 | O 2.79383200 6.34781500 1.08608700 8 23 | Ti -5.12952800 9.60626100 0.03091100 22 24 | Ti 8.27045900 4.71883200 0.01210300 22 25 | Ti 5.70374700 0.09342800 249.95335100 22 26 | Ti 1.95647800 4.89917200 249.98599900 22 27 | Sr 0.18076700 3.43354900 248.75057100 38 28 | Sr -2.90991500 8.21460300 248.65167400 38 29 | Sr 5.82802400 3.04844500 248.55908200 38 30 | Sr 2.53348900 8.09399300 248.62561600 38 31 | O 9.91345600 0.71845900 248.89105200 8 32 | O 7.06403200 5.58854700 248.87729700 8 33 | O 4.27250900 0.75618900 248.88373300 8 34 | O 1.46875000 5.59099500 248.86842600 8 35 | O 1.32321000 0.75812300 248.87418200 8 36 | O -1.51072400 5.57812000 248.88581900 8 37 | O 6.89568600 0.75336400 248.87835800 8 38 | O 4.00640600 5.51897400 248.90747600 8 39 | O 8.37579200 3.31576900 248.89109300 8 40 | O 5.58419400 8.15113300 248.89852400 8 41 | O 2.79801000 3.32043100 248.91953100 8 42 | O 0.00349700 8.15900200 248.90433000 8 43 | Ti 8.07834100 1.71128500 247.45592500 22 44 | Ti 5.91885100 6.62539700 247.37489000 22 45 | Ti 2.69742300 1.46358900 247.49356900 22 46 | Ti 0.21439600 6.38018300 247.50693200 22 47 | -------------------------------------------------------------------------------- /tests/data/cu55.xyz: -------------------------------------------------------------------------------- 1 | 55 2 | Lattice="0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0" Properties=species:S:1:pos:R:3:Z:I:1 pbc="F F F" i=61, E=-2646.041112833 3 | Cu 8.17379637 8.17387807 8.17379165 29 4 | Cu 8.15643879 9.43538372 10.24834839 29 5 | Cu 8.14714424 9.45015505 6.10796411 29 6 | Cu 8.20281666 6.90007724 10.24172739 29 7 | Cu 8.19260398 6.91393770 6.10091601 29 8 | Cu 9.43538443 10.24835544 8.15643499 29 9 | Cu 6.90005255 10.24175555 8.20281655 29 10 | Cu 9.45013157 6.10798973 8.14714427 29 11 | Cu 6.91393682 6.10092393 8.19259859 29 12 | Cu 10.24834276 8.15647122 9.43537660 29 13 | Cu 10.24173946 8.20281843 6.90006276 29 14 | Cu 6.10797273 8.14714656 9.45013978 29 15 | Cu 6.10091050 8.19261048 6.91392995 29 16 | Cu 4.06126927 8.18182425 5.68635152 29 17 | Cu 8.20075323 10.66368373 12.28792703 29 18 | Cu 8.14060392 10.73025312 4.11319479 29 19 | Cu 8.21163178 5.61960572 12.23611803 29 20 | Cu 8.18184821 5.68635252 4.06127124 29 21 | Cu 10.66368277 12.28793103 8.20075539 29 22 | Cu 5.61959652 12.23612838 8.21163151 29 23 | Cu 10.73024903 4.11320581 8.14060331 29 24 | Cu 5.68635409 4.06127344 8.18181416 29 25 | Cu 12.28792311 8.20075384 10.66368165 29 26 | Cu 12.23612360 8.21163314 5.61960020 29 27 | Cu 4.11319977 8.14060405 10.73025300 29 28 | Cu 8.14072816 12.37343236 8.20343447 29 29 | Cu 8.20962156 3.97629647 8.14498076 29 30 | Cu 3.97629040 8.14498094 8.20961989 29 31 | Cu 12.37342873 8.20343000 8.14072919 29 32 | Cu 8.14498236 8.20962056 3.97629070 29 33 | Cu 8.20342148 8.14072910 12.37342768 29 34 | Cu 9.50785189 4.79098431 10.24796798 29 35 | Cu 6.91816687 4.74047363 10.25834422 29 36 | Cu 9.50097758 4.79994022 6.03051398 29 37 | Cu 6.91011585 4.80001691 6.08446784 29 38 | Cu 11.55961919 6.10235349 6.84320449 29 39 | Cu 11.61047394 6.09518565 9.43290744 29 40 | Cu 4.80001558 6.08447303 6.91011189 29 41 | Cu 4.79994025 6.03051055 9.50097952 29 42 | Cu 9.43291113 11.61047408 6.09517654 29 43 | Cu 9.43917461 11.54980726 10.26758681 29 44 | Cu 6.84320132 11.55962465 6.10234978 29 45 | Cu 6.84985298 11.54878782 10.32122350 29 46 | Cu 6.03050976 9.50097889 4.79993638 29 47 | Cu 10.24797057 9.50785402 4.79097685 29 48 | Cu 6.08447422 6.91011159 4.80001473 29 49 | Cu 10.25835142 6.91816753 4.74047282 29 50 | Cu 4.79097618 10.24797074 9.50785417 29 51 | Cu 4.74046836 10.25835021 6.91816566 29 52 | Cu 6.10235387 6.84320429 11.55961800 29 53 | Cu 10.32121871 6.84985632 11.54878183 29 54 | Cu 10.26758563 9.43917771 11.54980629 29 55 | Cu 6.09517822 9.43291162 11.61047211 29 56 | Cu 11.54878627 10.32122454 6.84985363 29 57 | Cu 11.54980726 10.26759532 9.43917536 29 58 | -------------------------------------------------------------------------------- /tests/performance/benchmark_cpu.sh: -------------------------------------------------------------------------------- 1 | for key in 500 1000 1500 2000 2500 3000 3500 4000 2 | do 3 | python performance.py benchmark-cpu-single --system="ordered" -s $key 4 | done 5 | for key in 100 200 300 400 500 6 | do 7 | python performance.py benchmark-cpu-single --system="unordered" -s $key 8 | done 9 | 10 | -------------------------------------------------------------------------------- /tests/performance/benchmark_memory.sh: -------------------------------------------------------------------------------- 1 | for key in 500 1000 1500 2000 2500 3000 3500 4000 2 | do 3 | python performance.py benchmark-memory-single --system="ordered" -s $key 4 | done 5 | for key in 100 200 300 400 500 6 | do 7 | python performance.py benchmark-memory-single --system="unordered" -s $key 8 | done 9 | 10 | -------------------------------------------------------------------------------- /tests/performance/plot.sh: -------------------------------------------------------------------------------- 1 | python performance.py plot 2 | 3 | -------------------------------------------------------------------------------- /tests/symmetry/test_symmetry.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import ase.io 3 | import ase.build 4 | from ase.visualize import view 5 | 6 | from matid.symmetry.wyckoffset import WyckoffSet 7 | from matid.symmetry.symmetryanalyzer import SymmetryAnalyzer 8 | from conftest import create_mos2, create_graphene, create_si, create_fe, create_sic 9 | 10 | 11 | mos2 = create_mos2() 12 | mos2_rotated = mos2.copy() 13 | cell = mos2.get_cell() 14 | rotation_axis = cell[0] + cell[1] 15 | mos2_rotated.rotate(180, rotation_axis, center=mos2[0].position) 16 | mos2_supercell = mos2.copy() 17 | mos2_supercell *= [5, 5, 1] 18 | mos2_vacuum = ase.build.mx2( 19 | formula="MoS2", kind="2H", a=3.18, thickness=3.19, size=(1, 1, 1), vacuum=8 20 | ) 21 | graphene = create_graphene() 22 | graphene_rotated = graphene.copy() 23 | cell = graphene.get_cell() 24 | rotation_axis = cell[0] + cell[1] 25 | graphene_rotated.rotate(180, rotation_axis, center=graphene[0].position) 26 | 27 | 28 | material_id_mos2 = "7ZWsaQmykAhJ4SK8Fkp-lod63p5m" 29 | wyckoff_sets_mos2 = [ 30 | WyckoffSet(element="Mo", wyckoff_letter="a"), 31 | WyckoffSet(element="S", wyckoff_letter="h"), 32 | ] 33 | material_id_graphene = "jPcWsq-Rb0gtgx2krJDmLvQUxpcL" 34 | wyckoff_sets_graphene = [ 35 | WyckoffSet(element="C", wyckoff_letter="c"), 36 | ] 37 | material_id_si = "fh3UBjhUVm4nxzeRd2JJuqw5oXYa" 38 | wyckoff_sets_si = [ 39 | WyckoffSet(element="Si", wyckoff_letter="a"), 40 | ] 41 | material_id_fe = "TpsQ0uU5Y6ureJqxP0lQqPVGBh13" 42 | wyckoff_sets_fe = [ 43 | WyckoffSet(element="Fe", wyckoff_letter="a"), 44 | ] 45 | material_id_sic = "kbzX8WFgkBEIQD0kAYJwgril4ck8" 46 | wyckoff_sets_sic = [ 47 | WyckoffSet(element="C", wyckoff_letter="a"), 48 | WyckoffSet(element="Si", wyckoff_letter="c"), 49 | ] 50 | 51 | 52 | @pytest.mark.parametrize( 53 | "system, material_id_expected, wyckoff_sets_expected", 54 | [ 55 | pytest.param(mos2, material_id_mos2, wyckoff_sets_mos2, id="2D, non-flat."), 56 | pytest.param( 57 | mos2_rotated, 58 | material_id_mos2, 59 | wyckoff_sets_mos2, 60 | id="2D, non-flat, rotated", 61 | ), 62 | pytest.param( 63 | mos2_vacuum, 64 | material_id_mos2, 65 | wyckoff_sets_mos2, 66 | id="2D, non-flat, with vacuum", 67 | ), 68 | pytest.param( 69 | mos2_supercell, 70 | material_id_mos2, 71 | wyckoff_sets_mos2, 72 | id="2D, non-flat, supercell", 73 | ), 74 | pytest.param( 75 | graphene, material_id_graphene, wyckoff_sets_graphene, id="2D, flat." 76 | ), 77 | pytest.param( 78 | graphene_rotated, 79 | material_id_graphene, 80 | wyckoff_sets_graphene, 81 | id="2D, flat, rotated", 82 | ), 83 | pytest.param( 84 | create_si(cubic=True), 85 | material_id_si, 86 | wyckoff_sets_si, 87 | id="3D, cubic, silicon diamond", 88 | ), 89 | pytest.param( 90 | create_si(cubic=False), 91 | material_id_si, 92 | wyckoff_sets_si, 93 | id="3D, primitive, silicon diamond", 94 | ), 95 | pytest.param( 96 | create_fe(cubic=True), 97 | material_id_fe, 98 | wyckoff_sets_fe, 99 | id="3D, cubic, iron BCC", 100 | ), 101 | pytest.param( 102 | create_fe(cubic=False), 103 | material_id_fe, 104 | wyckoff_sets_fe, 105 | id="3D, primitive, iron BCC", 106 | ), 107 | pytest.param( 108 | create_sic(cubic=True), 109 | material_id_sic, 110 | wyckoff_sets_sic, 111 | id="3D, cubic, SiC zinc blende", 112 | ), 113 | pytest.param( 114 | create_sic(cubic=False), 115 | material_id_sic, 116 | wyckoff_sets_sic, 117 | id="3D, primitive, SiC zinc blende", 118 | ), 119 | ], 120 | ) 121 | def test_conventional_system(system, material_id_expected, wyckoff_sets_expected): 122 | analyzer = SymmetryAnalyzer(system) 123 | material_id = analyzer.get_material_id() 124 | wyckoff_sets_conv = analyzer.get_wyckoff_sets_conventional() 125 | conv_sys = analyzer.get_conventional_system() 126 | assert_wyckoff_sets(wyckoff_sets_conv, wyckoff_sets_expected) 127 | assert material_id == material_id_expected 128 | 129 | 130 | def assert_wyckoff_sets(a, b): 131 | """Checks that the given Wyckoff sets are the same.""" 132 | assert len(a) == len(b) 133 | 134 | def sets(wyckoff_sets): 135 | sets = set() 136 | for wyckoff_set in wyckoff_sets: 137 | sets.add((wyckoff_set.element, wyckoff_set.wyckoff_letter)) 138 | return sets 139 | 140 | assert sets(a) == sets(b) 141 | -------------------------------------------------------------------------------- /tests/testrunner.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | 4 | # Import the test modules 5 | import classificationtests 6 | import symmetrytests 7 | 8 | # Initialize the test suite 9 | loader = unittest.TestLoader() 10 | suite = unittest.TestSuite() 11 | 12 | # Add tests to the test suite 13 | suite.addTests(loader.loadTestsFromModule(classificationtests)) 14 | suite.addTests(loader.loadTestsFromModule(symmetrytests)) 15 | 16 | # Initialize a runner, pass it the suite and run it 17 | runner = unittest.TextTestRunner(verbosity=3) 18 | result = runner.run(suite) 19 | 20 | # We need to return a non-zero exit code for the CI to detect errors 21 | sys.exit(not result.wasSuccessful()) 22 | --------------------------------------------------------------------------------