# Introduction Given two structures, this code snippet is used to compute the transformation matrix from a "source" structure to a "destination" structure. The most common example is the source being the primitive cell and destination the conventional cell, but this can be used for pretty much anything. Essentially, all we're doing is finding a matrix $T$ such that $ \begin{aligned} \left(\boldsymbol{a}_d, \boldsymbol{b}_d, \boldsymbol{c}_d\right)= & (\boldsymbol{a}_s, \boldsymbol{b}_s, \boldsymbol{c}_s) \boldsymbol{T} \\ \end{aligned} $ where $a, b, c$ are the lattice constants and the $s$ subscript denotes source and $d$ denotes destination. We're using VESTA's convention here. We can define our lattice matrices as $ \begin{align} \boldsymbol{D} &= \left(\boldsymbol{a}_d, \boldsymbol{b}_d, \boldsymbol{c}_d\right),\\ \boldsymbol{S} &= \left(\boldsymbol{a}_s, \boldsymbol{b}_s, \boldsymbol{c}_s\right) \end{align} $ so we simply have $ \boldsymbol{T} = \boldsymbol{S}^{-1} \boldsymbol{D} $ >[!WARNING] > `pymatgen` uses the opposite convention to VESTA, i.e., the transformation matrix used by, e.g., `Structure.make_supercell` and `SymmOp.from_rotation_and_translation`, is the transpose of the one shown above. ^921dcc # Option 1: Pymatgen This is the most straightforward way to do it. If you already have your structures in `POSCAR` or `.cif` formats (or anything else `pymatgen` supports), it's easy to do it this way. ```python import numpy as np from pymatgen.core.structure import Structure # Get structures from files source = Structure.from_file("source.vasp") # e.g., primitive destination = Structure.from_file("destination.vasp") # e.g., conventional # Define the lattice matrices S = source.lattice.matrix.T D = destination.lattice.matrix.T # Compute transformation matrix T = np.matmul(np.linalg.inv(S), D) ``` # Option 2: By hand You can just define your lattice matrices by hand if you choose to. For example, if you have your structures in Quantum ESPRESSO formats, `pymatgen` can't read them (but you can and should just convert them to `cif` using, e.g., [cif2cell](https://github.com/torbjornbjorkman/cif2cell)). Either way, if your QE input files look like this ##### Source: ``` CELL_PARAMETERS angstrom 4.2972855568 0.0000000000 0.0000000000 0.0000000000 4.2972855568 0.0000000000 2.1486428272 2.1486428272 4.8912462131 ``` ##### Destination: ``` CELL_PARAMETERS angstrom -2.1486427784 2.1486427784 4.8912463188 2.1486427784 -2.1486427784 4.8912463188 2.1486427784 2.1486427784 -4.8912463188 ``` Just define your lattice matrices manually: ```python a_d = [-2.1486427784, 2.1486427784, 4.8912463188] b_d = [2.1486427784, -2.1486427784, 4.8912463188] c_d = [2.1486427784, 2.1486427784, -4.8912463188] a_s = [4.2972855568, 0.0000000000, 0.0000000000] b_s = [0.0000000000, 4.2972855568, 0.0000000000] c_s = [2.1486428272, 2.1486428272, 4.8912462131] D = np.vstack((a_d, b_d, c_d)).T S = np.vstack((a_s, b_s, c_s)).T ``` And proceed as before to compute `T`. # Tips 1. See the [[Quick Transformation Matrix#^921dcc|warning]] above regarding conventions. 2. `VESTA` generally only accepts integral and half-integral transformation matrices. It won't throw an error but you'll end up with a mess. 3. `pymatgen`'s `Structure.make_supercell` method only accepts integral transformation matrices. 4. You can generate use an arbitrary rotation matrix in `pymatgen` as follows ```python from pymatgen.core.structure import Structure from pymatgen.core.operations import SymmOp # load file my_struct = Structure.from_file("struct.cif") # Don't use random numbers... # Shift Origin (if necessary) s = np.random.rand(1,3) # Transfromation matrix # See note above about VESTA vs pmg convention T = np.random.rand(3,3) shift = np.rand((3,1)) symop = SymmOp.from_rotation_and_translation(rotation_matrix=T, translation_vec=s) my_struct.apply_operation(symop, fractional=True) ``` 5. As a sanity check, if our transformation matrix is given by $ \begin{aligned} \boldsymbol{T} = \left(\begin{array}{lll} T_{11} & T_{12} & T_{13} \\ T_{21} & T_{22} & T_{23} \\ T_{31} & T_{32} & T_{33} \end{array}\right) \\ \end{aligned} $ Then we can define a rescaled transformation matrix $ \begin{aligned} \boldsymbol{T}^{\prime} = \left(\begin{array}{lll} T^{\prime}_{11} & T^{\prime}_{12} & T^{\prime}_{13} \\ T^{\prime}_{21} & T^{\prime}_{22} & T^{\prime}_{23} \\ T^{\prime}_{31} & T^{\prime}_{32} & T^{\prime}_{33} \end{array}\right) \\ \end{aligned} $ where $ T_{ij}^{\prime} = \frac{T_{ij}}{\sqrt{\sum_{k=1}^{3} T_{ik}^2}}, \quad \forall j \in \{1,2,3\} $ (Be careful with conventions here, again, see the [[Quick Transformation Matrix#^921dcc|warning]].) Then, $\pmb{T}^\prime$ should be an $O(3)$ matrix, i.e., $ \begin{align} |\det T| &= 1, \\ T^{T} = T^{-1} \implies T^TT = TT^T &= \mathbb{1} \end{align} $ We can check this in python easily ```python import numpy as np # Transforms c axis of simple cubic to point in[-2 1 1] direction T = np.array([[1, 1, 1], [0, -1, 1], [2, -1, -1]]) np.linalg.det(T) # 6 # Computes scale s = np.array([np.sqrt(np.sum([T[i,j]**2 for j in range(3)])) for i in range(3)]) T_prime = T/s[:,None] np.any(np.isclose(np.linalg.inv(T_prime), T_prime.T)) # True np.abs(np.linalg.det(T_prime)) # 1.0 ```