Skip to content

Commit

Permalink
- Move out electronic physical properties
Browse files Browse the repository at this point in the history
- TODO: re-group derived sections
  • Loading branch information
Nathan Daelman committed Dec 18, 2024
1 parent ba97b08 commit 430bfb8
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 362 deletions.
71 changes: 29 additions & 42 deletions src/nomad_simulations/schema_packages/properties/band_gap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,46 @@
import numpy as np
from nomad.units import ureg
from nomad.datamodel.data import ArchiveSection
from nomad.metainfo import MEnum, Quantity
from nomad.metainfo.datasets import DatasetTemplate, Energy
from ..variables import (
SpinChannel,
MomentumTransfer,
KMomentumTransfer,
ElectronicBandGap,
BandGapType,
)

if TYPE_CHECKING:
from nomad.datamodel.datamodel import EntryArchive
from structlog.stdlib import BoundLogger


class HomoLumoGap(ArchiveSection): # ? class description
values = DatasetTemplate(
name='HomoLumoGap',
mandatory_fields=[Energy],
mandatory_variables=[SpinChannel], # presence checked via annotations
iri='http://fairmat-nfdi.eu/taxonomy/HomoLumoGap',
description="""
The energy difference between the highest occupied spin state
and the lowest unoccupied spin state.
""",
)

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
self.m_all_validate() # ensure constraints
if np.any((energies := self.get_variable(Energy).get_values()) < 0): # ? failure handling
logger.warning(f'Negative band gap detected: {energies.to('J').magnitude} J')
class ElectronicBandGapSection(ArchiveSection): # ! TODO: add optical band gap
band_gap = ElectronicBandGap()

band_gap_type = BandGapType()

class ElectronicBandGap(HomoLumoGap): # ! TODO: add optical band gap
values = HomoLumoGap.values.m_copy() # ? `m_copy` supported
values.mandatory_variables.append(MomentumTransfer)
values.iri = 'http://fairmat-nfdi.eu/taxonomy/ElectronicBandGap'
def type_to_gap(self, logger: 'BoundLogger'):
bg, bt = self.band_gap.get_fields(), self.band_gap_type.get_fields()
if bg is not None:
return

if self.type == 'direct':
self.variables.append(
KMomentumTransfer(data=[2 * [3 * [0.0]]] * ureg.angstrom**-1)
)
elif self.type == 'indirect':
logger.warning(
f'Indirect band gap without specifying any momentum transfer: {self.variables}'
)

type = Quantity(
type=MEnum('direct', 'indirect'),
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.
def gap_to_type(self):
if self.band_gap_type.get_fields() is not None:
return
if self.band_gap.get_variables(KMomentumTransfer):
self.band_gap_type = ...
else:
self.band_gap_type = 'unknown'

Note: in the case of finite `variables`, this quantity refers to all of the `value` in the array.
""",
)

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
if not self.get_variable(MomentumTransfer).get_values(): # getter
if self.type == 'direct':
self.variables.append(
MomentumTransfer(data=[2 * [3 * [0.0]]] * ureg.angstrom**-1)
)
elif self.type == 'indirect':
logger.warning(
f'Indirect band gap without specifying any momentum transfer: {self.variables}'
)
super().normalize(archive, logger)
self.type_to_gap(logger)
self.gap_to_type()
74 changes: 7 additions & 67 deletions src/nomad_simulations/schema_packages/properties/band_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@

import numpy as np
import pint
import plotly.express as px

from nomad.datamodel.data import ArchiveSection
from nomad.metainfo import MEnum, Quantity
from nomad.metainfo.physical_properties import DatasetTemplate, Count, Energy
from ..variables import SpinChannel, KMesh
from ..variables import SpinChannel, KPoint, ElectronicEigenEnergies

if TYPE_CHECKING:
from nomad.datamodel.datamodel import EntryArchive
from structlog.stdlib import BoundLogger

from nomad_simulations.schema_packages.atoms_state import AtomsState, OrbitalsState
from nomad_simulations.schema_packages.numerical_settings import KSpace
from nomad_simulations.schema_packages.properties.band_gap import ElectronicBandGap

Expand All @@ -24,53 +21,16 @@
)


Occupancy = Count.m_copy() # ? establish semantic connection # values between 0 - 1 or 0 - 2
Occupancy.iri='http://fairmat-nfdi.eu/taxonomy/Occupancy',
Occupancy.description="""
Electrons occupancy of an atom per orbital and spin. This is a number defined between 0 and 1 for
spin-polarized systems, and between 0 and 2 for non-spin-polarized systems. This property is
important when studying if an orbital or spin channel are fully occupied, at half-filling, or
fully emptied, which have an effect on the electron-electron interaction effects.
"""
class ElectronicEigenEnergiesSection(ArchiveSection):
electronic_eigen_energies = ElectronicEigenEnergies()

highest_occupied = Energy() # ? C4: remove

class ElectronicEigenstates(ArchiveSection):
values = DatasetTemplate(
name='ElectronicEigenstates',
mandatory_fields=[Energy, Occupancy],
mandatory_variables=[SpinChannel],
iri='http://fairmat-nfdi.eu/taxonomy/ElectronicEigenvalues',
description="""
A base section used to define basic quantities for
the `ElectronicEigenvalues` and `ElectronicEigenstates` properties.
""",
)
lowest_unoccupied = Energy() # ? C4: remove

# ? Should we add functionalities to handle min/max of the `value` in some specific cases, e.g. bands around the Fermi level,
# ? core bands separated by gaps, and equivalently, higher-energy valence bands separated by gaps?

# references
atoms_state_ref = Quantity(
type=AtomsState,
description="""
Reference to the matching `AtomsState` section.
""",
)

orbitals_state_ref = Quantity(
type=OrbitalsState,
description="""
Reference to the matching `OrbitalsState` section.
""",
) # ! TODO: unify with `atoms_state_ref`

# derived properties
n_eigenvalues = Count # ? remove

highest_occupied = Energy # ? property

lowest_unoccupied = Energy # ? property

def order_eigenvalues(self) -> Union[bool, tuple['pint.Quantity', np.ndarray]]:
"""
Order the eigenvalues based on the `value` and `occupation`. The return `value` and
Expand Down Expand Up @@ -180,26 +140,6 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
self.reciprocal_cell = self.resolve_reciprocal_cell()


class DensityOfStates(ElectronicEigenstates):
# fermi_level

# band_gap

partial_dos = DatasetTemplate(
mandatory_fields=[Energy, Count], # ! relax
mandatory_variables=[SpinChannel, AtomsState], # to be interpreted as the symbol, i.e. p_x
) # ? instead of subsection

def plot(self):
self.m_all_validate()
energy_axes = self.values.get_values(Energy).by(SpinChannel)
figure_main = px.line(
x=np.sort(np.array(set(*energy_axes))), # overlay along spin dim
y=self.values.get_values(Count).by(SpinChannel),
color=self.values.get_variable(SpinChannel),
)


class BandStructure(ArchiveSection):
values = DatasetTemplate(
name='BandStructure',
Expand Down Expand Up @@ -230,11 +170,11 @@ class BandStructure(ArchiveSection):
highest_occupied = DatasetTemplate(
name='HighestOccupied',
mandatory_fields=[Energy, KMesh],
) # ? property
) # ? property

lowest_unoccupied = ighest_occupied = DatasetTemplate(
name='LowestUnoccupied',
mandatory_fields=[Energy, KMesh],
) # ? property
) # ? property

# ! plot
Loading

0 comments on commit 430bfb8

Please sign in to comment.