Examples
Creating a Cell
To create a Cell
, we first need to create a Lattice
. Then we can add atoms and their positions (in crystal coordinates):
julia> using Spglib
julia> lattice = [ -3.0179389205999998 -3.0179389205999998 0.0000000000000000 -5.2272235447000002 5.2272235447000002 0.0000000000000000 0.0000000000000000 0.0000000000000000 -9.7736219469000005 ]
3×3 Matrix{Float64}: -3.01794 -3.01794 0.0 -5.22722 5.22722 0.0 0.0 0.0 -9.77362
julia> positions = [[2 / 3, 1 / 3, 1 / 4], [1 / 3, 2 / 3, 3 / 4]]
2-element Vector{Vector{Float64}}: [0.6666666666666666, 0.3333333333333333, 0.25] [0.3333333333333333, 0.6666666666666666, 0.75]
julia> atoms = [1, 1]
2-element Vector{Int64}: 1 1
julia> cell = Cell(lattice, positions, atoms)
SpglibCell{Float64, Float64, Int64, Any} lattice: -3.0179389206 -3.0179389206 0.0 -5.2272235447 5.2272235447 0.0 0.0 0.0 -9.7736219469 2 atomic positions: 0.6666666666666666 0.3333333333333333 0.25 0.3333333333333333 0.6666666666666666 0.75 2 atoms: 1 1
Crystallographic choice and rigid rotation
The following example of a python script gives a crystal structure of Br whose space group type is Cmce. The basis vectors $\begin{bmatrix} \mathbf{a} & \mathbf{b} & \mathbf{c} \end{bmatrix}$ are fixed by the symmetry crystal in the standardization. The C-centering determines the c-axis, and m and c operations in Cmce fix which directions a- and b-axes should be with respect to each other axis. This is the first one choice appearing in the list of Hall symbols among 6 different choices for this space group type.
julia> using Spglib
julia> lattice = Lattice([[7.17851431, 0, 0], # a [0, 3.99943947, 0], # b [0, 0, 8.57154746]]) # c
3×3 Lattice{Float64} 7.17851431 0.0 0.0 0.0 3.99943947 0.0 0.0 0.0 8.57154746
julia> positions = [[0.0, 0.84688439, 0.1203133], [0.0, 0.65311561, 0.6203133], [0.0, 0.34688439, 0.3796867], [0.0, 0.15311561, 0.8796867], [0.5, 0.34688439, 0.1203133], [0.5, 0.15311561, 0.6203133], [0.5, 0.84688439, 0.3796867], [0.5, 0.65311561, 0.8796867]];
julia> atoms = fill(35, length(positions));
julia> cell = Cell(lattice, positions, atoms)
SpglibCell{Float64, Float64, Int64, Any} lattice: 7.17851431 0.0 0.0 0.0 3.99943947 0.0 0.0 0.0 8.57154746 8 atomic positions: 0.0 0.84688439 0.1203133 0.0 0.65311561 0.6203133 0.0 0.34688439 0.3796867 0.0 0.15311561 0.8796867 0.5 0.34688439 0.1203133 0.5 0.15311561 0.6203133 0.5 0.84688439 0.3796867 0.5 0.65311561 0.8796867 8 atoms: 35 35 35 35 35 35 35 35
julia> dataset = get_dataset(cell, 1e-5)
Dataset spacegroup_number: 64 hall_number: 304 international_symbol: Cmce hall_symbol: -C 2bc 2 choice: transformation_matrix: [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0] origin_shift: [0.0, 0.0, 0.0] n_operations: 16 rotations: StaticArraysCore.SMatrix{3, 3, Int32, 9}[[1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 1]] translations: StaticArraysCore.SVector{3, Float64}[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.5, 0.5], [0.0, 0.5, 0.5], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.5, 0.5], [0.0, 0.5, 0.5], [0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.5, 0.0, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.5, 0.0, 0.5], [0.5, 0.0, 0.5]] n_atoms: 8 wyckoffs: ['f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'] site_symmetry_symbols: ["m..", "m..", "m..", "m..", "m..", "m..", "m..", "m.."] equivalent_atoms: Int32[1, 1, 1, 1, 1, 1, 1, 1] crystallographic_orbits: Int32[1, 1, 1, 1, 1, 1, 1, 1] primitive_lattice: [0.0 3.589257155 0.0; -3.99943947 1.999719735 0.0; 0.0 0.0 8.57154746] mapping_to_primitive: Int32[1, 2, 3, 4, 1, 2, 3, 4] n_std_atoms: 8 std_lattice: [7.17851431 0.0 0.0; 0.0 3.99943947 0.0; 0.0 0.0 8.57154746] std_types: Int32[1, 1, 1, 1, 1, 1, 1, 1] std_positions: StaticArraysCore.SVector{3, Float64}[[0.0, 0.84688439, 0.1203133], [0.0, 0.65311561, 0.6203133], [0.0, 0.34688439000000004, 0.3796867], [0.0, 0.15311560999999996, 0.8796867], [0.5, 0.34688439000000004, 0.1203133], [0.5, 0.15311560999999996, 0.6203133], [0.5, 0.84688439, 0.3796867], [0.5, 0.65311561, 0.8796867]] std_rotation_matrix: [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0] std_mapping_to_primitive: Int32[1, 2, 3, 4, 1, 2, 3, 4] pointgroup_symbol: mmm
we get
julia> print("Space group type: ", dataset.international_symbol)
Space group type: Cmce
julia> print("Space group number: ", dataset.spacegroup_number)
Space group number: 64
julia> print("Transformation matrix: ")
Transformation matrix:
julia> dataset.transformation_matrix
3×3 StaticArraysCore.SMatrix{3, 3, Float64, 9} with indices SOneTo(3)×SOneTo(3): 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0
julia> print("Origin shift: ", dataset.origin_shift)
Origin shift: [0.0, 0.0, 0.0]
No rotation was introduced in the idealization. Next, we swap the a- and c-axes.
julia> lattice = Lattice([[8.57154746, 0, 0], # a [0, 3.99943947, 0], # b [0, 0, 7.17851431]]) # c
3×3 Lattice{Float64} 8.57154746 0.0 0.0 0.0 3.99943947 0.0 0.0 0.0 7.17851431
julia> positions = [[0.1203133, 0.84688439, 0.0], [0.6203133, 0.65311561, 0.0], [0.3796867, 0.34688439, 0.0], [0.8796867, 0.15311561, 0.0], [0.1203133, 0.34688439, 0.5], [0.6203133, 0.15311561, 0.5], [0.3796867, 0.84688439, 0.5], [0.8796867, 0.65311561, 0.5]];
julia> atoms = fill(35, length(positions));
julia> cell = Cell(lattice, positions, atoms)
SpglibCell{Float64, Float64, Int64, Any} lattice: 8.57154746 0.0 0.0 0.0 3.99943947 0.0 0.0 0.0 7.17851431 8 atomic positions: 0.1203133 0.84688439 0.0 0.6203133 0.65311561 0.0 0.3796867 0.34688439 0.0 0.8796867 0.15311561 0.0 0.1203133 0.34688439 0.5 0.6203133 0.15311561 0.5 0.3796867 0.84688439 0.5 0.8796867 0.65311561 0.5 8 atoms: 35 35 35 35 35 35 35 35
julia> dataset = get_dataset(cell, 1e-5)
Dataset spacegroup_number: 64 hall_number: 304 international_symbol: Cmce hall_symbol: -C 2bc 2 choice: transformation_matrix: [0.0 0.0 1.0; 0.0 1.0 0.0; -1.0 0.0 0.0] origin_shift: [0.0, 0.0, 0.0] n_operations: 16 rotations: StaticArraysCore.SMatrix{3, 3, Int32, 9}[[1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 -1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 -1], [-1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 -1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 -1], [-1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 1]] translations: StaticArraysCore.SVector{3, Float64}[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.0, 0.5, 0.5], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.0, 0.5], [0.0, 0.5, 0.5], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.0, 0.5]] n_atoms: 8 wyckoffs: ['f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'] site_symmetry_symbols: ["m..", "m..", "m..", "m..", "m..", "m..", "m..", "m.."] equivalent_atoms: Int32[1, 1, 1, 1, 1, 1, 1, 1] crystallographic_orbits: Int32[1, 1, 1, 1, 1, 1, 1, 1] primitive_lattice: [0.0 0.0 8.57154746; 3.99943947 1.999719735 0.0; 0.0 3.589257155 0.0] mapping_to_primitive: Int32[1, 2, 3, 4, 1, 2, 3, 4] n_std_atoms: 8 std_lattice: [7.17851431 0.0 0.0; 0.0 3.99943947 0.0; 0.0 0.0 8.57154746] std_types: Int32[1, 1, 1, 1, 1, 1, 1, 1] std_positions: StaticArraysCore.SVector{3, Float64}[[0.0, 0.84688439, 0.8796867], [0.0, 0.65311561, 0.37968670000000015], [0.0, 0.34688439000000004, 0.6203133], [0.0, 0.15311560999999996, 0.12031329999999996], [0.5, 0.34688439000000004, 0.8796867], [0.5, 0.15311560999999996, 0.37968670000000015], [0.5, 0.84688439, 0.6203133], [0.5, 0.65311561, 0.12031329999999996]] std_rotation_matrix: [0.0 0.0 1.0; 0.0 1.0 0.0; -1.0 0.0 0.0] std_mapping_to_primitive: Int32[1, 2, 3, 4, 1, 2, 3, 4] pointgroup_symbol: mmm
By this, we get
julia> print("Space group type: ", dataset.international_symbol)
Space group type: Cmce
julia> print("Space group number: ", dataset.spacegroup_number)
Space group number: 64
julia> print("Transformation matrix: ")
Transformation matrix:
julia> dataset.transformation_matrix
3×3 StaticArraysCore.SMatrix{3, 3, Float64, 9} with indices SOneTo(3)×SOneTo(3): 0.0 0.0 1.0 0.0 1.0 0.0 -1.0 0.0 0.0
julia> print("Origin shift: ", dataset.origin_shift)
Origin shift: [0.0, 0.0, 0.0]
We get a non-identity transformation matrix, which wants to transform back to the original (above) crystal structure by swapping a- and c-axes. The transformation back of the basis vectors is achieved by
\[\begin{bmatrix} \mathbf{a} & \mathbf{b} & \mathbf{c} \end{bmatrix} = \begin{bmatrix} \mathbf{a}_\text{s} & \mathbf{b}_\text{s} & \mathbf{c}_\text{s} \end{bmatrix} \mathbf{P},\]
Next, we try to rotate rigidly the crystal structure by $45^\circ$ around c-axis in Cartesian coordinates from the first one:
julia> lattice = Lattice([[5.0759761474456697, 5.0759761474456697, 0], # a [-2.8280307701821314, 2.8280307701821314, 0], # b [0, 0, 8.57154746]]) # c
3×3 Lattice{Float64} 5.07597614744567 -2.8280307701821314 0.0 5.07597614744567 2.8280307701821314 0.0 0.0 0.0 8.57154746
julia> positions = [[0.0, 0.84688439, 0.1203133], [0.0, 0.65311561, 0.6203133], [0.0, 0.34688439, 0.3796867], [0.0, 0.15311561, 0.8796867], [0.5, 0.34688439, 0.1203133], [0.5, 0.15311561, 0.6203133], [0.5, 0.84688439, 0.3796867], [0.5, 0.65311561, 0.8796867]];
julia> atoms = fill(35, length(positions));
julia> cell = Cell(lattice, positions, atoms)
SpglibCell{Float64, Float64, Int64, Any} lattice: 5.07597614744567 -2.8280307701821314 0.0 5.07597614744567 2.8280307701821314 0.0 0.0 0.0 8.57154746 8 atomic positions: 0.0 0.84688439 0.1203133 0.0 0.65311561 0.6203133 0.0 0.34688439 0.3796867 0.0 0.15311561 0.8796867 0.5 0.34688439 0.1203133 0.5 0.15311561 0.6203133 0.5 0.84688439 0.3796867 0.5 0.65311561 0.8796867 8 atoms: 35 35 35 35 35 35 35 35
julia> dataset = get_dataset(cell, 1e-5)
Dataset spacegroup_number: 64 hall_number: 304 international_symbol: Cmce hall_symbol: -C 2bc 2 choice: transformation_matrix: [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0] origin_shift: [5.551115123125783e-17, 0.0, 0.0] n_operations: 16 rotations: StaticArraysCore.SMatrix{3, 3, Int32, 9}[[1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 -1 0; 0 0 1], [1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 -1], [-1 0 0; 0 1 0; 0 0 1], [-1 0 0; 0 1 0; 0 0 -1], [1 0 0; 0 -1 0; 0 0 1]] translations: StaticArraysCore.SVector{3, Float64}[[0.0, 0.0, 0.0], [-1.1102230246251565e-16, 2.465190328815662e-32, 0.0], [-1.1102230246251565e-16, 0.5, 0.5], [0.0, 0.5, 0.5], [0.0, 0.0, 0.0], [-1.1102230246251565e-16, 2.465190328815662e-32, 0.0], [-1.1102230246251565e-16, 0.5, 0.5], [0.0, 0.5, 0.5], [0.5, 0.5, 0.0], [0.4999999999999999, 0.5, 0.0], [0.4999999999999999, 0.0, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0], [0.4999999999999999, 0.5, 0.0], [0.4999999999999999, 0.0, 0.5], [0.5, 0.0, 0.5]] n_atoms: 8 wyckoffs: ['f', 'f', 'f', 'f', 'f', 'f', 'f', 'f'] site_symmetry_symbols: ["m..", "m..", "m..", "m..", "m..", "m..", "m..", "m.."] equivalent_atoms: Int32[1, 1, 1, 1, 1, 1, 1, 1] crystallographic_orbits: Int32[1, 1, 1, 1, 1, 1, 1, 1] primitive_lattice: [2.8280307701821314 1.1239726886317691 0.0; -2.8280307701821314 3.9520034588139006 0.0; 0.0 0.0 8.57154746] mapping_to_primitive: Int32[1, 2, 3, 4, 1, 2, 3, 4] n_std_atoms: 8 std_lattice: [7.178514309999999 0.0 0.0; 0.0 3.99943947 0.0; 0.0 0.0 8.57154746] std_types: Int32[1, 1, 1, 1, 1, 1, 1, 1] std_positions: StaticArraysCore.SVector{3, Float64}[[0.0, 0.84688439, 0.1203133], [0.0, 0.65311561, 0.6203133], [0.0, 0.34688439000000004, 0.3796867], [0.0, 0.15311560999999996, 0.8796867], [0.5, 0.34688439000000004, 0.1203133], [0.5, 0.15311560999999996, 0.6203133], [0.5, 0.84688439, 0.3796867], [0.5, 0.65311561, 0.8796867]] std_rotation_matrix: [0.7071067811865475 0.7071067811865475 0.0; -0.7071067811865475 0.7071067811865475 0.0; 0.0 0.0 1.0] std_mapping_to_primitive: Int32[1, 2, 3, 4, 1, 2, 3, 4] pointgroup_symbol: mmm
and
julia> print("Space group type: ", dataset.international_symbol)
Space group type: Cmce
julia> print("Space group number: ", dataset.spacegroup_number)
Space group number: 64
julia> print("Transformation matrix: ")
Transformation matrix:
julia> dataset.transformation_matrix
3×3 StaticArraysCore.SMatrix{3, 3, Float64, 9} with indices SOneTo(3)×SOneTo(3): 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0
julia> print("Origin shift: ", dataset.origin_shift)
Origin shift: [5.551115123125783e-17, 0.0, 0.0]
The transformation matrix is kept unchanged even though the crystal structure is rotated in Cartesian coordinates. The origin shift is different but it changes only the order of atoms, so effectively it does nothing.
Transformation to a primitive cell
There are infinite number of choices of primitive cell. The transformation from a primitive cell basis vectors to the other primitive cell basis vectors is always done by an integer matrix because any lattice points can be generated by the linear combination of the three primitive basis vectors.
When we have a non-primitive cell basis vectors as given in the above example:
julia> using Spglib
julia> lattice = Lattice([[7.17851431, 0, 0], # a [0, 3.99943947, 0], # b [0, 0, 8.57154746]]) # c
3×3 Lattice{Float64} 7.17851431 0.0 0.0 0.0 3.99943947 0.0 0.0 0.0 8.57154746
This has the C-centring, so it must be transformed to a primitive cell. A possible transformation is shown at Transformation to the primitive cell, which is $\mathbf{P}_\text{C}$. With the following script:
julia> Pc = [ 1//2 1//2 0 -1//2 1//2 0 0 0 1 ]
3×3 Matrix{Rational{Int64}}: 1//2 1//2 0 -1//2 1//2 0 0 0 1
julia> primitive_lattice = lattice * Pc
3×3 Matrix{Float64}: 3.58926 3.58926 0.0 -1.99972 1.99972 0.0 0.0 0.0 8.57155
we get the primitive cell basis vectors primitive_lattice
.
find_primitive
gives a primitive cell that is obtained by transforming standardized and idealized crystal structure to the primitive cell using the transformation matrix. Therefore by this script we get:
julia> positions = [ [0.0, 0.84688439, 0.1203133], [0.0, 0.65311561, 0.6203133], [0.0, 0.34688439, 0.3796867], [0.0, 0.15311561, 0.8796867], [0.5, 0.34688439, 0.1203133], [0.5, 0.15311561, 0.6203133], [0.5, 0.84688439, 0.3796867], [0.5, 0.65311561, 0.8796867], ];
julia> atoms = fill(8, length(positions));
julia> cell = Cell(lattice, positions, atoms)
SpglibCell{Float64, Float64, Int64, Any} lattice: 7.17851431 0.0 0.0 0.0 3.99943947 0.0 0.0 0.0 8.57154746 8 atomic positions: 0.0 0.84688439 0.1203133 0.0 0.65311561 0.6203133 0.0 0.34688439 0.3796867 0.0 0.15311561 0.8796867 0.5 0.34688439 0.1203133 0.5 0.15311561 0.6203133 0.5 0.84688439 0.3796867 0.5 0.65311561 0.8796867 8 atoms: 8 8 8 8 8 8 8 8
julia> find_primitive(cell).lattice
3×3 Lattice{Float64} 3.589257155 3.589257155 0.0 -1.999719735 1.999719735 0.0 0.0 0.0 8.57154746
This is same as what we manually obtained above. Even when the basis vectors are rigidly rotated as:
julia> new_lattice = Lattice([[5.0759761474456697, 5.0759761474456697, 0], [-2.8280307701821314, 2.8280307701821314, 0], [0, 0, 8.57154746]])
3×3 Lattice{Float64} 5.07597614744567 -2.8280307701821314 0.0 5.07597614744567 2.8280307701821314 0.0 0.0 0.0 8.57154746
the relationship of $a$, $b$, $c$ axes is unchanged. Therefore the same transformation matrix to the primitive cell can be used. Then we get:
julia> new_lattice * Pc
3×3 Matrix{Float64}: 3.952 1.12397 0.0 1.12397 3.952 0.0 0.0 0.0 8.57155
However applying find_primitive
rigidly rotates automatically and so the following script doesn't give this basis vectors:
julia> lattice = Lattice([ [5.0759761474456697, 5.0759761474456697, 0], [-2.8280307701821314, 2.8280307701821314, 0], [0, 0, 8.57154746], ])
3×3 Lattice{Float64} 5.07597614744567 -2.8280307701821314 0.0 5.07597614744567 2.8280307701821314 0.0 0.0 0.0 8.57154746
julia> positions = [ [0.0, 0.84688439, 0.1203133], [0.0, 0.65311561, 0.6203133], [0.0, 0.34688439, 0.3796867], [0.0, 0.15311561, 0.8796867], [0.5, 0.34688439, 0.1203133], [0.5, 0.15311561, 0.6203133], [0.5, 0.84688439, 0.3796867], [0.5, 0.65311561, 0.8796867], ];
julia> atoms = fill(8, length(positions));
julia> cell = Cell(lattice, positions, atoms)
SpglibCell{Float64, Float64, Int64, Any} lattice: 5.07597614744567 -2.8280307701821314 0.0 5.07597614744567 2.8280307701821314 0.0 0.0 0.0 8.57154746 8 atomic positions: 0.0 0.84688439 0.1203133 0.0 0.65311561 0.6203133 0.0 0.34688439 0.3796867 0.0 0.15311561 0.8796867 0.5 0.34688439 0.1203133 0.5 0.15311561 0.6203133 0.5 0.84688439 0.3796867 0.5 0.65311561 0.8796867 8 atoms: 8 8 8 8 8 8 8 8
but gives those with respect to the idealized ones:
julia> find_primitive(cell).lattice
3×3 Lattice{Float64} 3.5892571549999994 3.5892571549999994 0.0 -1.999719735 1.999719735 0.0 0.0 0.0 8.57154746
To obtain the rotated primitive cell basis vectors, we can use standardize_cell
as shown below:
julia> standardize_cell(cell, to_primitive=true, no_idealize=true).lattice
3×3 Lattice{Float64} 3.9520034588139006 1.1239726886317691 0.0 1.1239726886317691 3.9520034588139006 0.0 0.0 0.0 8.57154746
which is equivalent to that we get manually. However, using standardize_cell
, distortion is not removed for the distorted crystal structure.
Computing rigid rotation introduced by idealization
This example is from here.
In this package, rigid rotation is purposely introduced in the idealization step though this is unlikely as a crystallographic operation.
julia> using StaticArrays, Spglib
ERROR: ArgumentError: Package StaticArrays not found in current path. - Run `import Pkg; Pkg.add("StaticArrays")` to install the StaticArrays package.
julia> lattice = Lattice([ [5.0759761474456697, 5.0759761474456697, 0], [-2.8280307701821314, 2.8280307701821314, 0], [0, 0, 8.57154746], ]);
ERROR: UndefVarError: `Lattice` not defined in `Main` Suggestion: check for spelling errors or missing imports. Hint: a global variable of this name may be made accessible by importing CrystallographyCore in the current active module Main Hint: a global variable of this name also exists in Spglib.
julia> positions = [ [0.0, 0.84688439, 0.1203133], [0.0, 0.65311561, 0.6203133], [0.0, 0.34688439, 0.3796867], [0.0, 0.15311561, 0.8796867], [0.5, 0.34688439, 0.1203133], [0.5, 0.15311561, 0.6203133], [0.5, 0.84688439, 0.3796867], [0.5, 0.65311561, 0.8796867], ];
julia> atoms = fill(35, length(positions));
julia> cell = Cell(lattice, positions, atoms)
ERROR: UndefVarError: `Cell` not defined in `Main` Suggestion: check for spelling errors or missing imports. Hint: a global variable of this name may be made accessible by importing CrystallographyCore in the current active module Main Hint: a global variable of this name also exists in Spglib.
julia> dataset = get_dataset(cell, 1e-5)
ERROR: UndefVarError: `get_dataset` not defined in `Main` Suggestion: check for spelling errors or missing imports. Hint: a global variable of this name also exists in Spglib.
julia> dataset.international_symbol
ERROR: UndefVarError: `dataset` not defined in `Main` Suggestion: check for spelling errors or missing imports.
julia> dataset.spacegroup_number
ERROR: UndefVarError: `dataset` not defined in `Main` Suggestion: check for spelling errors or missing imports.
julia> dataset.transformation_matrix
ERROR: UndefVarError: `dataset` not defined in `Main` Suggestion: check for spelling errors or missing imports.
We can see the transformation matrix from the given lattice to the standardized lattice is the identity matrix, i.e., the given lattice is already a standardized lattice.
julia> std_lattice_before_idealization = convert(Matrix{Float64}, lattice) * inv(dataset.transformation_matrix)
ERROR: UndefVarError: `lattice` not defined in `Main` Suggestion: check for spelling errors or missing imports.
This is based on formula in Transformation matrix $\mathbf{P}$ and origin shift $\mathbf{p}$ and Passive/forward/alias transformation:
\[\begin{align} \begin{bmatrix} \mathbf{a} & \mathbf{b} & \mathbf{c} \end{bmatrix} &= \begin{bmatrix} \mathbf{a}_\text{s} & \mathbf{b}_\text{s} & \mathbf{c}_\text{s} \end{bmatrix} \mathbf{P},\\ \therefore \begin{bmatrix} \mathbf{a}_\text{s} & \mathbf{b}_\text{s} & \mathbf{c}_\text{s} \end{bmatrix} &= \begin{bmatrix} \mathbf{a} & \mathbf{b} & \mathbf{c} \end{bmatrix} \mathbf{P}^{-1}. \end{align}\]
Here, $\mathbf{P}$ is the dataset.transformation_matrix
.
Note that in contrast to the Python code:
std_lattice_before_idealization = np.dot(
np.transpose(lattice),
np.linalg.inv(dataset['transformation_matrix'])).T
where there are multiple transpose operations, we do not have to do that in our Julia code since we choose a column-major order of stacking lattice vectors as described in Basis vectors, and we return transformation matrix in column-major order, too.
Now, we obtain the standardized basis vectors after idealization $\begin{bmatrix} \bar{\mathbf{a}}_\text{s} & \bar{\mathbf{b}}_\text{s} & \bar{\mathbf{c}}_\text{s} \end{bmatrix}$:
julia> std_lattice_after_idealization = dataset.std_lattice
ERROR: UndefVarError: `dataset` not defined in `Main` Suggestion: check for spelling errors or missing imports.
This is different from the standardized basis vectors before idealization $\begin{bmatrix} \mathbf{a}_\text{s} & \mathbf{b}_\text{s} & \mathbf{c}_\text{s} \end{bmatrix}$. Unless this crystal structure is distorted from the crystal structure that has the ideal symmetry, this means that the crystal was rotated rigidly in the idealization step by
\[\begin{bmatrix} \bar{\mathbf{a}}_\text{s} & \bar{\mathbf{b}}_\text{s} & \bar{\mathbf{c}}_\text{s} \end{bmatrix} = \mathbf{R} \begin{bmatrix} \mathbf{a}_\text{s} & \mathbf{b}_\text{s} & \mathbf{c}_\text{s} \end{bmatrix},\]
as stated in Rotation introduced by idealization. where $\mathbf{R}$ is the rotation matrix. This is computed by
\[\mathbf{R} = \begin{bmatrix} \bar{\mathbf{a}}_\text{s} & \bar{\mathbf{b}}_\text{s} & \bar{\mathbf{c}}_\text{s} \end{bmatrix} \begin{bmatrix} \mathbf{a}_\text{s} & \mathbf{b}_\text{s} & \mathbf{c}_\text{s} \end{bmatrix}^{-1}\]
In Julia code, this is
julia> 𝐀 = convert(Matrix{Float64}, std_lattice_after_idealization)
ERROR: UndefVarError: `std_lattice_after_idealization` not defined in `Main` Suggestion: check for spelling errors or missing imports.
julia> 𝐁 = convert(Matrix{Float64}, std_lattice_before_idealization)
ERROR: UndefVarError: `std_lattice_before_idealization` not defined in `Main` Suggestion: check for spelling errors or missing imports.
julia> 𝐑 = 𝐀 * inv(𝐁)
ERROR: UndefVarError: `𝐀` not defined in `Main` Suggestion: check for spelling errors or missing imports.
Note also the transpose is not applied here in contrast to the Python code:
R = np.dot(dataset['std_lattice'].T, np.linalg.inv(std_lattice_before_idealization.T))
This equals to
\[\begin{bmatrix} \cos \theta & -\sin \theta & 0\\ \sin \theta & \cos \theta & 0\\ 0 & 0 & 1 \end{bmatrix}\]
where $\theta = -\pi/4$ and $\det(\mathbf{R}) = 1$ when no distortion:
julia> θ = -π/4
-0.7853981633974483
julia> [ cos(θ) -sin(θ) 0 sin(θ) cos(θ) 0 0 0 1 ]
3×3 Matrix{Float64}: 0.707107 0.707107 0.0 -0.707107 0.707107 0.0 0.0 0.0 1.0
Compared to dataset.std_rotation_matrix
:
julia> dataset.std_rotation_matrix
ERROR: UndefVarError: `dataset` not defined in `Main` Suggestion: check for spelling errors or missing imports.
we have approximately the same result.
In summary of the two steps,
\[\begin{align} \begin{bmatrix} \mathbf{a}_\text{s} & \mathbf{b}_\text{s} & \mathbf{c}_\text{s} \end{bmatrix} = \begin{bmatrix} \mathbf{a} & \mathbf{b} & \mathbf{c} \end{bmatrix} \mathbf{P}^{-1} &= \mathbf{R}^{-1} \begin{bmatrix} \bar{\mathbf{a}}_\text{s} & \bar{\mathbf{b}}_\text{s} & \bar{\mathbf{c}}_\text{s} \end{bmatrix},\\ \begin{bmatrix} \bar{\mathbf{a}}_\text{s} & \bar{\mathbf{b}}_\text{s} & \bar{\mathbf{c}}_\text{s} \end{bmatrix} \mathbf{P} &= \mathbf{R} \begin{bmatrix} \mathbf{a}_\text{s} & \mathbf{b}_\text{s} & \mathbf{c}_\text{s} \end{bmatrix}. \end{align}\]