def eos_fit(self, eos_name="murnaghan"): """ Fit E(V) For the list of available models, see EOS.MODELS TODO: which default? all should return a list of fits """ # Read volumes and energies from the GSR files. energies, volumes = [], [] for label, gsr in self: energies.append(gsr.energy) volumes.append(gsr.structure.volume) # Note that eos.fit expects lengths in Angstrom, and energies in eV. if eos_name != "all": return EOS(eos_name=eos_name).fit(volumes, energies) else: # Use all the available models. fits, rows = [], [] for eos_name in EOS.MODELS: fit = EOS(eos_name=eos_name).fit(volumes, energies) fits.append(fit) rows.append(fit.results) import pandas as pd frame = pd.DataFrame(rows, index=EOS.MODELS, columns=list(rows[0].keys())) return fits, frame
def getnwrite_eosdata(self, write_json=True): """ This method is called when all tasks reach S_OK. It reads the energies and the volumes from the GSR file, computes the EOS and produce a JSON file `eos_data.json` in outdata. """ energies_ev, volumes = [], [] for task in self: with task.open_gsr() as gsr: volumes.append(float(gsr.structure.volume)) energies_ev.append(float(gsr.energy)) from pymatgen.analysis.eos import EOS eos_data = { "input_volumes_ang3": self.input_volumes, "volumes_ang3": volumes, "energies_ev": energies_ev } for model in EOS.MODELS: if model in ("deltafactor", "numerical_eos"): continue try: fit = EOS(model).fit(volumes, energies_ev) eos_data[model] = {k: float(v) for k, v in fit.results.items()} except Exception as exc: eos_data[model] = {"exception": str(exc)} if write_json: with open(self.outdir.path_in("eos_data.json"), "wt") as fh: json.dump(eos_data, fh, indent=4, sort_keys=True) return eos_data
def __init__(self, energies, volumes, structure, t_min=5, t_step=5, t_max=2000.0, eos="vinet", poisson=0.363615, gruneisen=True, bp2gru=1., mass_average_mode='arithmetic'): self.energies = energies self.volumes = volumes self.structure = structure self.temperatures = np.arange(t_min, t_max+t_step, t_step) self.eos_name = eos self.poisson = poisson self.bp2gru = bp2gru self.gruneisen = gruneisen self.natoms = self.structure.composition.num_atoms self.kb = physical_constants["Boltzmann constant in eV/K"][0] # calculate the average masses masses = np.array([e.atomic_mass for e in self.structure.species]) * physical_constants["atomic mass constant"][0] if mass_average_mode == 'arithmetic': self.avg_mass = np.mean(masses) elif mass_average_mode == 'geometric': self.avg_mass = gmean(masses) else: raise ValueError("DebyeModel mass_average_mode must be either 'arithmetic' or 'geometric'") # fit E and V and get the bulk modulus(used to compute the Debye temperature) self.eos = EOS(eos) self.ev_eos_fit = self.eos.fit(volumes, energies) self.bulk_modulus = self.ev_eos_fit.b0_GPa # in GPa self.calculate_F_el()
def birch_murnaghan(self, title=None): """ Fit E,V data with Birch-Murnaghan EOS Parameters ---------- title : (str) Plot title Returns ------- plt : Matplotlib object. eos_fit : Pymatgen EosBase object. """ V, E = [], [] for j in self.jobs: V.append(j.initial_structure.lattice.volume) E.append(j.final_energy) eos = EOS(eos_name='birch_murnaghan') eos_fit = eos.fit(V, E) eos_fit.plot(width=10, height=10, text='', markersize=15, label='Birch-Murnaghan fit') plt.legend(loc=2, prop={'size': 20}) if title: plt.title(title, size=25) plt.tight_layout() return plt, eos_fit
def set_eos(self, eos_name): """ Updates the EOS used for the fit. Args: eos_name: string indicating the expression used to fit the energies. See pymatgen.analysis.eos.EOS. """ self.eos = EOS(eos_name)
def test_run_all_models(self): # these have been checked for plausibility, # but are not benchmarked against independently known values test_output = { "birch": { "b0": 0.5369258244952931, "b1": 4.178644231838501, "e0": -10.8428039082307, "v0": 40.98926572870838, }, "birch_murnaghan": { "b0": 0.5369258245417454, "b1": 4.178644235500821, "e0": -10.842803908240892, "v0": 40.98926572528106, }, "deltafactor": { "b0": 0.5369258245611414, "b1": 4.178644231924639, "e0": -10.842803908299294, "v0": 40.989265727927936, }, "murnaghan": { "b0": 0.5144967693786603, "b1": 3.9123862262572264, "e0": -10.836794514626673, "v0": 41.13757930387086, }, "numerical_eos": { "b0": 0.5557257614101998, "b1": 4.344039148405489, "e0": -10.847490826530702, "v0": 40.857200064982536, }, "pourier_tarantola": { "b0": 0.5667729960804602, "b1": 4.331688936974368, "e0": -10.851486685041658, "v0": 40.86770643373908, }, "vinet": { "b0": 0.5493839425156859, "b1": 4.3051929654936885, "e0": -10.846160810560756, "v0": 40.916875663779784, }, } for eos_name in EOS.MODELS: eos = EOS(eos_name=eos_name) _ = eos.fit(self.volumes, self.energies) for param in ("b0", "b1", "e0", "b0"): # TODO: solutions only stable to 2 decimal places # between different machines, this seems far too low? self.assertArrayAlmostEqual(_.results[param], test_output[eos_name][param], decimal=1)
def get_eos_fits_dataframe(self, eos_names="murnaghan"): """ Fit energy as function of volume to get the equation of state, equilibrium volume, bulk modulus and its derivative wrt to pressure. Args: eos_names: String or list of strings with EOS names. For the list of available models, see pymatgen.analysis.eos. Return: (fits, dataframe) namedtuple. fits is a list of ``EOSFit object`` dataframe is a |pandas-DataFrame| with the final results. """ # Read volumes and energies from the GSR files. energies, volumes = [], [] for label, gsr in self.items(): energies.append(float(gsr.energy)) volumes.append(float(gsr.structure.volume)) # Order data by volumes if needed. if np.any(np.diff(volumes) < 0): ves = sorted(zip(volumes, energies), key=lambda t: t[0]) volumes = [t[0] for t in ves] energies = [t[1] for t in ves] # Note that eos.fit expects lengths in Angstrom, and energies in eV. # I'm also monkey-patching the plot method. from pymatgen.analysis.eos import EOS if eos_names == "all": # Use all the available models. eos_names = [ n for n in EOS.MODELS if n not in ("deltafactor", "numerical_eos") ] else: eos_names = list_strings(eos_names) fits, index, rows = [], [], [] for eos_name in eos_names: try: fit = EOS(eos_name=eos_name).fit(volumes, energies) except Exception as exc: cprint("EOS %s raised exception:\n%s" % (eos_name, str(exc))) continue # Replace plot with plot_ax method fit.plot = fit.plot_ax fits.append(fit) index.append(eos_name) rows.append( OrderedDict([(aname, getattr(fit, aname)) for aname in ("v0", "e0", "b0_GPa", "b1")])) dataframe = pd.DataFrame( rows, index=index, columns=list(rows[0].keys()) if rows else None) return dict2namedtuple(fits=fits, dataframe=dataframe)
def gibbs_minimizer(energies, volumes, mass, natoms, temperature=298.0, pressure=0, poisson=0.25, eos="murnaghan"): """ Fit the input energies and volumes to the equation of state to obtain the bulk modulus which is subsequently used to obtain the debye temperature. The debye temperature is then used to compute the vibrational free energy and the gibbs free energy as a function of volume, temperature and pressure. A second fit is preformed to get the functional form of gibbs free energy:(G, V, T, P). Finally G(V, P, T) is minimized with respect to V and the optimum value of G evaluated at V_opt, G_opt(V_opt, T, P), is returned. Args: energies (list): list of energies volumes (list): list of volumes mass (float): total mass natoms (int): number of atoms temperature (float): temperature in K pressure (float): pressure in GPa poisson (float): poisson ratio eos (str): name of the equation of state supported by pymatgen. See pymatgen.analysis.eos.py Returns: float: gibbs free energy at the given temperature and pressure minimized wrt volume. """ try: from scipy.optimize import minimize from scipy.integrate import quadrature except ImportError: import sys print("Install scipy. Exiting.") sys.exit() integrator = quadrature eos = EOS(eos) eos_fit_1 = eos.fit(volumes, energies) G_V = [] for i, v in enumerate(volumes): debye = debye_temperature_gibbs(v, mass, natoms, eos_fit_1.b0_GPa, poisson=poisson) G_V.append(energies[i] + pressure * v + A_vib(temperature, debye, natoms, integrator)) # G(V, T, P) eos_fit_2 = eos.fit(volumes, G_V) params = eos_fit_2.eos_params.tolist() # G_opt(V_opt, T, P) return params[0]
def run_task(self, fw_spec): from pymatgen.analysis.eos import EOS eos = self.get("eos", "vinet") tag = self["tag"] db_file = env_chk(self.get("db_file"), fw_spec) summary_dict = {"eos": eos} to_db = self.get("to_db", True) # collect and store task_id of all related tasks to make unique links with "tasks" collection all_task_ids = [] mmdb = VaspCalcDb.from_db_file(db_file, admin=True) # get the optimized structure d = mmdb.collection.find_one({"task_label": "{} structure optimization".format(tag)}) all_task_ids.append(d["task_id"]) structure = Structure.from_dict(d["calcs_reversed"][-1]["output"]['structure']) summary_dict["structure"] = structure.as_dict() summary_dict["formula_pretty"] = structure.composition.reduced_formula # get the data(energy, volume, force constant) from the deformation runs docs = mmdb.collection.find({"task_label": {"$regex": "{} bulk_modulus*".format(tag)}, "formula_pretty": structure.composition.reduced_formula}) energies = [] volumes = [] for d in docs: s = Structure.from_dict(d["calcs_reversed"][-1]["output"]['structure']) energies.append(d["calcs_reversed"][-1]["output"]['energy']) volumes.append(s.volume) all_task_ids.append(d["task_id"]) summary_dict["energies"] = energies summary_dict["volumes"] = volumes summary_dict["all_task_ids"] = all_task_ids # fit the equation of state eos = EOS(eos) eos_fit = eos.fit(volumes, energies) summary_dict["bulk_modulus"] = eos_fit.b0_GPa # TODO: find a better way for passing tags of the entire workflow to db - albalu if fw_spec.get("tags", None): summary_dict["tags"] = fw_spec["tags"] summary_dict["results"] = dict(eos_fit.results) summary_dict["created_at"] = datetime.utcnow() # db_file itself is required but the user can choose to pass the results to db or not if to_db: mmdb.collection = mmdb.db["eos"] mmdb.collection.insert_one(summary_dict) else: with open("bulk_modulus.json", "w") as f: f.write(json.dumps(summary_dict, default=DATETIME_HANDLER)) # TODO: @matk86 - there needs to be a builder to put it into materials collection... -computron logger.info("Bulk modulus calculation complete.")
def __init__(self, energies, volumes, structure, t_min=300.0, t_step=100, t_max=300.0, eos="vinet", pressure=0.0, poisson=0.25, use_mie_gruneisen=False, anharmonic_contribution=False): """ Args: energies (list): list of DFT energies in eV volumes (list): list of volumes in Ang^3 structure (Structure): t_min (float): min temperature t_step (float): temperature step t_max (float): max temperature eos (str): equation of state used for fitting the energies and the volumes. options supported by pymatgen: "quadratic", "murnaghan", "birch", "birch_murnaghan", "pourier_tarantola", "vinet", "deltafactor", "numerical_eos" pressure (float): in GPa, optional. poisson (float): poisson ratio. use_mie_gruneisen (bool): whether or not to use the mie-gruneisen formulation to compute the gruneisen parameter. The default is the slater-gamma formulation. anharmonic_contribution (bool): whether or not to consider the anharmonic contribution to the Debye temperature. Cannot be used with use_mie_gruneisen. Defaults to False. """ self.energies = energies self.volumes = volumes self.structure = structure self.temperature_min = t_min self.temperature_max = t_max self.temperature_step = t_step self.eos_name = eos self.pressure = pressure self.poisson = poisson self.use_mie_gruneisen = use_mie_gruneisen self.anharmonic_contribution = anharmonic_contribution if self.use_mie_gruneisen and self.anharmonic_contribution: raise ValueError('The Mie-Gruneisen formulation and anharmonic contribution are circular referenced and ' 'cannot be used together.') self.mass = sum([e.atomic_mass for e in self.structure.species]) self.natoms = self.structure.composition.num_atoms self.avg_mass = physical_constants["atomic mass constant"][0] * self.mass / self.natoms # kg self.kb = physical_constants["Boltzmann constant in eV/K"][0] self.hbar = physical_constants["Planck constant over 2 pi in eV s"][0] self.gpa_to_ev_ang = 1./160.21766208 # 1 GPa in ev/Ang^3 self.gibbs_free_energy = [] # optimized values, eV # list of temperatures for which the optimized values are available, K self.temperatures = [] self.optimum_volumes = [] # in Ang^3 # fit E and V and get the bulk modulus(used to compute the Debye # temperature) logger.info("Fitting E and V") self.eos = EOS(eos) self.ev_eos_fit = self.eos.fit(volumes, energies) self.bulk_modulus = self.ev_eos_fit.b0_GPa # in GPa self.optimize_gibbs_free_energy()
def OnFitButton(self, event): model = self.model_choice.GetStringSelection() try: eos = EOS(eos_name=model) fit = eos.fit(self.volumes, self.energies, vol_unit=self.vol_unit, ene_unit=self.ene_unit) print(fit) fit.plot() except: awx.showErrorMessage(self)
def __init__(self, structures, energies, eos_name='vinet', pressure=0): """ Args: structures: list of structures at different volumes. energies: list of SCF energies for the structures in eV. eos_name: string indicating the expression used to fit the energies. See pymatgen.analysis.eos.EOS. pressure: value of the pressure in GPa that will be considered in the p*V contribution to the energy. """ self.structures = structures self.energies = np.array(energies) self.eos = EOS(eos_name) self.pressure = pressure self.volumes = np.array([s.volume for s in structures]) self.iv0 = np.argmin(energies)
def test_eosfit_stderr(): volume = [ 64.26025658624827, 66.6402902061661, 69.02026278463558, 71.40028395651238, 73.7802955243384, 76.16031916837127, 78.5402791170494, 80.94268976633485 ] energy = [ -34.69037673, -34.88126365, -34.98844492, -35.02444959, -34.99974727, -34.92873799, -34.8383195, -34.69550342 ] eos = EOS('vinet') eos_fit = eos.fit(volume, energy) fit_value = eos_fit.func(volume) print(fit_value) stderr = eosfit_stderr(eos_fit, volume, energy) assert (stderr == pytest.approx(1.18844e-5, abs=1e-7))
def __init__(self, energies, volumes, structure, t_min=300.0, t_step=100, t_max=300.0, eos="vinet", pressure=0.0, poisson=0.25, use_mie_gruneisen=False, anharmonic_contribution=False): self.energies = energies self.volumes = volumes self.structure = structure self.temperature_min = t_min self.temperature_max = t_max self.temperature_step = t_step self.eos_name = eos self.pressure = pressure self.poisson = poisson self.use_mie_gruneisen = use_mie_gruneisen self.anharmonic_contribution = anharmonic_contribution if self.use_mie_gruneisen and self.anharmonic_contribution: raise ValueError( 'The Mie-Gruneisen formulation and anharmonic contribution are circular referenced and cannot be used together.' ) self.mass = sum([e.atomic_mass for e in self.structure.species]) self.natoms = self.structure.composition.num_atoms self.avg_mass = physical_constants["atomic mass constant"][0] \ * self.mass / self.natoms # kg self.kb = physical_constants["Boltzmann constant in eV/K"][0] self.hbar = physical_constants["Planck constant over 2 pi in eV s"][0] self.gpa_to_ev_ang = 1. / 160.21766208 # 1 GPa in ev/Ang^3 self.gibbs_free_energy = [] # optimized values, eV # list of temperatures for which the optimized values are available, K self.temperatures = [] self.optimum_volumes = [] # in Ang^3 # fit E and V and get the bulk modulus(used to compute the Debye # temperature) print("Fitting E and V") self.eos = EOS(eos) self.ev_eos_fit = self.eos.fit(volumes, energies) self.bulk_modulus = self.ev_eos_fit.b0_GPa # in GPa self.optimize_gibbs_free_energy()
def __init__(self, energies, volumes, structure, dos_objects=None, F_vib=None, S_vib=None, C_vib=None, t_min=5, t_step=5, t_max=2000.0, eos="vinet", pressure=0.0, poisson=0.25, bp2gru=1., vib_kwargs=None): self.energies = np.array(energies) self.volumes = np.array(volumes) self.natoms = len(structure) self.temperatures = np.arange(t_min, t_max+t_step, t_step) self.eos_name = eos self.pressure = pressure self.gpa_to_ev_ang = 1./160.21766208 # 1 GPa in ev/Ang^3 self.eos = EOS(eos) # get the vibrational properties as a function of V and T if F_vib is None: # use the Debye model vib_kwargs = vib_kwargs or {} debye_model = DebyeModel(energies, volumes, structure, t_min=t_min, t_step=t_step, t_max=t_max, eos=eos, poisson=poisson, bp2gru=bp2gru, **vib_kwargs) self.F_vib = debye_model.F_vib # vibrational free energy as a function of volume and temperature self.S_vib = debye_model.S_vib # vibrational entropy as a function of volume and temperature self.C_vib = debye_model.C_vib # vibrational heat capacity as a function of volume and temperature self.D_vib = debye_model.D_vib # Debye temperature else: self.F_vib = F_vib self.S_vib = S_vib self.C_vib = C_vib # get the electronic properties as a function of V and T if dos_objects: # we set natom to 1 always because we want the property per formula unit here. thermal_electronic_props = [calculate_thermal_electronic_contribution(dos, t0=t_min, t1=t_max, td=t_step, natom=1) for dos in dos_objects] self.F_el = [p['free_energy'] for p in thermal_electronic_props] else: self.F_el = np.zeros((self.volumes.size, self.temperatures.size)) # Set up the array of Gibbs energies # G = E_0(V) + F_vib(V,T) + F_el(V,T) + PV self.G = self.energies[:, np.newaxis] + self.F_vib + self.F_el + self.pressure * self.volumes[:, np.newaxis] * self.gpa_to_ev_ang # set up the final variables of the optimized Gibbs energies self.gibbs_free_energy = [] # optimized values, eV self.optimum_volumes = [] # in Ang^3 self.optimize_gibbs_free_energy()
def run_task(self, fw_spec): from pymatgen.analysis.eos import EOS tag = self["tag"] db_file = env_chk(self.get("db_file"), fw_spec) summary_dict = {"eos": self["eos"]} mmdb = MMVaspDb.from_db_file(db_file, admin=True) # get the optimized structure d = mmdb.collection.find_one( {"task_label": "{} structure optimization".format(tag)}) structure = Structure.from_dict( d["calcs_reversed"][-1]["output"]['structure']) summary_dict["structure"] = structure.as_dict() # get the data(energy, volume, force constant) from the deformation runs docs = mmdb.collection.find({ "task_label": { "$regex": "{} bulk_modulus*".format(tag) }, "formula_pretty": structure.composition.reduced_formula }) energies = [] volumes = [] for d in docs: s = Structure.from_dict( d["calcs_reversed"][-1]["output"]['structure']) energies.append(d["calcs_reversed"][-1]["output"]['energy']) volumes.append(s.volume) summary_dict["energies"] = energies summary_dict["volumes"] = volumes # fit the equation of state eos = EOS(self["eos"]) eos_fit = eos.fit(volumes, energies) summary_dict["results"] = dict(eos_fit.results) with open("bulk_modulus.json", "w") as f: f.write(json.dumps(summary_dict, default=DATETIME_HANDLER)) logger.info("BULK MODULUS CALCULATION COMPLETE")
def test_eos_func(self): # list vs np.array arguments np.testing.assert_almost_equal(self.num_eos_fit.func([0, 1, 2]), self.num_eos_fit.func(np.array([0, 1, 2])), decimal=10) # func vs _func np.testing.assert_almost_equal(self.num_eos_fit.func(0.), self.num_eos_fit._func( 0., self.num_eos_fit.eos_params), decimal=10) # test the eos function: energy = f(volume) # numerical eos evaluated at volume=0 == a0 of the fit polynomial np.testing.assert_almost_equal(self.num_eos_fit.func(0.), self.num_eos_fit.eos_params[-1], decimal=6) birch_eos = EOS(eos_name="birch") birch_eos_fit = birch_eos.fit(self.volumes, self.energies) # birch eos evaluated at v0 == e0 np.testing.assert_almost_equal(birch_eos_fit.func(birch_eos_fit.v0), birch_eos_fit.e0, decimal=6)
def setUp(self): # Si data from Cormac self.volumes = [ 25.987454833, 26.9045702104, 27.8430241908, 28.8029649591, 29.7848370694, 30.7887887064, 31.814968055, 32.8638196693, 33.9353435494, 35.0299842495, 36.1477417695, 37.2892088485, 38.4543854865, 39.6437162376, 40.857201102, 42.095136449, 43.3579668329, 44.6456922537, 45.9587572656, 47.2973100535, 48.6614988019, 50.0517680652, 51.4682660281, 52.9112890601, 54.3808371612, 55.8775030703, 57.4014349722, 58.9526328669 ] self.energies = [ -7.63622156576, -8.16831294894, -8.63871612686, -9.05181213218, -9.41170988374, -9.72238224345, -9.98744832526, -10.210309552, -10.3943401353, -10.5427238068, -10.6584266073, -10.7442240979, -10.8027285713, -10.8363890521, -10.8474912964, -10.838157792, -10.8103477586, -10.7659387815, -10.7066179666, -10.6339907853, -10.5495538639, -10.4546677714, -10.3506386542, -10.2386366017, -10.1197772808, -9.99504030111, -9.86535084973, -9.73155247952 ] num_eos = EOS(eos_name="numerical_eos") self.num_eos_fit = num_eos.fit(self.volumes, self.energies)
def run_task(self, fw_spec): db_file = env_chk(self.get("db_file"), fw_spec) tag = self["tag"] vasp_db = VaspCalcDb.from_db_file(db_file, admin=True) static_calculations = vasp_db.collection.find({"metadata.tag": tag}) energies = [] volumes = [] structure = None # single Structure for QHA calculation for calc in static_calculations: energies.append(calc['output']['energy']) volumes.append(calc['output']['structure']['lattice']['volume']) if structure is None: structure = Structure.from_dict(calc['output']['structure']) eos = EOS(self.get('eos')) ev_eos_fit = eos.fit(volumes, energies) equil_volume = ev_eos_fit.v0 structure.scale_lattice(equil_volume) analysis_result = ev_eos_fit.results analysis_result['b0_GPa'] = float(ev_eos_fit.b0_GPa) analysis_result['structure'] = structure.as_dict() analysis_result[ 'formula_pretty'] = structure.composition.reduced_formula analysis_result['metadata'] = self.get('metadata', {}) analysis_result['energies'] = energies analysis_result['volumes'] = volumes # write to JSON for debugging purposes import json with open('eos_summary.json', 'w') as fp: json.dump(analysis_result, fp) vasp_db.db['eos'].insert_one(analysis_result)
def test_run_all_models(self): for eos_name in EOS.MODELS: eos = EOS(eos_name=eos_name) _ = eos.fit(self.volumes, self.energies)
def analyze_eos_flow(flow, **kwargs): work = flow[0] etotals = work.read_etotals(unit="eV") eos_fit = EOS(eos_name="birch_murnaghan").fit(flow.volumes, etotals) return eos_fit.plot(**kwargs)
def test_fitting(self): # courtesy of @katherinelatimer2013 # known correct values for Vinet # Mg mp153_volumes = [ 16.69182365, 17.25441763, 17.82951915, 30.47573817, 18.41725977, 29.65211363, 28.84346369, 19.01777055, 28.04965916, 19.63120886, 27.27053682, 26.5059864, 20.25769112, 25.75586879, 20.89736201, 25.02003097, 21.55035204, 24.29834347, 22.21681221, 23.59066888, 22.89687316, ] mp153_energies = [ -1.269884575, -1.339411225, -1.39879471, -1.424480995, -1.44884184, -1.45297499, -1.4796246, -1.49033594, -1.504198485, -1.52397006, -1.5264432, -1.54609291, -1.550269435, -1.56284009, -1.569937375, -1.576420935, -1.583470925, -1.58647189, -1.591436505, -1.592563495, -1.594347355, ] mp153_known_energies_vinet = [ -1.270038831, -1.339366487, -1.398683238, -1.424556061, -1.448746649, -1.453000456, -1.479614511, -1.490266797, -1.504163502, -1.523910268, -1.526395734, -1.546038792, -1.550298657, -1.562800797, -1.570015274, -1.576368392, -1.583605186, -1.586404575, -1.591578378, -1.592547954, -1.594410995, ] # C: 4.590843262 # B: 2.031381599 mp153_known_e0_vinet = -1.594429229 mp153_known_v0_vinet = 22.95764159 eos = EOS(eos_name="vinet") fit = eos.fit(mp153_volumes, mp153_energies) np.testing.assert_array_almost_equal(fit.func(mp153_volumes), mp153_known_energies_vinet, decimal=5) self.assertAlmostEqual(mp153_known_e0_vinet, fit.e0, places=4) self.assertAlmostEqual(mp153_known_v0_vinet, fit.v0, places=4) # expt. value 35.5, known fit 36.16 self.assertAlmostEqual(fit.b0_GPa, 36.16258687442761, 4) # Si mp149_volumes = [ 15.40611854, 14.90378698, 16.44439516, 21.0636307, 17.52829835, 16.98058208, 18.08767363, 18.65882487, 19.83693435, 15.91961152, 22.33987173, 21.69548924, 22.99688883, 23.66666322, 20.44414922, 25.75374305, 19.24187473, 24.34931029, 25.04496106, 27.21116571, 26.4757653, ] mp149_energies = [ -4.866909695, -4.7120965, -5.10921253, -5.42036228, -5.27448405, -5.200810795, -5.331915665, -5.3744186, -5.420058145, -4.99862686, -5.3836163, -5.40610838, -5.353700425, -5.31714654, -5.425263555, -5.174988295, -5.403353105, -5.27481447, -5.227210275, -5.058992615, -5.118805775, ] mp149_known_energies_vinet = [ -4.866834585, -4.711786499, -5.109642598, -5.420093739, -5.274605844, -5.201025714, -5.331899365, -5.374315789, -5.419671568, -4.998827503, -5.383703409, -5.406038887, -5.353926272, -5.317484252, -5.424963418, -5.175090887, -5.403166824, -5.275096644, -5.227427635, -5.058639193, -5.118654229, ] # C: 4.986513158 # B: 4.964976215 mp149_known_e0_vinet = -5.424963506 mp149_known_v0_vinet = 20.44670279 eos = EOS(eos_name="vinet") fit = eos.fit(mp149_volumes, mp149_energies) np.testing.assert_array_almost_equal(fit.func(mp149_volumes), mp149_known_energies_vinet, decimal=5) self.assertAlmostEqual(mp149_known_e0_vinet, fit.e0, places=4) self.assertAlmostEqual(mp149_known_v0_vinet, fit.v0, places=4) # expt. value 97.9, known fit 88.39 self.assertAlmostEqual(fit.b0_GPa, 88.38629337404822, 4) # Ti mp72_volumes = [ 12.49233296, 12.91339188, 13.34380224, 22.80836212, 22.19195533, 13.78367177, 21.58675559, 14.23310328, 20.99266009, 20.4095592, 14.69220297, 19.83736385, 15.16106697, 19.2759643, 15.63980711, 18.72525771, 16.12851491, 18.18514127, 16.62729878, 17.65550599, 17.13626153, ] mp72_energies = [ -7.189983803, -7.33985647, -7.468745423, -7.47892835, -7.54945107, -7.578012237, -7.61513166, -7.66891898, -7.67549721, -7.73000681, -7.74290386, -7.77803379, -7.801246383, -7.818964483, -7.84488189, -7.85211192, -7.87486651, -7.876767777, -7.892161533, -7.892199957, -7.897605303, ] mp72_known_energies_vinet = [ -7.189911138, -7.339810181, -7.468716095, -7.478678021, -7.549402394, -7.578034391, -7.615240977, -7.669091347, -7.675683891, -7.730188653, -7.74314028, -7.778175824, -7.801363213, -7.819030923, -7.844878053, -7.852099741, -7.874737806, -7.876686864, -7.891937429, -7.892053535, -7.897414664, ] # C: 3.958192998 # B: 6.326790098 mp72_known_e0_vinet = -7.897414997 mp72_known_v0_vinet = 17.13223229 eos = EOS(eos_name="vinet") fit = eos.fit(mp72_volumes, mp72_energies) np.testing.assert_array_almost_equal(fit.func(mp72_volumes), mp72_known_energies_vinet, decimal=5) self.assertAlmostEqual(mp72_known_e0_vinet, fit.e0, places=4) self.assertAlmostEqual(mp72_known_v0_vinet, fit.v0, places=4) # expt. value 107.3, known fit 112.63 self.assertAlmostEqual(fit.b0_GPa, 112.62927187296167, 4)
def test_fit(self): """Test EOS fit""" for eos_name in EOS.MODELS: eos = EOS(eos_name=eos_name) fit = eos.fit(self.volumes, self.energies) print(fit)
def check_fit(self, volumes, energies): eos = EOS('vinet') self.eos_fit = eos.fit(volumes, energies)
def volume_workflow_is_converged(pwd, max_num_sub, min_num_vols, volume_tolerance): workflow_converged_list = [] E, V = [], [] minE = 100 for root, dirs, files in os.walk(pwd): for file in files: if file == 'POTCAR' and check_vasp_input(root) == True: if os.path.exists(os.path.join(root, 'vasprun.xml')): try: Vr = Vasprun(os.path.join(root, 'vasprun.xml')) fizzled = False except: fizzled = True workflow_converged_list.append(False) if fizzled == False: job = is_converged(root) if job == 'converged': workflow_converged_list.append(True) vol = Poscar.from_file(os.path.join( root, 'POSCAR')).structure.volume if Vr.final_energy < minE: minE = Vr.final_energy minV = vol minE_path = root minE_formula = str( Poscar.from_file( os.path.join(path, 'POSCAR')). structure.composition.reduced_formula) E.append(Vr.final_energy) V.append(vol) elif fizzled == True and get_incar_value( path, 'STAGE_NUMBER' ) == 0: #job is failing on initial relaxation num_sub = get_number_of_subs(root) if num_sub == max_num_sub: os.remove(os.path.join(root, 'POTCAR')) #job failed too many times.... just ignore this job for the remainder of the workflow else: workflow_converged_list.append(False) num_jobs = check_num_jobs_in_workflow(pwd) if num_jobs < min_num_vols and len(E) > 0: scale_around_min = [0.98, 1.02] for s in scale_around_min: write_path = os.path.join(pwd, minE_formula + str(s * minV)) os.mkdir(write_path) structure = Poscar.from_file(os.path.join(minE_path, 'POSCAR')).structure structure.scale_lattice(s * minV) Poscar.write_file(structure, os.path.join(write_path, 'POSCAR')) files_copy = [ 'backup/Init/INCAR', 'CONVERGENCE', 'KPOINTS', 'POTCAR' ] for fc in files_copy: copy_from_path = os.path.join(minE_path, fc) if os.path.exists(copy_from_path): copy(copy_from_path, write_path) remove_sys_incar(write_path) #create new jobs if False not in workflow_converged_list: if len(E) > min_num_vols - 1: volumes = V energies = E eos = EOS(eos_name='murnaghan') eos_fit = eos.fit(volumes, energies) eos_minV = eos_fit.v0 if abs(eos_minV - minV) < volume_tolerance: # ang^3 cutoff return True #eos_fit.plot() else: scale_around_min = [0.99, 1, 1.01] for s in scale_around_min: write_path = os.path.join(pwd, minE_formula + str(s * eos_minV)) os.mkdir(write_path) structure = Poscar.from_file( os.path.join(minE_path, 'POSCAR')).structure structure.scale_lattice(s * eos_minV) Poscar.write_file(structure, os.path.join(write_path, 'POSCAR')) files_copy = [ 'backup/Init/INCAR', 'CONVERGENCE', 'KPOINTS', 'POTCAR' ] for fc in files_copy: copy_from_path = os.path.join(minE_path, fc) if os.path.exists(copy_from_path): copy(copy_from_path, write_path) remove_sys_incar(write_path) return False else: return False
def test_bulk_modulus(self): eos = EOS(self.eos) eos_fit = eos.fit(self.volumes, self.energies) bulk_modulus = float(str(eos_fit.b0_GPa).split()[0]) bulk_modulus_ans = float(str(self.qhda.bulk_modulus).split()[0]) np.testing.assert_almost_equal(bulk_modulus, bulk_modulus_ans, 3)
print(f'Data printed in "{system_name}_eos.dat"') import matplotlib.pyplot as plt import numpy as np import matplotlib matplotlib.rcParams.update({'font.size': 22}) data = np.loadtxt(f"{system_name}_eos.dat", 'f') V = data[:, 0] E = data[:, 1] # making b-m fit and plot with pymatgen eos = EOS(eos_name='birch_murnaghan') eos_fit = eos.fit(V, E) eos_fit.plot(width=10, height=10, text='', markersize=15, label='Birch-Murnaghan fit') plt.legend(loc=2, prop={'size': 20}) plt.title(f'{system_name}') plt.tight_layout() #plt.show() plt.savefig(f'{system_name}_fit_eos.png') print('') print(f'Plot saved as "{system_name}_fit_eos.png"') # getting fitted parameters