diff --git a/nomad/metainfo/encyclopedia.py b/nomad/metainfo/encyclopedia.py index 0a1de6e35fe1edde20bdb77bf21d205015e73e9c..fa6fc375601e772a184e61c9facac3d6b7869082 100644 --- a/nomad/metainfo/encyclopedia.py +++ b/nomad/metainfo/encyclopedia.py @@ -61,34 +61,90 @@ class WyckoffSet(MSection): variables = SubSection(sub_section=WyckoffVariables.m_def, repeats=False) -class Material(MSection): +class IdealizedStructure(MSection): m_def = Section( a_flask=dict(skip_none=True), a_elastic=dict(type=InnerDoc), description=""" - Section for storing the data that links an Encyclopedia entry into a - specific material. + Contains structural information for an idealized representation of the + material used in the calculation. This idealization is used for + visualizing the material and for calculating the structural properties. + The properties of the idealized structure may slightly vary from the + original structure used in the calculation. """ ) - - # Material-specific - material_type = Quantity( - type=MEnum(bulk="bulk", two_d="2D", one_d="1D", unavailable="unavailable"), + atom_labels = Quantity( + type=str, + shape=['1..*'], description=""" - "Character of physical system's geometry, e.g. bulk, 2D, 1D... ", + Type (element, species) of each atom. """ ) - material_hash = Quantity( - type=str, + atom_positions = Quantity( + type=np.dtype('f8'), + shape=['number_of_atoms', 3], description=""" - A fixed length, unique material identifier in the form of a hash - digest. + Atom positions given in coordinates that are relative to the idealized + cell. + """ + ) + lattice_vectors = Quantity( + type=np.dtype('f8'), + shape=[3, 3], + description=""" + Lattice vectors of the idealized structure. For bulk materials it is + the Bravais cell. This cell is representative and is idealized to match + the detected symmetry properties. + """ + ) + lattice_vectors_primitive = Quantity( + type=np.dtype('f8'), + shape=[3, 3], + description=""" + Lattice vectors of the the primitive unit cell in a form to be visualized + within the idealized cell. This cell is representative and is + idealized to match the detected symmemtry properties. + """ + ) + lattice_parameters = Quantity( + type=np.dtype('f8'), + shape=[6], + description=""" + Lattice parameters of the idealized cell. The lattice parameters can + only be reported consistently after idealization and may not perfectly + correspond to the original simulation cell. + """ + ) + periodicity = Quantity( + type=np.bool, + shape=[3], + description=""" + Automatically detected true periodicity of each lattice direction. May + not correspond to the periodicity used in the calculation. """ ) number_of_atoms = Quantity( type=int, description=""" - Number of atoms in the bravais cell." + Number of atoms in the idealized structure." + """ + ) + cell_volume = Quantity( + type=float, + description=""" + Volume of the idealized cell. The cell volume can only be reported + consistently after idealization and may not perfectly correspond to the + original simulation cell. + """ + ) + + +class Bulk(MSection): + m_def = Section( + a_flask=dict(skip_none=True), + a_elastic=dict(type=InnerDoc), + description=""" + Contains information that is specific to bulk crystalline materials. """ ) bravais_lattice = Quantity( @@ -123,21 +179,6 @@ class Material(MSection): The detected crystal system. One of seven possibilities in three dimensions. """ ) - formula = Quantity( - type=str, - description=""" - Formula giving the composition and occurrences of the elements in the - Hill notation for the irreducible unit cell. - """ - ) - formula_reduced = Quantity( - type=str, - description=""" - Formula giving the composition and occurrences of the elements in the - Hill notation for the irreducible unit cell. In this reduced form the - number of occurences have been divided by the greatest common divisor. - """ - ) has_free_wyckoff_parameters = Quantity( type=bool, description=""" @@ -147,26 +188,6 @@ class Material(MSection): move with possible restrictions set by the symmetry. """ ) - material_classification = Quantity( - type=str, - description=""" - Contains the compound class and classification of the material - according to springer materials in JSON format. - """ - ) - material_name = Quantity( - type=str, - description=""" - Most meaningful name for a material. - """ - ) - periodicity = Quantity( - type=np.bool, - shape=[3], - description=""" - Periodicity of each lattice direction. - """ - ) point_group = Quantity( type=MEnum("1", "-1", "2", "m", "2/m", "222", "mm2", "mmm", "4", "-4", "4/m", "422", "4mm", "-42m", "4/mmm", "3", "-3", "32", "3m", "-3m", "6", "-6", "6/m", "622", "6mm", "-6m2", "6/mmm", "23", "m-3", "432", "-43m", "m-3m"), description=""" @@ -206,70 +227,67 @@ class Material(MSection): Classification of the material according to the historically grown "strukturbericht". """ ) + wyckoff_sets = SubSection(sub_section=WyckoffSet.m_def, repeats=True) - # Calculation-specific - atom_labels = Quantity( - type=str, - shape=['1..*'], + +class Material(MSection): + m_def = Section( + a_flask=dict(skip_none=True), + a_elastic=dict(type=InnerDoc), description=""" - Type (element, species) of each atom, + Section for storing the data that links an Encyclopedia entry into a + specific material. """ ) - atom_positions = Quantity( - type=np.dtype('f8'), - shape=['number_of_atoms', 3], + material_type = Quantity( + type=MEnum(bulk="bulk", two_d="2D", one_d="1D", unavailable="unavailable"), description=""" - Position of each atom, given in relative coordinates. + "Broad structural classification for the material, e.g. bulk, 2D, 1D... ", """ ) - cell_normalized = Quantity( - type=np.dtype('f8'), - shape=[3, 3], + material_hash = Quantity( + type=str, description=""" - Unit cell in normalized form, meaning the bravais cell. This cell is - representative and is idealized to match the detected symmetry - properties. + A fixed length, unique material identifier in the form of a hash + digest. """ ) - cell_primitive = Quantity( - type=np.dtype('f8'), - shape=[3, 3], + material_name = Quantity( + type=str, description=""" - Definition of the primitive unit cell in a form to be visualized well - within the normalized cell. This cell is representative and is - idealized to match the detected symmemtry properties. + Most meaningful name for a material. """ ) - - wyckoff_sets = SubSection(sub_section=WyckoffSet.m_def, repeats=True) - - cell_angles_string = Quantity( + material_classification = Quantity( type=str, description=""" - A summary of the cell angles, part of material definition. + Contains the compound class and classification of the material + according to springer materials in JSON format. """ ) - cell_volume = Quantity( - type=float, + formula = Quantity( + type=str, description=""" - Cell volume for a specific calculation. The cell volume can only be - reported consistently after normalization. Thus it corresponds to the - normalized cell that is idealized to fit the detected symmetry and may - not perfectly correspond to the original simulation cell. + Formula giving the composition and occurrences of the elements in the + Hill notation. For periodic materials the formula is calculated fom the + primitive unit cell. """ ) - lattice_parameters = Quantity( - type=np.dtype('f8'), - shape=[6], + formula_reduced = Quantity( + type=str, description=""" - Lattice parameters of a specific calculation. The lattice parameters - can only be reported consistently after normalization. Thus they - correspond to the normalized cell that is idealized to fit the detected - symmetry and may not perfectly correspond to the original simulation - cell. + Formula giving the composition and occurrences of the elements in the + Hill notation whre the number of occurences have been divided by the + greatest common divisor. """ ) + # The idealized structure for this material + idealized_structure = SubSection(sub_section=IdealizedStructure.m_def, repeats=False) + + # Bulk-specific properties + bulk = SubSection(sub_section=Bulk.m_def, repeats=False) + class Method(MSection): m_def = Section( @@ -498,6 +516,13 @@ class ElectronicBandStructure(MSection): Stores information related to an electronic band structure. """ ) + scc_index = Quantity( + type=int, + description=""" + Index of the single configuration calculation that contains the band + structure. + """ + ) fermi_level = Quantity( type=float, unit=units.J, @@ -551,6 +576,13 @@ class ElectronicDOS(MSection): Stores the electronic density of states (DOS). """ ) + scc_index = Quantity( + type=int, + description=""" + Index of the single configuration calculation that contains the density + of states. + """ + ) fermi_level = Quantity( type=float, unit=units.J, @@ -588,12 +620,6 @@ class Properties(MSection): are used by the NOMAD Encyclopedia. """ ) - scc_index = Quantity( - type=int, - description=""" - Index of a representative single configuration calculation." - """ - ) atomic_density = Quantity( type=float, unit=units.m**(-3), @@ -628,12 +654,6 @@ class Encyclopedia(MSection): Section which stores information for the NOMAD Encyclopedia. """ ) - mainfile_uri = Quantity( - type=str, - description=""" - Path of the main file. - """ - ) material = SubSection(sub_section=Material.m_def, repeats=False) method = SubSection(sub_section=Method.m_def, repeats=False) properties = SubSection(sub_section=Properties.m_def, repeats=False) diff --git a/nomad/normalizing/encyclopedia.py b/nomad/normalizing/encyclopedia.py index d6cfd71760244493d231c3069284c41060ef64ae..7307f58e765aa4a3ab59cbf37db2b0820256f279 100644 --- a/nomad/normalizing/encyclopedia.py +++ b/nomad/normalizing/encyclopedia.py @@ -44,7 +44,9 @@ from nomad.metainfo.encyclopedia import ( Properties, RunType, WyckoffSet, + Bulk, WyckoffVariables, + IdealizedStructure, ElectronicBandStructure, BandGap, ) @@ -233,13 +235,6 @@ class EncyclopediaNormalizer(Normalizer): method.method_type = method_id return repr_method, method_id - def mainfile_uri(self, encyclopedia: Encyclopedia): - entry_info = self._backend["section_entry_info"][0] - upload_id = entry_info["upload_id"] - mainfile_path = entry_info["mainfile"] - uri = f"nmd://R{upload_id}/data/{mainfile_path}" - encyclopedia.mainfile_uri = uri - # def similar_materials(self) -> None: # pass @@ -264,30 +259,30 @@ class EncyclopediaNormalizer(Normalizer): # def number_of_calculations(self) -> None: # pass - def fill(self, ctx: Context): + def fill(self, context: Context): # Fill structure related metainfo struct: Any = None - if ctx.material_type == Material.material_type.type.bulk: + if context.material_type == Material.material_type.type.bulk: struct = MaterialBulkNormalizer(self.backend, self.logger) - elif ctx.material_type == Material.material_type.type.two_d: + elif context.material_type == Material.material_type.type.two_d: struct = Material2DNormalizer(self.backend, self.logger) - elif ctx.material_type == Material.material_type.type.one_d: + elif context.material_type == Material.material_type.type.one_d: struct = Material1DNormalizer(self.backend, self.logger) if struct is not None: - struct.normalize(ctx) + struct.normalize(context) # Fill method related metainfo method = None - if ctx.method_type == Method.method_type.type.DFT or ctx.method_type == Method.method_type.type.DFTU: + if context.method_type == Method.method_type.type.DFT or context.method_type == Method.method_type.type.DFTU: method = MethodDFTNormalizer(self._backend, self.logger) - elif ctx.method_type == Method.method_type.type.GW: + elif context.method_type == Method.method_type.type.GW: method = MethodGWNormalizer(self._backend, self.logger) if method is not None: - method.normalize(ctx) + method.normalize(context) # Fill properties related metainfo properties = PropertiesNormalizer(self.backend, self.logger) - properties.normalize(ctx) + properties.normalize(context) def normalize(self, logger=None) -> None: """The caller will automatically log if the normalizer succeeds or ends @@ -303,9 +298,6 @@ class EncyclopediaNormalizer(Normalizer): sec_enc.m_create(Properties) run_type = sec_enc.m_create(RunType) - # Get generic data - self.mainfile_uri(sec_enc) - # Determine run type, stop if unknown run_type_name = self.run_type(run_type) if run_type_name == config.services.unavailable_value: @@ -377,18 +369,18 @@ class MaterialNormalizer(): self.backend = backend self.logger = logger - def atom_labels(self, material: Material, std_atoms: Atoms) -> None: - material.atom_labels = std_atoms.get_chemical_symbols() + def atom_labels(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: + ideal.atom_labels = std_atoms.get_chemical_symbols() - def atom_positions(self, material: Material, std_atoms: Atoms) -> None: - material.atom_positions = std_atoms.get_scaled_positions(wrap=False) + def atom_positions(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: + ideal.atom_positions = std_atoms.get_scaled_positions(wrap=False) @abstractmethod - def cell_normalized(self, material: Material, std_atoms: Atoms) -> None: + def lattice_vectors(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: pass - def cell_volume(self, material: Material, std_atoms: Atoms) -> None: - material.cell_volume = float(std_atoms.get_volume() * 1e-10**3) + def cell_volume(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: + ideal.cell_volume = float(std_atoms.get_volume() * 1e-10**3) def formula(self, material: Material, names: List[str], counts: List[int]) -> None: formula = structure.get_formula_string(names, counts) @@ -403,11 +395,11 @@ class MaterialNormalizer(): norm_hash_string = structure.get_symmetry_string(spg_number, wyckoff_sets) material.material_hash = hash(norm_hash_string) - def number_of_atoms(self, material: Material, std_atoms: Atoms) -> None: - material.number_of_atoms = len(std_atoms) + def number_of_atoms(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: + ideal.number_of_atoms = len(std_atoms) @abstractmethod - def normalize(self, ctx: Context) -> None: + def normalize(self, context: Context) -> None: pass @@ -419,30 +411,30 @@ class MaterialBulkNormalizer(MaterialNormalizer): orig_volume = repr_system.get_volume() * (1e-10)**3 properties.atomic_density = float(orig_n_atoms / orig_volume) - def bravais_lattice(self, material: Material, section_symmetry: Section) -> None: + def bravais_lattice(self, bulk: Bulk, section_symmetry: Section) -> None: bravais_lattice = section_symmetry["bravais_lattice"] - material.bravais_lattice = bravais_lattice + bulk.bravais_lattice = bravais_lattice - def cell_normalized(self, material: Material, std_atoms: Atoms) -> None: + def lattice_vectors(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: cell_normalized = std_atoms.get_cell() cell_normalized *= 1e-10 - material.cell_normalized = cell_normalized + ideal.lattice_vectors = cell_normalized - def cell_primitive(self, material: Material, prim_atoms: Atoms) -> None: + def lattice_vectors_primitive(self, ideal: IdealizedStructure, prim_atoms: Atoms) -> None: cell_prim = prim_atoms.get_cell() cell_prim *= 1e-10 - material.cell_primitive = cell_prim + ideal.lattice_vectors_primitive = cell_prim - def crystal_system(self, material: Material, section_symmetry: Section) -> None: - material.crystal_system = section_symmetry["crystal_system"] + def crystal_system(self, bulk: Bulk, section_symmetry: Section) -> None: + bulk.crystal_system = section_symmetry["crystal_system"] - def has_free_wyckoff_parameters(self, material: Material, symmetry_analyzer: SymmetryAnalyzer) -> None: + def has_free_wyckoff_parameters(self, bulk: Bulk, symmetry_analyzer: SymmetryAnalyzer) -> None: has_free_param = symmetry_analyzer.get_has_free_wyckoff_parameters() - material.has_free_wyckoff_parameters = has_free_param + bulk.has_free_wyckoff_parameters = has_free_param - def lattice_parameters(self, material: Material, std_atoms: Atoms) -> None: + def lattice_parameters(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: cell_normalized = std_atoms.get_cell() * 1E-10 - material.lattice_parameters = structure.get_lattice_parameters(cell_normalized) + ideal.lattice_parameters = structure.get_lattice_parameters(cell_normalized) def mass_density(self, properties: Properties, repr_system: Atoms) -> None: mass = structure.get_summed_atomic_mass(repr_system.get_atomic_numbers()) @@ -570,19 +562,19 @@ class MaterialBulkNormalizer(MaterialNormalizer): material.material_name = name - def periodicity(self, material: Material) -> None: - material.periodicity = np.array([True, True, True], dtype=np.bool) + def periodicity(self, ideal: IdealizedStructure) -> None: + ideal.periodicity = np.array([True, True, True], dtype=np.bool) - def point_group(self, material: Material, section_symmetry: Section) -> None: + def point_group(self, bulk: Bulk, section_symmetry: Section) -> None: point_group = section_symmetry["point_group"] - material.point_group = point_group + bulk.point_group = point_group - def space_group_number(self, material: Material, spg_number: int) -> None: - material.space_group_number = spg_number + def space_group_number(self, bulk: Bulk, spg_number: int) -> None: + bulk.space_group_number = spg_number - def space_group_international_short_symbol(self, material: Material, symmetry_analyzer: SymmetryAnalyzer) -> None: + def space_group_international_short_symbol(self, bulk: Bulk, symmetry_analyzer: SymmetryAnalyzer) -> None: spg_int_symb = symmetry_analyzer.get_space_group_international_short() - material.space_group_international_short_symbol = spg_int_symb + bulk.space_group_international_short_symbol = spg_int_symb def material_classification(self, material: Material, section_system: Section) -> None: try: @@ -606,7 +598,7 @@ class MaterialBulkNormalizer(MaterialNormalizer): if classes: material.material_classification = json.dumps(classes) - def structure_type(self, material: Material, section_system: Section) -> None: + def structure_type(self, bulk: Bulk, section_system: Section) -> None: try: sec_prototype = section_system["section_prototype"][0] notes = sec_prototype.tmp['prototype_notes'] @@ -638,18 +630,18 @@ class MaterialBulkNormalizer(MaterialNormalizer): } enc_note = note_map.get(notes, None) if enc_note is not None: - material.structure_type = enc_note + bulk.structure_type = enc_note - def structure_prototype(self, material: Material, section_system: Section) -> None: + def structure_prototype(self, bulk: Bulk, section_system: Section) -> None: try: sec_prototype = section_system["section_prototype"][0] name = sec_prototype.tmp['prototype_name'] except Exception: return - material.structure_prototype = name + bulk.structure_prototype = name - def strukturbericht_designation(self, material: Material, section_system: Section) -> None: + def strukturbericht_designation(self, bulk: Bulk, section_system: Section) -> None: try: sec_prototype = section_system["section_prototype"][0] strukturbericht = sec_prototype.tmp["strukturbericht_designation"] @@ -658,11 +650,11 @@ class MaterialBulkNormalizer(MaterialNormalizer): # In the current GUI we replace LaTeX with plain text strukturbericht = re.sub('[$_{}]', '', strukturbericht) - material.strukturbericht_designation = strukturbericht + bulk.strukturbericht_designation = strukturbericht - def wyckoff_sets(self, material: Material, wyckoff_sets: Dict) -> None: + def wyckoff_sets(self, bulk: Bulk, wyckoff_sets: Dict) -> None: for group in wyckoff_sets: - wset = material.m_create(WyckoffSet) + wset = bulk.m_create(WyckoffSet) if group.x is not None or group.y is not None or group.z is not None: variables = wset.m_create(WyckoffVariables) if group.x is not None: @@ -675,9 +667,9 @@ class MaterialBulkNormalizer(MaterialNormalizer): wset.element = group.element wset.wyckoff_letter = group.wyckoff_letter - def normalize(self, ctx: Context) -> None: + def normalize(self, context: Context) -> None: # Fetch resources - sec_system = ctx.representative_system + sec_system = context.representative_system sec_enc = self.backend.get_mi2_section(Encyclopedia.m_def) material = sec_enc.material properties = sec_enc.properties @@ -690,51 +682,53 @@ class MaterialBulkNormalizer(MaterialNormalizer): wyckoff_sets = symmetry_analyzer.get_wyckoff_sets_conventional(return_parameters=True) names, counts = structure.get_hill_decomposition(prim_atoms.get_chemical_symbols(), reduced=False) greatest_common_divisor = reduce(gcd, counts) - ctx.greatest_common_divisor = greatest_common_divisor + context.greatest_common_divisor = greatest_common_divisor reduced_counts = np.array(counts) / greatest_common_divisor # Fill structural information + bulk = material.m_create(Bulk) + ideal = material.m_create(IdealizedStructure) self.mass_density(properties, repr_atoms) self.material_hash(material, spg_number, wyckoff_sets) - self.number_of_atoms(material, std_atoms) - self.atom_labels(material, std_atoms) - self.atom_positions(material, std_atoms) + self.number_of_atoms(ideal, std_atoms) + self.atom_labels(ideal, std_atoms) + self.atom_positions(ideal, std_atoms) self.atomic_density(properties, repr_atoms) - self.bravais_lattice(material, sec_symmetry) - self.cell_normalized(material, std_atoms) - self.cell_volume(material, std_atoms) - self.crystal_system(material, sec_symmetry) - self.cell_primitive(material, prim_atoms) + self.bravais_lattice(bulk, sec_symmetry) + self.lattice_vectors(ideal, std_atoms) + self.cell_volume(ideal, std_atoms) + self.crystal_system(bulk, sec_symmetry) + self.lattice_vectors_primitive(ideal, prim_atoms) self.formula(material, names, counts) self.formula_reduced(material, names, reduced_counts) - self.has_free_wyckoff_parameters(material, symmetry_analyzer) - self.lattice_parameters(material, std_atoms) + self.has_free_wyckoff_parameters(bulk, symmetry_analyzer) + self.lattice_parameters(ideal, std_atoms) self.material_name(material, names, reduced_counts) self.material_classification(material, sec_system) - self.periodicity(material) - self.point_group(material, sec_symmetry) - self.space_group_number(material, spg_number) - self.space_group_international_short_symbol(material, symmetry_analyzer) - self.structure_type(material, sec_system) - self.structure_prototype(material, sec_system) - self.strukturbericht_designation(material, sec_system) - self.wyckoff_sets(material, wyckoff_sets) + self.periodicity(ideal) + self.point_group(bulk, sec_symmetry) + self.space_group_number(bulk, spg_number) + self.space_group_international_short_symbol(bulk, symmetry_analyzer) + self.structure_type(bulk, sec_system) + self.structure_prototype(bulk, sec_system) + self.strukturbericht_designation(bulk, sec_system) + self.wyckoff_sets(bulk, wyckoff_sets) class Material2DNormalizer(MaterialNormalizer): """Processes structure related metainfo for Encyclopedia 2D structures. """ - def cell_normalized(self, material: Material, std_atoms: Atoms) -> None: + def lattice_vectors(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: cell_normalized = std_atoms.get_cell() cell_normalized *= 1e-10 - material.cell_normalized = cell_normalized + ideal.lattice_vectors = cell_normalized - def cell_primitive(self, material: Material, prim_atoms: Atoms) -> None: + def lattice_vectors_primitive(self, ideal: IdealizedStructure, prim_atoms: Atoms) -> None: cell_prim = prim_atoms.get_cell() cell_prim *= 1e-10 - material.cell_primitive = cell_prim + ideal.lattice_vectors_primitive = cell_prim - def lattice_parameters(self, material: Material, std_atoms: Atoms, periodicity: np.array) -> None: + def lattice_parameters(self, ideal: IdealizedStructure, std_atoms: Atoms, periodicity: np.array) -> None: # 2D systems only have three lattice parameter: two length and angle between them periodic_indices = np.where(np.array(periodicity) == True)[0] # noqa: E712 cell = std_atoms.get_cell() @@ -744,11 +738,11 @@ class Material2DNormalizer(MaterialNormalizer): b = np.linalg.norm(b_vec) alpha = np.clip(np.dot(a_vec, b_vec) / (a * b), -1.0, 1.0) alpha = np.arccos(alpha) - material.lattice_parameters = np.array([a, b, 0.0, alpha, 0.0, 0.0]) + ideal.lattice_parameters = np.array([a, b, 0.0, alpha, 0.0, 0.0]) - def periodicity(self, material: Material, std_atoms: Atoms) -> None: + def periodicity(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: # MatID already provides the correct periodicity - material.periodicity = std_atoms.get_pbc() + ideal.periodicity = std_atoms.get_pbc() def get_symmetry_analyzer(self, original_system: Atoms) -> SymmetryAnalyzer: # Get dimension of system by also taking into account the covalent radii @@ -787,11 +781,11 @@ class Material2DNormalizer(MaterialNormalizer): ) return symmetry_analyzer - def normalize(self, ctx: Context) -> None: + def normalize(self, context: Context) -> None: # Fetch resources sec_enc = self.backend.get_mi2_section(Encyclopedia.m_def) material = sec_enc.material - repr_atoms = ctx.representative_system.tmp["representative_atoms"] # Temporary value stored by SystemNormalizer + repr_atoms = context.representative_system.tmp["representative_atoms"] # Temporary value stored by SystemNormalizer symmetry_analyzer = self.get_symmetry_analyzer(repr_atoms) spg_number = symmetry_analyzer.get_space_group_number() wyckoff_sets = symmetry_analyzer.get_wyckoff_sets_conventional(return_parameters=False) @@ -799,20 +793,21 @@ class Material2DNormalizer(MaterialNormalizer): prim_atoms = symmetry_analyzer.get_primitive_system() names, counts = structure.get_hill_decomposition(prim_atoms.get_chemical_symbols(), reduced=False) greatest_common_divisor = reduce(gcd, counts) - ctx.greatest_common_divisor = greatest_common_divisor + context.greatest_common_divisor = greatest_common_divisor reduced_counts = np.array(counts) / greatest_common_divisor # Fill metainfo - self.periodicity(material, std_atoms) + ideal = material.m_create(IdealizedStructure) + self.periodicity(ideal, std_atoms) self.material_hash(material, spg_number, wyckoff_sets) - self.number_of_atoms(material, std_atoms) - self.atom_labels(material, std_atoms) - self.atom_positions(material, std_atoms) - self.cell_normalized(material, std_atoms) - self.cell_primitive(material, prim_atoms) + self.number_of_atoms(ideal, std_atoms) + self.atom_labels(ideal, std_atoms) + self.atom_positions(ideal, std_atoms) + self.lattice_vectors(ideal, std_atoms) + self.lattice_vectors_primitive(ideal, prim_atoms) self.formula(material, names, counts) self.formula_reduced(material, names, reduced_counts) - self.lattice_parameters(material, std_atoms, material.periodicity) + self.lattice_parameters(ideal, std_atoms, ideal.periodicity) class Material1DNormalizer(MaterialNormalizer): @@ -832,19 +827,19 @@ class Material1DNormalizer(MaterialNormalizer): hash_val = hash(hash_seed) material.material_hash = hash_val - def cell_normalized(self, material: Material, std_atoms: Atoms) -> None: + def lattice_vectors(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None: cell_normalized = std_atoms.get_cell() cell_normalized *= 1e-10 - material.cell_normalized = cell_normalized + ideal.lattice_vectors = cell_normalized - def lattice_parameters(self, material: Material, std_atoms: Atoms, periodicity: np.array) -> None: + def lattice_parameters(self, ideal: IdealizedStructure, std_atoms: Atoms, periodicity: np.array) -> None: # 1D systems only have one lattice parameter: length in periodic dimension periodic_indices = np.where(np.array(periodicity) == True)[0] # noqa: E712 cell = std_atoms.get_cell() a = np.linalg.norm(cell[periodic_indices[0], :]) * 1e-10 - material.lattice_parameters = np.array([a, 0.0, 0.0, 0.0, 0.0, 0.0]) + ideal.lattice_parameters = np.array([a, 0.0, 0.0, 0.0, 0.0, 0.0]) - def periodicity(self, material: Material, prim_atoms: Atoms) -> None: + def periodicity(self, ideal: IdealizedStructure, prim_atoms: Atoms) -> None: # Get dimension of system by also taking into account the covalent radii dimensions = matid.geometry.get_dimensions(prim_atoms, [True, True, True]) basis_dimensions = np.linalg.norm(prim_atoms.get_cell(), axis=1) @@ -856,7 +851,7 @@ class Material1DNormalizer(MaterialNormalizer): if sum(periodicity) != 1: raise ValueError("Could not detect the periodic dimensions in a 1D system.") - material.periodicity = periodicity + ideal.periodicity = periodicity def get_structure_fingerprint(self, prim_atoms: Atoms) -> str: """Calculates a numeric fingerprint that coarsely encodes the atomic @@ -1000,9 +995,9 @@ class Material1DNormalizer(MaterialNormalizer): return std_atoms - def normalize(self, ctx: Context) -> None: + def normalize(self, context: Context) -> None: # Fetch resources - sec_system = ctx.representative_system + sec_system = context.representative_system sec_enc = self.backend.get_mi2_section(Encyclopedia.m_def) material = sec_enc.material repr_atoms = sec_system.tmp["representative_atoms"] # Temporary value stored by SystemNormalizer @@ -1011,20 +1006,21 @@ class Material1DNormalizer(MaterialNormalizer): prim_atoms.set_pbc(True) names, counts = structure.get_hill_decomposition(prim_atoms.get_chemical_symbols(), reduced=False) greatest_common_divisor = reduce(gcd, counts) - ctx.greatest_common_divisor = greatest_common_divisor + context.greatest_common_divisor = greatest_common_divisor reduced_counts = np.array(counts) / greatest_common_divisor # Fill metainfo - self.periodicity(material, prim_atoms) - std_atoms = self.get_std_atoms(material.periodicity, prim_atoms) - self.number_of_atoms(material, std_atoms) - self.atom_labels(material, std_atoms) - self.atom_positions(material, std_atoms) - self.cell_normalized(material, std_atoms) + ideal = material.m_create(IdealizedStructure) + self.periodicity(ideal, prim_atoms) + std_atoms = self.get_std_atoms(ideal.periodicity, prim_atoms) + self.number_of_atoms(ideal, std_atoms) + self.atom_labels(ideal, std_atoms) + self.atom_positions(ideal, std_atoms) + self.lattice_vectors(ideal, std_atoms) self.formula(material, names, counts) self.formula_reduced(material, names, reduced_counts) self.material_hash_1d(material, std_atoms) - self.lattice_parameters(material, std_atoms, material.periodicity) + self.lattice_parameters(ideal, std_atoms, ideal.periodicity) class MethodNormalizer(): @@ -1159,7 +1155,7 @@ class MethodNormalizer(): pass @abstractmethod - def normalize(self, ctx: Context) -> None: + def normalize(self, context: Context) -> None: pass @@ -1421,14 +1417,14 @@ class MethodDFTNormalizer(MethodNormalizer): return shortname - def normalize(self, ctx: Context) -> None: + def normalize(self, context: Context) -> None: # Fetch resources - repr_method = ctx.representative_method - repr_system = ctx.representative_system + repr_method = context.representative_method + repr_system = context.representative_system sec_enc = self.backend.get_mi2_section(Encyclopedia.m_def) method = sec_enc.method material = sec_enc.material - settings_basis_set = get_basis_set_settings(ctx, self.backend, self.logger) + settings_basis_set = get_basis_set_settings(context, self.backend, self.logger) # Fill metainfo self.basis_set_type(method) @@ -1467,9 +1463,9 @@ class MethodGWNormalizer(MethodDFTNormalizer): def gw_type(self, method: Method, repr_method: Section) -> None: method.gw_type = repr_method["electronic_structure_method"] - def normalize(self, ctx: Context) -> None: + def normalize(self, context: Context) -> None: # Fetch resources - repr_method = ctx.representative_method + repr_method = context.representative_method sec_enc = self.backend.get_mi2_section(Encyclopedia.m_def) method = sec_enc.method @@ -1477,7 +1473,7 @@ class MethodGWNormalizer(MethodDFTNormalizer): self.code_name(method) self.code_version(method) self.functional_type(method) - self.gw_type(method, ctx.representative_method) + self.gw_type(method, context.representative_method) self.gw_starting_point(method, repr_method) @@ -1647,7 +1643,7 @@ class PropertiesNormalizer(): bz_json = json.dumps(brillouin_zone) band_structure.brillouin_zone = bz_json - def band_structure(self, properties: Properties, run_type: str, material_type: str, representative_scc: Section, sec_system: Section) -> None: + def band_structure(self, properties: Properties, run_type: str, material_type: str, context: Context, sec_system: Section) -> None: """Band structure data following arbitrary path. Currently this function is only taking into account the normalized band @@ -1662,6 +1658,7 @@ class PropertiesNormalizer(): if run_type != RunType.run_type.type.single_point or material_type != Material.material_type.type.bulk: return + representative_scc = context.representative_scc orig_atoms = sec_system.tmp["representative_atoms"] symmetry_analyzer = sec_system["section_symmetry"][0].tmp["symmetry_analyzer"] prim_atoms = symmetry_analyzer.get_primitive_system() @@ -1677,6 +1674,7 @@ class PropertiesNormalizer(): # Loop over bands for band_data in bands: band_structure = ElectronicBandStructure() + band_structure.scc_index = int(context.representative_scc_idx) kpoints = [] energies = [] try: @@ -1770,21 +1768,20 @@ class PropertiesNormalizer(): energies = json.dumps(energy_dict) properties.energies = energies - def normalize(self, ctx: Context) -> None: + def normalize(self, context: Context) -> None: # There needs to be a valid SCC in order to extract any properties - representative_scc = ctx.representative_scc + representative_scc = context.representative_scc if representative_scc is None: return # Fetch resources sec_enc = self.backend.get_mi2_section(Encyclopedia.m_def) properties = sec_enc.properties - properties.scc_index = int(ctx.representative_scc_idx) - run_type = ctx.run_type - material_type = ctx.material_type - sec_system = ctx.representative_system - gcd = ctx.greatest_common_divisor + run_type = context.run_type + material_type = context.material_type + sec_system = context.representative_system + gcd = context.greatest_common_divisor # Save metainfo - self.band_structure(properties, run_type, material_type, representative_scc, sec_system) + self.band_structure(properties, run_type, material_type, context, sec_system) self.energies(properties, gcd, representative_scc) diff --git a/tests/normalizing/test_encyclopedia.py b/tests/normalizing/test_encyclopedia.py index 0bb4d6bd295318e32e1800bbdc8b367f974b9381..3f99590058f55b5152cf9e832ad626601f101927 100644 --- a/tests/normalizing/test_encyclopedia.py +++ b/tests/normalizing/test_encyclopedia.py @@ -73,17 +73,19 @@ def test_1d_metainfo(one_d: LocalBackend): """ enc = one_d.get_mi2_section(Encyclopedia.m_def) # Material - assert enc.material.material_type == "1D" - assert enc.material.formula == "C6H4" - assert enc.material.formula_reduced == "C3H2" - assert np.array_equal(enc.material.periodicity, [True, False, False]) + material = enc.material + assert material.material_type == "1D" + assert material.formula == "C6H4" + assert material.formula_reduced == "C3H2" - # Representative system - assert enc.material.number_of_atoms == 10 - assert enc.material.atom_labels == ["C", "C", "C", "C", "C", "C", "H", "H", "H", "H"] - assert enc.material.atom_positions is not None - assert enc.material.cell_normalized is not None - assert np.allclose(enc.material.lattice_parameters, [4.33793652e-10, 0, 0, 0, 0, 0], atol=0) + # Idealized structure + ideal = enc.material.idealized_structure + assert ideal.number_of_atoms == 10 + assert ideal.atom_labels == ["C", "C", "C", "C", "C", "C", "H", "H", "H", "H"] + assert ideal.atom_positions is not None + assert ideal.lattice_vectors is not None + assert np.array_equal(ideal.periodicity, [True, False, False]) + assert np.allclose(ideal.lattice_parameters, [4.33793652e-10, 0, 0, 0, 0, 0], atol=0) def test_2d_metainfo(two_d: LocalBackend): @@ -91,18 +93,20 @@ def test_2d_metainfo(two_d: LocalBackend): """ enc = two_d.get_mi2_section(Encyclopedia.m_def) # Material - assert enc.material.material_type == "2D" - assert enc.material.formula == "C2" - assert enc.material.formula_reduced == "C" - assert np.array_equal(enc.material.periodicity, [True, True, False]) - - # Representative system - assert enc.material.number_of_atoms == 2 - assert enc.material.atom_labels == ["C", "C"] - assert enc.material.atom_positions is not None - assert enc.material.cell_normalized is not None - assert enc.material.cell_primitive is not None - assert np.allclose(enc.material.lattice_parameters, [2.46559821e-10, 2.46559821e-10, 0, 120 / 180 * np.pi, 0, 0], atol=0) + material = enc.material + assert material.material_type == "2D" + assert material.formula == "C2" + assert material.formula_reduced == "C" + + # Idealized structure + ideal = enc.material.idealized_structure + assert ideal.number_of_atoms == 2 + assert ideal.atom_labels == ["C", "C"] + assert ideal.atom_positions is not None + assert ideal.lattice_vectors is not None + assert ideal.lattice_vectors_primitive is not None + assert np.array_equal(ideal.periodicity, [True, True, False]) + assert np.allclose(ideal.lattice_parameters, [2.46559821e-10, 2.46559821e-10, 0, 120 / 180 * np.pi, 0, 0], atol=0) def test_bulk_metainfo(bulk: LocalBackend): @@ -110,36 +114,40 @@ def test_bulk_metainfo(bulk: LocalBackend): """ enc = bulk.get_mi2_section(Encyclopedia.m_def) # Material - assert enc.material.material_type == "bulk" - assert enc.material.formula == "Si2" - assert enc.material.formula_reduced == "Si" - assert enc.material.material_name == "Silicon" - assert enc.material.structure_type == "diamond" - assert enc.material.structure_prototype == "C" - assert enc.material.strukturbericht_designation == "A4" - - # Symmetry - assert enc.material.crystal_system == "cubic" - assert enc.material.bravais_lattice == "cF" - assert enc.material.has_free_wyckoff_parameters is False - assert enc.material.point_group == "m-3m" - assert enc.material.wyckoff_sets is not None - assert enc.material.space_group_number == 227 - assert enc.material.space_group_international_short_symbol == "Fd-3m" - - # Representative system - assert enc.material.number_of_atoms == 8 - assert enc.material.atom_labels == ["Si", "Si", "Si", "Si", "Si", "Si", "Si", "Si"] - assert enc.material.atom_positions is not None - assert enc.material.cell_normalized is not None - assert enc.material.cell_primitive is not None - assert np.array_equal(enc.material.periodicity, [True, True, True]) - assert enc.material.lattice_parameters is not None - assert enc.material.cell_volume == pytest.approx(5.431**3 * 1e-30) - - # Calculation - assert enc.properties.atomic_density == pytest.approx(4.99402346512432e+28) - assert enc.properties.mass_density == pytest.approx(8 * 28.0855 * 1.6605389e-27 / (5.431**3 * 1e-30)) # Atomic mass in kg/m^3 + material = enc.material + assert material.material_type == "bulk" + assert material.formula == "Si2" + assert material.formula_reduced == "Si" + assert material.material_name == "Silicon" + + # Bulk + bulk = enc.material.bulk + assert bulk.crystal_system == "cubic" + assert bulk.bravais_lattice == "cF" + assert bulk.has_free_wyckoff_parameters is False + assert bulk.point_group == "m-3m" + assert bulk.wyckoff_sets is not None + assert bulk.space_group_number == 227 + assert bulk.structure_type == "diamond" + assert bulk.structure_prototype == "C" + assert bulk.strukturbericht_designation == "A4" + assert bulk.space_group_international_short_symbol == "Fd-3m" + + # Idealized structure + ideal = enc.material.idealized_structure + assert ideal.number_of_atoms == 8 + assert ideal.atom_labels == ["Si", "Si", "Si", "Si", "Si", "Si", "Si", "Si"] + assert ideal.atom_positions is not None + assert ideal.lattice_vectors is not None + assert ideal.lattice_vectors_primitive is not None + assert np.array_equal(ideal.periodicity, [True, True, True]) + assert ideal.lattice_parameters is not None + assert ideal.cell_volume == pytest.approx(5.431**3 * 1e-30) + + # Properties + prop = enc.properties + assert prop.atomic_density == pytest.approx(4.99402346512432e+28) + assert prop.mass_density == pytest.approx(8 * 28.0855 * 1.6605389e-27 / (5.431**3 * 1e-30)) # Atomic mass in kg/m^3 def test_1d_material_identification(): @@ -404,9 +412,10 @@ def test_1d_structure_structure_at_cell_boundary(): "C", ] - assert np.allclose(enc.material.atom_positions, expected_pos) - assert np.array_equal(enc.material.atom_labels, expected_labels) - assert np.allclose(enc.material.cell_normalized, expected_cell) + ideal = enc.material.idealized_structure + assert np.allclose(ideal.atom_positions, expected_pos) + assert np.array_equal(ideal.atom_labels, expected_labels) + assert np.allclose(ideal.lattice_vectors, expected_cell) def test_2d_structure_structure_at_cell_boundary(): @@ -444,9 +453,10 @@ def test_2d_structure_structure_at_cell_boundary(): "C", ] - assert np.allclose(enc.material.atom_positions, expected_pos) - assert np.array_equal(enc.material.atom_labels, expected_labels) - assert np.allclose(enc.material.cell_normalized, expected_cell) + ideal = enc.material.idealized_structure + assert np.allclose(ideal.atom_positions, expected_pos) + assert np.array_equal(ideal.atom_labels, expected_labels) + assert np.allclose(ideal.lattice_vectors, expected_cell) def test_method_dft_metainfo(single_point):