diff --git a/nomad/normalizing/data/aflow_prototypes.py b/nomad/aflow_prototypes.py similarity index 100% rename from nomad/normalizing/data/aflow_prototypes.py rename to nomad/aflow_prototypes.py diff --git a/nomad/atomutils.py b/nomad/atomutils.py index 14035ada1a89fdcd40f1e7fe0ae1ba6468858280..6ec85c0ccab7d7ba76b940bea8d7a530e85a58cb 100644 --- a/nomad/atomutils.py +++ b/nomad/atomutils.py @@ -23,7 +23,7 @@ import numpy as np from scipy.spatial import Voronoi # pylint: disable=no-name-in-module from matid.symmetry import WyckoffSet -from nomad.normalizing.data.aflow_prototypes import aflow_prototypes +from nomad.aflow_prototypes import aflow_prototypes from nomad.constants import atomic_masses diff --git a/nomad/cli/admin/admin.py b/nomad/cli/admin/admin.py index fe493794cc19c6c680533b28e4dde321e7042017..9b970bec458269a479028cabe482f7cd63ade781 100644 --- a/nomad/cli/admin/admin.py +++ b/nomad/cli/admin/admin.py @@ -409,7 +409,7 @@ def write_prototype_data_file(aflow_prototypes: dict, filepath) -> None: def prototypes_update(ctx, filepath, matches_only): if matches_only: - from nomad.normalizing.data.aflow_prototypes import aflow_prototypes + from nomad.aflow_prototypes import aflow_prototypes else: # The basic AFLOW prototype data is available in a Javascript file. Here we # retrieve it and read only the prototype list from it. diff --git a/nomad/datamodel/metainfo/public.py b/nomad/datamodel/metainfo/public.py index 99426480983d89d27289d575ce624282b0417cd3..19243a4f7c4d7358e894c30dc8029b4970f810d2 100644 --- a/nomad/datamodel/metainfo/public.py +++ b/nomad/datamodel/metainfo/public.py @@ -2,9 +2,10 @@ import numpy as np # pylint: disable=unused-import import typing # pylint: disable=unused-import from nomad.metainfo import ( # pylint: disable=unused-import MSection, MCategory, Category, Package, Quantity, Section, SubSection, SectionProxy, - Reference, MEnum + Reference, MEnum, derived ) from nomad.metainfo.legacy import LegacyDefinition +import nomad.atomutils m_package = Package( @@ -5157,6 +5158,30 @@ class section_thermodynamical_properties(MSection): ''', a_legacy=LegacyDefinition(name='thermodynamical_property_heat_capacity_C_v')) + @derived( + type=np.dtype(np.float64), + shape=['number_of_thermodynamical_property_values'], + unit='joule / kelvin * kilogram', + description=''' + Stores the specific heat capacity at constant volume. + ''', + a_legacy=LegacyDefinition(name='specific_heat_capacity'), + cached=True + ) + def specific_heat_capacity(self) -> np.array: + """Returns the specific heat capacity by dividing the heat capacity per + cell with the mass of the atoms in the cell. + """ + s_frame_sequence = self.m_parent + first_frame = s_frame_sequence.frame_sequence_local_frames_ref[0] + system = first_frame.single_configuration_calculation_to_system_ref + atomic_numbers = system.atom_species + mass_per_unit_cell = nomad.atomutils.get_summed_atomic_mass(atomic_numbers) + heat_capacity = self.thermodynamical_property_heat_capacity_C_v + specific_heat_capacity = heat_capacity / mass_per_unit_cell + + return specific_heat_capacity + thermodynamical_property_temperature = Quantity( type=np.dtype(np.float64), shape=['number_of_thermodynamical_property_values'], diff --git a/nomad/metainfo/encyclopedia.py b/nomad/metainfo/encyclopedia.py index 1553793d43bc2ae663d031ea2c66f4d406e8c67c..aed7e9c183e22137f20e45546537f9088feac730 100644 --- a/nomad/metainfo/encyclopedia.py +++ b/nomad/metainfo/encyclopedia.py @@ -1,7 +1,7 @@ import numpy as np from elasticsearch_dsl import InnerDoc from nomad.metainfo import MSection, Section, SubSection, Quantity, MEnum, Reference -from nomad.datamodel.metainfo.public import section_k_band, section_dos +from nomad.datamodel.metainfo.public import section_k_band, section_dos, section_thermodynamical_properties class WyckoffVariables(MSection): @@ -452,6 +452,27 @@ class Properties(MSection): Reference to an electronic density of states. """ ) + phonon_band_structure = Quantity( + type=Reference(section_k_band.m_def), + shape=[], + description=""" + Reference to a phonon band structure. + """ + ) + phonon_dos = Quantity( + type=Reference(section_dos.m_def), + shape=[], + description=""" + Reference to a phonon density of states. + """ + ) + thermodynamical_properties = Quantity( + type=Reference(section_thermodynamical_properties.m_def), + shape=[], + description=""" + Reference to a specific heat capacity. + """ + ) class section_encyclopedia(MSection): diff --git a/nomad/normalizing/data/__init__.py b/nomad/normalizing/data/__init__.py deleted file mode 100644 index 355a267cc43cb62fc063f74c37d50632d442372c..0000000000000000000000000000000000000000 --- a/nomad/normalizing/data/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2018 Markus Scheidgen -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an"AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/nomad/normalizing/encyclopedia/encyclopedia.py b/nomad/normalizing/encyclopedia/encyclopedia.py index 786cb0d4147926057b3a12e4f1c526183162d205..37ce29152fce21092d6e8b170f81646c9b70d84e 100644 --- a/nomad/normalizing/encyclopedia/encyclopedia.py +++ b/nomad/normalizing/encyclopedia/encyclopedia.py @@ -279,14 +279,9 @@ class EncyclopediaNormalizer(Normalizer): ) return - # Get the method type, stop if unknown + # Get the method type. For now, we allow unknown method type. + # Mostly to allow phonon calculations through. representative_method, method_type = self.method_type(method, calc_type) - if method_type == config.services.unavailable_value: - self.logger.info( - "Unsupported method type for encyclopedia, encyclopedia metainfo not created.", - enc_status="unsupported_method_type", - ) - return # Get representative scc try: diff --git a/nomad/normalizing/encyclopedia/properties.py b/nomad/normalizing/encyclopedia/properties.py index d13ebebd120d6e5e9572ec18cfdd051afec0aecb..501c8bb829ec90d9db894f688c79109eb76f0f92 100644 --- a/nomad/normalizing/encyclopedia/properties.py +++ b/nomad/normalizing/encyclopedia/properties.py @@ -102,20 +102,51 @@ class PropertiesNormalizer(): def has_fermi_surface(self) -> None: pass - def has_thermal_properties(self) -> None: - pass - - def phonon_dispersion(self) -> None: - pass - - def phonon_dos(self) -> None: - pass - - def specific_heat_cv(self) -> None: - pass + def thermodynamical_properties(self, properties: Properties) -> None: + """Tries to resolve a reference to a representative set of + thermodynamical properties. Will not store any data if it cannot be + resolved unambiguously. + """ + try: + resolved_section = None + frame_sequences = self.backend.entry_archive.section_run[0].section_frame_sequence + for frame_sequence in frame_sequences: + thermodynamical_props = frame_sequence.section_thermodynamical_properties + for thermodynamical_prop in thermodynamical_props: + if resolved_section is None: + resolved_section = thermodynamical_prop + else: + self.logger("Could not unambiguously select data to display for specific heat.") + return + except Exception: + return + if resolved_section is not None: + properties.thermodynamical_properties = resolved_section - def helmholtz_free_energy(self) -> None: - pass + def phonon_band_structure(self, properties: Properties, context: Context) -> None: + """Tries to resolve a reference to a representative phonon band + structure. Will not store any data if it cannot be resolved + unambiguously. + """ + try: + representative_scc = context.representative_scc + bands = representative_scc.section_k_band + if bands is None: + return + + representative_phonon_band = None + for band in bands: + if band.band_structure_kind == "vibrational": + if representative_phonon_band is None: + representative_phonon_band = band + else: + self.logger("Could not unambiguously select data to display for a phonon band structure.") + return + + except Exception: + return + if representative_phonon_band is not None: + properties.phonon_band_structure = representative_phonon_band def energies(self, properties: Properties, gcd: int, representative_scc: Section) -> None: energy_dict = {} @@ -152,3 +183,8 @@ class PropertiesNormalizer(): # Save metainfo self.electronic_band_structure(properties, calc_type, material_type, context, sec_system) self.energies(properties, gcd, representative_scc) + + # Phonon calculations have a specific set of properties to extract + if context.calc_type == Calculation.calculation_type.type.phonon_calculation: + self.thermodynamical_properties(properties) + self.phonon_band_structure(properties, context) diff --git a/tests/normalizing/test_encyclopedia.py b/tests/normalizing/test_encyclopedia.py index d60de2c32a6044b3afaf098d9ee8fd061819470b..c80cb50fc13bfe67ab403ba5d66f2d3ce34546b0 100644 --- a/tests/normalizing/test_encyclopedia.py +++ b/tests/normalizing/test_encyclopedia.py @@ -54,15 +54,6 @@ def test_molecular_dynamics(molecular_dynamics: EntryArchive): assert calc_type == "molecular dynamics" -# Disabled until the method information can be retrieved -# def test_phonon(phonon: EntryArchive): - # """Tests that geometry optimizations are correctly processed." - # """ - # enc = phonon.get_mi2_section(Encyclopedia.m_def) - # calc_type = enc.calc_type.calc_type - # assert calc_type == "phonon calculation" - - def test_1d_metainfo(one_d: EntryArchive): """Tests that metainfo for 1D systems is correctly processed. """ @@ -476,3 +467,29 @@ def test_hashes_undefined(hash_vasp): # not really need the method to be accurately defined. assert method_hash is None assert group_eos_hash is None + + +def test_phonon(phonon: EntryArchive): + """Tests that phonon calculations are correctly processed. + """ + enc = phonon.entry_archive.section_encyclopedia + calc_type = enc.calculation.calculation_type + prop = enc.properties + band = prop.phonon_band_structure + thermo_props = prop.thermodynamical_properties + assert calc_type == "phonon calculation" + + # Check band structure + assert band is not None + assert band.band_structure_kind == "vibrational" + for segment in band.section_k_band_segment: + assert segment.band_energies is not None + assert segment.band_k_points is not None + assert segment.band_segm_labels is not None + + # Check thermodynamical properties + assert thermo_props is not None + assert thermo_props.thermodynamical_property_heat_capacity_C_v is not None + assert thermo_props.specific_heat_capacity is not None + assert thermo_props.thermodynamical_property_temperature is not None + assert thermo_props.vibrational_free_energy_at_constant_volume is not None