# Source code for sympy.physics.quantum.hilbert

"""Hilbert spaces for quantum mechanics.

Authors:
* Brian Granger
* Matt Curry
"""

from __future__ import print_function, division

from sympy import Basic, Interval, oo, sympify
from sympy.core.compatibility import u
from sympy.printing.pretty.stringpict import prettyForm

from sympy.physics.quantum.qexpr import QuantumError

from sympy.core.compatibility import reduce

__all__ = [
'HilbertSpaceError',
'HilbertSpace',
'ComplexSpace',
'L2',
'FockSpace'
]

#-----------------------------------------------------------------------------
# Main objects
#-----------------------------------------------------------------------------

class HilbertSpaceError(QuantumError):
pass

#-----------------------------------------------------------------------------
# Main objects
#-----------------------------------------------------------------------------

[docs]class HilbertSpace(Basic):
"""An abstract Hilbert space for quantum mechanics.

In short, a Hilbert space is an abstract vector space that is complete
with inner products defined [1]_.

Examples
========

>>> from sympy.physics.quantum.hilbert import HilbertSpace
>>> hs = HilbertSpace()
>>> hs
H

References
==========

.. [1] http://en.wikipedia.org/wiki/Hilbert_space
"""

def __new__(cls):
obj = Basic.__new__(cls)
return obj

@property
[docs]    def dimension(self):
"""Return the Hilbert dimension of the space."""
raise NotImplementedError('This Hilbert space has no dimension.')

return DirectSumHilbertSpace(self, other)

return DirectSumHilbertSpace(other, self)

def __mul__(self, other):
return TensorProductHilbertSpace(self, other)

def __rmul__(self, other):
return TensorProductHilbertSpace(other, self)

def __pow__(self, other, mod=None):
if mod is not None:
raise ValueError('The third argument to __pow__ is not supported \
for Hilbert spaces.')
return TensorPowerHilbertSpace(self, other)

def __contains__(self, other):
"""Is the operator or state in this Hilbert space.

This is checked by comparing the classes of the Hilbert spaces, not
the instances. This is to allow Hilbert Spaces with symbolic
dimensions.
"""
if other.hilbert_space.__class__ == self.__class__:
return True
else:
return False

def _sympystr(self, printer, *args):
return u('H')

def _pretty(self, printer, *args):
# u = u('\u2108') # script
ustr = u('\u0048')
return prettyForm(ustr)

def _latex(self, printer, *args):
return r'\mathcal{H}'

[docs]class ComplexSpace(HilbertSpace):
"""Finite dimensional Hilbert space of complex vectors.

The elements of this Hilbert space are n-dimensional complex valued
vectors with the usual inner product that takes the complex conjugate
of the vector on the right.

A classic example of this type of Hilbert space is spin-1/2, which is
ComplexSpace(2). Generalizing to spin-s, the space is
ComplexSpace(2*s+1).  Quantum computing with N qubits is done with the
direct product space ComplexSpace(2)**N.

Examples
========

>>> from sympy import symbols
>>> from sympy.physics.quantum.hilbert import ComplexSpace
>>> c1 = ComplexSpace(2)
>>> c1
C(2)
>>> c1.dimension
2

>>> n = symbols('n')
>>> c2 = ComplexSpace(n)
>>> c2
C(n)
>>> c2.dimension
n

"""

def __new__(cls, dimension):
dimension = sympify(dimension)
r = cls.eval(dimension)
if isinstance(r, Basic):
return r
obj = Basic.__new__(cls, dimension)
return obj

@classmethod
def eval(cls, dimension):
if len(dimension.atoms()) == 1:
if not (dimension.is_Integer and dimension > 0 or dimension is oo
or dimension.is_Symbol):
raise TypeError('The dimension of a ComplexSpace can only'
'be a positive integer, oo, or a Symbol: %r'
% dimension)
else:
for dim in dimension.atoms():
if not (dim.is_Integer or dim is oo or dim.is_Symbol):
raise TypeError('The dimension of a ComplexSpace can only'
' contain integers, oo, or a Symbol: %r'
% dim)

@property
def dimension(self):
return self.args[0]

def _sympyrepr(self, printer, *args):
return "%s(%s)" % (self.__class__.__name__,
printer._print(self.dimension, *args))

def _sympystr(self, printer, *args):
return "C(%s)" % printer._print(self.dimension, *args)

def _pretty(self, printer, *args):
# u = u('\u2102') # script
ustr = u('\u0043')
pform_exp = printer._print(self.dimension, *args)
pform_base = prettyForm(ustr)
return pform_base**pform_exp

def _latex(self, printer, *args):
return r'\mathcal{C}^{%s}' % printer._print(self.dimension, *args)

[docs]class L2(HilbertSpace):
"""The Hilbert space of square integrable functions on an interval.

An L2 object takes in a single sympy Interval argument which represents
the interval its functions (vectors) are defined on.

Examples
========

>>> from sympy import Interval, oo
>>> from sympy.physics.quantum.hilbert import L2
>>> hs = L2(Interval(0,oo))
>>> hs
L2([0, oo))
>>> hs.dimension
oo
>>> hs.interval
[0, oo)

"""

def __new__(cls, interval):
if not isinstance(interval, Interval):
raise TypeError('L2 interval must be an Interval instance: %r'
% interval)
obj = Basic.__new__(cls, interval)
return obj

@property
def dimension(self):
return oo

@property
def interval(self):
return self.args[0]

def _sympyrepr(self, printer, *args):
return "L2(%s)" % printer._print(self.interval, *args)

def _sympystr(self, printer, *args):
return "L2(%s)" % printer._print(self.interval, *args)

def _pretty(self, printer, *args):
pform_exp = prettyForm(u('2'))
pform_base = prettyForm(u('L'))
return pform_base**pform_exp

def _latex(self, printer, *args):
interval = printer._print(self.interval, *args)
return r'{\mathcal{L}^2}\left( %s \right)' % interval

[docs]class FockSpace(HilbertSpace):
"""The Hilbert space for second quantization.

Technically, this Hilbert space is a infinite direct sum of direct
products of single particle Hilbert spaces [1]_. This is a mess, so we have
a class to represent it directly.

Examples
========

>>> from sympy.physics.quantum.hilbert import FockSpace
>>> hs = FockSpace()
>>> hs
F
>>> hs.dimension
oo

References
==========

.. [1] http://en.wikipedia.org/wiki/Fock_space
"""

def __new__(cls):
obj = Basic.__new__(cls)
return obj

@property
def dimension(self):
return oo

def _sympyrepr(self, printer, *args):
return "FockSpace()"

def _sympystr(self, printer, *args):
return "F"

def _pretty(self, printer, *args):
# u = u('\u2131') # script
ustr = u('\u0046')
return prettyForm(ustr)

def _latex(self, printer, *args):
return r'\mathcal{F}'

class TensorProductHilbertSpace(HilbertSpace):
"""A tensor product of Hilbert spaces [1]_.

The tensor product between Hilbert spaces is represented by the
operator * Products of the same Hilbert space will be combined into
tensor powers.

A TensorProductHilbertSpace object takes in an arbitrary number of
HilbertSpace objects as its arguments. In addition, multiplication of
HilbertSpace objects will automatically return this tensor product
object.

Examples
========

>>> from sympy.physics.quantum.hilbert import ComplexSpace, FockSpace
>>> from sympy import symbols

>>> c = ComplexSpace(2)
>>> f = FockSpace()
>>> hs = c*f
>>> hs
C(2)*F
>>> hs.dimension
oo
>>> hs.spaces
(C(2), F)

>>> c1 = ComplexSpace(2)
>>> n = symbols('n')
>>> c2 = ComplexSpace(n)
>>> hs = c1*c2
>>> hs
C(2)*C(n)
>>> hs.dimension
2*n

References
==========

.. [1] http://en.wikipedia.org/wiki/Hilbert_space#Tensor_products
"""

def __new__(cls, *args):
r = cls.eval(args)
if isinstance(r, Basic):
return r
obj = Basic.__new__(cls, *args)
return obj

@classmethod
def eval(cls, args):
"""Evaluates the direct product."""
new_args = []
recall = False
#flatten arguments
for arg in args:
if isinstance(arg, TensorProductHilbertSpace):
new_args.extend(arg.args)
recall = True
elif isinstance(arg, (HilbertSpace, TensorPowerHilbertSpace)):
new_args.append(arg)
else:
raise TypeError('Hilbert spaces can only be multiplied by \
other Hilbert spaces: %r' % arg)
#combine like arguments into direct powers
comb_args = []
prev_arg = None
for new_arg in new_args:
if prev_arg is not None:
if isinstance(new_arg, TensorPowerHilbertSpace) and \
isinstance(prev_arg, TensorPowerHilbertSpace) and \
new_arg.base == prev_arg.base:
prev_arg = new_arg.base**(new_arg.exp + prev_arg.exp)
elif isinstance(new_arg, TensorPowerHilbertSpace) and \
new_arg.base == prev_arg:
prev_arg = prev_arg**(new_arg.exp + 1)
elif isinstance(prev_arg, TensorPowerHilbertSpace) and \
new_arg == prev_arg.base:
prev_arg = new_arg**(prev_arg.exp + 1)
elif new_arg == prev_arg:
prev_arg = new_arg**2
else:
comb_args.append(prev_arg)
prev_arg = new_arg
elif prev_arg is None:
prev_arg = new_arg
comb_args.append(prev_arg)
if recall:
return TensorProductHilbertSpace(*comb_args)
elif len(comb_args) == 1:
return TensorPowerHilbertSpace(comb_args[0].base, comb_args[0].exp)
else:
return None

@property
def dimension(self):
arg_list = [arg.dimension for arg in self.args]
if oo in arg_list:
return oo
else:
return reduce(lambda x, y: x*y, arg_list)

@property
def spaces(self):
"""A tuple of the Hilbert spaces in this tensor product."""
return self.args

def _spaces_printer(self, printer, *args):
spaces_strs = []
for arg in self.args:
s = printer._print(arg, *args)
if isinstance(arg, DirectSumHilbertSpace):
s = '(%s)' % s
spaces_strs.append(s)
return spaces_strs

def _sympyrepr(self, printer, *args):
spaces_reprs = self._spaces_printer(printer, *args)
return "TensorProductHilbertSpace(%s)" % ','.join(spaces_reprs)

def _sympystr(self, printer, *args):
spaces_strs = self._spaces_printer(printer, *args)
return '*'.join(spaces_strs)

def _pretty(self, printer, *args):
length = len(self.args)
pform = printer._print('', *args)
for i in range(length):
next_pform = printer._print(self.args[i], *args)
if isinstance(self.args[i], (DirectSumHilbertSpace,
TensorProductHilbertSpace)):
next_pform = prettyForm(
*next_pform.parens(left='(', right=')')
)
pform = prettyForm(*pform.right(next_pform))
if i != length - 1:
if printer._use_unicode:
pform = prettyForm(*pform.right(u(' ') + u('\u2a02') + u(' ')))
else:
pform = prettyForm(*pform.right(' x '))
return pform

def _latex(self, printer, *args):
length = len(self.args)
s = ''
for i in range(length):
arg_s = printer._print(self.args[i], *args)
if isinstance(self.args[i], (DirectSumHilbertSpace,
TensorProductHilbertSpace)):
arg_s = r'\left(%s\right)' % arg_s
s = s + arg_s
if i != length - 1:
s = s + r'\otimes '
return s

class DirectSumHilbertSpace(HilbertSpace):
"""A direct sum of Hilbert spaces [1]_.

This class uses the + operator to represent direct sums between
different Hilbert spaces.

A DirectSumHilbertSpace object takes in an arbitrary number of
HilbertSpace objects as its arguments. Also, addition of
HilbertSpace objects will automatically return a direct sum object.

Examples
========

>>> from sympy.physics.quantum.hilbert import ComplexSpace, FockSpace
>>> from sympy import symbols

>>> c = ComplexSpace(2)
>>> f = FockSpace()
>>> hs = c+f
>>> hs
C(2)+F
>>> hs.dimension
oo
>>> list(hs.spaces)
[C(2), F]

References
==========

.. [1] http://en.wikipedia.org/wiki/Hilbert_space#Direct_sums
"""
def __new__(cls, *args):
r = cls.eval(args)
if isinstance(r, Basic):
return r
obj = Basic.__new__(cls, *args)
return obj

@classmethod
def eval(cls, args):
"""Evaluates the direct product."""
new_args = []
recall = False
#flatten arguments
for arg in args:
if isinstance(arg, DirectSumHilbertSpace):
new_args.extend(arg.args)
recall = True
elif isinstance(arg, HilbertSpace):
new_args.append(arg)
else:
raise TypeError('Hilbert spaces can only be summed with other \
Hilbert spaces: %r' % arg)
if recall:
return DirectSumHilbertSpace(*new_args)
else:
return None

@property
def dimension(self):
arg_list = [arg.dimension for arg in self.args]
if oo in arg_list:
return oo
else:
return reduce(lambda x, y: x + y, arg_list)

@property
def spaces(self):
"""A tuple of the Hilbert spaces in this direct sum."""
return self.args

def _sympyrepr(self, printer, *args):
spaces_reprs = [printer._print(arg, *args) for arg in self.args]
return "DirectSumHilbertSpace(%s)" % ','.join(spaces_reprs)

def _sympystr(self, printer, *args):
spaces_strs = [printer._print(arg, *args) for arg in self.args]
return '+'.join(spaces_strs)

def _pretty(self, printer, *args):
length = len(self.args)
pform = printer._print('', *args)
for i in range(length):
next_pform = printer._print(self.args[i], *args)
if isinstance(self.args[i], (DirectSumHilbertSpace,
TensorProductHilbertSpace)):
next_pform = prettyForm(
*next_pform.parens(left='(', right=')')
)
pform = prettyForm(*pform.right(next_pform))
if i != length - 1:
if printer._use_unicode:
pform = prettyForm(*pform.right(u(' ') + u('\u2295') + u(' ')))
else:
pform = prettyForm(*pform.right(' + '))
return pform

def _latex(self, printer, *args):
length = len(self.args)
s = ''
for i in range(length):
arg_s = printer._print(self.args[i], *args)
if isinstance(self.args[i], (DirectSumHilbertSpace,
TensorProductHilbertSpace)):
arg_s = r'\left(%s\right)' % arg_s
s = s + arg_s
if i != length - 1:
s = s + r'\oplus '
return s

class TensorPowerHilbertSpace(HilbertSpace):
"""An exponentiated Hilbert space [1]_.

Tensor powers (repeated tensor products) are represented by the
operator ** Identical Hilbert spaces that are multiplied together
will be automatically combined into a single tensor power object.

Any Hilbert space, product, or sum may be raised to a tensor power. The
TensorPowerHilbertSpace takes two arguments: the Hilbert space; and the
tensor power (number).

Examples
========

>>> from sympy.physics.quantum.hilbert import ComplexSpace, FockSpace
>>> from sympy import symbols

>>> n = symbols('n')
>>> c = ComplexSpace(2)
>>> hs = c**n
>>> hs
C(2)**n
>>> hs.dimension
2**n

>>> c = ComplexSpace(2)
>>> c*c
C(2)**2
>>> f = FockSpace()
>>> c*f*f
C(2)*F**2

References
==========

.. [1] http://en.wikipedia.org/wiki/Hilbert_space#Tensor_products
"""

def __new__(cls, *args):
r = cls.eval(args)
if isinstance(r, Basic):
return r
return Basic.__new__(cls, *r)

@classmethod
def eval(cls, args):
new_args = args[0], sympify(args[1])
exp = new_args[1]
#simplify hs**1 -> hs
if exp == 1:
return args[0]
#simplify hs**0 -> 1
if exp == 0:
return sympify(1)
#check (and allow) for hs**(x+42+y...) case
if len(exp.atoms()) == 1:
if not (exp.is_Integer and exp >= 0 or exp.is_Symbol):
raise ValueError('Hilbert spaces can only be raised to \
positive integers or Symbols: %r' % exp)
else:
for power in exp.atoms():
if not (power.is_Integer or power.is_Symbol):
raise ValueError('Tensor powers can only contain integers \
or Symbols: %r' % power)
return new_args

@property
def base(self):
return self.args[0]

@property
def exp(self):
return self.args[1]

@property
def dimension(self):
if self.base.dimension == oo:
return oo
else:
return self.base.dimension**self.exp

def _sympyrepr(self, printer, *args):
return "TensorPowerHilbertSpace(%s,%s)" % (printer._print(self.base,
*args), printer._print(self.exp, *args))

def _sympystr(self, printer, *args):
return "%s**%s" % (printer._print(self.base, *args),
printer._print(self.exp, *args))

def _pretty(self, printer, *args):
pform_exp = printer._print(self.exp, *args)
if printer._use_unicode:
pform_exp = prettyForm(*pform_exp.left(prettyForm(u('\u2a02'))))
else:
pform_exp = prettyForm(*pform_exp.left(prettyForm('x')))
pform_base = printer._print(self.base, *args)
return pform_base**pform_exp

def _latex(self, printer, *args):
base = printer._print(self.base, *args)
exp = printer._print(self.exp, *args)
return r'{%s}^{\otimes %s}' % (base, exp)