From 011e6f032b3f40862253d1c0c007ff7dbce4b898 Mon Sep 17 00:00:00 2001 From: Victor Trinquet <60815457+VicTrqt@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:21:40 +0200 Subject: [PATCH 1/9] New factories for atomate2 WFs (#298) * Read SHG AR contrib * new factories for Atomate2 SHG WF * update factories.py __all__ * Atomate2 convention: gs_inp -> gs_input * nband in ddkpert_from_gsinput because of factory_kwrags in atomate2 * Add ONCVPSP LDA v0.4 * test spinat * modif set_autospinat * modif for atomate2 * fix EventParser for RelaxConvergenceWarning + quick fix wrapper as_dict pmg_serialize * remove VT * remove autoparal 1 of GS from ddk, dde, dte * activate ONCVPSP-LDA-SR-v0.3 * backward compatibility * remove comments --- abipy/abio/factories.py | 99 ++++++++++++++++++++--- abipy/abio/inputs.py | 175 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 261 insertions(+), 13 deletions(-) diff --git a/abipy/abio/factories.py b/abipy/abio/factories.py index 54737e1ec..3284df01e 100644 --- a/abipy/abio/factories.py +++ b/abipy/abio/factories.py @@ -32,6 +32,9 @@ "piezo_elastic_inputs_from_gsinput", "scf_piezo_elastic_inputs", "scf_for_phonons", + "ddkpert_from_gsinput", + "ddepert_from_gsinput", + "dtepert_from_gsinput", "dte_from_gsinput", "dfpt_from_gsinput", "minimal_scf_input", @@ -1376,8 +1379,80 @@ def scf_for_phonons(structure, pseudos, kppa=None, ecut=None, pawecutdg=None, nb return abiinput +def ddkpert_from_gsinput(gs_input, ddk_pert, nband=None, use_symmetries=False, ddk_tol=None, manager=None) -> AbinitInput: + """ + Returns an |AbinitInput| to perform a DDK calculations for a specific perturbation and based on a ground state |AbinitInput|. + + Args: + gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. + ddk_pert: dict with the Abinit variables defining the perturbation + Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, + use_symmetries: boolean that determines if the irreducible components of the perturbation are used. + Default to False. (TODO: Should be implemented) + ddk_tol: a dictionary with a single key defining the type of tolerance used for the DDK calculations and its value. Default: {"tolvrs": 1.0e-22}. + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + """ + gs_input = gs_input.deepcopy() + gs_input.pop_irdvars() + gs_input.pop_vars(['autoparal', 'npfft']) + + if ddk_tol is None: + ddk_tol = {"tolwfr": 1.0e-22} + + ddk_inp = gs_input.make_ddkpert_input(perturbation=ddk_pert, use_symmetries=use_symmetries, tolerance=ddk_tol, manager=manager) + + # TODO: add to see how it behaves wrt nband from atomate2 + #if nband is None: + # nband = _find_nscf_nband_from_gsinput(gs_input) + + #ddk_inp.set_vars(nband=nband) + + return ddk_inp + +def ddepert_from_gsinput(gs_input, dde_pert, use_symmetries=True, dde_tol=None, manager=None) -> AbinitInput: + """ + Returns an |AbinitInput| to perform a DDE calculations for a specific perturbation and based on a ground state |AbinitInput|. + + Args: + gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. + dde_pert: dict with the Abinit variables defining the perturbation + Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, + use_symmetries: boolean that determines if the irreducible components of the perturbation are used. + Default to True. Should be set to False for nonlinear coefficients calculation. + dde_tol: a dictionary with a single key defining the type of tolerance used for the DDE calculations and + its value. Default: {"tolvrs": 1.0e-22}. + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + """ + gs_input = gs_input.deepcopy() + gs_input.pop_irdvars() + gs_input.pop_vars(['autoparal', 'npfft']) + + if dde_tol is None: + dde_tol = {"tolvrs": 1.0e-22} + + dde_inp = gs_input.make_ddepert_input(perturbation=dde_pert, use_symmetries=use_symmetries, tolerance=dde_tol, manager=manager) + + return dde_inp + +def dtepert_from_gsinput(gs_input, dte_pert, manager=None) -> AbinitInput: + """ + Returns an |AbinitInput| to perform a DTE calculations for a specific perturbation and based on a ground state |AbinitInput|. -def dte_from_gsinput(gs_inp, use_phonons=True, ph_tol=None, ddk_tol=None, dde_tol=None, + Args: + gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. + dte_pert: dict with the Abinit variables defining the perturbation + Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + """ + gs_input = gs_input.deepcopy() + gs_input.pop_irdvars() + gs_input.pop_vars(['autoparal', 'npfft']) + + dte_inp = gs_input.make_dtepert_input(perturbation=dte_pert, manager=manager) + + return dte_inp + +def dte_from_gsinput(gs_input, use_phonons=True, ph_tol=None, ddk_tol=None, dde_tol=None, skip_dte_permutations=False, manager=None) -> MultiDataset: """ Returns a list of inputs in the form of a |MultiDataset| to perform calculations of non-linear properties, based on @@ -1387,7 +1462,7 @@ def dte_from_gsinput(gs_inp, use_phonons=True, ph_tol=None, ddk_tol=None, dde_to All of them have the tag "dfpt". Args: - gs_inp: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. + gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. use_phonons: determine wether the phonon perturbations at gamma should be included or not ph_tol: a dictionary with a single key defining the type of tolerance used for the phonon calculations and its value. Default: {"tolvrs": 1.0e-22}. @@ -1400,8 +1475,8 @@ def dte_from_gsinput(gs_inp, use_phonons=True, ph_tol=None, ddk_tol=None, dde_to duplicated outputs. manager: |TaskManager| of the task. If None, the manager is initialized from the config file. """ - gs_inp = gs_inp.deepcopy() - gs_inp.pop_irdvars() + gs_input = gs_input.deepcopy() + gs_input.pop_irdvars() if ph_tol is None: ph_tol = {"tolvrs": 1.0e-22} @@ -1414,26 +1489,26 @@ def dte_from_gsinput(gs_inp, use_phonons=True, ph_tol=None, ddk_tol=None, dde_to multi = [] - multi_ddk = gs_inp.make_ddk_inputs(tolerance=ddk_tol) + multi_ddk = gs_input.make_ddk_inputs(tolerance=ddk_tol) multi_ddk.add_tags(atags.DDK) multi.extend(multi_ddk) - multi_dde = gs_inp.make_dde_inputs(dde_tol, use_symmetries=False, manager=manager) + multi_dde = gs_input.make_dde_inputs(dde_tol, use_symmetries=False, manager=manager) multi_dde.add_tags(atags.DDE) multi.extend(multi_dde) if use_phonons: - multi_ph = gs_inp.make_ph_inputs_qpoint([0, 0, 0], ph_tol, manager=manager) + multi_ph = gs_input.make_ph_inputs_qpoint([0, 0, 0], ph_tol, manager=manager) multi_ph.add_tags(atags.PH_Q_PERT) multi.extend(multi_ph) # non-linear calculations do not accept more bands than those in the valence. Set the correct values. # Do this as last, so not to interfere with the the generation of the other steps. - nval = gs_inp.structure.num_valence_electrons(gs_inp.pseudos) - nval -= gs_inp['charge'] + nval = gs_input.structure.num_valence_electrons(gs_input.pseudos) + nval -= gs_input['charge'] nband = int(round(nval / 2)) - gs_inp.set_vars(nband=nband) - gs_inp.pop('nbdbuf', None) - multi_dte = gs_inp.make_dte_inputs(phonon_pert=use_phonons, skip_permutations=skip_dte_permutations, + gs_input.set_vars(nband=nband) + gs_input.pop('nbdbuf', None) + multi_dte = gs_input.make_dte_inputs(phonon_pert=use_phonons, skip_permutations=skip_dte_permutations, manager=manager) multi_dte.add_tags(atags.DTE) multi.extend(multi_dte) diff --git a/abipy/abio/inputs.py b/abipy/abio/inputs.py index 48c8be0b7..a41e2095e 100644 --- a/abipy/abio/inputs.py +++ b/abipy/abio/inputs.py @@ -671,6 +671,11 @@ def runlevel(self): else: runlevel.update([atags.MANY_BODY, atags.SIGMA]) + elif optdriver == 5: + # DTE run. + runlevel.add(atags.DFPT) + runlevel.add(atags.DTE) + elif optdriver == 99: # BSE run runlevel.update([atags.MANY_BODY, atags.BSE]) @@ -1921,6 +1926,65 @@ def make_ph_inputs_qpoint(self, qpt, tolerance=None, return ph_inputs + def make_ddkpert_input(self, perturbation, kptopt=2, only_vk=False, use_symmetries=False, tolerance=None, manager=None) -> AbinitInput: + """ + Returns |AbinitInput| for the calculation of an electric field perturbation. + This function should be called with an input that represents a GS run and + an electric field perturbation. + + Args: + perturbation: dict with the Abinit variables defining the irreducible perturbation + Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, + kptopt: 2 to take into account time-reversal symmetry. Note that kptopt 1 is not available. + only_vk: If only matrix elements of the velocity operator are needed. + First-order wavefunctions won't be converged --> not usable for other DFPT calculations. + use_symmetries: boolean that determines if the irreducible components of the perturbation are used. + Default to False. (TODO: Should be implemented) + tolerance: dict {varname: value} with the tolerance to be used in the DFPT run. + Defaults to {"tolwfr": 1.0e-22}. + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + """ + if tolerance is None: + tolerance = {"tolwfr": 1.0e-22} + + if len(tolerance) != 1 or any(k not in _TOLVARS for k in tolerance): + raise self.Error("Invalid tolerance: %s" % str(tolerance)) + + if "tolvrs" in tolerance: + raise self.Error("tolvrs should not be used in a DDK calculation") + + inp = self.deepcopy() + inp.pop_irdvars() + + rfdir = 3 * [0] + rfdir[perturbation['idir'] - 1] = 1 + + inp.set_vars( + rfelfd=2, # Activate the calculation of the d/dk perturbation + # only the derivative of ground-state wavefunctions with respect to k + rfdir=rfdir, # Direction of the ddk. + nqpt=1, # One wavevector is to be considered + qpt=(0, 0, 0), # q-wavevector. + kptopt=kptopt, # 2 to take into account time-reversal symmetry. + iscf=-3, # The d/dk perturbation must be treated in a non-self-consistent way + ) + + inp.pop_vars("dfpt_sciss") # TODO: to add? + + if only_vk: + inp.set_vars(nstep=1, nline=1) + + # TODO: to implement + #if not use_symmetries: + # inp.set_vars( + # comment="Input file for ddk calculation without symmetries.", + # ) + + inp.pop_tolerances() + inp.set_vars(tolerance, comment="Input file for DDK calculation.") + + return inp + def make_ddk_inputs(self, tolerance=None, kptopt=2, only_vk=False, manager=None) -> MultiDataset: """ Return inputs for performing DDK calculations. @@ -2009,7 +2073,56 @@ def make_dkdk_input(self, tolerance=None, kptopt=2, manager=None) -> AbinitInput dkdk_input.set_vars(tolerance) return dkdk_input + + def make_ddepert_input(self, perturbation, use_symmetries=True, tolerance=None, manager=None) -> AbinitInput: + """ + Returns |AbinitInput| for the calculation of an electric field perturbation. + This function should be called with an input that represents a GS run and + an electric field perturbation. + + Args: + perturbation: dict with the Abinit variables defining the irreducible perturbation + Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, + use_symmetries: boolean that determines if the irreducible components of the perturbation are used. + Default to True. Should be set to False for nonlinear coefficients calculation. + tolerance: dict {varname: value} with the tolerance to be used in the DFPT run. + Defaults to {"tolvrs": 1.0e-22}. + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + """ + if tolerance is None: + tolerance = {"tolvrs": 1.0e-22} + + if len(tolerance) != 1 or any(k not in _TOLVARS for k in tolerance): + raise self.Error("Invalid tolerance: %s" % str(tolerance)) + inp = self.deepcopy() + inp.pop_irdvars() + + rfdir = 3 * [0] + rfdir[perturbation['idir'] - 1] = 1 + + inp.set_vars( + rfdir=rfdir, # Direction of the dde perturbation. + comment="Input file for DDE calculation with symmetries.", + rfelfd=3, # Activate the calculation of the electric field perturbation + # Assuming the data on derivative of ground-state wavefunction with respect + # to k (DDK) is available on disk and will be read with getddk/irddk + nqpt=1, # One wavevector is to be considered + qpt=(0, 0, 0), # q-wavevector. + kptopt=2, # Take into account time-reversal symmetry. + ) + + if not use_symmetries: + inp.set_vars( + comment="Input file for DDE calculation without symmetries.", + prepanl=1, + ) + + inp.pop_tolerances() + inp.set_vars(tolerance) + + return inp + def make_dde_inputs(self, tolerance=None, use_symmetries=True, manager=None) -> MultiDataset: """ Return |MultiDataset| inputs for the calculation of electric field perturbations. @@ -2074,7 +2187,67 @@ def make_dde_inputs(self, tolerance=None, use_symmetries=True, manager=None) -> return multi - def make_dte_inputs(self, phonon_pert=False, skip_permutations=False, ixc=7, manager=None) -> MultiDataset: + def make_dtepert_input(self, perturbation, ixc=None, manager=None) -> AbinitInput: + """ + Return |AbinitInput| for DTE calculation for a given perturbation. + This functions should be called with an input that represents a GS run. + + Args: + perturbation: dict with the Abinit variables defining the irreducible perturbation + Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, + ixc: Value of ixc variable. Used to overwrite the default value read from pseudos. + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + """ + inp = self.deepcopy() + # non-linear calculations do not accept more bands than those in the valence. Set the correct values. + # Do this as last, so not to interfere with the the generation of the other steps. + nval = inp.structure.num_valence_electrons(inp.pseudos) + nval -= inp['charge'] + nband = int(round(nval / 2)) + inp.set_vars(nband=nband) + inp.pop('nbdbuf', None) + + if ixc is not None: + inp.set_vars(ixc=int(ixc)) + + # See tutorespfn/Input/tnlo_2.in + na = len(self.structure) + + rfdir1 = 3 * [0] + rfdir1[perturbation['i1dir'] - 1] = 1 + rfdir2 = 3 * [0] + rfdir2[perturbation['i2dir'] - 1] = 1 + rfdir3 = 3 * [0] + rfdir3[perturbation['i3dir'] - 1] = 1 + + # atpol if needed. Since there can be only one spatial perturbation + m = min(perturbation['i1pert'], perturbation['i2pert'], perturbation['i3pert']) + atpol = [m, m] if m <= na else None + + inp.set_vars( + # Activate the calculation of the electric field perturbation + d3e_pert1_elfd=1 if perturbation['i1pert'] == na + 2 else 0, + d3e_pert2_elfd=1 if perturbation['i2pert'] == na + 2 else 0, + d3e_pert3_elfd=1 if perturbation['i3pert'] == na + 2 else 0, + d3e_pert1_dir=rfdir1, # Direction of the dte perturbation. + d3e_pert2_dir=rfdir2, + d3e_pert3_dir=rfdir3, + d3e_pert1_phon=1 if perturbation['i1pert'] <= na else 0, + d3e_pert2_phon=1 if perturbation['i2pert'] <= na else 0, + d3e_pert3_phon=1 if perturbation['i3pert'] <= na else 0, + d3e_pert1_atpol=atpol, + nqpt=1, # One wavevector is to be considered + qpt=(0, 0, 0), # q-wavevector. + optdriver=5, # non-linear response functions, using the 2n+1 theorem. + kptopt=2, # Take into account time-reversal symmetry. + comment="Input file for DTE calculation.", + ) + + inp.pop_tolerances() + + return inp + + def make_dte_inputs(self, phonon_pert=False, skip_permutations=False, ixc=None, manager=None) -> MultiDataset: """ Return |MultiDataset| inputs for DTE calculation. This functions should be called with an input that represents a GS run. From 47fada0b37ff0fc9fd198122538ff6e8f892fc4b Mon Sep 17 00:00:00 2001 From: Matteo Giantomassi Date: Mon, 14 Oct 2024 17:54:22 +0200 Subject: [PATCH 2/9] Sketching unit tests for new factory functions --- abipy/abio/factories.py | 30 ++++++++++++--------- abipy/abio/tests/test_factories.py | 43 +++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/abipy/abio/factories.py b/abipy/abio/factories.py index 3284df01e..2fc7e928b 100644 --- a/abipy/abio/factories.py +++ b/abipy/abio/factories.py @@ -1381,16 +1381,17 @@ def scf_for_phonons(structure, pseudos, kppa=None, ecut=None, pawecutdg=None, nb def ddkpert_from_gsinput(gs_input, ddk_pert, nband=None, use_symmetries=False, ddk_tol=None, manager=None) -> AbinitInput: """ - Returns an |AbinitInput| to perform a DDK calculations for a specific perturbation and based on a ground state |AbinitInput|. + Returns an |AbinitInput| to perform a DDK calculations for a specific perturbation based on a ground state |AbinitInput|. Args: - gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. + gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. ddk_pert: dict with the Abinit variables defining the perturbation Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, use_symmetries: boolean that determines if the irreducible components of the perturbation are used. Default to False. (TODO: Should be implemented) - ddk_tol: a dictionary with a single key defining the type of tolerance used for the DDK calculations and its value. Default: {"tolvrs": 1.0e-22}. - manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + ddk_tol: a dictionary with a single key defining the type of tolerance used for the DDK calculations and its value. + Default: {"tolvrs": 1.0e-22}. + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. """ gs_input = gs_input.deepcopy() gs_input.pop_irdvars() @@ -1409,19 +1410,20 @@ def ddkpert_from_gsinput(gs_input, ddk_pert, nband=None, use_symmetries=False, d return ddk_inp + def ddepert_from_gsinput(gs_input, dde_pert, use_symmetries=True, dde_tol=None, manager=None) -> AbinitInput: """ - Returns an |AbinitInput| to perform a DDE calculations for a specific perturbation and based on a ground state |AbinitInput|. + Returns an |AbinitInput| to perform a DDE calculations for a specific perturbation based on a ground state |AbinitInput|. Args: - gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. + gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. dde_pert: dict with the Abinit variables defining the perturbation - Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, + Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, use_symmetries: boolean that determines if the irreducible components of the perturbation are used. Default to True. Should be set to False for nonlinear coefficients calculation. - dde_tol: a dictionary with a single key defining the type of tolerance used for the DDE calculations and + dde_tol: a dictionary with a single key defining the type of tolerance used for the DDE calculations and its value. Default: {"tolvrs": 1.0e-22}. - manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. """ gs_input = gs_input.deepcopy() gs_input.pop_irdvars() @@ -1434,15 +1436,16 @@ def ddepert_from_gsinput(gs_input, dde_pert, use_symmetries=True, dde_tol=None, return dde_inp + def dtepert_from_gsinput(gs_input, dte_pert, manager=None) -> AbinitInput: """ Returns an |AbinitInput| to perform a DTE calculations for a specific perturbation and based on a ground state |AbinitInput|. Args: - gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. + gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. dte_pert: dict with the Abinit variables defining the perturbation - Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, - manager: |TaskManager| of the task. If None, the manager is initialized from the config file. + Example: {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]}, + manager: |TaskManager| of the task. If None, the manager is initialized from the config file. """ gs_input = gs_input.deepcopy() gs_input.pop_irdvars() @@ -1452,6 +1455,7 @@ def dtepert_from_gsinput(gs_input, dte_pert, manager=None) -> AbinitInput: return dte_inp + def dte_from_gsinput(gs_input, use_phonons=True, ph_tol=None, ddk_tol=None, dde_tol=None, skip_dte_permutations=False, manager=None) -> MultiDataset: """ @@ -1509,7 +1513,7 @@ def dte_from_gsinput(gs_input, use_phonons=True, ph_tol=None, ddk_tol=None, dde_ gs_input.set_vars(nband=nband) gs_input.pop('nbdbuf', None) multi_dte = gs_input.make_dte_inputs(phonon_pert=use_phonons, skip_permutations=skip_dte_permutations, - manager=manager) + manager=manager) multi_dte.add_tags(atags.DTE) multi.extend(multi_dte) diff --git a/abipy/abio/tests/test_factories.py b/abipy/abio/tests/test_factories.py index 334ced144..dc94ea853 100644 --- a/abipy/abio/tests/test_factories.py +++ b/abipy/abio/tests/test_factories.py @@ -1,3 +1,4 @@ +import json import abipy.data as abidata import abipy.abilab as abilab @@ -7,8 +8,9 @@ from abipy.abio.factories import * from abipy.abio.factories import (BandsFromGsFactory, IoncellRelaxFromGsFactory, HybridOneShotFromGsFactory, ScfForPhononsFactory, PhononsFromGsFactory, PiezoElasticFactory, PiezoElasticFromGsFactory, ShiftMode) -from abipy.abio.factories import _find_nscf_nband_from_gsinput -import json +from abipy.abio.factories import _find_nscf_nband_from_gsinput, minimal_scf_input +from abipy.abio.input_tags import DDK, DDE, PH_Q_PERT, STRAIN, DTE, PH_Q_PERT + write_inputs_to_json = False @@ -304,7 +306,6 @@ def test_phonons_from_gsinput(self): with_bec=False, ph_tol=None, ddk_tol=None, dde_tol=None) self.abivalidate_multi(multi) - from abipy.abio.input_tags import DDK, DDE, PH_Q_PERT inp_ddk = multi.filter_by_tags(DDK)[0] inp_dde = multi.filter_by_tags(DDE)[0] inp_ph_q_pert_1 = multi.filter_by_tags(PH_Q_PERT)[0] @@ -499,7 +500,7 @@ def test_dfpt_from_gsinput(self): do_dte=True, ph_tol=None, ddk_tol=None, dde_tol=None) self.abivalidate_multi(multi) - from abipy.abio.input_tags import DDK, DDE, PH_Q_PERT, STRAIN, DTE + inp_ddk = multi.filter_by_tags(DDK)[0] inp_dde = multi.filter_by_tags(DDE)[0] inp_ph_q_pert_1 = multi.filter_by_tags(PH_Q_PERT)[0] @@ -530,10 +531,40 @@ def test_dfpt_from_gsinput(self): self.assert_input_equality('dfpt_from_gsinput_dte.json', inp_dte) def test_minimal_scf_input(self): - from abipy.abio.factories import minimal_scf_input inp = minimal_scf_input(self.si_structure, self.si_pseudo) self.abivalidate_input(inp) - self.assertEqual(inp["nband"], 1) self.assertEqual(inp["nstep"], 0) + + def test_ddkpert_from_gsinput(self): + gs_inp = gs_input(self.si_structure, self.si_pseudo, kppa=None, ecut=2, spin_mode="unpolarized") + gs_inp["nband"] = 4 + gs_inp["autoparal"] = 1 + gs_inp["npfft"] = 10 + + ddk_pert = {'idir': 1, 'ipert': 3, 'qpt': [0.0, 0.0, 0.0]} + ddk_input = ddkpert_from_gsinput(gs_inp, ddk_pert) + assert ddk_input["tolwfr"] == 1.0e-22 + assert "autoparal" not in ddk_input + assert "npfft" not in ddk_input + self.abivalidate_input(ddk_input) + + dde_pert = {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]} + dde_input = ddepert_from_gsinput(gs_inp, dde_pert) + assert "autoparal" not in dde_input + assert "npfft" not in dde_input + assert dde_input["tolvrs"] == 1.0e-22 + self.abivalidate_input(dde_input) + + #dte_pert = {'i1dir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]} + #dte_input = dtepert_from_gsinput(gs_inp, dte_pert) + #assert "autoparal" not in dte_input + #assert "npfft" not in dte_input + #assert dte_input["tolvrs"] == 1.0e-22 + #self.abivalidate_input(dte_input) + + #def test_dte_from_gsinput(self): + # gs_inp = gs_input(self.si_structure, self.si_pseudo, kppa=None, ecut=2, spin_mode="unpolarized") + # multi = dte_from_gsinput(gs_inp, use_phonons=True) + # self.abivalidate_input(multi) From 2747d22ab18f25d32f9d08b2ef55f4c569a2deb2 Mon Sep 17 00:00:00 2001 From: Max Mignolet <33008868+MaxMignolet@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:14:00 +0200 Subject: [PATCH 3/9] Phonon angular mometum + PhononWork:prepgkk option (#296) * add plot of phonon angulor momentum * add qptopt option in Phononwork for the generation of the q-mesh * add support for GSTORE dependency * add prepgkk option for phonon work * minor change * documetation --------- Co-authored-by: Max Mignolet Co-authored-by: Mignolet Maxime Co-authored-by: Max Mignolet --- abipy/core/abinit_units.py | 1 + abipy/dfpt/phonons.py | 77 +++++++++++++++++++++++++++++++++++++- abipy/flowtk/utils.py | 1 + abipy/flowtk/works.py | 12 ++++-- 4 files changed, 85 insertions(+), 6 deletions(-) diff --git a/abipy/core/abinit_units.py b/abipy/core/abinit_units.py index c45961297..85a091f5d 100644 --- a/abipy/core/abinit_units.py +++ b/abipy/core/abinit_units.py @@ -119,6 +119,7 @@ def wlabel_from_units(units: str, unicode=False) -> str: 'cm-1': r'Frequency (cm$^{-1}$)', 'cm^-1': r'Frequency (cm$^{-1}$)', 'thz': r'Frequency (Thz)', + 'hbar': r'Angular momentum ($\hbar$)', } try: s = d[units.lower().strip()] diff --git a/abipy/dfpt/phonons.py b/abipy/dfpt/phonons.py index f6462f1a5..1f9e8ea12 100644 --- a/abipy/dfpt/phonons.py +++ b/abipy/dfpt/phonons.py @@ -159,6 +159,7 @@ def from_file(cls, filepath: str) -> PhononBands: qpoints=qpoints, phfreqs=r.read_phfreqs(), phdispl_cart=r.read_phdispl_cart(), + phangmom=r.read_phangmom(), amu=amu, non_anal_ph=non_anal_ph, epsinf=epsinf, zcart=zcart, @@ -237,8 +238,9 @@ def set_phonopy_obj_from_ananc(self, ananc, supercell_matrix, symmetrize_tensors symprec=symprec, set_masses=set_masses) self.phonopy_obj = ph - def __init__(self, structure, qpoints, phfreqs, phdispl_cart, non_anal_ph=None, amu=None, - epsinf=None, zcart=None, linewidths=None, phonopy_obj=None): + def __init__(self, structure, qpoints, phfreqs, phdispl_cart, phangmom=None, + non_anal_ph=None, amu=None, epsinf=None, zcart=None, linewidths=None, + phonopy_obj=None): """ Args: structure: |Structure| object. @@ -246,6 +248,7 @@ def __init__(self, structure, qpoints, phfreqs, phdispl_cart, non_anal_ph=None, phfreqs: Phonon frequencies in eV. phdispl_cart: [nqpt, 3*natom, 3*natom] array with displacement in Cartesian coordinates in Angstrom. The last dimension stores the cartesian components. + phangmom: Phonon angular momentum in units of hbar. non_anal_ph: :class:`NonAnalyticalPh` with information of the non analytical contribution None if contribution is not present. amu: dictionary that associates the atomic species present in the structure to the values of the atomic @@ -269,6 +272,10 @@ def __init__(self, structure, qpoints, phfreqs, phdispl_cart, non_anal_ph=None, # The last dimension stores the cartesian components. self.phdispl_cart = phdispl_cart + # phonon angular momentum in units of hbar + # ndarray of shape (nqpt, 3*natom, 3) + self.phangmom = phangmom + # Handy variables used to loop. self.num_atoms = structure.num_sites self.num_branches = 3 * self.num_atoms @@ -1319,6 +1326,66 @@ def plot_colored_matched(self, ax=None, units="eV", qlabels=None, branch_range=N return fig + @add_fig_kwargs + def plot_phangmom(self, ax=None, pj_dir=[0, 0, 1], units="hbar", + qlabels=None, branch_range=None, colormap="rainbow", + max_colors=None, **kwargs): + r""" + Plot the phonon angular momentum with different colors for each line. + + Args: + ax: |matplotlib-Axes| or None if a new figure should be created. + pj_dir: direction along which the angular momentum is projected + in cartesian coordinates + units: Units for phonon plots. Only possible option: "hbar", maybe others to come ? + Case-insensitive. + qlabels: dictionary whose keys are tuples with the reduced coordinates of the q-points. + The values are the labels. e.g. ``qlabels = {(0.0,0.0,0.0): "$\Gamma$", (0.5,0,0): "L"}``. + branch_range: Tuple specifying the minimum and maximum branch_i index to plot + (default: all branches are plotted). + colormap: matplotlib colormap to determine the colors available. The colors will be chosen not in a + sequential order to avoid difficulties in distinguishing the lines. + http://matplotlib.sourceforge.net/examples/pylab_examples/show_colormaps.html + max_colors: maximum number of colors to be used. If max_colors < num_braches the colors will be reapeated. + It may be useful to better distinguish close bands when the number of branches is large. + + Returns: |matplotlib-Figure| + """ + # Select the band range. + if branch_range is None: + branch_range = range(self.num_branches) + else: + branch_range = range(branch_range[0], branch_range[1], 1) + + ax, fig, plt = get_ax_fig_plt(ax=ax) + + # Decorate the axis (e.g add ticks and labels). + self.decorate_ax(ax, units=units, qlabels=qlabels) + + first_xx = 0 + lines = [] + + if max_colors is None: + max_colors = len(branch_range) + + colormap = plt.get_cmap(colormap) + + sorted_phangmom = np.empty_like(self.phangmom) + for i, pam in enumerate(self.phangmom): + ind = self.split_matched_indices[0][i] + sorted_phangmom[i, :, :] = pam[ind, :] + + pj_dir = np.array(pj_dir) + pj_dir = pj_dir / np.linalg.norm(pj_dir) + proj_phangmom = np.dot(sorted_phangmom, pj_dir) + colors = itertools.cycle(colormap(np.linspace(0, 1, max_colors))) + for branch_i in branch_range: + kwargs = dict(kwargs) + kwargs['color'] = next(colors) + lines.extend(ax.plot(proj_phangmom[:, branch_i], **kwargs)) + + return fig + @add_fig_kwargs def plot_lt_character(self, units="eV", qlabels=None, ax=None, xlims=None, ylims=None, scale_size=50, use_becs=True, colormap="jet", fontsize=12, **kwargs) -> Figure: @@ -2909,6 +2976,12 @@ def read_phdispl_cart(self): """ return self.read_value("phdispl_cart", cmode="c") + def read_phangmom(self): + """ + Real array with the phonon angular momentum in units of hbar + """ + return self.read_value("phangmom", default=None) + def read_amu(self): """The atomic mass units""" return self.read_value("atomic_mass_units", default=None) diff --git a/abipy/flowtk/utils.py b/abipy/flowtk/utils.py index 2e43fa728..55090f635 100644 --- a/abipy/flowtk/utils.py +++ b/abipy/flowtk/utils.py @@ -515,6 +515,7 @@ def find_1den_files(self): "DKDK": {}, # irddkdk is not defined. #"DKDE": {"getdkde": 1}, #"DELFD": {"getdelfd": 1}, + "GSTORE": {"getgstore_filepath": '"indata/in_GSTORE.nc"'}, } diff --git a/abipy/flowtk/works.py b/abipy/flowtk/works.py index c20dcf46a..bf8fcb2aa 100644 --- a/abipy/flowtk/works.py +++ b/abipy/flowtk/works.py @@ -1704,7 +1704,7 @@ def from_scf_task(cls, scf_task: ScfTask, qpoints, is_ngqpt=False, with_becs=False, with_quad=False, with_flexoe=False, with_dvdb=True, tolerance=None, ddk_tolerance=None, ndivsm=0, qptopt=1, - prtwf=-1, manager=None) -> PhononWork: + prtwf=-1, prepgkk=0, manager=None) -> PhononWork: """ Construct a `PhononWork` from a |ScfTask| object. The input file for phonons is automatically generated from the input of the ScfTask. @@ -1738,6 +1738,8 @@ def from_scf_task(cls, scf_task: ScfTask, to restart the DFPT task if the calculation is not converged (worst case scenario) but we avoid the output of the 1-st order WFK if the calculation converged successfully. Non-linear DFPT tasks should not be affected since they assume q == 0. + prepgkk: option to compute only the irreducible preturbations or all perturbations + for the chosen set of q-point. Default: 0 (irred. pret. only) manager: |TaskManager| object. """ if not isinstance(scf_task, ScfTask): @@ -1761,7 +1763,8 @@ def from_scf_task(cls, scf_task: ScfTask, for qpt in qpoints: is_gamma = np.sum(qpt ** 2) < 1e-12 if with_becs and is_gamma: continue - multi = scf_task.input.make_ph_inputs_qpoint(qpt, tolerance=tolerance) + multi = scf_task.input.make_ph_inputs_qpoint(qpt, tolerance=tolerance, + prepgkk=prepgkk) for ph_inp in multi: # Here we set the value of prtwf for the DFPT tasks if q != Gamma. if not is_gamma: ph_inp.set_vars(prtwf=prtwf) @@ -1777,7 +1780,7 @@ def from_scf_task(cls, scf_task: ScfTask, def from_scf_input(cls, scf_input: AbinitInput, qpoints, is_ngqpt=False, with_becs=False, with_quad=False, with_flexoe=False, with_dvdb=True, tolerance=None, ddk_tolerance=None, ndivsm=0, qptopt=1, - prtwf=-1, manager=None) -> PhononWork: + prtwf=-1, prepgkk=0, manager=None) -> PhononWork: """ Similar to `from_scf_task`, the difference is that this method requires an input for SCF calculation. A new |ScfTask| is created and added to the Work. @@ -1806,7 +1809,8 @@ def from_scf_input(cls, scf_input: AbinitInput, qpoints, is_ngqpt=False, with_be for qpt in qpoints: is_gamma = np.sum(qpt ** 2) < 1e-12 if with_becs and is_gamma: continue - multi = scf_task.input.make_ph_inputs_qpoint(qpt, tolerance=tolerance) + multi = scf_task.input.make_ph_inputs_qpoint(qpt, tolerance=tolerance, + prepgkk=prepgkk) for ph_inp in multi: # Here we set the value of prtwf for the DFPT tasks if q != Gamma. if not is_gamma: ph_inp.set_vars(prtwf=prtwf) From 567fa3f95be4bc6ecef02f39957d2979dc663de0 Mon Sep 17 00:00:00 2001 From: Samare Date: Tue, 15 Oct 2024 17:35:28 +0200 Subject: [PATCH 4/9] Some modification and new files for QHA (#294) * Add file for generating deformations for QHA. * Update qha.py: Added tref and a new method for calculating the thermal expansion coefficient. * Add a script for v-ZSISA-QHA . "Physical Review B 110 (1), 014103" * modification in qha_aproximation.py. * Some modification on deformation_utils and qha. * Some modifications --- abipy/dfpt/deformation_utils.py | 193 +++ abipy/dfpt/qha.py | 232 +++- abipy/dfpt/qha_aproximation.py | 1971 +++++++++++++++++++++++++++++++ 3 files changed, 2390 insertions(+), 6 deletions(-) create mode 100644 abipy/dfpt/deformation_utils.py create mode 100644 abipy/dfpt/qha_aproximation.py diff --git a/abipy/dfpt/deformation_utils.py b/abipy/dfpt/deformation_utils.py new file mode 100644 index 000000000..c40527955 --- /dev/null +++ b/abipy/dfpt/deformation_utils.py @@ -0,0 +1,193 @@ +# deformation_utils.py + +import numpy as np +from pymatgen.core import Structure, Lattice, Element +from abipy.core.symmetries import AbinitSpaceGroup +from pymatgen.symmetry.analyzer import SpacegroupAnalyzer +from abipy.abio.inputs import AbinitInput +import re + + +def generate_deformations_volumic(structure, eps_V=0.02, scales=None): + if scales is None: + scales = [-1, 0, 1, 2, 3] + rprim = structure.lattice.matrix + structures_new = {} + + for i in scales: + rprim2 = np.copy(rprim) + rprim2[:, :] = rprim[:, :] * (1.00 + eps_V * i)**(1/3.) + + structure2 = structure.copy() + structure2.lattice = Lattice(rprim2) + #structure2.scale_lattice(structure2.volume*(1.00 + eps_V * i)) + namei = int(round(1000 * (1.00 + eps_V * i))) + formatted_namei = f"{namei:04d}" + structures_new[formatted_namei] = structure2 + + + return structures_new + +def generate_deformations(structure , eps=0.005): + spgrp = AbinitSpaceGroup.from_structure(structure ) + print (spgrp) + spgrp_number=spgrp.spgid + rprim= structure.lattice.matrix + + rprim2 = np.copy(rprim) + rprim_new = {} + structures_new = {} + + if 1 <= spgrp_number <= 2: + disp=[[1,1,1,1,1,1], [0,1,1,1,1,1], [2,1,1,1,1,1], [1,0,1,1,1,1], [1,2,1,1,1,1], [1,1,0,1,1,1], + [1,1,2,1,1,1], [1,1,1,0,1,1], [1,1,1,2,1,1], [1,1,1,1,0,1], [1,1,1,1,2,1], [1,1,1,1,1,0], + [1,1,1,1,1,2], [0,0,1,1,1,1], [1,0,0,1,1,1], [1,1,0,0,1,1], [1,1,1,0,0,1], [1,1,1,1,0,0], + [0,1,0,1,1,1], [0,1,1,0,1,1], [0,1,1,1,0,1], [0,1,1,1,1,0], [1,0,1,0,1,1], [1,0,1,1,0,1], + [1,0,1,1,1,0], [1,1,0,1,0,1], [1,1,0,1,1,0], [1,1,1,0,1,0] , [0 ,0,0,0,0,0]] + if abs(rprim[1, 0]) > 1e-9 or abs(rprim[2, 0]) > 1e-9 or abs(rprim[2, 1]) > 1e-9: + print("Warning: The lattice is oriented such that xz =xy =yz =0 .") + rprim0 = np.copy(rprim) + a=rprim[0, :] + b=rprim[1, :] + c=rprim[2, :] + norm_a = np.linalg.norm(a) + norm_b = np.linalg.norm(b) + norm_c = np.linalg.norm(c) + + # Compute angles between vectors + cos_ab = np.dot(a, b) / (norm_a * norm_b) + cos_ac = np.dot(a, c) / (norm_a * norm_c) + cos_bc = np.dot(b, c) / (norm_b * norm_c) + + rprim0[0,0] = 1.0 + rprim0[0,1] = 0.0 + rprim0[0,2] = 0.0 + rprim0[1,0] = cos_ab + rprim0[1,1] = np.sqrt(1-cos_ab**2) + rprim0[1,2] = 0.0 + rprim0[2,0] = cos_ac + rprim0[2,1] = (cos_bc-rprim0[1,0]*rprim0[2,0])/rprim0[1,1] + rprim0[2,2] = np.sqrt(1.0-rprim0[2,0]**2-rprim0[2,1]**2) + rprim0[0,:] = rprim0[0,:]*norm_a + rprim0[1,:] = rprim0[1,:]*norm_b + rprim0[2,:] = rprim0[2,:]*norm_c + print("Old rprim:") + print(rprim) + print("New rprim:") + print(rprim0) + + for pair in disp: + i,j,k,l,m,n = pair + rprim2[ :,0] = rprim0[ :,0] * (1.00 + eps * i) + rprim0[ :,1] * (eps * l) +rprim0[ :,2] * (eps * m) + rprim2[ :,1] = rprim0[ :,1] * (1.00 + eps * j) + rprim0[ :,2] * (eps * n) + rprim2[ :,2] = rprim0[ :,2] * (1.00 + eps * k) + + namei = int(round(1000 * (1.00 + eps * i))) + namej = int(round(1000 * (1.00 + eps * j))) + namek = int(round(1000 * (1.00 + eps * k))) + namel = int(round(1000 * (1.00 + eps * l))) + namem = int(round(1000 * (1.00 + eps * m))) + namen = int(round(1000 * (1.00 + eps * n))) + formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}_{namel:04d}_{namem:04d}_{namen:04d}" + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + + return structures_new + elif 3 <= spgrp_number <= 15: + disp=[[1,1,1,1], [0,1,1,1], [2,1,1,1], [1,0,1,1], [1,2,1,1], [1,1,0,1], [1,1,2,1], [1,1,1,0], + [1,1,1,2], [0,0,1,1], [1,0,0,1], [1,1,0,0], [0,1,0,1], [1,0,1,0], [0,1,1,0]] + if abs(rprim[1, 0]) > 1e-9 or abs(rprim[0, 1]) > 1e-9 or abs(rprim[2, 1]) > 1e-9 or abs(rprim[1, 2]) > 1e-9: + print("Error: Monoclinic structure with yx=xy=0 and yz=zy=0 lattice required.") + elif abs(rprim[0, 2]) > 1e-9 : + print("Warning: The lattice is oriented such that xz = 0.") + rprim0 = np.copy(rprim) + a=rprim[0, :] + b=rprim[1, :] + c=rprim[2, :] + norm_a = np.linalg.norm(a) + norm_b = np.linalg.norm(b) + norm_c = np.linalg.norm(c) + + # Compute angles between vectors + cos_ab = np.dot(a, b) / (norm_a * norm_b) + cos_ac = np.dot(a, c) / (norm_a * norm_c) + cos_bc = np.dot(b, c) / (norm_b * norm_c) + + rprim0[0,0] = norm_a + rprim0[0,2] = 0.0 + rprim0[1,1] = norm_b + rprim0[2,0] = norm_c*cos_ac + rprim0[2,2] = norm_c*np.sqrt(1-cos_ac**2) + print("Old rprim:") + print(rprim) + print("New rprim:") + print(rprim0) + + for pair in disp: + i,j,k,l = pair + rprim2[ :,0] = rprim0[ :,0] * (1.00 + eps * i) +rprim0[ :,2] * (eps * l) + rprim2[ :,1] = rprim0[ :,1] * (1.00 + eps * j) + rprim2[ :,2] = rprim0[ :,2] * (1.00 + eps * k) + + namei = int(round(1000 * (1.00 + eps * i))) + namej = int(round(1000 * (1.00 + eps * j))) + namek = int(round(1000 * (1.00 + eps * k))) + namel = int(round(1000 * (1.00 + eps * l))) + formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}_{namel:04d}" + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + + return structures_new + elif 16 <= spgrp_number <= 74: + disp=[[0,0,1],[0,1,0],[1,0,0],[1,1,1],[0,1,1],[2,1,1],[1,0,1],[1,2,1],[1,1,0],[1,1,2]] + for pair in disp: + i,j,k = pair + rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) + rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * j) + rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * k) + + namei = int(round(1000 * (1.00 + eps * i))) + namej = int(round(1000 * (1.00 + eps * j))) + namek = int(round(1000 * (1.00 + eps * k))) + formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}" + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + + return structures_new + elif 75 <= spgrp_number <= 194: + disp=[[0,0],[1,1],[0,1],[2,1],[1,0],[1,2]] + for pair in disp: + i, k = pair + rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) + rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * i) + rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * k) + + namei = int(round(1000 * (1.00 + eps * i))) + namek = int(round(1000 * (1.00 + eps * k))) + formatted_namei = f"{namei:04d}_{namek:04d}" + rprim_new[formatted_namei] = rprim2 + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + + return structures_new + elif 195 <= spgrp_number <= 230: + for i in range(3): + rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) + rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * i) + rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * i) + namei = int(round(1000 * (1.00 + eps * i))) + formatted_namei = f"{namei:04d}" + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + return structures_new + diff --git a/abipy/dfpt/qha.py b/abipy/dfpt/qha.py index e368e1ad0..52761b006 100644 --- a/abipy/dfpt/qha.py +++ b/abipy/dfpt/qha.py @@ -39,10 +39,18 @@ def __init__(self, structures, energies, eos_name='vinet', pressure=0): self.structures = structures self.energies = np.array(energies) self.eos = EOS(eos_name) + self.eos_name = eos_name self.pressure = pressure self.volumes = np.array([s.volume for s in structures]) self.iv0 = np.argmin(energies) + self.lattice_a = np.array([s.lattice.abc[0] for s in structures]) + self.lattice_b = np.array([s.lattice.abc[1] for s in structures]) + self.lattice_c = np.array([s.lattice.abc[2] for s in structures]) + + self.angles_alpha = np.array([s.lattice.angles[0] for s in structures]) + self.angles_beta = np.array([s.lattice.angles[1] for s in structures]) + self.angles_gama = np.array([s.lattice.angles[2] for s in structures]) def fit_energies(self, tstart=0, tstop=800, num=100): """ @@ -92,8 +100,9 @@ def fit_energies(self, tstart=0, tstop=800, num=100): # 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]) + F2D= np.array([fit.b0 for fit in fits]) /min_volumes - return dict2namedtuple(tot_en=tot_en, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh) + return dict2namedtuple(tot_en=tot_en, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh , F2D=F2D) @abc.abstractmethod def get_vib_free_energies(self, tstart=0, tstop=800, num=100): @@ -146,6 +155,7 @@ def set_eos(self, eos_name: str) -> None: eos_name: string indicating the expression used to fit the energies. See pymatgen.analysis.eos.EOS. """ self.eos = EOS(eos_name) + self.eos_name = eos_name @add_fig_kwargs def plot_energies(self, tstart=0, tstop=800, num=10, ax=None, **kwargs) -> Figure: @@ -173,12 +183,12 @@ def plot_energies(self, tstart=0, tstop=800, num=10, ax=None, **kwargs) -> Figur 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)') + ax.set_ylabel('F (eV)') #ax.grid(True) return fig - def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100) -> Function1D: + def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, tref=None) -> Function1D: """ 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. @@ -186,25 +196,58 @@ def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100) -> Function1 Args: tstart: The starting value (in Kelvin) of the temperature mesh. tstop: The end value (in Kelvin) of the mesh. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + num: int, optional Number of samples to generate. Default is 100. Returns: |Function1D| """ f = self.fit_energies(tstart, tstop, num) + if tref is not None: + f0 = self.fit_energies(tref, tref, 1) + eos_list = [ 'taylor', 'murnaghan', 'birch', 'birch_murnaghan','pourier_tarantola', 'vinet', 'antonschmidt'] dt = f.temp[1] - f.temp[0] - alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f.min_vol[1:-1] + if (self.eos_name in eos_list) : + thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) + entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro + df_t = np.zeros((num,self.nvols)) + df_t = - entropy + param = np.zeros((num,4)) + param2 = np.zeros((num,3)) + d2f_t_v = np.zeros(num) + gamma = np.zeros(num) + + for j in range (1,num-1): + param[j]=np.polyfit(self.volumes,df_t[j] , 3) + param2[j] = np.array([3*param[j][0],2*param[j][1],param[j][2]]) + + p = np.poly1d(param2[j]) + d2f_t_v[j]= p(f.min_vol[j]) + + if tref is None: + alpha= - 1/f.min_vol[1:-1] *d2f_t_v[1:-1] / f.F2D[1:-1] + else : + alpha= - 1/f0.min_vol * d2f_t_v[1:-1] / f.F2D[1:-1] + else : + if tref is None: + alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f.min_vol[1:-1] + else : + alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f0.min_vol 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) -> Figure: + def plot_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, tref=None, ax=None, **kwargs) -> Figure: """ 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) num: int, optional Number of samples to generate. Default is 100. ax: |matplotlib-Axes| or None if a new figure should be created. @@ -218,7 +261,7 @@ def plot_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, ax=None, ** if "color" not in kwargs: kwargs["color"] = "b" - alpha = self.get_thermal_expansion_coeff(tstart, tstop, num) + alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) ax.plot(alpha.mesh, alpha.values, **kwargs) ax.set_xlabel(r'T (K)') @@ -260,6 +303,183 @@ def plot_vol_vs_t(self, tstart=0, tstop=800, num=100, ax=None, **kwargs) -> Figu return fig + def get_abc(self, tstart=0, tstop=800 , num=100): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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| + """ + f = self.fit_energies(tstart, tstop, num) + param=np.polyfit(self.volumes, self.lattice_a , 3) + param0=[3*param[0],2*param[1],param[2]] + pa = np.poly1d(param) + dpa=np.poly1d(param0) + aa_qha=pa(f.min_vol) + daa_dv_qha=dpa(f.min_vol) + param=np.polyfit(self.volumes, self.lattice_b , 3) + param0=[3*param[0],2*param[1],param[2]] + pb = np.poly1d(param) + dpb = np.poly1d(param0) + bb_qha=pb(f.min_vol) + dbb_dv_qha=dpb(f.min_vol) + param=np.polyfit(self.volumes, self.lattice_c , 3) + param0=[3*param[0],2*param[1],param[2]] + pc = np.poly1d(param) + dpc = np.poly1d(param0) + cc_qha=pc(f.min_vol) + dcc_dv_qha=dpc(f.min_vol) + + return aa_qha,bb_qha,cc_qha, daa_dv_qha,dbb_dv_qha,dcc_dv_qha + + def get_angles(self, tstart=0, tstop=800 , num=100): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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| + """ + f = self.fit_energies(tstart, tstop, num) + param=np.polyfit(self.volumes, self.angles_alpha, 3) + pa = np.poly1d(param) + alpha=pa(f.min_vol) + param=np.polyfit(self.volumes, self.angles_beta , 3) + pb = np.poly1d(param) + beta=pb(f.min_vol) + param=np.polyfit(self.volumes, self.angles_gama , 3) + pc = np.poly1d(param) + gamma=pc(f.min_vol) + + return alpha,beta,gamma + + @add_fig_kwargs + def plot_thermal_expansion_coeff_abc(self, tstart=0, tstop=800, num=100, tref=None, ax=None, **kwargs) -> Figure: + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + + tmesh = np.linspace(tstart, tstop, num) + + alpha_v = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + aa,bb,cc, daa_dv,dbb_dv,dcc_dv = self.get_abc(tstart, tstop, num) + + if tref is None: + f = self.fit_energies(tstart, tstop, num) + dv_dt=alpha_v*f.min_vol[1:-1] + alpha_qha_a = dv_dt*daa_dv[1:-1]/ aa[1:-1] + alpha_qha_b = dv_dt*dbb_dv[1:-1]/ bb[1:-1] + alpha_qha_c = dv_dt*dcc_dv[1:-1]/ cc[1:-1] + else: + f0 = self.fit_energies(tref, tref, num) + dv_dt=alpha_v*f0.min_vol[1:-1] + aa_tref,bb_tref,cc_tref , daa_dv_tref,dbb_dv_tref,dcc_dv_tref = self.get_abc(tref, tref, 1) + alpha_qha_a = dv_dt*daa_dv[1:-1]/ aa_tref + alpha_qha_b = dv_dt*dbb_dv[1:-1]/ bb_tref + alpha_qha_c = dv_dt*dcc_dv[1:-1]/ cc_tref + + ax.plot(tmesh[1:-1] ,alpha_qha_a , color='r',lw=2 , **kwargs) + ax.plot(tmesh[1:-1] ,alpha_qha_b , color='b', lw=2 ) + ax.plot(tmesh[1:-1] ,alpha_qha_c , color='m', lw=2 ) + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend([r"$\alpha_a$",r"$\alpha_b$",r"$\alpha_c$"]) + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + + @add_fig_kwargs + def plot_abc_vs_t(self, tstart=0 , tstop=800, num=100, tref=None, ax=None, **kwargs) -> Figure: + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + aa,bb,cc, daa_dv,dbb_dv,dcc_dv = self.get_abc(tstart, tstop, num) + + ax.plot(tmesh ,aa , color='r',lw=2, **kwargs ) + ax.plot(tmesh ,bb , color='b', lw=2 ) + ax.plot(tmesh ,cc , color='m', lw=2 ) + ax.set_xlabel(r'T (K)') + ax.legend(["a(V(T))","b(V(T))","c(V(T))"]) + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + + @add_fig_kwargs + def plot_angle_vs_t(self, tstart=0, tstop=800, num=100, tref=None, ax=None, **kwargs)-> Figure: + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + alpha,beta,gamma = self.get_angles(tstart, tstop, num) + + ax.plot(tmesh ,alpha , color='r',lw=2, **kwargs ) + ax.plot(tmesh ,beta , color='b', lw=2 ) + ax.plot(tmesh ,gamma , color='m', lw=2 ) + ax.set_xlabel(r'T (K)') + ax.legend([r"$\alpha$(V(T))",r"$\beta$(V(T))",r"$\gamma$(V(T))"]) + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig @add_fig_kwargs def plot_phbs(self, phbands, temperatures=None, t_max=1000, colormap="plasma", **kwargs) -> Figure: """ diff --git a/abipy/dfpt/qha_aproximation.py b/abipy/dfpt/qha_aproximation.py new file mode 100644 index 000000000..e528cd2c3 --- /dev/null +++ b/abipy/dfpt/qha_aproximation.py @@ -0,0 +1,1971 @@ +# coding: utf-8 + +import os +import abc +import numpy as np +import abipy.core.abinit_units as abu + +from scipy.interpolate import UnivariateSpline +from monty.collections import dict2namedtuple +from monty.functools import lazy_property +from pymatgen.analysis.eos import EOS +from abipy.core.func1d import Function1D +from abipy.tools.plotting import add_fig_kwargs, get_ax_fig_plt, get_axarray_fig_plt +from abipy.electrons.gsr import GsrFile +from abipy.dfpt.ddb import DdbFile +from abipy.dfpt.phonons import PhononBandsPlotter, PhononDos, PhdosFile +from abipy.dfpt.gruneisen import GrunsNcFile + + +class QHA_App(metaclass=abc.ABCMeta): + """ + Class for the approximations on QHA analysis. + Provides some basic methods and plotting utils. + These can be used to obtain other quantities and plots. + Does not include electronic entropic contributions for metals. + """ + + def __init__(self, structures,structures_from_phdos,index_list, doses,energies, pressures, 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.eos_name = eos_name + self.pressure = pressure + self.pressures = np.array(pressures) + + self.volumes = np.array([s.volume for s in structures]) + self.iv0 = np.argmin(energies) + self.lattice_a = np.array([s.lattice.abc[0] for s in structures]) + self.lattice_b = np.array([s.lattice.abc[1] for s in structures]) + self.lattice_c = np.array([s.lattice.abc[2] for s in structures]) + + self.angles_alpha = np.array([s.lattice.angles[0] for s in structures]) + self.angles_beta = np.array([s.lattice.angles[1] for s in structures]) + self.angles_gama = np.array([s.lattice.angles[2] for s in structures]) + + self.doses = doses + self.structures_from_phdos = np.array(structures_from_phdos) + self.volumes_from_phdos = np.array([s.volume for s in structures_from_phdos]) + self.energies_pdos=self.energies[index_list] + self.index_list = index_list + if (len(self.index_list)==5): + self.iv0_vib=1 + self.iv1_vib=3 + self.V0_vib=self.volumes_from_phdos[2] + elif (len(self.index_list)==3): + self.iv0_vib=0 + self.iv1_vib=2 + self.V0_vib=self.volumes_from_phdos[1] + else : + self.iv0_vib=0 + self.iv1_vib=1 + self.V0_vib=0.5*(self.volumes_from_phdos[1]+self.volumes_from_phdos[0]) + if abs(self.volumes_from_phdos[self.iv0_vib]+self.volumes_from_phdos[self.iv1_vib]-2*self.volumes[self.iv0])<1e-3 : + self.scale_points="S" # Symmetry + else: + self.scale_points="D" # Displaced + +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + def fit_tot_energies(self, tstart=0, tstop=1000, num=101,tot_energies="energies" ,volumes="volumes"): + """ + 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 + """ + # Generate a mesh of temperatures + tmesh = np.linspace(tstart, tstop, num) + + # List of fit objects, one for each temperature + fits = [self.eos.fit(volumes, e) for e in tot_energies.T] + + # Extract parameters from the fit objects + v0 = np.array([fit.v0 for fit in fits]) + e0 = np.array([fit.e0 for fit in fits]) + b0 = np.array([fit.b0 for fit in fits]) + b1 = np.array([fit.b1 for fit in fits]) + + # Minimum volumes and energies + min_volumes = np.array([fit.v0 for fit in fits]) + min_energies = np.array([fit.e0 for fit in fits]) + + v = min_volumes + eta = (v / v0) ** (1.0 / 3.0) + + # Calculate the second derivative of free energy + F2D = ( b0 / v0 * (-2.0 * (eta - 1) * eta ** -5.0 + (1 - 3.0 / 2.0 * (b1 - 1) * (eta - 1)) * eta ** -4.0) + * np.exp(-3.0 * (b1 - 1.0) * (v ** (1.0 / 3.0) / v0 ** (1 / 3.0) - 1.0) / 2.0)) + + return dict2namedtuple(tot_en=tot_energies, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh , F2D=F2D) + + def second_derivative_energy_v(self, vol="vol"): + """ + Performs a fit of the energies as a function of the volume at different temperatures. + + Args: + vol: The volume at which to evaluate the second derivative of the energy. + + Returns: + E2D_V: The second derivative of the energy with respect to volume. + """ + # Initialize variables for temperature range (not used in the current function) + tstart = 0 + tstop = 0 + num = 1 + + tot_en = self.energies[np.newaxis, :].T + fits = [self.eos.fit(self.volumes, e) for e in tot_en.T] + + # Extract parameters from the fit objects + v0 = np.array([fit.v0 for fit in fits]) + e0 = np.array([fit.e0 for fit in fits]) + b0 = np.array([fit.b0 for fit in fits]) + b1 = np.array([fit.b1 for fit in fits]) + + v = vol + eta = (v / v0) ** (1.0 / 3.0) + E2D_V = ( b0 / v0 * (-2.0 * (eta - 1) * eta ** -5.0 + (1 - 3.0 / 2.0 * (b1 - 1) * (eta - 1)) * eta ** -4.0) * + np.exp(-3.0 * (b1 - 1.0) * (v ** (1.0 / 3.0) / v0 ** (1.0 / 3.0) - 1.0) / 2.0)) + + return E2D_V +#============================================================================================= + def vol_E2Vib1(self, tstart=0, tstop=1000, num=101): + """ + Compute the volume as a function of temperature using the E2Vib1 method with the Vinet equation of state. + + Args: + tstart: The starting value (in Kelvin) of the temperature mesh. + tstop: The end value (in Kelvin) of the mesh. + num: Number of samples to generate. Default is 101. + + Returns: + vol: The calculated volumes as a function of temperature. + fits: The list of fit objects for the energies as a function of volume. + """ + # Generate temperature mesh + tmesh = np.linspace(tstart, tstop, num) + + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + + vol = np.zeros(num) + dfe_dV1 = np.zeros(num) + volumes0 = self.volumes_from_phdos + volumes = self.volumes + iv0 = self.iv0_vib + iv1 = self.iv1_vib + dV = volumes0[iv1] - volumes0[iv0] + V0 = volumes[self.iv0] + E2D = self.second_derivative_energy_v(V0) + + # Calculate derivative of free energy with respect to volume and updated volumes + for i, e in enumerate(ph_energies.T): + dfe_dV1[i] = (e[iv1] - e[iv0]) / dV + vol[i] = V0 - dfe_dV1[i] / E2D + + # Calculate total energies + tot_en = self.energies[self.iv0] + 0.5*(volumes[np.newaxis, :].T - V0)**2*E2D +(volumes[np.newaxis, :].T - V0)*dfe_dV1 + + # Fit the energies as a function of volume + fits = [self.eos.fit(volumes, e) for e in tot_en.T] + + return vol, fits + +#============================================================================================= + def vol_Einf_Vib1(self, tstart=0, tstop=1000, num=101): + """ + Compute the volume as a function of temperature using the EinfVib1 method with the Vinet equation of state. + + Args: + tstart: The starting value (in Kelvin) of the temperature mesh. + tstop: The end value (in Kelvin) of the mesh. + num: Number of samples to generate. Default is 101. + + Returns: + vol: The calculated volumes as a function of temperature. + fits: The list of fit objects for the energies as a function of volume. + """ + # Generate temperature mesh + tmesh = np.linspace(tstart, tstop, num) + + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + + vol = np.zeros(num) + dfe_dV1 = np.zeros(num) + volumes0 = self.volumes_from_phdos + volumes = self.volumes + iv0 = self.iv0_vib + iv1 = self.iv1_vib + V0 = self.V0_vib + + dV = volumes0[iv1] - volumes0[iv0] + + # Calculate derivative of free energy with respect to volume + for i, e in enumerate(ph_energies.T): + dfe_dV1[i] = (e[iv1] - e[iv0]) / dV + + # Calculate total energies + tot_en = self.energies[np.newaxis, :].T + (volumes[np.newaxis, :].T - V0) * dfe_dV1 + + # Fit the energies as a function of volume + fits = [self.eos.fit(volumes, e) for e in tot_en.T] + + # Extract minimum volumes from the fit objects + vol = np.array([fit.v0 for fit in fits]) + + return vol, fits + +#============================================================================================= + def vol_Einf_Vib2(self, tstart=0, tstop=1000, num=101): + """ + Compute the volume as a function of temperature using the EinfVib2 method with the Vinet equation of state. + + Args: + tstart: The starting value (in Kelvin) of the temperature mesh. + tstop: The end value (in Kelvin) of the mesh. + num: Number of samples to generate. Default is 101. + + Returns: + vol: The calculated volumes as a function of temperature. + fits: The list of fit objects for the energies as a function of volume. + """ + # Generate temperature mesh + tmesh = np.linspace(tstart, tstop, num) + + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + + vol = np.zeros(num) + dfe_dV1 = np.zeros(num) + dfe_dV2 = np.zeros(num) + fe_V0 = np.zeros(num) + + # Determine index for volume calculations + iv0 = 2 if len(self.index_list) == 5 else 1 + + dV = self.volumes_from_phdos[iv0] - self.volumes_from_phdos[iv0 - 1] + + # Compute derivatives of free energy with respect to volume + for i, e in enumerate(ph_energies.T): + dfe_dV1[i] = (e[iv0 + 1] - e[iv0 - 1]) / (self.volumes_from_phdos[iv0 + 1] - self.volumes_from_phdos[iv0 - 1]) + dfe_dV2[i] = (e[iv0 + 1] - 2.0 * e[iv0] + e[iv0 - 1]) / (self.volumes_from_phdos[iv0 + 1] - self.volumes_from_phdos[iv0])**2 + fe_V0[i] = e[iv0] + + # Reference volume + V0 = self.volumes_from_phdos[iv0] + + # Calculate total energies + tot_en = ( self.energies[np.newaxis, :].T +fe_V0 + +(self.volumes[np.newaxis, :].T - V0) * dfe_dV1 + 0.5 * (self.volumes[np.newaxis, :].T - V0)**2 * dfe_dV2) + + # Fit the energies as a function of volume + fits = [self.eos.fit(self.volumes, e) for e in tot_en.T] + + # Extract minimum volumes from the fit objects + vol = np.array([fit.v0 for fit in fits]) + + return vol, fits + +#================================================================================================================ + + def vol_Einf_Vib4(self, tstart=0, tstop=1000, num=101): + """ + Compute the volume as a function of temperature using the EinfVib4 method with the Vinet equation of state. + + Args: + tstart: The starting value (in Kelvin) of the temperature mesh. + tstop: The end value (in Kelvin) of the mesh. + num: Number of samples to generate. Default is 101. + + Returns: + vol: The calculated volumes as a function of temperature. + fits: The list of fit objects for the energies as a function of volume. + """ + + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + energies = self.energies + volumes0= self.volumes_from_phdos + volumes= self.volumes + vol = np.zeros( num) + dfe_dV1= np.zeros( num) + dfe_dV2= np.zeros( num) + dfe_dV3= np.zeros( num) + dfe_dV4= np.zeros( num) + fe_V0= np.zeros( num) + iv0=2 + dV=volumes0[2]-volumes0[1] + + for i,e in enumerate(ph_energies.T): + dfe_dV1[i]=(-e[iv0+2]+ 8*e[iv0+1]-8*e[iv0-1]+e[iv0-2])/(12*dV) + dfe_dV2[i]=(-e[iv0+2]+16*e[iv0+1]-30*e[iv0]+16*e[iv0-1]-e[iv0-2])/(12*dV**2) + dfe_dV3[i]=(e[iv0+2]-2*e[iv0+1]+2*e[iv0-1]-e[iv0-2])/(2*dV**3) + dfe_dV4[i]=(e[iv0+2]-4*e[iv0+1]+6*e[iv0]-4*e[iv0-1]+e[iv0-2])/(dV**4) + + fe_V0[i]= e[iv0] + V0=volumes0[iv0] + + tot_en = (( volumes[np.newaxis, :].T -V0) * dfe_dV1 + 0.5* ( volumes[np.newaxis, :].T -V0)**2*(dfe_dV2) + +( volumes[np.newaxis, :].T -V0)**3 *dfe_dV3/6.0 + ( volumes[np.newaxis, :].T -V0)**4*(dfe_dV4/24.0) + + fe_V0[:] +energies[np.newaxis, :].T ) + + fits = [self.eos.fit(volumes, e) for e in tot_en.T] + vol = np.array([fit.v0 for fit in fits]) + + return vol , fits +#********************************************************************************************* + @property + def nvols(self): + """Number of volumes""" + return len(self.volumes_from_phdos) + + def set_eos(self, eos_name): + """ + Set the EOS model 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) + self.eos_name = eos_name + if (eos_name!= "vinet"): + raise RuntimeError("This approximation method is only developed for the Vinet equation of state.") + + + + @add_fig_kwargs + def plot_energies(self, tstart=0, tstop=1000 ,num=1, ax=None, **kwargs): + """ + Plots the BO energy as a function of volume + + 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| + """ + + tmesh = np.linspace(tstart, tstop, num) + + f = self.fit_tot_energies(0, 0, 1 ,self.energies[np.newaxis, :].T,self.volumes) + + 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='s', s=10) + 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', linestyle='dashed' , lw=1, marker='o', ms=5) + + ax.set_xlabel(r'V (${\AA}^3$)') + ax.set_ylabel('E (eV)') + return fig + + @add_fig_kwargs + def plot_vol_vs_t(self, tstart=0, tstop=1000, num=101, ax=None, **kwargs): + """ + Plot the volume as a function of temperature using various methods. + + 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 101. + ax: Matplotlib Axes object or None. If None, a new figure will be created. + **kwargs: Additional keyword arguments to pass to plotting functions. + + Returns: + fig: The Matplotlib Figure object. + """ + # Get or create the matplotlib Axes and Figure + ax, fig, plt = get_ax_fig_plt(ax) + + # Generate temperature mesh + tmesh = np.linspace(tstart, tstop, num) + + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + + # Initialize data storage + iv0 = self.iv0_vib + iv1 = self.iv1_vib + volumes = self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + + # Method 1: E2Vib1 + if self.scale_points=="S": + vol, _ = self.vol_E2Vib1(tstart=tstart, tstop=tstop, num=num) + ax.plot(tmesh, vol, color='b', lw=2, label="E2Vib1") + data_to_save = np.column_stack((data_to_save, vol)) + columns.append('E2vib1') + + # Method 2: Einf_Vib1 + if len(self.index_list) >= 2: + vol2, _ = self.vol_Einf_Vib1(tstart=tstart, tstop=tstop, num=num) + ax.plot(tmesh, vol2, color='gold', lw=2, label=r"$E_\infty$ Vib1") + data_to_save = np.column_stack((data_to_save, vol2)) + columns.append('Einfvib1') + + # Method 3: Einf_Vib2 + if len(self.index_list) >= 3: + vol3, _ = self.vol_Einf_Vib2(tstart=tstart, tstop=tstop, num=num) + ax.plot(tmesh, vol3, color='m', lw=2, label=r"$E_\infty$ Vib2") + data_to_save = np.column_stack((data_to_save, vol3)) + columns.append('Einfvib2') + + # Method 4: Einf_Vib4 and QHA + if len(self.index_list) == 5: + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num, tot_en, self.volumes_from_phdos) + vol4, _ = self.vol_Einf_Vib4(tstart=tstart, tstop=tstop, num=num) + ax.plot(tmesh, vol4, color='c', lw=2, label=r"$E_\infty$ Vib4") + ax.plot(tmesh, f0.min_vol, color='k', linestyle='dashed', lw=1.5, label="QHA") + data_to_save = np.column_stack((data_to_save, vol4, f0.min_vol)) + columns.append('Einfvib4') + columns.append('QHA') + + # Plot V0 + ax.plot(0, self.volumes[self.iv0], color='g', lw=0, marker='o', ms=10, label="V0") + + # Set labels and limits + ax.set_xlabel('T (K)') + ax.set_ylabel(r'V (${\AA}^3$)') + ax.set_xlim(tstart, tstop) + ax.grid(True) + ax.legend() + + return fig + +################################################################################################### + def get_thermal_expansion_coeff(self, tstart=0, tstop=1000, num=101, tref=None): + """ + Calculates the thermal expansion coefficient 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + num: Number of samples to generate. Default is 101. + + Returns: + Function1D: The thermal expansion coefficient as a function of temperature. + """ + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f = self.fit_tot_energies(tstart, tstop, num, tot_en, self.volumes_from_phdos) + + if tref != None: + ph_energies2 = self.get_vib_free_energies(tref, tref, 1) + tot_en2 = self.energies_pdos[np.newaxis, :].T + ph_energies2 + f0 = self.fit_tot_energies(tref, tref, 1, tot_en2, self.volumes_from_phdos) + + dt = f.temp[1] - f.temp[0] + + # Get thermodynamic properties + thermo = self.get_thermodynamic_properties(tstart, tstop, num) + entropy = thermo.entropy.T + df_t = -entropy + + param = np.zeros((num, 4)) + param2 = np.zeros((num, 3)) + d2f_t_v = np.zeros(num) + + for j in range(num): + param[j] = np.polyfit(self.volumes_from_phdos, df_t[j], 3) + param2[j] = np.array([3 * param[j][0], 2 * param[j][1], param[j][2]]) + p = np.poly1d(param2[j]) + d2f_t_v[j] = p(f.min_vol[j]) + + F2D = f.F2D + if tref == None: + alpha = -1 / f.min_vol * d2f_t_v / F2D + else: + alpha = -1 / f0.min_vol * d2f_t_v / F2D + + return Function1D(f.temp, alpha) + + @add_fig_kwargs + def plot_thermal_expansion_coeff(self, tstart=0, tstop=1000, num=101, tref=None, 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) + + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + tmesh = np.linspace(tstart, tstop, num) + thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) + entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro + df_t = np.zeros((num,self.nvols)) + df_t = - entropy + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + iv0=self.iv0_vib + iv1=self.iv1_vib + dV=volumes[iv0+1]-volumes[iv0] + + if self.scale_points=="S": + vol,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + E2D = self.second_derivative_energy_v(self.volumes[self.iv0]) + #f = self.fit_tot_energies(0, 0, 1 ,self.energies[np.newaxis, :].T,self.volumes) + #E2D = f.F2D + if (tref==None): + alpha_1 = - 1/vol[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D + else: + vol_ref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) + b0 = np.array([fit.b0 for fit in fits]) + print("B (E2vib1) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) + alpha_1 = - 1/vol_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D + ax.plot(tmesh, alpha_1,color='b', lw=2, label="E2Vib1") + data_to_save = np.column_stack((data_to_save,alpha_1)) + columns.append( 'E2vib1') + + if (len(self.index_list)>=2): + vol2 ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + E2D_V = self.second_derivative_energy_v(vol2) + if (tref==None): + alpha_2 = - 1/vol2[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] + else: + vol2_ref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) + b0 = np.array([fit.b0 for fit in fits]) + print("B (Einfvib1) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) + alpha_2 = - 1/vol2_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] + ax.plot(tmesh, alpha_2,color='gold', lw=2 , label=r"$E_\infty Vib1$") + data_to_save = np.column_stack((data_to_save,alpha_2)) + columns.append( 'Einfvib1') + + if (len(self.index_list)>=3): + vol3,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + E2D_V = self.second_derivative_energy_v(vol3) + dfe_dV2= np.zeros( num) + for i,e in enumerate(ph_energies.T): + dfe_dV2[i]=(e[iv0+2]-2.0*e[iv0+1]+e[iv0])/(dV)**2 + + ds_dv = (df_t[:,iv0+2]-df_t[:,iv0])/(2*dV) + ds_dv = ds_dv+ (df_t[:,iv0+2]-2*df_t[:,iv0+1]+df_t[:,iv0])/dV**2 * (vol3[:]-volumes[iv0+1]) + if (tref==None): + alpha_3 = - 1/vol3[:] * ds_dv / (E2D_V[:]+dfe_dV2[:]) + else: + vol3_ref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) + b0 = np.array([fit.b0 for fit in fits]) + print("B (Einfvib2) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) + alpha_3 = - 1/vol3_ref * ds_dv / (E2D_V[:]+dfe_dV2[:]) + ax.plot(tmesh, alpha_3,color='m', lw=2 , label=r"$E_\infty Vib2$") + data_to_save = np.column_stack((data_to_save,alpha_3)) + columns.append( 'Einfvib2') + + if (len(self.index_list)==5): + alpha_qha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + vol4,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + E2D_V = self.second_derivative_energy_v(vol4) + + d2fe_dV2= np.zeros( num) + d3fe_dV3= np.zeros( num) + d4fe_dV4= np.zeros( num) + for i,e in enumerate(ph_energies.T): + d2fe_dV2[i]=(-e[4]+16*e[3]-30*e[2]+16*e[1]-e[0])/(12*dV**2) + d3fe_dV3[i]=(e[4]-2*e[3]+2*e[1]-e[0])/(2*dV**3) + d4fe_dV4[i]=(e[4]-4*e[3]+6*e[2]-4*e[1]+e[0])/(dV**4) + + ds_dv =(-df_t[:,4]+ 8*df_t[:,3]-8*df_t[:,1]+df_t[:,0])/(12*dV) + ds_dv = ds_dv+ (-df_t[:,4]+16*df_t[:,3]-30*df_t[:,2]+16*df_t[:,1]-df_t[:,0])/(12*dV**2) * (vol4[:]-volumes[2]) + ds_dv = ds_dv+ 1.0/2.0*(df_t[:,4]-2*df_t[:,3]+2*df_t[:,1]-df_t[:,0])/(2*dV**3) * (vol4[:]-volumes[2])**2 + ds_dv = ds_dv+ 1.0/6.0* (df_t[:,4]-4*df_t[:,3]+6*df_t[:,2]-4*df_t[:,1]+df_t[:,0])/(dV**4)* (vol4[:]-volumes[2])**3 + D2F=E2D_V[:]+d2fe_dV2[:]+ (vol4[:]-volumes[2])*d3fe_dV3[:]+0.5*(vol4[:]-volumes[2])**2*d4fe_dV4[:] + if (tref==None): + alpha_4 = - 1/vol4[:] * ds_dv / D2F + else: + vol4_ref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) + b0 = np.array([fit.b0 for fit in fits]) + print("B (Einfvib4) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) + alpha_4 = - 1/vol4_ref * ds_dv / D2F + + ax.plot(tmesh, alpha_4,color='c',linewidth=2 , label=r"$E_\infty Vib4$") + ax.plot(alpha_qha.mesh, alpha_qha.values, color='k',linestyle='dashed', lw=1.5 ,label="QHA") + data_to_save = np.column_stack((data_to_save,alpha_4,alpha_qha.values)) + columns.append( 'Einfvib4') + columns.append( 'QHA') + + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.grid(True) + ax.legend() + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + def get_abc(self, tstart=0, tstop=1000, num=101,volumes="volumes"): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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| + """ + param = np.zeros((num,4)) + param=np.polyfit(self.volumes, self.lattice_a , 3) + pa = np.poly1d(param) + aa_qha=pa(volumes) + param=np.polyfit(self.volumes, self.lattice_b , 3) + pb = np.poly1d(param) + bb_qha=pb(volumes) + param=np.polyfit(self.volumes, self.lattice_c , 3) + pc = np.poly1d(param) + cc_qha=pc(volumes) + + return aa_qha,bb_qha,cc_qha + + def get_angles(self, tstart=0, tstop=1000, num=101,volumes="volumes"): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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| + """ + param = np.zeros((num,4)) + param=np.polyfit(self.volumes, self.angles_alpha, 3) + pa = np.poly1d(param) + gamma=pa(volumes) + param=np.polyfit(self.volumes, self.angles_beta , 3) + pb = np.poly1d(param) + beta=pb(volumes) + param=np.polyfit(self.volumes, self.angles_gama , 3) + pc = np.poly1d(param) + alpha=pc(volumes) + + return alpha,beta,gamma + +################################################################################################### + @add_fig_kwargs + def plot_thermal_expansion_coeff_abc(self, tstart=0, tstop=1000, num=101, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + data_to_save = tmesh[1:-1] + columns = ['#Tmesh'] + + if self.scale_points=="S": + vol2 ,fits= self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol2_tref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) + + if (len(self.index_list)==2): + vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) + method =r"$ (E_\infty Vib4)$" + vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + dt= tmesh[1] - tmesh[0] + + aa,bb,cc = self.get_abc(tstart, tstop, num,vol) + if (tref!=None): + aa_tref,bb_tref,cc_tref = self.get_abc(tref, tref, 1,vol_tref) + + alpha_a = np.zeros( num-2) + alpha_b = np.zeros( num-2) + alpha_c = np.zeros( num-2) + if (tref==None): + alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa[1:-1] + alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb[1:-1] + alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] + else: + alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa_tref + alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb_tref + alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref + + ax.plot(tmesh[1:-1] ,alpha_a , color='r', lw=2,label = r"$\alpha_a$"+method, **kwargs) + ax.plot(tmesh[1:-1] ,alpha_b , color='b', lw=2,label = r"$\alpha_b$"+method) + ax.plot(tmesh[1:-1] ,alpha_c , color='m', lw=2,label = r"$\alpha_c$"+method) + + method_header=method+" (alpha_a,alpha_b,alpha_c) |" + data_to_save = np.column_stack((data_to_save,alpha_a,alpha_b,alpha_c)) + columns.append( method_header) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) + if (tref!=None): + aa2_tref,bb2_tref,cc2_tref = self.get_abc(tref, tref, 1,vol2_tref) + + alpha2_a = np.zeros( num-2) + alpha2_b = np.zeros( num-2) + alpha2_c = np.zeros( num-2) + if (tref==None): + alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2[1:-1] + alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2[1:-1] + alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] + else: + alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2_tref + alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2_tref + alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref + + ax.plot(tmesh[1:-1] ,alpha2_a , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_a$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_b , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_b$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_c , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_c$"" (E2vib1)") + data_to_save = np.column_stack((data_to_save,alpha2_a,alpha2_b,alpha2_c)) + columns.append( 'E2vib1 (alpha_a,alpha_b,alpha_c) ') + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_thermal_expansion_coeff_angles(self, tstart=0, tstop=1000, num=101, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + data_to_save = tmesh[1:-1] + columns = ['#Tmesh'] + + if self.scale_points=="S": + vol2 ,fits= self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol2_tref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) + + if (len(self.index_list)==2): + vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) + method =r"$ (E_\infty Vib4)$" + vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + dt= tmesh[1] - tmesh[0] + + alpha,beta,cc = self.get_angles(tstart, tstop, num,vol) + if (tref!=None): + alpha_tref,beta_tref,cc_tref = self.get_angles(tref, tref, 1,vol_tref) + + alpha_alpha = np.zeros( num-2) + alpha_beta = np.zeros( num-2) + alpha_gamma = np.zeros( num-2) + if (tref==None): + alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha[1:-1] + alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta[1:-1] + alpha_gamma = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] + else: + alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha_tref + alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta_tref + alpha_gamma = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref + + ax.plot(tmesh[1:-1] ,alpha_alpha , color='r', lw=2,label = r"$\alpha_alpha$"+method, **kwargs) + ax.plot(tmesh[1:-1] ,alpha_beta , color='b', lw=2,label = r"$\alpha_beta$"+method) + ax.plot(tmesh[1:-1] ,alpha_gamma , color='m', lw=2,label = r"$\alpha_gamma$"+method) + + method_header=method+" (alpha_alpha,alpha_beta,alpha_gamma) |" + data_to_save = np.column_stack((data_to_save,alpha_alpha,alpha_beta,alpha_gamma)) + columns.append( method_header) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + alpha2,beta2,cc2 = self.get_angles(tstart, tstop, num,vol2) + if (tref!=None): + alpha2_tref,beta2_tref,cc2_tref = self.get_angles(tref, tref, 1,vol2_tref) + + alpha2_alpha = np.zeros( num-2) + alpha2_beta = np.zeros( num-2) + alpha2_gamma = np.zeros( num-2) + if (tref==None): + alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2[1:-1] + alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2[1:-1] + alpha2_gamma = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] + else: + alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2_tref + alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2_tref + alpha2_gamma = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref + + ax.plot(tmesh[1:-1] ,alpha2_alpha , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_alpha$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_beta , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_beta$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_gamma , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_gamma$"" (E2vib1)") + data_to_save = np.column_stack((data_to_save,alpha2_alpha,alpha2_beta,alpha2_gamma)) + columns.append( 'E2vib1 (alpha_alpha,alpha_beta,alpha_gamma) ') + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + + @add_fig_kwargs + def plot_abc_vs_t(self, tstart=0, tstop=1000, num=101, lattice=None, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) + data_to_save = np.column_stack((data_to_save,aa2,bb2,cc2)) + columns.append( 'E2vib1 (a,b,c) | ') + + if (len(self.index_list)==2): + vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) + method =r"$ (E_\infty Vib4)$" + vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + aa,bb,cc = self.get_abc(tstart, tstop, num,vol) + + method_header=method+" (a,b,c) |" + data_to_save = np.column_stack((data_to_save,aa,bb,cc)) + columns.append( method_header) + + if (lattice==None or lattice=="a"): + ax.plot(tmesh ,aa , color='r', lw=2,label = r"$a(V(T))$"+method, **kwargs ) + if (lattice==None or lattice=="b"): + ax.plot(tmesh ,bb , color='b', lw=2,label = r"$b(V(T))$"+method ) + if (lattice==None or lattice=="c"): + ax.plot(tmesh ,cc , color='m', lw=2,label = r"$c(V(T))$"+method ) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + if (lattice==None or lattice=="a"): + ax.plot(tmesh ,aa2 , linestyle='dashed' , color='r', lw=2,label = r"$a(V(T))$""E2vib1" ) + if (lattice==None or lattice=="b"): + ax.plot(tmesh ,bb2 , linestyle='dashed' , color='b', lw=2,label = r"$b(V(T))$""E2vib1" ) + if (lattice==None or lattice=="c"): + ax.plot(tmesh ,cc2 , linestyle='dashed' , color='m', lw=2,label = r"$c(V(T))$""E2vib1" ) + + ax.set_xlabel(r'T (K)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_angles_vs_t(self, tstart=0, tstop=1000, num=101, angle=None, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + ax, fig, plt = get_ax_fig_plt(ax) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) + data_to_save = np.column_stack((data_to_save,alpha2,beta2,gamma2)) + columns.append( 'E2vib1 (alpha,beta,gamma) | ') + + if (len(self.index_list)==2): + vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) + method =r"$ (E_\infty Vib4)$" + vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) + + method_header=method+" (alpha,beta,gamm) |" + data_to_save = np.column_stack((data_to_save,alpha,beta,gamma)) + columns.append( method_header) + + if (angle==None or angle==1): + ax.plot(tmesh ,alpha , color='r', lw=2,label = r"$alpha(V(T))$"+method, **kwargs ) + if (angle==None or angle==2): + ax.plot(tmesh ,beta , color='b', lw=2,label = r"$beta(V(T))$"+method ) + if (angle==None or angle==3): + ax.plot(tmesh ,gamma , color='m', lw=2,label = r"$gamma(V(T))$"+method ) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + if (angle==None or angle==1): + ax.plot(tmesh ,alpha2 , linestyle='dashed' , color='r', lw=2,label = r"$alpha(V(T))$""E2vib1" ) + if (angle==None or angle==2): + ax.plot(tmesh ,beta2 , linestyle='dashed' , color='b', lw=2,label = r"$beta(V(T))$""E2vib1" ) + if (angle==None or angle==3): + ax.plot(tmesh ,gamma2 , linestyle='dashed' , color='m', lw=2,label = r"$gamma(V(T))$""E2vib1" ) + + ax.set_xlabel(r'T (K)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& + def fit_forth(self, tstart=0, tstop=1000, num=1,energy="energy",volumes="volumes"): + """ + 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) + + param = np.zeros((num,5)) + param2 = np.zeros((num,4)) + param3 = np.zeros((num,3)) + min_vol = np.zeros((num)) + min_en = np.zeros((num)) + F2D_V = np.zeros((num)) + for j,e in enumerate(energy.T): + param[j]=np.polyfit(volumes,e , 4) + param2[j]=np.array([4*param[j][0],3*param[j][1],2*param[j][2],param[j][3]]) + param3[j]=np.array([12*param[j][0],6*param[j][1],2*param[j][2]]) + p = np.poly1d(param[j]) + p2 = np.poly1d(param2[j]) + p3 = np.poly1d(param3[j]) + min_vol[j]=self.volumes[self.iv0] + vv=self.volumes[self.iv0] + while p2(min_vol[j])**2 > 1e-16 : + min_vol[j]=min_vol[j]-p2(min_vol[j])*10 + min_en[j]=p(min_vol[j]) + F2D_V[j]=p3(min_vol[j]) + + return dict2namedtuple(min_vol=min_vol, temp=tmesh , min_en=min_en , param=param , F2D_V=F2D_V)#, fits=fits) + + def vol_E2Vib1_forth(self, tstart=0, tstop=1000, num=101): + + volumes0= self.volumes_from_phdos + iv0=self.iv0_vib + iv1=self.iv1_vib + + dV=volumes0[iv1]-volumes0[iv0] + V0=self.volumes[self.iv0] + + energy = self.energies[np.newaxis, :].T + f=self.fit_forth( tstart=0, tstop=0, num=1 ,energy=energy,volumes=self.volumes) + param3 = np.zeros((num,3)) + param3=np.array([12*f.param[0][0],6*f.param[0][1],2*f.param[0][2]]) + p3 = np.poly1d(param3) + E2D = p3(V0) + + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + vol = np.zeros( num) + + for i,e in enumerate(ph_energies.T): + dfe_dV1=(e[iv1]-e[iv0])/dV + vol[i]=V0-dfe_dV1*E2D**-1 + + return vol + + def vol_EinfVib1_forth(self, tstart=0, tstop=1000, num=101): + """ + Plot the volume as a function of the temperature. + Returns: |Vol| + """ + + volumes0= self.volumes_from_phdos + iv0=self.iv0_vib + iv1=self.iv1_vib + V0= self.V0_vib + + dV=volumes0[iv1]-volumes0[iv0] + + energy = self.energies[np.newaxis, :].T + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + vol = np.zeros( num) + + dfe_dV= np.zeros( num) + + for i,e in enumerate(ph_energies.T): + dfe_dV[i]=(e[iv1]-e[iv0])/dV + tot_en = self.energies[np.newaxis, :].T + ( self.volumes[np.newaxis, :].T -V0) * dfe_dV + + f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) + vol=f.min_vol + + return vol + def vol_Einf_Vib2_forth(self, tstart=0, tstop=1000, num=101): + """ + 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: |Vol| + """ + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + energies = self.energies + volumes0= self.volumes_from_phdos + volumes= self.volumes + vol = np.zeros( num) + dfe_dV= np.zeros( num) + d2fe_dV2= np.zeros( num) + fe_V0= np.zeros( num) + if (len(self.index_list)==5): + iv0=2 + else : + iv0=1 + dV=volumes0[iv0]-volumes0[iv0-1] + V0=volumes0[iv0] + for i,e in enumerate(ph_energies.T): + dfe_dV[i]=(e[iv0+1]-e[iv0-1])/(2*dV) + d2fe_dV2[i]=(e[iv0+1]-2.0*e[iv0]+e[iv0-1])/(dV)**2 + fe_V0[i]= e[iv0] + + tot_en = self.energies[np.newaxis, :].T + ( self.volumes[np.newaxis, :].T -V0) * dfe_dV + tot_en = tot_en + 0.5* (( self.volumes[np.newaxis, :].T -V0))**2*(d2fe_dV2) + tot_en = tot_en+ fe_V0 + f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) + vol=f.min_vol + + return vol + def vol_Einf_Vib4_forth(self, tstart=0, tstop=1000, num=101): + """ + + 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: |Vol| + """ + + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + energies = self.energies + volumes0= self.volumes_from_phdos + volumes= self.volumes + vol = np.zeros( num) + dfe_dV1= np.zeros( num) + dfe_dV2= np.zeros( num) + dfe_dV3= np.zeros( num) + dfe_dV4= np.zeros( num) + fe_V0= np.zeros( num) + iv0=2 + dV=volumes0[2]-volumes0[1] + + for i,e in enumerate(ph_energies.T): + dfe_dV1[i]=(-e[iv0+2]+ 8*e[iv0+1]-8*e[iv0-1]+e[iv0-2])/(12*dV) + dfe_dV2[i]=(-e[iv0+2]+16*e[iv0+1]-30*e[iv0]+16*e[iv0-1]-e[iv0-2])/(12*dV**2) + dfe_dV3[i]=(e[iv0+2]-2*e[iv0+1]+2*e[iv0-1]-e[iv0-2])/(2*dV**3) + dfe_dV4[i]=(e[iv0+2]-4*e[iv0+1]+6*e[iv0]-4*e[iv0-1]+e[iv0-2])/(dV**4) + + fe_V0[i]= e[iv0] + V0=volumes0[iv0] + + tot_en = ( volumes[np.newaxis, :].T -V0) * dfe_dV1 + 0.5* ( volumes[np.newaxis, :].T -V0)**2*(dfe_dV2) + tot_en = tot_en+( volumes[np.newaxis, :].T -V0)**3 *dfe_dV3/6 + ( volumes[np.newaxis, :].T -V0)**4*(dfe_dV4/24) + tot_en = tot_en + fe_V0 +energies[np.newaxis, :].T + + f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) + vol=f.min_vol + + return vol +#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + @add_fig_kwargs + def plot_vol_vs_t_4th(self, tstart=0, tstop=1000, num=101, ax=None, **kwargs): + """ + Plot 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol_4th = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + ax.plot(tmesh, vol_4th,color='b', lw=2, label="E2Vib1") + data_to_save = np.column_stack((data_to_save,vol_4th)) + columns.append( 'E2vib1') + + if (len(self.index_list)>=2): + vol2_4th = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + ax.plot(tmesh, vol2_4th,color='gold', lw=2 , label=r"$E_\infty Vib1$") + data_to_save = np.column_stack((data_to_save,vol2_4th)) + columns.append( 'Einfvib1') + + if (len(self.index_list)>=3): + vol3_4th = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + ax.plot(tmesh, vol3_4th,color='m', lw=2 , label=r"$E_\infty Vib2$") + data_to_save = np.column_stack((data_to_save,vol3_4th)) + columns.append( 'Einfvib2') + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + vol4_4th = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + ax.plot(tmesh, vol4_4th,color='c', lw=2 ,label=r"$E_\infty Vib4$") + ax.plot(tmesh, f0.min_vol, color='k',linestyle='dashed', lw=1.5 ,label="QHA") + data_to_save = np.column_stack((data_to_save,vol4_4th,f0.min_vol)) + columns.append( 'Einfvib4') + columns.append( 'QHA') + + ax.plot(0, self.volumes[self.iv0], color='g', lw=0, marker='o', ms=10,label="V0") + ax.set_xlabel('T (K)') + ax.set_ylabel(r'V (${\AA}^3$)') + ax.set_xlim(tstart, tstop) + ax.grid(True) + ax.legend() + + return fig + + def get_thermal_expansion_coeff_4th(self, tstart=0, tstop=1000, num=101 , tref=None): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + + num: int, optional Number of samples to generate. Default is 100. + + Returns: |Function1D| + """ + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f = self.fit_forth( tstart, tstop, num ,tot_en,self.volumes_from_phdos) + if (tref!=None): + ph_energies2 = self.get_vib_free_energies(tref, tref, 1) + tot_en2 = self.energies_pdos[np.newaxis, :].T + ph_energies2 + f0 = self.fit_forth(tref, tref , 1 ,tot_en2 , self.volumes_from_phdos) + + dt = f.temp[1] - f.temp[0] + thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) + entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro + df_t = np.zeros((num,self.nvols)) + df_t = - entropy + param = np.zeros((num,4)) + param2 = np.zeros((num,3)) + d2f_t_v = np.zeros(num) + gamma = np.zeros(num) + + #for j in range (1,num-1): + for j in range (num): + param[j]=np.polyfit(self.volumes_from_phdos,df_t[j] , 3) + param2[j] = np.array([3*param[j][0],2*param[j][1],param[j][2]]) + + p = np.poly1d(param2[j]) + d2f_t_v[j]= p(f.min_vol[j]) + + F2D = f.F2D_V + if (tref==None): + #alpha= - 1/f.min_vol[1:-1] *d2f_t_v[1:-1] / F2D[1:-1] + alpha= - 1/f.min_vol *d2f_t_v / F2D + else : + #alpha= - 1/f0.min_vol * d2f_t_v[1:-1] / F2D[1:-1] + alpha= - 1/f0.min_vol * d2f_t_v / F2D + + return alpha + @add_fig_kwargs + def plot_thermal_expansion_coeff_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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) + + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + tmesh = np.linspace(tstart, tstop, num) + thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) + entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro + df_t = np.zeros((num,self.nvols)) + df_t = - entropy + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + iv0=self.iv0_vib + iv1=self.iv1_vib + dV=volumes[iv0+1]-volumes[iv0] + energy = self.energies[np.newaxis, :].T + f=self.fit_forth( tstart=0, tstop=0, num=1 ,energy=energy,volumes=self.volumes) + param3 = np.zeros((num,3)) + param3=np.array([12*f.param[0][0],6*f.param[0][1],2*f.param[0][2]]) + p3 = np.poly1d(param3) + + if self.scale_points=="S": + vol_4th = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + E2D = p3(self.volumes[self.iv0]) + if (tref==None): + alpha_1 = - 1/vol_4th[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D + else : + vol_4th_ref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) + alpha_1 = - 1/vol_4th_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D + ax.plot(tmesh, alpha_1,color='b', lw=2, label="E2Vib1") + data_to_save = np.column_stack((data_to_save,alpha_1)) + columns.append( 'E2vib1') + + if (len(self.index_list)>=2): + vol2_4th = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + #E2D_V = self.second_derivative_energy_v(vol2) + E2D_V = p3(vol2_4th) + if (tref==None): + alpha_2 = - 1/vol2_4th[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] + else : + vol2_4th_ref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) + alpha_2 = - 1/vol2_4th_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] + ax.plot(tmesh, alpha_2,color='gold', lw=2 , label=r"$E_\infty Vib1$") + data_to_save = np.column_stack((data_to_save,alpha_2)) + columns.append( 'Einfvib1') + + if (len(self.index_list)>=3): + vol3_4th = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + E2D_V = p3(vol3_4th) + dfe_dV2= np.zeros( num) + for i,e in enumerate(ph_energies.T): + dfe_dV2[i]=(e[iv0+2]-2.0*e[iv0+1]+e[iv0])/(dV)**2 + + ds_dv = (df_t[:,iv0+2]-df_t[:,iv0])/(2*dV) + ds_dv = ds_dv+ (df_t[:,iv0+2]-2*df_t[:,iv0+1]+df_t[:,iv0])/dV**2 * (vol3_4th[:]-volumes[iv0+1]) + if (tref==None): + alpha_3 = - 1/vol3_4th[:] * ds_dv / (E2D_V[:]+dfe_dV2[:]) + else : + vol3_4th_ref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) + alpha_3 = - 1/vol3_4th_ref * ds_dv / (E2D_V[:]+dfe_dV2[:]) + ax.plot(tmesh, alpha_3,color='m', lw=2 , label=r"$E_\infty Vib2$") + data_to_save = np.column_stack((data_to_save,alpha_3)) + columns.append( 'Einfvib2') + + if (len(self.index_list)==5): + vol4_4th = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + E2D_V = p3(vol4_4th) + + d2fe_dV2= np.zeros( num) + d3fe_dV3= np.zeros( num) + d4fe_dV4= np.zeros( num) + for i,e in enumerate(ph_energies.T): + d2fe_dV2[i]=(-e[4]+16*e[3]-30*e[2]+16*e[1]-e[0])/(12*dV**2) + d3fe_dV3[i]=(e[4]-2*e[3]+2*e[1]-e[0])/(2*dV**3) + d4fe_dV4[i]=(e[4]-4*e[3]+6*e[2]-4*e[1]+e[0])/(dV**4) + + ds_dv =(-df_t[:,4]+ 8*df_t[:,3]-8*df_t[:,1]+df_t[:,0])/(12*dV) + ds_dv = ds_dv+ (-df_t[:,4]+16*df_t[:,3]-30*df_t[:,2]+16*df_t[:,1]-df_t[:,0])/(12*dV**2) * (vol4_4th[:]-volumes[2]) + ds_dv = ds_dv+ 1.0/2.0*(df_t[:,4]-2*df_t[:,3]+2*df_t[:,1]-df_t[:,0])/(2*dV**3) * (vol4_4th[:]-volumes[2])**2 + ds_dv = ds_dv+ 1.0/6.0* (df_t[:,4]-4*df_t[:,3]+6*df_t[:,2]-4*df_t[:,1]+df_t[:,0])/(dV**4)* (vol4_4th[:]-volumes[2])**3 + D2F=E2D_V[:]+d2fe_dV2[:]+ (vol4_4th[:]-volumes[2])*d3fe_dV3[:]+0.5*(vol4_4th[:]-volumes[2])**2*d4fe_dV4[:] + if (tref==None): + alpha_4 = - 1/vol4_4th[:] * ds_dv / D2F + else : + vol4_4th_ref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) + alpha_4 = - 1/vol4_4th_ref * ds_dv / D2F + + + ax.plot(tmesh, alpha_4,color='c',linewidth=2 , label=r"$E_\infty Vib4$") + + alpha_qha = self.get_thermal_expansion_coeff_4th(tstart, tstop, num, tref) + ax.plot(tmesh, alpha_qha, color='k',linestyle='dashed', lw=1.5 ,label="QHA") + data_to_save = np.column_stack((data_to_save,alpha_4,alpha_qha)) + columns.append( 'Einfvib4') + columns.append( 'QHA') + + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.grid(True) + ax.legend() + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_abc_vs_t_4th(self, tstart=0, tstop=1000, num=101, lattice=None,tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + ax, fig, plt = get_ax_fig_plt(ax) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) + data_to_save = np.column_stack((data_to_save,aa2,bb2,cc2)) + columns.append( 'E2vib1 (a,b,c) | ') + + if (len(self.index_list)==2): + vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + method =r"$ (E_\infty Vib4)$" + vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + aa,bb,cc = self.get_abc(tstart, tstop, num,vol) + + method_header=method+" (a,b,c) |" + data_to_save = np.column_stack((data_to_save,aa,bb,cc)) + columns.append( method_header) + + if (lattice==None or lattice=="a"): + ax.plot(tmesh ,aa , color='r', lw=2,label = r"$a(V(T))$"+method, **kwargs ) + if (lattice==None or lattice=="b"): + ax.plot(tmesh ,bb , color='b', lw=2,label = r"$b(V(T))$"+method ) + if (lattice==None or lattice=="c"): + ax.plot(tmesh ,cc , color='m', lw=2,label = r"$c(V(T))$"+method ) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + if (lattice==None or lattice=="a"): + ax.plot(tmesh ,aa2 , linestyle='dashed' , color='r', lw=2,label = r"$a(V(T))$""E2vib1" ) + if (lattice==None or lattice=="b"): + ax.plot(tmesh ,bb2 , linestyle='dashed' , color='b', lw=2,label = r"$b(V(T))$""E2vib1" ) + if (lattice==None or lattice=="c"): + ax.plot(tmesh ,cc2 , linestyle='dashed' , color='m', lw=2,label = r"$c(V(T))$""E2vib1" ) + + ax.set_xlabel(r'T (K)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_angles_vs_t_4th(self, tstart=0, tstop=1000, num=101,angle=None, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + ax, fig, plt = get_ax_fig_plt(ax) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) + data_to_save = np.column_stack((data_to_save,alpha2,beta2,gamma2)) + columns.append( 'E2vib1 (alpha,beta,gamma) | ') + + if (len(self.index_list)==2): + vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + method =r"$ (E_\infty Vib4)$" + vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) + + method_header=method+" (alpha,beta,gamma) |" + data_to_save = np.column_stack((data_to_save,alpha,beta,gamma)) + columns.append( method_header) + + if (angle==None or angle==1): + ax.plot(tmesh ,alpha , color='r', lw=2,label = r"$alpha(V(T))$"+method, **kwargs ) + if (angle==None or angle==2): + ax.plot(tmesh ,beta , color='b', lw=2,label = r"$beta(V(T))$"+method ) + if (angle==None or angle==3): + ax.plot(tmesh ,gamma , color='m', lw=2,label = r"$gamma(V(T))$"+method ) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + if (angle==None or angle==1): + ax.plot(tmesh ,alpha2 , linestyle='dashed' , color='r', lw=2,label = r"$alpha(V(T))$""E2vib1" ) + if (angle==None or angle==2): + ax.plot(tmesh ,beta2 , linestyle='dashed' , color='b', lw=2,label = r"$beta(V(T))$""E2vib1" ) + if (angle==None or angle==3): + ax.plot(tmesh ,gamma2 , linestyle='dashed' , color='m', lw=2,label = r"$gamma(V(T))$""E2vib1" ) + + ax.set_xlabel(r'T (K)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig +################################################################################################### + @add_fig_kwargs + def plot_thermal_expansion_coeff_abc_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh[1:-1] + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol2_tref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) + + if (len(self.index_list)==2): + vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + method =r"$ (E_\infty Vib4)$" + vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + dt= tmesh[1] - tmesh[0] + + aa,bb,cc = self.get_abc(tstart, tstop, num,vol) + if (tref!=None): + aa_tref,bb_tref,cc_tref = self.get_abc(tref, tref, 1,vol_tref) + + alpha_a = np.zeros( num-2) + alpha_b = np.zeros( num-2) + alpha_c = np.zeros( num-2) + if (tref==None): + alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa[1:-1] + alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb[1:-1] + alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] + else: + alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa_tref + alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb_tref + alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref + + ax.plot(tmesh[1:-1] ,alpha_a , color='r', lw=2,label = r"$\alpha_a$"+method, **kwargs) + ax.plot(tmesh[1:-1] ,alpha_b , color='b', lw=2,label = r"$\alpha_b$"+method) + ax.plot(tmesh[1:-1] ,alpha_c , color='m', lw=2,label = r"$\alpha_c$"+method) + + method_header=method+" (alpha_a,alpha_b,alpha_c) |" + data_to_save = np.column_stack((data_to_save,alpha_a,alpha_b,alpha_c)) + columns.append( method_header) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) + if (tref!=None): + aa2_tref,bb2_tref,cc2_tref = self.get_abc(tref, tref, 1,vol2_tref) + + alpha2_a = np.zeros( num-2) + alpha2_b = np.zeros( num-2) + alpha2_c = np.zeros( num-2) + if (tref==None): + alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2[1:-1] + alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2[1:-1] + alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] + else: + alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2_tref + alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2_tref + alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref + + ax.plot(tmesh[1:-1] ,alpha2_a , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_a$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_b , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_b$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_c , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_c$"" (E2vib1)") + data_to_save = np.column_stack((data_to_save,alpha2_a,alpha2_b,alpha2_c)) + columns.append( 'E2vib1 (alpha_a,alpha_b,alpha_c) ') + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_thermal_expansion_coeff_angles_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh[1:-1] + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol2_tref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) + + if (len(self.index_list)==2): + vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + method =r"$ (E_\infty Vib4)$" + vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) + + tmesh = np.linspace(tstart, tstop, num) + dt= tmesh[1] - tmesh[0] + + alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) + if (tref!=None): + alpha_tref,beta_tref,gamma_tref = self.get_angles(tref, tref, 1,vol_tref) + + alpha_alpha = np.zeros( num-2) + alpha_beta = np.zeros( num-2) + alpha_gamma = np.zeros( num-2) + if (tref==None): + alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha[1:-1] + alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta[1:-1] + alpha_gamma = (gamma[2:] - gamma[:-2]) / (2 * dt) / gamma[1:-1] + else: + alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha_tref + alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta_tref + alpha_gamma = (gamma[2:] - gamma[:-2]) / (2 * dt) / gamma_tref + + ax.plot(tmesh[1:-1] ,alpha_alpha , color='r', lw=2,label = r"$\alpha_alpha$"+method, **kwargs) + ax.plot(tmesh[1:-1] ,alpha_beta , color='b', lw=2,label = r"$\alpha_beta$"+method) + ax.plot(tmesh[1:-1] ,alpha_gamma , color='m', lw=2,label = r"$\alpha_gamma$"+method) + + method_header=method+" (alpha_alpha,alpha_beta,alpha_gamma) |" + data_to_save = np.column_stack((data_to_save,alpha_alpha,alpha_beta,alpha_gamma)) + columns.append( method_header) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) + if (tref!=None): + alpha2_tref,beta2_tref,gamma2_tref = self.get_angles(tref, tref, 1,vol2_tref) + + alpha2_alpha = np.zeros( num-2) + alpha2_beta = np.zeros( num-2) + alpha2_gamma = np.zeros( num-2) + if (tref==None): + alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2[1:-1] + alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2[1:-1] + alpha2_gamma = (gamma2[2:] - gamma2[:-2]) / (2 * dt) / gamma2[1:-1] + else: + alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2_tref + alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2_tref + alpha2_gamma = (gamma2[2:] - gamma2[:-2]) / (2 * dt) / gamma2_tref + + ax.plot(tmesh[1:-1] ,alpha2_alpha , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_alpha$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_beta , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_beta$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_gamma , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_gamma$"" (E2vib1)") + data_to_save = np.column_stack((data_to_save,alpha2_alpha,alpha2_beta,alpha2_gamma)) + columns.append( 'E2vib1 (alpha_alpha,alpha_beta,alpha_gamma) ') + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig +#********************************************************************************************* + @classmethod + def from_files_app(cls, gsr_paths, phdos_paths): + """ + Creates an instance of QHA from a list of GSR files and a list of PHDOS.nc files. + The list should have the same size and the volumes should match. + + Args: + gsr_paths: list of paths to GSR files. + phdos_paths: list of paths to PHDOS.nc files. + + Returns: A new instance of QHA + """ + energies = [] + structures = [] + pressures = [] + for gp in gsr_paths: + with GsrFile.from_file(gp) as g: + energies.append(g.energy) + structures.append(g.structure) + pressures.append(g.pressure) + + #doses = [PhononDos.as_phdos(dp) for dp in phdos_paths] + + doses = [] + structures_from_phdos = [] + for path in phdos_paths: + with PhdosFile(path) as p: + doses.append(p.phdos) + structures_from_phdos.append(p.structure) + + # cls._check_volumes_id(structures, structures_from_phdos) + vols1 = [s.volume for s in structures] + vols2 = [s.volume for s in structures_from_phdos] + dv=np.zeros((len(vols2)-1)) + for j in range(len(vols2)-1): + dv[j]=vols2[j+1]-vols2[j] + tolerance = 1e-3 + if (len(vols2)!=2): + max_difference = np.max(np.abs(dv - dv[0])) + if max_difference > tolerance: + raise RuntimeError("Expecting an equal volume change for structures from PDOS." ) + + index_list = [i for v2 in vols2 for i, v1 in enumerate(vols1) if abs(v2 - v1) < 1e-3] + if len(index_list) != len(vols2): + raise RuntimeError("Expecting the ground state files for all PDOS files!") + if len(index_list) not in (2, 3, 5): + raise RuntimeError("Expecting just 2, 3, or 5 PDOS files in the approximation method.") + + return cls(structures,structures_from_phdos,index_list, doses, energies , pressures) + + def get_vib_free_energies(self, tstart=0, tstop=1000, num=101) -> np.ndarray: + """ + Generates the vibrational free energy from the phonon DOS. + + 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 the vibrational contribution to the free energy + """ + f = np.zeros((self.nvols, num)) + + for i, dos in enumerate(self.doses): + f[i] = dos.get_free_energy(tstart, tstop, num).values + return f + + def get_thermodynamic_properties(self, tstart=0, tstop=1000, num=101): + """ + Generates all the thermodynamic properties corresponding to all the volumes using the phonon DOS. + + 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). + """ + tmesh = np.linspace(tstart, tstop, num) + cv = np.zeros((self.nvols, num)) + free_energy = np.zeros((self.nvols, num)) + entropy = np.zeros((self.nvols, num)) + internal_energy = np.zeros((self.nvols, num)) + zpe = np.zeros(self.nvols) + + for i, d in enumerate(self.doses): + cv[i] = d.get_cv(tstart, tstop, num).values + free_energy[i] = d.get_free_energy(tstart, tstop, num).values + entropy[i] = d.get_entropy(tstart, tstop, num).values + zpe[i] = d.zero_point_energy + + return dict2namedtuple(tmesh=tmesh, cv=cv, free_energy=free_energy, entropy=entropy, zpe=zpe) +#======================================================================================================= + @classmethod + def from_files_app_ddb(cls, ddb_paths, phdos_paths): + """ + Creates an instance of QHA from a list of GSR files and a list of PHDOS.nc files. + The list should have the same size and the volumes should match. + + Args: + ddb_paths: list of paths to DDB files. + phdos_paths: list of paths to PHDOS.nc files. + + Returns: A new instance of QHA + """ + energies = [] + structures = [] + pressures = [] + for gp in ddb_paths: + with DdbFile.from_file(gp) as g: + energies.append(g.total_energy) + structures.append(g.structure) + #pressures.append(g.pressure) + + #doses = [PhononDos.as_phdos(dp) for dp in phdos_paths] + + doses = [] + structures_from_phdos = [] + for path in phdos_paths: + with PhdosFile(path) as p: + doses.append(p.phdos) + structures_from_phdos.append(p.structure) + + # cls._check_volumes_id(structures, structures_from_phdos) + vols1 = [s.volume for s in structures] + vols2 = [s.volume for s in structures_from_phdos] + dv=np.zeros((len(vols2)-1)) + for j in range(len(vols2)-1): + dv[j]=vols2[j+1]-vols2[j] + tolerance = 1e-3 + if (len(vols2)!=2): + max_difference = np.max(np.abs(dv - dv[0])) + if max_difference > tolerance: + raise RuntimeError("Expecting an equal volume change for structures from PDOS." ) + + index_list = [i for v2 in vols2 for i, v1 in enumerate(vols1) if abs(v2 - v1) < 1e-3] + if len(index_list) != len(vols2): + raise RuntimeError("Expecting the ground state files for all PDOS files!") + if len(index_list) not in (2, 3, 5): + raise RuntimeError("Expecting just 2, 3, or 5 PDOS files in the approximation method.") + + return cls(structures,structures_from_phdos,index_list, doses, energies , pressures) + From d01f097c7fbc37e3c17b06fe712d4e0fac823225 Mon Sep 17 00:00:00 2001 From: Matteo Giantomassi Date: Tue, 15 Oct 2024 23:18:00 +0200 Subject: [PATCH 5/9] Revert "Some modification and new files for QHA (#294)" (#299) This reverts commit 567fa3f95be4bc6ecef02f39957d2979dc663de0. --- abipy/dfpt/deformation_utils.py | 193 --- abipy/dfpt/qha.py | 232 +--- abipy/dfpt/qha_aproximation.py | 1971 ------------------------------- 3 files changed, 6 insertions(+), 2390 deletions(-) delete mode 100644 abipy/dfpt/deformation_utils.py delete mode 100644 abipy/dfpt/qha_aproximation.py diff --git a/abipy/dfpt/deformation_utils.py b/abipy/dfpt/deformation_utils.py deleted file mode 100644 index c40527955..000000000 --- a/abipy/dfpt/deformation_utils.py +++ /dev/null @@ -1,193 +0,0 @@ -# deformation_utils.py - -import numpy as np -from pymatgen.core import Structure, Lattice, Element -from abipy.core.symmetries import AbinitSpaceGroup -from pymatgen.symmetry.analyzer import SpacegroupAnalyzer -from abipy.abio.inputs import AbinitInput -import re - - -def generate_deformations_volumic(structure, eps_V=0.02, scales=None): - if scales is None: - scales = [-1, 0, 1, 2, 3] - rprim = structure.lattice.matrix - structures_new = {} - - for i in scales: - rprim2 = np.copy(rprim) - rprim2[:, :] = rprim[:, :] * (1.00 + eps_V * i)**(1/3.) - - structure2 = structure.copy() - structure2.lattice = Lattice(rprim2) - #structure2.scale_lattice(structure2.volume*(1.00 + eps_V * i)) - namei = int(round(1000 * (1.00 + eps_V * i))) - formatted_namei = f"{namei:04d}" - structures_new[formatted_namei] = structure2 - - - return structures_new - -def generate_deformations(structure , eps=0.005): - spgrp = AbinitSpaceGroup.from_structure(structure ) - print (spgrp) - spgrp_number=spgrp.spgid - rprim= structure.lattice.matrix - - rprim2 = np.copy(rprim) - rprim_new = {} - structures_new = {} - - if 1 <= spgrp_number <= 2: - disp=[[1,1,1,1,1,1], [0,1,1,1,1,1], [2,1,1,1,1,1], [1,0,1,1,1,1], [1,2,1,1,1,1], [1,1,0,1,1,1], - [1,1,2,1,1,1], [1,1,1,0,1,1], [1,1,1,2,1,1], [1,1,1,1,0,1], [1,1,1,1,2,1], [1,1,1,1,1,0], - [1,1,1,1,1,2], [0,0,1,1,1,1], [1,0,0,1,1,1], [1,1,0,0,1,1], [1,1,1,0,0,1], [1,1,1,1,0,0], - [0,1,0,1,1,1], [0,1,1,0,1,1], [0,1,1,1,0,1], [0,1,1,1,1,0], [1,0,1,0,1,1], [1,0,1,1,0,1], - [1,0,1,1,1,0], [1,1,0,1,0,1], [1,1,0,1,1,0], [1,1,1,0,1,0] , [0 ,0,0,0,0,0]] - if abs(rprim[1, 0]) > 1e-9 or abs(rprim[2, 0]) > 1e-9 or abs(rprim[2, 1]) > 1e-9: - print("Warning: The lattice is oriented such that xz =xy =yz =0 .") - rprim0 = np.copy(rprim) - a=rprim[0, :] - b=rprim[1, :] - c=rprim[2, :] - norm_a = np.linalg.norm(a) - norm_b = np.linalg.norm(b) - norm_c = np.linalg.norm(c) - - # Compute angles between vectors - cos_ab = np.dot(a, b) / (norm_a * norm_b) - cos_ac = np.dot(a, c) / (norm_a * norm_c) - cos_bc = np.dot(b, c) / (norm_b * norm_c) - - rprim0[0,0] = 1.0 - rprim0[0,1] = 0.0 - rprim0[0,2] = 0.0 - rprim0[1,0] = cos_ab - rprim0[1,1] = np.sqrt(1-cos_ab**2) - rprim0[1,2] = 0.0 - rprim0[2,0] = cos_ac - rprim0[2,1] = (cos_bc-rprim0[1,0]*rprim0[2,0])/rprim0[1,1] - rprim0[2,2] = np.sqrt(1.0-rprim0[2,0]**2-rprim0[2,1]**2) - rprim0[0,:] = rprim0[0,:]*norm_a - rprim0[1,:] = rprim0[1,:]*norm_b - rprim0[2,:] = rprim0[2,:]*norm_c - print("Old rprim:") - print(rprim) - print("New rprim:") - print(rprim0) - - for pair in disp: - i,j,k,l,m,n = pair - rprim2[ :,0] = rprim0[ :,0] * (1.00 + eps * i) + rprim0[ :,1] * (eps * l) +rprim0[ :,2] * (eps * m) - rprim2[ :,1] = rprim0[ :,1] * (1.00 + eps * j) + rprim0[ :,2] * (eps * n) - rprim2[ :,2] = rprim0[ :,2] * (1.00 + eps * k) - - namei = int(round(1000 * (1.00 + eps * i))) - namej = int(round(1000 * (1.00 + eps * j))) - namek = int(round(1000 * (1.00 + eps * k))) - namel = int(round(1000 * (1.00 + eps * l))) - namem = int(round(1000 * (1.00 + eps * m))) - namen = int(round(1000 * (1.00 + eps * n))) - formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}_{namel:04d}_{namem:04d}_{namen:04d}" - - structure2=structure.copy() - structure2.lattice=Lattice(rprim2) - structures_new[formatted_namei] = structure2 - - return structures_new - elif 3 <= spgrp_number <= 15: - disp=[[1,1,1,1], [0,1,1,1], [2,1,1,1], [1,0,1,1], [1,2,1,1], [1,1,0,1], [1,1,2,1], [1,1,1,0], - [1,1,1,2], [0,0,1,1], [1,0,0,1], [1,1,0,0], [0,1,0,1], [1,0,1,0], [0,1,1,0]] - if abs(rprim[1, 0]) > 1e-9 or abs(rprim[0, 1]) > 1e-9 or abs(rprim[2, 1]) > 1e-9 or abs(rprim[1, 2]) > 1e-9: - print("Error: Monoclinic structure with yx=xy=0 and yz=zy=0 lattice required.") - elif abs(rprim[0, 2]) > 1e-9 : - print("Warning: The lattice is oriented such that xz = 0.") - rprim0 = np.copy(rprim) - a=rprim[0, :] - b=rprim[1, :] - c=rprim[2, :] - norm_a = np.linalg.norm(a) - norm_b = np.linalg.norm(b) - norm_c = np.linalg.norm(c) - - # Compute angles between vectors - cos_ab = np.dot(a, b) / (norm_a * norm_b) - cos_ac = np.dot(a, c) / (norm_a * norm_c) - cos_bc = np.dot(b, c) / (norm_b * norm_c) - - rprim0[0,0] = norm_a - rprim0[0,2] = 0.0 - rprim0[1,1] = norm_b - rprim0[2,0] = norm_c*cos_ac - rprim0[2,2] = norm_c*np.sqrt(1-cos_ac**2) - print("Old rprim:") - print(rprim) - print("New rprim:") - print(rprim0) - - for pair in disp: - i,j,k,l = pair - rprim2[ :,0] = rprim0[ :,0] * (1.00 + eps * i) +rprim0[ :,2] * (eps * l) - rprim2[ :,1] = rprim0[ :,1] * (1.00 + eps * j) - rprim2[ :,2] = rprim0[ :,2] * (1.00 + eps * k) - - namei = int(round(1000 * (1.00 + eps * i))) - namej = int(round(1000 * (1.00 + eps * j))) - namek = int(round(1000 * (1.00 + eps * k))) - namel = int(round(1000 * (1.00 + eps * l))) - formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}_{namel:04d}" - - structure2=structure.copy() - structure2.lattice=Lattice(rprim2) - structures_new[formatted_namei] = structure2 - - return structures_new - elif 16 <= spgrp_number <= 74: - disp=[[0,0,1],[0,1,0],[1,0,0],[1,1,1],[0,1,1],[2,1,1],[1,0,1],[1,2,1],[1,1,0],[1,1,2]] - for pair in disp: - i,j,k = pair - rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) - rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * j) - rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * k) - - namei = int(round(1000 * (1.00 + eps * i))) - namej = int(round(1000 * (1.00 + eps * j))) - namek = int(round(1000 * (1.00 + eps * k))) - formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}" - - structure2=structure.copy() - structure2.lattice=Lattice(rprim2) - structures_new[formatted_namei] = structure2 - - return structures_new - elif 75 <= spgrp_number <= 194: - disp=[[0,0],[1,1],[0,1],[2,1],[1,0],[1,2]] - for pair in disp: - i, k = pair - rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) - rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * i) - rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * k) - - namei = int(round(1000 * (1.00 + eps * i))) - namek = int(round(1000 * (1.00 + eps * k))) - formatted_namei = f"{namei:04d}_{namek:04d}" - rprim_new[formatted_namei] = rprim2 - - structure2=structure.copy() - structure2.lattice=Lattice(rprim2) - structures_new[formatted_namei] = structure2 - - return structures_new - elif 195 <= spgrp_number <= 230: - for i in range(3): - rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) - rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * i) - rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * i) - namei = int(round(1000 * (1.00 + eps * i))) - formatted_namei = f"{namei:04d}" - - structure2=structure.copy() - structure2.lattice=Lattice(rprim2) - structures_new[formatted_namei] = structure2 - return structures_new - diff --git a/abipy/dfpt/qha.py b/abipy/dfpt/qha.py index 52761b006..e368e1ad0 100644 --- a/abipy/dfpt/qha.py +++ b/abipy/dfpt/qha.py @@ -39,18 +39,10 @@ def __init__(self, structures, energies, eos_name='vinet', pressure=0): self.structures = structures self.energies = np.array(energies) self.eos = EOS(eos_name) - self.eos_name = eos_name self.pressure = pressure self.volumes = np.array([s.volume for s in structures]) self.iv0 = np.argmin(energies) - self.lattice_a = np.array([s.lattice.abc[0] for s in structures]) - self.lattice_b = np.array([s.lattice.abc[1] for s in structures]) - self.lattice_c = np.array([s.lattice.abc[2] for s in structures]) - - self.angles_alpha = np.array([s.lattice.angles[0] for s in structures]) - self.angles_beta = np.array([s.lattice.angles[1] for s in structures]) - self.angles_gama = np.array([s.lattice.angles[2] for s in structures]) def fit_energies(self, tstart=0, tstop=800, num=100): """ @@ -100,9 +92,8 @@ def fit_energies(self, tstart=0, tstop=800, num=100): # 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]) - F2D= np.array([fit.b0 for fit in fits]) /min_volumes - return dict2namedtuple(tot_en=tot_en, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh , F2D=F2D) + 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): @@ -155,7 +146,6 @@ def set_eos(self, eos_name: str) -> None: eos_name: string indicating the expression used to fit the energies. See pymatgen.analysis.eos.EOS. """ self.eos = EOS(eos_name) - self.eos_name = eos_name @add_fig_kwargs def plot_energies(self, tstart=0, tstop=800, num=10, ax=None, **kwargs) -> Figure: @@ -183,12 +173,12 @@ def plot_energies(self, tstart=0, tstop=800, num=10, ax=None, **kwargs) -> Figur 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('F (eV)') + ax.set_ylabel('E (eV)') #ax.grid(True) return fig - def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, tref=None) -> Function1D: + def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100) -> Function1D: """ 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. @@ -196,58 +186,25 @@ def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, tref=None) - Args: tstart: The starting value (in Kelvin) of the temperature mesh. tstop: The end value (in Kelvin) of the mesh. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - num: int, optional Number of samples to generate. Default is 100. Returns: |Function1D| """ f = self.fit_energies(tstart, tstop, num) - if tref is not None: - f0 = self.fit_energies(tref, tref, 1) - eos_list = [ 'taylor', 'murnaghan', 'birch', 'birch_murnaghan','pourier_tarantola', 'vinet', 'antonschmidt'] dt = f.temp[1] - f.temp[0] - if (self.eos_name in eos_list) : - thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) - entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro - df_t = np.zeros((num,self.nvols)) - df_t = - entropy - param = np.zeros((num,4)) - param2 = np.zeros((num,3)) - d2f_t_v = np.zeros(num) - gamma = np.zeros(num) - - for j in range (1,num-1): - param[j]=np.polyfit(self.volumes,df_t[j] , 3) - param2[j] = np.array([3*param[j][0],2*param[j][1],param[j][2]]) - - p = np.poly1d(param2[j]) - d2f_t_v[j]= p(f.min_vol[j]) - - if tref is None: - alpha= - 1/f.min_vol[1:-1] *d2f_t_v[1:-1] / f.F2D[1:-1] - else : - alpha= - 1/f0.min_vol * d2f_t_v[1:-1] / f.F2D[1:-1] - else : - if tref is None: - alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f.min_vol[1:-1] - else : - alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f0.min_vol + 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, tref=None, ax=None, **kwargs) -> Figure: + def plot_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, ax=None, **kwargs) -> Figure: """ 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) num: int, optional Number of samples to generate. Default is 100. ax: |matplotlib-Axes| or None if a new figure should be created. @@ -261,7 +218,7 @@ def plot_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, tref=None, if "color" not in kwargs: kwargs["color"] = "b" - alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + alpha = self.get_thermal_expansion_coeff(tstart, tstop, num) ax.plot(alpha.mesh, alpha.values, **kwargs) ax.set_xlabel(r'T (K)') @@ -303,183 +260,6 @@ def plot_vol_vs_t(self, tstart=0, tstop=800, num=100, ax=None, **kwargs) -> Figu return fig - def get_abc(self, tstart=0, tstop=800 , num=100): - """ - 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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| - """ - f = self.fit_energies(tstart, tstop, num) - param=np.polyfit(self.volumes, self.lattice_a , 3) - param0=[3*param[0],2*param[1],param[2]] - pa = np.poly1d(param) - dpa=np.poly1d(param0) - aa_qha=pa(f.min_vol) - daa_dv_qha=dpa(f.min_vol) - param=np.polyfit(self.volumes, self.lattice_b , 3) - param0=[3*param[0],2*param[1],param[2]] - pb = np.poly1d(param) - dpb = np.poly1d(param0) - bb_qha=pb(f.min_vol) - dbb_dv_qha=dpb(f.min_vol) - param=np.polyfit(self.volumes, self.lattice_c , 3) - param0=[3*param[0],2*param[1],param[2]] - pc = np.poly1d(param) - dpc = np.poly1d(param0) - cc_qha=pc(f.min_vol) - dcc_dv_qha=dpc(f.min_vol) - - return aa_qha,bb_qha,cc_qha, daa_dv_qha,dbb_dv_qha,dcc_dv_qha - - def get_angles(self, tstart=0, tstop=800 , num=100): - """ - 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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| - """ - f = self.fit_energies(tstart, tstop, num) - param=np.polyfit(self.volumes, self.angles_alpha, 3) - pa = np.poly1d(param) - alpha=pa(f.min_vol) - param=np.polyfit(self.volumes, self.angles_beta , 3) - pb = np.poly1d(param) - beta=pb(f.min_vol) - param=np.polyfit(self.volumes, self.angles_gama , 3) - pc = np.poly1d(param) - gamma=pc(f.min_vol) - - return alpha,beta,gamma - - @add_fig_kwargs - def plot_thermal_expansion_coeff_abc(self, tstart=0, tstop=800, num=100, tref=None, ax=None, **kwargs) -> Figure: - """ - 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - - tmesh = np.linspace(tstart, tstop, num) - - alpha_v = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) - aa,bb,cc, daa_dv,dbb_dv,dcc_dv = self.get_abc(tstart, tstop, num) - - if tref is None: - f = self.fit_energies(tstart, tstop, num) - dv_dt=alpha_v*f.min_vol[1:-1] - alpha_qha_a = dv_dt*daa_dv[1:-1]/ aa[1:-1] - alpha_qha_b = dv_dt*dbb_dv[1:-1]/ bb[1:-1] - alpha_qha_c = dv_dt*dcc_dv[1:-1]/ cc[1:-1] - else: - f0 = self.fit_energies(tref, tref, num) - dv_dt=alpha_v*f0.min_vol[1:-1] - aa_tref,bb_tref,cc_tref , daa_dv_tref,dbb_dv_tref,dcc_dv_tref = self.get_abc(tref, tref, 1) - alpha_qha_a = dv_dt*daa_dv[1:-1]/ aa_tref - alpha_qha_b = dv_dt*dbb_dv[1:-1]/ bb_tref - alpha_qha_c = dv_dt*dcc_dv[1:-1]/ cc_tref - - ax.plot(tmesh[1:-1] ,alpha_qha_a , color='r',lw=2 , **kwargs) - ax.plot(tmesh[1:-1] ,alpha_qha_b , color='b', lw=2 ) - ax.plot(tmesh[1:-1] ,alpha_qha_c , color='m', lw=2 ) - ax.set_xlabel(r'T (K)') - ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') - ax.legend([r"$\alpha_a$",r"$\alpha_b$",r"$\alpha_c$"]) - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - - @add_fig_kwargs - def plot_abc_vs_t(self, tstart=0 , tstop=800, num=100, tref=None, ax=None, **kwargs) -> Figure: - """ - 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - - #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) - tmesh = np.linspace(tstart, tstop, num) - aa,bb,cc, daa_dv,dbb_dv,dcc_dv = self.get_abc(tstart, tstop, num) - - ax.plot(tmesh ,aa , color='r',lw=2, **kwargs ) - ax.plot(tmesh ,bb , color='b', lw=2 ) - ax.plot(tmesh ,cc , color='m', lw=2 ) - ax.set_xlabel(r'T (K)') - ax.legend(["a(V(T))","b(V(T))","c(V(T))"]) - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - - @add_fig_kwargs - def plot_angle_vs_t(self, tstart=0, tstop=800, num=100, tref=None, ax=None, **kwargs)-> Figure: - """ - 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - - #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) - tmesh = np.linspace(tstart, tstop, num) - alpha,beta,gamma = self.get_angles(tstart, tstop, num) - - ax.plot(tmesh ,alpha , color='r',lw=2, **kwargs ) - ax.plot(tmesh ,beta , color='b', lw=2 ) - ax.plot(tmesh ,gamma , color='m', lw=2 ) - ax.set_xlabel(r'T (K)') - ax.legend([r"$\alpha$(V(T))",r"$\beta$(V(T))",r"$\gamma$(V(T))"]) - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig @add_fig_kwargs def plot_phbs(self, phbands, temperatures=None, t_max=1000, colormap="plasma", **kwargs) -> Figure: """ diff --git a/abipy/dfpt/qha_aproximation.py b/abipy/dfpt/qha_aproximation.py deleted file mode 100644 index e528cd2c3..000000000 --- a/abipy/dfpt/qha_aproximation.py +++ /dev/null @@ -1,1971 +0,0 @@ -# coding: utf-8 - -import os -import abc -import numpy as np -import abipy.core.abinit_units as abu - -from scipy.interpolate import UnivariateSpline -from monty.collections import dict2namedtuple -from monty.functools import lazy_property -from pymatgen.analysis.eos import EOS -from abipy.core.func1d import Function1D -from abipy.tools.plotting import add_fig_kwargs, get_ax_fig_plt, get_axarray_fig_plt -from abipy.electrons.gsr import GsrFile -from abipy.dfpt.ddb import DdbFile -from abipy.dfpt.phonons import PhononBandsPlotter, PhononDos, PhdosFile -from abipy.dfpt.gruneisen import GrunsNcFile - - -class QHA_App(metaclass=abc.ABCMeta): - """ - Class for the approximations on QHA analysis. - Provides some basic methods and plotting utils. - These can be used to obtain other quantities and plots. - Does not include electronic entropic contributions for metals. - """ - - def __init__(self, structures,structures_from_phdos,index_list, doses,energies, pressures, 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.eos_name = eos_name - self.pressure = pressure - self.pressures = np.array(pressures) - - self.volumes = np.array([s.volume for s in structures]) - self.iv0 = np.argmin(energies) - self.lattice_a = np.array([s.lattice.abc[0] for s in structures]) - self.lattice_b = np.array([s.lattice.abc[1] for s in structures]) - self.lattice_c = np.array([s.lattice.abc[2] for s in structures]) - - self.angles_alpha = np.array([s.lattice.angles[0] for s in structures]) - self.angles_beta = np.array([s.lattice.angles[1] for s in structures]) - self.angles_gama = np.array([s.lattice.angles[2] for s in structures]) - - self.doses = doses - self.structures_from_phdos = np.array(structures_from_phdos) - self.volumes_from_phdos = np.array([s.volume for s in structures_from_phdos]) - self.energies_pdos=self.energies[index_list] - self.index_list = index_list - if (len(self.index_list)==5): - self.iv0_vib=1 - self.iv1_vib=3 - self.V0_vib=self.volumes_from_phdos[2] - elif (len(self.index_list)==3): - self.iv0_vib=0 - self.iv1_vib=2 - self.V0_vib=self.volumes_from_phdos[1] - else : - self.iv0_vib=0 - self.iv1_vib=1 - self.V0_vib=0.5*(self.volumes_from_phdos[1]+self.volumes_from_phdos[0]) - if abs(self.volumes_from_phdos[self.iv0_vib]+self.volumes_from_phdos[self.iv1_vib]-2*self.volumes[self.iv0])<1e-3 : - self.scale_points="S" # Symmetry - else: - self.scale_points="D" # Displaced - -#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - def fit_tot_energies(self, tstart=0, tstop=1000, num=101,tot_energies="energies" ,volumes="volumes"): - """ - 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 - """ - # Generate a mesh of temperatures - tmesh = np.linspace(tstart, tstop, num) - - # List of fit objects, one for each temperature - fits = [self.eos.fit(volumes, e) for e in tot_energies.T] - - # Extract parameters from the fit objects - v0 = np.array([fit.v0 for fit in fits]) - e0 = np.array([fit.e0 for fit in fits]) - b0 = np.array([fit.b0 for fit in fits]) - b1 = np.array([fit.b1 for fit in fits]) - - # Minimum volumes and energies - min_volumes = np.array([fit.v0 for fit in fits]) - min_energies = np.array([fit.e0 for fit in fits]) - - v = min_volumes - eta = (v / v0) ** (1.0 / 3.0) - - # Calculate the second derivative of free energy - F2D = ( b0 / v0 * (-2.0 * (eta - 1) * eta ** -5.0 + (1 - 3.0 / 2.0 * (b1 - 1) * (eta - 1)) * eta ** -4.0) - * np.exp(-3.0 * (b1 - 1.0) * (v ** (1.0 / 3.0) / v0 ** (1 / 3.0) - 1.0) / 2.0)) - - return dict2namedtuple(tot_en=tot_energies, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh , F2D=F2D) - - def second_derivative_energy_v(self, vol="vol"): - """ - Performs a fit of the energies as a function of the volume at different temperatures. - - Args: - vol: The volume at which to evaluate the second derivative of the energy. - - Returns: - E2D_V: The second derivative of the energy with respect to volume. - """ - # Initialize variables for temperature range (not used in the current function) - tstart = 0 - tstop = 0 - num = 1 - - tot_en = self.energies[np.newaxis, :].T - fits = [self.eos.fit(self.volumes, e) for e in tot_en.T] - - # Extract parameters from the fit objects - v0 = np.array([fit.v0 for fit in fits]) - e0 = np.array([fit.e0 for fit in fits]) - b0 = np.array([fit.b0 for fit in fits]) - b1 = np.array([fit.b1 for fit in fits]) - - v = vol - eta = (v / v0) ** (1.0 / 3.0) - E2D_V = ( b0 / v0 * (-2.0 * (eta - 1) * eta ** -5.0 + (1 - 3.0 / 2.0 * (b1 - 1) * (eta - 1)) * eta ** -4.0) * - np.exp(-3.0 * (b1 - 1.0) * (v ** (1.0 / 3.0) / v0 ** (1.0 / 3.0) - 1.0) / 2.0)) - - return E2D_V -#============================================================================================= - def vol_E2Vib1(self, tstart=0, tstop=1000, num=101): - """ - Compute the volume as a function of temperature using the E2Vib1 method with the Vinet equation of state. - - Args: - tstart: The starting value (in Kelvin) of the temperature mesh. - tstop: The end value (in Kelvin) of the mesh. - num: Number of samples to generate. Default is 101. - - Returns: - vol: The calculated volumes as a function of temperature. - fits: The list of fit objects for the energies as a function of volume. - """ - # Generate temperature mesh - tmesh = np.linspace(tstart, tstop, num) - - # Get phonon free energies - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - - vol = np.zeros(num) - dfe_dV1 = np.zeros(num) - volumes0 = self.volumes_from_phdos - volumes = self.volumes - iv0 = self.iv0_vib - iv1 = self.iv1_vib - dV = volumes0[iv1] - volumes0[iv0] - V0 = volumes[self.iv0] - E2D = self.second_derivative_energy_v(V0) - - # Calculate derivative of free energy with respect to volume and updated volumes - for i, e in enumerate(ph_energies.T): - dfe_dV1[i] = (e[iv1] - e[iv0]) / dV - vol[i] = V0 - dfe_dV1[i] / E2D - - # Calculate total energies - tot_en = self.energies[self.iv0] + 0.5*(volumes[np.newaxis, :].T - V0)**2*E2D +(volumes[np.newaxis, :].T - V0)*dfe_dV1 - - # Fit the energies as a function of volume - fits = [self.eos.fit(volumes, e) for e in tot_en.T] - - return vol, fits - -#============================================================================================= - def vol_Einf_Vib1(self, tstart=0, tstop=1000, num=101): - """ - Compute the volume as a function of temperature using the EinfVib1 method with the Vinet equation of state. - - Args: - tstart: The starting value (in Kelvin) of the temperature mesh. - tstop: The end value (in Kelvin) of the mesh. - num: Number of samples to generate. Default is 101. - - Returns: - vol: The calculated volumes as a function of temperature. - fits: The list of fit objects for the energies as a function of volume. - """ - # Generate temperature mesh - tmesh = np.linspace(tstart, tstop, num) - - # Get phonon free energies - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - - vol = np.zeros(num) - dfe_dV1 = np.zeros(num) - volumes0 = self.volumes_from_phdos - volumes = self.volumes - iv0 = self.iv0_vib - iv1 = self.iv1_vib - V0 = self.V0_vib - - dV = volumes0[iv1] - volumes0[iv0] - - # Calculate derivative of free energy with respect to volume - for i, e in enumerate(ph_energies.T): - dfe_dV1[i] = (e[iv1] - e[iv0]) / dV - - # Calculate total energies - tot_en = self.energies[np.newaxis, :].T + (volumes[np.newaxis, :].T - V0) * dfe_dV1 - - # Fit the energies as a function of volume - fits = [self.eos.fit(volumes, e) for e in tot_en.T] - - # Extract minimum volumes from the fit objects - vol = np.array([fit.v0 for fit in fits]) - - return vol, fits - -#============================================================================================= - def vol_Einf_Vib2(self, tstart=0, tstop=1000, num=101): - """ - Compute the volume as a function of temperature using the EinfVib2 method with the Vinet equation of state. - - Args: - tstart: The starting value (in Kelvin) of the temperature mesh. - tstop: The end value (in Kelvin) of the mesh. - num: Number of samples to generate. Default is 101. - - Returns: - vol: The calculated volumes as a function of temperature. - fits: The list of fit objects for the energies as a function of volume. - """ - # Generate temperature mesh - tmesh = np.linspace(tstart, tstop, num) - - # Get phonon free energies - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - - vol = np.zeros(num) - dfe_dV1 = np.zeros(num) - dfe_dV2 = np.zeros(num) - fe_V0 = np.zeros(num) - - # Determine index for volume calculations - iv0 = 2 if len(self.index_list) == 5 else 1 - - dV = self.volumes_from_phdos[iv0] - self.volumes_from_phdos[iv0 - 1] - - # Compute derivatives of free energy with respect to volume - for i, e in enumerate(ph_energies.T): - dfe_dV1[i] = (e[iv0 + 1] - e[iv0 - 1]) / (self.volumes_from_phdos[iv0 + 1] - self.volumes_from_phdos[iv0 - 1]) - dfe_dV2[i] = (e[iv0 + 1] - 2.0 * e[iv0] + e[iv0 - 1]) / (self.volumes_from_phdos[iv0 + 1] - self.volumes_from_phdos[iv0])**2 - fe_V0[i] = e[iv0] - - # Reference volume - V0 = self.volumes_from_phdos[iv0] - - # Calculate total energies - tot_en = ( self.energies[np.newaxis, :].T +fe_V0 - +(self.volumes[np.newaxis, :].T - V0) * dfe_dV1 + 0.5 * (self.volumes[np.newaxis, :].T - V0)**2 * dfe_dV2) - - # Fit the energies as a function of volume - fits = [self.eos.fit(self.volumes, e) for e in tot_en.T] - - # Extract minimum volumes from the fit objects - vol = np.array([fit.v0 for fit in fits]) - - return vol, fits - -#================================================================================================================ - - def vol_Einf_Vib4(self, tstart=0, tstop=1000, num=101): - """ - Compute the volume as a function of temperature using the EinfVib4 method with the Vinet equation of state. - - Args: - tstart: The starting value (in Kelvin) of the temperature mesh. - tstop: The end value (in Kelvin) of the mesh. - num: Number of samples to generate. Default is 101. - - Returns: - vol: The calculated volumes as a function of temperature. - fits: The list of fit objects for the energies as a function of volume. - """ - - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - energies = self.energies - volumes0= self.volumes_from_phdos - volumes= self.volumes - vol = np.zeros( num) - dfe_dV1= np.zeros( num) - dfe_dV2= np.zeros( num) - dfe_dV3= np.zeros( num) - dfe_dV4= np.zeros( num) - fe_V0= np.zeros( num) - iv0=2 - dV=volumes0[2]-volumes0[1] - - for i,e in enumerate(ph_energies.T): - dfe_dV1[i]=(-e[iv0+2]+ 8*e[iv0+1]-8*e[iv0-1]+e[iv0-2])/(12*dV) - dfe_dV2[i]=(-e[iv0+2]+16*e[iv0+1]-30*e[iv0]+16*e[iv0-1]-e[iv0-2])/(12*dV**2) - dfe_dV3[i]=(e[iv0+2]-2*e[iv0+1]+2*e[iv0-1]-e[iv0-2])/(2*dV**3) - dfe_dV4[i]=(e[iv0+2]-4*e[iv0+1]+6*e[iv0]-4*e[iv0-1]+e[iv0-2])/(dV**4) - - fe_V0[i]= e[iv0] - V0=volumes0[iv0] - - tot_en = (( volumes[np.newaxis, :].T -V0) * dfe_dV1 + 0.5* ( volumes[np.newaxis, :].T -V0)**2*(dfe_dV2) - +( volumes[np.newaxis, :].T -V0)**3 *dfe_dV3/6.0 + ( volumes[np.newaxis, :].T -V0)**4*(dfe_dV4/24.0) - + fe_V0[:] +energies[np.newaxis, :].T ) - - fits = [self.eos.fit(volumes, e) for e in tot_en.T] - vol = np.array([fit.v0 for fit in fits]) - - return vol , fits -#********************************************************************************************* - @property - def nvols(self): - """Number of volumes""" - return len(self.volumes_from_phdos) - - def set_eos(self, eos_name): - """ - Set the EOS model 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) - self.eos_name = eos_name - if (eos_name!= "vinet"): - raise RuntimeError("This approximation method is only developed for the Vinet equation of state.") - - - - @add_fig_kwargs - def plot_energies(self, tstart=0, tstop=1000 ,num=1, ax=None, **kwargs): - """ - Plots the BO energy as a function of volume - - 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| - """ - - tmesh = np.linspace(tstart, tstop, num) - - f = self.fit_tot_energies(0, 0, 1 ,self.energies[np.newaxis, :].T,self.volumes) - - 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='s', s=10) - 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', linestyle='dashed' , lw=1, marker='o', ms=5) - - ax.set_xlabel(r'V (${\AA}^3$)') - ax.set_ylabel('E (eV)') - return fig - - @add_fig_kwargs - def plot_vol_vs_t(self, tstart=0, tstop=1000, num=101, ax=None, **kwargs): - """ - Plot the volume as a function of temperature using various methods. - - 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 101. - ax: Matplotlib Axes object or None. If None, a new figure will be created. - **kwargs: Additional keyword arguments to pass to plotting functions. - - Returns: - fig: The Matplotlib Figure object. - """ - # Get or create the matplotlib Axes and Figure - ax, fig, plt = get_ax_fig_plt(ax) - - # Generate temperature mesh - tmesh = np.linspace(tstart, tstop, num) - - # Get phonon free energies - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - - # Initialize data storage - iv0 = self.iv0_vib - iv1 = self.iv1_vib - volumes = self.volumes_from_phdos - - data_to_save = tmesh - columns = ['#Tmesh'] - - # Method 1: E2Vib1 - if self.scale_points=="S": - vol, _ = self.vol_E2Vib1(tstart=tstart, tstop=tstop, num=num) - ax.plot(tmesh, vol, color='b', lw=2, label="E2Vib1") - data_to_save = np.column_stack((data_to_save, vol)) - columns.append('E2vib1') - - # Method 2: Einf_Vib1 - if len(self.index_list) >= 2: - vol2, _ = self.vol_Einf_Vib1(tstart=tstart, tstop=tstop, num=num) - ax.plot(tmesh, vol2, color='gold', lw=2, label=r"$E_\infty$ Vib1") - data_to_save = np.column_stack((data_to_save, vol2)) - columns.append('Einfvib1') - - # Method 3: Einf_Vib2 - if len(self.index_list) >= 3: - vol3, _ = self.vol_Einf_Vib2(tstart=tstart, tstop=tstop, num=num) - ax.plot(tmesh, vol3, color='m', lw=2, label=r"$E_\infty$ Vib2") - data_to_save = np.column_stack((data_to_save, vol3)) - columns.append('Einfvib2') - - # Method 4: Einf_Vib4 and QHA - if len(self.index_list) == 5: - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_tot_energies(tstart, tstop, num, tot_en, self.volumes_from_phdos) - vol4, _ = self.vol_Einf_Vib4(tstart=tstart, tstop=tstop, num=num) - ax.plot(tmesh, vol4, color='c', lw=2, label=r"$E_\infty$ Vib4") - ax.plot(tmesh, f0.min_vol, color='k', linestyle='dashed', lw=1.5, label="QHA") - data_to_save = np.column_stack((data_to_save, vol4, f0.min_vol)) - columns.append('Einfvib4') - columns.append('QHA') - - # Plot V0 - ax.plot(0, self.volumes[self.iv0], color='g', lw=0, marker='o', ms=10, label="V0") - - # Set labels and limits - ax.set_xlabel('T (K)') - ax.set_ylabel(r'V (${\AA}^3$)') - ax.set_xlim(tstart, tstop) - ax.grid(True) - ax.legend() - - return fig - -################################################################################################### - def get_thermal_expansion_coeff(self, tstart=0, tstop=1000, num=101, tref=None): - """ - Calculates the thermal expansion coefficient 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - num: Number of samples to generate. Default is 101. - - Returns: - Function1D: The thermal expansion coefficient as a function of temperature. - """ - # Get phonon free energies - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f = self.fit_tot_energies(tstart, tstop, num, tot_en, self.volumes_from_phdos) - - if tref != None: - ph_energies2 = self.get_vib_free_energies(tref, tref, 1) - tot_en2 = self.energies_pdos[np.newaxis, :].T + ph_energies2 - f0 = self.fit_tot_energies(tref, tref, 1, tot_en2, self.volumes_from_phdos) - - dt = f.temp[1] - f.temp[0] - - # Get thermodynamic properties - thermo = self.get_thermodynamic_properties(tstart, tstop, num) - entropy = thermo.entropy.T - df_t = -entropy - - param = np.zeros((num, 4)) - param2 = np.zeros((num, 3)) - d2f_t_v = np.zeros(num) - - for j in range(num): - param[j] = np.polyfit(self.volumes_from_phdos, df_t[j], 3) - param2[j] = np.array([3 * param[j][0], 2 * param[j][1], param[j][2]]) - p = np.poly1d(param2[j]) - d2f_t_v[j] = p(f.min_vol[j]) - - F2D = f.F2D - if tref == None: - alpha = -1 / f.min_vol * d2f_t_v / F2D - else: - alpha = -1 / f0.min_vol * d2f_t_v / F2D - - return Function1D(f.temp, alpha) - - @add_fig_kwargs - def plot_thermal_expansion_coeff(self, tstart=0, tstop=1000, num=101, tref=None, 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) - - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - tmesh = np.linspace(tstart, tstop, num) - thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) - entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro - df_t = np.zeros((num,self.nvols)) - df_t = - entropy - volumes=self.volumes_from_phdos - - data_to_save = tmesh - columns = ['#Tmesh'] - iv0=self.iv0_vib - iv1=self.iv1_vib - dV=volumes[iv0+1]-volumes[iv0] - - if self.scale_points=="S": - vol,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) - E2D = self.second_derivative_energy_v(self.volumes[self.iv0]) - #f = self.fit_tot_energies(0, 0, 1 ,self.energies[np.newaxis, :].T,self.volumes) - #E2D = f.F2D - if (tref==None): - alpha_1 = - 1/vol[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D - else: - vol_ref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) - b0 = np.array([fit.b0 for fit in fits]) - print("B (E2vib1) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) - alpha_1 = - 1/vol_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D - ax.plot(tmesh, alpha_1,color='b', lw=2, label="E2Vib1") - data_to_save = np.column_stack((data_to_save,alpha_1)) - columns.append( 'E2vib1') - - if (len(self.index_list)>=2): - vol2 ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) - E2D_V = self.second_derivative_energy_v(vol2) - if (tref==None): - alpha_2 = - 1/vol2[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] - else: - vol2_ref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) - b0 = np.array([fit.b0 for fit in fits]) - print("B (Einfvib1) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) - alpha_2 = - 1/vol2_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] - ax.plot(tmesh, alpha_2,color='gold', lw=2 , label=r"$E_\infty Vib1$") - data_to_save = np.column_stack((data_to_save,alpha_2)) - columns.append( 'Einfvib1') - - if (len(self.index_list)>=3): - vol3,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) - E2D_V = self.second_derivative_energy_v(vol3) - dfe_dV2= np.zeros( num) - for i,e in enumerate(ph_energies.T): - dfe_dV2[i]=(e[iv0+2]-2.0*e[iv0+1]+e[iv0])/(dV)**2 - - ds_dv = (df_t[:,iv0+2]-df_t[:,iv0])/(2*dV) - ds_dv = ds_dv+ (df_t[:,iv0+2]-2*df_t[:,iv0+1]+df_t[:,iv0])/dV**2 * (vol3[:]-volumes[iv0+1]) - if (tref==None): - alpha_3 = - 1/vol3[:] * ds_dv / (E2D_V[:]+dfe_dV2[:]) - else: - vol3_ref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) - b0 = np.array([fit.b0 for fit in fits]) - print("B (Einfvib2) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) - alpha_3 = - 1/vol3_ref * ds_dv / (E2D_V[:]+dfe_dV2[:]) - ax.plot(tmesh, alpha_3,color='m', lw=2 , label=r"$E_\infty Vib2$") - data_to_save = np.column_stack((data_to_save,alpha_3)) - columns.append( 'Einfvib2') - - if (len(self.index_list)==5): - alpha_qha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) - vol4,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) - E2D_V = self.second_derivative_energy_v(vol4) - - d2fe_dV2= np.zeros( num) - d3fe_dV3= np.zeros( num) - d4fe_dV4= np.zeros( num) - for i,e in enumerate(ph_energies.T): - d2fe_dV2[i]=(-e[4]+16*e[3]-30*e[2]+16*e[1]-e[0])/(12*dV**2) - d3fe_dV3[i]=(e[4]-2*e[3]+2*e[1]-e[0])/(2*dV**3) - d4fe_dV4[i]=(e[4]-4*e[3]+6*e[2]-4*e[1]+e[0])/(dV**4) - - ds_dv =(-df_t[:,4]+ 8*df_t[:,3]-8*df_t[:,1]+df_t[:,0])/(12*dV) - ds_dv = ds_dv+ (-df_t[:,4]+16*df_t[:,3]-30*df_t[:,2]+16*df_t[:,1]-df_t[:,0])/(12*dV**2) * (vol4[:]-volumes[2]) - ds_dv = ds_dv+ 1.0/2.0*(df_t[:,4]-2*df_t[:,3]+2*df_t[:,1]-df_t[:,0])/(2*dV**3) * (vol4[:]-volumes[2])**2 - ds_dv = ds_dv+ 1.0/6.0* (df_t[:,4]-4*df_t[:,3]+6*df_t[:,2]-4*df_t[:,1]+df_t[:,0])/(dV**4)* (vol4[:]-volumes[2])**3 - D2F=E2D_V[:]+d2fe_dV2[:]+ (vol4[:]-volumes[2])*d3fe_dV3[:]+0.5*(vol4[:]-volumes[2])**2*d4fe_dV4[:] - if (tref==None): - alpha_4 = - 1/vol4[:] * ds_dv / D2F - else: - vol4_ref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) - b0 = np.array([fit.b0 for fit in fits]) - print("B (Einfvib4) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) - alpha_4 = - 1/vol4_ref * ds_dv / D2F - - ax.plot(tmesh, alpha_4,color='c',linewidth=2 , label=r"$E_\infty Vib4$") - ax.plot(alpha_qha.mesh, alpha_qha.values, color='k',linestyle='dashed', lw=1.5 ,label="QHA") - data_to_save = np.column_stack((data_to_save,alpha_4,alpha_qha.values)) - columns.append( 'Einfvib4') - columns.append( 'QHA') - - - ax.set_xlabel(r'T (K)') - ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') - ax.grid(True) - ax.legend() - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - def get_abc(self, tstart=0, tstop=1000, num=101,volumes="volumes"): - """ - 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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| - """ - param = np.zeros((num,4)) - param=np.polyfit(self.volumes, self.lattice_a , 3) - pa = np.poly1d(param) - aa_qha=pa(volumes) - param=np.polyfit(self.volumes, self.lattice_b , 3) - pb = np.poly1d(param) - bb_qha=pb(volumes) - param=np.polyfit(self.volumes, self.lattice_c , 3) - pc = np.poly1d(param) - cc_qha=pc(volumes) - - return aa_qha,bb_qha,cc_qha - - def get_angles(self, tstart=0, tstop=1000, num=101,volumes="volumes"): - """ - 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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| - """ - param = np.zeros((num,4)) - param=np.polyfit(self.volumes, self.angles_alpha, 3) - pa = np.poly1d(param) - gamma=pa(volumes) - param=np.polyfit(self.volumes, self.angles_beta , 3) - pb = np.poly1d(param) - beta=pb(volumes) - param=np.polyfit(self.volumes, self.angles_gama , 3) - pc = np.poly1d(param) - alpha=pc(volumes) - - return alpha,beta,gamma - -################################################################################################### - @add_fig_kwargs - def plot_thermal_expansion_coeff_abc(self, tstart=0, tstop=1000, num=101, tref=None, 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - data_to_save = tmesh[1:-1] - columns = ['#Tmesh'] - - if self.scale_points=="S": - vol2 ,fits= self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol2_tref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) - - if (len(self.index_list)==2): - vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) - method =r"$ (E_\infty Vib1)$" - - if (len(self.index_list)==3): - vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) - method =r"$ (E_\infty Vib2)$" - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) - method =r"$ (E_\infty Vib4)$" - vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) - - #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) - tmesh = np.linspace(tstart, tstop, num) - dt= tmesh[1] - tmesh[0] - - aa,bb,cc = self.get_abc(tstart, tstop, num,vol) - if (tref!=None): - aa_tref,bb_tref,cc_tref = self.get_abc(tref, tref, 1,vol_tref) - - alpha_a = np.zeros( num-2) - alpha_b = np.zeros( num-2) - alpha_c = np.zeros( num-2) - if (tref==None): - alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa[1:-1] - alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb[1:-1] - alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] - else: - alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa_tref - alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb_tref - alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref - - ax.plot(tmesh[1:-1] ,alpha_a , color='r', lw=2,label = r"$\alpha_a$"+method, **kwargs) - ax.plot(tmesh[1:-1] ,alpha_b , color='b', lw=2,label = r"$\alpha_b$"+method) - ax.plot(tmesh[1:-1] ,alpha_c , color='m', lw=2,label = r"$\alpha_c$"+method) - - method_header=method+" (alpha_a,alpha_b,alpha_c) |" - data_to_save = np.column_stack((data_to_save,alpha_a,alpha_b,alpha_c)) - columns.append( method_header) - - if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : - aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) - if (tref!=None): - aa2_tref,bb2_tref,cc2_tref = self.get_abc(tref, tref, 1,vol2_tref) - - alpha2_a = np.zeros( num-2) - alpha2_b = np.zeros( num-2) - alpha2_c = np.zeros( num-2) - if (tref==None): - alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2[1:-1] - alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2[1:-1] - alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] - else: - alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2_tref - alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2_tref - alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref - - ax.plot(tmesh[1:-1] ,alpha2_a , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_a$"" (E2vib1)") - ax.plot(tmesh[1:-1] ,alpha2_b , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_b$"" (E2vib1)") - ax.plot(tmesh[1:-1] ,alpha2_c , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_c$"" (E2vib1)") - data_to_save = np.column_stack((data_to_save,alpha2_a,alpha2_b,alpha2_c)) - columns.append( 'E2vib1 (alpha_a,alpha_b,alpha_c) ') - - ax.set_xlabel(r'T (K)') - ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') - ax.legend() - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - @add_fig_kwargs - def plot_thermal_expansion_coeff_angles(self, tstart=0, tstop=1000, num=101, tref=None, 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - data_to_save = tmesh[1:-1] - columns = ['#Tmesh'] - - if self.scale_points=="S": - vol2 ,fits= self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol2_tref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) - - if (len(self.index_list)==2): - vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) - method =r"$ (E_\infty Vib1)$" - - if (len(self.index_list)==3): - vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) - method =r"$ (E_\infty Vib2)$" - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) - method =r"$ (E_\infty Vib4)$" - vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) - - #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) - tmesh = np.linspace(tstart, tstop, num) - dt= tmesh[1] - tmesh[0] - - alpha,beta,cc = self.get_angles(tstart, tstop, num,vol) - if (tref!=None): - alpha_tref,beta_tref,cc_tref = self.get_angles(tref, tref, 1,vol_tref) - - alpha_alpha = np.zeros( num-2) - alpha_beta = np.zeros( num-2) - alpha_gamma = np.zeros( num-2) - if (tref==None): - alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha[1:-1] - alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta[1:-1] - alpha_gamma = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] - else: - alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha_tref - alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta_tref - alpha_gamma = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref - - ax.plot(tmesh[1:-1] ,alpha_alpha , color='r', lw=2,label = r"$\alpha_alpha$"+method, **kwargs) - ax.plot(tmesh[1:-1] ,alpha_beta , color='b', lw=2,label = r"$\alpha_beta$"+method) - ax.plot(tmesh[1:-1] ,alpha_gamma , color='m', lw=2,label = r"$\alpha_gamma$"+method) - - method_header=method+" (alpha_alpha,alpha_beta,alpha_gamma) |" - data_to_save = np.column_stack((data_to_save,alpha_alpha,alpha_beta,alpha_gamma)) - columns.append( method_header) - - if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : - alpha2,beta2,cc2 = self.get_angles(tstart, tstop, num,vol2) - if (tref!=None): - alpha2_tref,beta2_tref,cc2_tref = self.get_angles(tref, tref, 1,vol2_tref) - - alpha2_alpha = np.zeros( num-2) - alpha2_beta = np.zeros( num-2) - alpha2_gamma = np.zeros( num-2) - if (tref==None): - alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2[1:-1] - alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2[1:-1] - alpha2_gamma = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] - else: - alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2_tref - alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2_tref - alpha2_gamma = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref - - ax.plot(tmesh[1:-1] ,alpha2_alpha , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_alpha$"" (E2vib1)") - ax.plot(tmesh[1:-1] ,alpha2_beta , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_beta$"" (E2vib1)") - ax.plot(tmesh[1:-1] ,alpha2_gamma , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_gamma$"" (E2vib1)") - data_to_save = np.column_stack((data_to_save,alpha2_alpha,alpha2_beta,alpha2_gamma)) - columns.append( 'E2vib1 (alpha_alpha,alpha_beta,alpha_gamma) ') - - ax.set_xlabel(r'T (K)') - ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') - ax.legend() - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - - @add_fig_kwargs - def plot_abc_vs_t(self, tstart=0, tstop=1000, num=101, lattice=None, tref=None, 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - - data_to_save = tmesh - columns = ['#Tmesh'] - if self.scale_points=="S": - vol2,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) - aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) - data_to_save = np.column_stack((data_to_save,aa2,bb2,cc2)) - columns.append( 'E2vib1 (a,b,c) | ') - - if (len(self.index_list)==2): - vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) - method =r"$ (E_\infty Vib1)$" - - if (len(self.index_list)==3): - vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) - method =r"$ (E_\infty Vib2)$" - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) - method =r"$ (E_\infty Vib4)$" - vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) - aa,bb,cc = self.get_abc(tstart, tstop, num,vol) - - method_header=method+" (a,b,c) |" - data_to_save = np.column_stack((data_to_save,aa,bb,cc)) - columns.append( method_header) - - if (lattice==None or lattice=="a"): - ax.plot(tmesh ,aa , color='r', lw=2,label = r"$a(V(T))$"+method, **kwargs ) - if (lattice==None or lattice=="b"): - ax.plot(tmesh ,bb , color='b', lw=2,label = r"$b(V(T))$"+method ) - if (lattice==None or lattice=="c"): - ax.plot(tmesh ,cc , color='m', lw=2,label = r"$c(V(T))$"+method ) - - if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : - if (lattice==None or lattice=="a"): - ax.plot(tmesh ,aa2 , linestyle='dashed' , color='r', lw=2,label = r"$a(V(T))$""E2vib1" ) - if (lattice==None or lattice=="b"): - ax.plot(tmesh ,bb2 , linestyle='dashed' , color='b', lw=2,label = r"$b(V(T))$""E2vib1" ) - if (lattice==None or lattice=="c"): - ax.plot(tmesh ,cc2 , linestyle='dashed' , color='m', lw=2,label = r"$c(V(T))$""E2vib1" ) - - ax.set_xlabel(r'T (K)') - ax.legend() - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - @add_fig_kwargs - def plot_angles_vs_t(self, tstart=0, tstop=1000, num=101, angle=None, tref=None, 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - ax, fig, plt = get_ax_fig_plt(ax) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - - data_to_save = tmesh - columns = ['#Tmesh'] - if self.scale_points=="S": - vol2,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) - alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) - data_to_save = np.column_stack((data_to_save,alpha2,beta2,gamma2)) - columns.append( 'E2vib1 (alpha,beta,gamma) | ') - - if (len(self.index_list)==2): - vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) - method =r"$ (E_\infty Vib1)$" - - if (len(self.index_list)==3): - vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) - method =r"$ (E_\infty Vib2)$" - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) - method =r"$ (E_\infty Vib4)$" - vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) - alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) - - method_header=method+" (alpha,beta,gamm) |" - data_to_save = np.column_stack((data_to_save,alpha,beta,gamma)) - columns.append( method_header) - - if (angle==None or angle==1): - ax.plot(tmesh ,alpha , color='r', lw=2,label = r"$alpha(V(T))$"+method, **kwargs ) - if (angle==None or angle==2): - ax.plot(tmesh ,beta , color='b', lw=2,label = r"$beta(V(T))$"+method ) - if (angle==None or angle==3): - ax.plot(tmesh ,gamma , color='m', lw=2,label = r"$gamma(V(T))$"+method ) - - if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : - if (angle==None or angle==1): - ax.plot(tmesh ,alpha2 , linestyle='dashed' , color='r', lw=2,label = r"$alpha(V(T))$""E2vib1" ) - if (angle==None or angle==2): - ax.plot(tmesh ,beta2 , linestyle='dashed' , color='b', lw=2,label = r"$beta(V(T))$""E2vib1" ) - if (angle==None or angle==3): - ax.plot(tmesh ,gamma2 , linestyle='dashed' , color='m', lw=2,label = r"$gamma(V(T))$""E2vib1" ) - - ax.set_xlabel(r'T (K)') - ax.legend() - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& - def fit_forth(self, tstart=0, tstop=1000, num=1,energy="energy",volumes="volumes"): - """ - 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) - - param = np.zeros((num,5)) - param2 = np.zeros((num,4)) - param3 = np.zeros((num,3)) - min_vol = np.zeros((num)) - min_en = np.zeros((num)) - F2D_V = np.zeros((num)) - for j,e in enumerate(energy.T): - param[j]=np.polyfit(volumes,e , 4) - param2[j]=np.array([4*param[j][0],3*param[j][1],2*param[j][2],param[j][3]]) - param3[j]=np.array([12*param[j][0],6*param[j][1],2*param[j][2]]) - p = np.poly1d(param[j]) - p2 = np.poly1d(param2[j]) - p3 = np.poly1d(param3[j]) - min_vol[j]=self.volumes[self.iv0] - vv=self.volumes[self.iv0] - while p2(min_vol[j])**2 > 1e-16 : - min_vol[j]=min_vol[j]-p2(min_vol[j])*10 - min_en[j]=p(min_vol[j]) - F2D_V[j]=p3(min_vol[j]) - - return dict2namedtuple(min_vol=min_vol, temp=tmesh , min_en=min_en , param=param , F2D_V=F2D_V)#, fits=fits) - - def vol_E2Vib1_forth(self, tstart=0, tstop=1000, num=101): - - volumes0= self.volumes_from_phdos - iv0=self.iv0_vib - iv1=self.iv1_vib - - dV=volumes0[iv1]-volumes0[iv0] - V0=self.volumes[self.iv0] - - energy = self.energies[np.newaxis, :].T - f=self.fit_forth( tstart=0, tstop=0, num=1 ,energy=energy,volumes=self.volumes) - param3 = np.zeros((num,3)) - param3=np.array([12*f.param[0][0],6*f.param[0][1],2*f.param[0][2]]) - p3 = np.poly1d(param3) - E2D = p3(V0) - - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - vol = np.zeros( num) - - for i,e in enumerate(ph_energies.T): - dfe_dV1=(e[iv1]-e[iv0])/dV - vol[i]=V0-dfe_dV1*E2D**-1 - - return vol - - def vol_EinfVib1_forth(self, tstart=0, tstop=1000, num=101): - """ - Plot the volume as a function of the temperature. - Returns: |Vol| - """ - - volumes0= self.volumes_from_phdos - iv0=self.iv0_vib - iv1=self.iv1_vib - V0= self.V0_vib - - dV=volumes0[iv1]-volumes0[iv0] - - energy = self.energies[np.newaxis, :].T - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - vol = np.zeros( num) - - dfe_dV= np.zeros( num) - - for i,e in enumerate(ph_energies.T): - dfe_dV[i]=(e[iv1]-e[iv0])/dV - tot_en = self.energies[np.newaxis, :].T + ( self.volumes[np.newaxis, :].T -V0) * dfe_dV - - f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) - vol=f.min_vol - - return vol - def vol_Einf_Vib2_forth(self, tstart=0, tstop=1000, num=101): - """ - 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: |Vol| - """ - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - energies = self.energies - volumes0= self.volumes_from_phdos - volumes= self.volumes - vol = np.zeros( num) - dfe_dV= np.zeros( num) - d2fe_dV2= np.zeros( num) - fe_V0= np.zeros( num) - if (len(self.index_list)==5): - iv0=2 - else : - iv0=1 - dV=volumes0[iv0]-volumes0[iv0-1] - V0=volumes0[iv0] - for i,e in enumerate(ph_energies.T): - dfe_dV[i]=(e[iv0+1]-e[iv0-1])/(2*dV) - d2fe_dV2[i]=(e[iv0+1]-2.0*e[iv0]+e[iv0-1])/(dV)**2 - fe_V0[i]= e[iv0] - - tot_en = self.energies[np.newaxis, :].T + ( self.volumes[np.newaxis, :].T -V0) * dfe_dV - tot_en = tot_en + 0.5* (( self.volumes[np.newaxis, :].T -V0))**2*(d2fe_dV2) - tot_en = tot_en+ fe_V0 - f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) - vol=f.min_vol - - return vol - def vol_Einf_Vib4_forth(self, tstart=0, tstop=1000, num=101): - """ - - 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: |Vol| - """ - - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - energies = self.energies - volumes0= self.volumes_from_phdos - volumes= self.volumes - vol = np.zeros( num) - dfe_dV1= np.zeros( num) - dfe_dV2= np.zeros( num) - dfe_dV3= np.zeros( num) - dfe_dV4= np.zeros( num) - fe_V0= np.zeros( num) - iv0=2 - dV=volumes0[2]-volumes0[1] - - for i,e in enumerate(ph_energies.T): - dfe_dV1[i]=(-e[iv0+2]+ 8*e[iv0+1]-8*e[iv0-1]+e[iv0-2])/(12*dV) - dfe_dV2[i]=(-e[iv0+2]+16*e[iv0+1]-30*e[iv0]+16*e[iv0-1]-e[iv0-2])/(12*dV**2) - dfe_dV3[i]=(e[iv0+2]-2*e[iv0+1]+2*e[iv0-1]-e[iv0-2])/(2*dV**3) - dfe_dV4[i]=(e[iv0+2]-4*e[iv0+1]+6*e[iv0]-4*e[iv0-1]+e[iv0-2])/(dV**4) - - fe_V0[i]= e[iv0] - V0=volumes0[iv0] - - tot_en = ( volumes[np.newaxis, :].T -V0) * dfe_dV1 + 0.5* ( volumes[np.newaxis, :].T -V0)**2*(dfe_dV2) - tot_en = tot_en+( volumes[np.newaxis, :].T -V0)**3 *dfe_dV3/6 + ( volumes[np.newaxis, :].T -V0)**4*(dfe_dV4/24) - tot_en = tot_en + fe_V0 +energies[np.newaxis, :].T - - f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) - vol=f.min_vol - - return vol -#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ - @add_fig_kwargs - def plot_vol_vs_t_4th(self, tstart=0, tstop=1000, num=101, ax=None, **kwargs): - """ - Plot 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) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - - data_to_save = tmesh - columns = ['#Tmesh'] - if self.scale_points=="S": - vol_4th = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) - ax.plot(tmesh, vol_4th,color='b', lw=2, label="E2Vib1") - data_to_save = np.column_stack((data_to_save,vol_4th)) - columns.append( 'E2vib1') - - if (len(self.index_list)>=2): - vol2_4th = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) - ax.plot(tmesh, vol2_4th,color='gold', lw=2 , label=r"$E_\infty Vib1$") - data_to_save = np.column_stack((data_to_save,vol2_4th)) - columns.append( 'Einfvib1') - - if (len(self.index_list)>=3): - vol3_4th = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) - ax.plot(tmesh, vol3_4th,color='m', lw=2 , label=r"$E_\infty Vib2$") - data_to_save = np.column_stack((data_to_save,vol3_4th)) - columns.append( 'Einfvib2') - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) - vol4_4th = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) - ax.plot(tmesh, vol4_4th,color='c', lw=2 ,label=r"$E_\infty Vib4$") - ax.plot(tmesh, f0.min_vol, color='k',linestyle='dashed', lw=1.5 ,label="QHA") - data_to_save = np.column_stack((data_to_save,vol4_4th,f0.min_vol)) - columns.append( 'Einfvib4') - columns.append( 'QHA') - - ax.plot(0, self.volumes[self.iv0], color='g', lw=0, marker='o', ms=10,label="V0") - ax.set_xlabel('T (K)') - ax.set_ylabel(r'V (${\AA}^3$)') - ax.set_xlim(tstart, tstop) - ax.grid(True) - ax.legend() - - return fig - - def get_thermal_expansion_coeff_4th(self, tstart=0, tstop=1000, num=101 , tref=None): - """ - 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - - num: int, optional Number of samples to generate. Default is 100. - - Returns: |Function1D| - """ - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f = self.fit_forth( tstart, tstop, num ,tot_en,self.volumes_from_phdos) - if (tref!=None): - ph_energies2 = self.get_vib_free_energies(tref, tref, 1) - tot_en2 = self.energies_pdos[np.newaxis, :].T + ph_energies2 - f0 = self.fit_forth(tref, tref , 1 ,tot_en2 , self.volumes_from_phdos) - - dt = f.temp[1] - f.temp[0] - thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) - entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro - df_t = np.zeros((num,self.nvols)) - df_t = - entropy - param = np.zeros((num,4)) - param2 = np.zeros((num,3)) - d2f_t_v = np.zeros(num) - gamma = np.zeros(num) - - #for j in range (1,num-1): - for j in range (num): - param[j]=np.polyfit(self.volumes_from_phdos,df_t[j] , 3) - param2[j] = np.array([3*param[j][0],2*param[j][1],param[j][2]]) - - p = np.poly1d(param2[j]) - d2f_t_v[j]= p(f.min_vol[j]) - - F2D = f.F2D_V - if (tref==None): - #alpha= - 1/f.min_vol[1:-1] *d2f_t_v[1:-1] / F2D[1:-1] - alpha= - 1/f.min_vol *d2f_t_v / F2D - else : - #alpha= - 1/f0.min_vol * d2f_t_v[1:-1] / F2D[1:-1] - alpha= - 1/f0.min_vol * d2f_t_v / F2D - - return alpha - @add_fig_kwargs - def plot_thermal_expansion_coeff_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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) - - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - tmesh = np.linspace(tstart, tstop, num) - thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) - entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro - df_t = np.zeros((num,self.nvols)) - df_t = - entropy - volumes=self.volumes_from_phdos - - data_to_save = tmesh - columns = ['#Tmesh'] - iv0=self.iv0_vib - iv1=self.iv1_vib - dV=volumes[iv0+1]-volumes[iv0] - energy = self.energies[np.newaxis, :].T - f=self.fit_forth( tstart=0, tstop=0, num=1 ,energy=energy,volumes=self.volumes) - param3 = np.zeros((num,3)) - param3=np.array([12*f.param[0][0],6*f.param[0][1],2*f.param[0][2]]) - p3 = np.poly1d(param3) - - if self.scale_points=="S": - vol_4th = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) - E2D = p3(self.volumes[self.iv0]) - if (tref==None): - alpha_1 = - 1/vol_4th[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D - else : - vol_4th_ref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) - alpha_1 = - 1/vol_4th_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D - ax.plot(tmesh, alpha_1,color='b', lw=2, label="E2Vib1") - data_to_save = np.column_stack((data_to_save,alpha_1)) - columns.append( 'E2vib1') - - if (len(self.index_list)>=2): - vol2_4th = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) - #E2D_V = self.second_derivative_energy_v(vol2) - E2D_V = p3(vol2_4th) - if (tref==None): - alpha_2 = - 1/vol2_4th[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] - else : - vol2_4th_ref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) - alpha_2 = - 1/vol2_4th_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] - ax.plot(tmesh, alpha_2,color='gold', lw=2 , label=r"$E_\infty Vib1$") - data_to_save = np.column_stack((data_to_save,alpha_2)) - columns.append( 'Einfvib1') - - if (len(self.index_list)>=3): - vol3_4th = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) - E2D_V = p3(vol3_4th) - dfe_dV2= np.zeros( num) - for i,e in enumerate(ph_energies.T): - dfe_dV2[i]=(e[iv0+2]-2.0*e[iv0+1]+e[iv0])/(dV)**2 - - ds_dv = (df_t[:,iv0+2]-df_t[:,iv0])/(2*dV) - ds_dv = ds_dv+ (df_t[:,iv0+2]-2*df_t[:,iv0+1]+df_t[:,iv0])/dV**2 * (vol3_4th[:]-volumes[iv0+1]) - if (tref==None): - alpha_3 = - 1/vol3_4th[:] * ds_dv / (E2D_V[:]+dfe_dV2[:]) - else : - vol3_4th_ref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) - alpha_3 = - 1/vol3_4th_ref * ds_dv / (E2D_V[:]+dfe_dV2[:]) - ax.plot(tmesh, alpha_3,color='m', lw=2 , label=r"$E_\infty Vib2$") - data_to_save = np.column_stack((data_to_save,alpha_3)) - columns.append( 'Einfvib2') - - if (len(self.index_list)==5): - vol4_4th = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) - E2D_V = p3(vol4_4th) - - d2fe_dV2= np.zeros( num) - d3fe_dV3= np.zeros( num) - d4fe_dV4= np.zeros( num) - for i,e in enumerate(ph_energies.T): - d2fe_dV2[i]=(-e[4]+16*e[3]-30*e[2]+16*e[1]-e[0])/(12*dV**2) - d3fe_dV3[i]=(e[4]-2*e[3]+2*e[1]-e[0])/(2*dV**3) - d4fe_dV4[i]=(e[4]-4*e[3]+6*e[2]-4*e[1]+e[0])/(dV**4) - - ds_dv =(-df_t[:,4]+ 8*df_t[:,3]-8*df_t[:,1]+df_t[:,0])/(12*dV) - ds_dv = ds_dv+ (-df_t[:,4]+16*df_t[:,3]-30*df_t[:,2]+16*df_t[:,1]-df_t[:,0])/(12*dV**2) * (vol4_4th[:]-volumes[2]) - ds_dv = ds_dv+ 1.0/2.0*(df_t[:,4]-2*df_t[:,3]+2*df_t[:,1]-df_t[:,0])/(2*dV**3) * (vol4_4th[:]-volumes[2])**2 - ds_dv = ds_dv+ 1.0/6.0* (df_t[:,4]-4*df_t[:,3]+6*df_t[:,2]-4*df_t[:,1]+df_t[:,0])/(dV**4)* (vol4_4th[:]-volumes[2])**3 - D2F=E2D_V[:]+d2fe_dV2[:]+ (vol4_4th[:]-volumes[2])*d3fe_dV3[:]+0.5*(vol4_4th[:]-volumes[2])**2*d4fe_dV4[:] - if (tref==None): - alpha_4 = - 1/vol4_4th[:] * ds_dv / D2F - else : - vol4_4th_ref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) - alpha_4 = - 1/vol4_4th_ref * ds_dv / D2F - - - ax.plot(tmesh, alpha_4,color='c',linewidth=2 , label=r"$E_\infty Vib4$") - - alpha_qha = self.get_thermal_expansion_coeff_4th(tstart, tstop, num, tref) - ax.plot(tmesh, alpha_qha, color='k',linestyle='dashed', lw=1.5 ,label="QHA") - data_to_save = np.column_stack((data_to_save,alpha_4,alpha_qha)) - columns.append( 'Einfvib4') - columns.append( 'QHA') - - - ax.set_xlabel(r'T (K)') - ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') - ax.grid(True) - ax.legend() - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - @add_fig_kwargs - def plot_abc_vs_t_4th(self, tstart=0, tstop=1000, num=101, lattice=None,tref=None, 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - ax, fig, plt = get_ax_fig_plt(ax) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - - data_to_save = tmesh - columns = ['#Tmesh'] - if self.scale_points=="S": - vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) - aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) - data_to_save = np.column_stack((data_to_save,aa2,bb2,cc2)) - columns.append( 'E2vib1 (a,b,c) | ') - - if (len(self.index_list)==2): - vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) - method =r"$ (E_\infty Vib1)$" - - if (len(self.index_list)==3): - vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) - method =r"$ (E_\infty Vib2)$" - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) - method =r"$ (E_\infty Vib4)$" - vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) - aa,bb,cc = self.get_abc(tstart, tstop, num,vol) - - method_header=method+" (a,b,c) |" - data_to_save = np.column_stack((data_to_save,aa,bb,cc)) - columns.append( method_header) - - if (lattice==None or lattice=="a"): - ax.plot(tmesh ,aa , color='r', lw=2,label = r"$a(V(T))$"+method, **kwargs ) - if (lattice==None or lattice=="b"): - ax.plot(tmesh ,bb , color='b', lw=2,label = r"$b(V(T))$"+method ) - if (lattice==None or lattice=="c"): - ax.plot(tmesh ,cc , color='m', lw=2,label = r"$c(V(T))$"+method ) - - if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : - if (lattice==None or lattice=="a"): - ax.plot(tmesh ,aa2 , linestyle='dashed' , color='r', lw=2,label = r"$a(V(T))$""E2vib1" ) - if (lattice==None or lattice=="b"): - ax.plot(tmesh ,bb2 , linestyle='dashed' , color='b', lw=2,label = r"$b(V(T))$""E2vib1" ) - if (lattice==None or lattice=="c"): - ax.plot(tmesh ,cc2 , linestyle='dashed' , color='m', lw=2,label = r"$c(V(T))$""E2vib1" ) - - ax.set_xlabel(r'T (K)') - ax.legend() - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - @add_fig_kwargs - def plot_angles_vs_t_4th(self, tstart=0, tstop=1000, num=101,angle=None, tref=None, 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - ax, fig, plt = get_ax_fig_plt(ax) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - - data_to_save = tmesh - columns = ['#Tmesh'] - if self.scale_points=="S": - vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) - alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) - data_to_save = np.column_stack((data_to_save,alpha2,beta2,gamma2)) - columns.append( 'E2vib1 (alpha,beta,gamma) | ') - - if (len(self.index_list)==2): - vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) - method =r"$ (E_\infty Vib1)$" - - if (len(self.index_list)==3): - vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) - method =r"$ (E_\infty Vib2)$" - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) - method =r"$ (E_\infty Vib4)$" - vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) - alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) - - method_header=method+" (alpha,beta,gamma) |" - data_to_save = np.column_stack((data_to_save,alpha,beta,gamma)) - columns.append( method_header) - - if (angle==None or angle==1): - ax.plot(tmesh ,alpha , color='r', lw=2,label = r"$alpha(V(T))$"+method, **kwargs ) - if (angle==None or angle==2): - ax.plot(tmesh ,beta , color='b', lw=2,label = r"$beta(V(T))$"+method ) - if (angle==None or angle==3): - ax.plot(tmesh ,gamma , color='m', lw=2,label = r"$gamma(V(T))$"+method ) - - if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : - if (angle==None or angle==1): - ax.plot(tmesh ,alpha2 , linestyle='dashed' , color='r', lw=2,label = r"$alpha(V(T))$""E2vib1" ) - if (angle==None or angle==2): - ax.plot(tmesh ,beta2 , linestyle='dashed' , color='b', lw=2,label = r"$beta(V(T))$""E2vib1" ) - if (angle==None or angle==3): - ax.plot(tmesh ,gamma2 , linestyle='dashed' , color='m', lw=2,label = r"$gamma(V(T))$""E2vib1" ) - - ax.set_xlabel(r'T (K)') - ax.legend() - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig -################################################################################################### - @add_fig_kwargs - def plot_thermal_expansion_coeff_abc_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - - data_to_save = tmesh[1:-1] - columns = ['#Tmesh'] - if self.scale_points=="S": - vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol2_tref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) - - if (len(self.index_list)==2): - vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) - method =r"$ (E_\infty Vib1)$" - - if (len(self.index_list)==3): - vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) - method =r"$ (E_\infty Vib2)$" - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) - method =r"$ (E_\infty Vib4)$" - vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) - - #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) - tmesh = np.linspace(tstart, tstop, num) - dt= tmesh[1] - tmesh[0] - - aa,bb,cc = self.get_abc(tstart, tstop, num,vol) - if (tref!=None): - aa_tref,bb_tref,cc_tref = self.get_abc(tref, tref, 1,vol_tref) - - alpha_a = np.zeros( num-2) - alpha_b = np.zeros( num-2) - alpha_c = np.zeros( num-2) - if (tref==None): - alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa[1:-1] - alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb[1:-1] - alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] - else: - alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa_tref - alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb_tref - alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref - - ax.plot(tmesh[1:-1] ,alpha_a , color='r', lw=2,label = r"$\alpha_a$"+method, **kwargs) - ax.plot(tmesh[1:-1] ,alpha_b , color='b', lw=2,label = r"$\alpha_b$"+method) - ax.plot(tmesh[1:-1] ,alpha_c , color='m', lw=2,label = r"$\alpha_c$"+method) - - method_header=method+" (alpha_a,alpha_b,alpha_c) |" - data_to_save = np.column_stack((data_to_save,alpha_a,alpha_b,alpha_c)) - columns.append( method_header) - - if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : - aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) - if (tref!=None): - aa2_tref,bb2_tref,cc2_tref = self.get_abc(tref, tref, 1,vol2_tref) - - alpha2_a = np.zeros( num-2) - alpha2_b = np.zeros( num-2) - alpha2_c = np.zeros( num-2) - if (tref==None): - alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2[1:-1] - alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2[1:-1] - alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] - else: - alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2_tref - alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2_tref - alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref - - ax.plot(tmesh[1:-1] ,alpha2_a , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_a$"" (E2vib1)") - ax.plot(tmesh[1:-1] ,alpha2_b , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_b$"" (E2vib1)") - ax.plot(tmesh[1:-1] ,alpha2_c , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_c$"" (E2vib1)") - data_to_save = np.column_stack((data_to_save,alpha2_a,alpha2_b,alpha2_c)) - columns.append( 'E2vib1 (alpha_a,alpha_b,alpha_c) ') - - ax.set_xlabel(r'T (K)') - ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') - ax.legend() - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig - @add_fig_kwargs - def plot_thermal_expansion_coeff_angles_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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. - tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. - (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) - 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) - tmesh = np.linspace(tstart, tstop, num) - ph_energies = self.get_vib_free_energies(tstart, tstop, num) - iv0=self.iv0_vib - iv1=self.iv1_vib - volumes=self.volumes_from_phdos - - data_to_save = tmesh[1:-1] - columns = ['#Tmesh'] - if self.scale_points=="S": - vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol2_tref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) - - if (len(self.index_list)==2): - vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) - method =r"$ (E_\infty Vib1)$" - - if (len(self.index_list)==3): - vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) - method =r"$ (E_\infty Vib2)$" - - if (len(self.index_list)==5): - tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies - f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) - method =r"$ (E_\infty Vib4)$" - vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) - if (tref!=None): - vol_tref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) - - tmesh = np.linspace(tstart, tstop, num) - dt= tmesh[1] - tmesh[0] - - alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) - if (tref!=None): - alpha_tref,beta_tref,gamma_tref = self.get_angles(tref, tref, 1,vol_tref) - - alpha_alpha = np.zeros( num-2) - alpha_beta = np.zeros( num-2) - alpha_gamma = np.zeros( num-2) - if (tref==None): - alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha[1:-1] - alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta[1:-1] - alpha_gamma = (gamma[2:] - gamma[:-2]) / (2 * dt) / gamma[1:-1] - else: - alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha_tref - alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta_tref - alpha_gamma = (gamma[2:] - gamma[:-2]) / (2 * dt) / gamma_tref - - ax.plot(tmesh[1:-1] ,alpha_alpha , color='r', lw=2,label = r"$\alpha_alpha$"+method, **kwargs) - ax.plot(tmesh[1:-1] ,alpha_beta , color='b', lw=2,label = r"$\alpha_beta$"+method) - ax.plot(tmesh[1:-1] ,alpha_gamma , color='m', lw=2,label = r"$\alpha_gamma$"+method) - - method_header=method+" (alpha_alpha,alpha_beta,alpha_gamma) |" - data_to_save = np.column_stack((data_to_save,alpha_alpha,alpha_beta,alpha_gamma)) - columns.append( method_header) - - if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : - alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) - if (tref!=None): - alpha2_tref,beta2_tref,gamma2_tref = self.get_angles(tref, tref, 1,vol2_tref) - - alpha2_alpha = np.zeros( num-2) - alpha2_beta = np.zeros( num-2) - alpha2_gamma = np.zeros( num-2) - if (tref==None): - alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2[1:-1] - alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2[1:-1] - alpha2_gamma = (gamma2[2:] - gamma2[:-2]) / (2 * dt) / gamma2[1:-1] - else: - alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2_tref - alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2_tref - alpha2_gamma = (gamma2[2:] - gamma2[:-2]) / (2 * dt) / gamma2_tref - - ax.plot(tmesh[1:-1] ,alpha2_alpha , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_alpha$"" (E2vib1)") - ax.plot(tmesh[1:-1] ,alpha2_beta , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_beta$"" (E2vib1)") - ax.plot(tmesh[1:-1] ,alpha2_gamma , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_gamma$"" (E2vib1)") - data_to_save = np.column_stack((data_to_save,alpha2_alpha,alpha2_beta,alpha2_gamma)) - columns.append( 'E2vib1 (alpha_alpha,alpha_beta,alpha_gamma) ') - - ax.set_xlabel(r'T (K)') - ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') - ax.legend() - ax.grid(True) - - ax.set_xlim(tstart, tstop) - ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) - - return fig -#********************************************************************************************* - @classmethod - def from_files_app(cls, gsr_paths, phdos_paths): - """ - Creates an instance of QHA from a list of GSR files and a list of PHDOS.nc files. - The list should have the same size and the volumes should match. - - Args: - gsr_paths: list of paths to GSR files. - phdos_paths: list of paths to PHDOS.nc files. - - Returns: A new instance of QHA - """ - energies = [] - structures = [] - pressures = [] - for gp in gsr_paths: - with GsrFile.from_file(gp) as g: - energies.append(g.energy) - structures.append(g.structure) - pressures.append(g.pressure) - - #doses = [PhononDos.as_phdos(dp) for dp in phdos_paths] - - doses = [] - structures_from_phdos = [] - for path in phdos_paths: - with PhdosFile(path) as p: - doses.append(p.phdos) - structures_from_phdos.append(p.structure) - - # cls._check_volumes_id(structures, structures_from_phdos) - vols1 = [s.volume for s in structures] - vols2 = [s.volume for s in structures_from_phdos] - dv=np.zeros((len(vols2)-1)) - for j in range(len(vols2)-1): - dv[j]=vols2[j+1]-vols2[j] - tolerance = 1e-3 - if (len(vols2)!=2): - max_difference = np.max(np.abs(dv - dv[0])) - if max_difference > tolerance: - raise RuntimeError("Expecting an equal volume change for structures from PDOS." ) - - index_list = [i for v2 in vols2 for i, v1 in enumerate(vols1) if abs(v2 - v1) < 1e-3] - if len(index_list) != len(vols2): - raise RuntimeError("Expecting the ground state files for all PDOS files!") - if len(index_list) not in (2, 3, 5): - raise RuntimeError("Expecting just 2, 3, or 5 PDOS files in the approximation method.") - - return cls(structures,structures_from_phdos,index_list, doses, energies , pressures) - - def get_vib_free_energies(self, tstart=0, tstop=1000, num=101) -> np.ndarray: - """ - Generates the vibrational free energy from the phonon DOS. - - 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 the vibrational contribution to the free energy - """ - f = np.zeros((self.nvols, num)) - - for i, dos in enumerate(self.doses): - f[i] = dos.get_free_energy(tstart, tstop, num).values - return f - - def get_thermodynamic_properties(self, tstart=0, tstop=1000, num=101): - """ - Generates all the thermodynamic properties corresponding to all the volumes using the phonon DOS. - - 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). - """ - tmesh = np.linspace(tstart, tstop, num) - cv = np.zeros((self.nvols, num)) - free_energy = np.zeros((self.nvols, num)) - entropy = np.zeros((self.nvols, num)) - internal_energy = np.zeros((self.nvols, num)) - zpe = np.zeros(self.nvols) - - for i, d in enumerate(self.doses): - cv[i] = d.get_cv(tstart, tstop, num).values - free_energy[i] = d.get_free_energy(tstart, tstop, num).values - entropy[i] = d.get_entropy(tstart, tstop, num).values - zpe[i] = d.zero_point_energy - - return dict2namedtuple(tmesh=tmesh, cv=cv, free_energy=free_energy, entropy=entropy, zpe=zpe) -#======================================================================================================= - @classmethod - def from_files_app_ddb(cls, ddb_paths, phdos_paths): - """ - Creates an instance of QHA from a list of GSR files and a list of PHDOS.nc files. - The list should have the same size and the volumes should match. - - Args: - ddb_paths: list of paths to DDB files. - phdos_paths: list of paths to PHDOS.nc files. - - Returns: A new instance of QHA - """ - energies = [] - structures = [] - pressures = [] - for gp in ddb_paths: - with DdbFile.from_file(gp) as g: - energies.append(g.total_energy) - structures.append(g.structure) - #pressures.append(g.pressure) - - #doses = [PhononDos.as_phdos(dp) for dp in phdos_paths] - - doses = [] - structures_from_phdos = [] - for path in phdos_paths: - with PhdosFile(path) as p: - doses.append(p.phdos) - structures_from_phdos.append(p.structure) - - # cls._check_volumes_id(structures, structures_from_phdos) - vols1 = [s.volume for s in structures] - vols2 = [s.volume for s in structures_from_phdos] - dv=np.zeros((len(vols2)-1)) - for j in range(len(vols2)-1): - dv[j]=vols2[j+1]-vols2[j] - tolerance = 1e-3 - if (len(vols2)!=2): - max_difference = np.max(np.abs(dv - dv[0])) - if max_difference > tolerance: - raise RuntimeError("Expecting an equal volume change for structures from PDOS." ) - - index_list = [i for v2 in vols2 for i, v1 in enumerate(vols1) if abs(v2 - v1) < 1e-3] - if len(index_list) != len(vols2): - raise RuntimeError("Expecting the ground state files for all PDOS files!") - if len(index_list) not in (2, 3, 5): - raise RuntimeError("Expecting just 2, 3, or 5 PDOS files in the approximation method.") - - return cls(structures,structures_from_phdos,index_list, doses, energies , pressures) - From c86d2c6e2f366ba173ab9b553d820d9d6d2b300e Mon Sep 17 00:00:00 2001 From: Samare Date: Thu, 17 Oct 2024 00:17:04 +0200 Subject: [PATCH 6/9] small modification for QHA (#300) * Add file for generating deformations for QHA. * Update qha.py: Added tref and a new method for calculating the thermal expansion coefficient. * Add a script for v-ZSISA-QHA . "Physical Review B 110 (1), 014103" * modification in qha_aproximation.py. * Some modification on deformation_utils and qha. * Some modifications * Modification for qha.py --- abipy/dfpt/deformation_utils.py | 193 +++ abipy/dfpt/qha.py | 236 +++- abipy/dfpt/qha_aproximation.py | 1971 +++++++++++++++++++++++++++++++ abipy/dfpt/tests/test_qha.py | 6 +- 4 files changed, 2396 insertions(+), 10 deletions(-) create mode 100644 abipy/dfpt/deformation_utils.py create mode 100644 abipy/dfpt/qha_aproximation.py diff --git a/abipy/dfpt/deformation_utils.py b/abipy/dfpt/deformation_utils.py new file mode 100644 index 000000000..c40527955 --- /dev/null +++ b/abipy/dfpt/deformation_utils.py @@ -0,0 +1,193 @@ +# deformation_utils.py + +import numpy as np +from pymatgen.core import Structure, Lattice, Element +from abipy.core.symmetries import AbinitSpaceGroup +from pymatgen.symmetry.analyzer import SpacegroupAnalyzer +from abipy.abio.inputs import AbinitInput +import re + + +def generate_deformations_volumic(structure, eps_V=0.02, scales=None): + if scales is None: + scales = [-1, 0, 1, 2, 3] + rprim = structure.lattice.matrix + structures_new = {} + + for i in scales: + rprim2 = np.copy(rprim) + rprim2[:, :] = rprim[:, :] * (1.00 + eps_V * i)**(1/3.) + + structure2 = structure.copy() + structure2.lattice = Lattice(rprim2) + #structure2.scale_lattice(structure2.volume*(1.00 + eps_V * i)) + namei = int(round(1000 * (1.00 + eps_V * i))) + formatted_namei = f"{namei:04d}" + structures_new[formatted_namei] = structure2 + + + return structures_new + +def generate_deformations(structure , eps=0.005): + spgrp = AbinitSpaceGroup.from_structure(structure ) + print (spgrp) + spgrp_number=spgrp.spgid + rprim= structure.lattice.matrix + + rprim2 = np.copy(rprim) + rprim_new = {} + structures_new = {} + + if 1 <= spgrp_number <= 2: + disp=[[1,1,1,1,1,1], [0,1,1,1,1,1], [2,1,1,1,1,1], [1,0,1,1,1,1], [1,2,1,1,1,1], [1,1,0,1,1,1], + [1,1,2,1,1,1], [1,1,1,0,1,1], [1,1,1,2,1,1], [1,1,1,1,0,1], [1,1,1,1,2,1], [1,1,1,1,1,0], + [1,1,1,1,1,2], [0,0,1,1,1,1], [1,0,0,1,1,1], [1,1,0,0,1,1], [1,1,1,0,0,1], [1,1,1,1,0,0], + [0,1,0,1,1,1], [0,1,1,0,1,1], [0,1,1,1,0,1], [0,1,1,1,1,0], [1,0,1,0,1,1], [1,0,1,1,0,1], + [1,0,1,1,1,0], [1,1,0,1,0,1], [1,1,0,1,1,0], [1,1,1,0,1,0] , [0 ,0,0,0,0,0]] + if abs(rprim[1, 0]) > 1e-9 or abs(rprim[2, 0]) > 1e-9 or abs(rprim[2, 1]) > 1e-9: + print("Warning: The lattice is oriented such that xz =xy =yz =0 .") + rprim0 = np.copy(rprim) + a=rprim[0, :] + b=rprim[1, :] + c=rprim[2, :] + norm_a = np.linalg.norm(a) + norm_b = np.linalg.norm(b) + norm_c = np.linalg.norm(c) + + # Compute angles between vectors + cos_ab = np.dot(a, b) / (norm_a * norm_b) + cos_ac = np.dot(a, c) / (norm_a * norm_c) + cos_bc = np.dot(b, c) / (norm_b * norm_c) + + rprim0[0,0] = 1.0 + rprim0[0,1] = 0.0 + rprim0[0,2] = 0.0 + rprim0[1,0] = cos_ab + rprim0[1,1] = np.sqrt(1-cos_ab**2) + rprim0[1,2] = 0.0 + rprim0[2,0] = cos_ac + rprim0[2,1] = (cos_bc-rprim0[1,0]*rprim0[2,0])/rprim0[1,1] + rprim0[2,2] = np.sqrt(1.0-rprim0[2,0]**2-rprim0[2,1]**2) + rprim0[0,:] = rprim0[0,:]*norm_a + rprim0[1,:] = rprim0[1,:]*norm_b + rprim0[2,:] = rprim0[2,:]*norm_c + print("Old rprim:") + print(rprim) + print("New rprim:") + print(rprim0) + + for pair in disp: + i,j,k,l,m,n = pair + rprim2[ :,0] = rprim0[ :,0] * (1.00 + eps * i) + rprim0[ :,1] * (eps * l) +rprim0[ :,2] * (eps * m) + rprim2[ :,1] = rprim0[ :,1] * (1.00 + eps * j) + rprim0[ :,2] * (eps * n) + rprim2[ :,2] = rprim0[ :,2] * (1.00 + eps * k) + + namei = int(round(1000 * (1.00 + eps * i))) + namej = int(round(1000 * (1.00 + eps * j))) + namek = int(round(1000 * (1.00 + eps * k))) + namel = int(round(1000 * (1.00 + eps * l))) + namem = int(round(1000 * (1.00 + eps * m))) + namen = int(round(1000 * (1.00 + eps * n))) + formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}_{namel:04d}_{namem:04d}_{namen:04d}" + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + + return structures_new + elif 3 <= spgrp_number <= 15: + disp=[[1,1,1,1], [0,1,1,1], [2,1,1,1], [1,0,1,1], [1,2,1,1], [1,1,0,1], [1,1,2,1], [1,1,1,0], + [1,1,1,2], [0,0,1,1], [1,0,0,1], [1,1,0,0], [0,1,0,1], [1,0,1,0], [0,1,1,0]] + if abs(rprim[1, 0]) > 1e-9 or abs(rprim[0, 1]) > 1e-9 or abs(rprim[2, 1]) > 1e-9 or abs(rprim[1, 2]) > 1e-9: + print("Error: Monoclinic structure with yx=xy=0 and yz=zy=0 lattice required.") + elif abs(rprim[0, 2]) > 1e-9 : + print("Warning: The lattice is oriented such that xz = 0.") + rprim0 = np.copy(rprim) + a=rprim[0, :] + b=rprim[1, :] + c=rprim[2, :] + norm_a = np.linalg.norm(a) + norm_b = np.linalg.norm(b) + norm_c = np.linalg.norm(c) + + # Compute angles between vectors + cos_ab = np.dot(a, b) / (norm_a * norm_b) + cos_ac = np.dot(a, c) / (norm_a * norm_c) + cos_bc = np.dot(b, c) / (norm_b * norm_c) + + rprim0[0,0] = norm_a + rprim0[0,2] = 0.0 + rprim0[1,1] = norm_b + rprim0[2,0] = norm_c*cos_ac + rprim0[2,2] = norm_c*np.sqrt(1-cos_ac**2) + print("Old rprim:") + print(rprim) + print("New rprim:") + print(rprim0) + + for pair in disp: + i,j,k,l = pair + rprim2[ :,0] = rprim0[ :,0] * (1.00 + eps * i) +rprim0[ :,2] * (eps * l) + rprim2[ :,1] = rprim0[ :,1] * (1.00 + eps * j) + rprim2[ :,2] = rprim0[ :,2] * (1.00 + eps * k) + + namei = int(round(1000 * (1.00 + eps * i))) + namej = int(round(1000 * (1.00 + eps * j))) + namek = int(round(1000 * (1.00 + eps * k))) + namel = int(round(1000 * (1.00 + eps * l))) + formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}_{namel:04d}" + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + + return structures_new + elif 16 <= spgrp_number <= 74: + disp=[[0,0,1],[0,1,0],[1,0,0],[1,1,1],[0,1,1],[2,1,1],[1,0,1],[1,2,1],[1,1,0],[1,1,2]] + for pair in disp: + i,j,k = pair + rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) + rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * j) + rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * k) + + namei = int(round(1000 * (1.00 + eps * i))) + namej = int(round(1000 * (1.00 + eps * j))) + namek = int(round(1000 * (1.00 + eps * k))) + formatted_namei = f"{namei:04d}_{namej:04d}_{namek:04d}" + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + + return structures_new + elif 75 <= spgrp_number <= 194: + disp=[[0,0],[1,1],[0,1],[2,1],[1,0],[1,2]] + for pair in disp: + i, k = pair + rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) + rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * i) + rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * k) + + namei = int(round(1000 * (1.00 + eps * i))) + namek = int(round(1000 * (1.00 + eps * k))) + formatted_namei = f"{namei:04d}_{namek:04d}" + rprim_new[formatted_namei] = rprim2 + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + + return structures_new + elif 195 <= spgrp_number <= 230: + for i in range(3): + rprim2[ :,0] = rprim[ :,0] * (1.00 + eps * i) + rprim2[ :,1] = rprim[ :,1] * (1.00 + eps * i) + rprim2[ :,2] = rprim[ :,2] * (1.00 + eps * i) + namei = int(round(1000 * (1.00 + eps * i))) + formatted_namei = f"{namei:04d}" + + structure2=structure.copy() + structure2.lattice=Lattice(rprim2) + structures_new[formatted_namei] = structure2 + return structures_new + diff --git a/abipy/dfpt/qha.py b/abipy/dfpt/qha.py index e368e1ad0..6e0474eb0 100644 --- a/abipy/dfpt/qha.py +++ b/abipy/dfpt/qha.py @@ -39,10 +39,18 @@ def __init__(self, structures, energies, eos_name='vinet', pressure=0): self.structures = structures self.energies = np.array(energies) self.eos = EOS(eos_name) + self.eos_name = eos_name self.pressure = pressure self.volumes = np.array([s.volume for s in structures]) self.iv0 = np.argmin(energies) + self.lattice_a = np.array([s.lattice.abc[0] for s in structures]) + self.lattice_b = np.array([s.lattice.abc[1] for s in structures]) + self.lattice_c = np.array([s.lattice.abc[2] for s in structures]) + + self.angles_alpha = np.array([s.lattice.angles[0] for s in structures]) + self.angles_beta = np.array([s.lattice.angles[1] for s in structures]) + self.angles_gama = np.array([s.lattice.angles[2] for s in structures]) def fit_energies(self, tstart=0, tstop=800, num=100): """ @@ -92,8 +100,9 @@ def fit_energies(self, tstart=0, tstop=800, num=100): # 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]) + F2D= np.array([fit.b0 for fit in fits]) /min_volumes - return dict2namedtuple(tot_en=tot_en, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh) + return dict2namedtuple(tot_en=tot_en, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh , F2D=F2D) @abc.abstractmethod def get_vib_free_energies(self, tstart=0, tstop=800, num=100): @@ -146,6 +155,7 @@ def set_eos(self, eos_name: str) -> None: eos_name: string indicating the expression used to fit the energies. See pymatgen.analysis.eos.EOS. """ self.eos = EOS(eos_name) + self.eos_name = eos_name @add_fig_kwargs def plot_energies(self, tstart=0, tstop=800, num=10, ax=None, **kwargs) -> Figure: @@ -173,12 +183,12 @@ def plot_energies(self, tstart=0, tstop=800, num=10, ax=None, **kwargs) -> Figur 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)') + ax.set_ylabel('F (eV)') #ax.grid(True) return fig - def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100) -> Function1D: + def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, tref=None, method=None) -> Function1D: """ 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. @@ -186,25 +196,60 @@ def get_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100) -> Function1 Args: tstart: The starting value (in Kelvin) of the temperature mesh. tstop: The end value (in Kelvin) of the mesh. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + num: int, optional Number of samples to generate. Default is 100. Returns: |Function1D| """ f = self.fit_energies(tstart, tstop, num) + if tref is not None: + f0 = self.fit_energies(tref, tref, 1) + eos_list = [ 'taylor', 'murnaghan', 'birch', 'birch_murnaghan','pourier_tarantola', 'vinet', 'antonschmidt'] dt = f.temp[1] - f.temp[0] - alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f.min_vol[1:-1] - + if (method=="finite_difference"): + alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f.min_vol[1:-1] + else : + if (self.eos_name in eos_list) : + thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) + entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro + df_t = np.zeros((num,self.nvols)) + df_t = - entropy + param = np.zeros((num,4)) + param2 = np.zeros((num,3)) + d2f_t_v = np.zeros(num) + gamma = np.zeros(num) + + for j in range (1,num-1): + param[j]=np.polyfit(self.volumes,df_t[j] , 3) + param2[j] = np.array([3*param[j][0],2*param[j][1],param[j][2]]) + + p = np.poly1d(param2[j]) + d2f_t_v[j]= p(f.min_vol[j]) + + if tref is None: + alpha= - 1/f.min_vol[1:-1] *d2f_t_v[1:-1] / f.F2D[1:-1] + else : + alpha= - 1/f0.min_vol * d2f_t_v[1:-1] / f.F2D[1:-1] + else : + if tref is None: + alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f.min_vol[1:-1] + else : + alpha = (f.min_vol[2:] - f.min_vol[:-2]) / (2 * dt) / f0.min_vol 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) -> Figure: + def plot_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, tref=None, ax=None, **kwargs) -> Figure: """ 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) num: int, optional Number of samples to generate. Default is 100. ax: |matplotlib-Axes| or None if a new figure should be created. @@ -218,7 +263,7 @@ def plot_thermal_expansion_coeff(self, tstart=0, tstop=800, num=100, ax=None, ** if "color" not in kwargs: kwargs["color"] = "b" - alpha = self.get_thermal_expansion_coeff(tstart, tstop, num) + alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) ax.plot(alpha.mesh, alpha.values, **kwargs) ax.set_xlabel(r'T (K)') @@ -260,6 +305,183 @@ def plot_vol_vs_t(self, tstart=0, tstop=800, num=100, ax=None, **kwargs) -> Figu return fig + def get_abc(self, tstart=0, tstop=800 , num=100): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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| + """ + f = self.fit_energies(tstart, tstop, num) + param=np.polyfit(self.volumes, self.lattice_a , 3) + param0=[3*param[0],2*param[1],param[2]] + pa = np.poly1d(param) + dpa=np.poly1d(param0) + aa_qha=pa(f.min_vol) + daa_dv_qha=dpa(f.min_vol) + param=np.polyfit(self.volumes, self.lattice_b , 3) + param0=[3*param[0],2*param[1],param[2]] + pb = np.poly1d(param) + dpb = np.poly1d(param0) + bb_qha=pb(f.min_vol) + dbb_dv_qha=dpb(f.min_vol) + param=np.polyfit(self.volumes, self.lattice_c , 3) + param0=[3*param[0],2*param[1],param[2]] + pc = np.poly1d(param) + dpc = np.poly1d(param0) + cc_qha=pc(f.min_vol) + dcc_dv_qha=dpc(f.min_vol) + + return aa_qha,bb_qha,cc_qha, daa_dv_qha,dbb_dv_qha,dcc_dv_qha + + def get_angles(self, tstart=0, tstop=800 , num=100): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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| + """ + f = self.fit_energies(tstart, tstop, num) + param=np.polyfit(self.volumes, self.angles_alpha, 3) + pa = np.poly1d(param) + alpha=pa(f.min_vol) + param=np.polyfit(self.volumes, self.angles_beta , 3) + pb = np.poly1d(param) + beta=pb(f.min_vol) + param=np.polyfit(self.volumes, self.angles_gama , 3) + pc = np.poly1d(param) + gamma=pc(f.min_vol) + + return alpha,beta,gamma + + @add_fig_kwargs + def plot_thermal_expansion_coeff_abc(self, tstart=0, tstop=800, num=100, tref=None, ax=None, **kwargs) -> Figure: + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + + tmesh = np.linspace(tstart, tstop, num) + + alpha_v = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + aa,bb,cc, daa_dv,dbb_dv,dcc_dv = self.get_abc(tstart, tstop, num) + + if tref is None: + f = self.fit_energies(tstart, tstop, num) + dv_dt=alpha_v*f.min_vol[1:-1] + alpha_qha_a = dv_dt*daa_dv[1:-1]/ aa[1:-1] + alpha_qha_b = dv_dt*dbb_dv[1:-1]/ bb[1:-1] + alpha_qha_c = dv_dt*dcc_dv[1:-1]/ cc[1:-1] + else: + f0 = self.fit_energies(tref, tref, num) + dv_dt=alpha_v*f0.min_vol[1:-1] + aa_tref,bb_tref,cc_tref , daa_dv_tref,dbb_dv_tref,dcc_dv_tref = self.get_abc(tref, tref, 1) + alpha_qha_a = dv_dt*daa_dv[1:-1]/ aa_tref + alpha_qha_b = dv_dt*dbb_dv[1:-1]/ bb_tref + alpha_qha_c = dv_dt*dcc_dv[1:-1]/ cc_tref + + ax.plot(tmesh[1:-1] ,alpha_qha_a , color='r',lw=2 , **kwargs) + ax.plot(tmesh[1:-1] ,alpha_qha_b , color='b', lw=2 ) + ax.plot(tmesh[1:-1] ,alpha_qha_c , color='m', lw=2 ) + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend([r"$\alpha_a$",r"$\alpha_b$",r"$\alpha_c$"]) + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + + @add_fig_kwargs + def plot_abc_vs_t(self, tstart=0 , tstop=800, num=100, tref=None, ax=None, **kwargs) -> Figure: + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + aa,bb,cc, daa_dv,dbb_dv,dcc_dv = self.get_abc(tstart, tstop, num) + + ax.plot(tmesh ,aa , color='r',lw=2, **kwargs ) + ax.plot(tmesh ,bb , color='b', lw=2 ) + ax.plot(tmesh ,cc , color='m', lw=2 ) + ax.set_xlabel(r'T (K)') + ax.legend(["a(V(T))","b(V(T))","c(V(T))"]) + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + + @add_fig_kwargs + def plot_angle_vs_t(self, tstart=0, tstop=800, num=100, tref=None, ax=None, **kwargs)-> Figure: + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + alpha,beta,gamma = self.get_angles(tstart, tstop, num) + + ax.plot(tmesh ,alpha , color='r',lw=2, **kwargs ) + ax.plot(tmesh ,beta , color='b', lw=2 ) + ax.plot(tmesh ,gamma , color='m', lw=2 ) + ax.set_xlabel(r'T (K)') + ax.legend([r"$\alpha$(V(T))",r"$\beta$(V(T))",r"$\gamma$(V(T))"]) + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig @add_fig_kwargs def plot_phbs(self, phbands, temperatures=None, t_max=1000, colormap="plasma", **kwargs) -> Figure: """ diff --git a/abipy/dfpt/qha_aproximation.py b/abipy/dfpt/qha_aproximation.py new file mode 100644 index 000000000..e528cd2c3 --- /dev/null +++ b/abipy/dfpt/qha_aproximation.py @@ -0,0 +1,1971 @@ +# coding: utf-8 + +import os +import abc +import numpy as np +import abipy.core.abinit_units as abu + +from scipy.interpolate import UnivariateSpline +from monty.collections import dict2namedtuple +from monty.functools import lazy_property +from pymatgen.analysis.eos import EOS +from abipy.core.func1d import Function1D +from abipy.tools.plotting import add_fig_kwargs, get_ax_fig_plt, get_axarray_fig_plt +from abipy.electrons.gsr import GsrFile +from abipy.dfpt.ddb import DdbFile +from abipy.dfpt.phonons import PhononBandsPlotter, PhononDos, PhdosFile +from abipy.dfpt.gruneisen import GrunsNcFile + + +class QHA_App(metaclass=abc.ABCMeta): + """ + Class for the approximations on QHA analysis. + Provides some basic methods and plotting utils. + These can be used to obtain other quantities and plots. + Does not include electronic entropic contributions for metals. + """ + + def __init__(self, structures,structures_from_phdos,index_list, doses,energies, pressures, 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.eos_name = eos_name + self.pressure = pressure + self.pressures = np.array(pressures) + + self.volumes = np.array([s.volume for s in structures]) + self.iv0 = np.argmin(energies) + self.lattice_a = np.array([s.lattice.abc[0] for s in structures]) + self.lattice_b = np.array([s.lattice.abc[1] for s in structures]) + self.lattice_c = np.array([s.lattice.abc[2] for s in structures]) + + self.angles_alpha = np.array([s.lattice.angles[0] for s in structures]) + self.angles_beta = np.array([s.lattice.angles[1] for s in structures]) + self.angles_gama = np.array([s.lattice.angles[2] for s in structures]) + + self.doses = doses + self.structures_from_phdos = np.array(structures_from_phdos) + self.volumes_from_phdos = np.array([s.volume for s in structures_from_phdos]) + self.energies_pdos=self.energies[index_list] + self.index_list = index_list + if (len(self.index_list)==5): + self.iv0_vib=1 + self.iv1_vib=3 + self.V0_vib=self.volumes_from_phdos[2] + elif (len(self.index_list)==3): + self.iv0_vib=0 + self.iv1_vib=2 + self.V0_vib=self.volumes_from_phdos[1] + else : + self.iv0_vib=0 + self.iv1_vib=1 + self.V0_vib=0.5*(self.volumes_from_phdos[1]+self.volumes_from_phdos[0]) + if abs(self.volumes_from_phdos[self.iv0_vib]+self.volumes_from_phdos[self.iv1_vib]-2*self.volumes[self.iv0])<1e-3 : + self.scale_points="S" # Symmetry + else: + self.scale_points="D" # Displaced + +#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + def fit_tot_energies(self, tstart=0, tstop=1000, num=101,tot_energies="energies" ,volumes="volumes"): + """ + 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 + """ + # Generate a mesh of temperatures + tmesh = np.linspace(tstart, tstop, num) + + # List of fit objects, one for each temperature + fits = [self.eos.fit(volumes, e) for e in tot_energies.T] + + # Extract parameters from the fit objects + v0 = np.array([fit.v0 for fit in fits]) + e0 = np.array([fit.e0 for fit in fits]) + b0 = np.array([fit.b0 for fit in fits]) + b1 = np.array([fit.b1 for fit in fits]) + + # Minimum volumes and energies + min_volumes = np.array([fit.v0 for fit in fits]) + min_energies = np.array([fit.e0 for fit in fits]) + + v = min_volumes + eta = (v / v0) ** (1.0 / 3.0) + + # Calculate the second derivative of free energy + F2D = ( b0 / v0 * (-2.0 * (eta - 1) * eta ** -5.0 + (1 - 3.0 / 2.0 * (b1 - 1) * (eta - 1)) * eta ** -4.0) + * np.exp(-3.0 * (b1 - 1.0) * (v ** (1.0 / 3.0) / v0 ** (1 / 3.0) - 1.0) / 2.0)) + + return dict2namedtuple(tot_en=tot_energies, fits=fits, min_en=min_energies, min_vol=min_volumes, temp=tmesh , F2D=F2D) + + def second_derivative_energy_v(self, vol="vol"): + """ + Performs a fit of the energies as a function of the volume at different temperatures. + + Args: + vol: The volume at which to evaluate the second derivative of the energy. + + Returns: + E2D_V: The second derivative of the energy with respect to volume. + """ + # Initialize variables for temperature range (not used in the current function) + tstart = 0 + tstop = 0 + num = 1 + + tot_en = self.energies[np.newaxis, :].T + fits = [self.eos.fit(self.volumes, e) for e in tot_en.T] + + # Extract parameters from the fit objects + v0 = np.array([fit.v0 for fit in fits]) + e0 = np.array([fit.e0 for fit in fits]) + b0 = np.array([fit.b0 for fit in fits]) + b1 = np.array([fit.b1 for fit in fits]) + + v = vol + eta = (v / v0) ** (1.0 / 3.0) + E2D_V = ( b0 / v0 * (-2.0 * (eta - 1) * eta ** -5.0 + (1 - 3.0 / 2.0 * (b1 - 1) * (eta - 1)) * eta ** -4.0) * + np.exp(-3.0 * (b1 - 1.0) * (v ** (1.0 / 3.0) / v0 ** (1.0 / 3.0) - 1.0) / 2.0)) + + return E2D_V +#============================================================================================= + def vol_E2Vib1(self, tstart=0, tstop=1000, num=101): + """ + Compute the volume as a function of temperature using the E2Vib1 method with the Vinet equation of state. + + Args: + tstart: The starting value (in Kelvin) of the temperature mesh. + tstop: The end value (in Kelvin) of the mesh. + num: Number of samples to generate. Default is 101. + + Returns: + vol: The calculated volumes as a function of temperature. + fits: The list of fit objects for the energies as a function of volume. + """ + # Generate temperature mesh + tmesh = np.linspace(tstart, tstop, num) + + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + + vol = np.zeros(num) + dfe_dV1 = np.zeros(num) + volumes0 = self.volumes_from_phdos + volumes = self.volumes + iv0 = self.iv0_vib + iv1 = self.iv1_vib + dV = volumes0[iv1] - volumes0[iv0] + V0 = volumes[self.iv0] + E2D = self.second_derivative_energy_v(V0) + + # Calculate derivative of free energy with respect to volume and updated volumes + for i, e in enumerate(ph_energies.T): + dfe_dV1[i] = (e[iv1] - e[iv0]) / dV + vol[i] = V0 - dfe_dV1[i] / E2D + + # Calculate total energies + tot_en = self.energies[self.iv0] + 0.5*(volumes[np.newaxis, :].T - V0)**2*E2D +(volumes[np.newaxis, :].T - V0)*dfe_dV1 + + # Fit the energies as a function of volume + fits = [self.eos.fit(volumes, e) for e in tot_en.T] + + return vol, fits + +#============================================================================================= + def vol_Einf_Vib1(self, tstart=0, tstop=1000, num=101): + """ + Compute the volume as a function of temperature using the EinfVib1 method with the Vinet equation of state. + + Args: + tstart: The starting value (in Kelvin) of the temperature mesh. + tstop: The end value (in Kelvin) of the mesh. + num: Number of samples to generate. Default is 101. + + Returns: + vol: The calculated volumes as a function of temperature. + fits: The list of fit objects for the energies as a function of volume. + """ + # Generate temperature mesh + tmesh = np.linspace(tstart, tstop, num) + + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + + vol = np.zeros(num) + dfe_dV1 = np.zeros(num) + volumes0 = self.volumes_from_phdos + volumes = self.volumes + iv0 = self.iv0_vib + iv1 = self.iv1_vib + V0 = self.V0_vib + + dV = volumes0[iv1] - volumes0[iv0] + + # Calculate derivative of free energy with respect to volume + for i, e in enumerate(ph_energies.T): + dfe_dV1[i] = (e[iv1] - e[iv0]) / dV + + # Calculate total energies + tot_en = self.energies[np.newaxis, :].T + (volumes[np.newaxis, :].T - V0) * dfe_dV1 + + # Fit the energies as a function of volume + fits = [self.eos.fit(volumes, e) for e in tot_en.T] + + # Extract minimum volumes from the fit objects + vol = np.array([fit.v0 for fit in fits]) + + return vol, fits + +#============================================================================================= + def vol_Einf_Vib2(self, tstart=0, tstop=1000, num=101): + """ + Compute the volume as a function of temperature using the EinfVib2 method with the Vinet equation of state. + + Args: + tstart: The starting value (in Kelvin) of the temperature mesh. + tstop: The end value (in Kelvin) of the mesh. + num: Number of samples to generate. Default is 101. + + Returns: + vol: The calculated volumes as a function of temperature. + fits: The list of fit objects for the energies as a function of volume. + """ + # Generate temperature mesh + tmesh = np.linspace(tstart, tstop, num) + + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + + vol = np.zeros(num) + dfe_dV1 = np.zeros(num) + dfe_dV2 = np.zeros(num) + fe_V0 = np.zeros(num) + + # Determine index for volume calculations + iv0 = 2 if len(self.index_list) == 5 else 1 + + dV = self.volumes_from_phdos[iv0] - self.volumes_from_phdos[iv0 - 1] + + # Compute derivatives of free energy with respect to volume + for i, e in enumerate(ph_energies.T): + dfe_dV1[i] = (e[iv0 + 1] - e[iv0 - 1]) / (self.volumes_from_phdos[iv0 + 1] - self.volumes_from_phdos[iv0 - 1]) + dfe_dV2[i] = (e[iv0 + 1] - 2.0 * e[iv0] + e[iv0 - 1]) / (self.volumes_from_phdos[iv0 + 1] - self.volumes_from_phdos[iv0])**2 + fe_V0[i] = e[iv0] + + # Reference volume + V0 = self.volumes_from_phdos[iv0] + + # Calculate total energies + tot_en = ( self.energies[np.newaxis, :].T +fe_V0 + +(self.volumes[np.newaxis, :].T - V0) * dfe_dV1 + 0.5 * (self.volumes[np.newaxis, :].T - V0)**2 * dfe_dV2) + + # Fit the energies as a function of volume + fits = [self.eos.fit(self.volumes, e) for e in tot_en.T] + + # Extract minimum volumes from the fit objects + vol = np.array([fit.v0 for fit in fits]) + + return vol, fits + +#================================================================================================================ + + def vol_Einf_Vib4(self, tstart=0, tstop=1000, num=101): + """ + Compute the volume as a function of temperature using the EinfVib4 method with the Vinet equation of state. + + Args: + tstart: The starting value (in Kelvin) of the temperature mesh. + tstop: The end value (in Kelvin) of the mesh. + num: Number of samples to generate. Default is 101. + + Returns: + vol: The calculated volumes as a function of temperature. + fits: The list of fit objects for the energies as a function of volume. + """ + + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + energies = self.energies + volumes0= self.volumes_from_phdos + volumes= self.volumes + vol = np.zeros( num) + dfe_dV1= np.zeros( num) + dfe_dV2= np.zeros( num) + dfe_dV3= np.zeros( num) + dfe_dV4= np.zeros( num) + fe_V0= np.zeros( num) + iv0=2 + dV=volumes0[2]-volumes0[1] + + for i,e in enumerate(ph_energies.T): + dfe_dV1[i]=(-e[iv0+2]+ 8*e[iv0+1]-8*e[iv0-1]+e[iv0-2])/(12*dV) + dfe_dV2[i]=(-e[iv0+2]+16*e[iv0+1]-30*e[iv0]+16*e[iv0-1]-e[iv0-2])/(12*dV**2) + dfe_dV3[i]=(e[iv0+2]-2*e[iv0+1]+2*e[iv0-1]-e[iv0-2])/(2*dV**3) + dfe_dV4[i]=(e[iv0+2]-4*e[iv0+1]+6*e[iv0]-4*e[iv0-1]+e[iv0-2])/(dV**4) + + fe_V0[i]= e[iv0] + V0=volumes0[iv0] + + tot_en = (( volumes[np.newaxis, :].T -V0) * dfe_dV1 + 0.5* ( volumes[np.newaxis, :].T -V0)**2*(dfe_dV2) + +( volumes[np.newaxis, :].T -V0)**3 *dfe_dV3/6.0 + ( volumes[np.newaxis, :].T -V0)**4*(dfe_dV4/24.0) + + fe_V0[:] +energies[np.newaxis, :].T ) + + fits = [self.eos.fit(volumes, e) for e in tot_en.T] + vol = np.array([fit.v0 for fit in fits]) + + return vol , fits +#********************************************************************************************* + @property + def nvols(self): + """Number of volumes""" + return len(self.volumes_from_phdos) + + def set_eos(self, eos_name): + """ + Set the EOS model 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) + self.eos_name = eos_name + if (eos_name!= "vinet"): + raise RuntimeError("This approximation method is only developed for the Vinet equation of state.") + + + + @add_fig_kwargs + def plot_energies(self, tstart=0, tstop=1000 ,num=1, ax=None, **kwargs): + """ + Plots the BO energy as a function of volume + + 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| + """ + + tmesh = np.linspace(tstart, tstop, num) + + f = self.fit_tot_energies(0, 0, 1 ,self.energies[np.newaxis, :].T,self.volumes) + + 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='s', s=10) + 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', linestyle='dashed' , lw=1, marker='o', ms=5) + + ax.set_xlabel(r'V (${\AA}^3$)') + ax.set_ylabel('E (eV)') + return fig + + @add_fig_kwargs + def plot_vol_vs_t(self, tstart=0, tstop=1000, num=101, ax=None, **kwargs): + """ + Plot the volume as a function of temperature using various methods. + + 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 101. + ax: Matplotlib Axes object or None. If None, a new figure will be created. + **kwargs: Additional keyword arguments to pass to plotting functions. + + Returns: + fig: The Matplotlib Figure object. + """ + # Get or create the matplotlib Axes and Figure + ax, fig, plt = get_ax_fig_plt(ax) + + # Generate temperature mesh + tmesh = np.linspace(tstart, tstop, num) + + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + + # Initialize data storage + iv0 = self.iv0_vib + iv1 = self.iv1_vib + volumes = self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + + # Method 1: E2Vib1 + if self.scale_points=="S": + vol, _ = self.vol_E2Vib1(tstart=tstart, tstop=tstop, num=num) + ax.plot(tmesh, vol, color='b', lw=2, label="E2Vib1") + data_to_save = np.column_stack((data_to_save, vol)) + columns.append('E2vib1') + + # Method 2: Einf_Vib1 + if len(self.index_list) >= 2: + vol2, _ = self.vol_Einf_Vib1(tstart=tstart, tstop=tstop, num=num) + ax.plot(tmesh, vol2, color='gold', lw=2, label=r"$E_\infty$ Vib1") + data_to_save = np.column_stack((data_to_save, vol2)) + columns.append('Einfvib1') + + # Method 3: Einf_Vib2 + if len(self.index_list) >= 3: + vol3, _ = self.vol_Einf_Vib2(tstart=tstart, tstop=tstop, num=num) + ax.plot(tmesh, vol3, color='m', lw=2, label=r"$E_\infty$ Vib2") + data_to_save = np.column_stack((data_to_save, vol3)) + columns.append('Einfvib2') + + # Method 4: Einf_Vib4 and QHA + if len(self.index_list) == 5: + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num, tot_en, self.volumes_from_phdos) + vol4, _ = self.vol_Einf_Vib4(tstart=tstart, tstop=tstop, num=num) + ax.plot(tmesh, vol4, color='c', lw=2, label=r"$E_\infty$ Vib4") + ax.plot(tmesh, f0.min_vol, color='k', linestyle='dashed', lw=1.5, label="QHA") + data_to_save = np.column_stack((data_to_save, vol4, f0.min_vol)) + columns.append('Einfvib4') + columns.append('QHA') + + # Plot V0 + ax.plot(0, self.volumes[self.iv0], color='g', lw=0, marker='o', ms=10, label="V0") + + # Set labels and limits + ax.set_xlabel('T (K)') + ax.set_ylabel(r'V (${\AA}^3$)') + ax.set_xlim(tstart, tstop) + ax.grid(True) + ax.legend() + + return fig + +################################################################################################### + def get_thermal_expansion_coeff(self, tstart=0, tstop=1000, num=101, tref=None): + """ + Calculates the thermal expansion coefficient 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + num: Number of samples to generate. Default is 101. + + Returns: + Function1D: The thermal expansion coefficient as a function of temperature. + """ + # Get phonon free energies + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f = self.fit_tot_energies(tstart, tstop, num, tot_en, self.volumes_from_phdos) + + if tref != None: + ph_energies2 = self.get_vib_free_energies(tref, tref, 1) + tot_en2 = self.energies_pdos[np.newaxis, :].T + ph_energies2 + f0 = self.fit_tot_energies(tref, tref, 1, tot_en2, self.volumes_from_phdos) + + dt = f.temp[1] - f.temp[0] + + # Get thermodynamic properties + thermo = self.get_thermodynamic_properties(tstart, tstop, num) + entropy = thermo.entropy.T + df_t = -entropy + + param = np.zeros((num, 4)) + param2 = np.zeros((num, 3)) + d2f_t_v = np.zeros(num) + + for j in range(num): + param[j] = np.polyfit(self.volumes_from_phdos, df_t[j], 3) + param2[j] = np.array([3 * param[j][0], 2 * param[j][1], param[j][2]]) + p = np.poly1d(param2[j]) + d2f_t_v[j] = p(f.min_vol[j]) + + F2D = f.F2D + if tref == None: + alpha = -1 / f.min_vol * d2f_t_v / F2D + else: + alpha = -1 / f0.min_vol * d2f_t_v / F2D + + return Function1D(f.temp, alpha) + + @add_fig_kwargs + def plot_thermal_expansion_coeff(self, tstart=0, tstop=1000, num=101, tref=None, 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) + + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + tmesh = np.linspace(tstart, tstop, num) + thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) + entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro + df_t = np.zeros((num,self.nvols)) + df_t = - entropy + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + iv0=self.iv0_vib + iv1=self.iv1_vib + dV=volumes[iv0+1]-volumes[iv0] + + if self.scale_points=="S": + vol,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + E2D = self.second_derivative_energy_v(self.volumes[self.iv0]) + #f = self.fit_tot_energies(0, 0, 1 ,self.energies[np.newaxis, :].T,self.volumes) + #E2D = f.F2D + if (tref==None): + alpha_1 = - 1/vol[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D + else: + vol_ref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) + b0 = np.array([fit.b0 for fit in fits]) + print("B (E2vib1) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) + alpha_1 = - 1/vol_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D + ax.plot(tmesh, alpha_1,color='b', lw=2, label="E2Vib1") + data_to_save = np.column_stack((data_to_save,alpha_1)) + columns.append( 'E2vib1') + + if (len(self.index_list)>=2): + vol2 ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + E2D_V = self.second_derivative_energy_v(vol2) + if (tref==None): + alpha_2 = - 1/vol2[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] + else: + vol2_ref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) + b0 = np.array([fit.b0 for fit in fits]) + print("B (Einfvib1) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) + alpha_2 = - 1/vol2_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] + ax.plot(tmesh, alpha_2,color='gold', lw=2 , label=r"$E_\infty Vib1$") + data_to_save = np.column_stack((data_to_save,alpha_2)) + columns.append( 'Einfvib1') + + if (len(self.index_list)>=3): + vol3,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + E2D_V = self.second_derivative_energy_v(vol3) + dfe_dV2= np.zeros( num) + for i,e in enumerate(ph_energies.T): + dfe_dV2[i]=(e[iv0+2]-2.0*e[iv0+1]+e[iv0])/(dV)**2 + + ds_dv = (df_t[:,iv0+2]-df_t[:,iv0])/(2*dV) + ds_dv = ds_dv+ (df_t[:,iv0+2]-2*df_t[:,iv0+1]+df_t[:,iv0])/dV**2 * (vol3[:]-volumes[iv0+1]) + if (tref==None): + alpha_3 = - 1/vol3[:] * ds_dv / (E2D_V[:]+dfe_dV2[:]) + else: + vol3_ref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) + b0 = np.array([fit.b0 for fit in fits]) + print("B (Einfvib2) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) + alpha_3 = - 1/vol3_ref * ds_dv / (E2D_V[:]+dfe_dV2[:]) + ax.plot(tmesh, alpha_3,color='m', lw=2 , label=r"$E_\infty Vib2$") + data_to_save = np.column_stack((data_to_save,alpha_3)) + columns.append( 'Einfvib2') + + if (len(self.index_list)==5): + alpha_qha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + vol4,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + E2D_V = self.second_derivative_energy_v(vol4) + + d2fe_dV2= np.zeros( num) + d3fe_dV3= np.zeros( num) + d4fe_dV4= np.zeros( num) + for i,e in enumerate(ph_energies.T): + d2fe_dV2[i]=(-e[4]+16*e[3]-30*e[2]+16*e[1]-e[0])/(12*dV**2) + d3fe_dV3[i]=(e[4]-2*e[3]+2*e[1]-e[0])/(2*dV**3) + d4fe_dV4[i]=(e[4]-4*e[3]+6*e[2]-4*e[1]+e[0])/(dV**4) + + ds_dv =(-df_t[:,4]+ 8*df_t[:,3]-8*df_t[:,1]+df_t[:,0])/(12*dV) + ds_dv = ds_dv+ (-df_t[:,4]+16*df_t[:,3]-30*df_t[:,2]+16*df_t[:,1]-df_t[:,0])/(12*dV**2) * (vol4[:]-volumes[2]) + ds_dv = ds_dv+ 1.0/2.0*(df_t[:,4]-2*df_t[:,3]+2*df_t[:,1]-df_t[:,0])/(2*dV**3) * (vol4[:]-volumes[2])**2 + ds_dv = ds_dv+ 1.0/6.0* (df_t[:,4]-4*df_t[:,3]+6*df_t[:,2]-4*df_t[:,1]+df_t[:,0])/(dV**4)* (vol4[:]-volumes[2])**3 + D2F=E2D_V[:]+d2fe_dV2[:]+ (vol4[:]-volumes[2])*d3fe_dV3[:]+0.5*(vol4[:]-volumes[2])**2*d4fe_dV4[:] + if (tref==None): + alpha_4 = - 1/vol4[:] * ds_dv / D2F + else: + vol4_ref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) + b0 = np.array([fit.b0 for fit in fits]) + print("B (Einfvib4) @ ",tref," K =",b0*160.21766208 ,"(GPa)" ) + alpha_4 = - 1/vol4_ref * ds_dv / D2F + + ax.plot(tmesh, alpha_4,color='c',linewidth=2 , label=r"$E_\infty Vib4$") + ax.plot(alpha_qha.mesh, alpha_qha.values, color='k',linestyle='dashed', lw=1.5 ,label="QHA") + data_to_save = np.column_stack((data_to_save,alpha_4,alpha_qha.values)) + columns.append( 'Einfvib4') + columns.append( 'QHA') + + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.grid(True) + ax.legend() + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + def get_abc(self, tstart=0, tstop=1000, num=101,volumes="volumes"): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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| + """ + param = np.zeros((num,4)) + param=np.polyfit(self.volumes, self.lattice_a , 3) + pa = np.poly1d(param) + aa_qha=pa(volumes) + param=np.polyfit(self.volumes, self.lattice_b , 3) + pb = np.poly1d(param) + bb_qha=pb(volumes) + param=np.polyfit(self.volumes, self.lattice_c , 3) + pc = np.poly1d(param) + cc_qha=pc(volumes) + + return aa_qha,bb_qha,cc_qha + + def get_angles(self, tstart=0, tstop=1000, num=101,volumes="volumes"): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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| + """ + param = np.zeros((num,4)) + param=np.polyfit(self.volumes, self.angles_alpha, 3) + pa = np.poly1d(param) + gamma=pa(volumes) + param=np.polyfit(self.volumes, self.angles_beta , 3) + pb = np.poly1d(param) + beta=pb(volumes) + param=np.polyfit(self.volumes, self.angles_gama , 3) + pc = np.poly1d(param) + alpha=pc(volumes) + + return alpha,beta,gamma + +################################################################################################### + @add_fig_kwargs + def plot_thermal_expansion_coeff_abc(self, tstart=0, tstop=1000, num=101, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + data_to_save = tmesh[1:-1] + columns = ['#Tmesh'] + + if self.scale_points=="S": + vol2 ,fits= self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol2_tref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) + + if (len(self.index_list)==2): + vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) + method =r"$ (E_\infty Vib4)$" + vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + dt= tmesh[1] - tmesh[0] + + aa,bb,cc = self.get_abc(tstart, tstop, num,vol) + if (tref!=None): + aa_tref,bb_tref,cc_tref = self.get_abc(tref, tref, 1,vol_tref) + + alpha_a = np.zeros( num-2) + alpha_b = np.zeros( num-2) + alpha_c = np.zeros( num-2) + if (tref==None): + alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa[1:-1] + alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb[1:-1] + alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] + else: + alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa_tref + alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb_tref + alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref + + ax.plot(tmesh[1:-1] ,alpha_a , color='r', lw=2,label = r"$\alpha_a$"+method, **kwargs) + ax.plot(tmesh[1:-1] ,alpha_b , color='b', lw=2,label = r"$\alpha_b$"+method) + ax.plot(tmesh[1:-1] ,alpha_c , color='m', lw=2,label = r"$\alpha_c$"+method) + + method_header=method+" (alpha_a,alpha_b,alpha_c) |" + data_to_save = np.column_stack((data_to_save,alpha_a,alpha_b,alpha_c)) + columns.append( method_header) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) + if (tref!=None): + aa2_tref,bb2_tref,cc2_tref = self.get_abc(tref, tref, 1,vol2_tref) + + alpha2_a = np.zeros( num-2) + alpha2_b = np.zeros( num-2) + alpha2_c = np.zeros( num-2) + if (tref==None): + alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2[1:-1] + alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2[1:-1] + alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] + else: + alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2_tref + alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2_tref + alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref + + ax.plot(tmesh[1:-1] ,alpha2_a , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_a$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_b , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_b$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_c , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_c$"" (E2vib1)") + data_to_save = np.column_stack((data_to_save,alpha2_a,alpha2_b,alpha2_c)) + columns.append( 'E2vib1 (alpha_a,alpha_b,alpha_c) ') + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_thermal_expansion_coeff_angles(self, tstart=0, tstop=1000, num=101, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + data_to_save = tmesh[1:-1] + columns = ['#Tmesh'] + + if self.scale_points=="S": + vol2 ,fits= self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol2_tref,fits = self.vol_E2Vib1(num=1,tstop=tref,tstart=tref) + + if (len(self.index_list)==2): + vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib1(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib2(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) + method =r"$ (E_\infty Vib4)$" + vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref,fits = self.vol_Einf_Vib4(num=1,tstop=tref,tstart=tref) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + dt= tmesh[1] - tmesh[0] + + alpha,beta,cc = self.get_angles(tstart, tstop, num,vol) + if (tref!=None): + alpha_tref,beta_tref,cc_tref = self.get_angles(tref, tref, 1,vol_tref) + + alpha_alpha = np.zeros( num-2) + alpha_beta = np.zeros( num-2) + alpha_gamma = np.zeros( num-2) + if (tref==None): + alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha[1:-1] + alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta[1:-1] + alpha_gamma = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] + else: + alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha_tref + alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta_tref + alpha_gamma = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref + + ax.plot(tmesh[1:-1] ,alpha_alpha , color='r', lw=2,label = r"$\alpha_alpha$"+method, **kwargs) + ax.plot(tmesh[1:-1] ,alpha_beta , color='b', lw=2,label = r"$\alpha_beta$"+method) + ax.plot(tmesh[1:-1] ,alpha_gamma , color='m', lw=2,label = r"$\alpha_gamma$"+method) + + method_header=method+" (alpha_alpha,alpha_beta,alpha_gamma) |" + data_to_save = np.column_stack((data_to_save,alpha_alpha,alpha_beta,alpha_gamma)) + columns.append( method_header) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + alpha2,beta2,cc2 = self.get_angles(tstart, tstop, num,vol2) + if (tref!=None): + alpha2_tref,beta2_tref,cc2_tref = self.get_angles(tref, tref, 1,vol2_tref) + + alpha2_alpha = np.zeros( num-2) + alpha2_beta = np.zeros( num-2) + alpha2_gamma = np.zeros( num-2) + if (tref==None): + alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2[1:-1] + alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2[1:-1] + alpha2_gamma = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] + else: + alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2_tref + alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2_tref + alpha2_gamma = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref + + ax.plot(tmesh[1:-1] ,alpha2_alpha , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_alpha$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_beta , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_beta$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_gamma , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_gamma$"" (E2vib1)") + data_to_save = np.column_stack((data_to_save,alpha2_alpha,alpha2_beta,alpha2_gamma)) + columns.append( 'E2vib1 (alpha_alpha,alpha_beta,alpha_gamma) ') + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + + @add_fig_kwargs + def plot_abc_vs_t(self, tstart=0, tstop=1000, num=101, lattice=None, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) + data_to_save = np.column_stack((data_to_save,aa2,bb2,cc2)) + columns.append( 'E2vib1 (a,b,c) | ') + + if (len(self.index_list)==2): + vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) + method =r"$ (E_\infty Vib4)$" + vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + aa,bb,cc = self.get_abc(tstart, tstop, num,vol) + + method_header=method+" (a,b,c) |" + data_to_save = np.column_stack((data_to_save,aa,bb,cc)) + columns.append( method_header) + + if (lattice==None or lattice=="a"): + ax.plot(tmesh ,aa , color='r', lw=2,label = r"$a(V(T))$"+method, **kwargs ) + if (lattice==None or lattice=="b"): + ax.plot(tmesh ,bb , color='b', lw=2,label = r"$b(V(T))$"+method ) + if (lattice==None or lattice=="c"): + ax.plot(tmesh ,cc , color='m', lw=2,label = r"$c(V(T))$"+method ) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + if (lattice==None or lattice=="a"): + ax.plot(tmesh ,aa2 , linestyle='dashed' , color='r', lw=2,label = r"$a(V(T))$""E2vib1" ) + if (lattice==None or lattice=="b"): + ax.plot(tmesh ,bb2 , linestyle='dashed' , color='b', lw=2,label = r"$b(V(T))$""E2vib1" ) + if (lattice==None or lattice=="c"): + ax.plot(tmesh ,cc2 , linestyle='dashed' , color='m', lw=2,label = r"$c(V(T))$""E2vib1" ) + + ax.set_xlabel(r'T (K)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_angles_vs_t(self, tstart=0, tstop=1000, num=101, angle=None, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + ax, fig, plt = get_ax_fig_plt(ax) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2,fits = self.vol_E2Vib1(num=num,tstop=tstop,tstart=tstart) + alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) + data_to_save = np.column_stack((data_to_save,alpha2,beta2,gamma2)) + columns.append( 'E2vib1 (alpha,beta,gamma) | ') + + if (len(self.index_list)==2): + vol ,fits = self.vol_Einf_Vib1(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol,fits = self.vol_Einf_Vib2(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_tot_energies(tstart, tstop, num,tot_en , self.volumes_from_phdos) + method =r"$ (E_\infty Vib4)$" + vol,fits = self.vol_Einf_Vib4(num=num,tstop=tstop,tstart=tstart) + alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) + + method_header=method+" (alpha,beta,gamm) |" + data_to_save = np.column_stack((data_to_save,alpha,beta,gamma)) + columns.append( method_header) + + if (angle==None or angle==1): + ax.plot(tmesh ,alpha , color='r', lw=2,label = r"$alpha(V(T))$"+method, **kwargs ) + if (angle==None or angle==2): + ax.plot(tmesh ,beta , color='b', lw=2,label = r"$beta(V(T))$"+method ) + if (angle==None or angle==3): + ax.plot(tmesh ,gamma , color='m', lw=2,label = r"$gamma(V(T))$"+method ) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + if (angle==None or angle==1): + ax.plot(tmesh ,alpha2 , linestyle='dashed' , color='r', lw=2,label = r"$alpha(V(T))$""E2vib1" ) + if (angle==None or angle==2): + ax.plot(tmesh ,beta2 , linestyle='dashed' , color='b', lw=2,label = r"$beta(V(T))$""E2vib1" ) + if (angle==None or angle==3): + ax.plot(tmesh ,gamma2 , linestyle='dashed' , color='m', lw=2,label = r"$gamma(V(T))$""E2vib1" ) + + ax.set_xlabel(r'T (K)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& + def fit_forth(self, tstart=0, tstop=1000, num=1,energy="energy",volumes="volumes"): + """ + 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) + + param = np.zeros((num,5)) + param2 = np.zeros((num,4)) + param3 = np.zeros((num,3)) + min_vol = np.zeros((num)) + min_en = np.zeros((num)) + F2D_V = np.zeros((num)) + for j,e in enumerate(energy.T): + param[j]=np.polyfit(volumes,e , 4) + param2[j]=np.array([4*param[j][0],3*param[j][1],2*param[j][2],param[j][3]]) + param3[j]=np.array([12*param[j][0],6*param[j][1],2*param[j][2]]) + p = np.poly1d(param[j]) + p2 = np.poly1d(param2[j]) + p3 = np.poly1d(param3[j]) + min_vol[j]=self.volumes[self.iv0] + vv=self.volumes[self.iv0] + while p2(min_vol[j])**2 > 1e-16 : + min_vol[j]=min_vol[j]-p2(min_vol[j])*10 + min_en[j]=p(min_vol[j]) + F2D_V[j]=p3(min_vol[j]) + + return dict2namedtuple(min_vol=min_vol, temp=tmesh , min_en=min_en , param=param , F2D_V=F2D_V)#, fits=fits) + + def vol_E2Vib1_forth(self, tstart=0, tstop=1000, num=101): + + volumes0= self.volumes_from_phdos + iv0=self.iv0_vib + iv1=self.iv1_vib + + dV=volumes0[iv1]-volumes0[iv0] + V0=self.volumes[self.iv0] + + energy = self.energies[np.newaxis, :].T + f=self.fit_forth( tstart=0, tstop=0, num=1 ,energy=energy,volumes=self.volumes) + param3 = np.zeros((num,3)) + param3=np.array([12*f.param[0][0],6*f.param[0][1],2*f.param[0][2]]) + p3 = np.poly1d(param3) + E2D = p3(V0) + + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + vol = np.zeros( num) + + for i,e in enumerate(ph_energies.T): + dfe_dV1=(e[iv1]-e[iv0])/dV + vol[i]=V0-dfe_dV1*E2D**-1 + + return vol + + def vol_EinfVib1_forth(self, tstart=0, tstop=1000, num=101): + """ + Plot the volume as a function of the temperature. + Returns: |Vol| + """ + + volumes0= self.volumes_from_phdos + iv0=self.iv0_vib + iv1=self.iv1_vib + V0= self.V0_vib + + dV=volumes0[iv1]-volumes0[iv0] + + energy = self.energies[np.newaxis, :].T + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + vol = np.zeros( num) + + dfe_dV= np.zeros( num) + + for i,e in enumerate(ph_energies.T): + dfe_dV[i]=(e[iv1]-e[iv0])/dV + tot_en = self.energies[np.newaxis, :].T + ( self.volumes[np.newaxis, :].T -V0) * dfe_dV + + f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) + vol=f.min_vol + + return vol + def vol_Einf_Vib2_forth(self, tstart=0, tstop=1000, num=101): + """ + 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: |Vol| + """ + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + energies = self.energies + volumes0= self.volumes_from_phdos + volumes= self.volumes + vol = np.zeros( num) + dfe_dV= np.zeros( num) + d2fe_dV2= np.zeros( num) + fe_V0= np.zeros( num) + if (len(self.index_list)==5): + iv0=2 + else : + iv0=1 + dV=volumes0[iv0]-volumes0[iv0-1] + V0=volumes0[iv0] + for i,e in enumerate(ph_energies.T): + dfe_dV[i]=(e[iv0+1]-e[iv0-1])/(2*dV) + d2fe_dV2[i]=(e[iv0+1]-2.0*e[iv0]+e[iv0-1])/(dV)**2 + fe_V0[i]= e[iv0] + + tot_en = self.energies[np.newaxis, :].T + ( self.volumes[np.newaxis, :].T -V0) * dfe_dV + tot_en = tot_en + 0.5* (( self.volumes[np.newaxis, :].T -V0))**2*(d2fe_dV2) + tot_en = tot_en+ fe_V0 + f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) + vol=f.min_vol + + return vol + def vol_Einf_Vib4_forth(self, tstart=0, tstop=1000, num=101): + """ + + 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: |Vol| + """ + + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + energies = self.energies + volumes0= self.volumes_from_phdos + volumes= self.volumes + vol = np.zeros( num) + dfe_dV1= np.zeros( num) + dfe_dV2= np.zeros( num) + dfe_dV3= np.zeros( num) + dfe_dV4= np.zeros( num) + fe_V0= np.zeros( num) + iv0=2 + dV=volumes0[2]-volumes0[1] + + for i,e in enumerate(ph_energies.T): + dfe_dV1[i]=(-e[iv0+2]+ 8*e[iv0+1]-8*e[iv0-1]+e[iv0-2])/(12*dV) + dfe_dV2[i]=(-e[iv0+2]+16*e[iv0+1]-30*e[iv0]+16*e[iv0-1]-e[iv0-2])/(12*dV**2) + dfe_dV3[i]=(e[iv0+2]-2*e[iv0+1]+2*e[iv0-1]-e[iv0-2])/(2*dV**3) + dfe_dV4[i]=(e[iv0+2]-4*e[iv0+1]+6*e[iv0]-4*e[iv0-1]+e[iv0-2])/(dV**4) + + fe_V0[i]= e[iv0] + V0=volumes0[iv0] + + tot_en = ( volumes[np.newaxis, :].T -V0) * dfe_dV1 + 0.5* ( volumes[np.newaxis, :].T -V0)**2*(dfe_dV2) + tot_en = tot_en+( volumes[np.newaxis, :].T -V0)**3 *dfe_dV3/6 + ( volumes[np.newaxis, :].T -V0)**4*(dfe_dV4/24) + tot_en = tot_en + fe_V0 +energies[np.newaxis, :].T + + f=self.fit_forth( tstart, tstop, num ,tot_en,self.volumes) + vol=f.min_vol + + return vol +#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + @add_fig_kwargs + def plot_vol_vs_t_4th(self, tstart=0, tstop=1000, num=101, ax=None, **kwargs): + """ + Plot 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol_4th = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + ax.plot(tmesh, vol_4th,color='b', lw=2, label="E2Vib1") + data_to_save = np.column_stack((data_to_save,vol_4th)) + columns.append( 'E2vib1') + + if (len(self.index_list)>=2): + vol2_4th = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + ax.plot(tmesh, vol2_4th,color='gold', lw=2 , label=r"$E_\infty Vib1$") + data_to_save = np.column_stack((data_to_save,vol2_4th)) + columns.append( 'Einfvib1') + + if (len(self.index_list)>=3): + vol3_4th = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + ax.plot(tmesh, vol3_4th,color='m', lw=2 , label=r"$E_\infty Vib2$") + data_to_save = np.column_stack((data_to_save,vol3_4th)) + columns.append( 'Einfvib2') + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + vol4_4th = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + ax.plot(tmesh, vol4_4th,color='c', lw=2 ,label=r"$E_\infty Vib4$") + ax.plot(tmesh, f0.min_vol, color='k',linestyle='dashed', lw=1.5 ,label="QHA") + data_to_save = np.column_stack((data_to_save,vol4_4th,f0.min_vol)) + columns.append( 'Einfvib4') + columns.append( 'QHA') + + ax.plot(0, self.volumes[self.iv0], color='g', lw=0, marker='o', ms=10,label="V0") + ax.set_xlabel('T (K)') + ax.set_ylabel(r'V (${\AA}^3$)') + ax.set_xlim(tstart, tstop) + ax.grid(True) + ax.legend() + + return fig + + def get_thermal_expansion_coeff_4th(self, tstart=0, tstop=1000, num=101 , tref=None): + """ + 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + + num: int, optional Number of samples to generate. Default is 100. + + Returns: |Function1D| + """ + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f = self.fit_forth( tstart, tstop, num ,tot_en,self.volumes_from_phdos) + if (tref!=None): + ph_energies2 = self.get_vib_free_energies(tref, tref, 1) + tot_en2 = self.energies_pdos[np.newaxis, :].T + ph_energies2 + f0 = self.fit_forth(tref, tref , 1 ,tot_en2 , self.volumes_from_phdos) + + dt = f.temp[1] - f.temp[0] + thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) + entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro + df_t = np.zeros((num,self.nvols)) + df_t = - entropy + param = np.zeros((num,4)) + param2 = np.zeros((num,3)) + d2f_t_v = np.zeros(num) + gamma = np.zeros(num) + + #for j in range (1,num-1): + for j in range (num): + param[j]=np.polyfit(self.volumes_from_phdos,df_t[j] , 3) + param2[j] = np.array([3*param[j][0],2*param[j][1],param[j][2]]) + + p = np.poly1d(param2[j]) + d2f_t_v[j]= p(f.min_vol[j]) + + F2D = f.F2D_V + if (tref==None): + #alpha= - 1/f.min_vol[1:-1] *d2f_t_v[1:-1] / F2D[1:-1] + alpha= - 1/f.min_vol *d2f_t_v / F2D + else : + #alpha= - 1/f0.min_vol * d2f_t_v[1:-1] / F2D[1:-1] + alpha= - 1/f0.min_vol * d2f_t_v / F2D + + return alpha + @add_fig_kwargs + def plot_thermal_expansion_coeff_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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) + + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + tmesh = np.linspace(tstart, tstop, num) + thermo = self.get_thermodynamic_properties(tstart=tstart, tstop=tstop, num=num) + entropy = thermo.entropy.T #* abu.e_Cb * abu.Avogadro + df_t = np.zeros((num,self.nvols)) + df_t = - entropy + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + iv0=self.iv0_vib + iv1=self.iv1_vib + dV=volumes[iv0+1]-volumes[iv0] + energy = self.energies[np.newaxis, :].T + f=self.fit_forth( tstart=0, tstop=0, num=1 ,energy=energy,volumes=self.volumes) + param3 = np.zeros((num,3)) + param3=np.array([12*f.param[0][0],6*f.param[0][1],2*f.param[0][2]]) + p3 = np.poly1d(param3) + + if self.scale_points=="S": + vol_4th = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + E2D = p3(self.volumes[self.iv0]) + if (tref==None): + alpha_1 = - 1/vol_4th[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D + else : + vol_4th_ref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) + alpha_1 = - 1/vol_4th_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D + ax.plot(tmesh, alpha_1,color='b', lw=2, label="E2Vib1") + data_to_save = np.column_stack((data_to_save,alpha_1)) + columns.append( 'E2vib1') + + if (len(self.index_list)>=2): + vol2_4th = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + #E2D_V = self.second_derivative_energy_v(vol2) + E2D_V = p3(vol2_4th) + if (tref==None): + alpha_2 = - 1/vol2_4th[:] * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] + else : + vol2_4th_ref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) + alpha_2 = - 1/vol2_4th_ref * (df_t[:,iv1]-df_t[:,iv0])/(volumes[iv1]-volumes[iv0]) / E2D_V[:] + ax.plot(tmesh, alpha_2,color='gold', lw=2 , label=r"$E_\infty Vib1$") + data_to_save = np.column_stack((data_to_save,alpha_2)) + columns.append( 'Einfvib1') + + if (len(self.index_list)>=3): + vol3_4th = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + E2D_V = p3(vol3_4th) + dfe_dV2= np.zeros( num) + for i,e in enumerate(ph_energies.T): + dfe_dV2[i]=(e[iv0+2]-2.0*e[iv0+1]+e[iv0])/(dV)**2 + + ds_dv = (df_t[:,iv0+2]-df_t[:,iv0])/(2*dV) + ds_dv = ds_dv+ (df_t[:,iv0+2]-2*df_t[:,iv0+1]+df_t[:,iv0])/dV**2 * (vol3_4th[:]-volumes[iv0+1]) + if (tref==None): + alpha_3 = - 1/vol3_4th[:] * ds_dv / (E2D_V[:]+dfe_dV2[:]) + else : + vol3_4th_ref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) + alpha_3 = - 1/vol3_4th_ref * ds_dv / (E2D_V[:]+dfe_dV2[:]) + ax.plot(tmesh, alpha_3,color='m', lw=2 , label=r"$E_\infty Vib2$") + data_to_save = np.column_stack((data_to_save,alpha_3)) + columns.append( 'Einfvib2') + + if (len(self.index_list)==5): + vol4_4th = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + E2D_V = p3(vol4_4th) + + d2fe_dV2= np.zeros( num) + d3fe_dV3= np.zeros( num) + d4fe_dV4= np.zeros( num) + for i,e in enumerate(ph_energies.T): + d2fe_dV2[i]=(-e[4]+16*e[3]-30*e[2]+16*e[1]-e[0])/(12*dV**2) + d3fe_dV3[i]=(e[4]-2*e[3]+2*e[1]-e[0])/(2*dV**3) + d4fe_dV4[i]=(e[4]-4*e[3]+6*e[2]-4*e[1]+e[0])/(dV**4) + + ds_dv =(-df_t[:,4]+ 8*df_t[:,3]-8*df_t[:,1]+df_t[:,0])/(12*dV) + ds_dv = ds_dv+ (-df_t[:,4]+16*df_t[:,3]-30*df_t[:,2]+16*df_t[:,1]-df_t[:,0])/(12*dV**2) * (vol4_4th[:]-volumes[2]) + ds_dv = ds_dv+ 1.0/2.0*(df_t[:,4]-2*df_t[:,3]+2*df_t[:,1]-df_t[:,0])/(2*dV**3) * (vol4_4th[:]-volumes[2])**2 + ds_dv = ds_dv+ 1.0/6.0* (df_t[:,4]-4*df_t[:,3]+6*df_t[:,2]-4*df_t[:,1]+df_t[:,0])/(dV**4)* (vol4_4th[:]-volumes[2])**3 + D2F=E2D_V[:]+d2fe_dV2[:]+ (vol4_4th[:]-volumes[2])*d3fe_dV3[:]+0.5*(vol4_4th[:]-volumes[2])**2*d4fe_dV4[:] + if (tref==None): + alpha_4 = - 1/vol4_4th[:] * ds_dv / D2F + else : + vol4_4th_ref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) + alpha_4 = - 1/vol4_4th_ref * ds_dv / D2F + + + ax.plot(tmesh, alpha_4,color='c',linewidth=2 , label=r"$E_\infty Vib4$") + + alpha_qha = self.get_thermal_expansion_coeff_4th(tstart, tstop, num, tref) + ax.plot(tmesh, alpha_qha, color='k',linestyle='dashed', lw=1.5 ,label="QHA") + data_to_save = np.column_stack((data_to_save,alpha_4,alpha_qha)) + columns.append( 'Einfvib4') + columns.append( 'QHA') + + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.grid(True) + ax.legend() + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_abc_vs_t_4th(self, tstart=0, tstop=1000, num=101, lattice=None,tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + ax, fig, plt = get_ax_fig_plt(ax) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) + data_to_save = np.column_stack((data_to_save,aa2,bb2,cc2)) + columns.append( 'E2vib1 (a,b,c) | ') + + if (len(self.index_list)==2): + vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + method =r"$ (E_\infty Vib4)$" + vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + aa,bb,cc = self.get_abc(tstart, tstop, num,vol) + + method_header=method+" (a,b,c) |" + data_to_save = np.column_stack((data_to_save,aa,bb,cc)) + columns.append( method_header) + + if (lattice==None or lattice=="a"): + ax.plot(tmesh ,aa , color='r', lw=2,label = r"$a(V(T))$"+method, **kwargs ) + if (lattice==None or lattice=="b"): + ax.plot(tmesh ,bb , color='b', lw=2,label = r"$b(V(T))$"+method ) + if (lattice==None or lattice=="c"): + ax.plot(tmesh ,cc , color='m', lw=2,label = r"$c(V(T))$"+method ) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + if (lattice==None or lattice=="a"): + ax.plot(tmesh ,aa2 , linestyle='dashed' , color='r', lw=2,label = r"$a(V(T))$""E2vib1" ) + if (lattice==None or lattice=="b"): + ax.plot(tmesh ,bb2 , linestyle='dashed' , color='b', lw=2,label = r"$b(V(T))$""E2vib1" ) + if (lattice==None or lattice=="c"): + ax.plot(tmesh ,cc2 , linestyle='dashed' , color='m', lw=2,label = r"$c(V(T))$""E2vib1" ) + + ax.set_xlabel(r'T (K)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_angles_vs_t_4th(self, tstart=0, tstop=1000, num=101,angle=None, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + ax, fig, plt = get_ax_fig_plt(ax) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) + data_to_save = np.column_stack((data_to_save,alpha2,beta2,gamma2)) + columns.append( 'E2vib1 (alpha,beta,gamma) | ') + + if (len(self.index_list)==2): + vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + method =r"$ (E_\infty Vib4)$" + vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) + + method_header=method+" (alpha,beta,gamma) |" + data_to_save = np.column_stack((data_to_save,alpha,beta,gamma)) + columns.append( method_header) + + if (angle==None or angle==1): + ax.plot(tmesh ,alpha , color='r', lw=2,label = r"$alpha(V(T))$"+method, **kwargs ) + if (angle==None or angle==2): + ax.plot(tmesh ,beta , color='b', lw=2,label = r"$beta(V(T))$"+method ) + if (angle==None or angle==3): + ax.plot(tmesh ,gamma , color='m', lw=2,label = r"$gamma(V(T))$"+method ) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + if (angle==None or angle==1): + ax.plot(tmesh ,alpha2 , linestyle='dashed' , color='r', lw=2,label = r"$alpha(V(T))$""E2vib1" ) + if (angle==None or angle==2): + ax.plot(tmesh ,beta2 , linestyle='dashed' , color='b', lw=2,label = r"$beta(V(T))$""E2vib1" ) + if (angle==None or angle==3): + ax.plot(tmesh ,gamma2 , linestyle='dashed' , color='m', lw=2,label = r"$gamma(V(T))$""E2vib1" ) + + ax.set_xlabel(r'T (K)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig +################################################################################################### + @add_fig_kwargs + def plot_thermal_expansion_coeff_abc_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh[1:-1] + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol2_tref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) + + if (len(self.index_list)==2): + vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + method =r"$ (E_\infty Vib4)$" + vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) + + #alpha = self.get_thermal_expansion_coeff(tstart, tstop, num, tref) + tmesh = np.linspace(tstart, tstop, num) + dt= tmesh[1] - tmesh[0] + + aa,bb,cc = self.get_abc(tstart, tstop, num,vol) + if (tref!=None): + aa_tref,bb_tref,cc_tref = self.get_abc(tref, tref, 1,vol_tref) + + alpha_a = np.zeros( num-2) + alpha_b = np.zeros( num-2) + alpha_c = np.zeros( num-2) + if (tref==None): + alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa[1:-1] + alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb[1:-1] + alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc[1:-1] + else: + alpha_a = (aa[2:] - aa[:-2]) / (2 * dt) / aa_tref + alpha_b = (bb[2:] - bb[:-2]) / (2 * dt) / bb_tref + alpha_c = (cc[2:] - cc[:-2]) / (2 * dt) / cc_tref + + ax.plot(tmesh[1:-1] ,alpha_a , color='r', lw=2,label = r"$\alpha_a$"+method, **kwargs) + ax.plot(tmesh[1:-1] ,alpha_b , color='b', lw=2,label = r"$\alpha_b$"+method) + ax.plot(tmesh[1:-1] ,alpha_c , color='m', lw=2,label = r"$\alpha_c$"+method) + + method_header=method+" (alpha_a,alpha_b,alpha_c) |" + data_to_save = np.column_stack((data_to_save,alpha_a,alpha_b,alpha_c)) + columns.append( method_header) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + aa2,bb2,cc2 = self.get_abc(tstart, tstop, num,vol2) + if (tref!=None): + aa2_tref,bb2_tref,cc2_tref = self.get_abc(tref, tref, 1,vol2_tref) + + alpha2_a = np.zeros( num-2) + alpha2_b = np.zeros( num-2) + alpha2_c = np.zeros( num-2) + if (tref==None): + alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2[1:-1] + alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2[1:-1] + alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2[1:-1] + else: + alpha2_a = (aa2[2:] - aa2[:-2]) / (2 * dt) / aa2_tref + alpha2_b = (bb2[2:] - bb2[:-2]) / (2 * dt) / bb2_tref + alpha2_c = (cc2[2:] - cc2[:-2]) / (2 * dt) / cc2_tref + + ax.plot(tmesh[1:-1] ,alpha2_a , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_a$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_b , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_b$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_c , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_c$"" (E2vib1)") + data_to_save = np.column_stack((data_to_save,alpha2_a,alpha2_b,alpha2_c)) + columns.append( 'E2vib1 (alpha_a,alpha_b,alpha_c) ') + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig + @add_fig_kwargs + def plot_thermal_expansion_coeff_angles_4th(self, tstart=0, tstop=1000, num=101, tref=None, 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. + tref: The reference temperature (in Kelvin) used to compute the thermal expansion coefficient 1/V(tref) * dV(T)/dT. + (If tref is not available, it uses 1/V(T) * dV(T)/dT instead.) + 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) + tmesh = np.linspace(tstart, tstop, num) + ph_energies = self.get_vib_free_energies(tstart, tstop, num) + iv0=self.iv0_vib + iv1=self.iv1_vib + volumes=self.volumes_from_phdos + + data_to_save = tmesh[1:-1] + columns = ['#Tmesh'] + if self.scale_points=="S": + vol2 = self.vol_E2Vib1_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol2_tref = self.vol_E2Vib1_forth(num=1,tstop=tref,tstart=tref) + + if (len(self.index_list)==2): + vol = self.vol_EinfVib1_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_EinfVib1_forth(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib1)$" + + if (len(self.index_list)==3): + vol = self.vol_Einf_Vib2_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_Einf_Vib2_forth(num=1,tstop=tref,tstart=tref) + method =r"$ (E_\infty Vib2)$" + + if (len(self.index_list)==5): + tot_en = self.energies_pdos[np.newaxis, :].T + ph_energies + f0 = self.fit_forth( tstart, tstop, num ,tot_en,volumes) + method =r"$ (E_\infty Vib4)$" + vol = self.vol_Einf_Vib4_forth(num=num,tstop=tstop,tstart=tstart) + if (tref!=None): + vol_tref = self.vol_Einf_Vib4_forth(num=1,tstop=tref,tstart=tref) + + tmesh = np.linspace(tstart, tstop, num) + dt= tmesh[1] - tmesh[0] + + alpha,beta,gamma = self.get_angles(tstart, tstop, num,vol) + if (tref!=None): + alpha_tref,beta_tref,gamma_tref = self.get_angles(tref, tref, 1,vol_tref) + + alpha_alpha = np.zeros( num-2) + alpha_beta = np.zeros( num-2) + alpha_gamma = np.zeros( num-2) + if (tref==None): + alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha[1:-1] + alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta[1:-1] + alpha_gamma = (gamma[2:] - gamma[:-2]) / (2 * dt) / gamma[1:-1] + else: + alpha_alpha = (alpha[2:] - alpha[:-2]) / (2 * dt) / alpha_tref + alpha_beta = (beta[2:] - beta[:-2]) / (2 * dt) / beta_tref + alpha_gamma = (gamma[2:] - gamma[:-2]) / (2 * dt) / gamma_tref + + ax.plot(tmesh[1:-1] ,alpha_alpha , color='r', lw=2,label = r"$\alpha_alpha$"+method, **kwargs) + ax.plot(tmesh[1:-1] ,alpha_beta , color='b', lw=2,label = r"$\alpha_beta$"+method) + ax.plot(tmesh[1:-1] ,alpha_gamma , color='m', lw=2,label = r"$\alpha_gamma$"+method) + + method_header=method+" (alpha_alpha,alpha_beta,alpha_gamma) |" + data_to_save = np.column_stack((data_to_save,alpha_alpha,alpha_beta,alpha_gamma)) + columns.append( method_header) + + if abs(abs(self.volumes[self.iv0]-volumes[iv0])-abs(volumes[iv1]-self.volumes[self.iv0]))<1e-3 : + alpha2,beta2,gamma2 = self.get_angles(tstart, tstop, num,vol2) + if (tref!=None): + alpha2_tref,beta2_tref,gamma2_tref = self.get_angles(tref, tref, 1,vol2_tref) + + alpha2_alpha = np.zeros( num-2) + alpha2_beta = np.zeros( num-2) + alpha2_gamma = np.zeros( num-2) + if (tref==None): + alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2[1:-1] + alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2[1:-1] + alpha2_gamma = (gamma2[2:] - gamma2[:-2]) / (2 * dt) / gamma2[1:-1] + else: + alpha2_alpha = (alpha2[2:] - alpha2[:-2]) / (2 * dt) / alpha2_tref + alpha2_beta = (beta2[2:] - beta2[:-2]) / (2 * dt) / beta2_tref + alpha2_gamma = (gamma2[2:] - gamma2[:-2]) / (2 * dt) / gamma2_tref + + ax.plot(tmesh[1:-1] ,alpha2_alpha , linestyle='dashed' , color='r', lw=2 ,label = r"$\alpha_alpha$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_beta , linestyle='dashed' , color='b', lw=2 ,label = r"$\alpha_beta$"" (E2vib1)") + ax.plot(tmesh[1:-1] ,alpha2_gamma , linestyle='dashed' , color='m', lw=2 ,label = r"$\alpha_gamma$"" (E2vib1)") + data_to_save = np.column_stack((data_to_save,alpha2_alpha,alpha2_beta,alpha2_gamma)) + columns.append( 'E2vib1 (alpha_alpha,alpha_beta,alpha_gamma) ') + + ax.set_xlabel(r'T (K)') + ax.set_ylabel(r'$\alpha$ (K$^{-1}$)') + ax.legend() + ax.grid(True) + + ax.set_xlim(tstart, tstop) + ax.get_yaxis().get_major_formatter().set_powerlimits((0, 0)) + + return fig +#********************************************************************************************* + @classmethod + def from_files_app(cls, gsr_paths, phdos_paths): + """ + Creates an instance of QHA from a list of GSR files and a list of PHDOS.nc files. + The list should have the same size and the volumes should match. + + Args: + gsr_paths: list of paths to GSR files. + phdos_paths: list of paths to PHDOS.nc files. + + Returns: A new instance of QHA + """ + energies = [] + structures = [] + pressures = [] + for gp in gsr_paths: + with GsrFile.from_file(gp) as g: + energies.append(g.energy) + structures.append(g.structure) + pressures.append(g.pressure) + + #doses = [PhononDos.as_phdos(dp) for dp in phdos_paths] + + doses = [] + structures_from_phdos = [] + for path in phdos_paths: + with PhdosFile(path) as p: + doses.append(p.phdos) + structures_from_phdos.append(p.structure) + + # cls._check_volumes_id(structures, structures_from_phdos) + vols1 = [s.volume for s in structures] + vols2 = [s.volume for s in structures_from_phdos] + dv=np.zeros((len(vols2)-1)) + for j in range(len(vols2)-1): + dv[j]=vols2[j+1]-vols2[j] + tolerance = 1e-3 + if (len(vols2)!=2): + max_difference = np.max(np.abs(dv - dv[0])) + if max_difference > tolerance: + raise RuntimeError("Expecting an equal volume change for structures from PDOS." ) + + index_list = [i for v2 in vols2 for i, v1 in enumerate(vols1) if abs(v2 - v1) < 1e-3] + if len(index_list) != len(vols2): + raise RuntimeError("Expecting the ground state files for all PDOS files!") + if len(index_list) not in (2, 3, 5): + raise RuntimeError("Expecting just 2, 3, or 5 PDOS files in the approximation method.") + + return cls(structures,structures_from_phdos,index_list, doses, energies , pressures) + + def get_vib_free_energies(self, tstart=0, tstop=1000, num=101) -> np.ndarray: + """ + Generates the vibrational free energy from the phonon DOS. + + 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 the vibrational contribution to the free energy + """ + f = np.zeros((self.nvols, num)) + + for i, dos in enumerate(self.doses): + f[i] = dos.get_free_energy(tstart, tstop, num).values + return f + + def get_thermodynamic_properties(self, tstart=0, tstop=1000, num=101): + """ + Generates all the thermodynamic properties corresponding to all the volumes using the phonon DOS. + + 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). + """ + tmesh = np.linspace(tstart, tstop, num) + cv = np.zeros((self.nvols, num)) + free_energy = np.zeros((self.nvols, num)) + entropy = np.zeros((self.nvols, num)) + internal_energy = np.zeros((self.nvols, num)) + zpe = np.zeros(self.nvols) + + for i, d in enumerate(self.doses): + cv[i] = d.get_cv(tstart, tstop, num).values + free_energy[i] = d.get_free_energy(tstart, tstop, num).values + entropy[i] = d.get_entropy(tstart, tstop, num).values + zpe[i] = d.zero_point_energy + + return dict2namedtuple(tmesh=tmesh, cv=cv, free_energy=free_energy, entropy=entropy, zpe=zpe) +#======================================================================================================= + @classmethod + def from_files_app_ddb(cls, ddb_paths, phdos_paths): + """ + Creates an instance of QHA from a list of GSR files and a list of PHDOS.nc files. + The list should have the same size and the volumes should match. + + Args: + ddb_paths: list of paths to DDB files. + phdos_paths: list of paths to PHDOS.nc files. + + Returns: A new instance of QHA + """ + energies = [] + structures = [] + pressures = [] + for gp in ddb_paths: + with DdbFile.from_file(gp) as g: + energies.append(g.total_energy) + structures.append(g.structure) + #pressures.append(g.pressure) + + #doses = [PhononDos.as_phdos(dp) for dp in phdos_paths] + + doses = [] + structures_from_phdos = [] + for path in phdos_paths: + with PhdosFile(path) as p: + doses.append(p.phdos) + structures_from_phdos.append(p.structure) + + # cls._check_volumes_id(structures, structures_from_phdos) + vols1 = [s.volume for s in structures] + vols2 = [s.volume for s in structures_from_phdos] + dv=np.zeros((len(vols2)-1)) + for j in range(len(vols2)-1): + dv[j]=vols2[j+1]-vols2[j] + tolerance = 1e-3 + if (len(vols2)!=2): + max_difference = np.max(np.abs(dv - dv[0])) + if max_difference > tolerance: + raise RuntimeError("Expecting an equal volume change for structures from PDOS." ) + + index_list = [i for v2 in vols2 for i, v1 in enumerate(vols1) if abs(v2 - v1) < 1e-3] + if len(index_list) != len(vols2): + raise RuntimeError("Expecting the ground state files for all PDOS files!") + if len(index_list) not in (2, 3, 5): + raise RuntimeError("Expecting just 2, 3, or 5 PDOS files in the approximation method.") + + return cls(structures,structures_from_phdos,index_list, doses, energies , pressures) + diff --git a/abipy/dfpt/tests/test_qha.py b/abipy/dfpt/tests/test_qha.py index 99222016d..71bb92d3e 100644 --- a/abipy/dfpt/tests/test_qha.py +++ b/abipy/dfpt/tests/test_qha.py @@ -35,7 +35,7 @@ def test_qha(self): qha.set_eos("murnaghan") self.assert_equal(qha.eos._eos_name, "murnaghan") - te = qha.get_thermal_expansion_coeff(num=4) + te = qha.get_thermal_expansion_coeff(num=4,method="finite_difference") self.assert_almost_equal(te.values[1], 1.4676820862386381e-05) self.assert_almost_equal(qha.get_vol_at_t(200), 41.07441539803265, decimal=4) @@ -118,7 +118,7 @@ def test_qha3pf(self): qha.set_eos("murnaghan") self.assert_equal(qha.eos._eos_name, "murnaghan") - te = qha.get_thermal_expansion_coeff(num=4) + te = qha.get_thermal_expansion_coeff(num=4,method="finite_difference") self.assert_almost_equal(te.values[1], 1.2773693323408941e-05) self.assert_almost_equal(qha.get_vol_at_t(200), 41.10212044734946, decimal=4) @@ -172,7 +172,7 @@ def test_qha3p(self): qha.set_eos("murnaghan") self.assert_equal(qha.eos._eos_name, "murnaghan") - te = qha.get_thermal_expansion_coeff(num=4) + te = qha.get_thermal_expansion_coeff(num=4,method="finite_difference") self.assert_almost_equal(te.values[1], 1.2725767394824783e-05) self.assert_almost_equal(qha.get_vol_at_t(200), 41.1083743159003, decimal=4) From 2dc6bd21bd00896d17fe055c378e6c09588f1a84 Mon Sep 17 00:00:00 2001 From: Matteo Giantomassi Date: Thu, 17 Oct 2024 00:22:40 +0200 Subject: [PATCH 7/9] Test py3.12 --- .github/workflows/test.yml | 5 ++++- abipy/lumi/deltaSCF.py | 6 +++--- abipy/scripts/oncv.py | 24 ++++++++++++------------ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb151ffe9..3ebb3245f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,9 @@ jobs: config: - os: ubuntu-latest python: '3.11' + config: + - os: ubuntu-latest + python: '3.12' runs-on: ${{ matrix.config.os }} env: @@ -51,7 +54,7 @@ jobs: cp abipy/data/managers/gh_manager.yml $HOME/.abinit/abipy/manager.yml cp abipy/data/managers/gh_scheduler.yml $HOME/.abinit/abipy/scheduler.yml # FIXME TEMPORARY HACK - pip install git+https://github.com/gmatteo/pymatgen.git@master -U + #pip install git+https://github.com/gmatteo/pymatgen.git@master -U - name: pytest run: | diff --git a/abipy/lumi/deltaSCF.py b/abipy/lumi/deltaSCF.py index bf83b1f89..602459c02 100644 --- a/abipy/lumi/deltaSCF.py +++ b/abipy/lumi/deltaSCF.py @@ -496,7 +496,7 @@ def draw_displacements_vesta(self,in_path, mass_weighted = False, scale_vector=20,width_vector=0.3,color_vector=[255,0,0],centered=True, factor_keep_vectors=0.1, out_path="VESTA_FILES",out_filename="gs_ex_relaxation"): - """ + r""" Draw the ground state to excited state atomic relaxation on a vesta structure. Args: @@ -613,7 +613,7 @@ def displacements_visu(self, a_g=10, **kwargs): @add_fig_kwargs def plot_delta_R_distance(self, defect_symbol,colors=["k","r","g","b","c","m"],ax=None, **kwargs): - """ + r""" Plot \DeltaR vs distance from defect for each atom, colored by species. Args: @@ -646,7 +646,7 @@ def plot_delta_R_distance(self, defect_symbol,colors=["k","r","g","b","c","m"],a @add_fig_kwargs def plot_delta_F_distance(self, defect_symbol,colors=["k","r","g","b","c","m"],ax=None, **kwargs): - """ + r""" Plot \DeltaF vs distance from defect for each atom, colored by species. Args: diff --git a/abipy/scripts/oncv.py b/abipy/scripts/oncv.py index 4567ee587..c8ac360c3 100755 --- a/abipy/scripts/oncv.py +++ b/abipy/scripts/oncv.py @@ -28,7 +28,7 @@ def _find_oncv_output(path: str) -> str: new_path = root + ".out" if not os.path.exists(new_path): raise ValueError("Cannot find neither %s nor %s" % (path, new_path)) - cprint("Maybe you meant %s" % new_path, "yellow") + cprint("Maybe you meant %s" % new_path, color="yellow") return new_path @@ -51,7 +51,7 @@ def oncv_gnuplot(options): # Parse output file. onc_parser = OncvParser(out_path).scan() if not onc_parser.run_completed: - cprint("oncvpsp output is not completed. Exiting", "red") + cprint("oncvpsp output is not completed. Exiting", color="red") return 1 onc_parser.gnuplot() @@ -155,7 +155,7 @@ def oncv_run(options): elif options.rel == "fr": if not root.endswith("_r"): root += "_r" - cprint("FR calculation with input file without `_r` suffix. Will add `_r` to output files", "yellow") + cprint("FR calculation with input file without `_r` suffix. Will add `_r` to output files", color="yellow") elif options.rel == "from_file": calc_type = "scalar-relativistic" @@ -168,11 +168,11 @@ def oncv_run(options): out_path = root + ".out" if os.path.exists(psp8_path): - cprint("%s already exists and will be overwritten" % os.path.relpath(psp8_path), "yellow") + cprint("%s already exists and will be overwritten" % os.path.relpath(psp8_path), color="yellow") if os.path.exists(djrepo_path): - cprint("%s already exists and will be overwritten" % os.path.relpath(djrepo_path), "yellow") + cprint("%s already exists and will be overwritten" % os.path.relpath(djrepo_path), color="yellow") if os.path.exists(out_path): - cprint("%s already exists and will be overwritten" % os.path.relpath(out_path), "yellow") + cprint("%s already exists and will be overwritten" % os.path.relpath(out_path), color="yellow") # Select calc_type if calc_type is None: @@ -188,11 +188,11 @@ def oncv_run(options): print(f"Output files produced in directory:\n\t{psgen.workdir}") if retcode := psgen.start_and_wait() != 0: - cprint("oncvpsp returned %s. Exiting" % retcode, "red") + cprint("oncvpsp returned %s. Exiting" % retcode, color="red") return retcode if psgen.status != psgen.S_OK: - cprint(f"psgen.status = {psgen.status} != psgen.S_OK", "red") + cprint(f"psgen.status = {psgen.status} != psgen.S_OK", color="red") if psgen.parser.warnings: print(2 * "\n") @@ -212,7 +212,7 @@ def oncv_run(options): # Parse the output file. onc_parser = OncvParser(out_path).scan() if not onc_parser.run_completed: - cprint("oncvpsp output is not completed. Exiting", "red") + cprint("oncvpsp output is not completed. Exiting", color="red") return 1 # Extract psp8 files from the oncvpsp output and write it to file. @@ -225,11 +225,11 @@ def oncv_run(options): with open(psp8_path.replace(".psp8", ".upf"), "wt") as fh: fh.write(upf_str) else: - cprint("UPF2 file has not been produced. Use `both` in input file!", "red") + cprint("UPF2 file has not been produced. Use `both` in input file!", color="red") pseudo = Pseudo.from_file(psp8_path) if pseudo is None: - cprint("Cannot parse psp8 file: %s" % psp8_path, "red") + cprint("Cannot parse psp8 file: %s" % psp8_path, color="red") return 1 # Initialize and write djson file. @@ -346,7 +346,7 @@ def get_copts_parser(multi=False): # Subparser for compare command. copts_parser_multi = get_copts_parser(multi=True) - p_compare = subparsers.add_parser('compare', parents=[copts_parser_multi, plot_parser], + p_compare = subparsers.add_parser("compare", parents=[copts_parser_multi, plot_parser], help=oncv_compare.__doc__) # notebook options. From dbcd307a5a39484b0734fdeb998b8f2966e58eba Mon Sep 17 00:00:00 2001 From: Victor Trinquet Date: Thu, 17 Oct 2024 18:17:54 +0200 Subject: [PATCH 8/9] test factories --- abipy/abio/factories.py | 2 +- abipy/abio/tests/test_factories.py | 45 ++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/abipy/abio/factories.py b/abipy/abio/factories.py index 2fc7e928b..c776eacb1 100644 --- a/abipy/abio/factories.py +++ b/abipy/abio/factories.py @@ -1439,7 +1439,7 @@ def ddepert_from_gsinput(gs_input, dde_pert, use_symmetries=True, dde_tol=None, def dtepert_from_gsinput(gs_input, dte_pert, manager=None) -> AbinitInput: """ - Returns an |AbinitInput| to perform a DTE calculations for a specific perturbation and based on a ground state |AbinitInput|. + Returns an |AbinitInput| to perform a DTE calculations for a specific perturbation based on a ground state |AbinitInput|. Args: gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. diff --git a/abipy/abio/tests/test_factories.py b/abipy/abio/tests/test_factories.py index dc94ea853..c7337d92d 100644 --- a/abipy/abio/tests/test_factories.py +++ b/abipy/abio/tests/test_factories.py @@ -545,11 +545,17 @@ def test_ddkpert_from_gsinput(self): ddk_pert = {'idir': 1, 'ipert': 3, 'qpt': [0.0, 0.0, 0.0]} ddk_input = ddkpert_from_gsinput(gs_inp, ddk_pert) - assert ddk_input["tolwfr"] == 1.0e-22 assert "autoparal" not in ddk_input assert "npfft" not in ddk_input + assert ddk_input["tolwfr"] == 1.0e-22 self.abivalidate_input(ddk_input) + def test_ddepert_from_gsinput(self): + gs_inp = gs_input(self.si_structure, self.si_pseudo, kppa=None, ecut=2, spin_mode="unpolarized") + gs_inp["nband"] = 4 + gs_inp["autoparal"] = 1 + gs_inp["npfft"] = 10 + dde_pert = {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]} dde_input = ddepert_from_gsinput(gs_inp, dde_pert) assert "autoparal" not in dde_input @@ -557,14 +563,29 @@ def test_ddkpert_from_gsinput(self): assert dde_input["tolvrs"] == 1.0e-22 self.abivalidate_input(dde_input) - #dte_pert = {'i1dir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]} - #dte_input = dtepert_from_gsinput(gs_inp, dte_pert) - #assert "autoparal" not in dte_input - #assert "npfft" not in dte_input - #assert dte_input["tolvrs"] == 1.0e-22 - #self.abivalidate_input(dte_input) - - #def test_dte_from_gsinput(self): - # gs_inp = gs_input(self.si_structure, self.si_pseudo, kppa=None, ecut=2, spin_mode="unpolarized") - # multi = dte_from_gsinput(gs_inp, use_phonons=True) - # self.abivalidate_input(multi) + def test_dtepert_from_gsinput(self): + gs_inp = scf_for_phonons(self.si_structure, self.si_pseudo, kppa=None, ecut=2, smearing="nosmearing", spin_mode="unpolarized") + gs_inp["nband"] = 4 + gs_inp["autoparal"] = 1 + gs_inp["npfft"] = 1 + + dte_pert = {'i1dir': 1, 'i1pert': 4, 'qpt': [0.0, 0.0, 0.0], + 'i2dir': 1, 'i2pert': 4, + 'i3dir': 1, 'i3pert': 4,} + dte_input = dtepert_from_gsinput(gs_inp, dte_pert) + dte_input["ixc"] = 7 + + assert "autoparal" not in dte_input + assert "npfft" not in dte_input + assert dte_input["optdriver"] == 5 + assert dte_input["d3e_pert1_elfd"] == 1 + assert dte_input["d3e_pert2_elfd"] == 1 + assert dte_input["d3e_pert3_elfd"] == 1 + assert dte_input["d3e_pert1_dir"] == [1,0,0] + assert dte_input["d3e_pert2_dir"] == [1,0,0] + assert dte_input["d3e_pert3_dir"] == [1,0,0] + assert dte_input["d3e_pert1_phon"] == 0 + assert dte_input["d3e_pert2_phon"] == 0 + assert dte_input["d3e_pert3_phon"] == 0 + self.abivalidate_input(dte_input) + From 7337f2ff273a1c894453ab62f496565980492cfa Mon Sep 17 00:00:00 2001 From: Victor Trinquet <60815457+VicTrqt@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:45:33 +0200 Subject: [PATCH 9/9] Test factories ddkpert, ddepert, dtepert (#301) * Read SHG AR contrib * new factories for Atomate2 SHG WF * update factories.py __all__ * Atomate2 convention: gs_inp -> gs_input * nband in ddkpert_from_gsinput because of factory_kwrags in atomate2 * Add ONCVPSP LDA v0.4 * test spinat * modif set_autospinat * modif for atomate2 * fix EventParser for RelaxConvergenceWarning + quick fix wrapper as_dict pmg_serialize * remove VT * remove autoparal 1 of GS from ddk, dde, dte * activate ONCVPSP-LDA-SR-v0.3 * backward compatibility * remove comments * test factories --- abipy/abio/factories.py | 2 +- abipy/abio/tests/test_factories.py | 45 ++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/abipy/abio/factories.py b/abipy/abio/factories.py index 2fc7e928b..c776eacb1 100644 --- a/abipy/abio/factories.py +++ b/abipy/abio/factories.py @@ -1439,7 +1439,7 @@ def ddepert_from_gsinput(gs_input, dde_pert, use_symmetries=True, dde_tol=None, def dtepert_from_gsinput(gs_input, dte_pert, manager=None) -> AbinitInput: """ - Returns an |AbinitInput| to perform a DTE calculations for a specific perturbation and based on a ground state |AbinitInput|. + Returns an |AbinitInput| to perform a DTE calculations for a specific perturbation based on a ground state |AbinitInput|. Args: gs_input: an |AbinitInput| representing a ground state calculation, likely the SCF performed to get the WFK. diff --git a/abipy/abio/tests/test_factories.py b/abipy/abio/tests/test_factories.py index dc94ea853..c7337d92d 100644 --- a/abipy/abio/tests/test_factories.py +++ b/abipy/abio/tests/test_factories.py @@ -545,11 +545,17 @@ def test_ddkpert_from_gsinput(self): ddk_pert = {'idir': 1, 'ipert': 3, 'qpt': [0.0, 0.0, 0.0]} ddk_input = ddkpert_from_gsinput(gs_inp, ddk_pert) - assert ddk_input["tolwfr"] == 1.0e-22 assert "autoparal" not in ddk_input assert "npfft" not in ddk_input + assert ddk_input["tolwfr"] == 1.0e-22 self.abivalidate_input(ddk_input) + def test_ddepert_from_gsinput(self): + gs_inp = gs_input(self.si_structure, self.si_pseudo, kppa=None, ecut=2, spin_mode="unpolarized") + gs_inp["nband"] = 4 + gs_inp["autoparal"] = 1 + gs_inp["npfft"] = 10 + dde_pert = {'idir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]} dde_input = ddepert_from_gsinput(gs_inp, dde_pert) assert "autoparal" not in dde_input @@ -557,14 +563,29 @@ def test_ddkpert_from_gsinput(self): assert dde_input["tolvrs"] == 1.0e-22 self.abivalidate_input(dde_input) - #dte_pert = {'i1dir': 1, 'ipert': 4, 'qpt': [0.0, 0.0, 0.0]} - #dte_input = dtepert_from_gsinput(gs_inp, dte_pert) - #assert "autoparal" not in dte_input - #assert "npfft" not in dte_input - #assert dte_input["tolvrs"] == 1.0e-22 - #self.abivalidate_input(dte_input) - - #def test_dte_from_gsinput(self): - # gs_inp = gs_input(self.si_structure, self.si_pseudo, kppa=None, ecut=2, spin_mode="unpolarized") - # multi = dte_from_gsinput(gs_inp, use_phonons=True) - # self.abivalidate_input(multi) + def test_dtepert_from_gsinput(self): + gs_inp = scf_for_phonons(self.si_structure, self.si_pseudo, kppa=None, ecut=2, smearing="nosmearing", spin_mode="unpolarized") + gs_inp["nband"] = 4 + gs_inp["autoparal"] = 1 + gs_inp["npfft"] = 1 + + dte_pert = {'i1dir': 1, 'i1pert': 4, 'qpt': [0.0, 0.0, 0.0], + 'i2dir': 1, 'i2pert': 4, + 'i3dir': 1, 'i3pert': 4,} + dte_input = dtepert_from_gsinput(gs_inp, dte_pert) + dte_input["ixc"] = 7 + + assert "autoparal" not in dte_input + assert "npfft" not in dte_input + assert dte_input["optdriver"] == 5 + assert dte_input["d3e_pert1_elfd"] == 1 + assert dte_input["d3e_pert2_elfd"] == 1 + assert dte_input["d3e_pert3_elfd"] == 1 + assert dte_input["d3e_pert1_dir"] == [1,0,0] + assert dte_input["d3e_pert2_dir"] == [1,0,0] + assert dte_input["d3e_pert3_dir"] == [1,0,0] + assert dte_input["d3e_pert1_phon"] == 0 + assert dte_input["d3e_pert2_phon"] == 0 + assert dte_input["d3e_pert3_phon"] == 0 + self.abivalidate_input(dte_input) +