Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/nomad_simulations/schema_packages/numerical_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,15 @@ class KMesh(Mesh):
""",
)

symmetrized_points = Quantity(
type=np.float64,
shape=['*', 3],
description="""
List of the mesh points after applying symmetry operations in units of the `reciprocal_lattice_vectors`. This quantity
is typically smaller than `all_points` and contains only the unique points in the Brillouin zone.
""",
)

high_symmetry_points = Quantity(
type=JSON,
description="""
Expand Down Expand Up @@ -785,11 +794,6 @@ class KSpace(NumericalSettings):

k_line_path = SubSection(sub_section=KLinePath.m_def)

def __init__(self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs):
super().__init__(m_def, m_context, **kwargs)
# Set the name of the section
self.name = self.m_def.name

def resolve_reciprocal_lattice_vectors(
self, model_systems: list[ModelSystem], logger: 'BoundLogger'
) -> Optional[pint.Quantity]:
Expand Down
31 changes: 20 additions & 11 deletions src/nomad_simulations/schema_packages/outputs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import TYPE_CHECKING, Optional

from nomad_simulations.schema_packages.utils.electronic import bandstructure_to_bandgap, bandstructure_to_dos
import numpy as np
from nomad.datamodel.data import ArchiveSection
from nomad.metainfo import Quantity, SubSection

if TYPE_CHECKING:
Expand All @@ -27,7 +27,6 @@
HoppingMatrix,
HybridizationFunction,
KineticEnergy,
Occupancy,
Permittivity,
PotentialEnergy,
QuasiparticleWeight,
Expand Down Expand Up @@ -78,23 +77,23 @@ class Outputs(Time):

hopping_matrices = SubSection(sub_section=HoppingMatrix.m_def, repeats=True)

electronic_eigenvalues = SubSection(
sub_section=ElectronicEigenvalues.m_def, repeats=True
electronic_band_structures = SubSection(
sub_section=ElectronicBandStructure.m_def, # repeats=True
)

electronic_band_gaps = SubSection(sub_section=ElectronicBandGap.m_def, repeats=True)
electronic_eigenvalues = SubSection(
sub_section=ElectronicEigenvalues.m_def, repeats=True
) # TODO: @EBB2675 should we remove these?

electronic_dos = SubSection(
sub_section=ElectronicDensityOfStates.m_def, repeats=True
sub_section=ElectronicDensityOfStates.m_def, # repeats=True
)

fermi_surfaces = SubSection(sub_section=FermiSurface.m_def, repeats=True)

electronic_band_structures = SubSection(
sub_section=ElectronicBandStructure.m_def, repeats=True
electronic_band_gaps = SubSection(
sub_section=ElectronicBandGap.m_def, # repeats=True
)

occupancies = SubSection(sub_section=Occupancy.m_def, repeats=True)
fermi_surfaces = SubSection(sub_section=FermiSurface.m_def, repeats=True)

electronic_greens_functions = SubSection(
sub_section=ElectronicGreensFunction.m_def, repeats=True
Expand Down Expand Up @@ -197,6 +196,16 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
if self.model_method_ref is None:
self.model_method_ref = self.set_model_method_ref()

# build up derived electronic structures
if not self.electronic_band_gaps:
self.electronic_band_gaps = bandstructure_to_bandgap(self.electronic_band_structures)
if self.electronic_band_gaps:
self.electronic_band_gaps.normalize(archive, logger)
if not self.electronic_dos:
self.electronic_dos = bandstructure_to_dos(self.electronic_band_structures)
if self.electronic_dos:
self.electronic_dos.normalize(archive, logger)


class SCFOutputs(Outputs):
"""
Expand Down
30 changes: 22 additions & 8 deletions src/nomad_simulations/schema_packages/physical_property.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from functools import wraps
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

import numpy as np
from nomad import utils
from nomad.datamodel.data import ArchiveSection
from nomad.datamodel.metainfo.plot import PlotSection
from nomad.datamodel.metainfo.basesections.v2 import Entity
from nomad.metainfo import URL, MEnum, Quantity, Reference, SectionProxy, SubSection
from nomad.metainfo import URL, MEnum, Quantity, Reference, SectionProxy

if TYPE_CHECKING:
from nomad.datamodel.datamodel import EntryArchive
Expand Down Expand Up @@ -57,7 +57,7 @@ def wrapper(self, *args, **kwargs):
return decorator


class PhysicalProperty(ArchiveSection):
class PhysicalProperty(PlotSection):
"""
A base section used to define the physical properties obtained in a simulation, experiment, or in a post-processing
analysis. The main quantity of the `PhysicalProperty` is `value`, whose instantiation has to be overwritten in the derived classes
Expand All @@ -77,6 +77,7 @@ class PhysicalProperty(ArchiveSection):

iri = Quantity(
type=URL,
default='',
description="""
Internationalized Resource Identifier (IRI) of the physical property defined in the FAIRmat
taxonomy, https://fairmat-nfdi.github.io/fairmat-taxonomy/.
Expand Down Expand Up @@ -223,12 +224,25 @@ def _is_derived(self) -> bool:
Returns:
(bool): The flag indicating whether the physical property is derived or not.
"""
if self.physical_property_ref is not None:
return True
return False
return self.physical_property_ref is not None

def plot(self, *args, **kwargs) -> list:
"""
Placeholder for a method to plot the physical property. This method should be overridden in derived classes
to provide specific plotting functionality.

Returns:
(list): A list of figures (`PlotlyFigure`) representing the physical property.
"""
return []

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
def normalize(self, *args, **kwargs) -> None:
self.is_derived = self._is_derived()
super(PlotSection, self).normalize(*args, **kwargs)
if (plot_figures := self.plot(*args, **kwargs)):
self.figures.extend(plot_figures)
if self.m_def.name is not None:
self.name = self.m_def.name


class PropertyContribution(PhysicalProperty):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from .permittivity import Permittivity
from .spectral_profile import (
AbsorptionSpectrum,
DOSProfile,
ElectronicDensityOfStates,
SpectralProfile,
XASSpectrum,
Expand Down
109 changes: 32 additions & 77 deletions src/nomad_simulations/schema_packages/properties/band_gap.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

import numpy as np
from nomad.metainfo import MEnum, Quantity

if TYPE_CHECKING:
from nomad.datamodel.datamodel import EntryArchive
from nomad.metainfo import Context, Section
from structlog.stdlib import BoundLogger
from nomad.metainfo import Quantity

from nomad_simulations.schema_packages.data_types import positive_float
from nomad_simulations.schema_packages.physical_property import PhysicalProperty
Expand All @@ -19,87 +14,47 @@ class ElectronicBandGap(PhysicalProperty):

iri = 'http://fairmat-nfdi.eu/taxonomy/ElectronicBandGap'

type = Quantity(
type=MEnum('direct', 'indirect'),
value = Quantity(
type=positive_float(),
unit='joule',
description="""
Type categorization of the electronic band gap. This quantity is directly related with `momentum_transfer` as by
definition, the electronic band gap is `'direct'` for zero momentum transfer (or if `momentum_transfer` is `None`) and `'indirect'`
for finite momentum transfer.
The value of the electronic band gap. This value must be positive.
`None` indicates that no band gap could be determined, e.g., if the system is metallic.
""",
)

momentum_transfer = Quantity(
type=np.float64,
shape=[2, 3],
unit='1/meter',
description="""
If the electronic band gap is `'indirect'`, the reciprocal momentum transfer for which the band gap is defined
in units of the `reciprocal_lattice_vectors`. The initial and final momentum 3D vectors are given in the first
and second element. Example, the momentum transfer in bulk Si2 happens between the Γ and the (approximately)
X points in the Brillouin zone; thus:
`momentum_transfer = [[0, 0, 0], [0.5, 0.5, 0]]`.

Note: this quantity only refers to scalar `value`, not to arrays of `value`.
The length of the difference in reciprocal space between the initial and final momentum transfer
along which the electronic band gap is defined.

Example, the momentum transfer in bulk Si2 happens between the Γ and (approximately) X points, thus:
`momentum_transfer = ||[0, 0, 0] - [0.5, 0.5, 0]|| ~= 0.612`.
""",
)

spin_channel = Quantity(
type=np.int32,
description="""
Spin channel of the corresponding electronic band gap. It can take values of 0 or 1.
""",
)

value = Quantity(
type=positive_float(),
unit='joule',
description="""
The value of the electronic band gap. This value must be positive.
""",
)

def __init__(
self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs
) -> None:
super().__init__(m_def, m_context, **kwargs)
self.name = self.m_def.name

def resolve_type(self, logger: 'BoundLogger') -> Optional[str]:
# TODO: give it a place
def extract_band_gap(self) -> 'ElectronicBandGap | None':
"""
Resolves the `type` of the electronic band gap based on the stored `momentum_transfer` values.

Args:
logger (BoundLogger): The logger to log messages.
Extract the electronic band gap from the `highest_occupied_energy` and `lowest_unoccupied_energy` stored
in `m_cache` from `resolve_energies_origin()`. If the difference of `highest_occupied_energy` and
`lowest_unoccupied_energy` is negative, the band gap `value` is set to 0.0.

Returns:
(Optional[str]): The resolved `type` of the electronic band gap.
(Optional[ElectronicBandGap]): The extracted electronic band gap section to be stored in `Outputs`.
"""
mtr = self.momentum_transfer if self.momentum_transfer is not None else []

# Check if the `momentum_transfer` is [], and return the type and a warning in the log for `indirect` band gaps
if len(mtr) == 0:
if self.type == 'indirect':
logger.warning(
'The `momentum_transfer` is not stored for an `indirect` band gap.'
)
return self.type

# Check if the `momentum_transfer` has at least two elements, and return None if it does not
if len(mtr) == 1:
logger.warning(
'The `momentum_transfer` should have at least two elements so that the difference can be calculated and the type of electronic band gap can be resolved.'
)
return None

# Resolve `type` from the difference between the initial and final momentum transfer
momentum_difference = np.diff(mtr, axis=0)
if (np.isclose(momentum_difference, np.zeros(3))).all():
return 'direct'
else:
return 'indirect'

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)

# Resolve the `type` of the electronic band gap from `momentum_transfer`, ONLY for scalar `value`
if self.value is not None:
self.type = self.resolve_type(logger)
band_gap = None
homo = self.m_cache.get('highest_occupied_energy')
lumo = self.m_cache.get('lowest_unoccupied_energy')
if homo and lumo:
band_gap = ElectronicBandGap()
band_gap.is_derived = True
band_gap.physical_property_ref = self

if (homo - lumo).magnitude < 0:
band_gap.value = 0.0
else:
band_gap.value = homo - lumo
return band_gap
Loading
Loading