kim-tools 0.3.6__py3-none-any.whl → 0.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kim_tools/__init__.py +1 -1
- kim_tools/aflow_util/core.py +81 -27
- kim_tools/ase/core.py +122 -33
- kim_tools/kimunits.py +26 -10
- kim_tools/symmetry_util/core.py +276 -102
- kim_tools/symmetry_util/data/elast_cubic.svg +105 -0
- kim_tools/symmetry_util/data/elast_hexagonal.svg +105 -0
- kim_tools/symmetry_util/data/elast_monoclinic.svg +108 -0
- kim_tools/symmetry_util/data/elast_orthorhombic.svg +96 -0
- kim_tools/symmetry_util/data/elast_tetragonal_4_slash_m.svg +114 -0
- kim_tools/symmetry_util/data/elast_tetragonal_4_slash_mmm.svg +105 -0
- kim_tools/symmetry_util/data/elast_triclinic.svg +132 -0
- kim_tools/symmetry_util/data/elast_trigonal_3bar.svg +129 -0
- kim_tools/symmetry_util/data/elast_trigonal_3bar_m_2nd_pos.svg +117 -0
- kim_tools/symmetry_util/data/elast_trigonal_3bar_m_3rd_pos.svg +117 -0
- kim_tools/symmetry_util/elasticity.py +390 -0
- kim_tools/test_driver/core.py +436 -80
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/METADATA +3 -2
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/RECORD +22 -11
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/WHEEL +0 -0
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/licenses/LICENSE.CDDL +0 -0
- {kim_tools-0.3.6.dist-info → kim_tools-0.4.2.dist-info}/top_level.txt +0 -0
kim_tools/symmetry_util/core.py
CHANGED
|
@@ -9,19 +9,18 @@ from itertools import product
|
|
|
9
9
|
from math import ceil
|
|
10
10
|
from typing import Dict, List, Optional, Tuple, Union
|
|
11
11
|
|
|
12
|
-
import matplotlib.pyplot as plt
|
|
13
12
|
import numpy as np
|
|
14
13
|
import numpy.typing as npt
|
|
14
|
+
import sympy as sp
|
|
15
15
|
from ase import Atoms
|
|
16
16
|
from ase.cell import Cell
|
|
17
17
|
from ase.constraints import FixSymmetry
|
|
18
18
|
from ase.geometry import get_distances, get_duplicate_atoms
|
|
19
|
-
from
|
|
19
|
+
from ase.neighborlist import natural_cutoffs, neighbor_list
|
|
20
20
|
from pymatgen.core.operations import SymmOp
|
|
21
21
|
from pymatgen.core.tensors import Tensor
|
|
22
|
-
from scipy.stats import kstest
|
|
23
|
-
from sklearn.decomposition import PCA
|
|
24
22
|
from sympy import Matrix, cos, matrix2numpy, sin, sqrt, symbols
|
|
23
|
+
from sympy.tensor.array.expressions import ArrayContraction, ArrayTensorProduct
|
|
25
24
|
|
|
26
25
|
logger = logging.getLogger(__name__)
|
|
27
26
|
logging.basicConfig(filename="kim-tools.log", level=logging.INFO, force=True)
|
|
@@ -48,6 +47,7 @@ __all__ = [
|
|
|
48
47
|
"change_of_basis_atoms",
|
|
49
48
|
"get_possible_primitive_shifts",
|
|
50
49
|
"get_primitive_genpos_ops",
|
|
50
|
+
"get_smallest_nn_dist",
|
|
51
51
|
]
|
|
52
52
|
|
|
53
53
|
C_CENTERED_ORTHORHOMBIC_GROUPS = (20, 21, 35, 36, 37, 63, 64, 65, 66, 67, 68)
|
|
@@ -514,7 +514,9 @@ def get_change_of_basis_matrix_to_conventional_cell_from_formal_bravais_lattice(
|
|
|
514
514
|
return np.round(change_of_basis_matrix)
|
|
515
515
|
|
|
516
516
|
|
|
517
|
-
def change_of_basis_atoms(
|
|
517
|
+
def change_of_basis_atoms(
|
|
518
|
+
atoms: Atoms, change_of_basis: npt.ArrayLike, cutoff: Optional[float] = None
|
|
519
|
+
) -> Atoms:
|
|
518
520
|
"""
|
|
519
521
|
Perform an arbitrary basis change on an ``Atoms`` object, duplicating or cropping
|
|
520
522
|
atoms as needed. A basic check is made that the determinant of ``change_of_basis``
|
|
@@ -522,7 +524,7 @@ def change_of_basis_atoms(atoms: Atoms, change_of_basis: npt.ArrayLike) -> Atoms
|
|
|
522
524
|
that ``change_of_basis`` is appropriate for the particuar crystal described by
|
|
523
525
|
``atoms``, which is up to the user.
|
|
524
526
|
|
|
525
|
-
TODO: Incorporate
|
|
527
|
+
TODO: Incorporate period extension test into this function
|
|
526
528
|
|
|
527
529
|
Args:
|
|
528
530
|
atoms:
|
|
@@ -541,6 +543,9 @@ def change_of_basis_atoms(atoms: Atoms, change_of_basis: npt.ArrayLike) -> Atoms
|
|
|
541
543
|
|
|
542
544
|
Relationship between fractional coordinates in each basis:
|
|
543
545
|
**x** = **P** **x**'
|
|
546
|
+
cutoff:
|
|
547
|
+
The cutoff to use for deleting duplicate atoms. If not specified,
|
|
548
|
+
the AFLOW tolerance of 0.01*(smallest NN distance) is used.
|
|
544
549
|
|
|
545
550
|
Returns:
|
|
546
551
|
The transformed ``Atoms`` object, containing the original number of
|
|
@@ -575,7 +580,9 @@ def change_of_basis_atoms(atoms: Atoms, change_of_basis: npt.ArrayLike) -> Atoms
|
|
|
575
580
|
new_atoms = atoms.repeat(repeat)
|
|
576
581
|
new_atoms.set_cell(new_cell)
|
|
577
582
|
new_atoms.wrap()
|
|
578
|
-
|
|
583
|
+
if cutoff is None:
|
|
584
|
+
cutoff = get_smallest_nn_dist(atoms) * 0.01
|
|
585
|
+
get_duplicate_atoms(new_atoms, cutoff=cutoff, delete=True)
|
|
579
586
|
|
|
580
587
|
volume_change = np.linalg.det(change_of_basis)
|
|
581
588
|
if not np.isclose(len(atoms) * volume_change, len(new_atoms)):
|
|
@@ -632,14 +639,16 @@ def transform_atoms(atoms: Atoms, op: Dict) -> Atoms:
|
|
|
632
639
|
return atoms_transformed
|
|
633
640
|
|
|
634
641
|
|
|
635
|
-
def reduce_and_avg(
|
|
636
|
-
atoms: Atoms, repeat: Tuple[int, int, int]
|
|
637
|
-
) -> Tuple[Atoms, npt.ArrayLike]:
|
|
642
|
+
def reduce_and_avg(atoms: Atoms, repeat: Tuple[int, int, int]) -> Atoms:
|
|
638
643
|
"""
|
|
639
644
|
TODO: Upgrade :func:`change_of_basis_atoms` to provide the distances
|
|
640
645
|
array, obviating this function
|
|
641
646
|
|
|
642
|
-
Function to reduce all atoms to the original unit cell position
|
|
647
|
+
Function to reduce all atoms to the original unit cell position,
|
|
648
|
+
assuming the supercell is built from contiguous repeats of the unit cell
|
|
649
|
+
(i.e. atoms 0 to N-1 in the supercell are the original unit cell, atoms N to
|
|
650
|
+
2*[N-1] are the original unit cell shifted by an integer multiple of
|
|
651
|
+
the lattice vectors, and so on)
|
|
643
652
|
|
|
644
653
|
Args:
|
|
645
654
|
atoms:
|
|
@@ -649,10 +658,13 @@ def reduce_and_avg(
|
|
|
649
658
|
provided supercell
|
|
650
659
|
|
|
651
660
|
Returns:
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
661
|
+
The reduced unit cell
|
|
662
|
+
|
|
663
|
+
Raises:
|
|
664
|
+
PeriodExtensionException:
|
|
665
|
+
If two atoms that should be identical by translational symmetry
|
|
666
|
+
are further than 0.01*(smallest NN distance) apart when
|
|
667
|
+
reduced to the unit cell
|
|
656
668
|
"""
|
|
657
669
|
new_atoms = atoms.copy()
|
|
658
670
|
|
|
@@ -683,126 +695,267 @@ def reduce_and_avg(
|
|
|
683
695
|
# Start from end of the atoms
|
|
684
696
|
# because we will remove all atoms except the reference ones.
|
|
685
697
|
for i in reversed(range(number_atoms)):
|
|
698
|
+
reference_atom_index = i % original_number_atoms
|
|
686
699
|
if i >= original_number_atoms:
|
|
687
700
|
# Get the distance to the reference atom in the original unit cell with the
|
|
688
701
|
# minimum image convention.
|
|
689
702
|
distance = new_atoms.get_distance(
|
|
690
|
-
|
|
703
|
+
reference_atom_index, i, mic=True, vector=True
|
|
691
704
|
)
|
|
692
705
|
# Get the position that has the closest distance to
|
|
693
706
|
# the reference atom in the original unit cell.
|
|
694
|
-
position_i = positions[
|
|
707
|
+
position_i = positions[reference_atom_index] + distance
|
|
695
708
|
# Remove atom from atoms object.
|
|
696
709
|
new_atoms.pop()
|
|
697
710
|
else:
|
|
698
711
|
# Atom was part of the original unit cell.
|
|
699
712
|
position_i = positions[i]
|
|
700
713
|
# Average
|
|
701
|
-
avg_positions_in_prim_cell[
|
|
714
|
+
avg_positions_in_prim_cell[reference_atom_index] += position_i / M
|
|
702
715
|
positions_in_prim_cell[i] = position_i
|
|
703
716
|
|
|
704
717
|
new_atoms.set_positions(avg_positions_in_prim_cell)
|
|
705
718
|
|
|
706
|
-
#
|
|
707
|
-
|
|
708
|
-
for
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
719
|
+
# Check that all atoms are within tolerance of their translational images
|
|
720
|
+
cutoff = get_smallest_nn_dist(new_atoms) * 0.01
|
|
721
|
+
logger.info(f"Cutoff for period extension test is {cutoff}")
|
|
722
|
+
for i in range(original_number_atoms):
|
|
723
|
+
positions_of_all_images_of_atom_i = [
|
|
724
|
+
positions_in_prim_cell[j * original_number_atoms + i] for j in range(M)
|
|
725
|
+
]
|
|
726
|
+
_, r = get_distances(
|
|
727
|
+
positions_of_all_images_of_atom_i,
|
|
712
728
|
cell=new_atoms.get_cell(),
|
|
713
729
|
pbc=True,
|
|
714
730
|
)
|
|
715
|
-
#
|
|
716
|
-
|
|
717
|
-
|
|
731
|
+
# Checking full MxM matrix, could probably speed up by
|
|
732
|
+
# checking upper triangle only. Could also save memory
|
|
733
|
+
# by looping over individual distances instead of
|
|
734
|
+
# checking the max of a giant matrix
|
|
735
|
+
assert r.shape == (M, M)
|
|
736
|
+
if r.max() > cutoff:
|
|
737
|
+
raise PeriodExtensionException(
|
|
738
|
+
f"At least one image of atom {i} is outside of tolerance"
|
|
739
|
+
)
|
|
740
|
+
return new_atoms
|
|
718
741
|
|
|
719
|
-
|
|
742
|
+
|
|
743
|
+
def voigt_to_full_symb(voigt_input: sp.Array) -> sp.MutableDenseNDimArray:
|
|
744
|
+
"""
|
|
745
|
+
Convert a 3-dimensional symbolic Voigt matrix to a full tensor. Order is
|
|
746
|
+
automatically detected. For now, only works with tensors that don't have special
|
|
747
|
+
scaling for the Voigt matrix (e.g. this doesn't work with the
|
|
748
|
+
compliance tensor)
|
|
749
|
+
"""
|
|
750
|
+
order = sum(voigt_input.shape) // 3
|
|
751
|
+
this_voigt_map = Tensor.get_voigt_dict(order)
|
|
752
|
+
t = sp.MutableDenseNDimArray(np.zeros([3] * order))
|
|
753
|
+
for ind, v in this_voigt_map.items():
|
|
754
|
+
t[ind] = voigt_input[v]
|
|
755
|
+
return t
|
|
720
756
|
|
|
721
757
|
|
|
722
|
-
def
|
|
723
|
-
reduced_distances: npt.ArrayLike,
|
|
724
|
-
significance_level: float = 0.05,
|
|
725
|
-
plot_filename: Optional[str] = None,
|
|
726
|
-
number_bins: Optional[int] = None,
|
|
727
|
-
) -> None:
|
|
758
|
+
def full_to_voigt_symb(full: sp.Array) -> sp.MutableDenseNDimArray:
|
|
728
759
|
"""
|
|
729
|
-
|
|
760
|
+
Convert a 3-dimensional symbolic full tensor to a Voigt matrix. Order is
|
|
761
|
+
automatically detected. For now, only works with tensors that don't have special
|
|
762
|
+
scaling for the Voigt matrix (e.g. this doesn't work with the
|
|
763
|
+
compliance tensor). No error checking is done to see if the
|
|
764
|
+
full tensor has the required symmetries to be converted to Voigt.
|
|
765
|
+
"""
|
|
766
|
+
order = len(full.shape)
|
|
767
|
+
vshape = tuple([3] * (order % 2) + [6] * (order // 2))
|
|
768
|
+
v_matrix = sp.MutableDenseNDimArray(np.zeros(vshape))
|
|
769
|
+
this_voigt_map = Tensor.get_voigt_dict(order)
|
|
770
|
+
for ind, v in this_voigt_map.items():
|
|
771
|
+
v_matrix[v] = full[ind]
|
|
772
|
+
return v_matrix
|
|
730
773
|
|
|
731
|
-
|
|
732
|
-
|
|
774
|
+
|
|
775
|
+
def rotate_tensor_symb(t: sp.Array, r: sp.Array) -> sp.Array:
|
|
776
|
+
"""
|
|
777
|
+
Rotate a 3-dimensional symbolic Cartesian tensor by a rotation matrix.
|
|
733
778
|
|
|
734
779
|
Args:
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
780
|
+
t: The tensor to rotate
|
|
781
|
+
r:
|
|
782
|
+
The rotation matrix, or a precomputed tensor product of rotation matrices
|
|
783
|
+
with the correct rank
|
|
784
|
+
"""
|
|
785
|
+
order = len(t.shape)
|
|
786
|
+
if r.shape == (3, 3):
|
|
787
|
+
r_tenprod = [sp.Array(r)] * order
|
|
788
|
+
elif r.shape == tuple([3] * 2 * order):
|
|
789
|
+
r_tenprod = [sp.Array(r)]
|
|
790
|
+
else:
|
|
791
|
+
raise RuntimeError(
|
|
792
|
+
"r must be a 3x3 rotation matrix or a tensor product of n 3x3 rotation "
|
|
793
|
+
f"matrices, where n is the rank of t. Instead got shape f{r.shape}"
|
|
794
|
+
)
|
|
795
|
+
args = r_tenprod + [t]
|
|
796
|
+
fullproduct = ArrayTensorProduct(*args)
|
|
797
|
+
for i in range(order):
|
|
798
|
+
current_order = len(fullproduct.shape)
|
|
799
|
+
# Count back from end: one component of tensor,
|
|
800
|
+
# plus two components for each rotation matrix.
|
|
801
|
+
# Then, step forward by 2*i + 1 to land on the second
|
|
802
|
+
# component of the correct rotation matrix.
|
|
803
|
+
# but, step forward by i more, because we've knocked out
|
|
804
|
+
# that many components of the tensor already
|
|
805
|
+
# (the knocked out components of the rotation matrices
|
|
806
|
+
# are lower than the current component we are summing)
|
|
807
|
+
rotation_component = current_order - order * 3 + 3 * i + 1
|
|
808
|
+
tensor_component = current_order - order + i # Count back from end
|
|
809
|
+
fullproduct = ArrayContraction(
|
|
810
|
+
fullproduct, (rotation_component, tensor_component)
|
|
811
|
+
)
|
|
812
|
+
return fullproduct.as_explicit()
|
|
742
813
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
814
|
+
|
|
815
|
+
def fit_voigt_tensor_to_cell_and_space_group_symb(
|
|
816
|
+
symb_voigt_inp: sp.Array,
|
|
817
|
+
cell: npt.ArrayLike,
|
|
818
|
+
sgnum: Union[int, str],
|
|
819
|
+
):
|
|
746
820
|
"""
|
|
747
|
-
|
|
748
|
-
|
|
821
|
+
Given a Cartesian symbolic tensor in Voigt form, average it over all the operations
|
|
822
|
+
in the crystal's space group in order to remove violations of the material symmetry
|
|
823
|
+
due to numerical errors. Similar to
|
|
824
|
+
:meth:`pymatgen.core.tensors.Tensor.fit_to_structure`,
|
|
825
|
+
except the input in output are Voigt, and the symmetry operations are tabulated
|
|
826
|
+
instead of being detected on the fly from a structure.
|
|
749
827
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
"number_bins must be specified if plot_filename is specified"
|
|
754
|
-
)
|
|
755
|
-
if not plot_filename.endswith(".pdf"):
|
|
756
|
-
raise ValueError(f"{plot_filename} is not a PDF file")
|
|
757
|
-
with PdfPages(plot_filename) as pdf:
|
|
758
|
-
for i in range(reduced_distances.shape[0]):
|
|
759
|
-
fig, axs = plt.subplots(1, 3, figsize=(10.0, 4.0))
|
|
760
|
-
for j in range(reduced_distances.shape[2]):
|
|
761
|
-
axs[j].hist(reduced_distances[i, :, j], bins=number_bins)
|
|
762
|
-
axs[j].set_xlabel(f"$x_{j}$")
|
|
763
|
-
axs[0].set_ylabel("Counts")
|
|
764
|
-
fig.suptitle(f"Atom {i}")
|
|
765
|
-
pdf.savefig()
|
|
766
|
-
else:
|
|
767
|
-
if number_bins is not None:
|
|
768
|
-
raise ValueError(
|
|
769
|
-
"number_bins must not be specified if plot_filename is not specified"
|
|
770
|
-
)
|
|
828
|
+
The provided tensor and cell must be in the standard primitive
|
|
829
|
+
setting and orientation w.r.t. Cartesian coordinates as defined in
|
|
830
|
+
https://doi.org/10.1016/j.commatsci.2017.01.017
|
|
771
831
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
832
|
+
Args:
|
|
833
|
+
symb_voigt_inp:
|
|
834
|
+
Tensor in Voigt form as understood by
|
|
835
|
+
:meth:`pymatgen.core.tensors.Tensor.from_voigt`
|
|
836
|
+
cell:
|
|
837
|
+
The cell of the crystal, with each row being a cartesian vector
|
|
838
|
+
representing a lattice vector
|
|
839
|
+
sgnum:
|
|
840
|
+
Space group number
|
|
775
841
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
842
|
+
Returns:
|
|
843
|
+
Tensor symmetrized w.r.t. operations of the space group,
|
|
844
|
+
additionally the symmetrized error if `voigt_error`
|
|
845
|
+
is provided
|
|
846
|
+
"""
|
|
847
|
+
t = voigt_to_full_symb(symb_voigt_inp)
|
|
848
|
+
order = len(t.shape)
|
|
782
849
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
850
|
+
# Precompute the average Q (x) Q (x) Q (x) Q for each
|
|
851
|
+
# Q in G, where (x) is tensor product. Better
|
|
852
|
+
# to do this with numpy, sympy is SLOW
|
|
853
|
+
r_tensprod_ave = np.zeros([3] * 2 * order, dtype=float)
|
|
854
|
+
space_group_ops = get_primitive_genpos_ops(sgnum)
|
|
855
|
+
for op in space_group_ops:
|
|
856
|
+
frac_rot = op["W"]
|
|
857
|
+
cart_rot = fractional_to_cartesian_itc_rotation_from_ase_cell(frac_rot, cell)
|
|
858
|
+
r_tensprod = 1
|
|
859
|
+
for _ in range(order):
|
|
860
|
+
# tensordot with axes=0 is tensor product
|
|
861
|
+
r_tensprod = np.tensordot(r_tensprod, cart_rot, axes=0)
|
|
862
|
+
r_tensprod_ave += r_tensprod
|
|
863
|
+
r_tensprod_ave /= len(space_group_ops)
|
|
864
|
+
t_symmetrized = rotate_tensor_symb(t, r_tensprod_ave)
|
|
865
|
+
return full_to_voigt_symb(t_symmetrized)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def fit_voigt_tensor_and_error_to_cell_and_space_group(
|
|
869
|
+
voigt_input: npt.ArrayLike,
|
|
870
|
+
voigt_error: npt.ArrayLike,
|
|
871
|
+
cell: npt.ArrayLike,
|
|
872
|
+
sgnum: Union[int, str],
|
|
873
|
+
symmetric: bool = False,
|
|
874
|
+
) -> Tuple[npt.ArrayLike, npt.ArrayLike]:
|
|
875
|
+
"""
|
|
876
|
+
Given a Cartesian Tensor and its errors in Voigt form, average them over
|
|
877
|
+
all the operations in the
|
|
878
|
+
crystal's space group in order to remove violations of the material symmetry due to
|
|
879
|
+
numerical errors. Similar to :meth:`pymatgen.core.tensors.Tensor.fit_to_structure`,
|
|
880
|
+
except the input in output are Voigt, and the symmetry operations are tabulated
|
|
881
|
+
instead of being detected on the fly from a structure.
|
|
882
|
+
|
|
883
|
+
Only use this function if you need the errors. If you do not,
|
|
884
|
+
use
|
|
885
|
+
:func:`fit_voigt_tensor_to_cell_and_space_group`, which is significantly faster.
|
|
886
|
+
|
|
887
|
+
The provided tensor and cell must be in the standard primitive
|
|
888
|
+
setting and orientation w.r.t. Cartesian coordinates as defined in
|
|
889
|
+
https://doi.org/10.1016/j.commatsci.2017.01.017
|
|
890
|
+
|
|
891
|
+
Args:
|
|
892
|
+
voigt_input:
|
|
893
|
+
Tensor in Voigt form as understood by
|
|
894
|
+
:meth:`pymatgen.core.tensors.Tensor.from_voigt`
|
|
895
|
+
voigt_error:
|
|
896
|
+
The error corresponding to voigt_input
|
|
897
|
+
cell:
|
|
898
|
+
The cell of the crystal, with each row being a cartesian vector
|
|
899
|
+
representing a lattice vector
|
|
900
|
+
sgnum:
|
|
901
|
+
Space group number
|
|
902
|
+
symmetric:
|
|
903
|
+
Whether the provided matrix is symmetric. Currently
|
|
904
|
+
only supported for 6x6 Voigt matrices
|
|
905
|
+
|
|
906
|
+
Returns:
|
|
907
|
+
Tensor symmetrized w.r.t. operations of the space group,
|
|
908
|
+
and its symmetrized error
|
|
909
|
+
"""
|
|
910
|
+
# First, get the symmetrized tensor as a symbolic
|
|
911
|
+
voigt_shape = voigt_input.shape
|
|
912
|
+
symb_voigt_inp = sp.symarray("t", voigt_shape)
|
|
913
|
+
if symmetric:
|
|
914
|
+
if voigt_shape != (6, 6):
|
|
915
|
+
raise NotImplementedError(
|
|
916
|
+
"Symmetric input only supported for 6x6 Voigt matrices"
|
|
917
|
+
)
|
|
918
|
+
for i in range(5):
|
|
919
|
+
for j in range(i + 1, 6):
|
|
920
|
+
symb_voigt_inp[j, i] = symb_voigt_inp[i, j]
|
|
921
|
+
|
|
922
|
+
sym_voigt_out = fit_voigt_tensor_to_cell_and_space_group_symb(
|
|
923
|
+
symb_voigt_inp=symb_voigt_inp, cell=cell, sgnum=sgnum
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
# OK, got the symbolic voigt output. Set up machinery for
|
|
927
|
+
# substitution
|
|
928
|
+
voigt_ranges = [range(n) for n in voigt_shape]
|
|
929
|
+
# Convert to list so can be reused
|
|
930
|
+
voigt_ranges_product = list(product(*voigt_ranges))
|
|
931
|
+
|
|
932
|
+
# Substitute result. Symmetry not an issue, keys will get overwritten
|
|
933
|
+
sub_dict = {}
|
|
934
|
+
for symb, num in zip(symb_voigt_inp.flatten(), voigt_input.flatten()):
|
|
935
|
+
sub_dict[symb] = num
|
|
936
|
+
|
|
937
|
+
sub_dict_err = {}
|
|
938
|
+
for symb, num in zip(symb_voigt_inp.flatten(), voigt_error.flatten()):
|
|
939
|
+
sub_dict_err[symb] = num
|
|
940
|
+
|
|
941
|
+
voigt_out = np.zeros(voigt_shape, dtype=float)
|
|
942
|
+
voigt_err_out = np.zeros(voigt_shape, dtype=float)
|
|
943
|
+
for indices in voigt_ranges_product:
|
|
944
|
+
compon_expr = sym_voigt_out[indices]
|
|
945
|
+
voigt_out[indices] = compon_expr.subs(sub_dict)
|
|
946
|
+
# For the error, consider the current component (indicated by ``indices``)
|
|
947
|
+
# as a random variable that is a linear combination of all the components
|
|
948
|
+
# of voigt_inp. The variance of the
|
|
949
|
+
# current component will be the sum of a_i^2 var_i, where a_i is the
|
|
950
|
+
# coefficient of the ith component of voigt_inp
|
|
951
|
+
voigt_out_var_compon = 0
|
|
952
|
+
for symb in sub_dict_err:
|
|
953
|
+
inp_compon_coeff = float(compon_expr.coeff(symb))
|
|
954
|
+
inp_compon_var = sub_dict_err[symb] ** 2
|
|
955
|
+
voigt_out_var_compon += inp_compon_coeff**2 * inp_compon_var
|
|
956
|
+
voigt_err_out[indices] = voigt_out_var_compon**0.5
|
|
957
|
+
|
|
958
|
+
return voigt_out, voigt_err_out
|
|
806
959
|
|
|
807
960
|
|
|
808
961
|
def fit_voigt_tensor_to_cell_and_space_group(
|
|
@@ -815,6 +968,10 @@ def fit_voigt_tensor_to_cell_and_space_group(
|
|
|
815
968
|
except the input in output are Voigt, and the symmetry operations are tabulated
|
|
816
969
|
instead of being detected on the fly from a structure.
|
|
817
970
|
|
|
971
|
+
If you need to symmetrize the errors as well, use
|
|
972
|
+
:func:`fit_voigt_tensor_and_error_to_cell_and_space_group`, which properly
|
|
973
|
+
handles errors, but is much slower.
|
|
974
|
+
|
|
818
975
|
The provided tensor and cell must be in the standard primitive
|
|
819
976
|
setting and orientation w.r.t. Cartesian coordinates as defined in
|
|
820
977
|
https://doi.org/10.1016/j.commatsci.2017.01.017
|
|
@@ -849,6 +1006,23 @@ def fit_voigt_tensor_to_cell_and_space_group(
|
|
|
849
1006
|
return t_symmetrized.voigt
|
|
850
1007
|
|
|
851
1008
|
|
|
1009
|
+
def get_smallest_nn_dist(atoms: Atoms) -> float:
|
|
1010
|
+
"""
|
|
1011
|
+
Get the smallest NN distance in an Atoms object
|
|
1012
|
+
"""
|
|
1013
|
+
nl_len = 0
|
|
1014
|
+
cov_mult = 1
|
|
1015
|
+
while nl_len == 0:
|
|
1016
|
+
logger.info(
|
|
1017
|
+
"Attempting to find NN distance by searching "
|
|
1018
|
+
f"within covalent radii times {cov_mult}"
|
|
1019
|
+
)
|
|
1020
|
+
nl = neighbor_list("d", atoms, natural_cutoffs(atoms, mult=cov_mult))
|
|
1021
|
+
nl_len = nl.size
|
|
1022
|
+
cov_mult += 1
|
|
1023
|
+
return nl.min()
|
|
1024
|
+
|
|
1025
|
+
|
|
852
1026
|
class FixProvidedSymmetry(FixSymmetry):
|
|
853
1027
|
"""
|
|
854
1028
|
A modification of :obj:`~ase.constraints.FixSymmetry` that takes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
|
2
|
+
<!-- This file was generated by dvisvgm 2.13.1 -->
|
|
3
|
+
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='350pt' height='350pt' viewBox='66.550958 63.999777 350 350'>
|
|
4
|
+
<g id='page1'>
|
|
5
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
6
|
+
<path d='M 10.1795 10.1795L 76.3702 76.3702L 142.561 142.561' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='1.50562'/>
|
|
7
|
+
</g>
|
|
8
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
9
|
+
<path d='M 76.3702 10.1795L 142.561 10.1795L 142.561 76.3702' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='1.50562'/>
|
|
10
|
+
</g>
|
|
11
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
12
|
+
<path d='M 208.752 208.752L 274.942 274.942L 341.133 341.133' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='1.50562'/>
|
|
13
|
+
</g>
|
|
14
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
15
|
+
<path d='M 20.1081 10.1795C 20.1081 4.69612 15.663 0.250938 10.1795 0.250937C 4.69612 0.250937 0.250937 4.69612 0.250937 10.1795C 0.250937 15.663 4.69612 20.1081 10.1795 20.1081C 15.663 20.1081 20.1081 15.663 20.1081 10.1795Z' fill='#000000'/>
|
|
16
|
+
</g>
|
|
17
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
18
|
+
<path d='M 20.1081 10.1795C 20.1081 4.69612 15.663 0.250938 10.1795 0.250937C 4.69612 0.250937 0.250937 4.69612 0.250937 10.1795C 0.250937 15.663 4.69612 20.1081 10.1795 20.1081C 15.663 20.1081 20.1081 15.663 20.1081 10.1795Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
19
|
+
</g>
|
|
20
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
21
|
+
<path d='M 86.2988 10.1795C 86.2988 4.69612 81.8536 0.250938 76.3702 0.250937C 70.8868 0.250937 66.4416 4.69612 66.4416 10.1795C 66.4416 15.663 70.8868 20.1081 76.3702 20.1081C 81.8536 20.1081 86.2988 15.663 86.2988 10.1795Z' fill='#000000'/>
|
|
22
|
+
</g>
|
|
23
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
24
|
+
<path d='M 86.2988 10.1795C 86.2988 4.69612 81.8536 0.250938 76.3702 0.250937C 70.8868 0.250937 66.4416 4.69612 66.4416 10.1795C 66.4416 15.663 70.8868 20.1081 76.3702 20.1081C 81.8536 20.1081 86.2988 15.663 86.2988 10.1795Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
25
|
+
</g>
|
|
26
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
27
|
+
<path d='M 152.49 10.1795C 152.49 4.69612 148.044 0.250938 142.561 0.250937C 137.077 0.250937 132.632 4.69612 132.632 10.1795C 132.632 15.663 137.077 20.1081 142.561 20.1081C 148.044 20.1081 152.49 15.663 152.49 10.1795Z' fill='#000000'/>
|
|
28
|
+
</g>
|
|
29
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
30
|
+
<path d='M 152.49 10.1795C 152.49 4.69612 148.044 0.250938 142.561 0.250937C 137.077 0.250937 132.632 4.69612 132.632 10.1795C 132.632 15.663 137.077 20.1081 142.561 20.1081C 148.044 20.1081 152.49 15.663 152.49 10.1795Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
31
|
+
</g>
|
|
32
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
33
|
+
<circle cx='208.752' cy='10.1795' fill='#000000' r='2.50937'/>
|
|
34
|
+
</g>
|
|
35
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
36
|
+
<circle cx='274.942' cy='10.1795' fill='#000000' r='2.50937'/>
|
|
37
|
+
</g>
|
|
38
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
39
|
+
<circle cx='341.133' cy='10.1795' fill='#000000' r='2.50937'/>
|
|
40
|
+
</g>
|
|
41
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
42
|
+
<path d='M 86.2988 76.3702C 86.2988 70.8868 81.8536 66.4416 76.3702 66.4416C 70.8868 66.4416 66.4416 70.8868 66.4416 76.3702C 66.4416 81.8536 70.8868 86.2988 76.3702 86.2988C 81.8536 86.2988 86.2988 81.8536 86.2988 76.3702Z' fill='#000000'/>
|
|
43
|
+
</g>
|
|
44
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
45
|
+
<path d='M 86.2988 76.3702C 86.2988 70.8868 81.8536 66.4416 76.3702 66.4416C 70.8868 66.4416 66.4416 70.8868 66.4416 76.3702C 66.4416 81.8536 70.8868 86.2988 76.3702 86.2988C 81.8536 86.2988 86.2988 81.8536 86.2988 76.3702Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
46
|
+
</g>
|
|
47
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
48
|
+
<path d='M 152.49 76.3702C 152.49 70.8868 148.044 66.4416 142.561 66.4416C 137.077 66.4416 132.632 70.8868 132.632 76.3702C 132.632 81.8536 137.077 86.2988 142.561 86.2988C 148.044 86.2988 152.49 81.8536 152.49 76.3702Z' fill='#000000'/>
|
|
49
|
+
</g>
|
|
50
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
51
|
+
<path d='M 152.49 76.3702C 152.49 70.8868 148.044 66.4416 142.561 66.4416C 137.077 66.4416 132.632 70.8868 132.632 76.3702C 132.632 81.8536 137.077 86.2988 142.561 86.2988C 148.044 86.2988 152.49 81.8536 152.49 76.3702Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
52
|
+
</g>
|
|
53
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
54
|
+
<circle cx='208.752' cy='76.3702' fill='#000000' r='2.50937'/>
|
|
55
|
+
</g>
|
|
56
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
57
|
+
<circle cx='274.942' cy='76.3702' fill='#000000' r='2.50937'/>
|
|
58
|
+
</g>
|
|
59
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
60
|
+
<circle cx='341.133' cy='76.3702' fill='#000000' r='2.50937'/>
|
|
61
|
+
</g>
|
|
62
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
63
|
+
<path d='M 152.49 142.561C 152.49 137.077 148.044 132.632 142.561 132.632C 137.077 132.632 132.632 137.077 132.632 142.561C 132.632 148.044 137.077 152.49 142.561 152.49C 148.044 152.49 152.49 148.044 152.49 142.561Z' fill='#000000'/>
|
|
64
|
+
</g>
|
|
65
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
66
|
+
<path d='M 152.49 142.561C 152.49 137.077 148.044 132.632 142.561 132.632C 137.077 132.632 132.632 137.077 132.632 142.561C 132.632 148.044 137.077 152.49 142.561 152.49C 148.044 152.49 152.49 148.044 152.49 142.561Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
67
|
+
</g>
|
|
68
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
69
|
+
<circle cx='208.752' cy='142.561' fill='#000000' r='2.50937'/>
|
|
70
|
+
</g>
|
|
71
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
72
|
+
<circle cx='274.942' cy='142.561' fill='#000000' r='2.50937'/>
|
|
73
|
+
</g>
|
|
74
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
75
|
+
<circle cx='341.133' cy='142.561' fill='#000000' r='2.50937'/>
|
|
76
|
+
</g>
|
|
77
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
78
|
+
<path d='M 218.68 208.752C 218.68 203.268 214.235 198.823 208.752 198.823C 203.268 198.823 198.823 203.268 198.823 208.752C 198.823 214.235 203.268 218.68 208.752 218.68C 214.235 218.68 218.68 214.235 218.68 208.752Z' fill='#000000'/>
|
|
79
|
+
</g>
|
|
80
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
81
|
+
<path d='M 218.68 208.752C 218.68 203.268 214.235 198.823 208.752 198.823C 203.268 198.823 198.823 203.268 198.823 208.752C 198.823 214.235 203.268 218.68 208.752 218.68C 214.235 218.68 218.68 214.235 218.68 208.752Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
82
|
+
</g>
|
|
83
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
84
|
+
<circle cx='274.942' cy='208.752' fill='#000000' r='2.50937'/>
|
|
85
|
+
</g>
|
|
86
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
87
|
+
<circle cx='341.133' cy='208.752' fill='#000000' r='2.50937'/>
|
|
88
|
+
</g>
|
|
89
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
90
|
+
<path d='M 284.871 274.942C 284.871 269.459 280.426 265.014 274.942 265.014C 269.459 265.014 265.014 269.459 265.014 274.942C 265.014 280.426 269.459 284.871 274.942 284.871C 280.426 284.871 284.871 280.426 284.871 274.942Z' fill='#000000'/>
|
|
91
|
+
</g>
|
|
92
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
93
|
+
<path d='M 284.871 274.942C 284.871 269.459 280.426 265.014 274.942 265.014C 269.459 265.014 265.014 269.459 265.014 274.942C 265.014 280.426 269.459 284.871 274.942 284.871C 280.426 284.871 284.871 280.426 284.871 274.942Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
94
|
+
</g>
|
|
95
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
96
|
+
<circle cx='341.133' cy='274.942' fill='#000000' r='2.50937'/>
|
|
97
|
+
</g>
|
|
98
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
99
|
+
<path d='M 351.062 341.133C 351.062 335.65 346.616 331.204 341.133 331.204C 335.65 331.204 331.204 335.65 331.204 341.133C 331.204 346.616 335.65 351.062 341.133 351.062C 346.616 351.062 351.062 346.616 351.062 341.133Z' fill='#000000'/>
|
|
100
|
+
</g>
|
|
101
|
+
<g transform='translate(66.551 63.9998)scale(.996264)'>
|
|
102
|
+
<path d='M 351.062 341.133C 351.062 335.65 346.616 331.204 341.133 331.204C 335.65 331.204 331.204 335.65 331.204 341.133C 331.204 346.616 335.65 351.062 341.133 351.062C 346.616 351.062 351.062 346.616 351.062 341.133Z' fill='none' stroke='#000000' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10.0375' stroke-width='0.501875'/>
|
|
103
|
+
</g>
|
|
104
|
+
</g>
|
|
105
|
+
</svg>
|