├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── examples ├── example1_fcc_layer_shift.py ├── example2_cell_symmetrization.py └── example3_pca_vector_projection.py ├── setup.py ├── src ├── auguste_module.cpp ├── constants.h ├── eigendecomposition.cpp ├── eigendecomposition.h ├── lup_decomposition.cpp ├── lup_decomposition.h ├── mahalonobis_transform.cpp ├── mahalonobis_transform.h ├── matrix_vector.cpp ├── matrix_vector.h ├── minkowski_reduction.cpp ├── minkowski_reduction.h ├── parse_string.cpp ├── parse_string.h ├── polar_decomposition.cpp ├── polar_decomposition.h ├── quaternion.cpp ├── quaternion.h ├── sqp_newton_lagrange.cpp ├── sqp_newton_lagrange.h ├── stepwise_iteration.cpp ├── stepwise_iteration.h ├── symmetrization.cpp ├── symmetrization.h ├── templates.h ├── unimodular_functions.cpp ├── unimodular_functions.h └── unimodular_neighbourhood.h └── tests ├── minkowski_reduction.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | include: 3 | - language: python 4 | python: 5 | - "3.6" # current default Python on Travis CI 6 | # command to install dependencies 7 | install: 8 | - export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) 9 | - python3 -m pip install git+https://github.com/pmla/auguste@$BRANCH 10 | - python3 -m pip install scipy 11 | # command to run tests 12 | script: 13 | - python3 -m pytest tests/* 14 | 15 | - language: sh 16 | os: osx 17 | env: 18 | - TOXENV=py3 19 | - HOMEBREW_NO_INSTALL_CLEANUP=1 20 | - HOMEBREW_NO_ANALYTICS=1 21 | before_cache: 22 | # - brew cleanup 23 | - rm -f "$HOME/Library/Caches/pip/log/debug.log" 24 | cache: 25 | directories: 26 | # - "$HOME/Library/Caches/Homebrew" 27 | - "$HOME/Library/Caches/pip" 28 | addons: 29 | homebrew: 30 | # update: true 31 | packages: python3 32 | before_install: 33 | - python3 -m pip install scipy 34 | - python3 -m pip install --upgrade virtualenv 35 | - virtualenv -p python3 --system-site-packages "$HOME/venv" 36 | - source "$HOME/venv/bin/activate" 37 | - python3 -m pip install --upgrade pytest 38 | # command to install dependencies 39 | install: 40 | - export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) 41 | - python3 -m pip install git+https://github.com/pmla/auguste@$BRANCH 42 | # command to run tests 43 | script: 44 | - python3 -m pytest tests/* 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | include setup.py 3 | include examples/*.py 4 | recursive-include src/ *.cpp 5 | recursive-include src/ *.h 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # auguste 2 | 3 | A python module for minimum-strain symmetrization of Bravais lattices. Symmetrization produces a strain tensor, which is reduced to a scalar quantity. This quantifies the 'distance' of a lattice from each Bravais class. The distance can be used for classification of Bravais lattices, and visualization in an abstract Bravais space. 4 | 5 | 6 | ### Installation: 7 | 8 | To install the module with pip (recommended): 9 | ``` 10 | pip install --user auguste 11 | ``` 12 | 13 | To install directly from the git repository: 14 | ``` 15 | pip install --user git+https://github.com/pmla/auguste 16 | ``` 17 | 18 | To do a manual build and installation: 19 | ``` 20 | python3 setup.py build 21 | python3 setup.py install --user 22 | ``` 23 | 24 | ### Usage: 25 | To import the module: 26 | ``` 27 | >>> import auguste 28 | ``` 29 | 30 | To symmetrize a lattice (with rows as unit vectors): 31 | ``` 32 | >>> cell = [[0, 1, 1], [1, 0, 1], [1, 1, 0]] 33 | >>> distance, symmetrized = auguste.symmetrize_lattice(cell, "primitive tetragonal") 34 | >>> distance 35 | 0.4714045207910317 36 | >>> symmetrized 37 | array([[-0.37037037, 0.74074074, 0.74074074], 38 | [ 0.74074074, -0.37037037, 0.74074074], 39 | [ 0.74074074, 0.74074074, -0.37037037]]) 40 | ``` 41 | 42 | Supported lattice names are stored in the module variables: 43 | ``` 44 | >>> auguste.names 45 | ('primitive triclinic', 'primitive monoclinic', 'base-centred monoclinic', 'primitive orthorhombic', 'base-centred orthorhombic', 'body-centred orthorhombic', 'face-centred orthorhombic', 'primitive tetragonal', 'body-centred tetragonal', 'primitive rhombohedral', 'primitive hexagonal', 'primitive cubic', 'body-centred cubic', 'face-centred cubic') 46 | >>> auguste.pearson 47 | ('aP', 'mP', 'mS', 'oP', 'oS', 'oF', 'oI', 'tP', 'tI', 'hP', 'hR', 'cP', 'cF', 'cI') 48 | ``` 49 | To calculate the symmetrization distances from all Bravais types: 50 | ``` 51 | >>> auguste.calculate_vector(cell) 52 | array([0.00000000e+00, 4.28372991e-01, 5.24426159e-16, 4.71404521e-01, 53 | 4.28372991e-01, 3.80565279e-16, 1.92296269e-16, 4.71404521e-01, 54 | 4.07921987e-16, 1.92296269e-16, 4.60895951e-01, 4.71404521e-01, 55 | 2.61971659e-01, 0.00000000e+00]) 56 | ``` 57 | 58 | ### Information 59 | If you use auguste in a publication, please cite: 60 | 61 | Peter M. Larsen, Edward L. Pang, Pablo A. Parrilo, and Karsten W. Jacobsen; "Minimum-strain symmetrization of Bravais lattices"; Phys. Rev. Research 2, 013077 62 | 63 | Article available at https://doi.org/10.1103/PhysRevResearch.2.013077 64 | 65 | Preprint available at https://arxiv.org/abs/1910.03433. 66 | -------------------------------------------------------------------------------- /examples/example1_fcc_layer_shift.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from auguste import symmetrize_lattice 4 | 5 | 6 | def run(): 7 | """This example creates a FCC crystal and slides the 111 layers over each 8 | other. The symmetrization distance is measured in a periodic interval.""" 9 | names = ['fcc', 'bcc', 'rhombohedral', 'hexagonal'] 10 | 11 | delta = np.array([0, 0, 2]) - [1, 1, 0] 12 | fcc_cell = np.array([[1, 1, 0], [1, 0, 1], [0, 1, 1.]]) 13 | 14 | fs = [] 15 | ts = np.linspace(0, 1, 100) 16 | for t in ts: 17 | cell = fcc_cell + t * delta 18 | row = np.array([symmetrize_lattice(cell, name)[0] for name in names]) 19 | fs.append(row) 20 | print(t, row) 21 | 22 | fs = np.array(fs) 23 | for label, ds in zip(names, fs.T): 24 | plt.plot(ts, ds, label=label) 25 | 26 | plt.xlabel("Periodic layer shift") 27 | plt.ylabel("Symmetrization distance") 28 | plt.legend() 29 | plt.show() 30 | 31 | 32 | if __name__ == "__main__": 33 | run() 34 | -------------------------------------------------------------------------------- /examples/example2_cell_symmetrization.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import auguste 4 | 5 | 6 | def transform_isometric(p): 7 | 8 | M = np.array([[np.sqrt(3), 0, -np.sqrt(3)], 9 | [1, 2, 1], 10 | [np.sqrt(2), -np.sqrt(2), np.sqrt(2)]]) 11 | 12 | return 1 / np.sqrt(6) * np.dot(M, p.T)[:2].T 13 | 14 | 15 | def plot_points(cell, ax, c='k'): 16 | 17 | u, v, w = cell 18 | ps = np.array([[0, 0, 0], u, v, w, u + v, v + w, u + w, u + v + w]) 19 | qs = transform_isometric(ps) 20 | 21 | edges = [[0, 1], [0, 2], [0, 3], [1, 4], 22 | [1, 6], [2, 4], [2, 5], [3, 5], 23 | [3, 6], [4, 7], [5, 7], [6, 7]] 24 | 25 | for edge in edges: 26 | 27 | q = qs[edge] 28 | zorder = -np.max(ps[edge]) + 0.001 * ord(c[0]) 29 | ls = '-:'[4 in edge] 30 | ax.plot(q[:, 0], q[:, 1], c=c, zorder=zorder, lw=3, ls=ls) 31 | 32 | ax.plot(qs[:, 0], qs[:, 1], c=c, marker='o', markersize=6, ls='') 33 | 34 | 35 | def run(): 36 | cell = np.array([[0.06, 0, 2.01], 37 | [3.04, 0, 0], 38 | [1.46, 4.72, 0.88]]) 39 | 40 | fig, subplots = plt.subplots(nrows=3, ncols=5, figsize=(14, 10)) 41 | for row in subplots: 42 | for ax in row: 43 | ax.axis('off') 44 | ax.set_aspect('equal', 'datalim') 45 | 46 | maxlen = max([len(name) for name in auguste.names]) 47 | for i, name in enumerate(auguste.names): 48 | distance, optcell = auguste.symmetrize_lattice(cell, name) 49 | ax = subplots[i // 5][i % 5] 50 | ax.set_title("%s\ndistance: %.3f" % (name, distance)) 51 | plot_points(optcell, ax, 'C1') 52 | plot_points(cell, ax, 'C0') 53 | print(name.ljust(maxlen + 1), "%.3f" % distance) 54 | plt.show() 55 | 56 | 57 | if __name__ == "__main__": 58 | run() 59 | -------------------------------------------------------------------------------- /examples/example3_pca_vector_projection.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from sklearn import decomposition 5 | import auguste 6 | 7 | 8 | def random_vector(n): 9 | while 1: 10 | v = np.random.uniform(0, 1, n) 11 | norm = np.linalg.norm(v) 12 | if norm < 1: 13 | return v / norm 14 | 15 | 16 | def random_sample(name): 17 | cell = random_vector(9).reshape((3, 3)) 18 | _, cell = auguste.symmetrize_lattice(cell, name) 19 | return auguste.calculate_vector(cell) 20 | 21 | 22 | def sample_path(name, theta): 23 | s = np.sin(theta) 24 | c = np.cos(theta) 25 | 26 | if name == "primitive tetragonal": 27 | cell = np.array([[s, 0, 0], [0, s, 0], [0, 0, c]]) 28 | elif name == "primitive rhombohedral": 29 | cell = np.array([[s, c, c], [c, s, c], [c, c, s]]) 30 | elif name == "primitive hexagonal": 31 | th = np.deg2rad(60) 32 | cell = np.array([[s, 0, 0], 33 | [np.cos(th) * s, np.sin(th) * s, 0], 34 | [0, 0, c]]) 35 | elif name == "body-centred tetragonal": 36 | cell = np.array([[-s, s, c], [s, -s, c], [s, s, -c]]) 37 | else: 38 | raise Exception("Bravais type not implemented") 39 | 40 | return auguste.calculate_vector(cell) 41 | 42 | 43 | def run(): 44 | print("This example takes 5-10 mins depending on your computer.\n") 45 | np.random.seed(0) 46 | data = collections.defaultdict(list) 47 | 48 | # Sample the zero-parameter lattices (distances are scale invariant, 49 | # which reduces the number of parameters by one). 50 | print("sampling zero-parameter types...") 51 | print("\tcubic") 52 | data['cubic'].append(auguste.calculate_vector(np.eye(3))) 53 | print("\tfcc") 54 | data['fcc'].append(auguste.calculate_vector(1 - np.eye(3))) 55 | print("\tbcc") 56 | data['bcc'].append(auguste.calculate_vector([[-1, 1, 1], 57 | [1, -1, 1], 58 | [1, 1, -1]])) 59 | 60 | # Sample the paths of the 1-parameter lattices 61 | print("\nsampling single-parameter types...") 62 | single_types = ["primitive tetragonal", 63 | "body-centred tetragonal", 64 | "primitive hexagonal", 65 | "primitive rhombohedral"] 66 | 67 | for name in single_types: 68 | print("\t%s" % name) 69 | for t in np.linspace(0, np.pi / 2, 200)[1:-1]: 70 | if "rhombohedral" in name: 71 | t *= 2 72 | d = sample_path(name, t) 73 | if np.max(d) < 2: 74 | data[name].append(d) 75 | 76 | # Sample some points from the monoclinic lattices 77 | print("\nsampling monoclinic lattice types...") 78 | num_samples = 500 79 | double_types = ["base-centred monoclinic", "primitive monoclinic"] 80 | for name in double_types: 81 | print("\t%s" % name) 82 | for i in range(num_samples): 83 | data[name].append(random_sample(name)) 84 | 85 | # Perform a principal component analysis (PCA) of all the data points 86 | points = np.concatenate(list(data.values())) 87 | indices = np.where(np.max(points, axis=1) < 2)[0] 88 | points = points[indices] 89 | pca = decomposition.PCA() 90 | pca.fit(points) 91 | 92 | # Plot the zero-parameter lattices in the PCA coordinate system 93 | for label in ['FCC', 'BCC', 'Cubic']: 94 | 95 | name = label.lower() 96 | x, y = pca.transform(data[name])[0, :2] 97 | plt.scatter(x, y, c='k') 98 | 99 | props = {'FCC': ('right', 'top'), 100 | 'BCC': ('right', 'bottom'), 101 | 'Cubic': ('right', 'top')} 102 | ha, va = props[label] 103 | plt.text(x, y, label, ha=ha, va=va, fontsize=20) 104 | 105 | # Plot the paths of the single-parameter lattices in PCA coordinates 106 | for i, name in enumerate(single_types): 107 | x, y = pca.transform(data[name])[:, :2].T 108 | plt.plot(x, y, c='C%d' % (i // 2), ls='-:'[i % 2], label=name) 109 | 110 | # Plot the monoclinic sample points in PCA coordinates 111 | for i, name in enumerate(double_types): 112 | x, y = pca.transform(data[name])[:, :2].T 113 | plt.scatter(x, y, c='C%d' % i, label=name) 114 | 115 | plt.legend() 116 | plt.show() 117 | 118 | 119 | if __name__ == "__main__": 120 | run() 121 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import numpy 4 | from setuptools import Extension, find_packages, setup 5 | from distutils.sysconfig import get_config_vars 6 | from distutils.version import LooseVersion 7 | import platform 8 | 9 | 10 | major_version = 0 11 | minor_version = 1 12 | subminor_version = 5 13 | version = f'{major_version}.{minor_version}.{subminor_version}' 14 | extra_compile_args = [] 15 | 16 | 17 | def is_platform_mac(): 18 | return sys.platform == "darwin" 19 | 20 | 21 | # Build for at least macOS 10.9 when compiling on a 10.9 system or above, 22 | # overriding CPython distuitls behaviour which is to target the version that 23 | # python was built for. This may be overridden by setting 24 | # MACOSX_DEPLOYMENT_TARGET before calling setup.py 25 | if is_platform_mac(): 26 | if "MACOSX_DEPLOYMENT_TARGET" not in os.environ: 27 | current_system = platform.mac_ver()[0] 28 | python_target = get_config_vars().get( 29 | "MACOSX_DEPLOYMENT_TARGET", current_system 30 | ) 31 | if ( 32 | LooseVersion(python_target) < "10.9" 33 | and LooseVersion(current_system) >= "10.9" 34 | ): 35 | os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.9" 36 | 37 | if sys.version_info[:2] == (3, 8): 38 | extra_compile_args.append("-Wno-error=deprecated-declarations") 39 | 40 | 41 | # read the contents of README.md 42 | this_directory = os.path.abspath(os.path.dirname(__file__)) 43 | with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f: 44 | long_description = f.read() 45 | 46 | module = Extension( 47 | 'auguste', 48 | sources=['src/eigendecomposition.cpp', 49 | 'src/lup_decomposition.cpp', 50 | 'src/mahalonobis_transform.cpp', 51 | 'src/matrix_vector.cpp', 52 | 'src/minkowski_reduction.cpp', 53 | 'src/parse_string.cpp', 54 | 'src/polar_decomposition.cpp', 55 | 'src/quaternion.cpp', 56 | 'src/sqp_newton_lagrange.cpp', 57 | 'src/stepwise_iteration.cpp', 58 | 'src/symmetrization.cpp', 59 | 'src/unimodular_functions.cpp', 60 | 'src/auguste_module.cpp'], 61 | include_dirs=[os.path.join(numpy.get_include(), 'numpy'), 62 | 'src'], 63 | extra_compile_args=extra_compile_args, 64 | language='c++' 65 | ) 66 | 67 | setup(name='auguste', 68 | python_requires='>=3.6', 69 | ext_modules=[module], 70 | version=version, 71 | description='Minimum-strain symmetrization of Bravais lattices', 72 | author='Peter M. Larsen', 73 | author_email='peter.mahler.larsen@gmail.com', 74 | url='https://github.com/pmla/auguste', 75 | long_description_content_type='text/markdown', 76 | long_description=long_description, 77 | install_requires=['numpy'], 78 | packages=find_packages()) 79 | -------------------------------------------------------------------------------- /src/auguste_module.cpp: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "symmetrization.h" 29 | #include "minkowski_reduction.h" 30 | #include "constants.h" 31 | 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | static PyObject* error(PyObject* type, const char* msg) 38 | { 39 | PyErr_SetString(type, msg); 40 | return NULL; 41 | } 42 | 43 | static void transpose(int n, double* A) 44 | { 45 | for (int i=0;i 25 | #include 26 | #include 27 | 28 | 29 | //Consider the lowly 2 x 2 matrix 30 | //J. Blinn, Microsoft Res., USA 31 | //IEEE Computer Graphics and Applications (Volume: 16, Issue: 2, March 1996) 32 | //doi: 10.1109/38.486688 33 | static void eigendecomposition_2x2(double* H, double* l, double* V) 34 | { 35 | double a = H[0]; 36 | double b = H[1]; 37 | double c = H[2]; 38 | double d = H[3]; 39 | 40 | if (b * c <= 1E-18) 41 | { 42 | l[0] = a; 43 | l[1] = d; 44 | V[0] = 1; 45 | V[1] = 0; 46 | V[2] = 0; 47 | V[3] = 1; 48 | } 49 | else 50 | { 51 | double sum = a + d; 52 | double sqrt_disc = sqrt(std::max(0., sum * sum / 4 - (a * d - b * c))); 53 | l[0] = sum / 2 + sqrt_disc; 54 | l[1] = sum / 2 - sqrt_disc; 55 | 56 | double dif = a - d; 57 | double k = sqrt(std::max(0., dif * dif / 4 + b * c)); 58 | if (a - d >= 0) 59 | { 60 | //Blinn's article has these vectors the wrong way around 61 | V[0] = +(a - d) / 2 + k; 62 | V[2] = b; 63 | 64 | V[1] = c; 65 | V[3] = -(a - d) / 2 - k; 66 | } 67 | else 68 | { 69 | V[0] = c; 70 | V[2] = -(a - d) / 2 + k; 71 | 72 | V[1] = +(a - d) / 2 - k; 73 | V[3] = b; 74 | } 75 | 76 | double norm1 = sqrt(V[0] * V[0] + V[2] * V[2]); 77 | V[0] /= norm1; 78 | V[2] /= norm1; 79 | 80 | double norm2 = sqrt(V[1] * V[1] + V[3] * V[3]); 81 | V[1] /= norm2; 82 | V[3] /= norm2; 83 | } 84 | } 85 | 86 | static void eigendecomposition_4x4(double* H, double* l, double* V) 87 | { 88 | double corners[4] = {H[0], H[3], H[12], H[15]}; 89 | 90 | double cl[2], cV[4]; 91 | eigendecomposition_2x2(corners, cl, cV); 92 | 93 | l[0] = cl[0]; 94 | l[1] = H[5]; 95 | l[2] = H[10]; 96 | l[3] = cl[1]; 97 | 98 | memset(V, 0, 16 * sizeof(double)); 99 | V[0] = cV[0]; 100 | V[3] = cV[1]; 101 | V[5] = 1; 102 | V[10] = 1; 103 | V[12] = cV[2]; 104 | V[15] = cV[3]; 105 | } 106 | 107 | void eigendecomposition(int n, double* H, double* l, double* V) 108 | { 109 | if (n == 1) 110 | { 111 | l[0] = H[0]; 112 | V[0] = 1; 113 | } 114 | else if (n == 2) 115 | { 116 | eigendecomposition_2x2(H, l, V); 117 | } 118 | else if (n == 4) 119 | { 120 | eigendecomposition_4x4(H, l, V); 121 | } 122 | else if (n == 3) 123 | { 124 | l[0] = H[0]; 125 | l[1] = H[4]; 126 | l[2] = H[8]; 127 | 128 | memset(V, 0, 9 * sizeof(double)); 129 | V[0] = 1; 130 | V[4] = 1; 131 | V[8] = 1; 132 | } 133 | } 134 | 135 | -------------------------------------------------------------------------------- /src/eigendecomposition.h: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #ifndef EIGENDECOMPOSITION_H 25 | #define EIGENDECOMPOSITION_H 26 | 27 | void eigendecomposition(int n, double* H, double* l, double* V); 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /src/lup_decomposition.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The copyright in this software is being made available under the 2-clauses 3 | * BSD License, included below. This software may be subject to other third 4 | * party and contributor rights, including patent rights, and no such rights 5 | * are granted under this license. 6 | * 7 | * Copyright (c) 2008, Jerome Fimes, Communications & Systemes 8 | * All rights reserved. 9 | * 10 | * Modifided for use in "auguste" module by P. M. Larsen, 2019 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 25 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | * POSSIBILITY OF SUCH DAMAGE. 32 | */ 33 | 34 | #include 35 | #include 36 | 37 | #define MAX_LEN 10 38 | bool lup_decompose(int n, double* A, int* P) 39 | { 40 | double swap_area[MAX_LEN]; 41 | 42 | int* tmpPermutations = P; 43 | int* dstPermutations; 44 | int k2 = 0, t; 45 | double temp; 46 | double p; 47 | int lLastColum = n - 1; 48 | int lSwapSize = n * (int)sizeof(double); 49 | double* lTmpMatrix = A; 50 | double* lColumnMatrix, * lDestMatrix; 51 | int offset = 1; 52 | int lStride = n - 1; 53 | 54 | // Initialize P 55 | for (int i = 0; i < n; i++) 56 | *tmpPermutations++ = i; 57 | 58 | // Now make a pivot with column switch 59 | tmpPermutations = P; 60 | for (int k = 0; k < lLastColum; k++) 61 | { 62 | p = 0.0; 63 | 64 | // Take the middle element 65 | lColumnMatrix = lTmpMatrix + k; 66 | 67 | // Make permutation with the biggest value in the column 68 | for (int i = k; i < n; i++) 69 | { 70 | temp = ((*lColumnMatrix > 0) ? *lColumnMatrix : -(*lColumnMatrix)); 71 | if (temp > p) 72 | { 73 | p = temp; 74 | k2 = i; 75 | } 76 | 77 | // Next line 78 | lColumnMatrix += n; 79 | } 80 | 81 | // A whole rest of 0 -> non singular 82 | if (p == 0.0) 83 | return false; 84 | 85 | // Should we permute? 86 | if (k2 != k) 87 | { 88 | // exchange of line 89 | // k2 > k 90 | dstPermutations = tmpPermutations + k2 - k; 91 | // swap indices 92 | t = *tmpPermutations; 93 | *tmpPermutations = *dstPermutations; 94 | *dstPermutations = t; 95 | 96 | // and swap entire line. 97 | lColumnMatrix = lTmpMatrix + (k2 - k) * n; 98 | memcpy(swap_area, lColumnMatrix, lSwapSize); 99 | memcpy(lColumnMatrix, lTmpMatrix, lSwapSize); 100 | memcpy(lTmpMatrix, swap_area, lSwapSize); 101 | } 102 | 103 | // now update data in the rest of the line and line after 104 | lDestMatrix = lTmpMatrix + k; 105 | lColumnMatrix = lDestMatrix + n; 106 | 107 | // take the middle element 108 | temp = *(lDestMatrix++); 109 | 110 | // now compute up data (i.e. coeff up of the diagonal). 111 | for (int i = offset; i < n; i++) 112 | { 113 | // lColumnMatrix; 114 | // divide the lower column elements by the diagonal value 115 | 116 | // A[i][k] /= A[k][k]; 117 | // p = A[i][k] 118 | p = *lColumnMatrix / temp; 119 | *(lColumnMatrix++) = p; 120 | 121 | for (int j = /* k + 1 */ offset; j < n; j++) 122 | { 123 | // A[i][j] -= A[i][k] * A[k][j]; 124 | *(lColumnMatrix++) -= p * (*(lDestMatrix++)); 125 | } 126 | 127 | // come back to the k+1th element 128 | lDestMatrix -= lStride; 129 | 130 | // go to kth element of the next line 131 | lColumnMatrix += k; 132 | } 133 | 134 | // offset is now k+2 135 | ++offset; 136 | 137 | // 1 element less for stride 138 | --lStride; 139 | 140 | // next line 141 | lTmpMatrix += n; 142 | 143 | // next permutation element 144 | ++tmpPermutations; 145 | } 146 | 147 | return true; 148 | } 149 | 150 | void lup_solve(int n, double* A, int* P, double* b, double* x) 151 | { 152 | double intermediate_data[MAX_LEN * MAX_LEN]; 153 | 154 | int lStride = n + 1; 155 | double* lLineMatrix = A; 156 | double* lBeginPtr = x + n - 1; 157 | int* lCurrentPermutationPtr = P; 158 | 159 | 160 | double* lIntermediatePtr = intermediate_data; 161 | double* lGeneratedData = intermediate_data + n - 1; 162 | 163 | for (int i = 0; i < n; i++) 164 | { 165 | double sum = 0; 166 | double* lCurrentPtr = intermediate_data; 167 | double* lTmpMatrix = lLineMatrix; 168 | 169 | for (int j = 1; j <= i; j++) 170 | { 171 | // sum += A[i][j-1] * y[j-1]; 172 | sum += (*(lTmpMatrix++)) * (*(lCurrentPtr++)); 173 | } 174 | 175 | // y[i] = b[P[i]] - sum; 176 | *(lIntermediatePtr++) = b[*(lCurrentPermutationPtr++)] - sum; 177 | lLineMatrix += n; 178 | } 179 | 180 | // we take the last point of the A 181 | lLineMatrix = A + n * n - 1; 182 | 183 | // and we take after the last point of the destination vector 184 | double* lDestPtr = x + n; 185 | 186 | 187 | assert(n != 0); 188 | for (int k = (int)n - 1; k != -1; k--) 189 | { 190 | double sum = 0; 191 | double* lTmpMatrix = lLineMatrix; 192 | double u = *(lTmpMatrix++); 193 | double* lCurrentPtr = lDestPtr--; 194 | 195 | for (int j = (int)(k + 1); j < n; ++j) 196 | { 197 | // sum += A[k][j] * x[j] 198 | sum += (*(lTmpMatrix++)) * (*(lCurrentPtr++)); 199 | } 200 | 201 | // x[k] = (y[k] - sum) / u; 202 | *(lBeginPtr--) = (*(lGeneratedData--) - sum) / u; 203 | lLineMatrix -= lStride; 204 | } 205 | } 206 | 207 | -------------------------------------------------------------------------------- /src/lup_decomposition.h: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #ifndef LUP_DECOMPOSITION_H 25 | #define LUP_DECOMPOSITION_H 26 | 27 | bool lup_decompose(int n, double* A, int* P); 28 | void lup_solve(int n, double* A, int* P, double* b, double* x); 29 | 30 | #endif 31 | 32 | -------------------------------------------------------------------------------- /src/mahalonobis_transform.cpp: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #include 25 | #include 26 | #include "matrix_vector.h" 27 | #include "eigendecomposition.h" 28 | #include "lup_decomposition.h" 29 | 30 | 31 | static void compute_matrix_inverse_product(int n, double* LU, int* pivot, double* A, double* C) 32 | { 33 | // We want to calculate C = A B^{1} without inverting B. 34 | // We can do this by solving the linear equation C B = A 35 | // LU is the LU decomposition of B 36 | 37 | for (int j=0;j 25 | #include 26 | #include 27 | 28 | 29 | double vector_dot(int n, double* a, double* b) 30 | { 31 | double dot = 0; 32 | for (int i=0;i 28 | 29 | 30 | void swap(double* a, double* b); 31 | double vector_dot(int n, double* a, double* b); 32 | double vector_norm(int n, double* x); 33 | void normalize_vector(int n, double* x); 34 | void matvec(int n, double* A, double* x, double* b); 35 | void matveci(int n, double* A, int* x, double* b); 36 | void transpose(int n, double* A); 37 | void transposei(int n, int* A); 38 | double frobenius_inner_product(double* A, double* B); 39 | void matmul(int n, double* A, double* x, double* b); 40 | void matmuli(int n, int* A, int* x, int* b); 41 | void matmul_int8(int n, int* A, int8_t* x, int* b); 42 | void matmul_di(int n, double* A, int* x, double* b); 43 | void matmul_id(int n, int* A, double* x, double* b); 44 | double determinant_3x3(double* m); 45 | void unimodular_inverse_3x3i(int* A, int* B); 46 | void flip_matrix(int n, double* m); 47 | void flip_matrix_i(int n, int* m); 48 | 49 | #endif 50 | 51 | -------------------------------------------------------------------------------- /src/minkowski_reduction.cpp: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | // Implements the Minkowski lattice basis reduction algorithm. Described in: 25 | // Nguyen, Phong Q. and Stehlé, Damien 26 | // "Low-dimensional Lattice Basis Reduction Revisited" 27 | // ACM Transactions on Algorithms 28 | // Volume 5 Issue 4, October 2009 29 | 30 | #include 31 | #include 32 | #include 33 | #include "matrix_vector.h" 34 | 35 | 36 | #define SIGN(x) ((x > 0 ? 1 : -1)) 37 | 38 | const int max_it = 10000; //in practice this is not exceeded 39 | 40 | 41 | class CycleChecker 42 | { 43 | public: 44 | CycleChecker(int d) { 45 | visited = data; 46 | if (d == 2) { 47 | max_cycle_length = (6 * 5) * (2 * 1); 48 | size = 6; 49 | } 50 | else if (d == 3) { 51 | max_cycle_length = (12 * 11 * 10) * (3 * 2 * 1); 52 | size = 9; 53 | } 54 | 55 | memset(visited, 0, max_cycle_length * sizeof(int)); 56 | } 57 | 58 | bool add_site(int* path) { 59 | bool found = false; 60 | for (int i=0;i= 0) 98 | return rounded - 1; 99 | else 100 | return rounded + 1; 101 | } 102 | 103 | static int gauss(double (*BT)[3], int* hu, int *hv) 104 | { 105 | CycleChecker cycle_checker = CycleChecker(2); 106 | double u[3], v[3]; 107 | matveci(3, (double*)BT, hu, u); 108 | matveci(3, (double*)BT, hv, v); 109 | 110 | int temp[3]; 111 | for (int it=0;it= vector_dot(3, v, v) or cycle_checker.add_site(path)) 125 | { 126 | memcpy(temp, hv, 3 * sizeof(int)); 127 | memcpy(hv, hu, 3 * sizeof(int)); 128 | memcpy(hu, temp, 3 * sizeof(int)); 129 | return 0; 130 | } 131 | } 132 | 133 | return -1; 134 | } 135 | 136 | static int closest_vector(double* t0, double* u, double* v, int* _a) 137 | { 138 | double t[2] = {t0[0], t0[1]}; 139 | int cs[9][2] = { {0, 0}, {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1} }; 140 | 141 | //double sizes[9]; 142 | double vs[9][2]; 143 | for (int i=0;i<9;i++) 144 | { 145 | double x = cs[i][0] * u[0] + cs[i][1] * v[0]; 146 | double y = cs[i][0] * u[1] + cs[i][1] * v[1]; 147 | 148 | //double size = x * x + y * y; 149 | vs[i][0] = x; 150 | vs[i][1] = y; 151 | } 152 | 153 | int a[2] = {0, 0}; 154 | double dprev = INFINITY; 155 | for (int it=0;it= dprev) 174 | { 175 | _a[0] = a[0]; 176 | _a[1] = a[1]; 177 | return 0; 178 | } 179 | 180 | dprev = best; 181 | int kopt = (int)round_even(-vector_dot(2, t, vs[index]) / vector_dot(2, vs[index], vs[index])); 182 | a[0] += kopt * cs[index][0]; 183 | a[1] += kopt * cs[index][1]; 184 | 185 | t[0] = t0[0] + a[0] * u[0] + a[1] * v[0]; 186 | t[1] = t0[1] + a[0] * u[1] + a[1] * v[1]; 187 | } 188 | 189 | return -1; 190 | } 191 | 192 | static void argsort3(double* x, int* order) 193 | { 194 | order[0] = 0; 195 | order[1] = 1; 196 | order[2] = 2; 197 | 198 | double c[3] = {x[0], x[1], x[2]}; 199 | if (c[0] > c[1]) 200 | { 201 | std::swap(c[0], c[1]); 202 | std::swap(order[0], order[1]); 203 | } 204 | 205 | if (c[0] > c[2]) 206 | { 207 | std::swap(c[0], c[2]); 208 | std::swap(order[0], order[2]); 209 | } 210 | 211 | if (c[1] > c[2]) 212 | { 213 | std::swap(c[1], c[2]); 214 | std::swap(order[1], order[2]); 215 | } 216 | } 217 | 218 | static void order_path_by_norms(double* norms, int (*path)[3]) 219 | { 220 | int order[3]; 221 | argsort3(norms, order); 222 | 223 | int mtemp[3][3]; 224 | memcpy(mtemp[0], path[order[0]], 3 * sizeof(int)); 225 | memcpy(mtemp[1], path[order[1]], 3 * sizeof(int)); 226 | memcpy(mtemp[2], path[order[2]], 3 * sizeof(int)); 227 | memcpy(path, mtemp, 9 * sizeof(int)); 228 | } 229 | 230 | static void column_norms(double (*BT)[3], double* norms) 231 | { 232 | for (int i=0;i<3;i++) 233 | { 234 | double norm = 0; 235 | 236 | for (int j=0;j<3;j++) 237 | norm += BT[j][i] * BT[j][i]; 238 | norms[i] = sqrt(norm); 239 | } 240 | } 241 | 242 | static void gram_schmidt(double (*Bprime)[3], double* X, double* Y) 243 | { 244 | memcpy(X, Bprime[0], 3 * sizeof(double)); 245 | normalize_vector(3, X); 246 | double dot = vector_dot(3, Bprime[1], X); 247 | for (int i=0;i<3;i++) 248 | Y[i] = Bprime[1][i] - dot * X[i]; 249 | normalize_vector(3, Y); 250 | } 251 | 252 | static int _minkowski_basis(double (*BT)[3], double (*reduced_basis)[3], int (*output_path)[3]) 253 | { 254 | int ret = 0; 255 | int path[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; 256 | double norms[3]; 257 | column_norms(BT, norms); 258 | int sign0 = SIGN(determinant_3x3(BT[0])); 259 | 260 | CycleChecker cycle_checker = CycleChecker(3); 261 | 262 | for (int it=0;it= norms[1] or cycle_checker.add_site((int*)path)) 308 | { 309 | if (SIGN(determinant_3x3(Bprime[0])) != sign0) 310 | { 311 | for (int i=0;i<3;i++) 312 | for (int j=0;j<3;j++) 313 | { 314 | Bprime[i][j] = -Bprime[i][j]; 315 | path[i][j] = -path[i][j]; 316 | } 317 | } 318 | 319 | transpose(3, (double*)Bprime); 320 | transposei(3, (int*)path); 321 | memcpy(reduced_basis, Bprime, 9 * sizeof(double)); 322 | memcpy(output_path, path, 9 * sizeof(int)); 323 | return 0; 324 | } 325 | } 326 | 327 | return -1; 328 | } 329 | 330 | #ifdef __cplusplus 331 | extern "C" { 332 | #endif 333 | 334 | int minkowski_basis(double (*BT)[3], double (*reduced_basis)[3], int (*output_path)[3]) 335 | { 336 | return _minkowski_basis(BT, reduced_basis, output_path); 337 | } 338 | 339 | #ifdef __cplusplus 340 | } 341 | #endif 342 | 343 | -------------------------------------------------------------------------------- /src/minkowski_reduction.h: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #ifndef MINKOWSKI_REDUCTION_H 25 | #define MINKOWSKI_REDUCTION_H 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | int minkowski_basis(double (*B)[3], double (*reduced_basis)[3], int (*path)[3]); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /src/parse_string.cpp: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #include 25 | #include 26 | #include "constants.h" 27 | 28 | 29 | int parse_string(char* name) 30 | { 31 | std::map lookup; 32 | lookup.insert(std::make_pair("triclinic", TRICLINIC)); 33 | lookup.insert(std::make_pair("primitive triclinic", TRICLINIC)); 34 | lookup.insert(std::make_pair("aP", TRICLINIC)); 35 | 36 | lookup.insert(std::make_pair("monoclinic", MONOCLINIC)); 37 | lookup.insert(std::make_pair("primitive monoclinic", MONOCLINIC)); 38 | lookup.insert(std::make_pair("mP", MONOCLINIC)); 39 | 40 | lookup.insert(std::make_pair("base-centred monoclinic", BASEMONOCLINIC)); 41 | lookup.insert(std::make_pair("base-centered monoclinic", BASEMONOCLINIC)); 42 | lookup.insert(std::make_pair("mS", BASEMONOCLINIC)); 43 | lookup.insert(std::make_pair("mC", BASEMONOCLINIC)); 44 | 45 | lookup.insert(std::make_pair("orthorhombic", ORTHORHOMBIC)); 46 | lookup.insert(std::make_pair("primitive orthorhombic", ORTHORHOMBIC)); 47 | lookup.insert(std::make_pair("oP", ORTHORHOMBIC)); 48 | 49 | lookup.insert(std::make_pair("base-centred orthorhombic", BASECO)); 50 | lookup.insert(std::make_pair("base-centered orthorhombic", BASECO)); 51 | lookup.insert(std::make_pair("oS", BASECO)); 52 | lookup.insert(std::make_pair("oC", BASECO)); 53 | 54 | lookup.insert(std::make_pair("body-centred orthorhombic", BCO)); 55 | lookup.insert(std::make_pair("body-centered orthorhombic", BCO)); 56 | lookup.insert(std::make_pair("oF", BCO)); 57 | 58 | lookup.insert(std::make_pair("face-centred orthorhombic", FCO)); 59 | lookup.insert(std::make_pair("face-centered orthorhombic", FCO)); 60 | lookup.insert(std::make_pair("oI", FCO)); 61 | 62 | lookup.insert(std::make_pair("tetragonal", TETRAGONAL)); 63 | lookup.insert(std::make_pair("primitive tetragonal", TETRAGONAL)); 64 | lookup.insert(std::make_pair("tP", TETRAGONAL)); 65 | 66 | lookup.insert(std::make_pair("body-centred tetragonal", BCT)); 67 | lookup.insert(std::make_pair("body-centered tetragonal", BCT)); 68 | lookup.insert(std::make_pair("tI", BCT)); 69 | 70 | lookup.insert(std::make_pair("rhombohedral", RHOMBOHEDRAL)); 71 | lookup.insert(std::make_pair("primitive rhombohedral", RHOMBOHEDRAL)); 72 | lookup.insert(std::make_pair("hP", RHOMBOHEDRAL)); 73 | 74 | lookup.insert(std::make_pair("hexagonal", HEXAGONAL)); 75 | lookup.insert(std::make_pair("primitive hexagonal", HEXAGONAL)); 76 | lookup.insert(std::make_pair("hR", HEXAGONAL)); 77 | 78 | lookup.insert(std::make_pair("cubic", CUBIC)); 79 | lookup.insert(std::make_pair("primitive cubic", CUBIC)); 80 | lookup.insert(std::make_pair("cP", CUBIC)); 81 | 82 | lookup.insert(std::make_pair("body-centred cubic", BCC)); 83 | lookup.insert(std::make_pair("body-centered cubic", BCC)); 84 | lookup.insert(std::make_pair("bcc", BCC)); 85 | lookup.insert(std::make_pair("BCC", BCC)); 86 | lookup.insert(std::make_pair("cF", BCC)); 87 | 88 | lookup.insert(std::make_pair("face-centred cubic", FCC)); 89 | lookup.insert(std::make_pair("face-centered cubic", FCC)); 90 | lookup.insert(std::make_pair("fcc", FCC)); 91 | lookup.insert(std::make_pair("FCC", FCC)); 92 | lookup.insert(std::make_pair("cI", FCC)); 93 | 94 | std::string sname = std::string(name); 95 | std::map::iterator it = lookup.find(sname); 96 | if (it != lookup.end()) { 97 | return it->second; 98 | } 99 | 100 | return -1; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/parse_string.h: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #ifndef PARSE_STRING_H 25 | #define PARSE_STRING_H 26 | 27 | int parse_string(char* name); 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /src/polar_decomposition.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * -/_|:|_|_\- 3 | * 4 | * This code is a modification of D.L. Theobald's QCP rotation code. 5 | * It has been adapted to calculate the polar decomposition of a 3x3 matrix 6 | * Adaption by P.M. Larsen 7 | * 8 | * Original Author(s): Douglas L. Theobald 9 | * Department of Biochemistry 10 | * MS 009 11 | * Brandeis University 12 | * 415 South St 13 | * Waltham, MA 02453 14 | * USA 15 | * 16 | * dtheobald@brandeis.edu 17 | * 18 | * Pu Liu 19 | * Johnson & Johnson Pharmaceutical Research and Development, L.L.C. 20 | * 665 Stockton Drive 21 | * Exton, PA 19341 22 | * USA 23 | * 24 | * pliu24@its.jnj.com 25 | * 26 | * 27 | * If you use this QCP rotation calculation method in a publication, please 28 | * reference: 29 | * 30 | * Douglas L. Theobald (2005) 31 | * "Rapid calculation of RMSD using a quaternion-based characteristic 32 | * polynomial." 33 | * Acta Crystallographica A 61(4):478-480. 34 | * 35 | * Pu Liu, Dmitris K. Agrafiotis, and Douglas L. Theobald (2009) 36 | * "Fast determination of the optimal rotational matrix for macromolecular 37 | * superpositions." 38 | * Journal of Computational Chemistry 31(7):1561-1563. 39 | * 40 | * 41 | * Copyright (c) 2009-2013 Pu Liu and Douglas L. Theobald 42 | * All rights reserved. 43 | * 44 | * Redistribution and use in source and binary forms, with or without modification, are permitted 45 | * provided that the following conditions are met: 46 | * 47 | * * Redistributions of source code must retain the above copyright notice, this list of 48 | * conditions and the following disclaimer. 49 | * * Redistributions in binary form must reproduce the above copyright notice, this list 50 | * of conditions and the following disclaimer in the documentation and/or other materials 51 | * provided with the distribution. 52 | * * Neither the name of the nor the names of its contributors may be used to 53 | * endorse or promote products derived from this software without specific prior written 54 | * permission. 55 | * 56 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 57 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 58 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 59 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 60 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 61 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 62 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 63 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 64 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 65 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 66 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 67 | * 68 | * Source: started anew. 69 | * 70 | * Change History: 71 | * 2009/04/13 Started source 72 | * 2010/03/28 Modified FastCalcRMSDAndRotation() to handle tiny qsqr 73 | * If trying all rows of the adjoint still gives too small 74 | * qsqr, then just return identity matrix. (DLT) 75 | * 2010/06/30 Fixed prob in assigning A[9] = 0 in InnerProduct() 76 | * invalid mem access 77 | * 2011/02/21 Made CenterCoords use weights 78 | * 2011/05/02 Finally changed CenterCoords declaration in qcprot.h 79 | * Also changed some functions to static 80 | * 2011/07/08 put in fabs() to fix taking sqrt of small neg numbers, fp error 81 | * 2012/07/26 minor changes to comments and main.c, more info (v.1.4) 82 | * 83 | * 2016/05/29 QCP method adapted for polar decomposition of a 3x3 matrix, 84 | * for use in Polyhedral Template Matching. 85 | * 86 | ******************************************************************************/ 87 | 88 | #include 89 | #include 90 | #include 91 | #include "quaternion.h" 92 | 93 | 94 | static void matmul_3x3(double* A, double* x, double* b) 95 | { 96 | b[0] = A[0] * x[0] + A[1] * x[3] + A[2] * x[6]; 97 | b[3] = A[3] * x[0] + A[4] * x[3] + A[5] * x[6]; 98 | b[6] = A[6] * x[0] + A[7] * x[3] + A[8] * x[6]; 99 | 100 | b[1] = A[0] * x[1] + A[1] * x[4] + A[2] * x[7]; 101 | b[4] = A[3] * x[1] + A[4] * x[4] + A[5] * x[7]; 102 | b[7] = A[6] * x[1] + A[7] * x[4] + A[8] * x[7]; 103 | 104 | b[2] = A[0] * x[2] + A[1] * x[5] + A[2] * x[8]; 105 | b[5] = A[3] * x[2] + A[4] * x[5] + A[5] * x[8]; 106 | b[8] = A[6] * x[2] + A[7] * x[5] + A[8] * x[8]; 107 | } 108 | 109 | static double matrix_determinant_3x3(double* A) 110 | { 111 | return A[0] * (A[4]*A[8] - A[5]*A[7]) 112 | - A[1] * (A[3]*A[8] - A[5]*A[6]) 113 | + A[2] * (A[3]*A[7] - A[4]*A[6]); 114 | } 115 | 116 | static void flip_matrix(double* A) 117 | { 118 | for (int i=0;i<9;i++) 119 | A[i] = -A[i]; 120 | } 121 | 122 | static bool optimal_quaternion(double* A, bool polar, double E0, double* p_nrmsdsq, double* qopt) 123 | { 124 | const double evecprec = 1e-6; 125 | const double evalprec = 1e-11; 126 | 127 | double Sxx = A[0], Sxy = A[1], Sxz = A[2], 128 | Syx = A[3], Syy = A[4], Syz = A[5], 129 | Szx = A[6], Szy = A[7], Szz = A[8]; 130 | 131 | double Sxx2 = Sxx * Sxx, Syy2 = Syy * Syy, Szz2 = Szz * Szz, 132 | Sxy2 = Sxy * Sxy, Syz2 = Syz * Syz, Sxz2 = Sxz * Sxz, 133 | Syx2 = Syx * Syx, Szy2 = Szy * Szy, Szx2 = Szx * Szx; 134 | 135 | double fnorm_squared = Sxx2 + Syy2 + Szz2 + Sxy2 + Syz2 + Sxz2 + Syx2 + Szy2 + Szx2; 136 | 137 | double SyzSzymSyySzz2 = 2.0 * (Syz * Szy - Syy * Szz); 138 | double Sxx2Syy2Szz2Syz2Szy2 = Syy2 + Szz2 - Sxx2 + Syz2 + Szy2; 139 | double SxzpSzx = Sxz + Szx; 140 | double SyzpSzy = Syz + Szy; 141 | double SxypSyx = Sxy + Syx; 142 | double SyzmSzy = Syz - Szy; 143 | double SxzmSzx = Sxz - Szx; 144 | double SxymSyx = Sxy - Syx; 145 | double SxxpSyy = Sxx + Syy; 146 | double SxxmSyy = Sxx - Syy; 147 | double Sxy2Sxz2Syx2Szx2 = Sxy2 + Sxz2 - Syx2 - Szx2; 148 | 149 | double C[3]; 150 | C[0] = Sxy2Sxz2Syx2Szx2 * Sxy2Sxz2Syx2Szx2 151 | + (Sxx2Syy2Szz2Syz2Szy2 + SyzSzymSyySzz2) * (Sxx2Syy2Szz2Syz2Szy2 - SyzSzymSyySzz2) 152 | + (-(SxzpSzx)*(SyzmSzy)+(SxymSyx)*(SxxmSyy-Szz)) * (-(SxzmSzx)*(SyzpSzy)+(SxymSyx)*(SxxmSyy+Szz)) 153 | + (-(SxzpSzx)*(SyzpSzy)-(SxypSyx)*(SxxpSyy-Szz)) * (-(SxzmSzx)*(SyzmSzy)-(SxypSyx)*(SxxpSyy+Szz)) 154 | + (+(SxypSyx)*(SyzpSzy)+(SxzpSzx)*(SxxmSyy+Szz)) * (-(SxymSyx)*(SyzmSzy)+(SxzpSzx)*(SxxpSyy+Szz)) 155 | + (+(SxypSyx)*(SyzmSzy)+(SxzmSzx)*(SxxmSyy-Szz)) * (-(SxymSyx)*(SyzpSzy)+(SxzmSzx)*(SxxpSyy-Szz)); 156 | 157 | C[1] = 8.0 * (Sxx*Syz*Szy + Syy*Szx*Sxz + Szz*Sxy*Syx - Sxx*Syy*Szz - Syz*Szx*Sxy - Szy*Syx*Sxz); 158 | C[2] = -2.0 * fnorm_squared; 159 | 160 | //Newton-Raphson 161 | double mxEigenV = polar ? sqrt(3 * fnorm_squared) : E0; 162 | if (mxEigenV > evalprec) 163 | { 164 | for (int i=0;i<50;i++) 165 | { 166 | double oldg = mxEigenV; 167 | double x2 = mxEigenV*mxEigenV; 168 | double b = (x2 + C[2])*mxEigenV; 169 | double a = b + C[1]; 170 | double delta = ((a * mxEigenV + C[0]) / (2 * x2 * mxEigenV + b + a)); 171 | mxEigenV -= delta; 172 | if (fabs(mxEigenV - oldg) < fabs(evalprec * mxEigenV)) 173 | break; 174 | } 175 | } 176 | else 177 | { 178 | mxEigenV = 0.0; 179 | } 180 | 181 | (*p_nrmsdsq) = std::max(0.0, 2.0 * (E0 - mxEigenV)); 182 | 183 | double a11 = SxxpSyy + Szz - mxEigenV; 184 | double a12 = SyzmSzy; 185 | double a13 = -SxzmSzx; 186 | double a14 = SxymSyx; 187 | 188 | double a21 = SyzmSzy; 189 | double a22 = SxxmSyy - Szz -mxEigenV; 190 | double a23 = SxypSyx; 191 | double a24 = SxzpSzx; 192 | 193 | double a31 = a13; 194 | double a32 = a23; 195 | double a33 = Syy - Sxx - Szz - mxEigenV; 196 | double a34 = SyzpSzy; 197 | 198 | double a41 = a14; 199 | double a42 = a24; 200 | double a43 = a34; 201 | double a44 = Szz - SxxpSyy - mxEigenV; 202 | 203 | double a3344_4334 = a33 * a44 - a43 * a34; 204 | double a3244_4234 = a32 * a44 - a42 * a34; 205 | double a3243_4233 = a32 * a43 - a42 * a33; 206 | double a3143_4133 = a31 * a43 - a41 * a33; 207 | double a3144_4134 = a31 * a44 - a41 * a34; 208 | double a3142_4132 = a31 * a42 - a41 * a32; 209 | double a1324_1423 = a13 * a24 - a14 * a23; 210 | double a1224_1422 = a12 * a24 - a14 * a22; 211 | double a1223_1322 = a12 * a23 - a13 * a22; 212 | double a1124_1421 = a11 * a24 - a14 * a21; 213 | double a1123_1321 = a11 * a23 - a13 * a21; 214 | double a1122_1221 = a11 * a22 - a12 * a21; 215 | 216 | double q[4][4]; 217 | q[0][0] = a12 * a3344_4334 - a13 * a3244_4234 + a14 * a3243_4233; 218 | q[0][1] = -a11 * a3344_4334 + a13 * a3144_4134 - a14 * a3143_4133; 219 | q[0][2] = a11 * a3244_4234 - a12 * a3144_4134 + a14 * a3142_4132; 220 | q[0][3] = -a11 * a3243_4233 + a12 * a3143_4133 - a13 * a3142_4132; 221 | 222 | q[1][0] = a22 * a3344_4334 - a23 * a3244_4234 + a24 * a3243_4233; 223 | q[1][1] = -a21 * a3344_4334 + a23 * a3144_4134 - a24 * a3143_4133; 224 | q[1][2] = a21 * a3244_4234 - a22 * a3144_4134 + a24 * a3142_4132; 225 | q[1][3] = -a21 * a3243_4233 + a22 * a3143_4133 - a23 * a3142_4132; 226 | 227 | q[2][0] = a32 * a1324_1423 - a33 * a1224_1422 + a34 * a1223_1322; 228 | q[2][1] = -a31 * a1324_1423 + a33 * a1124_1421 - a34 * a1123_1321; 229 | q[2][2] = a31 * a1224_1422 - a32 * a1124_1421 + a34 * a1122_1221; 230 | q[2][3] = -a31 * a1223_1322 + a32 * a1123_1321 - a33 * a1122_1221; 231 | 232 | q[3][0] = a42 * a1324_1423 - a43 * a1224_1422 + a44 * a1223_1322; 233 | q[3][1] = -a41 * a1324_1423 + a43 * a1124_1421 - a44 * a1123_1321; 234 | q[3][2] = a41 * a1224_1422 - a42 * a1124_1421 + a44 * a1122_1221; 235 | q[3][3] = -a41 * a1223_1322 + a42 * a1123_1321 - a43 * a1122_1221; 236 | 237 | double qsqr[4]; 238 | for (int i=0;i<4;i++) 239 | qsqr[i] = q[i][0]*q[i][0] + q[i][1]*q[i][1] + q[i][2]*q[i][2] + q[i][3]*q[i][3]; 240 | 241 | int bi = 0; 242 | double max = 0; 243 | for (int i=0;i<4;i++) 244 | { 245 | if (qsqr[i] > max) 246 | { 247 | bi = i; 248 | max = qsqr[i]; 249 | } 250 | } 251 | 252 | double normq = sqrt(qsqr[bi]); 253 | bool too_small = false; 254 | if (normq < evecprec) 255 | { 256 | //if qsqr is still too small, return the identity rotation. 257 | q[bi][0] = 1; 258 | q[bi][1] = 0; 259 | q[bi][2] = 0; 260 | q[bi][3] = 0; 261 | too_small = true; 262 | } 263 | else 264 | { 265 | q[bi][0] /= normq; 266 | q[bi][1] /= normq; 267 | q[bi][2] /= normq; 268 | q[bi][3] /= normq; 269 | } 270 | 271 | memcpy(qopt, q[bi], 4 * sizeof(double)); 272 | return !too_small; 273 | } 274 | 275 | int polar_decomposition_3x3(double* _A, bool right_sided, double* U, double* P) 276 | { 277 | double A[9]; 278 | memcpy(A, _A, 9 * sizeof(double)); 279 | 280 | double det = matrix_determinant_3x3(A); 281 | if (det < 0) 282 | flip_matrix(A); 283 | 284 | double q[4]; 285 | double nrmsdsq = 0; 286 | optimal_quaternion(A, true, -1, &nrmsdsq, q); 287 | q[0] = -q[0]; 288 | quaternion_to_rotation_matrix(q, U); 289 | 290 | if (det < 0) 291 | flip_matrix(U); 292 | 293 | double UT[9] = {U[0], U[3], U[6], U[1], U[4], U[7], U[2], U[5], U[8]}; 294 | 295 | if (right_sided) 296 | matmul_3x3(UT, _A, P); 297 | else 298 | matmul_3x3(_A, UT, P); 299 | 300 | return 0; 301 | } 302 | 303 | -------------------------------------------------------------------------------- /src/polar_decomposition.h: -------------------------------------------------------------------------------- 1 | #ifndef POLAR_DECOMPOSITION_H 2 | #define POLAR_DECOMPOSITION_H 3 | 4 | #include 5 | 6 | int polar_decomposition_3x3(double* _A, bool right_sided, double* U, double* P); 7 | 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /src/quaternion.cpp: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | 29 | #define SIGN(x) (x >= 0 ? 1 : -1) 30 | #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) 31 | #define MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) 32 | 33 | 34 | double quat_dot(double* a, double* b) 35 | { 36 | return a[0] * b[0] 37 | + a[1] * b[1] 38 | + a[2] * b[2] 39 | + a[3] * b[3]; 40 | } 41 | 42 | double quat_size(double* q) 43 | { 44 | return sqrt(quat_dot(q, q)); 45 | } 46 | 47 | void normalize_quaternion(double* q) 48 | { 49 | double size = quat_size(q); 50 | 51 | q[0] /= size; 52 | q[1] /= size; 53 | q[2] /= size; 54 | q[3] /= size; 55 | } 56 | 57 | void rotation_matrix_to_quaternion(double* u, double* q) 58 | { 59 | double r11 = u[0]; 60 | double r12 = u[1]; 61 | double r13 = u[2]; 62 | double r21 = u[3]; 63 | double r22 = u[4]; 64 | double r23 = u[5]; 65 | double r31 = u[6]; 66 | double r32 = u[7]; 67 | double r33 = u[8]; 68 | 69 | q[0] = (1.0 + r11 + r22 + r33) / 4.0; 70 | q[1] = (1.0 + r11 - r22 - r33) / 4.0; 71 | q[2] = (1.0 - r11 + r22 - r33) / 4.0; 72 | q[3] = (1.0 - r11 - r22 + r33) / 4.0; 73 | 74 | q[0] = sqrt(MAX(0, q[0])); 75 | q[1] = sqrt(MAX(0, q[1])); 76 | q[2] = sqrt(MAX(0, q[2])); 77 | q[3] = sqrt(MAX(0, q[3])); 78 | 79 | double m0 = MAX(q[0], q[1]); 80 | double m1 = MAX(q[2], q[3]); 81 | double max = MAX(m0, m1); 82 | 83 | int i = 0; 84 | for (i=0;i<4;i++) 85 | if (q[i] == max) 86 | break; 87 | 88 | if (i == 0) 89 | { 90 | q[1] *= SIGN(r32 - r23); 91 | q[2] *= SIGN(r13 - r31); 92 | q[3] *= SIGN(r21 - r12); 93 | } 94 | else if (i == 1) 95 | { 96 | q[0] *= SIGN(r32 - r23); 97 | q[2] *= SIGN(r21 + r12); 98 | q[3] *= SIGN(r13 + r31); 99 | } 100 | else if (i == 2) 101 | { 102 | q[0] *= SIGN(r13 - r31); 103 | q[1] *= SIGN(r21 + r12); 104 | q[3] *= SIGN(r32 + r23); 105 | } 106 | else if (i == 3) 107 | { 108 | q[0] *= SIGN(r21 - r12); 109 | q[1] *= SIGN(r31 + r13); 110 | q[2] *= SIGN(r32 + r23); 111 | } 112 | 113 | normalize_quaternion(q); 114 | } 115 | 116 | void quaternion_to_rotation_matrix(double* q, double* u) 117 | { 118 | double a = q[0]; 119 | double b = q[1]; 120 | double c = q[2]; 121 | double d = q[3]; 122 | 123 | u[0] = a*a + b*b - c*c - d*d; 124 | u[1] = 2*b*c - 2*a*d; 125 | u[2] = 2*b*d + 2*a*c; 126 | 127 | u[3] = 2*b*c + 2*a*d; 128 | u[4] = a*a - b*b + c*c - d*d; 129 | u[5] = 2*c*d - 2*a*b; 130 | 131 | u[6] = 2*b*d - 2*a*c; 132 | u[7] = 2*c*d + 2*a*b; 133 | u[8] = a*a - b*b - c*c + d*d; 134 | } 135 | 136 | -------------------------------------------------------------------------------- /src/quaternion.h: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #ifndef QUATERNION_H 25 | #define QUATERNION_H 26 | 27 | double quat_dot(double* a, double* b); 28 | double quat_size(double* q); 29 | void normalize_quaternion(double* q); 30 | void quaternion_to_rotation_matrix(double* q, double* U); 31 | void rotation_matrix_to_quaternion(double* u, double* q); 32 | 33 | #endif 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/sqp_newton_lagrange.cpp: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "lup_decomposition.h" 29 | #include "matrix_vector.h" 30 | #include "quaternion.h" 31 | 32 | 33 | #define HESSIAN(a,b) hessian[(a) * (6 + n) + b] 34 | 35 | double newton_lagrange_step(int n, double* args, double (*key)[4][4], double* step) 36 | { 37 | double k = args[0]; //kappa 38 | double l = args[1]; //lambda 39 | 40 | double qw = args[2]; 41 | double qx = args[3]; 42 | double qy = args[4]; 43 | double qz = args[5]; 44 | 45 | double* q = &args[2]; 46 | double* x = &args[6]; 47 | 48 | double gradient[10] = {1, 1, -2*l*qw, -2*l*qx, -2*l*qy, -2*l*qz, 0, 0, 0, 0}; 49 | 50 | double hessian[10 * 10]; 51 | memset(hessian, 0, 10 * 10 * sizeof(double)); 52 | 53 | gradient[0] += -vector_dot(n, x, x); 54 | gradient[1] += -vector_dot(4, q, q); 55 | 56 | HESSIAN(2,2) += -2 * l; 57 | HESSIAN(3,3) += -2 * l; 58 | HESSIAN(4,4) += -2 * l; 59 | HESSIAN(5,5) += -2 * l; 60 | 61 | for (int i=0;i 25 | #include 26 | #include "matrix_vector.h" 27 | #include "polar_decomposition.h" 28 | 29 | 30 | double calculate_trace(int n, double* x, double* Ktrans, double* Q, double* P) 31 | { 32 | double t[9] = {0}; 33 | for (int i=0;i 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "mahalonobis_transform.h" 32 | #include "matrix_vector.h" 33 | #include "minkowski_reduction.h" 34 | #include "quaternion.h" 35 | #include "sqp_newton_lagrange.h" 36 | #include "stepwise_iteration.h" 37 | #include "unimodular_neighbourhood.h" 38 | #include "unimodular_functions.h" 39 | #include "templates.h" 40 | #include "constants.h" 41 | #include "parse_string.h" 42 | 43 | 44 | static double optimal_scaling_factor(double* P) 45 | { 46 | double trace = P[0] + P[4] + P[8]; 47 | 48 | double normsq = 0; 49 | for (int i=0;i<9;i++) 50 | normsq += P[i] * P[i]; 51 | 52 | return trace / normsq; 53 | } 54 | 55 | static void get_key_matrix(double* S, double* key) 56 | { 57 | double sxx = S[0], sxy = S[1], sxz = S[2]; 58 | double syx = S[3], syy = S[4], syz = S[5]; 59 | double szx = S[6], szy = S[7], szz = S[8]; 60 | 61 | key[0] = sxx + syy + szz; key[1] = szy - syz; key[2] = sxz - szx; key[3] = syx - sxy; 62 | key[4] = szy - syz; key[5] = sxx - syy - szz; key[6] = sxy + syx; key[7] = szx + sxz; 63 | key[8] = sxz - szx; key[9] = sxy + syx; key[10] = -sxx + syy - szz; key[11] = syz + szy; 64 | key[12] = syx - sxy; key[13] = szx + sxz; key[14] = syz + szy; key[15] = -sxx -syy + szz; 65 | } 66 | 67 | static double optimize_lattice_basis(int n, double* x, double* T, double* B, double* Q, double* opt) 68 | { 69 | // compute Mahalonobis transform 70 | double Ktrans[4 * 9]; 71 | mahalonobis_transform(n, T, B, Ktrans); 72 | 73 | // perform stepwise iteration to get a good initial guess 74 | // for cubic templates (those with a single template parameter) the initial guess is optimal 75 | double P[9]; 76 | double tolerance = 1E-5; 77 | int max_it = n == 1 ? 1 : 100; //cubic lattice types need a single iteration only 78 | optimize_stepwise(n, x, Ktrans, Q, P, max_it, tolerance); 79 | 80 | // use Newton's method to get fast convergence from initial guess to optimal solution 81 | // (non-cubic templates only) 82 | if (n >= 2) 83 | { 84 | // need rotation in quaternion form for SQP 85 | double q[4]; 86 | rotation_matrix_to_quaternion(Q, q); 87 | 88 | double key[4][16]; 89 | for (int i=0;i visited; 220 | 221 | int num_neighbours = search_correspondences ? NUM_UNIMODULAR_NEIGHBOURS : 1; 222 | int max_it = search_correspondences ? 40 : 1; 223 | 224 | const int n = template_sizes[type]; 225 | double* T = (double*)templates[type]; 226 | 227 | for (int it=0;it 28 | #include "constants.h" 29 | 30 | const double template_monoclinic[][3][3] = 31 | { 32 | { { 1, 0, 0}, 33 | { 0, 0, 0}, 34 | { 0, 0, 0}, }, 35 | 36 | { { 0, 0, 0}, 37 | { 0, 1, 0}, 38 | { 0, 0, 0}, }, 39 | 40 | { { 0, 0, 0}, 41 | { 0, 0, 0}, 42 | { 0, 0, 1}, }, 43 | 44 | { { 0, 0, 1}, 45 | { 0, 0, 0}, 46 | { 0, 0, 0}, }, 47 | 48 | }; 49 | 50 | const double template_basemonoclinic[][3][3] = 51 | { 52 | { { 0.5, -0.5, 0}, 53 | { 0, 0, 0}, 54 | { 0, 0, 0}, }, 55 | 56 | { { 0, 0, 0}, 57 | { 0.5, 0.5, 0}, 58 | { 0, 0, 0}, }, 59 | 60 | { { 0, 0, 0}, 61 | { 0, 0, 0}, 62 | { 0, 0, 1}, }, 63 | 64 | { { 0, 0, 1}, 65 | { 0, 0, 0}, 66 | { 0, 0, 0}, }, 67 | 68 | }; 69 | 70 | const double template_orthorhombic[][3][3] = 71 | { 72 | { { 1, 0, 0}, 73 | { 0, 0, 0}, 74 | { 0, 0, 0}, }, 75 | 76 | { { 0, 0, 0}, 77 | { 0, 1, 0}, 78 | { 0, 0, 0}, }, 79 | 80 | { { 0, 0, 0}, 81 | { 0, 0, 0}, 82 | { 0, 0, 1}, }, 83 | 84 | }; 85 | 86 | const double template_baseco[][3][3] = 87 | { 88 | { { 0.5, -0.5, 0}, 89 | { 0, 0, 0}, 90 | { 0, 0, 0}, }, 91 | 92 | { { 0, 0, 0}, 93 | { 0.5, 0.5, 0}, 94 | { 0, 0, 0}, }, 95 | 96 | { { 0, 0, 0}, 97 | { 0, 0, 0}, 98 | { 0, 0, 1}, }, 99 | 100 | }; 101 | 102 | const double template_bco[][3][3] = 103 | { 104 | { {-0.5, 0.5, 0.5}, 105 | { 0, 0, 0}, 106 | { 0, 0, 0}, }, 107 | 108 | { { 0, 0, 0}, 109 | { 0.5, -0.5, 0.5}, 110 | { 0, 0, 0}, }, 111 | 112 | { { 0, 0, 0}, 113 | { 0, 0, 0}, 114 | { 0.5, 0.5, -0.5}, }, 115 | 116 | }; 117 | 118 | const double template_fco[][3][3] = 119 | { 120 | { { 0, 1, 1}, 121 | { 0, 0, 0}, 122 | { 0, 0, 0}, }, 123 | 124 | { { 0, 0, 0}, 125 | { 1, 0, 1}, 126 | { 0, 0, 0}, }, 127 | 128 | { { 0, 0, 0}, 129 | { 0, 0, 0}, 130 | { 1, 1, 0}, }, 131 | 132 | }; 133 | 134 | const double template_tetragonal[][3][3] = 135 | { 136 | { { 1, 0, 0}, 137 | { 0, 1, 0}, 138 | { 0, 0, 0}, }, 139 | 140 | { { 0, 0, 0}, 141 | { 0, 0, 0}, 142 | { 0, 0, 1}, }, 143 | 144 | }; 145 | 146 | const double template_bct[][3][3] = 147 | { 148 | { {-0.5, 0.5, 0.5}, 149 | { 0.5, -0.5, 0.5}, 150 | { 0, 0, 0}, }, 151 | 152 | { { 0, 0, 0}, 153 | { 0, 0, 0}, 154 | { 0.5, 0.5, -0.5}, }, 155 | 156 | }; 157 | 158 | const double template_rhombohedral[][3][3] = 159 | { 160 | { { 1, 0, 0}, 161 | { 0, 1, 0}, 162 | { 0, 0, 1}, }, 163 | 164 | { { 0, 1, 1}, 165 | { 1, 0, 1}, 166 | { 1, 1, 0}, }, 167 | 168 | }; 169 | 170 | const double template_hexagonal[][3][3] = 171 | { 172 | { { 1, -0.5, 0}, 173 | { 0,sqrt(3)/2,0}, 174 | { 0, 0, 0}, }, 175 | 176 | { { 0, 0, 0}, 177 | { 0, 0, 0}, 178 | { 0, 0, 1}, }, 179 | 180 | }; 181 | 182 | const double template_cubic[][3][3] = 183 | { 184 | { { 1, 0, 0}, 185 | { 0, 1, 0}, 186 | { 0, 0, 1}, }, 187 | 188 | }; 189 | 190 | const double template_bcc[][3][3] = 191 | { 192 | { { -1, 1, 1}, 193 | { 1, -1, 1}, 194 | { 1, 1, -1}, }, 195 | 196 | }; 197 | 198 | const double template_fcc[][3][3] = 199 | { 200 | { { 0, 1, 1}, 201 | { 1, 0, 1}, 202 | { 1, 1, 0}, }, 203 | 204 | }; 205 | 206 | const int template_sizes[14] = {0, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1}; 207 | 208 | const double* templates[14] = { 209 | NULL, 210 | template_monoclinic[0][0], 211 | template_basemonoclinic[0][0], 212 | template_orthorhombic[0][0], 213 | template_baseco[0][0], 214 | template_bco[0][0], 215 | template_fco[0][0], 216 | template_tetragonal[0][0], 217 | template_bct[0][0], 218 | template_rhombohedral[0][0], 219 | template_hexagonal[0][0], 220 | template_cubic[0][0], 221 | template_bcc[0][0], 222 | template_fcc[0][0], 223 | }; 224 | 225 | #endif 226 | 227 | -------------------------------------------------------------------------------- /src/unimodular_functions.cpp: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #include 25 | #include 26 | 27 | 28 | static uint64_t ipow(uint64_t base, int exp) 29 | { 30 | uint64_t result = 1; 31 | for (;;) 32 | { 33 | if (exp & 1) 34 | result *= base; 35 | exp >>= 1; 36 | 37 | if (!exp) 38 | break; 39 | base *= base; 40 | } 41 | 42 | return result; 43 | } 44 | 45 | bool unimodular_too_large(int* L) 46 | { 47 | for (int i=0;i<9;i++) 48 | if (abs(L[i]) > 68) 49 | return true; 50 | return false; 51 | } 52 | 53 | uint64_t unimodular_hash(int* L) 54 | { 55 | //absolute values must be less than or equal to 68 56 | //log2(sum([137**i for i in range(1, 10)])) = 63.892857988354926 57 | //this fits in 64 bits 58 | 59 | uint64_t hash = 0; 60 | for (int i=0;i<9;i++) 61 | { 62 | uint64_t base = L[i] + 68; 63 | uint64_t r = ipow(base, i + 1); 64 | hash += r; 65 | } 66 | 67 | return hash; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/unimodular_functions.h: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 P. M. Larsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | 24 | #ifndef UNIMODULAR_FUNCTIONS_H 25 | #define UNIMODULAR_FUNCTIONS_H 26 | 27 | #include 28 | 29 | 30 | bool unimodular_too_large(int* L); 31 | uint64_t unimodular_hash(int* L); 32 | 33 | #endif 34 | 35 | -------------------------------------------------------------------------------- /tests/minkowski_reduction.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | from auguste import minkowski_reduce 4 | 5 | 6 | TOL = 1E-12 7 | 8 | 9 | @pytest.mark.parametrize("seed", range(20)) 10 | def test_random_3D(seed): 11 | rng = np.random.RandomState(seed) 12 | B = rng.uniform(-1, 1, (3, 3)) 13 | R, H = minkowski_reduce(B) 14 | 15 | assert np.allclose(H @ B, R, atol=TOL) 16 | assert np.sign(np.linalg.det(B)) == np.sign(np.linalg.det(R)) 17 | 18 | norms = np.linalg.norm(R, axis=1) 19 | assert (np.argsort(norms) == range(3)).all() 20 | 21 | 22 | # Issue 4 23 | def test_similar_norms(): 24 | 25 | cell = np.array([[-5.35538034, -0.1350599, -5.2795042], 26 | [+5.35538034, -5.2795042, -0.1350599], 27 | [-5.35538034, -5.2795042, -0.1350599]]) 28 | minkowski_reduce(cell) 29 | 30 | 31 | # Also issue 4 32 | def test_cycle(): 33 | cell = np.array([[-4.1855226, 4.17604824, 0.02341527], 34 | [+4.1855226, 0.02341527, 4.17604824], 35 | [+4.1855226, 4.17604824, 0.02341527]]) 36 | minkowski_reduce(cell) 37 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import scipy.linalg 4 | from numpy.testing import assert_allclose 5 | from auguste import symmetrize_lattice 6 | from scipy.spatial.transform import Rotation 7 | import auguste 8 | 9 | 10 | TOL = 1E-10 11 | 12 | 13 | input_data = {"primitive cubic": np.eye(3), 14 | "primitive tetragonal": np.diag([1, 1, 2]), 15 | "primitive orthorhombic": np.diag([1, 2, 3]), 16 | "primitive hexagonal": np.array([[1, 0, 0], 17 | [-1/2, np.sqrt(3) / 2, 0], 18 | [0, 0, 1]]), 19 | "face-centred cubic": np.array([[1, 1, 0], 20 | [1, 0, 1], 21 | [0, 1, 1]]), 22 | "body-centred cubic": np.array([[1, 1, -1], 23 | [1, -1, 1], 24 | [-1, 1, 1]]), 25 | "base-centred orthorhombic": np.array([[2, 1, 0], 26 | [2, -1, 0], 27 | [0, 0, 4]]) 28 | } 29 | 30 | simple = ["primitive cubic", 31 | "primitive tetragonal", 32 | "primitive orthorhombic", 33 | "primitive hexagonal"] 34 | 35 | 36 | @pytest.mark.parametrize("seed", range(5)) 37 | @pytest.mark.parametrize("name, cell", input_data.items()) 38 | def test_strain(name, cell, seed): 39 | rng = np.random.RandomState(seed=seed) 40 | P = rng.uniform(-0.1, 0.1, (3, 3)) 41 | _, P = scipy.linalg.polar(P) 42 | P += np.eye(3) 43 | strained = (P @ cell.T).T 44 | 45 | distance, symmetrized = symmetrize_lattice(strained, name) 46 | 47 | F = symmetrized.T @ np.linalg.inv(strained.T) 48 | assert_allclose(symmetrized.T, F @ strained.T, atol=TOL) 49 | 50 | _, P = scipy.linalg.polar(F) 51 | assert_allclose(distance, np.linalg.norm(P - np.eye(3)), atol=TOL) 52 | 53 | 54 | @pytest.mark.parametrize("seed", range(5)) 55 | @pytest.mark.parametrize("name, cell", [(k, v) for k, v in input_data.items() 56 | if k in simple]) 57 | def test_correspondence(name, cell, seed): 58 | 59 | rng = np.random.RandomState(seed=seed) 60 | q = Rotation.random(random_state=rng).as_matrix() 61 | 62 | inputL = np.array([[1, 2, 3], [0, 1, 0], [0, 0, 1]]) 63 | assert np.linalg.det(inputL) == 1 64 | cellLQ = (cell.T @ inputL.T).T @ q.T 65 | result = symmetrize_lattice(cellLQ, name, 66 | return_correspondence=True) 67 | distance, symmetrized, Q, L = result 68 | 69 | assert distance < TOL 70 | assert_allclose(symmetrized, cellLQ, atol=TOL) 71 | assert_allclose((cellLQ.T @ L).T @ Q.T, cell, atol=TOL) 72 | 73 | 74 | @pytest.mark.parametrize("name, cell", input_data.items()) 75 | def test_equality(name, cell): 76 | distance, symmetrized = symmetrize_lattice(cell, name) 77 | assert distance < TOL 78 | assert_allclose(symmetrized, cell, atol=TOL) 79 | 80 | 81 | @pytest.mark.parametrize("name, cell", input_data.items()) 82 | def test_calculate_vector(name, cell): 83 | index = auguste.names.index(name) 84 | distances = auguste.calculate_vector(cell) 85 | assert distances[0] < TOL 86 | assert distances[index] < TOL 87 | 88 | 89 | @pytest.mark.parametrize("name, cell", input_data.items()) 90 | def test_calculate_vector_basis_invariance(name, cell): 91 | inputL = np.array([[1, 2, 3], [0, 1, 0], [0, 0, 1]]) 92 | cellL = (cell.T @ inputL.T).T 93 | 94 | distancesL = auguste.calculate_vector(cellL) 95 | distances = auguste.calculate_vector(cell) 96 | assert_allclose(distances, distancesL, atol=TOL) 97 | --------------------------------------------------------------------------------