def test_from_pseudoinverse(self): strain_list = [Strain.from_deformation(def_matrix) for def_matrix in self.def_stress_dict['deformations']] stress_list = [stress for stress in self.def_stress_dict['stresses']] with warnings.catch_warnings(record=True): et_fl = -0.1*ElasticTensor.from_pseudoinverse(strain_list, stress_list).voigt self.assertArrayAlmostEqual(et_fl.round(2), [[59.29, 24.36, 22.46, 0, 0, 0], [28.06, 56.91, 22.46, 0, 0, 0], [28.06, 25.98, 54.67, 0, 0, 0], [0, 0, 0, 26.35, 0, 0], [0, 0, 0, 0, 26.35, 0], [0, 0, 0, 0, 0, 26.35]])
def test_from_pseudoinverse(self): strain_list = [Strain.from_deformation(def_matrix) for def_matrix in self.def_stress_dict['deformations']] stress_list = [stress for stress in self.def_stress_dict['stresses']] with warnings.catch_warnings(record=True): et_fl = -0.1 * ElasticTensor.from_pseudoinverse(strain_list, stress_list).voigt self.assertArrayAlmostEqual(et_fl.round(2), [[59.29, 24.36, 22.46, 0, 0, 0], [28.06, 56.91, 22.46, 0, 0, 0], [28.06, 25.98, 54.67, 0, 0, 0], [0, 0, 0, 26.35, 0, 0], [0, 0, 0, 0, 26.35, 0], [0, 0, 0, 0, 0, 26.35]])
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 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']) d_structs = [ Structure.from_dict(d['output']['structure']) for d in defo_tasks ] defos = [ calculate_deformation(opt_struct, def_structure) for def_structure in d_structs ] # Warning if deformation is not equivalent to stored deformation stored_defos = [d['transmuter']['transformation_params'][0]\ ['deformation'] for d in defo_tasks] if not np.allclose(defos, stored_defos, atol=1e-5): wmsg = "Inequivalent stored and calc. deformations." logger.warning(wmsg) elastic_doc["warnings"].append(wmsg) # Collect all fitting data and task ids defos = [Deformation(d) for d in defos] strains = [d.green_lagrange_strain for d in defos] vasp_stresses = [d['output']['stress'] for d in defo_tasks] cauchy_stresses = [-0.1 * Stress(s) for s in vasp_stresses] pk_stresses = [ Stress(s.piola_kirchoff_2(d)) for s, d in zip(cauchy_stresses, defos) ] defo_task_ids = [d['task_id'] for d in defo_tasks] # Determine whether data is sufficient to fit tensor # If raw data is insufficient but can be symmetrically transformed # to provide a sufficient set, use the expanded set with appropriate # symmetry transformations, fstresses/strains are "fitting # strains" below. vstrains = [s.voigt for s in strains] if np.linalg.matrix_rank(vstrains) < 6: symmops = SpacegroupAnalyzer(opt_struct).get_symmetry_operations() fstrains = [[s.transform(symmop) for symmop in symmops] for s in strains] fstrains = list(chain.from_iterable(fstrains)) vfstrains = [s.voigt for s in fstrains] if not np.linalg.matrix_rank(vfstrains) == 6: logger.warning("Insufficient data to form SOEC") elastic_doc['warnings'].append("insufficient strains") return None else: fstresses = [[s.transform(symmop) for symmop in symmops] for s in pk_stresses] fstresses = list(chain.from_iterable(fstresses)) else: fstrains = strains fstresses = pk_stresses with warnings.catch_warnings(): warnings.simplefilter('ignore') # TODO: more intelligently determine if independent # strain fitting can be done if len(cauchy_stresses) == 24: elastic_doc['legacy_fit'] = legacy_fit(strains, cauchy_stresses) et_raw = ElasticTensor.from_pseudoinverse(fstrains, fstresses) et = et_raw.voigt_symmetrized.convert_to_ieee(opt_struct) defo_tasks = sorted(defo_tasks, key=lambda x: x['completed_at']) vasp_input = opt_task['input'] vasp_input.pop('structure') elastic_doc.update({ "deformation_task_ids": defo_task_ids, "optimization_task_id": opt_task['task_id'], "pk_stresses": pk_stresses, "cauchy_stresses": cauchy_stresses, "strains": strains, "deformations": defos, "elastic_tensor": et.voigt, "elastic_tensor_raw": et_raw.voigt, "optimized_structure": opt_struct, "completed_at": defo_tasks[-1]['completed_at'], "optimization_input": vasp_input }) # Process input elastic_doc['warnings'] = get_warnings(et, opt_struct) or None #TODO: process MPWorks metadata? #TODO: higher order #TODO: add some of the relevant DFT params, kpoints elastic_doc['state'] = "filter_failed" if elastic_doc['warnings']\ else "successful" return elastic_doc
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()