kim-tools 0.3.5__py3-none-any.whl → 0.3.7__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 +66 -4
- kim_tools/symmetry_util/core.py +314 -0
- 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 +95 -18
- {kim_tools-0.3.5.dist-info → kim_tools-0.3.7.dist-info}/METADATA +1 -1
- {kim_tools-0.3.5.dist-info → kim_tools-0.3.7.dist-info}/RECORD +20 -9
- {kim_tools-0.3.5.dist-info → kim_tools-0.3.7.dist-info}/WHEEL +0 -0
- {kim_tools-0.3.5.dist-info → kim_tools-0.3.7.dist-info}/licenses/LICENSE.CDDL +0 -0
- {kim_tools-0.3.5.dist-info → kim_tools-0.3.7.dist-info}/top_level.txt +0 -0
kim_tools/__init__.py
CHANGED
kim_tools/aflow_util/core.py
CHANGED
@@ -392,6 +392,67 @@ def get_wyckoff_lists_from_prototype(prototype_label: str) -> List[str]:
|
|
392
392
|
return expanded_wyckoff_letters
|
393
393
|
|
394
394
|
|
395
|
+
def get_atom_indices_for_each_wyckoff_orb(prototype_label: str) -> List[Dict]:
|
396
|
+
"""
|
397
|
+
Get a list of dictionaries containing the atom indices of each Wyckoff
|
398
|
+
orbit.
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
The information is in this format -- ``[{"letter":"a", "indices":[0,1]}, ... ]``
|
402
|
+
"""
|
403
|
+
return_list = []
|
404
|
+
wyck_lists = get_wyckoff_lists_from_prototype(prototype_label)
|
405
|
+
sgnum = get_space_group_number_from_prototype(prototype_label)
|
406
|
+
range_start = 0
|
407
|
+
for letter in "".join(wyck_lists):
|
408
|
+
multiplicity = get_primitive_wyckoff_multiplicity(sgnum, letter)
|
409
|
+
range_end = range_start + multiplicity
|
410
|
+
return_list.append(
|
411
|
+
{"letter": letter, "indices": list(range(range_start, range_end))}
|
412
|
+
)
|
413
|
+
range_start = range_end
|
414
|
+
return return_list
|
415
|
+
|
416
|
+
|
417
|
+
def get_all_equivalent_labels(prototype_label: str) -> List[str]:
|
418
|
+
"""
|
419
|
+
Return all possible permutations of the Wyckoff letters in a prototype
|
420
|
+
label under the operations of the affine normalizer.
|
421
|
+
|
422
|
+
NOTE: For now this function will not completely enumerate the possibilities
|
423
|
+
for triclinic and monoclinic space groups
|
424
|
+
"""
|
425
|
+
sgnum = get_space_group_number_from_prototype(prototype_label)
|
426
|
+
prototype_label_split = prototype_label.split("_")
|
427
|
+
equivalent_labels = []
|
428
|
+
for wyck_pos_xform in get_wyck_pos_xform_under_normalizer(sgnum):
|
429
|
+
prototype_label_split_permuted = prototype_label_split[:3]
|
430
|
+
for wycksec in prototype_label_split[3:]:
|
431
|
+
# list of letters joined with their nums, e.g. ["a", "2i"]
|
432
|
+
wycksec_permuted_list = []
|
433
|
+
prev_lett_ind = -1
|
434
|
+
for i, num_or_lett in enumerate(wycksec):
|
435
|
+
if isalpha(num_or_lett):
|
436
|
+
if num_or_lett == "A":
|
437
|
+
# Wyckoff position A comes after z in sg 47
|
438
|
+
lett_index = ord("z") + 1 - ord("a")
|
439
|
+
else:
|
440
|
+
lett_index = ord(num_or_lett) - ord("a")
|
441
|
+
# The start position of the (optional) numbers +
|
442
|
+
# letter describing this position
|
443
|
+
this_pos_start_ind = prev_lett_ind + 1
|
444
|
+
permuted_lett_and_num = wycksec[this_pos_start_ind:i]
|
445
|
+
permuted_lett_and_num += wyck_pos_xform[lett_index]
|
446
|
+
wycksec_permuted_list.append(permuted_lett_and_num)
|
447
|
+
prev_lett_ind = i
|
448
|
+
wycksec_permuted_list_sorted = sorted(
|
449
|
+
wycksec_permuted_list, key=lambda x: x[-1]
|
450
|
+
)
|
451
|
+
prototype_label_split_permuted.append("".join(wycksec_permuted_list_sorted))
|
452
|
+
equivalent_labels.append("_".join(prototype_label_split_permuted))
|
453
|
+
return list(set(equivalent_labels))
|
454
|
+
|
455
|
+
|
395
456
|
def prototype_labels_are_equivalent(
|
396
457
|
prototype_label_1: str,
|
397
458
|
prototype_label_2: str,
|
@@ -841,9 +902,8 @@ class AFLOW:
|
|
841
902
|
"that the AFLOW executable was not found."
|
842
903
|
)
|
843
904
|
# I am fine with allowing prereleases
|
844
|
-
|
845
|
-
|
846
|
-
if aflow_ver_no_prerelease < Version.parse(REQUIRED_AFLOW):
|
905
|
+
aflow_ver = Version.parse(ver_str)
|
906
|
+
if aflow_ver.replace(prerelease=None) < Version.parse(REQUIRED_AFLOW):
|
847
907
|
raise self.AFLOWNotFoundException(
|
848
908
|
f"Your AFLOW version {ver_str} is less "
|
849
909
|
f"than the required {REQUIRED_AFLOW}"
|
@@ -1580,6 +1640,8 @@ class AFLOW:
|
|
1580
1640
|
self.get_library_prototype_label_and_shortname_from_atoms(atoms)
|
1581
1641
|
)
|
1582
1642
|
|
1643
|
+
# NOTE: Because of below, this only works if the provided prototype label is
|
1644
|
+
# correctly alphabetized. Change this?
|
1583
1645
|
if not prototype_labels_are_equivalent(
|
1584
1646
|
prototype_label, prototype_label_detected
|
1585
1647
|
):
|
@@ -1642,7 +1704,7 @@ class AFLOW:
|
|
1642
1704
|
)
|
1643
1705
|
|
1644
1706
|
position_set_list = get_equivalent_atom_sets_from_prototype_and_atom_map(
|
1645
|
-
atoms,
|
1707
|
+
atoms, prototype_label_detected, atom_map, sort_atoms=True
|
1646
1708
|
)
|
1647
1709
|
|
1648
1710
|
# get equation sets
|
kim_tools/symmetry_util/core.py
CHANGED
@@ -12,8 +12,10 @@ from typing import Dict, List, Optional, Tuple, Union
|
|
12
12
|
import matplotlib.pyplot as plt
|
13
13
|
import numpy as np
|
14
14
|
import numpy.typing as npt
|
15
|
+
import sympy as sp
|
15
16
|
from ase import Atoms
|
16
17
|
from ase.cell import Cell
|
18
|
+
from ase.constraints import FixSymmetry
|
17
19
|
from ase.geometry import get_distances, get_duplicate_atoms
|
18
20
|
from matplotlib.backends.backend_pdf import PdfPages
|
19
21
|
from pymatgen.core.operations import SymmOp
|
@@ -21,6 +23,7 @@ from pymatgen.core.tensors import Tensor
|
|
21
23
|
from scipy.stats import kstest
|
22
24
|
from sklearn.decomposition import PCA
|
23
25
|
from sympy import Matrix, cos, matrix2numpy, sin, sqrt, symbols
|
26
|
+
from sympy.tensor.array.expressions import ArrayContraction, ArrayTensorProduct
|
24
27
|
|
25
28
|
logger = logging.getLogger(__name__)
|
26
29
|
logging.basicConfig(filename="kim-tools.log", level=logging.INFO, force=True)
|
@@ -617,6 +620,20 @@ def get_primitive_genpos_ops(sgnum: Union[int, str]) -> List[Dict]:
|
|
617
620
|
return np.asarray(json.load(f)[str(sgnum)])
|
618
621
|
|
619
622
|
|
623
|
+
def transform_atoms(atoms: Atoms, op: Dict) -> Atoms:
|
624
|
+
"""
|
625
|
+
Transform atoms by an operation defined by a dictionary containing a matrix 'W' and
|
626
|
+
translation 'w' defined as fractional operations in the unit cell. 'W' should be
|
627
|
+
oriented to operate on column vectors
|
628
|
+
"""
|
629
|
+
frac_pos_columns = atoms.get_scaled_positions().T
|
630
|
+
frac_pos_cols_xform = op["W"] @ frac_pos_columns + np.reshape(op["w"], (3, 1))
|
631
|
+
atoms_transformed = atoms.copy()
|
632
|
+
atoms_transformed.set_scaled_positions(frac_pos_cols_xform.T)
|
633
|
+
atoms_transformed.wrap()
|
634
|
+
return atoms_transformed
|
635
|
+
|
636
|
+
|
620
637
|
def reduce_and_avg(
|
621
638
|
atoms: Atoms, repeat: Tuple[int, int, int]
|
622
639
|
) -> Tuple[Atoms, npt.ArrayLike]:
|
@@ -790,6 +807,224 @@ def kstest_reduced_distances(
|
|
790
807
|
)
|
791
808
|
|
792
809
|
|
810
|
+
def voigt_to_full_symb(voigt_input: sp.Array) -> sp.MutableDenseNDimArray:
|
811
|
+
"""
|
812
|
+
Convert a 3-dimensional symbolic Voigt matrix to a full tensor. Order is
|
813
|
+
automatically detected. For now, only works with tensors that don't have special
|
814
|
+
scaling for the Voigt matrix (e.g. this doesn't work with the
|
815
|
+
compliance tensor)
|
816
|
+
"""
|
817
|
+
order = sum(voigt_input.shape) // 3
|
818
|
+
this_voigt_map = Tensor.get_voigt_dict(order)
|
819
|
+
t = sp.MutableDenseNDimArray(np.zeros([3] * order))
|
820
|
+
for ind, v in this_voigt_map.items():
|
821
|
+
t[ind] = voigt_input[v]
|
822
|
+
return t
|
823
|
+
|
824
|
+
|
825
|
+
def full_to_voigt_symb(full: sp.Array) -> sp.MutableDenseNDimArray:
|
826
|
+
"""
|
827
|
+
Convert a 3-dimensional symbolic full tensor to a Voigt matrix. Order is
|
828
|
+
automatically detected. For now, only works with tensors that don't have special
|
829
|
+
scaling for the Voigt matrix (e.g. this doesn't work with the
|
830
|
+
compliance tensor). No error checking is done to see if the
|
831
|
+
full tensor has the required symmetries to be converted to Voigt.
|
832
|
+
"""
|
833
|
+
order = len(full.shape)
|
834
|
+
vshape = tuple([3] * (order % 2) + [6] * (order // 2))
|
835
|
+
v_matrix = sp.MutableDenseNDimArray(np.zeros(vshape))
|
836
|
+
this_voigt_map = Tensor.get_voigt_dict(order)
|
837
|
+
for ind, v in this_voigt_map.items():
|
838
|
+
v_matrix[v] = full[ind]
|
839
|
+
return v_matrix
|
840
|
+
|
841
|
+
|
842
|
+
def rotate_tensor_symb(t: sp.Array, r: sp.Array) -> sp.Array:
|
843
|
+
"""
|
844
|
+
Rotate a 3-dimensional symbolic Cartesian tensor by a rotation matrix.
|
845
|
+
|
846
|
+
Args:
|
847
|
+
t: The tensor to rotate
|
848
|
+
r:
|
849
|
+
The rotation matrix, or a precomputed tensor product of rotation matrices
|
850
|
+
with the correct rank
|
851
|
+
"""
|
852
|
+
order = len(t.shape)
|
853
|
+
if r.shape == (3, 3):
|
854
|
+
r_tenprod = [sp.Array(r)] * order
|
855
|
+
elif r.shape == tuple([3] * 2 * order):
|
856
|
+
r_tenprod = [sp.Array(r)]
|
857
|
+
else:
|
858
|
+
raise RuntimeError(
|
859
|
+
"r must be a 3x3 rotation matrix or a tensor product of n 3x3 rotation "
|
860
|
+
f"matrices, where n is the rank of t. Instead got shape f{r.shape}"
|
861
|
+
)
|
862
|
+
args = r_tenprod + [t]
|
863
|
+
fullproduct = ArrayTensorProduct(*args)
|
864
|
+
for i in range(order):
|
865
|
+
current_order = len(fullproduct.shape)
|
866
|
+
# Count back from end: one component of tensor,
|
867
|
+
# plus two components for each rotation matrix.
|
868
|
+
# Then, step forward by 2*i + 1 to land on the second
|
869
|
+
# component of the correct rotation matrix.
|
870
|
+
# but, step forward by i more, because we've knocked out
|
871
|
+
# that many components of the tensor already
|
872
|
+
# (the knocked out components of the rotation matrices
|
873
|
+
# are lower than the current component we are summing)
|
874
|
+
rotation_component = current_order - order * 3 + 3 * i + 1
|
875
|
+
tensor_component = current_order - order + i # Count back from end
|
876
|
+
fullproduct = ArrayContraction(
|
877
|
+
fullproduct, (rotation_component, tensor_component)
|
878
|
+
)
|
879
|
+
return fullproduct.as_explicit()
|
880
|
+
|
881
|
+
|
882
|
+
def fit_voigt_tensor_to_cell_and_space_group_symb(
|
883
|
+
symb_voigt_inp: sp.Array,
|
884
|
+
cell: npt.ArrayLike,
|
885
|
+
sgnum: Union[int, str],
|
886
|
+
):
|
887
|
+
"""
|
888
|
+
Given a Cartesian symbolic tensor in Voigt form, average it over all the operations
|
889
|
+
in the crystal's space group in order to remove violations of the material symmetry
|
890
|
+
due to numerical errors. Similar to
|
891
|
+
:meth:`pymatgen.core.tensors.Tensor.fit_to_structure`,
|
892
|
+
except the input in output are Voigt, and the symmetry operations are tabulated
|
893
|
+
instead of being detected on the fly from a structure.
|
894
|
+
|
895
|
+
The provided tensor and cell must be in the standard primitive
|
896
|
+
setting and orientation w.r.t. Cartesian coordinates as defined in
|
897
|
+
https://doi.org/10.1016/j.commatsci.2017.01.017
|
898
|
+
|
899
|
+
Args:
|
900
|
+
symb_voigt_inp:
|
901
|
+
Tensor in Voigt form as understood by
|
902
|
+
:meth:`pymatgen.core.tensors.Tensor.from_voigt`
|
903
|
+
cell:
|
904
|
+
The cell of the crystal, with each row being a cartesian vector
|
905
|
+
representing a lattice vector
|
906
|
+
sgnum:
|
907
|
+
Space group number
|
908
|
+
|
909
|
+
Returns:
|
910
|
+
Tensor symmetrized w.r.t. operations of the space group,
|
911
|
+
additionally the symmetrized error if `voigt_error`
|
912
|
+
is provided
|
913
|
+
"""
|
914
|
+
t = voigt_to_full_symb(symb_voigt_inp)
|
915
|
+
order = len(t.shape)
|
916
|
+
|
917
|
+
# Precompute the average Q (x) Q (x) Q (x) Q for each
|
918
|
+
# Q in G, where (x) is tensor product. Better
|
919
|
+
# to do this with numpy, sympy is SLOW
|
920
|
+
r_tensprod_ave = np.zeros([3] * 2 * order, dtype=float)
|
921
|
+
space_group_ops = get_primitive_genpos_ops(sgnum)
|
922
|
+
for op in space_group_ops:
|
923
|
+
frac_rot = op["W"]
|
924
|
+
cart_rot = fractional_to_cartesian_itc_rotation_from_ase_cell(frac_rot, cell)
|
925
|
+
r_tensprod = 1
|
926
|
+
for _ in range(order):
|
927
|
+
# tensordot with axes=0 is tensor product
|
928
|
+
r_tensprod = np.tensordot(r_tensprod, cart_rot, axes=0)
|
929
|
+
r_tensprod_ave += r_tensprod
|
930
|
+
r_tensprod_ave /= len(space_group_ops)
|
931
|
+
t_symmetrized = rotate_tensor_symb(t, r_tensprod_ave)
|
932
|
+
return full_to_voigt_symb(t_symmetrized)
|
933
|
+
|
934
|
+
|
935
|
+
def fit_voigt_tensor_and_error_to_cell_and_space_group(
|
936
|
+
voigt_input: npt.ArrayLike,
|
937
|
+
voigt_error: npt.ArrayLike,
|
938
|
+
cell: npt.ArrayLike,
|
939
|
+
sgnum: Union[int, str],
|
940
|
+
symmetric: bool = False,
|
941
|
+
) -> Tuple[npt.ArrayLike, npt.ArrayLike]:
|
942
|
+
"""
|
943
|
+
Given a Cartesian Tensor and its errors in Voigt form, average them over
|
944
|
+
all the operations in the
|
945
|
+
crystal's space group in order to remove violations of the material symmetry due to
|
946
|
+
numerical errors. Similar to :meth:`pymatgen.core.tensors.Tensor.fit_to_structure`,
|
947
|
+
except the input in output are Voigt, and the symmetry operations are tabulated
|
948
|
+
instead of being detected on the fly from a structure.
|
949
|
+
|
950
|
+
Only use this function if you need the errors. If you do not,
|
951
|
+
use
|
952
|
+
:func:`fit_voigt_tensor_to_cell_and_space_group`, which is significantly faster.
|
953
|
+
|
954
|
+
The provided tensor and cell must be in the standard primitive
|
955
|
+
setting and orientation w.r.t. Cartesian coordinates as defined in
|
956
|
+
https://doi.org/10.1016/j.commatsci.2017.01.017
|
957
|
+
|
958
|
+
Args:
|
959
|
+
voigt_input:
|
960
|
+
Tensor in Voigt form as understood by
|
961
|
+
:meth:`pymatgen.core.tensors.Tensor.from_voigt`
|
962
|
+
voigt_error:
|
963
|
+
The error corresponding to voigt_input
|
964
|
+
cell:
|
965
|
+
The cell of the crystal, with each row being a cartesian vector
|
966
|
+
representing a lattice vector
|
967
|
+
sgnum:
|
968
|
+
Space group number
|
969
|
+
symmetric:
|
970
|
+
Whether the provided matrix is symmetric. Currently
|
971
|
+
only supported for 6x6 Voigt matrices
|
972
|
+
|
973
|
+
Returns:
|
974
|
+
Tensor symmetrized w.r.t. operations of the space group,
|
975
|
+
and its symmetrized error
|
976
|
+
"""
|
977
|
+
# First, get the symmetrized tensor as a symbolic
|
978
|
+
voigt_shape = voigt_input.shape
|
979
|
+
symb_voigt_inp = sp.symarray("t", voigt_shape)
|
980
|
+
if symmetric:
|
981
|
+
if voigt_shape != (6, 6):
|
982
|
+
raise NotImplementedError(
|
983
|
+
"Symmetric input only supported for 6x6 Voigt matrices"
|
984
|
+
)
|
985
|
+
for i in range(5):
|
986
|
+
for j in range(i + 1, 6):
|
987
|
+
symb_voigt_inp[j, i] = symb_voigt_inp[i, j]
|
988
|
+
|
989
|
+
sym_voigt_out = fit_voigt_tensor_to_cell_and_space_group_symb(
|
990
|
+
symb_voigt_inp=symb_voigt_inp, cell=cell, sgnum=sgnum
|
991
|
+
)
|
992
|
+
|
993
|
+
# OK, got the symbolic voigt output. Set up machinery for
|
994
|
+
# substitution
|
995
|
+
voigt_ranges = [range(n) for n in voigt_shape]
|
996
|
+
# Convert to list so can be reused
|
997
|
+
voigt_ranges_product = list(product(*voigt_ranges))
|
998
|
+
|
999
|
+
# Substitute result. Symmetry not an issue, keys will get overwritten
|
1000
|
+
sub_dict = {}
|
1001
|
+
for symb, num in zip(symb_voigt_inp.flatten(), voigt_input.flatten()):
|
1002
|
+
sub_dict[symb] = num
|
1003
|
+
|
1004
|
+
sub_dict_err = {}
|
1005
|
+
for symb, num in zip(symb_voigt_inp.flatten(), voigt_error.flatten()):
|
1006
|
+
sub_dict_err[symb] = num
|
1007
|
+
|
1008
|
+
voigt_out = np.zeros(voigt_shape, dtype=float)
|
1009
|
+
voigt_err_out = np.zeros(voigt_shape, dtype=float)
|
1010
|
+
for indices in voigt_ranges_product:
|
1011
|
+
compon_expr = sym_voigt_out[indices]
|
1012
|
+
voigt_out[indices] = compon_expr.subs(sub_dict)
|
1013
|
+
# For the error, consider the current component (indicated by ``indices``)
|
1014
|
+
# as a random variable that is a linear combination of all the components
|
1015
|
+
# of voigt_inp. The variance of the
|
1016
|
+
# current component will be the sum of a_i^2 var_i, where a_i is the
|
1017
|
+
# coefficient of the ith component of voigt_inp
|
1018
|
+
voigt_out_var_compon = 0
|
1019
|
+
for symb in sub_dict_err:
|
1020
|
+
inp_compon_coeff = float(compon_expr.coeff(symb))
|
1021
|
+
inp_compon_var = sub_dict_err[symb] ** 2
|
1022
|
+
voigt_out_var_compon += inp_compon_coeff**2 * inp_compon_var
|
1023
|
+
voigt_err_out[indices] = voigt_out_var_compon**0.5
|
1024
|
+
|
1025
|
+
return voigt_out, voigt_err_out
|
1026
|
+
|
1027
|
+
|
793
1028
|
def fit_voigt_tensor_to_cell_and_space_group(
|
794
1029
|
voigt_input: npt.ArrayLike, cell: npt.ArrayLike, sgnum: Union[int, str]
|
795
1030
|
) -> npt.ArrayLike:
|
@@ -800,6 +1035,10 @@ def fit_voigt_tensor_to_cell_and_space_group(
|
|
800
1035
|
except the input in output are Voigt, and the symmetry operations are tabulated
|
801
1036
|
instead of being detected on the fly from a structure.
|
802
1037
|
|
1038
|
+
If you need to symmetrize the errors as well, use
|
1039
|
+
:func:`fit_voigt_tensor_and_error_to_cell_and_space_group`, which properly
|
1040
|
+
handles errors, but is much slower.
|
1041
|
+
|
803
1042
|
The provided tensor and cell must be in the standard primitive
|
804
1043
|
setting and orientation w.r.t. Cartesian coordinates as defined in
|
805
1044
|
https://doi.org/10.1016/j.commatsci.2017.01.017
|
@@ -832,3 +1071,78 @@ def fit_voigt_tensor_to_cell_and_space_group(
|
|
832
1071
|
t_symmetrized = sum(t_rotated_list) / len(t_rotated_list)
|
833
1072
|
|
834
1073
|
return t_symmetrized.voigt
|
1074
|
+
|
1075
|
+
|
1076
|
+
class FixProvidedSymmetry(FixSymmetry):
|
1077
|
+
"""
|
1078
|
+
A modification of :obj:`~ase.constraints.FixSymmetry` that takes
|
1079
|
+
a prescribed symmetry instead of analyzing the atoms object on the fly
|
1080
|
+
"""
|
1081
|
+
|
1082
|
+
def __init__(
|
1083
|
+
self,
|
1084
|
+
atoms: Atoms,
|
1085
|
+
symmetry: Union[str, int, List[Dict]],
|
1086
|
+
adjust_positions=True,
|
1087
|
+
adjust_cell=True,
|
1088
|
+
):
|
1089
|
+
"""
|
1090
|
+
Args:
|
1091
|
+
symmetry:
|
1092
|
+
Either the space group number, or a list of operations
|
1093
|
+
as dictionaries with keys "W": (fractional rotation matrix),
|
1094
|
+
"w": (fractional translation). The space group number input
|
1095
|
+
will not work correctly unless this contraint is applied to
|
1096
|
+
a primitive unit cell as defined in
|
1097
|
+
http://doi.org/10.1016/j.commatsci.2017.01.017
|
1098
|
+
"""
|
1099
|
+
self.atoms = atoms.copy()
|
1100
|
+
self.symmetry = symmetry
|
1101
|
+
|
1102
|
+
if isinstance(symmetry, str) or isinstance(symmetry, int):
|
1103
|
+
primitive_genpos_ops = get_primitive_genpos_ops(symmetry)
|
1104
|
+
else:
|
1105
|
+
try:
|
1106
|
+
for op in symmetry:
|
1107
|
+
assert np.asarray(op["W"]).shape == (3, 3)
|
1108
|
+
assert np.asarray(op["w"]).shape == (3,)
|
1109
|
+
primitive_genpos_ops = symmetry
|
1110
|
+
except Exception:
|
1111
|
+
raise RuntimeError("Incorrect input provided to FixProvidedSymmetry")
|
1112
|
+
|
1113
|
+
self.rotations = []
|
1114
|
+
self.translations = []
|
1115
|
+
for op in primitive_genpos_ops:
|
1116
|
+
self.rotations.append(np.asarray(op["W"]))
|
1117
|
+
self.translations.append(np.asarray(op["w"]))
|
1118
|
+
self.prep_symm_map()
|
1119
|
+
|
1120
|
+
self.do_adjust_positions = adjust_positions
|
1121
|
+
self.do_adjust_cell = adjust_cell
|
1122
|
+
|
1123
|
+
def prep_symm_map(self) -> None:
|
1124
|
+
"""
|
1125
|
+
Prepare self.symm_map using provided symmetries
|
1126
|
+
"""
|
1127
|
+
self.symm_map = []
|
1128
|
+
scaled_pos = self.atoms.get_scaled_positions()
|
1129
|
+
for rot, trans in zip(self.rotations, self.translations):
|
1130
|
+
this_op_map = [-1] * len(self.atoms)
|
1131
|
+
for i_at in range(len(self.atoms)):
|
1132
|
+
new_p = rot @ scaled_pos[i_at, :] + trans
|
1133
|
+
dp = scaled_pos - new_p
|
1134
|
+
dp -= np.round(dp)
|
1135
|
+
i_at_map = np.argmin(np.linalg.norm(dp, axis=1))
|
1136
|
+
this_op_map[i_at] = i_at_map
|
1137
|
+
self.symm_map.append(this_op_map)
|
1138
|
+
|
1139
|
+
def todict(self):
|
1140
|
+
return {
|
1141
|
+
"name": "FixProvidedSymmetry",
|
1142
|
+
"kwargs": {
|
1143
|
+
"atoms": self.atoms,
|
1144
|
+
"symmetry": self.symmetry,
|
1145
|
+
"adjust_positions": self.do_adjust_positions,
|
1146
|
+
"adjust_cell": self.do_adjust_cell,
|
1147
|
+
},
|
1148
|
+
}
|
@@ -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>
|