├── .github └── workflows │ └── deploy_page.yml ├── .gitignore ├── LICENSE.txt ├── LICENSE_Apache_v2.txt ├── LICENSE_gpl-3.0.txt ├── README.md ├── benchmark_test └── README.md ├── docs ├── Doxyfile.in ├── Makefile ├── conf.py ├── howto │ ├── ira_howto.rst │ └── sofi_howto.rst ├── index.rst ├── interf │ ├── interf_ira.rst │ ├── interf_sofi.rst │ └── py_interf.rst └── src │ ├── c_wrappers.rst │ ├── cshda.rst │ ├── ira.rst │ ├── refs.rst │ └── sofi.rst ├── examples ├── IRA │ ├── Makefile │ ├── README.md │ ├── c_program.c │ ├── example_inputs │ │ ├── input1.xyz │ │ ├── input2.xyz │ │ ├── input3.xyz │ │ ├── s1.xyz │ │ └── s2.xyz │ ├── f90_program.f90 │ └── python_program.py └── SOFI │ ├── Makefile │ ├── README.md │ ├── c_program.c │ ├── ex_D6h_subgroup │ └── test.py │ ├── ex_simplePG │ ├── compile.sh │ └── simple_pg.f90 │ ├── example_inputs │ ├── C540-Ih.xyz │ ├── Ci.xyz │ ├── Cs.xyz │ ├── D6.xyz │ ├── O.xyz │ ├── S10.xyz │ ├── S4.xyz │ ├── S6.xyz │ ├── S6_D3d.xyz │ ├── S8.xyz │ ├── T.xyz │ ├── Th.xyz │ ├── c137v.xyz │ ├── c18.xyz │ ├── c18h.xyz │ ├── c18v.xyz │ ├── c28v.xyz │ ├── c66v.xyz │ ├── d12.xyz │ ├── d12d.xyz │ └── d12h.xyz │ ├── f90_program.f90 │ └── python_program.py ├── interface ├── ira_mod.py └── iralib_interf.h ├── pixi.lock ├── pixi.toml ├── src ├── Makefile ├── README.md ├── cshda.f90 ├── err_module.f90 ├── ira_precision.f90 ├── ira_routines.f90 ├── lap.f ├── library_ira.f90 ├── library_sofi.f90 ├── set_candidate.f90 ├── sofi_routines.f90 ├── sofi_tools.f90 ├── sorting_module.f90 ├── timer.f90 └── version.f90 └── version_history.md /.github/workflows/deploy_page.yml: -------------------------------------------------------------------------------- 1 | name: deploy page 2 | 3 | # Controls when the workflow will run 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the "main" branch 6 | push: 7 | branches: [ "master" ] 8 | 9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 10 | jobs: 11 | build: 12 | runs-on: ubuntu-22.04 13 | 14 | # Steps represent a sequence of tasks that will be executed as part of the job 15 | steps: 16 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 17 | - uses: actions/checkout@v3 18 | 19 | # Runs a single command using the runners shell 20 | - name: the node.js thing 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 20 24 | 25 | # installation 26 | - name: install deps 27 | run: | 28 | sudo apt-get install doxygen 29 | python3 -m pip install numpy==1.24.2 30 | python3 -m pip install six==1.16.0 31 | python3 -m pip install sphinx==5.3.0 32 | python3 -m pip install sphinx-rtd-theme==1.1.1 33 | python3 -m pip install sphinx-sitemap==2.2.1 34 | python3 -m pip install breathe==4.34.0 35 | python3 -m pip install sphinx-fortran==1.1.1 36 | python3 -m pip install sphinx-rtd-size==0.2.0 37 | 38 | 39 | - name: build 40 | run: ls -a ${{github.workspace }} && cd docs && make html 41 | #run: sphinx-build -b html docs public 42 | #- run: make html 43 | 44 | - name: Archive artifact 45 | shell: sh 46 | if: runner.os == 'Linux' 47 | run: | 48 | ls -a ${{github.workspace }} 49 | tar \ 50 | --dereference --hard-dereference \ 51 | --exclude=.git \ 52 | --exclude=.github \ 53 | -cvf "$RUNNER_TEMP/artifact.tar" \ 54 | --directory=docs/_build/html . 55 | 56 | - name: Upload artifacts 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: github-pages 60 | path: ${{ runner.temp }}/artifact.tar 61 | if-no-files-found: error 62 | 63 | deploy: 64 | needs: build 65 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 66 | permissions: 67 | pages: write # to deploy to Pages 68 | id-token: write # to verify the deployment originates from an appropriate source 69 | # Deploy to the github-pages environment 70 | environment: 71 | name: github-pages 72 | url: ${{ steps.deployment.outputs.page_url }} 73 | # Specify runner + deployment step 74 | runs-on: ubuntu-latest 75 | steps: 76 | - name: Deploy to GitHub Pages 77 | id: deployment 78 | uses: actions/deploy-pages@v4 # or the latest "vX.X.X" version tag for this action 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Generated by gibo (https://github.com/simonwhitaker/gibo) 2 | ### https://raw.github.com/github/gitignore/807578222ddd1dcef7d41c1dc9215751620f7246/Fortran.gitignore 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | ### Generated by gibo (https://github.com/simonwhitaker/gibo) 37 | ### https://raw.github.com/github/gitignore/807578222ddd1dcef7d41c1dc9215751620f7246/Python.gitignore 38 | 39 | # Byte-compiled / optimized / DLL files 40 | __pycache__/ 41 | *.py[cod] 42 | *$py.class 43 | 44 | # C extensions 45 | *.so 46 | 47 | # Distribution / packaging 48 | .Python 49 | build/ 50 | develop-eggs/ 51 | dist/ 52 | downloads/ 53 | eggs/ 54 | .eggs/ 55 | lib/ 56 | lib64/ 57 | parts/ 58 | sdist/ 59 | var/ 60 | wheels/ 61 | share/python-wheels/ 62 | *.egg-info/ 63 | .installed.cfg 64 | *.egg 65 | MANIFEST 66 | 67 | # PyInstaller 68 | # Usually these files are written by a python script from a template 69 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 70 | *.manifest 71 | *.spec 72 | 73 | # Installer logs 74 | pip-log.txt 75 | pip-delete-this-directory.txt 76 | 77 | # Unit test / coverage reports 78 | htmlcov/ 79 | .tox/ 80 | .nox/ 81 | .coverage 82 | .coverage.* 83 | .cache 84 | nosetests.xml 85 | coverage.xml 86 | *.cover 87 | *.py,cover 88 | .hypothesis/ 89 | .pytest_cache/ 90 | cover/ 91 | 92 | # Translations 93 | *.mo 94 | *.pot 95 | 96 | # Django stuff: 97 | *.log 98 | local_settings.py 99 | db.sqlite3 100 | db.sqlite3-journal 101 | 102 | # Flask stuff: 103 | instance/ 104 | .webassets-cache 105 | 106 | # Scrapy stuff: 107 | .scrapy 108 | 109 | # Sphinx documentation 110 | docs/_build/ 111 | 112 | # PyBuilder 113 | .pybuilder/ 114 | target/ 115 | 116 | # Jupyter Notebook 117 | .ipynb_checkpoints 118 | 119 | # IPython 120 | profile_default/ 121 | ipython_config.py 122 | 123 | # pyenv 124 | # For a library or package, you might want to ignore these files since the code is 125 | # intended to run in multiple environments; otherwise, check them in: 126 | # .python-version 127 | 128 | # pipenv 129 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 130 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 131 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 132 | # install all needed dependencies. 133 | #Pipfile.lock 134 | 135 | # poetry 136 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 137 | # This is especially recommended for binary packages to ensure reproducibility, and is more 138 | # commonly ignored for libraries. 139 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 140 | #poetry.lock 141 | 142 | # pdm 143 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 144 | #pdm.lock 145 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 146 | # in version control. 147 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 148 | .pdm.toml 149 | .pdm-python 150 | .pdm-build/ 151 | 152 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 153 | __pypackages__/ 154 | 155 | # Celery stuff 156 | celerybeat-schedule 157 | celerybeat.pid 158 | 159 | # SageMath parsed files 160 | *.sage.py 161 | 162 | # Environments 163 | .env 164 | .venv 165 | env/ 166 | venv/ 167 | ENV/ 168 | env.bak/ 169 | venv.bak/ 170 | 171 | # Spyder project settings 172 | .spyderproject 173 | .spyproject 174 | 175 | # Rope project settings 176 | .ropeproject 177 | 178 | # mkdocs documentation 179 | /site 180 | 181 | # mypy 182 | .mypy_cache/ 183 | .dmypy.json 184 | dmypy.json 185 | 186 | # Pyre type checker 187 | .pyre/ 188 | 189 | # pytype static type analyzer 190 | .pytype/ 191 | 192 | # Cython debug symbols 193 | cython_debug/ 194 | 195 | # PyCharm 196 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 197 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 198 | # and can be added to the global gitignore or merged into this file. For a more nuclear 199 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 200 | #.idea/ 201 | ### Generated by gibo (https://github.com/simonwhitaker/gibo) 202 | ### https://raw.github.com/github/gitignore/807578222ddd1dcef7d41c1dc9215751620f7246/Global/macOS.gitignore 203 | 204 | # General 205 | .DS_Store 206 | .AppleDouble 207 | .LSOverride 208 | 209 | # Icon must end with two \r 210 | Icon 211 | 212 | # Thumbnails 213 | ._* 214 | 215 | # Files that might appear in the root of a volume 216 | .DocumentRevisions-V100 217 | .fseventsd 218 | .Spotlight-V100 219 | .TemporaryItems 220 | .Trashes 221 | .VolumeIcon.icns 222 | .com.apple.timemachine.donotpresent 223 | 224 | # Directories potentially created on remote AFP share 225 | .AppleDB 226 | .AppleDesktop 227 | Network Trash Folder 228 | Temporary Items 229 | .apdisk 230 | ### Generated by gibo (https://github.com/simonwhitaker/gibo) 231 | ### https://raw.github.com/github/gitignore/807578222ddd1dcef7d41c1dc9215751620f7246/Global/Emacs.gitignore 232 | 233 | # -*- mode: gitignore; -*- 234 | *~ 235 | \#*\# 236 | /.emacs.desktop 237 | /.emacs.desktop.lock 238 | *.elc 239 | auto-save-list 240 | tramp 241 | .\#* 242 | 243 | # Org-mode 244 | .org-id-locations 245 | *_archive 246 | 247 | # flymake-mode 248 | *_flymake.* 249 | 250 | # eshell files 251 | /eshell/history 252 | /eshell/lastdir 253 | 254 | # elpa packages 255 | /elpa/ 256 | 257 | # reftex files 258 | *.rel 259 | 260 | # AUCTeX auto folder 261 | /auto/ 262 | 263 | # cask packages 264 | .cask/ 265 | dist/ 266 | 267 | # Flycheck 268 | flycheck_*.el 269 | 270 | # server auth directory 271 | /server/ 272 | 273 | # projectiles files 274 | .projectile 275 | 276 | # directory configuration 277 | .dir-locals.el 278 | 279 | # network security 280 | /network-security.data 281 | 282 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, MAMMASMIAS Consortium 2 | SPDX-License-Identifier: Apache-2.0 3 | SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | This code may be used, at your choice, under the terms of the 6 | GNU General Public License version 3.0 or later, available 7 | at https://www.gnu.org/licenses/gpl-3.0.txt, or the Apache 2.0 8 | license, available at https://www.apache.org/licenses/LICENSE-2.0. 9 | Both licenses are also available in the files "LICENSE_gpl-3.0.txt", 10 | and "LICENSE_Apache_v2.txt" in the current directory 11 | 12 | -------------------------------------------------------------------------------- /LICENSE_Apache_v2.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This is the repository of shape matching algorithm 4 | Iterative Rotations and Assignments (IRA), and the Symmetry Operations FInder (SOFI) algorithm. 5 | 6 | Online documentation and tutorials: [LINK](https://mammasmias.github.io/IterativeRotationsAssignments/). 7 | GitHub repository: [LINK](https://github.com/mammasmias/IterativeRotationsAssignments). 8 | 9 | IRA is described in the publication [[1]](#1). 10 | It is also the main subject of the dissertation [[2]](#2), where a workflow 11 | inserting IRA into an off-lattice kMC algorithm is developed. 12 | 13 | SOFI is described in the publication [[3]](#3). 14 | 15 | # Directory contents 16 | 17 | `/src`: 18 | Contains the source code of the algorithms, and the C-bound API library. 19 | 20 | `/examples`: 21 | Contains example programs which use different functionalities of the IRA/CShDA/SOFI algorithms. 22 | 23 | `/interface`: 24 | Contains the C-headers, and Python module to interface the API. 25 | 26 | `/benchmark_test`: 27 | Contains data and other software used for benchmark tests done in [[1]](#1). See 28 | also `/benchmark_test/README`. NOTE: the contents have been moved to [zenodo](https://zenodo.org/doi/10.5281/zenodo.10568513). 29 | 30 | 31 | # Compile and run 32 | To run IRA/SOFI on any platform, you first need to compile it. See the [online documentation](https://mammasmias.github.io/IterativeRotationsAssignments/#installation). 33 | 34 | 35 | # Terms and conditions 36 | The software in this repository is subject to the license(s) provided in the `LICENSE.txt` file. 37 | 38 | 39 | ## References 40 | 41 | [1] 42 | Gunde M., Salles N., Hemeryck A., Martin Samos L. 43 | *IRA: A shape matching approach for recognition and comparison of generic atomic patterns*, 44 | Journal of Chemical Information and Modeling (2021), DOI: 45 | [https://doi.org/10.1021/acs.jcim.1c00567](https://doi.org/10.1021/acs.jcim.1c00567), 46 | HAL: [hal-03406717](https://hal.laas.fr/hal-03406717), arXiv: 47 | [2111.00939](https://export.arxiv.org/abs/2111.00939) 48 | 49 | [2] 50 | Gunde M.: *Development of IRA: a shape matching algorithm, its implementation 51 | and utility in a general off-lattice kMC kernel*, PhD dissertation, Université Toulouse III - Paul Sabatier, 52 | November 2021, 53 | [link](https://theses.hal.science/tel-03635139v2). 54 | 55 | [3] 56 | Gunde M., Salles N., Grisanti L., Hemeryck A., Martin Samos L. 57 | *SOFI: Finding point group symmetries in atomic clusters as finding the set of degenerate solutions in a shape-matching problem*, 58 | Journal of Chemical Physics (2024), DOI: 59 | [https://doi.org/10.1063/5.0215689](https://doi.org/10.1063/5.0215689), 60 | arXiv: [2408.06131](https://arxiv.org/abs/2408.06131). 61 | -------------------------------------------------------------------------------- /benchmark_test/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | ## IRA benchmark tests 4 | The data and scripts used for benchmark tests done in the original IRA publication [[1]](#1), are available from [zenodo](https://zenodo.org/doi/10.5281/zenodo.10568513). 5 | 6 | ## SOFI comparison tests 7 | The data and scripts used for comparison tests done in the original SOFI publication [[2]](#2), are available from [zenodo](https://zenodo.org/records/12773914). 8 | The results are available in the [Supplementary Materials](https://aip.figshare.com/articles/journal_contribution/Supplementary_Material/26352949?backTo=%2Fcollections%2FSOFI_Finding_point_group_symmetries_in_atomic_clusters_as_finding_the_set_of_degenerate_solutions_in_a_shape-matching_problem%2F7360975&file=47859538) of the related article. 9 | 10 | 11 | 12 | ## References 13 | 14 | [1] 15 | Gunde M., Salles N., Hemeryck A., Martin Samos L. 16 | *IRA: A shape matching approach for recognition and comparison of generic atomic patterns*, 17 | Journal of Chemical Information and Modeling (2021), DOI: 18 | [https://doi.org/10.1021/acs.jcim.1c00567](https://doi.org/10.1021/acs.jcim.1c00567), 19 | HAL: [hal-03406717](https://hal.laas.fr/hal-03406717), arXiv: 20 | [2111.00939](https://export.arxiv.org/abs/2111.00939) 21 | 22 | [2] 23 | Gunde M., Salles N., Grisanti L., Hemeryck A., Martin Samos L. 24 | *SOFI: Finding point group symmetries in atomic clusters as finding the set of degenerate solutions in a shape-matching problem*, 25 | Journal of Chemical Physics (2024), DOI: 26 | [https://doi.org/10.1063/5.0215689](https://doi.org/10.1063/5.0215689), 27 | arXiv: [2408.06131](https://arxiv.org/abs/2408.06131). 28 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | 10 | # -- Path setup -------------------------------------------------------------- 11 | 12 | # If extensions (or modules to document with autodoc) are in another directory, 13 | # add these directories to sys.path here. If the directory is relative to the 14 | # documentation root, use os.path.abspath to make it absolute, like shown here. 15 | # 16 | # import os 17 | # import sys 18 | import sys; sys.setrecursionlimit(1500) 19 | # sys.path.insert(0, os.path.abspath('.')) 20 | # sys.path.insert(0, os.path.abspath('./..')) 21 | from sphinx.builders.html import StandaloneHTMLBuilder 22 | import subprocess, os 23 | sys.path.insert(0, os.path.abspath('.')) 24 | sys.path.insert(0, os.path.abspath('..')) 25 | 26 | # Doxygen 27 | subprocess.call('doxygen Doxyfile.in', shell=True) 28 | 29 | 30 | project = 'lib_IRA' 31 | copyright = '2024, MAMMASMIAS Consortium' 32 | author = 'MG' 33 | release = 'v1.6' 34 | 35 | # -- General configuration --------------------------------------------------- 36 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 37 | 38 | extensions = [ 39 | # 'myst_parser', 40 | 'sphinx.ext.autodoc', 41 | 'sphinx.ext.intersphinx', 42 | # 'sphinx.ext.autosectionlabel', 43 | 'sphinx.ext.todo', 44 | 'sphinx.ext.coverage', 45 | 'sphinx.ext.mathjax', 46 | 'sphinx.ext.ifconfig', 47 | 'sphinx.ext.viewcode', 48 | 'sphinx_sitemap', 49 | 'sphinx.ext.inheritance_diagram', 50 | 'breathe', 51 | 'sphinxfortran.fortran_domain', 52 | 'sphinxfortran.fortran_autodoc', 53 | 'sphinx_rtd_size' 54 | ] 55 | 56 | source_suffix = { 57 | '.rst': 'restructuredtext', 58 | # '.md': 'markdown', 59 | } 60 | 61 | fortran_src=[os.path.abspath('../src/*.f90'), ] 62 | templates_path = ['_templates'] 63 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 64 | 65 | 66 | sys.path.insert(0, os.path.abspath('../interface')) 67 | 68 | 69 | # -- Options for HTML output ------------------------------------------------- 70 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 71 | 72 | sphinx_rtd_size_width = "90%" 73 | #html_theme = 'alabaster' 74 | html_theme = 'sphinx_rtd_theme' 75 | html_static_path = ['_static'] 76 | 77 | 78 | # -- Breathe configuration ------------------------------------------------- 79 | 80 | breathe_projects = { 81 | "lib_IRA": "_build/xml/" 82 | } 83 | breathe_default_project = "lib_IRA" 84 | breathe_default_members = ('members', 'undoc-members') 85 | -------------------------------------------------------------------------------- /docs/howto/ira_howto.rst: -------------------------------------------------------------------------------- 1 | .. _ira_howto: 2 | 3 | ###################################### 4 | IRA & CShDA tutorial and How-to guides 5 | ###################################### 6 | 7 | 8 | .. note:: 9 | 10 | The guides are given for python use. The same can be done from C/Fortran by calls to 11 | appropriate routines, which generally correspond in the name to the ones from python. 12 | For more info refer to :ref:`ref_api` and :ref:`src_refs`. 13 | 14 | 15 | 16 | 17 | .. contents:: Contents 18 | :local: 19 | :depth: 2 20 | 21 | 22 | Importing the ira module 23 | ======================== 24 | 25 | The IRA library is imported into python by: 26 | 27 | >>> import ira_mod 28 | 29 | 30 | .. warning:: 31 | If the ``ira_mod`` module cannot be found at ``import``, then make sure there is a path to ``/IRA_library/interface`` 32 | in the environment variable ``PYTHONPATH``. 33 | 34 | .. code-block:: bash 35 | 36 | echo $PYTHONPATH 37 | 38 | If not, add it by: 39 | 40 | .. code-block:: bash 41 | 42 | export PYTHONPATH=/your/path/to/IRA_library/interface:$PYTHONPATH 43 | 44 | 45 | The corresponding algorithm class (IRA or SOFI) has to be initialised by: 46 | 47 | >>> ira = ira_mod.IRA() 48 | 49 | or 50 | 51 | >>> sofi = ira_mod.SOFI() 52 | 53 | now, the functions in either class are availably by typing ``ira.`` or ``sofi.``. 54 | Quick help can be accessed by ``help( ira )``, ``help( ira. )`` or the same for ``sofi``. 55 | 56 | 57 | Direct comparison of structures using CShDA 58 | =========================================== 59 | 60 | Two structures that are already in the same rotational frame, can be directly compared by 61 | computing the atom-atom distances in a way that is invariant to the 62 | permutations with the CShDA algorithm: 63 | 64 | >>> import numpy as np 65 | >>> ## 66 | >>> ## set up the first atomic structure containing 10 atoms 67 | >>> nat1 = 10 68 | >>> typ1 = np.ones( nat1, dtype=int ) 69 | >>> coords1 = np.array([[-0.49580341, 0.9708181 , 0.37341428], 70 | ... [-1.05611656, -0.4724503 , -0.37449784], 71 | ... [-0.63509644, -0.66670776, 0.66219897], 72 | ... [-0.83642178, 0.59155936, -0.64507703], 73 | ... [ 0.59636159, 0.80558701, 0.23843962], 74 | ... [ 0.25975284, 0.71540297, -0.78971024], 75 | ... [-0.09743308, -1.03812804, -0.31233049], 76 | ... [ 0.09254502, 0.20016738, 1.03021068], 77 | ... [-0.18424967, -0.24756757, -1.07217522], 78 | ... [ 0.46705991, -0.73516435, 0.56288325]]) 79 | >>> ## 80 | >>> ## set the second atomic structure as identical, with slightly perturbed atomic positions 81 | >>> nat2 = nat1 82 | >>> typ2 = typ1 83 | >>> coords2= np.array([[-0.50010644, 0.96625779, 0.37944221], 84 | ... [-1.05658467, -0.46953529, -0.37456054], 85 | ... [-0.63373056, -0.66591152, 0.66168751], 86 | ... [-0.83286912, 0.5942803 , -0.64527646], 87 | ... [ 0.59310547, 0.80745772, 0.23711422], 88 | ... [ 0.2636203 , 0.7126221 , -0.79370807], 89 | ... [-0.09940056, -1.03859144, -0.31064337], 90 | ... [ 0.09208454, 0.19985156, 1.03003579], 91 | ... [-0.18468815, -0.24935304, -1.07257697], 92 | ... [ 0.4691676 , -0.73356138, 0.56184166]]) 93 | >>> ## 94 | >>> ## add some random permutation to the atoms in second structure 95 | >>> coords2 = coords2[ [2,4,3,5,9,8,6,7,0,1] ] 96 | >>> ## 97 | >>> ## call cshda: 98 | >>> perm, dist = ira.cshda( nat1, typ1, coords1, nat2, typ2, coords2 ) 99 | >>> ## 100 | >>> ## the `perm` contains permutations of the second structure, which matches the first structure. 101 | >>> ## Therefore, the following command should return a structure exactly equal to the first structure: 102 | >>> coords2[ perm ] 103 | array([[-0.50010644, 0.96625779, 0.37944221], 104 | [-1.05658467, -0.46953529, -0.37456054], 105 | [-0.63373056, -0.66591152, 0.66168751], 106 | [-0.83286912, 0.5942803 , -0.64527646], 107 | [ 0.59310547, 0.80745772, 0.23711422], 108 | [ 0.2636203 , 0.7126221 , -0.79370807], 109 | [-0.09940056, -1.03859144, -0.31064337], 110 | [ 0.09208454, 0.19985156, 1.03003579], 111 | [-0.18468815, -0.24935304, -1.07257697], 112 | [ 0.4691676 , -0.73356138, 0.56184166]]) 113 | >>> ## 114 | >>> ## The `dist` array contains atom-atom distances, upon permuting coords2 by `perm`, such that: 115 | >>> ## dist[ i ] = norm( coords1[ i ] - coords2[perm[ i ]] ) 116 | 117 | 118 | Solving the shape-matching problem using IRA 119 | ============================================ 120 | 121 | For two structures which are also rotated with respect to each other, the IRA algorithm is used 122 | to obtain the rotation, permutation, and translation. 123 | 124 | >>> ## set the first atomic structure 125 | >>> nat1 = 10 126 | >>> typ1 = np.ones( nat1, dtype=int ) 127 | >>> coords1 = np.array([[-0.49580341, 0.9708181 , 0.37341428], 128 | ... [-1.05611656, -0.4724503 , -0.37449784], 129 | ... [-0.63509644, -0.66670776, 0.66219897], 130 | ... [-0.83642178, 0.59155936, -0.64507703], 131 | ... [ 0.59636159, 0.80558701, 0.23843962], 132 | ... [ 0.25975284, 0.71540297, -0.78971024], 133 | ... [-0.09743308, -1.03812804, -0.31233049], 134 | ... [ 0.09254502, 0.20016738, 1.03021068], 135 | ... [-0.18424967, -0.24756757, -1.07217522], 136 | ... [ 0.46705991, -0.73516435, 0.56288325]]) 137 | >>> ## 138 | >>> ## set the second atomic structure 139 | >>> nat2 = 10 140 | >>> typ2 = np.ones(nat2, dtype=int) 141 | >>> coords2 = np.array([[-1.10284703, 0.14412375, 0.19443024], 142 | ... [ 0.66659232, 0.55627796, -0.56721304], 143 | ... [ 0.48071837, 0.23696574, 1.09688377], 144 | ... [ 1.08098955, -0.07699871, 0.21481947], 145 | ... [-0.66132935, -0.27573102, -0.73025453], 146 | ... [ 0.39018548, -0.81148351, 0.65078612], 147 | ... [-0.57686949, -0.8993001 , 0.15398734], 148 | ... [-0.42460153, 0.78820488, -0.54634801], 149 | ... [ 0.27879878, 1.07299866, 0.34477351], 150 | ... [-0.52245748, -0.27294984, 1.07110467]]) 151 | >>> ## 152 | >>> ## find the shape-matching 153 | >>> kmax_factor = 1.8 154 | >>> r, t, p, hd = ira.match( nat1, typ1, coords1, nat2, typ2, coords2, kmax_factor ) 155 | >>> ## `r` contains the rotation matrix, `t` the translation vector, 156 | >>> ## `p` the permutation, and ``hd`` the hasudorff distance. 157 | >>> hd 158 | 0.00751228170905401 159 | 160 | .. note:: 161 | The ``kmax_factor`` is a multiplicative factor that needs to be larger than 1.0. Larger value increases the 162 | search space of the rotations, but slows down the algorithm. Default value of 1.8 seems to be quite ok. 163 | 164 | 165 | 166 | (under construction) 167 | 168 | ideas: 169 | 170 | - comparing nonequal strucs (using candidates) 171 | - determine kmax value 172 | - using some thr. 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | ========================= 3 | IRA library documentation 4 | ========================= 5 | 6 | GitHub `repository `_ 7 | Online `documentation and tutorials `_ 8 | 9 | .. contents:: Contents 10 | :local: 11 | :depth: 2 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | :caption: Reference 16 | 17 | src/refs 18 | src/c_wrappers 19 | interf/py_interf 20 | 21 | 22 | 23 | 24 | Introduction 25 | ============ 26 | 27 | Even though this library is called the **IRA library**, 28 | there are three main algorithms contained inside: 29 | 30 | * CShDA: Constrained Shortest Distance Assignments; 31 | * IRA: Iterative Rotations and Assignments; 32 | * SOFI: Symmetry Operation FInder. 33 | 34 | Each of those algorithms is designed to solve a different problem related to characterization of generic atomic 35 | structures. 36 | 37 | Some other miscallaneous routines are contained, see the section of :ref:`How-to guides `. 38 | 39 | 40 | CShDA 41 | ----- 42 | 43 | Constrained Shortest Distance Assignments (CShDA, [ira2021]_) is the really fundamental algorithm for the rest of this library. 44 | It solves the so-called Linear Assignment Problem (LAP), and then computes the distance between two 45 | atomic structures :math:`A` and :math:`B`. 46 | 47 | More precisely, it solves the problem sometimes referred to as `bottleneck-LAP`, and uses that solution to 48 | compute the Hausdorff distance: 49 | 50 | .. math:: 51 | d_H( A, B ) = \max_i \min_j \big\{ d(a_i, b_j) \big\} \qquad(1) 52 | 53 | where :math:`a_i \in A` and :math:`b_j \in B` are atomic positions, and :math:`d()` is the Cartesian 54 | distance function. 55 | 56 | By first solving the LAP, computing the distance between two atomic structures becomes trivial. 57 | In fact, such distance is invariant to permutations of atoms :math:`P_B`, and variant to all other 58 | rigid transformations. 59 | CShDA returns the atomic permutations, and values of atom-atom distances w.r.t. the permutation: 60 | distances :math:`d(a_i, b_i)`, 61 | with indices :math:`i`, after applying the permutation computed in the beginning (LAP solution). 62 | Finally, the distance between :math:`A` and :math:`B` is taken as the maximal value in the set. 63 | 64 | It enforces a strict one-to-one assignment of the atoms, and works also for structures containing 65 | different numbers of atoms. 66 | 67 | Since CShDA is quite computationally heavy, a heuristic early-exit criterion is set up, and used whenever possible. 68 | The criterion is in the form of a distance threshold: as soon as it is established that the final distance 69 | value returned by CShDA cannot be below the given threshold, the computation exits with a high distance value. 70 | The heuristic can be disregarded by simply inputting a very high value for this threshold. 71 | 72 | (under construction) 73 | 74 | 75 | IRA 76 | --- 77 | 78 | Iterative Rotations and Assignments (IRA, [ira2021]_) solves the so-called `shape matching` problem, also sometimes called `structural alignment`, or similar: 79 | 80 | .. _eq-pb3: 81 | .. math:: 82 | P_B B = \mathbf{R} A + \mathbf{t} \qquad (2) 83 | 84 | where :math:`A` and :math:`B` are two atomic structures, :math:`\mathbf{R}` is a rotation/reflection matrix, 85 | :math:`\mathbf{t}` is a translation vector, and :math:`P_B` is a permutation matrix of atomic indices in :math:`B`. 86 | 87 | The problem of finding an optimal rotation :math:`\mathbf{R}` when :math:`A` and :math:`B` do not include permutations is known as 88 | the `orthogonal Procrustes problem`, and can be easily solved by Singular Value Decomposition method (SVD), 89 | see `the wikipedia article `_. 90 | The routines to solve it are also included in this library. 91 | 92 | The shape-matching problem of Eq. :ref:`(2) ` is however more complicated, and can be rewritten as optimization problem: 93 | 94 | .. _eq-argmin: 95 | .. math:: 96 | \DeclareMathOperator*{\argmin}{arg\,min} 97 | \argmin_{\bf R,t} \big\{ D({\bf R}A + {\bf t}, B) \big\} \qquad (3) 98 | 99 | in which :math:`D` is a general distance function between two sets, that is (i) variant under :math:`{\bf R}` and 100 | :math:`{\bf t}`, (ii) invariant under permutation :math:`P_B`, and (iii) returns value 0 when :math:`{\bf R}` and 101 | :math:`{\bf t}` are such that Eq. :ref:`(2) ` is satisfied, i.e. when the best match is found. 102 | 103 | The function :math:`D` in Eq. :ref:`(3) ` is computed by CShDA. 104 | 105 | The IRA algorithm specifies a way to parse the space of rigid transformations associated to the atomic structure, 106 | computes CShDA on the way, and returns the single transformation that minimizes it. 107 | In presence of distortions, it calls the SVD-based algorithm to further minimize the rotations, given permutation 108 | computed by CShDA. 109 | 110 | When :math:`A` and :math:`B` are congruent, IRA is guaranteed to return the optimal solution, 111 | independently of the initial orientation and permutation of the structures, which is not entirely 112 | obvious (see benchmarks in [ira2021]_). 113 | In case of nearly-congruent structures, this becomes debatable, since 114 | the notion of near-congruency is not uniquely defined, and is dependent on the involved structures. 115 | 116 | Therefore, IRA is not recommended for applications where the `value` of (dis-)similarity between structures 117 | is the key quantity. It is however highly efficient in tasks like retrieving a certain structure from a 118 | list of structures, where the structure is certain to be present in the list 119 | (in any orientation and permutation). 120 | 121 | .. [ira2021] M. Gunde, N. Salles, A. Hemeryck, L. Martin-Samos, `JCIM, 2021`, `DOI: 10.1021/acs.jcim.1c00567 `_, `arXiv: 2111.00939 `_ 122 | 123 | 124 | 125 | SOFI 126 | ---- 127 | 128 | Symmetry Operation Finder (SOFI, [sofi2024]_) is an algorithm for finding point group symmetry operations of 129 | atomic structures. 130 | 131 | By definition, the transformation of a structure by a symmetry operation, should give a structure that 132 | is equivalent to the original, up to a permutation of indices: 133 | 134 | .. _eq-sofi2: 135 | .. math:: 136 | P_A A = \boldsymbol{\theta} A \qquad (4) 137 | 138 | where :math:`A` is an atomic structure, 139 | :math:`P_A` is a permutation of atomic indices in :math:`A`, and 140 | :math:`\boldsymbol{\theta}` is a symmetry operation in the form of 141 | 3x3 orthonormal matrix. 142 | 143 | Notice the similarity of Eq. :ref:`(4) ` to Eq. :ref:`(2) `: the structure :math:`B` is now 144 | equal to :math:`A`, the rigid transformation :math:`\mathbf{R}` becomes a symmetry 145 | operation :math:`\boldsymbol{\theta}`, and the translation :math:`\mathbf{t}` vanishes. 146 | 147 | When the structure :math:`A` contains point group symmetries, Eq. :ref:`(4) ` has degenrate 148 | solutions in form of pairs :math:`(\boldsymbol{\theta}, P_A)`. 149 | The set of all such pairs represents the set of point group symmetry operations for the structure. 150 | SOFI solves this problem. 151 | It can be seen as an extension of IRA, where IRA gives a single, optimal solution to matching two (near-)congruent 152 | structures, SOFI gives all degenerate solutions of matching a structure to itself. 153 | 154 | .. [sofi2024] M. Gunde, N. Salles, L. Grisanti, L. Martin-Samos, A. Hemeryck, `JCP, 2024`, `DOI: 10.1063/5.0215689 `_, `arXiv: 2408.06131 `_ 155 | 156 | Compilation 157 | =========== 158 | 159 | Traditional ``make`` 160 | -------------------- 161 | 162 | To compile the IRA library, you need the ``lapack`` library. 163 | On a standard linux machine, it should suffice to type: 164 | 165 | .. code-block:: bash 166 | 167 | cd src/ 168 | make all 169 | 170 | 171 | This will generate the static and shared libraries: ``libira.a``, and ``libira.so``. 172 | To compile only one of them, type ``make lib`` or ``make shlib``, respectively. 173 | 174 | To clean the build type: 175 | 176 | .. code-block:: bash 177 | 178 | make clean 179 | 180 | 181 | .. admonition:: lapack library 182 | :class: tip 183 | 184 | The lapack library is needed for compilation. 185 | Default flag is ``LIBLAPACK=-llapack``, however if you wish to change that, i.e. with openblas, you can specify it from the command as: 186 | 187 | .. code-block:: bash 188 | 189 | LIBLAPACK=-lopenblas make all 190 | 191 | If the lapack library is not available on your system, you can leave the variable undefined (this will compile a local version of the needed lapack routines, which is however not optimal): 192 | 193 | .. code-block:: bash 194 | 195 | LIBLAPACK='' make all 196 | 197 | 198 | ``conda`` compatible build 199 | -------------------------- 200 | 201 | For local machines, it is possible to use ``pixi`` [pixi-install]_ to get a working version of the 202 | Python bindings in a fairly automated manner. 203 | 204 | .. code-block:: bash 205 | 206 | curl -fsSL https://pixi.sh/install.sh | bash 207 | # build the library with openblas 208 | pixi run build_lib 209 | pixi shell # sets the environment variable 210 | cd examples/IRA 211 | python python_program.py 212 | 213 | .. [pixi-install] Installation instructions here: ``_ 214 | 215 | 216 | Linking a program to libira 217 | =========================== 218 | 219 | A program compiled with ``gcc`` or ``gfortran`` can easily link the IRA library, as-is, by linking either the shared 220 | library ``libira.so``, or the static version ``libira.a``. They are both located in the ``src/`` directory after 221 | compilation. 222 | 223 | Example for fortran program: 224 | 225 | .. code-block:: bash 226 | 227 | gfortran -o caller_program.x caller_program.f90 -L/your/path/to/IRA/src/ -lira -Wl,-rpath,/your/path/to/IRA/src 228 | 229 | The base-level implementations are not placed in modules, therefore all routines are in principle acessible to the 230 | caller. Care must be taken to ensure the correct type, kind, shape, etc. of the arguments, i.e. interface matching 231 | needs to be checked manually. 232 | The default precision is equivalent to ``c_int`` for integers, and ``c_double`` for reals. 233 | 234 | The C-headers are located in the ``IRA/interface`` directory, and can be included in compilation by ``-I/your/path/to/IRA/interface``. 235 | 236 | When linking the static library ``libira.a`` to a C-program, you need to add the math (``-lm``), and fortran (``-lgfortran``, or equivalent) to the compilation: 237 | 238 | .. code-block:: bash 239 | 240 | gcc -I/your/path/IRA/interface -o c_prog.x c_prog.c -L/your/path/to/IRA/src -lira -Wl,-rpath,/your/path/to/IRA/src -lm -lgfortran 241 | 242 | 243 | 244 | Tutorials and How-to 245 | ==================== 246 | .. _sec_ex_howto: 247 | 248 | .. toctree:: 249 | :maxdepth: 2 250 | :caption: How-to guides 251 | 252 | howto/ira_howto 253 | howto/sofi_howto 254 | 255 | 256 | (under construction) 257 | 258 | -------------------------------------------------------------------------------- /docs/interf/interf_ira.rst: -------------------------------------------------------------------------------- 1 | .. _py_ira: 2 | 3 | ############# 4 | IRA interface 5 | ############# 6 | 7 | .. autoclass:: ira_mod.IRA 8 | :members: 9 | :special-members: __init__ 10 | :member-order: bysource 11 | 12 | -------------------------------------------------------------------------------- /docs/interf/interf_sofi.rst: -------------------------------------------------------------------------------- 1 | .. _py_sofi: 2 | 3 | ############## 4 | SOFI interface 5 | ############## 6 | 7 | .. autoclass:: ira_mod.SOFI 8 | :members: 9 | :special-members: __init__ 10 | :member-order: bysource 11 | 12 | 13 | .. autoclass:: ira_mod.sym_data 14 | :members: 15 | :member-order: bysource 16 | -------------------------------------------------------------------------------- /docs/interf/py_interf.rst: -------------------------------------------------------------------------------- 1 | .. _py_interf: 2 | 3 | ################ 4 | Python interface 5 | ################ 6 | 7 | The python interface to IRA library is located in the file ``/IRA/interface/ira_mod.py``. 8 | It specifies wrappers to the fortran functions in ``IRA/src``. 9 | For complete details refer to :ref:`src_refs`. 10 | 11 | .. autoclass:: ira_mod.algo 12 | :members: 13 | 14 | 15 | Further details 16 | =============== 17 | 18 | .. toctree:: 19 | :maxdepth: 1 20 | 21 | interf_ira 22 | interf_sofi 23 | -------------------------------------------------------------------------------- /docs/src/c_wrappers.rst: -------------------------------------------------------------------------------- 1 | .. _ref_api: 2 | 3 | ############### 4 | The C-bound API 5 | ############### 6 | 7 | The API is defined in files ``library_ira.f90`` and ``library_sofi.f90``. It can be used 8 | to call IRA/SOFI routines directly from C. Calling from other languages needs an interface, see 9 | for example :ref:`py_interf`. 10 | 11 | .. note:: 12 | 13 | This file defines the wrappers to IRA routines, which are part of the 14 | shared library libira.so. 15 | The routines here are defined with "bind(C)" and "use iso_c_binding", and 16 | they effectively behave as C-code. **The arrays passed to these routines are 17 | assumed to be already allocated by the caller**, and are assumed to have 18 | C-shape. Take care of transposed matrices, starting indices (1 or 0), etc. 19 | Likewise, the **output arrays are assumed to be already allocated by the caller, 20 | and it's up to the caller to assure the correct shape and indices**. 21 | The routines here receive C-pointers to the memory where output should be 22 | written. Therefore, **the output data appears as "intent(in)"**. 23 | 24 | .. note:: 25 | 26 | There is no automatic checking of the type/kind/shape of arguments in the API. 27 | The **programmer is trusted** that her/his calls to the API functions are correct. 28 | 29 | .. contents:: Contents 30 | :local: 31 | :depth: 1 32 | 33 | 34 | CShDA & IRA 35 | =========== 36 | 37 | .. doxygenfile:: library_ira.f90 38 | :project: lib_IRA 39 | 40 | 41 | SOFI 42 | ==== 43 | 44 | .. note:: 45 | 46 | The size of arguments in many of the SOFI functions are pre-defined with the ``nmax`` variable, which 47 | is defined in sofi_tools.f90, and is by default ``nmax=400``. 48 | The actual output is written to the first ``n_mat`` elements, where ``n_mat`` is the number of matrices/symmtry operations. 49 | 50 | .. doxygenfile:: library_sofi.f90 51 | :project: lib_IRA 52 | -------------------------------------------------------------------------------- /docs/src/cshda.rst: -------------------------------------------------------------------------------- 1 | .. _ref_cshda: 2 | 3 | ############### 4 | CShDA reference 5 | ############### 6 | 7 | 8 | .. doxygenfile:: cshda.f90 9 | :project: lib_IRA 10 | 11 | -------------------------------------------------------------------------------- /docs/src/ira.rst: -------------------------------------------------------------------------------- 1 | .. _ref_ira: 2 | 3 | ############# 4 | IRA reference 5 | ############# 6 | 7 | 8 | .. doxygenfile:: ira_routines.f90 9 | :project: lib_IRA 10 | 11 | -------------------------------------------------------------------------------- /docs/src/refs.rst: -------------------------------------------------------------------------------- 1 | .. _src_refs: 2 | 3 | ############## 4 | Fortran source 5 | ############## 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | :caption: Contains 11 | 12 | cshda 13 | ira 14 | sofi 15 | -------------------------------------------------------------------------------- /docs/src/sofi.rst: -------------------------------------------------------------------------------- 1 | .. _ref_sofi: 2 | 3 | ############## 4 | SOFI reference 5 | ############## 6 | 7 | 8 | .. doxygenfile:: sofi_routines.f90 9 | :project: lib_IRA 10 | 11 | -------------------------------------------------------------------------------- /examples/IRA/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Fortran compiler 3 | # 4 | FCOMP = gfortran 5 | CCOMP=gcc 6 | 7 | # 8 | # compilation flags 9 | # 10 | FFLAGS = -fdefault-real-8 -fbounds-check 11 | CFLAGS = 12 | 13 | SRC := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 14 | SRCDIR = $(realpath $(SRC)/../../src) 15 | INTERFDIR = $(realpath $(SRC)/../../interface) 16 | OBJ = $(SRC)/Obj 17 | 18 | MOD = # -I$(OBJ) -J$(OBJ) 19 | 20 | # 21 | # path to src/libira 22 | # 23 | SHLIB = -L$(SRCDIR) -lira -Wl,-rpath,$(SRCDIR) 24 | 25 | # 26 | # when linking to static library libira.a define these: 27 | # 28 | LIBS= #-lm -lgfortran -llapack 29 | 30 | # Produced filenames 31 | # 32 | IRAUNI_X = $(SRC)/f90_program.x 33 | C_PROG = $(SRC)/c_program.x 34 | 35 | 36 | #======================= 37 | # object files 38 | # 39 | f_uni := $(SRC)/f90_program.f90 40 | o_uni := $(patsubst $(SRC)%, $(OBJ)%, $(f_uni:%.f90=%.o)) 41 | 42 | cprog := $(SRC)/c_program.c 43 | 44 | 45 | default: msg 46 | 47 | all : f90 cprog 48 | 49 | $(OBJ)%.o: $(SRC)/%.f90 50 | $(FCOMP) $(FFLAGS) $(MOD) -c $< -o $@ 51 | 52 | 53 | f90: $(OBJ) $(IRAUNI_X) 54 | cprog: $(OBJ) $(C_PROG) 55 | 56 | # 57 | # object folder 58 | # 59 | $(OBJ): 60 | @if [ ! -d $(OBJ) ]; then mkdir $(OBJ) ; fi 61 | 62 | 63 | 64 | #==== 65 | # Compile the f90 executable 66 | #==== 67 | $(IRAUNI_X): $(o_uni) 68 | $(FCOMP) $(FFLAGS) $(MOD) $^ -o $@ $(SHLIB) $(LIBS) 69 | 70 | #==== 71 | # Compile the C executable 72 | #==== 73 | $(C_PROG): $(cprog) 74 | $(CCOMP) -I$(INTERFDIR) $(CFLAGS) $(MOD) -o $@ $^ $(SHLIB) $(LIBS) 75 | 76 | 77 | msg: 78 | @echo 'Use one of the following:' 79 | @echo '' 80 | @echo ' make f90 -> compiles the f90 example program $(IRAUNI_X) executable' 81 | @echo ' make cprog -> compiles the C example program $(C_PROG)' 82 | @echo ' make all -> all of the above' 83 | @echo '' 84 | @echo ' make clean -> remove all files generated by make' 85 | @echo '' 86 | @echo 'Quitting.' 87 | 88 | 89 | 90 | 91 | clean: 92 | rm -rf $(IRAUNI_X) $(C_PROG) 93 | rm -rf $(OBJ) 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /examples/IRA/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This directory contains three example programs which utilise the IRA/CShDA algorithms: 4 | 5 | `f90_program.f90` is an example program in Fortran; 6 | `c_program.c` is an example program in C; 7 | `python_program.py` is an example program in python3. 8 | 9 | > NOTE: these are only simple examples intended to show how IRA/CShDA could be called from either of the three languages, i.e. they show how to manage the memory, how to compile the caller programs, and show some simple calls to the library. In order to change the input/output format, the programs should be modified as needed. 10 | 11 | 12 | # Compile and run 13 | 14 | The f90 and C programs need to be compiled, type (see `Makefile`): 15 | 16 | make all 17 | 18 | The f90 program is run as follows: 19 | 20 | ./f90_program.x < example_inputs/input1.xyz 21 | ./f90_program.x < example_inputs/input2.xyz 22 | ./f90_program.x < example_inputs/input3.xyz 23 | 24 | The C program is run as: 25 | 26 | ./c_program.x example_inputs/input1.xyz 27 | ./c_program.x example_inputs/input2.xyz 28 | ./c_program.x example_inputs/input3.xyz 29 | 30 | The python program needs to know the location of the `ira_mod` module, to do this type (you might also put this into your `.bashrc`): 31 | 32 | export PYTHONPATH=$PYTHONPATH:/path/to/IterativeRotationsAssignments/interface 33 | 34 | with the correct path to the `/interface` directory where `ira_mod.py` is located. 35 | Then run the python program as: 36 | 37 | python3 python_program.py 38 | -------------------------------------------------------------------------------- /examples/IRA/c_program.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* load the C headers of ira lib */ 6 | #include "iralib_interf.h" 7 | 8 | /* 9 | This is a small example program in C, that shows how to declare and call routines 10 | from the shared library shlib_ira.so, that are defined in library_ira.f90 11 | 12 | WARNING: this code is NOT very robust to read different file types, empty lines, etc. 13 | USE AT OWN RISK 14 | 15 | Note the input file containing the structure is passed as argument, not stdin. Run as: 16 | 17 | c_program.x input_file 18 | 19 | */ 20 | 21 | 22 | int main( int argc, char **argv ){ 23 | 24 | char *fname; 25 | fname = argv[1]; 26 | FILE * fp = fopen( fname, "r" ); 27 | 28 | /* ======================== */ 29 | /* read the first structure */ 30 | /* ======================== */ 31 | int nat1; 32 | int *typ1; 33 | double **coords1; 34 | 35 | /* read nat */ 36 | fscanf(fp, "%d\n", &nat1 ); 37 | 38 | /* allocate memory */ 39 | typ1=malloc( sizeof(int)*nat1); 40 | double *data1; 41 | data1=malloc(sizeof(double)*3*nat1); 42 | coords1=malloc( sizeof(double)*nat1); 43 | int n = 0; 44 | for( int i=0; i< nat1; i++){ 45 | coords1[i] = &data1[n]; 46 | n+=3; 47 | } 48 | 49 | /* read line, scratch */ 50 | fscanf( fp, "%*s\n" ); 51 | 52 | /* read atoms */ 53 | for( int i=0; i< nat1; i++ ){ 54 | fscanf( fp, "%d %lf %lf %lf\n", &typ1[i], &coords1[i][0], &coords1[i][1], &coords1[i][2] ); 55 | } 56 | 57 | 58 | /* ========================= */ 59 | /* read the second structure */ 60 | /* ========================= */ 61 | int nat2; 62 | int *typ2; 63 | double **coords2; 64 | 65 | /* read nat */ 66 | fscanf(fp, "%d\n", &nat2 ); 67 | 68 | /* allocate memory */ 69 | typ2=malloc( sizeof(int)*nat2); 70 | double *data2; 71 | data2=malloc(sizeof(double)*3*nat2); 72 | coords2=malloc( sizeof(double)*nat2); 73 | n = 0; 74 | for( int i=0; i< nat2; i++){ 75 | coords2[i] = &data2[n]; 76 | n+=3; 77 | } 78 | 79 | /* read line, scratch */ 80 | fscanf( fp, "%*s\n" ); 81 | 82 | /* read atoms */ 83 | for( int i=0; i< nat2; i++ ){ 84 | fscanf( fp, "%d %lf %lf %lf\n", &typ2[i], &coords2[i][0], &coords2[i][1], &coords2[i][2] ); 85 | } 86 | 87 | 88 | printf( "%d\n", nat1); 89 | printf("original structure1\n"); 90 | for ( int i=0; i< nat1; i++) 91 | { 92 | printf( "%d %lf %lf %lf \n", typ1[i], coords1[i][0], coords1[i][1], coords1[i][2]); 93 | } 94 | printf( "%d\n", nat2); 95 | printf("original structure2\n"); 96 | for ( int i=0; i< nat2; i++) 97 | { 98 | printf( "%d %lf %lf %lf \n", typ2[i], coords2[i][0], coords2[i][1], coords2[i][2]); 99 | } 100 | 101 | 102 | 103 | /* form the candidate arrays */ 104 | int *candidate1; 105 | int *candidate2; 106 | 107 | candidate1=malloc( sizeof(int)*nat1 ); 108 | candidate2=malloc( sizeof(int)*nat2 ); 109 | 110 | for( int i = 0; i compiles the f90 example program $(F_PROG) executable' 90 | @echo ' make cprog -> compiles the C example program $(C_PROG)' 91 | @echo ' make all -> all of the above' 92 | @echo '' 93 | @echo ' make clean -> remove all files generated by make' 94 | @echo '' 95 | @echo 'Quitting.' 96 | 97 | 98 | 99 | 100 | clean: 101 | rm -rf $(F_PROG) $(C_PROG) 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /examples/SOFI/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This directory contains three example programs which utilise the SOFI algorithm: 4 | 5 | `f90_program.f90` is an example program in Fortran; 6 | `c_program.c` is an example program in C; 7 | `python_program.py` is an example program in python3. 8 | 9 | > NOTE: these are only simple examples intended to show how SOFI could be called from either of the three languages, i.e. they show how to manage the memory, how to compile the caller programs, and show some simple calls to the library. In order to change the input/output format, the programs should be modified as needed. 10 | 11 | # Compile and run 12 | 13 | The f90 and C programs need to be compiled, type (see `Makefile`): 14 | 15 | make all 16 | 17 | The f90 program is run as follows: 18 | 19 | ./f90_program.x < example_inputs/"filename" 20 | 21 | The threshold `sym_thr` is hard-coded in `f90_program.f90`, thus the program should be recompiled upon changing the value. 22 | 23 | The C program is run as: 24 | 25 | ./c_program.x < example_inputs/"filename" 26 | 27 | In the `c_program.c`, the threshold `sym_thr` is likewise hard-coded. 28 | 29 | The python program needs to know the location of the `ira_mod` module, to do this type (you might also put this into your `.bashrc`): 30 | 31 | export PYTHONPATH=$PYTHONPATH:/path/to/IterativeRotationsAssignments/interface 32 | 33 | with the correct path to the `/interface` directory where `ira_mod.py` is located. 34 | Then run the python program as: 35 | 36 | python3 python_program.py example_inputs/"filename" 37 | 38 | # Example inputs 39 | 40 | The `example_inputs` directory contains some structures written in .xyz format. The C program expects 41 | a structure with atomic types given as integers, while the f90 and python programs can take also strings. 42 | Note that not all example inputs are compatible to the example C program. 43 | 44 | The structure `S6_D3d.xyz` has some inexact symmetries, if you set the `sym_thr=0.02` you should obtain the S6 point group, and if you set `sym_thr=0.03` it should be D3d. Likewise for some other structures, changing `sym_thr` can change the outcome. 45 | -------------------------------------------------------------------------------- /examples/SOFI/c_program.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* load the C headers of ira lib */ 6 | #include "iralib_interf.h" 7 | 8 | /* a small function to slice strings */ 9 | void slice(const char* str, char* result, size_t start, size_t end) 10 | { 11 | strncpy(result, str + start, end - start); 12 | } 13 | 14 | 15 | int main( void ){ 16 | 17 | 18 | /* check IRA version */ 19 | 20 | /* char *version; */ 21 | /* int date; */ 22 | /* version = malloc( sizeof(char)*10); */ 23 | /* libira_get_version( &version[0], &date ); */ 24 | /* printf( "version %s\n", version ); */ 25 | /* printf( "date %d\n", date); */ 26 | /* free( version ); */ 27 | 28 | 29 | 30 | int nat; //! number of atoms 31 | int *typ; //! integer of atomic types 32 | double *coords_data; //! atomic positions 33 | double **coords; 34 | double sym_thr; //! threshold for symmetry, sym_thr is read from second line of the input file 35 | 36 | // set symmetry threshold 37 | sym_thr=0.05; 38 | 39 | 40 | /* ---------------------------------------------- */ 41 | /* read an xyz file with empty second line */ 42 | scanf( "%d\n", &nat); 43 | 44 | /* alolcate typ */ 45 | typ=malloc( sizeof(int)*nat); 46 | 47 | /* allocate data space which is contigus in memory, after cut it into (3,nat) for coords */ 48 | coords_data = malloc( sizeof(double)*3*nat); 49 | /* allocate coords */ 50 | coords=malloc( sizeof(double)*nat ); 51 | /* coords[i] points to a stride in data for each atom */ 52 | int n=0; 53 | for( int i=0; i< nat; i++) 54 | { 55 | coords[i] = &coords_data[n]; 56 | n+=3; 57 | } 58 | 59 | /* read empty line */ 60 | fscanf( stdin, "\n"); 61 | 62 | /* read coords */ 63 | for( int i=0; i @details 3 | !! 4 | !! This is a program which returns the symmetry operations 5 | !! of a structure given in input. It also prints the Point Group associated 6 | !! with the list of found SymmOps. 7 | !! 8 | !! 9 | implicit none 10 | 11 | integer :: nat !< @brief number of atoms 12 | integer, allocatable :: typ(:) !< @brief integer of atomic types 13 | character(len=4), allocatable :: styp(:) !< @string of atomic types 14 | real, allocatable :: coords(:,:) !< @brief atomic positions 15 | 16 | integer :: i, j 17 | 18 | real, dimension(3) :: gc !< @brief origin point (geometric center) 19 | real :: sym_thr !< @brief threshold for symmetry operations 20 | real, allocatable :: bas_list(:,:,:) !< @brief list of symmetry operations 21 | integer, allocatable :: perm_list(:,:) !< @brief list of permutations for each symmetry operation 22 | integer :: nbas !< @brief total number of symmetry operations 23 | integer :: nmax !< @brief max space for allocation (see sofi_tools.f90) 24 | character(len=10) :: pg !< @brief the point group 25 | integer :: n_prin_ax !< @brief number of principal axes 26 | real, allocatable :: prin_ax(:,:) !< @brief list of principal axes 27 | logical :: prescreen_ih 28 | 29 | real, allocatable :: angle_out(:) !< @brief list of angles of symm operations 30 | real, allocatable :: ax_out(:,:) !< @brief list of axes of symmetry operations 31 | character(len=1), allocatable :: op_out(:) !< @brief list of Op 32 | integer, allocatable :: n_out(:) !< @brief list of n values 33 | integer, allocatable :: p_out(:) !< @brief list of p values 34 | real, allocatable :: dHausdorff_out(:) !< @brief list of dHausdorff values 35 | integer :: ierr 36 | character(128) :: msg 37 | 38 | !! nmax is imposed from sofi_tools.f90 39 | nmax = 400 40 | 41 | !! threshold for sym 42 | sym_thr = 0.05 43 | 44 | read(*,*) nat 45 | read(*,*) 46 | allocate( typ(1:nat), source=0) 47 | allocate( coords(1:3,1:nat), source=0.0) 48 | allocate( styp(1:nat), source="aaaa") 49 | do i = 1, nat 50 | !! read at typ into string 51 | read(*,*) styp(i), coords(:,i) 52 | end do 53 | !! convert atomic typ from string into integer 54 | call str_to_int( styp, typ ) 55 | 56 | !! recenter to gc 57 | gc = sum( coords(:,:),2)/nat 58 | do i = 1, nat 59 | coords(:,i) = coords(:,i) - gc 60 | end do 61 | 62 | 63 | write(*,*) nat 64 | write(*,*) 65 | do i = 1, nat 66 | write(*,*) styp(i), coords(:,i), typ(i) 67 | end do 68 | 69 | !! allocate to nmax just for space. The routine get_symmops expects size 1:nmax, 70 | !! all entries except for the first nbas elements are zero on output 71 | allocate( bas_list(1:3, 1:3, 1:nmax)) 72 | allocate( perm_list(1:nat, 1:nmax)) 73 | allocate( op_out(1:nmax)) 74 | allocate( n_out(1:nmax)) 75 | allocate( p_out(1:nmax)) 76 | allocate( ax_out(1:3, 1:nmax)) 77 | allocate( angle_out(1:nmax)) 78 | allocate( dHausdorff_out(1:nmax)) 79 | allocate( prin_ax(1:3, 1:nmax)) 80 | prescreen_ih = .False. 81 | 82 | call sofi_compute_all( nat, typ, coords, sym_thr, prescreen_ih, & 83 | nbas, bas_list, perm_list, op_out, n_out, p_out, ax_out, angle_out, dHausdorff_out, pg,& 84 | n_prin_ax, prin_ax, ierr ) 85 | if( ierr /= 0 ) then 86 | write(*,*) "f90 prog got nonzero ierr:", ierr 87 | call sofi_get_err_msg( ierr, msg ) 88 | write(*,*) trim(msg) 89 | return 90 | end if 91 | 92 | 93 | do i = 1, nbas 94 | write(*,'(2x,i0)') i 95 | write(*,'(2x,a,3x,a2,x,i0,"^",i0)') "operation:",op_out(i), n_out(i), p_out(i) 96 | write(*,'(2x, a,f9.4)') "angle",angle_out(i) 97 | write(*,'(2x, a,3f9.5)') "axis",ax_out(:,i) 98 | write(*,'(2x,a)') "matrix:" 99 | do j = 1, 3 100 | write(*,'(3f12.6)') bas_list(j,:,i) 101 | end do 102 | write(*,'(2x,a,f12.7)') "dHausdorff",dHausdorff_out(i) 103 | write(*,'(2x, a)') "permutation of atoms:" 104 | write(*,'(20i4)') perm_list(:,i) 105 | write(*,*) 106 | end do 107 | 108 | write(*,*) "PG:",trim(pg) 109 | write(*,"(a,1x,i0)") "List of principal axes, N_prin_ax =",n_prin_ax 110 | do i = 1, n_prin_ax 111 | write(*,"(3f9.4)") prin_ax(:,i) 112 | end do 113 | deallocate( bas_list, perm_list, op_out, n_out, p_out, ax_out, angle_out, dHausdorff_out, prin_ax ) 114 | 115 | ! allocate( bas_list(1:3,1:3,1:nmax)) 116 | ! allocate( perm_list(1:nat, 1:nmax)) 117 | 118 | ! !! find the symmetry operations and associated permutations 119 | ! call sofi_get_symmops( nat, typ, coords, sym_thr, nbas, bas_list, perm_list ) 120 | 121 | 122 | ! !! output symmetry operations 123 | ! write(*,*) "Number of SymmOps found:",nbas 124 | ! do i = 1, nbas 125 | ! rmat = bas_list(:,:,i) 126 | ! call sofi_analmat( rmat, op, n, p, ax, angle ) 127 | ! write(*,*) repeat('=',30) 128 | ! write(*,'(a9,a4,g0,a1,g0,a8,3f9.4,a8,x,f8.4)') "SymmOp:",op, n, '^', p, "axis:",ax, "angle:", angle 129 | ! write(*,*) 'associated matrix:' 130 | ! do j = 1, 3 131 | ! write(*,'(3f12.6)') rmat(j,:) 132 | ! end do 133 | ! end do 134 | 135 | ! write(*,*) 136 | ! write(*,*) 137 | 138 | 139 | ! write(*,*) "number of SymmOps entering get_pg:",nbas 140 | 141 | ! verb = .false. 142 | ! call sofi_get_pg( nbas, bas_list, pg, verb ) 143 | ! write(*,*) repeat('=',20) 144 | ! write(*,*) 'PG is: ',pg 145 | 146 | 147 | ! !! write the list of unique axes and angles 148 | ! allocate( op_out(1:nbas)) 149 | ! allocate( angle_out(1:nbas)) 150 | ! allocate( ax_out(3,nbas)) 151 | ! call sofi_unique_ax_angle( nbas, bas_list, op_out, ax_out, angle_out ) 152 | ! do i = 1, nbas 153 | ! write(*,'(i4,x,a,x,3f9.4,4x,f9.4)') i, op_out(i), ax_out(:,i), angle_out(i) 154 | ! end do 155 | ! deallocate( op_out, angle_out, ax_out ) 156 | 157 | 158 | ! deallocate( bas_list, perm_list ) 159 | 160 | 161 | 162 | deallocate( typ, coords ) 163 | deallocate( styp ) 164 | 165 | 166 | 167 | contains 168 | 169 | subroutine str_to_int( str_arr, int_arr ) 170 | 171 | implicit none 172 | character(len=*), dimension(:), intent(in) :: str_arr 173 | integer, dimension(:), intent(out) :: int_arr 174 | 175 | integer :: i, j, n, wlen 176 | character(:), allocatable :: old(:) 177 | logical :: isnew 178 | integer :: nnew 179 | 180 | if( size(int_arr) /= size(str_arr) ) then 181 | write(*,*) "unequal size arrays in str_to_int. Stopping" 182 | stop 183 | end if 184 | 185 | wlen = len(str_arr(1)) 186 | n = size(int_arr, 1) 187 | allocate( character(len=wlen)::old(n) ) 188 | 189 | old(:) = "xcba" 190 | nnew = 1 191 | old(nnew) = str_arr(1) 192 | do i = 2, n 193 | !! check if element is new 194 | isnew = .not. any( old .eq. str_arr(i) ) 195 | if( isnew ) then 196 | nnew = nnew + 1 197 | old( nnew ) = str_arr(i) 198 | end if 199 | end do 200 | 201 | do i = 1, n 202 | do j = 1, nnew 203 | if( str_arr(i) .eq. old(j) ) int_arr(i) = j 204 | end do 205 | end do 206 | 207 | 208 | deallocate(old) 209 | end subroutine str_to_int 210 | 211 | 212 | end program sofi 213 | 214 | -------------------------------------------------------------------------------- /examples/SOFI/python_program.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | 4 | m=len(sys.argv) 5 | if m==1: 6 | print("================================") 7 | print("usage: ./test_py.py filename.xyz") 8 | print("================================") 9 | quit() 10 | 11 | fname=sys.argv[1] 12 | 13 | 14 | def read_xyz(fname): 15 | import numpy as np 16 | typ = np.genfromtxt( fname, skip_header=2, usecols=[0], dtype=None, encoding=None ) 17 | coords = np.genfromtxt( fname, skip_header = 2, usecols = [1,2,3], dtype=np.float64 ) 18 | nat = len( typ ) 19 | cc=np.ndarray( (nat,3), dtype=np.float64, order="C" ) 20 | cc=coords 21 | return nat, typ, cc 22 | 23 | 24 | import ira_mod 25 | 26 | nat, typ, coords = read_xyz(fname) 27 | 28 | ## recenter to chosen origin; 29 | # in this case take the geometric center 30 | gc = np.mean( coords, axis=0 ) 31 | # gc=coords[:][3] 32 | coords = coords - gc 33 | 34 | ## initialize SOFI 35 | sofi = ira_mod.SOFI() 36 | 37 | ## threshold for symmetries 38 | sym_thr = 0.2 39 | 40 | ## compute all sofi data and store into "sym" object 41 | sym = sofi.compute( nat, typ, coords, sym_thr ) 42 | 43 | 44 | sym.print() 45 | 46 | -------------------------------------------------------------------------------- /interface/iralib_interf.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* declare functions from library_ira.f90 */ 4 | void libira_cshda( int nat1, int *typ1, double *coords1, \ 5 | int nat2, int *typ2, double *coords2, \ 6 | double thr, int **found, double **dists); 7 | 8 | void libira_cshda_pbc( int nat1, int *typ1, double *coords1, \ 9 | int nat2, int *typ2, double *coords2, double * lat2, \ 10 | double thr, int **found, double **dists); 11 | 12 | void libira_match(int nat1, int *typ1, double *coords1, int *cand1,\ 13 | int nat2, int *typ2, double *coords2, int *cand2, \ 14 | double km_factor, double **rmat, double **tr, int **perm, double *hd, int *cerr); 15 | 16 | void libira_get_version( char *string, int *date ); 17 | 18 | 19 | /* declare functions from library_sofi.f90 */ 20 | void libira_compute_all( int nat, int *typ, double *coords, double sym_thr, int prescreen_ih, \ 21 | int *n_mat, double **mat_data, int **perm_data, \ 22 | char **op_data, int **n_data, int **p_data, \ 23 | double **ax_data, double **angle_data, double **dHausdorff_data, char **pg, \ 24 | int* n_prin_ax, double **prin_ax, int *cerr ); 25 | 26 | void libira_get_symm_ops( int nat, int *typ, double *coords, double sym_thr, int prescreen_ih, \ 27 | int *nmat, double **mat_data, int *cerr ); 28 | 29 | void libira_get_pg( int n_mat, double *mat_data, char **pg, int* n_prin_ax, double **prin_ax, int verb, int *cerr); 30 | 31 | void libira_unique_ax_angle( int nmat, double *mat_data, \ 32 | char **op_data, double **ax_data, double **angle_data, int *cerr); 33 | 34 | void libira_analmat( double *mat, char **op, int *n, int *p, double **ax, double *angle, int *cerr); 35 | 36 | void libira_get_perm( int nat, int *typ, double *coords, \ 37 | int nmat, double *mat_data, int **perm_data, double **dHausdorff_data); 38 | 39 | void libira_get_combos( int nat, int *typ, double *coords, int nmat, double *mat_data, \ 40 | int *nmat_out, double **mat_out, int *cerr ); 41 | 42 | void libira_try_mat( int nat, int *typ, double *coords, double *rmat, double *dh, int **perm); 43 | 44 | void libira_construct_operation( char *op, double *axis, double angle, double **rmat, int *cerr); 45 | 46 | void libira_mat_combos( int nmat, double *mat_data, int *nmat_out, double **mat_out); 47 | 48 | void libira_ext_bfield( int nmat, double *mat_data, double *b_vec, \ 49 | int *nmat_out, double **mat_out); 50 | 51 | void libira_get_err_msg( int ierr, char** msg ); 52 | 53 | void libira_matrix_distance( const double *mat1, const double *mat2, double *dist); 54 | 55 | void libira_check_collinear( int nat, double *coords, int *collinear, double **ax ); 56 | -------------------------------------------------------------------------------- /pixi.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | authors = ["MAMMASMIAS Consortium"] 3 | channels = ["conda-forge"] 4 | description = "Algorithms for Iterative Rotations and Assignments (IRA), and the Symmetry Operations FInder (SOFI)." 5 | name = "IterativeRotationsAssignments" 6 | platforms = ["win-64", "linux-64", "osx-64", "osx-arm64"] 7 | version = "0.1.0" 8 | 9 | [tasks] 10 | build_lib = { cmd = "make all", cwd = "src", env = { LIBLAPACK = "-lopenblas" } } 11 | 12 | [activation] 13 | env = { PYTHONPATH = "$PYTHONPATH:$(pwd)/interface" } 14 | 15 | [dependencies] 16 | openblas = ">=0.3.28,<0.4" 17 | python = ">=3.13.1,<3.14" 18 | pip = ">=24.3.1,<25" 19 | fortran-compiler = ">=1.9.0,<2" 20 | make = ">=4.4.1,<5" 21 | numpy = "*" 22 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Fortran compiler 3 | # 4 | F90 = gfortran 5 | 6 | # 7 | # archiver 8 | # 9 | AR = ar 10 | 11 | # 12 | # lapack flag, optional: 13 | # if not provided, local copy will be compiled (not optimized). 14 | # 15 | LIBLAPACK?= -llapack 16 | 17 | 18 | #======================= 19 | # 20 | # compilation flags 21 | # 22 | FFLAGS = -fcheck=bounds -ffree-line-length-512 -fPIC -cpp -Ofast -march=native -ffast-math -funroll-loops 23 | #FFLAGS = -fcheck=bounds -ffree-line-length-512 -fPIC -cpp -O0 24 | #FFLAGS += -g -DDEBUG -pedantic -std=f2008 25 | 26 | # absolute path of this Makefile 27 | SRC := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 28 | OBJ = $(SRC)/Obj 29 | 30 | MOD = -I$(OBJ) -J$(OBJ) 31 | 32 | 33 | 34 | # Produced filenames 35 | # 36 | IRALIB = $(SRC)/libira.a 37 | SHLIB = $(SRC)/libira.so 38 | 39 | 40 | # object files 41 | # 42 | # utils 43 | futils := \ 44 | $(SRC)/ira_precision.f90 \ 45 | $(SRC)/timer.f90 \ 46 | $(SRC)/sofi_tools.f90 \ 47 | $(SRC)/err_module.f90 \ 48 | $(SRC)/version.f90 \ 49 | $(SRC)/sorting_module.f90 \ 50 | $(SRC)/set_candidate.f90 \ 51 | $(SRC)/cshda.f90 \ 52 | $(SRC)/ira_routines.f90 \ 53 | $(SRC)/sofi_routines.f90 54 | 55 | outils:= $(patsubst $(SRC)%,$(OBJ)%,$(futils:%.f90=%.o)) 56 | 57 | ifndef LIBLAPACK 58 | outils:=$(OBJ)/lap.o $(outils) 59 | endif 60 | 61 | 62 | 63 | flib := $(SRC)/library_ira.f90 \ 64 | $(SRC)/library_sofi.f90 65 | olib := $(patsubst $(SRC)%,$(OBJ)%,$(flib:%.f90=%.o)) 66 | 67 | 68 | default: msg 69 | all : lib shlib 70 | 71 | # rules 72 | $(OBJ)/%.o: $(SRC)/%.f90 73 | $(F90) $(FFLAGS) $(MOD) -c $^ -o $@ 74 | 75 | # local lapack file if needed (keep -O0 to be sure) 76 | $(OBJ)/lap.o: $(SRC)/lap.f 77 | $(F90) $(MOD) -c -fPIC -O0 -funroll-loops -g $^ -o $@ 78 | 79 | 80 | 81 | lib: $(OBJ) $(IRALIB) 82 | shlib: $(OBJ) $(SHLIB) 83 | 84 | # 85 | # object folder 86 | # 87 | $(OBJ): 88 | @if [ ! -d $(OBJ) ]; then mkdir $(OBJ) ; fi 89 | 90 | 91 | #==== 92 | # pack the library 93 | #==== 94 | $(IRALIB): $(outils) $(olib) 95 | $(AR) -rcv $@ $^ 96 | 97 | #==== 98 | # pack the shared library 99 | #==== 100 | $(SHLIB): $(outils) $(olib) 101 | $(F90) -o $@ -shared $(MOD) $^ $(FFLAGS) $(LIBLAPACK) -Wl,-soname,libira.so 102 | 103 | msg: 104 | @echo 'Use one of the following:' 105 | @echo '' 106 | @echo ' make lib -> compiles the $(IRALIB) library' 107 | @echo ' make shlib -> compiles the $(SHLIB) shared library' 108 | @echo ' make all -> all of the above' 109 | @echo '' 110 | @echo ' make clean -> remove all files generated by make' 111 | @echo '' 112 | @echo 'Quitting.' 113 | 114 | 115 | 116 | 117 | clean: 118 | rm -rf $(IRALIB) $(SHLIB) 119 | rm -rf $(OBJ) 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | 4 | 5 | 6 | This directory contains the source code to IRA, CShDA, and SOFI algorithms, implemented in Fortran. 7 | Further descriptions of these algorithms are given in [[1]](#1), [[2]](#2), and [[3]](#3). 8 | 9 | The default compilation also produces a shared library `libira.so`, which contains wrapper routines to the main IRA, CShDA, and SOFI routines, such that they are bound to the C-namespace via `bind(C)` and `use iso_c_binding`. 10 | 11 | # IRA: Iterative Rotations and Assignments 12 | 13 | The Iterative Rotations and Assignments (IRA) algorithm is a shape matching 14 | algorithm for matching generic atomic structures, including structures with 15 | different number of atoms. 16 | 17 | # CShDA: Constrained Shortest Distance Assignment 18 | 19 | The Constrained Shortest Distance Assignment (CShDA) algorithm is a Linear 20 | Assignment Problem (LAP) solver, which is used by the IRA algorithm. 21 | 22 | # SOFI: Symmetry Operations FInder 23 | 24 | The Symmetry Operations FInder (SOFI) algorithm is a modification of IRA, that 25 | is designed to find the symmetry operations of a given structure, by solving the 26 | degenerate shape-matching problem. It also uses the CShDA algorithm. 27 | 28 | ## Files 29 | 30 | Source files: 31 | 32 | - cshda.f90 : the CShDA routine for pbc and non-pbc cases; 33 | - ira_routines.f90 : all the routines specific to IRA; 34 | - set_candidate.f90 : routines for selecting candidate central atoms and their vectors; 35 | - read_typ.f90 : a function to read atomic types from xyz format; 36 | - sorting_module.f90 : the mergesort algorithm; 37 | - sofi_tools.f90 : misc routines and definitions for SOFI; 38 | - sofi_routines : all routines specific to SOFI; 39 | - library_ira.f90 : C-bound wrappers to the main routines of IRA, compiles into `libira.so`; 40 | - library_sofi.f90 : C-bound wrappers to the main routines of SOFI, compiles into `libira.so` 41 | 42 | ## Compile 43 | 44 | TO COMPILE: (you need lapack library, see the Makefile) 45 | 46 | make all 47 | 48 | 49 | ## References 50 | [1] 51 | Gunde M., Salles N., Hemeryck A., Martin Samos L. 52 | *IRA: A shape matching approach for recognition and comparison of generic atomic patterns*, 53 | Journal of Chemical Information and Modeling (2021), DOI: [https://doi.org/10.1021/acs.jcim.1c00567](https://doi.org/10.1021/acs.jcim.1c00567), HAL: [hal-03406717](https://hal.laas.fr/hal-03406717), arXiv: [2111.00939](https://export.arxiv.org/abs/2111.00939) 54 | 55 | [2] 56 | Gunde M.: *Development of IRA: a shape matching algorithm, its implementation 57 | and utility in a general off-lattice kMC kernel*, PhD dissertation, 58 | November 2021. 59 | [PDF link](http://thesesups.ups-tlse.fr/5109/1/2021TOU30132.pdf) 60 | 61 | [3] 62 | Gunde M., Salles N., Grisanti L., Hemeryck A., Martin Samos L. 63 | *SOFI: Finding point group symmetries in atomic clusters as finding the set of degenerate solutions in a shape-matching problem*, 64 | Journal of Chemical Information and Modeling (submitted) 65 | -------------------------------------------------------------------------------- /src/err_module.f90: -------------------------------------------------------------------------------- 1 | module err_module 2 | 3 | use ira_precision 4 | implicit none 5 | private 6 | public :: get_err_msg 7 | 8 | integer(ip), parameter, public :: & 9 | !! 10 | !! IRA errors 11 | ERR_TOO_SMALL_KMAX = -1, & 12 | ERR_OTHER = -2, & 13 | ERR_BETA = -3, & 14 | ERR_SVD = -9, & 15 | !! 16 | !! SOFI errors 17 | ERR_DETERMINANT = -4, & 18 | ERR_ACOS_ARG = -5, & 19 | ERR_SIZE_NMAX = -6, & 20 | ERR_LIST_TOO_SMALL = -7, & 21 | ERR_UNKNOWN_OP = -8 22 | 23 | character(len=*), parameter :: warn = "WARNING FROM IRA/SOFI library :::" 24 | character(len=*), parameter :: err = "ERROR FROM IRA/SOFI library :::" 25 | contains 26 | 27 | function get_err_msg( ierr )result(msg) 28 | integer(ip), intent(in) :: ierr 29 | character(:), allocatable :: msg 30 | 31 | character(len=512) :: str 32 | 33 | 34 | select case( ierr ) 35 | case( ERR_TOO_SMALL_KMAX ) 36 | write(str, "(5x, a,1x,a)") err, & 37 | "No basis could be found. Possible cause: too small `kmax_factor` value." 38 | 39 | case( ERR_OTHER ) 40 | write(str, "(5x,a,1x,a,1x,i0)") err, & 41 | "Some other error has occured ... ierr =",ierr 42 | 43 | case( ERR_BETA ) 44 | write(str, "(5x,a,1x,a)") err, & 45 | "Cannot set beta basis!" 46 | 47 | case( ERR_SVD ) 48 | write(str, "(5x,a,1x,a)") err, & 49 | "Lapack could not compute SVD." 50 | 51 | case( ERR_DETERMINANT ) 52 | write(str, "(5x,a,1x,a)") err, & 53 | "Value of matrix determinant out of range!" 54 | 55 | case( ERR_ACOS_ARG ) 56 | write(str, "(5x,a,1x,a)") err, & 57 | "Invalid value for acos argument, should be strictly on range [-1:1]" 58 | 59 | case( ERR_SIZE_NMAX ) 60 | write(str, "(5x,a,1x,a)") err, & 61 | "The size of list for symmetry operation matrices is not nmax!" 62 | 63 | case( ERR_LIST_TOO_SMALL ) 64 | write(str, "(5x,a,1x,a)") err, & 65 | "Number of symm operations exceeds the list size! Check your configuration." 66 | 67 | case( ERR_UNKNOWN_OP ) 68 | write(str, "(5x,a,1x,a)") err, & 69 | "Unknown operation!" 70 | 71 | case default 72 | write(str, "(5x, a,1x,a,1x,i0)") warn, & 73 | "Unknown error code:", ierr 74 | 75 | end select 76 | 77 | allocate( msg, source=trim(str) ) 78 | end function get_err_msg 79 | 80 | 81 | end module err_module 82 | 83 | -------------------------------------------------------------------------------- /src/ira_precision.f90: -------------------------------------------------------------------------------- 1 | module ira_precision 2 | use, intrinsic :: iso_c_binding, only: ip => c_int, rp => c_double 3 | implicit none 4 | 5 | private 6 | public :: rp, ip 7 | 8 | end module ira_precision 9 | -------------------------------------------------------------------------------- /src/library_ira.f90: -------------------------------------------------------------------------------- 1 | !! 2 | !! Copyright (C) 2023, MAMMASMIAS Consortium 3 | !! Written by: Miha Gunde 4 | !! 5 | !! SPDX-License-Identifier: GPL-3.0-or-later 6 | !! SPDX-License-Identifier: Apache-2.0 7 | !! See the file LICENSE.txt for further information. 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 | !> @defgroup info_libira 17 | !> @{ 18 | 19 | !!=============================================================================== 20 | !! @note 21 | !! This file defines the wrappers to IRA routines, which are part of the 22 | !! shared library libira.so. 23 | !! The routines here are defined with "bind(C)" and "use iso_c_binding", and 24 | !! they effectively behave as C-code. The arrays passed to these routines are 25 | !! assumed to be already allocated by the caller, and are assumed to have 26 | !! C-shape. Take care of transposed matrices, starting indices (1 or 0), etc. 27 | !! Likewise, the output arrays are assumed to be already allocated by the caller, 28 | !! and it's up to the caller to assure the correct shape and indices. 29 | !! The routines here receive C-pointers to the memory where output should be 30 | !! stored. Therefore, the output data appears as "intent(in)". 31 | !!=============================================================================== 32 | !> @} 33 | 34 | !> @brief wrapper to the cshda routine from cshda.f90 35 | !! 36 | !! @details 37 | !! All parameters are as intent(in). The output is written into arrays 38 | !! ``found`` and ``dists``, which are assumed to be allocated by the caller 39 | !! to their correct size. ``size(found) = size(dists) = [nat2]`` 40 | !! 41 | !! @param[in] nat1 :: number of atoms in structure 1 42 | !! @param[in] typ1(nat1) :: atomic types of structure 1 43 | !! @param[in] coords1(3,nat1) :: atomic positions of structure 1 44 | !! @param[in] nat2 :: number of atoms in structure 2 45 | !! @param[in] typ2(nat2) :: atomic types of structure 2 46 | !! @param[in] coords2(3,nat2) :: atomic positions of structure 2 47 | !! @param[in] thr :: threshold for the Hausdorff distance, used for early exit; 48 | !! @param[in] found(nat2) :: list of assigned atoms of conf 2 to conf 1: 49 | !! e.g. found(3) = 9 means atom 3 from conf 1 is assigned 50 | !! to atom 9 in conf 2; 51 | !! @param[in] dists(nat2) :: distances from atom i in conf 1 to atom found(i) in conf 2; 52 | !! @return found, dists 53 | !! 54 | !! 55 | !! C-header: 56 | !! ~~~~~~~~~~~~~~~{.c} 57 | !! void libira_cshda( int nat1, int *typ1, double *coords1, \ 58 | !! int nat2, int *typ2, double *coords2, \ 59 | !! double thr, int **found, double **dists); 60 | !! ~~~~~~~~~~~~~~~ 61 | !! 62 | subroutine libira_cshda( nat1, typ1, coords1, nat2, typ2, coords2, thr, found, dists )& 63 | bind(C, name="libira_cshda") 64 | use, intrinsic :: iso_c_binding 65 | implicit none 66 | integer(c_int), value, intent(in) :: nat1 67 | type( c_ptr ), value, intent(in) :: typ1 68 | type( c_ptr ), value, intent(in) :: coords1 69 | integer(c_int), value, intent(in) :: nat2 70 | type( c_ptr ), value, intent(in) :: typ2 71 | type( c_ptr ), value, intent(in) :: coords2 72 | real( c_double ), value, intent(in) :: thr 73 | !! 74 | type( c_ptr ), intent(in) :: found 75 | type( c_ptr ), intent(in) :: dists 76 | 77 | !! f ptrs 78 | integer(c_int), dimension(:), pointer :: p_typ1, p_typ2, p_found 79 | real( c_double ), dimension(:,:), pointer :: p_coords1, p_coords2 80 | real( c_double ), dimension(:), pointer :: p_dists 81 | 82 | !! local f memory 83 | real(c_double) :: some_thr 84 | 85 | integer :: i 86 | 87 | !! connect c ptrs to f 88 | call c_f_pointer( typ1, p_typ1, [nat1] ) 89 | call c_f_pointer( typ2, p_typ2, [nat2] ) 90 | 91 | call c_f_pointer( coords1, p_coords1, [3,nat1] ) 92 | call c_f_pointer( coords2, p_coords2, [3,nat2] ) 93 | 94 | call c_f_pointer( found, p_found, [nat2] ) 95 | call c_f_pointer( dists, p_dists, [nat2] ) 96 | 97 | some_thr=thr 98 | 99 | call cshda( nat1, p_typ1, p_coords1, nat2, p_typ2, p_coords2, some_thr, & 100 | p_found, p_dists ) 101 | 102 | !! output C-style indices, starting at 0 103 | p_found = p_found - 1 104 | 105 | end subroutine libira_cshda 106 | 107 | 108 | !> @brief wrapper to the cshda_pbc routine from cshda.f90 109 | !! 110 | !! @details 111 | !! All parameters are as intent(in). The output is written into arrays 112 | !! ``found`` and ``dists``, which are assumed to be allocated by the caller 113 | !! to their correct size. ``size(found) = size(dists) = [nat2]`` 114 | !! 115 | !! @param[in] nat1 :: number of atoms in structure 1 116 | !! @param[in] typ1(nat1) :: atomic types of structure 1 117 | !! @param[in] coords1(3,nat1) :: atomic positions of structure 1 118 | !! @param[in] nat2 :: number of atoms in structure 2 119 | !! @param[in] typ2(nat2) :: atomic types of structure 2 120 | !! @param[in] coords2(3,nat2) :: atomic positions of structure 2 121 | !! @param[in] lat2(3,3) :: lattice vectors of conf 2 in C order; 122 | !! @param[in] thr :: threshold for the Hausdorff distance, used for early exit; 123 | !! @param[in] found(nat2) :: list of assigned atoms of conf 2 to conf 1: 124 | !! e.g. found(3) = 9 means atom 3 from conf 1 is assigned 125 | !! to atom 9 in conf 2. Indices in C order (start at 0); 126 | !! @param[in] dists(nat2) :: distances from atom i in conf 1 to atom found(i) in conf 2; 127 | !! @return found, dists 128 | !! 129 | !! 130 | !! C-header: 131 | !! ~~~~~~~~~~~~~~~{.c} 132 | !! void libira_cshda_pbc( int nat1, int *typ1, double *coords1, \ 133 | !! int nat2, int *typ2, double *coords2, double * lat2, \ 134 | !! double thr, int **found, double **dists); 135 | !! ~~~~~~~~~~~~~~~ 136 | !! 137 | subroutine libira_cshda_pbc( nat1, typ1, coords1, nat2, typ2, coords2, lat, thr, found, dists )& 138 | bind(C, name="libira_cshda_pbc") 139 | !! wrapper to the cshda_pbc routine from cshda.f90 140 | use, intrinsic :: iso_c_binding 141 | implicit none 142 | integer(c_int), value, intent(in) :: nat1 143 | type( c_ptr ), value, intent(in) :: typ1 144 | type( c_ptr ), value, intent(in) :: coords1 145 | integer(c_int), value, intent(in) :: nat2 146 | type( c_ptr ), value, intent(in) :: typ2 147 | type( c_ptr ), value, intent(in) :: coords2 148 | type( c_ptr ), value, intent(in) :: lat 149 | real( c_double ), value, intent(in) :: thr 150 | !! 151 | type( c_ptr ), intent(in) :: found 152 | type( c_ptr ), intent(in) :: dists 153 | 154 | !! f ptrs 155 | integer(c_int), dimension(:), pointer :: p_typ1, p_typ2, p_found 156 | real( c_double ), dimension(:,:), pointer :: p_coords1, p_coords2 157 | real( c_double ), dimension(:), pointer :: p_dists, p_lat 158 | 159 | !! local f memory 160 | real(c_double), dimension(3,3) :: f_lat 161 | real(c_double) :: some_thr 162 | 163 | 164 | !! connect c ptrs to f 165 | call c_f_pointer( typ1, p_typ1, [nat1] ) 166 | call c_f_pointer( typ2, p_typ2, [nat2] ) 167 | call c_f_pointer( coords1, p_coords1, [3,nat1] ) 168 | call c_f_pointer( coords2, p_coords2, [3,nat2] ) 169 | call c_f_pointer( lat, p_lat, [9] ) 170 | f_lat = reshape( p_lat, [3,3] ) 171 | !! receive C-style array, do transpose 172 | f_lat = transpose( f_lat ) 173 | 174 | ! write(*,*) "Received lattice vectors:" 175 | ! write(*,*) "vec 1:", f_lat(1,:) 176 | ! write(*,*) "vec 2:", f_lat(2,:) 177 | ! write(*,*) "vec 3:", f_lat(3,:) 178 | 179 | call c_f_pointer( found, p_found, [nat2] ) 180 | call c_f_pointer( dists, p_dists, [nat2] ) 181 | 182 | some_thr=thr 183 | 184 | call cshda_pbc( nat1, p_typ1, p_coords1, nat2, p_typ2, p_coords2, f_lat, & 185 | some_thr, p_found, p_dists ) 186 | 187 | !! output C-style array, strating indices at 0 188 | p_found = p_found - 1 189 | 190 | end subroutine libira_cshda_pbc 191 | 192 | !> @brief the IRA matching procedure 193 | !! 194 | !! @details 195 | !! wrapper call to match two structures. This includes call to ira_unify to get apx, 196 | !! and then call to svdrot_m to obtain final match. Routines from ira_routines.f90 197 | !! 198 | !! @note 199 | !! Warning: 200 | !! the indices in candidate1, and candidate2 need to be F-style (start by 1) on input! 201 | !! 202 | !! The result can be applied to struc2, as: 203 | !! ~~~~~~~~~~~~~~~{.f90} 204 | !! idx = permutation(i) 205 | !! coords2(:,i) = matmul( rotation, coords2(:,idx) ) + translation 206 | !! ~~~~~~~~~~~~~~~ 207 | !! 208 | !! @param[in] nat1 :: number of atoms in structure 1 209 | !! @param[in] typ1(nat1) :: atomic types of structure 1 210 | !! @param[in] coords1(3,nat1) :: atomic positions of structure 1 211 | !! @param[in] candidate1(nat1) :: list of candidate central atoms in structure 1 212 | !! @param[in] nat2 :: number of atoms in structure 2 213 | !! @param[in] typ2(nat2) :: atomic types of structure 2 214 | !! @param[in] coords2(3,nat2) :: atomic positions of structure 2 215 | !! @param[in] candidate2(nat2) :: list of candidate central atoms in structure 2 216 | !! @param[in] kmax_factor :: the factor to multiply kmax (should be > 1.0) 217 | !! @param[in] rotation(3,3) :: the 3x3 rotation matrix in C order 218 | !! @param[in] translation(3) :: the 3D translation vector 219 | !! @param[in] permutation(nat2) :: the atomic permutations in C order (start at 0) 220 | !! @param[out] hd :: final value of the Hausdorff distance 221 | !! @param[out] cerr :: error value (negative on error, zero otherwise) 222 | !! @returns rotation, translation, permutation, hd, cerr 223 | !! 224 | !! C-header: 225 | !! ~~~~~~~~~~~~~~~{.c} 226 | !! void libira_match(int nat1, int *typ1, double *coords1, int *cand1,\ 227 | !! int nat2, int *typ2, double *coords2, int *cand2, \ 228 | !! double km_factor, double **rmat, double **tr, \ 229 | !! int **perm, double *hd, int *cerr); 230 | !! ~~~~~~~~~~~~~~~ 231 | subroutine libira_match( nat1, typ1, coords1, candidate1, & 232 | nat2, typ2, coords2, candidate2, & 233 | kmax_factor, rotation, translation, permutation, hd, cerr ) bind(C, name="libira_match") 234 | use, intrinsic :: iso_c_binding 235 | use err_module, only: get_err_msg 236 | implicit none 237 | integer(c_int), value, intent(in) :: nat1 238 | type( c_ptr ), value, intent(in) :: typ1 239 | type( c_ptr ), value, intent(in) :: coords1 240 | type( c_ptr ), value, intent(in) :: candidate1 241 | integer(c_int), value, intent(in) :: nat2 242 | type( c_ptr ), value, intent(in) :: typ2 243 | type( c_ptr ), value, intent(in) :: coords2 244 | type( c_ptr ), value, intent(in) :: candidate2 245 | real( c_double ), value, intent(in) :: kmax_factor 246 | !! 247 | type( c_ptr ), intent(in) :: rotation 248 | type( c_ptr ), intent(in) :: translation 249 | type( c_ptr ), intent(in) :: permutation 250 | real(c_double), intent(out) :: hd 251 | integer( c_int ), intent(out) :: cerr 252 | 253 | !! f ptrs 254 | integer(c_int), dimension(:), pointer :: p_typ1, p_typ2, p_c1, p_c2, p_perm 255 | real( c_double ), dimension(:,:), pointer :: p_coords1, p_coords2 256 | real( c_double ), dimension(:,:), pointer :: p_matrix 257 | real(c_double), dimension(:), pointer :: p_tr 258 | 259 | integer :: i, ierr 260 | real( c_double ), dimension(3,nat2) :: fcoords2 261 | integer(c_int), dimension(nat2) :: ftyp2 262 | real( c_double ), dimension(3,3) :: srot 263 | real( c_double ), dimension(3) :: str, rdum 264 | real( c_double ) :: pp 265 | 266 | !! connect c ptrs to f 267 | call c_f_pointer( typ1, p_typ1, [nat1] ) 268 | call c_f_pointer( typ2, p_typ2, [nat2] ) 269 | call c_f_pointer( coords1, p_coords1, [3,nat1] ) 270 | call c_f_pointer( coords2, p_coords2, [3,nat2] ) 271 | call c_f_pointer( candidate1, p_c1, [nat1] ) 272 | call c_f_pointer( candidate2, p_c2, [nat2] ) 273 | 274 | call c_f_pointer( rotation, p_matrix, [3,3] ) 275 | call c_f_pointer( translation, p_tr, [3] ) 276 | call c_f_pointer( permutation, p_perm, [nat2] ) 277 | 278 | !! get apx 279 | call ira_unify( nat1, p_typ1, p_coords1, p_c1, & 280 | nat2, p_typ2, p_coords2, p_c2, & 281 | kmax_factor, p_matrix, p_tr, p_perm, hd, ierr ) 282 | cerr = int( ierr, c_int ) 283 | ! write(*,*) "HD after unify",hd 284 | if( ierr /= 0 ) then 285 | write(*,*) "ERROR in libira_match" 286 | write(*,*) get_err_msg( ierr ) 287 | return 288 | end if 289 | 290 | 291 | !! transform 292 | ftyp2(:) = p_typ2(p_perm(:)) 293 | fcoords2(:,:) = p_coords2(:,p_perm(:)) 294 | do i = 1, nat2 295 | fcoords2(:,i) = matmul( p_matrix, fcoords2(:,i)) + p_tr 296 | end do 297 | 298 | !! call svd 299 | call svdrot_m( nat1, p_typ1, p_coords1, & 300 | nat1, ftyp2(1:nat1), fcoords2(:,1:nat1), & 301 | srot, str, ierr ) 302 | if( ierr /= 0 ) then 303 | return 304 | end if 305 | 306 | 307 | !! apply svd 308 | do i = 1, nat2 309 | fcoords2(:,i) = matmul( srot, fcoords2(:,i) ) + str 310 | end do 311 | 312 | !! measure dH 313 | pp=0.0 314 | do i = 1, nat1 315 | rdum = p_coords1(:,i) - fcoords2(:,i) 316 | pp = max( pp, norm2(rdum) ) 317 | end do 318 | hd=pp 319 | 320 | !! put together 321 | p_matrix = matmul( srot, p_matrix ) 322 | p_tr = matmul(srot, p_tr) + str 323 | 324 | !! return C-style data 325 | p_matrix = transpose( p_matrix ) 326 | p_perm(:) = p_perm(:) - 1 327 | 328 | end subroutine libira_match 329 | 330 | 331 | !> @details 332 | !! Get the IRA version string, and date. 333 | !! This is a wrapper to get_version from version.f90 334 | !! 335 | !! C header: 336 | !!~~~~~~~~~~~~~~~~~~~~~~~~~{.c} 337 | !! void libira_get_version( char *string, int *date ); 338 | !!~~~~~~~~~~~~~~~~~~~~~~~~~ 339 | !! 340 | !! @param[out] cstring(10) :: version string 341 | !! @param[out] cdate :: version date, format YYYYmm 342 | !! @returns cstring, cdate 343 | !! 344 | subroutine libira_get_version( cstring, cdate )bind(C, name="libira_get_version") 345 | use, intrinsic :: iso_c_binding, only: c_int, c_null_char, c_char 346 | implicit none 347 | character(len=1, kind=c_char), dimension(6) :: cstring 348 | integer( c_int ) :: cdate 349 | 350 | character(5) :: fstring 351 | integer :: fdate, i, n 352 | 353 | call ira_get_version( fstring, fdate ) 354 | cdate = int( fdate, c_int ) 355 | n = len_trim(fstring) 356 | if( n .gt. 5 ) write(*,*) "WARNING: IRA version string seems long, check!" 357 | do i = 1, n 358 | cstring(i) = fstring(i:i) 359 | end do 360 | cstring(n+1) = c_null_char 361 | 362 | end subroutine libira_get_version 363 | -------------------------------------------------------------------------------- /src/set_candidate.f90: -------------------------------------------------------------------------------- 1 | !> @brief 2 | !! Subroutine to set candidate central atoms for structures 1 and 2. 3 | !! 4 | !! The decision is currently based simply on the total number of atoms, 5 | !! if the number of atoms is equal, then both candidates get a -1 value, 6 | !! otherwise the value of candidates is the atomic index of desired 7 | !! central atom. 8 | !! 9 | !! UPDATE NEEDED: 10 | !! candidate central atoms in 2 can only be of the same typ as in 1 11 | subroutine set_candidates( nat1, typ1, coords1, & 12 | nat2, typ2, coords2, & 13 | candidate1, candidate2 ) 14 | 15 | use ira_precision 16 | implicit none 17 | integer(ip), intent(in) :: nat1 18 | integer(ip), dimension(nat1), intent(in) :: typ1 19 | real(rp), dimension(3,nat1), intent(in) :: coords1 20 | integer(ip), intent(in) :: nat2 21 | integer(ip), dimension(nat2), intent(in) :: typ2 22 | real(rp), dimension(3,nat2), intent(in) :: coords2 23 | integer(ip), dimension(nat1), intent(out) :: candidate1 24 | integer(ip), dimension(nat2), intent(out) :: candidate2 25 | 26 | integer(ip) :: i, dnat, k 27 | 28 | candidate1(:) = 0 29 | candidate2(:) = 0 30 | !! 31 | !! some preprocessing 32 | dnat = nat2-nat1 33 | !! 34 | if( dnat .eq. 0 ) then 35 | !! nat1 = nat2 36 | 37 | !! value -1 indicates some special vector (geometrical center) 38 | candidate1(1) = -1 39 | 40 | !! value -1 indicates some special vector (geometrical center) 41 | candidate2(1) = -1 42 | 43 | 44 | elseif( dnat .gt. 0) then 45 | !! nat2 > nat1 46 | 47 | !! in struc 1 take the first atom 48 | candidate1(1) = 1 49 | 50 | !! in struc 2 take all atoms 51 | k = 1 52 | do i = 1, nat2 53 | if( typ2(i) .ne. typ1(1) ) cycle 54 | candidate2(k) = i 55 | k = k + 1 56 | end do 57 | 58 | else 59 | write(*,*) 'error in set_candidate' 60 | stop 61 | endif 62 | 63 | 64 | 65 | end subroutine set_candidates 66 | 67 | 68 | 69 | subroutine select_rc( nat, coords, c_idx, rc ) 70 | use ira_precision 71 | implicit none 72 | integer(ip), intent(in) :: nat 73 | real(rp), dimension(3,nat), intent(in) :: coords 74 | integer(ip), intent(in) :: c_idx 75 | real(rp), dimension(3), intent(out) :: rc 76 | 77 | if( c_idx .eq. 0 ) then 78 | write(*,*) 'ERROR in select_rc' 79 | return 80 | endif 81 | 82 | if( c_idx .eq. -1 ) then 83 | !! rc is geometric center 84 | rc = sum(coords(:,:),2)/nat 85 | else 86 | !! rc is vector of atom c_idx 87 | rc = coords(:,c_idx) 88 | endif 89 | 90 | return 91 | end subroutine select_rc 92 | 93 | -------------------------------------------------------------------------------- /src/sofi_tools.f90: -------------------------------------------------------------------------------- 1 | !! 2 | !! Copyright (C) 2023, MAMMASMIAS Consortium 3 | !! Written by: Miha Gunde 4 | !! 5 | !! SPDX-License-Identifier: GPL-3.0-or-later 6 | !! SPDX-License-Identifier: Apache-2.0 7 | !! See the file LICENSE.txt for further information. 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 | 17 | module sofi_tools 18 | 19 | use ira_precision 20 | implicit none 21 | public 22 | 23 | !! maximum size of output lists. The real number of elements is output as integer 24 | !! This is pre-defined for security. 25 | integer(ip), parameter :: nmax = 400 26 | 27 | 28 | !! matrix-distance thr: value 1.4 captures 1/6*2pi rotations, which are 29 | !! needed to distinguish D2h from D6h, since D2h is subgroup of D6h! 30 | !! see the comment under matrix_distance in sofi_tools.f90 for some ref. values 31 | !! 1.07 captures 1/8*2pi 32 | !! NOTE: decreasing this value gives "higher resolution", but slows down the algo! 33 | !! Also, groups with order > 8 are super rare in atomic clusters. But can happen in 34 | !! for example nanotubes, where main ax is in center of tube, around this ax 35 | !! many rotations can happen, then order of group can be any. 36 | ! real(rp), parameter :: m_thr = 1.4 !! C6 37 | ! real(rp), parameter :: m_thr = 1.07 !! C8 38 | ! real(rp), parameter :: m_thr = 0.73 !! C12 39 | ! real(rp), parameter :: m_thr = 0.49 !! C18 40 | ! real(rp), parameter :: m_thr = 0.36 !! C24 41 | ! real(rp), parameter :: m_thr = 0.19 !! C48 42 | ! real(rp), parameter :: m_thr = 0.092 !! C96 43 | real(rp), parameter :: m_thr = 0.044 !! C200 44 | 45 | 46 | !! Schoenflies symbols for operations 47 | character(len=1), parameter :: & 48 | OP_ERROR = "X", & 49 | OP_IDENTITY = "E", & 50 | OP_INVERSION = "I", & 51 | OP_PROP_ROT = "C", & 52 | OP_IMPROP_ROT = "S", & 53 | OP_MIRROR = "S" !! "M" 54 | 55 | 56 | real(rp), parameter :: pi = 4.0*atan(1.0) 57 | real(rp), parameter :: epsilon = 1e-6 58 | real(rp), parameter :: collinearity_thr = 0.95 59 | 60 | !! limit value for n in sofi_analmat. 61 | ! integer(ip), parameter :: lim_n_val = 24 62 | ! integer(ip), parameter :: lim_n_val = 48 63 | ! integer(ip), parameter :: lim_n_val = 96 64 | integer(ip), parameter :: lim_n_val = 200 65 | 66 | contains 67 | 68 | subroutine cross_prod( a, b, c ) 69 | !> @brief Cross product of two vectors 70 | implicit none 71 | real(rp), dimension(3), intent(in) :: a 72 | real(rp), dimension(3), intent(in) :: b 73 | real(rp), dimension(3), intent(out) :: c 74 | 75 | c(1) = a(2)*b(3) - a(3)*b(2) 76 | c(2) = a(3)*b(1) - a(1)*b(3) 77 | c(3) = a(1)*b(2) - a(2)*b(1) 78 | 79 | end subroutine cross_prod 80 | 81 | 82 | recursive function gcd_rec(u, v) result(gcd) 83 | !! greatest common denominator 84 | integer :: gcd 85 | integer(ip), intent(in) :: u, v 86 | 87 | if (mod(u, v) /= 0) then 88 | gcd = gcd_rec(v, mod(u, v)) 89 | else 90 | gcd = v 91 | end if 92 | end function gcd_rec 93 | 94 | 95 | subroutine matrix_distance( a, b, dist ) 96 | !! distance between two 3x3 matrices a and b, computed element-wise as sqrt( sum( (a-b)^2 ) ) 97 | !! This is an arbitrary choice. 98 | !! 99 | !! Some reference values of distance: 100 | !! 101 | !! a, b rotated by angle || dist 102 | !! 1/2 * 2pi || 2.8 103 | !! 1/3 * 2pi || 2.45 104 | !! 1/6 * 2pi || 1.4142 105 | !! 1/12 * 2pi || 0.7321 106 | !! 1/18 * 2pi || 0.4912 107 | !! 1/24 * 2pi || 0.3692 108 | implicit none 109 | real(rp), dimension(3,3), intent(in) :: a, b 110 | real(rp), intent(out) :: dist 111 | 112 | integer(ip) :: i, j 113 | 114 | dist = 0.0 115 | do i = 1, 3 116 | do j =1, 3 117 | dist = dist + ( a(i,j) - b(i,j) )**2 118 | end do 119 | end do 120 | dist = sqrt(dist) 121 | ! call md(a,b,dist) 122 | 123 | end subroutine matrix_distance 124 | 125 | subroutine md(a,b,dist) 126 | !! unused 127 | implicit none 128 | real(rp), dimension(3,3), intent(in) :: a, b 129 | real(rp), intent(out) :: dist 130 | 131 | real(rp), dimension(3,3) :: rij 132 | real(rp) :: tr 133 | 134 | rij = matmul( a, transpose(b) ) 135 | rij = transpose(rij) 136 | tr = rij(1,1) + rij(2,2) + rij(3,3) - 3.0 137 | tr = abs(tr) 138 | dist = tr 139 | 140 | end subroutine md 141 | 142 | 143 | SUBROUTINE diag(n, A, eigvals, vec) 144 | !> @brief 145 | !! assuming a general square matrix (can be nonsymmetric). 146 | !! On output A is overwritten by eigenvectors in rows, if vec=0, then 147 | !! A is just 0.0 on output. 148 | !! 149 | !! @param [in] n dimension of matrix A 150 | !! @param [inout] A matrix to be diagonalised, overwritten by eigenvectors in columns on output 151 | !! @param [out] eigvals output vector of eigenvalues, not sorted! 152 | !! @param [in] vec 0 if don't want to compute eigenvectors, 1 otherwise 153 | !! 154 | IMPLICIT NONE 155 | INTEGER(IP), intent(in) :: n 156 | REAL(RP), DIMENSION(n,n), intent(inout) :: A 157 | REAL(RP), DIMENSION(n), intent(out) :: eigvals 158 | INTEGER(IP), intent(in) :: vec 159 | REAL(RP), DIMENSION(n) :: eigvals_i !! imaginary part of the eigenvalues 160 | REAL(RP), DIMENSION(n,n) :: eigvec 161 | INTEGER(IP) :: lda 162 | INTEGER(IP) :: lwork 163 | REAL(RP) :: Dummy(1000) 164 | INTEGER(IP) :: info 165 | CHARACTER(len=1) :: getvec 166 | 167 | real(rp), dimension(3,3) :: mat 168 | getvec = 'N' 169 | if( vec == 1 ) getvec='V' 170 | lda = n 171 | eigvals_i(:) = 0.0 172 | eigvec(:,:) = 0.0 173 | 174 | mat = A 175 | !! test workspace 176 | lwork = -1 177 | call dgeev('N',getvec, n, A, lda, eigvals, eigvals_i, & 178 | dummy, 1, eigvec, n, dummy, lwork, info) 179 | 180 | !! choose optimal size of workspace (as in example from intel website) 181 | lwork = min( 1000, nint(dummy(1)) ) 182 | !! compute stuffs 183 | call dgeev('N',getvec, n, A, lda, eigvals, eigvals_i, & 184 | dummy, 1, eigvec, n, dummy, lwork, info) 185 | 186 | ! write(*,*) 'diagonal matrix' 187 | ! ! mat = matmul(mat, transpose(A)) 188 | ! write(*,'(3f9.4)')a(1,:) 189 | ! write(*,'(3f9.4)')a(2,:) 190 | ! write(*,'(3f9.4)')a(3,:) 191 | 192 | ! write(*,*) 'eigvals' 193 | ! write(*,'(3f9.5)') eigvals 194 | ! write(*,'(3f9.5)') eigvals_i 195 | ! write(*,*) eigvec(:,1) 196 | ! write(*,*) matmul(mat,eigvec(:,1)) 197 | ! write(*,*) 198 | ! write(*,*) eigvec(:,2) 199 | ! write(*,*) matmul(mat,eigvec(:,2)) 200 | ! write(*,*) 201 | ! write(*,*) eigvec(:,3) 202 | ! write(*,*) matmul(mat,eigvec(:,3)) 203 | 204 | !! overwrite a on output 205 | A(:,:) = eigvec(:,:) 206 | END SUBROUTINE diag 207 | 208 | 209 | function check_pg_Nop( pg, Nop ) result( err ) 210 | !! check whether the name of PG is consistent with the number of operations Nop of that group 211 | !! NOTE: errors currently go up to order 10 PGs. 212 | implicit none 213 | integer(ip) :: err 214 | character(len=10), intent(in) :: pg 215 | integer(ip), intent(in) :: Nop 216 | integer(ip) :: exp_Nop 217 | 218 | err = 0 219 | 220 | !! compute expected Nop based on pg string 221 | exp_Nop = get_expected_Nop( pg ) 222 | if( exp_Nop < 1 ) then 223 | write(*,*) "at:",__FILE__, " line:",__LINE__ 224 | return 225 | end if 226 | 227 | if( Nop < exp_Nop ) then 228 | !! if Nop is less than should be, return err=-1 229 | err=-1 230 | elseif( Nop > exp_Nop ) then 231 | !! if Nop is more than should be, return err=1 232 | err = 1 233 | end if 234 | 235 | end function check_pg_Nop 236 | 237 | 238 | function get_expected_Nop( pg ) result( Nop ) 239 | !! compute expected number of symmetry operations, given a pg name. 240 | !! formula: Nop = order * multiplier * mod 241 | !! where: 242 | !! `order` is the order of the C,D,S groups, or: `order(T)=12`, `order(O)=24`, `order(I)=60` 243 | !! `multiplier=2` for D groups, `multiplier=1` otherwise 244 | !! `mod=2` if group name has any of: v, h, d, s, i as the last letter, and `mod=1` otherwise 245 | !! 246 | !! On error, return Nop = -1 247 | implicit none 248 | character(len=*), intent(in) :: pg 249 | integer(ip) :: Nop 250 | !!local 251 | character(len=1) :: letter, mod 252 | character(len=10) :: pg_cpy 253 | integer(ip) :: order, ntot 254 | integer(ip) :: multiply_mod, multiply_letter 255 | 256 | Nop = -1 257 | 258 | order = 1 259 | mod = "x" 260 | 261 | pg_cpy = pg 262 | ntot = len_trim(pg_cpy) 263 | !! empty or overflow string? 264 | if( ntot < 1 .or. ntot > 10 ) then 265 | write(*,*) "at:",__FILE__," line:",__LINE__ 266 | write(*,*) "ERROR:: string `pg` has invalid length:",ntot 267 | write(*,*) "`pg` string is:", pg 268 | return 269 | end if 270 | 271 | !! read first letter 272 | read( pg_cpy, "(a1)") letter 273 | ntot = ntot - 1 274 | 275 | if( ntot > 0 ) then 276 | !! cut first letter from string 277 | pg_cpy = trim(pg(2:)) 278 | !! check last letter for mod, if it is one of those, copy it 279 | select case( pg_cpy(ntot:ntot) ) 280 | case( "v", "h", "d", "s", "i" ) 281 | mod = pg_cpy(ntot:ntot) 282 | !! shift n back by 1 letter 283 | ntot = ntot - 1 284 | end select 285 | end if 286 | 287 | if( ntot > 0 ) then 288 | !! read order integer 289 | select case( letter ) 290 | case( "C", "D", "S" ) 291 | !! read 1:n for order 292 | read( pg_cpy(1:ntot), * ) order 293 | end select 294 | end if 295 | 296 | !! compute expected number of elements: 297 | multiply_letter = 1 298 | multiply_mod = 1 299 | if( letter == "T" ) order = 12 300 | if( letter == "O" ) order = 24 301 | if( letter == "I" ) order = 60 302 | !! D groups have order*2 elements 303 | if( letter == "D" ) multiply_letter = 2 304 | !! any nonzero mod doubles the number of elements 305 | if( mod /= "x" ) multiply_mod = 2 306 | !! final number of operations 307 | Nop = order * multiply_letter * multiply_mod 308 | end function get_expected_Nop 309 | 310 | 311 | function find_inversion( nbas, op ) result( has_inversion ) 312 | !! find if there is inversion in the list 313 | implicit none 314 | logical :: has_inversion 315 | integer(ip), intent(in) :: nbas 316 | character(len=1), dimension(nbas), intent(in) :: op 317 | integer(ip) :: i 318 | 319 | has_inversion = .false. 320 | do i = 1, nbas 321 | if( op(i) == OP_INVERSION ) then 322 | has_inversion = .true. 323 | end if 324 | end do 325 | return 326 | 327 | end function find_inversion 328 | 329 | function find_sigma( nbas, op, n_int ) result( has_sigma ) 330 | !! find if there is sigma op in the list 331 | implicit none 332 | logical :: has_sigma 333 | integer(ip), intent(in) :: nbas 334 | character(len=1), dimension(nbas), intent(in) :: op 335 | integer(ip), dimension(nbas), intent(in) :: n_int 336 | integer(ip) :: i 337 | 338 | has_sigma = .false. 339 | do i = 1, nbas 340 | if( op(i) .eq. OP_IMPROP_ROT .and. n_int(i) .eq. 0 ) has_sigma = .true. 341 | end do 342 | return 343 | end function find_sigma 344 | 345 | function find_cn( nbas, op, n_int ) result( has_cn ) 346 | !! find if there is Cn op in the list 347 | implicit none 348 | logical :: has_cn 349 | integer(ip), intent(in) :: nbas 350 | character(len=1), dimension(nbas), intent(in) :: op 351 | integer(ip), dimension(nbas), intent(in) :: n_int 352 | integer(ip) :: i 353 | 354 | has_cn = .false. 355 | do i = 1, nbas 356 | if( op(i) .eq. OP_PROP_ROT .and. n_int(i) .gt. 1 ) has_cn = .true. 357 | end do 358 | return 359 | 360 | end function find_cn 361 | 362 | 363 | function op_valid_ext_b(rmat, bfield) result( is_valid ) 364 | !! test if Op matrix is valid in externam Bfield: 365 | !! 366 | !! det(M) MB = B 367 | !! 368 | implicit none 369 | logical :: is_valid 370 | real(rp), dimension(3,3), intent(in) :: rmat 371 | real(rp), dimension(3), intent(in) :: bfield 372 | 373 | real(rp), dimension(3) :: bdir, vec 374 | real(rp) :: det, dotp 375 | 376 | is_valid = .false. 377 | 378 | !! direction of B field 379 | bdir = bfield/norm2(bfield) 380 | 381 | call determinant3x3(rmat, det) 382 | 383 | !! det(M)*MB 384 | vec = matmul( rmat, bdir ) 385 | vec = vec * det 386 | 387 | !! compare vec to bdir, shoudl be equal: dot = 1.0 388 | dotp = dot_product( vec, bdir ) 389 | if( dotp .gt. 0.999 ) is_valid = .true. 390 | 391 | end function op_valid_ext_b 392 | 393 | subroutine construct_reflection( ax, rmat ) 394 | !! construct reflection matrix from axis, as: 395 | !! rmat = I - 2.0* ( / ), where x is the axis 396 | real(rp), dimension(3), intent(in) :: ax 397 | real(rp), dimension(3,3), intent(out) :: rmat 398 | 399 | real(rp), dimension(3,3) :: id, rr 400 | integer(ip) :: i, j 401 | 402 | id(:,:) = 0.0_rp 403 | do i = 1, 3 404 | id(i,i) = 1.0_rp 405 | end do 406 | 407 | !! outer product 408 | forall (i=1:3) 409 | forall(j=1:3) rr(i,j) = ax(i)*ax(j) 410 | end forall 411 | rr(:,:) = rr / norm2(ax) 412 | 413 | rmat(:,:) = id(:,:) - 2.0_rp*rr(:,:) 414 | 415 | end subroutine construct_reflection 416 | subroutine construct_rotation(ax_in, angle, rmat) 417 | !! wikipedia/rotation_matrix#quaternion 418 | implicit none 419 | real(rp), dimension(3), intent(in) :: ax_in 420 | real(rp), intent(in) :: angle 421 | real(rp), dimension(3,3), intent(out) :: rmat 422 | 423 | real(rp), dimension(3) :: ax 424 | real(rp) :: c, s, c_one 425 | 426 | ax = ax_in/norm2(ax_in) 427 | 428 | c = cos(angle) 429 | s = sin(angle) 430 | c_one = 1.0_rp - c 431 | 432 | rmat(1,1) = ax(1)**2*c_one + c 433 | rmat(1,2) = ax(1)*ax(2)*c_one - ax(3)*s 434 | rmat(1,3) = ax(1)*ax(3)*c_one + ax(2)*s 435 | 436 | rmat(2,1) = ax(2)*ax(1)*c_one + ax(3)*s 437 | rmat(2,2) = ax(2)**2*c_one + c 438 | rmat(2,3) = ax(2)*ax(3)*c_one - ax(1)*s 439 | 440 | rmat(3,1) = ax(3)*ax(1)*c_one - ax(2)*s 441 | rmat(3,2) = ax(3)*ax(2)*c_one + ax(1)*s 442 | rmat(3,3) = ax(3)**2*c_one + c 443 | 444 | !call determinant(rmat,dr) 445 | !write(*,*) 'det r:',dr 446 | end subroutine construct_rotation 447 | 448 | 449 | !> @details 450 | !! convention for axis direction: 451 | !! flip such that z>0 452 | !! if z==0, then flip such that x>0 453 | !! if x==0, then flip such that y>0 454 | subroutine ax_convention( ax ) 455 | implicit none 456 | real(rp), intent(inout) :: ax(3) 457 | 458 | real(rp) :: flip 459 | 460 | flip = 1.0 461 | if( ax(3) .lt. -epsilon ) then 462 | !! z is negative, flip 463 | flip = -flip 464 | elseif( abs(ax(3)) < epsilon ) then 465 | !! z==0, check x 466 | if( ax(1) < -epsilon ) then 467 | !! x is negative, flip 468 | flip = -flip 469 | elseif( abs(ax(1)) < epsilon ) then 470 | !! x==0, check y 471 | if( ax(2) < -epsilon ) then 472 | !! y is negative, flip 473 | flip = -flip 474 | end if 475 | end if 476 | end if 477 | ax = ax*flip 478 | end subroutine ax_convention 479 | 480 | 481 | end module sofi_tools 482 | -------------------------------------------------------------------------------- /src/sorting_module.f90: -------------------------------------------------------------------------------- 1 | module sorting_module 2 | 3 | !! 4 | !! Merge sort algorithm, obtained from Rosetta Code wiki on 29.11.2019: 5 | !! 6 | !! https://rosettacode.org/wiki/Sorting_algorithms/Merge_sort 7 | !! 8 | !! Slightly modified to sort 2D arrays in given input axis. 9 | !! 10 | use ira_precision 11 | implicit none 12 | 13 | private 14 | public :: mergesort 15 | contains 16 | 17 | 18 | subroutine merge_a(A, B, C, ax) 19 | implicit none 20 | ! The targe attribute is necessary, because A .or. B might overlap with C. 21 | real(rp), target, intent(in) :: A(:,:), B(:,:) 22 | real(rp), target, intent(inout) :: C(:,:) 23 | integer(ip), intent(in) :: ax 24 | 25 | integer(ip) :: i, j, k, sizea, sizeb, sizec 26 | 27 | sizea = size(A,2) 28 | sizeb = size(B,2) 29 | sizec = size(C,2) 30 | 31 | if (sizeA + sizeB > sizeC) stop "(1)" 32 | 33 | i = 1; j = 1 34 | do k = 1, sizeC 35 | if (i <= sizeA .and. j <= sizeB) then 36 | if (A(ax,i) <= B(ax,j)) then 37 | C(:,k) = A(:,i) 38 | i = i + 1 39 | else 40 | C(:,k) = B(:,j) 41 | j = j + 1 42 | end if 43 | else if (i <= sizeA) then 44 | C(:,k) = A(:,i) 45 | i = i + 1 46 | else if (j <= sizeB) then 47 | C(:,k) = B(:,j) 48 | j = j + 1 49 | end if 50 | end do 51 | end subroutine merge_a 52 | 53 | 54 | subroutine swap(ndim, x, y) 55 | implicit none 56 | integer(ip), intent(in) :: ndim 57 | real(rp), dimension(ndim), intent(inout) :: x, y 58 | 59 | real(rp), dimension(ndim) :: tmp 60 | 61 | tmp = x 62 | x = y 63 | y = tmp 64 | end subroutine 65 | 66 | 67 | recursive subroutine mergesort(A, work, ax) 68 | implicit none 69 | real(rp), intent(inout) :: A(:,:) 70 | real(rp), intent(inout) :: work(:,:) 71 | integer(ip), intent(in) :: ax 72 | 73 | integer(ip) :: half, sizeA, ndim 74 | 75 | ndim = size(A,1) 76 | sizeA =size(A,2) 77 | half = (sizeA + 1) / 2 78 | if (sizeA < 2) then 79 | continue 80 | else if (sizeA == 2) then 81 | if (A(ax,1) > A(ax,2)) then 82 | call swap(ndim,A(:,1), A(:,2)) 83 | end if 84 | else 85 | call mergesort(A(:, : half), work,ax) 86 | call mergesort(A(:, half + 1 :), work,ax) 87 | if (A(ax,half) > A(ax,half + 1)) then 88 | work(:,1 : half) = A(:,1 : half) 89 | call merge_a(work(:,1 : half), A(:,half + 1:), A, ax) 90 | endif 91 | end if 92 | end subroutine mergesort 93 | 94 | 95 | end module sorting_module 96 | -------------------------------------------------------------------------------- /src/timer.f90: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | module timer 6 | 7 | !> @details 8 | !! a simple timer module with 4 memory slots 9 | !! Call as: 10 | !! 11 | !! @code{.f90} 12 | !! use timer 13 | !! ! start timing slot 1 14 | !! call timer_start( LOC_T1 ) 15 | !! ... 16 | !! ! start timing slot 2 17 | !! call timer_start( LOC_T2 ) 18 | !! ... 19 | !! ! stop timer slot 2 20 | !! call timer_stop( LOC_T2 ) 21 | !! ... 22 | !! ... 23 | !! ! stop timing slot 1 24 | !! call timer_stop( LOC_T1 ) 25 | !! 26 | !! ! print timings 27 | !! call timer_print() 28 | !! @endcode 29 | 30 | use ira_precision 31 | implicit none 32 | public 33 | 34 | 35 | !! slots for accessing the timer 36 | integer(ip), parameter :: & 37 | LOC_T1 = 1, & 38 | LOC_T2 = 2, & 39 | LOC_T3 = 3, & 40 | LOC_T4 = 4 41 | 42 | 43 | !! a single timer, store value dt1 which is clock when timer started 44 | type tim 45 | real(rp) :: dt1 46 | contains 47 | procedure :: start => tim_start 48 | procedure :: end => tim_end 49 | end type tim 50 | 51 | interface tim 52 | module procedure :: tim_constructor 53 | end interface tim 54 | 55 | 56 | !! definition of local timer for use within single routine 57 | type local_timer 58 | type( tim ), pointer :: & 59 | t_1 => null(), & 60 | t_2 => null(), & 61 | t_3 => null(), & 62 | t_4 => null() 63 | real(rp) :: timed(4) !! array for total sum of each slot 64 | character(:), allocatable :: tag1, tag2, tag3, tag4 65 | contains 66 | procedure :: start => timer_start 67 | procedure :: stop => timer_stop 68 | procedure :: tag => timer_tag 69 | procedure :: print => timer_print 70 | end type local_timer 71 | 72 | 73 | !! definiton of global_timer which works accross routines ... not really 74 | type( tim ), pointer :: & 75 | t_1 => null(), & 76 | t_2 => null(), & 77 | t_3 => null(), & 78 | t_4 => null() 79 | real(rp), protected :: timed(4) !! array for total sum of each slot 80 | character(:), allocatable :: tag1, tag2, tag3, tag4 81 | 82 | 83 | contains 84 | 85 | !!===== functions for single value of timer === 86 | function tim_constructor( ) result( this ) 87 | type( tim ), pointer :: this 88 | allocate( tim::this ) 89 | end function tim_constructor 90 | 91 | subroutine tim_start( self ) 92 | implicit none 93 | class(tim), intent(inout) :: self 94 | !! save current clock into dt1 95 | call clock( self% dt1 ) 96 | end subroutine tim_start 97 | 98 | function tim_end( self )result(dt) 99 | implicit none 100 | class(tim), intent(inout) :: self 101 | real(rp) :: dt 102 | real(rp) :: dt2 103 | !! get current clock 104 | call clock( dt2 ) 105 | !! return difference of current clock and when i started the clock 106 | dt = dt2 - self% dt1 107 | end function tim_end 108 | !! ============ 109 | 110 | 111 | 112 | !! ==== functions for local timer ======= 113 | subroutine timer_start( self, loc ) 114 | !! start the timer for slot `loc` 115 | implicit none 116 | class( local_timer ), intent(inout) :: self 117 | integer(ip), intent(in) :: loc 118 | select case( loc ) 119 | case( LOC_T1 ); if( .not. associated(self% t_1) ) self% t_1 => tim(); call self% t_1% start() 120 | case( LOC_T2 ); if( .not. associated(self% t_2) ) self% t_2 => tim(); call self% t_2% start() 121 | case( LOC_T3 ); if( .not. associated(self% t_3) ) self% t_3 => tim(); call self% t_3% start() 122 | case( LOC_T4 ); if( .not. associated(self% t_4) ) self% t_4 => tim(); call self% t_4% start() 123 | end select 124 | end subroutine timer_start 125 | 126 | subroutine timer_stop( self, loc ) 127 | !! stop timer for slot `loc` and add dt to total sum of time for slot 128 | implicit none 129 | class( local_timer ), intent(inout) :: self 130 | integer(ip), intent(in) :: loc 131 | real(rp) :: dt 132 | select case( loc ) 133 | case( LOC_T1 ); dt = self% t_1% end() 134 | case( LOC_T2 ); dt = self% t_2% end() 135 | case( LOC_T3 ); dt = self% t_3% end() 136 | case( LOC_T4 ); dt = self% t_4% end() 137 | end select 138 | self% timed( loc ) = self% timed(loc) + dt 139 | end subroutine timer_stop 140 | 141 | subroutine timer_tag( self, loc, tag ) 142 | !! set a tag to loc 143 | implicit none 144 | class( local_timer ), intent(inout) :: self 145 | integer(ip), intent(in) :: loc 146 | character(*), intent(in) :: tag 147 | select case( loc ) 148 | case( LOC_T1 ); self% tag1 = tag 149 | case( LOC_T2 ); self% tag2 = tag 150 | case( LOC_T3 ); self% tag3 = tag 151 | case( LOC_T4 ); self% tag4 = tag 152 | end select 153 | end subroutine timer_tag 154 | 155 | subroutine timer_print( self ) 156 | !! print current total times for all slots 157 | implicit none 158 | class( local_timer ), intent(inout) :: self 159 | write(*,"(1x,a)") ":::: local timer ::::" 160 | write(*,"(3x,a,2x, 'tag:',1x,a10, 2x, g0.8)") "t1 ::", self% tag1, self% timed(LOC_T1) 161 | write(*,"(3x,a,2x, 'tag:',1x,a10, 2x, g0.8)") "t2 ::", self% tag2, self% timed(LOC_T2) 162 | write(*,"(3x,a,2x, 'tag:',1x,a10, 2x, g0.8)") "t3 ::", self% tag3, self% timed(LOC_T3) 163 | write(*,"(3x,a,2x, 'tag:',1x,a10, 2x, g0.8)") "t4 ::", self% tag4, self% timed(LOC_T4) 164 | write(*,"(1x,a)") ":::::::::::::::" 165 | end subroutine timer_print 166 | !!=========================== 167 | 168 | 169 | !! ========= functions for global timer ============== 170 | subroutine global_timer_start( loc ) 171 | !! start the timer for slot `loc` 172 | implicit none 173 | integer(ip), intent(in) :: loc 174 | select case( loc ) 175 | case( LOC_T1 ); if( .not. associated(t_1) ) t_1 => tim(); call t_1% start() 176 | case( LOC_T2 ); if( .not. associated(t_2) ) t_2 => tim(); call t_2% start() 177 | case( LOC_T3 ); if( .not. associated(t_3) ) t_3 => tim(); call t_3% start() 178 | case( LOC_T4 ); if( .not. associated(t_4) ) t_4 => tim(); call t_4% start() 179 | end select 180 | end subroutine global_timer_start 181 | 182 | subroutine global_timer_stop( loc ) 183 | !! stop timer for slot `loc` and add dt to total sum of time for slot 184 | implicit none 185 | integer(ip), intent(in) :: loc 186 | real(rp) :: dt 187 | select case( loc ) 188 | case( LOC_T1 ); dt = t_1% end() 189 | case( LOC_T2 ); dt = t_2% end() 190 | case( LOC_T3 ); dt = t_3% end() 191 | case( LOC_T4 ); dt = t_4% end() 192 | end select 193 | timed( loc ) = timed(loc) + dt 194 | end subroutine global_timer_stop 195 | 196 | subroutine global_timer_tag( loc, tag ) 197 | !! set a tag to loc 198 | implicit none 199 | integer(ip), intent(in) :: loc 200 | character(*), intent(in) :: tag 201 | select case( loc ) 202 | case( LOC_T1 ); tag1 = tag 203 | case( LOC_T2 ); tag2 = tag 204 | case( LOC_T3 ); tag3 = tag 205 | case( LOC_T4 ); tag4 = tag 206 | end select 207 | end subroutine global_timer_tag 208 | 209 | subroutine global_timer_print( ) 210 | !! print current total times for all slots 211 | implicit none 212 | write(*,"(1x,a)") ":::: global timer ::::" 213 | write(*,"(3x,a,2x, 'tag:',1x,a10, 2x, g0.8)") "t1 ::", tag1, timed(LOC_T1) 214 | write(*,"(3x,a,2x, 'tag:',1x,a10, 2x, g0.8)") "t2 ::", tag2, timed(LOC_T2) 215 | write(*,"(3x,a,2x, 'tag:',1x,a10, 2x, g0.8)") "t3 ::", tag3, timed(LOC_T3) 216 | write(*,"(3x,a,2x, 'tag:',1x,a10, 2x, g0.8)") "t4 ::", tag4, timed(LOC_T4) 217 | write(*,"(1x,a)") ":::::::::::::::" 218 | end subroutine global_timer_print 219 | !! ========================= 220 | 221 | 222 | 223 | 224 | subroutine clock( time ) 225 | use, intrinsic :: iso_fortran_env, only: int64 226 | real(rp), intent(out) :: time 227 | 228 | integer( int64 ) :: tick, rate 229 | 230 | call system_clock( tick, rate ) 231 | time = real( tick, kind(time) ) / real( rate, kind(time) ) 232 | end subroutine clock 233 | 234 | end module timer 235 | 236 | -------------------------------------------------------------------------------- /src/version.f90: -------------------------------------------------------------------------------- 1 | 2 | !> @details 3 | !! get the current IRA version, string and date of release 4 | !! 5 | !! @param[out] string :: version string 6 | !! @param[out] date :: date of release, format: YYYYmmdd 7 | subroutine ira_get_version( string, date ) 8 | implicit none 9 | character(len=5), intent(out) :: string 10 | integer, intent(out) :: date 11 | 12 | !! version string 13 | string = "2.1.0" 14 | 15 | 16 | !! date string, format: YYYYmmdd 17 | date = 20240718 18 | 19 | 20 | end subroutine ira_get_version 21 | -------------------------------------------------------------------------------- /version_history.md: -------------------------------------------------------------------------------- 1 | Version history: 2 | 3 | * Version 1.0.0: November 2021 4 | 5 | the IRA code is released without the data and scripts of the benchmark tests 6 | from the original publication. 7 | 8 | 9 | * Version 1.5.0: May 2022 10 | 11 | changes with respect to previous version: 12 | - added a threshold for early exit of the cshda computation; 13 | - the threshold from the latter point is updated within the main IRA loop in a self-consistent manner, which significantly improves the speed of IRA; 14 | - unification of eq and noneq routines into ira_unify; 15 | - added set_candidate routines for modifiable control of candidate central atoms; 16 | - added example calling program from f90; 17 | - added interface to python in ira_interf.pyf, which compiles into a python module using f2py3; 18 | - added example calling program from python. 19 | 20 | 21 | * Version 2.0.0: April 2024 (20240425) 22 | 23 | Major changes with respect to previous version: 24 | - the SOFI algorithm for finding point group symmetries is added; 25 | - all routines have a BIND(C) equivalent, the library is fully interoperable with C; 26 | - replaced f2py with ctypes for python interfacing; 27 | - added routine for obtaining the current version, get_version(); 28 | - online documentation with some tutorials is added. 29 | 30 | 31 | * Version 2.1.0: July 2024 (20240718) 32 | 33 | Minor changes: 34 | - add functionality to deal with linear structures in SOFI; 35 | - add some example inputs for SOFI; 36 | - some changes in API calls; 37 | - slight speedup in CShDA achieved through more aggressive compiler flags; 38 | --------------------------------------------------------------------------------