Source code for siegpy.analyticeigenstates

# -*- coding: utf-8 -*-
r"""
The classes defining the analytic eigenstates of a generic 1D potential
with compact support are presented below:
* :class:`AnalyticEigenstate` is the base class of the other two,
* :class:`AnalyticSiegert` is meant to represent analytic Siegert states,
* :class:`AnalyticContinuum` is meant to represent analytic continuum
  states

They all are abstract base classes:

A new exception is also defined:
:class:`~siegpy/analyticeigenstates/WavenumberError`.
"""

from abc import ABCMeta, abstractmethod
import numpy as np
from .eigenstates import Eigenstate
from .functions import AnalyticFunction


[docs]class AnalyticEigenstate(AnalyticFunction, Eigenstate, metaclass=ABCMeta): r""" Class gathering the attributes and methods necessary to define an analytic eigenstate of any analytical problem (such as the 1D Square-Well Potential case). .. note:: To consider a case as analytic, the wavefunctions of the Siegert and continuum states should be known analytically. The analytic scalar product of both types of eigenstates with at least one type of test function should also be defined. """ # You should be able to define the type of an analytical eigenstate SIEGERT_TYPES = { "b": "bound", "ab": "anti-bound", "r": "resonant", "ar": "anti-resonant", None: "continuum", } def __init__(self, k, potential, grid, analytic): r""" Parameters ---------- k: complex Wavenumber of the eigenstate. potential: Potential Potential for which the analytic eigenstate is known. grid: list or set or numpy array Discretization grid. analytic: bool If ``True``, the scalar products must be computed analytically. Raises ------ WavenumberError If the inferred Siegert type is unknown. """ S_type = self._find_Siegert_type(k) if S_type in self.SIEGERT_TYPES: self._Siegert_type = S_type else: # Error only if bad implementation, hence no coverage raise WavenumberError() # pragma: no cover self._wavenumber = k self._energy = k ** 2 / 2 self._potential = potential self.analytic = analytic super().__init__(grid) @property def potential(self): r""" Returns ------- complex or float Potential for which the eigenstate is a solution. """ return self._potential @property def wavenumber(self): r""" Returns ------- complex or float Wavenumber of the eigenstate. """ return self._wavenumber @property def energy(self): r""" Returns ------- complex or float Energy of the Eigenstate """ return self._energy
[docs] @abstractmethod def _find_Siegert_type(self, k): # pragma: no cover r""" .. note:: This is an asbtract method. Parameters ---------- k: float or complex Wavenumber of the eigenstate Returns ------- str or NoneType Type of the eigenstate from its wavenumber k (``'b'`` for bound states, ``'ab'`` for anti-bound states, ``'r'`` for resonant states, ``'ar'`` for anti-resonant states, and ``None`` for continuum states). """ pass
@property def analytic(self): r""" Returns ------- bool Analyticity of the scalar products. """ return self._analytic @analytic.setter def analytic(self, new_value): r""" Setter of the analyticity of the eigenstate. Parameters ---------- new_value: bool New value of the analyticity of the scalar products. Raises ------ ValueError If ``new_value`` is not a Boolean. """ if isinstance(new_value, bool): self._analytic = new_value else: raise ValueError( "analytic must be a Boolean (cannot be {})".format(new_value) )
[docs] def __eq__(self, other): r""" Two analytic eigenstates are the same if they share the same wavenumber, potential and Siegert_type. Parameters ---------- other: object Another object Returns ------- bool ``True`` if both eigenstates are the same. """ if isinstance(other, AnalyticEigenstate): return ( np.isclose(self.wavenumber, other.wavenumber) and (self.potential == other.potential) and (self.Siegert_type == other.Siegert_type) ) # Not any else or elif covered, since there is only one # analytical case actually implemented (SWP case) else: return False # pragma: no cover
def __repr__(self): r""" Returns ------- str Representation of an analytic eigenstate. """ Siegert_type = self.SIEGERT_TYPES[self.Siegert_type].capitalize() return "{} eigenstate of energy {:.3f}".format(Siegert_type, self.energy)
[docs] @abstractmethod def scal_prod(self, other, xlim=None): r""" .. note:: This is an asbtract method. Evaluate the scalar product between the state and a test function ``other``. It ensures that the numerical scalar product is computed when the analytical scalar product with test functions is not implemented in the child class. Parameters ---------- other: Function Test function. xlim: tuple(float, float) Bounds of the space grid defining the interval over which the scalar product must be computed. Returns ------- float or complex Value of the scalar product of the state with a test function. """ return super().scal_prod(other, xlim=xlim)
[docs]class AnalyticSiegert(AnalyticEigenstate, metaclass=ABCMeta): r""" Class specifying the abstract method allowing to find the type of the analytic eigenstate, in the case it is a Siegert state. """
[docs] def _find_Siegert_type(self, k): r""" Parameters ---------- k: complex Wavenumber of the Siegert state. Returns ------- str The type of the Siegert state, namely: * ``'b'`` for a bound state * ``'ab'`` for an anti-bound state * ``'r'`` for a resonant state * ``'ar'`` for an anti-resonant state Raises ------ WavenumberError If the wavenumber is equal to zero. """ # If its real part is zero, then it's either a bound # or an antibound state if k.real == 0.0: if k.imag > 0.0: S_type = "b" elif k.imag < 0.0: S_type = "ab" else: # This is a very rare case, hence no coverage raise WavenumberError( "The wavenumber is zero: impossible to " "define the parity." ) # pragma: no cover # Else, if its real part is positive... elif k.real > 0.0: # ... if it has a negative imaginary part, # then it's a resonant state if k.imag < 0.0: S_type = "r" # Finally, if the real part is negative... elif k.real < 0.0: # ... if it has a negative imaginary part, # then it's an anti-resonant state if k.imag < 0.0: S_type = "ar" return S_type
[docs]class AnalyticContinuum(AnalyticEigenstate, metaclass=ABCMeta): r""" Class specifying the abstract method allowing to find the type of the analytic eigenstate, in the case it is a continuum state. """
[docs] def _find_Siegert_type(self, k): r""" Parameters ---------- k: float Wavenumber of the continuum state. Returns ------- NoneType ``None``, given that a continuum state is not a Siegert state. Raises ------ WavenumberError If the wavenumber is imaginary. """ if not np.isclose(k.imag, 0.0): raise WavenumberError( "A continuum state cannot have an imaginary wavenumber" ) else: return None
class WavenumberError(Exception): r""" Error thrown if the wavenumber of an eigenstate is incorrect. """ pass