diff --git a/pyproject.toml b/pyproject.toml index 117c62f6..da497aba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,6 @@ indent-width = 4 [tool.ruff.lint] select = [ "E", # pycodestyle - "W", # pycodestyle "PL", # pylint "F", # Pyflakes "UP", # pyupgrade @@ -92,11 +91,11 @@ select = [ ] ignore = [ + "F401", # Module imported but unused "E501", # Line too long ({width} > {limit} characters) "E701", # Multiple statements on one line (colon) "E731", # Do not assign a lambda expression, use a def "E402", # Module level import not at top of file - "F401", "PLR0911", # Too many return statements "PLR0912", # Too many branches "PLR0913", # Too many arguments in function definition diff --git a/src/nomad_simulations/schema_packages/outputs.py b/src/nomad_simulations/schema_packages/outputs.py index 5d3896fb..4b673ef9 100644 --- a/src/nomad_simulations/schema_packages/outputs.py +++ b/src/nomad_simulations/schema_packages/outputs.py @@ -39,12 +39,17 @@ ElectronicBandStructure, ElectronicDensityOfStates, ElectronicEigenvalues, + ElectronicGreensFunction, + ElectronicSelfEnergy, FermiLevel, FermiSurface, HoppingMatrix, + HybridizationFunction, KineticEnergy, + Occupancy, Permittivity, PotentialEnergy, + QuasiparticleWeight, Temperature, TotalEnergy, TotalForce, @@ -112,6 +117,24 @@ class Outputs(ArchiveSection): sub_section=ElectronicBandStructure.m_def, repeats=True ) + occupancies = SubSection(sub_section=Occupancy.m_def, repeats=True) + + electronic_greens_functions = SubSection( + sub_section=ElectronicGreensFunction.m_def, repeats=True + ) + + electronic_self_energies = SubSection( + sub_section=ElectronicSelfEnergy.m_def, repeats=True + ) + + hybridization_functions = SubSection( + sub_section=HybridizationFunction.m_def, repeats=True + ) + + quasiparticle_weights = SubSection( + sub_section=QuasiparticleWeight.m_def, repeats=True + ) + permittivities = SubSection(sub_section=Permittivity.m_def, repeats=True) absorption_spectra = SubSection(sub_section=AbsorptionSpectrum.m_def, repeats=True) diff --git a/src/nomad_simulations/schema_packages/properties/__init__.py b/src/nomad_simulations/schema_packages/properties/__init__.py index 59b31d0b..435738cc 100644 --- a/src/nomad_simulations/schema_packages/properties/__init__.py +++ b/src/nomad_simulations/schema_packages/properties/__init__.py @@ -17,7 +17,7 @@ # limitations under the License. from .band_gap import ElectronicBandGap -from .band_structure import ElectronicBandStructure, ElectronicEigenvalues +from .band_structure import ElectronicBandStructure, ElectronicEigenvalues, Occupancy from .energies import ( EnergyContribution, FermiLevel, @@ -27,6 +27,12 @@ ) from .fermi_surface import FermiSurface from .forces import BaseForce, ForceContribution, TotalForce +from .greens_function import ( + ElectronicGreensFunction, + ElectronicSelfEnergy, + HybridizationFunction, + QuasiparticleWeight, +) from .hopping_matrix import CrystalFieldSplitting, HoppingMatrix from .permittivity import Permittivity from .spectral_profile import ( diff --git a/src/nomad_simulations/schema_packages/properties/band_structure.py b/src/nomad_simulations/schema_packages/properties/band_structure.py index 59560fc0..f2c89b5f 100644 --- a/src/nomad_simulations/schema_packages/properties/band_structure.py +++ b/src/nomad_simulations/schema_packages/properties/band_structure.py @@ -28,6 +28,7 @@ from nomad.metainfo import Context, Section 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.physical_property import ( PhysicalProperty, @@ -92,8 +93,8 @@ class ElectronicEigenvalues(BaseElectronicEigenvalues): shape=['*', 'n_bands'], description=""" Occupation of the electronic eigenvalues. This is a number depending whether the `spin_channel` has been set or not. - If `spin_channel` is set, then this number is between 0 and 2, where 0 means that the state is unoccupied and 2 means - that the state is fully occupied; if `spin_channel` is not set, then this number is between 0 and 1. The shape of + If `spin_channel` is set, then this number is between 0 and 1, where 0 means that the state is unoccupied and 1 means + that the state is fully occupied; if `spin_channel` is not set, then this number is between 0 and 2. The shape of this quantity is defined as `[K.n_points, K.dimensionality, n_bands]`, where `K` is a `variable` which can be `KMesh` or `KLinePath`, depending whether the simulation mapped the whole Brillouin zone or just a specific path. @@ -340,3 +341,56 @@ def __init__( def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: super().normalize(archive, logger) + + +class Occupancy(PhysicalProperty): + """ + 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. + """ + + iri = 'http://fairmat-nfdi.eu/taxonomy/Occupancy' + + atoms_state_ref = Quantity( + type=AtomsState, + description=""" + Reference to the `AtomsState` section in which the occupancy is calculated. + """, + ) + + orbitals_state_ref = Quantity( + type=OrbitalsState, + description=""" + Reference to the `OrbitalsState` section in which the occupancy is calculated. + """, + ) + + spin_channel = Quantity( + type=np.int32, + description=""" + Spin channel of the corresponding electronic property. It can take values of 0 and 1. + """, + ) + + value = Quantity( + type=np.float64, + description=""" + Value of the electronic occupancy in the atom defined by `atoms_state_ref` and the orbital + defined by `orbitals_state_ref`. the orbital. If `spin_channel` is set, then this number is + between 0 and 1, where 0 means that the state is unoccupied and 1 means that the state is + fully occupied; if `spin_channel` is not set, then this number is between 0 and 2. + """, + ) + + 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 + + # TODO add extraction from `ElectronicEigenvalues.occupation` + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) diff --git a/src/nomad_simulations/schema_packages/properties/greens_function.py b/src/nomad_simulations/schema_packages/properties/greens_function.py new file mode 100644 index 00000000..51928a84 --- /dev/null +++ b/src/nomad_simulations/schema_packages/properties/greens_function.py @@ -0,0 +1,421 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# 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. +# + +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_simulations.schema_packages.atoms_state import AtomsState, OrbitalsState +from nomad_simulations.schema_packages.physical_property import PhysicalProperty +from nomad_simulations.schema_packages.utils import get_variables +from nomad_simulations.schema_packages.variables import ( + Frequency, + ImaginaryTime, + KMesh, + MatsubaraFrequency, + Time, + WignerSeitz, +) + + +class BaseGreensFunction(PhysicalProperty): + """ + A base class used to define shared commonalities between Green's function-related properties. This is the case for `ElectronicGreensFunction`, + `ElectronicSelfEnergy`, `HybridizationFunction` in DMFT simulations. + + These physical properties are matrices matrix represented in different spaces. These spaces are stored in + `variables` and can be: `WignerSeitz` (real space), `KMesh`, `MatsubaraFrequency`, `Frequency`, `Time`, or `ImaginaryTime`. + For example, G(k, ω) will have `variables = [KMesh(points), RealFrequency(points)]`. + + The `rank` is determined by the number of atoms and orbitals involved in correlations, so: + `rank = [n_atoms, n_correlated_orbitals]` + + Further information in M. Wallerberger et al., Comput. Phys. Commun. 235, 2 (2019). + """ + + # ! we use `atoms_state_ref` and `orbitals_state_ref` to enforce order in the matrices + + n_atoms = Quantity( + type=np.int32, + description=""" + Number of atoms involved in the correlations effect and used for the matrix representation of the property. + """, + ) + + atoms_state_ref = Quantity( + type=AtomsState, + shape=['n_atoms'], + description=""" + Reference to the `AtomsState` section in which the Green's function properties are calculated. + """, + ) + + n_correlated_orbitals = Quantity( + type=np.int32, + description=""" + Number of orbitals involved in the correlations effect and used for the matrix representation of the property. + """, + ) + + correlated_orbitals_ref = Quantity( + type=OrbitalsState, + shape=['n_correlated_orbitals'], + description=""" + Reference to the `OrbitalsState` section in which the Green's function properties are calculated. + """, + ) + + spin_channel = Quantity( + type=np.int32, + description=""" + Spin channel of the corresponding electronic property. It can take values of 0 and 1. + """, + ) + + local_model_type = Quantity( + type=MEnum('impurity', 'lattice'), + description=""" + Type of Green's function calculated from the mapping of the local Hubbard-Kanamori model + into the Anderson impurity model. + + The `impurity` Green's function describe the electronic correlations for the impurity, and it + is a local function. The `lattice` Green's function includes the coupling to the lattice + and hence it is a non-local function. In DMFT, the `lattice` term is approximated to be the + `impurity` one, so that these simulations are converged if both types of the local + part of the `lattice` Green's function coincides with the `impurity` Green's function. + """, + ) + + space_id = Quantity( + type=MEnum( + 'r', + 'rt', + 'rw', + 'rit', + 'riw', + 'k', + 'kt', + 'kw', + 'kit', + 'kiw', + 't', + 'it', + 'w', + 'iw', + ), + description=""" + String used to identify the space in which the Green's function property is represented. The spaces are: + + | `space_id` | `variables` | + | ------ | ------ | + | 'r' | WignerSeitz | + | 'rt' | WignerSeitz + Time | + | 'rw' | WignerSeitz + Frequency | + | 'rit' | WignerSeitz + ImaginaryTime | + | 'riw' | WignerSeitz + MatsubaraFrequency | + | 'k' | KMesh | + | 'kt' | KMesh + Time | + | 'kw' | KMesh + Frequency | + | 'kit' | KMesh + ImaginaryTime | + | 'kiw' | KMesh + MatsubaraFrequency | + | 't' | Time | + | 'it' | Frequency | + | 'w' | ImaginaryTime | + | 'iw' | MatsubaraFrequency | + """, + ) + + def __init__( + self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs + ) -> None: + super().__init__(m_def, m_context, **kwargs) + # ! n_orbitals need to be set up during initialization of the class + self.rank = [ + int(kwargs.get('n_atoms')), + int(kwargs.get('n_correlated_orbitals')), + ] + + def resolve_space_id(self) -> str: + """ + Resolves the `space_id` based on the stored `variables` in the class. + + Returns: + str: The resolved `space_id` of the Green's function property. + """ + _real_space_map = { + 'r': WignerSeitz, # ? check if this is correct + 'k': KMesh, + } + _time_space_map = { + 't': Time, + 'it': ImaginaryTime, + 'w': Frequency, + 'iw': MatsubaraFrequency, + } + + def find_space_id(space_map: dict) -> str: + """ + Finds the id string for a given map. + + Args: + space_map (dict[str, Variables]): _description_ + + Returns: + str: _description_ + """ + for space_id, variable_cls in space_map.items(): + space_variable = get_variables( + variables=self.variables, variable_cls=variable_cls + ) + if len(space_variable) > 0: + return space_id + return '' + + space_id = find_space_id(_real_space_map) + find_space_id(_time_space_map) + return space_id if space_id else None + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + space_id = self.resolve_space_id() + if self.space_id is not None and self.space_id != space_id: + logger.warning( + f'The stored `space_id`, {self.space_id}, does not coincide with the resolved one, {space_id}. We will update it.' + ) + self.space_id = space_id + + +class ElectronicGreensFunction(BaseGreensFunction): + """ + Charge-charge correlation functions. + """ + + iri = 'http://fairmat-nfdi.eu/taxonomy/ElectronicGreensFunction' + + value = Quantity( + type=np.complex128, + unit='1/joule', + description=""" + Value of the electronic Green's function matrix. + """, + ) + + 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 normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + +class ElectronicSelfEnergy(BaseGreensFunction): + """ + Corrections to the energy of an electron due to its interactions with its environment. + """ + + iri = 'http://fairmat-nfdi.eu/taxonomy/ElectronicSelfEnergy' + + value = Quantity( + type=np.complex128, + unit='joule', + description=""" + Value of the electronic self-energy matrix. + """, + ) + + 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 normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + +class HybridizationFunction(BaseGreensFunction): + """ + Dynamical hopping of the electrons in a lattice in and out of the reservoir or bath. + """ + + iri = 'http://fairmat-nfdi.eu/taxonomy/HybridizationFunction' + + value = Quantity( + type=np.complex128, + unit='joule', + description=""" + Value of the electronic hybridization function. + """, + ) + + 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 normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + +class QuasiparticleWeight(PhysicalProperty): + """ + Renormalization of the electronic mass due to the interactions with the environment. Within the Fermi liquid + theory of solids, this is calculated as: + + Z = 1 - ∂Σ/∂ω|ω=0 + + where Σ is the `ElectronicSelfEnergy`. The quasiparticle weight is a measure of the strength of the + electron-electron interactions and takes values between 0 and 1, with Z = 1 representing a non-correlated + system, and Z = 0 the Mott state. + """ + + # ! we use `atoms_state_ref` and `orbitals_state_ref` to enforce order in the matrices + + iri = 'http://fairmat-nfdi.eu/taxonomy/HybridizationFunction' + + system_correlation_strengths = Quantity( + type=MEnum( + 'non-correlated metal', + 'strongly-correlated metal', + 'OSMI', + 'Mott insulator', + ), + description=""" + String used to identify the type of system based on the strength of the electron-electron interactions. + + | `type` | Description | + | ------ | ------ | + | 'non-correlated metal' | All `value` are above 0.7. Renormalization effects are negligible. | + | 'strongly-correlated metal' | All `value` are below 0.4 and above 0. Renormalization effects are important. | + | 'OSMI' | Orbital-selective Mott insulator: some orbitals have a zero `value` while others a finite one. | + | 'Mott insulator' | All `value` are 0.0. Mott insulator state. | + """, + ) + + n_atoms = Quantity( + type=np.int32, + description=""" + Number of atoms involved in the correlations effect and used for the matrix representation of the quasiparticle weight. + """, + ) + + atoms_state_ref = Quantity( + type=AtomsState, + shape=['n_atoms'], + description=""" + Reference to the `AtomsState` section in which the Green's function properties are calculated. + """, + ) + + n_correlated_orbitals = Quantity( + type=np.int32, + description=""" + Number of orbitals involved in the correlations effect and used for the matrix representation of the quasiparticle weight. + """, + ) + + correlated_orbitals_ref = Quantity( + type=OrbitalsState, + shape=['n_correlated_orbitals'], + description=""" + Reference to the `OrbitalsState` section in which the Green's function properties are calculated. + """, + ) + + spin_channel = Quantity( + type=np.int32, + description=""" + Spin channel of the corresponding electronic property. It can take values of 0 and 1. + """, + ) + + value = Quantity( + type=np.float64, + description=""" + Value of the quasiparticle weight matrices. + """, + ) + + def __init__( + self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs + ) -> None: + super().__init__(m_def, m_context, **kwargs) + # ! n_orbitals need to be set up during initialization of the class + self.rank = [ + int(kwargs.get('n_atoms')), + int(kwargs.get('n_correlated_orbitals')), + ] + self.name = self.m_def.name + + def is_valid_quasiparticle_weight(self) -> bool: + """ + Check if the quasiparticle weight values are valid, i.e., if all `value` are defined between + 0 and 1. + + Returns: + (bool): True if the quasiparticle weight is valid, False otherwise. + """ + if (self.value < 0.0).any() or (self.value > 1.0).any(): + return False + return True + + def resolve_system_correlation_strengths(self) -> str: + """ + Resolves the `system_correlation_strengths` of the quasiparticle weight based on the stored `value` values. + + Returns: + str: The resolved `system_correlation_strengths` of the quasiparticle weight. + """ + if np.all(self.value > 0.7): + return 'non-correlated metal' + elif np.all((self.value < 0.4) & (self.value > 0)): + return 'strongly-correlated metal' + elif np.any(self.value == 0) and np.any(self.value > 0): + return 'OSMI' + elif np.all(self.value < 1e-2): + return 'Mott insulator' + return None + + def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + if self.is_valid_quasiparticle_weight() is False: + logger.error( + 'Invalid negative quasiparticle weights found: could not validate them.' + ) + return + + system_correlation_strengths = self.resolve_system_correlation_strengths() + if ( + self.system_correlation_strengths is not None + and self.system_correlation_strengths != system_correlation_strengths + ): + logger.warning( + f'The stored `system_correlation_strengths`, {self.system_correlation_strengths}, does not coincide with the resolved one, {system_correlation_strengths}. We will update it.' + ) + self.system_correlation_strengths = system_correlation_strengths diff --git a/src/nomad_simulations/schema_packages/properties/hopping_matrix.py b/src/nomad_simulations/schema_packages/properties/hopping_matrix.py index 699f866e..795ce5fb 100644 --- a/src/nomad_simulations/schema_packages/properties/hopping_matrix.py +++ b/src/nomad_simulations/schema_packages/properties/hopping_matrix.py @@ -39,7 +39,8 @@ class HoppingMatrix(PhysicalProperty): n_orbitals = Quantity( type=np.int32, description=""" - Number of orbitals in the tight-binding model. + Number of orbitals in the tight-binding model. The `entity_ref` reference is used to refer to + the `OrbitalsState` section. """, ) @@ -85,7 +86,8 @@ class CrystalFieldSplitting(PhysicalProperty): n_orbitals = Quantity( type=np.int32, description=""" - Number of orbitals in the tight-binding model. + Number of orbitals in the tight-binding model. The `entity_ref` reference is used to refer to + the `OrbitalsState` section. """, ) diff --git a/src/nomad_simulations/schema_packages/variables.py b/src/nomad_simulations/schema_packages/variables.py index 7ca3ef27..ce02cfbb 100644 --- a/src/nomad_simulations/schema_packages/variables.py +++ b/src/nomad_simulations/schema_packages/variables.py @@ -171,7 +171,73 @@ class Frequency(Variables): unit='joule', shape=['n_points'], description=""" - Points in which the frequency is discretized in joules. + Points in which the frequency is discretized, in joules. + """, + ) + + 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 normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + +class MatsubaraFrequency(Variables): + """ """ + + points = Quantity( + type=np.complex128, + unit='joule', + shape=['n_points'], + description=""" + Points in which the imaginary or Matsubara frequency is discretized, in joules. + """, + ) + + 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 normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + +class Time(Variables): + """ """ + + points = Quantity( + type=np.float64, + unit='second', + shape=['n_points'], + description=""" + Points in which the time is discretized, in seconds. + """, + ) + + 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 normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: + super().normalize(archive, logger) + + +class ImaginaryTime(Variables): + """ """ + + points = Quantity( + type=np.complex128, + unit='second', + shape=['n_points'], + description=""" + Points in which the imaginary time is discretized, in seconds. """, ) diff --git a/tests/test_greens_function.py b/tests/test_greens_function.py new file mode 100644 index 00000000..36b96222 --- /dev/null +++ b/tests/test_greens_function.py @@ -0,0 +1,185 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# 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. +# + +from typing import Optional, Union + +import pytest +from nomad.datamodel import EntryArchive + +from nomad_simulations.schema_packages.properties import ( + QuasiparticleWeight, +) +from nomad_simulations.schema_packages.properties.greens_function import ( + BaseGreensFunction, +) +from nomad_simulations.schema_packages.variables import ( + Frequency, + ImaginaryTime, + KMesh, + MatsubaraFrequency, + Time, + WignerSeitz, +) + +from . import logger + + +class TestBaseGreensFunction: + """ + Test the `BaseGreensFunction` class defined in `properties/greens_function.py`.aa + """ + + @pytest.mark.parametrize( + 'variables, result', + [ + ([], None), + ([WignerSeitz()], 'r'), + ([KMesh()], 'k'), + ([Time()], 't'), + ([ImaginaryTime()], 'it'), + ([Frequency()], 'w'), + ([MatsubaraFrequency()], 'iw'), + ([WignerSeitz(), Time()], 'rt'), + ([WignerSeitz(), ImaginaryTime()], 'rit'), + ([WignerSeitz(), Frequency()], 'rw'), + ([WignerSeitz(), MatsubaraFrequency()], 'riw'), + ([KMesh(), Time()], 'kt'), + ([KMesh(), ImaginaryTime()], 'kit'), + ([KMesh(), Frequency()], 'kw'), + ([KMesh(), MatsubaraFrequency()], 'kiw'), + ], + ) + def test_resolve_space_id( + self, + variables: list[ + Union[ + WignerSeitz, KMesh, Time, ImaginaryTime, Frequency, MatsubaraFrequency + ] + ], + result: str, + ): + """ + Test the `resolve_space_id` method of the `BaseGreensFunction` class. + """ + gfs = BaseGreensFunction(n_atoms=1, n_correlated_orbitals=1) + gfs.variables = variables + assert gfs.resolve_space_id() == result + + @pytest.mark.parametrize( + 'space_id, variables, result', + [ + ('', [], None), # empty `space_id` + ('rt', [], None), # `space_id` set by parser + ('', [WignerSeitz()], 'r'), # resolving `space_id` + ('rt', [WignerSeitz()], 'r'), # normalize overwrites `space_id` + ('', [KMesh()], 'k'), + ('', [Time()], 't'), + ('', [ImaginaryTime()], 'it'), + ('', [Frequency()], 'w'), + ('', [MatsubaraFrequency()], 'iw'), + ('', [WignerSeitz(), Time()], 'rt'), + ('', [WignerSeitz(), ImaginaryTime()], 'rit'), + ('', [WignerSeitz(), Frequency()], 'rw'), + ('', [WignerSeitz(), MatsubaraFrequency()], 'riw'), + ('', [KMesh(), Time()], 'kt'), + ('', [KMesh(), ImaginaryTime()], 'kit'), + ('', [KMesh(), Frequency()], 'kw'), + ('', [KMesh(), MatsubaraFrequency()], 'kiw'), + ], + ) + def test_normalize( + self, + space_id: str, + variables: list[ + Union[ + WignerSeitz, KMesh, Time, ImaginaryTime, Frequency, MatsubaraFrequency + ] + ], + result: Optional[str], + ): + """ + Test the `normalize` method of the `BaseGreensFunction` class. + """ + gfs = BaseGreensFunction(n_atoms=1, n_correlated_orbitals=1) + gfs.variables = variables + gfs.space_id = space_id if space_id else None + gfs.normalize(archive=EntryArchive(), logger=logger) + assert gfs.space_id == result + + +class TestQuasiparticleWeight: + """ + Test the `QuasiparticleWeight` class defined in `properties/greens_function.py`. + """ + + @pytest.mark.parametrize( + 'value, result', + [ + ([[1, 0.5, -2]], False), + ([[1, 0.5, 8]], False), + ([[1, 0.5, 0.8]], True), + ], + ) + def test_is_valid_quasiparticle_weight(self, value: list[float], result: bool): + """ + Test the `is_valid_quasiparticle_weight` method of the `QuasiparticleWeight` class. + """ + quasiparticle_weight = QuasiparticleWeight(n_atoms=1, n_correlated_orbitals=3) + quasiparticle_weight.value = value + assert quasiparticle_weight.is_valid_quasiparticle_weight() == result + + @pytest.mark.parametrize( + 'value, result', + [ + ([[1, 0.9, 0.8]], 'non-correlated metal'), + ([[0.2, 0.3, 0.1]], 'strongly-correlated metal'), + ([[0, 0.3, 0.1]], 'OSMI'), + ([[0, 0, 0]], 'Mott insulator'), + ([[1.0, 0.8, 0.2]], None), + ], + ) + def test_resolve_system_correlation_strengths( + self, value: list[float], result: Optional[str] + ): + """ + Test the `resolve_system_correlation_strengths` method of the `QuasiparticleWeight` class. + """ + quasiparticle_weight = QuasiparticleWeight(n_atoms=1, n_correlated_orbitals=3) + quasiparticle_weight.value = value + assert quasiparticle_weight.resolve_system_correlation_strengths() == result + + @pytest.mark.parametrize( + 'value, result', + [ + ([[1, 0.5, -2]], None), + ([[1, 0.5, 8]], None), + ([[1, 0.9, 0.8]], 'non-correlated metal'), + ([[0.2, 0.3, 0.1]], 'strongly-correlated metal'), + ([[0, 0.3, 0.1]], 'OSMI'), + ([[0, 0, 0]], 'Mott insulator'), + ([[1.0, 0.8, 0.2]], None), + ], + ) + def test_normalize(self, value: list[float], result: Optional[str]): + """ + Test the `normalize` method of the `QuasiparticleWeight` class. + """ + quasiparticle_weight = QuasiparticleWeight(n_atoms=1, n_correlated_orbitals=3) + quasiparticle_weight.value = value + quasiparticle_weight.normalize(archive=EntryArchive(), logger=logger) + assert quasiparticle_weight.system_correlation_strengths == result diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 9aaee9c4..a048b8ec 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -42,7 +42,7 @@ def test_number_of_properties(self): which properties are already defined and in which order to control their normalizations """ outputs = Outputs() - assert len(outputs.m_def.all_sub_sections) == 17 + assert len(outputs.m_def.all_sub_sections) == 22 defined_properties = [ 'fermi_levels', 'chemical_potentials', @@ -53,6 +53,11 @@ def test_number_of_properties(self): 'electronic_dos', 'fermi_surfaces', 'electronic_band_structures', + 'occupancies', + 'electronic_greens_functions', + 'electronic_self_energies', + 'hybridization_functions', + 'quasiparticle_weights', 'permittivities', 'absorption_spectra', 'xas_spectra',