kim-tools 0.3.13__py3-none-any.whl → 0.4.0__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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.3.13"
1
+ __version__ = "0.4.0"
2
2
 
3
3
  from .aflow_util import *
4
4
  from .aflow_util import __all__ as aflow_all
@@ -17,7 +17,6 @@ import numpy as np
17
17
  import numpy.typing as npt
18
18
  from ase import Atoms
19
19
  from ase.cell import Cell
20
- from ase.neighborlist import natural_cutoffs, neighbor_list
21
20
  from semver import Version
22
21
  from sympy import Symbol, linear_eq_to_matrix, matrix2numpy, parse_expr
23
22
 
@@ -30,6 +29,7 @@ from ..symmetry_util import (
30
29
  cartesian_rotation_is_in_point_group,
31
30
  get_possible_primitive_shifts,
32
31
  get_primitive_wyckoff_multiplicity,
32
+ get_smallest_nn_dist,
33
33
  get_wyck_pos_xform_under_normalizer,
34
34
  space_group_numbers_are_enantiomorphic,
35
35
  )
@@ -1605,20 +1605,12 @@ class AFLOW:
1605
1605
  """
1606
1606
  # If max_resid not provided, determine it from neighborlist
1607
1607
  if max_resid is None:
1608
- nl_len = 0
1609
- cov_mult = 1
1610
- while nl_len == 0:
1611
- logger.info(
1612
- "Attempting to find NN distance by searching "
1613
- f"within covalent radii times {cov_mult}"
1614
- )
1615
- nl = neighbor_list("d", atoms, natural_cutoffs(atoms, mult=cov_mult))
1616
- nl_len = nl.size
1617
- cov_mult += 1
1618
1608
  # set the maximum error to 1% of NN distance to follow AFLOW convention
1619
1609
  # rescale by cube root of cell volume to get rough conversion from
1620
1610
  # cartesian to fractional
1621
- max_resid = nl.min() * 0.01 * atoms.get_volume() ** (-1 / 3)
1611
+ max_resid = (
1612
+ get_smallest_nn_dist(atoms) * 0.01 * atoms.get_volume() ** (-1 / 3)
1613
+ )
1622
1614
  logger.info(
1623
1615
  "Automatically set max fractional residual for solving position "
1624
1616
  f"equations to {max_resid}"
@@ -9,7 +9,6 @@ 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
15
14
  import sympy as sp
@@ -17,11 +16,9 @@ from ase import Atoms
17
16
  from ase.cell import Cell
18
17
  from ase.constraints import FixSymmetry
19
18
  from ase.geometry import get_distances, get_duplicate_atoms
20
- from matplotlib.backends.backend_pdf import PdfPages
19
+ from ase.neighborlist import natural_cutoffs, neighbor_list
21
20
  from pymatgen.core.operations import SymmOp
22
21
  from pymatgen.core.tensors import Tensor
23
- from scipy.stats import kstest
24
- from sklearn.decomposition import PCA
25
22
  from sympy import Matrix, cos, matrix2numpy, sin, sqrt, symbols
26
23
  from sympy.tensor.array.expressions import ArrayContraction, ArrayTensorProduct
27
24
 
@@ -50,6 +47,7 @@ __all__ = [
50
47
  "change_of_basis_atoms",
51
48
  "get_possible_primitive_shifts",
52
49
  "get_primitive_genpos_ops",
50
+ "get_smallest_nn_dist",
53
51
  ]
54
52
 
55
53
  C_CENTERED_ORTHORHOMBIC_GROUPS = (20, 21, 35, 36, 37, 63, 64, 65, 66, 67, 68)
@@ -516,7 +514,9 @@ def get_change_of_basis_matrix_to_conventional_cell_from_formal_bravais_lattice(
516
514
  return np.round(change_of_basis_matrix)
517
515
 
518
516
 
519
- def change_of_basis_atoms(atoms: Atoms, change_of_basis: npt.ArrayLike) -> Atoms:
517
+ def change_of_basis_atoms(
518
+ atoms: Atoms, change_of_basis: npt.ArrayLike, cutoff: Optional[float] = None
519
+ ) -> Atoms:
520
520
  """
521
521
  Perform an arbitrary basis change on an ``Atoms`` object, duplicating or cropping
522
522
  atoms as needed. A basic check is made that the determinant of ``change_of_basis``
@@ -524,7 +524,7 @@ def change_of_basis_atoms(atoms: Atoms, change_of_basis: npt.ArrayLike) -> Atoms
524
524
  that ``change_of_basis`` is appropriate for the particuar crystal described by
525
525
  ``atoms``, which is up to the user.
526
526
 
527
- TODO: Incorporate :func:`kstest_reduced_distances` into this function
527
+ TODO: Incorporate :func:`cutoff_test_reduced_distances` into this function
528
528
 
529
529
  Args:
530
530
  atoms:
@@ -543,6 +543,9 @@ def change_of_basis_atoms(atoms: Atoms, change_of_basis: npt.ArrayLike) -> Atoms
543
543
 
544
544
  Relationship between fractional coordinates in each basis:
545
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.
546
549
 
547
550
  Returns:
548
551
  The transformed ``Atoms`` object, containing the original number of
@@ -577,7 +580,9 @@ def change_of_basis_atoms(atoms: Atoms, change_of_basis: npt.ArrayLike) -> Atoms
577
580
  new_atoms = atoms.repeat(repeat)
578
581
  new_atoms.set_cell(new_cell)
579
582
  new_atoms.wrap()
580
- get_duplicate_atoms(new_atoms, delete=True)
583
+ if cutoff is None:
584
+ cutoff = get_smallest_nn_dist(atoms) * 0.01
585
+ get_duplicate_atoms(new_atoms, cutoff=cutoff, delete=True)
581
586
 
582
587
  volume_change = np.linalg.det(change_of_basis)
583
588
  if not np.isclose(len(atoms) * volume_change, len(new_atoms)):
@@ -634,14 +639,16 @@ def transform_atoms(atoms: Atoms, op: Dict) -> Atoms:
634
639
  return atoms_transformed
635
640
 
636
641
 
637
- def reduce_and_avg(
638
- atoms: Atoms, repeat: Tuple[int, int, int]
639
- ) -> Tuple[Atoms, npt.ArrayLike]:
642
+ def reduce_and_avg(atoms: Atoms, repeat: Tuple[int, int, int]) -> Atoms:
640
643
  """
641
644
  TODO: Upgrade :func:`change_of_basis_atoms` to provide the distances
642
645
  array, obviating this function
643
646
 
644
- 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)
645
652
 
646
653
  Args:
647
654
  atoms:
@@ -651,10 +658,13 @@ def reduce_and_avg(
651
658
  provided supercell
652
659
 
653
660
  Returns:
654
- * The reduced unit cell
655
- * An array of displacement vectors. First dimension: index of reference atom
656
- in reduced cell. Second dimension: index of atom in provided supercell.
657
- Third dimension: x, y, z
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
658
668
  """
659
669
  new_atoms = atoms.copy()
660
670
 
@@ -685,126 +695,49 @@ def reduce_and_avg(
685
695
  # Start from end of the atoms
686
696
  # because we will remove all atoms except the reference ones.
687
697
  for i in reversed(range(number_atoms)):
698
+ reference_atom_index = i % original_number_atoms
688
699
  if i >= original_number_atoms:
689
700
  # Get the distance to the reference atom in the original unit cell with the
690
701
  # minimum image convention.
691
702
  distance = new_atoms.get_distance(
692
- i % original_number_atoms, i, mic=True, vector=True
703
+ reference_atom_index, i, mic=True, vector=True
693
704
  )
694
705
  # Get the position that has the closest distance to
695
706
  # the reference atom in the original unit cell.
696
- position_i = positions[i % original_number_atoms] + distance
707
+ position_i = positions[reference_atom_index] + distance
697
708
  # Remove atom from atoms object.
698
709
  new_atoms.pop()
699
710
  else:
700
711
  # Atom was part of the original unit cell.
701
712
  position_i = positions[i]
702
713
  # Average
703
- avg_positions_in_prim_cell[i % original_number_atoms] += position_i / M
714
+ avg_positions_in_prim_cell[reference_atom_index] += position_i / M
704
715
  positions_in_prim_cell[i] = position_i
705
716
 
706
717
  new_atoms.set_positions(avg_positions_in_prim_cell)
707
718
 
708
- # Calculate the distances.
709
- distances = np.empty((original_number_atoms, M, 3))
710
- for i in range(number_atoms):
711
- dr, _ = get_distances(
712
- positions_in_prim_cell[i],
713
- avg_positions_in_prim_cell[i % original_number_atoms],
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,
714
728
  cell=new_atoms.get_cell(),
715
729
  pbc=True,
716
730
  )
717
- # dr is a distance matrix, here we only have one distance
718
- assert dr.shape == (1, 1, 3)
719
- distances[i % original_number_atoms, i // original_number_atoms] = dr[0][0]
720
-
721
- return new_atoms, distances
722
-
723
-
724
- def kstest_reduced_distances(
725
- reduced_distances: npt.ArrayLike,
726
- significance_level: float = 0.05,
727
- plot_filename: Optional[str] = None,
728
- number_bins: Optional[int] = None,
729
- ) -> None:
730
- """
731
- TODO: Incorporate this into :func:`change_of_basis_atoms`
732
-
733
- Function to test whether the reduced atom positions are normally distributed
734
- around their average.
735
-
736
- Args:
737
- reduced_distances:
738
- Distance array provided by :func:`reduce_and_avg`
739
- significance_level:
740
- Significance level for Kolmogorov-Smirnov
741
- plot_filename:
742
- number_bins:
743
- Number of bins for plot
744
-
745
- Raises:
746
- PeriodExtensionException:
747
- If a non-normal distribution is detected
748
- """
749
- assert len(reduced_distances.shape) == 3
750
- assert reduced_distances.shape[2] == 3
751
-
752
- if plot_filename is not None:
753
- if number_bins is None:
754
- raise ValueError(
755
- "number_bins must be specified if plot_filename is specified"
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"
756
739
  )
757
- if not plot_filename.endswith(".pdf"):
758
- raise ValueError(f"{plot_filename} is not a PDF file")
759
- with PdfPages(plot_filename) as pdf:
760
- for i in range(reduced_distances.shape[0]):
761
- fig, axs = plt.subplots(1, 3, figsize=(10.0, 4.0))
762
- for j in range(reduced_distances.shape[2]):
763
- axs[j].hist(reduced_distances[i, :, j], bins=number_bins)
764
- axs[j].set_xlabel(f"$x_{j}$")
765
- axs[0].set_ylabel("Counts")
766
- fig.suptitle(f"Atom {i}")
767
- pdf.savefig()
768
- else:
769
- if number_bins is not None:
770
- raise ValueError(
771
- "number_bins must not be specified if plot_filename is not specified"
772
- )
773
-
774
- p_values = np.empty((reduced_distances.shape[0], reduced_distances.shape[2]))
775
- for i in range(reduced_distances.shape[0]):
776
- atom_distances = reduced_distances[i]
777
-
778
- # Perform PCA on the xyz distribution.
779
- pca = PCA(n_components=atom_distances.shape[1])
780
- pca_components = pca.fit_transform(atom_distances)
781
- assert (
782
- pca_components.shape == atom_distances.shape == reduced_distances.shape[1:]
783
- )
784
-
785
- # Test each component with a KS test.
786
- for j in range(pca_components.shape[1]):
787
- component = pca_components[:, j]
788
- component_mean = np.mean(component)
789
- assert abs(component_mean) < 1.0e-7
790
- component_std = np.std(component)
791
- # Normalize component
792
- normalized_component = (component - component_mean) / component_std
793
- assert abs(np.mean(normalized_component)) < 1.0e-7
794
- assert abs(np.std(normalized_component) - 1.0) < 1.0e-7
795
- res = kstest(normalized_component, "norm")
796
- p_values[i, j] = res.pvalue
797
-
798
- if np.any(p_values <= significance_level):
799
- raise PeriodExtensionException(
800
- "Detected non-normal distribution of reduced atom positions around their "
801
- f"average (smallest p value {np.min(p_values)})."
802
- )
803
- else:
804
- print(
805
- "Detected normal distribution or reduced atom positions around their "
806
- f"average (smallest p value {np.min(p_values)})."
807
- )
740
+ return new_atoms
808
741
 
809
742
 
810
743
  def voigt_to_full_symb(voigt_input: sp.Array) -> sp.MutableDenseNDimArray:
@@ -1073,6 +1006,23 @@ def fit_voigt_tensor_to_cell_and_space_group(
1073
1006
  return t_symmetrized.voigt
1074
1007
 
1075
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
+
1076
1026
  class FixProvidedSymmetry(FixSymmetry):
1077
1027
  """
1078
1028
  A modification of :obj:`~ase.constraints.FixSymmetry` that takes
@@ -395,8 +395,15 @@ def _add_property_instance(
395
395
  property_name = path_str
396
396
  found_custom_property = True
397
397
  break
398
- except Exception:
399
- pass
398
+ except Exception as e:
399
+ msg = (
400
+ "MESSAGE: Trying to load a property from the .edn file at\n"
401
+ f"{path}\n"
402
+ "failed with the following exception:\n"
403
+ f"{repr(e)}"
404
+ )
405
+ logger.info(msg)
406
+ print(msg)
400
407
 
401
408
  if not found_custom_property:
402
409
  raise KIMTestDriverError(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kim-tools
3
- Version: 0.3.13
3
+ Version: 0.4.0
4
4
  Summary: Base classes and helper routines for writing KIM Tests
5
5
  Author-email: ilia Nikiforov <nikif002@umn.edu>, Ellad Tadmor <tadmor@umn.edu>, Claire Waters <bwaters@umn.edu>, "Daniel S. Karls" <karl0100umn@gmail.com>, Matt Bierbaum <matt.bierbaum@gmail.com>, Eric Fuemmeler <efuemmel@umn.edu>, Philipp Hoellmer <ph2484@nyu.edu>, Guanming Zhang <gz2241@nyu.edu>, Tom Egg <tje3676@nyu.edu>, Navaneeth Mohan <mohan227@umn.edu>
6
6
  Maintainer-email: ilia Nikiforov <nikif002@umn.edu>
@@ -1,7 +1,7 @@
1
- kim_tools/__init__.py,sha256=z9M4HWOpT0qCOUbGIKeUY-Oskrw_ZG6xeXli9iLTyzo,434
1
+ kim_tools/__init__.py,sha256=5zBMy-IWgNOROy8kqq0jxJ4bPsz-87y2FNW9LY9VjqA,433
2
2
  kim_tools/kimunits.py,sha256=jOxBv9gRVhxPE6ygAIUxOzCAfPI6tT6sBaF_FNl9m-M,5387
3
3
  kim_tools/aflow_util/__init__.py,sha256=lJnQ8fZCma80QVRQeKvY4MQ87oCWu-9KATV3dKJfpDc,80
4
- kim_tools/aflow_util/core.py,sha256=mMS2r9ayJJ6ApOFTAIkCZ72Dg3g1EnREbrqe2YEipMo,81273
4
+ kim_tools/aflow_util/core.py,sha256=OmU4-m2jl81jfs0ni4bSECpC8GYt7-hEEH7cfKmrdoM,80886
5
5
  kim_tools/aflow_util/aflow_prototype_encyclopedia/data/A108B24C11D24_cP334_222_h4i_i_bf_i-001/info.json,sha256=IsFiO9X2Ko7yoq2QkDurUVP7k1BE4WFgblu7oxl6iZs,2013
6
6
  kim_tools/aflow_util/aflow_prototype_encyclopedia/data/A10B11_tI84_139_dehim_eh2n-001/info.json,sha256=f1EdtouuSL2y9NNw40Rvz2J9ZZcsqQBcyEmlHj6XoW8,1186
7
7
  kim_tools/aflow_util/aflow_prototype_encyclopedia/data/A10B2C_hP39_171_5c_c_a-001/info.json,sha256=vD1xjZKWShL0E6XNsSlmIhilGcGNefl56oQDLQlHO1M,1596
@@ -2004,7 +2004,7 @@ kim_tools/aflow_util/aflow_prototype_encyclopedia/data/A_tP50_134_a2m2n-001/info
2004
2004
  kim_tools/ase/__init__.py,sha256=1i6ko5tNr0VZC3T7hoEzq4fnSU0DdxNpxXcSaWMcJWc,76
2005
2005
  kim_tools/ase/core.py,sha256=umJY0LV3_zrEZLOXAFoz6AFigxA69sAOBzAGoBTDzxA,34335
2006
2006
  kim_tools/symmetry_util/__init__.py,sha256=uu-ZSUDUTe2P81rkAS3tXverx31s_uZ3wL4SD_dn5aI,86
2007
- kim_tools/symmetry_util/core.py,sha256=eMGgVt9MPcaLi_8jVMJ5UyQHvYbiiAFQ4HdJotnp8dk,43277
2007
+ kim_tools/symmetry_util/core.py,sha256=DR2vOYvuFTRYLbvFHeM5Xvwey-jCbRR5zFe4RkMOVsA,41408
2008
2008
  kim_tools/symmetry_util/elasticity.py,sha256=VxJ8wUcsSOPyJ8id6OpKmVlRAbIUIWxtzYJtkvVJIVs,13328
2009
2009
  kim_tools/symmetry_util/data/elast_cubic.svg,sha256=UpN4XcoLoOwv8a7KE0WyINkSH5AU2DPVZ08eGQf-Cds,8953
2010
2010
  kim_tools/symmetry_util/data/elast_hexagonal.svg,sha256=wOkw5IO3fZy039tLHhvtwrkatVYs5XzigUormLKtRlw,8705
@@ -2023,11 +2023,11 @@ kim_tools/symmetry_util/data/wyck_pos_xform_under_normalizer.json,sha256=6g1YuYh
2023
2023
  kim_tools/symmetry_util/data/wyckoff_multiplicities.json,sha256=qG2RPBd_-ejDIfz-E4ZhkHyRpIboxRy7oiXkdDf5Eg8,32270
2024
2024
  kim_tools/symmetry_util/data/wyckoff_sets.json,sha256=f5ZpHKDHo6_JWki1b7KUGoYLlhU-44Qikw_-PtbLssw,9248
2025
2025
  kim_tools/test_driver/__init__.py,sha256=KOiceeZNqkfrgZ66CiRiUdniceDrCmmDXQkOw0wXaCQ,92
2026
- kim_tools/test_driver/core.py,sha256=vHmhSWix5t95SCmJEapdpZXfJS0ZlbG-8bcmV9O9v9M,101261
2026
+ kim_tools/test_driver/core.py,sha256=3UUru2HfDZMD4zeA6lxVnUPr5KjQQhFAbRS1XA-WngY,101610
2027
2027
  kim_tools/vc/__init__.py,sha256=zXjhxXCKVMLBMXXWYG3if7VOpBnsFrn_RjVpnohDm5c,74
2028
2028
  kim_tools/vc/core.py,sha256=BIjzEExnQAL2S90a_npptRm3ACqAo4fZBtvTDBMWMdw,13963
2029
- kim_tools-0.3.13.dist-info/licenses/LICENSE.CDDL,sha256=I2luEED_SHjuZ01B4rYG-AF_135amL24JpHvZ1Jhqe8,16373
2030
- kim_tools-0.3.13.dist-info/METADATA,sha256=7ldqSmsLFyzrEtIeV-wJacw1BEJoj55DgMOupJlycIs,2069
2031
- kim_tools-0.3.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
2032
- kim_tools-0.3.13.dist-info/top_level.txt,sha256=w_YCpJ5ERigj9te74ln7k64tqj1VumOzM_s9dsalIWY,10
2033
- kim_tools-0.3.13.dist-info/RECORD,,
2029
+ kim_tools-0.4.0.dist-info/licenses/LICENSE.CDDL,sha256=I2luEED_SHjuZ01B4rYG-AF_135amL24JpHvZ1Jhqe8,16373
2030
+ kim_tools-0.4.0.dist-info/METADATA,sha256=AS9mzIzat5Kgj3ly4h6R6chJrqQZ68J6wmNHU0I0e0A,2068
2031
+ kim_tools-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
2032
+ kim_tools-0.4.0.dist-info/top_level.txt,sha256=w_YCpJ5ERigj9te74ln7k64tqj1VumOzM_s9dsalIWY,10
2033
+ kim_tools-0.4.0.dist-info/RECORD,,