wavepacket 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wavepacket/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ """
2
+ A package for the propagation of quantum-mechanical wave functions.
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ from . import builder
8
+ from . import expression
9
+ from . import grid
10
+ from . import operator
11
+ from . import solver
12
+ from . import testing
13
+ from . import typing
14
+
15
+
16
+ from .exceptions import (BadFunctionCall, BadGridError, BadStateError, ExecutionError,
17
+ InvalidValueError)
18
+ from .generators import Gaussian, PlaneWave
19
+ from .logging import log
20
+
21
+
22
+ __all__ = ['__version__', 'log', 'BadFunctionCall', 'BadGridError', 'BadStateError',
23
+ 'ExecutionError', 'InvalidValueError',
24
+ 'Gaussian', 'PlaneWave',
25
+ 'builder', 'expression', 'grid', 'operator', 'solver', 'testing', 'typing']
@@ -0,0 +1,8 @@
1
+ """
2
+ Functions to assemble wave functions or density operators.
3
+ """
4
+
5
+ __all__ = ['direct_product', 'pure_density', 'product_wave_function']
6
+
7
+ from .density import direct_product, pure_density
8
+ from .wave_function import product_wave_function
@@ -0,0 +1,73 @@
1
+ import numpy as np
2
+
3
+ import wavepacket as wp
4
+ from ..grid import State
5
+
6
+
7
+ def pure_density(psi: State) -> State:
8
+ """
9
+ Given an input wave function, create the corresponding pure density operator.
10
+
11
+ This function only performs the direct product, it does not apply
12
+ further modifications like normalizations.
13
+
14
+ Parameters
15
+ ----------
16
+ psi : wp.grid.State
17
+ The input wave function
18
+
19
+ Returns
20
+ -------
21
+ wp.grid.State
22
+ The corresponding density operator.
23
+
24
+ Raises
25
+ ------
26
+ wp.BadStateError
27
+ If the input is not a valid wave function.
28
+
29
+ See also
30
+ --------
31
+ direct_product : This function is identical to `direct_product(psi, psi)`
32
+ """
33
+ return direct_product(psi, psi)
34
+
35
+
36
+ def direct_product(ket: State, bra: State) -> State:
37
+ """
38
+ Returns the direct product of wave functions as a density operator.
39
+
40
+ Given two wave functions :math:`\psi, \phi`, this function returns the
41
+ density operator as :math:`| \psi \\rangle\langle \phi |`.
42
+ This operation can be useful to build up a
43
+ density operator piece by piece.
44
+
45
+ Parameters
46
+ ----------
47
+ ket : wp.grid.State
48
+ The ket state :math:`\psi`
49
+ bra : wp.grid.State
50
+ The bra state :math:`\phi`. Note that the function performs a
51
+ complex conjugation of this state prior to multiplication.
52
+
53
+ Returns
54
+ -------
55
+ wp.grid.State
56
+ The direct product of the two states.
57
+
58
+ Raises
59
+ ------
60
+ wp.BadStateError
61
+ If one of the input states is not a valid wave function.
62
+ wp.BadGridError
63
+ If the input states are defined on different grids.
64
+ """
65
+ if not ket.is_wave_function() or not bra.is_wave_function():
66
+ raise wp.BadStateError("Density operator can only be constructed from wave functions.")
67
+
68
+ if ket.grid != bra.grid:
69
+ raise wp.BadGridError("Grid for bra and ket states does not match")
70
+
71
+ rho_matrix = np.outer(ket.data, np.conj(bra.data))
72
+
73
+ return State(ket.grid, np.reshape(rho_matrix, ket.grid.operator_shape))
@@ -0,0 +1,63 @@
1
+ from collections.abc import Sequence
2
+ from typing import Iterable
3
+
4
+ import numpy as np
5
+
6
+ import wavepacket as wp
7
+ import wavepacket.typing as wpt
8
+ from ..grid import Grid, State
9
+
10
+
11
+ def product_wave_function(grid: Grid,
12
+ generators: wpt.Generator | Sequence[wpt.Generator],
13
+ normalize: bool = True) -> State:
14
+ """
15
+ Builds a product wave function from a set of one-dimensional wave functions.
16
+
17
+ Parameters
18
+ ----------
19
+ grid : wp.grid.Grid
20
+ The grid on which the product wave function is assembled
21
+ generators : Sequence[wp.typing.Generator]
22
+ A list of callables that specifies the wave function
23
+ along each degree of freedom. The `generators` return
24
+ the one-dimensional functions in the DVR, i.e., raw function
25
+ values at the grid points.
26
+ normalize : bool, default=true
27
+ If the norm is non-zero and this value is set, the resulting
28
+ product wave function is normalized, otherwise the product
29
+ is returned directly.
30
+
31
+ Returns
32
+ -------
33
+ wp.grid.State
34
+ The product wave function in the Wavepacket-default weighted DVR.
35
+
36
+ Raises
37
+ ------
38
+ wp.InvalidValueError
39
+ If the number of generators does not match the grid dimensions.
40
+ """
41
+ generator_list = generators
42
+ if not isinstance(generator_list, Iterable):
43
+ generator_list = [generators]
44
+
45
+ if len(generator_list) != len(grid.dofs):
46
+ raise wp.InvalidValueError(
47
+ "To build a wave function, you need as many generators as degrees of freedoms."
48
+ f"Given {len(generator_list)} generators for {len(grid.dofs)} DOFs.")
49
+
50
+ result_data = np.ones(grid.shape, dtype=complex)
51
+ for dof_index, generator in enumerate(generator_list):
52
+ dof = grid.dofs[dof_index]
53
+ array = generator(dof.dvr_points)
54
+ array = dof.from_dvr(array, 0)
55
+
56
+ result_data *= grid.broadcast(array, dof_index)
57
+
58
+ result = State(grid, result_data)
59
+ norm = np.sqrt(wp.grid.trace(result))
60
+ if normalize and norm > 0:
61
+ return result / norm
62
+ else:
63
+ return result
@@ -0,0 +1,48 @@
1
+ class BadFunctionCall(Exception):
2
+ """
3
+ Signals that a function was called incorrectly.
4
+
5
+ A typical but rare use-case would be a function that was
6
+ called with incorrect parameters.
7
+ """
8
+ pass
9
+
10
+
11
+ class BadGridError(Exception):
12
+ """
13
+ An invalid grid was supplied.
14
+
15
+ Most often, you attempt an operation between objects that must be defined on the
16
+ same grid. For example, the addition of two operators defined on different grids
17
+ is not a useful operation. The grid may also miss required properties, for example
18
+ an operation may expect a specific degree of freedom type along some index.
19
+ """
20
+ pass
21
+
22
+
23
+ class BadStateError(Exception):
24
+ """
25
+ An invalid state was supplied.
26
+
27
+ Either the state is completely invalid (neither wave function nor density operator),
28
+ or you supplied the wrong type of state to a function, for example passing a
29
+ density operator where a wave function was required.
30
+ """
31
+ pass
32
+
33
+
34
+ class ExecutionError(Exception):
35
+ """
36
+ An unrecoverable problem was encountered in foreign code.
37
+
38
+ The main example is the :py:class:`wavepacket.solver.odesolver` getting
39
+ an error back while integrating.
40
+ """
41
+ pass
42
+
43
+
44
+ class InvalidValueError(Exception):
45
+ """
46
+ A function argument was incorrect, for example out of bounds.
47
+ """
48
+ pass
@@ -0,0 +1,10 @@
1
+ """
2
+ Classes that wrap operators into expressions for use in partial differential equations.
3
+ """
4
+
5
+ __all__ = ['CommutatorLiouvillian',
6
+ 'ExpressionBase', 'SchroedingerEquation']
7
+
8
+ from .expressionbase import ExpressionBase
9
+ from .liouvillian import CommutatorLiouvillian
10
+ from .schroedingerequation import SchroedingerEquation
@@ -0,0 +1,44 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+
4
+ from ..grid import State
5
+
6
+
7
+ class ExpressionBase(ABC):
8
+ """
9
+ Base class for expressions.
10
+
11
+ By deriving from this class and implementing the method
12
+ :py:meth:`ExpressionBase.apply`, you can add custom expressions.
13
+
14
+ Notes
15
+ -----
16
+ All differential equations have the form
17
+ :math:`\dot \\rho = \mathcal{L}(\\rho)` (or equivalently
18
+ :math:`\dot \psi = \hat H \psi`), that is, the left-hand side
19
+ is just the time derivative. This matches the common convention for the
20
+ Liouville von-Neumann equation, but differs from the usual
21
+ form of the Schrödinger equation, where the imaginary factor
22
+ is on the left-hand side of the equation.
23
+ """
24
+
25
+ @abstractmethod
26
+ def apply(self, state: State, t: Optional[float] = None) -> State:
27
+ """
28
+ Applies the expression to the input state and returns the result.
29
+
30
+ Parameters
31
+ ----------
32
+ state : wp.grid.State
33
+ The state on which the expression is applied.
34
+ t : float, optional
35
+ The time at which the expression is evaluated. Default is None,
36
+ which will raise an exception if the contained expression is time-dependent.
37
+
38
+ Raises
39
+ ------
40
+ wp.BadStateError
41
+ If the state is invalid or has the wrong time. For example, a Schroedinger equaiton
42
+ makes little sense for a density operator.
43
+ """
44
+ pass
@@ -0,0 +1,64 @@
1
+ from typing import Optional
2
+
3
+ import wavepacket as wp
4
+ from .expressionbase import ExpressionBase
5
+ from ..grid import State
6
+ from ..operator import OperatorBase
7
+
8
+
9
+ class CommutatorLiouvillian(ExpressionBase):
10
+ """
11
+ Represents a commutator expression in a Liouville von-Neumann equation.
12
+
13
+ Given an operator `H`, this commutator expression is given by
14
+ :math:`\mathcal{L}(\hat \\rho) = -\imath (\hat H \hat \\rho - \hat \\rho \hat H)`.
15
+
16
+ Parameters
17
+ ----------
18
+ op : wp.operator.OperatorBase
19
+ The operator to commute with the density operator.
20
+
21
+ Notes
22
+ -----
23
+ The extra factor of -i is added to ensure that the commutator can be directly
24
+ plugged into a Liouville von-Neumann equation. defined as
25
+ :math:`\\frac{\partial \hat \\rho}{\partial t} = \mathcal{L}(\hat \\rho)`.
26
+ If you need the raw commutator, you have to multiply the result with
27
+ the imaginary number.
28
+ """
29
+
30
+ def __init__(self, op: OperatorBase):
31
+ self._op = op
32
+
33
+ def apply(self, rho: State, t: Optional[float] = None) -> State:
34
+ """
35
+ Evaluates the commutator for the given density operator and time.
36
+
37
+ Parameters
38
+ ----------
39
+ rho : wp.grid.State
40
+ The density operator to commute with
41
+ t : float, optional
42
+ The time at which the operator is evaluated. By default it is None,
43
+ which will throw if the contained operator is time-dependent.
44
+
45
+ Returns
46
+ -------
47
+ wp.grid.State
48
+ The result of the commutator.
49
+
50
+ Raises
51
+ ------
52
+ wp.BadGridError
53
+ If the grids of the density operator and the wrapped operator do not match.
54
+ wp.BadStateError
55
+ If the input state is not a valid density operator.
56
+ """
57
+ if rho.grid != self._op.grid:
58
+ raise wp.BadGridError("Input state is defined on the wrong grid.")
59
+
60
+ if not rho.is_density_operator():
61
+ raise wp.BadStateError("CommutatorLiouvillian requires a density operator.")
62
+
63
+ return State(rho.grid,
64
+ -1j * (self._op.apply_from_left(rho.data, t) - self._op.apply_from_right(rho.data, t)))
@@ -0,0 +1,65 @@
1
+ from typing import Optional
2
+
3
+ import wavepacket as wp
4
+ from .expressionbase import ExpressionBase
5
+ from ..grid import State
6
+ from ..operator import OperatorBase
7
+
8
+
9
+ class SchroedingerEquation(ExpressionBase):
10
+ """
11
+ Expression wrapper for a Schrödinger equation.
12
+
13
+ You should wrap a Hamiltonian in an object of this type
14
+ so that the solvers can subsequently solve the resulting
15
+ equation.
16
+
17
+ Parameters
18
+ ----------
19
+ hamiltonian : wp.operator.OperatorBase
20
+ The Hamiltonian that is wrapped by this class.
21
+
22
+ Notes
23
+ -----
24
+ The Schrödinger equation is given by
25
+ :math:`\dot \psi = -\imath \hat H \psi`,
26
+ so this class only multiplies the wave function with the
27
+ negative imaginary number, and wraps the input Hamiltonian
28
+ into an expression so that solvers can work with it.
29
+ """
30
+
31
+ def __init__(self, hamiltonian: OperatorBase):
32
+ self._hamiltonian = hamiltonian
33
+
34
+ def apply(self, psi: State, t: Optional[float] = None) -> State:
35
+ """
36
+ Evaluates the right-hand side of the Schrödinger equation
37
+ for the given input state and time.
38
+
39
+ Parameters
40
+ ----------
41
+ psi : wp.grid.State
42
+ The input state that is evaluated.
43
+ t : float, optional
44
+ The time at which the Schrödinger equation is evaluated.
45
+ The default `None` throws if the wrapped operator is time-dependent.
46
+
47
+ Returns
48
+ -------
49
+ wp.grid.State
50
+ The result of the evaluation.
51
+
52
+ Raises
53
+ ------
54
+ wp.grid.BadGridError
55
+ If the state's grid differs from that of the Hamiltonian.
56
+ wp.grid.BadStateError
57
+ If the input state is not a wave function.
58
+ """
59
+ if psi.grid != self._hamiltonian.grid:
60
+ raise wp.BadGridError("Input state has wrong grid.")
61
+
62
+ if not psi.is_wave_function():
63
+ raise wp.BadStateError("SchroedingerEquation requires a wave function.")
64
+
65
+ return State(psi.grid, -1j * self._hamiltonian.apply_to_wave_function(psi.data, t))
@@ -0,0 +1,88 @@
1
+ from typing import Optional
2
+
3
+ import numpy as np
4
+
5
+ import wavepacket as wp
6
+ import wavepacket.typing as wpt
7
+
8
+
9
+ class Gaussian(wpt.Generator):
10
+ """
11
+ Callable that defines a one-dimensional Gaussian function.
12
+
13
+ This callable can be supplied wherever a callable is required. An example
14
+ would be an initial wave function for :py:func:`wavepacket.builder.product_wave_function`,
15
+ or a potential wrapped in a :py:class:`wavepacket.operator.Potential1D`.
16
+
17
+ Parameters
18
+ ----------
19
+ x : float, default=0
20
+ The center of the Gaussian.
21
+ p : float, default=0
22
+ The momentum of the Gaussian.
23
+ rms, fwhm : float
24
+ You must specify the width of the Gaussian using exactly one of these values,
25
+ either through the root-mean-square width, or the full-width-at-half-maximum.
26
+
27
+ Raises
28
+ ------
29
+ wp.InvalidValueError
30
+ If the width of the Gaussian is not positive.
31
+ wp.BadFunctionCall
32
+ If both rms and fwhm have either been set or not supplied.
33
+
34
+ Notes
35
+ -----
36
+
37
+ Up to scaling, the functional form of the Gaussian is
38
+ :math:`f(x) = e^{-(x-x_0)^2 / 2 \sigma^2 + \imath p (x-x_0)}`.
39
+ Here, sigma is the rms width, which is connected to the FWHM by
40
+ :math:`\sigma = \mathrm{FWHM} / \sqrt{8 \ln 2}`.
41
+ """
42
+
43
+ def __init__(self, x: float = 0.0, p: float = 0.0,
44
+ rms: Optional[float] = None, fwhm: Optional[float] = None):
45
+ if rms is not None and rms <= 0:
46
+ raise wp.InvalidValueError(f"RMS width of Gaussian is {rms}, but should be positive.")
47
+
48
+ if fwhm is not None and fwhm <= 0:
49
+ raise wp.InvalidValueError(f"FWHM of Gaussian is {rms}, but should be positive.")
50
+
51
+ if fwhm is not None and rms is not None:
52
+ raise wp.BadFunctionCall("Only one of RMS width or FWHM must be set, not both.")
53
+ if fwhm is None and rms is None:
54
+ raise wp.BadFunctionCall("One of RMS width or FWHM must be set.")
55
+
56
+ self._x = x
57
+ self._p = p
58
+ if rms:
59
+ self._rms = rms
60
+ else:
61
+ self._rms = fwhm / np.sqrt(8 * np.log(2))
62
+
63
+ def __call__(self, x: wpt.RealData) -> wpt.ComplexData:
64
+ shifted = x - self._x
65
+ arg = - shifted ** 2 / (2 * self._rms ** 2) + 1j * self._p * shifted
66
+ return np.exp(arg)
67
+
68
+
69
+ class PlaneWave:
70
+ """
71
+ Callable that defines a plane wave.
72
+
73
+ You will typically use this callable for initial states. There are often
74
+ better options, especially if your FBR already defines a plane wave basis,
75
+ but sometimes you may just want to represent a reasonable plane wave and
76
+ not count indices to get the correct wave vector.
77
+
78
+ Parameters
79
+ ----------
80
+ k : float
81
+ The wave vector of the plane wave.
82
+ """
83
+
84
+ def __init__(self, k: float):
85
+ self._k = k
86
+
87
+ def __call__(self, x: wpt.RealData) -> wpt.ComplexData:
88
+ return np.exp(1j * self._k * x)
@@ -0,0 +1,12 @@
1
+ """
2
+ This module contains the classes to define a grid and represent states on it.
3
+ """
4
+
5
+ __all__ = ['DofBase', 'Grid', 'PlaneWaveDof', 'State',
6
+ 'dvr_density', 'trace']
7
+
8
+ from .dofbase import DofBase
9
+ from .grid import Grid
10
+ from .planewavedof import PlaneWaveDof
11
+ from .state import State
12
+ from .state_utilities import dvr_density, trace