From b5671727e753a0279258319b778176f2f4cc7c38 Mon Sep 17 00:00:00 2001 From: jrudz Date: Thu, 27 Jun 2024 12:49:22 +0200 Subject: [PATCH] expanded thermodynamics --- .../schema_packages/outputs.py | 1 + .../schema_packages/properties/__init__.py | 22 +- .../schema_packages/properties/energies.py | 16 +- .../schema_packages/properties/forces.py | 11 +- .../properties/thermodynamics.py | 189 +++++++++------ tests/test_thermodynamics.py | 221 ++++++++++++------ 6 files changed, 306 insertions(+), 154 deletions(-) diff --git a/src/nomad_simulations/schema_packages/outputs.py b/src/nomad_simulations/schema_packages/outputs.py index 9e8db3f9..973af6c3 100644 --- a/src/nomad_simulations/schema_packages/outputs.py +++ b/src/nomad_simulations/schema_packages/outputs.py @@ -350,6 +350,7 @@ class WorkflowOutputs(Outputs): """, ) + # TODO add this in when we link to nomad-simulations-workflow schema # workflow_ref = Quantity( # type=SimulationWorkflow, # description=""" diff --git a/src/nomad_simulations/schema_packages/properties/__init__.py b/src/nomad_simulations/schema_packages/properties/__init__.py index 495663b7..b2d8059f 100644 --- a/src/nomad_simulations/schema_packages/properties/__init__.py +++ b/src/nomad_simulations/schema_packages/properties/__init__.py @@ -24,10 +24,27 @@ EnergyContribution, TotalEnergy, KineticEnergy, - PotentialEnergy + PotentialEnergy, ) from .forces import BaseForce, ForceContribution, TotalForce -from .thermodynamics import Enthalpy, Entropy, ChemicalPotential, Pressure, Virial, Temperature, Volume, Density, Hessian, HeatCapacityCV, HeatCapacityCP +from .thermodynamics import ( + Pressure, + Volume, + Temperature, + HeatAdded, + WorkDone, + InternalEnergy, + Enthalpy, + Entropy, + GibbsFreeEnergy, + HelmholtzFreeEnergy, + ChemicalPotential, + HeatCapacityCV, + HeatCapacityCP, + Virial, + Density, + Hessian, +) from .fermi_surface import FermiSurface from .hopping_matrix import CrystalFieldSplitting, HoppingMatrix from .permittivity import Permittivity @@ -38,4 +55,3 @@ SpectralProfile, XASSpectrum, ) - diff --git a/src/nomad_simulations/schema_packages/properties/energies.py b/src/nomad_simulations/schema_packages/properties/energies.py index 162d02ce..d288da01 100644 --- a/src/nomad_simulations/schema_packages/properties/energies.py +++ b/src/nomad_simulations/schema_packages/properties/energies.py @@ -27,12 +27,16 @@ from nomad.datamodel.datamodel import EntryArchive from structlog.stdlib import BoundLogger -from nomad_simulations.schema_packages.physical_property import PhysicalProperty, PropertyContribution +from nomad_simulations.schema_packages.physical_property import ( + PhysicalProperty, + PropertyContribution, +) ################## # Abstract classes ################## + class BaseEnergy(PhysicalProperty): """ Abstract physical property section describing some energy of a (sub)system. @@ -49,6 +53,7 @@ class BaseEnergy(PhysicalProperty): def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: super().normalize(archive, logger) + class EnergyContribution(BaseEnergy, PropertyContribution): """ Abstract physical property section linking a property contribution to a contribution @@ -58,10 +63,12 @@ class EnergyContribution(BaseEnergy, PropertyContribution): def normalize(self, archive, logger) -> None: super().normalize(archive, logger) + #################################### # List of specific energy properties #################################### + class FermiLevel(BaseEnergy): """ Physical property section describing the Fermi level, i.e., the energy required to add or extract a charge from a material at zero temperature. @@ -82,6 +89,7 @@ def __init__( def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: super().normalize(archive, logger) + #! The only issue with this structure is that total energy will never be a sum of its contributions, #! since kinetic energy lives separately, but I think maybe this is ok? class TotalEnergy(BaseEnergy): @@ -100,7 +108,8 @@ def __init__( def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -#? Separate quantities for nuclear and electronic KEs? + +# ? Separate quantities for nuclear and electronic KEs? class KineticEnergy(BaseEnergy): """ Physical property section describing the kinetic energy of a (sub)system. @@ -109,6 +118,7 @@ class KineticEnergy(BaseEnergy): def normalize(self, archive, logger) -> None: super().normalize(archive, logger) + class PotentialEnergy(BaseEnergy): """ Physical property section describing the potential energy of a (sub)system. @@ -119,7 +129,7 @@ def normalize(self, archive, logger) -> None: #! I removed all previous contributions associated in some way with terms in the Hamiltonian. -#? Should the remaining contributions below be incorporated into some sort of workflow results if still relevant? +# ? Should the remaining contributions below be incorporated into some sort of workflow results if still relevant? # class ZeroTemperatureEnergy(QuantumEnergy): diff --git a/src/nomad_simulations/schema_packages/properties/forces.py b/src/nomad_simulations/schema_packages/properties/forces.py index 4ecf10b7..dfb68fa3 100644 --- a/src/nomad_simulations/schema_packages/properties/forces.py +++ b/src/nomad_simulations/schema_packages/properties/forces.py @@ -37,7 +37,10 @@ import numpy as np from nomad.metainfo import Quantity, Section, Context, SubSection -from nomad_simulations.schema_packages.physical_property import PhysicalProperty, PropertyContribution +from nomad_simulations.schema_packages.physical_property import ( + PhysicalProperty, + PropertyContribution, +) #################################################### # Abstract force classes @@ -47,6 +50,7 @@ # Abstract classes ################## + class BaseForce(PhysicalProperty): """ Abstract physical property section describing some force of a (sub)system. @@ -63,6 +67,7 @@ class BaseForce(PhysicalProperty): def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: super().normalize(archive, logger) + class ForceContribution(BaseForce, PropertyContribution): """ Abstract physical property section linking a property contribution to a contribution @@ -77,6 +82,7 @@ def normalize(self, archive, logger) -> None: # List of specific force properties ################################### + class TotalForce(BaseForce): """ Physical property section describing the total force of a (sub)system. @@ -93,7 +99,8 @@ def __init__( def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -#? See questions about corresponding energies + +# ? See questions about corresponding energies # class FreeForce(Force): # """ # Physical property section describing... diff --git a/src/nomad_simulations/schema_packages/properties/thermodynamics.py b/src/nomad_simulations/schema_packages/properties/thermodynamics.py index 76673c1f..6200d6cb 100644 --- a/src/nomad_simulations/schema_packages/properties/thermodynamics.py +++ b/src/nomad_simulations/schema_packages/properties/thermodynamics.py @@ -35,30 +35,42 @@ # import numpy as np -from nomad.metainfo import Quantity, SubSection, MEnum +from nomad.metainfo import Quantity from nomad_simulations.schema_packages.physical_property import PhysicalProperty from nomad_simulations.schema_packages.properties import BaseEnergy +###################################### +# fundamental thermodynamic properties +###################################### -class Enthalpy(BaseEnergy): + +class Pressure(PhysicalProperty): """ - Physical property section describing the enthalpy (i.e. energy_total + pressure * volume.) of a (sub)system. + Physical property section describing the pressure of a (sub)system. """ + value = Quantity( + type=np.float64, + unit='pascal', + description=""" + The value of the pressure. + """, + ) + def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -class Entropy(PhysicalProperty): +class Volume(PhysicalProperty): """ - Physical property section describing the entropy of a (sub)system. + Physical property section describing the volume of a (sub)system. """ value = Quantity( type=np.float64, - unit='joule / kelvin', + unit='m ** 3', description=""" - The value of the entropy. + The value of the volume. """, ) @@ -66,62 +78,69 @@ def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -class ChemicalPotential(BaseEnergy): +class Temperature(PhysicalProperty): """ - Free energy cost of adding or extracting a particle from a thermodynamic system. + Physical property section describing the temperature of a (sub)system. """ - # ! implement `iri` and `rank` as part of `m_def = Section()` + value = Quantity( + type=np.float64, + unit='kelvin', + description=""" + The value of the temperature. + """, + ) - iri = 'http://fairmat-nfdi.eu/taxonomy/ChemicalPotential' + def normalize(self, archive, logger) -> None: + super().normalize(archive, logger) - def __init__( - self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs - ) -> None: - super().__init__(m_def, m_context, **kwargs) - self.rank = [] - self.name = self.m_def.name - def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: +class HeatAdded(BaseEnergy): + """ + Physical property section describing the heat added to a (sub)system. + """ + + def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -class Pressure(PhysicalProperty): +class WorkDone(BaseEnergy): """ - Physical property section describing the pressure of a (sub)system. + Physical property section describing the internal energy (i.e., heat added - work) for a (sub)system. """ - value = Quantity( - type=np.float64, - unit='pascal', - description=""" - The value of the pressure. - """, - ) + def normalize(self, archive, logger) -> None: + super().normalize(archive, logger) + + +class InternalEnergy(BaseEnergy): + """ + Physical property section describing the internal energy (i.e., heat added - work) for a (sub)system. + """ def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -class Virial(BaseEnergy): +class Enthalpy(BaseEnergy): """ - Physical property section describing the virial (cross product between positions and forces) of a (sub)system. + Physical property section describing the enthalpy (i.e. internal_energy + pressure * volume.) of a (sub)system. """ def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -class Temperature(PhysicalProperty): +class Entropy(PhysicalProperty): """ - Physical property section describing the temperature of a (sub)system. + Physical property section describing the entropy of a (sub)system. """ value = Quantity( type=np.float64, - unit='kelvin', + unit='joule / kelvin', description=""" - The value of the temperature. + The value of the entropy. """, ) @@ -129,53 +148,68 @@ def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -class Volume(PhysicalProperty): +class GibbsFreeEnergy(BaseEnergy): """ - Physical property section describing the volume of a (sub)system. + Physical property section describing the Gibbs free energy (i.e., enthalpy - temperature * entropy) of a (sub)system. """ - value = Quantity( - type=np.float64, - unit='m ** 3', - description=""" - The value of the volume. - """, - ) + def normalize(self, archive, logger) -> None: + super().normalize(archive, logger) + + +class HelmholtzFreeEnergy(BaseEnergy): + """ + Physical property section describing the Gibbs free energy (i.e., internal_energy - temperature * entropy) of a (sub)system. + """ def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -class Density(PhysicalProperty): +class ChemicalPotential(BaseEnergy): """ - Physical property section describing the density of a (sub)system. + Free energy cost of adding or extracting a particle from a thermodynamic system. + """ + + # ! implement `iri` and `rank` as part of `m_def = Section()` + + iri = 'http://fairmat-nfdi.eu/taxonomy/ChemicalPotential' + + def __init__( + self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs + ) -> None: + super().__init__(m_def, m_context, **kwargs) + self.rank = [] + self.name = self.m_def.name + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + +class HeatCapacityCV(PhysicalProperty): + """ + Physical property section describing the heat capacity at constant volume for a (sub)system. """ value = Quantity( type=np.float64, - unit='kg / m ** 3', + unit='joule / kelvin', description=""" - The value of the density. + The value of the heat capacity. """, ) - def normalize(self, archive, logger) -> None: - super().normalize(archive, logger) - -# ? Does this go here or in energies? -# ? Naming specific to Potential Energy? -class Hessian(PhysicalProperty): +class HeatCapacityCP(PhysicalProperty): """ - Physical property section describing the Hessian matrix, i.e., 2nd derivatives with respect to geometric (typically particle) displacements, - of the potential energy of a (sub)system. + Physical property section describing the heat capacity at constant volume for a (sub)system. """ value = Quantity( type=np.float64, - unit='joule / m ** 2', + unit='joule / kelvin', description=""" - The value of the Hessian. + The value of the heat capacity. """, ) @@ -183,30 +217,48 @@ def normalize(self, archive, logger) -> None: super().normalize(archive, logger) -class HeatCapacityCV(PhysicalProperty): +################################ +# other thermodynamic properties +################################ + + +class Virial(BaseEnergy): """ - Physical property section describing the heat capacity at constant volume for a (sub)system. + Physical property section describing the virial of a (sub)system. + """ + + def normalize(self, archive, logger) -> None: + super().normalize(archive, logger) + + +class Density(PhysicalProperty): + """ + Physical property section describing the density of a (sub)system. """ value = Quantity( type=np.float64, - unit='joule / kelvin', + unit='kg / m ** 3', description=""" - The value of the heat capacity. + The value of the density. """, ) + def normalize(self, archive, logger) -> None: + super().normalize(archive, logger) -class HeatCapacityCP(PhysicalProperty): + +class Hessian(PhysicalProperty): """ - Physical property section describing the heat capacity at constant volume for a (sub)system. + Physical property section describing the Hessian matrix, i.e., 2nd derivatives with respect to geometric (typically particle) displacements, + of the potential energy of a (sub)system. """ value = Quantity( type=np.float64, - unit='joule / kelvin', + unit='joule / m ** 2', description=""" - The value of the heat capacity. + The value of the Hessian. """, ) @@ -214,15 +266,6 @@ def normalize(self, archive, logger) -> None: super().normalize(archive, logger) # ? Is this ever used? - # internal_energy = Quantity( - # type=np.dtype(np.float64), - # shape=[], - # unit='joule', - # description=""" - # Value of the internal energy. - # """, - # ) - # vibrational_free_energy_at_constant_volume = Quantity( # type=np.dtype(np.float64), # shape=[], diff --git a/tests/test_thermodynamics.py b/tests/test_thermodynamics.py index 9e4802c8..fa4b0935 100644 --- a/tests/test_thermodynamics.py +++ b/tests/test_thermodynamics.py @@ -17,19 +17,112 @@ # from nomad_simulations.schema_packages.properties import ( + Pressure, + Volume, + Temperature, + HeatAdded, + WorkDone, + InternalEnergy, Enthalpy, Entropy, + GibbsFreeEnergy, + HelmholtzFreeEnergy, ChemicalPotential, - Pressure, + HeatCapacityCV, + HeatCapacityCP, Virial, - Temperature, - Volume, Density, Hessian, - HeatCapacityCV, - HeatCapacityCP, ) +class TestPressure: + """ + Test the `Pressure` class defined in `properties/thermodynamics.py`. + """ + + # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes + def test_default_quantities(self): + """ + Test the default quantities assigned when creating an instance of the `Pressure` class. + """ + pressure = Pressure() + assert pressure.iri == 'http://fairmat-nfdi.eu/taxonomy/Pressure' + assert pressure.name == 'Pressure' + assert pressure.rank == [] +class TestVolume: + """ + Test the `Volume` class defined in `properties/thermodynamics.py`. + """ + + # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes + def test_default_quantities(self): + """ + Test the default quantities assigned when creating an instance of the `Volume` class. + """ + volume = Volume() + assert volume.iri == 'http://fairmat-nfdi.eu/taxonomy/Volume' + assert volume.name == 'Volume' + assert volume.rank == [] + +class TestTemperature: + """ + Test the `Temperature` class defined in `properties/thermodynamics.py`. + """ + + # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes + def test_default_quantities(self): + """ + Test the default quantities assigned when creating an instance of the `Temperature` class. + """ + temperature = Temperature() + assert temperature.iri == 'http://fairmat-nfdi.eu/taxonomy/Temperature' + assert temperature.name == 'Temperature' + assert temperature.rank == [] + +class TestHeatAdded: + """ + Test the `HeatAdded` class defined in `properties/thermodynamics.py`. + """ + + # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes + def test_default_quantities(self): + """ + Test the default quantities assigned when creating an instance of the `HeatAdded` class. + """ + heat_added = HeatAdded() + assert heat_added.iri == 'http://fairmat-nfdi.eu/taxonomy/HeatAdded' + assert heat_added.name == 'HeatAdded' + assert heat_added.rank == [] + +class TestWorkDone: + """ + Test the `WorkDone` class defined in `properties/thermodynamics.py`. + """ + + # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes + def test_default_quantities(self): + """ + Test the default quantities assigned when creating an instance of the `WorkDone` class. + """ + work_done = WorkDone() + assert work_done.iri == 'http://fairmat-nfdi.eu/taxonomy/WorkDone' + assert work_done.name == 'WorkDone' + assert work_done.rank == [] + +class TestInternalEnergy: + """ + Test the `InternalEnergy` class defined in `properties/thermodynamics.py`. + """ + + # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes + def test_default_quantities(self): + """ + Test the default quantities assigned when creating an instance of the `InternalEnergy` class. + """ + internal_energy = InternalEnergy() + assert internal_energy.iri == 'http://fairmat-nfdi.eu/taxonomy/InternalEnergy' + assert internal_energy.name == 'InternalEnergy' + assert internal_energy.rank == [] class TestEnthalpy: """ @@ -62,6 +155,35 @@ def test_default_quantities(self): assert entropy.name == 'Entropy' assert entropy.rank == [] +class TestGibbsFreeEnergy: + """ + Test the `GibbsFreeEnergy` class defined in `properties/thermodynamics.py`. + """ + + # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes + def test_default_quantities(self): + """ + Test the default quantities assigned when creating an instance of the `GibbsFreeEnergy` class. + """ + gibbs_free_energy = GibbsFreeEnergy() + assert gibbs_free_energy.iri == 'http://fairmat-nfdi.eu/taxonomy/GibbsFreeEnergy' + assert gibbs_free_energy.name == 'GibbsFreeEnergy' + assert gibbs_free_energy.rank == [] + +class TestHelmholtzFreeEnergy: + """ + Test the `HelmholtzFreeEnergy` class defined in `properties/thermodynamics.py`. + """ + + # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes + def test_default_quantities(self): + """ + Test the default quantities assigned when creating an instance of the `HelmholtzFreeEnergy` class. + """ + helmholtz_free_energy = HelmholtzFreeEnergy() + assert helmholtz_free_energy.iri == 'http://fairmat-nfdi.eu/taxonomy/HelmholtzFreeEnergy' + assert helmholtz_free_energy.name == 'HelmholtzFreeEnergy' + assert helmholtz_free_energy.rank == [] class TestChemicalPotential: """ @@ -82,68 +204,51 @@ def test_default_quantities(self): assert chemical_potential.rank == [] -class TestPressure: - """ - Test the `Pressure` class defined in `properties/thermodynamics.py`. - """ - - # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes - def test_default_quantities(self): - """ - Test the default quantities assigned when creating an instance of the `Pressure` class. - """ - pressure = Pressure() - assert pressure.iri == 'http://fairmat-nfdi.eu/taxonomy/Pressure' - assert pressure.name == 'Pressure' - assert pressure.rank == [] - - -class TestVirial: +class TestHeatCapacityCV: """ - Test the `Virial` class defined in `properties/thermodynamics.py`. + Test the `HeatCapacityCV` class defined in `properties/thermodynamics.py`. """ # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes def test_default_quantities(self): """ - Test the default quantities assigned when creating an instance of the `Virial` class. + Test the default quantities assigned when creating an instance of the `HeatCapacityCV` class. """ - virial = Virial() - assert virial.iri == 'http://fairmat-nfdi.eu/taxonomy/Virial' - assert virial.name == 'Virial' - assert virial.rank == [] + heat_capacity_cv = HeatCapacityCV() + assert heat_capacity_cv.iri == 'http://fairmat-nfdi.eu/taxonomy/HeatCapacityCV' + assert heat_capacity_cv.name == 'HeatCapacityCV' + assert heat_capacity_cv.rank == [] -class TestTemperature: +class TestHeatCapacityCP: """ - Test the `Temperature` class defined in `properties/thermodynamics.py`. + Test the `HeatCapacityCP` class defined in `properties/thermodynamics.py`. """ # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes def test_default_quantities(self): """ - Test the default quantities assigned when creating an instance of the `Temperature` class. + Test the default quantities assigned when creating an instance of the `HeatCapacityCP` class. """ - temperature = Temperature() - assert temperature.iri == 'http://fairmat-nfdi.eu/taxonomy/Temperature' - assert temperature.name == 'Temperature' - assert temperature.rank == [] - + heat_capacity_cp = HeatCapacityCP() + assert heat_capacity_cp.iri == 'http://fairmat-nfdi.eu/taxonomy/HeatCapacityCP' + assert heat_capacity_cp.name == 'HeatCapacityCP' + assert heat_capacity_cp.rank == [] -class TestVolume: +class TestVirial: """ - Test the `Volume` class defined in `properties/thermodynamics.py`. + Test the `Virial` class defined in `properties/thermodynamics.py`. """ # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes def test_default_quantities(self): """ - Test the default quantities assigned when creating an instance of the `Volume` class. + Test the default quantities assigned when creating an instance of the `Virial` class. """ - volume = Volume() - assert volume.iri == 'http://fairmat-nfdi.eu/taxonomy/Volume' - assert volume.name == 'Volume' - assert volume.rank == [] + virial = Virial() + assert virial.iri == 'http://fairmat-nfdi.eu/taxonomy/Virial' + assert virial.name == 'Virial' + assert virial.rank == [] class TestDensity: @@ -178,33 +283,3 @@ def test_default_quantities(self): assert hessian.rank == [] -class TestHeatCapacityCV: - """ - Test the `HeatCapacityCV` class defined in `properties/thermodynamics.py`. - """ - - # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes - def test_default_quantities(self): - """ - Test the default quantities assigned when creating an instance of the `HeatCapacityCV` class. - """ - heat_capacity_cv = HeatCapacityCV() - assert heat_capacity_cv.iri == 'http://fairmat-nfdi.eu/taxonomy/HeatCapacityCV' - assert heat_capacity_cv.name == 'HeatCapacityCV' - assert heat_capacity_cv.rank == [] - - -class TestHeatCapacityCP: - """ - Test the `HeatCapacityCP` class defined in `properties/thermodynamics.py`. - """ - - # ! Include this initial `test_default_quantities` method when testing your PhysicalProperty classes - def test_default_quantities(self): - """ - Test the default quantities assigned when creating an instance of the `HeatCapacityCP` class. - """ - heat_capacity_cp = HeatCapacityCP() - assert heat_capacity_cp.iri == 'http://fairmat-nfdi.eu/taxonomy/HeatCapacityCP' - assert heat_capacity_cp.name == 'HeatCapacityCP' - assert heat_capacity_cp.rank == []