Source code for WaveBlocksND.FourierPropagator

r"""The WaveBlocks Project

This file contains the Fourier propagator class. The wavefunction
:math:`\Psi` is propagated in time with a strang splitting of the
exponential :math:`\exp(-\frac{i}{\varepsilon^2} \tau H)`.

@author: R. Bourquin
@copyright: Copyright (C) 2012, 2016 R. Bourquin
@license: Modified BSD License
"""

from numpy import zeros, complexfloating
from numpy.fft import fftn, ifftn

from WaveBlocksND.Propagator import Propagator
from WaveBlocksND.KineticOperator import KineticOperator

__all__ = ["FourierPropagator"]


[docs]class FourierPropagator(Propagator): r"""This class can numerically propagate given initial values :math:`\Psi(x_0, t_0)` on a potential hyper surface :math:`V(x)`. The propagation is done with a Strang splitting of the time propagation operator :math:`\exp(-\frac{i}{\varepsilon^2} \tau H)`. """
[docs] def __init__(self, parameters, potential, initial_values): r"""Initialize a new :py:class:`FourierPropagator` instance. Precalculate the the kinetic operator :math:`T_e` and the potential operator :math:`V_e` used for time propagation. :param parameters: The set of simulation parameters. It must contain at least the semi-classical parameter :math:`\varepsilon` and the time step size :math:`\tau`. :param potential: The potential :math:`V(x)` governing the time evolution. :type potential: A :py:class:`MatrixPotential` instance. :param initial_values: The initial values :math:`\Psi(\Gamma, t_0)` given in the canonical basis. :type initial_values: A :py:class:`WaveFunction` instance. :raise: :py:class:`ValueError` If the number of components of :math:`\Psi` does not match the number of energy surfaces :math:`\lambda_i(x)` of the potential. """ # The embedded 'MatrixPotential' instance representing the potential 'V'. self._potential = potential # The initial values of the components '\psi_i' sampled at the given grid. self._psi = initial_values if self._potential.get_number_components() != self._psi.get_number_components(): raise ValueError("Potential dimension and number of components do not match.") # The position space grid nodes '\Gamma'. self._grid = initial_values.get_grid() # The kinetic operator 'T' defined in momentum space. self._KO = KineticOperator(self._grid, parameters["eps"]) # Exponential '\exp(-i/2*eps^2*dt*T)' used in the Strang splitting. self._KO.calculate_exponential(-0.5j * parameters["dt"] * parameters["eps"]**2) self._TE = self._KO.evaluate_exponential_at() # Exponential '\exp(-i/eps^2*dt*V)' used in the Strang splitting. self._potential.calculate_exponential(-0.5j * parameters["dt"] / parameters["eps"]**2) VE = self._potential.evaluate_exponential_at(self._grid) self._VE = tuple([ve.reshape(self._grid.get_number_nodes()) for ve in VE])
# TODO: Consider removing this, duplicate
[docs] def get_number_components(self): r"""Get the number :math:`N` of components of :math:`\Psi`. :return: The number :math:`N`. """ return self._potential.get_number_components()
[docs] def get_wavefunction(self): r"""Get the wavefunction that stores the current data :math:`\Psi(\Gamma)`. :return: The :py:class:`WaveFunction` instance. """ return self._psi
[docs] def get_operators(self): r"""Get the kinetic and potential operators :math:`T(\Omega)` and :math:`V(\Gamma)`. :return: A tuple :math:`(T, V)` containing two ``ndarrays``. """ # TODO: What kind of object exactly do we want to return? self._KO.calculate_operator() T = self._KO.evaluate_at() V = self._potential.evaluate_at(self._grid) V = tuple([v.reshape(self._grid.get_number_nodes()) for v in V]) return (T, V)
[docs] def propagate(self): r"""Given the wavefunction values :math:`\Psi(\Gamma)` at time :math:`t`, calculate new values :math:`\Psi^\prime(\Gamma)` at time :math:`t + \tau`. We perform exactly one single timestep of size :math:`\tau` within this function. """ # How many components does Psi have N = self._psi.get_number_components() # Unpack the values from the current WaveFunction values = self._psi.get_values() tmp = [zeros(value.shape, dtype=complexfloating) for value in values] # The first step with the potential for row in range(N): for col in range(N): tmp[row] = tmp[row] + self._VE[row * N + col] * values[col] # Go to Fourier space tmp = [fftn(component) for component in tmp] # Apply the kinetic operator tmp = [self._TE * component for component in tmp] # Go back to real space tmp = [ifftn(component) for component in tmp] # The second step with the potential values = [zeros(component.shape, dtype=complexfloating) for component in tmp] for row in range(N): for col in range(N): values[row] = values[row] + self._VE[row * N + col] * tmp[col] # Pack values back to WaveFunction object # TODO: Consider squeeze(.) of data before repacking self._psi.set_values(values)