651 lines
19 KiB
Python
651 lines
19 KiB
Python
"""Quantum mechanical operators.
|
|
|
|
TODO:
|
|
|
|
* Fix early 0 in apply_operators.
|
|
* Debug and test apply_operators.
|
|
* Get cse working with classes in this file.
|
|
* Doctests and documentation of special methods for InnerProduct, Commutator,
|
|
AntiCommutator, represent, apply_operators.
|
|
"""
|
|
|
|
from sympy.core.add import Add
|
|
from sympy.core.expr import Expr
|
|
from sympy.core.function import (Derivative, expand)
|
|
from sympy.core.mul import Mul
|
|
from sympy.core.numbers import oo
|
|
from sympy.core.singleton import S
|
|
from sympy.printing.pretty.stringpict import prettyForm
|
|
from sympy.physics.quantum.dagger import Dagger
|
|
from sympy.physics.quantum.qexpr import QExpr, dispatch_method
|
|
from sympy.matrices import eye
|
|
|
|
__all__ = [
|
|
'Operator',
|
|
'HermitianOperator',
|
|
'UnitaryOperator',
|
|
'IdentityOperator',
|
|
'OuterProduct',
|
|
'DifferentialOperator'
|
|
]
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Operators and outer products
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
class Operator(QExpr):
|
|
"""Base class for non-commuting quantum operators.
|
|
|
|
An operator maps between quantum states [1]_. In quantum mechanics,
|
|
observables (including, but not limited to, measured physical values) are
|
|
represented as Hermitian operators [2]_.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
args : tuple
|
|
The list of numbers or parameters that uniquely specify the
|
|
operator. For time-dependent operators, this will include the time.
|
|
|
|
Examples
|
|
========
|
|
|
|
Create an operator and examine its attributes::
|
|
|
|
>>> from sympy.physics.quantum import Operator
|
|
>>> from sympy import I
|
|
>>> A = Operator('A')
|
|
>>> A
|
|
A
|
|
>>> A.hilbert_space
|
|
H
|
|
>>> A.label
|
|
(A,)
|
|
>>> A.is_commutative
|
|
False
|
|
|
|
Create another operator and do some arithmetic operations::
|
|
|
|
>>> B = Operator('B')
|
|
>>> C = 2*A*A + I*B
|
|
>>> C
|
|
2*A**2 + I*B
|
|
|
|
Operators do not commute::
|
|
|
|
>>> A.is_commutative
|
|
False
|
|
>>> B.is_commutative
|
|
False
|
|
>>> A*B == B*A
|
|
False
|
|
|
|
Polymonials of operators respect the commutation properties::
|
|
|
|
>>> e = (A+B)**3
|
|
>>> e.expand()
|
|
A*B*A + A*B**2 + A**2*B + A**3 + B*A*B + B*A**2 + B**2*A + B**3
|
|
|
|
Operator inverses are handle symbolically::
|
|
|
|
>>> A.inv()
|
|
A**(-1)
|
|
>>> A*A.inv()
|
|
1
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Operator_%28physics%29
|
|
.. [2] https://en.wikipedia.org/wiki/Observable
|
|
"""
|
|
|
|
@classmethod
|
|
def default_args(self):
|
|
return ("O",)
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Printing
|
|
#-------------------------------------------------------------------------
|
|
|
|
_label_separator = ','
|
|
|
|
def _print_operator_name(self, printer, *args):
|
|
return self.__class__.__name__
|
|
|
|
_print_operator_name_latex = _print_operator_name
|
|
|
|
def _print_operator_name_pretty(self, printer, *args):
|
|
return prettyForm(self.__class__.__name__)
|
|
|
|
def _print_contents(self, printer, *args):
|
|
if len(self.label) == 1:
|
|
return self._print_label(printer, *args)
|
|
else:
|
|
return '%s(%s)' % (
|
|
self._print_operator_name(printer, *args),
|
|
self._print_label(printer, *args)
|
|
)
|
|
|
|
def _print_contents_pretty(self, printer, *args):
|
|
if len(self.label) == 1:
|
|
return self._print_label_pretty(printer, *args)
|
|
else:
|
|
pform = self._print_operator_name_pretty(printer, *args)
|
|
label_pform = self._print_label_pretty(printer, *args)
|
|
label_pform = prettyForm(
|
|
*label_pform.parens(left='(', right=')')
|
|
)
|
|
pform = prettyForm(*pform.right(label_pform))
|
|
return pform
|
|
|
|
def _print_contents_latex(self, printer, *args):
|
|
if len(self.label) == 1:
|
|
return self._print_label_latex(printer, *args)
|
|
else:
|
|
return r'%s\left(%s\right)' % (
|
|
self._print_operator_name_latex(printer, *args),
|
|
self._print_label_latex(printer, *args)
|
|
)
|
|
|
|
#-------------------------------------------------------------------------
|
|
# _eval_* methods
|
|
#-------------------------------------------------------------------------
|
|
|
|
def _eval_commutator(self, other, **options):
|
|
"""Evaluate [self, other] if known, return None if not known."""
|
|
return dispatch_method(self, '_eval_commutator', other, **options)
|
|
|
|
def _eval_anticommutator(self, other, **options):
|
|
"""Evaluate [self, other] if known."""
|
|
return dispatch_method(self, '_eval_anticommutator', other, **options)
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Operator application
|
|
#-------------------------------------------------------------------------
|
|
|
|
def _apply_operator(self, ket, **options):
|
|
return dispatch_method(self, '_apply_operator', ket, **options)
|
|
|
|
def matrix_element(self, *args):
|
|
raise NotImplementedError('matrix_elements is not defined')
|
|
|
|
def inverse(self):
|
|
return self._eval_inverse()
|
|
|
|
inv = inverse
|
|
|
|
def _eval_inverse(self):
|
|
return self**(-1)
|
|
|
|
def __mul__(self, other):
|
|
|
|
if isinstance(other, IdentityOperator):
|
|
return self
|
|
|
|
return Mul(self, other)
|
|
|
|
|
|
class HermitianOperator(Operator):
|
|
"""A Hermitian operator that satisfies H == Dagger(H).
|
|
|
|
Parameters
|
|
==========
|
|
|
|
args : tuple
|
|
The list of numbers or parameters that uniquely specify the
|
|
operator. For time-dependent operators, this will include the time.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.physics.quantum import Dagger, HermitianOperator
|
|
>>> H = HermitianOperator('H')
|
|
>>> Dagger(H)
|
|
H
|
|
"""
|
|
|
|
is_hermitian = True
|
|
|
|
def _eval_inverse(self):
|
|
if isinstance(self, UnitaryOperator):
|
|
return self
|
|
else:
|
|
return Operator._eval_inverse(self)
|
|
|
|
def _eval_power(self, exp):
|
|
if isinstance(self, UnitaryOperator):
|
|
# so all eigenvalues of self are 1 or -1
|
|
if exp.is_even:
|
|
from sympy.core.singleton import S
|
|
return S.One # is identity, see Issue 24153.
|
|
elif exp.is_odd:
|
|
return self
|
|
# No simplification in all other cases
|
|
return Operator._eval_power(self, exp)
|
|
|
|
|
|
class UnitaryOperator(Operator):
|
|
"""A unitary operator that satisfies U*Dagger(U) == 1.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
args : tuple
|
|
The list of numbers or parameters that uniquely specify the
|
|
operator. For time-dependent operators, this will include the time.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.physics.quantum import Dagger, UnitaryOperator
|
|
>>> U = UnitaryOperator('U')
|
|
>>> U*Dagger(U)
|
|
1
|
|
"""
|
|
|
|
def _eval_adjoint(self):
|
|
return self._eval_inverse()
|
|
|
|
|
|
class IdentityOperator(Operator):
|
|
"""An identity operator I that satisfies op * I == I * op == op for any
|
|
operator op.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
N : Integer
|
|
Optional parameter that specifies the dimension of the Hilbert space
|
|
of operator. This is used when generating a matrix representation.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.physics.quantum import IdentityOperator
|
|
>>> IdentityOperator()
|
|
I
|
|
"""
|
|
@property
|
|
def dimension(self):
|
|
return self.N
|
|
|
|
@classmethod
|
|
def default_args(self):
|
|
return (oo,)
|
|
|
|
def __init__(self, *args, **hints):
|
|
if not len(args) in (0, 1):
|
|
raise ValueError('0 or 1 parameters expected, got %s' % args)
|
|
|
|
self.N = args[0] if (len(args) == 1 and args[0]) else oo
|
|
|
|
def _eval_commutator(self, other, **hints):
|
|
return S.Zero
|
|
|
|
def _eval_anticommutator(self, other, **hints):
|
|
return 2 * other
|
|
|
|
def _eval_inverse(self):
|
|
return self
|
|
|
|
def _eval_adjoint(self):
|
|
return self
|
|
|
|
def _apply_operator(self, ket, **options):
|
|
return ket
|
|
|
|
def _apply_from_right_to(self, bra, **options):
|
|
return bra
|
|
|
|
def _eval_power(self, exp):
|
|
return self
|
|
|
|
def _print_contents(self, printer, *args):
|
|
return 'I'
|
|
|
|
def _print_contents_pretty(self, printer, *args):
|
|
return prettyForm('I')
|
|
|
|
def _print_contents_latex(self, printer, *args):
|
|
return r'{\mathcal{I}}'
|
|
|
|
def __mul__(self, other):
|
|
|
|
if isinstance(other, (Operator, Dagger)):
|
|
return other
|
|
|
|
return Mul(self, other)
|
|
|
|
def _represent_default_basis(self, **options):
|
|
if not self.N or self.N == oo:
|
|
raise NotImplementedError('Cannot represent infinite dimensional' +
|
|
' identity operator as a matrix')
|
|
|
|
format = options.get('format', 'sympy')
|
|
if format != 'sympy':
|
|
raise NotImplementedError('Representation in format ' +
|
|
'%s not implemented.' % format)
|
|
|
|
return eye(self.N)
|
|
|
|
|
|
class OuterProduct(Operator):
|
|
"""An unevaluated outer product between a ket and bra.
|
|
|
|
This constructs an outer product between any subclass of ``KetBase`` and
|
|
``BraBase`` as ``|a><b|``. An ``OuterProduct`` inherits from Operator as they act as
|
|
operators in quantum expressions. For reference see [1]_.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
ket : KetBase
|
|
The ket on the left side of the outer product.
|
|
bar : BraBase
|
|
The bra on the right side of the outer product.
|
|
|
|
Examples
|
|
========
|
|
|
|
Create a simple outer product by hand and take its dagger::
|
|
|
|
>>> from sympy.physics.quantum import Ket, Bra, OuterProduct, Dagger
|
|
>>> from sympy.physics.quantum import Operator
|
|
|
|
>>> k = Ket('k')
|
|
>>> b = Bra('b')
|
|
>>> op = OuterProduct(k, b)
|
|
>>> op
|
|
|k><b|
|
|
>>> op.hilbert_space
|
|
H
|
|
>>> op.ket
|
|
|k>
|
|
>>> op.bra
|
|
<b|
|
|
>>> Dagger(op)
|
|
|b><k|
|
|
|
|
In simple products of kets and bras outer products will be automatically
|
|
identified and created::
|
|
|
|
>>> k*b
|
|
|k><b|
|
|
|
|
But in more complex expressions, outer products are not automatically
|
|
created::
|
|
|
|
>>> A = Operator('A')
|
|
>>> A*k*b
|
|
A*|k>*<b|
|
|
|
|
A user can force the creation of an outer product in a complex expression
|
|
by using parentheses to group the ket and bra::
|
|
|
|
>>> A*(k*b)
|
|
A*|k><b|
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikipedia.org/wiki/Outer_product
|
|
"""
|
|
is_commutative = False
|
|
|
|
def __new__(cls, *args, **old_assumptions):
|
|
from sympy.physics.quantum.state import KetBase, BraBase
|
|
|
|
if len(args) != 2:
|
|
raise ValueError('2 parameters expected, got %d' % len(args))
|
|
|
|
ket_expr = expand(args[0])
|
|
bra_expr = expand(args[1])
|
|
|
|
if (isinstance(ket_expr, (KetBase, Mul)) and
|
|
isinstance(bra_expr, (BraBase, Mul))):
|
|
ket_c, kets = ket_expr.args_cnc()
|
|
bra_c, bras = bra_expr.args_cnc()
|
|
|
|
if len(kets) != 1 or not isinstance(kets[0], KetBase):
|
|
raise TypeError('KetBase subclass expected'
|
|
', got: %r' % Mul(*kets))
|
|
|
|
if len(bras) != 1 or not isinstance(bras[0], BraBase):
|
|
raise TypeError('BraBase subclass expected'
|
|
', got: %r' % Mul(*bras))
|
|
|
|
if not kets[0].dual_class() == bras[0].__class__:
|
|
raise TypeError(
|
|
'ket and bra are not dual classes: %r, %r' %
|
|
(kets[0].__class__, bras[0].__class__)
|
|
)
|
|
|
|
# TODO: make sure the hilbert spaces of the bra and ket are
|
|
# compatible
|
|
obj = Expr.__new__(cls, *(kets[0], bras[0]), **old_assumptions)
|
|
obj.hilbert_space = kets[0].hilbert_space
|
|
return Mul(*(ket_c + bra_c)) * obj
|
|
|
|
op_terms = []
|
|
if isinstance(ket_expr, Add) and isinstance(bra_expr, Add):
|
|
for ket_term in ket_expr.args:
|
|
for bra_term in bra_expr.args:
|
|
op_terms.append(OuterProduct(ket_term, bra_term,
|
|
**old_assumptions))
|
|
elif isinstance(ket_expr, Add):
|
|
for ket_term in ket_expr.args:
|
|
op_terms.append(OuterProduct(ket_term, bra_expr,
|
|
**old_assumptions))
|
|
elif isinstance(bra_expr, Add):
|
|
for bra_term in bra_expr.args:
|
|
op_terms.append(OuterProduct(ket_expr, bra_term,
|
|
**old_assumptions))
|
|
else:
|
|
raise TypeError(
|
|
'Expected ket and bra expression, got: %r, %r' %
|
|
(ket_expr, bra_expr)
|
|
)
|
|
|
|
return Add(*op_terms)
|
|
|
|
@property
|
|
def ket(self):
|
|
"""Return the ket on the left side of the outer product."""
|
|
return self.args[0]
|
|
|
|
@property
|
|
def bra(self):
|
|
"""Return the bra on the right side of the outer product."""
|
|
return self.args[1]
|
|
|
|
def _eval_adjoint(self):
|
|
return OuterProduct(Dagger(self.bra), Dagger(self.ket))
|
|
|
|
def _sympystr(self, printer, *args):
|
|
return printer._print(self.ket) + printer._print(self.bra)
|
|
|
|
def _sympyrepr(self, printer, *args):
|
|
return '%s(%s,%s)' % (self.__class__.__name__,
|
|
printer._print(self.ket, *args), printer._print(self.bra, *args))
|
|
|
|
def _pretty(self, printer, *args):
|
|
pform = self.ket._pretty(printer, *args)
|
|
return prettyForm(*pform.right(self.bra._pretty(printer, *args)))
|
|
|
|
def _latex(self, printer, *args):
|
|
k = printer._print(self.ket, *args)
|
|
b = printer._print(self.bra, *args)
|
|
return k + b
|
|
|
|
def _represent(self, **options):
|
|
k = self.ket._represent(**options)
|
|
b = self.bra._represent(**options)
|
|
return k*b
|
|
|
|
def _eval_trace(self, **kwargs):
|
|
# TODO if operands are tensorproducts this may be will be handled
|
|
# differently.
|
|
|
|
return self.ket._eval_trace(self.bra, **kwargs)
|
|
|
|
|
|
class DifferentialOperator(Operator):
|
|
"""An operator for representing the differential operator, i.e. d/dx
|
|
|
|
It is initialized by passing two arguments. The first is an arbitrary
|
|
expression that involves a function, such as ``Derivative(f(x), x)``. The
|
|
second is the function (e.g. ``f(x)``) which we are to replace with the
|
|
``Wavefunction`` that this ``DifferentialOperator`` is applied to.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
expr : Expr
|
|
The arbitrary expression which the appropriate Wavefunction is to be
|
|
substituted into
|
|
|
|
func : Expr
|
|
A function (e.g. f(x)) which is to be replaced with the appropriate
|
|
Wavefunction when this DifferentialOperator is applied
|
|
|
|
Examples
|
|
========
|
|
|
|
You can define a completely arbitrary expression and specify where the
|
|
Wavefunction is to be substituted
|
|
|
|
>>> from sympy import Derivative, Function, Symbol
|
|
>>> from sympy.physics.quantum.operator import DifferentialOperator
|
|
>>> from sympy.physics.quantum.state import Wavefunction
|
|
>>> from sympy.physics.quantum.qapply import qapply
|
|
>>> f = Function('f')
|
|
>>> x = Symbol('x')
|
|
>>> d = DifferentialOperator(1/x*Derivative(f(x), x), f(x))
|
|
>>> w = Wavefunction(x**2, x)
|
|
>>> d.function
|
|
f(x)
|
|
>>> d.variables
|
|
(x,)
|
|
>>> qapply(d*w)
|
|
Wavefunction(2, x)
|
|
|
|
"""
|
|
|
|
@property
|
|
def variables(self):
|
|
"""
|
|
Returns the variables with which the function in the specified
|
|
arbitrary expression is evaluated
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.physics.quantum.operator import DifferentialOperator
|
|
>>> from sympy import Symbol, Function, Derivative
|
|
>>> x = Symbol('x')
|
|
>>> f = Function('f')
|
|
>>> d = DifferentialOperator(1/x*Derivative(f(x), x), f(x))
|
|
>>> d.variables
|
|
(x,)
|
|
>>> y = Symbol('y')
|
|
>>> d = DifferentialOperator(Derivative(f(x, y), x) +
|
|
... Derivative(f(x, y), y), f(x, y))
|
|
>>> d.variables
|
|
(x, y)
|
|
"""
|
|
|
|
return self.args[-1].args
|
|
|
|
@property
|
|
def function(self):
|
|
"""
|
|
Returns the function which is to be replaced with the Wavefunction
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.physics.quantum.operator import DifferentialOperator
|
|
>>> from sympy import Function, Symbol, Derivative
|
|
>>> x = Symbol('x')
|
|
>>> f = Function('f')
|
|
>>> d = DifferentialOperator(Derivative(f(x), x), f(x))
|
|
>>> d.function
|
|
f(x)
|
|
>>> y = Symbol('y')
|
|
>>> d = DifferentialOperator(Derivative(f(x, y), x) +
|
|
... Derivative(f(x, y), y), f(x, y))
|
|
>>> d.function
|
|
f(x, y)
|
|
"""
|
|
|
|
return self.args[-1]
|
|
|
|
@property
|
|
def expr(self):
|
|
"""
|
|
Returns the arbitrary expression which is to have the Wavefunction
|
|
substituted into it
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.physics.quantum.operator import DifferentialOperator
|
|
>>> from sympy import Function, Symbol, Derivative
|
|
>>> x = Symbol('x')
|
|
>>> f = Function('f')
|
|
>>> d = DifferentialOperator(Derivative(f(x), x), f(x))
|
|
>>> d.expr
|
|
Derivative(f(x), x)
|
|
>>> y = Symbol('y')
|
|
>>> d = DifferentialOperator(Derivative(f(x, y), x) +
|
|
... Derivative(f(x, y), y), f(x, y))
|
|
>>> d.expr
|
|
Derivative(f(x, y), x) + Derivative(f(x, y), y)
|
|
"""
|
|
|
|
return self.args[0]
|
|
|
|
@property
|
|
def free_symbols(self):
|
|
"""
|
|
Return the free symbols of the expression.
|
|
"""
|
|
|
|
return self.expr.free_symbols
|
|
|
|
def _apply_operator_Wavefunction(self, func, **options):
|
|
from sympy.physics.quantum.state import Wavefunction
|
|
var = self.variables
|
|
wf_vars = func.args[1:]
|
|
|
|
f = self.function
|
|
new_expr = self.expr.subs(f, func(*var))
|
|
new_expr = new_expr.doit()
|
|
|
|
return Wavefunction(new_expr, *wf_vars)
|
|
|
|
def _eval_derivative(self, symbol):
|
|
new_expr = Derivative(self.expr, symbol)
|
|
return DifferentialOperator(new_expr, self.args[-1])
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Printing
|
|
#-------------------------------------------------------------------------
|
|
|
|
def _print(self, printer, *args):
|
|
return '%s(%s)' % (
|
|
self._print_operator_name(printer, *args),
|
|
self._print_label(printer, *args)
|
|
)
|
|
|
|
def _print_pretty(self, printer, *args):
|
|
pform = self._print_operator_name_pretty(printer, *args)
|
|
label_pform = self._print_label_pretty(printer, *args)
|
|
label_pform = prettyForm(
|
|
*label_pform.parens(left='(', right=')')
|
|
)
|
|
pform = prettyForm(*pform.right(label_pform))
|
|
return pform
|