├── .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 |
--------------------------------------------------------------------------------