def __init__(self, dimension, ncomponents, eps): r"""Initialize a new homogeneous Hagedorn wavepacket. :param dimension: The space dimension :math:`D` the packet has. :param ncomponents: The number :math:`N` of components the packet has. :param eps: The semi-classical scaling parameter :math:`\varepsilon` of the basis functions. :return: An instance of :py:class:`HagedornWavepacket`. """ self._dimension = dimension self._number_components = ncomponents self._eps = eps # The basis shapes K_i self._basis_shapes = [] # The coefficients c^i self._coefficients = [] for d in xrange(self._number_components): # Default basis shapes for all components bs = HyperCubicShape( self._dimension*[1] ) self._basis_shapes.append(bs) # A Gaussian self._coefficients.append(zeros((bs.get_basis_size(),1), dtype=complexfloating)) # Cache basis sizes self._basis_sizes = [ bs.get_basis_size() for bs in self._basis_shapes ] # Default parameters of harmonic oscillator eigenstates q = zeros((self._dimension, 1)) p = zeros((self._dimension, 1)) Q = eye(self._dimension) P = 1.0j * eye(self._dimension) S = 0.0 # The parameter set Pi self._Pis = [q, p, Q, P, S] # No quadrature set self._QE = None # Function for taking continuous roots self._sqrt = ContinuousSqrt()
class HagedornWavepacket(HagedornWavepacketBase): r"""This class represents homogeneous vector valued Hagedorn wavepackets :math:`\Psi` with :math:`N` components in :math:`D` space dimensions. """ def __init__(self, dimension, ncomponents, eps): r"""Initialize a new homogeneous Hagedorn wavepacket. :param dimension: The space dimension :math:`D` the packet has. :param ncomponents: The number :math:`N` of components the packet has. :param eps: The semi-classical scaling parameter :math:`\varepsilon` of the basis functions. :return: An instance of :py:class:`HagedornWavepacket`. """ self._dimension = dimension self._number_components = ncomponents self._eps = eps # The basis shapes K_i self._basis_shapes = [] # The coefficients c^i self._coefficients = [] for d in xrange(self._number_components): # Default basis shapes for all components bs = HyperCubicShape( self._dimension*[1] ) self._basis_shapes.append(bs) # A Gaussian self._coefficients.append(zeros((bs.get_basis_size(),1), dtype=complexfloating)) # Cache basis sizes self._basis_sizes = [ bs.get_basis_size() for bs in self._basis_shapes ] # Default parameters of harmonic oscillator eigenstates q = zeros((self._dimension, 1)) p = zeros((self._dimension, 1)) Q = eye(self._dimension) P = 1.0j * eye(self._dimension) S = 0.0 # The parameter set Pi self._Pis = [q, p, Q, P, S] # No quadrature set self._QE = None # Function for taking continuous roots self._sqrt = ContinuousSqrt() def __str__(self): r""":return: A string describing the Hagedorn wavepacket :math:`\Psi`. """ s = ("Homogeneous Hagedorn wavepacket with "+str(self._number_components) +" component(s) in "+str(self._dimension)+" space dimension(s)\n") return s def get_description(self): r"""Return a description of this wavepacket object. A description is a ``dict`` containing all key-value pairs necessary to reconstruct the current instance. A description never contains any data. """ d = {} d["type"] = "HagedornWavepacket" d["dimension"] = self._dimension d["ncomponents"] = self._number_components d["eps"] = self._eps if self._QE is not None: d["quadrature"] = self._QE.get_description() return d def clone(self, keepid=False): # Parameters of this packet params = self.get_description() # Create a new Packet # TODO: Consider using the block factory other = HagedornWavepacket(params["dimension"], params["ncomponents"], params["eps"]) # If we wish to keep the packet ID if keepid is True: other.set_id(self.get_id()) # And copy over all (private) data # Basis shapes are immutable, no issues with sharing same instance other.set_basis_shape(self.get_basis_shape()) other.set_parameters(self.get_parameters()) other.set_coefficients(self.get_coefficients()) # Quadratures are immutable, no issues with sharing same instance other.set_quadrature(self.get_quadrature()) # The complex root cache other._sqrt = self._sqrt.clone() return other def get_parameters(self, component=None, aslist=False): r"""Get the Hagedorn parameter set :math:`\Pi` of the wavepacket :math:`\Psi`. :param component: Dummy parameter for API compatibility with the inhomogeneous packets. :param aslist: Return a list of :math:`N` parameter tuples. This is for API compatibility with inhomogeneous packets. :return: The Hagedorn parameter set :math:`\Pi = (q, p, Q, P, S)` in this order. """ if aslist is True: return self._number_components * [ self._Pis ] return self._Pis[:] def set_parameters(self, Pi, component=None): r"""Set the Hagedorn parameters :math:`\Pi` of the wavepacket :math:`\Psi`. :param Pi: The Hagedorn parameter set :math:`\Pi = (q, p, Q, P, S)` in this order. :param component: Dummy parameter for API compatibility with the inhomogeneous packets. """ self._Pis = [ atleast_2d(array(item, dtype=complexfloating)) for item in Pi ] def evaluate_basis_at(self, grid, component, prefactor=False): r"""Evaluate the basis functions :math:`\phi_k` recursively at the given nodes :math:`\gamma`. :param grid: The grid :math:\Gamma` containing the nodes :math:`\gamma`. :type grid: A class having a :py:meth:`get_nodes(...)` method. :param component: The index :math:`i` of a single component :math:`\Phi_i` to evaluate. We need this to choose the correct basis shape. :param prefactor: Whether to include a factor of :math:`\frac{1}{\sqrt{\det(Q)}}`. :type prefactor: bool, default is ``False``. :return: A two-dimensional ndarray :math:`H` of shape :math:`(|\mathcal{K}_i|, |\Gamma|)` where the entry :math:`H[\mu(k), i]` is the value of :math:`\phi_k(\gamma_i)`. """ D = self._dimension bas = self._basis_shapes[component] bs = self._basis_sizes[component] # TODO: Consider putting this into the Grid class as 2nd level API # Allow ndarrays for the 'grid' argument if isinstance(grid, Grid): # The overall number of nodes nn = grid.get_number_nodes(overall=True) # The grid nodes nodes = grid.get_nodes() else: # The overall number of nodes nn = prod(grid.shape[1:]) # The grid nodes nodes = grid # Allocate the storage array phi = zeros((bs, nn), dtype=complexfloating) # Precompute some constants q, p, Q, P, S = self._Pis Qinv = inv(Q) Qbar = conj(Q) QQ = dot(Qinv, Qbar) # Compute the ground state phi_0 via direct evaluation mu0 = bas[tuple(D*[0])] phi[mu0,:] = self._evaluate_phi0(self._Pis, nodes, prefactor=False) # Compute all higher order states phi_k via recursion for d in xrange(D): # Iterator for all valid index vectors k indices = bas.get_node_iterator(mode="chain", direction=d) for k in indices: # Current index vector ki = vstack(k) # Access predecessors phim = zeros((D, nn), dtype=complexfloating) for j, kpj in bas.get_neighbours(k, selection="backward"): mukpj = bas[kpj] phim[j,:] = phi[mukpj,:] # Compute 3-term recursion p1 = (nodes - q) * phi[bas[k],:] p2 = sqrt(ki) * phim t1 = sqrt(2.0/self._eps**2) * dot(Qinv[d,:], p1) t2 = dot(QQ[d,:], p2) # Find multi-index where to store the result kped = bas.get_neighbours(k, selection="forward", direction=d) # Did we find this k? if len(kped) > 0: kped = kped[0] # Store computed value phi[bas[kped[1]],:] = (t1 - t2) / sqrt(ki[d] + 1.0) if prefactor is True: phi = phi / self._sqrt(det(Q)) return phi def evaluate_at(self, grid, component=None, prefactor=False): r"""Evaluate the Hagedorn wavepacket :math:`\Psi` at the given nodes :math:`\gamma`. :param grid: The grid :math:\Gamma` containing the nodes :math:`\gamma`. :type grid: A class having a :py:meth:`get_nodes(...)` method. :param component: The index :math:`i` of a single component :math:`\Phi_i` to evaluate. (Defaults to ``None`` for evaluating all components.) :param prefactor: Whether to include a factor of :math:`\frac{1}{\sqrt{\det(Q)}}`. :type prefactor: bool, default is ``False``. :return: A list of arrays or a single array containing the values of the :math:`\Phi_i` at the nodes :math:`\gamma`. """ # The global phase part phase = exp(1.0j * self._Pis[4] / self._eps**2) if component is not None: basis = self.evaluate_basis_at(grid, component, prefactor=prefactor) values = phase * sum(self._coefficients[component] * basis, axis=0) else: values = [] for component in xrange(self._number_components): # Note: This is very inefficient! We may evaluate the same basis functions multiple # times. But as long as we don't know that the basis shapes are true subsets # of the largest one, we can not evaluate just all functions in this # maximal set. # TODO: Find more efficient way to do this basis = self.evaluate_basis_at(grid, component, prefactor=prefactor) values.append( phase * sum(self._coefficients[component] * basis, axis=0) ) return values
class HagedornWavepacket(HagedornWavepacketBase): r"""This class represents homogeneous vector valued Hagedorn wavepackets :math:`\Psi` with :math:`N` components in :math:`D` space dimensions. """ def __init__(self, dimension, ncomponents, eps): r"""Initialize a new homogeneous Hagedorn wavepacket. :param dimension: The space dimension :math:`D` the packet has. :param ncomponents: The number :math:`N` of components the packet has. :param eps: The semi-classical scaling parameter :math:`\varepsilon` of the basis functions. :return: An instance of :py:class:`HagedornWavepacket`. """ self._dimension = dimension self._number_components = ncomponents self._eps = eps # The basis shapes K_i self._basis_shapes = [] # The coefficients c^i self._coefficients = [] for d in xrange(self._number_components): # Default basis shapes for all components bs = HyperCubicShape( self._dimension*[1] ) self._basis_shapes.append(bs) # A Gaussian self._coefficients.append(zeros((bs.get_basis_size(),1), dtype=complexfloating)) # Cache basis sizes self._basis_sizes = [ bs.get_basis_size() for bs in self._basis_shapes ] # Default parameters of harmonic oscillator eigenstates q = zeros((self._dimension, 1), dtype=complexfloating) p = zeros((self._dimension, 1), dtype=complexfloating) Q = eye(self._dimension, dtype=complexfloating) P = 1.0j * eye(self._dimension, dtype=complexfloating) S = zeros((1, 1), dtype=complexfloating) # The parameter set Pi self._Pis = [q, p, Q, P, S] # No inner product set self._IP = None # Function for taking continuous roots self._sqrt = ContinuousSqrt(reference=angle(det(Q))) def __str__(self): r""":return: A string describing the Hagedorn wavepacket :math:`\Psi`. """ s = ("Homogeneous Hagedorn wavepacket with "+str(self._number_components) +" component(s) in "+str(self._dimension)+" space dimension(s)\n") return s def _get_sqrt(self, component): r"""Compatibility method """ return self._sqrt def get_description(self): r"""Return a description of this wavepacket object. A description is a ``dict`` containing all key-value pairs necessary to reconstruct the current instance. A description never contains any data. """ d = {} d["type"] = "HagedornWavepacket" d["dimension"] = self._dimension d["ncomponents"] = self._number_components d["eps"] = self._eps if self._IP is not None: d["innerproduct"] = self._IP.get_description() return d def clone(self, keepid=False): # Parameters of this packet params = self.get_description() # Create a new Packet # TODO: Consider using the block factory other = HagedornWavepacket(params["dimension"], params["ncomponents"], params["eps"]) # If we wish to keep the packet ID if keepid is True: other.set_id(self.get_id()) # And copy over all (private) data # Basis shapes are immutable, no issues with sharing same instance other.set_basis_shapes(self.get_basis_shapes()) other.set_parameters(self.get_parameters()) other.set_coefficients(self.get_coefficients()) # Innerproducts are stateless and finally immutable, # no issues with sharing same instance other.set_innerproduct(self.get_innerproduct()) # The complex root cache other._sqrt = self._sqrt.clone() return other def get_parameters(self, component=None, aslist=False, key=("q","p","Q","P","S")): r"""Get the Hagedorn parameter set :math:`\Pi` of the wavepacket :math:`\Psi`. :param component: Dummy parameter for API compatibility with the inhomogeneous packets. :param aslist: Return a list of :math:`N` parameter tuples. This is for API compatibility with inhomogeneous packets. :return: The Hagedorn parameter set :math:`\Pi = (q, p, Q, P, S)` in this order. """ Pilist = [] for k in key: if k == "q": Pilist.append(self._Pis[0]) elif k == "p": Pilist.append(self._Pis[1]) elif k == "Q": Pilist.append(self._Pis[2]) elif k == "P": Pilist.append(self._Pis[3]) elif k == "S": Pilist.append(self._Pis[4]) elif k == "adQ": Pilist.append(array(self._get_sqrt(component).get(), dtype=complexfloating)) else: raise KeyError("Invalid parameter key: "+str(key)) if aslist is True: return self._number_components * [ Pilist ] return Pilist def set_parameters(self, Pi, component=None, key=("q","p","Q","P","S")): r"""Set the Hagedorn parameters :math:`\Pi` of the wavepacket :math:`\Psi`. :param Pi: The Hagedorn parameter set :math:`\Pi = (q, p, Q, P, S)` in this order. :param component: Dummy parameter for API compatibility with the inhomogeneous packets. """ for k, item in zip(key, Pi): if k == "q": self._Pis[0] = atleast_2d(array(item, dtype=complexfloating)) elif k == "p": self._Pis[1] = atleast_2d(array(item, dtype=complexfloating)) elif k == "Q": self._Pis[2] = atleast_2d(array(item, dtype=complexfloating)) elif k == "P": self._Pis[3] = atleast_2d(array(item, dtype=complexfloating)) elif k == "S": self._Pis[4] = atleast_2d(array(item, dtype=complexfloating)) elif k == "adQ": self._get_sqrt(component).set(squeeze(item)) else: raise KeyError("Invalid parameter key: "+str(key))