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)
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)
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)
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
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]])
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
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
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
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")
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]])
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"])
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"]]
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
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"])
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)
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
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"])
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)
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
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)
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)))
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)
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)
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
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])
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()
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']]
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
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()
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