207 lines
7.0 KiB
Python
207 lines
7.0 KiB
Python
"""Logic for applying operators to states.
|
|
|
|
Todo:
|
|
* Sometimes the final result needs to be expanded, we should do this by hand.
|
|
"""
|
|
|
|
from sympy.core.add import Add
|
|
from sympy.core.mul import Mul
|
|
from sympy.core.power import Pow
|
|
from sympy.core.singleton import S
|
|
from sympy.core.sympify import sympify
|
|
|
|
from sympy.physics.quantum.anticommutator import AntiCommutator
|
|
from sympy.physics.quantum.commutator import Commutator
|
|
from sympy.physics.quantum.dagger import Dagger
|
|
from sympy.physics.quantum.innerproduct import InnerProduct
|
|
from sympy.physics.quantum.operator import OuterProduct, Operator
|
|
from sympy.physics.quantum.state import State, KetBase, BraBase, Wavefunction
|
|
from sympy.physics.quantum.tensorproduct import TensorProduct
|
|
|
|
__all__ = [
|
|
'qapply'
|
|
]
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Main code
|
|
#-----------------------------------------------------------------------------
|
|
|
|
def qapply(e, **options):
|
|
"""Apply operators to states in a quantum expression.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
e : Expr
|
|
The expression containing operators and states. This expression tree
|
|
will be walked to find operators acting on states symbolically.
|
|
options : dict
|
|
A dict of key/value pairs that determine how the operator actions
|
|
are carried out.
|
|
|
|
The following options are valid:
|
|
|
|
* ``dagger``: try to apply Dagger operators to the left
|
|
(default: False).
|
|
* ``ip_doit``: call ``.doit()`` in inner products when they are
|
|
encountered (default: True).
|
|
|
|
Returns
|
|
=======
|
|
|
|
e : Expr
|
|
The original expression, but with the operators applied to states.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.physics.quantum import qapply, Ket, Bra
|
|
>>> b = Bra('b')
|
|
>>> k = Ket('k')
|
|
>>> A = k * b
|
|
>>> A
|
|
|k><b|
|
|
>>> qapply(A * b.dual / (b * b.dual))
|
|
|k>
|
|
>>> qapply(k.dual * A / (k.dual * k), dagger=True)
|
|
<b|
|
|
>>> qapply(k.dual * A / (k.dual * k))
|
|
<k|*|k><b|/<k|k>
|
|
"""
|
|
from sympy.physics.quantum.density import Density
|
|
|
|
dagger = options.get('dagger', False)
|
|
|
|
if e == 0:
|
|
return S.Zero
|
|
|
|
# This may be a bit aggressive but ensures that everything gets expanded
|
|
# to its simplest form before trying to apply operators. This includes
|
|
# things like (A+B+C)*|a> and A*(|a>+|b>) and all Commutators and
|
|
# TensorProducts. The only problem with this is that if we can't apply
|
|
# all the Operators, we have just expanded everything.
|
|
# TODO: don't expand the scalars in front of each Mul.
|
|
e = e.expand(commutator=True, tensorproduct=True)
|
|
|
|
# If we just have a raw ket, return it.
|
|
if isinstance(e, KetBase):
|
|
return e
|
|
|
|
# We have an Add(a, b, c, ...) and compute
|
|
# Add(qapply(a), qapply(b), ...)
|
|
elif isinstance(e, Add):
|
|
result = 0
|
|
for arg in e.args:
|
|
result += qapply(arg, **options)
|
|
return result.expand()
|
|
|
|
# For a Density operator call qapply on its state
|
|
elif isinstance(e, Density):
|
|
new_args = [(qapply(state, **options), prob) for (state,
|
|
prob) in e.args]
|
|
return Density(*new_args)
|
|
|
|
# For a raw TensorProduct, call qapply on its args.
|
|
elif isinstance(e, TensorProduct):
|
|
return TensorProduct(*[qapply(t, **options) for t in e.args])
|
|
|
|
# For a Pow, call qapply on its base.
|
|
elif isinstance(e, Pow):
|
|
return qapply(e.base, **options)**e.exp
|
|
|
|
# We have a Mul where there might be actual operators to apply to kets.
|
|
elif isinstance(e, Mul):
|
|
c_part, nc_part = e.args_cnc()
|
|
c_mul = Mul(*c_part)
|
|
nc_mul = Mul(*nc_part)
|
|
if isinstance(nc_mul, Mul):
|
|
result = c_mul*qapply_Mul(nc_mul, **options)
|
|
else:
|
|
result = c_mul*qapply(nc_mul, **options)
|
|
if result == e and dagger:
|
|
return Dagger(qapply_Mul(Dagger(e), **options))
|
|
else:
|
|
return result
|
|
|
|
# In all other cases (State, Operator, Pow, Commutator, InnerProduct,
|
|
# OuterProduct) we won't ever have operators to apply to kets.
|
|
else:
|
|
return e
|
|
|
|
|
|
def qapply_Mul(e, **options):
|
|
|
|
ip_doit = options.get('ip_doit', True)
|
|
|
|
args = list(e.args)
|
|
|
|
# If we only have 0 or 1 args, we have nothing to do and return.
|
|
if len(args) <= 1 or not isinstance(e, Mul):
|
|
return e
|
|
rhs = args.pop()
|
|
lhs = args.pop()
|
|
|
|
# Make sure we have two non-commutative objects before proceeding.
|
|
if (not isinstance(rhs, Wavefunction) and sympify(rhs).is_commutative) or \
|
|
(not isinstance(lhs, Wavefunction) and sympify(lhs).is_commutative):
|
|
return e
|
|
|
|
# For a Pow with an integer exponent, apply one of them and reduce the
|
|
# exponent by one.
|
|
if isinstance(lhs, Pow) and lhs.exp.is_Integer:
|
|
args.append(lhs.base**(lhs.exp - 1))
|
|
lhs = lhs.base
|
|
|
|
# Pull OuterProduct apart
|
|
if isinstance(lhs, OuterProduct):
|
|
args.append(lhs.ket)
|
|
lhs = lhs.bra
|
|
|
|
# Call .doit() on Commutator/AntiCommutator.
|
|
if isinstance(lhs, (Commutator, AntiCommutator)):
|
|
comm = lhs.doit()
|
|
if isinstance(comm, Add):
|
|
return qapply(
|
|
e.func(*(args + [comm.args[0], rhs])) +
|
|
e.func(*(args + [comm.args[1], rhs])),
|
|
**options
|
|
)
|
|
else:
|
|
return qapply(e.func(*args)*comm*rhs, **options)
|
|
|
|
# Apply tensor products of operators to states
|
|
if isinstance(lhs, TensorProduct) and all(isinstance(arg, (Operator, State, Mul, Pow)) or arg == 1 for arg in lhs.args) and \
|
|
isinstance(rhs, TensorProduct) and all(isinstance(arg, (Operator, State, Mul, Pow)) or arg == 1 for arg in rhs.args) and \
|
|
len(lhs.args) == len(rhs.args):
|
|
result = TensorProduct(*[qapply(lhs.args[n]*rhs.args[n], **options) for n in range(len(lhs.args))]).expand(tensorproduct=True)
|
|
return qapply_Mul(e.func(*args), **options)*result
|
|
|
|
# Now try to actually apply the operator and build an inner product.
|
|
try:
|
|
result = lhs._apply_operator(rhs, **options)
|
|
except (NotImplementedError, AttributeError):
|
|
try:
|
|
result = rhs._apply_from_right_to(lhs, **options)
|
|
except (NotImplementedError, AttributeError):
|
|
if isinstance(lhs, BraBase) and isinstance(rhs, KetBase):
|
|
result = InnerProduct(lhs, rhs)
|
|
if ip_doit:
|
|
result = result.doit()
|
|
else:
|
|
result = None
|
|
|
|
# TODO: I may need to expand before returning the final result.
|
|
if result == 0:
|
|
return S.Zero
|
|
elif result is None:
|
|
if len(args) == 0:
|
|
# We had two args to begin with so args=[].
|
|
return e
|
|
else:
|
|
return qapply_Mul(e.func(*(args + [lhs])), **options)*rhs
|
|
elif isinstance(result, InnerProduct):
|
|
return result*qapply_Mul(e.func(*args), **options)
|
|
else: # result is a scalar times a Mul, Add or TensorProduct
|
|
return qapply(e.func(*args)*result, **options)
|