Пример #1
0
    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")
Пример #2
0
    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
Пример #3
0
    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()
Пример #4
0
    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
Пример #5
0
    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)
Пример #6
0
    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)
Пример #7
0
    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)
Пример #8
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.")
Пример #9
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.")
Пример #10
0
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]
Пример #11
0
 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()
Пример #12
0
    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)
Пример #13
0
    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)
Пример #14
0
    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)
Пример #15
0
    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
Пример #16
0
 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()
Пример #17
0
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))
Пример #18
0
 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()
Пример #19
0
    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 __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()
Пример #21
0
    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.assertAlmostEqual(_.results[param],
                                       test_output[eos_name][param],
                                       places=1)
Пример #22
0
 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)
Пример #23
0
 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)
Пример #24
0
    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")
Пример #25
0
 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)
Пример #26
0
    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)
Пример #27
0
 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)
Пример #28
0
    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)
Пример #29
0
class DebyeModel(object):
    """
    Calculate the vibrational free energy for volumes/temperatures using the Debye model

    Note that the properties are per unit formula!

    Parameters
    ----------
    energies : list
        List of DFT energies in eV
    volumes : list
        List of volumes in Ang^3
    structure : pymatgen.Structure
        One of the structures on the E-V curve (can be any volume).
    dos_objects : list
        List of pymatgen Dos objects corresponding to the volumes. If passed, will enable the
        electronic contribution.
    t_min : float
        Minimum temperature
    t_step : float
        Temperature step size
    t_max : float
        Maximum temperature (inclusive)
    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". Default is "vinet".
    pressure : float
        Pressure to apply to the E-V curve/Gibbs energies in GPa. Defaults to 0.
    poisson : float
        Poisson ratio, defaults to 0.363615, corresponding to the cubic scaling factor of 0.617 by Moruzzi
    gruneisen : bool
        Whether to use the Debye-Gruneisen model. Defaults to True.
    bp2gru : float
        Fitting parameter for dBdP in the Gruneisen parameter. 2/3 is the high temperature
        value and 1 is the low temperature value. Defaults to 1.
    mass_average_mode : str
        Either 'arithmetic' or 'geometric'. Default is 'arithmetic'
    """
    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 calculate_F_el(self):
        """
        Calculate the Helmholtz vibrational free energy

        """
        self.F_vib = np.zeros((len(self.volumes), self.temperatures.size ))
        for v_idx, vol in enumerate(self.volumes):
            for t_idx, temp in enumerate(self.temperatures):
                self.F_vib[v_idx, t_idx] = self.vibrational_free_energy(temp, vol)


    def vibrational_free_energy(self, temperature, volume):
        """
        Vibrational Helmholtz free energy, A_vib(V, T).
        Eq(4) in doi.org/10.1016/j.comphy.2003.12.001

        Args:
            temperature (float): temperature in K
            volume (float)

        Returns:
            float: vibrational free energy in eV
        """
        y = self.debye_temperature(volume) / temperature
        return self.kb * self.natoms * temperature * (9./8. * y + 3 * np.log(1 - np.exp(-y)) - self.debye_integral(y))


    def debye_temperature(self, volume):
        """
        Calculates the debye temperature.
        Eq(6) in doi.org/10.1016/j.comphy.2003.12.001. Thanks to Joey.

        Eq(6) above is equivalent to Eq(3) in doi.org/10.1103/PhysRevB.37.790
        which does not consider anharmonic effects. Eq(20) in the same paper
        and Eq(18) in doi.org/10.1016/j.commatsci.2009.12.006 both consider
        anharmonic contributions to the Debye temperature through the Gruneisen
        parameter at 0K (Gruneisen constant).

        The anharmonic contribution is toggled by setting the anharmonic_contribution
        to True or False in the QuasiharmonicDebyeApprox constructor.

        Args:
            volume (float): in Ang^3

        Returns:
            float: debye temperature in K
         """
        term1 = (2./3. * (1. + self.poisson) / (1. - 2. * self.poisson))**1.5
        term2 = (1./3. * (1. + self.poisson) / (1. - self.poisson))**1.5
        f = (3. / (2. * term1 + term2))**(1. / 3.)
        debye = 2.9772e-11 * (volume / self.natoms) ** (-1. / 6.) * f * np.sqrt(self.bulk_modulus/self.avg_mass)
        if self.gruneisen:
            # bp2gru should be the correction to the Gruneisen constant.
            # High temperature limit: 2/3
            # Low temperature limit: 1
            # take 0 K E-V curve properties
            dBdP = self.ev_eos_fit.b1  # bulk modulus/pressure derivative
            gamma = (1+dBdP)/2 - self.bp2gru  # 0K equilibrium Gruneisen parameter
            return debye * (self.ev_eos_fit.v0 / volume) ** (gamma)
        else:
            return debye


    @staticmethod
    def debye_integral(y):
        """
        Debye integral. Eq(5) in  doi.org/10.1016/j.comphy.2003.12.001

        Args:
            y (float): debye temperature/T, upper limit

        Returns:
            float: unitless
        """
        # floating point limit is reached around y=155, so values beyond that
        # are set to the limiting value(T-->0, y --> \infty) of
        # 6.4939394 (from wolfram alpha).
        factor = 3. / y ** 3
        if y < 155:
            integral = quadrature(lambda x: x ** 3 / (np.exp(x) - 1.), 0, y)
            return list(integral)[0] * factor
        else:
            return 6.493939 * factor
 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)
Пример #31
0
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
Пример #32
0
class AbstractQHA(six.with_metaclass(abc.ABCMeta, object)):
    """
    Abstract class for the quasi-harmonic approximation  analysis.
    Provides some basic methods and plotting utils, plus a converter to write input files for phonopy-qha or to
    generate an instance of phonopy.qha.QHA. These can be used to obtain other quantities and plots.
    Does not include electronic entropic contributions for metals.
    """

    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 fit_energies(self, tstart=0, tstop=800, num=100):
        """
        Performs a fit of the energies as a function of the volume at different temperatures.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.

        Returns:
            `namedtuple` with the following attributes::

                tot_en: numpy array with shape (nvols, num) with the energies used for the fit
                fits: list of subclasses of pymatgen.analysis.eos.EOSBase, depending on the type of
                    eos chosen. Contains the fit for the energies at the different temperatures.
                min_en: numpy array with the minimum energies for the list of temperatures
                min_vol: numpy array with the minimum volumes for the list of temperatures
                temp: numpy array with the temperatures considered

        """

        tmesh = np.linspace(tstart, tstop, num)

        # array with phonon energies and shape (n_vol, n_temp)
        ph_energies = self.get_vib_free_energies(tstart, tstop, num)

        tot_en = self.energies[np.newaxis, :].T + ph_energies + self.volumes[np.newaxis, :].T * self.pressure / abu.eVA3_GPa

        # list of fits objects, one for each temperature
        fits = [self.eos.fit(self.volumes, e) for e in tot_en.T]

        # list of minimum volumes and energies, one for each temperature
        min_volumes = np.array([fit.v0 for fit in fits])
        min_energies = np.array([fit.e0 for fit in fits])

        return dict2namedtuple(tot_en=tot_en, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh)

    @abc.abstractmethod
    def get_vib_free_energies(self, tstart=0, tstop=800, num=100):
        """
        Generates the vibrational free energy corresponding to all the structures.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.

        Returns:
            A numpy array of `num` values of of the vibrational contribution to the free energy
        """
        pass

    @abc.abstractmethod
    def get_thermodynamic_properties(self, tstart=0, tstop=800, num=100):
        """
        Generates all the thermodynamic properties corresponding to all the volumes.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.

        Returns:
            `namedtuple` with the following attributes for all the volumes:

                tmesh: numpy array with the list of temperatures. Shape (num).
                cv: constant-volume specific heat, in eV/K. Shape (nvols, num).
                free_energy: free energy, in eV. Shape (nvols, num).
                entropy: entropy, in eV/K. Shape (nvols, num).
                zpe: zero point energy in eV. Shape (nvols).
        """
        pass

    @property
    def nvols(self):
        return len(self.volumes)

    @property
    def natoms(self):
        return len(self.structures[0])

    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)

    @add_fig_kwargs
    def plot_energies(self, tstart=0, tstop=800, num=10, ax=None, **kwargs):
        """
        Plots the energies as a function of volume at different temperatures.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 10.
            ax: |matplotlib-Axes| or None if a new figure should be created.

        Returns: |matplotlib-Figure|
        """

        f = self.fit_energies(tstart, tstop, num)

        ax, fig, plt = get_ax_fig_plt(ax)
        xmin, xmax = np.floor(self.volumes.min() * 0.97), np.ceil(self.volumes.max() * 1.03)
        x = np.linspace(xmin, xmax, 100)

        for fit, e, t in zip(f.fits, f.tot_en.T - self.energies[self.iv0], f.temp):
            ax.scatter(self.volumes, e, label=t, color='b', marker='x', s=5)
            ax.plot(x, fit.func(x) - self.energies[self.iv0], color='b', lw=1)

        ax.plot(f.min_vol, f.min_en - self.energies[self.iv0] , color='r', lw=1, marker='x', ms=5)

        ax.set_xlabel(r'V (${\AA}^3$)')
        ax.set_ylabel('E (eV)')

        return fig

    def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100):
        """
        Calculates the thermal expansion coefficient as a function of temperature, using
        finite difference on the fitted values of the volume as a function of temperature.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.

        Returns: |Function1D|
        """

        f = self.fit_energies(tstart, tstop, num)

        dt = f.temp[1] - f.temp[0]
        alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f.min_vol[1:-1]

        return Function1D(f.temp[1:-1], alpha)

    @add_fig_kwargs
    def plot_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, ax=None, **kwargs):
        """
        Plots the thermal expansion coefficient as a function of the temperature.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.
            ax: |matplotlib-Axes| or None if a new figure should be created.

        Returns: |matplotlib-Figure|
        """

        ax, fig, plt = get_ax_fig_plt(ax)

        if 'linewidth' not in kwargs and 'lw' not in kwargs:
            kwargs['linewidth'] = 2

        if "color" not in kwargs:
            kwargs["color"] = "b"

        alpha = self.get_thermal_expansion_coeff(tstart, tstop, num)

        ax.plot(alpha.mesh, alpha.values, **kwargs)
        ax.set_xlabel(r'T (K)')
        ax.set_ylabel(r'$\alpha$ (K$^{-1}$)')

        ax.set_xlim(tstart, tstop)

        ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0))

        return fig

    @add_fig_kwargs
    def plot_vol_vs_t(self, tstart=0, tstop=800, num=100, ax=None, **kwargs):
        """
        Plots the volume as a function of the temperature.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.
            ax: |matplotlib-Axes| or None if a new figure should be created.

        Returns: |matplotlib-Figure|
        """
        ax, fig, plt = get_ax_fig_plt(ax)
        f = self.fit_energies(tstart, tstop, num)

        if 'linewidth' not in kwargs and 'lw' not in kwargs:
            kwargs['linewidth'] = 2

        if "color" not in kwargs:
            kwargs["color"] = "b"

        ax.plot(f.temp, f.min_vol, **kwargs)
        ax.set_xlabel('T (K)')
        ax.set_ylabel(r'V (${\AA}^3$)')

        ax.set_xlim(tstart, tstop)

        return fig

    @add_fig_kwargs
    def plot_phbs(self, phbands, temperatures=None, t_max=1000, colormap="plasma", **kwargs):
        """
        Given a list of |PhononBands| plots the band structures with a color depending on
        the temperature using a |PhononBandsPlotter|.
        If temperatures are not given they will be deduced inverting the dependence of volume
        with respect to the temperature. If a unique volume could not be identified an error will
        be raised.

        Args:
            phbands: list of |PhononBands| objects.
            temperatures: list of temperatures.
            t_max: maximum temperature considered for plotting.
            colormap: matplotlib color map.

        Returns: |matplotlib-Figure|
        """

        if temperatures is None:
            tv = self.get_t_for_vols([b.structure.volume for b in phbands], t_max=t_max)
            temperatures = []
            for b, t in zip(phbands, tv):
                if len(t) != 1:
                    raise ValueError("Couldn't find a single temperature for structure with "
                                     "volume {}. Found {}: {}".format(b.structure.volume, len(t), list(t)))
                temperatures.append(t[0])

        temperatures_str = ["{:.0f} K".format(t) for t in temperatures]

        import matplotlib.pyplot as plt
        cmap = plt.get_cmap(colormap)
        colors = [cmap(t / max(temperatures)) for t in temperatures]
        labels_phbs = zip(temperatures_str, phbands)

        pbp = PhononBandsPlotter(labels_phbs)
        pbp._LINE_COLORS = colors
        pbp._LINE_STYLES = ['-']

        fig = pbp.combiplot(show=False, **kwargs)

        return fig

    def get_vol_at_t(self, t):
        """
        Calculates the volume corresponding to a specific temperature.

        Args:
            t: a temperature in K

        Returns:
            The volume
        """

        f = self.fit_energies(t, t, 1)

        return f.min_vol[0]

    def get_t_for_vols(self, vols, t_max=1000):
        """
        Find the temperatures corresponding to a specific volume.
        The search is performed interpolating the V(T) dependence with a spline and
        finding the roots with of V(t) - v.
        It may return more than one temperature for a volume in case of non monotonic behavior.

        Args:
            vols: list of volumes
            t_max: maximum temperature considered for the fit

        Returns:
            A list of lists of temperatures. For each volume more than one temperature can
            be identified.
        """

        if not isinstance(vols, (list, tuple, np.ndarray)):
            vols = [vols]


        f = self.fit_energies(0, t_max, t_max+1)

        temps = []
        for v in vols:
            spline = UnivariateSpline(f.temp, f.min_vol - v, s=0)
            temps.append(spline.roots())

        return temps

    def write_phonopy_qha_inputs(self, tstart=0, tstop=2100, num=211, path=None):
        """
        Writes nvols thermal_properties-i.yaml files that can be used as inputs for phonopy-qha.
        Notice that phonopy apparently requires the value of the 300 K temperature to be present
        in the list. Choose the values of tstart, tstop and num to satisfy this condition.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 211.
            path: a path to a folder where the files will be stored
        """

        if path is None:
            path = os.getcwd()

        thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num)

        np.savetxt(os.path.join(path, 'e-v.dat'), np.array([self.volumes, self.energies]).T, fmt='%.10f')

        # generator for thermal_properties.yaml extracted from phonopy:
        # phonopy.phonon.thermal_properties.ThermalProperties._get_tp_yaml_lines
        for j in range(self.nvols):
            lines = []
            lines.append("# Thermal properties / unit cell (natom)")
            lines.append("")
            lines.append("unit:")
            lines.append("  temperature:   K")
            lines.append("  free_energy:   kJ/mol")
            lines.append("  entropy:       J/K/mol")
            lines.append("  heat_capacity: J/K/mol")
            lines.append("")
            lines.append("natom: %5d" % (self.natoms))

            lines.append("num_modes: %d" % (3 * self.natoms))
            lines.append("num_integrated_modes: %d" % (3 * self.natoms))

            lines.append("")
            lines.append("zero_point_energy: %15.7f" % (thermo.zpe[j] * abu.e_Cb * abu.Avogadro ))
            lines.append("high_T_entropy:    %15.7f" % 0) # high_T_entropy is not used in QHA
            lines.append("")
            lines.append("thermal_properties:")
            fe = thermo.free_energy[j] * abu.e_Cb * abu.Avogadro
            entropy = thermo.entropy[j] * abu.e_Cb * abu.Avogadro
            cv = thermo.cv[j] * abu.e_Cb * abu.Avogadro
            temperatures = thermo.tmesh
            for i, t in enumerate(temperatures):
                lines.append("- temperature:   %15.7f" % t)
                lines.append("  free_energy:   %15.7f" % (fe[i] / 1000))
                lines.append("  entropy:       %15.7f" % entropy[i])
                # Sometimes 'nan' of C_V is returned at low temperature.
                if np.isnan(cv[i]):
                    lines.append("  heat_capacity: %15.7f" % 0)
                else:
                    lines.append("  heat_capacity: %15.7f" % cv[i])
                lines.append("  energy:        %15.7f" %
                             (fe[i] / 1000 + entropy[i] * t / 1000))

                lines.append("")

            with open(os.path.join(path, "thermal_properties-{}.yaml".format(j)), 'wt') as f:
                f.write("\n".join(lines))

    def get_phonopy_qha(self, tstart=0, tstop=2100, num=211, eos='vinet', t_max=None, energy_plot_factor=None):
        """
        Creates an instance of phonopy.qha.QHA that can be used generate further plots and output data.
        The object is returned right after the construction. The "run()" method should be executed
        before getting results and plots.
        Notice that phonopy apparently requires the value of the 300 K temperature to be present
        in the list. Choose the values of tstart, tstop and num to satisfy this condition.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 211.
            eos: the expression used to fit the energies in phonopy. Possible values are "vinet",
                "murnaghan" and "birch_murnaghan". Passed to phonopy's QHA.
            t_max: maximum temperature. Passed to phonopy's QHA.
            energy_plot_factor: factor multiplying the energies. Passed to phonopy's QHA.

        Returns:
            An instance of phonopy.qha.QHA
        """

        try:
            from phonopy.qha import QHA as QHA_phonopy
        except ImportError as exc:
            print("Phonopy is required to generate the QHA phonopy object")
            raise exc

        thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num)

        fe = thermo.free_energy.T * abu.e_Cb * abu.Avogadro / 1000
        entropy = thermo.entropy.T * abu.e_Cb * abu.Avogadro
        cv = thermo.cv.T * abu.e_Cb * abu.Avogadro
        temperatures = thermo.tmesh

        en = self.energies + self.volumes * self.pressure / abu.eVA3_GPa

        qha_p = QHA_phonopy(self.volumes, en, temperatures, cv, entropy, fe, eos, t_max, energy_plot_factor)

        return qha_p
Пример #33
0
    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.16258657649159)

        # 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.38629264585195)

        # 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.62927094503254)
Пример #34
0
class Quasiharmonic(object):
    """
    Class to perform quasiharmonic calculations.

    In principle, helps to abstract away where different energy contributions come from.

    Parameters
    ----------
    energies : list
        List of DFT energies in eV
    volumes : list
        List of volumes in Ang^3
    structure : pymatgen.Structure
        One of the structures on the E-V curve (can be any volume).
    dos_objects : list
        List of pymatgen Dos objects corresponding to the volumes. If passed, will enable the
        electronic contribution.
    f_vib : numpy.ndarray
        Array of F_vib(V,T) of shape (len(volumes), len(temperatures)). If absent, will use the Debye model.
    t_min : float
        Minimum temperature
    t_step : float
        Temperature step size
    t_max : float
        Maximum temperature (inclusive)
    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". Default is "vinet".
    pressure : float
        Pressure to apply to the E-V curve/Gibbs energies in GPa. Defaults to 0.
    poisson : float
        Poisson ratio, defaults to 0.25. Only used in QHA
    bp2gru : float
        Fitting parameter for dBdP in the Gruneisen parameter. 2/3 is the high temperature
        value and 1 is the low temperature value. Defaults to 1.
    vib_kwargs : dict
        Additional keyword arguments to pass to the vibrational calculator
    """
    def __init__(self,
                 energies,
                 volumes,
                 structure,
                 dos_objects=None,
                 F_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
        else:
            self.F_vib = F_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 optimize_gibbs_free_energy(self):
        """
        Evaluate the gibbs free energy as a function of V, T and P i.e
        G(V, T, P), minimize G(V, T, P) wrt V for each T and store the
        optimum values.

        Note: The data points for which the equation of state fitting fails
            are skipped.
        """
        for temp_idx in range(self.temperatures.size):
            G_opt, V_opt = self.optimizer(temp_idx)
            self.gibbs_free_energy.append(float(G_opt))
            self.optimum_volumes.append(float(V_opt))

    def optimizer(self, temp_idx):
        """
        Evaluate G(V, T, P) at the given temperature(and pressure) and
        minimize it wrt V.

        1. Compute the  vibrational helmholtz free energy, A_vib.
        2. Compute the gibbs free energy as a function of volume, temperature
            and pressure, G(V,T,P).
        3. Preform an equation of state fit to get the functional form of
            gibbs free energy:G(V, T, P).
        4. Finally G(V, P, T) is minimized with respect to V.

        Args:
            temp_idx : int
            Index of the temperature of interest from self.temperatures

        Returns:
            float, float: G_opt(V_opt, T, P) in eV and V_opt in Ang^3.
        """
        G_V = self.G[:, temp_idx]

        # fit equation of state, G(V, T, P)
        try:
            eos_fit = self.eos.fit(self.volumes, G_V)
        except EOSError:
            return np.nan, np.nan
        # minimize the fit eos wrt volume
        # Note: the ref energy and the ref volume(E0 and V0) not necessarily
        # the same as minimum energy and min volume.
        volume_guess = eos_fit.volumes[np.argmin(eos_fit.energies)]
        min_wrt_vol = minimize(eos_fit.func, volume_guess)
        # G_opt=G(V_opt, T, P), V_opt
        return min_wrt_vol.fun, min_wrt_vol.x[0]

    def get_summary_dict(self):
        """
        Returns a dict with a summary of the computed properties.
        """
        d = defaultdict(list)
        d["pressure"] = self.pressure
        d["natoms"] = int(self.natoms)
        d["gibbs_free_energy"] = self.gibbs_free_energy
        d["temperatures"] = self.temperatures
        d["optimum_volumes"] = self.optimum_volumes
        return d
Пример #35
0
class AbstractQHA(six.with_metaclass(abc.ABCMeta, object)):
    """
    Abstract class for the quasi-harmonic approximation  analysis.
    Provides some basic methods and plotting utils, plus a converter to write input files for phonopy-qha or to
    generate an instance of phonopy.qha.QHA. These can be used to obtain other quantities and plots.
    Does not include electronic entropic contributions for metals.
    """
    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 fit_energies(self, tstart=0, tstop=800, num=100):
        """
        Performs a fit of the energies as a function of the volume at different temperatures.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.

        Returns:
            `namedtuple` with the following attributes::

                tot_en: numpy array with shape (nvols, num) with the energies used for the fit
                fits: list of subclasses of pymatgen.analysis.eos.EOSBase, depending on the type of
                    eos chosen. Contains the fit for the energies at the different temperatures.
                min_en: numpy array with the minimum energies for the list of temperatures
                min_vol: numpy array with the minimum volumes for the list of temperatures
                temp: numpy array with the temperatures considered

        """

        tmesh = np.linspace(tstart, tstop, num)

        # array with phonon energies and shape (n_vol, n_temp)
        ph_energies = self.get_vib_free_energies(tstart, tstop, num)

        tot_en = self.energies[np.newaxis, :].T + ph_energies + self.volumes[
            np.newaxis, :].T * self.pressure / abu.eVA3_GPa

        # list of fits objects, one for each temperature
        fits = [self.eos.fit(self.volumes, e) for e in tot_en.T]

        # list of minimum volumes and energies, one for each temperature
        min_volumes = np.array([fit.v0 for fit in fits])
        min_energies = np.array([fit.e0 for fit in fits])

        return dict2namedtuple(tot_en=tot_en,
                               fits=fits,
                               min_en=min_energies,
                               min_vol=min_volumes,
                               temp=tmesh)

    @abc.abstractmethod
    def get_vib_free_energies(self, tstart=0, tstop=800, num=100):
        """
        Generates the vibrational free energy corresponding to all the structures.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.

        Returns:
            A numpy array of `num` values of of the vibrational contribution to the free energy
        """
        pass

    @abc.abstractmethod
    def get_thermodynamic_properties(self, tstart=0, tstop=800, num=100):
        """
        Generates all the thermodynamic properties corresponding to all the volumes.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.

        Returns:
            `namedtuple` with the following attributes for all the volumes:

                tmesh: numpy array with the list of temperatures. Shape (num).
                cv: constant-volume specific heat, in eV/K. Shape (nvols, num).
                free_energy: free energy, in eV. Shape (nvols, num).
                entropy: entropy, in eV/K. Shape (nvols, num).
                zpe: zero point energy in eV. Shape (nvols).
        """
        pass

    @property
    def nvols(self):
        return len(self.volumes)

    @property
    def natoms(self):
        return len(self.structures[0])

    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)

    @add_fig_kwargs
    def plot_energies(self, tstart=0, tstop=800, num=10, ax=None, **kwargs):
        """
        Plots the energies as a function of volume at different temperatures.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 10.
            ax: |matplotlib-Axes| or None if a new figure should be created.

        Returns: |matplotlib-Figure|
        """

        f = self.fit_energies(tstart, tstop, num)

        ax, fig, plt = get_ax_fig_plt(ax)
        xmin, xmax = np.floor(self.volumes.min() * 0.97), np.ceil(
            self.volumes.max() * 1.03)
        x = np.linspace(xmin, xmax, 100)

        for fit, e, t in zip(f.fits, f.tot_en.T - self.energies[self.iv0],
                             f.temp):
            ax.scatter(self.volumes, e, label=t, color='b', marker='x', s=5)
            ax.plot(x, fit.func(x) - self.energies[self.iv0], color='b', lw=1)

        ax.plot(f.min_vol,
                f.min_en - self.energies[self.iv0],
                color='r',
                lw=1,
                marker='x',
                ms=5)

        ax.set_xlabel(r'V (${\AA}^3$)')
        ax.set_ylabel('E (eV)')

        return fig

    def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100):
        """
        Calculates the thermal expansion coefficient as a function of temperature, using
        finite difference on the fitted values of the volume as a function of temperature.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.

        Returns: |Function1D|
        """

        f = self.fit_energies(tstart, tstop, num)

        dt = f.temp[1] - f.temp[0]
        alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f.min_vol[1:-1]

        return Function1D(f.temp[1:-1], alpha)

    @add_fig_kwargs
    def plot_thermal_expansion_coeff(self,
                                     tstart=0,
                                     tstop=800,
                                     num=100,
                                     ax=None,
                                     **kwargs):
        """
        Plots the thermal expansion coefficient as a function of the temperature.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.
            ax: |matplotlib-Axes| or None if a new figure should be created.

        Returns: |matplotlib-Figure|
        """

        ax, fig, plt = get_ax_fig_plt(ax)

        if 'linewidth' not in kwargs and 'lw' not in kwargs:
            kwargs['linewidth'] = 2

        if "color" not in kwargs:
            kwargs["color"] = "b"

        alpha = self.get_thermal_expansion_coeff(tstart, tstop, num)

        ax.plot(alpha.mesh, alpha.values, **kwargs)
        ax.set_xlabel(r'T (K)')
        ax.set_ylabel(r'$\alpha$ (K$^{-1}$)')

        ax.set_xlim(tstart, tstop)

        ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0))

        return fig

    @add_fig_kwargs
    def plot_vol_vs_t(self, tstart=0, tstop=800, num=100, ax=None, **kwargs):
        """
        Plots the volume as a function of the temperature.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 100.
            ax: |matplotlib-Axes| or None if a new figure should be created.

        Returns: |matplotlib-Figure|
        """
        ax, fig, plt = get_ax_fig_plt(ax)
        f = self.fit_energies(tstart, tstop, num)

        if 'linewidth' not in kwargs and 'lw' not in kwargs:
            kwargs['linewidth'] = 2

        if "color" not in kwargs:
            kwargs["color"] = "b"

        ax.plot(f.temp, f.min_vol, **kwargs)
        ax.set_xlabel('T (K)')
        ax.set_ylabel(r'V (${\AA}^3$)')

        ax.set_xlim(tstart, tstop)

        return fig

    @add_fig_kwargs
    def plot_phbs(self,
                  phbands,
                  temperatures=None,
                  t_max=1000,
                  colormap="plasma",
                  **kwargs):
        """
        Given a list of |PhononBands| plots the band structures with a color depending on
        the temperature using a |PhononBandsPlotter|.
        If temperatures are not given they will be deduced inverting the dependence of volume
        with respect to the temperature. If a unique volume could not be identified an error will
        be raised.

        Args:
            phbands: list of |PhononBands| objects.
            temperatures: list of temperatures.
            t_max: maximum temperature considered for plotting.
            colormap: matplotlib color map.

        Returns: |matplotlib-Figure|
        """

        if temperatures is None:
            tv = self.get_t_for_vols([b.structure.volume for b in phbands],
                                     t_max=t_max)
            temperatures = []
            for b, t in zip(phbands, tv):
                if len(t) != 1:
                    raise ValueError(
                        "Couldn't find a single temperature for structure with "
                        "volume {}. Found {}: {}".format(
                            b.structure.volume, len(t), list(t)))
                temperatures.append(t[0])

        temperatures_str = ["{:.0f} K".format(t) for t in temperatures]

        import matplotlib.pyplot as plt
        cmap = plt.get_cmap(colormap)
        colors = [cmap(t / max(temperatures)) for t in temperatures]
        labels_phbs = zip(temperatures_str, phbands)

        pbp = PhononBandsPlotter(labels_phbs)
        pbp._LINE_COLORS = colors
        pbp._LINE_STYLES = ['-']

        fig = pbp.combiplot(show=False, **kwargs)

        return fig

    def get_vol_at_t(self, t):
        """
        Calculates the volume corresponding to a specific temperature.

        Args:
            t: a temperature in K

        Returns:
            The volume
        """

        f = self.fit_energies(t, t, 1)

        return f.min_vol[0]

    def get_t_for_vols(self, vols, t_max=1000):
        """
        Find the temperatures corresponding to a specific volume.
        The search is performed interpolating the V(T) dependence with a spline and
        finding the roots with of V(t) - v.
        It may return more than one temperature for a volume in case of non monotonic behavior.

        Args:
            vols: list of volumes
            t_max: maximum temperature considered for the fit

        Returns:
            A list of lists of temperatures. For each volume more than one temperature can
            be identified.
        """

        if not isinstance(vols, (list, tuple, np.ndarray)):
            vols = [vols]

        f = self.fit_energies(0, t_max, t_max + 1)

        temps = []
        for v in vols:
            spline = UnivariateSpline(f.temp, f.min_vol - v, s=0)
            temps.append(spline.roots())

        return temps

    def write_phonopy_qha_inputs(self,
                                 tstart=0,
                                 tstop=2100,
                                 num=211,
                                 path=None):
        """
        Writes nvols thermal_properties-i.yaml files that can be used as inputs for phonopy-qha.
        Notice that phonopy apparently requires the value of the 300 K temperature to be present
        in the list. Choose the values of tstart, tstop and num to satisfy this condition.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 211.
            path: a path to a folder where the files will be stored
        """

        if path is None:
            path = os.getcwd()

        thermo = self.get_thermodynamic_properties(tstart=tstart,
                                                   tstop=tstop,
                                                   num=num)

        np.savetxt(os.path.join(path, 'e-v.dat'),
                   np.array([self.volumes, self.energies]).T,
                   fmt='%.10f')

        # generator for thermal_properties.yaml extracted from phonopy:
        # phonopy.phonon.thermal_properties.ThermalProperties._get_tp_yaml_lines
        for j in range(self.nvols):
            lines = []
            lines.append("# Thermal properties / unit cell (natom)")
            lines.append("")
            lines.append("unit:")
            lines.append("  temperature:   K")
            lines.append("  free_energy:   kJ/mol")
            lines.append("  entropy:       J/K/mol")
            lines.append("  heat_capacity: J/K/mol")
            lines.append("")
            lines.append("natom: %5d" % (self.natoms))

            lines.append("num_modes: %d" % (3 * self.natoms))
            lines.append("num_integrated_modes: %d" % (3 * self.natoms))

            lines.append("")
            lines.append("zero_point_energy: %15.7f" %
                         (thermo.zpe[j] * abu.e_Cb * abu.Avogadro))
            lines.append("high_T_entropy:    %15.7f" %
                         0)  # high_T_entropy is not used in QHA
            lines.append("")
            lines.append("thermal_properties:")
            fe = thermo.free_energy[j] * abu.e_Cb * abu.Avogadro
            entropy = thermo.entropy[j] * abu.e_Cb * abu.Avogadro
            cv = thermo.cv[j] * abu.e_Cb * abu.Avogadro
            temperatures = thermo.tmesh
            for i, t in enumerate(temperatures):
                lines.append("- temperature:   %15.7f" % t)
                lines.append("  free_energy:   %15.7f" % (fe[i] / 1000))
                lines.append("  entropy:       %15.7f" % entropy[i])
                # Sometimes 'nan' of C_V is returned at low temperature.
                if np.isnan(cv[i]):
                    lines.append("  heat_capacity: %15.7f" % 0)
                else:
                    lines.append("  heat_capacity: %15.7f" % cv[i])
                lines.append("  energy:        %15.7f" %
                             (fe[i] / 1000 + entropy[i] * t / 1000))

                lines.append("")

            with open(
                    os.path.join(path, "thermal_properties-{}.yaml".format(j)),
                    'wt') as f:
                f.write("\n".join(lines))

    def get_phonopy_qha(self,
                        tstart=0,
                        tstop=2100,
                        num=211,
                        eos='vinet',
                        t_max=None,
                        energy_plot_factor=None):
        """
        Creates an instance of phonopy.qha.QHA that can be used generate further plots and output data.
        The object is returned right after the construction. The "run()" method should be executed
        before getting results and plots.
        Notice that phonopy apparently requires the value of the 300 K temperature to be present
        in the list. Choose the values of tstart, tstop and num to satisfy this condition.

        Args:
            tstart: The starting value (in Kelvin) of the temperature mesh.
            tstop: The end value (in Kelvin) of the mesh.
            num: int, optional Number of samples to generate. Default is 211.
            eos: the expression used to fit the energies in phonopy. Possible values are "vinet",
                "murnaghan" and "birch_murnaghan". Passed to phonopy's QHA.
            t_max: maximum temperature. Passed to phonopy's QHA.
            energy_plot_factor: factor multiplying the energies. Passed to phonopy's QHA.

        Returns:
            An instance of phonopy.qha.QHA
        """

        try:
            from phonopy.qha import QHA as QHA_phonopy
        except ImportError as exc:
            print("Phonopy is required to generate the QHA phonopy object")
            raise exc

        thermo = self.get_thermodynamic_properties(tstart=tstart,
                                                   tstop=tstop,
                                                   num=num)

        fe = thermo.free_energy.T * abu.e_Cb * abu.Avogadro / 1000
        entropy = thermo.entropy.T * abu.e_Cb * abu.Avogadro
        cv = thermo.cv.T * abu.e_Cb * abu.Avogadro
        temperatures = thermo.tmesh

        en = self.energies + self.volumes * self.pressure / abu.eVA3_GPa

        qha_p = QHA_phonopy(self.volumes, en, temperatures, cv, entropy, fe,
                            eos, t_max, energy_plot_factor)

        return qha_p
Пример #36
0
    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)
Пример #37
0
 def test_run_all_models(self):
     for eos_name in EOS.MODELS:
         eos = EOS(eos_name=eos_name)
         _ = eos.fit(self.volumes, self.energies)
Пример #38
0
 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 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)
Пример #40
0
class QuasiharmonicDebyeApprox:
    """
    Quasiharmonic approximation.
    """

    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.0 / 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 optimize_gibbs_free_energy(self):
        """
        Evaluate the gibbs free energy as a function of V, T and P i.e
        G(V, T, P), minimize G(V, T, P) wrt V for each T and store the
        optimum values.

        Note: The data points for which the equation of state fitting fails
            are skipped.
        """
        temperatures = np.linspace(
            self.temperature_min,
            self.temperature_max,
            int(np.ceil((self.temperature_max - self.temperature_min) / self.temperature_step) + 1),
        )

        for t in temperatures:
            try:
                G_opt, V_opt = self.optimizer(t)
            except Exception:
                if len(temperatures) <= 1:
                    raise
                logger.info(f"EOS fitting failed, so skipping this data point, {t}")
            self.gibbs_free_energy.append(G_opt)
            self.temperatures.append(t)
            self.optimum_volumes.append(V_opt)

    def optimizer(self, temperature):
        """
        Evaluate G(V, T, P) at the given temperature(and pressure) and
        minimize it wrt V.

        1. Compute the  vibrational helmholtz free energy, A_vib.
        2. Compute the gibbs free energy as a function of volume, temperature
            and pressure, G(V,T,P).
        3. Preform an equation of state fit to get the functional form of
            gibbs free energy:G(V, T, P).
        4. Finally G(V, P, T) is minimized with respect to V.

        Args:
            temperature (float): temperature in K

        Returns:
            float, float: G_opt(V_opt, T, P) in eV and V_opt in Ang^3.
        """
        G_V = []  # G for each volume
        # G = E(V) + PV + A_vib(V, T)
        for i, v in enumerate(self.volumes):
            G_V.append(
                self.energies[i] + self.pressure * v * self.gpa_to_ev_ang + self.vibrational_free_energy(temperature, v)
            )

        # fit equation of state, G(V, T, P)
        eos_fit = self.eos.fit(self.volumes, G_V)
        # minimize the fit eos wrt volume
        # Note: the ref energy and the ref volume(E0 and V0) not necessarily
        # the same as minimum energy and min volume.
        volume_guess = eos_fit.volumes[np.argmin(eos_fit.energies)]
        min_wrt_vol = minimize(eos_fit.func, volume_guess)
        # G_opt=G(V_opt, T, P), V_opt
        return min_wrt_vol.fun, min_wrt_vol.x[0]

    def vibrational_free_energy(self, temperature, volume):
        """
        Vibrational Helmholtz free energy, A_vib(V, T).
        Eq(4) in doi.org/10.1016/j.comphy.2003.12.001

        Args:
            temperature (float): temperature in K
            volume (float)

        Returns:
            float: vibrational free energy in eV
        """
        y = self.debye_temperature(volume) / temperature
        return (
            self.kb * self.natoms * temperature * (9.0 / 8.0 * y + 3 * np.log(1 - np.exp(-y)) - self.debye_integral(y))
        )

    def vibrational_internal_energy(self, temperature, volume):
        """
        Vibrational internal energy, U_vib(V, T).
        Eq(4) in doi.org/10.1016/j.comphy.2003.12.001

        Args:
            temperature (float): temperature in K
            volume (float): in Ang^3

        Returns:
            float: vibrational internal energy in eV
        """
        y = self.debye_temperature(volume) / temperature
        return self.kb * self.natoms * temperature * (9.0 / 8.0 * y + 3 * self.debye_integral(y))

    def debye_temperature(self, volume):
        """
        Calculates the debye temperature.
        Eq(6) in doi.org/10.1016/j.comphy.2003.12.001. Thanks to Joey.

        Eq(6) above is equivalent to Eq(3) in doi.org/10.1103/PhysRevB.37.790
        which does not consider anharmonic effects. Eq(20) in the same paper
        and Eq(18) in doi.org/10.1016/j.commatsci.2009.12.006 both consider
        anharmonic contributions to the Debye temperature through the Gruneisen
        parameter at 0K (Gruneisen constant).

        The anharmonic contribution is toggled by setting the anharmonic_contribution
        to True or False in the QuasiharmonicDebyeApprox constructor.

        Args:
            volume (float): in Ang^3

        Returns:
            float: debye temperature in K
        """
        term1 = (2.0 / 3.0 * (1.0 + self.poisson) / (1.0 - 2.0 * self.poisson)) ** 1.5
        term2 = (1.0 / 3.0 * (1.0 + self.poisson) / (1.0 - self.poisson)) ** 1.5
        f = (3.0 / (2.0 * term1 + term2)) ** (1.0 / 3.0)
        debye = 2.9772e-11 * (volume / self.natoms) ** (-1.0 / 6.0) * f * np.sqrt(self.bulk_modulus / self.avg_mass)
        if self.anharmonic_contribution:
            gamma = self.gruneisen_parameter(0, self.ev_eos_fit.v0)  # 0K equilibrium Gruneisen parameter
            return debye * (self.ev_eos_fit.v0 / volume) ** (gamma)
        return debye

    @staticmethod
    def debye_integral(y):
        """
        Debye integral. Eq(5) in  doi.org/10.1016/j.comphy.2003.12.001

        Args:
            y (float): debye temperature/T, upper limit

        Returns:
            float: unitless
        """
        # floating point limit is reached around y=155, so values beyond that
        # are set to the limiting value(T-->0, y --> \infty) of
        # 6.4939394 (from wolfram alpha).
        factor = 3.0 / y ** 3
        if y < 155:
            integral = quadrature(lambda x: x ** 3 / (np.exp(x) - 1.0), 0, y)
            return list(integral)[0] * factor
        return 6.493939 * factor

    def gruneisen_parameter(self, temperature, volume):
        """
        Slater-gamma formulation(the default):
            gruneisen paramter = - d log(theta)/ d log(V)
                               = - ( 1/6 + 0.5 d log(B)/ d log(V) )
                               = - (1/6 + 0.5 V/B dB/dV),
                                    where dB/dV = d^2E/dV^2 + V * d^3E/dV^3

        Mie-gruneisen formulation:
            Eq(31) in doi.org/10.1016/j.comphy.2003.12.001
            Eq(7) in Blanco et. al. Joumal of Molecular Structure (Theochem)
                368 (1996) 245-255
            Also se J.P. Poirier, Introduction to the Physics of the Earth’s
                Interior, 2nd ed. (Cambridge University Press, Cambridge,
                2000) Eq(3.53)

        Args:
            temperature (float): temperature in K
            volume (float): in Ang^3

        Returns:
            float: unitless
        """
        if isinstance(self.eos, PolynomialEOS):
            p = np.poly1d(self.eos.eos_params)  # pylint: disable=E1101
            # first derivative of energy at 0K wrt volume evaluated at the
            # given volume, in eV/Ang^3
            dEdV = np.polyder(p, 1)(volume)
            # second derivative of energy at 0K wrt volume evaluated at the
            # given volume, in eV/Ang^6
            d2EdV2 = np.polyder(p, 2)(volume)
            # third derivative of energy at 0K wrt volume evaluated at the
            # given volume, in eV/Ang^9
            d3EdV3 = np.polyder(p, 3)(volume)
        else:
            func = self.ev_eos_fit.func
            dEdV = derivative(func, volume, dx=1e-3)
            d2EdV2 = derivative(func, volume, dx=1e-3, n=2, order=5)
            d3EdV3 = derivative(func, volume, dx=1e-3, n=3, order=7)

        # Mie-gruneisen formulation
        if self.use_mie_gruneisen:
            p0 = dEdV
            return (
                self.gpa_to_ev_ang
                * volume
                * (self.pressure + p0 / self.gpa_to_ev_ang)
                / self.vibrational_internal_energy(temperature, volume)
            )

        # Slater-gamma formulation
        # first derivative of bulk modulus wrt volume, eV/Ang^6
        dBdV = d2EdV2 + d3EdV3 * volume
        return -(1.0 / 6.0 + 0.5 * volume * dBdV / FloatWithUnit(self.ev_eos_fit.b0_GPa, "GPa").to("eV ang^-3"))

    def thermal_conductivity(self, temperature, volume):
        """
        Eq(17) in 10.1103/PhysRevB.90.174107

        Args:
            temperature (float): temperature in K
            volume (float): in Ang^3

        Returns:
            float: thermal conductivity in W/K/m
        """
        gamma = self.gruneisen_parameter(temperature, volume)
        theta_d = self.debye_temperature(volume)  # K
        theta_a = theta_d * self.natoms ** (-1.0 / 3.0)  # K
        prefactor = (0.849 * 3 * 4 ** (1.0 / 3.0)) / (20.0 * np.pi ** 3)
        # kg/K^3/s^3
        prefactor = prefactor * (self.kb / self.hbar) ** 3 * self.avg_mass
        kappa = prefactor / (gamma ** 2 - 0.514 * gamma + 0.228)
        # kg/K/s^3 * Ang = (kg m/s^2)/(Ks)*1e-10
        # = N/(Ks)*1e-10 = Nm/(Kms)*1e-10 = W/K/m*1e-10
        kappa = kappa * theta_a ** 2 * volume ** (1.0 / 3.0) * 1e-10
        return kappa

    def get_summary_dict(self):
        """
        Returns a dict with a summary of the computed properties.
        """
        d = defaultdict(list)
        d["pressure"] = self.pressure
        d["poisson"] = self.poisson
        d["mass"] = self.mass
        d["natoms"] = int(self.natoms)
        d["bulk_modulus"] = self.bulk_modulus
        d["gibbs_free_energy"] = self.gibbs_free_energy
        d["temperatures"] = self.temperatures
        d["optimum_volumes"] = self.optimum_volumes
        for v, t in zip(self.optimum_volumes, self.temperatures):
            d["debye_temperature"].append(self.debye_temperature(v))
            d["gruneisen_parameter"].append(self.gruneisen_parameter(t, v))
            d["thermal_conductivity"].append(self.thermal_conductivity(t, v))
        return d
Пример #41
0
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
Пример #42
0
class QuasiharmonicDebyeApprox(object):
    """
    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.
    """
    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 optimize_gibbs_free_energy(self):
        """
        Evaluate the gibbs free energy as a function of V, T and P i.e
        G(V, T, P), minimize G(V, T, P) wrt V for each T and store the
        optimum values.

        Note: The data points for which the equation of state fitting fails
            are skipped.
        """
        temperatures = np.linspace(
            self.temperature_min,  self.temperature_max,
            int(np.ceil((self.temperature_max - self.temperature_min)
            / self.temperature_step) + 1))

        for t in temperatures:
            try:
                G_opt, V_opt = self.optimizer(t)
            except:
                if len(temperatures) > 1:
                    print("EOS fitting failed, so skipping this data point, {}".
                          format(t))
                    continue
                else:
                    raise
            self.gibbs_free_energy.append(G_opt)
            self.temperatures.append(t)
            self.optimum_volumes.append(V_opt)

    def optimizer(self, temperature):
        """
        Evaluate G(V, T, P) at the given temperature(and pressure) and
        minimize it wrt V.

        1. Compute the  vibrational helmholtz free energy, A_vib.
        2. Compute the gibbs free energy as a function of volume, temperature
            and pressure, G(V,T,P).
        3. Preform an equation of state fit to get the functional form of
            gibbs free energy:G(V, T, P).
        4. Finally G(V, P, T) is minimized with respect to V.

        Args:
            temperature (float): temperature in K

        Returns:
            float, float: G_opt(V_opt, T, P) in eV and V_opt in Ang^3.
        """
        G_V = []  # G for each volume
        # G = E(V) + PV + A_vib(V, T)
        for i, v in enumerate(self.volumes):
            G_V.append(self.energies[i] +
                       self.pressure * v * self.gpa_to_ev_ang +
                       self.vibrational_free_energy(temperature, v))

        # fit equation of state, G(V, T, P)
        eos_fit = self.eos.fit(self.volumes, G_V)
        # minimize the fit eos wrt volume
        # Note: the ref energy and the ref volume(E0 and V0) not necessarily
        # the same as minimum energy and min volume.
        volume_guess = eos_fit.volumes[np.argmin(eos_fit.energies)]
        min_wrt_vol = minimize(eos_fit.func, volume_guess)
        # G_opt=G(V_opt, T, P), V_opt
        return min_wrt_vol.fun, min_wrt_vol.x[0]

    def vibrational_free_energy(self, temperature, volume):
        """
        Vibrational Helmholtz free energy, A_vib(V, T).
        Eq(4) in doi.org/10.1016/j.comphy.2003.12.001

        Args:
            temperature (float): temperature in K
            volume (float)

        Returns:
            float: vibrational free energy in eV
        """
        y = self.debye_temperature(volume) / temperature
        return self.kb * self.natoms * temperature * (
            9./8. * y + 3 * np.log(1 - np.exp(-y)) - self.debye_integral(y))

    def vibrational_internal_energy(self, temperature, volume):
        """
        Vibrational internal energy, U_vib(V, T).
        Eq(4) in doi.org/10.1016/j.comphy.2003.12.001

        Args:
            temperature (float): temperature in K
            volume (float): in Ang^3

        Returns:
            float: vibrational internal energy in eV
        """
        y = self.debye_temperature(volume) / temperature
        return self.kb * self.natoms * temperature * (9./8. * y +
                                                      3*self.debye_integral(y))

    def debye_temperature(self, volume):
        """
        Calculates the debye temperature.
        Eq(6) in doi.org/10.1016/j.comphy.2003.12.001. Thanks to Joey.

        Eq(6) above is equivalent to Eq(3) in doi.org/10.1103/PhysRevB.37.790
        which does not consider anharmonic effects. Eq(20) in the same paper
        and Eq(18) in doi.org/10.1016/j.commatsci.2009.12.006 both consider
        anharmonic contributions to the Debye temperature through the Gruneisen
        parameter at 0K (Gruneisen constant).

        The anharmonic contribution is toggled by setting the anharmonic_contribution
        to True or False in the QuasiharmonicDebyeApprox constructor.

        Args:
            volume (float): in Ang^3

        Returns:
            float: debye temperature in K
         """
        term1 = (2./3. * (1. + self.poisson) / (1. - 2. * self.poisson))**1.5
        term2 = (1./3. * (1. + self.poisson) / (1. - self.poisson))**1.5
        f = (3. / (2. * term1 + term2))**(1. / 3.)
        debye = 2.9772e-11 * (volume / self.natoms) ** (-1. / 6.) * f * \
               np.sqrt(self.bulk_modulus/self.avg_mass)
        if self.anharmonic_contribution:
            gamma = self.gruneisen_parameter(0, self.ev_eos_fit.v0)  # 0K equilibrium Gruneisen parameter
            return debye * (self.ev_eos_fit.v0 / volume) ** (gamma)
        else:
            return debye


    @staticmethod
    def debye_integral(y):
        """
        Debye integral. Eq(5) in  doi.org/10.1016/j.comphy.2003.12.001

        Args:
            y (float): debye temperature/T, upper limit

        Returns:
            float: unitless
        """
        # floating point limit is reached around y=155, so values beyond that
        # are set to the limiting value(T-->0, y --> \infty) of
        # 6.4939394 (from wolfram alpha).
        factor = 3. / y ** 3
        if y < 155:
            integral = quadrature(lambda x: x ** 3 / (np.exp(x) - 1.), 0, y)
            return list(integral)[0] * factor
        else:
            return 6.493939 * factor

    def gruneisen_parameter(self, temperature, volume):
        """
        Slater-gamma formulation(the default):
            gruneisen paramter = - d log(theta)/ d log(V)
                               = - ( 1/6 + 0.5 d log(B)/ d log(V) )
                               = - (1/6 + 0.5 V/B dB/dV),
                                    where dB/dV = d^2E/dV^2 + V * d^3E/dV^3

        Mie-gruneisen formulation:
            Eq(31) in doi.org/10.1016/j.comphy.2003.12.001
            Eq(7) in Blanco et. al. Joumal of Molecular Structure (Theochem)
                368 (1996) 245-255
            Also se J.P. Poirier, Introduction to the Physics of the Earth’s
                Interior, 2nd ed. (Cambridge University Press, Cambridge,
                2000) Eq(3.53)

        Args:
            temperature (float): temperature in K
            volume (float): in Ang^3

        Returns:
            float: unitless
        """
        if isinstance(self.eos, PolynomialEOS):
            p = np.poly1d(self.eos.eos_params)
            # first derivative of energy at 0K wrt volume evaluated at the
            # given volume, in eV/Ang^3
            dEdV = np.polyder(p, 1)(volume)
            # second derivative of energy at 0K wrt volume evaluated at the
            # given volume, in eV/Ang^6
            d2EdV2 = np.polyder(p, 2)(volume)
            # third derivative of energy at 0K wrt volume evaluated at the
            # given volume, in eV/Ang^9
            d3EdV3 = np.polyder(p, 3)(volume)
        else:
            func = self.ev_eos_fit.func
            dEdV = derivative(func, volume, dx=1e-3)
            d2EdV2 = derivative(func, volume, dx=1e-3, n=2, order=5)
            d3EdV3 = derivative(func, volume, dx=1e-3, n=3, order=7)

        # Mie-gruneisen formulation
        if self.use_mie_gruneisen:
            p0 = dEdV
            return (self.gpa_to_ev_ang * volume *
                    (self.pressure + p0 / self.gpa_to_ev_ang) /
                    self.vibrational_internal_energy(temperature, volume))

        # Slater-gamma formulation
        # first derivative of bulk modulus wrt volume, eV/Ang^6
        dBdV = d2EdV2 + d3EdV3 * volume
        return -(1./6. + 0.5 * volume * dBdV /
                 FloatWithUnit(self.ev_eos_fit.b0_GPa, "GPa").to("eV ang^-3"))

    def thermal_conductivity(self, temperature, volume):
        """
        Eq(17) in 10.1103/PhysRevB.90.174107

        Args:
            temperature (float): temperature in K
            volume (float): in Ang^3

        Returns:
            float: thermal conductivity in W/K/m
        """
        gamma = self.gruneisen_parameter(temperature, volume)
        theta_d = self.debye_temperature(volume)  # K
        theta_a = theta_d * self.natoms**(-1./3.)  # K
        prefactor = (0.849 * 3 * 4**(1./3.)) / (20. * np.pi**3)
        # kg/K^3/s^3
        prefactor = prefactor * (self.kb/self.hbar)**3 * self.avg_mass
        kappa = prefactor / (gamma**2 - 0.514 * gamma + 0.228)
        # kg/K/s^3 * Ang = (kg m/s^2)/(Ks)*1e-10
        # = N/(Ks)*1e-10 = Nm/(Kms)*1e-10 = W/K/m*1e-10
        kappa = kappa * theta_a**2 * volume**(1./3.) * 1e-10
        return kappa

    def get_summary_dict(self):
        """
        Returns a dict with a summary of the computed properties.
        """
        d = defaultdict(list)
        d["pressure"] = self.pressure
        d["poisson"] = self.poisson
        d["mass"] = self.mass
        d["natoms"] = int(self.natoms)
        d["bulk_modulus"] = self.bulk_modulus
        d["gibbs_free_energy"] = self.gibbs_free_energy
        d["temperatures"] = self.temperatures
        d["optimum_volumes"] = self.optimum_volumes
        for v, t in zip(self.optimum_volumes, self.temperatures):
            d["debye_temperature"].append(self.debye_temperature(v))
            d["gruneisen_parameter"].append(self.gruneisen_parameter(t, v))
            d["thermal_conductivity"].append(self.thermal_conductivity(t, v))
        return d
Пример #43
0
 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)
Пример #44
0
 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)
Пример #45
0
 def check_fit(self, volumes, energies):
     eos = EOS('vinet')
     self.eos_fit = eos.fit(volumes, energies)