Source code for solrat.atom_model.multi_term_atom_model_lte.statistical_equilibrium_equations

try:
    from typing import Self  # Python 3.11+
except ImportError:
    from typing_extensions import Self  # Python <3.11

import logging
from typing import Union

import numpy as np
from numpy import sqrt

from solrat.atom_model.base_atom_model.statistical_equilibrium_equations import BaseSEE
from solrat.atom_model.multi_term_atom_model.object.atmosphere_parameters import AtmosphereParameters
from solrat.atom_model.multi_term_atom_model.object.level_registry import LevelRegistry
from solrat.atom_model.multi_term_atom_model.object.multi_term_atom_config import MultiTermAtomConfig
from solrat.atom_model.multi_term_atom_model.object.radiation_tensor import RadiationTensor
from solrat.atom_model.multi_term_atom_model.object.rho_matrix_builder import Rho, RhoMatrixBuilder
from solrat.atom_model.shared.utility.constants import kB_erg_Km1
from solrat.atom_model.shared.utility.functions import energy_cmm1_to_erg
from solrat.engine.functions.decorators import log_method


[docs] class MultiTermAtomSEELTE(BaseSEE): r""" Statistical Equilibrium Equations within Multi-Term atom model - an LTE implementation. This class will always output an LTE-distributed Rho tensor. :param level_registry: LevelRegistry instance for the multi-term atom under study. This is needed to be able to use SEELTE directly in nonLTE Radiative Transfer Equations. """ def __init__(self, level_registry: LevelRegistry): self.level_registry = level_registry self.matrix_builder: RhoMatrixBuilder = RhoMatrixBuilder(terms=list(self.level_registry.terms.values())) self._atmosphere_parameters: Union[AtmosphereParameters, None] = None @property def atmosphere_parameters(self) -> AtmosphereParameters: if self._atmosphere_parameters is None: raise RuntimeError("atmosphere_parameters has not been initialized") return self._atmosphere_parameters
[docs] @classmethod def from_model_config(cls, config: MultiTermAtomConfig) -> Self: r""" Constructor from the model config. """ logging.info("Constructing MultiTermAtomSEELTE instance") return cls( level_registry=config.level_registry, )
[docs] @log_method def fill_all_equations( self, atmosphere_parameters: AtmosphereParameters, radiation_tensor_in_magnetic_frame: RadiationTensor, ): r""" Loop through all equations to construct the complete system of equations for rho. :param atmosphere_parameters: AtmosphereParameters instance carrying the magnetic field and other variables. :param radiation_tensor_in_magnetic_frame: RadiationTensor instance """ self._atmosphere_parameters = atmosphere_parameters
[docs] @log_method def get_solution(self) -> Rho: r""" Return LTE Rho solution. Assume Zeeman splitting does not cause energy shifts so large that they affect populations. Then: .. math:: \rho^K_Q(J,Jʹ) &\sim \delta_{J,Jʹ}\delta_{K,0}\delta_{Q,0} \sqrt{2J+1} \mathrm{exp}(-E_J/kT) Tr[\rho^K_Q(J,Jʹ)] &= \Sigma \sqrt{2J+1} \rho^0_0(J,Jʹ) = 1 Reference: (LL04 3.108) (LL04 10.118) """ logging.info("Applying LTE Statistical Equilibrium Equations solution") T = self.atmosphere_parameters.temperature_K # Fill with zeros rho = Rho(terms=list(self.level_registry.terms.values())) for index, (term_id, k, q, j, j_prime) in self.matrix_builder.index_to_parameters.items(): rho.set_from_term_id(term_id=term_id, K=k, Q=q, J=j, =j_prime, value=0.0) min_energy = min([level.energy_cmm1 for level in self.level_registry.levels.values()]) trace = 0.0 for term in self.level_registry.terms.values(): for level in term.levels: J = level.J E_erg = energy_cmm1_to_erg(level.energy_cmm1 - min_energy) rho_00 = np.sqrt(2 * J + 1) * np.exp(-E_erg / (kB_erg_Km1 * T)) trace += rho_00 * sqrt(2 * J + 1) rho.set_from_term_id( term_id=term.term_id, K=0, Q=0, J=J, =J, value=rho_00, ) # Trace-normalize for k, v in rho.data.items(): rho.data[k] = v / trace return rho