示例#1
0
def cmpt_deepmd_lammps(jdata, conf_dir, task_name) :
    conf_path = os.path.abspath(conf_dir)
    conf_poscar = os.path.join(conf_path, 'POSCAR')
    task_path = re.sub('confs', global_task_name, conf_path)
    task_path = os.path.join(task_path, task_name)
    equi_stress = Stress(np.loadtxt(os.path.join(task_path, 'equi.stress.out')))

    lst_dfm_path = glob.glob(os.path.join(task_path, 'dfm-*'))
    lst_strain = []
    lst_stress = []
    for ii in lst_dfm_path :
        strain = np.loadtxt(os.path.join(ii, 'strain.out'))
        stress = lammps.get_stress(os.path.join(ii, 'log.lammps'))
        # convert from pressure to stress
        stress = -stress
        lst_strain.append(Strain(strain))
        lst_stress.append(Stress(stress))
    et = ElasticTensor.from_independent_strains(lst_strain, lst_stress, eq_stress = equi_stress, vasp = False)
    # et = ElasticTensor.from_independent_strains(lst_strain, lst_stress, eq_stress = None)
    # bar to GPa
    # et = -et / 1e4
    print_et(et)
    result = os.path.join(task_path,'result')
    result_et(et,conf_dir,result)
    if 'upload_username' in jdata.keys() and task_name=='deepmd':
        upload_username=jdata['upload_username']
        util.insert_data('elastic','deepmd',upload_username,result)
示例#2
0
def cmpt_deepmd_lammps(jdata, conf_dir, task_name):
    deepmd_model_dir = jdata['deepmd_model_dir']
    deepmd_type_map = jdata['deepmd_type_map']
    ntypes = len(deepmd_type_map)

    conf_path = os.path.abspath(conf_dir)
    conf_poscar = os.path.join(conf_path, 'POSCAR')
    task_path = re.sub('confs', global_task_name, conf_path)
    task_path = os.path.join(task_path, task_name)
    equi_stress = Stress(np.loadtxt(os.path.join(task_path,
                                                 'equi.stress.out')))

    lst_dfm_path = glob.glob(os.path.join(task_path, 'dfm-*'))
    lst_strain = []
    lst_stress = []
    for ii in lst_dfm_path:
        strain = np.loadtxt(os.path.join(ii, 'strain.out'))
        stress = lammps.get_stress(os.path.join(ii, 'log.lammps'))
        # convert from pressure to stress
        stress = -stress
        lst_strain.append(Strain(strain))
        lst_stress.append(Stress(stress))
    et = ElasticTensor.from_independent_strains(lst_strain,
                                                lst_stress,
                                                eq_stress=equi_stress,
                                                vasp=False)
    # et = ElasticTensor.from_independent_strains(lst_strain, lst_stress, eq_stress = None)
    # bar to GPa
    # et = -et / 1e4
    print_et(et)
示例#3
0
def cmpt_vasp(jdata, conf_dir):
    fp_params = jdata['vasp_params']
    kspacing = fp_params['kspacing']
    kgamma = fp_params['kgamma']

    conf_path = os.path.abspath(conf_dir)
    conf_poscar = os.path.join(conf_path, 'POSCAR')
    task_path = re.sub('confs', global_task_name, conf_path)
    if 'relax_incar' in jdata.keys():
        vasp_str = 'vasp-relax_incar'
    else:
        vasp_str = 'vasp-k%.2f' % kspacing
    task_path = os.path.join(task_path, vasp_str)

    equi_stress = Stress(np.loadtxt(os.path.join(task_path,
                                                 'equi.stress.out')))

    lst_dfm_path = glob.glob(os.path.join(task_path, 'dfm-*'))
    lst_strain = []
    lst_stress = []
    for ii in lst_dfm_path:
        strain = np.loadtxt(os.path.join(ii, 'strain.out'))
        stress = vasp.get_stress(os.path.join(ii, 'OUTCAR'))
        # convert from pressure in kB to stress
        stress *= -1000
        lst_strain.append(Strain(strain))
        lst_stress.append(Stress(stress))
    et = ElasticTensor.from_independent_strains(lst_strain,
                                                lst_stress,
                                                eq_stress=equi_stress,
                                                vasp=False)
    # et = ElasticTensor.from_independent_strains(lst_strain, lst_stress, eq_stress = None)
    # bar to GPa
    # et = -et / 1e4
    print_et(et)
示例#4
0
def get_strain_state_dict(strains, stresses, eq_stress=None,
                          tol=1e-10, add_eq=True, sort=True):
    """
    Creates a dictionary of voigt-notation stress-strain sets 
    keyed by "strain state", i. e. a tuple corresponding to 
    the non-zero entries in ratios to the lowest nonzero value, 
    e.g. [0, 0.1, 0, 0.2, 0, 0] -> (0,1,0,2,0,0)
    This allows strains to be collected in stencils as to
    evaluate parameterized finite difference derivatives

    Args:
        strains (Nx3x3 array-like): strain matrices
        stresses (Nx3x3 array-like): stress matrices
        eq_stress (Nx3x3 array-like): equilibrium stress
        tol (float): tolerance for sorting strain states
        add_eq (bool): flag for whether to add eq_strain
            to stress-strain sets for each strain state
        sort (bool): flag for whether to sort strain states

    Returns:
        OrderedDict with strain state keys and dictionaries
        with stress-strain data corresponding to strain state 
    """
    # Recast stress/strains
    vstrains = np.array([Strain(s).zeroed(tol).voigt for s in strains])
    vstresses = np.array([Stress(s).zeroed(tol).voigt for s in stresses])
    # Collect independent strain states:
    independent = set([tuple(np.nonzero(vstrain)[0].tolist())
                       for vstrain in vstrains])
    strain_state_dict = OrderedDict()
    if add_eq:
        if eq_stress is not None:
            veq_stress = Stress(eq_stress).voigt
        else:
            veq_stress = find_eq_stress(strains, stresses).voigt

    for n, ind in enumerate(independent):
        # match strains with templates
        template = np.zeros(6, dtype=bool)
        np.put(template, ind, True)
        template = np.tile(template, [vstresses.shape[0], 1])
        mode = (template == (np.abs(vstrains) > 1e-10)).all(axis=1)
        mstresses = vstresses[mode]
        mstrains = vstrains[mode]

        if add_eq:
            # add zero strain state
            mstrains = np.vstack([mstrains, np.zeros(6)])
            mstresses = np.vstack([mstresses, veq_stress])
        # sort strains/stresses by strain values
        if sort:
            mstresses = mstresses[mstrains[:, ind[0]].argsort()]
            mstrains = mstrains[mstrains[:, ind[0]].argsort()]
        # Get "strain state", i.e. ratio of each value to minimum strain
        strain_state = mstrains[-1] / np.min(np.take(mstrains[-1], ind))
        strain_state = tuple(strain_state)
        strain_state_dict[strain_state] = {"strains": mstrains,
                                           "stresses": mstresses}
    return strain_state_dict
示例#5
0
 def setUp(self):
     self.rand_stress = Stress(np.random.randn(3, 3))
     self.symm_stress = Stress([[0.51, 2.29, 2.42],
                                [2.29, 5.14, 5.07],
                                [2.42, 5.07, 5.33]])
     self.non_symm = Stress([[0.1, 0.2, 0.3],
                             [0.4, 0.5, 0.6],
                             [0.2, 0.5, 0.5]])
示例#6
0
    def _compute_lower(self, output_file, all_tasks, all_res):
        output_file = os.path.abspath(output_file)
        res_data = {}
        ptr_data = output_file + '\n'
        equi_stress = Stress(
            np.loadtxt(
                os.path.join(os.path.dirname(output_file), 'equi.stress.out')))
        lst_strain = []
        lst_stress = []
        for ii in all_tasks:
            with open(os.path.join(ii, 'inter.json')) as fp:
                idata = json.load(fp)
            inter_type = idata['type']
            strain = np.loadtxt(os.path.join(ii, 'strain.out'))
            if inter_type == 'vasp':
                stress = vasp.get_stress(os.path.join(ii, 'OUTCAR'))
                # convert from pressure in kB to stress
                stress *= -1000
                lst_strain.append(Strain(strain))
                lst_stress.append(Stress(stress))
            elif inter_type in ['deepmd', 'meam', 'eam_fs', 'eam_alloy']:
                stress = lammps.get_stress(os.path.join(ii, 'log.lammps'))
                # convert from pressure to stress
                stress = -stress
                lst_strain.append(Strain(strain))
                lst_stress.append(Stress(stress))
        et = ElasticTensor.from_independent_strains(lst_strain,
                                                    lst_stress,
                                                    eq_stress=equi_stress,
                                                    vasp=False)
        res_data['elastic_tensor'] = []
        for ii in range(6):
            for jj in range(6):
                res_data['elastic_tensor'].append(et.voigt[ii][jj] / 1e4)
                ptr_data += "%7.2f " % (et.voigt[ii][jj] / 1e4)
            ptr_data += '\n'

        BV = et.k_voigt / 1e4
        GV = et.g_voigt / 1e4
        EV = 9 * BV * GV / (3 * BV + GV)
        uV = 0.5 * (3 * BV - 2 * GV) / (3 * BV + GV)

        res_data['BV'] = BV
        res_data['GV'] = GV
        res_data['EV'] = EV
        res_data['uV'] = uV
        ptr_data += "# Bulk   Modulus BV = %.2f GPa\n" % BV
        ptr_data += "# Shear  Modulus GV = %.2f GPa\n" % GV
        ptr_data += "# Youngs Modulus EV = %.2f GPa\n" % EV
        ptr_data += "# Poission Ratio uV = %.2f " % uV

        with open(output_file, 'w') as fp:
            json.dump(res_data, fp, indent=4)

        return res_data, ptr_data
示例#7
0
    def _compute_lower(self, output_file, all_tasks, all_res):
        output_file = os.path.abspath(output_file)
        res_data = {}
        ptr_data = os.path.dirname(output_file) + '\n'
        equi_stress = Stress(
            loadfn(
                os.path.join(os.path.dirname(output_file),
                             'equi.stress.json')))
        lst_strain = []
        lst_stress = []
        for ii in all_tasks:
            strain = loadfn(os.path.join(ii, 'strain.json'))
            # stress, deal with unsupported stress in dpdata
            #with open(os.path.join(ii, 'result_task.json')) as fin:
            #    task_result = json.load(fin)
            #stress = np.array(task_result['stress']['data'])[-1]
            stress = loadfn(os.path.join(ii, 'result_task.json'))['stress'][-1]
            lst_strain.append(strain)
            lst_stress.append(Stress(stress * -1000))

        et = ElasticTensor.from_independent_strains(lst_strain,
                                                    lst_stress,
                                                    eq_stress=equi_stress,
                                                    vasp=False)
        res_data['elastic_tensor'] = []
        for ii in range(6):
            for jj in range(6):
                res_data['elastic_tensor'].append(et.voigt[ii][jj] / 1e4)
                ptr_data += "%7.2f " % (et.voigt[ii][jj] / 1e4)
            ptr_data += '\n'

        BV = et.k_voigt / 1e4
        GV = et.g_voigt / 1e4
        EV = 9 * BV * GV / (3 * BV + GV)
        uV = 0.5 * (3 * BV - 2 * GV) / (3 * BV + GV)

        res_data['BV'] = BV
        res_data['GV'] = GV
        res_data['EV'] = EV
        res_data['uV'] = uV
        ptr_data += "# Bulk   Modulus BV = %.2f GPa\n" % BV
        ptr_data += "# Shear  Modulus GV = %.2f GPa\n" % GV
        ptr_data += "# Youngs Modulus EV = %.2f GPa\n" % EV
        ptr_data += "# Poission Ratio uV = %.2f\n " % uV

        dumpfn(res_data, output_file, indent=4)

        return res_data, ptr_data
示例#8
0
def find_eq_stress(strains, stresses, tol=1e-10):
    """
    Finds stress corresponding to zero strain state in stress-strain list

    Args:
        strains (Nx3x3 array-like): array corresponding to strains
        stresses (Nx3x3 array-like): array corresponding to stresses
        tol (float): tolerance to find zero strain state
    """
    stress_array = np.array(stresses)
    strain_array = np.array(strains)
    eq_stress = stress_array[np.all(abs(strain_array) < tol, axis=(1, 2))]

    if eq_stress.size != 0:
        all_same = (abs(eq_stress - eq_stress[0]) < 1e-8).all()
        if len(eq_stress) > 1 and not all_same:
            raise ValueError(
                "Multiple stresses found for equilibrium strain"
                " state, please specify equilibrium stress or  "
                " remove extraneous stresses."
            )
        eq_stress = eq_stress[0]
    else:
        warnings.warn("No eq state found, returning zero voigt stress")
        eq_stress = Stress(np.zeros((3, 3)))
    return eq_stress
示例#9
0
 def setUp(self):
     with open(os.path.join(test_dir, 'test_toec_data.json')) as f:
         self.data_dict = json.load(f)
     self.strains = [Strain(sm) for sm in self.data_dict['strains']]
     self.pk_stresses = [Stress(d) for d in self.data_dict['pk_stresses']]
     self.c2 = self.data_dict["C2_raw"]
     self.c3 = self.data_dict["C3_raw"]
     self.exp = ElasticTensorExpansion.from_voigt([self.c2, self.c3])
     self.cu = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3.623),
                                         ["Cu"], [[0] * 3])
     indices = [(0, 0), (0, 1), (3, 3)]
     values = [167.8, 113.5, 74.5]
     cu_c2 = ElasticTensor.from_values_indices(values,
                                               indices,
                                               structure=self.cu,
                                               populate=True)
     indices = [(0, 0, 0), (0, 0, 1), (0, 1, 2), (0, 3, 3), (0, 5, 5),
                (3, 4, 5)]
     values = [-1507., -965., -71., -7., -901., 45.]
     cu_c3 = Tensor.from_values_indices(values,
                                        indices,
                                        structure=self.cu,
                                        populate=True)
     self.exp_cu = ElasticTensorExpansion([cu_c2, cu_c3])
     cu_c4 = Tensor.from_voigt(self.data_dict["Cu_fourth_order"])
     self.exp_cu_4 = ElasticTensorExpansion([cu_c2, cu_c3, cu_c4])
     warnings.simplefilter("ignore")
示例#10
0
 def test_get_strain_state_dict(self):
     strain_inds = [(0,), (1,), (2,), (1, 3), (1, 2, 3)]
     vecs = {}
     strain_states = []
     for strain_ind in strain_inds:
         ss = np.zeros(6)
         np.put(ss, strain_ind, 1)
         strain_states.append(tuple(ss))
         vec = np.zeros((4, 6))
         rand_values = np.random.uniform(0.1, 1, 4)
         for i in strain_ind:
             vec[:, i] = rand_values
         vecs[strain_ind] = vec
     all_strains = [Strain.from_voigt(v).zeroed() for vec in vecs.values() for v in vec]
     random.shuffle(all_strains)
     all_stresses = [Stress.from_voigt(np.random.random(6)).zeroed() for s in all_strains]
     strain_dict = {k.tostring(): v for k, v in zip(all_strains, all_stresses)}
     ss_dict = get_strain_state_dict(all_strains, all_stresses, add_eq=False)
     # Check length of ss_dict
     self.assertEqual(len(strain_inds), len(ss_dict))
     # Check sets of strain states are correct
     self.assertEqual(set(strain_states), set(ss_dict.keys()))
     for strain_state, data in ss_dict.items():
         # Check correspondence of strains/stresses
         for strain, stress in zip(data["strains"], data["stresses"]):
             self.assertArrayAlmostEqual(
                 Stress.from_voigt(stress),
                 strain_dict[Strain.from_voigt(strain).tostring()],
             )
     # Add test to ensure zero strain state doesn't cause issue
     strains, stresses = [Strain.from_voigt([-0.01] + [0] * 5)], [Stress(np.eye(3))]
     ss_dict = get_strain_state_dict(strains, stresses)
     self.assertArrayAlmostEqual(list(ss_dict.keys()), [[1, 0, 0, 0, 0, 0]])
示例#11
0
 def setUp(self):
     with open(os.path.join(PymatgenTest.TEST_FILES_DIR, "test_toec_data.json")) as f:
         self.data_dict = json.load(f)
     self.strains = [Strain(sm) for sm in self.data_dict["strains"]]
     self.pk_stresses = [Stress(d) for d in self.data_dict["pk_stresses"]]
     self.c2 = NthOrderElasticTensor.from_voigt(self.data_dict["C2_raw"])
     self.c3 = NthOrderElasticTensor.from_voigt(self.data_dict["C3_raw"])
示例#12
0
 def setUp(self):
     with open(
             os.path.join(PymatgenTest.TEST_FILES_DIR,
                          "test_toec_data.json")) as f:
         self.data_dict = json.load(f)
     self.strains = [Strain(sm) for sm in self.data_dict["strains"]]
     self.pk_stresses = [Stress(d) for d in self.data_dict["pk_stresses"]]
示例#13
0
 def test_properties(self):
     # mean_stress
     self.assertEqual(
         self.rand_stress.mean_stress,
         1. / 3. * (self.rand_stress[0, 0] + self.rand_stress[1, 1] +
                    self.rand_stress[2, 2]))
     self.assertAlmostEqual(self.symm_stress.mean_stress, 3.66)
     # deviator_stress
     self.assertArrayAlmostEqual(
         self.symm_stress.deviator_stress,
         Stress([[-3.15, 2.29, 2.42], [2.29, 1.48, 5.07],
                 [2.42, 5.07, 1.67]]))
     self.assertArrayAlmostEqual(
         self.non_symm.deviator_stress,
         [[-0.2666666667, 0.2, 0.3], [0.4, 0.133333333, 0.6],
          [0.2, 0.5, 0.133333333]])
     # deviator_principal_invariants
     self.assertArrayAlmostEqual(self.symm_stress.dev_principal_invariants,
                                 [0, 44.2563, 111.953628])
     # von_mises
     self.assertAlmostEqual(self.symm_stress.von_mises, 11.52253878275)
     # piola_kirchoff 1, 2
     f = Deformation.from_index_amount((0, 1), 0.03)
     self.assertArrayAlmostEqual(
         self.symm_stress.piola_kirchoff_1(f),
         [[0.4413, 2.29, 2.42], [2.1358, 5.14, 5.07], [2.2679, 5.07, 5.33]])
     self.assertArrayAlmostEqual(
         self.symm_stress.piola_kirchoff_2(f),
         [[0.377226, 2.1358, 2.2679], [2.1358, 5.14, 5.07],
          [2.2679, 5.07, 5.33]])
     # voigt
     self.assertArrayEqual(self.symm_stress.voigt,
                           [0.51, 5.14, 5.33, 5.07, 2.42, 2.29])
     with self.assertRaises(ValueError):
         self.non_symm.voigt
示例#14
0
 def setUp(self):
     with open(os.path.join(test_dir, 'test_toec_data.json')) as f:
         self.data_dict = json.load(f)
     self.strains = [Strain(sm) for sm in self.data_dict['strains']]
     self.pk_stresses = [Stress(d) for d in self.data_dict['pk_stresses']]
     self.c2 = NthOrderElasticTensor.from_voigt(self.data_dict["C2_raw"])
     self.c3 = NthOrderElasticTensor.from_voigt(self.data_dict["C3_raw"])
示例#15
0
 def test_from_stress_dict(self):
     stress_dict = dict(list(zip([IndependentStrain(def_matrix) for def_matrix
                             in self.def_stress_dict['deformations']],
                             [Stress(stress_matrix) for stress_matrix
                             in self.def_stress_dict['stresses']])))
     with warnings.catch_warnings(record = True):
         et_from_sd = ElasticTensor.from_stress_dict(stress_dict)
     self.assertArrayAlmostEqual(et_from_sd.voigt_symmetrized.round(2),
                                 self.elastic_tensor_1)
示例#16
0
def readLammps(desired_return):
    from pymatgen.io.lammps.outputs import parse_lammps_dumps, parse_lammps_log
    from pymatgen import Structure, Element
    from pymatgen.analysis.elasticity.stress import Stress
    from numpy import unique, array, argmin
    try:
        log = parse_lammps_log(filename="log.lammps")[-1]
    except IndexError:
        return_dict = {}
        for ret in desired_return:
            return_dict[ret] = None
        return return_dict
    result_dict = {}
    result_dict["energies"] = list(log['PotEng'])[-1]

    for dump in parse_lammps_dumps("dump.atoms"):
        atoms = dump.data
        coords = [''] * dump.natoms
        forces = [''] * dump.natoms
        masses = [''] * dump.natoms
        for atom in range(dump.natoms):
            coords[atoms["id"][atom] -
                   1] = [atoms["x"][atom], atoms["y"][atom], atoms["z"][atom]]
            forces[atoms['id'][atom] - 1] = [
                atoms["fx"][atom], atoms["fy"][atom], atoms["fz"][atom]
            ]
            masses[atoms['id'][atom] - 1] = atoms["mass"][atom]

        box = dump.box

    unique_masses = unique(masses)
    ref_masses = [el.atomic_mass.real for el in Element]
    diff = abs(array(ref_masses) - unique_masses[:, None])
    atomic_numbers = argmin(diff, axis=1) + 1
    symbols = [Element.from_Z(an).symbol for an in atomic_numbers]
    species_map = {}
    for i in range(len(unique_masses)):
        species_map[unique_masses[i]] = symbols[i]
    atom_species = [species_map[mass] for mass in masses]

    result_dict["structures"] = Structure(box.to_lattice(),
                                          atom_species,
                                          coords,
                                          coords_are_cartesian=True)
    result_dict["forces"] = forces

    pressure = [
        1e-1 * list(log['c_press[{}]'.format(i)])[-1] for i in range(1, 7)
    ]
    result_dict["stresses"] = Stress([[pressure[0], pressure[3], pressure[4]],
                                      [pressure[3], pressure[1], pressure[5]],
                                      [pressure[4], pressure[5], pressure[2]]])
    return_dict = {}
    for ret in desired_return:
        return_dict[ret] = result_dict[ret]
    return return_dict
示例#17
0
 def test_toec_fit(self):
     with open(os.path.join(test_dir, 'test_toec_data.json')) as f:
         toec_dict = json.load(f)
     strains = [Strain(sm) for sm in toec_dict['strains']]
     pk_stresses = [Stress(d) for d in toec_dict['pk_stresses']]
     with warnings.catch_warnings(record=True) as w:
         c2, c3 = toec_fit(strains,
                           pk_stresses,
                           eq_stress=toec_dict["eq_stress"])
         self.assertArrayAlmostEqual(c2.voigt, toec_dict["C2_raw"])
         self.assertArrayAlmostEqual(c3.voigt, toec_dict["C3_raw"])
示例#18
0
    def calculate_stress(self, strain):
        """
        Calculate's a given elastic tensor's contribution to the
        stress using Einstein summation

        Args:
            strain (3x3 array-like): matrix corresponding to strain
        """
        strain = np.array(strain)
        if strain.shape == (6,):
            strain = Strain.from_voigt(strain)
        assert strain.shape == (3, 3), "Strain must be 3x3 or voigt-notation"
        stress_matrix = self.einsum_sequence([strain] * (self.order - 1)) / factorial(self.order - 1)
        return Stress(stress_matrix)
示例#19
0
文件: mtp.py 项目: jmeziere/testMTP
def readCfg(desired_return, num_structs, args):
    from pymatgen.analysis.elasticity.stress import Stress

    energies = [None] * num_structs
    forces = [None] * num_structs
    stresses = [None] * num_structs
    structs = [None] * num_structs
    id_mapping = {v: k for k, v in args["atom_mapping"].items()}
    from pymatgen import Structure
    with open("calc.cfg", "r") as cfg_in:
        line = cfg_in.readline()
        while line:
            if "size" in line.lower():
                size = int(cfg_in.readline())
            if "supercell" in line.lower():
                lattice = [[float(val) for val in cfg_in.readline().split()] for j in range(3)]
            if "atomdata" in line.lower():
                atom_data = [[float(val) for val in cfg_in.readline().split()] for j in range(size)]
                species = [id_mapping[int(atom[1])] for atom in atom_data]
                coords = [atom[2:5] for atom in atom_data]
                if len(atom_data[0]) == 8:
                    force = [[float(val) for val in atom[5:8]] for atom in atom_data]
            if "energy" in line.lower():
                energy = float(cfg_in.readline())
            if "plusstress" in line.lower():
                stress = [float(val) for val in cfg_in.readline().split()]
                stress = [[stress[0],stress[5],stress[4]],
                          [stress[5],stress[1],stress[3]],
                          [stress[4],stress[3],stress[2]]]
            if "conf_id" in line.lower():
                conf_id = int(line.split()[-1])
            if "end_cfg" in line.lower():
                structs[conf_id] = Structure(lattice,species,coords,coords_are_cartesian=True)
                energies[conf_id] = energy
                forces[conf_id] = force
                stresses[conf_id] = Stress(stress)*1.602176634*1e3/float(structs[conf_id].volume)
                energy = None
                force = None
                stress = None
            line = cfg_in.readline()
    result_dict = {"structures":structs, "energies":energies, "forces": forces, "stresses":stresses}
    return_dict = {}
    for ret in desired_return:
        return_dict[ret] = result_dict[ret]
    return return_dict
示例#20
0
    def get_relaxed_structure_stress(self):
        '''
        Verify that the PwRelaxWorkChain finished successfully

        NOTE: I would really like to modify this to be flexible (LAMMPS or QE)
        '''
        workchain = self.ctx.initial_relax_workchain
        self.report('Getting relaxed structure stresses')

        if not workchain.is_finished_ok:
            self.report('PwRelaxWorkChain<{}> failed with exit status {}'
                        .format(workchain.pk, workchain.exit_status))
            return self.exit_codes.ERROR_SUB_PROCESS_FAILED_RELAX
        else:
            stress = get_qerelax_stress(workchain)

            self.ctx.equilibrium_structure = workchain.outputs.output_structure
            self.ctx.equilibrium_stress = Stress(stress)
示例#21
0
    def from_strain_stress_list(cls, strains, stresses):
        """
        Class method to fit an elastic tensor from stress/strain data.  Method
        uses Moore-Penrose pseudoinverse to invert the s = C*e equation with
        elastic tensor, stress, and strain in voigt notation

        Args:
            stresses (Nx3x3 array-like): list or array of stresses
            strains (Nx3x3 array-like): list or array of strains
        """
        # convert the stress/strain to Nx6 arrays of voigt-notation
        warnings.warn("Linear fitting of Strain/Stress lists may yield "
                      "questionable results from vasp data, use with caution.")
        stresses = np.array([Stress(stress).voigt for stress in stresses])
        with warnings.catch_warnings(record=True):
            strains = np.array([Strain(strain).voigt for strain in strains])

        return cls(np.transpose(np.dot(np.linalg.pinv(strains), stresses)))
示例#22
0
    def calculate_stress(self, strain):
        """
        Calculate's a given elastic tensor's contribution to the
        stress using Einstein summation

        Args:
            strain (3x3 array-like): matrix corresponding to strain
        """
        strain = np.array(strain)
        if strain.shape == (6, ):
            strain = Strain.from_voigt(strain)
        assert strain.shape == (3, 3), "Strain must be 3x3 or voigt-notation"
        lc = string.ascii_lowercase[:self.rank - 2]
        lc_pairs = map(''.join, zip(*[iter(lc)] * 2))
        einsum_string = "ij" + lc + ',' + ','.join(lc_pairs) + "->ij"
        einsum_args = [self] + [strain] * (self.order - 1)
        stress_matrix = np.einsum(einsum_string, *einsum_args) \
                / factorial(self.order - 1)
        return Stress(stress_matrix)
示例#23
0
 def test_toec_fit(self):
     with open(os.path.join(test_dir, 'test_toec_data.json')) as f:
         toec_dict = json.load(f)
     strains = [Strain(sm) for sm in toec_dict['strains']]
     pk_stresses = [Stress(d) for d in toec_dict['pk_stresses']]
     reduced = [(strain, pks) for strain, pks in zip(strains, pk_stresses)
                if not (abs(abs(strain) - 0.05) < 1e-10).any()]
     with warnings.catch_warnings(record=True) as w:
         c2, c3 = toec_fit(strains,
                           pk_stresses,
                           eq_stress=toec_dict["eq_stress"])
         self.assertArrayAlmostEqual(c2.voigt, toec_dict["C2_raw"])
         self.assertArrayAlmostEqual(c3.voigt, toec_dict["C3_raw"])
         # Try with reduced data set
         r_strains, r_pk_stresses = zip(*reduced)
         c2_red, c3_red = toec_fit(r_strains,
                                   r_pk_stresses,
                                   eq_stress=toec_dict["eq_stress"])
         self.assertArrayAlmostEqual(c2, c2_red, decimal=0)
         self.assertArrayAlmostEqual(c3, c3_red, decimal=-1)
示例#24
0
    def gather_computed_stresses(self):
        '''
        Retrieve final stress from the defomed structure relax workflows
        '''
        self.report('Gathering computed stresses')
        deformed_structures = self.ctx.deformed_structures

        computed_stresses = []
        for key_index in range(len(deformed_structures)):
            deformation_key = 'deformation_{}'.format(key_index)
            deformation_key = 'deformation_{}'.format(key_index)
            workchain = self.ctx[deformation_key]
            if not workchain.is_finished_ok:
                self.report('PwRelaxWorkChain failed with exit status {}'
                            .format(workchain.exit_status))
                return self.exit_codes.ERROR_SUB_PROCESS_FAILED_RELAX
            else:
                computed_stress = get_qerelax_stress(workchain)
                computed_stresses.append(Stress(computed_stress))
        
        self.ctx.stresses = computed_stresses 
示例#25
0
 def test_from_stress_dict(self):
     stress_dict = dict(
         list(
             zip([
                 IndependentStrain(def_matrix)
                 for def_matrix in self.def_stress_dict['deformations']
             ], [
                 Stress(stress_matrix)
                 for stress_matrix in self.def_stress_dict['stresses']
             ])))
     minimal_sd = {
         k: v
         for k, v in stress_dict.items()
         if (abs(k[k.ij] - 0.015) < 1e-10 or abs(k[k.ij] - 0.01005) < 1e-10)
     }
     with warnings.catch_warnings(record=True):
         et_from_sd = ElasticTensor.from_stress_dict(stress_dict)
         et_from_minimal_sd = ElasticTensor.from_stress_dict(minimal_sd)
     self.assertArrayAlmostEqual(et_from_sd.voigt_symmetrized.round(2),
                                 self.elastic_tensor_1)
     self.assertAlmostEqual(50.63394169, et_from_minimal_sd[0, 0, 0, 0])
示例#26
0
    def run_task(self, fw_spec):
        db_dir = os.environ['DB_LOC']
        db_path = os.path.join(db_dir, 'tasks_db.json')
        i = fw_spec['original_task_id']

        with open(db_path) as f:
            db_creds = json.load(f)
        connection = MongoClient(db_creds['host'], db_creds['port'])
        tdb = connection[db_creds['database']]
        tdb.authenticate(db_creds['admin_user'], db_creds['admin_password'])
        tasks = tdb[db_creds['collection']]
        elasticity = tdb['elasticity']
        ndocs = tasks.find({"original_task_id": i, 
                            "state":"successful"}).count()
        existing_doc = elasticity.find_one({"relaxation_task_id" : i})
        if existing_doc:
            print "Updating: " + i
        else:
            print "New material: " + i
        d = {"analysis": {}, "error": [], "warning": []}
        d["ndocs"] = ndocs
        o = tasks.find_one({"task_id" : i},
                           {"pretty_formula" : 1, "spacegroup" : 1,
                            "snl" : 1, "snl_final" : 1, "run_tags" : 1})
        if not o:
            raise ValueError("Cannot find original task id")
        # Get stress from deformed structure
        d["deformation_tasks"] = {}
        ss_dict = {}
        for k in tasks.find({"original_task_id": i}, 
                            {"deformation_matrix":1,
                             "calculations.output":1,
                             "state":1, "task_id":1}):
            defo = k['deformation_matrix']
            d_ind = np.nonzero(defo - np.eye(3))
            delta = Decimal((defo - np.eye(3))[d_ind][0])
            # Normal deformation
            if d_ind[0] == d_ind[1]:
                dtype = "_".join(["d", str(d_ind[0][0]), 
                                  "{:.0e}".format(delta)])
            # Shear deformation
            else:
                dtype = "_".join(["s", str(d_ind[0] + d_ind[1]),
                                  "{:.0e}".format(delta)])
            sm = IndependentStrain(defo)
            if dtype in d["deformation_tasks"].keys():
                print "old_task: {}".format(d["deformation_tasks"][dtype]["task_id"])
                print "new_task: {}".format(k["task_id"])
                raise ValueError("Duplicate deformation task in database.")
            d["deformation_tasks"][dtype] = {"state" : k["state"],
                                             "deformation_matrix" : defo,
                                             "strain" : sm.tolist(),
                                             "task_id": k["task_id"]}
            if k["state"] == "successful":
                st = Stress(k["calculations"][-1]["output"] \
                            ["ionic_steps"][-1]["stress"])
                ss_dict[sm] = st
        d["snl"] = o["snl"]
        if "run_tags" in o.keys():
            d["run_tags"] = o["run_tags"]
            for tag in o["run_tags"]:
                if isinstance(tag, dict):
                    if "input_id" in tag.keys():
                        d["input_mp_id"] = tag["input_id"]
        d["snl_final"] = o["snl_final"]
        d["pretty_formula"] = o["pretty_formula"]

        # Old input mp-id style
        if o["snl"]["about"].get("_mp_id"):
            d["material_id"] = o["snl"]["about"]["_mp_id"]

        # New style
        elif "input_mp_id" in d:
            d["material_id"] = d["input_mp_id"]
        else:
            d["material_id"] = None
        d["relaxation_task_id"] = i

        calc_struct = Structure.from_dict(o["snl_final"])
        # TODO:
        # JHM: This test is unnecessary at the moment, but should be redone
        """
        conventional = is_conventional(calc_struct)
        if conventional:
            d["analysis"]["is_conventional"] = True
        else:
            d["analysis"]["is_conventional"] = False
        """
        d["spacegroup"]=o.get("spacegroup", "Unknown")
        
        if ndocs >= 20:
            # Perform Elastic tensor fitting and analysis
            result = ElasticTensor.from_stress_dict(ss_dict)
            d["elastic_tensor"] = result.voigt.tolist()
            kg_average = result.kg_average
            d.update({"K_Voigt":kg_average[0], "G_Voigt":kg_average[1], 
                      "K_Reuss":kg_average[2], "G_Reuss":kg_average[3], 
                      "K_Voigt_Reuss_Hill":kg_average[4], 
                      "G_Voigt_Reuss_Hill":kg_average[5]})
            d["universal_anisotropy"] = result.universal_anisotropy
            d["homogeneous_poisson"] = result.homogeneous_poisson
            if ndocs < 24:
                d["warning"].append("less than 24 tasks completed")

            # Perform filter checks
            symm_t = result.voigt_symmetrized
            d["symmetrized_tensor"] = symm_t.voigt.tolist()
            d["analysis"]["not_rare_earth"] = True
            for s in calc_struct.species:
                if s.is_rare_earth_metal:
                    d["analysis"]["not_rare_earth"] = False
            eigvals = np.linalg.eigvals(symm_t.voigt)
            eig_positive = np.all((eigvals > 0) & np.isreal(eigvals))
            d["analysis"]["eigval_positive"] = bool(eig_positive)
            c11 = symm_t.voigt[0][0]
            c12 = symm_t.voigt[0][1]
            c13 = symm_t.voigt[0][2]
            c23 = symm_t.voigt[1][2]
            d["analysis"]["c11_c12"]= not (abs((c11-c12)/c11) < 0.05
                                           or c11 < c12)
            d["analysis"]["c11_c13"]= not (abs((c11-c13)/c11) < 0.05 
                                           or c11 < c13)
            d["analysis"]["c11_c23"]= not (abs((c11-c23)/c11) < 0.1 
                                           or c11 < c23)
            d["analysis"]["K_R"] = not (d["K_Reuss"] < 2)
            d["analysis"]["G_R"] = not (d["G_Reuss"] < 2)
            d["analysis"]["K_V"] = not (d["K_Voigt"] < 2)
            d["analysis"]["G_V"] = not (d["G_Voigt"] < 2)
            filter_state = np.all(d["analysis"].values())
            d["analysis"]["filter_pass"] = bool(filter_state)
            d["analysis"]["eigval"] = list(eigvals)

            # TODO:
            # JHM: eventually we can reintroduce the IEEE conversion
            #       but as of now it's not being used, and it should
            #       be in pymatgen
            """
            # IEEE Conversion
            try:
                ieee_tensor = IEEE_conversion.get_ieee_tensor(struct_final, result)
                d["elastic_tensor_IEEE"] = ieee_tensor[0].tolist()
                d["analysis"]["IEEE"] = True
            except Exception as e:
                d["elastic_tensor_IEEE"] = None
                d["analysis"]["IEEE"] = False
                d["error"].append("Unable to get IEEE tensor: {}".format(e))
            """
            # Add thermal properties
            nsites = calc_struct.num_sites
            volume = calc_struct.volume
            natoms = calc_struct.composition.num_atoms
            weight = calc_struct.composition.weight
            num_density = 1e30 * nsites / volume
            mass_density = 1.6605e3 * nsites * volume * weight / \
                           (natoms * volume)
            tot_mass = sum([e.atomic_mass for e in calc_struct.species])
            avg_mass =  1.6605e-27 * tot_mass / natoms
            y_mod = 9e9 * result.k_vrh * result.g_vrh / \
                    (3. * result.k_vrh * result.g_vrh)
            trans_v = 1e9 * result.k_vrh / mass_density**0.5
            long_v = 1e9 * result.k_vrh + \
                     4./3. * result.g_vrh / mass_density**0.5
            clarke = 0.87 * 1.3806e-23 * avg_mass**(-2./3.) * \
                     mass_density**(1./6.) * y_mod**0.5
            cahill = 1.3806e-23 / 2.48 * num_density**(2./3.) * long_v + \
                     2 * trans_v
            snyder_ac = 0.38483 * avg_mass * \
                        (long_v + 2./3.*trans_v)**3. / \
                        (300. * num_density**(-2./3.) * nsites**(1./3.))
            snyder_opt = 1.66914e-23 * (long_v + 2./3.*trans_v) / \
                         num_density**(-2./3.) * \
                         (1 - nsites**(-1./3.))
            snyder_total = snyder_ac + snyder_opt
            debye = 2.489e-11 * avg_mass**(-1./3.) * \
                    mass_density**(-1./6.) * y_mod**0.5

            d["thermal"]={"num_density" : num_density,
                          "mass_density" : mass_density,
                          "avg_mass" : avg_mass,
                          "num_atom_per_unit_formula" : natoms,
                          "youngs_modulus" : y_mod,
                          "trans_velocity" : trans_v,
                          "long_velocity" : long_v,
                          "clarke" : clarke,
                          "cahill" : cahill,
                          "snyder_acou_300K" : snyder_ac,
                          "snyder_opt" : snyder_opt,
                          "snyder_total" : snyder_total,
                          "debye": debye
                         }
        else:
            d['state'] = "Fewer than 20 successful tasks completed"
            return FWAction()

        if o["snl"]["about"].get("_kpoint_density"):
            d["kpoint_density"]= o["snl"]["about"].get("_kpoint_density")

        if d["error"]:
            raise ValueError("Elastic analysis failed: {}".format(d["error"]))
        elif d["analysis"]["filter_pass"]:
            d["state"] = "successful"
        else:
            d["state"] = "filter_failed"
        elasticity.update({"relaxation_task_id": d["relaxation_task_id"]}, 
                           d, upsert=True)
        return FWAction()
示例#27
0
 def setUp(self):
     with open(os.path.join(test_dir, 'test_toec_data.json')) as f:
         self.data_dict = json.load(f)
     self.strains = [Strain(sm) for sm in self.data_dict['strains']]
     self.pk_stresses = [Stress(d) for d in self.data_dict['pk_stresses']]
示例#28
0
def get_elastic_analysis(opt_task, defo_tasks):
    """
    Performs the analysis of opt_tasks and defo_tasks necessary for
    an elastic analysis

    Args:
        opt_task: task doc corresponding to optimization
        defo_tasks: task_doc corresponding to deformations

    Returns:
        elastic document with fitted elastic tensor and analysis

    """
    elastic_doc = {"warnings": []}
    opt_struct = Structure.from_dict(opt_task['output']['structure'])
    input_struct = Structure.from_dict(opt_task['input']['structure'])
    # For now, discern order (i.e. TOEC) using parameters from optimization
    # TODO: figure this out more intelligently
    diff = get(opt_task, "input.incar.EDIFFG", 0)
    order = 3 if np.isclose(diff, -0.001) else 2
    explicit, derived = process_elastic_calcs(opt_task, defo_tasks)
    all_calcs = explicit + derived
    stresses = [c.get("cauchy_stress") for c in all_calcs]
    pk_stresses = [c.get("pk_stress") for c in all_calcs]
    strains = [c.get("strain") for c in all_calcs]
    elastic_doc['calculations'] = all_calcs
    vstrains = [s.zeroed(0.002).voigt for s in strains]
    if np.linalg.matrix_rank(vstrains) == 6:
        if order == 2:
            et_fit = legacy_fit(strains, stresses)
        elif order == 3:
            # Test for TOEC
            if len(strains) < 70:
                logger.info("insufficient valid strains for {} TOEC".format(
                    opt_task['formula_pretty']))
                return None
            eq_stress = -0.1 * Stress(opt_task['output']['stress'])
            # strains = [s.zeroed(0.0001) for s in strains]
            # et_expansion = pdb_function(ElasticTensorExpansion.from_diff_fit,
            #     strains, pk_stresses, eq_stress=eq_stress, tol=1e-5)
            et_exp_raw = ElasticTensorExpansion.from_diff_fit(
                strains, pk_stresses, eq_stress=eq_stress, tol=1e-6)
            et_exp = et_exp_raw.voigt_symmetrized.convert_to_ieee(opt_struct)
            et_exp = et_exp.round(1)
            et_fit = ElasticTensor(et_exp[0])
            # Update elastic doc with TOEC stuff
            tec = et_exp.thermal_expansion_coeff(opt_struct, 300)
            elastic_doc.update({
                "elastic_tensor_expansion":
                elastic_sanitize(et_exp),
                "elastic_tensor_expansion_original":
                elastic_sanitize(et_exp_raw),
                "thermal_expansion_tensor":
                tec,
                "average_linear_thermal_expansion":
                np.trace(tec) / 3
            })
        et = et_fit.voigt_symmetrized.convert_to_ieee(opt_struct)
        vasp_input = opt_task['input']
        if 'structure' in vasp_input:
            vasp_input.pop('structure')
        completed_at = max([d['completed_at'] for d in defo_tasks])
        elastic_doc.update({
            "optimization_task_id":
            opt_task['task_id'],
            "optimization_dir_name":
            opt_task['dir_name'],
            "cauchy_stresses":
            stresses,
            "strains":
            strains,
            "elastic_tensor":
            elastic_sanitize(et.zeroed(0.01).round(0)),
            # Convert compliance to 10^-12 Pa
            "compliance_tensor":
            elastic_sanitize(et.compliance_tensor * 1000),
            "elastic_tensor_original":
            elastic_sanitize(et_fit),
            "optimized_structure":
            opt_struct,
            "spacegroup":
            input_struct.get_space_group_info()[0],
            "input_structure":
            input_struct,
            "completed_at":
            completed_at,
            "optimization_input":
            vasp_input,
            "order":
            order,
            "pretty_formula":
            opt_struct.composition.reduced_formula
        })
        # Add magnetic type
        mag = CollinearMagneticStructureAnalyzer(opt_struct).ordering.value
        elastic_doc['magnetic_type'] = mag_types[mag]
        try:
            prop_dict = et.get_structure_property_dict(opt_struct)
            prop_dict.pop('structure')
        except ValueError:
            logger.debug("Negative K or G found, structure property "
                         "dict not computed")
            prop_dict = et.property_dict
        for k, v in prop_dict.items():
            if k in ['homogeneous_poisson', 'universal_anisotropy']:
                prop_dict[k] = np.round(v, 2)
            else:
                prop_dict[k] = np.round(v, 0)
        elastic_doc.update(prop_dict)
        # Update with state and warnings
        state, warnings = get_state_and_warnings(elastic_doc)
        elastic_doc.update({"state": state, "warnings": warnings})
        # TODO: add kpoints params?
        return elastic_doc
    else:
        logger.info("insufficient valid strains for {}".format(
            opt_task['formula_pretty']))
        return None
示例#29
0
    def run_task(self, fw_spec):
        ref_struct = self['structure']
        d = {"analysis": {}, "initial_structure": self['structure'].as_dict()}

        # Get optimized structure
        calc_locs_opt = [
            cl for cl in fw_spec.get('calc_locs', [])
            if 'optimiz' in cl['name']
        ]
        if calc_locs_opt:
            optimize_loc = calc_locs_opt[-1]['path']
            logger.info("Parsing initial optimization directory: {}".format(
                optimize_loc))
            drone = VaspDrone()
            optimize_doc = drone.assimilate(optimize_loc)
            opt_struct = Structure.from_dict(
                optimize_doc["calcs_reversed"][0]["output"]["structure"])
            d.update({"optimized_structure": opt_struct.as_dict()})
            ref_struct = opt_struct
            eq_stress = -0.1 * Stress(optimize_doc["calcs_reversed"][0]
                                      ["output"]["ionic_steps"][-1]["stress"])
        else:
            eq_stress = None

        if self.get("fw_spec_field"):
            d.update({
                self.get("fw_spec_field"):
                fw_spec.get(self.get("fw_spec_field"))
            })

        # Get the stresses, strains, deformations from deformation tasks
        defo_dicts = fw_spec["deformation_tasks"].values()
        stresses, strains, deformations = [], [], []
        for defo_dict in defo_dicts:
            stresses.append(Stress(defo_dict["stress"]))
            strains.append(Strain(defo_dict["strain"]))
            deformations.append(Deformation(defo_dict["deformation_matrix"]))
            # Add derived stresses and strains if symmops is present
            for symmop in defo_dict.get("symmops", []):
                stresses.append(Stress(defo_dict["stress"]).transform(symmop))
                strains.append(Strain(defo_dict["strain"]).transform(symmop))
                deformations.append(
                    Deformation(
                        defo_dict["deformation_matrix"]).transform(symmop))

        stresses = [-0.1 * s for s in stresses]
        pk_stresses = [
            stress.piola_kirchoff_2(deformation)
            for stress, deformation in zip(stresses, deformations)
        ]

        d['fitting_data'] = {
            'cauchy_stresses': stresses,
            'eq_stress': eq_stress,
            'strains': strains,
            'pk_stresses': pk_stresses,
            'deformations': deformations
        }

        logger.info("Analyzing stress/strain data")
        # TODO: @montoyjh: what if it's a cubic system? don't need 6. -computron
        # TODO: Can add population method but want to think about how it should
        #           be done. -montoyjh
        order = self.get('order', 2)
        if order > 2:
            method = 'finite_difference'
        else:
            method = self.get('fitting_method', 'finite_difference')

        if method == 'finite_difference':
            result = ElasticTensorExpansion.from_diff_fit(strains,
                                                          pk_stresses,
                                                          eq_stress=eq_stress,
                                                          order=order)
            if order == 2:
                result = ElasticTensor(result[0])
        elif method == 'pseudoinverse':
            result = ElasticTensor.from_pseudoinverse(strains, pk_stresses)
        elif method == 'independent':
            result = ElasticTensor.from_independent_strains(
                strains, pk_stresses, eq_stress=eq_stress)
        else:
            raise ValueError(
                "Unsupported method, method must be finite_difference, "
                "pseudoinverse, or independent")

        ieee = result.convert_to_ieee(ref_struct)
        d.update({
            "elastic_tensor": {
                "raw": result.voigt,
                "ieee_format": ieee.voigt
            }
        })
        if order == 2:
            d.update({
                "derived_properties":
                ieee.get_structure_property_dict(ref_struct)
            })
        else:
            soec = ElasticTensor(ieee[0])
            d.update({
                "derived_properties":
                soec.get_structure_property_dict(ref_struct)
            })

        d["formula_pretty"] = ref_struct.composition.reduced_formula
        d["fitting_method"] = method
        d["order"] = order

        d = jsanitize(d)

        # Save analysis results in json or db
        db_file = env_chk(self.get('db_file'), fw_spec)
        if not db_file:
            with open("elasticity.json", "w") as f:
                f.write(json.dumps(d, default=DATETIME_HANDLER))
        else:
            db = VaspCalcDb.from_db_file(db_file, admin=True)
            db.collection = db.db["elasticity"]
            db.collection.insert_one(d)
            logger.info("Elastic analysis complete.")

        return FWAction()
示例#30
0
def process_elastic_calcs(opt_doc, defo_docs, add_derived=True, tol=0.002):
    """
    Generates the list of calcs from deformation docs, along with 'derived
    stresses', i. e. stresses derived from symmop transformations of existing
    calcs from transformed strains resulting in an independent strain
    not in the input list

    Args:
        opt_doc (dict): document for optimization task
        defo_docs ([dict]) list of documents for deformation tasks
        add_derived (bool): flag for whether or not to add derived
            stress-strain pairs based on symmetry
        tol (float): tolerance for assigning equivalent stresses/strains

    Returns ([dict], [dict]):
        Two lists of summary documents corresponding to strains
        and stresses, one explicit and one derived
    """
    structure = Structure.from_dict(opt_doc['output']['structure'])
    input_structure = Structure.from_dict(opt_doc['input']['structure'])

    # Process explicit calcs, store in dict keyed by strain
    explicit_calcs = TensorMapping()
    for doc in defo_docs:
        calc = {
            "type": "explicit",
            "input": doc["input"],
            "output": doc["output"],
            "task_id": doc["task_id"],
            "completed_at": doc["completed_at"]
        }
        deformed_structure = Structure.from_dict(doc['output']['structure'])
        defo = Deformation(calculate_deformation(structure,
                                                 deformed_structure))
        # Warning if deformation is not equivalent to stored deformation
        stored_defo = doc['transmuter']['transformation_params'][0]\
            ['deformation']
        if not np.allclose(defo, stored_defo, atol=1e-5):
            wmsg = "Inequivalent stored and calc. deformations."
            logger.debug(wmsg)
            calc["warnings"] = wmsg
        cauchy_stress = -0.1 * Stress(doc['output']['stress'])
        pk_stress = cauchy_stress.piola_kirchoff_2(defo)
        strain = defo.green_lagrange_strain
        calc.update({
            "deformation": defo,
            "cauchy_stress": cauchy_stress,
            "strain": strain,
            "pk_stress": pk_stress
        })
        if strain in explicit_calcs:
            existing_value = explicit_calcs[strain]
            if doc['completed_at'] > existing_value['completed_at']:
                explicit_calcs[strain] = calc
        else:
            explicit_calcs[strain] = calc

    if not add_derived:
        return explicit_calcs.values(), None

    # Determine all of the implicit calculations to include
    sga = SpacegroupAnalyzer(structure, symprec=0.1)
    symmops = sga.get_symmetry_operations(cartesian=True)
    derived_calcs_by_strain = TensorMapping(tol=0.002)
    for strain, calc in explicit_calcs.items():
        # Generate all transformed strains
        task_id = calc['task_id']
        tstrains = [(symmop, strain.transform(symmop)) for symmop in symmops]
        # Filter strains by those which are independent and new
        # For second order
        if len(explicit_calcs) < 30:
            tstrains = [(symmop, tstrain) for symmop, tstrain in tstrains
                        if tstrain.get_deformation_matrix().is_independent(tol)
                        and not tstrain in explicit_calcs]
        # For third order
        else:
            strain_states = get_default_strain_states(3)
            # Default stencil in atomate, this maybe shouldn't be hard-coded
            stencil = np.linspace(-0.075, 0.075, 7)
            valid_strains = [
                Strain.from_voigt(s * np.array(strain_state))
                for s, strain_state in product(stencil, strain_states)
            ]
            valid_strains = [v for v in valid_strains if not np.allclose(v, 0)]
            valid_strains = TensorMapping(valid_strains,
                                          [True] * len(valid_strains))
            tstrains = [
                (symmop, tstrain) for symmop, tstrain in tstrains
                if tstrain in valid_strains and not tstrain in explicit_calcs
            ]
        # Add surviving tensors to derived_strains dict
        for symmop, tstrain in tstrains:
            # curr_set = derived_calcs_by_strain[tstrain]
            if tstrain in derived_calcs_by_strain:
                curr_set = derived_calcs_by_strain[tstrain]
                curr_task_ids = [c[1] for c in curr_set]
                if task_id not in curr_task_ids:
                    curr_set.append((symmop, calc['task_id']))
            else:
                derived_calcs_by_strain[tstrain] = [(symmop, calc['task_id'])]

    # Process derived calcs
    explicit_calcs_by_id = {d['task_id']: d for d in explicit_calcs.values()}
    derived_calcs = []
    for strain, calc_set in derived_calcs_by_strain.items():
        symmops, task_ids = zip(*calc_set)
        task_strains = [
            Strain(explicit_calcs_by_id[task_id]['strain'])
            for task_id in task_ids
        ]
        task_stresses = [
            explicit_calcs_by_id[task_id]['cauchy_stress']
            for task_id in task_ids
        ]
        derived_strains = [
            tstrain.transform(symmop)
            for tstrain, symmop in zip(task_strains, symmops)
        ]
        for derived_strain in derived_strains:
            if not np.allclose(derived_strain, strain, atol=2e-3):
                logger.info("Issue with derived strains")
                raise ValueError("Issue with derived strains")
        derived_stresses = [
            tstress.transform(sop)
            for sop, tstress in zip(symmops, task_stresses)
        ]
        input_docs = [{
            "task_id": task_id,
            "strain": task_strain,
            "cauchy_stress": task_stress,
            "symmop": symmop
        } for task_id, task_strain, task_stress, symmop in zip(
            task_ids, task_strains, task_stresses, symmops)]
        calc = {
            "strain": strain,
            "cauchy_stress": Stress(np.average(derived_stresses, axis=0)),
            "deformation": strain.get_deformation_matrix(),
            "input_tasks": input_docs,
            "type": "derived"
        }
        calc['pk_stress'] = calc['cauchy_stress'].piola_kirchoff_2(
            calc['deformation'])
        derived_calcs.append(calc)

    return list(explicit_calcs.values()), derived_calcs