Example #1
0
    def thermal_expansion_coeff(self, structure, temperature, mode="debye"):
        """
        Gets thermal expansion coefficient from third-order constants.

        Args:
            temperature (float): Temperature in kelvin, if not specified
                will return non-cv-normalized value
            structure (Structure): Structure to be used in directional heat
                capacity determination, only necessary if temperature
                is specified
            mode (string): mode for finding average heat-capacity,
                current supported modes are 'debye' and 'dulong-petit'
        """
        soec = ElasticTensor(self[0])
        v0 = structure.volume * 1e-30 / structure.num_sites
        if mode == "debye":
            td = soec.debye_temperature(structure)
            t_ratio = temperature / td

            def integrand(x):
                return (x**4 * np.exp(x)) / (np.exp(x) - 1)**2

            cv = 9 * 8.314 * t_ratio**3 * quad(integrand, 0, t_ratio**-1)[0]
        elif mode == "dulong-petit":
            cv = 3 * 8.314
        else:
            raise ValueError("Mode must be debye or dulong-petit")
        tgt = self.get_tgt(temperature, structure)
        alpha = np.einsum("ijkl,ij", soec.compliance_tensor, tgt)
        alpha *= cv / (1e9 * v0 * 6.022e23)
        return SquareTensor(alpha)
Example #2
0
def generate_elastic_workflow(structure, tags=None):
    """
    Generates a standard production workflow.

    Notes:
        Uses a primitive structure transformed into
        the conventional basis (for equivalent deformations).

        Adds the "minimal" category to the minimal portion
        of the workflow necessary to generate the elastic tensor,
        and the "minimal_full_stencil" category to the portion that
        includes all of the strain stencil, but is symmetrically complete
    """
    if tags == None:
        tags = []
    # transform the structure
    ieee_rot = Tensor.get_ieee_rotation(structure)
    if not SquareTensor(ieee_rot).is_rotation(tol=0.005):
        raise ValueError(
            "Rotation matrix does not satisfy rotation conditions")
    symm_op = SymmOp.from_rotation_and_translation(ieee_rot)
    ieee_structure = structure.copy()
    ieee_structure.apply_operation(symm_op)

    # construct workflow
    wf = wf_elastic_constant(ieee_structure)

    # Set categories, starting with optimization
    opt_fws = get_fws_and_tasks(wf, fw_name_constraint="optimization")
    wf.fws[opt_fws[0][0]].spec['elastic_category'] = "minimal"

    # find minimal set of fireworks using symmetry reduction
    fws_by_strain = {
        Strain(fw.tasks[-1]['pass_dict']['strain']): n
        for n, fw in enumerate(wf.fws) if 'deformation' in fw.name
    }
    unique_tensors = symmetry_reduce(list(fws_by_strain.keys()),
                                     ieee_structure)
    for unique_tensor in unique_tensors:
        fw_index = get_tkd_value(fws_by_strain, unique_tensor)
        if np.isclose(unique_tensor, 0.005).any():
            wf.fws[fw_index].spec['elastic_category'] = "minimal"
        else:
            wf.fws[fw_index].spec['elastic_category'] = "minimal_full_stencil"

    # Add tags
    if tags:
        wf = add_tags(wf, tags)

    wf = add_modify_incar(wf)
    priority = 500 - structure.num_sites
    wf = add_priority(wf, priority)
    for fw in wf.fws:
        if fw.spec.get('elastic_category') == 'minimal':
            fw.spec['_priority'] += 2000
        elif fw.spec.get('elastic_category') == 'minimal_full_stencil':
            fw.spec['_priority'] += 1000
    return wf
Example #3
0
    def test_properties(self):
        # transpose
        self.assertArrayEqual(
            self.non_symm.trans,
            SquareTensor([[0.1, 0.4, 0.2], [0.2, 0.5, 0.5], [0.3, 0.6, 0.5]]),
        )
        self.assertArrayEqual(self.rand_sqtensor.trans,
                              np.transpose(self.rand_sqtensor))
        self.assertArrayEqual(self.symm_sqtensor, self.symm_sqtensor.trans)
        # inverse
        self.assertArrayEqual(self.non_symm.inv, np.linalg.inv(self.non_symm))
        with self.assertRaises(ValueError):
            self.non_invertible.inv

        # determinant
        self.assertEqual(self.rand_sqtensor.det,
                         np.linalg.det(self.rand_sqtensor))
        self.assertEqual(self.non_invertible.det, 0.0)
        self.assertEqual(self.non_symm.det, 0.009)

        # symmetrized
        self.assertArrayEqual(
            self.rand_sqtensor.symmetrized,
            0.5 * (self.rand_sqtensor + self.rand_sqtensor.trans),
        )
        self.assertArrayEqual(self.symm_sqtensor,
                              self.symm_sqtensor.symmetrized)
        self.assertArrayAlmostEqual(
            self.non_symm.symmetrized,
            SquareTensor([[0.1, 0.3, 0.25], [0.3, 0.5, 0.55],
                          [0.25, 0.55, 0.5]]),
        )

        # invariants
        i1 = np.trace(self.rand_sqtensor)
        i2 = (self.rand_sqtensor[0, 0] * self.rand_sqtensor[1, 1] +
              self.rand_sqtensor[1, 1] * self.rand_sqtensor[2, 2] +
              self.rand_sqtensor[2, 2] * self.rand_sqtensor[0, 0] -
              self.rand_sqtensor[0, 1] * self.rand_sqtensor[1, 0] -
              self.rand_sqtensor[0, 2] * self.rand_sqtensor[2, 0] -
              self.rand_sqtensor[2, 1] * self.rand_sqtensor[1, 2])
        i3 = np.linalg.det(self.rand_sqtensor)
        self.assertArrayAlmostEqual([i1, i2, i3],
                                    self.rand_sqtensor.principal_invariants)
Example #4
0
 def test_rotate(self):
     self.assertArrayEqual(
         self.vec.rotate([[0, -1, 0], [1, 0, 0], [0, 0, 1]]), [0, 1, 0])
     self.assertArrayAlmostEqual(
         self.non_symm.rotate(self.rotation),
         SquareTensor([[0.531, 0.485, 0.271], [0.700, 0.5, 0.172],
                       [0.171, 0.233, 0.068]]),
         decimal=3,
     )
     self.assertRaises(ValueError, self.non_symm.rotate, self.symm_rank2)
Example #5
0
    def piola_kirchoff_1(self, def_grad):
        """
        calculates the first Piola-Kirchoff stress

        Args:
            def_grad (3x3 array-like): deformation gradient tensor
        """
        if not self.is_symmetric:
            raise ValueError("The stress tensor is not symmetric, \
                             PK stress is based on a symmetric stress tensor.")
        def_grad = SquareTensor(def_grad)
        return def_grad.det * np.dot(self, def_grad.inv.trans)
Example #6
0
    def piola_kirchoff_2(self, def_grad):
        """
        calculates the second Piola-Kirchoff stress

        Args:
            def_grad (3x3 array-like): rate of deformation tensor
        """

        def_grad = SquareTensor(def_grad)
        if not self.is_symmetric:
            raise ValueError("The stress tensor is not symmetric, \
                             PK stress is based on a symmetric stress tensor.")
        return def_grad.det * np.dot(np.dot(def_grad.inv, self),
                                     def_grad.inv.trans)
Example #7
0
    def test_serialization(self):
        # Test base serialize-deserialize
        d = self.rand_sqtensor.as_dict()
        new = SquareTensor.from_dict(d)
        self.assertArrayAlmostEqual(new, self.rand_sqtensor)
        self.assertIsInstance(new, SquareTensor)

        # Ensure proper object-independent deserialization
        obj = MontyDecoder().process_decoded(d)
        self.assertIsInstance(obj, SquareTensor)

        with warnings.catch_warnings(record=True):
            vsym = self.rand_sqtensor.voigt_symmetrized
            d_vsym = vsym.as_dict(voigt=True)
            new_voigt = Tensor.from_dict(d_vsym)
            self.assertArrayAlmostEqual(vsym, new_voigt)
Example #8
0
def convert_strain_to_deformation(strain, shape="upper"):
    """
    This function converts a strain to a deformation gradient that will
    produce that strain.  Supports three methods:

    Args:
        strain (3x3 array-like): strain matrix
        shape: (string): method for determining deformation, supports
            "upper" produces an upper triangular defo
            "lower" produces a lower triangular defo
            "symmetric" produces a symmetric defo
    """
    strain = SquareTensor(strain)
    ftdotf = 2*strain + np.eye(3)
    if shape == "upper":
        result = scipy.linalg.cholesky(ftdotf)
    elif shape == "symmetric":
        result = scipy.linalg.sqrtm(ftdotf)
    else:
        raise ValueError("shape must be \"upper\" or \"symmetric\"")
    return Deformation(result)
Example #9
0
    def get_tgt(self, temperature=None, structure=None, quad=None):
        """
        Gets the thermodynamic Gruneisen tensor (TGT) by via an
        integration of the GGT weighted by the directional heat
        capacity.

        See refs:
            R. N. Thurston and K. Brugger, Phys. Rev. 113, A1604 (1964).
            K. Brugger Phys. Rev. 137, A1826 (1965).

        Args:
            temperature (float): Temperature in kelvin, if not specified
                will return non-cv-normalized value
            structure (float): Structure to be used in directional heat
                capacity determination, only necessary if temperature
                is specified
            quad (dict): quadrature for integration, should be
                dictionary with "points" and "weights" keys defaults
                to quadpy.sphere.Lebedev(19) as read from file
        """
        if temperature and not structure:
            raise ValueError("If using temperature input, you must also "
                             "include structure")

        quad = quad if quad else DEFAULT_QUAD
        points = quad["points"]
        weights = quad["weights"]
        num, denom, c = np.zeros((3, 3)), 0, 1
        for p, w in zip(points, weights):
            gk = ElasticTensor(self[0]).green_kristoffel(p)
            rho_wsquareds, us = np.linalg.eigh(gk)
            us = [u / np.linalg.norm(u) for u in np.transpose(us)]
            for u in us:
                # TODO: this should be benchmarked
                if temperature:
                    c = self.get_heat_capacity(temperature, structure, p, u)
                num += c * self.get_ggt(p, u) * w
                denom += c * w
        return SquareTensor(num / denom)
Example #10
0
 def test_get_scaled(self):
     self.assertArrayEqual(
         self.non_symm.get_scaled(10.0),
         SquareTensor([[1, 2, 3], [4, 5, 6], [2, 5, 5]]),
     )
Example #11
0
 def setUp(self):
     self.rand_sqtensor = SquareTensor(np.random.randn(3, 3))
     self.symm_sqtensor = SquareTensor([[0.1, 0.3, 0.4], [0.3, 0.5, 0.2],
                                        [0.4, 0.2, 0.6]])
     self.non_invertible = SquareTensor([[0.1, 0, 0], [0.2, 0, 0],
                                         [0, 0, 0]])
     self.non_symm = SquareTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6],
                                   [0.2, 0.5, 0.5]])
     self.low_val = SquareTensor([[1e-6, 1 + 1e-5, 1e-6],
                                  [1 + 1e-6, 1e-6, 1e-6],
                                  [1e-7, 1e-7, 1 + 1e-5]])
     self.low_val_2 = SquareTensor([[1e-6, -1 - 1e-6, 1e-6],
                                    [1 + 1e-7, 1e-6, 1e-6],
                                    [1e-7, 1e-7, 1 + 1e-6]])
     a = 3.14 * 42.5 / 180
     self.rotation = SquareTensor([[math.cos(a), 0,
                                    math.sin(a)], [0, 1, 0],
                                   [-math.sin(a), 0,
                                    math.cos(a)]])
Example #12
0
class SquareTensorTest(PymatgenTest):
    def setUp(self):
        self.rand_sqtensor = SquareTensor(np.random.randn(3, 3))
        self.symm_sqtensor = SquareTensor([[0.1, 0.3, 0.4], [0.3, 0.5, 0.2],
                                           [0.4, 0.2, 0.6]])
        self.non_invertible = SquareTensor([[0.1, 0, 0], [0.2, 0, 0],
                                            [0, 0, 0]])
        self.non_symm = SquareTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6],
                                      [0.2, 0.5, 0.5]])
        self.low_val = SquareTensor([[1e-6, 1 + 1e-5, 1e-6],
                                     [1 + 1e-6, 1e-6, 1e-6],
                                     [1e-7, 1e-7, 1 + 1e-5]])
        self.low_val_2 = SquareTensor([[1e-6, -1 - 1e-6, 1e-6],
                                       [1 + 1e-7, 1e-6, 1e-6],
                                       [1e-7, 1e-7, 1 + 1e-6]])
        a = 3.14 * 42.5 / 180
        self.rotation = SquareTensor([[math.cos(a), 0,
                                       math.sin(a)], [0, 1, 0],
                                      [-math.sin(a), 0,
                                       math.cos(a)]])

    def test_new(self):
        non_sq_matrix = [
            [0.1, 0.2, 0.1],
            [0.1, 0.2, 0.3],
            [0.1, 0.2, 0.3],
            [0.1, 0.1, 0.1],
        ]
        bad_matrix = [[0.1, 0.2], [0.2, 0.3, 0.4], [0.2, 0.3, 0.5]]
        too_high_rank = np.zeros((3, 3, 3))
        self.assertRaises(ValueError, SquareTensor, non_sq_matrix)
        self.assertRaises(ValueError, SquareTensor, bad_matrix)
        self.assertRaises(ValueError, SquareTensor, too_high_rank)

    def test_properties(self):
        # transpose
        self.assertArrayEqual(
            self.non_symm.trans,
            SquareTensor([[0.1, 0.4, 0.2], [0.2, 0.5, 0.5], [0.3, 0.6, 0.5]]),
        )
        self.assertArrayEqual(self.rand_sqtensor.trans,
                              np.transpose(self.rand_sqtensor))
        self.assertArrayEqual(self.symm_sqtensor, self.symm_sqtensor.trans)
        # inverse
        self.assertArrayEqual(self.non_symm.inv, np.linalg.inv(self.non_symm))
        with self.assertRaises(ValueError):
            self.non_invertible.inv

        # determinant
        self.assertEqual(self.rand_sqtensor.det,
                         np.linalg.det(self.rand_sqtensor))
        self.assertEqual(self.non_invertible.det, 0.0)
        self.assertEqual(self.non_symm.det, 0.009)

        # symmetrized
        self.assertArrayEqual(
            self.rand_sqtensor.symmetrized,
            0.5 * (self.rand_sqtensor + self.rand_sqtensor.trans),
        )
        self.assertArrayEqual(self.symm_sqtensor,
                              self.symm_sqtensor.symmetrized)
        self.assertArrayAlmostEqual(
            self.non_symm.symmetrized,
            SquareTensor([[0.1, 0.3, 0.25], [0.3, 0.5, 0.55],
                          [0.25, 0.55, 0.5]]),
        )

        # invariants
        i1 = np.trace(self.rand_sqtensor)
        i2 = (self.rand_sqtensor[0, 0] * self.rand_sqtensor[1, 1] +
              self.rand_sqtensor[1, 1] * self.rand_sqtensor[2, 2] +
              self.rand_sqtensor[2, 2] * self.rand_sqtensor[0, 0] -
              self.rand_sqtensor[0, 1] * self.rand_sqtensor[1, 0] -
              self.rand_sqtensor[0, 2] * self.rand_sqtensor[2, 0] -
              self.rand_sqtensor[2, 1] * self.rand_sqtensor[1, 2])
        i3 = np.linalg.det(self.rand_sqtensor)
        self.assertArrayAlmostEqual([i1, i2, i3],
                                    self.rand_sqtensor.principal_invariants)

    def test_is_rotation(self):
        self.assertTrue(self.rotation.is_rotation())
        self.assertFalse(self.symm_sqtensor.is_rotation())
        self.assertTrue(self.low_val_2.is_rotation())
        self.assertFalse(self.low_val_2.is_rotation(tol=1e-8))

    def test_refine_rotation(self):
        self.assertArrayAlmostEqual(self.rotation,
                                    self.rotation.refine_rotation())
        new = self.rotation.copy()
        new[2, 2] += 0.02
        self.assertFalse(new.is_rotation())
        self.assertArrayAlmostEqual(self.rotation, new.refine_rotation())
        new[1] *= 1.05
        self.assertArrayAlmostEqual(self.rotation, new.refine_rotation())

    def test_get_scaled(self):
        self.assertArrayEqual(
            self.non_symm.get_scaled(10.0),
            SquareTensor([[1, 2, 3], [4, 5, 6], [2, 5, 5]]),
        )

    def test_polar_decomposition(self):
        u, p = self.rand_sqtensor.polar_decomposition()
        self.assertArrayAlmostEqual(np.dot(u, p), self.rand_sqtensor)
        self.assertArrayAlmostEqual(np.eye(3),
                                    np.dot(u, np.conjugate(np.transpose(u))))

    def test_serialization(self):
        # Test base serialize-deserialize
        d = self.rand_sqtensor.as_dict()
        new = SquareTensor.from_dict(d)
        self.assertArrayAlmostEqual(new, self.rand_sqtensor)
        self.assertIsInstance(new, SquareTensor)

        # Ensure proper object-independent deserialization
        obj = MontyDecoder().process_decoded(d)
        self.assertIsInstance(obj, SquareTensor)

        with warnings.catch_warnings(record=True):
            vsym = self.rand_sqtensor.voigt_symmetrized
            d_vsym = vsym.as_dict(voigt=True)
            new_voigt = Tensor.from_dict(d_vsym)
            self.assertArrayAlmostEqual(vsym, new_voigt)
Example #13
0
    def test_list_based_functions(self):
        # zeroed
        tc = TensorCollection([1e-4 * Tensor(np.eye(3))] * 4)
        for t in tc.zeroed():
            self.assertArrayEqual(t, np.zeros((3, 3)))
        for t in tc.zeroed(1e-5):
            self.assertArrayEqual(t, 1e-4 * np.eye(3))
        self.list_based_function_check("zeroed", tc)
        self.list_based_function_check("zeroed", tc, tol=1e-5)

        # transform
        symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False,
                                                         [0, 0, 1])
        self.list_based_function_check("transform",
                                       self.seq_tc,
                                       symm_op=symm_op)

        # symmetrized
        self.list_based_function_check("symmetrized", self.seq_tc)

        # rotation
        a = 3.14 * 42.5 / 180
        rotation = SquareTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0],
                                 [-math.sin(a), 0,
                                  math.cos(a)]])
        self.list_based_function_check("rotate",
                                       self.diff_rank,
                                       matrix=rotation)

        # is_symmetric
        self.assertFalse(self.seq_tc.is_symmetric())
        self.assertTrue(self.diff_rank.is_symmetric())

        # fit_to_structure
        self.list_based_function_check("fit_to_structure", self.diff_rank,
                                       self.struct)
        self.list_based_function_check("fit_to_structure", self.seq_tc,
                                       self.struct)

        # fit_to_structure
        self.list_based_function_check("fit_to_structure", self.diff_rank,
                                       self.struct)
        self.list_based_function_check("fit_to_structure", self.seq_tc,
                                       self.struct)

        # voigt
        self.list_based_function_check("voigt", self.diff_rank)

        # is_voigt_symmetric
        self.assertTrue(self.diff_rank.is_voigt_symmetric())
        self.assertFalse(self.seq_tc.is_voigt_symmetric())

        # Convert to ieee
        for entry in self.ieee_data[:2]:
            entry["xtal"]
            tc = TensorCollection([entry["original_tensor"]] * 3)
            struct = entry["structure"]
            self.list_based_function_check("convert_to_ieee", tc, struct)

        # from_voigt
        tc_input = [t for t in np.random.random((3, 6, 6))]
        tc = TensorCollection.from_voigt(tc_input)
        for t_input, t in zip(tc_input, tc):
            self.assertArrayAlmostEqual(Tensor.from_voigt(t_input), t)
Example #14
0
    def setUp(self):
        self.vec = Tensor([1.0, 0.0, 0.0])
        self.rand_rank2 = Tensor(np.random.randn(3, 3))
        self.rand_rank3 = Tensor(np.random.randn(3, 3, 3))
        self.rand_rank4 = Tensor(np.random.randn(3, 3, 3, 3))
        a = 3.14 * 42.5 / 180
        self.non_symm = SquareTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6],
                                      [0.2, 0.5, 0.5]])
        self.rotation = SquareTensor([[math.cos(a), 0,
                                       math.sin(a)], [0, 1, 0],
                                      [-math.sin(a), 0,
                                       math.cos(a)]])
        self.low_val = Tensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6],
                               [1e-7, 1e-7, 1 + 1e-5]])
        self.symm_rank2 = Tensor([[1, 2, 3], [2, 4, 5], [3, 5, 6]])
        self.symm_rank3 = Tensor([
            [[1, 2, 3], [2, 4, 5], [3, 5, 6]],
            [[2, 4, 5], [4, 7, 8], [5, 8, 9]],
            [[3, 5, 6], [5, 8, 9], [6, 9, 10]],
        ])
        self.symm_rank4 = Tensor([
            [
                [[1.2, 0.4, -0.92], [0.4, 0.05, 0.11], [-0.92, 0.11, -0.02]],
                [[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, -0.0]],
                [[-0.92, 0.11, -0.02], [0.11, 0.09, 0.0], [-0.02, 0.0, -0.3]],
            ],
            [
                [[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, 0.0]],
                [[0.05, -0.47, 0.09], [-0.47, 0.17, 0.62], [0.09, 0.62, 0.3]],
                [[0.11, 0.09, 0.0], [0.09, 0.62, 0.3], [0.0, 0.3, -0.18]],
            ],
            [
                [[-0.92, 0.11, -0.02], [0.11, 0.09, 0.0], [-0.02, 0, -0.3]],
                [[0.11, 0.09, 0.0], [0.09, 0.62, 0.3], [0.0, 0.3, -0.18]],
                [[-0.02, 0.0, -0.3], [0.0, 0.3, -0.18], [-0.3, -0.18, -0.51]],
            ],
        ])

        # Structural symmetries tested using BaNiO3 piezo/elastic tensors
        self.fit_r3 = Tensor([
            [[0.0, 0.0, 0.03839], [0.0, 0.0, 0.0], [0.03839, 0.0, 0.0]],
            [[0.0, 0.0, 0.0], [0.0, 0.0, 0.03839], [0.0, 0.03839, 0.0]],
            [[6.89822, 0.0, 0.0], [0.0, 6.89822, 0.0], [0.0, 0.0, 27.4628]],
        ])
        self.fit_r4 = Tensor([
            [
                [[157.9, 0.0, 0.0], [0.0, 63.1, 0.0], [0.0, 0.0, 29.4]],
                [[0.0, 47.4, 0.0], [47.4, 0.0, 0.0], [0.0, 0.0, 0.0]],
                [[0.0, 0.0, 4.3], [0.0, 0.0, 0.0], [4.3, 0.0, 0.0]],
            ],
            [
                [[0.0, 47.4, 0.0], [47.4, 0.0, 0.0], [0.0, 0.0, 0.0]],
                [[63.1, 0.0, 0.0], [0.0, 157.9, 0.0], [0.0, 0.0, 29.4]],
                [[0.0, 0.0, 0.0], [0.0, 0.0, 4.3], [0.0, 4.3, 0.0]],
            ],
            [
                [[0.0, 0.0, 4.3], [0.0, 0.0, 0.0], [4.3, 0.0, 0.0]],
                [[0.0, 0.0, 0.0], [0.0, 0.0, 4.3], [0.0, 4.3, 0.0]],
                [[29.4, 0.0, 0.0], [0.0, 29.4, 0.0], [0.0, 0.0, 207.6]],
            ],
        ])

        self.unfit4 = Tensor([
            [
                [[161.26, 0.0, 0.0], [0.0, 62.76, 0.0], [0.0, 0.0, 30.18]],
                [[0.0, 47.08, 0.0], [47.08, 0.0, 0.0], [0.0, 0.0, 0.0]],
                [[0.0, 0.0, 4.23], [0.0, 0.0, 0.0], [4.23, 0.0, 0.0]],
            ],
            [
                [[0.0, 47.08, 0.0], [47.08, 0.0, 0.0], [0.0, 0.0, 0.0]],
                [[62.76, 0.0, 0.0], [0.0, 155.28, -0.06], [0.0, -0.06, 28.53]],
                [[0.0, 0.0, 0.0], [0.0, -0.06, 4.44], [0.0, 4.44, 0.0]],
            ],
            [
                [[0.0, 0.0, 4.23], [0.0, 0.0, 0.0], [4.23, 0.0, 0.0]],
                [[0.0, 0.0, 0.0], [0.0, -0.06, 4.44], [0.0, 4.44, 0.0]],
                [[30.18, 0.0, 0.0], [0.0, 28.53, 0.0], [0.0, 0.0, 207.57]],
            ],
        ])

        self.structure = self.get_structure("BaNiO3")
        ieee_file_path = os.path.join(PymatgenTest.TEST_FILES_DIR,
                                      "ieee_conversion_data.json")
        self.ones = Tensor(np.ones((3, 3)))
        self.ieee_data = loadfn(ieee_file_path)
Example #15
0
class TensorTest(PymatgenTest):
    _multiprocess_shared_ = True

    def setUp(self):
        self.vec = Tensor([1.0, 0.0, 0.0])
        self.rand_rank2 = Tensor(np.random.randn(3, 3))
        self.rand_rank3 = Tensor(np.random.randn(3, 3, 3))
        self.rand_rank4 = Tensor(np.random.randn(3, 3, 3, 3))
        a = 3.14 * 42.5 / 180
        self.non_symm = SquareTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6],
                                      [0.2, 0.5, 0.5]])
        self.rotation = SquareTensor([[math.cos(a), 0,
                                       math.sin(a)], [0, 1, 0],
                                      [-math.sin(a), 0,
                                       math.cos(a)]])
        self.low_val = Tensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6],
                               [1e-7, 1e-7, 1 + 1e-5]])
        self.symm_rank2 = Tensor([[1, 2, 3], [2, 4, 5], [3, 5, 6]])
        self.symm_rank3 = Tensor([
            [[1, 2, 3], [2, 4, 5], [3, 5, 6]],
            [[2, 4, 5], [4, 7, 8], [5, 8, 9]],
            [[3, 5, 6], [5, 8, 9], [6, 9, 10]],
        ])
        self.symm_rank4 = Tensor([
            [
                [[1.2, 0.4, -0.92], [0.4, 0.05, 0.11], [-0.92, 0.11, -0.02]],
                [[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, -0.0]],
                [[-0.92, 0.11, -0.02], [0.11, 0.09, 0.0], [-0.02, 0.0, -0.3]],
            ],
            [
                [[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, 0.0]],
                [[0.05, -0.47, 0.09], [-0.47, 0.17, 0.62], [0.09, 0.62, 0.3]],
                [[0.11, 0.09, 0.0], [0.09, 0.62, 0.3], [0.0, 0.3, -0.18]],
            ],
            [
                [[-0.92, 0.11, -0.02], [0.11, 0.09, 0.0], [-0.02, 0, -0.3]],
                [[0.11, 0.09, 0.0], [0.09, 0.62, 0.3], [0.0, 0.3, -0.18]],
                [[-0.02, 0.0, -0.3], [0.0, 0.3, -0.18], [-0.3, -0.18, -0.51]],
            ],
        ])

        # Structural symmetries tested using BaNiO3 piezo/elastic tensors
        self.fit_r3 = Tensor([
            [[0.0, 0.0, 0.03839], [0.0, 0.0, 0.0], [0.03839, 0.0, 0.0]],
            [[0.0, 0.0, 0.0], [0.0, 0.0, 0.03839], [0.0, 0.03839, 0.0]],
            [[6.89822, 0.0, 0.0], [0.0, 6.89822, 0.0], [0.0, 0.0, 27.4628]],
        ])
        self.fit_r4 = Tensor([
            [
                [[157.9, 0.0, 0.0], [0.0, 63.1, 0.0], [0.0, 0.0, 29.4]],
                [[0.0, 47.4, 0.0], [47.4, 0.0, 0.0], [0.0, 0.0, 0.0]],
                [[0.0, 0.0, 4.3], [0.0, 0.0, 0.0], [4.3, 0.0, 0.0]],
            ],
            [
                [[0.0, 47.4, 0.0], [47.4, 0.0, 0.0], [0.0, 0.0, 0.0]],
                [[63.1, 0.0, 0.0], [0.0, 157.9, 0.0], [0.0, 0.0, 29.4]],
                [[0.0, 0.0, 0.0], [0.0, 0.0, 4.3], [0.0, 4.3, 0.0]],
            ],
            [
                [[0.0, 0.0, 4.3], [0.0, 0.0, 0.0], [4.3, 0.0, 0.0]],
                [[0.0, 0.0, 0.0], [0.0, 0.0, 4.3], [0.0, 4.3, 0.0]],
                [[29.4, 0.0, 0.0], [0.0, 29.4, 0.0], [0.0, 0.0, 207.6]],
            ],
        ])

        self.unfit4 = Tensor([
            [
                [[161.26, 0.0, 0.0], [0.0, 62.76, 0.0], [0.0, 0.0, 30.18]],
                [[0.0, 47.08, 0.0], [47.08, 0.0, 0.0], [0.0, 0.0, 0.0]],
                [[0.0, 0.0, 4.23], [0.0, 0.0, 0.0], [4.23, 0.0, 0.0]],
            ],
            [
                [[0.0, 47.08, 0.0], [47.08, 0.0, 0.0], [0.0, 0.0, 0.0]],
                [[62.76, 0.0, 0.0], [0.0, 155.28, -0.06], [0.0, -0.06, 28.53]],
                [[0.0, 0.0, 0.0], [0.0, -0.06, 4.44], [0.0, 4.44, 0.0]],
            ],
            [
                [[0.0, 0.0, 4.23], [0.0, 0.0, 0.0], [4.23, 0.0, 0.0]],
                [[0.0, 0.0, 0.0], [0.0, -0.06, 4.44], [0.0, 4.44, 0.0]],
                [[30.18, 0.0, 0.0], [0.0, 28.53, 0.0], [0.0, 0.0, 207.57]],
            ],
        ])

        self.structure = self.get_structure("BaNiO3")
        ieee_file_path = os.path.join(PymatgenTest.TEST_FILES_DIR,
                                      "ieee_conversion_data.json")
        self.ones = Tensor(np.ones((3, 3)))
        self.ieee_data = loadfn(ieee_file_path)

    def test_new(self):
        bad_2 = np.zeros((4, 4))
        bad_3 = np.zeros((4, 4, 4))
        self.assertRaises(ValueError, Tensor, bad_2)
        self.assertRaises(ValueError, Tensor, bad_3)
        self.assertEqual(self.rand_rank2.rank, 2)
        self.assertEqual(self.rand_rank3.rank, 3)
        self.assertEqual(self.rand_rank4.rank, 4)

    def test_zeroed(self):
        self.assertArrayEqual(
            self.low_val.zeroed(),
            Tensor([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]]),
        )
        self.assertArrayEqual(
            self.low_val.zeroed(tol=1e-6),
            Tensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6],
                    [0, 0, 1 + 1e-5]]),
        )
        self.assertArrayEqual(
            Tensor([[1e-6, -30, 1], [1e-7, 1, 0], [1e-8, 0, 1]]).zeroed(),
            Tensor([[0, -30, 1], [0, 1, 0], [0, 0, 1]]),
        )

    def test_transform(self):
        # Rank 3
        tensor = Tensor(np.arange(0, 27).reshape(3, 3, 3))
        symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False,
                                                         [0, 0, 1])
        new_tensor = tensor.transform(symm_op)

        self.assertArrayAlmostEqual(
            new_tensor,
            [
                [
                    [-0.871, -2.884, -1.928],
                    [-2.152, -6.665, -4.196],
                    [-1.026, -2.830, -1.572],
                ],
                [
                    [0.044, 1.531, 1.804],
                    [4.263, 21.008, 17.928],
                    [5.170, 23.026, 18.722],
                ],
                [
                    [1.679, 7.268, 5.821],
                    [9.268, 38.321, 29.919],
                    [8.285, 33.651, 26.000],
                ],
            ],
            3,
        )

    def test_rotate(self):
        self.assertArrayEqual(
            self.vec.rotate([[0, -1, 0], [1, 0, 0], [0, 0, 1]]), [0, 1, 0])
        self.assertArrayAlmostEqual(
            self.non_symm.rotate(self.rotation),
            SquareTensor([[0.531, 0.485, 0.271], [0.700, 0.5, 0.172],
                          [0.171, 0.233, 0.068]]),
            decimal=3,
        )
        self.assertRaises(ValueError, self.non_symm.rotate, self.symm_rank2)

    def test_einsum_sequence(self):
        x = [1, 0, 0]
        test = Tensor(np.arange(0, 3**4).reshape((3, 3, 3, 3)))
        self.assertArrayAlmostEqual([0, 27, 54], test.einsum_sequence([x] * 3))
        self.assertEqual(360, test.einsum_sequence([np.eye(3)] * 2))
        self.assertRaises(ValueError, test.einsum_sequence,
                          Tensor(np.zeros(3)))

    def test_symmetrized(self):
        self.assertTrue(self.rand_rank2.symmetrized.is_symmetric())
        self.assertTrue(self.rand_rank3.symmetrized.is_symmetric())
        self.assertTrue(self.rand_rank4.symmetrized.is_symmetric())

    def test_is_symmetric(self):
        self.assertTrue(self.symm_rank2.is_symmetric())
        self.assertTrue(self.symm_rank3.is_symmetric())
        self.assertTrue(self.symm_rank4.is_symmetric())
        tol_test = self.symm_rank4
        tol_test[0, 1, 2, 2] += 1e-6
        self.assertFalse(self.low_val.is_symmetric(tol=1e-8))

    def test_fit_to_structure(self):
        new_fit = self.unfit4.fit_to_structure(self.structure)
        self.assertArrayAlmostEqual(new_fit, self.fit_r4, 1)

    def test_is_fit_to_structure(self):
        self.assertFalse(self.unfit4.is_fit_to_structure(self.structure))
        self.assertTrue(self.fit_r3.is_fit_to_structure(self.structure))
        self.assertTrue(self.fit_r4.is_fit_to_structure(self.structure))

    def test_convert_to_ieee(self):
        for entry in self.ieee_data:
            xtal = entry["xtal"]
            struct = entry["structure"]
            orig = Tensor(entry["original_tensor"])
            ieee = Tensor(entry["ieee_tensor"])
            diff = np.max(abs(ieee - orig.convert_to_ieee(struct)))
            err_msg = f"{xtal} IEEE conversion failed with max diff {diff}. Numpy version: {np.__version__}"
            converted = orig.convert_to_ieee(struct, refine_rotation=False)
            self.assertArrayAlmostEqual(ieee,
                                        converted,
                                        err_msg=err_msg,
                                        decimal=3)
            converted_refined = orig.convert_to_ieee(struct,
                                                     refine_rotation=True)
            err_msg = "{} IEEE conversion with refinement failed with max diff {}. Numpy version: {}".format(
                xtal, diff, np.__version__)
            self.assertArrayAlmostEqual(ieee,
                                        converted_refined,
                                        err_msg=err_msg,
                                        decimal=2)

    def test_structure_transform(self):
        # Test trivial case
        trivial = self.fit_r4.structure_transform(self.structure,
                                                  self.structure.copy())
        self.assertArrayAlmostEqual(trivial, self.fit_r4)

        # Test simple rotation
        rot_symm_op = SymmOp.from_axis_angle_and_translation([1, 1, 1], 55.5)
        rot_struct = self.structure.copy()
        rot_struct.apply_operation(rot_symm_op)
        rot_tensor = self.fit_r4.rotate(rot_symm_op.rotation_matrix)
        trans_tensor = self.fit_r4.structure_transform(self.structure,
                                                       rot_struct)
        self.assertArrayAlmostEqual(rot_tensor, trans_tensor)

        # Test supercell
        bigcell = self.structure.copy()
        bigcell.make_supercell([2, 2, 3])
        trans_tensor = self.fit_r4.structure_transform(self.structure, bigcell)
        self.assertArrayAlmostEqual(self.fit_r4, trans_tensor)

        # Test rotated primitive to conventional for fcc structure
        sn = self.get_structure("Sn")
        sn_prim = SpacegroupAnalyzer(sn).get_primitive_standard_structure()
        sn_prim.apply_operation(rot_symm_op)
        rotated = self.fit_r4.rotate(rot_symm_op.rotation_matrix)
        transformed = self.fit_r4.structure_transform(sn, sn_prim)
        self.assertArrayAlmostEqual(rotated, transformed)

    def test_from_voigt(self):
        with self.assertRaises(ValueError):
            Tensor.from_voigt([
                [59.33, 28.08, 28.08, 0],
                [28.08, 59.31, 28.07, 0],
                [28.08, 28.07, 59.32, 0, 0],
                [0, 0, 0, 26.35, 0],
                [0, 0, 0, 0, 26.35],
            ])
        # Rank 4
        Tensor.from_voigt([
            [59.33, 28.08, 28.08, 0, 0, 0],
            [28.08, 59.31, 28.07, 0, 0, 0],
            [28.08, 28.07, 59.32, 0, 0, 0],
            [0, 0, 0, 26.35, 0, 0],
            [0, 0, 0, 0, 26.35, 0],
            [0, 0, 0, 0, 0, 26.35],
        ])
        # Rank 3
        Tensor.from_voigt(np.zeros((3, 6)))
        # Rank 2
        Tensor.from_voigt(np.zeros(6))
        # Addresses occasional cast issues for integers
        Tensor.from_voigt(np.arange(6))

    def test_symmetry_reduce(self):
        tbs = [Tensor.from_voigt(row) for row in np.eye(6) * 0.01]
        reduced = symmetry_reduce(tbs, self.get_structure("Sn"))
        self.assertEqual(len(reduced), 2)
        self.assertArrayEqual([len(i) for i in reduced.values()], [2, 2])
        reconstructed = []
        for k, v in reduced.items():
            reconstructed.extend([k.voigt] +
                                 [k.transform(op).voigt for op in v])
        reconstructed = sorted(reconstructed, key=lambda x: np.argmax(x))
        self.assertArrayAlmostEqual([tb for tb in reconstructed],
                                    np.eye(6) * 0.01)

    def test_tensor_mapping(self):
        # Test get
        tbs = [Tensor.from_voigt(row) for row in np.eye(6) * 0.01]
        reduced = symmetry_reduce(tbs, self.get_structure("Sn"))
        tkey = Tensor.from_values_indices([0.01], [(0, 0)])
        tval = reduced[tkey]
        for tens_1, tens_2 in zip(tval, reduced[tbs[0]]):
            self.assertAlmostEqual(tens_1, tens_2)
        # Test set
        reduced[tkey] = "test_val"
        self.assertEqual(reduced[tkey], "test_val")
        # Test empty initialization
        empty = TensorMapping()
        self.assertEqual(empty._tensor_list, [])

    def test_populate(self):
        test_data = loadfn(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "test_toec_data.json"))

        sn = self.get_structure("Sn")
        vtens = np.zeros((6, 6))
        vtens[0, 0] = 259.31
        vtens[0, 1] = 160.71
        vtens[3, 3] = 73.48
        et = Tensor.from_voigt(vtens)
        populated = et.populate(sn, prec=1e-3).voigt.round(2)
        self.assertAlmostEqual(populated[1, 1], 259.31)
        self.assertAlmostEqual(populated[2, 2], 259.31)
        self.assertAlmostEqual(populated[0, 2], 160.71)
        self.assertAlmostEqual(populated[1, 2], 160.71)
        self.assertAlmostEqual(populated[4, 4], 73.48)
        self.assertAlmostEqual(populated[5, 5], 73.48)
        # test a rank 6 example
        vtens = np.zeros([6] * 3)
        indices = [(0, 0, 0), (0, 0, 1), (0, 1, 2), (0, 3, 3), (0, 5, 5),
                   (3, 4, 5)]
        values = [-1271.0, -814.0, -50.0, -3.0, -780.0, -95.0]
        for v, idx in zip(values, indices):
            vtens[idx] = v
        toec = Tensor.from_voigt(vtens)
        toec = toec.populate(sn, prec=1e-3, verbose=True)
        self.assertAlmostEqual(toec.voigt[1, 1, 1], -1271)
        self.assertAlmostEqual(toec.voigt[0, 1, 1], -814)
        self.assertAlmostEqual(toec.voigt[0, 2, 2], -814)
        self.assertAlmostEqual(toec.voigt[1, 4, 4], -3)
        self.assertAlmostEqual(toec.voigt[2, 5, 5], -3)
        self.assertAlmostEqual(toec.voigt[1, 2, 0], -50)
        self.assertAlmostEqual(toec.voigt[4, 5, 3], -95)

        et = Tensor.from_voigt(test_data["C3_raw"]).fit_to_structure(sn)
        new = np.zeros(et.voigt.shape)
        for idx in indices:
            new[idx] = et.voigt[idx]
        new = Tensor.from_voigt(new).populate(sn)
        self.assertArrayAlmostEqual(new, et, decimal=2)

    def test_from_values_indices(self):
        sn = self.get_structure("Sn")
        indices = [(0, 0), (0, 1), (3, 3)]
        values = [259.31, 160.71, 73.48]
        et = Tensor.from_values_indices(values,
                                        indices,
                                        structure=sn,
                                        populate=True).voigt.round(4)
        self.assertAlmostEqual(et[1, 1], 259.31)
        self.assertAlmostEqual(et[2, 2], 259.31)
        self.assertAlmostEqual(et[0, 2], 160.71)
        self.assertAlmostEqual(et[1, 2], 160.71)
        self.assertAlmostEqual(et[4, 4], 73.48)
        self.assertAlmostEqual(et[5, 5], 73.48)

    def test_serialization(self):
        # Test base serialize-deserialize
        d = self.symm_rank2.as_dict()
        new = Tensor.from_dict(d)
        self.assertArrayAlmostEqual(new, self.symm_rank2)

        d = self.symm_rank3.as_dict(voigt=True)
        new = Tensor.from_dict(d)
        self.assertArrayAlmostEqual(new, self.symm_rank3)

    def test_projection_methods(self):
        self.assertAlmostEqual(self.rand_rank2.project([1, 0, 0]),
                               self.rand_rank2[0, 0])
        self.assertAlmostEqual(self.rand_rank2.project([1, 1, 1]),
                               np.sum(self.rand_rank2) / 3)
        # Test integration
        self.assertArrayAlmostEqual(self.ones.average_over_unit_sphere(), 1)

    def test_summary_methods(self):
        self.assertEqual(
            set(self.ones.get_grouped_indices()[0]),
            set(itertools.product(range(3), range(3))),
        )
        self.assertEqual(
            self.ones.get_grouped_indices(voigt=True)[0],
            [(i, ) for i in range(6)])
        self.assertEqual(self.ones.get_symbol_dict(), {"T_1": 1})
        self.assertEqual(self.ones.get_symbol_dict(voigt=False), {"T_11": 1})

    def test_round(self):
        test = self.non_symm + 0.01
        rounded = test.round(1)
        self.assertArrayAlmostEqual(rounded, self.non_symm)
        self.assertTrue(isinstance(rounded, Tensor))