Source code for pyiron_atomistics.atomistics.master.quasi

# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.

import matplotlib
import numpy as np

from pyiron_atomistics.atomistics.master.murnaghan import MurnaghanJobGenerator
from pyiron_atomistics.atomistics.master.parallel import AtomisticParallelMaster

__author__ = "Jan Janssen"
__copyright__ = (
    "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - "
    "Computational Materials Design (CM) Department"
)
__version__ = "0.0.1"
__maintainer__ = "Jan Janssen"
__email__ = "janssen@mpie.de"
__status__ = "development"
__date__ = "Oct 29, 2020"


[docs] def calc_v0_from_fit_funct(fit_funct, x, save_range=0.0, return_ind=False): fit_funct_der = fit_funct.deriv().r fit_funct_der_r = fit_funct_der[fit_funct_der.imag == 0].real fit_funct_der_val = fit_funct.deriv(2)(fit_funct_der_r) select = ( (fit_funct_der_val > 0) & (fit_funct_der_r > np.min(x) * (1 - save_range)) & (fit_funct_der_r < np.max(x) * (1 + save_range)) ) v0_lst = fit_funct_der_r[select] if len(v0_lst) == 1: if return_ind: return v0_lst[0], (np.abs(x - v0_lst[0])).argmin() else: return v0_lst[0] else: select = fit_funct_der_val > 0 v0_lst = fit_funct_der_r[select] if len(v0_lst) == 1: if return_ind: return v0_lst[0], (np.abs(x - v0_lst[0])).argmin() else: return v0_lst[0] else: if return_ind: return None, None else: return None
[docs] class QuasiHarmonicJob(AtomisticParallelMaster): """ Obtain finite temperature properties in the framework of quasi harmonic approximation. For the theoretical understanding take a look at the Wikipedia page: https://en.wikipedia.org/wiki/Quasi-harmonic_approximation Example: >>> pr = Project('my_project') >>> lmp = pr.create_job('Lammps', 'lmp') >>> lmp.structure = structure_of_your_choice >>> phono = lmp.create_job('PhonopyJob', 'phono') >>> qha = phono.create_job('QuasiHarmonicJob', 'qha') >>> qha.run() The final results can be obtained through `qha.optimise_volume()`. The temperature range defined in the input can be modified afterwards. For this, follow these lines: >>> qha.input['temperature_end'] = temperature_end >>> qha.input['temperature_steps'] = temperature_steps >>> qha.input['temperature_start'] = temperature_start >>> qha.collect_output() """
[docs] def __init__(self, project, job_name="murnaghan"): """ Args: project: job_name: """ super(QuasiHarmonicJob, self).__init__(project, job_name) self.__version__ = "0.0.1" # define default input self.input["num_points"] = (11, "number of sample points") self.input["vol_range"] = ( 0.1, "relative volume variation around volume defined by ref_ham", ) self.input["temperature_start"] = 0 self.input["temperature_end"] = 500 self.input["temperature_steps"] = 10 self.input["polynomial_degree"] = 3 self.input["strains"] = ( None, "List of strains that should be calculated. If given vol_range and num_points take no effect.", ) self.input["axes"] = ( ["x", "y", "z"], "Axes along which the strain will be applied", ) self._job_generator = MurnaghanJobGenerator(self)
[docs] def collect_output(self): free_energy_lst, entropy_lst, cv_lst, volume_lst = [], [], [], [] for job_id in self.child_ids: job = self.project_hdf5.load(job_id) # the underlying phonopy job might create a supercell; if its # reference job is interactive this is reflected in its # job.structure and the output quantities, so we need to rescale # all the output here; if the structure did not change the # conversion_factor will simply be 1. conversion_factor = len(self.structure) / len(job.structure) thermal_properties = job.get_thermal_properties( temperatures=np.linspace( self.input["temperature_start"], self.input["temperature_end"], int(self.input["temperature_steps"]), ) ) free_energy_lst.append(thermal_properties.free_energies * conversion_factor) entropy_lst.append(thermal_properties.entropy * conversion_factor) cv_lst.append(thermal_properties.cv * conversion_factor) volume_lst.append(job.structure.get_volume() * conversion_factor) arg_lst = np.argsort(volume_lst) self._output["free_energy"] = np.array(free_energy_lst)[arg_lst] self._output["entropy"] = np.array(entropy_lst)[arg_lst] self._output["cv"] = np.array(cv_lst)[arg_lst] temperature_mesh, volume_mesh = np.meshgrid( np.linspace( self.input["temperature_start"], self.input["temperature_end"], int(self.input["temperature_steps"]), ), np.array(volume_lst)[arg_lst], ) self._output["volumes"] = volume_mesh self._output["temperatures"] = temperature_mesh with self.project_hdf5.open("output") as hdf5_out: for key, val in self._output.items(): hdf5_out[key] = val
[docs] def optimise_volume(self, bulk_eng): """ Get finite temperature properties. Args: bulk_eng (numpy.ndarray): array of bulk energies corresponding to the box sizes given in the quasi harmonic calculations. For the sake of compatibility, it is strongly recommended to use the pyiron Murnaghan class (and make sure that you use the same values for `num_points` and `vol_range`). Returns: volume, free energy, entropy, heat capacity The corresponding temperature values can be obtained from `job['output/temperatures'][0]` """ v0_lst, free_eng_lst, entropy_lst, cv_lst = [], [], [], [] for i, [t, free_energy, cv, entropy, v] in enumerate( zip( self["output/temperatures"].T, self["output/free_energy"].T, self["output/cv"].T, self["output/entropy"].T, self["output/volumes"].T, ) ): fit = np.poly1d( np.polyfit( v, free_energy + bulk_eng, int(self.input["polynomial_degree"]) ) ) v0, ind = calc_v0_from_fit_funct( fit_funct=fit, x=v, save_range=0.0, return_ind=True ) if v0 is not None: v0_lst.append(v0) free_eng_lst.append(fit(v0)) entropy_lst.append(entropy[ind]) cv_lst.append(cv[ind]) else: v0_lst.append(np.nan) free_eng_lst.append(np.nan) entropy_lst.append(np.nan) cv_lst.append(np.nan) return v0_lst, free_eng_lst, entropy_lst, cv_lst
[docs] def plot_free_energy_volume_temperature( self, murnaghan_job=None, temperature_start=None, temperature_end=None, temperature_steps=None, color_map="coolwarm", axis=None, *args, **kwargs, ): """ Plot volume vs free energy curves for defined temperatures. If no Murnaghan job is assigned, plots free energy without total electronic energy at T=0. Args: murnaghan_job: job_name or job_id of the Murnaghan job. if None, total electronic energy at T=0 is set to zero. temperature_start: if None, value will be used from job.input['temperature_start'] temperature_end: if None, value will be used from job.input['temperature_end'] temperature_steps: if None, value will be used from job.input['temperature_steps'] color_map: colormaps options accessible via matplotlib.cm.get_cmap. Default is 'coolwarm'. axis (matplotlib axis, optional): plot to this axis, if not given a new one is created. *args: passed through to matplotlib.pyplot.plot when plotting free energies. **kwargs: passed through to matplotlib.pyplot.plot when plotting free energies. Returns: matplib axis: the axis the figure has been drawn to, if axis is given the same object is returned. """ energy_zero = 0 if murnaghan_job != None: job_murn = self.project.load(murnaghan_job) energy_zero = job_murn["output/energy"] if not self.status.finished: raise ValueError( "QHA Job must be successfully run, before calling this method." ) if not job_murn.status.finished: raise ValueError( "Murnaghan Job must be successfully run, before calling this method." ) if axis is None: _, axis = matplotlib.pyplot.subplots(1, 1) cmap = matplotlib.cm.get_cmap(color_map) if temperature_start != None: self.input["temperature_start"] = temperature_start if temperature_end != None: self.input["temperature_end"] = temperature_end if temperature_steps != None: self.input["temperature_steps"] = temperature_steps self.collect_output() for i, [t, free_energy, v] in enumerate( zip( self["output/temperatures"].T, self["output/free_energy"].T, self["output/volumes"].T, ) ): color = cmap(i / len(self["output/temperatures"].T)) axis.plot(v, free_energy + energy_zero, color=color) axis.set_xlabel("Volume") axis.set_ylabel("Free Energy") temperatures = self["output/temperatures"] normalize = matplotlib.colors.Normalize( vmin=temperatures.min(), vmax=temperatures.max() ) scalarmappaple = matplotlib.cm.ScalarMappable(norm=normalize, cmap=cmap) scalarmappaple.set_array(temperatures) cbar = matplotlib.pyplot.colorbar(scalarmappaple, ax=axis) cbar.set_label("Temperature") return axis