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]]) # c3×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_matrix3×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]])  # c3×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_matrix3×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]])  # c3×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_matrix3×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]]) # c3×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 * Pc3×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).lattice3×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 * Pc3×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).lattice3×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).lattice3×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
julia> lattice = Lattice([ [5.0759761474456697, 5.0759761474456697, 0], [-2.8280307701821314, 2.8280307701821314, 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
julia> dataset.international_symbol"Cmce"
julia> dataset.spacegroup_number64
julia> dataset.transformation_matrix3×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

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)3×3 Matrix{Float64}:
 5.07598  -2.82803  0.0
 5.07598   2.82803  0.0
 0.0       0.0      8.57155

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_lattice3×3 Lattice{Float64}
 7.178514309999999  0.0  0.0
 0.0  3.99943947  0.0
 0.0  0.0  8.57154746

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)3×3 Matrix{Float64}:
 7.17851  0.0      0.0
 0.0      3.99944  0.0
 0.0      0.0      8.57155
julia> 𝐁 = convert(Matrix{Float64}, std_lattice_before_idealization)3×3 Matrix{Float64}: 5.07598 -2.82803 0.0 5.07598 2.82803 0.0 0.0 0.0 8.57155
julia> 𝐑 = 𝐀 * inv(𝐁)3×3 Matrix{Float64}: 0.707107 0.707107 0.0 -0.707107 0.707107 0.0 0.0 0.0 1.0

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_matrix3×3 StaticArraysCore.SMatrix{3, 3, Float64, 9} with indices SOneTo(3)×SOneTo(3):
  0.707107  0.707107  0.0
 -0.707107  0.707107  0.0
  0.0       0.0       1.0

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}\]