|
"""Logic for representing operators in state in various bases. |
|
|
|
TODO: |
|
|
|
* Get represent working with continuous hilbert spaces. |
|
* Document default basis functionality. |
|
""" |
|
|
|
from sympy.core.add import Add |
|
from sympy.core.expr import Expr |
|
from sympy.core.mul import Mul |
|
from sympy.core.numbers import I |
|
from sympy.core.power import Pow |
|
from sympy.integrals.integrals import integrate |
|
from sympy.physics.quantum.dagger import Dagger |
|
from sympy.physics.quantum.commutator import Commutator |
|
from sympy.physics.quantum.anticommutator import AntiCommutator |
|
from sympy.physics.quantum.innerproduct import InnerProduct |
|
from sympy.physics.quantum.qexpr import QExpr |
|
from sympy.physics.quantum.tensorproduct import TensorProduct |
|
from sympy.physics.quantum.matrixutils import flatten_scalar |
|
from sympy.physics.quantum.state import KetBase, BraBase, StateBase |
|
from sympy.physics.quantum.operator import Operator, OuterProduct |
|
from sympy.physics.quantum.qapply import qapply |
|
from sympy.physics.quantum.operatorset import operators_to_state, state_to_operators |
|
|
|
|
|
__all__ = [ |
|
'represent', |
|
'rep_innerproduct', |
|
'rep_expectation', |
|
'integrate_result', |
|
'get_basis', |
|
'enumerate_states' |
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
def _sympy_to_scalar(e): |
|
"""Convert from a SymPy scalar to a Python scalar.""" |
|
if isinstance(e, Expr): |
|
if e.is_Integer: |
|
return int(e) |
|
elif e.is_Float: |
|
return float(e) |
|
elif e.is_Rational: |
|
return float(e) |
|
elif e.is_Number or e.is_NumberSymbol or e == I: |
|
return complex(e) |
|
raise TypeError('Expected number, got: %r' % e) |
|
|
|
|
|
def represent(expr, **options): |
|
"""Represent the quantum expression in the given basis. |
|
|
|
In quantum mechanics abstract states and operators can be represented in |
|
various basis sets. Under this operation the follow transforms happen: |
|
|
|
* Ket -> column vector or function |
|
* Bra -> row vector of function |
|
* Operator -> matrix or differential operator |
|
|
|
This function is the top-level interface for this action. |
|
|
|
This function walks the SymPy expression tree looking for ``QExpr`` |
|
instances that have a ``_represent`` method. This method is then called |
|
and the object is replaced by the representation returned by this method. |
|
By default, the ``_represent`` method will dispatch to other methods |
|
that handle the representation logic for a particular basis set. The |
|
naming convention for these methods is the following:: |
|
|
|
def _represent_FooBasis(self, e, basis, **options) |
|
|
|
This function will have the logic for representing instances of its class |
|
in the basis set having a class named ``FooBasis``. |
|
|
|
Parameters |
|
========== |
|
|
|
expr : Expr |
|
The expression to represent. |
|
basis : Operator, basis set |
|
An object that contains the information about the basis set. If an |
|
operator is used, the basis is assumed to be the orthonormal |
|
eigenvectors of that operator. In general though, the basis argument |
|
can be any object that contains the basis set information. |
|
options : dict |
|
Key/value pairs of options that are passed to the underlying method |
|
that finds the representation. These options can be used to |
|
control how the representation is done. For example, this is where |
|
the size of the basis set would be set. |
|
|
|
Returns |
|
======= |
|
|
|
e : Expr |
|
The SymPy expression of the represented quantum expression. |
|
|
|
Examples |
|
======== |
|
|
|
Here we subclass ``Operator`` and ``Ket`` to create the z-spin operator |
|
and its spin 1/2 up eigenstate. By defining the ``_represent_SzOp`` |
|
method, the ket can be represented in the z-spin basis. |
|
|
|
>>> from sympy.physics.quantum import Operator, represent, Ket |
|
>>> from sympy import Matrix |
|
|
|
>>> class SzUpKet(Ket): |
|
... def _represent_SzOp(self, basis, **options): |
|
... return Matrix([1,0]) |
|
... |
|
>>> class SzOp(Operator): |
|
... pass |
|
... |
|
>>> sz = SzOp('Sz') |
|
>>> up = SzUpKet('up') |
|
>>> represent(up, basis=sz) |
|
Matrix([ |
|
[1], |
|
[0]]) |
|
|
|
Here we see an example of representations in a continuous |
|
basis. We see that the result of representing various combinations |
|
of cartesian position operators and kets give us continuous |
|
expressions involving DiracDelta functions. |
|
|
|
>>> from sympy.physics.quantum.cartesian import XOp, XKet, XBra |
|
>>> X = XOp() |
|
>>> x = XKet() |
|
>>> y = XBra('y') |
|
>>> represent(X*x) |
|
x*DiracDelta(x - x_2) |
|
""" |
|
|
|
format = options.get('format', 'sympy') |
|
if format == 'numpy': |
|
import numpy as np |
|
if isinstance(expr, QExpr) and not isinstance(expr, OuterProduct): |
|
options['replace_none'] = False |
|
temp_basis = get_basis(expr, **options) |
|
if temp_basis is not None: |
|
options['basis'] = temp_basis |
|
try: |
|
return expr._represent(**options) |
|
except NotImplementedError as strerr: |
|
|
|
|
|
|
|
options['replace_none'] = True |
|
|
|
if isinstance(expr, (KetBase, BraBase)): |
|
try: |
|
return rep_innerproduct(expr, **options) |
|
except NotImplementedError: |
|
raise NotImplementedError(strerr) |
|
elif isinstance(expr, Operator): |
|
try: |
|
return rep_expectation(expr, **options) |
|
except NotImplementedError: |
|
raise NotImplementedError(strerr) |
|
else: |
|
raise NotImplementedError(strerr) |
|
elif isinstance(expr, Add): |
|
result = represent(expr.args[0], **options) |
|
for args in expr.args[1:]: |
|
|
|
result = result + represent(args, **options) |
|
return result |
|
elif isinstance(expr, Pow): |
|
base, exp = expr.as_base_exp() |
|
if format in ('numpy', 'scipy.sparse'): |
|
exp = _sympy_to_scalar(exp) |
|
base = represent(base, **options) |
|
|
|
|
|
if format == 'scipy.sparse' and exp < 0: |
|
from scipy.sparse.linalg import inv |
|
exp = - exp |
|
base = inv(base.tocsc()).tocsr() |
|
if format == 'numpy': |
|
return np.linalg.matrix_power(base, exp) |
|
return base ** exp |
|
elif isinstance(expr, TensorProduct): |
|
new_args = [represent(arg, **options) for arg in expr.args] |
|
return TensorProduct(*new_args) |
|
elif isinstance(expr, Dagger): |
|
return Dagger(represent(expr.args[0], **options)) |
|
elif isinstance(expr, Commutator): |
|
A = expr.args[0] |
|
B = expr.args[1] |
|
return represent(Mul(A, B) - Mul(B, A), **options) |
|
elif isinstance(expr, AntiCommutator): |
|
A = expr.args[0] |
|
B = expr.args[1] |
|
return represent(Mul(A, B) + Mul(B, A), **options) |
|
elif not isinstance(expr, (Mul, OuterProduct, InnerProduct)): |
|
|
|
|
|
|
|
if format in ('numpy', 'scipy.sparse'): |
|
return _sympy_to_scalar(expr) |
|
return expr |
|
|
|
if not isinstance(expr, (Mul, OuterProduct, InnerProduct)): |
|
raise TypeError('Mul expected, got: %r' % expr) |
|
|
|
if "index" in options: |
|
options["index"] += 1 |
|
else: |
|
options["index"] = 1 |
|
|
|
if "unities" not in options: |
|
options["unities"] = [] |
|
|
|
result = represent(expr.args[-1], **options) |
|
last_arg = expr.args[-1] |
|
|
|
for arg in reversed(expr.args[:-1]): |
|
if isinstance(last_arg, Operator): |
|
options["index"] += 1 |
|
options["unities"].append(options["index"]) |
|
elif isinstance(last_arg, BraBase) and isinstance(arg, KetBase): |
|
options["index"] += 1 |
|
elif isinstance(last_arg, KetBase) and isinstance(arg, Operator): |
|
options["unities"].append(options["index"]) |
|
elif isinstance(last_arg, KetBase) and isinstance(arg, BraBase): |
|
options["unities"].append(options["index"]) |
|
|
|
next_arg = represent(arg, **options) |
|
if format == 'numpy' and isinstance(next_arg, np.ndarray): |
|
|
|
result = np.matmul(next_arg, result) |
|
else: |
|
result = next_arg*result |
|
last_arg = arg |
|
|
|
|
|
|
|
result = flatten_scalar(result) |
|
|
|
result = integrate_result(expr, result, **options) |
|
|
|
return result |
|
|
|
|
|
def rep_innerproduct(expr, **options): |
|
""" |
|
Returns an innerproduct like representation (e.g. ``<x'|x>``) for the |
|
given state. |
|
|
|
Attempts to calculate inner product with a bra from the specified |
|
basis. Should only be passed an instance of KetBase or BraBase |
|
|
|
Parameters |
|
========== |
|
|
|
expr : KetBase or BraBase |
|
The expression to be represented |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.quantum.represent import rep_innerproduct |
|
>>> from sympy.physics.quantum.cartesian import XOp, XKet, PxOp, PxKet |
|
>>> rep_innerproduct(XKet()) |
|
DiracDelta(x - x_1) |
|
>>> rep_innerproduct(XKet(), basis=PxOp()) |
|
sqrt(2)*exp(-I*px_1*x/hbar)/(2*sqrt(hbar)*sqrt(pi)) |
|
>>> rep_innerproduct(PxKet(), basis=XOp()) |
|
sqrt(2)*exp(I*px*x_1/hbar)/(2*sqrt(hbar)*sqrt(pi)) |
|
|
|
""" |
|
|
|
if not isinstance(expr, (KetBase, BraBase)): |
|
raise TypeError("expr passed is not a Bra or Ket") |
|
|
|
basis = get_basis(expr, **options) |
|
|
|
if not isinstance(basis, StateBase): |
|
raise NotImplementedError("Can't form this representation!") |
|
|
|
if "index" not in options: |
|
options["index"] = 1 |
|
|
|
basis_kets = enumerate_states(basis, options["index"], 2) |
|
|
|
if isinstance(expr, BraBase): |
|
bra = expr |
|
ket = (basis_kets[1] if basis_kets[0].dual == expr else basis_kets[0]) |
|
else: |
|
bra = (basis_kets[1].dual if basis_kets[0] |
|
== expr else basis_kets[0].dual) |
|
ket = expr |
|
|
|
prod = InnerProduct(bra, ket) |
|
result = prod.doit() |
|
|
|
format = options.get('format', 'sympy') |
|
result = expr._format_represent(result, format) |
|
return result |
|
|
|
|
|
def rep_expectation(expr, **options): |
|
""" |
|
Returns an ``<x'|A|x>`` type representation for the given operator. |
|
|
|
Parameters |
|
========== |
|
|
|
expr : Operator |
|
Operator to be represented in the specified basis |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.quantum.cartesian import XOp, PxOp, PxKet |
|
>>> from sympy.physics.quantum.represent import rep_expectation |
|
>>> rep_expectation(XOp()) |
|
x_1*DiracDelta(x_1 - x_2) |
|
>>> rep_expectation(XOp(), basis=PxOp()) |
|
<px_2|*X*|px_1> |
|
>>> rep_expectation(XOp(), basis=PxKet()) |
|
<px_2|*X*|px_1> |
|
|
|
""" |
|
|
|
if "index" not in options: |
|
options["index"] = 1 |
|
|
|
if not isinstance(expr, Operator): |
|
raise TypeError("The passed expression is not an operator") |
|
|
|
basis_state = get_basis(expr, **options) |
|
|
|
if basis_state is None or not isinstance(basis_state, StateBase): |
|
raise NotImplementedError("Could not get basis kets for this operator") |
|
|
|
basis_kets = enumerate_states(basis_state, options["index"], 2) |
|
|
|
bra = basis_kets[1].dual |
|
ket = basis_kets[0] |
|
|
|
result = qapply(bra*expr*ket) |
|
return result |
|
|
|
|
|
def integrate_result(orig_expr, result, **options): |
|
""" |
|
Returns the result of integrating over any unities ``(|x><x|)`` in |
|
the given expression. Intended for integrating over the result of |
|
representations in continuous bases. |
|
|
|
This function integrates over any unities that may have been |
|
inserted into the quantum expression and returns the result. |
|
It uses the interval of the Hilbert space of the basis state |
|
passed to it in order to figure out the limits of integration. |
|
The unities option must be |
|
specified for this to work. |
|
|
|
Note: This is mostly used internally by represent(). Examples are |
|
given merely to show the use cases. |
|
|
|
Parameters |
|
========== |
|
|
|
orig_expr : quantum expression |
|
The original expression which was to be represented |
|
|
|
result: Expr |
|
The resulting representation that we wish to integrate over |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import symbols, DiracDelta |
|
>>> from sympy.physics.quantum.represent import integrate_result |
|
>>> from sympy.physics.quantum.cartesian import XOp, XKet |
|
>>> x_ket = XKet() |
|
>>> X_op = XOp() |
|
>>> x, x_1, x_2 = symbols('x, x_1, x_2') |
|
>>> integrate_result(X_op*x_ket, x*DiracDelta(x-x_1)*DiracDelta(x_1-x_2)) |
|
x*DiracDelta(x - x_1)*DiracDelta(x_1 - x_2) |
|
>>> integrate_result(X_op*x_ket, x*DiracDelta(x-x_1)*DiracDelta(x_1-x_2), |
|
... unities=[1]) |
|
x*DiracDelta(x - x_2) |
|
|
|
""" |
|
if not isinstance(result, Expr): |
|
return result |
|
|
|
options['replace_none'] = True |
|
if "basis" not in options: |
|
arg = orig_expr.args[-1] |
|
options["basis"] = get_basis(arg, **options) |
|
elif not isinstance(options["basis"], StateBase): |
|
options["basis"] = get_basis(orig_expr, **options) |
|
|
|
basis = options.pop("basis", None) |
|
|
|
if basis is None: |
|
return result |
|
|
|
unities = options.pop("unities", []) |
|
|
|
if len(unities) == 0: |
|
return result |
|
|
|
kets = enumerate_states(basis, unities) |
|
coords = [k.label[0] for k in kets] |
|
|
|
for coord in coords: |
|
if coord in result.free_symbols: |
|
|
|
basis_op = state_to_operators(basis) |
|
start = basis_op.hilbert_space.interval.start |
|
end = basis_op.hilbert_space.interval.end |
|
result = integrate(result, (coord, start, end)) |
|
|
|
return result |
|
|
|
|
|
def get_basis(expr, *, basis=None, replace_none=True, **options): |
|
""" |
|
Returns a basis state instance corresponding to the basis specified in |
|
options=s. If no basis is specified, the function tries to form a default |
|
basis state of the given expression. |
|
|
|
There are three behaviors: |
|
|
|
1. The basis specified in options is already an instance of StateBase. If |
|
this is the case, it is simply returned. If the class is specified but |
|
not an instance, a default instance is returned. |
|
|
|
2. The basis specified is an operator or set of operators. If this |
|
is the case, the operator_to_state mapping method is used. |
|
|
|
3. No basis is specified. If expr is a state, then a default instance of |
|
its class is returned. If expr is an operator, then it is mapped to the |
|
corresponding state. If it is neither, then we cannot obtain the basis |
|
state. |
|
|
|
If the basis cannot be mapped, then it is not changed. |
|
|
|
This will be called from within represent, and represent will |
|
only pass QExpr's. |
|
|
|
TODO (?): Support for Muls and other types of expressions? |
|
|
|
Parameters |
|
========== |
|
|
|
expr : Operator or StateBase |
|
Expression whose basis is sought |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.quantum.represent import get_basis |
|
>>> from sympy.physics.quantum.cartesian import XOp, XKet, PxOp, PxKet |
|
>>> x = XKet() |
|
>>> X = XOp() |
|
>>> get_basis(x) |
|
|x> |
|
>>> get_basis(X) |
|
|x> |
|
>>> get_basis(x, basis=PxOp()) |
|
|px> |
|
>>> get_basis(x, basis=PxKet) |
|
|px> |
|
|
|
""" |
|
|
|
if basis is None and not replace_none: |
|
return None |
|
|
|
if basis is None: |
|
if isinstance(expr, KetBase): |
|
return _make_default(expr.__class__) |
|
elif isinstance(expr, BraBase): |
|
return _make_default(expr.dual_class()) |
|
elif isinstance(expr, Operator): |
|
state_inst = operators_to_state(expr) |
|
return (state_inst if state_inst is not None else None) |
|
else: |
|
return None |
|
elif (isinstance(basis, Operator) or |
|
(not isinstance(basis, StateBase) and issubclass(basis, Operator))): |
|
state = operators_to_state(basis) |
|
if state is None: |
|
return None |
|
elif isinstance(state, StateBase): |
|
return state |
|
else: |
|
return _make_default(state) |
|
elif isinstance(basis, StateBase): |
|
return basis |
|
elif issubclass(basis, StateBase): |
|
return _make_default(basis) |
|
else: |
|
return None |
|
|
|
|
|
def _make_default(expr): |
|
|
|
|
|
|
|
try: |
|
expr = expr() |
|
except TypeError: |
|
return expr |
|
|
|
return expr |
|
|
|
|
|
def enumerate_states(*args, **options): |
|
""" |
|
Returns instances of the given state with dummy indices appended |
|
|
|
Operates in two different modes: |
|
|
|
1. Two arguments are passed to it. The first is the base state which is to |
|
be indexed, and the second argument is a list of indices to append. |
|
|
|
2. Three arguments are passed. The first is again the base state to be |
|
indexed. The second is the start index for counting. The final argument |
|
is the number of kets you wish to receive. |
|
|
|
Tries to call state._enumerate_state. If this fails, returns an empty list |
|
|
|
Parameters |
|
========== |
|
|
|
args : list |
|
See list of operation modes above for explanation |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.quantum.cartesian import XBra, XKet |
|
>>> from sympy.physics.quantum.represent import enumerate_states |
|
>>> test = XKet('foo') |
|
>>> enumerate_states(test, 1, 3) |
|
[|foo_1>, |foo_2>, |foo_3>] |
|
>>> test2 = XBra('bar') |
|
>>> enumerate_states(test2, [4, 5, 10]) |
|
[<bar_4|, <bar_5|, <bar_10|] |
|
|
|
""" |
|
|
|
state = args[0] |
|
|
|
if len(args) not in (2, 3): |
|
raise NotImplementedError("Wrong number of arguments!") |
|
|
|
if not isinstance(state, StateBase): |
|
raise TypeError("First argument is not a state!") |
|
|
|
if len(args) == 3: |
|
num_states = args[2] |
|
options['start_index'] = args[1] |
|
else: |
|
num_states = len(args[1]) |
|
options['index_list'] = args[1] |
|
|
|
try: |
|
ret = state._enumerate_state(num_states, **options) |
|
except NotImplementedError: |
|
ret = [] |
|
|
|
return ret |
|
|