chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# Names exposed by 'from sympy.physics.quantum import *'
|
||||
|
||||
__all__ = [
|
||||
'AntiCommutator',
|
||||
|
||||
'qapply',
|
||||
|
||||
'Commutator',
|
||||
|
||||
'Dagger',
|
||||
|
||||
'HilbertSpaceError', 'HilbertSpace', 'TensorProductHilbertSpace',
|
||||
'TensorPowerHilbertSpace', 'DirectSumHilbertSpace', 'ComplexSpace', 'L2',
|
||||
'FockSpace',
|
||||
|
||||
'InnerProduct',
|
||||
|
||||
'Operator', 'HermitianOperator', 'UnitaryOperator', 'IdentityOperator',
|
||||
'OuterProduct', 'DifferentialOperator',
|
||||
|
||||
'represent', 'rep_innerproduct', 'rep_expectation', 'integrate_result',
|
||||
'get_basis', 'enumerate_states',
|
||||
|
||||
'KetBase', 'BraBase', 'StateBase', 'State', 'Ket', 'Bra', 'TimeDepState',
|
||||
'TimeDepBra', 'TimeDepKet', 'OrthogonalKet', 'OrthogonalBra',
|
||||
'OrthogonalState', 'Wavefunction',
|
||||
|
||||
'TensorProduct', 'tensor_product_simp',
|
||||
|
||||
'hbar', 'HBar',
|
||||
|
||||
'_postprocess_state_mul', '_postprocess_state_pow'
|
||||
]
|
||||
|
||||
from .anticommutator import AntiCommutator
|
||||
|
||||
from .qapply import qapply
|
||||
|
||||
from .commutator import Commutator
|
||||
|
||||
from .dagger import Dagger
|
||||
|
||||
from .hilbert import (HilbertSpaceError, HilbertSpace,
|
||||
TensorProductHilbertSpace, TensorPowerHilbertSpace,
|
||||
DirectSumHilbertSpace, ComplexSpace, L2, FockSpace)
|
||||
|
||||
from .innerproduct import InnerProduct
|
||||
|
||||
from .operator import (Operator, HermitianOperator, UnitaryOperator,
|
||||
IdentityOperator, OuterProduct, DifferentialOperator)
|
||||
|
||||
from .represent import (represent, rep_innerproduct, rep_expectation,
|
||||
integrate_result, get_basis, enumerate_states)
|
||||
|
||||
from .state import (KetBase, BraBase, StateBase, State, Ket, Bra,
|
||||
TimeDepState, TimeDepBra, TimeDepKet, OrthogonalKet,
|
||||
OrthogonalBra, OrthogonalState, Wavefunction)
|
||||
|
||||
from .tensorproduct import TensorProduct, tensor_product_simp
|
||||
|
||||
from .constants import hbar, HBar
|
||||
|
||||
# These are private, but need to be imported so they are registered
|
||||
# as postprocessing transformers with Mul and Pow.
|
||||
from .transforms import _postprocess_state_mul, _postprocess_state_pow
|
||||
@@ -0,0 +1,166 @@
|
||||
"""The anti-commutator: ``{A,B} = A*B + B*A``."""
|
||||
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.kind import KindDispatcher
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.singleton import S
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.kind import _OperatorKind, OperatorKind
|
||||
|
||||
__all__ = [
|
||||
'AntiCommutator'
|
||||
]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Anti-commutator
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AntiCommutator(Expr):
|
||||
"""The standard anticommutator, in an unevaluated state.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Evaluating an anticommutator is defined [1]_ as: ``{A, B} = A*B + B*A``.
|
||||
This class returns the anticommutator in an unevaluated form. To evaluate
|
||||
the anticommutator, use the ``.doit()`` method.
|
||||
|
||||
Canonical ordering of an anticommutator is ``{A, B}`` for ``A < B``. The
|
||||
arguments of the anticommutator are put into canonical order using
|
||||
``__cmp__``. If ``B < A``, then ``{A, B}`` is returned as ``{B, A}``.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
A : Expr
|
||||
The first argument of the anticommutator {A,B}.
|
||||
B : Expr
|
||||
The second argument of the anticommutator {A,B}.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols
|
||||
>>> from sympy.physics.quantum import AntiCommutator
|
||||
>>> from sympy.physics.quantum import Operator, Dagger
|
||||
>>> x, y = symbols('x,y')
|
||||
>>> A = Operator('A')
|
||||
>>> B = Operator('B')
|
||||
|
||||
Create an anticommutator and use ``doit()`` to multiply them out.
|
||||
|
||||
>>> ac = AntiCommutator(A,B); ac
|
||||
{A,B}
|
||||
>>> ac.doit()
|
||||
A*B + B*A
|
||||
|
||||
The commutator orders it arguments in canonical order:
|
||||
|
||||
>>> ac = AntiCommutator(B,A); ac
|
||||
{A,B}
|
||||
|
||||
Commutative constants are factored out:
|
||||
|
||||
>>> AntiCommutator(3*x*A,x*y*B)
|
||||
3*x**2*y*{A,B}
|
||||
|
||||
Adjoint operations applied to the anticommutator are properly applied to
|
||||
the arguments:
|
||||
|
||||
>>> Dagger(AntiCommutator(A,B))
|
||||
{Dagger(A),Dagger(B)}
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Commutator
|
||||
"""
|
||||
is_commutative = False
|
||||
|
||||
_kind_dispatcher = KindDispatcher("AntiCommutator_kind_dispatcher", commutative=True)
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
arg_kinds = (a.kind for a in self.args)
|
||||
return self._kind_dispatcher(*arg_kinds)
|
||||
|
||||
def __new__(cls, A, B):
|
||||
r = cls.eval(A, B)
|
||||
if r is not None:
|
||||
return r
|
||||
obj = Expr.__new__(cls, A, B)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def eval(cls, a, b):
|
||||
if not (a and b):
|
||||
return S.Zero
|
||||
if a == b:
|
||||
return Integer(2)*a**2
|
||||
if a.is_commutative or b.is_commutative:
|
||||
return Integer(2)*a*b
|
||||
|
||||
# [xA,yB] -> xy*[A,B]
|
||||
ca, nca = a.args_cnc()
|
||||
cb, ncb = b.args_cnc()
|
||||
c_part = ca + cb
|
||||
if c_part:
|
||||
return Mul(Mul(*c_part), cls(Mul._from_args(nca), Mul._from_args(ncb)))
|
||||
|
||||
# Canonical ordering of arguments
|
||||
#The Commutator [A,B] is on canonical form if A < B.
|
||||
if a.compare(b) == 1:
|
||||
return cls(b, a)
|
||||
|
||||
def doit(self, **hints):
|
||||
""" Evaluate anticommutator """
|
||||
# Keep the import of Operator here to avoid problems with
|
||||
# circular imports.
|
||||
from sympy.physics.quantum.operator import Operator
|
||||
A = self.args[0]
|
||||
B = self.args[1]
|
||||
if isinstance(A, Operator) and isinstance(B, Operator):
|
||||
try:
|
||||
comm = A._eval_anticommutator(B, **hints)
|
||||
except NotImplementedError:
|
||||
try:
|
||||
comm = B._eval_anticommutator(A, **hints)
|
||||
except NotImplementedError:
|
||||
comm = None
|
||||
if comm is not None:
|
||||
return comm.doit(**hints)
|
||||
return (A*B + B*A).doit(**hints)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return AntiCommutator(Dagger(self.args[0]), Dagger(self.args[1]))
|
||||
|
||||
def _sympyrepr(self, printer, *args):
|
||||
return "%s(%s,%s)" % (
|
||||
self.__class__.__name__, printer._print(
|
||||
self.args[0]), printer._print(self.args[1])
|
||||
)
|
||||
|
||||
def _sympystr(self, printer, *args):
|
||||
return "{%s,%s}" % (
|
||||
printer._print(self.args[0]), printer._print(self.args[1]))
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
pform = printer._print(self.args[0], *args)
|
||||
pform = prettyForm(*pform.right(prettyForm(',')))
|
||||
pform = prettyForm(*pform.right(printer._print(self.args[1], *args)))
|
||||
pform = prettyForm(*pform.parens(left='{', right='}'))
|
||||
return pform
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
return "\\left\\{%s,%s\\right\\}" % tuple([
|
||||
printer._print(arg, *args) for arg in self.args])
|
||||
|
||||
|
||||
@AntiCommutator._kind_dispatcher.register(_OperatorKind, _OperatorKind)
|
||||
def find_op_kind(e1, e2):
|
||||
"""Find the kind of an anticommutator of two OperatorKinds."""
|
||||
return OperatorKind
|
||||
@@ -0,0 +1,243 @@
|
||||
"""Bosonic quantum operators."""
|
||||
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.complexes import conjugate
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.physics.quantum import Operator
|
||||
from sympy.physics.quantum import HilbertSpace, FockSpace, Ket, Bra
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
|
||||
|
||||
__all__ = [
|
||||
'BosonOp',
|
||||
'BosonFockKet',
|
||||
'BosonFockBra',
|
||||
'BosonCoherentKet',
|
||||
'BosonCoherentBra'
|
||||
]
|
||||
|
||||
|
||||
class BosonOp(Operator):
|
||||
"""A bosonic operator that satisfies [a, Dagger(a)] == 1.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
name : str
|
||||
A string that labels the bosonic mode.
|
||||
|
||||
annihilation : bool
|
||||
A bool that indicates if the bosonic operator is an annihilation (True,
|
||||
default value) or creation operator (False)
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import Dagger, Commutator
|
||||
>>> from sympy.physics.quantum.boson import BosonOp
|
||||
>>> a = BosonOp("a")
|
||||
>>> Commutator(a, Dagger(a)).doit()
|
||||
1
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def is_annihilation(self):
|
||||
return bool(self.args[1])
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("a", True)
|
||||
|
||||
def __new__(cls, *args, **hints):
|
||||
if not len(args) in [1, 2]:
|
||||
raise ValueError('1 or 2 parameters expected, got %s' % args)
|
||||
|
||||
if len(args) == 1:
|
||||
args = (args[0], S.One)
|
||||
|
||||
if len(args) == 2:
|
||||
args = (args[0], Integer(args[1]))
|
||||
|
||||
return Operator.__new__(cls, *args)
|
||||
|
||||
def _eval_commutator_BosonOp(self, other, **hints):
|
||||
if self.name == other.name:
|
||||
# [a^\dagger, a] = -1
|
||||
if not self.is_annihilation and other.is_annihilation:
|
||||
return S.NegativeOne
|
||||
|
||||
elif 'independent' in hints and hints['independent']:
|
||||
# [a, b] = 0
|
||||
return S.Zero
|
||||
|
||||
return None
|
||||
|
||||
def _eval_commutator_FermionOp(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_anticommutator_BosonOp(self, other, **hints):
|
||||
if 'independent' in hints and hints['independent']:
|
||||
# {a, b} = 2 * a * b, because [a, b] = 0
|
||||
return 2 * self * other
|
||||
|
||||
return None
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return BosonOp(str(self.name), not self.is_annihilation)
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
if self.is_annihilation:
|
||||
return r'{%s}' % str(self.name)
|
||||
else:
|
||||
return r'{{%s}^\dagger}' % str(self.name)
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
if self.is_annihilation:
|
||||
return r'%s' % str(self.name)
|
||||
else:
|
||||
return r'Dagger(%s)' % str(self.name)
|
||||
|
||||
def _print_contents_pretty(self, printer, *args):
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
pform = printer._print(self.args[0], *args)
|
||||
if self.is_annihilation:
|
||||
return pform
|
||||
else:
|
||||
return pform**prettyForm('\N{DAGGER}')
|
||||
|
||||
|
||||
class BosonFockKet(Ket):
|
||||
"""Fock state ket for a bosonic mode.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Number
|
||||
The Fock state number.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, n):
|
||||
return Ket.__new__(cls, n)
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
return self.label[0]
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return BosonFockBra
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return FockSpace()
|
||||
|
||||
def _eval_innerproduct_BosonFockBra(self, bra, **hints):
|
||||
return KroneckerDelta(self.n, bra.n)
|
||||
|
||||
def _apply_from_right_to_BosonOp(self, op, **options):
|
||||
if op.is_annihilation:
|
||||
return sqrt(self.n) * BosonFockKet(self.n - 1)
|
||||
else:
|
||||
return sqrt(self.n + 1) * BosonFockKet(self.n + 1)
|
||||
|
||||
|
||||
class BosonFockBra(Bra):
|
||||
"""Fock state bra for a bosonic mode.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Number
|
||||
The Fock state number.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, n):
|
||||
return Bra.__new__(cls, n)
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
return self.label[0]
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return BosonFockKet
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return FockSpace()
|
||||
|
||||
|
||||
class BosonCoherentKet(Ket):
|
||||
"""Coherent state ket for a bosonic mode.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
alpha : Number, Symbol
|
||||
The complex amplitude of the coherent state.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, alpha):
|
||||
return Ket.__new__(cls, alpha)
|
||||
|
||||
@property
|
||||
def alpha(self):
|
||||
return self.label[0]
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return BosonCoherentBra
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return HilbertSpace()
|
||||
|
||||
def _eval_innerproduct_BosonCoherentBra(self, bra, **hints):
|
||||
if self.alpha == bra.alpha:
|
||||
return S.One
|
||||
else:
|
||||
return exp(-(abs(self.alpha)**2 + abs(bra.alpha)**2 - 2 * conjugate(bra.alpha) * self.alpha)/2)
|
||||
|
||||
def _apply_from_right_to_BosonOp(self, op, **options):
|
||||
if op.is_annihilation:
|
||||
return self.alpha * self
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class BosonCoherentBra(Bra):
|
||||
"""Coherent state bra for a bosonic mode.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
alpha : Number, Symbol
|
||||
The complex amplitude of the coherent state.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, alpha):
|
||||
return Bra.__new__(cls, alpha)
|
||||
|
||||
@property
|
||||
def alpha(self):
|
||||
return self.label[0]
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return BosonCoherentKet
|
||||
|
||||
def _apply_operator_BosonOp(self, op, **options):
|
||||
if not op.is_annihilation:
|
||||
return self.alpha * self
|
||||
else:
|
||||
return None
|
||||
@@ -0,0 +1,341 @@
|
||||
"""Operators and states for 1D cartesian position and momentum.
|
||||
|
||||
TODO:
|
||||
|
||||
* Add 3D classes to mappings in operatorset.py
|
||||
|
||||
"""
|
||||
|
||||
from sympy.core.numbers import (I, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.special.delta_functions import DiracDelta
|
||||
from sympy.sets.sets import Interval
|
||||
|
||||
from sympy.physics.quantum.constants import hbar
|
||||
from sympy.physics.quantum.hilbert import L2
|
||||
from sympy.physics.quantum.operator import DifferentialOperator, HermitianOperator
|
||||
from sympy.physics.quantum.state import Ket, Bra, State
|
||||
|
||||
__all__ = [
|
||||
'XOp',
|
||||
'YOp',
|
||||
'ZOp',
|
||||
'PxOp',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
'Px',
|
||||
'XKet',
|
||||
'XBra',
|
||||
'PxKet',
|
||||
'PxBra',
|
||||
'PositionState3D',
|
||||
'PositionKet3D',
|
||||
'PositionBra3D'
|
||||
]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Position operators
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
|
||||
class XOp(HermitianOperator):
|
||||
"""1D cartesian position operator."""
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("X",)
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(self, args):
|
||||
return L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
|
||||
def _eval_commutator_PxOp(self, other):
|
||||
return I*hbar
|
||||
|
||||
def _apply_operator_XKet(self, ket, **options):
|
||||
return ket.position*ket
|
||||
|
||||
def _apply_operator_PositionKet3D(self, ket, **options):
|
||||
return ket.position_x*ket
|
||||
|
||||
def _represent_PxKet(self, basis, *, index=1, **options):
|
||||
states = basis._enumerate_state(2, start_index=index)
|
||||
coord1 = states[0].momentum
|
||||
coord2 = states[1].momentum
|
||||
d = DifferentialOperator(coord1)
|
||||
delta = DiracDelta(coord1 - coord2)
|
||||
|
||||
return I*hbar*(d*delta)
|
||||
|
||||
|
||||
class YOp(HermitianOperator):
|
||||
""" Y cartesian coordinate operator (for 2D or 3D systems) """
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("Y",)
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(self, args):
|
||||
return L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
|
||||
def _apply_operator_PositionKet3D(self, ket, **options):
|
||||
return ket.position_y*ket
|
||||
|
||||
|
||||
class ZOp(HermitianOperator):
|
||||
""" Z cartesian coordinate operator (for 3D systems) """
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("Z",)
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(self, args):
|
||||
return L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
|
||||
def _apply_operator_PositionKet3D(self, ket, **options):
|
||||
return ket.position_z*ket
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Momentum operators
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
|
||||
class PxOp(HermitianOperator):
|
||||
"""1D cartesian momentum operator."""
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("Px",)
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(self, args):
|
||||
return L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
|
||||
def _apply_operator_PxKet(self, ket, **options):
|
||||
return ket.momentum*ket
|
||||
|
||||
def _represent_XKet(self, basis, *, index=1, **options):
|
||||
states = basis._enumerate_state(2, start_index=index)
|
||||
coord1 = states[0].position
|
||||
coord2 = states[1].position
|
||||
d = DifferentialOperator(coord1)
|
||||
delta = DiracDelta(coord1 - coord2)
|
||||
|
||||
return -I*hbar*(d*delta)
|
||||
|
||||
X = XOp('X')
|
||||
Y = YOp('Y')
|
||||
Z = ZOp('Z')
|
||||
Px = PxOp('Px')
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Position eigenstates
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
|
||||
class XKet(Ket):
|
||||
"""1D cartesian position eigenket."""
|
||||
|
||||
@classmethod
|
||||
def _operators_to_state(self, op, **options):
|
||||
return self.__new__(self, *_lowercase_labels(op), **options)
|
||||
|
||||
def _state_to_operators(self, op_class, **options):
|
||||
return op_class.__new__(op_class,
|
||||
*_uppercase_labels(self), **options)
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("x",)
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return XBra
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
"""The position of the state."""
|
||||
return self.label[0]
|
||||
|
||||
def _enumerate_state(self, num_states, **options):
|
||||
return _enumerate_continuous_1D(self, num_states, **options)
|
||||
|
||||
def _eval_innerproduct_XBra(self, bra, **hints):
|
||||
return DiracDelta(self.position - bra.position)
|
||||
|
||||
def _eval_innerproduct_PxBra(self, bra, **hints):
|
||||
return exp(-I*self.position*bra.momentum/hbar)/sqrt(2*pi*hbar)
|
||||
|
||||
|
||||
class XBra(Bra):
|
||||
"""1D cartesian position eigenbra."""
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("x",)
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return XKet
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
"""The position of the state."""
|
||||
return self.label[0]
|
||||
|
||||
|
||||
class PositionState3D(State):
|
||||
""" Base class for 3D cartesian position eigenstates """
|
||||
|
||||
@classmethod
|
||||
def _operators_to_state(self, op, **options):
|
||||
return self.__new__(self, *_lowercase_labels(op), **options)
|
||||
|
||||
def _state_to_operators(self, op_class, **options):
|
||||
return op_class.__new__(op_class,
|
||||
*_uppercase_labels(self), **options)
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("x", "y", "z")
|
||||
|
||||
@property
|
||||
def position_x(self):
|
||||
""" The x coordinate of the state """
|
||||
return self.label[0]
|
||||
|
||||
@property
|
||||
def position_y(self):
|
||||
""" The y coordinate of the state """
|
||||
return self.label[1]
|
||||
|
||||
@property
|
||||
def position_z(self):
|
||||
""" The z coordinate of the state """
|
||||
return self.label[2]
|
||||
|
||||
|
||||
class PositionKet3D(Ket, PositionState3D):
|
||||
""" 3D cartesian position eigenket """
|
||||
|
||||
def _eval_innerproduct_PositionBra3D(self, bra, **options):
|
||||
x_diff = self.position_x - bra.position_x
|
||||
y_diff = self.position_y - bra.position_y
|
||||
z_diff = self.position_z - bra.position_z
|
||||
|
||||
return DiracDelta(x_diff)*DiracDelta(y_diff)*DiracDelta(z_diff)
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return PositionBra3D
|
||||
|
||||
|
||||
# XXX: The type:ignore here is because mypy gives Definition of
|
||||
# "_state_to_operators" in base class "PositionState3D" is incompatible with
|
||||
# definition in base class "BraBase"
|
||||
class PositionBra3D(Bra, PositionState3D): # type: ignore
|
||||
""" 3D cartesian position eigenbra """
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return PositionKet3D
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Momentum eigenstates
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
|
||||
class PxKet(Ket):
|
||||
"""1D cartesian momentum eigenket."""
|
||||
|
||||
@classmethod
|
||||
def _operators_to_state(self, op, **options):
|
||||
return self.__new__(self, *_lowercase_labels(op), **options)
|
||||
|
||||
def _state_to_operators(self, op_class, **options):
|
||||
return op_class.__new__(op_class,
|
||||
*_uppercase_labels(self), **options)
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("px",)
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return PxBra
|
||||
|
||||
@property
|
||||
def momentum(self):
|
||||
"""The momentum of the state."""
|
||||
return self.label[0]
|
||||
|
||||
def _enumerate_state(self, *args, **options):
|
||||
return _enumerate_continuous_1D(self, *args, **options)
|
||||
|
||||
def _eval_innerproduct_XBra(self, bra, **hints):
|
||||
return exp(I*self.momentum*bra.position/hbar)/sqrt(2*pi*hbar)
|
||||
|
||||
def _eval_innerproduct_PxBra(self, bra, **hints):
|
||||
return DiracDelta(self.momentum - bra.momentum)
|
||||
|
||||
|
||||
class PxBra(Bra):
|
||||
"""1D cartesian momentum eigenbra."""
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("px",)
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return PxKet
|
||||
|
||||
@property
|
||||
def momentum(self):
|
||||
"""The momentum of the state."""
|
||||
return self.label[0]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Global helper functions
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _enumerate_continuous_1D(*args, **options):
|
||||
state = args[0]
|
||||
num_states = args[1]
|
||||
state_class = state.__class__
|
||||
index_list = options.pop('index_list', [])
|
||||
|
||||
if len(index_list) == 0:
|
||||
start_index = options.pop('start_index', 1)
|
||||
index_list = list(range(start_index, start_index + num_states))
|
||||
|
||||
enum_states = [0 for i in range(len(index_list))]
|
||||
|
||||
for i, ind in enumerate(index_list):
|
||||
label = state.args[0]
|
||||
enum_states[i] = state_class(str(label) + "_" + str(ind), **options)
|
||||
|
||||
return enum_states
|
||||
|
||||
|
||||
def _lowercase_labels(ops):
|
||||
if not isinstance(ops, set):
|
||||
ops = [ops]
|
||||
|
||||
return [str(arg.label[0]).lower() for arg in ops]
|
||||
|
||||
|
||||
def _uppercase_labels(ops):
|
||||
if not isinstance(ops, set):
|
||||
ops = [ops]
|
||||
|
||||
new_args = [str(arg.label[0])[0].upper() +
|
||||
str(arg.label[0])[1:] for arg in ops]
|
||||
|
||||
return new_args
|
||||
@@ -0,0 +1,754 @@
|
||||
#TODO:
|
||||
# -Implement Clebsch-Gordan symmetries
|
||||
# -Improve simplification method
|
||||
# -Implement new simplifications
|
||||
"""Clebsch-Gordon Coefficients."""
|
||||
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.function import expand
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Wild, symbols)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.printing.pretty.stringpict import prettyForm, stringPict
|
||||
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.physics.wigner import clebsch_gordan, wigner_3j, wigner_6j, wigner_9j
|
||||
from sympy.printing.precedence import PRECEDENCE
|
||||
|
||||
__all__ = [
|
||||
'CG',
|
||||
'Wigner3j',
|
||||
'Wigner6j',
|
||||
'Wigner9j',
|
||||
'cg_simp'
|
||||
]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# CG Coefficients
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Wigner3j(Expr):
|
||||
"""Class for the Wigner-3j symbols.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Wigner 3j-symbols are coefficients determined by the coupling of
|
||||
two angular momenta. When created, they are expressed as symbolic
|
||||
quantities that, for numerical parameters, can be evaluated using the
|
||||
``.doit()`` method [1]_.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
j1, m1, j2, m2, j3, m3 : Number, Symbol
|
||||
Terms determining the angular momentum of coupled angular momentum
|
||||
systems.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Declare a Wigner-3j coefficient and calculate its value
|
||||
|
||||
>>> from sympy.physics.quantum.cg import Wigner3j
|
||||
>>> w3j = Wigner3j(6,0,4,0,2,0)
|
||||
>>> w3j
|
||||
Wigner3j(6, 0, 4, 0, 2, 0)
|
||||
>>> w3j.doit()
|
||||
sqrt(715)/143
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
CG: Clebsch-Gordan coefficients
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Varshalovich, D A, Quantum Theory of Angular Momentum. 1988.
|
||||
"""
|
||||
|
||||
is_commutative = True
|
||||
|
||||
def __new__(cls, j1, m1, j2, m2, j3, m3):
|
||||
args = map(sympify, (j1, m1, j2, m2, j3, m3))
|
||||
return Expr.__new__(cls, *args)
|
||||
|
||||
@property
|
||||
def j1(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def m1(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def j2(self):
|
||||
return self.args[2]
|
||||
|
||||
@property
|
||||
def m2(self):
|
||||
return self.args[3]
|
||||
|
||||
@property
|
||||
def j3(self):
|
||||
return self.args[4]
|
||||
|
||||
@property
|
||||
def m3(self):
|
||||
return self.args[5]
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return not all(arg.is_number for arg in self.args)
|
||||
|
||||
# This is modified from the _print_Matrix method
|
||||
def _pretty(self, printer, *args):
|
||||
m = ((printer._print(self.j1), printer._print(self.m1)),
|
||||
(printer._print(self.j2), printer._print(self.m2)),
|
||||
(printer._print(self.j3), printer._print(self.m3)))
|
||||
hsep = 2
|
||||
vsep = 1
|
||||
maxw = [-1]*3
|
||||
for j in range(3):
|
||||
maxw[j] = max(m[j][i].width() for i in range(2))
|
||||
D = None
|
||||
for i in range(2):
|
||||
D_row = None
|
||||
for j in range(3):
|
||||
s = m[j][i]
|
||||
wdelta = maxw[j] - s.width()
|
||||
wleft = wdelta //2
|
||||
wright = wdelta - wleft
|
||||
|
||||
s = prettyForm(*s.right(' '*wright))
|
||||
s = prettyForm(*s.left(' '*wleft))
|
||||
|
||||
if D_row is None:
|
||||
D_row = s
|
||||
continue
|
||||
D_row = prettyForm(*D_row.right(' '*hsep))
|
||||
D_row = prettyForm(*D_row.right(s))
|
||||
if D is None:
|
||||
D = D_row
|
||||
continue
|
||||
for _ in range(vsep):
|
||||
D = prettyForm(*D.below(' '))
|
||||
D = prettyForm(*D.below(D_row))
|
||||
D = prettyForm(*D.parens())
|
||||
return D
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
label = map(printer._print, (self.j1, self.j2, self.j3,
|
||||
self.m1, self.m2, self.m3))
|
||||
return r'\left(\begin{array}{ccc} %s & %s & %s \\ %s & %s & %s \end{array}\right)' % \
|
||||
tuple(label)
|
||||
|
||||
def doit(self, **hints):
|
||||
if self.is_symbolic:
|
||||
raise ValueError("Coefficients must be numerical")
|
||||
return wigner_3j(self.j1, self.j2, self.j3, self.m1, self.m2, self.m3)
|
||||
|
||||
|
||||
class CG(Wigner3j):
|
||||
r"""Class for Clebsch-Gordan coefficient.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Clebsch-Gordan coefficients describe the angular momentum coupling between
|
||||
two systems. The coefficients give the expansion of a coupled total angular
|
||||
momentum state and an uncoupled tensor product state. The Clebsch-Gordan
|
||||
coefficients are defined as [1]_:
|
||||
|
||||
.. math ::
|
||||
C^{j_3,m_3}_{j_1,m_1,j_2,m_2} = \left\langle j_1,m_1;j_2,m_2 | j_3,m_3\right\rangle
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
j1, m1, j2, m2 : Number, Symbol
|
||||
Angular momenta of states 1 and 2.
|
||||
|
||||
j3, m3: Number, Symbol
|
||||
Total angular momentum of the coupled system.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Define a Clebsch-Gordan coefficient and evaluate its value
|
||||
|
||||
>>> from sympy.physics.quantum.cg import CG
|
||||
>>> from sympy import S
|
||||
>>> cg = CG(S(3)/2, S(3)/2, S(1)/2, -S(1)/2, 1, 1)
|
||||
>>> cg
|
||||
CG(3/2, 3/2, 1/2, -1/2, 1, 1)
|
||||
>>> cg.doit()
|
||||
sqrt(3)/2
|
||||
>>> CG(j1=S(1)/2, m1=-S(1)/2, j2=S(1)/2, m2=+S(1)/2, j3=1, m3=0).doit()
|
||||
sqrt(2)/2
|
||||
|
||||
|
||||
Compare [2]_.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
Wigner3j: Wigner-3j symbols
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Varshalovich, D A, Quantum Theory of Angular Momentum. 1988.
|
||||
.. [2] `Clebsch-Gordan Coefficients, Spherical Harmonics, and d Functions
|
||||
<https://pdg.lbl.gov/2020/reviews/rpp2020-rev-clebsch-gordan-coefs.pdf>`_
|
||||
in P.A. Zyla *et al.* (Particle Data Group), Prog. Theor. Exp. Phys.
|
||||
2020, 083C01 (2020).
|
||||
"""
|
||||
precedence = PRECEDENCE["Pow"] - 1
|
||||
|
||||
def doit(self, **hints):
|
||||
if self.is_symbolic:
|
||||
raise ValueError("Coefficients must be numerical")
|
||||
return clebsch_gordan(self.j1, self.j2, self.j3, self.m1, self.m2, self.m3)
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
bot = printer._print_seq(
|
||||
(self.j1, self.m1, self.j2, self.m2), delimiter=',')
|
||||
top = printer._print_seq((self.j3, self.m3), delimiter=',')
|
||||
|
||||
pad = max(top.width(), bot.width())
|
||||
bot = prettyForm(*bot.left(' '))
|
||||
top = prettyForm(*top.left(' '))
|
||||
|
||||
if not pad == bot.width():
|
||||
bot = prettyForm(*bot.right(' '*(pad - bot.width())))
|
||||
if not pad == top.width():
|
||||
top = prettyForm(*top.right(' '*(pad - top.width())))
|
||||
s = stringPict('C' + ' '*pad)
|
||||
s = prettyForm(*s.below(bot))
|
||||
s = prettyForm(*s.above(top))
|
||||
return s
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
label = map(printer._print, (self.j3, self.m3, self.j1,
|
||||
self.m1, self.j2, self.m2))
|
||||
return r'C^{%s,%s}_{%s,%s,%s,%s}' % tuple(label)
|
||||
|
||||
|
||||
class Wigner6j(Expr):
|
||||
"""Class for the Wigner-6j symbols
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
Wigner3j: Wigner-3j symbols
|
||||
|
||||
"""
|
||||
def __new__(cls, j1, j2, j12, j3, j, j23):
|
||||
args = map(sympify, (j1, j2, j12, j3, j, j23))
|
||||
return Expr.__new__(cls, *args)
|
||||
|
||||
@property
|
||||
def j1(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def j2(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def j12(self):
|
||||
return self.args[2]
|
||||
|
||||
@property
|
||||
def j3(self):
|
||||
return self.args[3]
|
||||
|
||||
@property
|
||||
def j(self):
|
||||
return self.args[4]
|
||||
|
||||
@property
|
||||
def j23(self):
|
||||
return self.args[5]
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return not all(arg.is_number for arg in self.args)
|
||||
|
||||
# This is modified from the _print_Matrix method
|
||||
def _pretty(self, printer, *args):
|
||||
m = ((printer._print(self.j1), printer._print(self.j3)),
|
||||
(printer._print(self.j2), printer._print(self.j)),
|
||||
(printer._print(self.j12), printer._print(self.j23)))
|
||||
hsep = 2
|
||||
vsep = 1
|
||||
maxw = [-1]*3
|
||||
for j in range(3):
|
||||
maxw[j] = max(m[j][i].width() for i in range(2))
|
||||
D = None
|
||||
for i in range(2):
|
||||
D_row = None
|
||||
for j in range(3):
|
||||
s = m[j][i]
|
||||
wdelta = maxw[j] - s.width()
|
||||
wleft = wdelta //2
|
||||
wright = wdelta - wleft
|
||||
|
||||
s = prettyForm(*s.right(' '*wright))
|
||||
s = prettyForm(*s.left(' '*wleft))
|
||||
|
||||
if D_row is None:
|
||||
D_row = s
|
||||
continue
|
||||
D_row = prettyForm(*D_row.right(' '*hsep))
|
||||
D_row = prettyForm(*D_row.right(s))
|
||||
if D is None:
|
||||
D = D_row
|
||||
continue
|
||||
for _ in range(vsep):
|
||||
D = prettyForm(*D.below(' '))
|
||||
D = prettyForm(*D.below(D_row))
|
||||
D = prettyForm(*D.parens(left='{', right='}'))
|
||||
return D
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
label = map(printer._print, (self.j1, self.j2, self.j12,
|
||||
self.j3, self.j, self.j23))
|
||||
return r'\left\{\begin{array}{ccc} %s & %s & %s \\ %s & %s & %s \end{array}\right\}' % \
|
||||
tuple(label)
|
||||
|
||||
def doit(self, **hints):
|
||||
if self.is_symbolic:
|
||||
raise ValueError("Coefficients must be numerical")
|
||||
return wigner_6j(self.j1, self.j2, self.j12, self.j3, self.j, self.j23)
|
||||
|
||||
|
||||
class Wigner9j(Expr):
|
||||
"""Class for the Wigner-9j symbols
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
Wigner3j: Wigner-3j symbols
|
||||
|
||||
"""
|
||||
def __new__(cls, j1, j2, j12, j3, j4, j34, j13, j24, j):
|
||||
args = map(sympify, (j1, j2, j12, j3, j4, j34, j13, j24, j))
|
||||
return Expr.__new__(cls, *args)
|
||||
|
||||
@property
|
||||
def j1(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def j2(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def j12(self):
|
||||
return self.args[2]
|
||||
|
||||
@property
|
||||
def j3(self):
|
||||
return self.args[3]
|
||||
|
||||
@property
|
||||
def j4(self):
|
||||
return self.args[4]
|
||||
|
||||
@property
|
||||
def j34(self):
|
||||
return self.args[5]
|
||||
|
||||
@property
|
||||
def j13(self):
|
||||
return self.args[6]
|
||||
|
||||
@property
|
||||
def j24(self):
|
||||
return self.args[7]
|
||||
|
||||
@property
|
||||
def j(self):
|
||||
return self.args[8]
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return not all(arg.is_number for arg in self.args)
|
||||
|
||||
# This is modified from the _print_Matrix method
|
||||
def _pretty(self, printer, *args):
|
||||
m = (
|
||||
(printer._print(
|
||||
self.j1), printer._print(self.j3), printer._print(self.j13)),
|
||||
(printer._print(
|
||||
self.j2), printer._print(self.j4), printer._print(self.j24)),
|
||||
(printer._print(self.j12), printer._print(self.j34), printer._print(self.j)))
|
||||
hsep = 2
|
||||
vsep = 1
|
||||
maxw = [-1]*3
|
||||
for j in range(3):
|
||||
maxw[j] = max(m[j][i].width() for i in range(3))
|
||||
D = None
|
||||
for i in range(3):
|
||||
D_row = None
|
||||
for j in range(3):
|
||||
s = m[j][i]
|
||||
wdelta = maxw[j] - s.width()
|
||||
wleft = wdelta //2
|
||||
wright = wdelta - wleft
|
||||
|
||||
s = prettyForm(*s.right(' '*wright))
|
||||
s = prettyForm(*s.left(' '*wleft))
|
||||
|
||||
if D_row is None:
|
||||
D_row = s
|
||||
continue
|
||||
D_row = prettyForm(*D_row.right(' '*hsep))
|
||||
D_row = prettyForm(*D_row.right(s))
|
||||
if D is None:
|
||||
D = D_row
|
||||
continue
|
||||
for _ in range(vsep):
|
||||
D = prettyForm(*D.below(' '))
|
||||
D = prettyForm(*D.below(D_row))
|
||||
D = prettyForm(*D.parens(left='{', right='}'))
|
||||
return D
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
label = map(printer._print, (self.j1, self.j2, self.j12, self.j3,
|
||||
self.j4, self.j34, self.j13, self.j24, self.j))
|
||||
return r'\left\{\begin{array}{ccc} %s & %s & %s \\ %s & %s & %s \\ %s & %s & %s \end{array}\right\}' % \
|
||||
tuple(label)
|
||||
|
||||
def doit(self, **hints):
|
||||
if self.is_symbolic:
|
||||
raise ValueError("Coefficients must be numerical")
|
||||
return wigner_9j(self.j1, self.j2, self.j12, self.j3, self.j4, self.j34, self.j13, self.j24, self.j)
|
||||
|
||||
|
||||
def cg_simp(e):
|
||||
"""Simplify and combine CG coefficients.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
This function uses various symmetry and properties of sums and
|
||||
products of Clebsch-Gordan coefficients to simplify statements
|
||||
involving these terms [1]_.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Simplify the sum over CG(a,alpha,0,0,a,alpha) for all alpha to
|
||||
2*a+1
|
||||
|
||||
>>> from sympy.physics.quantum.cg import CG, cg_simp
|
||||
>>> a = CG(1,1,0,0,1,1)
|
||||
>>> b = CG(1,0,0,0,1,0)
|
||||
>>> c = CG(1,-1,0,0,1,-1)
|
||||
>>> cg_simp(a+b+c)
|
||||
3
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
CG: Clebsh-Gordan coefficients
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Varshalovich, D A, Quantum Theory of Angular Momentum. 1988.
|
||||
"""
|
||||
if isinstance(e, Add):
|
||||
return _cg_simp_add(e)
|
||||
elif isinstance(e, Sum):
|
||||
return _cg_simp_sum(e)
|
||||
elif isinstance(e, Mul):
|
||||
return Mul(*[cg_simp(arg) for arg in e.args])
|
||||
elif isinstance(e, Pow):
|
||||
return Pow(cg_simp(e.base), e.exp)
|
||||
else:
|
||||
return e
|
||||
|
||||
|
||||
def _cg_simp_add(e):
|
||||
#TODO: Improve simplification method
|
||||
"""Takes a sum of terms involving Clebsch-Gordan coefficients and
|
||||
simplifies the terms.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
First, we create two lists, cg_part, which is all the terms involving CG
|
||||
coefficients, and other_part, which is all other terms. The cg_part list
|
||||
is then passed to the simplification methods, which return the new cg_part
|
||||
and any additional terms that are added to other_part
|
||||
"""
|
||||
cg_part = []
|
||||
other_part = []
|
||||
|
||||
e = expand(e)
|
||||
for arg in e.args:
|
||||
if arg.has(CG):
|
||||
if isinstance(arg, Sum):
|
||||
other_part.append(_cg_simp_sum(arg))
|
||||
elif isinstance(arg, Mul):
|
||||
terms = 1
|
||||
for term in arg.args:
|
||||
if isinstance(term, Sum):
|
||||
terms *= _cg_simp_sum(term)
|
||||
else:
|
||||
terms *= term
|
||||
if terms.has(CG):
|
||||
cg_part.append(terms)
|
||||
else:
|
||||
other_part.append(terms)
|
||||
else:
|
||||
cg_part.append(arg)
|
||||
else:
|
||||
other_part.append(arg)
|
||||
|
||||
cg_part, other = _check_varsh_871_1(cg_part)
|
||||
other_part.append(other)
|
||||
cg_part, other = _check_varsh_871_2(cg_part)
|
||||
other_part.append(other)
|
||||
cg_part, other = _check_varsh_872_9(cg_part)
|
||||
other_part.append(other)
|
||||
return Add(*cg_part) + Add(*other_part)
|
||||
|
||||
|
||||
def _check_varsh_871_1(term_list):
|
||||
# Sum( CG(a,alpha,b,0,a,alpha), (alpha, -a, a)) == KroneckerDelta(b,0)
|
||||
a, alpha, b, lt = map(Wild, ('a', 'alpha', 'b', 'lt'))
|
||||
expr = lt*CG(a, alpha, b, 0, a, alpha)
|
||||
simp = (2*a + 1)*KroneckerDelta(b, 0)
|
||||
sign = lt/abs(lt)
|
||||
build_expr = 2*a + 1
|
||||
index_expr = a + alpha
|
||||
return _check_cg_simp(expr, simp, sign, lt, term_list, (a, alpha, b, lt), (a, b), build_expr, index_expr)
|
||||
|
||||
|
||||
def _check_varsh_871_2(term_list):
|
||||
# Sum((-1)**(a-alpha)*CG(a,alpha,a,-alpha,c,0),(alpha,-a,a))
|
||||
a, alpha, c, lt = map(Wild, ('a', 'alpha', 'c', 'lt'))
|
||||
expr = lt*CG(a, alpha, a, -alpha, c, 0)
|
||||
simp = sqrt(2*a + 1)*KroneckerDelta(c, 0)
|
||||
sign = (-1)**(a - alpha)*lt/abs(lt)
|
||||
build_expr = 2*a + 1
|
||||
index_expr = a + alpha
|
||||
return _check_cg_simp(expr, simp, sign, lt, term_list, (a, alpha, c, lt), (a, c), build_expr, index_expr)
|
||||
|
||||
|
||||
def _check_varsh_872_9(term_list):
|
||||
# Sum( CG(a,alpha,b,beta,c,gamma)*CG(a,alpha',b,beta',c,gamma), (gamma, -c, c), (c, abs(a-b), a+b))
|
||||
a, alpha, alphap, b, beta, betap, c, gamma, lt = map(Wild, (
|
||||
'a', 'alpha', 'alphap', 'b', 'beta', 'betap', 'c', 'gamma', 'lt'))
|
||||
# Case alpha==alphap, beta==betap
|
||||
|
||||
# For numerical alpha,beta
|
||||
expr = lt*CG(a, alpha, b, beta, c, gamma)**2
|
||||
simp = S.One
|
||||
sign = lt/abs(lt)
|
||||
x = abs(a - b)
|
||||
y = abs(alpha + beta)
|
||||
build_expr = a + b + 1 - Piecewise((x, x > y), (0, Eq(x, y)), (y, y > x))
|
||||
index_expr = a + b - c
|
||||
term_list, other1 = _check_cg_simp(expr, simp, sign, lt, term_list, (a, alpha, b, beta, c, gamma, lt), (a, alpha, b, beta), build_expr, index_expr)
|
||||
|
||||
# For symbolic alpha,beta
|
||||
x = abs(a - b)
|
||||
y = a + b
|
||||
build_expr = (y + 1 - x)*(x + y + 1)
|
||||
index_expr = (c - x)*(x + c) + c + gamma
|
||||
term_list, other2 = _check_cg_simp(expr, simp, sign, lt, term_list, (a, alpha, b, beta, c, gamma, lt), (a, alpha, b, beta), build_expr, index_expr)
|
||||
|
||||
# Case alpha!=alphap or beta!=betap
|
||||
# Note: this only works with leading term of 1, pattern matching is unable to match when there is a Wild leading term
|
||||
# For numerical alpha,alphap,beta,betap
|
||||
expr = CG(a, alpha, b, beta, c, gamma)*CG(a, alphap, b, betap, c, gamma)
|
||||
simp = KroneckerDelta(alpha, alphap)*KroneckerDelta(beta, betap)
|
||||
sign = S.One
|
||||
x = abs(a - b)
|
||||
y = abs(alpha + beta)
|
||||
build_expr = a + b + 1 - Piecewise((x, x > y), (0, Eq(x, y)), (y, y > x))
|
||||
index_expr = a + b - c
|
||||
term_list, other3 = _check_cg_simp(expr, simp, sign, S.One, term_list, (a, alpha, alphap, b, beta, betap, c, gamma), (a, alpha, alphap, b, beta, betap), build_expr, index_expr)
|
||||
|
||||
# For symbolic alpha,alphap,beta,betap
|
||||
x = abs(a - b)
|
||||
y = a + b
|
||||
build_expr = (y + 1 - x)*(x + y + 1)
|
||||
index_expr = (c - x)*(x + c) + c + gamma
|
||||
term_list, other4 = _check_cg_simp(expr, simp, sign, S.One, term_list, (a, alpha, alphap, b, beta, betap, c, gamma), (a, alpha, alphap, b, beta, betap), build_expr, index_expr)
|
||||
|
||||
return term_list, other1 + other2 + other4
|
||||
|
||||
|
||||
def _check_cg_simp(expr, simp, sign, lt, term_list, variables, dep_variables, build_index_expr, index_expr):
|
||||
""" Checks for simplifications that can be made, returning a tuple of the
|
||||
simplified list of terms and any terms generated by simplification.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
expr: expression
|
||||
The expression with Wild terms that will be matched to the terms in
|
||||
the sum
|
||||
|
||||
simp: expression
|
||||
The expression with Wild terms that is substituted in place of the CG
|
||||
terms in the case of simplification
|
||||
|
||||
sign: expression
|
||||
The expression with Wild terms denoting the sign that is on expr that
|
||||
must match
|
||||
|
||||
lt: expression
|
||||
The expression with Wild terms that gives the leading term of the
|
||||
matched expr
|
||||
|
||||
term_list: list
|
||||
A list of all of the terms is the sum to be simplified
|
||||
|
||||
variables: list
|
||||
A list of all the variables that appears in expr
|
||||
|
||||
dep_variables: list
|
||||
A list of the variables that must match for all the terms in the sum,
|
||||
i.e. the dependent variables
|
||||
|
||||
build_index_expr: expression
|
||||
Expression with Wild terms giving the number of elements in cg_index
|
||||
|
||||
index_expr: expression
|
||||
Expression with Wild terms giving the index terms have when storing
|
||||
them to cg_index
|
||||
|
||||
"""
|
||||
other_part = 0
|
||||
i = 0
|
||||
while i < len(term_list):
|
||||
sub_1 = _check_cg(term_list[i], expr, len(variables))
|
||||
if sub_1 is None:
|
||||
i += 1
|
||||
continue
|
||||
if not build_index_expr.subs(sub_1).is_number:
|
||||
i += 1
|
||||
continue
|
||||
sub_dep = [(x, sub_1[x]) for x in dep_variables]
|
||||
cg_index = [None]*build_index_expr.subs(sub_1)
|
||||
for j in range(i, len(term_list)):
|
||||
sub_2 = _check_cg(term_list[j], expr.subs(sub_dep), len(variables) - len(dep_variables), sign=(sign.subs(sub_1), sign.subs(sub_dep)))
|
||||
if sub_2 is None:
|
||||
continue
|
||||
if not index_expr.subs(sub_dep).subs(sub_2).is_number:
|
||||
continue
|
||||
cg_index[index_expr.subs(sub_dep).subs(sub_2)] = j, expr.subs(lt, 1).subs(sub_dep).subs(sub_2), lt.subs(sub_2), sign.subs(sub_dep).subs(sub_2)
|
||||
if not any(i is None for i in cg_index):
|
||||
min_lt = min(*[ abs(term[2]) for term in cg_index ])
|
||||
indices = [ term[0] for term in cg_index]
|
||||
indices.sort()
|
||||
indices.reverse()
|
||||
[ term_list.pop(j) for j in indices ]
|
||||
for term in cg_index:
|
||||
if abs(term[2]) > min_lt:
|
||||
term_list.append( (term[2] - min_lt*term[3])*term[1] )
|
||||
other_part += min_lt*(sign*simp).subs(sub_1)
|
||||
else:
|
||||
i += 1
|
||||
return term_list, other_part
|
||||
|
||||
|
||||
def _check_cg(cg_term, expr, length, sign=None):
|
||||
"""Checks whether a term matches the given expression"""
|
||||
# TODO: Check for symmetries
|
||||
matches = cg_term.match(expr)
|
||||
if matches is None:
|
||||
return
|
||||
if sign is not None:
|
||||
if not isinstance(sign, tuple):
|
||||
raise TypeError('sign must be a tuple')
|
||||
if not sign[0] == (sign[1]).subs(matches):
|
||||
return
|
||||
if len(matches) == length:
|
||||
return matches
|
||||
|
||||
|
||||
def _cg_simp_sum(e):
|
||||
e = _check_varsh_sum_871_1(e)
|
||||
e = _check_varsh_sum_871_2(e)
|
||||
e = _check_varsh_sum_872_4(e)
|
||||
return e
|
||||
|
||||
|
||||
def _check_varsh_sum_871_1(e):
|
||||
a = Wild('a')
|
||||
alpha = symbols('alpha')
|
||||
b = Wild('b')
|
||||
match = e.match(Sum(CG(a, alpha, b, 0, a, alpha), (alpha, -a, a)))
|
||||
if match is not None and len(match) == 2:
|
||||
return ((2*a + 1)*KroneckerDelta(b, 0)).subs(match)
|
||||
return e
|
||||
|
||||
|
||||
def _check_varsh_sum_871_2(e):
|
||||
a = Wild('a')
|
||||
alpha = symbols('alpha')
|
||||
c = Wild('c')
|
||||
match = e.match(
|
||||
Sum((-1)**(a - alpha)*CG(a, alpha, a, -alpha, c, 0), (alpha, -a, a)))
|
||||
if match is not None and len(match) == 2:
|
||||
return (sqrt(2*a + 1)*KroneckerDelta(c, 0)).subs(match)
|
||||
return e
|
||||
|
||||
|
||||
def _check_varsh_sum_872_4(e):
|
||||
alpha = symbols('alpha')
|
||||
beta = symbols('beta')
|
||||
a = Wild('a')
|
||||
b = Wild('b')
|
||||
c = Wild('c')
|
||||
cp = Wild('cp')
|
||||
gamma = Wild('gamma')
|
||||
gammap = Wild('gammap')
|
||||
cg1 = CG(a, alpha, b, beta, c, gamma)
|
||||
cg2 = CG(a, alpha, b, beta, cp, gammap)
|
||||
match1 = e.match(Sum(cg1*cg2, (alpha, -a, a), (beta, -b, b)))
|
||||
if match1 is not None and len(match1) == 6:
|
||||
return (KroneckerDelta(c, cp)*KroneckerDelta(gamma, gammap)).subs(match1)
|
||||
match2 = e.match(Sum(cg1**2, (alpha, -a, a), (beta, -b, b)))
|
||||
if match2 is not None and len(match2) == 4:
|
||||
return S.One
|
||||
return e
|
||||
|
||||
|
||||
def _cg_list(term):
|
||||
if isinstance(term, CG):
|
||||
return (term,), 1, 1
|
||||
cg = []
|
||||
coeff = 1
|
||||
if not isinstance(term, (Mul, Pow)):
|
||||
raise NotImplementedError('term must be CG, Add, Mul or Pow')
|
||||
if isinstance(term, Pow) and term.exp.is_number:
|
||||
if term.exp.is_number:
|
||||
[ cg.append(term.base) for _ in range(term.exp) ]
|
||||
else:
|
||||
return (term,), 1, 1
|
||||
if isinstance(term, Mul):
|
||||
for arg in term.args:
|
||||
if isinstance(arg, CG):
|
||||
cg.append(arg)
|
||||
else:
|
||||
coeff *= arg
|
||||
return cg, coeff, coeff/abs(coeff)
|
||||
@@ -0,0 +1,370 @@
|
||||
"""Matplotlib based plotting of quantum circuits.
|
||||
|
||||
Todo:
|
||||
|
||||
* Optimize printing of large circuits.
|
||||
* Get this to work with single gates.
|
||||
* Do a better job checking the form of circuits to make sure it is a Mul of
|
||||
Gates.
|
||||
* Get multi-target gates plotting.
|
||||
* Get initial and final states to plot.
|
||||
* Get measurements to plot. Might need to rethink measurement as a gate
|
||||
issue.
|
||||
* Get scale and figsize to be handled in a better way.
|
||||
* Write some tests/examples!
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.external import import_module
|
||||
from sympy.physics.quantum.gate import Gate, OneQubitGate, CGate, CGateS
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CircuitPlot',
|
||||
'circuit_plot',
|
||||
'labeller',
|
||||
'Mz',
|
||||
'Mx',
|
||||
'CreateOneQubitGate',
|
||||
'CreateCGate',
|
||||
]
|
||||
|
||||
np = import_module('numpy')
|
||||
matplotlib = import_module(
|
||||
'matplotlib', import_kwargs={'fromlist': ['pyplot']},
|
||||
catch=(RuntimeError,)) # This is raised in environments that have no display.
|
||||
|
||||
if np and matplotlib:
|
||||
pyplot = matplotlib.pyplot
|
||||
Line2D = matplotlib.lines.Line2D
|
||||
Circle = matplotlib.patches.Circle
|
||||
|
||||
#from matplotlib import rc
|
||||
#rc('text',usetex=True)
|
||||
|
||||
class CircuitPlot:
|
||||
"""A class for managing a circuit plot."""
|
||||
|
||||
scale = 1.0
|
||||
fontsize = 20.0
|
||||
linewidth = 1.0
|
||||
control_radius = 0.05
|
||||
not_radius = 0.15
|
||||
swap_delta = 0.05
|
||||
labels: list[str] = []
|
||||
inits: dict[str, str] = {}
|
||||
label_buffer = 0.5
|
||||
|
||||
def __init__(self, c, nqubits, **kwargs):
|
||||
if not np or not matplotlib:
|
||||
raise ImportError('numpy or matplotlib not available.')
|
||||
self.circuit = c
|
||||
self.ngates = len(self.circuit.args)
|
||||
self.nqubits = nqubits
|
||||
self.update(kwargs)
|
||||
self._create_grid()
|
||||
self._create_figure()
|
||||
self._plot_wires()
|
||||
self._plot_gates()
|
||||
self._finish()
|
||||
|
||||
def update(self, kwargs):
|
||||
"""Load the kwargs into the instance dict."""
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def _create_grid(self):
|
||||
"""Create the grid of wires."""
|
||||
scale = self.scale
|
||||
wire_grid = np.arange(0.0, self.nqubits*scale, scale, dtype=float)
|
||||
gate_grid = np.arange(0.0, self.ngates*scale, scale, dtype=float)
|
||||
self._wire_grid = wire_grid
|
||||
self._gate_grid = gate_grid
|
||||
|
||||
def _create_figure(self):
|
||||
"""Create the main matplotlib figure."""
|
||||
self._figure = pyplot.figure(
|
||||
figsize=(self.ngates*self.scale, self.nqubits*self.scale),
|
||||
facecolor='w',
|
||||
edgecolor='w'
|
||||
)
|
||||
ax = self._figure.add_subplot(
|
||||
1, 1, 1,
|
||||
frameon=True
|
||||
)
|
||||
ax.set_axis_off()
|
||||
offset = 0.5*self.scale
|
||||
ax.set_xlim(self._gate_grid[0] - offset, self._gate_grid[-1] + offset)
|
||||
ax.set_ylim(self._wire_grid[0] - offset, self._wire_grid[-1] + offset)
|
||||
ax.set_aspect('equal')
|
||||
self._axes = ax
|
||||
|
||||
def _plot_wires(self):
|
||||
"""Plot the wires of the circuit diagram."""
|
||||
xstart = self._gate_grid[0]
|
||||
xstop = self._gate_grid[-1]
|
||||
xdata = (xstart - self.scale, xstop + self.scale)
|
||||
for i in range(self.nqubits):
|
||||
ydata = (self._wire_grid[i], self._wire_grid[i])
|
||||
line = Line2D(
|
||||
xdata, ydata,
|
||||
color='k',
|
||||
lw=self.linewidth
|
||||
)
|
||||
self._axes.add_line(line)
|
||||
if self.labels:
|
||||
init_label_buffer = 0
|
||||
if self.inits.get(self.labels[i]): init_label_buffer = 0.25
|
||||
self._axes.text(
|
||||
xdata[0]-self.label_buffer-init_label_buffer,ydata[0],
|
||||
render_label(self.labels[i],self.inits),
|
||||
size=self.fontsize,
|
||||
color='k',ha='center',va='center')
|
||||
self._plot_measured_wires()
|
||||
|
||||
def _plot_measured_wires(self):
|
||||
ismeasured = self._measurements()
|
||||
xstop = self._gate_grid[-1]
|
||||
dy = 0.04 # amount to shift wires when doubled
|
||||
# Plot doubled wires after they are measured
|
||||
for im in ismeasured:
|
||||
xdata = (self._gate_grid[ismeasured[im]],xstop+self.scale)
|
||||
ydata = (self._wire_grid[im]+dy,self._wire_grid[im]+dy)
|
||||
line = Line2D(
|
||||
xdata, ydata,
|
||||
color='k',
|
||||
lw=self.linewidth
|
||||
)
|
||||
self._axes.add_line(line)
|
||||
# Also double any controlled lines off these wires
|
||||
for i,g in enumerate(self._gates()):
|
||||
if isinstance(g, (CGate, CGateS)):
|
||||
wires = g.controls + g.targets
|
||||
for wire in wires:
|
||||
if wire in ismeasured and \
|
||||
self._gate_grid[i] > self._gate_grid[ismeasured[wire]]:
|
||||
ydata = min(wires), max(wires)
|
||||
xdata = self._gate_grid[i]-dy, self._gate_grid[i]-dy
|
||||
line = Line2D(
|
||||
xdata, ydata,
|
||||
color='k',
|
||||
lw=self.linewidth
|
||||
)
|
||||
self._axes.add_line(line)
|
||||
def _gates(self):
|
||||
"""Create a list of all gates in the circuit plot."""
|
||||
gates = []
|
||||
if isinstance(self.circuit, Mul):
|
||||
for g in reversed(self.circuit.args):
|
||||
if isinstance(g, Gate):
|
||||
gates.append(g)
|
||||
elif isinstance(self.circuit, Gate):
|
||||
gates.append(self.circuit)
|
||||
return gates
|
||||
|
||||
def _plot_gates(self):
|
||||
"""Iterate through the gates and plot each of them."""
|
||||
for i, gate in enumerate(self._gates()):
|
||||
gate.plot_gate(self, i)
|
||||
|
||||
def _measurements(self):
|
||||
"""Return a dict ``{i:j}`` where i is the index of the wire that has
|
||||
been measured, and j is the gate where the wire is measured.
|
||||
"""
|
||||
ismeasured = {}
|
||||
for i,g in enumerate(self._gates()):
|
||||
if getattr(g,'measurement',False):
|
||||
for target in g.targets:
|
||||
if target in ismeasured:
|
||||
if ismeasured[target] > i:
|
||||
ismeasured[target] = i
|
||||
else:
|
||||
ismeasured[target] = i
|
||||
return ismeasured
|
||||
|
||||
def _finish(self):
|
||||
# Disable clipping to make panning work well for large circuits.
|
||||
for o in self._figure.findobj():
|
||||
o.set_clip_on(False)
|
||||
|
||||
def one_qubit_box(self, t, gate_idx, wire_idx):
|
||||
"""Draw a box for a single qubit gate."""
|
||||
x = self._gate_grid[gate_idx]
|
||||
y = self._wire_grid[wire_idx]
|
||||
self._axes.text(
|
||||
x, y, t,
|
||||
color='k',
|
||||
ha='center',
|
||||
va='center',
|
||||
bbox={"ec": 'k', "fc": 'w', "fill": True, "lw": self.linewidth},
|
||||
size=self.fontsize
|
||||
)
|
||||
|
||||
def two_qubit_box(self, t, gate_idx, wire_idx):
|
||||
"""Draw a box for a two qubit gate. Does not work yet.
|
||||
"""
|
||||
# x = self._gate_grid[gate_idx]
|
||||
# y = self._wire_grid[wire_idx]+0.5
|
||||
print(self._gate_grid)
|
||||
print(self._wire_grid)
|
||||
# unused:
|
||||
# obj = self._axes.text(
|
||||
# x, y, t,
|
||||
# color='k',
|
||||
# ha='center',
|
||||
# va='center',
|
||||
# bbox=dict(ec='k', fc='w', fill=True, lw=self.linewidth),
|
||||
# size=self.fontsize
|
||||
# )
|
||||
|
||||
def control_line(self, gate_idx, min_wire, max_wire):
|
||||
"""Draw a vertical control line."""
|
||||
xdata = (self._gate_grid[gate_idx], self._gate_grid[gate_idx])
|
||||
ydata = (self._wire_grid[min_wire], self._wire_grid[max_wire])
|
||||
line = Line2D(
|
||||
xdata, ydata,
|
||||
color='k',
|
||||
lw=self.linewidth
|
||||
)
|
||||
self._axes.add_line(line)
|
||||
|
||||
def control_point(self, gate_idx, wire_idx):
|
||||
"""Draw a control point."""
|
||||
x = self._gate_grid[gate_idx]
|
||||
y = self._wire_grid[wire_idx]
|
||||
radius = self.control_radius
|
||||
c = Circle(
|
||||
(x, y),
|
||||
radius*self.scale,
|
||||
ec='k',
|
||||
fc='k',
|
||||
fill=True,
|
||||
lw=self.linewidth
|
||||
)
|
||||
self._axes.add_patch(c)
|
||||
|
||||
def not_point(self, gate_idx, wire_idx):
|
||||
"""Draw a NOT gates as the circle with plus in the middle."""
|
||||
x = self._gate_grid[gate_idx]
|
||||
y = self._wire_grid[wire_idx]
|
||||
radius = self.not_radius
|
||||
c = Circle(
|
||||
(x, y),
|
||||
radius,
|
||||
ec='k',
|
||||
fc='w',
|
||||
fill=False,
|
||||
lw=self.linewidth
|
||||
)
|
||||
self._axes.add_patch(c)
|
||||
l = Line2D(
|
||||
(x, x), (y - radius, y + radius),
|
||||
color='k',
|
||||
lw=self.linewidth
|
||||
)
|
||||
self._axes.add_line(l)
|
||||
|
||||
def swap_point(self, gate_idx, wire_idx):
|
||||
"""Draw a swap point as a cross."""
|
||||
x = self._gate_grid[gate_idx]
|
||||
y = self._wire_grid[wire_idx]
|
||||
d = self.swap_delta
|
||||
l1 = Line2D(
|
||||
(x - d, x + d),
|
||||
(y - d, y + d),
|
||||
color='k',
|
||||
lw=self.linewidth
|
||||
)
|
||||
l2 = Line2D(
|
||||
(x - d, x + d),
|
||||
(y + d, y - d),
|
||||
color='k',
|
||||
lw=self.linewidth
|
||||
)
|
||||
self._axes.add_line(l1)
|
||||
self._axes.add_line(l2)
|
||||
|
||||
def circuit_plot(c, nqubits, **kwargs):
|
||||
"""Draw the circuit diagram for the circuit with nqubits.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
c : circuit
|
||||
The circuit to plot. Should be a product of Gate instances.
|
||||
nqubits : int
|
||||
The number of qubits to include in the circuit. Must be at least
|
||||
as big as the largest ``min_qubits`` of the gates.
|
||||
"""
|
||||
return CircuitPlot(c, nqubits, **kwargs)
|
||||
|
||||
def render_label(label, inits={}):
|
||||
"""Slightly more flexible way to render labels.
|
||||
|
||||
>>> from sympy.physics.quantum.circuitplot import render_label
|
||||
>>> render_label('q0')
|
||||
'$\\\\left|q0\\\\right\\\\rangle$'
|
||||
>>> render_label('q0', {'q0':'0'})
|
||||
'$\\\\left|q0\\\\right\\\\rangle=\\\\left|0\\\\right\\\\rangle$'
|
||||
"""
|
||||
init = inits.get(label)
|
||||
if init:
|
||||
return r'$\left|%s\right\rangle=\left|%s\right\rangle$' % (label, init)
|
||||
return r'$\left|%s\right\rangle$' % label
|
||||
|
||||
def labeller(n, symbol='q'):
|
||||
"""Autogenerate labels for wires of quantum circuits.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : int
|
||||
number of qubits in the circuit.
|
||||
symbol : string
|
||||
A character string to precede all gate labels. E.g. 'q_0', 'q_1', etc.
|
||||
|
||||
>>> from sympy.physics.quantum.circuitplot import labeller
|
||||
>>> labeller(2)
|
||||
['q_1', 'q_0']
|
||||
>>> labeller(3,'j')
|
||||
['j_2', 'j_1', 'j_0']
|
||||
"""
|
||||
return ['%s_%d' % (symbol,n-i-1) for i in range(n)]
|
||||
|
||||
class Mz(OneQubitGate):
|
||||
"""Mock-up of a z measurement gate.
|
||||
|
||||
This is in circuitplot rather than gate.py because it's not a real
|
||||
gate, it just draws one.
|
||||
"""
|
||||
measurement = True
|
||||
gate_name='Mz'
|
||||
gate_name_latex='M_z'
|
||||
|
||||
class Mx(OneQubitGate):
|
||||
"""Mock-up of an x measurement gate.
|
||||
|
||||
This is in circuitplot rather than gate.py because it's not a real
|
||||
gate, it just draws one.
|
||||
"""
|
||||
measurement = True
|
||||
gate_name='Mx'
|
||||
gate_name_latex='M_x'
|
||||
|
||||
class CreateOneQubitGate(type):
|
||||
def __new__(mcl, name, latexname=None):
|
||||
if not latexname:
|
||||
latexname = name
|
||||
return type(name + "Gate", (OneQubitGate,),
|
||||
{'gate_name': name, 'gate_name_latex': latexname})
|
||||
|
||||
def CreateCGate(name, latexname=None):
|
||||
"""Use a lexical closure to make a controlled gate.
|
||||
"""
|
||||
if not latexname:
|
||||
latexname = name
|
||||
onequbitgate = CreateOneQubitGate(name, latexname)
|
||||
def ControlledGate(ctrls,target):
|
||||
return CGate(tuple(ctrls),onequbitgate(target))
|
||||
return ControlledGate
|
||||
@@ -0,0 +1,488 @@
|
||||
"""Primitive circuit operations on quantum circuits."""
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.utilities import numbered_symbols
|
||||
from sympy.physics.quantum.gate import Gate
|
||||
|
||||
__all__ = [
|
||||
'kmp_table',
|
||||
'find_subcircuit',
|
||||
'replace_subcircuit',
|
||||
'convert_to_symbolic_indices',
|
||||
'convert_to_real_indices',
|
||||
'random_reduce',
|
||||
'random_insert'
|
||||
]
|
||||
|
||||
|
||||
def kmp_table(word):
|
||||
"""Build the 'partial match' table of the Knuth-Morris-Pratt algorithm.
|
||||
|
||||
Note: This is applicable to strings or
|
||||
quantum circuits represented as tuples.
|
||||
"""
|
||||
|
||||
# Current position in subcircuit
|
||||
pos = 2
|
||||
# Beginning position of candidate substring that
|
||||
# may reappear later in word
|
||||
cnd = 0
|
||||
# The 'partial match' table that helps one determine
|
||||
# the next location to start substring search
|
||||
table = []
|
||||
table.append(-1)
|
||||
table.append(0)
|
||||
|
||||
while pos < len(word):
|
||||
if word[pos - 1] == word[cnd]:
|
||||
cnd = cnd + 1
|
||||
table.append(cnd)
|
||||
pos = pos + 1
|
||||
elif cnd > 0:
|
||||
cnd = table[cnd]
|
||||
else:
|
||||
table.append(0)
|
||||
pos = pos + 1
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def find_subcircuit(circuit, subcircuit, start=0, end=0):
|
||||
"""Finds the subcircuit in circuit, if it exists.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
If the subcircuit exists, the index of the start of
|
||||
the subcircuit in circuit is returned; otherwise,
|
||||
-1 is returned. The algorithm that is implemented
|
||||
is the Knuth-Morris-Pratt algorithm.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
circuit : tuple, Gate or Mul
|
||||
A tuple of Gates or Mul representing a quantum circuit
|
||||
subcircuit : tuple, Gate or Mul
|
||||
A tuple of Gates or Mul to find in circuit
|
||||
start : int
|
||||
The location to start looking for subcircuit.
|
||||
If start is the same or past end, -1 is returned.
|
||||
end : int
|
||||
The last place to look for a subcircuit. If end
|
||||
is less than 1 (one), then the length of circuit
|
||||
is taken to be end.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Find the first instance of a subcircuit:
|
||||
|
||||
>>> from sympy.physics.quantum.circuitutils import find_subcircuit
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z, H
|
||||
>>> circuit = X(0)*Z(0)*Y(0)*H(0)
|
||||
>>> subcircuit = Z(0)*Y(0)
|
||||
>>> find_subcircuit(circuit, subcircuit)
|
||||
1
|
||||
|
||||
Find the first instance starting at a specific position:
|
||||
|
||||
>>> find_subcircuit(circuit, subcircuit, start=1)
|
||||
1
|
||||
|
||||
>>> find_subcircuit(circuit, subcircuit, start=2)
|
||||
-1
|
||||
|
||||
>>> circuit = circuit*subcircuit
|
||||
>>> find_subcircuit(circuit, subcircuit, start=2)
|
||||
4
|
||||
|
||||
Find the subcircuit within some interval:
|
||||
|
||||
>>> find_subcircuit(circuit, subcircuit, start=2, end=2)
|
||||
-1
|
||||
"""
|
||||
|
||||
if isinstance(circuit, Mul):
|
||||
circuit = circuit.args
|
||||
|
||||
if isinstance(subcircuit, Mul):
|
||||
subcircuit = subcircuit.args
|
||||
|
||||
if len(subcircuit) == 0 or len(subcircuit) > len(circuit):
|
||||
return -1
|
||||
|
||||
if end < 1:
|
||||
end = len(circuit)
|
||||
|
||||
# Location in circuit
|
||||
pos = start
|
||||
# Location in the subcircuit
|
||||
index = 0
|
||||
# 'Partial match' table
|
||||
table = kmp_table(subcircuit)
|
||||
|
||||
while (pos + index) < end:
|
||||
if subcircuit[index] == circuit[pos + index]:
|
||||
index = index + 1
|
||||
else:
|
||||
pos = pos + index - table[index]
|
||||
index = table[index] if table[index] > -1 else 0
|
||||
|
||||
if index == len(subcircuit):
|
||||
return pos
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
def replace_subcircuit(circuit, subcircuit, replace=None, pos=0):
|
||||
"""Replaces a subcircuit with another subcircuit in circuit,
|
||||
if it exists.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
If multiple instances of subcircuit exists, the first instance is
|
||||
replaced. The position to being searching from (if different from
|
||||
0) may be optionally given. If subcircuit cannot be found, circuit
|
||||
is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
circuit : tuple, Gate or Mul
|
||||
A quantum circuit.
|
||||
subcircuit : tuple, Gate or Mul
|
||||
The circuit to be replaced.
|
||||
replace : tuple, Gate or Mul
|
||||
The replacement circuit.
|
||||
pos : int
|
||||
The location to start search and replace
|
||||
subcircuit, if it exists. This may be used
|
||||
if it is known beforehand that multiple
|
||||
instances exist, and it is desirable to
|
||||
replace a specific instance. If a negative number
|
||||
is given, pos will be defaulted to 0.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Find and remove the subcircuit:
|
||||
|
||||
>>> from sympy.physics.quantum.circuitutils import replace_subcircuit
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z, H
|
||||
>>> circuit = X(0)*Z(0)*Y(0)*H(0)*X(0)*H(0)*Y(0)
|
||||
>>> subcircuit = Z(0)*Y(0)
|
||||
>>> replace_subcircuit(circuit, subcircuit)
|
||||
(X(0), H(0), X(0), H(0), Y(0))
|
||||
|
||||
Remove the subcircuit given a starting search point:
|
||||
|
||||
>>> replace_subcircuit(circuit, subcircuit, pos=1)
|
||||
(X(0), H(0), X(0), H(0), Y(0))
|
||||
|
||||
>>> replace_subcircuit(circuit, subcircuit, pos=2)
|
||||
(X(0), Z(0), Y(0), H(0), X(0), H(0), Y(0))
|
||||
|
||||
Replace the subcircuit:
|
||||
|
||||
>>> replacement = H(0)*Z(0)
|
||||
>>> replace_subcircuit(circuit, subcircuit, replace=replacement)
|
||||
(X(0), H(0), Z(0), H(0), X(0), H(0), Y(0))
|
||||
"""
|
||||
|
||||
if pos < 0:
|
||||
pos = 0
|
||||
|
||||
if isinstance(circuit, Mul):
|
||||
circuit = circuit.args
|
||||
|
||||
if isinstance(subcircuit, Mul):
|
||||
subcircuit = subcircuit.args
|
||||
|
||||
if isinstance(replace, Mul):
|
||||
replace = replace.args
|
||||
elif replace is None:
|
||||
replace = ()
|
||||
|
||||
# Look for the subcircuit starting at pos
|
||||
loc = find_subcircuit(circuit, subcircuit, start=pos)
|
||||
|
||||
# If subcircuit was found
|
||||
if loc > -1:
|
||||
# Get the gates to the left of subcircuit
|
||||
left = circuit[0:loc]
|
||||
# Get the gates to the right of subcircuit
|
||||
right = circuit[loc + len(subcircuit):len(circuit)]
|
||||
# Recombine the left and right side gates into a circuit
|
||||
circuit = left + replace + right
|
||||
|
||||
return circuit
|
||||
|
||||
|
||||
def _sympify_qubit_map(mapping):
|
||||
new_map = {}
|
||||
for key in mapping:
|
||||
new_map[key] = sympify(mapping[key])
|
||||
return new_map
|
||||
|
||||
|
||||
def convert_to_symbolic_indices(seq, start=None, gen=None, qubit_map=None):
|
||||
"""Returns the circuit with symbolic indices and the
|
||||
dictionary mapping symbolic indices to real indices.
|
||||
|
||||
The mapping is 1 to 1 and onto (bijective).
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
seq : tuple, Gate/Integer/tuple or Mul
|
||||
A tuple of Gate, Integer, or tuple objects, or a Mul
|
||||
start : Symbol
|
||||
An optional starting symbolic index
|
||||
gen : object
|
||||
An optional numbered symbol generator
|
||||
qubit_map : dict
|
||||
An existing mapping of symbolic indices to real indices
|
||||
|
||||
All symbolic indices have the format 'i#', where # is
|
||||
some number >= 0.
|
||||
"""
|
||||
|
||||
if isinstance(seq, Mul):
|
||||
seq = seq.args
|
||||
|
||||
# A numbered symbol generator
|
||||
index_gen = numbered_symbols(prefix='i', start=-1)
|
||||
cur_ndx = next(index_gen)
|
||||
|
||||
# keys are symbolic indices; values are real indices
|
||||
ndx_map = {}
|
||||
|
||||
def create_inverse_map(symb_to_real_map):
|
||||
rev_items = lambda item: (item[1], item[0])
|
||||
return dict(map(rev_items, symb_to_real_map.items()))
|
||||
|
||||
if start is not None:
|
||||
if not isinstance(start, Symbol):
|
||||
msg = 'Expected Symbol for starting index, got %r.' % start
|
||||
raise TypeError(msg)
|
||||
cur_ndx = start
|
||||
|
||||
if gen is not None:
|
||||
if not isinstance(gen, numbered_symbols().__class__):
|
||||
msg = 'Expected a generator, got %r.' % gen
|
||||
raise TypeError(msg)
|
||||
index_gen = gen
|
||||
|
||||
if qubit_map is not None:
|
||||
if not isinstance(qubit_map, dict):
|
||||
msg = ('Expected dict for existing map, got ' +
|
||||
'%r.' % qubit_map)
|
||||
raise TypeError(msg)
|
||||
ndx_map = qubit_map
|
||||
|
||||
ndx_map = _sympify_qubit_map(ndx_map)
|
||||
# keys are real indices; keys are symbolic indices
|
||||
inv_map = create_inverse_map(ndx_map)
|
||||
|
||||
sym_seq = ()
|
||||
for item in seq:
|
||||
# Nested items, so recurse
|
||||
if isinstance(item, Gate):
|
||||
result = convert_to_symbolic_indices(item.args,
|
||||
qubit_map=ndx_map,
|
||||
start=cur_ndx,
|
||||
gen=index_gen)
|
||||
sym_item, new_map, cur_ndx, index_gen = result
|
||||
ndx_map.update(new_map)
|
||||
inv_map = create_inverse_map(ndx_map)
|
||||
|
||||
elif isinstance(item, (tuple, Tuple)):
|
||||
result = convert_to_symbolic_indices(item,
|
||||
qubit_map=ndx_map,
|
||||
start=cur_ndx,
|
||||
gen=index_gen)
|
||||
sym_item, new_map, cur_ndx, index_gen = result
|
||||
ndx_map.update(new_map)
|
||||
inv_map = create_inverse_map(ndx_map)
|
||||
|
||||
elif item in inv_map:
|
||||
sym_item = inv_map[item]
|
||||
|
||||
else:
|
||||
cur_ndx = next(gen)
|
||||
ndx_map[cur_ndx] = item
|
||||
inv_map[item] = cur_ndx
|
||||
sym_item = cur_ndx
|
||||
|
||||
if isinstance(item, Gate):
|
||||
sym_item = item.__class__(*sym_item)
|
||||
|
||||
sym_seq = sym_seq + (sym_item,)
|
||||
|
||||
return sym_seq, ndx_map, cur_ndx, index_gen
|
||||
|
||||
|
||||
def convert_to_real_indices(seq, qubit_map):
|
||||
"""Returns the circuit with real indices.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
seq : tuple, Gate/Integer/tuple or Mul
|
||||
A tuple of Gate, Integer, or tuple objects or a Mul
|
||||
qubit_map : dict
|
||||
A dictionary mapping symbolic indices to real indices.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Change the symbolic indices to real integers:
|
||||
|
||||
>>> from sympy import symbols
|
||||
>>> from sympy.physics.quantum.circuitutils import convert_to_real_indices
|
||||
>>> from sympy.physics.quantum.gate import X, Y, H
|
||||
>>> i0, i1 = symbols('i:2')
|
||||
>>> index_map = {i0 : 0, i1 : 1}
|
||||
>>> convert_to_real_indices(X(i0)*Y(i1)*H(i0)*X(i1), index_map)
|
||||
(X(0), Y(1), H(0), X(1))
|
||||
"""
|
||||
|
||||
if isinstance(seq, Mul):
|
||||
seq = seq.args
|
||||
|
||||
if not isinstance(qubit_map, dict):
|
||||
msg = 'Expected dict for qubit_map, got %r.' % qubit_map
|
||||
raise TypeError(msg)
|
||||
|
||||
qubit_map = _sympify_qubit_map(qubit_map)
|
||||
real_seq = ()
|
||||
for item in seq:
|
||||
# Nested items, so recurse
|
||||
if isinstance(item, Gate):
|
||||
real_item = convert_to_real_indices(item.args, qubit_map)
|
||||
|
||||
elif isinstance(item, (tuple, Tuple)):
|
||||
real_item = convert_to_real_indices(item, qubit_map)
|
||||
|
||||
else:
|
||||
real_item = qubit_map[item]
|
||||
|
||||
if isinstance(item, Gate):
|
||||
real_item = item.__class__(*real_item)
|
||||
|
||||
real_seq = real_seq + (real_item,)
|
||||
|
||||
return real_seq
|
||||
|
||||
|
||||
def random_reduce(circuit, gate_ids, seed=None):
|
||||
"""Shorten the length of a quantum circuit.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
random_reduce looks for circuit identities in circuit, randomly chooses
|
||||
one to remove, and returns a shorter yet equivalent circuit. If no
|
||||
identities are found, the same circuit is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
circuit : Gate tuple of Mul
|
||||
A tuple of Gates representing a quantum circuit
|
||||
gate_ids : list, GateIdentity
|
||||
List of gate identities to find in circuit
|
||||
seed : int or list
|
||||
seed used for _randrange; to override the random selection, provide a
|
||||
list of integers: the elements of gate_ids will be tested in the order
|
||||
given by the list
|
||||
|
||||
"""
|
||||
from sympy.core.random import _randrange
|
||||
|
||||
if not gate_ids:
|
||||
return circuit
|
||||
|
||||
if isinstance(circuit, Mul):
|
||||
circuit = circuit.args
|
||||
|
||||
ids = flatten_ids(gate_ids)
|
||||
|
||||
# Create the random integer generator with the seed
|
||||
randrange = _randrange(seed)
|
||||
|
||||
# Look for an identity in the circuit
|
||||
while ids:
|
||||
i = randrange(len(ids))
|
||||
id = ids.pop(i)
|
||||
if find_subcircuit(circuit, id) != -1:
|
||||
break
|
||||
else:
|
||||
# no identity was found
|
||||
return circuit
|
||||
|
||||
# return circuit with the identity removed
|
||||
return replace_subcircuit(circuit, id)
|
||||
|
||||
|
||||
def random_insert(circuit, choices, seed=None):
|
||||
"""Insert a circuit into another quantum circuit.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
random_insert randomly chooses a location in the circuit to insert
|
||||
a randomly selected circuit from amongst the given choices.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
circuit : Gate tuple or Mul
|
||||
A tuple or Mul of Gates representing a quantum circuit
|
||||
choices : list
|
||||
Set of circuit choices
|
||||
seed : int or list
|
||||
seed used for _randrange; to override the random selections, give
|
||||
a list two integers, [i, j] where i is the circuit location where
|
||||
choice[j] will be inserted.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Indices for insertion should be [0, n] if n is the length of the
|
||||
circuit.
|
||||
"""
|
||||
from sympy.core.random import _randrange
|
||||
|
||||
if not choices:
|
||||
return circuit
|
||||
|
||||
if isinstance(circuit, Mul):
|
||||
circuit = circuit.args
|
||||
|
||||
# get the location in the circuit and the element to insert from choices
|
||||
randrange = _randrange(seed)
|
||||
loc = randrange(len(circuit) + 1)
|
||||
choice = choices[randrange(len(choices))]
|
||||
|
||||
circuit = list(circuit)
|
||||
circuit[loc: loc] = choice
|
||||
return tuple(circuit)
|
||||
|
||||
# Flatten the GateIdentity objects (with gate rules) into one single list
|
||||
|
||||
|
||||
def flatten_ids(ids):
|
||||
collapse = lambda acc, an_id: acc + sorted(an_id.equivalent_ids,
|
||||
key=default_sort_key)
|
||||
ids = reduce(collapse, ids, [])
|
||||
ids.sort(key=default_sort_key)
|
||||
return ids
|
||||
@@ -0,0 +1,256 @@
|
||||
"""The commutator: [A,B] = A*B - B*A."""
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.kind import KindDispatcher
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.kind import _OperatorKind, OperatorKind
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Commutator'
|
||||
]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Commutator
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Commutator(Expr):
|
||||
"""The standard commutator, in an unevaluated state.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Evaluating a commutator is defined [1]_ as: ``[A, B] = A*B - B*A``. This
|
||||
class returns the commutator in an unevaluated form. To evaluate the
|
||||
commutator, use the ``.doit()`` method.
|
||||
|
||||
Canonical ordering of a commutator is ``[A, B]`` for ``A < B``. The
|
||||
arguments of the commutator are put into canonical order using ``__cmp__``.
|
||||
If ``B < A``, then ``[B, A]`` is returned as ``-[A, B]``.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
A : Expr
|
||||
The first argument of the commutator [A,B].
|
||||
B : Expr
|
||||
The second argument of the commutator [A,B].
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import Commutator, Dagger, Operator
|
||||
>>> from sympy.abc import x, y
|
||||
>>> A = Operator('A')
|
||||
>>> B = Operator('B')
|
||||
>>> C = Operator('C')
|
||||
|
||||
Create a commutator and use ``.doit()`` to evaluate it:
|
||||
|
||||
>>> comm = Commutator(A, B)
|
||||
>>> comm
|
||||
[A,B]
|
||||
>>> comm.doit()
|
||||
A*B - B*A
|
||||
|
||||
The commutator orders it arguments in canonical order:
|
||||
|
||||
>>> comm = Commutator(B, A); comm
|
||||
-[A,B]
|
||||
|
||||
Commutative constants are factored out:
|
||||
|
||||
>>> Commutator(3*x*A, x*y*B)
|
||||
3*x**2*y*[A,B]
|
||||
|
||||
Using ``.expand(commutator=True)``, the standard commutator expansion rules
|
||||
can be applied:
|
||||
|
||||
>>> Commutator(A+B, C).expand(commutator=True)
|
||||
[A,C] + [B,C]
|
||||
>>> Commutator(A, B+C).expand(commutator=True)
|
||||
[A,B] + [A,C]
|
||||
>>> Commutator(A*B, C).expand(commutator=True)
|
||||
[A,C]*B + A*[B,C]
|
||||
>>> Commutator(A, B*C).expand(commutator=True)
|
||||
[A,B]*C + B*[A,C]
|
||||
|
||||
Adjoint operations applied to the commutator are properly applied to the
|
||||
arguments:
|
||||
|
||||
>>> Dagger(Commutator(A, B))
|
||||
-[Dagger(A),Dagger(B)]
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Commutator
|
||||
"""
|
||||
is_commutative = False
|
||||
|
||||
_kind_dispatcher = KindDispatcher("Commutator_kind_dispatcher", commutative=True)
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
arg_kinds = (a.kind for a in self.args)
|
||||
return self._kind_dispatcher(*arg_kinds)
|
||||
|
||||
def __new__(cls, A, B):
|
||||
r = cls.eval(A, B)
|
||||
if r is not None:
|
||||
return r
|
||||
obj = Expr.__new__(cls, A, B)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def eval(cls, a, b):
|
||||
if not (a and b):
|
||||
return S.Zero
|
||||
if a == b:
|
||||
return S.Zero
|
||||
if a.is_commutative or b.is_commutative:
|
||||
return S.Zero
|
||||
|
||||
# [xA,yB] -> xy*[A,B]
|
||||
ca, nca = a.args_cnc()
|
||||
cb, ncb = b.args_cnc()
|
||||
c_part = ca + cb
|
||||
if c_part:
|
||||
return Mul(Mul(*c_part), cls(Mul._from_args(nca), Mul._from_args(ncb)))
|
||||
|
||||
# Canonical ordering of arguments
|
||||
# The Commutator [A, B] is in canonical form if A < B.
|
||||
if a.compare(b) == 1:
|
||||
return S.NegativeOne*cls(b, a)
|
||||
|
||||
def _expand_pow(self, A, B, sign):
|
||||
exp = A.exp
|
||||
if not exp.is_integer or not exp.is_constant() or abs(exp) <= 1:
|
||||
# nothing to do
|
||||
return self
|
||||
base = A.base
|
||||
if exp.is_negative:
|
||||
base = A.base**-1
|
||||
exp = -exp
|
||||
comm = Commutator(base, B).expand(commutator=True)
|
||||
|
||||
result = base**(exp - 1) * comm
|
||||
for i in range(1, exp):
|
||||
result += base**(exp - 1 - i) * comm * base**i
|
||||
return sign*result.expand()
|
||||
|
||||
def _eval_expand_commutator(self, **hints):
|
||||
A = self.args[0]
|
||||
B = self.args[1]
|
||||
|
||||
if isinstance(A, Add):
|
||||
# [A + B, C] -> [A, C] + [B, C]
|
||||
sargs = []
|
||||
for term in A.args:
|
||||
comm = Commutator(term, B)
|
||||
if isinstance(comm, Commutator):
|
||||
comm = comm._eval_expand_commutator()
|
||||
sargs.append(comm)
|
||||
return Add(*sargs)
|
||||
elif isinstance(B, Add):
|
||||
# [A, B + C] -> [A, B] + [A, C]
|
||||
sargs = []
|
||||
for term in B.args:
|
||||
comm = Commutator(A, term)
|
||||
if isinstance(comm, Commutator):
|
||||
comm = comm._eval_expand_commutator()
|
||||
sargs.append(comm)
|
||||
return Add(*sargs)
|
||||
elif isinstance(A, Mul):
|
||||
# [A*B, C] -> A*[B, C] + [A, C]*B
|
||||
a = A.args[0]
|
||||
b = Mul(*A.args[1:])
|
||||
c = B
|
||||
comm1 = Commutator(b, c)
|
||||
comm2 = Commutator(a, c)
|
||||
if isinstance(comm1, Commutator):
|
||||
comm1 = comm1._eval_expand_commutator()
|
||||
if isinstance(comm2, Commutator):
|
||||
comm2 = comm2._eval_expand_commutator()
|
||||
first = Mul(a, comm1)
|
||||
second = Mul(comm2, b)
|
||||
return Add(first, second)
|
||||
elif isinstance(B, Mul):
|
||||
# [A, B*C] -> [A, B]*C + B*[A, C]
|
||||
a = A
|
||||
b = B.args[0]
|
||||
c = Mul(*B.args[1:])
|
||||
comm1 = Commutator(a, b)
|
||||
comm2 = Commutator(a, c)
|
||||
if isinstance(comm1, Commutator):
|
||||
comm1 = comm1._eval_expand_commutator()
|
||||
if isinstance(comm2, Commutator):
|
||||
comm2 = comm2._eval_expand_commutator()
|
||||
first = Mul(comm1, c)
|
||||
second = Mul(b, comm2)
|
||||
return Add(first, second)
|
||||
elif isinstance(A, Pow):
|
||||
# [A**n, C] -> A**(n - 1)*[A, C] + A**(n - 2)*[A, C]*A + ... + [A, C]*A**(n-1)
|
||||
return self._expand_pow(A, B, 1)
|
||||
elif isinstance(B, Pow):
|
||||
# [A, C**n] -> C**(n - 1)*[C, A] + C**(n - 2)*[C, A]*C + ... + [C, A]*C**(n-1)
|
||||
return self._expand_pow(B, A, -1)
|
||||
|
||||
# No changes, so return self
|
||||
return self
|
||||
|
||||
def doit(self, **hints):
|
||||
""" Evaluate commutator """
|
||||
# Keep the import of Operator here to avoid problems with
|
||||
# circular imports.
|
||||
from sympy.physics.quantum.operator import Operator
|
||||
A = self.args[0]
|
||||
B = self.args[1]
|
||||
if isinstance(A, Operator) and isinstance(B, Operator):
|
||||
try:
|
||||
comm = A._eval_commutator(B, **hints)
|
||||
except NotImplementedError:
|
||||
try:
|
||||
comm = -1*B._eval_commutator(A, **hints)
|
||||
except NotImplementedError:
|
||||
comm = None
|
||||
if comm is not None:
|
||||
return comm.doit(**hints)
|
||||
return (A*B - B*A).doit(**hints)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return Commutator(Dagger(self.args[1]), Dagger(self.args[0]))
|
||||
|
||||
def _sympyrepr(self, printer, *args):
|
||||
return "%s(%s,%s)" % (
|
||||
self.__class__.__name__, printer._print(
|
||||
self.args[0]), printer._print(self.args[1])
|
||||
)
|
||||
|
||||
def _sympystr(self, printer, *args):
|
||||
return "[%s,%s]" % (
|
||||
printer._print(self.args[0]), printer._print(self.args[1]))
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
pform = printer._print(self.args[0], *args)
|
||||
pform = prettyForm(*pform.right(prettyForm(',')))
|
||||
pform = prettyForm(*pform.right(printer._print(self.args[1], *args)))
|
||||
pform = prettyForm(*pform.parens(left='[', right=']'))
|
||||
return pform
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
return "\\left[%s,%s\\right]" % tuple([
|
||||
printer._print(arg, *args) for arg in self.args])
|
||||
|
||||
|
||||
@Commutator._kind_dispatcher.register(_OperatorKind, _OperatorKind)
|
||||
def find_op_kind(e1, e2):
|
||||
"""Find the kind of an anticommutator of two OperatorKinds."""
|
||||
return OperatorKind
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Constants (like hbar) related to quantum mechanics."""
|
||||
|
||||
from sympy.core.numbers import NumberSymbol
|
||||
from sympy.core.singleton import Singleton
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
import mpmath.libmp as mlib
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Constants
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
__all__ = [
|
||||
'hbar',
|
||||
'HBar',
|
||||
]
|
||||
|
||||
|
||||
class HBar(NumberSymbol, metaclass=Singleton):
|
||||
"""Reduced Plank's constant in numerical and symbolic form [1]_.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.constants import hbar
|
||||
>>> hbar.evalf()
|
||||
1.05457162000000e-34
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Planck_constant
|
||||
"""
|
||||
|
||||
is_real = True
|
||||
is_positive = True
|
||||
is_negative = False
|
||||
is_irrational = True
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def _as_mpf_val(self, prec):
|
||||
return mlib.from_float(1.05457162e-34, prec)
|
||||
|
||||
def _sympyrepr(self, printer, *args):
|
||||
return 'HBar()'
|
||||
|
||||
def _sympystr(self, printer, *args):
|
||||
return 'hbar'
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
if printer._use_unicode:
|
||||
return prettyForm('\N{PLANCK CONSTANT OVER TWO PI}')
|
||||
return prettyForm('hbar')
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
return r'\hbar'
|
||||
|
||||
# Create an instance for everyone to use.
|
||||
hbar = HBar()
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Hermitian conjugation."""
|
||||
|
||||
from sympy.core import Expr, sympify
|
||||
from sympy.functions.elementary.complexes import adjoint
|
||||
|
||||
__all__ = [
|
||||
'Dagger'
|
||||
]
|
||||
|
||||
|
||||
class Dagger(adjoint):
|
||||
"""General Hermitian conjugate operation.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
Take the Hermetian conjugate of an argument [1]_. For matrices this
|
||||
operation is equivalent to transpose and complex conjugate [2]_.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
arg : Expr
|
||||
The SymPy expression that we want to take the dagger of.
|
||||
evaluate : bool
|
||||
Whether the resulting expression should be directly evaluated.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Daggering various quantum objects:
|
||||
|
||||
>>> from sympy.physics.quantum.dagger import Dagger
|
||||
>>> from sympy.physics.quantum.state import Ket, Bra
|
||||
>>> from sympy.physics.quantum.operator import Operator
|
||||
>>> Dagger(Ket('psi'))
|
||||
<psi|
|
||||
>>> Dagger(Bra('phi'))
|
||||
|phi>
|
||||
>>> Dagger(Operator('A'))
|
||||
Dagger(A)
|
||||
|
||||
Inner and outer products::
|
||||
|
||||
>>> from sympy.physics.quantum import InnerProduct, OuterProduct
|
||||
>>> Dagger(InnerProduct(Bra('a'), Ket('b')))
|
||||
<b|a>
|
||||
>>> Dagger(OuterProduct(Ket('a'), Bra('b')))
|
||||
|b><a|
|
||||
|
||||
Powers, sums and products::
|
||||
|
||||
>>> A = Operator('A')
|
||||
>>> B = Operator('B')
|
||||
>>> Dagger(A*B)
|
||||
Dagger(B)*Dagger(A)
|
||||
>>> Dagger(A+B)
|
||||
Dagger(A) + Dagger(B)
|
||||
>>> Dagger(A**2)
|
||||
Dagger(A)**2
|
||||
|
||||
Dagger also seamlessly handles complex numbers and matrices::
|
||||
|
||||
>>> from sympy import Matrix, I
|
||||
>>> m = Matrix([[1,I],[2,I]])
|
||||
>>> m
|
||||
Matrix([
|
||||
[1, I],
|
||||
[2, I]])
|
||||
>>> Dagger(m)
|
||||
Matrix([
|
||||
[ 1, 2],
|
||||
[-I, -I]])
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Hermitian_adjoint
|
||||
.. [2] https://en.wikipedia.org/wiki/Hermitian_transpose
|
||||
"""
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
"""Find the kind of a dagger of something (just the kind of the something)."""
|
||||
return self.args[0].kind
|
||||
|
||||
def __new__(cls, arg, evaluate=True):
|
||||
if hasattr(arg, 'adjoint') and evaluate:
|
||||
return arg.adjoint()
|
||||
elif hasattr(arg, 'conjugate') and hasattr(arg, 'transpose') and evaluate:
|
||||
return arg.conjugate().transpose()
|
||||
return Expr.__new__(cls, sympify(arg))
|
||||
|
||||
adjoint.__name__ = "Dagger"
|
||||
adjoint._sympyrepr = lambda a, b: "Dagger(%s)" % b._print(a.args[0])
|
||||
@@ -0,0 +1,315 @@
|
||||
from itertools import product
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.function import expand
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.exponential import log
|
||||
from sympy.matrices.dense import MutableDenseMatrix as Matrix
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.operator import HermitianOperator
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.physics.quantum.matrixutils import numpy_ndarray, scipy_sparse_matrix, to_numpy
|
||||
from sympy.physics.quantum.trace import Tr
|
||||
|
||||
|
||||
class Density(HermitianOperator):
|
||||
"""Density operator for representing mixed states.
|
||||
|
||||
TODO: Density operator support for Qubits
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
values : tuples/lists
|
||||
Each tuple/list should be of form (state, prob) or [state,prob]
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a density operator with 2 states represented by Kets.
|
||||
|
||||
>>> from sympy.physics.quantum.state import Ket
|
||||
>>> from sympy.physics.quantum.density import Density
|
||||
>>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
|
||||
>>> d
|
||||
Density((|0>, 0.5),(|1>, 0.5))
|
||||
|
||||
"""
|
||||
@classmethod
|
||||
def _eval_args(cls, args):
|
||||
# call this to qsympify the args
|
||||
args = super()._eval_args(args)
|
||||
|
||||
for arg in args:
|
||||
# Check if arg is a tuple
|
||||
if not (isinstance(arg, Tuple) and len(arg) == 2):
|
||||
raise ValueError("Each argument should be of form [state,prob]"
|
||||
" or ( state, prob )")
|
||||
|
||||
return args
|
||||
|
||||
def states(self):
|
||||
"""Return list of all states.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Ket
|
||||
>>> from sympy.physics.quantum.density import Density
|
||||
>>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
|
||||
>>> d.states()
|
||||
(|0>, |1>)
|
||||
|
||||
"""
|
||||
return Tuple(*[arg[0] for arg in self.args])
|
||||
|
||||
def probs(self):
|
||||
"""Return list of all probabilities.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Ket
|
||||
>>> from sympy.physics.quantum.density import Density
|
||||
>>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
|
||||
>>> d.probs()
|
||||
(0.5, 0.5)
|
||||
|
||||
"""
|
||||
return Tuple(*[arg[1] for arg in self.args])
|
||||
|
||||
def get_state(self, index):
|
||||
"""Return specific state by index.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
index : index of state to be returned
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Ket
|
||||
>>> from sympy.physics.quantum.density import Density
|
||||
>>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
|
||||
>>> d.states()[1]
|
||||
|1>
|
||||
|
||||
"""
|
||||
state = self.args[index][0]
|
||||
return state
|
||||
|
||||
def get_prob(self, index):
|
||||
"""Return probability of specific state by index.
|
||||
|
||||
Parameters
|
||||
===========
|
||||
|
||||
index : index of states whose probability is returned.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Ket
|
||||
>>> from sympy.physics.quantum.density import Density
|
||||
>>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
|
||||
>>> d.probs()[1]
|
||||
0.500000000000000
|
||||
|
||||
"""
|
||||
prob = self.args[index][1]
|
||||
return prob
|
||||
|
||||
def apply_op(self, op):
|
||||
"""op will operate on each individual state.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
op : Operator
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Ket
|
||||
>>> from sympy.physics.quantum.density import Density
|
||||
>>> from sympy.physics.quantum.operator import Operator
|
||||
>>> A = Operator('A')
|
||||
>>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
|
||||
>>> d.apply_op(A)
|
||||
Density((A*|0>, 0.5),(A*|1>, 0.5))
|
||||
|
||||
"""
|
||||
new_args = [(op*state, prob) for (state, prob) in self.args]
|
||||
return Density(*new_args)
|
||||
|
||||
def doit(self, **hints):
|
||||
"""Expand the density operator into an outer product format.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Ket
|
||||
>>> from sympy.physics.quantum.density import Density
|
||||
>>> from sympy.physics.quantum.operator import Operator
|
||||
>>> A = Operator('A')
|
||||
>>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
|
||||
>>> d.doit()
|
||||
0.5*|0><0| + 0.5*|1><1|
|
||||
|
||||
"""
|
||||
|
||||
terms = []
|
||||
for (state, prob) in self.args:
|
||||
state = state.expand() # needed to break up (a+b)*c
|
||||
if (isinstance(state, Add)):
|
||||
for arg in product(state.args, repeat=2):
|
||||
terms.append(prob*self._generate_outer_prod(arg[0],
|
||||
arg[1]))
|
||||
else:
|
||||
terms.append(prob*self._generate_outer_prod(state, state))
|
||||
|
||||
return Add(*terms)
|
||||
|
||||
def _generate_outer_prod(self, arg1, arg2):
|
||||
c_part1, nc_part1 = arg1.args_cnc()
|
||||
c_part2, nc_part2 = arg2.args_cnc()
|
||||
|
||||
if (len(nc_part1) == 0 or len(nc_part2) == 0):
|
||||
raise ValueError('Atleast one-pair of'
|
||||
' Non-commutative instance required'
|
||||
' for outer product.')
|
||||
|
||||
# We were able to remove some tensor product simplifications that
|
||||
# used to be here as those transformations are not automatically
|
||||
# applied by transforms.py.
|
||||
op = Mul(*nc_part1)*Dagger(Mul(*nc_part2))
|
||||
|
||||
return Mul(*c_part1)*Mul(*c_part2) * op
|
||||
|
||||
def _represent(self, **options):
|
||||
return represent(self.doit(), **options)
|
||||
|
||||
def _print_operator_name_latex(self, printer, *args):
|
||||
return r'\rho'
|
||||
|
||||
def _print_operator_name_pretty(self, printer, *args):
|
||||
return prettyForm('\N{GREEK SMALL LETTER RHO}')
|
||||
|
||||
def _eval_trace(self, **kwargs):
|
||||
indices = kwargs.get('indices', [])
|
||||
return Tr(self.doit(), indices).doit()
|
||||
|
||||
def entropy(self):
|
||||
""" Compute the entropy of a density matrix.
|
||||
|
||||
Refer to density.entropy() method for examples.
|
||||
"""
|
||||
return entropy(self)
|
||||
|
||||
|
||||
def entropy(density):
|
||||
"""Compute the entropy of a matrix/density object.
|
||||
|
||||
This computes -Tr(density*ln(density)) using the eigenvalue decomposition
|
||||
of density, which is given as either a Density instance or a matrix
|
||||
(numpy.ndarray, sympy.Matrix or scipy.sparse).
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
density : density matrix of type Density, SymPy matrix,
|
||||
scipy.sparse or numpy.ndarray
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.density import Density, entropy
|
||||
>>> from sympy.physics.quantum.spin import JzKet
|
||||
>>> from sympy import S
|
||||
>>> up = JzKet(S(1)/2,S(1)/2)
|
||||
>>> down = JzKet(S(1)/2,-S(1)/2)
|
||||
>>> d = Density((up,S(1)/2),(down,S(1)/2))
|
||||
>>> entropy(d)
|
||||
log(2)/2
|
||||
|
||||
"""
|
||||
if isinstance(density, Density):
|
||||
density = represent(density) # represent in Matrix
|
||||
|
||||
if isinstance(density, scipy_sparse_matrix):
|
||||
density = to_numpy(density)
|
||||
|
||||
if isinstance(density, Matrix):
|
||||
eigvals = density.eigenvals().keys()
|
||||
return expand(-sum(e*log(e) for e in eigvals))
|
||||
elif isinstance(density, numpy_ndarray):
|
||||
import numpy as np
|
||||
eigvals = np.linalg.eigvals(density)
|
||||
return -np.sum(eigvals*np.log(eigvals))
|
||||
else:
|
||||
raise ValueError(
|
||||
"numpy.ndarray, scipy.sparse or SymPy matrix expected")
|
||||
|
||||
|
||||
def fidelity(state1, state2):
|
||||
""" Computes the fidelity [1]_ between two quantum states
|
||||
|
||||
The arguments provided to this function should be a square matrix or a
|
||||
Density object. If it is a square matrix, it is assumed to be diagonalizable.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
state1, state2 : a density matrix or Matrix
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import S, sqrt
|
||||
>>> from sympy.physics.quantum.dagger import Dagger
|
||||
>>> from sympy.physics.quantum.spin import JzKet
|
||||
>>> from sympy.physics.quantum.density import fidelity
|
||||
>>> from sympy.physics.quantum.represent import represent
|
||||
>>>
|
||||
>>> up = JzKet(S(1)/2,S(1)/2)
|
||||
>>> down = JzKet(S(1)/2,-S(1)/2)
|
||||
>>> amp = 1/sqrt(2)
|
||||
>>> updown = (amp*up) + (amp*down)
|
||||
>>>
|
||||
>>> # represent turns Kets into matrices
|
||||
>>> up_dm = represent(up*Dagger(up))
|
||||
>>> down_dm = represent(down*Dagger(down))
|
||||
>>> updown_dm = represent(updown*Dagger(updown))
|
||||
>>>
|
||||
>>> fidelity(up_dm, up_dm)
|
||||
1
|
||||
>>> fidelity(up_dm, down_dm) #orthogonal states
|
||||
0
|
||||
>>> fidelity(up_dm, updown_dm).evalf().round(3)
|
||||
0.707
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Fidelity_of_quantum_states
|
||||
|
||||
"""
|
||||
state1 = represent(state1) if isinstance(state1, Density) else state1
|
||||
state2 = represent(state2) if isinstance(state2, Density) else state2
|
||||
|
||||
if not isinstance(state1, Matrix) or not isinstance(state2, Matrix):
|
||||
raise ValueError("state1 and state2 must be of type Density or Matrix "
|
||||
"received type=%s for state1 and type=%s for state2" %
|
||||
(type(state1), type(state2)))
|
||||
|
||||
if state1.shape != state2.shape and state1.is_square:
|
||||
raise ValueError("The dimensions of both args should be equal and the "
|
||||
"matrix obtained should be a square matrix")
|
||||
|
||||
sqrt_state1 = state1**S.Half
|
||||
return Tr((sqrt_state1*state2*sqrt_state1)**S.Half).doit()
|
||||
@@ -0,0 +1,191 @@
|
||||
"""Fermionic quantum operators."""
|
||||
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.singleton import S
|
||||
from sympy.physics.quantum import Operator
|
||||
from sympy.physics.quantum import HilbertSpace, Ket, Bra
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
|
||||
|
||||
__all__ = [
|
||||
'FermionOp',
|
||||
'FermionFockKet',
|
||||
'FermionFockBra'
|
||||
]
|
||||
|
||||
|
||||
class FermionOp(Operator):
|
||||
"""A fermionic operator that satisfies {c, Dagger(c)} == 1.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
name : str
|
||||
A string that labels the fermionic mode.
|
||||
|
||||
annihilation : bool
|
||||
A bool that indicates if the fermionic operator is an annihilation
|
||||
(True, default value) or creation operator (False)
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import Dagger, AntiCommutator
|
||||
>>> from sympy.physics.quantum.fermion import FermionOp
|
||||
>>> c = FermionOp("c")
|
||||
>>> AntiCommutator(c, Dagger(c)).doit()
|
||||
1
|
||||
"""
|
||||
@property
|
||||
def name(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def is_annihilation(self):
|
||||
return bool(self.args[1])
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("c", True)
|
||||
|
||||
def __new__(cls, *args, **hints):
|
||||
if not len(args) in [1, 2]:
|
||||
raise ValueError('1 or 2 parameters expected, got %s' % args)
|
||||
|
||||
if len(args) == 1:
|
||||
args = (args[0], S.One)
|
||||
|
||||
if len(args) == 2:
|
||||
args = (args[0], Integer(args[1]))
|
||||
|
||||
return Operator.__new__(cls, *args)
|
||||
|
||||
def _eval_commutator_FermionOp(self, other, **hints):
|
||||
if 'independent' in hints and hints['independent']:
|
||||
# [c, d] = 0
|
||||
return S.Zero
|
||||
|
||||
return None
|
||||
|
||||
def _eval_anticommutator_FermionOp(self, other, **hints):
|
||||
if self.name == other.name:
|
||||
# {a^\dagger, a} = 1
|
||||
if not self.is_annihilation and other.is_annihilation:
|
||||
return S.One
|
||||
|
||||
elif 'independent' in hints and hints['independent']:
|
||||
# {c, d} = 2 * c * d, because [c, d] = 0 for independent operators
|
||||
return 2 * self * other
|
||||
|
||||
return None
|
||||
|
||||
def _eval_anticommutator_BosonOp(self, other, **hints):
|
||||
# because fermions and bosons commute
|
||||
return 2 * self * other
|
||||
|
||||
def _eval_commutator_BosonOp(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return FermionOp(str(self.name), not self.is_annihilation)
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
if self.is_annihilation:
|
||||
return r'{%s}' % str(self.name)
|
||||
else:
|
||||
return r'{{%s}^\dagger}' % str(self.name)
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
if self.is_annihilation:
|
||||
return r'%s' % str(self.name)
|
||||
else:
|
||||
return r'Dagger(%s)' % str(self.name)
|
||||
|
||||
def _print_contents_pretty(self, printer, *args):
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
pform = printer._print(self.args[0], *args)
|
||||
if self.is_annihilation:
|
||||
return pform
|
||||
else:
|
||||
return pform**prettyForm('\N{DAGGER}')
|
||||
|
||||
def _eval_power(self, exp):
|
||||
from sympy.core.singleton import S
|
||||
if exp == 0:
|
||||
return S.One
|
||||
elif exp == 1:
|
||||
return self
|
||||
elif (exp > 1) == True and exp.is_integer == True:
|
||||
return S.Zero
|
||||
elif (exp < 0) == True or exp.is_integer == False:
|
||||
raise ValueError("Fermionic operators can only be raised to a"
|
||||
" positive integer power")
|
||||
return Operator._eval_power(self, exp)
|
||||
|
||||
class FermionFockKet(Ket):
|
||||
"""Fock state ket for a fermionic mode.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Number
|
||||
The Fock state number.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, n):
|
||||
if n not in (0, 1):
|
||||
raise ValueError("n must be 0 or 1")
|
||||
return Ket.__new__(cls, n)
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
return self.label[0]
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return FermionFockBra
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return HilbertSpace()
|
||||
|
||||
def _eval_innerproduct_FermionFockBra(self, bra, **hints):
|
||||
return KroneckerDelta(self.n, bra.n)
|
||||
|
||||
def _apply_from_right_to_FermionOp(self, op, **options):
|
||||
if op.is_annihilation:
|
||||
if self.n == 1:
|
||||
return FermionFockKet(0)
|
||||
else:
|
||||
return S.Zero
|
||||
else:
|
||||
if self.n == 0:
|
||||
return FermionFockKet(1)
|
||||
else:
|
||||
return S.Zero
|
||||
|
||||
|
||||
class FermionFockBra(Bra):
|
||||
"""Fock state bra for a fermionic mode.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Number
|
||||
The Fock state number.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, n):
|
||||
if n not in (0, 1):
|
||||
raise ValueError("n must be 0 or 1")
|
||||
return Bra.__new__(cls, n)
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
return self.label[0]
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return FermionFockKet
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,345 @@
|
||||
"""Grover's algorithm and helper functions.
|
||||
|
||||
Todo:
|
||||
|
||||
* W gate construction (or perhaps -W gate based on Mermin's book)
|
||||
* Generalize the algorithm for an unknown function that returns 1 on multiple
|
||||
qubit states, not just one.
|
||||
* Implement _represent_ZGate in OracleGate
|
||||
"""
|
||||
|
||||
from sympy.core.numbers import pi
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.core.basic import Atom
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.matrices.dense import eye
|
||||
from sympy.core.numbers import NegativeOne
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.qexpr import QuantumError
|
||||
from sympy.physics.quantum.hilbert import ComplexSpace
|
||||
from sympy.physics.quantum.operator import UnitaryOperator
|
||||
from sympy.physics.quantum.gate import Gate
|
||||
from sympy.physics.quantum.qubit import IntQubit
|
||||
|
||||
__all__ = [
|
||||
'OracleGate',
|
||||
'WGate',
|
||||
'superposition_basis',
|
||||
'grover_iteration',
|
||||
'apply_grover'
|
||||
]
|
||||
|
||||
|
||||
def superposition_basis(nqubits):
|
||||
"""Creates an equal superposition of the computational basis.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
nqubits : int
|
||||
The number of qubits.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
state : Qubit
|
||||
An equal superposition of the computational basis with nqubits.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create an equal superposition of 2 qubits::
|
||||
|
||||
>>> from sympy.physics.quantum.grover import superposition_basis
|
||||
>>> superposition_basis(2)
|
||||
|0>/2 + |1>/2 + |2>/2 + |3>/2
|
||||
"""
|
||||
|
||||
amp = 1/sqrt(2**nqubits)
|
||||
return sum(amp*IntQubit(n, nqubits=nqubits) for n in range(2**nqubits))
|
||||
|
||||
class OracleGateFunction(Atom):
|
||||
"""Wrapper for python functions used in `OracleGate`s"""
|
||||
|
||||
def __new__(cls, function):
|
||||
if not callable(function):
|
||||
raise TypeError('Callable expected, got: %r' % function)
|
||||
obj = Atom.__new__(cls)
|
||||
obj.function = function
|
||||
return obj
|
||||
|
||||
def _hashable_content(self):
|
||||
return type(self), self.function
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.function(*args)
|
||||
|
||||
|
||||
class OracleGate(Gate):
|
||||
"""A black box gate.
|
||||
|
||||
The gate marks the desired qubits of an unknown function by flipping
|
||||
the sign of the qubits. The unknown function returns true when it
|
||||
finds its desired qubits and false otherwise.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
qubits : int
|
||||
Number of qubits.
|
||||
|
||||
oracle : callable
|
||||
A callable function that returns a boolean on a computational basis.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Apply an Oracle gate that flips the sign of ``|2>`` on different qubits::
|
||||
|
||||
>>> from sympy.physics.quantum.qubit import IntQubit
|
||||
>>> from sympy.physics.quantum.qapply import qapply
|
||||
>>> from sympy.physics.quantum.grover import OracleGate
|
||||
>>> f = lambda qubits: qubits == IntQubit(2)
|
||||
>>> v = OracleGate(2, f)
|
||||
>>> qapply(v*IntQubit(2))
|
||||
-|2>
|
||||
>>> qapply(v*IntQubit(3))
|
||||
|3>
|
||||
"""
|
||||
|
||||
gate_name = 'V'
|
||||
gate_name_latex = 'V'
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Initialization/creation
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def _eval_args(cls, args):
|
||||
if len(args) != 2:
|
||||
raise QuantumError(
|
||||
'Insufficient/excessive arguments to Oracle. Please ' +
|
||||
'supply the number of qubits and an unknown function.'
|
||||
)
|
||||
sub_args = (args[0],)
|
||||
sub_args = UnitaryOperator._eval_args(sub_args)
|
||||
if not sub_args[0].is_Integer:
|
||||
raise TypeError('Integer expected, got: %r' % sub_args[0])
|
||||
|
||||
function = args[1]
|
||||
if not isinstance(function, OracleGateFunction):
|
||||
function = OracleGateFunction(function)
|
||||
|
||||
return (sub_args[0], function)
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, args):
|
||||
"""This returns the smallest possible Hilbert space."""
|
||||
return ComplexSpace(2)**args[0]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Properties
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def search_function(self):
|
||||
"""The unknown function that helps find the sought after qubits."""
|
||||
return self.label[1]
|
||||
|
||||
@property
|
||||
def targets(self):
|
||||
"""A tuple of target qubits."""
|
||||
return sympify(tuple(range(self.args[0])))
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Apply
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def _apply_operator_Qubit(self, qubits, **options):
|
||||
"""Apply this operator to a Qubit subclass.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
qubits : Qubit
|
||||
The qubit subclass to apply this operator to.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
state : Expr
|
||||
The resulting quantum state.
|
||||
"""
|
||||
if qubits.nqubits != self.nqubits:
|
||||
raise QuantumError(
|
||||
'OracleGate operates on %r qubits, got: %r'
|
||||
% (self.nqubits, qubits.nqubits)
|
||||
)
|
||||
# If function returns 1 on qubits
|
||||
# return the negative of the qubits (flip the sign)
|
||||
if self.search_function(qubits):
|
||||
return -qubits
|
||||
else:
|
||||
return qubits
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Represent
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def _represent_ZGate(self, basis, **options):
|
||||
"""
|
||||
Represent the OracleGate in the computational basis.
|
||||
"""
|
||||
nbasis = 2**self.nqubits # compute it only once
|
||||
matrixOracle = eye(nbasis)
|
||||
# Flip the sign given the output of the oracle function
|
||||
for i in range(nbasis):
|
||||
if self.search_function(IntQubit(i, nqubits=self.nqubits)):
|
||||
matrixOracle[i, i] = NegativeOne()
|
||||
return matrixOracle
|
||||
|
||||
|
||||
class WGate(Gate):
|
||||
"""General n qubit W Gate in Grover's algorithm.
|
||||
|
||||
The gate performs the operation ``2|phi><phi| - 1`` on some qubits.
|
||||
``|phi> = (tensor product of n Hadamards)*(|0> with n qubits)``
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
nqubits : int
|
||||
The number of qubits to operate on
|
||||
|
||||
"""
|
||||
|
||||
gate_name = 'W'
|
||||
gate_name_latex = 'W'
|
||||
|
||||
@classmethod
|
||||
def _eval_args(cls, args):
|
||||
if len(args) != 1:
|
||||
raise QuantumError(
|
||||
'Insufficient/excessive arguments to W gate. Please ' +
|
||||
'supply the number of qubits to operate on.'
|
||||
)
|
||||
args = UnitaryOperator._eval_args(args)
|
||||
if not args[0].is_Integer:
|
||||
raise TypeError('Integer expected, got: %r' % args[0])
|
||||
return args
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Properties
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def targets(self):
|
||||
return sympify(tuple(reversed(range(self.args[0]))))
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Apply
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def _apply_operator_Qubit(self, qubits, **options):
|
||||
"""
|
||||
qubits: a set of qubits (Qubit)
|
||||
Returns: quantum object (quantum expression - QExpr)
|
||||
"""
|
||||
if qubits.nqubits != self.nqubits:
|
||||
raise QuantumError(
|
||||
'WGate operates on %r qubits, got: %r'
|
||||
% (self.nqubits, qubits.nqubits)
|
||||
)
|
||||
|
||||
# See 'Quantum Computer Science' by David Mermin p.92 -> W|a> result
|
||||
# Return (2/(sqrt(2^n)))|phi> - |a> where |a> is the current basis
|
||||
# state and phi is the superposition of basis states (see function
|
||||
# create_computational_basis above)
|
||||
basis_states = superposition_basis(self.nqubits)
|
||||
change_to_basis = (2/sqrt(2**self.nqubits))*basis_states
|
||||
return change_to_basis - qubits
|
||||
|
||||
|
||||
def grover_iteration(qstate, oracle):
|
||||
"""Applies one application of the Oracle and W Gate, WV.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
qstate : Qubit
|
||||
A superposition of qubits.
|
||||
oracle : OracleGate
|
||||
The black box operator that flips the sign of the desired basis qubits.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
Qubit : The qubits after applying the Oracle and W gate.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Perform one iteration of grover's algorithm to see a phase change::
|
||||
|
||||
>>> from sympy.physics.quantum.qapply import qapply
|
||||
>>> from sympy.physics.quantum.qubit import IntQubit
|
||||
>>> from sympy.physics.quantum.grover import OracleGate
|
||||
>>> from sympy.physics.quantum.grover import superposition_basis
|
||||
>>> from sympy.physics.quantum.grover import grover_iteration
|
||||
>>> numqubits = 2
|
||||
>>> basis_states = superposition_basis(numqubits)
|
||||
>>> f = lambda qubits: qubits == IntQubit(2)
|
||||
>>> v = OracleGate(numqubits, f)
|
||||
>>> qapply(grover_iteration(basis_states, v))
|
||||
|2>
|
||||
|
||||
"""
|
||||
wgate = WGate(oracle.nqubits)
|
||||
return wgate*oracle*qstate
|
||||
|
||||
|
||||
def apply_grover(oracle, nqubits, iterations=None):
|
||||
"""Applies grover's algorithm.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
oracle : callable
|
||||
The unknown callable function that returns true when applied to the
|
||||
desired qubits and false otherwise.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
state : Expr
|
||||
The resulting state after Grover's algorithm has been iterated.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Apply grover's algorithm to an even superposition of 2 qubits::
|
||||
|
||||
>>> from sympy.physics.quantum.qapply import qapply
|
||||
>>> from sympy.physics.quantum.qubit import IntQubit
|
||||
>>> from sympy.physics.quantum.grover import apply_grover
|
||||
>>> f = lambda qubits: qubits == IntQubit(2)
|
||||
>>> qapply(apply_grover(f, 2))
|
||||
|2>
|
||||
|
||||
"""
|
||||
if nqubits <= 0:
|
||||
raise QuantumError(
|
||||
'Grover\'s algorithm needs nqubits > 0, received %r qubits'
|
||||
% nqubits
|
||||
)
|
||||
if iterations is None:
|
||||
iterations = floor(sqrt(2**nqubits)*(pi/4))
|
||||
|
||||
v = OracleGate(nqubits, oracle)
|
||||
iterated = superposition_basis(nqubits)
|
||||
for iter in range(iterations):
|
||||
iterated = grover_iteration(iterated, v)
|
||||
iterated = qapply(iterated)
|
||||
|
||||
return iterated
|
||||
@@ -0,0 +1,653 @@
|
||||
"""Hilbert spaces for quantum mechanics.
|
||||
|
||||
Authors:
|
||||
* Brian Granger
|
||||
* Matt Curry
|
||||
"""
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.sets.sets import Interval
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
from sympy.physics.quantum.qexpr import QuantumError
|
||||
|
||||
|
||||
__all__ = [
|
||||
'HilbertSpaceError',
|
||||
'HilbertSpace',
|
||||
'TensorProductHilbertSpace',
|
||||
'TensorPowerHilbertSpace',
|
||||
'DirectSumHilbertSpace',
|
||||
'ComplexSpace',
|
||||
'L2',
|
||||
'FockSpace'
|
||||
]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Main objects
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class HilbertSpaceError(QuantumError):
|
||||
pass
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Main objects
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
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] https://en.wikipedia.org/wiki/Hilbert_space
|
||||
"""
|
||||
|
||||
def __new__(cls):
|
||||
obj = Basic.__new__(cls)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def dimension(self):
|
||||
"""Return the Hilbert dimension of the space."""
|
||||
raise NotImplementedError('This Hilbert space has no dimension.')
|
||||
|
||||
def __add__(self, other):
|
||||
return DirectSumHilbertSpace(self, other)
|
||||
|
||||
def __radd__(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 'H'
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
ustr = '\N{LATIN CAPITAL LETTER H}'
|
||||
return prettyForm(ustr)
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
return r'\mathcal{H}'
|
||||
|
||||
|
||||
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 S.Infinity
|
||||
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 S.Infinity 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):
|
||||
ustr = '\N{LATIN CAPITAL LETTER C}'
|
||||
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)
|
||||
|
||||
|
||||
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(Interval(0, oo))
|
||||
>>> hs.dimension
|
||||
oo
|
||||
>>> hs.interval
|
||||
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 S.Infinity
|
||||
|
||||
@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('2')
|
||||
pform_base = prettyForm('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
|
||||
|
||||
|
||||
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] https://en.wikipedia.org/wiki/Fock_space
|
||||
"""
|
||||
|
||||
def __new__(cls):
|
||||
obj = Basic.__new__(cls)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def dimension(self):
|
||||
return S.Infinity
|
||||
|
||||
def _sympyrepr(self, printer, *args):
|
||||
return "FockSpace()"
|
||||
|
||||
def _sympystr(self, printer, *args):
|
||||
return "F"
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
ustr = '\N{LATIN CAPITAL LETTER F}'
|
||||
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] https://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 S.Infinity in arg_list:
|
||||
return S.Infinity
|
||||
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(' ' + '\N{N-ARY CIRCLED TIMES OPERATOR}' + ' '))
|
||||
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
|
||||
|
||||
>>> c = ComplexSpace(2)
|
||||
>>> f = FockSpace()
|
||||
>>> hs = c+f
|
||||
>>> hs
|
||||
C(2)+F
|
||||
>>> hs.dimension
|
||||
oo
|
||||
>>> list(hs.spaces)
|
||||
[C(2), F]
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://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 S.Infinity in arg_list:
|
||||
return S.Infinity
|
||||
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(' \N{CIRCLED PLUS} '))
|
||||
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] https://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 is S.One:
|
||||
return args[0]
|
||||
#simplify hs**0 -> 1
|
||||
if exp is S.Zero:
|
||||
return S.One
|
||||
#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 is S.Infinity:
|
||||
return S.Infinity
|
||||
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('\N{N-ARY CIRCLED TIMES OPERATOR}')))
|
||||
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)
|
||||
@@ -0,0 +1,853 @@
|
||||
from collections import deque
|
||||
from sympy.core.random import randint
|
||||
|
||||
from sympy.external import import_module
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import Number, equal_valued
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
|
||||
__all__ = [
|
||||
# Public interfaces
|
||||
'generate_gate_rules',
|
||||
'generate_equivalent_ids',
|
||||
'GateIdentity',
|
||||
'bfs_identity_search',
|
||||
'random_identity_search',
|
||||
|
||||
# "Private" functions
|
||||
'is_scalar_sparse_matrix',
|
||||
'is_scalar_nonsparse_matrix',
|
||||
'is_degenerate',
|
||||
'is_reducible',
|
||||
]
|
||||
|
||||
np = import_module('numpy')
|
||||
scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
|
||||
|
||||
|
||||
def is_scalar_sparse_matrix(circuit, nqubits, identity_only, eps=1e-11):
|
||||
"""Checks if a given scipy.sparse matrix is a scalar matrix.
|
||||
|
||||
A scalar matrix is such that B = bI, where B is the scalar
|
||||
matrix, b is some scalar multiple, and I is the identity
|
||||
matrix. A scalar matrix would have only the element b along
|
||||
it's main diagonal and zeroes elsewhere.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
circuit : Gate tuple
|
||||
Sequence of quantum gates representing a quantum circuit
|
||||
nqubits : int
|
||||
Number of qubits in the circuit
|
||||
identity_only : bool
|
||||
Check for only identity matrices
|
||||
eps : number
|
||||
The tolerance value for zeroing out elements in the matrix.
|
||||
Values in the range [-eps, +eps] will be changed to a zero.
|
||||
"""
|
||||
|
||||
if not np or not scipy:
|
||||
pass
|
||||
|
||||
matrix = represent(Mul(*circuit), nqubits=nqubits,
|
||||
format='scipy.sparse')
|
||||
|
||||
# In some cases, represent returns a 1D scalar value in place
|
||||
# of a multi-dimensional scalar matrix
|
||||
if (isinstance(matrix, int)):
|
||||
return matrix == 1 if identity_only else True
|
||||
|
||||
# If represent returns a matrix, check if the matrix is diagonal
|
||||
# and if every item along the diagonal is the same
|
||||
else:
|
||||
# Due to floating pointing operations, must zero out
|
||||
# elements that are "very" small in the dense matrix
|
||||
# See parameter for default value.
|
||||
|
||||
# Get the ndarray version of the dense matrix
|
||||
dense_matrix = matrix.todense().getA()
|
||||
# Since complex values can't be compared, must split
|
||||
# the matrix into real and imaginary components
|
||||
# Find the real values in between -eps and eps
|
||||
bool_real = np.logical_and(dense_matrix.real > -eps,
|
||||
dense_matrix.real < eps)
|
||||
# Find the imaginary values between -eps and eps
|
||||
bool_imag = np.logical_and(dense_matrix.imag > -eps,
|
||||
dense_matrix.imag < eps)
|
||||
# Replaces values between -eps and eps with 0
|
||||
corrected_real = np.where(bool_real, 0.0, dense_matrix.real)
|
||||
corrected_imag = np.where(bool_imag, 0.0, dense_matrix.imag)
|
||||
# Convert the matrix with real values into imaginary values
|
||||
corrected_imag = corrected_imag * complex(1j)
|
||||
# Recombine the real and imaginary components
|
||||
corrected_dense = corrected_real + corrected_imag
|
||||
|
||||
# Check if it's diagonal
|
||||
row_indices = corrected_dense.nonzero()[0]
|
||||
col_indices = corrected_dense.nonzero()[1]
|
||||
# Check if the rows indices and columns indices are the same
|
||||
# If they match, then matrix only contains elements along diagonal
|
||||
bool_indices = row_indices == col_indices
|
||||
is_diagonal = bool_indices.all()
|
||||
|
||||
first_element = corrected_dense[0][0]
|
||||
# If the first element is a zero, then can't rescale matrix
|
||||
# and definitely not diagonal
|
||||
if (first_element == 0.0 + 0.0j):
|
||||
return False
|
||||
|
||||
# The dimensions of the dense matrix should still
|
||||
# be 2^nqubits if there are elements all along the
|
||||
# the main diagonal
|
||||
trace_of_corrected = (corrected_dense/first_element).trace()
|
||||
expected_trace = pow(2, nqubits)
|
||||
has_correct_trace = trace_of_corrected == expected_trace
|
||||
|
||||
# If only looking for identity matrices
|
||||
# first element must be a 1
|
||||
real_is_one = abs(first_element.real - 1.0) < eps
|
||||
imag_is_zero = abs(first_element.imag) < eps
|
||||
is_one = real_is_one and imag_is_zero
|
||||
is_identity = is_one if identity_only else True
|
||||
return bool(is_diagonal and has_correct_trace and is_identity)
|
||||
|
||||
|
||||
def is_scalar_nonsparse_matrix(circuit, nqubits, identity_only, eps=None):
|
||||
"""Checks if a given circuit, in matrix form, is equivalent to
|
||||
a scalar value.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
circuit : Gate tuple
|
||||
Sequence of quantum gates representing a quantum circuit
|
||||
nqubits : int
|
||||
Number of qubits in the circuit
|
||||
identity_only : bool
|
||||
Check for only identity matrices
|
||||
eps : number
|
||||
This argument is ignored. It is just for signature compatibility with
|
||||
is_scalar_sparse_matrix.
|
||||
|
||||
Note: Used in situations when is_scalar_sparse_matrix has bugs
|
||||
"""
|
||||
|
||||
matrix = represent(Mul(*circuit), nqubits=nqubits)
|
||||
|
||||
# In some cases, represent returns a 1D scalar value in place
|
||||
# of a multi-dimensional scalar matrix
|
||||
if (isinstance(matrix, Number)):
|
||||
return matrix == 1 if identity_only else True
|
||||
|
||||
# If represent returns a matrix, check if the matrix is diagonal
|
||||
# and if every item along the diagonal is the same
|
||||
else:
|
||||
# Added up the diagonal elements
|
||||
matrix_trace = matrix.trace()
|
||||
# Divide the trace by the first element in the matrix
|
||||
# if matrix is not required to be the identity matrix
|
||||
adjusted_matrix_trace = (matrix_trace/matrix[0]
|
||||
if not identity_only
|
||||
else matrix_trace)
|
||||
|
||||
is_identity = equal_valued(matrix[0], 1) if identity_only else True
|
||||
|
||||
has_correct_trace = adjusted_matrix_trace == pow(2, nqubits)
|
||||
|
||||
# The matrix is scalar if it's diagonal and the adjusted trace
|
||||
# value is equal to 2^nqubits
|
||||
return bool(
|
||||
matrix.is_diagonal() and has_correct_trace and is_identity)
|
||||
|
||||
if np and scipy:
|
||||
is_scalar_matrix = is_scalar_sparse_matrix
|
||||
else:
|
||||
is_scalar_matrix = is_scalar_nonsparse_matrix
|
||||
|
||||
|
||||
def _get_min_qubits(a_gate):
|
||||
if isinstance(a_gate, Pow):
|
||||
return a_gate.base.min_qubits
|
||||
else:
|
||||
return a_gate.min_qubits
|
||||
|
||||
|
||||
def ll_op(left, right):
|
||||
"""Perform a LL operation.
|
||||
|
||||
A LL operation multiplies both left and right circuits
|
||||
with the dagger of the left circuit's leftmost gate, and
|
||||
the dagger is multiplied on the left side of both circuits.
|
||||
|
||||
If a LL is possible, it returns the new gate rule as a
|
||||
2-tuple (LHS, RHS), where LHS is the left circuit and
|
||||
and RHS is the right circuit of the new rule.
|
||||
If a LL is not possible, None is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
left : Gate tuple
|
||||
The left circuit of a gate rule expression.
|
||||
right : Gate tuple
|
||||
The right circuit of a gate rule expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Generate a new gate rule using a LL operation:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import ll_op
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> ll_op((x, y, z), ())
|
||||
((Y(0), Z(0)), (X(0),))
|
||||
|
||||
>>> ll_op((y, z), (x,))
|
||||
((Z(0),), (Y(0), X(0)))
|
||||
"""
|
||||
|
||||
if (len(left) > 0):
|
||||
ll_gate = left[0]
|
||||
ll_gate_is_unitary = is_scalar_matrix(
|
||||
(Dagger(ll_gate), ll_gate), _get_min_qubits(ll_gate), True)
|
||||
|
||||
if (len(left) > 0 and ll_gate_is_unitary):
|
||||
# Get the new left side w/o the leftmost gate
|
||||
new_left = left[1:len(left)]
|
||||
# Add the leftmost gate to the left position on the right side
|
||||
new_right = (Dagger(ll_gate),) + right
|
||||
# Return the new gate rule
|
||||
return (new_left, new_right)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def lr_op(left, right):
|
||||
"""Perform a LR operation.
|
||||
|
||||
A LR operation multiplies both left and right circuits
|
||||
with the dagger of the left circuit's rightmost gate, and
|
||||
the dagger is multiplied on the right side of both circuits.
|
||||
|
||||
If a LR is possible, it returns the new gate rule as a
|
||||
2-tuple (LHS, RHS), where LHS is the left circuit and
|
||||
and RHS is the right circuit of the new rule.
|
||||
If a LR is not possible, None is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
left : Gate tuple
|
||||
The left circuit of a gate rule expression.
|
||||
right : Gate tuple
|
||||
The right circuit of a gate rule expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Generate a new gate rule using a LR operation:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import lr_op
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> lr_op((x, y, z), ())
|
||||
((X(0), Y(0)), (Z(0),))
|
||||
|
||||
>>> lr_op((x, y), (z,))
|
||||
((X(0),), (Z(0), Y(0)))
|
||||
"""
|
||||
|
||||
if (len(left) > 0):
|
||||
lr_gate = left[len(left) - 1]
|
||||
lr_gate_is_unitary = is_scalar_matrix(
|
||||
(Dagger(lr_gate), lr_gate), _get_min_qubits(lr_gate), True)
|
||||
|
||||
if (len(left) > 0 and lr_gate_is_unitary):
|
||||
# Get the new left side w/o the rightmost gate
|
||||
new_left = left[0:len(left) - 1]
|
||||
# Add the rightmost gate to the right position on the right side
|
||||
new_right = right + (Dagger(lr_gate),)
|
||||
# Return the new gate rule
|
||||
return (new_left, new_right)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def rl_op(left, right):
|
||||
"""Perform a RL operation.
|
||||
|
||||
A RL operation multiplies both left and right circuits
|
||||
with the dagger of the right circuit's leftmost gate, and
|
||||
the dagger is multiplied on the left side of both circuits.
|
||||
|
||||
If a RL is possible, it returns the new gate rule as a
|
||||
2-tuple (LHS, RHS), where LHS is the left circuit and
|
||||
and RHS is the right circuit of the new rule.
|
||||
If a RL is not possible, None is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
left : Gate tuple
|
||||
The left circuit of a gate rule expression.
|
||||
right : Gate tuple
|
||||
The right circuit of a gate rule expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Generate a new gate rule using a RL operation:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import rl_op
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> rl_op((x,), (y, z))
|
||||
((Y(0), X(0)), (Z(0),))
|
||||
|
||||
>>> rl_op((x, y), (z,))
|
||||
((Z(0), X(0), Y(0)), ())
|
||||
"""
|
||||
|
||||
if (len(right) > 0):
|
||||
rl_gate = right[0]
|
||||
rl_gate_is_unitary = is_scalar_matrix(
|
||||
(Dagger(rl_gate), rl_gate), _get_min_qubits(rl_gate), True)
|
||||
|
||||
if (len(right) > 0 and rl_gate_is_unitary):
|
||||
# Get the new right side w/o the leftmost gate
|
||||
new_right = right[1:len(right)]
|
||||
# Add the leftmost gate to the left position on the left side
|
||||
new_left = (Dagger(rl_gate),) + left
|
||||
# Return the new gate rule
|
||||
return (new_left, new_right)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def rr_op(left, right):
|
||||
"""Perform a RR operation.
|
||||
|
||||
A RR operation multiplies both left and right circuits
|
||||
with the dagger of the right circuit's rightmost gate, and
|
||||
the dagger is multiplied on the right side of both circuits.
|
||||
|
||||
If a RR is possible, it returns the new gate rule as a
|
||||
2-tuple (LHS, RHS), where LHS is the left circuit and
|
||||
and RHS is the right circuit of the new rule.
|
||||
If a RR is not possible, None is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
left : Gate tuple
|
||||
The left circuit of a gate rule expression.
|
||||
right : Gate tuple
|
||||
The right circuit of a gate rule expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Generate a new gate rule using a RR operation:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import rr_op
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> rr_op((x, y), (z,))
|
||||
((X(0), Y(0), Z(0)), ())
|
||||
|
||||
>>> rr_op((x,), (y, z))
|
||||
((X(0), Z(0)), (Y(0),))
|
||||
"""
|
||||
|
||||
if (len(right) > 0):
|
||||
rr_gate = right[len(right) - 1]
|
||||
rr_gate_is_unitary = is_scalar_matrix(
|
||||
(Dagger(rr_gate), rr_gate), _get_min_qubits(rr_gate), True)
|
||||
|
||||
if (len(right) > 0 and rr_gate_is_unitary):
|
||||
# Get the new right side w/o the rightmost gate
|
||||
new_right = right[0:len(right) - 1]
|
||||
# Add the rightmost gate to the right position on the right side
|
||||
new_left = left + (Dagger(rr_gate),)
|
||||
# Return the new gate rule
|
||||
return (new_left, new_right)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def generate_gate_rules(gate_seq, return_as_muls=False):
|
||||
"""Returns a set of gate rules. Each gate rules is represented
|
||||
as a 2-tuple of tuples or Muls. An empty tuple represents an arbitrary
|
||||
scalar value.
|
||||
|
||||
This function uses the four operations (LL, LR, RL, RR)
|
||||
to generate the gate rules.
|
||||
|
||||
A gate rule is an expression such as ABC = D or AB = CD, where
|
||||
A, B, C, and D are gates. Each value on either side of the
|
||||
equal sign represents a circuit. The four operations allow
|
||||
one to find a set of equivalent circuits from a gate identity.
|
||||
The letters denoting the operation tell the user what
|
||||
activities to perform on each expression. The first letter
|
||||
indicates which side of the equal sign to focus on. The
|
||||
second letter indicates which gate to focus on given the
|
||||
side. Once this information is determined, the inverse
|
||||
of the gate is multiplied on both circuits to create a new
|
||||
gate rule.
|
||||
|
||||
For example, given the identity, ABCD = 1, a LL operation
|
||||
means look at the left value and multiply both left sides by the
|
||||
inverse of the leftmost gate A. If A is Hermitian, the inverse
|
||||
of A is still A. The resulting new rule is BCD = A.
|
||||
|
||||
The following is a summary of the four operations. Assume
|
||||
that in the examples, all gates are Hermitian.
|
||||
|
||||
LL : left circuit, left multiply
|
||||
ABCD = E -> AABCD = AE -> BCD = AE
|
||||
LR : left circuit, right multiply
|
||||
ABCD = E -> ABCDD = ED -> ABC = ED
|
||||
RL : right circuit, left multiply
|
||||
ABC = ED -> EABC = EED -> EABC = D
|
||||
RR : right circuit, right multiply
|
||||
AB = CD -> ABD = CDD -> ABD = C
|
||||
|
||||
The number of gate rules generated is n*(n+1), where n
|
||||
is the number of gates in the sequence (unproven).
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
gate_seq : Gate tuple, Mul, or Number
|
||||
A variable length tuple or Mul of Gates whose product is equal to
|
||||
a scalar matrix
|
||||
return_as_muls : bool
|
||||
True to return a set of Muls; False to return a set of tuples
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Find the gate rules of the current circuit using tuples:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import generate_gate_rules
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> generate_gate_rules((x, x))
|
||||
{((X(0),), (X(0),)), ((X(0), X(0)), ())}
|
||||
|
||||
>>> generate_gate_rules((x, y, z))
|
||||
{((), (X(0), Z(0), Y(0))), ((), (Y(0), X(0), Z(0))),
|
||||
((), (Z(0), Y(0), X(0))), ((X(0),), (Z(0), Y(0))),
|
||||
((Y(0),), (X(0), Z(0))), ((Z(0),), (Y(0), X(0))),
|
||||
((X(0), Y(0)), (Z(0),)), ((Y(0), Z(0)), (X(0),)),
|
||||
((Z(0), X(0)), (Y(0),)), ((X(0), Y(0), Z(0)), ()),
|
||||
((Y(0), Z(0), X(0)), ()), ((Z(0), X(0), Y(0)), ())}
|
||||
|
||||
Find the gate rules of the current circuit using Muls:
|
||||
|
||||
>>> generate_gate_rules(x*x, return_as_muls=True)
|
||||
{(1, 1)}
|
||||
|
||||
>>> generate_gate_rules(x*y*z, return_as_muls=True)
|
||||
{(1, X(0)*Z(0)*Y(0)), (1, Y(0)*X(0)*Z(0)),
|
||||
(1, Z(0)*Y(0)*X(0)), (X(0)*Y(0), Z(0)),
|
||||
(Y(0)*Z(0), X(0)), (Z(0)*X(0), Y(0)),
|
||||
(X(0)*Y(0)*Z(0), 1), (Y(0)*Z(0)*X(0), 1),
|
||||
(Z(0)*X(0)*Y(0), 1), (X(0), Z(0)*Y(0)),
|
||||
(Y(0), X(0)*Z(0)), (Z(0), Y(0)*X(0))}
|
||||
"""
|
||||
|
||||
if isinstance(gate_seq, Number):
|
||||
if return_as_muls:
|
||||
return {(S.One, S.One)}
|
||||
else:
|
||||
return {((), ())}
|
||||
|
||||
elif isinstance(gate_seq, Mul):
|
||||
gate_seq = gate_seq.args
|
||||
|
||||
# Each item in queue is a 3-tuple:
|
||||
# i) first item is the left side of an equality
|
||||
# ii) second item is the right side of an equality
|
||||
# iii) third item is the number of operations performed
|
||||
# The argument, gate_seq, will start on the left side, and
|
||||
# the right side will be empty, implying the presence of an
|
||||
# identity.
|
||||
queue = deque()
|
||||
# A set of gate rules
|
||||
rules = set()
|
||||
# Maximum number of operations to perform
|
||||
max_ops = len(gate_seq)
|
||||
|
||||
def process_new_rule(new_rule, ops):
|
||||
if new_rule is not None:
|
||||
new_left, new_right = new_rule
|
||||
|
||||
if new_rule not in rules and (new_right, new_left) not in rules:
|
||||
rules.add(new_rule)
|
||||
# If haven't reached the max limit on operations
|
||||
if ops + 1 < max_ops:
|
||||
queue.append(new_rule + (ops + 1,))
|
||||
|
||||
queue.append((gate_seq, (), 0))
|
||||
rules.add((gate_seq, ()))
|
||||
|
||||
while len(queue) > 0:
|
||||
left, right, ops = queue.popleft()
|
||||
|
||||
# Do a LL
|
||||
new_rule = ll_op(left, right)
|
||||
process_new_rule(new_rule, ops)
|
||||
# Do a LR
|
||||
new_rule = lr_op(left, right)
|
||||
process_new_rule(new_rule, ops)
|
||||
# Do a RL
|
||||
new_rule = rl_op(left, right)
|
||||
process_new_rule(new_rule, ops)
|
||||
# Do a RR
|
||||
new_rule = rr_op(left, right)
|
||||
process_new_rule(new_rule, ops)
|
||||
|
||||
if return_as_muls:
|
||||
# Convert each rule as tuples into a rule as muls
|
||||
mul_rules = set()
|
||||
for rule in rules:
|
||||
left, right = rule
|
||||
mul_rules.add((Mul(*left), Mul(*right)))
|
||||
|
||||
rules = mul_rules
|
||||
|
||||
return rules
|
||||
|
||||
|
||||
def generate_equivalent_ids(gate_seq, return_as_muls=False):
|
||||
"""Returns a set of equivalent gate identities.
|
||||
|
||||
A gate identity is a quantum circuit such that the product
|
||||
of the gates in the circuit is equal to a scalar value.
|
||||
For example, XYZ = i, where X, Y, Z are the Pauli gates and
|
||||
i is the imaginary value, is considered a gate identity.
|
||||
|
||||
This function uses the four operations (LL, LR, RL, RR)
|
||||
to generate the gate rules and, subsequently, to locate equivalent
|
||||
gate identities.
|
||||
|
||||
Note that all equivalent identities are reachable in n operations
|
||||
from the starting gate identity, where n is the number of gates
|
||||
in the sequence.
|
||||
|
||||
The max number of gate identities is 2n, where n is the number
|
||||
of gates in the sequence (unproven).
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
gate_seq : Gate tuple, Mul, or Number
|
||||
A variable length tuple or Mul of Gates whose product is equal to
|
||||
a scalar matrix.
|
||||
return_as_muls: bool
|
||||
True to return as Muls; False to return as tuples
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Find equivalent gate identities from the current circuit with tuples:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import generate_equivalent_ids
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> generate_equivalent_ids((x, x))
|
||||
{(X(0), X(0))}
|
||||
|
||||
>>> generate_equivalent_ids((x, y, z))
|
||||
{(X(0), Y(0), Z(0)), (X(0), Z(0), Y(0)), (Y(0), X(0), Z(0)),
|
||||
(Y(0), Z(0), X(0)), (Z(0), X(0), Y(0)), (Z(0), Y(0), X(0))}
|
||||
|
||||
Find equivalent gate identities from the current circuit with Muls:
|
||||
|
||||
>>> generate_equivalent_ids(x*x, return_as_muls=True)
|
||||
{1}
|
||||
|
||||
>>> generate_equivalent_ids(x*y*z, return_as_muls=True)
|
||||
{X(0)*Y(0)*Z(0), X(0)*Z(0)*Y(0), Y(0)*X(0)*Z(0),
|
||||
Y(0)*Z(0)*X(0), Z(0)*X(0)*Y(0), Z(0)*Y(0)*X(0)}
|
||||
"""
|
||||
|
||||
if isinstance(gate_seq, Number):
|
||||
return {S.One}
|
||||
elif isinstance(gate_seq, Mul):
|
||||
gate_seq = gate_seq.args
|
||||
|
||||
# Filter through the gate rules and keep the rules
|
||||
# with an empty tuple either on the left or right side
|
||||
|
||||
# A set of equivalent gate identities
|
||||
eq_ids = set()
|
||||
|
||||
gate_rules = generate_gate_rules(gate_seq)
|
||||
for rule in gate_rules:
|
||||
l, r = rule
|
||||
if l == ():
|
||||
eq_ids.add(r)
|
||||
elif r == ():
|
||||
eq_ids.add(l)
|
||||
|
||||
if return_as_muls:
|
||||
convert_to_mul = lambda id_seq: Mul(*id_seq)
|
||||
eq_ids = set(map(convert_to_mul, eq_ids))
|
||||
|
||||
return eq_ids
|
||||
|
||||
|
||||
class GateIdentity(Basic):
|
||||
"""Wrapper class for circuits that reduce to a scalar value.
|
||||
|
||||
A gate identity is a quantum circuit such that the product
|
||||
of the gates in the circuit is equal to a scalar value.
|
||||
For example, XYZ = i, where X, Y, Z are the Pauli gates and
|
||||
i is the imaginary value, is considered a gate identity.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : Gate tuple
|
||||
A variable length tuple of Gates that form an identity.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a GateIdentity and look at its attributes:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import GateIdentity
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> an_identity = GateIdentity(x, y, z)
|
||||
>>> an_identity.circuit
|
||||
X(0)*Y(0)*Z(0)
|
||||
|
||||
>>> an_identity.equivalent_ids
|
||||
{(X(0), Y(0), Z(0)), (X(0), Z(0), Y(0)), (Y(0), X(0), Z(0)),
|
||||
(Y(0), Z(0), X(0)), (Z(0), X(0), Y(0)), (Z(0), Y(0), X(0))}
|
||||
"""
|
||||
|
||||
def __new__(cls, *args):
|
||||
# args should be a tuple - a variable length argument list
|
||||
obj = Basic.__new__(cls, *args)
|
||||
obj._circuit = Mul(*args)
|
||||
obj._rules = generate_gate_rules(args)
|
||||
obj._eq_ids = generate_equivalent_ids(args)
|
||||
|
||||
return obj
|
||||
|
||||
@property
|
||||
def circuit(self):
|
||||
return self._circuit
|
||||
|
||||
@property
|
||||
def gate_rules(self):
|
||||
return self._rules
|
||||
|
||||
@property
|
||||
def equivalent_ids(self):
|
||||
return self._eq_ids
|
||||
|
||||
@property
|
||||
def sequence(self):
|
||||
return self.args
|
||||
|
||||
def __str__(self):
|
||||
"""Returns the string of gates in a tuple."""
|
||||
return str(self.circuit)
|
||||
|
||||
|
||||
def is_degenerate(identity_set, gate_identity):
|
||||
"""Checks if a gate identity is a permutation of another identity.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
identity_set : set
|
||||
A Python set with GateIdentity objects.
|
||||
gate_identity : GateIdentity
|
||||
The GateIdentity to check for existence in the set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Check if the identity is a permutation of another identity:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import (
|
||||
... GateIdentity, is_degenerate)
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> an_identity = GateIdentity(x, y, z)
|
||||
>>> id_set = {an_identity}
|
||||
>>> another_id = (y, z, x)
|
||||
>>> is_degenerate(id_set, another_id)
|
||||
True
|
||||
|
||||
>>> another_id = (x, x)
|
||||
>>> is_degenerate(id_set, another_id)
|
||||
False
|
||||
"""
|
||||
|
||||
# For now, just iteratively go through the set and check if the current
|
||||
# gate_identity is a permutation of an identity in the set
|
||||
for an_id in identity_set:
|
||||
if (gate_identity in an_id.equivalent_ids):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_reducible(circuit, nqubits, begin, end):
|
||||
"""Determines if a circuit is reducible by checking
|
||||
if its subcircuits are scalar values.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
circuit : Gate tuple
|
||||
A tuple of Gates representing a circuit. The circuit to check
|
||||
if a gate identity is contained in a subcircuit.
|
||||
nqubits : int
|
||||
The number of qubits the circuit operates on.
|
||||
begin : int
|
||||
The leftmost gate in the circuit to include in a subcircuit.
|
||||
end : int
|
||||
The rightmost gate in the circuit to include in a subcircuit.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Check if the circuit can be reduced:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import is_reducible
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> is_reducible((x, y, z), 1, 0, 3)
|
||||
True
|
||||
|
||||
Check if an interval in the circuit can be reduced:
|
||||
|
||||
>>> is_reducible((x, y, z), 1, 1, 3)
|
||||
False
|
||||
|
||||
>>> is_reducible((x, y, y), 1, 1, 3)
|
||||
True
|
||||
"""
|
||||
|
||||
current_circuit = ()
|
||||
# Start from the gate at "end" and go down to almost the gate at "begin"
|
||||
for ndx in reversed(range(begin, end)):
|
||||
next_gate = circuit[ndx]
|
||||
current_circuit = (next_gate,) + current_circuit
|
||||
|
||||
# If a circuit as a matrix is equivalent to a scalar value
|
||||
if (is_scalar_matrix(current_circuit, nqubits, False)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def bfs_identity_search(gate_list, nqubits, max_depth=None,
|
||||
identity_only=False):
|
||||
"""Constructs a set of gate identities from the list of possible gates.
|
||||
|
||||
Performs a breadth first search over the space of gate identities.
|
||||
This allows the finding of the shortest gate identities first.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
gate_list : list, Gate
|
||||
A list of Gates from which to search for gate identities.
|
||||
nqubits : int
|
||||
The number of qubits the quantum circuit operates on.
|
||||
max_depth : int
|
||||
The longest quantum circuit to construct from gate_list.
|
||||
identity_only : bool
|
||||
True to search for gate identities that reduce to identity;
|
||||
False to search for gate identities that reduce to a scalar.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Find a list of gate identities:
|
||||
|
||||
>>> from sympy.physics.quantum.identitysearch import bfs_identity_search
|
||||
>>> from sympy.physics.quantum.gate import X, Y, Z
|
||||
>>> x = X(0); y = Y(0); z = Z(0)
|
||||
>>> bfs_identity_search([x], 1, max_depth=2)
|
||||
{GateIdentity(X(0), X(0))}
|
||||
|
||||
>>> bfs_identity_search([x, y, z], 1)
|
||||
{GateIdentity(X(0), X(0)), GateIdentity(Y(0), Y(0)),
|
||||
GateIdentity(Z(0), Z(0)), GateIdentity(X(0), Y(0), Z(0))}
|
||||
|
||||
Find a list of identities that only equal to 1:
|
||||
|
||||
>>> bfs_identity_search([x, y, z], 1, identity_only=True)
|
||||
{GateIdentity(X(0), X(0)), GateIdentity(Y(0), Y(0)),
|
||||
GateIdentity(Z(0), Z(0))}
|
||||
"""
|
||||
|
||||
if max_depth is None or max_depth <= 0:
|
||||
max_depth = len(gate_list)
|
||||
|
||||
id_only = identity_only
|
||||
|
||||
# Start with an empty sequence (implicitly contains an IdentityGate)
|
||||
queue = deque([()])
|
||||
|
||||
# Create an empty set of gate identities
|
||||
ids = set()
|
||||
|
||||
# Begin searching for gate identities in given space.
|
||||
while (len(queue) > 0):
|
||||
current_circuit = queue.popleft()
|
||||
|
||||
for next_gate in gate_list:
|
||||
new_circuit = current_circuit + (next_gate,)
|
||||
|
||||
# Determines if a (strict) subcircuit is a scalar matrix
|
||||
circuit_reducible = is_reducible(new_circuit, nqubits,
|
||||
1, len(new_circuit))
|
||||
|
||||
# In many cases when the matrix is a scalar value,
|
||||
# the evaluated matrix will actually be an integer
|
||||
if (is_scalar_matrix(new_circuit, nqubits, id_only) and
|
||||
not is_degenerate(ids, new_circuit) and
|
||||
not circuit_reducible):
|
||||
ids.add(GateIdentity(*new_circuit))
|
||||
|
||||
elif (len(new_circuit) < max_depth and
|
||||
not circuit_reducible):
|
||||
queue.append(new_circuit)
|
||||
|
||||
return ids
|
||||
|
||||
|
||||
def random_identity_search(gate_list, numgates, nqubits):
|
||||
"""Randomly selects numgates from gate_list and checks if it is
|
||||
a gate identity.
|
||||
|
||||
If the circuit is a gate identity, the circuit is returned;
|
||||
Otherwise, None is returned.
|
||||
"""
|
||||
|
||||
gate_size = len(gate_list)
|
||||
circuit = ()
|
||||
|
||||
for i in range(numgates):
|
||||
next_gate = gate_list[randint(0, gate_size - 1)]
|
||||
circuit = circuit + (next_gate,)
|
||||
|
||||
is_scalar = is_scalar_matrix(circuit, nqubits, False)
|
||||
|
||||
return circuit if is_scalar else None
|
||||
@@ -0,0 +1,138 @@
|
||||
"""Symbolic inner product."""
|
||||
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.kind import NumberKind
|
||||
from sympy.functions.elementary.complexes import conjugate
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
|
||||
|
||||
__all__ = [
|
||||
'InnerProduct'
|
||||
]
|
||||
|
||||
|
||||
# InnerProduct is not an QExpr because it is really just a regular commutative
|
||||
# number. We have gone back and forth about this, but we gain a lot by having
|
||||
# it subclass Expr. The main challenges were getting Dagger to work
|
||||
# (we use _eval_conjugate) and represent (we can use atoms and subs). Having
|
||||
# it be an Expr, mean that there are no commutative QExpr subclasses,
|
||||
# which simplifies the design of everything.
|
||||
|
||||
class InnerProduct(Expr):
|
||||
"""An unevaluated inner product between a Bra and a Ket [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
bra : BraBase or subclass
|
||||
The bra on the left side of the inner product.
|
||||
ket : KetBase or subclass
|
||||
The ket on the right side of the inner product.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create an InnerProduct and check its properties:
|
||||
|
||||
>>> from sympy.physics.quantum import Bra, Ket
|
||||
>>> b = Bra('b')
|
||||
>>> k = Ket('k')
|
||||
>>> ip = b*k
|
||||
>>> ip
|
||||
<b|k>
|
||||
>>> ip.bra
|
||||
<b|
|
||||
>>> ip.ket
|
||||
|k>
|
||||
|
||||
In quantum expressions, inner products will be automatically
|
||||
identified and created::
|
||||
|
||||
>>> b*k
|
||||
<b|k>
|
||||
|
||||
In more complex expressions, where there is ambiguity in whether inner or
|
||||
outer products should be created, inner products have high priority::
|
||||
|
||||
>>> k*b*k*b
|
||||
<b|k>*|k><b|
|
||||
|
||||
Notice how the inner product <b|k> moved to the left of the expression
|
||||
because inner products are commutative complex numbers.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Inner_product
|
||||
"""
|
||||
|
||||
kind = NumberKind
|
||||
|
||||
is_complex = True
|
||||
|
||||
def __new__(cls, bra, ket):
|
||||
# Keep the import of BraBase and KetBase here to avoid problems
|
||||
# with circular imports.
|
||||
from sympy.physics.quantum.state import KetBase, BraBase
|
||||
if not isinstance(ket, KetBase):
|
||||
raise TypeError('KetBase subclass expected, got: %r' % ket)
|
||||
if not isinstance(bra, BraBase):
|
||||
raise TypeError('BraBase subclass expected, got: %r' % ket)
|
||||
obj = Expr.__new__(cls, bra, ket)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def bra(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def ket(self):
|
||||
return self.args[1]
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return InnerProduct(Dagger(self.ket), Dagger(self.bra))
|
||||
|
||||
def _sympyrepr(self, printer, *args):
|
||||
return '%s(%s,%s)' % (self.__class__.__name__,
|
||||
printer._print(self.bra, *args), printer._print(self.ket, *args))
|
||||
|
||||
def _sympystr(self, printer, *args):
|
||||
sbra = printer._print(self.bra)
|
||||
sket = printer._print(self.ket)
|
||||
return '%s|%s' % (sbra[:-1], sket[1:])
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
# Print state contents
|
||||
bra = self.bra._print_contents_pretty(printer, *args)
|
||||
ket = self.ket._print_contents_pretty(printer, *args)
|
||||
# Print brackets
|
||||
height = max(bra.height(), ket.height())
|
||||
use_unicode = printer._use_unicode
|
||||
lbracket, _ = self.bra._pretty_brackets(height, use_unicode)
|
||||
cbracket, rbracket = self.ket._pretty_brackets(height, use_unicode)
|
||||
# Build innerproduct
|
||||
pform = prettyForm(*bra.left(lbracket))
|
||||
pform = prettyForm(*pform.right(cbracket))
|
||||
pform = prettyForm(*pform.right(ket))
|
||||
pform = prettyForm(*pform.right(rbracket))
|
||||
return pform
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
bra_label = self.bra._print_contents_latex(printer, *args)
|
||||
ket = printer._print(self.ket, *args)
|
||||
return r'\left\langle %s \right. %s' % (bra_label, ket)
|
||||
|
||||
def doit(self, **hints):
|
||||
try:
|
||||
r = self.ket._eval_innerproduct(self.bra, **hints)
|
||||
except NotImplementedError:
|
||||
try:
|
||||
r = conjugate(
|
||||
self.bra.dual._eval_innerproduct(self.ket.dual, **hints)
|
||||
)
|
||||
except NotImplementedError:
|
||||
r = None
|
||||
if r is not None:
|
||||
return r
|
||||
return self
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Kinds for Operators, Bras, and Kets.
|
||||
|
||||
This module defines kinds for operators, bras, and kets. These are useful
|
||||
in various places in ``sympy.physics.quantum`` as you often want to know
|
||||
what the kind is of a compound expression. For example, if you multiply
|
||||
an operator, bra, or ket by a number, you get back another operator, bra,
|
||||
or ket - even though if you did an ``isinstance`` check you would find that
|
||||
you have a ``Mul`` instead. The kind system is meant to give you a quick
|
||||
way of determining how a compound expression behaves in terms of lower
|
||||
level kinds.
|
||||
|
||||
The resolution calculation of kinds for compound expressions can be found
|
||||
either in container classes or in functions that are registered with
|
||||
kind dispatchers.
|
||||
"""
|
||||
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.kind import Kind, _NumberKind
|
||||
|
||||
|
||||
__all__ = [
|
||||
'_KetKind',
|
||||
'KetKind',
|
||||
'_BraKind',
|
||||
'BraKind',
|
||||
'_OperatorKind',
|
||||
'OperatorKind',
|
||||
]
|
||||
|
||||
|
||||
class _KetKind(Kind):
|
||||
"""A kind for quantum kets."""
|
||||
|
||||
def __new__(cls):
|
||||
obj = super().__new__(cls)
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return "KetKind"
|
||||
|
||||
# Create an instance as many situations need this.
|
||||
KetKind = _KetKind()
|
||||
|
||||
|
||||
class _BraKind(Kind):
|
||||
"""A kind for quantum bras."""
|
||||
|
||||
def __new__(cls):
|
||||
obj = super().__new__(cls)
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return "BraKind"
|
||||
|
||||
# Create an instance as many situations need this.
|
||||
BraKind = _BraKind()
|
||||
|
||||
|
||||
from sympy.core.kind import Kind
|
||||
|
||||
class _OperatorKind(Kind):
|
||||
"""A kind for quantum operators."""
|
||||
|
||||
def __new__(cls):
|
||||
obj = super().__new__(cls)
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return "OperatorKind"
|
||||
|
||||
# Create an instance as many situations need this.
|
||||
OperatorKind = _OperatorKind()
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Kind resolution.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Note: We can't currently add kind dispatchers for the following combinations
|
||||
# as the Mul._kind_dispatcher is set to commutative and will also
|
||||
# register the opposite order, which isn't correct for these pairs:
|
||||
#
|
||||
# 1. (_OperatorKind, _KetKind)
|
||||
# 2. (_BraKind, _OperatorKind)
|
||||
# 3. (_BraKind, _KetKind)
|
||||
|
||||
|
||||
@Mul._kind_dispatcher.register(_NumberKind, _KetKind)
|
||||
def _mul_number_ket_kind(lhs, rhs):
|
||||
"""Perform the kind calculation of NumberKind*KetKind -> KetKind."""
|
||||
return KetKind
|
||||
|
||||
|
||||
@Mul._kind_dispatcher.register(_NumberKind, _BraKind)
|
||||
def _mul_number_bra_kind(lhs, rhs):
|
||||
"""Perform the kind calculation of NumberKind*BraKind -> BraKind."""
|
||||
return BraKind
|
||||
|
||||
|
||||
@Mul._kind_dispatcher.register(_NumberKind, _OperatorKind)
|
||||
def _mul_operator_kind(lhs, rhs):
|
||||
"""Perform the kind calculation of NumberKind*OperatorKind -> OperatorKind."""
|
||||
return OperatorKind
|
||||
@@ -0,0 +1,103 @@
|
||||
"""A cache for storing small matrices in multiple formats."""
|
||||
|
||||
from sympy.core.numbers import (I, Rational, pi)
|
||||
from sympy.core.power import Pow
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.matrices.dense import Matrix
|
||||
|
||||
from sympy.physics.quantum.matrixutils import (
|
||||
to_sympy, to_numpy, to_scipy_sparse
|
||||
)
|
||||
|
||||
|
||||
class MatrixCache:
|
||||
"""A cache for small matrices in different formats.
|
||||
|
||||
This class takes small matrices in the standard ``sympy.Matrix`` format,
|
||||
and then converts these to both ``numpy.matrix`` and
|
||||
``scipy.sparse.csr_matrix`` matrices. These matrices are then stored for
|
||||
future recovery.
|
||||
"""
|
||||
|
||||
def __init__(self, dtype='complex'):
|
||||
self._cache = {}
|
||||
self.dtype = dtype
|
||||
|
||||
def cache_matrix(self, name, m):
|
||||
"""Cache a matrix by its name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
A descriptive name for the matrix, like "identity2".
|
||||
m : list of lists
|
||||
The raw matrix data as a SymPy Matrix.
|
||||
"""
|
||||
try:
|
||||
self._sympy_matrix(name, m)
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
self._numpy_matrix(name, m)
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
self._scipy_sparse_matrix(name, m)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def get_matrix(self, name, format):
|
||||
"""Get a cached matrix by name and format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
A descriptive name for the matrix, like "identity2".
|
||||
format : str
|
||||
The format desired ('sympy', 'numpy', 'scipy.sparse')
|
||||
"""
|
||||
m = self._cache.get((name, format))
|
||||
if m is not None:
|
||||
return m
|
||||
raise NotImplementedError(
|
||||
'Matrix with name %s and format %s is not available.' %
|
||||
(name, format)
|
||||
)
|
||||
|
||||
def _store_matrix(self, name, format, m):
|
||||
self._cache[(name, format)] = m
|
||||
|
||||
def _sympy_matrix(self, name, m):
|
||||
self._store_matrix(name, 'sympy', to_sympy(m))
|
||||
|
||||
def _numpy_matrix(self, name, m):
|
||||
m = to_numpy(m, dtype=self.dtype)
|
||||
self._store_matrix(name, 'numpy', m)
|
||||
|
||||
def _scipy_sparse_matrix(self, name, m):
|
||||
# TODO: explore different sparse formats. But sparse.kron will use
|
||||
# coo in most cases, so we use that here.
|
||||
m = to_scipy_sparse(m, dtype=self.dtype)
|
||||
self._store_matrix(name, 'scipy.sparse', m)
|
||||
|
||||
|
||||
sqrt2_inv = Pow(2, Rational(-1, 2), evaluate=False)
|
||||
|
||||
# Save the common matrices that we will need
|
||||
matrix_cache = MatrixCache()
|
||||
matrix_cache.cache_matrix('eye2', Matrix([[1, 0], [0, 1]]))
|
||||
matrix_cache.cache_matrix('op11', Matrix([[0, 0], [0, 1]])) # |1><1|
|
||||
matrix_cache.cache_matrix('op00', Matrix([[1, 0], [0, 0]])) # |0><0|
|
||||
matrix_cache.cache_matrix('op10', Matrix([[0, 0], [1, 0]])) # |1><0|
|
||||
matrix_cache.cache_matrix('op01', Matrix([[0, 1], [0, 0]])) # |0><1|
|
||||
matrix_cache.cache_matrix('X', Matrix([[0, 1], [1, 0]]))
|
||||
matrix_cache.cache_matrix('Y', Matrix([[0, -I], [I, 0]]))
|
||||
matrix_cache.cache_matrix('Z', Matrix([[1, 0], [0, -1]]))
|
||||
matrix_cache.cache_matrix('S', Matrix([[1, 0], [0, I]]))
|
||||
matrix_cache.cache_matrix('T', Matrix([[1, 0], [0, exp(I*pi/4)]]))
|
||||
matrix_cache.cache_matrix('H', sqrt2_inv*Matrix([[1, 1], [1, -1]]))
|
||||
matrix_cache.cache_matrix('Hsqrt2', Matrix([[1, 1], [1, -1]]))
|
||||
matrix_cache.cache_matrix(
|
||||
'SWAP', Matrix([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]))
|
||||
matrix_cache.cache_matrix('ZX', sqrt2_inv*Matrix([[1, 1], [1, -1]]))
|
||||
matrix_cache.cache_matrix('ZY', Matrix([[I, 0], [0, -I]]))
|
||||
@@ -0,0 +1,272 @@
|
||||
"""Utilities to deal with sympy.Matrix, numpy and scipy.sparse."""
|
||||
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.singleton import S
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.matrices import eye, zeros
|
||||
from sympy.external import import_module
|
||||
|
||||
__all__ = [
|
||||
'numpy_ndarray',
|
||||
'scipy_sparse_matrix',
|
||||
'sympy_to_numpy',
|
||||
'sympy_to_scipy_sparse',
|
||||
'numpy_to_sympy',
|
||||
'scipy_sparse_to_sympy',
|
||||
'flatten_scalar',
|
||||
'matrix_dagger',
|
||||
'to_sympy',
|
||||
'to_numpy',
|
||||
'to_scipy_sparse',
|
||||
'matrix_tensor_product',
|
||||
'matrix_zeros'
|
||||
]
|
||||
|
||||
# Conditionally define the base classes for numpy and scipy.sparse arrays
|
||||
# for use in isinstance tests.
|
||||
|
||||
np = import_module('numpy')
|
||||
if not np:
|
||||
class numpy_ndarray:
|
||||
pass
|
||||
else:
|
||||
numpy_ndarray = np.ndarray # type: ignore
|
||||
|
||||
scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
|
||||
if not scipy:
|
||||
class scipy_sparse_matrix:
|
||||
pass
|
||||
sparse = None
|
||||
else:
|
||||
sparse = scipy.sparse
|
||||
scipy_sparse_matrix = sparse.spmatrix # type: ignore
|
||||
|
||||
|
||||
def sympy_to_numpy(m, **options):
|
||||
"""Convert a SymPy Matrix/complex number to a numpy matrix or scalar."""
|
||||
if not np:
|
||||
raise ImportError
|
||||
dtype = options.get('dtype', 'complex')
|
||||
if isinstance(m, MatrixBase):
|
||||
return np.array(m.tolist(), dtype=dtype)
|
||||
elif isinstance(m, Expr):
|
||||
if m.is_Number or m.is_NumberSymbol or m == I:
|
||||
return complex(m)
|
||||
raise TypeError('Expected MatrixBase or complex scalar, got: %r' % m)
|
||||
|
||||
|
||||
def sympy_to_scipy_sparse(m, **options):
|
||||
"""Convert a SymPy Matrix/complex number to a numpy matrix or scalar."""
|
||||
if not np or not sparse:
|
||||
raise ImportError
|
||||
dtype = options.get('dtype', 'complex')
|
||||
if isinstance(m, MatrixBase):
|
||||
return sparse.csr_matrix(np.array(m.tolist(), dtype=dtype))
|
||||
elif isinstance(m, Expr):
|
||||
if m.is_Number or m.is_NumberSymbol or m == I:
|
||||
return complex(m)
|
||||
raise TypeError('Expected MatrixBase or complex scalar, got: %r' % m)
|
||||
|
||||
|
||||
def scipy_sparse_to_sympy(m, **options):
|
||||
"""Convert a scipy.sparse matrix to a SymPy matrix."""
|
||||
return MatrixBase(m.todense())
|
||||
|
||||
|
||||
def numpy_to_sympy(m, **options):
|
||||
"""Convert a numpy matrix to a SymPy matrix."""
|
||||
return MatrixBase(m)
|
||||
|
||||
|
||||
def to_sympy(m, **options):
|
||||
"""Convert a numpy/scipy.sparse matrix to a SymPy matrix."""
|
||||
if isinstance(m, MatrixBase):
|
||||
return m
|
||||
elif isinstance(m, numpy_ndarray):
|
||||
return numpy_to_sympy(m)
|
||||
elif isinstance(m, scipy_sparse_matrix):
|
||||
return scipy_sparse_to_sympy(m)
|
||||
elif isinstance(m, Expr):
|
||||
return m
|
||||
raise TypeError('Expected sympy/numpy/scipy.sparse matrix, got: %r' % m)
|
||||
|
||||
|
||||
def to_numpy(m, **options):
|
||||
"""Convert a sympy/scipy.sparse matrix to a numpy matrix."""
|
||||
dtype = options.get('dtype', 'complex')
|
||||
if isinstance(m, (MatrixBase, Expr)):
|
||||
return sympy_to_numpy(m, dtype=dtype)
|
||||
elif isinstance(m, numpy_ndarray):
|
||||
return m
|
||||
elif isinstance(m, scipy_sparse_matrix):
|
||||
return m.todense()
|
||||
raise TypeError('Expected sympy/numpy/scipy.sparse matrix, got: %r' % m)
|
||||
|
||||
|
||||
def to_scipy_sparse(m, **options):
|
||||
"""Convert a sympy/numpy matrix to a scipy.sparse matrix."""
|
||||
dtype = options.get('dtype', 'complex')
|
||||
if isinstance(m, (MatrixBase, Expr)):
|
||||
return sympy_to_scipy_sparse(m, dtype=dtype)
|
||||
elif isinstance(m, numpy_ndarray):
|
||||
if not sparse:
|
||||
raise ImportError
|
||||
return sparse.csr_matrix(m)
|
||||
elif isinstance(m, scipy_sparse_matrix):
|
||||
return m
|
||||
raise TypeError('Expected sympy/numpy/scipy.sparse matrix, got: %r' % m)
|
||||
|
||||
|
||||
def flatten_scalar(e):
|
||||
"""Flatten a 1x1 matrix to a scalar, return larger matrices unchanged."""
|
||||
if isinstance(e, MatrixBase):
|
||||
if e.shape == (1, 1):
|
||||
e = e[0]
|
||||
if isinstance(e, (numpy_ndarray, scipy_sparse_matrix)):
|
||||
if e.shape == (1, 1):
|
||||
e = complex(e[0, 0])
|
||||
return e
|
||||
|
||||
|
||||
def matrix_dagger(e):
|
||||
"""Return the dagger of a sympy/numpy/scipy.sparse matrix."""
|
||||
if isinstance(e, MatrixBase):
|
||||
return e.H
|
||||
elif isinstance(e, (numpy_ndarray, scipy_sparse_matrix)):
|
||||
return e.conjugate().transpose()
|
||||
raise TypeError('Expected sympy/numpy/scipy.sparse matrix, got: %r' % e)
|
||||
|
||||
|
||||
# TODO: Move this into sympy.matrices.
|
||||
def _sympy_tensor_product(*matrices):
|
||||
"""Compute the kronecker product of a sequence of SymPy Matrices.
|
||||
"""
|
||||
from sympy.matrices.expressions.kronecker import matrix_kronecker_product
|
||||
|
||||
return matrix_kronecker_product(*matrices)
|
||||
|
||||
|
||||
def _numpy_tensor_product(*product):
|
||||
"""numpy version of tensor product of multiple arguments."""
|
||||
if not np:
|
||||
raise ImportError
|
||||
answer = product[0]
|
||||
for item in product[1:]:
|
||||
answer = np.kron(answer, item)
|
||||
return answer
|
||||
|
||||
|
||||
def _scipy_sparse_tensor_product(*product):
|
||||
"""scipy.sparse version of tensor product of multiple arguments."""
|
||||
if not sparse:
|
||||
raise ImportError
|
||||
answer = product[0]
|
||||
for item in product[1:]:
|
||||
answer = sparse.kron(answer, item)
|
||||
# The final matrices will just be multiplied, so csr is a good final
|
||||
# sparse format.
|
||||
return sparse.csr_matrix(answer)
|
||||
|
||||
|
||||
def matrix_tensor_product(*product):
|
||||
"""Compute the matrix tensor product of sympy/numpy/scipy.sparse matrices."""
|
||||
if isinstance(product[0], MatrixBase):
|
||||
return _sympy_tensor_product(*product)
|
||||
elif isinstance(product[0], numpy_ndarray):
|
||||
return _numpy_tensor_product(*product)
|
||||
elif isinstance(product[0], scipy_sparse_matrix):
|
||||
return _scipy_sparse_tensor_product(*product)
|
||||
|
||||
|
||||
def _numpy_eye(n):
|
||||
"""numpy version of complex eye."""
|
||||
if not np:
|
||||
raise ImportError
|
||||
return np.array(np.eye(n, dtype='complex'))
|
||||
|
||||
|
||||
def _scipy_sparse_eye(n):
|
||||
"""scipy.sparse version of complex eye."""
|
||||
if not sparse:
|
||||
raise ImportError
|
||||
return sparse.eye(n, n, dtype='complex')
|
||||
|
||||
|
||||
def matrix_eye(n, **options):
|
||||
"""Get the version of eye and tensor_product for a given format."""
|
||||
format = options.get('format', 'sympy')
|
||||
if format == 'sympy':
|
||||
return eye(n)
|
||||
elif format == 'numpy':
|
||||
return _numpy_eye(n)
|
||||
elif format == 'scipy.sparse':
|
||||
return _scipy_sparse_eye(n)
|
||||
raise NotImplementedError('Invalid format: %r' % format)
|
||||
|
||||
|
||||
def _numpy_zeros(m, n, **options):
|
||||
"""numpy version of zeros."""
|
||||
dtype = options.get('dtype', 'float64')
|
||||
if not np:
|
||||
raise ImportError
|
||||
return np.zeros((m, n), dtype=dtype)
|
||||
|
||||
|
||||
def _scipy_sparse_zeros(m, n, **options):
|
||||
"""scipy.sparse version of zeros."""
|
||||
spmatrix = options.get('spmatrix', 'csr')
|
||||
dtype = options.get('dtype', 'float64')
|
||||
if not sparse:
|
||||
raise ImportError
|
||||
if spmatrix == 'lil':
|
||||
return sparse.lil_matrix((m, n), dtype=dtype)
|
||||
elif spmatrix == 'csr':
|
||||
return sparse.csr_matrix((m, n), dtype=dtype)
|
||||
|
||||
|
||||
def matrix_zeros(m, n, **options):
|
||||
""""Get a zeros matrix for a given format."""
|
||||
format = options.get('format', 'sympy')
|
||||
if format == 'sympy':
|
||||
return zeros(m, n)
|
||||
elif format == 'numpy':
|
||||
return _numpy_zeros(m, n, **options)
|
||||
elif format == 'scipy.sparse':
|
||||
return _scipy_sparse_zeros(m, n, **options)
|
||||
raise NotImplementedError('Invaild format: %r' % format)
|
||||
|
||||
|
||||
def _numpy_matrix_to_zero(e):
|
||||
"""Convert a numpy zero matrix to the zero scalar."""
|
||||
if not np:
|
||||
raise ImportError
|
||||
test = np.zeros_like(e)
|
||||
if np.allclose(e, test):
|
||||
return 0.0
|
||||
else:
|
||||
return e
|
||||
|
||||
|
||||
def _scipy_sparse_matrix_to_zero(e):
|
||||
"""Convert a scipy.sparse zero matrix to the zero scalar."""
|
||||
if not np:
|
||||
raise ImportError
|
||||
edense = e.todense()
|
||||
test = np.zeros_like(edense)
|
||||
if np.allclose(edense, test):
|
||||
return 0.0
|
||||
else:
|
||||
return e
|
||||
|
||||
|
||||
def matrix_to_zero(e):
|
||||
"""Convert a zero matrix to the scalar zero."""
|
||||
if isinstance(e, MatrixBase):
|
||||
if zeros(*e.shape) == e:
|
||||
e = S.Zero
|
||||
elif isinstance(e, numpy_ndarray):
|
||||
e = _numpy_matrix_to_zero(e)
|
||||
elif isinstance(e, scipy_sparse_matrix):
|
||||
e = _scipy_sparse_matrix_to_zero(e)
|
||||
return e
|
||||
@@ -0,0 +1,653 @@
|
||||
"""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 typing import Optional
|
||||
|
||||
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.kind import OperatorKind
|
||||
from sympy.physics.quantum.qexpr import QExpr, dispatch_method
|
||||
from sympy.matrices import eye
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
|
||||
|
||||
|
||||
__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
|
||||
"""
|
||||
is_hermitian: Optional[bool] = None
|
||||
is_unitary: Optional[bool] = None
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("O",)
|
||||
|
||||
kind = OperatorKind
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# 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 _apply_from_right_to(self, bra, **options):
|
||||
return None
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
is_unitary = True
|
||||
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.
|
||||
|
||||
.. deprecated:: 1.14.
|
||||
Use the scalar S.One instead as the multiplicative identity for
|
||||
operators and states.
|
||||
|
||||
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() # doctest: +SKIP
|
||||
I
|
||||
"""
|
||||
is_hermitian = True
|
||||
is_unitary = True
|
||||
@property
|
||||
def dimension(self):
|
||||
return self.N
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return (oo,)
|
||||
|
||||
def __init__(self, *args, **hints):
|
||||
sympy_deprecation_warning(
|
||||
"""
|
||||
IdentityOperator has been deprecated. In the future, please use
|
||||
S.One as the identity for quantum operators and states.
|
||||
""",
|
||||
deprecated_since_version="1.14",
|
||||
active_deprecations_target='deprecated-operator-identity',
|
||||
)
|
||||
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 _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
|
||||
|
||||
>>> 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 quantum expressions, outer products will be automatically
|
||||
identified and created::
|
||||
|
||||
>>> k*b
|
||||
|k><b|
|
||||
|
||||
However, the creation of inner products always has higher priority than that of
|
||||
outer products:
|
||||
|
||||
>>> b*k*b
|
||||
<b|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
|
||||
@@ -0,0 +1,290 @@
|
||||
"""Functions for reordering operator expressions."""
|
||||
|
||||
import warnings
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.power import Pow
|
||||
from sympy.physics.quantum import Commutator, AntiCommutator
|
||||
from sympy.physics.quantum.boson import BosonOp
|
||||
from sympy.physics.quantum.fermion import FermionOp
|
||||
|
||||
__all__ = [
|
||||
'normal_order',
|
||||
'normal_ordered_form'
|
||||
]
|
||||
|
||||
|
||||
def _expand_powers(factors):
|
||||
"""
|
||||
Helper function for normal_ordered_form and normal_order: Expand a
|
||||
power expression to a multiplication expression so that that the
|
||||
expression can be handled by the normal ordering functions.
|
||||
"""
|
||||
|
||||
new_factors = []
|
||||
for factor in factors.args:
|
||||
if (isinstance(factor, Pow)
|
||||
and isinstance(factor.args[1], Integer)
|
||||
and factor.args[1] > 0):
|
||||
for n in range(factor.args[1]):
|
||||
new_factors.append(factor.args[0])
|
||||
else:
|
||||
new_factors.append(factor)
|
||||
|
||||
return new_factors
|
||||
|
||||
def _normal_ordered_form_factor(product, independent=False, recursive_limit=10,
|
||||
_recursive_depth=0):
|
||||
"""
|
||||
Helper function for normal_ordered_form_factor: Write multiplication
|
||||
expression with bosonic or fermionic operators on normally ordered form,
|
||||
using the bosonic and fermionic commutation relations. The resulting
|
||||
operator expression is equivalent to the argument, but will in general be
|
||||
a sum of operator products instead of a simple product.
|
||||
"""
|
||||
|
||||
factors = _expand_powers(product)
|
||||
|
||||
new_factors = []
|
||||
n = 0
|
||||
while n < len(factors) - 1:
|
||||
current, next = factors[n], factors[n + 1]
|
||||
if any(not isinstance(f, (FermionOp, BosonOp)) for f in (current, next)):
|
||||
new_factors.append(current)
|
||||
n += 1
|
||||
continue
|
||||
|
||||
key_1 = (current.is_annihilation, str(current.name))
|
||||
key_2 = (next.is_annihilation, str(next.name))
|
||||
|
||||
if key_1 <= key_2:
|
||||
new_factors.append(current)
|
||||
n += 1
|
||||
continue
|
||||
|
||||
n += 2
|
||||
if current.is_annihilation and not next.is_annihilation:
|
||||
if isinstance(current, BosonOp) and isinstance(next, BosonOp):
|
||||
if current.args[0] != next.args[0]:
|
||||
if independent:
|
||||
c = 0
|
||||
else:
|
||||
c = Commutator(current, next)
|
||||
new_factors.append(next * current + c)
|
||||
else:
|
||||
new_factors.append(next * current + 1)
|
||||
elif isinstance(current, FermionOp) and isinstance(next, FermionOp):
|
||||
if current.args[0] != next.args[0]:
|
||||
if independent:
|
||||
c = 0
|
||||
else:
|
||||
c = AntiCommutator(current, next)
|
||||
new_factors.append(-next * current + c)
|
||||
else:
|
||||
new_factors.append(-next * current + 1)
|
||||
elif (current.is_annihilation == next.is_annihilation and
|
||||
isinstance(current, FermionOp) and isinstance(next, FermionOp)):
|
||||
new_factors.append(-next * current)
|
||||
else:
|
||||
new_factors.append(next * current)
|
||||
|
||||
if n == len(factors) - 1:
|
||||
new_factors.append(factors[-1])
|
||||
|
||||
if new_factors == factors:
|
||||
return product
|
||||
else:
|
||||
expr = Mul(*new_factors).expand()
|
||||
return normal_ordered_form(expr,
|
||||
recursive_limit=recursive_limit,
|
||||
_recursive_depth=_recursive_depth + 1,
|
||||
independent=independent)
|
||||
|
||||
|
||||
def _normal_ordered_form_terms(expr, independent=False, recursive_limit=10,
|
||||
_recursive_depth=0):
|
||||
"""
|
||||
Helper function for normal_ordered_form: loop through each term in an
|
||||
addition expression and call _normal_ordered_form_factor to perform the
|
||||
factor to an normally ordered expression.
|
||||
"""
|
||||
|
||||
new_terms = []
|
||||
for term in expr.args:
|
||||
if isinstance(term, Mul):
|
||||
new_term = _normal_ordered_form_factor(
|
||||
term, recursive_limit=recursive_limit,
|
||||
_recursive_depth=_recursive_depth, independent=independent)
|
||||
new_terms.append(new_term)
|
||||
else:
|
||||
new_terms.append(term)
|
||||
|
||||
return Add(*new_terms)
|
||||
|
||||
|
||||
def normal_ordered_form(expr, independent=False, recursive_limit=10,
|
||||
_recursive_depth=0):
|
||||
"""Write an expression with bosonic or fermionic operators on normal
|
||||
ordered form, where each term is normally ordered. Note that this
|
||||
normal ordered form is equivalent to the original expression.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
expr : expression
|
||||
The expression write on normal ordered form.
|
||||
independent : bool (default False)
|
||||
Whether to consider operator with different names as operating in
|
||||
different Hilbert spaces. If False, the (anti-)commutation is left
|
||||
explicit.
|
||||
recursive_limit : int (default 10)
|
||||
The number of allowed recursive applications of the function.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import Dagger
|
||||
>>> from sympy.physics.quantum.boson import BosonOp
|
||||
>>> from sympy.physics.quantum.operatorordering import normal_ordered_form
|
||||
>>> a = BosonOp("a")
|
||||
>>> normal_ordered_form(a * Dagger(a))
|
||||
1 + Dagger(a)*a
|
||||
"""
|
||||
|
||||
if _recursive_depth > recursive_limit:
|
||||
warnings.warn("Too many recursions, aborting")
|
||||
return expr
|
||||
|
||||
if isinstance(expr, Add):
|
||||
return _normal_ordered_form_terms(expr,
|
||||
recursive_limit=recursive_limit,
|
||||
_recursive_depth=_recursive_depth,
|
||||
independent=independent)
|
||||
elif isinstance(expr, Mul):
|
||||
return _normal_ordered_form_factor(expr,
|
||||
recursive_limit=recursive_limit,
|
||||
_recursive_depth=_recursive_depth,
|
||||
independent=independent)
|
||||
else:
|
||||
return expr
|
||||
|
||||
|
||||
def _normal_order_factor(product, recursive_limit=10, _recursive_depth=0):
|
||||
"""
|
||||
Helper function for normal_order: Normal order a multiplication expression
|
||||
with bosonic or fermionic operators. In general the resulting operator
|
||||
expression will not be equivalent to original product.
|
||||
"""
|
||||
|
||||
factors = _expand_powers(product)
|
||||
|
||||
n = 0
|
||||
new_factors = []
|
||||
while n < len(factors) - 1:
|
||||
|
||||
if (isinstance(factors[n], BosonOp) and
|
||||
factors[n].is_annihilation):
|
||||
# boson
|
||||
if not isinstance(factors[n + 1], BosonOp):
|
||||
new_factors.append(factors[n])
|
||||
else:
|
||||
if factors[n + 1].is_annihilation:
|
||||
new_factors.append(factors[n])
|
||||
else:
|
||||
if factors[n].args[0] != factors[n + 1].args[0]:
|
||||
new_factors.append(factors[n + 1] * factors[n])
|
||||
else:
|
||||
new_factors.append(factors[n + 1] * factors[n])
|
||||
n += 1
|
||||
|
||||
elif (isinstance(factors[n], FermionOp) and
|
||||
factors[n].is_annihilation):
|
||||
# fermion
|
||||
if not isinstance(factors[n + 1], FermionOp):
|
||||
new_factors.append(factors[n])
|
||||
else:
|
||||
if factors[n + 1].is_annihilation:
|
||||
new_factors.append(factors[n])
|
||||
else:
|
||||
if factors[n].args[0] != factors[n + 1].args[0]:
|
||||
new_factors.append(-factors[n + 1] * factors[n])
|
||||
else:
|
||||
new_factors.append(-factors[n + 1] * factors[n])
|
||||
n += 1
|
||||
|
||||
else:
|
||||
new_factors.append(factors[n])
|
||||
|
||||
n += 1
|
||||
|
||||
if n == len(factors) - 1:
|
||||
new_factors.append(factors[-1])
|
||||
|
||||
if new_factors == factors:
|
||||
return product
|
||||
else:
|
||||
expr = Mul(*new_factors).expand()
|
||||
return normal_order(expr,
|
||||
recursive_limit=recursive_limit,
|
||||
_recursive_depth=_recursive_depth + 1)
|
||||
|
||||
|
||||
def _normal_order_terms(expr, recursive_limit=10, _recursive_depth=0):
|
||||
"""
|
||||
Helper function for normal_order: look through each term in an addition
|
||||
expression and call _normal_order_factor to perform the normal ordering
|
||||
on the factors.
|
||||
"""
|
||||
|
||||
new_terms = []
|
||||
for term in expr.args:
|
||||
if isinstance(term, Mul):
|
||||
new_term = _normal_order_factor(term,
|
||||
recursive_limit=recursive_limit,
|
||||
_recursive_depth=_recursive_depth)
|
||||
new_terms.append(new_term)
|
||||
else:
|
||||
new_terms.append(term)
|
||||
|
||||
return Add(*new_terms)
|
||||
|
||||
|
||||
def normal_order(expr, recursive_limit=10, _recursive_depth=0):
|
||||
"""Normal order an expression with bosonic or fermionic operators. Note
|
||||
that this normal order is not equivalent to the original expression, but
|
||||
the creation and annihilation operators in each term in expr is reordered
|
||||
so that the expression becomes normal ordered.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
expr : expression
|
||||
The expression to normal order.
|
||||
|
||||
recursive_limit : int (default 10)
|
||||
The number of allowed recursive applications of the function.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import Dagger
|
||||
>>> from sympy.physics.quantum.boson import BosonOp
|
||||
>>> from sympy.physics.quantum.operatorordering import normal_order
|
||||
>>> a = BosonOp("a")
|
||||
>>> normal_order(a * Dagger(a))
|
||||
Dagger(a)*a
|
||||
"""
|
||||
if _recursive_depth > recursive_limit:
|
||||
warnings.warn("Too many recursions, aborting")
|
||||
return expr
|
||||
|
||||
if isinstance(expr, Add):
|
||||
return _normal_order_terms(expr, recursive_limit=recursive_limit,
|
||||
_recursive_depth=_recursive_depth)
|
||||
elif isinstance(expr, Mul):
|
||||
return _normal_order_factor(expr, recursive_limit=recursive_limit,
|
||||
_recursive_depth=_recursive_depth)
|
||||
else:
|
||||
return expr
|
||||
@@ -0,0 +1,279 @@
|
||||
""" A module for mapping operators to their corresponding eigenstates
|
||||
and vice versa
|
||||
|
||||
It contains a global dictionary with eigenstate-operator pairings.
|
||||
If a new state-operator pair is created, this dictionary should be
|
||||
updated as well.
|
||||
|
||||
It also contains functions operators_to_state and state_to_operators
|
||||
for mapping between the two. These can handle both classes and
|
||||
instances of operators and states. See the individual function
|
||||
descriptions for details.
|
||||
|
||||
TODO List:
|
||||
- Update the dictionary with a complete list of state-operator pairs
|
||||
"""
|
||||
|
||||
from sympy.physics.quantum.cartesian import (XOp, YOp, ZOp, XKet, PxOp, PxKet,
|
||||
PositionKet3D)
|
||||
from sympy.physics.quantum.operator import Operator
|
||||
from sympy.physics.quantum.state import StateBase, BraBase, Ket
|
||||
from sympy.physics.quantum.spin import (JxOp, JyOp, JzOp, J2Op, JxKet, JyKet,
|
||||
JzKet)
|
||||
|
||||
__all__ = [
|
||||
'operators_to_state',
|
||||
'state_to_operators'
|
||||
]
|
||||
|
||||
#state_mapping stores the mappings between states and their associated
|
||||
#operators or tuples of operators. This should be updated when new
|
||||
#classes are written! Entries are of the form PxKet : PxOp or
|
||||
#something like 3DKet : (ROp, ThetaOp, PhiOp)
|
||||
|
||||
#frozenset is used so that the reverse mapping can be made
|
||||
#(regular sets are not hashable because they are mutable
|
||||
state_mapping = { JxKet: frozenset((J2Op, JxOp)),
|
||||
JyKet: frozenset((J2Op, JyOp)),
|
||||
JzKet: frozenset((J2Op, JzOp)),
|
||||
Ket: Operator,
|
||||
PositionKet3D: frozenset((XOp, YOp, ZOp)),
|
||||
PxKet: PxOp,
|
||||
XKet: XOp }
|
||||
|
||||
op_mapping = {v: k for k, v in state_mapping.items()}
|
||||
|
||||
|
||||
def operators_to_state(operators, **options):
|
||||
""" Returns the eigenstate of the given operator or set of operators
|
||||
|
||||
A global function for mapping operator classes to their associated
|
||||
states. It takes either an Operator or a set of operators and
|
||||
returns the state associated with these.
|
||||
|
||||
This function can handle both instances of a given operator or
|
||||
just the class itself (i.e. both XOp() and XOp)
|
||||
|
||||
There are multiple use cases to consider:
|
||||
|
||||
1) A class or set of classes is passed: First, we try to
|
||||
instantiate default instances for these operators. If this fails,
|
||||
then the class is simply returned. If we succeed in instantiating
|
||||
default instances, then we try to call state._operators_to_state
|
||||
on the operator instances. If this fails, the class is returned.
|
||||
Otherwise, the instance returned by _operators_to_state is returned.
|
||||
|
||||
2) An instance or set of instances is passed: In this case,
|
||||
state._operators_to_state is called on the instances passed. If
|
||||
this fails, a state class is returned. If the method returns an
|
||||
instance, that instance is returned.
|
||||
|
||||
In both cases, if the operator class or set does not exist in the
|
||||
state_mapping dictionary, None is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
arg: Operator or set
|
||||
The class or instance of the operator or set of operators
|
||||
to be mapped to a state
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.cartesian import XOp, PxOp
|
||||
>>> from sympy.physics.quantum.operatorset import operators_to_state
|
||||
>>> from sympy.physics.quantum.operator import Operator
|
||||
>>> operators_to_state(XOp)
|
||||
|x>
|
||||
>>> operators_to_state(XOp())
|
||||
|x>
|
||||
>>> operators_to_state(PxOp)
|
||||
|px>
|
||||
>>> operators_to_state(PxOp())
|
||||
|px>
|
||||
>>> operators_to_state(Operator)
|
||||
|psi>
|
||||
>>> operators_to_state(Operator())
|
||||
|psi>
|
||||
"""
|
||||
|
||||
if not (isinstance(operators, (Operator, set)) or issubclass(operators, Operator)):
|
||||
raise NotImplementedError("Argument is not an Operator or a set!")
|
||||
|
||||
if isinstance(operators, set):
|
||||
for s in operators:
|
||||
if not (isinstance(s, Operator)
|
||||
or issubclass(s, Operator)):
|
||||
raise NotImplementedError("Set is not all Operators!")
|
||||
|
||||
ops = frozenset(operators)
|
||||
|
||||
if ops in op_mapping: # ops is a list of classes in this case
|
||||
#Try to get an object from default instances of the
|
||||
#operators...if this fails, return the class
|
||||
try:
|
||||
op_instances = [op() for op in ops]
|
||||
ret = _get_state(op_mapping[ops], set(op_instances), **options)
|
||||
except NotImplementedError:
|
||||
ret = op_mapping[ops]
|
||||
|
||||
return ret
|
||||
else:
|
||||
tmp = [type(o) for o in ops]
|
||||
classes = frozenset(tmp)
|
||||
|
||||
if classes in op_mapping:
|
||||
ret = _get_state(op_mapping[classes], ops, **options)
|
||||
else:
|
||||
ret = None
|
||||
|
||||
return ret
|
||||
else:
|
||||
if operators in op_mapping:
|
||||
try:
|
||||
op_instance = operators()
|
||||
ret = _get_state(op_mapping[operators], op_instance, **options)
|
||||
except NotImplementedError:
|
||||
ret = op_mapping[operators]
|
||||
|
||||
return ret
|
||||
elif type(operators) in op_mapping:
|
||||
return _get_state(op_mapping[type(operators)], operators, **options)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def state_to_operators(state, **options):
|
||||
""" Returns the operator or set of operators corresponding to the
|
||||
given eigenstate
|
||||
|
||||
A global function for mapping state classes to their associated
|
||||
operators or sets of operators. It takes either a state class
|
||||
or instance.
|
||||
|
||||
This function can handle both instances of a given state or just
|
||||
the class itself (i.e. both XKet() and XKet)
|
||||
|
||||
There are multiple use cases to consider:
|
||||
|
||||
1) A state class is passed: In this case, we first try
|
||||
instantiating a default instance of the class. If this succeeds,
|
||||
then we try to call state._state_to_operators on that instance.
|
||||
If the creation of the default instance or if the calling of
|
||||
_state_to_operators fails, then either an operator class or set of
|
||||
operator classes is returned. Otherwise, the appropriate
|
||||
operator instances are returned.
|
||||
|
||||
2) A state instance is returned: Here, state._state_to_operators
|
||||
is called for the instance. If this fails, then a class or set of
|
||||
operator classes is returned. Otherwise, the instances are returned.
|
||||
|
||||
In either case, if the state's class does not exist in
|
||||
state_mapping, None is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
arg: StateBase class or instance (or subclasses)
|
||||
The class or instance of the state to be mapped to an
|
||||
operator or set of operators
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.cartesian import XKet, PxKet, XBra, PxBra
|
||||
>>> from sympy.physics.quantum.operatorset import state_to_operators
|
||||
>>> from sympy.physics.quantum.state import Ket, Bra
|
||||
>>> state_to_operators(XKet)
|
||||
X
|
||||
>>> state_to_operators(XKet())
|
||||
X
|
||||
>>> state_to_operators(PxKet)
|
||||
Px
|
||||
>>> state_to_operators(PxKet())
|
||||
Px
|
||||
>>> state_to_operators(PxBra)
|
||||
Px
|
||||
>>> state_to_operators(XBra)
|
||||
X
|
||||
>>> state_to_operators(Ket)
|
||||
O
|
||||
>>> state_to_operators(Bra)
|
||||
O
|
||||
"""
|
||||
|
||||
if not (isinstance(state, StateBase) or issubclass(state, StateBase)):
|
||||
raise NotImplementedError("Argument is not a state!")
|
||||
|
||||
if state in state_mapping: # state is a class
|
||||
state_inst = _make_default(state)
|
||||
try:
|
||||
ret = _get_ops(state_inst,
|
||||
_make_set(state_mapping[state]), **options)
|
||||
except (NotImplementedError, TypeError):
|
||||
ret = state_mapping[state]
|
||||
elif type(state) in state_mapping:
|
||||
ret = _get_ops(state,
|
||||
_make_set(state_mapping[type(state)]), **options)
|
||||
elif isinstance(state, BraBase) and state.dual_class() in state_mapping:
|
||||
ret = _get_ops(state,
|
||||
_make_set(state_mapping[state.dual_class()]))
|
||||
elif issubclass(state, BraBase) and state.dual_class() in state_mapping:
|
||||
state_inst = _make_default(state)
|
||||
try:
|
||||
ret = _get_ops(state_inst,
|
||||
_make_set(state_mapping[state.dual_class()]))
|
||||
except (NotImplementedError, TypeError):
|
||||
ret = state_mapping[state.dual_class()]
|
||||
else:
|
||||
ret = None
|
||||
|
||||
return _make_set(ret)
|
||||
|
||||
|
||||
def _make_default(expr):
|
||||
# XXX: Catching TypeError like this is a bad way of distinguishing between
|
||||
# classes and instances. The logic using this function should be rewritten
|
||||
# somehow.
|
||||
try:
|
||||
ret = expr()
|
||||
except TypeError:
|
||||
ret = expr
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _get_state(state_class, ops, **options):
|
||||
# Try to get a state instance from the operator INSTANCES.
|
||||
# If this fails, get the class
|
||||
try:
|
||||
ret = state_class._operators_to_state(ops, **options)
|
||||
except NotImplementedError:
|
||||
ret = _make_default(state_class)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _get_ops(state_inst, op_classes, **options):
|
||||
# Try to get operator instances from the state INSTANCE.
|
||||
# If this fails, just return the classes
|
||||
try:
|
||||
ret = state_inst._state_to_operators(op_classes, **options)
|
||||
except NotImplementedError:
|
||||
if isinstance(op_classes, (set, tuple, frozenset)):
|
||||
ret = tuple(_make_default(x) for x in op_classes)
|
||||
else:
|
||||
ret = _make_default(op_classes)
|
||||
|
||||
if isinstance(ret, set) and len(ret) == 1:
|
||||
return ret[0]
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _make_set(ops):
|
||||
if isinstance(ops, (tuple, list, frozenset)):
|
||||
return set(ops)
|
||||
else:
|
||||
return ops
|
||||
@@ -0,0 +1,675 @@
|
||||
"""Pauli operators and states"""
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.physics.quantum import Operator, Ket, Bra
|
||||
from sympy.physics.quantum import ComplexSpace
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
|
||||
__all__ = [
|
||||
'SigmaX', 'SigmaY', 'SigmaZ', 'SigmaMinus', 'SigmaPlus', 'SigmaZKet',
|
||||
'SigmaZBra', 'qsimplify_pauli'
|
||||
]
|
||||
|
||||
|
||||
class SigmaOpBase(Operator):
|
||||
"""Pauli sigma operator, base class"""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def use_name(self):
|
||||
return bool(self.args[0]) is not False
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return (False,)
|
||||
|
||||
def __new__(cls, *args, **hints):
|
||||
return Operator.__new__(cls, *args, **hints)
|
||||
|
||||
def _eval_commutator_BosonOp(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
|
||||
class SigmaX(SigmaOpBase):
|
||||
"""Pauli sigma x operator
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
name : str
|
||||
An optional string that labels the operator. Pauli operators with
|
||||
different names commute.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import represent
|
||||
>>> from sympy.physics.quantum.pauli import SigmaX
|
||||
>>> sx = SigmaX()
|
||||
>>> sx
|
||||
SigmaX()
|
||||
>>> represent(sx)
|
||||
Matrix([
|
||||
[0, 1],
|
||||
[1, 0]])
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **hints):
|
||||
return SigmaOpBase.__new__(cls, *args, **hints)
|
||||
|
||||
def _eval_commutator_SigmaY(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return 2 * I * SigmaZ(self.name)
|
||||
|
||||
def _eval_commutator_SigmaZ(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return - 2 * I * SigmaY(self.name)
|
||||
|
||||
def _eval_commutator_BosonOp(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_anticommutator_SigmaY(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_anticommutator_SigmaZ(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
if self.use_name:
|
||||
return r'{\sigma_x^{(%s)}}' % str(self.name)
|
||||
else:
|
||||
return r'{\sigma_x}'
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
return 'SigmaX()'
|
||||
|
||||
def _eval_power(self, e):
|
||||
if e.is_Integer and e.is_positive:
|
||||
return SigmaX(self.name).__pow__(int(e) % 2)
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
format = options.get('format', 'sympy')
|
||||
if format == 'sympy':
|
||||
return Matrix([[0, 1], [1, 0]])
|
||||
else:
|
||||
raise NotImplementedError('Representation in format ' +
|
||||
format + ' not implemented.')
|
||||
|
||||
|
||||
class SigmaY(SigmaOpBase):
|
||||
"""Pauli sigma y operator
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
name : str
|
||||
An optional string that labels the operator. Pauli operators with
|
||||
different names commute.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import represent
|
||||
>>> from sympy.physics.quantum.pauli import SigmaY
|
||||
>>> sy = SigmaY()
|
||||
>>> sy
|
||||
SigmaY()
|
||||
>>> represent(sy)
|
||||
Matrix([
|
||||
[0, -I],
|
||||
[I, 0]])
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **hints):
|
||||
return SigmaOpBase.__new__(cls, *args)
|
||||
|
||||
def _eval_commutator_SigmaZ(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return 2 * I * SigmaX(self.name)
|
||||
|
||||
def _eval_commutator_SigmaX(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return - 2 * I * SigmaZ(self.name)
|
||||
|
||||
def _eval_anticommutator_SigmaX(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_anticommutator_SigmaZ(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
if self.use_name:
|
||||
return r'{\sigma_y^{(%s)}}' % str(self.name)
|
||||
else:
|
||||
return r'{\sigma_y}'
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
return 'SigmaY()'
|
||||
|
||||
def _eval_power(self, e):
|
||||
if e.is_Integer and e.is_positive:
|
||||
return SigmaY(self.name).__pow__(int(e) % 2)
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
format = options.get('format', 'sympy')
|
||||
if format == 'sympy':
|
||||
return Matrix([[0, -I], [I, 0]])
|
||||
else:
|
||||
raise NotImplementedError('Representation in format ' +
|
||||
format + ' not implemented.')
|
||||
|
||||
|
||||
class SigmaZ(SigmaOpBase):
|
||||
"""Pauli sigma z operator
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
name : str
|
||||
An optional string that labels the operator. Pauli operators with
|
||||
different names commute.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import represent
|
||||
>>> from sympy.physics.quantum.pauli import SigmaZ
|
||||
>>> sz = SigmaZ()
|
||||
>>> sz ** 3
|
||||
SigmaZ()
|
||||
>>> represent(sz)
|
||||
Matrix([
|
||||
[1, 0],
|
||||
[0, -1]])
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **hints):
|
||||
return SigmaOpBase.__new__(cls, *args)
|
||||
|
||||
def _eval_commutator_SigmaX(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return 2 * I * SigmaY(self.name)
|
||||
|
||||
def _eval_commutator_SigmaY(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return - 2 * I * SigmaX(self.name)
|
||||
|
||||
def _eval_anticommutator_SigmaX(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_anticommutator_SigmaY(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
if self.use_name:
|
||||
return r'{\sigma_z^{(%s)}}' % str(self.name)
|
||||
else:
|
||||
return r'{\sigma_z}'
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
return 'SigmaZ()'
|
||||
|
||||
def _eval_power(self, e):
|
||||
if e.is_Integer and e.is_positive:
|
||||
return SigmaZ(self.name).__pow__(int(e) % 2)
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
format = options.get('format', 'sympy')
|
||||
if format == 'sympy':
|
||||
return Matrix([[1, 0], [0, -1]])
|
||||
else:
|
||||
raise NotImplementedError('Representation in format ' +
|
||||
format + ' not implemented.')
|
||||
|
||||
|
||||
class SigmaMinus(SigmaOpBase):
|
||||
"""Pauli sigma minus operator
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
name : str
|
||||
An optional string that labels the operator. Pauli operators with
|
||||
different names commute.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import represent, Dagger
|
||||
>>> from sympy.physics.quantum.pauli import SigmaMinus
|
||||
>>> sm = SigmaMinus()
|
||||
>>> sm
|
||||
SigmaMinus()
|
||||
>>> Dagger(sm)
|
||||
SigmaPlus()
|
||||
>>> represent(sm)
|
||||
Matrix([
|
||||
[0, 0],
|
||||
[1, 0]])
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **hints):
|
||||
return SigmaOpBase.__new__(cls, *args)
|
||||
|
||||
def _eval_commutator_SigmaX(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return -SigmaZ(self.name)
|
||||
|
||||
def _eval_commutator_SigmaY(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return I * SigmaZ(self.name)
|
||||
|
||||
def _eval_commutator_SigmaZ(self, other, **hints):
|
||||
return 2 * self
|
||||
|
||||
def _eval_commutator_SigmaMinus(self, other, **hints):
|
||||
return SigmaZ(self.name)
|
||||
|
||||
def _eval_anticommutator_SigmaZ(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_anticommutator_SigmaX(self, other, **hints):
|
||||
return S.One
|
||||
|
||||
def _eval_anticommutator_SigmaY(self, other, **hints):
|
||||
return I * S.NegativeOne
|
||||
|
||||
def _eval_anticommutator_SigmaPlus(self, other, **hints):
|
||||
return S.One
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return SigmaPlus(self.name)
|
||||
|
||||
def _eval_power(self, e):
|
||||
if e.is_Integer and e.is_positive:
|
||||
return S.Zero
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
if self.use_name:
|
||||
return r'{\sigma_-^{(%s)}}' % str(self.name)
|
||||
else:
|
||||
return r'{\sigma_-}'
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
return 'SigmaMinus()'
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
format = options.get('format', 'sympy')
|
||||
if format == 'sympy':
|
||||
return Matrix([[0, 0], [1, 0]])
|
||||
else:
|
||||
raise NotImplementedError('Representation in format ' +
|
||||
format + ' not implemented.')
|
||||
|
||||
|
||||
class SigmaPlus(SigmaOpBase):
|
||||
"""Pauli sigma plus operator
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
name : str
|
||||
An optional string that labels the operator. Pauli operators with
|
||||
different names commute.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import represent, Dagger
|
||||
>>> from sympy.physics.quantum.pauli import SigmaPlus
|
||||
>>> sp = SigmaPlus()
|
||||
>>> sp
|
||||
SigmaPlus()
|
||||
>>> Dagger(sp)
|
||||
SigmaMinus()
|
||||
>>> represent(sp)
|
||||
Matrix([
|
||||
[0, 1],
|
||||
[0, 0]])
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **hints):
|
||||
return SigmaOpBase.__new__(cls, *args)
|
||||
|
||||
def _eval_commutator_SigmaX(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return SigmaZ(self.name)
|
||||
|
||||
def _eval_commutator_SigmaY(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return I * SigmaZ(self.name)
|
||||
|
||||
def _eval_commutator_SigmaZ(self, other, **hints):
|
||||
if self.name != other.name:
|
||||
return S.Zero
|
||||
else:
|
||||
return -2 * self
|
||||
|
||||
def _eval_commutator_SigmaMinus(self, other, **hints):
|
||||
return SigmaZ(self.name)
|
||||
|
||||
def _eval_anticommutator_SigmaZ(self, other, **hints):
|
||||
return S.Zero
|
||||
|
||||
def _eval_anticommutator_SigmaX(self, other, **hints):
|
||||
return S.One
|
||||
|
||||
def _eval_anticommutator_SigmaY(self, other, **hints):
|
||||
return I
|
||||
|
||||
def _eval_anticommutator_SigmaMinus(self, other, **hints):
|
||||
return S.One
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return SigmaMinus(self.name)
|
||||
|
||||
def _eval_mul(self, other):
|
||||
return self * other
|
||||
|
||||
def _eval_power(self, e):
|
||||
if e.is_Integer and e.is_positive:
|
||||
return S.Zero
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
if self.use_name:
|
||||
return r'{\sigma_+^{(%s)}}' % str(self.name)
|
||||
else:
|
||||
return r'{\sigma_+}'
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
return 'SigmaPlus()'
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
format = options.get('format', 'sympy')
|
||||
if format == 'sympy':
|
||||
return Matrix([[0, 1], [0, 0]])
|
||||
else:
|
||||
raise NotImplementedError('Representation in format ' +
|
||||
format + ' not implemented.')
|
||||
|
||||
|
||||
class SigmaZKet(Ket):
|
||||
"""Ket for a two-level system quantum system.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Number
|
||||
The state number (0 or 1).
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, n):
|
||||
if n not in (0, 1):
|
||||
raise ValueError("n must be 0 or 1")
|
||||
return Ket.__new__(cls, n)
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
return self.label[0]
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return SigmaZBra
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return ComplexSpace(2)
|
||||
|
||||
def _eval_innerproduct_SigmaZBra(self, bra, **hints):
|
||||
return KroneckerDelta(self.n, bra.n)
|
||||
|
||||
def _apply_from_right_to_SigmaZ(self, op, **options):
|
||||
if self.n == 0:
|
||||
return self
|
||||
else:
|
||||
return S.NegativeOne * self
|
||||
|
||||
def _apply_from_right_to_SigmaX(self, op, **options):
|
||||
return SigmaZKet(1) if self.n == 0 else SigmaZKet(0)
|
||||
|
||||
def _apply_from_right_to_SigmaY(self, op, **options):
|
||||
return I * SigmaZKet(1) if self.n == 0 else (-I) * SigmaZKet(0)
|
||||
|
||||
def _apply_from_right_to_SigmaMinus(self, op, **options):
|
||||
if self.n == 0:
|
||||
return SigmaZKet(1)
|
||||
else:
|
||||
return S.Zero
|
||||
|
||||
def _apply_from_right_to_SigmaPlus(self, op, **options):
|
||||
if self.n == 0:
|
||||
return S.Zero
|
||||
else:
|
||||
return SigmaZKet(0)
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
format = options.get('format', 'sympy')
|
||||
if format == 'sympy':
|
||||
return Matrix([[1], [0]]) if self.n == 0 else Matrix([[0], [1]])
|
||||
else:
|
||||
raise NotImplementedError('Representation in format ' +
|
||||
format + ' not implemented.')
|
||||
|
||||
|
||||
class SigmaZBra(Bra):
|
||||
"""Bra for a two-level quantum system.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : Number
|
||||
The state number (0 or 1).
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, n):
|
||||
if n not in (0, 1):
|
||||
raise ValueError("n must be 0 or 1")
|
||||
return Bra.__new__(cls, n)
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
return self.label[0]
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return SigmaZKet
|
||||
|
||||
|
||||
def _qsimplify_pauli_product(a, b):
|
||||
"""
|
||||
Internal helper function for simplifying products of Pauli operators.
|
||||
"""
|
||||
if not (isinstance(a, SigmaOpBase) and isinstance(b, SigmaOpBase)):
|
||||
return Mul(a, b)
|
||||
|
||||
if a.name != b.name:
|
||||
# Pauli matrices with different labels commute; sort by name
|
||||
if a.name < b.name:
|
||||
return Mul(a, b)
|
||||
else:
|
||||
return Mul(b, a)
|
||||
|
||||
elif isinstance(a, SigmaX):
|
||||
|
||||
if isinstance(b, SigmaX):
|
||||
return S.One
|
||||
|
||||
if isinstance(b, SigmaY):
|
||||
return I * SigmaZ(a.name)
|
||||
|
||||
if isinstance(b, SigmaZ):
|
||||
return - I * SigmaY(a.name)
|
||||
|
||||
if isinstance(b, SigmaMinus):
|
||||
return (S.Half + SigmaZ(a.name)/2)
|
||||
|
||||
if isinstance(b, SigmaPlus):
|
||||
return (S.Half - SigmaZ(a.name)/2)
|
||||
|
||||
elif isinstance(a, SigmaY):
|
||||
|
||||
if isinstance(b, SigmaX):
|
||||
return - I * SigmaZ(a.name)
|
||||
|
||||
if isinstance(b, SigmaY):
|
||||
return S.One
|
||||
|
||||
if isinstance(b, SigmaZ):
|
||||
return I * SigmaX(a.name)
|
||||
|
||||
if isinstance(b, SigmaMinus):
|
||||
return -I * (S.One + SigmaZ(a.name))/2
|
||||
|
||||
if isinstance(b, SigmaPlus):
|
||||
return I * (S.One - SigmaZ(a.name))/2
|
||||
|
||||
elif isinstance(a, SigmaZ):
|
||||
|
||||
if isinstance(b, SigmaX):
|
||||
return I * SigmaY(a.name)
|
||||
|
||||
if isinstance(b, SigmaY):
|
||||
return - I * SigmaX(a.name)
|
||||
|
||||
if isinstance(b, SigmaZ):
|
||||
return S.One
|
||||
|
||||
if isinstance(b, SigmaMinus):
|
||||
return - SigmaMinus(a.name)
|
||||
|
||||
if isinstance(b, SigmaPlus):
|
||||
return SigmaPlus(a.name)
|
||||
|
||||
elif isinstance(a, SigmaMinus):
|
||||
|
||||
if isinstance(b, SigmaX):
|
||||
return (S.One - SigmaZ(a.name))/2
|
||||
|
||||
if isinstance(b, SigmaY):
|
||||
return - I * (S.One - SigmaZ(a.name))/2
|
||||
|
||||
if isinstance(b, SigmaZ):
|
||||
# (SigmaX(a.name) - I * SigmaY(a.name))/2
|
||||
return SigmaMinus(b.name)
|
||||
|
||||
if isinstance(b, SigmaMinus):
|
||||
return S.Zero
|
||||
|
||||
if isinstance(b, SigmaPlus):
|
||||
return S.Half - SigmaZ(a.name)/2
|
||||
|
||||
elif isinstance(a, SigmaPlus):
|
||||
|
||||
if isinstance(b, SigmaX):
|
||||
return (S.One + SigmaZ(a.name))/2
|
||||
|
||||
if isinstance(b, SigmaY):
|
||||
return I * (S.One + SigmaZ(a.name))/2
|
||||
|
||||
if isinstance(b, SigmaZ):
|
||||
#-(SigmaX(a.name) + I * SigmaY(a.name))/2
|
||||
return -SigmaPlus(a.name)
|
||||
|
||||
if isinstance(b, SigmaMinus):
|
||||
return (S.One + SigmaZ(a.name))/2
|
||||
|
||||
if isinstance(b, SigmaPlus):
|
||||
return S.Zero
|
||||
|
||||
else:
|
||||
return a * b
|
||||
|
||||
|
||||
def qsimplify_pauli(e):
|
||||
"""
|
||||
Simplify an expression that includes products of pauli operators.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
e : expression
|
||||
An expression that contains products of Pauli operators that is
|
||||
to be simplified.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.pauli import SigmaX, SigmaY
|
||||
>>> from sympy.physics.quantum.pauli import qsimplify_pauli
|
||||
>>> sx, sy = SigmaX(), SigmaY()
|
||||
>>> sx * sy
|
||||
SigmaX()*SigmaY()
|
||||
>>> qsimplify_pauli(sx * sy)
|
||||
I*SigmaZ()
|
||||
"""
|
||||
if isinstance(e, Operator):
|
||||
return e
|
||||
|
||||
if isinstance(e, (Add, Pow, exp)):
|
||||
t = type(e)
|
||||
return t(*(qsimplify_pauli(arg) for arg in e.args))
|
||||
|
||||
if isinstance(e, Mul):
|
||||
|
||||
c, nc = e.args_cnc()
|
||||
|
||||
nc_s = []
|
||||
while nc:
|
||||
curr = nc.pop(0)
|
||||
|
||||
while (len(nc) and
|
||||
isinstance(curr, SigmaOpBase) and
|
||||
isinstance(nc[0], SigmaOpBase) and
|
||||
curr.name == nc[0].name):
|
||||
|
||||
x = nc.pop(0)
|
||||
y = _qsimplify_pauli_product(curr, x)
|
||||
c1, nc1 = y.args_cnc()
|
||||
curr = Mul(*nc1)
|
||||
c = c + c1
|
||||
|
||||
nc_s.append(curr)
|
||||
|
||||
return Mul(*c) * Mul(*nc_s)
|
||||
|
||||
return e
|
||||
@@ -0,0 +1,72 @@
|
||||
"""1D quantum particle in a box."""
|
||||
|
||||
from sympy.core.numbers import pi
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import sin
|
||||
from sympy.sets.sets import Interval
|
||||
|
||||
from sympy.physics.quantum.operator import HermitianOperator
|
||||
from sympy.physics.quantum.state import Ket, Bra
|
||||
from sympy.physics.quantum.constants import hbar
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.physics.quantum.hilbert import L2
|
||||
|
||||
m = Symbol('m')
|
||||
L = Symbol('L')
|
||||
|
||||
|
||||
__all__ = [
|
||||
'PIABHamiltonian',
|
||||
'PIABKet',
|
||||
'PIABBra'
|
||||
]
|
||||
|
||||
|
||||
class PIABHamiltonian(HermitianOperator):
|
||||
"""Particle in a box Hamiltonian operator."""
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
|
||||
def _apply_operator_PIABKet(self, ket, **options):
|
||||
n = ket.label[0]
|
||||
return (n**2*pi**2*hbar**2)/(2*m*L**2)*ket
|
||||
|
||||
|
||||
class PIABKet(Ket):
|
||||
"""Particle in a box eigenket."""
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, args):
|
||||
return L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return PIABBra
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_XOp(None, **options)
|
||||
|
||||
def _represent_XOp(self, basis, **options):
|
||||
x = Symbol('x')
|
||||
n = Symbol('n')
|
||||
subs_info = options.get('subs', {})
|
||||
return sqrt(2/L)*sin(n*pi*x/L).subs(subs_info)
|
||||
|
||||
def _eval_innerproduct_PIABBra(self, bra):
|
||||
return KroneckerDelta(bra.label[0], self.label[0])
|
||||
|
||||
|
||||
class PIABBra(Bra):
|
||||
"""Particle in a box eigenbra."""
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return PIABKet
|
||||
@@ -0,0 +1,263 @@
|
||||
"""Logic for applying operators to states.
|
||||
|
||||
Todo:
|
||||
* Sometimes the final result needs to be expanded, we should do this by hand.
|
||||
"""
|
||||
|
||||
from sympy.concrete import Sum
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.kind import NumberKind
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import sympify, _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 ip_doit_func(e):
|
||||
"""Transform the inner products in an expression by calling ``.doit()``."""
|
||||
return e.replace(InnerProduct, lambda *args: InnerProduct(*args).doit())
|
||||
|
||||
|
||||
def sum_doit_func(e):
|
||||
"""Transform the sums in an expression by calling ``.doit()``."""
|
||||
return e.replace(Sum, lambda *args: Sum(*args).doit())
|
||||
|
||||
|
||||
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).
|
||||
* ``sum_doit``: call ``.doit()`` on sums when they are encountered
|
||||
(default: False). This is helpful for collapsing sums over Kronecker
|
||||
delta's that are created when calling ``qapply``.
|
||||
|
||||
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))
|
||||
<b|
|
||||
"""
|
||||
from sympy.physics.quantum.density import Density
|
||||
|
||||
dagger = options.get('dagger', False)
|
||||
sum_doit = options.get('sum_doit', False)
|
||||
ip_doit = options.get('ip_doit', True)
|
||||
|
||||
e = _sympify(e)
|
||||
|
||||
# Using the kind API here helps us to narrow what types of expressions
|
||||
# we call ``ip_doit_func`` on.
|
||||
if e.kind == NumberKind:
|
||||
return ip_doit_func(e) if ip_doit else e
|
||||
|
||||
# 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 Sum, call qapply on its function.
|
||||
elif isinstance(e, Sum):
|
||||
result = Sum(qapply(e.function, **options), *e.limits)
|
||||
result = sum_doit_func(result) if sum_doit else result
|
||||
return result
|
||||
|
||||
# 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 not nc_part: # If we only have a commuting part, just return it.
|
||||
result = c_mul
|
||||
elif 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:
|
||||
result = Dagger(qapply_Mul(Dagger(e), **options))
|
||||
result = ip_doit_func(result) if ip_doit else result
|
||||
result = sum_doit_func(result) if sum_doit else result
|
||||
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):
|
||||
|
||||
args = list(e.args)
|
||||
extra = S.One
|
||||
result = None
|
||||
|
||||
# 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
|
||||
|
||||
if isinstance(rhs, OuterProduct):
|
||||
extra = rhs.bra # Append to the right of the result
|
||||
rhs = rhs.ket
|
||||
|
||||
# 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
|
||||
)*extra
|
||||
else:
|
||||
return qapply(e.func(*args)*comm*rhs, **options)*extra
|
||||
|
||||
# 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*extra
|
||||
|
||||
# For Sums, move the Sum to the right.
|
||||
if isinstance(rhs, Sum):
|
||||
if isinstance(lhs, Sum):
|
||||
if set(lhs.variables).intersection(set(rhs.variables)):
|
||||
raise ValueError('Duplicated dummy indices in separate sums in qapply.')
|
||||
limits = lhs.limits + rhs.limits
|
||||
result = Sum(qapply(lhs.function*rhs.function, **options), *limits)
|
||||
return qapply_Mul(e.func(*args)*result, **options)
|
||||
else:
|
||||
result = Sum(qapply(lhs*rhs.function, **options), *rhs.limits)
|
||||
return qapply_Mul(e.func(*args)*result, **options)
|
||||
|
||||
if isinstance(lhs, Sum):
|
||||
result = Sum(qapply(lhs.function*rhs, **options), *lhs.limits)
|
||||
return qapply_Mul(e.func(*args)*result, **options)
|
||||
|
||||
# Now try to actually apply the operator and build an inner product.
|
||||
_apply = getattr(lhs, '_apply_operator', None)
|
||||
if _apply is not None:
|
||||
try:
|
||||
result = _apply(rhs, **options)
|
||||
except NotImplementedError:
|
||||
result = None
|
||||
else:
|
||||
result = None
|
||||
|
||||
if result is None:
|
||||
_apply_right = getattr(rhs, '_apply_from_right_to', None)
|
||||
if _apply_right is not None:
|
||||
try:
|
||||
result = _apply_right(lhs, **options)
|
||||
except NotImplementedError:
|
||||
result = None
|
||||
|
||||
if result is None:
|
||||
if isinstance(lhs, BraBase) and isinstance(rhs, KetBase):
|
||||
result = InnerProduct(lhs, rhs)
|
||||
|
||||
# TODO: I may need to expand before returning the final result.
|
||||
if isinstance(result, (int, complex, float)):
|
||||
return _sympify(result)
|
||||
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*extra
|
||||
elif isinstance(result, InnerProduct):
|
||||
return result*qapply_Mul(e.func(*args), **options)*extra
|
||||
else: # result is a scalar times a Mul, Add or TensorProduct
|
||||
return qapply(e.func(*args)*result, **options)*extra
|
||||
@@ -0,0 +1,224 @@
|
||||
"""
|
||||
|
||||
qasm.py - Functions to parse a set of qasm commands into a SymPy Circuit.
|
||||
|
||||
Examples taken from Chuang's page: https://web.archive.org/web/20220120121541/https://www.media.mit.edu/quanta/qasm2circ/
|
||||
|
||||
The code returns a circuit and an associated list of labels.
|
||||
|
||||
>>> from sympy.physics.quantum.qasm import Qasm
|
||||
>>> q = Qasm('qubit q0', 'qubit q1', 'h q0', 'cnot q0,q1')
|
||||
>>> q.get_circuit()
|
||||
CNOT(1,0)*H(1)
|
||||
|
||||
>>> q = Qasm('qubit q0', 'qubit q1', 'cnot q0,q1', 'cnot q1,q0', 'cnot q0,q1')
|
||||
>>> q.get_circuit()
|
||||
CNOT(1,0)*CNOT(0,1)*CNOT(1,0)
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'Qasm',
|
||||
]
|
||||
|
||||
from math import prod
|
||||
|
||||
from sympy.physics.quantum.gate import H, CNOT, X, Z, CGate, CGateS, SWAP, S, T,CPHASE
|
||||
from sympy.physics.quantum.circuitplot import Mz
|
||||
|
||||
def read_qasm(lines):
|
||||
return Qasm(*lines.splitlines())
|
||||
|
||||
def read_qasm_file(filename):
|
||||
return Qasm(*open(filename).readlines())
|
||||
|
||||
def flip_index(i, n):
|
||||
"""Reorder qubit indices from largest to smallest.
|
||||
|
||||
>>> from sympy.physics.quantum.qasm import flip_index
|
||||
>>> flip_index(0, 2)
|
||||
1
|
||||
>>> flip_index(1, 2)
|
||||
0
|
||||
"""
|
||||
return n-i-1
|
||||
|
||||
def trim(line):
|
||||
"""Remove everything following comment # characters in line.
|
||||
|
||||
>>> from sympy.physics.quantum.qasm import trim
|
||||
>>> trim('nothing happens here')
|
||||
'nothing happens here'
|
||||
>>> trim('something #happens here')
|
||||
'something '
|
||||
"""
|
||||
if '#' not in line:
|
||||
return line
|
||||
return line.split('#')[0]
|
||||
|
||||
def get_index(target, labels):
|
||||
"""Get qubit labels from the rest of the line,and return indices
|
||||
|
||||
>>> from sympy.physics.quantum.qasm import get_index
|
||||
>>> get_index('q0', ['q0', 'q1'])
|
||||
1
|
||||
>>> get_index('q1', ['q0', 'q1'])
|
||||
0
|
||||
"""
|
||||
nq = len(labels)
|
||||
return flip_index(labels.index(target), nq)
|
||||
|
||||
def get_indices(targets, labels):
|
||||
return [get_index(t, labels) for t in targets]
|
||||
|
||||
def nonblank(args):
|
||||
for line in args:
|
||||
line = trim(line)
|
||||
if line.isspace():
|
||||
continue
|
||||
yield line
|
||||
return
|
||||
|
||||
def fullsplit(line):
|
||||
words = line.split()
|
||||
rest = ' '.join(words[1:])
|
||||
return fixcommand(words[0]), [s.strip() for s in rest.split(',')]
|
||||
|
||||
def fixcommand(c):
|
||||
"""Fix Qasm command names.
|
||||
|
||||
Remove all of forbidden characters from command c, and
|
||||
replace 'def' with 'qdef'.
|
||||
"""
|
||||
forbidden_characters = ['-']
|
||||
c = c.lower()
|
||||
for char in forbidden_characters:
|
||||
c = c.replace(char, '')
|
||||
if c == 'def':
|
||||
return 'qdef'
|
||||
return c
|
||||
|
||||
def stripquotes(s):
|
||||
"""Replace explicit quotes in a string.
|
||||
|
||||
>>> from sympy.physics.quantum.qasm import stripquotes
|
||||
>>> stripquotes("'S'") == 'S'
|
||||
True
|
||||
>>> stripquotes('"S"') == 'S'
|
||||
True
|
||||
>>> stripquotes('S') == 'S'
|
||||
True
|
||||
"""
|
||||
s = s.replace('"', '') # Remove second set of quotes?
|
||||
s = s.replace("'", '')
|
||||
return s
|
||||
|
||||
class Qasm:
|
||||
"""Class to form objects from Qasm lines
|
||||
|
||||
>>> from sympy.physics.quantum.qasm import Qasm
|
||||
>>> q = Qasm('qubit q0', 'qubit q1', 'h q0', 'cnot q0,q1')
|
||||
>>> q.get_circuit()
|
||||
CNOT(1,0)*H(1)
|
||||
>>> q = Qasm('qubit q0', 'qubit q1', 'cnot q0,q1', 'cnot q1,q0', 'cnot q0,q1')
|
||||
>>> q.get_circuit()
|
||||
CNOT(1,0)*CNOT(0,1)*CNOT(1,0)
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.defs = {}
|
||||
self.circuit = []
|
||||
self.labels = []
|
||||
self.inits = {}
|
||||
self.add(*args)
|
||||
self.kwargs = kwargs
|
||||
|
||||
def add(self, *lines):
|
||||
for line in nonblank(lines):
|
||||
command, rest = fullsplit(line)
|
||||
if self.defs.get(command): #defs come first, since you can override built-in
|
||||
function = self.defs.get(command)
|
||||
indices = self.indices(rest)
|
||||
if len(indices) == 1:
|
||||
self.circuit.append(function(indices[0]))
|
||||
else:
|
||||
self.circuit.append(function(indices[:-1], indices[-1]))
|
||||
elif hasattr(self, command):
|
||||
function = getattr(self, command)
|
||||
function(*rest)
|
||||
else:
|
||||
print("Function %s not defined. Skipping" % command)
|
||||
|
||||
def get_circuit(self):
|
||||
return prod(reversed(self.circuit))
|
||||
|
||||
def get_labels(self):
|
||||
return list(reversed(self.labels))
|
||||
|
||||
def plot(self):
|
||||
from sympy.physics.quantum.circuitplot import CircuitPlot
|
||||
circuit, labels = self.get_circuit(), self.get_labels()
|
||||
CircuitPlot(circuit, len(labels), labels=labels, inits=self.inits)
|
||||
|
||||
def qubit(self, arg, init=None):
|
||||
self.labels.append(arg)
|
||||
if init: self.inits[arg] = init
|
||||
|
||||
def indices(self, args):
|
||||
return get_indices(args, self.labels)
|
||||
|
||||
def index(self, arg):
|
||||
return get_index(arg, self.labels)
|
||||
|
||||
def nop(self, *args):
|
||||
pass
|
||||
|
||||
def x(self, arg):
|
||||
self.circuit.append(X(self.index(arg)))
|
||||
|
||||
def z(self, arg):
|
||||
self.circuit.append(Z(self.index(arg)))
|
||||
|
||||
def h(self, arg):
|
||||
self.circuit.append(H(self.index(arg)))
|
||||
|
||||
def s(self, arg):
|
||||
self.circuit.append(S(self.index(arg)))
|
||||
|
||||
def t(self, arg):
|
||||
self.circuit.append(T(self.index(arg)))
|
||||
|
||||
def measure(self, arg):
|
||||
self.circuit.append(Mz(self.index(arg)))
|
||||
|
||||
def cnot(self, a1, a2):
|
||||
self.circuit.append(CNOT(*self.indices([a1, a2])))
|
||||
|
||||
def swap(self, a1, a2):
|
||||
self.circuit.append(SWAP(*self.indices([a1, a2])))
|
||||
|
||||
def cphase(self, a1, a2):
|
||||
self.circuit.append(CPHASE(*self.indices([a1, a2])))
|
||||
|
||||
def toffoli(self, a1, a2, a3):
|
||||
i1, i2, i3 = self.indices([a1, a2, a3])
|
||||
self.circuit.append(CGateS((i1, i2), X(i3)))
|
||||
|
||||
def cx(self, a1, a2):
|
||||
fi, fj = self.indices([a1, a2])
|
||||
self.circuit.append(CGate(fi, X(fj)))
|
||||
|
||||
def cz(self, a1, a2):
|
||||
fi, fj = self.indices([a1, a2])
|
||||
self.circuit.append(CGate(fi, Z(fj)))
|
||||
|
||||
def defbox(self, *args):
|
||||
print("defbox not supported yet. Skipping: ", args)
|
||||
|
||||
def qdef(self, name, ncontrols, symbol):
|
||||
from sympy.physics.quantum.circuitplot import CreateOneQubitGate, CreateCGate
|
||||
ncontrols = int(ncontrols)
|
||||
command = fixcommand(name)
|
||||
symbol = stripquotes(symbol)
|
||||
if ncontrols > 0:
|
||||
self.defs[command] = CreateCGate(symbol)
|
||||
else:
|
||||
self.defs[command] = CreateOneQubitGate(symbol)
|
||||
@@ -0,0 +1,409 @@
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.utilities.iterables import is_sequence
|
||||
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.matrixutils import (
|
||||
numpy_ndarray, scipy_sparse_matrix,
|
||||
to_sympy, to_numpy, to_scipy_sparse
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'QuantumError',
|
||||
'QExpr'
|
||||
]
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Error handling
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class QuantumError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _qsympify_sequence(seq):
|
||||
"""Convert elements of a sequence to standard form.
|
||||
|
||||
This is like sympify, but it performs special logic for arguments passed
|
||||
to QExpr. The following conversions are done:
|
||||
|
||||
* (list, tuple, Tuple) => _qsympify_sequence each element and convert
|
||||
sequence to a Tuple.
|
||||
* basestring => Symbol
|
||||
* Matrix => Matrix
|
||||
* other => sympify
|
||||
|
||||
Strings are passed to Symbol, not sympify to make sure that variables like
|
||||
'pi' are kept as Symbols, not the SymPy built-in number subclasses.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.qexpr import _qsympify_sequence
|
||||
>>> _qsympify_sequence((1,2,[3,4,[1,]]))
|
||||
(1, 2, (3, 4, (1,)))
|
||||
|
||||
"""
|
||||
|
||||
return tuple(__qsympify_sequence_helper(seq))
|
||||
|
||||
|
||||
def __qsympify_sequence_helper(seq):
|
||||
"""
|
||||
Helper function for _qsympify_sequence
|
||||
This function does the actual work.
|
||||
"""
|
||||
#base case. If not a list, do Sympification
|
||||
if not is_sequence(seq):
|
||||
if isinstance(seq, Matrix):
|
||||
return seq
|
||||
elif isinstance(seq, str):
|
||||
return Symbol(seq)
|
||||
else:
|
||||
return sympify(seq)
|
||||
|
||||
# base condition, when seq is QExpr and also
|
||||
# is iterable.
|
||||
if isinstance(seq, QExpr):
|
||||
return seq
|
||||
|
||||
#if list, recurse on each item in the list
|
||||
result = [__qsympify_sequence_helper(item) for item in seq]
|
||||
|
||||
return Tuple(*result)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Basic Quantum Expression from which all objects descend
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class QExpr(Expr):
|
||||
"""A base class for all quantum object like operators and states."""
|
||||
|
||||
# In sympy, slots are for instance attributes that are computed
|
||||
# dynamically by the __new__ method. They are not part of args, but they
|
||||
# derive from args.
|
||||
|
||||
# The Hilbert space a quantum Object belongs to.
|
||||
__slots__ = ('hilbert_space', )
|
||||
|
||||
is_commutative = False
|
||||
|
||||
# The separator used in printing the label.
|
||||
_label_separator = ''
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""Construct a new quantum object.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the
|
||||
quantum object. For a state, this will be its symbol or its
|
||||
set of quantum numbers.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.qexpr import QExpr
|
||||
>>> q = QExpr(0)
|
||||
>>> q
|
||||
0
|
||||
>>> q.label
|
||||
(0,)
|
||||
>>> q.hilbert_space
|
||||
H
|
||||
>>> q.args
|
||||
(0,)
|
||||
>>> q.is_commutative
|
||||
False
|
||||
"""
|
||||
|
||||
# First compute args and call Expr.__new__ to create the instance
|
||||
args = cls._eval_args(args, **kwargs)
|
||||
if len(args) == 0:
|
||||
args = cls._eval_args(tuple(cls.default_args()), **kwargs)
|
||||
inst = Expr.__new__(cls, *args)
|
||||
# Now set the slots on the instance
|
||||
inst.hilbert_space = cls._eval_hilbert_space(args)
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def _new_rawargs(cls, hilbert_space, *args, **old_assumptions):
|
||||
"""Create new instance of this class with hilbert_space and args.
|
||||
|
||||
This is used to bypass the more complex logic in the ``__new__``
|
||||
method in cases where you already have the exact ``hilbert_space``
|
||||
and ``args``. This should be used when you are positive these
|
||||
arguments are valid, in their final, proper form and want to optimize
|
||||
the creation of the object.
|
||||
"""
|
||||
|
||||
obj = Expr.__new__(cls, *args, **old_assumptions)
|
||||
obj.hilbert_space = hilbert_space
|
||||
return obj
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Properties
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
"""The label is the unique set of identifiers for the object.
|
||||
|
||||
Usually, this will include all of the information about the state
|
||||
*except* the time (in the case of time-dependent objects).
|
||||
|
||||
This must be a tuple, rather than a Tuple.
|
||||
"""
|
||||
if len(self.args) == 0: # If there is no label specified, return the default
|
||||
return self._eval_args(list(self.default_args()))
|
||||
else:
|
||||
return self.args
|
||||
|
||||
@property
|
||||
def is_symbolic(self):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
"""If no arguments are specified, then this will return a default set
|
||||
of arguments to be run through the constructor.
|
||||
|
||||
NOTE: Any classes that override this MUST return a tuple of arguments.
|
||||
Should be overridden by subclasses to specify the default arguments for kets and operators
|
||||
"""
|
||||
raise NotImplementedError("No default arguments for this class!")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# _eval_* methods
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def _eval_adjoint(self):
|
||||
obj = Expr._eval_adjoint(self)
|
||||
if obj is None:
|
||||
obj = Expr.__new__(Dagger, self)
|
||||
if isinstance(obj, QExpr):
|
||||
obj.hilbert_space = self.hilbert_space
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def _eval_args(cls, args):
|
||||
"""Process the args passed to the __new__ method.
|
||||
|
||||
This simply runs args through _qsympify_sequence.
|
||||
"""
|
||||
return _qsympify_sequence(args)
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, args):
|
||||
"""Compute the Hilbert space instance from the args.
|
||||
"""
|
||||
from sympy.physics.quantum.hilbert import HilbertSpace
|
||||
return HilbertSpace()
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Printing
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
# Utilities for printing: these operate on raw SymPy objects
|
||||
|
||||
def _print_sequence(self, seq, sep, printer, *args):
|
||||
result = []
|
||||
for item in seq:
|
||||
result.append(printer._print(item, *args))
|
||||
return sep.join(result)
|
||||
|
||||
def _print_sequence_pretty(self, seq, sep, printer, *args):
|
||||
pform = printer._print(seq[0], *args)
|
||||
for item in seq[1:]:
|
||||
pform = prettyForm(*pform.right(sep))
|
||||
pform = prettyForm(*pform.right(printer._print(item, *args)))
|
||||
return pform
|
||||
|
||||
# Utilities for printing: these operate prettyForm objects
|
||||
|
||||
def _print_subscript_pretty(self, a, b):
|
||||
top = prettyForm(*b.left(' '*a.width()))
|
||||
bot = prettyForm(*a.right(' '*b.width()))
|
||||
return prettyForm(binding=prettyForm.POW, *bot.below(top))
|
||||
|
||||
def _print_superscript_pretty(self, a, b):
|
||||
return a**b
|
||||
|
||||
def _print_parens_pretty(self, pform, left='(', right=')'):
|
||||
return prettyForm(*pform.parens(left=left, right=right))
|
||||
|
||||
# Printing of labels (i.e. args)
|
||||
|
||||
def _print_label(self, printer, *args):
|
||||
"""Prints the label of the QExpr
|
||||
|
||||
This method prints self.label, using self._label_separator to separate
|
||||
the elements. This method should not be overridden, instead, override
|
||||
_print_contents to change printing behavior.
|
||||
"""
|
||||
return self._print_sequence(
|
||||
self.label, self._label_separator, printer, *args
|
||||
)
|
||||
|
||||
def _print_label_repr(self, printer, *args):
|
||||
return self._print_sequence(
|
||||
self.label, ',', printer, *args
|
||||
)
|
||||
|
||||
def _print_label_pretty(self, printer, *args):
|
||||
return self._print_sequence_pretty(
|
||||
self.label, self._label_separator, printer, *args
|
||||
)
|
||||
|
||||
def _print_label_latex(self, printer, *args):
|
||||
return self._print_sequence(
|
||||
self.label, self._label_separator, printer, *args
|
||||
)
|
||||
|
||||
# Printing of contents (default to label)
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
"""Printer for contents of QExpr
|
||||
|
||||
Handles the printing of any unique identifying contents of a QExpr to
|
||||
print as its contents, such as any variables or quantum numbers. The
|
||||
default is to print the label, which is almost always the args. This
|
||||
should not include printing of any brackets or parentheses.
|
||||
"""
|
||||
return self._print_label(printer, *args)
|
||||
|
||||
def _print_contents_pretty(self, printer, *args):
|
||||
return self._print_label_pretty(printer, *args)
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
return self._print_label_latex(printer, *args)
|
||||
|
||||
# Main printing methods
|
||||
|
||||
def _sympystr(self, printer, *args):
|
||||
"""Default printing behavior of QExpr objects
|
||||
|
||||
Handles the default printing of a QExpr. To add other things to the
|
||||
printing of the object, such as an operator name to operators or
|
||||
brackets to states, the class should override the _print/_pretty/_latex
|
||||
functions directly and make calls to _print_contents where appropriate.
|
||||
This allows things like InnerProduct to easily control its printing the
|
||||
printing of contents.
|
||||
"""
|
||||
return self._print_contents(printer, *args)
|
||||
|
||||
def _sympyrepr(self, printer, *args):
|
||||
classname = self.__class__.__name__
|
||||
label = self._print_label_repr(printer, *args)
|
||||
return '%s(%s)' % (classname, label)
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
pform = self._print_contents_pretty(printer, *args)
|
||||
return pform
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
return self._print_contents_latex(printer, *args)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Represent
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
raise NotImplementedError('This object does not have a default basis')
|
||||
|
||||
def _represent(self, *, basis=None, **options):
|
||||
"""Represent this object in a given basis.
|
||||
|
||||
This method dispatches to the actual methods that perform the
|
||||
representation. Subclases of QExpr should define various methods to
|
||||
determine how the object will be represented in various bases. The
|
||||
format of these methods is::
|
||||
|
||||
def _represent_BasisName(self, basis, **options):
|
||||
|
||||
Thus to define how a quantum object is represented in the basis of
|
||||
the operator Position, you would define::
|
||||
|
||||
def _represent_Position(self, basis, **options):
|
||||
|
||||
Usually, basis object will be instances of Operator subclasses, but
|
||||
there is a chance we will relax this in the future to accommodate other
|
||||
types of basis sets that are not associated with an operator.
|
||||
|
||||
If the ``format`` option is given it can be ("sympy", "numpy",
|
||||
"scipy.sparse"). This will ensure that any matrices that result from
|
||||
representing the object are returned in the appropriate matrix format.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
basis : Operator
|
||||
The Operator whose basis functions will be used as the basis for
|
||||
representation.
|
||||
options : dict
|
||||
A dictionary of key/value pairs that give options and hints for
|
||||
the representation, such as the number of basis functions to
|
||||
be used.
|
||||
"""
|
||||
if basis is None:
|
||||
result = self._represent_default_basis(**options)
|
||||
else:
|
||||
result = dispatch_method(self, '_represent', basis, **options)
|
||||
|
||||
# If we get a matrix representation, convert it to the right format.
|
||||
format = options.get('format', 'sympy')
|
||||
result = self._format_represent(result, format)
|
||||
return result
|
||||
|
||||
def _format_represent(self, result, format):
|
||||
if format == 'sympy' and not isinstance(result, Matrix):
|
||||
return to_sympy(result)
|
||||
elif format == 'numpy' and not isinstance(result, numpy_ndarray):
|
||||
return to_numpy(result)
|
||||
elif format == 'scipy.sparse' and \
|
||||
not isinstance(result, scipy_sparse_matrix):
|
||||
return to_scipy_sparse(result)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def split_commutative_parts(e):
|
||||
"""Split into commutative and non-commutative parts."""
|
||||
c_part, nc_part = e.args_cnc()
|
||||
c_part = list(c_part)
|
||||
return c_part, nc_part
|
||||
|
||||
|
||||
def split_qexpr_parts(e):
|
||||
"""Split an expression into Expr and noncommutative QExpr parts."""
|
||||
expr_part = []
|
||||
qexpr_part = []
|
||||
for arg in e.args:
|
||||
if not isinstance(arg, QExpr):
|
||||
expr_part.append(arg)
|
||||
else:
|
||||
qexpr_part.append(arg)
|
||||
return expr_part, qexpr_part
|
||||
|
||||
|
||||
def dispatch_method(self, basename, arg, **options):
|
||||
"""Dispatch a method to the proper handlers."""
|
||||
method_name = '%s_%s' % (basename, arg.__class__.__name__)
|
||||
if hasattr(self, method_name):
|
||||
f = getattr(self, method_name)
|
||||
# This can raise and we will allow it to propagate.
|
||||
result = f(arg, **options)
|
||||
if result is not None:
|
||||
return result
|
||||
raise NotImplementedError(
|
||||
"%s.%s cannot handle: %r" %
|
||||
(self.__class__.__name__, basename, arg)
|
||||
)
|
||||
@@ -0,0 +1,215 @@
|
||||
"""An implementation of qubits and gates acting on them.
|
||||
|
||||
Todo:
|
||||
|
||||
* Update docstrings.
|
||||
* Update tests.
|
||||
* Implement apply using decompose.
|
||||
* Implement represent using decompose or something smarter. For this to
|
||||
work we first have to implement represent for SWAP.
|
||||
* Decide if we want upper index to be inclusive in the constructor.
|
||||
* Fix the printing of Rk gates in plotting.
|
||||
"""
|
||||
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.numbers import (I, Integer, pi)
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.functions import sqrt
|
||||
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.qexpr import QuantumError, QExpr
|
||||
from sympy.matrices import eye
|
||||
from sympy.physics.quantum.tensorproduct import matrix_tensor_product
|
||||
|
||||
from sympy.physics.quantum.gate import (
|
||||
Gate, HadamardGate, SwapGate, OneQubitGate, CGate, PhaseGate, TGate, ZGate
|
||||
)
|
||||
|
||||
from sympy.functions.elementary.complexes import sign
|
||||
|
||||
__all__ = [
|
||||
'QFT',
|
||||
'IQFT',
|
||||
'RkGate',
|
||||
'Rk'
|
||||
]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Fourier stuff
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class RkGate(OneQubitGate):
|
||||
"""This is the R_k gate of the QTF."""
|
||||
gate_name = 'Rk'
|
||||
gate_name_latex = 'R'
|
||||
|
||||
def __new__(cls, *args):
|
||||
if len(args) != 2:
|
||||
raise QuantumError(
|
||||
'Rk gates only take two arguments, got: %r' % args
|
||||
)
|
||||
# For small k, Rk gates simplify to other gates, using these
|
||||
# substitutions give us familiar results for the QFT for small numbers
|
||||
# of qubits.
|
||||
target = args[0]
|
||||
k = args[1]
|
||||
if k == 1:
|
||||
return ZGate(target)
|
||||
elif k == 2:
|
||||
return PhaseGate(target)
|
||||
elif k == 3:
|
||||
return TGate(target)
|
||||
args = cls._eval_args(args)
|
||||
inst = Expr.__new__(cls, *args)
|
||||
inst.hilbert_space = cls._eval_hilbert_space(args)
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def _eval_args(cls, args):
|
||||
# Fall back to this, because Gate._eval_args assumes that args is
|
||||
# all targets and can't contain duplicates.
|
||||
return QExpr._eval_args(args)
|
||||
|
||||
@property
|
||||
def k(self):
|
||||
return self.label[1]
|
||||
|
||||
@property
|
||||
def targets(self):
|
||||
return self.label[:1]
|
||||
|
||||
@property
|
||||
def gate_name_plot(self):
|
||||
return r'$%s_%s$' % (self.gate_name_latex, str(self.k))
|
||||
|
||||
def get_target_matrix(self, format='sympy'):
|
||||
if format == 'sympy':
|
||||
return Matrix([[1, 0], [0, exp(sign(self.k)*Integer(2)*pi*I/(Integer(2)**abs(self.k)))]])
|
||||
raise NotImplementedError(
|
||||
'Invalid format for the R_k gate: %r' % format)
|
||||
|
||||
|
||||
Rk = RkGate
|
||||
|
||||
|
||||
class Fourier(Gate):
|
||||
"""Superclass of Quantum Fourier and Inverse Quantum Fourier Gates."""
|
||||
|
||||
@classmethod
|
||||
def _eval_args(self, args):
|
||||
if len(args) != 2:
|
||||
raise QuantumError(
|
||||
'QFT/IQFT only takes two arguments, got: %r' % args
|
||||
)
|
||||
if args[0] >= args[1]:
|
||||
raise QuantumError("Start must be smaller than finish")
|
||||
return Gate._eval_args(args)
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_ZGate(None, **options)
|
||||
|
||||
def _represent_ZGate(self, basis, **options):
|
||||
"""
|
||||
Represents the (I)QFT In the Z Basis
|
||||
"""
|
||||
nqubits = options.get('nqubits', 0)
|
||||
if nqubits == 0:
|
||||
raise QuantumError(
|
||||
'The number of qubits must be given as nqubits.')
|
||||
if nqubits < self.min_qubits:
|
||||
raise QuantumError(
|
||||
'The number of qubits %r is too small for the gate.' % nqubits
|
||||
)
|
||||
size = self.size
|
||||
omega = self.omega
|
||||
|
||||
#Make a matrix that has the basic Fourier Transform Matrix
|
||||
arrayFT = [[omega**(
|
||||
i*j % size)/sqrt(size) for i in range(size)] for j in range(size)]
|
||||
matrixFT = Matrix(arrayFT)
|
||||
|
||||
#Embed the FT Matrix in a higher space, if necessary
|
||||
if self.label[0] != 0:
|
||||
matrixFT = matrix_tensor_product(eye(2**self.label[0]), matrixFT)
|
||||
if self.min_qubits < nqubits:
|
||||
matrixFT = matrix_tensor_product(
|
||||
matrixFT, eye(2**(nqubits - self.min_qubits)))
|
||||
|
||||
return matrixFT
|
||||
|
||||
@property
|
||||
def targets(self):
|
||||
return range(self.label[0], self.label[1])
|
||||
|
||||
@property
|
||||
def min_qubits(self):
|
||||
return self.label[1]
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""Size is the size of the QFT matrix"""
|
||||
return 2**(self.label[1] - self.label[0])
|
||||
|
||||
@property
|
||||
def omega(self):
|
||||
return Symbol('omega')
|
||||
|
||||
|
||||
class QFT(Fourier):
|
||||
"""The forward quantum Fourier transform."""
|
||||
|
||||
gate_name = 'QFT'
|
||||
gate_name_latex = 'QFT'
|
||||
|
||||
def decompose(self):
|
||||
"""Decomposes QFT into elementary gates."""
|
||||
start = self.label[0]
|
||||
finish = self.label[1]
|
||||
circuit = 1
|
||||
for level in reversed(range(start, finish)):
|
||||
circuit = HadamardGate(level)*circuit
|
||||
for i in range(level - start):
|
||||
circuit = CGate(level - i - 1, RkGate(level, i + 2))*circuit
|
||||
for i in range((finish - start)//2):
|
||||
circuit = SwapGate(i + start, finish - i - 1)*circuit
|
||||
return circuit
|
||||
|
||||
def _apply_operator_Qubit(self, qubits, **options):
|
||||
return qapply(self.decompose()*qubits)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return IQFT(*self.args)
|
||||
|
||||
@property
|
||||
def omega(self):
|
||||
return exp(2*pi*I/self.size)
|
||||
|
||||
|
||||
class IQFT(Fourier):
|
||||
"""The inverse quantum Fourier transform."""
|
||||
|
||||
gate_name = 'IQFT'
|
||||
gate_name_latex = '{QFT^{-1}}'
|
||||
|
||||
def decompose(self):
|
||||
"""Decomposes IQFT into elementary gates."""
|
||||
start = self.args[0]
|
||||
finish = self.args[1]
|
||||
circuit = 1
|
||||
for i in range((finish - start)//2):
|
||||
circuit = SwapGate(i + start, finish - i - 1)*circuit
|
||||
for level in range(start, finish):
|
||||
for i in reversed(range(level - start)):
|
||||
circuit = CGate(level - i - 1, RkGate(level, -i - 2))*circuit
|
||||
circuit = HadamardGate(level)*circuit
|
||||
return circuit
|
||||
|
||||
def _eval_inverse(self):
|
||||
return QFT(*self.args)
|
||||
|
||||
@property
|
||||
def omega(self):
|
||||
return exp(-2*pi*I/self.size)
|
||||
@@ -0,0 +1,811 @@
|
||||
"""Qubits for quantum computing.
|
||||
|
||||
Todo:
|
||||
* Finish implementing measurement logic. This should include POVM.
|
||||
* Update docstrings.
|
||||
* Update tests.
|
||||
"""
|
||||
|
||||
|
||||
import math
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.complexes import conjugate
|
||||
from sympy.functions.elementary.exponential import log
|
||||
from sympy.core.basic import _sympify
|
||||
from sympy.external.gmpy import SYMPY_INTS
|
||||
from sympy.matrices import Matrix, zeros
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
|
||||
from sympy.physics.quantum.hilbert import ComplexSpace
|
||||
from sympy.physics.quantum.state import Ket, Bra, State
|
||||
|
||||
from sympy.physics.quantum.qexpr import QuantumError
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.physics.quantum.matrixutils import (
|
||||
numpy_ndarray, scipy_sparse_matrix
|
||||
)
|
||||
from mpmath.libmp.libintmath import bitcount
|
||||
|
||||
__all__ = [
|
||||
'Qubit',
|
||||
'QubitBra',
|
||||
'IntQubit',
|
||||
'IntQubitBra',
|
||||
'qubit_to_matrix',
|
||||
'matrix_to_qubit',
|
||||
'matrix_to_density',
|
||||
'measure_all',
|
||||
'measure_partial',
|
||||
'measure_partial_oneshot',
|
||||
'measure_all_oneshot'
|
||||
]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Qubit Classes
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class QubitState(State):
|
||||
"""Base class for Qubit and QubitBra."""
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Initialization/creation
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def _eval_args(cls, args):
|
||||
# If we are passed a QubitState or subclass, we just take its qubit
|
||||
# values directly.
|
||||
if len(args) == 1 and isinstance(args[0], QubitState):
|
||||
return args[0].qubit_values
|
||||
|
||||
# Turn strings into tuple of strings
|
||||
if len(args) == 1 and isinstance(args[0], str):
|
||||
args = tuple( S.Zero if qb == "0" else S.One for qb in args[0])
|
||||
else:
|
||||
args = tuple( S.Zero if qb == "0" else S.One if qb == "1" else qb for qb in args)
|
||||
args = tuple(_sympify(arg) for arg in args)
|
||||
|
||||
# Validate input (must have 0 or 1 input)
|
||||
for element in args:
|
||||
if element not in (S.Zero, S.One):
|
||||
raise ValueError(
|
||||
"Qubit values must be 0 or 1, got: %r" % element)
|
||||
return args
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, args):
|
||||
return ComplexSpace(2)**len(args)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Properties
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def dimension(self):
|
||||
"""The number of Qubits in the state."""
|
||||
return len(self.qubit_values)
|
||||
|
||||
@property
|
||||
def nqubits(self):
|
||||
return self.dimension
|
||||
|
||||
@property
|
||||
def qubit_values(self):
|
||||
"""Returns the values of the qubits as a tuple."""
|
||||
return self.label
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Special methods
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def __len__(self):
|
||||
return self.dimension
|
||||
|
||||
def __getitem__(self, bit):
|
||||
return self.qubit_values[int(self.dimension - bit - 1)]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Utility methods
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def flip(self, *bits):
|
||||
"""Flip the bit(s) given."""
|
||||
newargs = list(self.qubit_values)
|
||||
for i in bits:
|
||||
bit = int(self.dimension - i - 1)
|
||||
if newargs[bit] == 1:
|
||||
newargs[bit] = 0
|
||||
else:
|
||||
newargs[bit] = 1
|
||||
return self.__class__(*tuple(newargs))
|
||||
|
||||
|
||||
class Qubit(QubitState, Ket):
|
||||
"""A multi-qubit ket in the computational (z) basis.
|
||||
|
||||
We use the normal convention that the least significant qubit is on the
|
||||
right, so ``|00001>`` has a 1 in the least significant qubit.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
values : list, str
|
||||
The qubit values as a list of ints ([0,0,0,1,1,]) or a string ('011').
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a qubit in a couple of different ways and look at their attributes:
|
||||
|
||||
>>> from sympy.physics.quantum.qubit import Qubit
|
||||
>>> Qubit(0,0,0)
|
||||
|000>
|
||||
>>> q = Qubit('0101')
|
||||
>>> q
|
||||
|0101>
|
||||
|
||||
>>> q.nqubits
|
||||
4
|
||||
>>> len(q)
|
||||
4
|
||||
>>> q.dimension
|
||||
4
|
||||
>>> q.qubit_values
|
||||
(0, 1, 0, 1)
|
||||
|
||||
We can flip the value of an individual qubit:
|
||||
|
||||
>>> q.flip(1)
|
||||
|0111>
|
||||
|
||||
We can take the dagger of a Qubit to get a bra:
|
||||
|
||||
>>> from sympy.physics.quantum.dagger import Dagger
|
||||
>>> Dagger(q)
|
||||
<0101|
|
||||
>>> type(Dagger(q))
|
||||
<class 'sympy.physics.quantum.qubit.QubitBra'>
|
||||
|
||||
Inner products work as expected:
|
||||
|
||||
>>> ip = Dagger(q)*q
|
||||
>>> ip
|
||||
<0101|0101>
|
||||
>>> ip.doit()
|
||||
1
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return QubitBra
|
||||
|
||||
def _eval_innerproduct_QubitBra(self, bra, **hints):
|
||||
if self.label == bra.label:
|
||||
return S.One
|
||||
else:
|
||||
return S.Zero
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_ZGate(None, **options)
|
||||
|
||||
def _represent_ZGate(self, basis, **options):
|
||||
"""Represent this qubits in the computational basis (ZGate).
|
||||
"""
|
||||
_format = options.get('format', 'sympy')
|
||||
n = 1
|
||||
definite_state = 0
|
||||
for it in reversed(self.qubit_values):
|
||||
definite_state += n*it
|
||||
n = n*2
|
||||
result = [0]*(2**self.dimension)
|
||||
result[int(definite_state)] = 1
|
||||
if _format == 'sympy':
|
||||
return Matrix(result)
|
||||
elif _format == 'numpy':
|
||||
import numpy as np
|
||||
return np.array(result, dtype='complex').transpose()
|
||||
elif _format == 'scipy.sparse':
|
||||
from scipy import sparse
|
||||
return sparse.csr_matrix(result, dtype='complex').transpose()
|
||||
|
||||
def _eval_trace(self, bra, **kwargs):
|
||||
indices = kwargs.get('indices', [])
|
||||
|
||||
#sort index list to begin trace from most-significant
|
||||
#qubit
|
||||
sorted_idx = list(indices)
|
||||
if len(sorted_idx) == 0:
|
||||
sorted_idx = list(range(0, self.nqubits))
|
||||
sorted_idx.sort()
|
||||
|
||||
#trace out for each of index
|
||||
new_mat = self*bra
|
||||
for i in range(len(sorted_idx) - 1, -1, -1):
|
||||
# start from tracing out from leftmost qubit
|
||||
new_mat = self._reduced_density(new_mat, int(sorted_idx[i]))
|
||||
|
||||
if (len(sorted_idx) == self.nqubits):
|
||||
#in case full trace was requested
|
||||
return new_mat[0]
|
||||
else:
|
||||
return matrix_to_density(new_mat)
|
||||
|
||||
def _reduced_density(self, matrix, qubit, **options):
|
||||
"""Compute the reduced density matrix by tracing out one qubit.
|
||||
The qubit argument should be of type Python int, since it is used
|
||||
in bit operations
|
||||
"""
|
||||
def find_index_that_is_projected(j, k, qubit):
|
||||
bit_mask = 2**qubit - 1
|
||||
return ((j >> qubit) << (1 + qubit)) + (j & bit_mask) + (k << qubit)
|
||||
|
||||
old_matrix = represent(matrix, **options)
|
||||
old_size = old_matrix.cols
|
||||
#we expect the old_size to be even
|
||||
new_size = old_size//2
|
||||
new_matrix = Matrix().zeros(new_size)
|
||||
|
||||
for i in range(new_size):
|
||||
for j in range(new_size):
|
||||
for k in range(2):
|
||||
col = find_index_that_is_projected(j, k, qubit)
|
||||
row = find_index_that_is_projected(i, k, qubit)
|
||||
new_matrix[i, j] += old_matrix[row, col]
|
||||
|
||||
return new_matrix
|
||||
|
||||
|
||||
class QubitBra(QubitState, Bra):
|
||||
"""A multi-qubit bra in the computational (z) basis.
|
||||
|
||||
We use the normal convention that the least significant qubit is on the
|
||||
right, so ``|00001>`` has a 1 in the least significant qubit.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
values : list, str
|
||||
The qubit values as a list of ints ([0,0,0,1,1,]) or a string ('011').
|
||||
|
||||
See also
|
||||
========
|
||||
|
||||
Qubit: Examples using qubits
|
||||
|
||||
"""
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return Qubit
|
||||
|
||||
|
||||
class IntQubitState(QubitState):
|
||||
"""A base class for qubits that work with binary representations."""
|
||||
|
||||
@classmethod
|
||||
def _eval_args(cls, args, nqubits=None):
|
||||
# The case of a QubitState instance
|
||||
if len(args) == 1 and isinstance(args[0], QubitState):
|
||||
return QubitState._eval_args(args)
|
||||
# otherwise, args should be integer
|
||||
elif not all(isinstance(a, (int, Integer)) for a in args):
|
||||
raise ValueError('values must be integers, got (%s)' % (tuple(type(a) for a in args),))
|
||||
# use nqubits if specified
|
||||
if nqubits is not None:
|
||||
if not isinstance(nqubits, (int, Integer)):
|
||||
raise ValueError('nqubits must be an integer, got (%s)' % type(nqubits))
|
||||
if len(args) != 1:
|
||||
raise ValueError(
|
||||
'too many positional arguments (%s). should be (number, nqubits=n)' % (args,))
|
||||
return cls._eval_args_with_nqubits(args[0], nqubits)
|
||||
# For a single argument, we construct the binary representation of
|
||||
# that integer with the minimal number of bits.
|
||||
if len(args) == 1 and args[0] > 1:
|
||||
#rvalues is the minimum number of bits needed to express the number
|
||||
rvalues = reversed(range(bitcount(abs(args[0]))))
|
||||
qubit_values = [(args[0] >> i) & 1 for i in rvalues]
|
||||
return QubitState._eval_args(qubit_values)
|
||||
# For two numbers, the second number is the number of bits
|
||||
# on which it is expressed, so IntQubit(0,5) == |00000>.
|
||||
elif len(args) == 2 and args[1] > 1:
|
||||
return cls._eval_args_with_nqubits(args[0], args[1])
|
||||
else:
|
||||
return QubitState._eval_args(args)
|
||||
|
||||
@classmethod
|
||||
def _eval_args_with_nqubits(cls, number, nqubits):
|
||||
need = bitcount(abs(number))
|
||||
if nqubits < need:
|
||||
raise ValueError(
|
||||
'cannot represent %s with %s bits' % (number, nqubits))
|
||||
qubit_values = [(number >> i) & 1 for i in reversed(range(nqubits))]
|
||||
return QubitState._eval_args(qubit_values)
|
||||
|
||||
def as_int(self):
|
||||
"""Return the numerical value of the qubit."""
|
||||
number = 0
|
||||
n = 1
|
||||
for i in reversed(self.qubit_values):
|
||||
number += n*i
|
||||
n = n << 1
|
||||
return number
|
||||
|
||||
def _print_label(self, printer, *args):
|
||||
return str(self.as_int())
|
||||
|
||||
def _print_label_pretty(self, printer, *args):
|
||||
label = self._print_label(printer, *args)
|
||||
return prettyForm(label)
|
||||
|
||||
_print_label_repr = _print_label
|
||||
_print_label_latex = _print_label
|
||||
|
||||
|
||||
class IntQubit(IntQubitState, Qubit):
|
||||
"""A qubit ket that store integers as binary numbers in qubit values.
|
||||
|
||||
The differences between this class and ``Qubit`` are:
|
||||
|
||||
* The form of the constructor.
|
||||
* The qubit values are printed as their corresponding integer, rather
|
||||
than the raw qubit values. The internal storage format of the qubit
|
||||
values in the same as ``Qubit``.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
values : int, tuple
|
||||
If a single argument, the integer we want to represent in the qubit
|
||||
values. This integer will be represented using the fewest possible
|
||||
number of qubits.
|
||||
If a pair of integers and the second value is more than one, the first
|
||||
integer gives the integer to represent in binary form and the second
|
||||
integer gives the number of qubits to use.
|
||||
List of zeros and ones is also accepted to generate qubit by bit pattern.
|
||||
|
||||
nqubits : int
|
||||
The integer that represents the number of qubits.
|
||||
This number should be passed with keyword ``nqubits=N``.
|
||||
You can use this in order to avoid ambiguity of Qubit-style tuple of bits.
|
||||
Please see the example below for more details.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a qubit for the integer 5:
|
||||
|
||||
>>> from sympy.physics.quantum.qubit import IntQubit
|
||||
>>> from sympy.physics.quantum.qubit import Qubit
|
||||
>>> q = IntQubit(5)
|
||||
>>> q
|
||||
|5>
|
||||
|
||||
We can also create an ``IntQubit`` by passing a ``Qubit`` instance.
|
||||
|
||||
>>> q = IntQubit(Qubit('101'))
|
||||
>>> q
|
||||
|5>
|
||||
>>> q.as_int()
|
||||
5
|
||||
>>> q.nqubits
|
||||
3
|
||||
>>> q.qubit_values
|
||||
(1, 0, 1)
|
||||
|
||||
We can go back to the regular qubit form.
|
||||
|
||||
>>> Qubit(q)
|
||||
|101>
|
||||
|
||||
Please note that ``IntQubit`` also accepts a ``Qubit``-style list of bits.
|
||||
So, the code below yields qubits 3, not a single bit ``1``.
|
||||
|
||||
>>> IntQubit(1, 1)
|
||||
|3>
|
||||
|
||||
To avoid ambiguity, use ``nqubits`` parameter.
|
||||
Use of this keyword is recommended especially when you provide the values by variables.
|
||||
|
||||
>>> IntQubit(1, nqubits=1)
|
||||
|1>
|
||||
>>> a = 1
|
||||
>>> IntQubit(a, nqubits=1)
|
||||
|1>
|
||||
"""
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return IntQubitBra
|
||||
|
||||
def _eval_innerproduct_IntQubitBra(self, bra, **hints):
|
||||
return Qubit._eval_innerproduct_QubitBra(self, bra)
|
||||
|
||||
class IntQubitBra(IntQubitState, QubitBra):
|
||||
"""A qubit bra that store integers as binary numbers in qubit values."""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return IntQubit
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Qubit <---> Matrix conversion functions
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def matrix_to_qubit(matrix):
|
||||
"""Convert from the matrix repr. to a sum of Qubit objects.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
matrix : Matrix, numpy.matrix, scipy.sparse
|
||||
The matrix to build the Qubit representation of. This works with
|
||||
SymPy matrices, numpy matrices and scipy.sparse sparse matrices.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Represent a state and then go back to its qubit form:
|
||||
|
||||
>>> from sympy.physics.quantum.qubit import matrix_to_qubit, Qubit
|
||||
>>> from sympy.physics.quantum.represent import represent
|
||||
>>> q = Qubit('01')
|
||||
>>> matrix_to_qubit(represent(q))
|
||||
|01>
|
||||
"""
|
||||
# Determine the format based on the type of the input matrix
|
||||
format = 'sympy'
|
||||
if isinstance(matrix, numpy_ndarray):
|
||||
format = 'numpy'
|
||||
if isinstance(matrix, scipy_sparse_matrix):
|
||||
format = 'scipy.sparse'
|
||||
|
||||
# Make sure it is of correct dimensions for a Qubit-matrix representation.
|
||||
# This logic should work with sympy, numpy or scipy.sparse matrices.
|
||||
if matrix.shape[0] == 1:
|
||||
mlistlen = matrix.shape[1]
|
||||
nqubits = log(mlistlen, 2)
|
||||
ket = False
|
||||
cls = QubitBra
|
||||
elif matrix.shape[1] == 1:
|
||||
mlistlen = matrix.shape[0]
|
||||
nqubits = log(mlistlen, 2)
|
||||
ket = True
|
||||
cls = Qubit
|
||||
else:
|
||||
raise QuantumError(
|
||||
'Matrix must be a row/column vector, got %r' % matrix
|
||||
)
|
||||
if not isinstance(nqubits, Integer):
|
||||
raise QuantumError('Matrix must be a row/column vector of size '
|
||||
'2**nqubits, got: %r' % matrix)
|
||||
# Go through each item in matrix, if element is non-zero, make it into a
|
||||
# Qubit item times the element.
|
||||
result = 0
|
||||
for i in range(mlistlen):
|
||||
if ket:
|
||||
element = matrix[i, 0]
|
||||
else:
|
||||
element = matrix[0, i]
|
||||
if format in ('numpy', 'scipy.sparse'):
|
||||
element = complex(element)
|
||||
if element:
|
||||
# Form Qubit array; 0 in bit-locations where i is 0, 1 in
|
||||
# bit-locations where i is 1
|
||||
qubit_array = [int(i & (1 << x) != 0) for x in range(nqubits)]
|
||||
qubit_array.reverse()
|
||||
result = result + element*cls(*qubit_array)
|
||||
|
||||
# If SymPy simplified by pulling out a constant coefficient, undo that.
|
||||
if isinstance(result, (Mul, Add, Pow)):
|
||||
result = result.expand()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def matrix_to_density(mat):
|
||||
"""
|
||||
Works by finding the eigenvectors and eigenvalues of the matrix.
|
||||
We know we can decompose rho by doing:
|
||||
sum(EigenVal*|Eigenvect><Eigenvect|)
|
||||
"""
|
||||
from sympy.physics.quantum.density import Density
|
||||
eigen = mat.eigenvects()
|
||||
args = [[matrix_to_qubit(Matrix(
|
||||
[vector, ])), x[0]] for x in eigen for vector in x[2] if x[0] != 0]
|
||||
if (len(args) == 0):
|
||||
return S.Zero
|
||||
else:
|
||||
return Density(*args)
|
||||
|
||||
|
||||
def qubit_to_matrix(qubit, format='sympy'):
|
||||
"""Converts an Add/Mul of Qubit objects into it's matrix representation
|
||||
|
||||
This function is the inverse of ``matrix_to_qubit`` and is a shorthand
|
||||
for ``represent(qubit)``.
|
||||
"""
|
||||
return represent(qubit, format=format)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Measurement
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def measure_all(qubit, format='sympy', normalize=True):
|
||||
"""Perform an ensemble measurement of all qubits.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
qubit : Qubit, Add
|
||||
The qubit to measure. This can be any Qubit or a linear combination
|
||||
of them.
|
||||
format : str
|
||||
The format of the intermediate matrices to use. Possible values are
|
||||
('sympy','numpy','scipy.sparse'). Currently only 'sympy' is
|
||||
implemented.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
result : list
|
||||
A list that consists of primitive states and their probabilities.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.qubit import Qubit, measure_all
|
||||
>>> from sympy.physics.quantum.gate import H
|
||||
>>> from sympy.physics.quantum.qapply import qapply
|
||||
|
||||
>>> c = H(0)*H(1)*Qubit('00')
|
||||
>>> c
|
||||
H(0)*H(1)*|00>
|
||||
>>> q = qapply(c)
|
||||
>>> measure_all(q)
|
||||
[(|00>, 1/4), (|01>, 1/4), (|10>, 1/4), (|11>, 1/4)]
|
||||
"""
|
||||
m = qubit_to_matrix(qubit, format)
|
||||
|
||||
if format == 'sympy':
|
||||
results = []
|
||||
|
||||
if normalize:
|
||||
m = m.normalized()
|
||||
|
||||
size = max(m.shape) # Max of shape to account for bra or ket
|
||||
nqubits = int(math.log(size)/math.log(2))
|
||||
for i in range(size):
|
||||
if m[i]:
|
||||
results.append(
|
||||
(Qubit(IntQubit(i, nqubits=nqubits)), m[i]*conjugate(m[i]))
|
||||
)
|
||||
return results
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"This function cannot handle non-SymPy matrix formats yet"
|
||||
)
|
||||
|
||||
|
||||
def measure_partial(qubit, bits, format='sympy', normalize=True):
|
||||
"""Perform a partial ensemble measure on the specified qubits.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
qubits : Qubit
|
||||
The qubit to measure. This can be any Qubit or a linear combination
|
||||
of them.
|
||||
bits : tuple
|
||||
The qubits to measure.
|
||||
format : str
|
||||
The format of the intermediate matrices to use. Possible values are
|
||||
('sympy','numpy','scipy.sparse'). Currently only 'sympy' is
|
||||
implemented.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
result : list
|
||||
A list that consists of primitive states and their probabilities.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.qubit import Qubit, measure_partial
|
||||
>>> from sympy.physics.quantum.gate import H
|
||||
>>> from sympy.physics.quantum.qapply import qapply
|
||||
|
||||
>>> c = H(0)*H(1)*Qubit('00')
|
||||
>>> c
|
||||
H(0)*H(1)*|00>
|
||||
>>> q = qapply(c)
|
||||
>>> measure_partial(q, (0,))
|
||||
[(sqrt(2)*|00>/2 + sqrt(2)*|10>/2, 1/2), (sqrt(2)*|01>/2 + sqrt(2)*|11>/2, 1/2)]
|
||||
"""
|
||||
m = qubit_to_matrix(qubit, format)
|
||||
|
||||
if isinstance(bits, (SYMPY_INTS, Integer)):
|
||||
bits = (int(bits),)
|
||||
|
||||
if format == 'sympy':
|
||||
if normalize:
|
||||
m = m.normalized()
|
||||
|
||||
possible_outcomes = _get_possible_outcomes(m, bits)
|
||||
|
||||
# Form output from function.
|
||||
output = []
|
||||
for outcome in possible_outcomes:
|
||||
# Calculate probability of finding the specified bits with
|
||||
# given values.
|
||||
prob_of_outcome = 0
|
||||
prob_of_outcome += (outcome.H*outcome)[0]
|
||||
|
||||
# If the output has a chance, append it to output with found
|
||||
# probability.
|
||||
if prob_of_outcome != 0:
|
||||
if normalize:
|
||||
next_matrix = matrix_to_qubit(outcome.normalized())
|
||||
else:
|
||||
next_matrix = matrix_to_qubit(outcome)
|
||||
|
||||
output.append((
|
||||
next_matrix,
|
||||
prob_of_outcome
|
||||
))
|
||||
|
||||
return output
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"This function cannot handle non-SymPy matrix formats yet"
|
||||
)
|
||||
|
||||
|
||||
def measure_partial_oneshot(qubit, bits, format='sympy'):
|
||||
"""Perform a partial oneshot measurement on the specified qubits.
|
||||
|
||||
A oneshot measurement is equivalent to performing a measurement on a
|
||||
quantum system. This type of measurement does not return the probabilities
|
||||
like an ensemble measurement does, but rather returns *one* of the
|
||||
possible resulting states. The exact state that is returned is determined
|
||||
by picking a state randomly according to the ensemble probabilities.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
qubits : Qubit
|
||||
The qubit to measure. This can be any Qubit or a linear combination
|
||||
of them.
|
||||
bits : tuple
|
||||
The qubits to measure.
|
||||
format : str
|
||||
The format of the intermediate matrices to use. Possible values are
|
||||
('sympy','numpy','scipy.sparse'). Currently only 'sympy' is
|
||||
implemented.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : Qubit
|
||||
The qubit that the system collapsed to upon measurement.
|
||||
"""
|
||||
import random
|
||||
m = qubit_to_matrix(qubit, format)
|
||||
|
||||
if format == 'sympy':
|
||||
m = m.normalized()
|
||||
possible_outcomes = _get_possible_outcomes(m, bits)
|
||||
|
||||
# Form output from function
|
||||
random_number = random.random()
|
||||
total_prob = 0
|
||||
for outcome in possible_outcomes:
|
||||
# Calculate probability of finding the specified bits
|
||||
# with given values
|
||||
total_prob += (outcome.H*outcome)[0]
|
||||
if total_prob >= random_number:
|
||||
return matrix_to_qubit(outcome.normalized())
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"This function cannot handle non-SymPy matrix formats yet"
|
||||
)
|
||||
|
||||
|
||||
def _get_possible_outcomes(m, bits):
|
||||
"""Get the possible states that can be produced in a measurement.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
m : Matrix
|
||||
The matrix representing the state of the system.
|
||||
bits : tuple, list
|
||||
Which bits will be measured.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : list
|
||||
The list of possible states which can occur given this measurement.
|
||||
These are un-normalized so we can derive the probability of finding
|
||||
this state by taking the inner product with itself
|
||||
"""
|
||||
|
||||
# This is filled with loads of dirty binary tricks...You have been warned
|
||||
|
||||
size = max(m.shape) # Max of shape to account for bra or ket
|
||||
nqubits = int(math.log2(size) + .1) # Number of qubits possible
|
||||
|
||||
# Make the output states and put in output_matrices, nothing in them now.
|
||||
# Each state will represent a possible outcome of the measurement
|
||||
# Thus, output_matrices[0] is the matrix which we get when all measured
|
||||
# bits return 0. and output_matrices[1] is the matrix for only the 0th
|
||||
# bit being true
|
||||
output_matrices = []
|
||||
for i in range(1 << len(bits)):
|
||||
output_matrices.append(zeros(2**nqubits, 1))
|
||||
|
||||
# Bitmasks will help sort how to determine possible outcomes.
|
||||
# When the bit mask is and-ed with a matrix-index,
|
||||
# it will determine which state that index belongs to
|
||||
bit_masks = []
|
||||
for bit in bits:
|
||||
bit_masks.append(1 << bit)
|
||||
|
||||
# Make possible outcome states
|
||||
for i in range(2**nqubits):
|
||||
trueness = 0 # This tells us to which output_matrix this value belongs
|
||||
# Find trueness
|
||||
for j in range(len(bit_masks)):
|
||||
if i & bit_masks[j]:
|
||||
trueness += j + 1
|
||||
# Put the value in the correct output matrix
|
||||
output_matrices[trueness][i] = m[i]
|
||||
return output_matrices
|
||||
|
||||
|
||||
def measure_all_oneshot(qubit, format='sympy'):
|
||||
"""Perform a oneshot ensemble measurement on all qubits.
|
||||
|
||||
A oneshot measurement is equivalent to performing a measurement on a
|
||||
quantum system. This type of measurement does not return the probabilities
|
||||
like an ensemble measurement does, but rather returns *one* of the
|
||||
possible resulting states. The exact state that is returned is determined
|
||||
by picking a state randomly according to the ensemble probabilities.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
qubits : Qubit
|
||||
The qubit to measure. This can be any Qubit or a linear combination
|
||||
of them.
|
||||
format : str
|
||||
The format of the intermediate matrices to use. Possible values are
|
||||
('sympy','numpy','scipy.sparse'). Currently only 'sympy' is
|
||||
implemented.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : Qubit
|
||||
The qubit that the system collapsed to upon measurement.
|
||||
"""
|
||||
import random
|
||||
m = qubit_to_matrix(qubit)
|
||||
|
||||
if format == 'sympy':
|
||||
m = m.normalized()
|
||||
random_number = random.random()
|
||||
total = 0
|
||||
result = 0
|
||||
for i in m:
|
||||
total += i*i.conjugate()
|
||||
if total > random_number:
|
||||
break
|
||||
result += 1
|
||||
return Qubit(IntQubit(result, nqubits=int(math.log2(max(m.shape)) + .1)))
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"This function cannot handle non-SymPy matrix formats yet"
|
||||
)
|
||||
@@ -0,0 +1,574 @@
|
||||
"""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'
|
||||
]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Represent
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
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:
|
||||
#If no _represent_FOO method exists, map to the
|
||||
#appropriate basis state and try
|
||||
#the other methods of representation
|
||||
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:]:
|
||||
# scipy.sparse doesn't support += so we use plain = here.
|
||||
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)
|
||||
# scipy.sparse doesn't support negative exponents
|
||||
# and warns when inverting a matrix in csr format.
|
||||
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)):
|
||||
# We have removed special handling of inner products that used to be
|
||||
# required (before automatic transforms).
|
||||
# For numpy and scipy.sparse, we can only handle numerical prefactors.
|
||||
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):
|
||||
# Must use np.matmult to "matrix multiply" two np.ndarray
|
||||
result = np.matmul(next_arg, result)
|
||||
else:
|
||||
result = next_arg*result
|
||||
last_arg = arg
|
||||
|
||||
# All three matrix formats create 1 by 1 matrices when inner products of
|
||||
# vectors are taken. In these cases, we simply return a scalar.
|
||||
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:
|
||||
#TODO: Add support for sets of operators
|
||||
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):
|
||||
# XXX: Catching TypeError like this is a bad way of distinguishing
|
||||
# instances from classes. The logic using this function should be
|
||||
# rewritten somehow.
|
||||
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
|
||||
@@ -0,0 +1,679 @@
|
||||
"""Simple Harmonic Oscillator 1-Dimension"""
|
||||
|
||||
from sympy.core.numbers import (I, Integer)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.physics.quantum.constants import hbar
|
||||
from sympy.physics.quantum.operator import Operator
|
||||
from sympy.physics.quantum.state import Bra, Ket, State
|
||||
from sympy.physics.quantum.qexpr import QExpr
|
||||
from sympy.physics.quantum.cartesian import X, Px
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.physics.quantum.hilbert import ComplexSpace
|
||||
from sympy.physics.quantum.matrixutils import matrix_zeros
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
class SHOOp(Operator):
|
||||
"""A base class for the SHO Operators.
|
||||
|
||||
We are limiting the number of arguments to be 1.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _eval_args(cls, args):
|
||||
args = QExpr._eval_args(args)
|
||||
if len(args) == 1:
|
||||
return args
|
||||
else:
|
||||
raise ValueError("Too many arguments")
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return ComplexSpace(S.Infinity)
|
||||
|
||||
class RaisingOp(SHOOp):
|
||||
"""The Raising Operator or a^dagger.
|
||||
|
||||
When a^dagger acts on a state it raises the state up by one. Taking
|
||||
the adjoint of a^dagger returns 'a', the Lowering Operator. a^dagger
|
||||
can be rewritten in terms of position and momentum. We can represent
|
||||
a^dagger as a matrix, which will be its default basis.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the
|
||||
operator.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a Raising Operator and rewrite it in terms of position and
|
||||
momentum, and show that taking its adjoint returns 'a':
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import RaisingOp
|
||||
>>> from sympy.physics.quantum import Dagger
|
||||
|
||||
>>> ad = RaisingOp('a')
|
||||
>>> ad.rewrite('xp').doit()
|
||||
sqrt(2)*(m*omega*X - I*Px)/(2*sqrt(hbar)*sqrt(m*omega))
|
||||
|
||||
>>> Dagger(ad)
|
||||
a
|
||||
|
||||
Taking the commutator of a^dagger with other Operators:
|
||||
|
||||
>>> from sympy.physics.quantum import Commutator
|
||||
>>> from sympy.physics.quantum.sho1d import RaisingOp, LoweringOp
|
||||
>>> from sympy.physics.quantum.sho1d import NumberOp
|
||||
|
||||
>>> ad = RaisingOp('a')
|
||||
>>> a = LoweringOp('a')
|
||||
>>> N = NumberOp('N')
|
||||
>>> Commutator(ad, a).doit()
|
||||
-1
|
||||
>>> Commutator(ad, N).doit()
|
||||
-RaisingOp(a)
|
||||
|
||||
Apply a^dagger to a state:
|
||||
|
||||
>>> from sympy.physics.quantum import qapply
|
||||
>>> from sympy.physics.quantum.sho1d import RaisingOp, SHOKet
|
||||
|
||||
>>> ad = RaisingOp('a')
|
||||
>>> k = SHOKet('k')
|
||||
>>> qapply(ad*k)
|
||||
sqrt(k + 1)*|k + 1>
|
||||
|
||||
Matrix Representation
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import RaisingOp
|
||||
>>> from sympy.physics.quantum.represent import represent
|
||||
>>> ad = RaisingOp('a')
|
||||
>>> represent(ad, basis=N, ndim=4, format='sympy')
|
||||
Matrix([
|
||||
[0, 0, 0, 0],
|
||||
[1, 0, 0, 0],
|
||||
[0, sqrt(2), 0, 0],
|
||||
[0, 0, sqrt(3), 0]])
|
||||
|
||||
"""
|
||||
|
||||
def _eval_rewrite_as_xp(self, *args, **kwargs):
|
||||
return (S.One/sqrt(Integer(2)*hbar*m*omega))*(
|
||||
S.NegativeOne*I*Px + m*omega*X)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return LoweringOp(*self.args)
|
||||
|
||||
def _eval_commutator_LoweringOp(self, other):
|
||||
return S.NegativeOne
|
||||
|
||||
def _eval_commutator_NumberOp(self, other):
|
||||
return S.NegativeOne*self
|
||||
|
||||
def _apply_operator_SHOKet(self, ket, **options):
|
||||
temp = ket.n + S.One
|
||||
return sqrt(temp)*SHOKet(temp)
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_NumberOp(None, **options)
|
||||
|
||||
def _represent_XOp(self, basis, **options):
|
||||
# This logic is good but the underlying position
|
||||
# representation logic is broken.
|
||||
# temp = self.rewrite('xp').doit()
|
||||
# result = represent(temp, basis=X)
|
||||
# return result
|
||||
raise NotImplementedError('Position representation is not implemented')
|
||||
|
||||
def _represent_NumberOp(self, basis, **options):
|
||||
ndim_info = options.get('ndim', 4)
|
||||
format = options.get('format','sympy')
|
||||
matrix = matrix_zeros(ndim_info, ndim_info, **options)
|
||||
for i in range(ndim_info - 1):
|
||||
value = sqrt(i + 1)
|
||||
if format == 'scipy.sparse':
|
||||
value = float(value)
|
||||
matrix[i + 1, i] = value
|
||||
if format == 'scipy.sparse':
|
||||
matrix = matrix.tocsr()
|
||||
return matrix
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Printing Methods
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
arg0 = printer._print(self.args[0], *args)
|
||||
return '%s(%s)' % (self.__class__.__name__, arg0)
|
||||
|
||||
def _print_contents_pretty(self, printer, *args):
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
pform = printer._print(self.args[0], *args)
|
||||
pform = pform**prettyForm('\N{DAGGER}')
|
||||
return pform
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
arg = printer._print(self.args[0])
|
||||
return '%s^{\\dagger}' % arg
|
||||
|
||||
class LoweringOp(SHOOp):
|
||||
"""The Lowering Operator or 'a'.
|
||||
|
||||
When 'a' acts on a state it lowers the state up by one. Taking
|
||||
the adjoint of 'a' returns a^dagger, the Raising Operator. 'a'
|
||||
can be rewritten in terms of position and momentum. We can
|
||||
represent 'a' as a matrix, which will be its default basis.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the
|
||||
operator.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a Lowering Operator and rewrite it in terms of position and
|
||||
momentum, and show that taking its adjoint returns a^dagger:
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import LoweringOp
|
||||
>>> from sympy.physics.quantum import Dagger
|
||||
|
||||
>>> a = LoweringOp('a')
|
||||
>>> a.rewrite('xp').doit()
|
||||
sqrt(2)*(m*omega*X + I*Px)/(2*sqrt(hbar)*sqrt(m*omega))
|
||||
|
||||
>>> Dagger(a)
|
||||
RaisingOp(a)
|
||||
|
||||
Taking the commutator of 'a' with other Operators:
|
||||
|
||||
>>> from sympy.physics.quantum import Commutator
|
||||
>>> from sympy.physics.quantum.sho1d import LoweringOp, RaisingOp
|
||||
>>> from sympy.physics.quantum.sho1d import NumberOp
|
||||
|
||||
>>> a = LoweringOp('a')
|
||||
>>> ad = RaisingOp('a')
|
||||
>>> N = NumberOp('N')
|
||||
>>> Commutator(a, ad).doit()
|
||||
1
|
||||
>>> Commutator(a, N).doit()
|
||||
a
|
||||
|
||||
Apply 'a' to a state:
|
||||
|
||||
>>> from sympy.physics.quantum import qapply
|
||||
>>> from sympy.physics.quantum.sho1d import LoweringOp, SHOKet
|
||||
|
||||
>>> a = LoweringOp('a')
|
||||
>>> k = SHOKet('k')
|
||||
>>> qapply(a*k)
|
||||
sqrt(k)*|k - 1>
|
||||
|
||||
Taking 'a' of the lowest state will return 0:
|
||||
|
||||
>>> from sympy.physics.quantum import qapply
|
||||
>>> from sympy.physics.quantum.sho1d import LoweringOp, SHOKet
|
||||
|
||||
>>> a = LoweringOp('a')
|
||||
>>> k = SHOKet(0)
|
||||
>>> qapply(a*k)
|
||||
0
|
||||
|
||||
Matrix Representation
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import LoweringOp
|
||||
>>> from sympy.physics.quantum.represent import represent
|
||||
>>> a = LoweringOp('a')
|
||||
>>> represent(a, basis=N, ndim=4, format='sympy')
|
||||
Matrix([
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, sqrt(2), 0],
|
||||
[0, 0, 0, sqrt(3)],
|
||||
[0, 0, 0, 0]])
|
||||
|
||||
"""
|
||||
|
||||
def _eval_rewrite_as_xp(self, *args, **kwargs):
|
||||
return (S.One/sqrt(Integer(2)*hbar*m*omega))*(
|
||||
I*Px + m*omega*X)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return RaisingOp(*self.args)
|
||||
|
||||
def _eval_commutator_RaisingOp(self, other):
|
||||
return S.One
|
||||
|
||||
def _eval_commutator_NumberOp(self, other):
|
||||
return self
|
||||
|
||||
def _apply_operator_SHOKet(self, ket, **options):
|
||||
temp = ket.n - Integer(1)
|
||||
if ket.n is S.Zero:
|
||||
return S.Zero
|
||||
else:
|
||||
return sqrt(ket.n)*SHOKet(temp)
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_NumberOp(None, **options)
|
||||
|
||||
def _represent_XOp(self, basis, **options):
|
||||
# This logic is good but the underlying position
|
||||
# representation logic is broken.
|
||||
# temp = self.rewrite('xp').doit()
|
||||
# result = represent(temp, basis=X)
|
||||
# return result
|
||||
raise NotImplementedError('Position representation is not implemented')
|
||||
|
||||
def _represent_NumberOp(self, basis, **options):
|
||||
ndim_info = options.get('ndim', 4)
|
||||
format = options.get('format', 'sympy')
|
||||
matrix = matrix_zeros(ndim_info, ndim_info, **options)
|
||||
for i in range(ndim_info - 1):
|
||||
value = sqrt(i + 1)
|
||||
if format == 'scipy.sparse':
|
||||
value = float(value)
|
||||
matrix[i,i + 1] = value
|
||||
if format == 'scipy.sparse':
|
||||
matrix = matrix.tocsr()
|
||||
return matrix
|
||||
|
||||
|
||||
class NumberOp(SHOOp):
|
||||
"""The Number Operator is simply a^dagger*a
|
||||
|
||||
It is often useful to write a^dagger*a as simply the Number Operator
|
||||
because the Number Operator commutes with the Hamiltonian. And can be
|
||||
expressed using the Number Operator. Also the Number Operator can be
|
||||
applied to states. We can represent the Number Operator as a matrix,
|
||||
which will be its default basis.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the
|
||||
operator.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a Number Operator and rewrite it in terms of the ladder
|
||||
operators, position and momentum operators, and Hamiltonian:
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import NumberOp
|
||||
|
||||
>>> N = NumberOp('N')
|
||||
>>> N.rewrite('a').doit()
|
||||
RaisingOp(a)*a
|
||||
>>> N.rewrite('xp').doit()
|
||||
-1/2 + (m**2*omega**2*X**2 + Px**2)/(2*hbar*m*omega)
|
||||
>>> N.rewrite('H').doit()
|
||||
-1/2 + H/(hbar*omega)
|
||||
|
||||
Take the Commutator of the Number Operator with other Operators:
|
||||
|
||||
>>> from sympy.physics.quantum import Commutator
|
||||
>>> from sympy.physics.quantum.sho1d import NumberOp, Hamiltonian
|
||||
>>> from sympy.physics.quantum.sho1d import RaisingOp, LoweringOp
|
||||
|
||||
>>> N = NumberOp('N')
|
||||
>>> H = Hamiltonian('H')
|
||||
>>> ad = RaisingOp('a')
|
||||
>>> a = LoweringOp('a')
|
||||
>>> Commutator(N,H).doit()
|
||||
0
|
||||
>>> Commutator(N,ad).doit()
|
||||
RaisingOp(a)
|
||||
>>> Commutator(N,a).doit()
|
||||
-a
|
||||
|
||||
Apply the Number Operator to a state:
|
||||
|
||||
>>> from sympy.physics.quantum import qapply
|
||||
>>> from sympy.physics.quantum.sho1d import NumberOp, SHOKet
|
||||
|
||||
>>> N = NumberOp('N')
|
||||
>>> k = SHOKet('k')
|
||||
>>> qapply(N*k)
|
||||
k*|k>
|
||||
|
||||
Matrix Representation
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import NumberOp
|
||||
>>> from sympy.physics.quantum.represent import represent
|
||||
>>> N = NumberOp('N')
|
||||
>>> represent(N, basis=N, ndim=4, format='sympy')
|
||||
Matrix([
|
||||
[0, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 2, 0],
|
||||
[0, 0, 0, 3]])
|
||||
|
||||
"""
|
||||
|
||||
def _eval_rewrite_as_a(self, *args, **kwargs):
|
||||
return ad*a
|
||||
|
||||
def _eval_rewrite_as_xp(self, *args, **kwargs):
|
||||
return (S.One/(Integer(2)*m*hbar*omega))*(Px**2 + (
|
||||
m*omega*X)**2) - S.Half
|
||||
|
||||
def _eval_rewrite_as_H(self, *args, **kwargs):
|
||||
return H/(hbar*omega) - S.Half
|
||||
|
||||
def _apply_operator_SHOKet(self, ket, **options):
|
||||
return ket.n*ket
|
||||
|
||||
def _eval_commutator_Hamiltonian(self, other):
|
||||
return S.Zero
|
||||
|
||||
def _eval_commutator_RaisingOp(self, other):
|
||||
return other
|
||||
|
||||
def _eval_commutator_LoweringOp(self, other):
|
||||
return S.NegativeOne*other
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_NumberOp(None, **options)
|
||||
|
||||
def _represent_XOp(self, basis, **options):
|
||||
# This logic is good but the underlying position
|
||||
# representation logic is broken.
|
||||
# temp = self.rewrite('xp').doit()
|
||||
# result = represent(temp, basis=X)
|
||||
# return result
|
||||
raise NotImplementedError('Position representation is not implemented')
|
||||
|
||||
def _represent_NumberOp(self, basis, **options):
|
||||
ndim_info = options.get('ndim', 4)
|
||||
format = options.get('format', 'sympy')
|
||||
matrix = matrix_zeros(ndim_info, ndim_info, **options)
|
||||
for i in range(ndim_info):
|
||||
value = i
|
||||
if format == 'scipy.sparse':
|
||||
value = float(value)
|
||||
matrix[i,i] = value
|
||||
if format == 'scipy.sparse':
|
||||
matrix = matrix.tocsr()
|
||||
return matrix
|
||||
|
||||
|
||||
class Hamiltonian(SHOOp):
|
||||
"""The Hamiltonian Operator.
|
||||
|
||||
The Hamiltonian is used to solve the time-independent Schrodinger
|
||||
equation. The Hamiltonian can be expressed using the ladder operators,
|
||||
as well as by position and momentum. We can represent the Hamiltonian
|
||||
Operator as a matrix, which will be its default basis.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the
|
||||
operator.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a Hamiltonian Operator and rewrite it in terms of the ladder
|
||||
operators, position and momentum, and the Number Operator:
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import Hamiltonian
|
||||
|
||||
>>> H = Hamiltonian('H')
|
||||
>>> H.rewrite('a').doit()
|
||||
hbar*omega*(1/2 + RaisingOp(a)*a)
|
||||
>>> H.rewrite('xp').doit()
|
||||
(m**2*omega**2*X**2 + Px**2)/(2*m)
|
||||
>>> H.rewrite('N').doit()
|
||||
hbar*omega*(1/2 + N)
|
||||
|
||||
Take the Commutator of the Hamiltonian and the Number Operator:
|
||||
|
||||
>>> from sympy.physics.quantum import Commutator
|
||||
>>> from sympy.physics.quantum.sho1d import Hamiltonian, NumberOp
|
||||
|
||||
>>> H = Hamiltonian('H')
|
||||
>>> N = NumberOp('N')
|
||||
>>> Commutator(H,N).doit()
|
||||
0
|
||||
|
||||
Apply the Hamiltonian Operator to a state:
|
||||
|
||||
>>> from sympy.physics.quantum import qapply
|
||||
>>> from sympy.physics.quantum.sho1d import Hamiltonian, SHOKet
|
||||
|
||||
>>> H = Hamiltonian('H')
|
||||
>>> k = SHOKet('k')
|
||||
>>> qapply(H*k)
|
||||
hbar*k*omega*|k> + hbar*omega*|k>/2
|
||||
|
||||
Matrix Representation
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import Hamiltonian
|
||||
>>> from sympy.physics.quantum.represent import represent
|
||||
|
||||
>>> H = Hamiltonian('H')
|
||||
>>> represent(H, basis=N, ndim=4, format='sympy')
|
||||
Matrix([
|
||||
[hbar*omega/2, 0, 0, 0],
|
||||
[ 0, 3*hbar*omega/2, 0, 0],
|
||||
[ 0, 0, 5*hbar*omega/2, 0],
|
||||
[ 0, 0, 0, 7*hbar*omega/2]])
|
||||
|
||||
"""
|
||||
|
||||
def _eval_rewrite_as_a(self, *args, **kwargs):
|
||||
return hbar*omega*(ad*a + S.Half)
|
||||
|
||||
def _eval_rewrite_as_xp(self, *args, **kwargs):
|
||||
return (S.One/(Integer(2)*m))*(Px**2 + (m*omega*X)**2)
|
||||
|
||||
def _eval_rewrite_as_N(self, *args, **kwargs):
|
||||
return hbar*omega*(N + S.Half)
|
||||
|
||||
def _apply_operator_SHOKet(self, ket, **options):
|
||||
return (hbar*omega*(ket.n + S.Half))*ket
|
||||
|
||||
def _eval_commutator_NumberOp(self, other):
|
||||
return S.Zero
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_NumberOp(None, **options)
|
||||
|
||||
def _represent_XOp(self, basis, **options):
|
||||
# This logic is good but the underlying position
|
||||
# representation logic is broken.
|
||||
# temp = self.rewrite('xp').doit()
|
||||
# result = represent(temp, basis=X)
|
||||
# return result
|
||||
raise NotImplementedError('Position representation is not implemented')
|
||||
|
||||
def _represent_NumberOp(self, basis, **options):
|
||||
ndim_info = options.get('ndim', 4)
|
||||
format = options.get('format', 'sympy')
|
||||
matrix = matrix_zeros(ndim_info, ndim_info, **options)
|
||||
for i in range(ndim_info):
|
||||
value = i + S.Half
|
||||
if format == 'scipy.sparse':
|
||||
value = float(value)
|
||||
matrix[i,i] = value
|
||||
if format == 'scipy.sparse':
|
||||
matrix = matrix.tocsr()
|
||||
return hbar*omega*matrix
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
class SHOState(State):
|
||||
"""State class for SHO states"""
|
||||
|
||||
@classmethod
|
||||
def _eval_hilbert_space(cls, label):
|
||||
return ComplexSpace(S.Infinity)
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
return self.args[0]
|
||||
|
||||
|
||||
class SHOKet(SHOState, Ket):
|
||||
"""1D eigenket.
|
||||
|
||||
Inherits from SHOState and Ket.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the ket
|
||||
This is usually its quantum numbers or its symbol.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Ket's know about their associated bra:
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import SHOKet
|
||||
|
||||
>>> k = SHOKet('k')
|
||||
>>> k.dual
|
||||
<k|
|
||||
>>> k.dual_class()
|
||||
<class 'sympy.physics.quantum.sho1d.SHOBra'>
|
||||
|
||||
Take the Inner Product with a bra:
|
||||
|
||||
>>> from sympy.physics.quantum import InnerProduct
|
||||
>>> from sympy.physics.quantum.sho1d import SHOKet, SHOBra
|
||||
|
||||
>>> k = SHOKet('k')
|
||||
>>> b = SHOBra('b')
|
||||
>>> InnerProduct(b,k).doit()
|
||||
KroneckerDelta(b, k)
|
||||
|
||||
Vector representation of a numerical state ket:
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import SHOKet, NumberOp
|
||||
>>> from sympy.physics.quantum.represent import represent
|
||||
|
||||
>>> k = SHOKet(3)
|
||||
>>> N = NumberOp('N')
|
||||
>>> represent(k, basis=N, ndim=4)
|
||||
Matrix([
|
||||
[0],
|
||||
[0],
|
||||
[0],
|
||||
[1]])
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return SHOBra
|
||||
|
||||
def _eval_innerproduct_SHOBra(self, bra, **hints):
|
||||
result = KroneckerDelta(self.n, bra.n)
|
||||
return result
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_NumberOp(None, **options)
|
||||
|
||||
def _represent_NumberOp(self, basis, **options):
|
||||
ndim_info = options.get('ndim', 4)
|
||||
format = options.get('format', 'sympy')
|
||||
options['spmatrix'] = 'lil'
|
||||
vector = matrix_zeros(ndim_info, 1, **options)
|
||||
if isinstance(self.n, Integer):
|
||||
if self.n >= ndim_info:
|
||||
return ValueError("N-Dimension too small")
|
||||
if format == 'scipy.sparse':
|
||||
vector[int(self.n), 0] = 1.0
|
||||
vector = vector.tocsr()
|
||||
elif format == 'numpy':
|
||||
vector[int(self.n), 0] = 1.0
|
||||
else:
|
||||
vector[self.n, 0] = S.One
|
||||
return vector
|
||||
else:
|
||||
return ValueError("Not Numerical State")
|
||||
|
||||
|
||||
class SHOBra(SHOState, Bra):
|
||||
"""A time-independent Bra in SHO.
|
||||
|
||||
Inherits from SHOState and Bra.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the ket
|
||||
This is usually its quantum numbers or its symbol.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Bra's know about their associated ket:
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import SHOBra
|
||||
|
||||
>>> b = SHOBra('b')
|
||||
>>> b.dual
|
||||
|b>
|
||||
>>> b.dual_class()
|
||||
<class 'sympy.physics.quantum.sho1d.SHOKet'>
|
||||
|
||||
Vector representation of a numerical state bra:
|
||||
|
||||
>>> from sympy.physics.quantum.sho1d import SHOBra, NumberOp
|
||||
>>> from sympy.physics.quantum.represent import represent
|
||||
|
||||
>>> b = SHOBra(3)
|
||||
>>> N = NumberOp('N')
|
||||
>>> represent(b, basis=N, ndim=4)
|
||||
Matrix([[0, 0, 0, 1]])
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return SHOKet
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_NumberOp(None, **options)
|
||||
|
||||
def _represent_NumberOp(self, basis, **options):
|
||||
ndim_info = options.get('ndim', 4)
|
||||
format = options.get('format', 'sympy')
|
||||
options['spmatrix'] = 'lil'
|
||||
vector = matrix_zeros(1, ndim_info, **options)
|
||||
if isinstance(self.n, Integer):
|
||||
if self.n >= ndim_info:
|
||||
return ValueError("N-Dimension too small")
|
||||
if format == 'scipy.sparse':
|
||||
vector[0, int(self.n)] = 1.0
|
||||
vector = vector.tocsr()
|
||||
elif format == 'numpy':
|
||||
vector[0, int(self.n)] = 1.0
|
||||
else:
|
||||
vector[0, self.n] = S.One
|
||||
return vector
|
||||
else:
|
||||
return ValueError("Not Numerical State")
|
||||
|
||||
|
||||
ad = RaisingOp('a')
|
||||
a = LoweringOp('a')
|
||||
H = Hamiltonian('H')
|
||||
N = NumberOp('N')
|
||||
omega = Symbol('omega')
|
||||
m = Symbol('m')
|
||||
@@ -0,0 +1,173 @@
|
||||
"""Shor's algorithm and helper functions.
|
||||
|
||||
Todo:
|
||||
|
||||
* Get the CMod gate working again using the new Gate API.
|
||||
* Fix everything.
|
||||
* Update docstrings and reformat.
|
||||
"""
|
||||
|
||||
import math
|
||||
import random
|
||||
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.exponential import log
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.core.intfunc import igcd
|
||||
from sympy.ntheory import continued_fraction_periodic as continued_fraction
|
||||
from sympy.utilities.iterables import variations
|
||||
|
||||
from sympy.physics.quantum.gate import Gate
|
||||
from sympy.physics.quantum.qubit import Qubit, measure_partial_oneshot
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.qft import QFT
|
||||
from sympy.physics.quantum.qexpr import QuantumError
|
||||
|
||||
|
||||
class OrderFindingException(QuantumError):
|
||||
pass
|
||||
|
||||
|
||||
class CMod(Gate):
|
||||
"""A controlled mod gate.
|
||||
|
||||
This is black box controlled Mod function for use by shor's algorithm.
|
||||
TODO: implement a decompose property that returns how to do this in terms
|
||||
of elementary gates
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _eval_args(cls, args):
|
||||
# t = args[0]
|
||||
# a = args[1]
|
||||
# N = args[2]
|
||||
raise NotImplementedError('The CMod gate has not been completed.')
|
||||
|
||||
@property
|
||||
def t(self):
|
||||
"""Size of 1/2 input register. First 1/2 holds output."""
|
||||
return self.label[0]
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
"""Base of the controlled mod function."""
|
||||
return self.label[1]
|
||||
|
||||
@property
|
||||
def N(self):
|
||||
"""N is the type of modular arithmetic we are doing."""
|
||||
return self.label[2]
|
||||
|
||||
def _apply_operator_Qubit(self, qubits, **options):
|
||||
"""
|
||||
This directly calculates the controlled mod of the second half of
|
||||
the register and puts it in the second
|
||||
This will look pretty when we get Tensor Symbolically working
|
||||
"""
|
||||
n = 1
|
||||
k = 0
|
||||
# Determine the value stored in high memory.
|
||||
for i in range(self.t):
|
||||
k += n*qubits[self.t + i]
|
||||
n *= 2
|
||||
|
||||
# The value to go in low memory will be out.
|
||||
out = int(self.a**k % self.N)
|
||||
|
||||
# Create array for new qbit-ket which will have high memory unaffected
|
||||
outarray = list(qubits.args[0][:self.t])
|
||||
|
||||
# Place out in low memory
|
||||
for i in reversed(range(self.t)):
|
||||
outarray.append((out >> i) & 1)
|
||||
|
||||
return Qubit(*outarray)
|
||||
|
||||
|
||||
def shor(N):
|
||||
"""This function implements Shor's factoring algorithm on the Integer N
|
||||
|
||||
The algorithm starts by picking a random number (a) and seeing if it is
|
||||
coprime with N. If it is not, then the gcd of the two numbers is a factor
|
||||
and we are done. Otherwise, it begins the period_finding subroutine which
|
||||
finds the period of a in modulo N arithmetic. This period, if even, can
|
||||
be used to calculate factors by taking a**(r/2)-1 and a**(r/2)+1.
|
||||
These values are returned.
|
||||
"""
|
||||
a = random.randrange(N - 2) + 2
|
||||
if igcd(N, a) != 1:
|
||||
return igcd(N, a)
|
||||
r = period_find(a, N)
|
||||
if r % 2 == 1:
|
||||
shor(N)
|
||||
answer = (igcd(a**(r/2) - 1, N), igcd(a**(r/2) + 1, N))
|
||||
return answer
|
||||
|
||||
|
||||
def getr(x, y, N):
|
||||
fraction = continued_fraction(x, y)
|
||||
# Now convert into r
|
||||
total = ratioize(fraction, N)
|
||||
return total
|
||||
|
||||
|
||||
def ratioize(list, N):
|
||||
if list[0] > N:
|
||||
return S.Zero
|
||||
if len(list) == 1:
|
||||
return list[0]
|
||||
return list[0] + ratioize(list[1:], N)
|
||||
|
||||
|
||||
def period_find(a, N):
|
||||
"""Finds the period of a in modulo N arithmetic
|
||||
|
||||
This is quantum part of Shor's algorithm. It takes two registers,
|
||||
puts first in superposition of states with Hadamards so: ``|k>|0>``
|
||||
with k being all possible choices. It then does a controlled mod and
|
||||
a QFT to determine the order of a.
|
||||
"""
|
||||
epsilon = .5
|
||||
# picks out t's such that maintains accuracy within epsilon
|
||||
t = int(2*math.ceil(log(N, 2)))
|
||||
# make the first half of register be 0's |000...000>
|
||||
start = [0 for x in range(t)]
|
||||
# Put second half into superposition of states so we have |1>x|0> + |2>x|0> + ... |k>x>|0> + ... + |2**n-1>x|0>
|
||||
factor = 1/sqrt(2**t)
|
||||
qubits = 0
|
||||
for arr in variations(range(2), t, repetition=True):
|
||||
qbitArray = list(arr) + start
|
||||
qubits = qubits + Qubit(*qbitArray)
|
||||
circuit = (factor*qubits).expand()
|
||||
# Controlled second half of register so that we have:
|
||||
# |1>x|a**1 %N> + |2>x|a**2 %N> + ... + |k>x|a**k %N >+ ... + |2**n-1=k>x|a**k % n>
|
||||
circuit = CMod(t, a, N)*circuit
|
||||
# will measure first half of register giving one of the a**k%N's
|
||||
|
||||
circuit = qapply(circuit)
|
||||
for i in range(t):
|
||||
circuit = measure_partial_oneshot(circuit, i)
|
||||
# Now apply Inverse Quantum Fourier Transform on the second half of the register
|
||||
|
||||
circuit = qapply(QFT(t, t*2).decompose()*circuit, floatingPoint=True)
|
||||
for i in range(t):
|
||||
circuit = measure_partial_oneshot(circuit, i + t)
|
||||
if isinstance(circuit, Qubit):
|
||||
register = circuit
|
||||
elif isinstance(circuit, Mul):
|
||||
register = circuit.args[-1]
|
||||
else:
|
||||
register = circuit.args[-1].args[-1]
|
||||
|
||||
n = 1
|
||||
answer = 0
|
||||
for i in range(len(register)/2):
|
||||
answer += n*register[i + t]
|
||||
n = n << 1
|
||||
if answer == 0:
|
||||
raise OrderFindingException(
|
||||
"Order finder returned 0. Happens with chance %f" % epsilon)
|
||||
#turn answer into r using continued fractions
|
||||
g = getr(answer, 2**t, N)
|
||||
return g
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,987 @@
|
||||
"""Dirac notation for states."""
|
||||
|
||||
from sympy.core.cache import cacheit
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.function import Function
|
||||
from sympy.core.numbers import oo, equal_valued
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.complexes import conjugate
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.integrals.integrals import integrate
|
||||
from sympy.printing.pretty.stringpict import stringPict
|
||||
from sympy.physics.quantum.qexpr import QExpr, dispatch_method
|
||||
from sympy.physics.quantum.kind import KetKind, BraKind
|
||||
|
||||
|
||||
__all__ = [
|
||||
'KetBase',
|
||||
'BraBase',
|
||||
'StateBase',
|
||||
'State',
|
||||
'Ket',
|
||||
'Bra',
|
||||
'TimeDepState',
|
||||
'TimeDepBra',
|
||||
'TimeDepKet',
|
||||
'OrthogonalKet',
|
||||
'OrthogonalBra',
|
||||
'OrthogonalState',
|
||||
'Wavefunction'
|
||||
]
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# States, bras and kets.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# ASCII brackets
|
||||
_lbracket = "<"
|
||||
_rbracket = ">"
|
||||
_straight_bracket = "|"
|
||||
|
||||
|
||||
# Unicode brackets
|
||||
# MATHEMATICAL ANGLE BRACKETS
|
||||
_lbracket_ucode = "\N{MATHEMATICAL LEFT ANGLE BRACKET}"
|
||||
_rbracket_ucode = "\N{MATHEMATICAL RIGHT ANGLE BRACKET}"
|
||||
# LIGHT VERTICAL BAR
|
||||
_straight_bracket_ucode = "\N{LIGHT VERTICAL BAR}"
|
||||
|
||||
# Other options for unicode printing of <, > and | for Dirac notation.
|
||||
|
||||
# LEFT-POINTING ANGLE BRACKET
|
||||
# _lbracket = "\u2329"
|
||||
# _rbracket = "\u232A"
|
||||
|
||||
# LEFT ANGLE BRACKET
|
||||
# _lbracket = "\u3008"
|
||||
# _rbracket = "\u3009"
|
||||
|
||||
# VERTICAL LINE
|
||||
# _straight_bracket = "\u007C"
|
||||
|
||||
|
||||
class StateBase(QExpr):
|
||||
"""Abstract base class for general abstract states in quantum mechanics.
|
||||
|
||||
All other state classes defined will need to inherit from this class. It
|
||||
carries the basic structure for all other states such as dual, _eval_adjoint
|
||||
and label.
|
||||
|
||||
This is an abstract base class and you should not instantiate it directly,
|
||||
instead use State.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _operators_to_state(self, ops, **options):
|
||||
""" Returns the eigenstate instance for the passed operators.
|
||||
|
||||
This method should be overridden in subclasses. It will handle being
|
||||
passed either an Operator instance or set of Operator instances. It
|
||||
should return the corresponding state INSTANCE or simply raise a
|
||||
NotImplementedError. See cartesian.py for an example.
|
||||
"""
|
||||
|
||||
raise NotImplementedError("Cannot map operators to states in this class. Method not implemented!")
|
||||
|
||||
def _state_to_operators(self, op_classes, **options):
|
||||
""" Returns the operators which this state instance is an eigenstate
|
||||
of.
|
||||
|
||||
This method should be overridden in subclasses. It will be called on
|
||||
state instances and be passed the operator classes that we wish to make
|
||||
into instances. The state instance will then transform the classes
|
||||
appropriately, or raise a NotImplementedError if it cannot return
|
||||
operator instances. See cartesian.py for examples,
|
||||
"""
|
||||
|
||||
raise NotImplementedError(
|
||||
"Cannot map this state to operators. Method not implemented!")
|
||||
|
||||
@property
|
||||
def operators(self):
|
||||
"""Return the operator(s) that this state is an eigenstate of"""
|
||||
from .operatorset import state_to_operators # import internally to avoid circular import errors
|
||||
return state_to_operators(self)
|
||||
|
||||
def _enumerate_state(self, num_states, **options):
|
||||
raise NotImplementedError("Cannot enumerate this state!")
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent(basis=self.operators)
|
||||
|
||||
def _apply_operator(self, op, **options):
|
||||
return None
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Dagger/dual
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def dual(self):
|
||||
"""Return the dual state of this one."""
|
||||
return self.dual_class()._new_rawargs(self.hilbert_space, *self.args)
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
"""Return the class used to construct the dual."""
|
||||
raise NotImplementedError(
|
||||
'dual_class must be implemented in a subclass'
|
||||
)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
"""Compute the dagger of this state using the dual."""
|
||||
return self.dual
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Printing
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def _pretty_brackets(self, height, use_unicode=True):
|
||||
# Return pretty printed brackets for the state
|
||||
# Ideally, this could be done by pform.parens but it does not support the angled < and >
|
||||
|
||||
# Setup for unicode vs ascii
|
||||
if use_unicode:
|
||||
lbracket, rbracket = getattr(self, 'lbracket_ucode', ""), getattr(self, 'rbracket_ucode', "")
|
||||
slash, bslash, vert = '\N{BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT}', \
|
||||
'\N{BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT}', \
|
||||
'\N{BOX DRAWINGS LIGHT VERTICAL}'
|
||||
else:
|
||||
lbracket, rbracket = getattr(self, 'lbracket', ""), getattr(self, 'rbracket', "")
|
||||
slash, bslash, vert = '/', '\\', '|'
|
||||
|
||||
# If height is 1, just return brackets
|
||||
if height == 1:
|
||||
return stringPict(lbracket), stringPict(rbracket)
|
||||
# Make height even
|
||||
height += (height % 2)
|
||||
|
||||
brackets = []
|
||||
for bracket in lbracket, rbracket:
|
||||
# Create left bracket
|
||||
if bracket in {_lbracket, _lbracket_ucode}:
|
||||
bracket_args = [ ' ' * (height//2 - i - 1) +
|
||||
slash for i in range(height // 2)]
|
||||
bracket_args.extend(
|
||||
[' ' * i + bslash for i in range(height // 2)])
|
||||
# Create right bracket
|
||||
elif bracket in {_rbracket, _rbracket_ucode}:
|
||||
bracket_args = [ ' ' * i + bslash for i in range(height // 2)]
|
||||
bracket_args.extend([ ' ' * (
|
||||
height//2 - i - 1) + slash for i in range(height // 2)])
|
||||
# Create straight bracket
|
||||
elif bracket in {_straight_bracket, _straight_bracket_ucode}:
|
||||
bracket_args = [vert] * height
|
||||
else:
|
||||
raise ValueError(bracket)
|
||||
brackets.append(
|
||||
stringPict('\n'.join(bracket_args), baseline=height//2))
|
||||
return brackets
|
||||
|
||||
def _sympystr(self, printer, *args):
|
||||
contents = self._print_contents(printer, *args)
|
||||
return '%s%s%s' % (getattr(self, 'lbracket', ""), contents, getattr(self, 'rbracket', ""))
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
# Get brackets
|
||||
pform = self._print_contents_pretty(printer, *args)
|
||||
lbracket, rbracket = self._pretty_brackets(
|
||||
pform.height(), printer._use_unicode)
|
||||
# Put together state
|
||||
pform = prettyForm(*pform.left(lbracket))
|
||||
pform = prettyForm(*pform.right(rbracket))
|
||||
return pform
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
contents = self._print_contents_latex(printer, *args)
|
||||
# The extra {} brackets are needed to get matplotlib's latex
|
||||
# rendered to render this properly.
|
||||
return '{%s%s%s}' % (getattr(self, 'lbracket_latex', ""), contents, getattr(self, 'rbracket_latex', ""))
|
||||
|
||||
|
||||
class KetBase(StateBase):
|
||||
"""Base class for Kets.
|
||||
|
||||
This class defines the dual property and the brackets for printing. This is
|
||||
an abstract base class and you should not instantiate it directly, instead
|
||||
use Ket.
|
||||
"""
|
||||
|
||||
kind = KetKind
|
||||
|
||||
lbracket = _straight_bracket
|
||||
rbracket = _rbracket
|
||||
lbracket_ucode = _straight_bracket_ucode
|
||||
rbracket_ucode = _rbracket_ucode
|
||||
lbracket_latex = r'\left|'
|
||||
rbracket_latex = r'\right\rangle '
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("psi",)
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return BraBase
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# _eval_* methods
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def _eval_innerproduct(self, bra, **hints):
|
||||
"""Evaluate the inner product between this ket and a bra.
|
||||
|
||||
This is called to compute <bra|ket>, where the ket is ``self``.
|
||||
|
||||
This method will dispatch to sub-methods having the format::
|
||||
|
||||
``def _eval_innerproduct_BraClass(self, **hints):``
|
||||
|
||||
Subclasses should define these methods (one for each BraClass) to
|
||||
teach the ket how to take inner products with bras.
|
||||
"""
|
||||
return dispatch_method(self, '_eval_innerproduct', bra, **hints)
|
||||
|
||||
def _apply_from_right_to(self, op, **options):
|
||||
"""Apply an Operator to this Ket as Operator*Ket
|
||||
|
||||
This method will dispatch to methods having the format::
|
||||
|
||||
``def _apply_from_right_to_OperatorName(op, **options):``
|
||||
|
||||
Subclasses should define these methods (one for each OperatorName) to
|
||||
teach the Ket how to implement OperatorName*Ket
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
op : Operator
|
||||
The Operator that is acting on the Ket as op*Ket
|
||||
options : dict
|
||||
A dict of key/value pairs that control how the operator is applied
|
||||
to the Ket.
|
||||
"""
|
||||
return dispatch_method(self, '_apply_from_right_to', op, **options)
|
||||
|
||||
|
||||
class BraBase(StateBase):
|
||||
"""Base class for Bras.
|
||||
|
||||
This class defines the dual property and the brackets for printing. This
|
||||
is an abstract base class and you should not instantiate it directly,
|
||||
instead use Bra.
|
||||
"""
|
||||
|
||||
kind = BraKind
|
||||
|
||||
lbracket = _lbracket
|
||||
rbracket = _straight_bracket
|
||||
lbracket_ucode = _lbracket_ucode
|
||||
rbracket_ucode = _straight_bracket_ucode
|
||||
lbracket_latex = r'\left\langle '
|
||||
rbracket_latex = r'\right|'
|
||||
|
||||
@classmethod
|
||||
def _operators_to_state(self, ops, **options):
|
||||
state = self.dual_class()._operators_to_state(ops, **options)
|
||||
return state.dual
|
||||
|
||||
def _state_to_operators(self, op_classes, **options):
|
||||
return self.dual._state_to_operators(op_classes, **options)
|
||||
|
||||
def _enumerate_state(self, num_states, **options):
|
||||
dual_states = self.dual._enumerate_state(num_states, **options)
|
||||
return [x.dual for x in dual_states]
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return self.dual_class().default_args()
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return KetBase
|
||||
|
||||
def _represent(self, **options):
|
||||
"""A default represent that uses the Ket's version."""
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
return Dagger(self.dual._represent(**options))
|
||||
|
||||
|
||||
class State(StateBase):
|
||||
"""General abstract quantum state used as a base class for Ket and Bra."""
|
||||
pass
|
||||
|
||||
|
||||
class Ket(State, KetBase):
|
||||
"""A general time-independent Ket in quantum mechanics.
|
||||
|
||||
Inherits from State and KetBase. This class should be used as the base
|
||||
class for all physical, time-independent Kets in a system. This class
|
||||
and its subclasses will be the main classes that users will use for
|
||||
expressing Kets in Dirac notation [1]_.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the
|
||||
ket. This will usually be its symbol or its quantum numbers. For
|
||||
time-dependent state, this will include the time.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a simple Ket and looking at its properties::
|
||||
|
||||
>>> from sympy.physics.quantum import Ket
|
||||
>>> from sympy import symbols, I
|
||||
>>> k = Ket('psi')
|
||||
>>> k
|
||||
|psi>
|
||||
>>> k.hilbert_space
|
||||
H
|
||||
>>> k.is_commutative
|
||||
False
|
||||
>>> k.label
|
||||
(psi,)
|
||||
|
||||
Ket's know about their associated bra::
|
||||
|
||||
>>> k.dual
|
||||
<psi|
|
||||
>>> k.dual_class()
|
||||
<class 'sympy.physics.quantum.state.Bra'>
|
||||
|
||||
Take a linear combination of two kets::
|
||||
|
||||
>>> k0 = Ket(0)
|
||||
>>> k1 = Ket(1)
|
||||
>>> 2*I*k0 - 4*k1
|
||||
2*I*|0> - 4*|1>
|
||||
|
||||
Compound labels are passed as tuples::
|
||||
|
||||
>>> n, m = symbols('n,m')
|
||||
>>> k = Ket(n,m)
|
||||
>>> k
|
||||
|nm>
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Bra-ket_notation
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return Bra
|
||||
|
||||
|
||||
class Bra(State, BraBase):
|
||||
"""A general time-independent Bra in quantum mechanics.
|
||||
|
||||
Inherits from State and BraBase. A Bra is the dual of a Ket [1]_. This
|
||||
class and its subclasses will be the main classes that users will use for
|
||||
expressing Bras in Dirac notation.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the
|
||||
ket. This will usually be its symbol or its quantum numbers. For
|
||||
time-dependent state, this will include the time.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a simple Bra and look at its properties::
|
||||
|
||||
>>> from sympy.physics.quantum import Bra
|
||||
>>> from sympy import symbols, I
|
||||
>>> b = Bra('psi')
|
||||
>>> b
|
||||
<psi|
|
||||
>>> b.hilbert_space
|
||||
H
|
||||
>>> b.is_commutative
|
||||
False
|
||||
|
||||
Bra's know about their dual Ket's::
|
||||
|
||||
>>> b.dual
|
||||
|psi>
|
||||
>>> b.dual_class()
|
||||
<class 'sympy.physics.quantum.state.Ket'>
|
||||
|
||||
Like Kets, Bras can have compound labels and be manipulated in a similar
|
||||
manner::
|
||||
|
||||
>>> n, m = symbols('n,m')
|
||||
>>> b = Bra(n,m) - I*Bra(m,n)
|
||||
>>> b
|
||||
-I*<mn| + <nm|
|
||||
|
||||
Symbols in a Bra can be substituted using ``.subs``::
|
||||
|
||||
>>> b.subs(n,m)
|
||||
<mm| - I*<mm|
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Bra-ket_notation
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return Ket
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Time dependent states, bras and kets.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TimeDepState(StateBase):
|
||||
"""Base class for a general time-dependent quantum state.
|
||||
|
||||
This class is used as a base class for any time-dependent state. The main
|
||||
difference between this class and the time-independent state is that this
|
||||
class takes a second argument that is the time in addition to the usual
|
||||
label argument.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the ket. This
|
||||
will usually be its symbol or its quantum numbers. For time-dependent
|
||||
state, this will include the time as the final argument.
|
||||
"""
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Initialization
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("psi", "t")
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Properties
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
"""The label of the state."""
|
||||
return self.args[:-1]
|
||||
|
||||
@property
|
||||
def time(self):
|
||||
"""The time of the state."""
|
||||
return self.args[-1]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Printing
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
def _print_time(self, printer, *args):
|
||||
return printer._print(self.time, *args)
|
||||
|
||||
_print_time_repr = _print_time
|
||||
_print_time_latex = _print_time
|
||||
|
||||
def _print_time_pretty(self, printer, *args):
|
||||
pform = printer._print(self.time, *args)
|
||||
return pform
|
||||
|
||||
def _print_contents(self, printer, *args):
|
||||
label = self._print_label(printer, *args)
|
||||
time = self._print_time(printer, *args)
|
||||
return '%s;%s' % (label, time)
|
||||
|
||||
def _print_label_repr(self, printer, *args):
|
||||
label = self._print_sequence(self.label, ',', printer, *args)
|
||||
time = self._print_time_repr(printer, *args)
|
||||
return '%s,%s' % (label, time)
|
||||
|
||||
def _print_contents_pretty(self, printer, *args):
|
||||
label = self._print_label_pretty(printer, *args)
|
||||
time = self._print_time_pretty(printer, *args)
|
||||
return printer._print_seq((label, time), delimiter=';')
|
||||
|
||||
def _print_contents_latex(self, printer, *args):
|
||||
label = self._print_sequence(
|
||||
self.label, self._label_separator, printer, *args)
|
||||
time = self._print_time_latex(printer, *args)
|
||||
return '%s;%s' % (label, time)
|
||||
|
||||
|
||||
class TimeDepKet(TimeDepState, KetBase):
|
||||
"""General time-dependent Ket in quantum mechanics.
|
||||
|
||||
This inherits from ``TimeDepState`` and ``KetBase`` and is the main class
|
||||
that should be used for Kets that vary with time. Its dual is a
|
||||
``TimeDepBra``.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the ket. This
|
||||
will usually be its symbol or its quantum numbers. For time-dependent
|
||||
state, this will include the time as the final argument.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Create a TimeDepKet and look at its attributes::
|
||||
|
||||
>>> from sympy.physics.quantum import TimeDepKet
|
||||
>>> k = TimeDepKet('psi', 't')
|
||||
>>> k
|
||||
|psi;t>
|
||||
>>> k.time
|
||||
t
|
||||
>>> k.label
|
||||
(psi,)
|
||||
>>> k.hilbert_space
|
||||
H
|
||||
|
||||
TimeDepKets know about their dual bra::
|
||||
|
||||
>>> k.dual
|
||||
<psi;t|
|
||||
>>> k.dual_class()
|
||||
<class 'sympy.physics.quantum.state.TimeDepBra'>
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return TimeDepBra
|
||||
|
||||
|
||||
class TimeDepBra(TimeDepState, BraBase):
|
||||
"""General time-dependent Bra in quantum mechanics.
|
||||
|
||||
This inherits from TimeDepState and BraBase and is the main class that
|
||||
should be used for Bras that vary with time. Its dual is a TimeDepBra.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
The list of numbers or parameters that uniquely specify the ket. This
|
||||
will usually be its symbol or its quantum numbers. For time-dependent
|
||||
state, this will include the time as the final argument.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum import TimeDepBra
|
||||
>>> b = TimeDepBra('psi', 't')
|
||||
>>> b
|
||||
<psi;t|
|
||||
>>> b.time
|
||||
t
|
||||
>>> b.label
|
||||
(psi,)
|
||||
>>> b.hilbert_space
|
||||
H
|
||||
>>> b.dual
|
||||
|psi;t>
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return TimeDepKet
|
||||
|
||||
|
||||
class OrthogonalState(State):
|
||||
"""General abstract quantum state used as a base class for Ket and Bra."""
|
||||
pass
|
||||
|
||||
class OrthogonalKet(OrthogonalState, KetBase):
|
||||
"""Orthogonal Ket in quantum mechanics.
|
||||
|
||||
The inner product of two states with different labels will give zero,
|
||||
states with the same label will give one.
|
||||
|
||||
>>> from sympy.physics.quantum import OrthogonalBra, OrthogonalKet
|
||||
>>> from sympy.abc import m, n
|
||||
>>> (OrthogonalBra(n)*OrthogonalKet(n)).doit()
|
||||
1
|
||||
>>> (OrthogonalBra(n)*OrthogonalKet(n+1)).doit()
|
||||
0
|
||||
>>> (OrthogonalBra(n)*OrthogonalKet(m)).doit()
|
||||
<n|m>
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return OrthogonalBra
|
||||
|
||||
def _eval_innerproduct(self, bra, **hints):
|
||||
|
||||
if len(self.args) != len(bra.args):
|
||||
raise ValueError('Cannot multiply a ket that has a different number of labels.')
|
||||
|
||||
for arg, bra_arg in zip(self.args, bra.args):
|
||||
diff = arg - bra_arg
|
||||
diff = diff.expand()
|
||||
|
||||
is_zero = diff.is_zero
|
||||
|
||||
if is_zero is False:
|
||||
return S.Zero # i.e. Integer(0)
|
||||
|
||||
if is_zero is None:
|
||||
return None
|
||||
|
||||
return S.One # i.e. Integer(1)
|
||||
|
||||
|
||||
class OrthogonalBra(OrthogonalState, BraBase):
|
||||
"""Orthogonal Bra in quantum mechanics.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return OrthogonalKet
|
||||
|
||||
|
||||
class Wavefunction(Function):
|
||||
"""Class for representations in continuous bases
|
||||
|
||||
This class takes an expression and coordinates in its constructor. It can
|
||||
be used to easily calculate normalizations and probabilities.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
expr : Expr
|
||||
The expression representing the functional form of the w.f.
|
||||
|
||||
coords : Symbol or tuple
|
||||
The coordinates to be integrated over, and their bounds
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Particle in a box, specifying bounds in the more primitive way of using
|
||||
Piecewise:
|
||||
|
||||
>>> from sympy import Symbol, Piecewise, pi, N
|
||||
>>> from sympy.functions import sqrt, sin
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> x = Symbol('x', real=True)
|
||||
>>> n = 1
|
||||
>>> L = 1
|
||||
>>> g = Piecewise((0, x < 0), (0, x > L), (sqrt(2//L)*sin(n*pi*x/L), True))
|
||||
>>> f = Wavefunction(g, x)
|
||||
>>> f.norm
|
||||
1
|
||||
>>> f.is_normalized
|
||||
True
|
||||
>>> p = f.prob()
|
||||
>>> p(0)
|
||||
0
|
||||
>>> p(L)
|
||||
0
|
||||
>>> p(0.5)
|
||||
2
|
||||
>>> p(0.85*L)
|
||||
2*sin(0.85*pi)**2
|
||||
>>> N(p(0.85*L))
|
||||
0.412214747707527
|
||||
|
||||
Additionally, you can specify the bounds of the function and the indices in
|
||||
a more compact way:
|
||||
|
||||
>>> from sympy import symbols, pi, diff
|
||||
>>> from sympy.functions import sqrt, sin
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> x, L = symbols('x,L', positive=True)
|
||||
>>> n = symbols('n', integer=True, positive=True)
|
||||
>>> g = sqrt(2/L)*sin(n*pi*x/L)
|
||||
>>> f = Wavefunction(g, (x, 0, L))
|
||||
>>> f.norm
|
||||
1
|
||||
>>> f(L+1)
|
||||
0
|
||||
>>> f(L-1)
|
||||
sqrt(2)*sin(pi*n*(L - 1)/L)/sqrt(L)
|
||||
>>> f(-1)
|
||||
0
|
||||
>>> f(0.85)
|
||||
sqrt(2)*sin(0.85*pi*n/L)/sqrt(L)
|
||||
>>> f(0.85, n=1, L=1)
|
||||
sqrt(2)*sin(0.85*pi)
|
||||
>>> f.is_commutative
|
||||
False
|
||||
|
||||
All arguments are automatically sympified, so you can define the variables
|
||||
as strings rather than symbols:
|
||||
|
||||
>>> expr = x**2
|
||||
>>> f = Wavefunction(expr, 'x')
|
||||
>>> type(f.variables[0])
|
||||
<class 'sympy.core.symbol.Symbol'>
|
||||
|
||||
Derivatives of Wavefunctions will return Wavefunctions:
|
||||
|
||||
>>> diff(f, x)
|
||||
Wavefunction(2*x, x)
|
||||
|
||||
"""
|
||||
|
||||
#Any passed tuples for coordinates and their bounds need to be
|
||||
#converted to Tuples before Function's constructor is called, to
|
||||
#avoid errors from calling is_Float in the constructor
|
||||
def __new__(cls, *args, **options):
|
||||
new_args = [None for i in args]
|
||||
ct = 0
|
||||
for arg in args:
|
||||
if isinstance(arg, tuple):
|
||||
new_args[ct] = Tuple(*arg)
|
||||
else:
|
||||
new_args[ct] = arg
|
||||
ct += 1
|
||||
|
||||
return super().__new__(cls, *new_args, **options)
|
||||
|
||||
def __call__(self, *args, **options):
|
||||
var = self.variables
|
||||
|
||||
if len(args) != len(var):
|
||||
raise NotImplementedError(
|
||||
"Incorrect number of arguments to function!")
|
||||
|
||||
ct = 0
|
||||
#If the passed value is outside the specified bounds, return 0
|
||||
for v in var:
|
||||
lower, upper = self.limits[v]
|
||||
|
||||
#Do the comparison to limits only if the passed symbol is actually
|
||||
#a symbol present in the limits;
|
||||
#Had problems with a comparison of x > L
|
||||
if isinstance(args[ct], Expr) and \
|
||||
not (lower in args[ct].free_symbols
|
||||
or upper in args[ct].free_symbols):
|
||||
continue
|
||||
|
||||
if (args[ct] < lower) == True or (args[ct] > upper) == True:
|
||||
return S.Zero
|
||||
|
||||
ct += 1
|
||||
|
||||
expr = self.expr
|
||||
|
||||
#Allows user to make a call like f(2, 4, m=1, n=1)
|
||||
for symbol in list(expr.free_symbols):
|
||||
if str(symbol) in options.keys():
|
||||
val = options[str(symbol)]
|
||||
expr = expr.subs(symbol, val)
|
||||
|
||||
return expr.subs(zip(var, args))
|
||||
|
||||
def _eval_derivative(self, symbol):
|
||||
expr = self.expr
|
||||
deriv = expr._eval_derivative(symbol)
|
||||
|
||||
return Wavefunction(deriv, *self.args[1:])
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return Wavefunction(conjugate(self.expr), *self.args[1:])
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self
|
||||
|
||||
@property
|
||||
def is_commutative(self):
|
||||
"""
|
||||
Override Function's is_commutative so that order is preserved in
|
||||
represented expressions
|
||||
"""
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def eval(self, *args):
|
||||
return None
|
||||
|
||||
@property
|
||||
def variables(self):
|
||||
"""
|
||||
Return the coordinates which the wavefunction depends on
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> from sympy import symbols
|
||||
>>> x,y = symbols('x,y')
|
||||
>>> f = Wavefunction(x*y, x, y)
|
||||
>>> f.variables
|
||||
(x, y)
|
||||
>>> g = Wavefunction(x*y, x)
|
||||
>>> g.variables
|
||||
(x,)
|
||||
|
||||
"""
|
||||
var = [g[0] if isinstance(g, Tuple) else g for g in self._args[1:]]
|
||||
return tuple(var)
|
||||
|
||||
@property
|
||||
def limits(self):
|
||||
"""
|
||||
Return the limits of the coordinates which the w.f. depends on If no
|
||||
limits are specified, defaults to ``(-oo, oo)``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> from sympy import symbols
|
||||
>>> x, y = symbols('x, y')
|
||||
>>> f = Wavefunction(x**2, (x, 0, 1))
|
||||
>>> f.limits
|
||||
{x: (0, 1)}
|
||||
>>> f = Wavefunction(x**2, x)
|
||||
>>> f.limits
|
||||
{x: (-oo, oo)}
|
||||
>>> f = Wavefunction(x**2 + y**2, x, (y, -1, 2))
|
||||
>>> f.limits
|
||||
{x: (-oo, oo), y: (-1, 2)}
|
||||
|
||||
"""
|
||||
limits = [(g[1], g[2]) if isinstance(g, Tuple) else (-oo, oo)
|
||||
for g in self._args[1:]]
|
||||
return dict(zip(self.variables, tuple(limits)))
|
||||
|
||||
@property
|
||||
def expr(self):
|
||||
"""
|
||||
Return the expression which is the functional form of the Wavefunction
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> from sympy import symbols
|
||||
>>> x, y = symbols('x, y')
|
||||
>>> f = Wavefunction(x**2, x)
|
||||
>>> f.expr
|
||||
x**2
|
||||
|
||||
"""
|
||||
return self._args[0]
|
||||
|
||||
@property
|
||||
def is_normalized(self):
|
||||
"""
|
||||
Returns true if the Wavefunction is properly normalized
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, pi
|
||||
>>> from sympy.functions import sqrt, sin
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> x, L = symbols('x,L', positive=True)
|
||||
>>> n = symbols('n', integer=True, positive=True)
|
||||
>>> g = sqrt(2/L)*sin(n*pi*x/L)
|
||||
>>> f = Wavefunction(g, (x, 0, L))
|
||||
>>> f.is_normalized
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
return equal_valued(self.norm, 1)
|
||||
|
||||
@property # type: ignore
|
||||
@cacheit
|
||||
def norm(self):
|
||||
"""
|
||||
Return the normalization of the specified functional form.
|
||||
|
||||
This function integrates over the coordinates of the Wavefunction, with
|
||||
the bounds specified.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, pi
|
||||
>>> from sympy.functions import sqrt, sin
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> x, L = symbols('x,L', positive=True)
|
||||
>>> n = symbols('n', integer=True, positive=True)
|
||||
>>> g = sqrt(2/L)*sin(n*pi*x/L)
|
||||
>>> f = Wavefunction(g, (x, 0, L))
|
||||
>>> f.norm
|
||||
1
|
||||
>>> g = sin(n*pi*x/L)
|
||||
>>> f = Wavefunction(g, (x, 0, L))
|
||||
>>> f.norm
|
||||
sqrt(2)*sqrt(L)/2
|
||||
|
||||
"""
|
||||
|
||||
exp = self.expr*conjugate(self.expr)
|
||||
var = self.variables
|
||||
limits = self.limits
|
||||
|
||||
for v in var:
|
||||
curr_limits = limits[v]
|
||||
exp = integrate(exp, (v, curr_limits[0], curr_limits[1]))
|
||||
|
||||
return sqrt(exp)
|
||||
|
||||
def normalize(self):
|
||||
"""
|
||||
Return a normalized version of the Wavefunction
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, pi
|
||||
>>> from sympy.functions import sin
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> x = symbols('x', real=True)
|
||||
>>> L = symbols('L', positive=True)
|
||||
>>> n = symbols('n', integer=True, positive=True)
|
||||
>>> g = sin(n*pi*x/L)
|
||||
>>> f = Wavefunction(g, (x, 0, L))
|
||||
>>> f.normalize()
|
||||
Wavefunction(sqrt(2)*sin(pi*n*x/L)/sqrt(L), (x, 0, L))
|
||||
|
||||
"""
|
||||
const = self.norm
|
||||
|
||||
if const is oo:
|
||||
raise NotImplementedError("The function is not normalizable!")
|
||||
else:
|
||||
return Wavefunction((const)**(-1)*self.expr, *self.args[1:])
|
||||
|
||||
def prob(self):
|
||||
r"""
|
||||
Return the absolute magnitude of the w.f., `|\psi(x)|^2`
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, pi
|
||||
>>> from sympy.functions import sin
|
||||
>>> from sympy.physics.quantum.state import Wavefunction
|
||||
>>> x, L = symbols('x,L', real=True)
|
||||
>>> n = symbols('n', integer=True)
|
||||
>>> g = sin(n*pi*x/L)
|
||||
>>> f = Wavefunction(g, (x, 0, L))
|
||||
>>> f.prob()
|
||||
Wavefunction(sin(pi*n*x/L)**2, x)
|
||||
|
||||
"""
|
||||
|
||||
return Wavefunction(self.expr*conjugate(self.expr), *self.variables)
|
||||
@@ -0,0 +1,363 @@
|
||||
"""Abstract tensor product."""
|
||||
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.kind import KindDispatcher
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.matrices.dense import DenseMatrix as Matrix
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix as ImmutableMatrix
|
||||
from sympy.printing.pretty.stringpict import prettyForm
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.kind import (
|
||||
KetKind, _KetKind,
|
||||
BraKind, _BraKind,
|
||||
OperatorKind, _OperatorKind
|
||||
)
|
||||
from sympy.physics.quantum.matrixutils import (
|
||||
numpy_ndarray,
|
||||
scipy_sparse_matrix,
|
||||
matrix_tensor_product
|
||||
)
|
||||
from sympy.physics.quantum.state import Ket, Bra
|
||||
from sympy.physics.quantum.trace import Tr
|
||||
|
||||
|
||||
__all__ = [
|
||||
'TensorProduct',
|
||||
'tensor_product_simp'
|
||||
]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Tensor product
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
_combined_printing = False
|
||||
|
||||
|
||||
def combined_tensor_printing(combined):
|
||||
"""Set flag controlling whether tensor products of states should be
|
||||
printed as a combined bra/ket or as an explicit tensor product of different
|
||||
bra/kets. This is a global setting for all TensorProduct class instances.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
combine : bool
|
||||
When true, tensor product states are combined into one ket/bra, and
|
||||
when false explicit tensor product notation is used between each
|
||||
ket/bra.
|
||||
"""
|
||||
global _combined_printing
|
||||
_combined_printing = combined
|
||||
|
||||
|
||||
class TensorProduct(Expr):
|
||||
"""The tensor product of two or more arguments.
|
||||
|
||||
For matrices, this uses ``matrix_tensor_product`` to compute the Kronecker
|
||||
or tensor product matrix. For other objects a symbolic ``TensorProduct``
|
||||
instance is returned. The tensor product is a non-commutative
|
||||
multiplication that is used primarily with operators and states in quantum
|
||||
mechanics.
|
||||
|
||||
Currently, the tensor product distinguishes between commutative and
|
||||
non-commutative arguments. Commutative arguments are assumed to be scalars
|
||||
and are pulled out in front of the ``TensorProduct``. Non-commutative
|
||||
arguments remain in the resulting ``TensorProduct``.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args : tuple
|
||||
A sequence of the objects to take the tensor product of.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Start with a simple tensor product of SymPy matrices::
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.physics.quantum import TensorProduct
|
||||
|
||||
>>> m1 = Matrix([[1,2],[3,4]])
|
||||
>>> m2 = Matrix([[1,0],[0,1]])
|
||||
>>> TensorProduct(m1, m2)
|
||||
Matrix([
|
||||
[1, 0, 2, 0],
|
||||
[0, 1, 0, 2],
|
||||
[3, 0, 4, 0],
|
||||
[0, 3, 0, 4]])
|
||||
>>> TensorProduct(m2, m1)
|
||||
Matrix([
|
||||
[1, 2, 0, 0],
|
||||
[3, 4, 0, 0],
|
||||
[0, 0, 1, 2],
|
||||
[0, 0, 3, 4]])
|
||||
|
||||
We can also construct tensor products of non-commutative symbols:
|
||||
|
||||
>>> from sympy import Symbol
|
||||
>>> A = Symbol('A',commutative=False)
|
||||
>>> B = Symbol('B',commutative=False)
|
||||
>>> tp = TensorProduct(A, B)
|
||||
>>> tp
|
||||
AxB
|
||||
|
||||
We can take the dagger of a tensor product (note the order does NOT reverse
|
||||
like the dagger of a normal product):
|
||||
|
||||
>>> from sympy.physics.quantum import Dagger
|
||||
>>> Dagger(tp)
|
||||
Dagger(A)xDagger(B)
|
||||
|
||||
Expand can be used to distribute a tensor product across addition:
|
||||
|
||||
>>> C = Symbol('C',commutative=False)
|
||||
>>> tp = TensorProduct(A+B,C)
|
||||
>>> tp
|
||||
(A + B)xC
|
||||
>>> tp.expand(tensorproduct=True)
|
||||
AxC + BxC
|
||||
"""
|
||||
is_commutative = False
|
||||
|
||||
_kind_dispatcher = KindDispatcher("TensorProduct_kind_dispatcher", commutative=True)
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
"""Calculate the kind of a tensor product by looking at its children."""
|
||||
arg_kinds = (a.kind for a in self.args)
|
||||
return self._kind_dispatcher(*arg_kinds)
|
||||
|
||||
def __new__(cls, *args):
|
||||
if isinstance(args[0], (Matrix, ImmutableMatrix, numpy_ndarray,
|
||||
scipy_sparse_matrix)):
|
||||
return matrix_tensor_product(*args)
|
||||
c_part, new_args = cls.flatten(sympify(args))
|
||||
c_part = Mul(*c_part)
|
||||
if len(new_args) == 0:
|
||||
return c_part
|
||||
elif len(new_args) == 1:
|
||||
return c_part * new_args[0]
|
||||
else:
|
||||
tp = Expr.__new__(cls, *new_args)
|
||||
return c_part * tp
|
||||
|
||||
@classmethod
|
||||
def flatten(cls, args):
|
||||
# TODO: disallow nested TensorProducts.
|
||||
c_part = []
|
||||
nc_parts = []
|
||||
for arg in args:
|
||||
cp, ncp = arg.args_cnc()
|
||||
c_part.extend(list(cp))
|
||||
nc_parts.append(Mul._from_args(ncp))
|
||||
return c_part, nc_parts
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return TensorProduct(*[Dagger(i) for i in self.args])
|
||||
|
||||
def _eval_rewrite(self, rule, args, **hints):
|
||||
return TensorProduct(*args).expand(tensorproduct=True)
|
||||
|
||||
def _sympystr(self, printer, *args):
|
||||
length = len(self.args)
|
||||
s = ''
|
||||
for i in range(length):
|
||||
if isinstance(self.args[i], (Add, Pow, Mul)):
|
||||
s = s + '('
|
||||
s = s + printer._print(self.args[i])
|
||||
if isinstance(self.args[i], (Add, Pow, Mul)):
|
||||
s = s + ')'
|
||||
if i != length - 1:
|
||||
s = s + 'x'
|
||||
return s
|
||||
|
||||
def _pretty(self, printer, *args):
|
||||
|
||||
if (_combined_printing and
|
||||
(all(isinstance(arg, Ket) for arg in self.args) or
|
||||
all(isinstance(arg, Bra) for arg in self.args))):
|
||||
|
||||
length = len(self.args)
|
||||
pform = printer._print('', *args)
|
||||
for i in range(length):
|
||||
next_pform = printer._print('', *args)
|
||||
length_i = len(self.args[i].args)
|
||||
for j in range(length_i):
|
||||
part_pform = printer._print(self.args[i].args[j], *args)
|
||||
next_pform = prettyForm(*next_pform.right(part_pform))
|
||||
if j != length_i - 1:
|
||||
next_pform = prettyForm(*next_pform.right(', '))
|
||||
|
||||
if len(self.args[i].args) > 1:
|
||||
next_pform = prettyForm(
|
||||
*next_pform.parens(left='{', right='}'))
|
||||
pform = prettyForm(*pform.right(next_pform))
|
||||
if i != length - 1:
|
||||
pform = prettyForm(*pform.right(',' + ' '))
|
||||
|
||||
pform = prettyForm(*pform.left(self.args[0].lbracket))
|
||||
pform = prettyForm(*pform.right(self.args[0].rbracket))
|
||||
return pform
|
||||
|
||||
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], (Add, Mul)):
|
||||
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('\N{N-ARY CIRCLED TIMES OPERATOR}' + ' '))
|
||||
else:
|
||||
pform = prettyForm(*pform.right('x' + ' '))
|
||||
return pform
|
||||
|
||||
def _latex(self, printer, *args):
|
||||
|
||||
if (_combined_printing and
|
||||
(all(isinstance(arg, Ket) for arg in self.args) or
|
||||
all(isinstance(arg, Bra) for arg in self.args))):
|
||||
|
||||
def _label_wrap(label, nlabels):
|
||||
return label if nlabels == 1 else r"\left\{%s\right\}" % label
|
||||
|
||||
s = r", ".join([_label_wrap(arg._print_label_latex(printer, *args),
|
||||
len(arg.args)) for arg in self.args])
|
||||
|
||||
return r"{%s%s%s}" % (self.args[0].lbracket_latex, s,
|
||||
self.args[0].rbracket_latex)
|
||||
|
||||
length = len(self.args)
|
||||
s = ''
|
||||
for i in range(length):
|
||||
if isinstance(self.args[i], (Add, Mul)):
|
||||
s = s + '\\left('
|
||||
# The extra {} brackets are needed to get matplotlib's latex
|
||||
# rendered to render this properly.
|
||||
s = s + '{' + printer._print(self.args[i], *args) + '}'
|
||||
if isinstance(self.args[i], (Add, Mul)):
|
||||
s = s + '\\right)'
|
||||
if i != length - 1:
|
||||
s = s + '\\otimes '
|
||||
return s
|
||||
|
||||
def doit(self, **hints):
|
||||
return TensorProduct(*[item.doit(**hints) for item in self.args])
|
||||
|
||||
def _eval_expand_tensorproduct(self, **hints):
|
||||
"""Distribute TensorProducts across addition."""
|
||||
args = self.args
|
||||
add_args = []
|
||||
for i in range(len(args)):
|
||||
if isinstance(args[i], Add):
|
||||
for aa in args[i].args:
|
||||
tp = TensorProduct(*args[:i] + (aa,) + args[i + 1:])
|
||||
c_part, nc_part = tp.args_cnc()
|
||||
# Check for TensorProduct object: is the one object in nc_part, if any:
|
||||
# (Note: any other object type to be expanded must be added here)
|
||||
if len(nc_part) == 1 and isinstance(nc_part[0], TensorProduct):
|
||||
nc_part = (nc_part[0]._eval_expand_tensorproduct(), )
|
||||
add_args.append(Mul(*c_part)*Mul(*nc_part))
|
||||
break
|
||||
|
||||
if add_args:
|
||||
return Add(*add_args)
|
||||
else:
|
||||
return self
|
||||
|
||||
def _eval_trace(self, **kwargs):
|
||||
indices = kwargs.get('indices', None)
|
||||
exp = self
|
||||
|
||||
if indices is None or len(indices) == 0:
|
||||
return Mul(*[Tr(arg).doit() for arg in exp.args])
|
||||
else:
|
||||
return Mul(*[Tr(value).doit() if idx in indices else value
|
||||
for idx, value in enumerate(exp.args)])
|
||||
|
||||
|
||||
def tensor_product_simp_Mul(e):
|
||||
"""Simplify a Mul with tensor products.
|
||||
|
||||
.. deprecated:: 1.14.
|
||||
The transformations applied by this function are not done automatically
|
||||
when tensor products are combined.
|
||||
|
||||
Originally, the main use of this function is to simplify a ``Mul`` of
|
||||
``TensorProduct``s to a ``TensorProduct`` of ``Muls``.
|
||||
"""
|
||||
sympy_deprecation_warning(
|
||||
"""
|
||||
tensor_product_simp_Mul has been deprecated. The transformations
|
||||
performed by this function are now done automatically when
|
||||
tensor products are multiplied.
|
||||
""",
|
||||
deprecated_since_version="1.14",
|
||||
active_deprecations_target='deprecated-tensorproduct-simp'
|
||||
)
|
||||
return e
|
||||
|
||||
def tensor_product_simp_Pow(e):
|
||||
"""Evaluates ``Pow`` expressions whose base is ``TensorProduct``
|
||||
|
||||
.. deprecated:: 1.14.
|
||||
The transformations applied by this function are not done automatically
|
||||
when tensor products are combined.
|
||||
"""
|
||||
sympy_deprecation_warning(
|
||||
"""
|
||||
tensor_product_simp_Pow has been deprecated. The transformations
|
||||
performed by this function are now done automatically when
|
||||
tensor products are exponentiated.
|
||||
""",
|
||||
deprecated_since_version="1.14",
|
||||
active_deprecations_target='deprecated-tensorproduct-simp'
|
||||
)
|
||||
return e
|
||||
|
||||
|
||||
def tensor_product_simp(e, **hints):
|
||||
"""Try to simplify and combine tensor products.
|
||||
|
||||
.. deprecated:: 1.14.
|
||||
The transformations applied by this function are not done automatically
|
||||
when tensor products are combined.
|
||||
|
||||
Originally, this function tried to pull expressions inside of ``TensorProducts``.
|
||||
It only worked for relatively simple cases where the products have
|
||||
only scalars, raw ``TensorProducts``, not ``Add``, ``Pow``, ``Commutators``
|
||||
of ``TensorProducts``.
|
||||
"""
|
||||
sympy_deprecation_warning(
|
||||
"""
|
||||
tensor_product_simp has been deprecated. The transformations
|
||||
performed by this function are now done automatically when
|
||||
tensor products are combined.
|
||||
""",
|
||||
deprecated_since_version="1.14",
|
||||
active_deprecations_target='deprecated-tensorproduct-simp'
|
||||
)
|
||||
return e
|
||||
|
||||
|
||||
@TensorProduct._kind_dispatcher.register(_OperatorKind, _OperatorKind)
|
||||
def find_op_kind(e1, e2):
|
||||
return OperatorKind
|
||||
|
||||
|
||||
@TensorProduct._kind_dispatcher.register(_KetKind, _KetKind)
|
||||
def find_ket_kind(e1, e2):
|
||||
return KetKind
|
||||
|
||||
|
||||
@TensorProduct._kind_dispatcher.register(_BraKind, _BraKind)
|
||||
def find_bra_kind(e1, e2):
|
||||
return BraKind
|
||||
@@ -0,0 +1,56 @@
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.symbol import symbols
|
||||
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.anticommutator import AntiCommutator as AComm
|
||||
from sympy.physics.quantum.operator import Operator
|
||||
|
||||
|
||||
a, b, c = symbols('a,b,c')
|
||||
A, B, C, D = symbols('A,B,C,D', commutative=False)
|
||||
|
||||
|
||||
def test_anticommutator():
|
||||
ac = AComm(A, B)
|
||||
assert isinstance(ac, AComm)
|
||||
assert ac.is_commutative is False
|
||||
assert ac.subs(A, C) == AComm(C, B)
|
||||
|
||||
|
||||
def test_commutator_identities():
|
||||
assert AComm(a*A, b*B) == a*b*AComm(A, B)
|
||||
assert AComm(A, A) == 2*A**2
|
||||
assert AComm(A, B) == AComm(B, A)
|
||||
assert AComm(a, b) == 2*a*b
|
||||
assert AComm(A, B).doit() == A*B + B*A
|
||||
|
||||
|
||||
def test_anticommutator_dagger():
|
||||
assert Dagger(AComm(A, B)) == AComm(Dagger(A), Dagger(B))
|
||||
|
||||
|
||||
class Foo(Operator):
|
||||
|
||||
def _eval_anticommutator_Bar(self, bar):
|
||||
return Integer(0)
|
||||
|
||||
|
||||
class Bar(Operator):
|
||||
pass
|
||||
|
||||
|
||||
class Tam(Operator):
|
||||
|
||||
def _eval_anticommutator_Foo(self, foo):
|
||||
return Integer(1)
|
||||
|
||||
|
||||
def test_eval_commutator():
|
||||
F = Foo('F')
|
||||
B = Bar('B')
|
||||
T = Tam('T')
|
||||
assert AComm(F, B).doit() == 0
|
||||
assert AComm(B, F).doit() == 0
|
||||
assert AComm(F, T).doit() == 1
|
||||
assert AComm(T, F).doit() == 1
|
||||
assert AComm(B, T).doit() == B*T + T*B
|
||||
@@ -0,0 +1,50 @@
|
||||
from math import prod
|
||||
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.physics.quantum import Dagger, Commutator, qapply
|
||||
from sympy.physics.quantum.boson import BosonOp
|
||||
from sympy.physics.quantum.boson import (
|
||||
BosonFockKet, BosonFockBra, BosonCoherentKet, BosonCoherentBra)
|
||||
|
||||
|
||||
def test_bosonoperator():
|
||||
a = BosonOp('a')
|
||||
b = BosonOp('b')
|
||||
|
||||
assert isinstance(a, BosonOp)
|
||||
assert isinstance(Dagger(a), BosonOp)
|
||||
|
||||
assert a.is_annihilation
|
||||
assert not Dagger(a).is_annihilation
|
||||
|
||||
assert BosonOp("a") == BosonOp("a", True)
|
||||
assert BosonOp("a") != BosonOp("c")
|
||||
assert BosonOp("a", True) != BosonOp("a", False)
|
||||
|
||||
assert Commutator(a, Dagger(a)).doit() == 1
|
||||
|
||||
assert Commutator(a, Dagger(b)).doit() == a * Dagger(b) - Dagger(b) * a
|
||||
|
||||
assert Dagger(exp(a)) == exp(Dagger(a))
|
||||
|
||||
|
||||
def test_boson_states():
|
||||
a = BosonOp("a")
|
||||
|
||||
# Fock states
|
||||
n = 3
|
||||
assert (BosonFockBra(0) * BosonFockKet(1)).doit() == 0
|
||||
assert (BosonFockBra(1) * BosonFockKet(1)).doit() == 1
|
||||
assert qapply(BosonFockBra(n) * Dagger(a)**n * BosonFockKet(0)) \
|
||||
== sqrt(prod(range(1, n+1)))
|
||||
|
||||
# Coherent states
|
||||
alpha1, alpha2 = 1.2, 4.3
|
||||
assert (BosonCoherentBra(alpha1) * BosonCoherentKet(alpha1)).doit() == 1
|
||||
assert (BosonCoherentBra(alpha2) * BosonCoherentKet(alpha2)).doit() == 1
|
||||
assert abs((BosonCoherentBra(alpha1) * BosonCoherentKet(alpha2)).doit() -
|
||||
exp((alpha1 - alpha2) ** 2 * Rational(-1, 2))) < 1e-12
|
||||
assert qapply(a * BosonCoherentKet(alpha1)) == \
|
||||
alpha1 * BosonCoherentKet(alpha1)
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Tests for cartesian.py"""
|
||||
|
||||
from sympy.core.numbers import (I, pi)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.special.delta_functions import DiracDelta
|
||||
from sympy.sets.sets import Interval
|
||||
from sympy.testing.pytest import XFAIL
|
||||
|
||||
from sympy.physics.quantum import qapply, represent, L2, Dagger
|
||||
from sympy.physics.quantum import Commutator, hbar
|
||||
from sympy.physics.quantum.cartesian import (
|
||||
XOp, YOp, ZOp, PxOp, X, Y, Z, Px, XKet, XBra, PxKet, PxBra,
|
||||
PositionKet3D, PositionBra3D
|
||||
)
|
||||
from sympy.physics.quantum.operator import DifferentialOperator
|
||||
|
||||
x, y, z, x_1, x_2, x_3, y_1, z_1 = symbols('x,y,z,x_1,x_2,x_3,y_1,z_1')
|
||||
px, py, px_1, px_2 = symbols('px py px_1 px_2')
|
||||
|
||||
|
||||
def test_x():
|
||||
assert X.hilbert_space == L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
assert Commutator(X, Px).doit() == I*hbar
|
||||
assert qapply(X*XKet(x)) == x*XKet(x)
|
||||
assert XKet(x).dual_class() == XBra
|
||||
assert XBra(x).dual_class() == XKet
|
||||
assert (Dagger(XKet(y))*XKet(x)).doit() == DiracDelta(x - y)
|
||||
assert (PxBra(px)*XKet(x)).doit() == \
|
||||
exp(-I*x*px/hbar)/sqrt(2*pi*hbar)
|
||||
assert represent(XKet(x)) == DiracDelta(x - x_1)
|
||||
assert represent(XBra(x)) == DiracDelta(-x + x_1)
|
||||
assert XBra(x).position == x
|
||||
assert represent(XOp()*XKet()) == x*DiracDelta(x - x_2)
|
||||
assert represent(XBra("y")*XKet()) == DiracDelta(x - y)
|
||||
assert represent(
|
||||
XKet()*XBra()) == DiracDelta(x - x_2) * DiracDelta(x_1 - x)
|
||||
|
||||
rep_p = represent(XOp(), basis=PxOp)
|
||||
assert rep_p == hbar*I*DiracDelta(px_1 - px_2)*DifferentialOperator(px_1)
|
||||
assert rep_p == represent(XOp(), basis=PxOp())
|
||||
assert rep_p == represent(XOp(), basis=PxKet)
|
||||
assert rep_p == represent(XOp(), basis=PxKet())
|
||||
|
||||
assert represent(XOp()*PxKet(), basis=PxKet) == \
|
||||
hbar*I*DiracDelta(px - px_2)*DifferentialOperator(px)
|
||||
|
||||
|
||||
@XFAIL
|
||||
def _text_x_broken():
|
||||
# represent has some broken logic that is relying in particular
|
||||
# forms of input, rather than a full and proper handling of
|
||||
# all valid quantum expressions. Marking this test as XFAIL until
|
||||
# we can refactor represent.
|
||||
assert represent(XOp()*XKet()*XBra('y')) == \
|
||||
x*DiracDelta(x - x_3)*DiracDelta(x_1 - y)
|
||||
|
||||
|
||||
def test_p():
|
||||
assert Px.hilbert_space == L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
assert qapply(Px*PxKet(px)) == px*PxKet(px)
|
||||
assert PxKet(px).dual_class() == PxBra
|
||||
assert PxBra(x).dual_class() == PxKet
|
||||
assert (Dagger(PxKet(py))*PxKet(px)).doit() == DiracDelta(px - py)
|
||||
assert (XBra(x)*PxKet(px)).doit() == \
|
||||
exp(I*x*px/hbar)/sqrt(2*pi*hbar)
|
||||
assert represent(PxKet(px)) == DiracDelta(px - px_1)
|
||||
|
||||
rep_x = represent(PxOp(), basis=XOp)
|
||||
assert rep_x == -hbar*I*DiracDelta(x_1 - x_2)*DifferentialOperator(x_1)
|
||||
assert rep_x == represent(PxOp(), basis=XOp())
|
||||
assert rep_x == represent(PxOp(), basis=XKet)
|
||||
assert rep_x == represent(PxOp(), basis=XKet())
|
||||
|
||||
assert represent(PxOp()*XKet(), basis=XKet) == \
|
||||
-hbar*I*DiracDelta(x - x_2)*DifferentialOperator(x)
|
||||
assert represent(XBra("y")*PxOp()*XKet(), basis=XKet) == \
|
||||
-hbar*I*DiracDelta(x - y)*DifferentialOperator(x)
|
||||
|
||||
|
||||
def test_3dpos():
|
||||
assert Y.hilbert_space == L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
assert Z.hilbert_space == L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
|
||||
test_ket = PositionKet3D(x, y, z)
|
||||
assert qapply(X*test_ket) == x*test_ket
|
||||
assert qapply(Y*test_ket) == y*test_ket
|
||||
assert qapply(Z*test_ket) == z*test_ket
|
||||
assert qapply(X*Y*test_ket) == x*y*test_ket
|
||||
assert qapply(X*Y*Z*test_ket) == x*y*z*test_ket
|
||||
assert qapply(Y*Z*test_ket) == y*z*test_ket
|
||||
|
||||
assert PositionKet3D() == test_ket
|
||||
assert YOp() == Y
|
||||
assert ZOp() == Z
|
||||
|
||||
assert PositionKet3D.dual_class() == PositionBra3D
|
||||
assert PositionBra3D.dual_class() == PositionKet3D
|
||||
|
||||
other_ket = PositionKet3D(x_1, y_1, z_1)
|
||||
assert (Dagger(other_ket)*test_ket).doit() == \
|
||||
DiracDelta(x - x_1)*DiracDelta(y - y_1)*DiracDelta(z - z_1)
|
||||
|
||||
assert test_ket.position_x == x
|
||||
assert test_ket.position_y == y
|
||||
assert test_ket.position_z == z
|
||||
assert other_ket.position_x == x_1
|
||||
assert other_ket.position_y == y_1
|
||||
assert other_ket.position_z == z_1
|
||||
|
||||
# TODO: Add tests for representations
|
||||
@@ -0,0 +1,183 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.physics.quantum.cg import Wigner3j, Wigner6j, Wigner9j, CG, cg_simp
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
|
||||
|
||||
def test_cg_simp_add():
|
||||
j, m1, m1p, m2, m2p = symbols('j m1 m1p m2 m2p')
|
||||
# Test Varshalovich 8.7.1 Eq 1
|
||||
a = CG(S.Half, S.Half, 0, 0, S.Half, S.Half)
|
||||
b = CG(S.Half, Rational(-1, 2), 0, 0, S.Half, Rational(-1, 2))
|
||||
c = CG(1, 1, 0, 0, 1, 1)
|
||||
d = CG(1, 0, 0, 0, 1, 0)
|
||||
e = CG(1, -1, 0, 0, 1, -1)
|
||||
assert cg_simp(a + b) == 2
|
||||
assert cg_simp(c + d + e) == 3
|
||||
assert cg_simp(a + b + c + d + e) == 5
|
||||
assert cg_simp(a + b + c) == 2 + c
|
||||
assert cg_simp(2*a + b) == 2 + a
|
||||
assert cg_simp(2*c + d + e) == 3 + c
|
||||
assert cg_simp(5*a + 5*b) == 10
|
||||
assert cg_simp(5*c + 5*d + 5*e) == 15
|
||||
assert cg_simp(-a - b) == -2
|
||||
assert cg_simp(-c - d - e) == -3
|
||||
assert cg_simp(-6*a - 6*b) == -12
|
||||
assert cg_simp(-4*c - 4*d - 4*e) == -12
|
||||
a = CG(S.Half, S.Half, j, 0, S.Half, S.Half)
|
||||
b = CG(S.Half, Rational(-1, 2), j, 0, S.Half, Rational(-1, 2))
|
||||
c = CG(1, 1, j, 0, 1, 1)
|
||||
d = CG(1, 0, j, 0, 1, 0)
|
||||
e = CG(1, -1, j, 0, 1, -1)
|
||||
assert cg_simp(a + b) == 2*KroneckerDelta(j, 0)
|
||||
assert cg_simp(c + d + e) == 3*KroneckerDelta(j, 0)
|
||||
assert cg_simp(a + b + c + d + e) == 5*KroneckerDelta(j, 0)
|
||||
assert cg_simp(a + b + c) == 2*KroneckerDelta(j, 0) + c
|
||||
assert cg_simp(2*a + b) == 2*KroneckerDelta(j, 0) + a
|
||||
assert cg_simp(2*c + d + e) == 3*KroneckerDelta(j, 0) + c
|
||||
assert cg_simp(5*a + 5*b) == 10*KroneckerDelta(j, 0)
|
||||
assert cg_simp(5*c + 5*d + 5*e) == 15*KroneckerDelta(j, 0)
|
||||
assert cg_simp(-a - b) == -2*KroneckerDelta(j, 0)
|
||||
assert cg_simp(-c - d - e) == -3*KroneckerDelta(j, 0)
|
||||
assert cg_simp(-6*a - 6*b) == -12*KroneckerDelta(j, 0)
|
||||
assert cg_simp(-4*c - 4*d - 4*e) == -12*KroneckerDelta(j, 0)
|
||||
# Test Varshalovich 8.7.1 Eq 2
|
||||
a = CG(S.Half, S.Half, S.Half, Rational(-1, 2), 0, 0)
|
||||
b = CG(S.Half, Rational(-1, 2), S.Half, S.Half, 0, 0)
|
||||
c = CG(1, 1, 1, -1, 0, 0)
|
||||
d = CG(1, 0, 1, 0, 0, 0)
|
||||
e = CG(1, -1, 1, 1, 0, 0)
|
||||
assert cg_simp(a - b) == sqrt(2)
|
||||
assert cg_simp(c - d + e) == sqrt(3)
|
||||
assert cg_simp(a - b + c - d + e) == sqrt(2) + sqrt(3)
|
||||
assert cg_simp(a - b + c) == sqrt(2) + c
|
||||
assert cg_simp(2*a - b) == sqrt(2) + a
|
||||
assert cg_simp(2*c - d + e) == sqrt(3) + c
|
||||
assert cg_simp(5*a - 5*b) == 5*sqrt(2)
|
||||
assert cg_simp(5*c - 5*d + 5*e) == 5*sqrt(3)
|
||||
assert cg_simp(-a + b) == -sqrt(2)
|
||||
assert cg_simp(-c + d - e) == -sqrt(3)
|
||||
assert cg_simp(-6*a + 6*b) == -6*sqrt(2)
|
||||
assert cg_simp(-4*c + 4*d - 4*e) == -4*sqrt(3)
|
||||
a = CG(S.Half, S.Half, S.Half, Rational(-1, 2), j, 0)
|
||||
b = CG(S.Half, Rational(-1, 2), S.Half, S.Half, j, 0)
|
||||
c = CG(1, 1, 1, -1, j, 0)
|
||||
d = CG(1, 0, 1, 0, j, 0)
|
||||
e = CG(1, -1, 1, 1, j, 0)
|
||||
assert cg_simp(a - b) == sqrt(2)*KroneckerDelta(j, 0)
|
||||
assert cg_simp(c - d + e) == sqrt(3)*KroneckerDelta(j, 0)
|
||||
assert cg_simp(a - b + c - d + e) == sqrt(
|
||||
2)*KroneckerDelta(j, 0) + sqrt(3)*KroneckerDelta(j, 0)
|
||||
assert cg_simp(a - b + c) == sqrt(2)*KroneckerDelta(j, 0) + c
|
||||
assert cg_simp(2*a - b) == sqrt(2)*KroneckerDelta(j, 0) + a
|
||||
assert cg_simp(2*c - d + e) == sqrt(3)*KroneckerDelta(j, 0) + c
|
||||
assert cg_simp(5*a - 5*b) == 5*sqrt(2)*KroneckerDelta(j, 0)
|
||||
assert cg_simp(5*c - 5*d + 5*e) == 5*sqrt(3)*KroneckerDelta(j, 0)
|
||||
assert cg_simp(-a + b) == -sqrt(2)*KroneckerDelta(j, 0)
|
||||
assert cg_simp(-c + d - e) == -sqrt(3)*KroneckerDelta(j, 0)
|
||||
assert cg_simp(-6*a + 6*b) == -6*sqrt(2)*KroneckerDelta(j, 0)
|
||||
assert cg_simp(-4*c + 4*d - 4*e) == -4*sqrt(3)*KroneckerDelta(j, 0)
|
||||
# Test Varshalovich 8.7.2 Eq 9
|
||||
# alpha=alphap,beta=betap case
|
||||
# numerical
|
||||
a = CG(S.Half, S.Half, S.Half, Rational(-1, 2), 1, 0)**2
|
||||
b = CG(S.Half, S.Half, S.Half, Rational(-1, 2), 0, 0)**2
|
||||
c = CG(1, 0, 1, 1, 1, 1)**2
|
||||
d = CG(1, 0, 1, 1, 2, 1)**2
|
||||
assert cg_simp(a + b) == 1
|
||||
assert cg_simp(c + d) == 1
|
||||
assert cg_simp(a + b + c + d) == 2
|
||||
assert cg_simp(4*a + 4*b) == 4
|
||||
assert cg_simp(4*c + 4*d) == 4
|
||||
assert cg_simp(5*a + 3*b) == 3 + 2*a
|
||||
assert cg_simp(5*c + 3*d) == 3 + 2*c
|
||||
assert cg_simp(-a - b) == -1
|
||||
assert cg_simp(-c - d) == -1
|
||||
# symbolic
|
||||
a = CG(S.Half, m1, S.Half, m2, 1, 1)**2
|
||||
b = CG(S.Half, m1, S.Half, m2, 1, 0)**2
|
||||
c = CG(S.Half, m1, S.Half, m2, 1, -1)**2
|
||||
d = CG(S.Half, m1, S.Half, m2, 0, 0)**2
|
||||
assert cg_simp(a + b + c + d) == 1
|
||||
assert cg_simp(4*a + 4*b + 4*c + 4*d) == 4
|
||||
assert cg_simp(3*a + 5*b + 3*c + 4*d) == 3 + 2*b + d
|
||||
assert cg_simp(-a - b - c - d) == -1
|
||||
a = CG(1, m1, 1, m2, 2, 2)**2
|
||||
b = CG(1, m1, 1, m2, 2, 1)**2
|
||||
c = CG(1, m1, 1, m2, 2, 0)**2
|
||||
d = CG(1, m1, 1, m2, 2, -1)**2
|
||||
e = CG(1, m1, 1, m2, 2, -2)**2
|
||||
f = CG(1, m1, 1, m2, 1, 1)**2
|
||||
g = CG(1, m1, 1, m2, 1, 0)**2
|
||||
h = CG(1, m1, 1, m2, 1, -1)**2
|
||||
i = CG(1, m1, 1, m2, 0, 0)**2
|
||||
assert cg_simp(a + b + c + d + e + f + g + h + i) == 1
|
||||
assert cg_simp(4*(a + b + c + d + e + f + g + h + i)) == 4
|
||||
assert cg_simp(a + b + 2*c + d + 4*e + f + g + h + i) == 1 + c + 3*e
|
||||
assert cg_simp(-a - b - c - d - e - f - g - h - i) == -1
|
||||
# alpha!=alphap or beta!=betap case
|
||||
# numerical
|
||||
a = CG(S.Half, S(
|
||||
1)/2, S.Half, Rational(-1, 2), 1, 0)*CG(S.Half, Rational(-1, 2), S.Half, S.Half, 1, 0)
|
||||
b = CG(S.Half, S(
|
||||
1)/2, S.Half, Rational(-1, 2), 0, 0)*CG(S.Half, Rational(-1, 2), S.Half, S.Half, 0, 0)
|
||||
c = CG(1, 1, 1, 0, 2, 1)*CG(1, 0, 1, 1, 2, 1)
|
||||
d = CG(1, 1, 1, 0, 1, 1)*CG(1, 0, 1, 1, 1, 1)
|
||||
assert cg_simp(a + b) == 0
|
||||
assert cg_simp(c + d) == 0
|
||||
# symbolic
|
||||
a = CG(S.Half, m1, S.Half, m2, 1, 1)*CG(S.Half, m1p, S.Half, m2p, 1, 1)
|
||||
b = CG(S.Half, m1, S.Half, m2, 1, 0)*CG(S.Half, m1p, S.Half, m2p, 1, 0)
|
||||
c = CG(S.Half, m1, S.Half, m2, 1, -1)*CG(S.Half, m1p, S.Half, m2p, 1, -1)
|
||||
d = CG(S.Half, m1, S.Half, m2, 0, 0)*CG(S.Half, m1p, S.Half, m2p, 0, 0)
|
||||
assert cg_simp(a + b + c + d) == KroneckerDelta(m1, m1p)*KroneckerDelta(m2, m2p)
|
||||
a = CG(1, m1, 1, m2, 2, 2)*CG(1, m1p, 1, m2p, 2, 2)
|
||||
b = CG(1, m1, 1, m2, 2, 1)*CG(1, m1p, 1, m2p, 2, 1)
|
||||
c = CG(1, m1, 1, m2, 2, 0)*CG(1, m1p, 1, m2p, 2, 0)
|
||||
d = CG(1, m1, 1, m2, 2, -1)*CG(1, m1p, 1, m2p, 2, -1)
|
||||
e = CG(1, m1, 1, m2, 2, -2)*CG(1, m1p, 1, m2p, 2, -2)
|
||||
f = CG(1, m1, 1, m2, 1, 1)*CG(1, m1p, 1, m2p, 1, 1)
|
||||
g = CG(1, m1, 1, m2, 1, 0)*CG(1, m1p, 1, m2p, 1, 0)
|
||||
h = CG(1, m1, 1, m2, 1, -1)*CG(1, m1p, 1, m2p, 1, -1)
|
||||
i = CG(1, m1, 1, m2, 0, 0)*CG(1, m1p, 1, m2p, 0, 0)
|
||||
assert cg_simp(
|
||||
a + b + c + d + e + f + g + h + i) == KroneckerDelta(m1, m1p)*KroneckerDelta(m2, m2p)
|
||||
|
||||
|
||||
def test_cg_simp_sum():
|
||||
x, a, b, c, cp, alpha, beta, gamma, gammap = symbols(
|
||||
'x a b c cp alpha beta gamma gammap')
|
||||
# Varshalovich 8.7.1 Eq 1
|
||||
assert cg_simp(x * Sum(CG(a, alpha, b, 0, a, alpha), (alpha, -a, a)
|
||||
)) == x*(2*a + 1)*KroneckerDelta(b, 0)
|
||||
assert cg_simp(x * Sum(CG(a, alpha, b, 0, a, alpha), (alpha, -a, a)) + CG(1, 0, 1, 0, 1, 0)) == x*(2*a + 1)*KroneckerDelta(b, 0) + CG(1, 0, 1, 0, 1, 0)
|
||||
assert cg_simp(2 * Sum(CG(1, alpha, 0, 0, 1, alpha), (alpha, -1, 1))) == 6
|
||||
# Varshalovich 8.7.1 Eq 2
|
||||
assert cg_simp(x*Sum((-1)**(a - alpha) * CG(a, alpha, a, -alpha, c,
|
||||
0), (alpha, -a, a))) == x*sqrt(2*a + 1)*KroneckerDelta(c, 0)
|
||||
assert cg_simp(3*Sum((-1)**(2 - alpha) * CG(
|
||||
2, alpha, 2, -alpha, 0, 0), (alpha, -2, 2))) == 3*sqrt(5)
|
||||
# Varshalovich 8.7.2 Eq 4
|
||||
assert cg_simp(Sum(CG(a, alpha, b, beta, c, gamma)*CG(a, alpha, b, beta, cp, gammap), (alpha, -a, a), (beta, -b, b))) == KroneckerDelta(c, cp)*KroneckerDelta(gamma, gammap)
|
||||
assert cg_simp(Sum(CG(a, alpha, b, beta, c, gamma)*CG(a, alpha, b, beta, c, gammap), (alpha, -a, a), (beta, -b, b))) == KroneckerDelta(gamma, gammap)
|
||||
assert cg_simp(Sum(CG(a, alpha, b, beta, c, gamma)*CG(a, alpha, b, beta, cp, gamma), (alpha, -a, a), (beta, -b, b))) == KroneckerDelta(c, cp)
|
||||
assert cg_simp(Sum(CG(
|
||||
a, alpha, b, beta, c, gamma)**2, (alpha, -a, a), (beta, -b, b))) == 1
|
||||
assert cg_simp(Sum(CG(2, alpha, 1, beta, 2, gamma)*CG(2, alpha, 1, beta, 2, gammap), (alpha, -2, 2), (beta, -1, 1))) == KroneckerDelta(gamma, gammap)
|
||||
|
||||
|
||||
def test_doit():
|
||||
assert Wigner3j(S.Half, Rational(-1, 2), S.Half, S.Half, 0, 0).doit() == -sqrt(2)/2
|
||||
assert Wigner3j(1/2,1/2,1/2,1/2,1/2,1/2).doit() == 0
|
||||
assert Wigner3j(9/2,9/2,9/2,9/2,9/2,9/2).doit() == 0
|
||||
assert Wigner6j(1, 2, 3, 2, 1, 2).doit() == sqrt(21)/105
|
||||
assert Wigner6j(3, 1, 2, 2, 2, 1).doit() == sqrt(21) / 105
|
||||
assert Wigner9j(
|
||||
2, 1, 1, Rational(3, 2), S.Half, 1, S.Half, S.Half, 0).doit() == sqrt(2)/12
|
||||
assert CG(S.Half, S.Half, S.Half, Rational(-1, 2), 1, 0).doit() == sqrt(2)/2
|
||||
# J minus M is not integer
|
||||
assert Wigner3j(1, -1, S.Half, S.Half, 1, S.Half).doit() == 0
|
||||
assert CG(4, -1, S.Half, S.Half, 4, Rational(-1, 2)).doit() == 0
|
||||
@@ -0,0 +1,69 @@
|
||||
from sympy.physics.quantum.circuitplot import labeller, render_label, Mz, CreateOneQubitGate,\
|
||||
CreateCGate
|
||||
from sympy.physics.quantum.gate import CNOT, H, SWAP, CGate, S, T
|
||||
from sympy.external import import_module
|
||||
from sympy.testing.pytest import skip
|
||||
|
||||
mpl = import_module('matplotlib')
|
||||
|
||||
def test_render_label():
|
||||
assert render_label('q0') == r'$\left|q0\right\rangle$'
|
||||
assert render_label('q0', {'q0': '0'}) == r'$\left|q0\right\rangle=\left|0\right\rangle$'
|
||||
|
||||
def test_Mz():
|
||||
assert str(Mz(0)) == 'Mz(0)'
|
||||
|
||||
def test_create1():
|
||||
Qgate = CreateOneQubitGate('Q')
|
||||
assert str(Qgate(0)) == 'Q(0)'
|
||||
|
||||
def test_createc():
|
||||
Qgate = CreateCGate('Q')
|
||||
assert str(Qgate([1],0)) == 'C((1),Q(0))'
|
||||
|
||||
def test_labeller():
|
||||
"""Test the labeller utility"""
|
||||
assert labeller(2) == ['q_1', 'q_0']
|
||||
assert labeller(3,'j') == ['j_2', 'j_1', 'j_0']
|
||||
|
||||
def test_cnot():
|
||||
"""Test a simple cnot circuit. Right now this only makes sure the code doesn't
|
||||
raise an exception, and some simple properties
|
||||
"""
|
||||
if not mpl:
|
||||
skip("matplotlib not installed")
|
||||
else:
|
||||
from sympy.physics.quantum.circuitplot import CircuitPlot
|
||||
|
||||
c = CircuitPlot(CNOT(1,0),2,labels=labeller(2))
|
||||
assert c.ngates == 2
|
||||
assert c.nqubits == 2
|
||||
assert c.labels == ['q_1', 'q_0']
|
||||
|
||||
c = CircuitPlot(CNOT(1,0),2)
|
||||
assert c.ngates == 2
|
||||
assert c.nqubits == 2
|
||||
assert c.labels == []
|
||||
|
||||
def test_ex1():
|
||||
if not mpl:
|
||||
skip("matplotlib not installed")
|
||||
else:
|
||||
from sympy.physics.quantum.circuitplot import CircuitPlot
|
||||
|
||||
c = CircuitPlot(CNOT(1,0)*H(1),2,labels=labeller(2))
|
||||
assert c.ngates == 2
|
||||
assert c.nqubits == 2
|
||||
assert c.labels == ['q_1', 'q_0']
|
||||
|
||||
def test_ex4():
|
||||
if not mpl:
|
||||
skip("matplotlib not installed")
|
||||
else:
|
||||
from sympy.physics.quantum.circuitplot import CircuitPlot
|
||||
|
||||
c = CircuitPlot(SWAP(0,2)*H(0)* CGate((0,),S(1)) *H(1)*CGate((0,),T(2))\
|
||||
*CGate((1,),S(2))*H(2),3,labels=labeller(3,'j'))
|
||||
assert c.ngates == 7
|
||||
assert c.nqubits == 3
|
||||
assert c.labels == ['j_2', 'j_1', 'j_0']
|
||||
@@ -0,0 +1,402 @@
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.utilities import numbered_symbols
|
||||
from sympy.physics.quantum.gate import X, Y, Z, H, CNOT, CGate
|
||||
from sympy.physics.quantum.identitysearch import bfs_identity_search
|
||||
from sympy.physics.quantum.circuitutils import (kmp_table, find_subcircuit,
|
||||
replace_subcircuit, convert_to_symbolic_indices,
|
||||
convert_to_real_indices, random_reduce, random_insert,
|
||||
flatten_ids)
|
||||
from sympy.testing.pytest import slow
|
||||
|
||||
|
||||
def create_gate_sequence(qubit=0):
|
||||
gates = (X(qubit), Y(qubit), Z(qubit), H(qubit))
|
||||
return gates
|
||||
|
||||
|
||||
def test_kmp_table():
|
||||
word = ('a', 'b', 'c', 'd', 'a', 'b', 'd')
|
||||
expected_table = [-1, 0, 0, 0, 0, 1, 2]
|
||||
assert expected_table == kmp_table(word)
|
||||
|
||||
word = ('P', 'A', 'R', 'T', 'I', 'C', 'I', 'P', 'A', 'T', 'E', ' ',
|
||||
'I', 'N', ' ', 'P', 'A', 'R', 'A', 'C', 'H', 'U', 'T', 'E')
|
||||
expected_table = [-1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0,
|
||||
0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0]
|
||||
assert expected_table == kmp_table(word)
|
||||
|
||||
x = X(0)
|
||||
y = Y(0)
|
||||
z = Z(0)
|
||||
h = H(0)
|
||||
word = (x, y, y, x, z)
|
||||
expected_table = [-1, 0, 0, 0, 1]
|
||||
assert expected_table == kmp_table(word)
|
||||
|
||||
word = (x, x, y, h, z)
|
||||
expected_table = [-1, 0, 1, 0, 0]
|
||||
assert expected_table == kmp_table(word)
|
||||
|
||||
|
||||
def test_find_subcircuit():
|
||||
x = X(0)
|
||||
y = Y(0)
|
||||
z = Z(0)
|
||||
h = H(0)
|
||||
x1 = X(1)
|
||||
y1 = Y(1)
|
||||
|
||||
i0 = Symbol('i0')
|
||||
x_i0 = X(i0)
|
||||
y_i0 = Y(i0)
|
||||
z_i0 = Z(i0)
|
||||
h_i0 = H(i0)
|
||||
|
||||
circuit = (x, y, z)
|
||||
|
||||
assert find_subcircuit(circuit, (x,)) == 0
|
||||
assert find_subcircuit(circuit, (x1,)) == -1
|
||||
assert find_subcircuit(circuit, (y,)) == 1
|
||||
assert find_subcircuit(circuit, (h,)) == -1
|
||||
assert find_subcircuit(circuit, Mul(x, h)) == -1
|
||||
assert find_subcircuit(circuit, Mul(x, y, z)) == 0
|
||||
assert find_subcircuit(circuit, Mul(y, z)) == 1
|
||||
assert find_subcircuit(Mul(*circuit), (x, y, z, h)) == -1
|
||||
assert find_subcircuit(Mul(*circuit), (z, y, x)) == -1
|
||||
assert find_subcircuit(circuit, (x,), start=2, end=1) == -1
|
||||
|
||||
circuit = (x, y, x, y, z)
|
||||
assert find_subcircuit(Mul(*circuit), Mul(x, y, z)) == 2
|
||||
assert find_subcircuit(circuit, (x,), start=1) == 2
|
||||
assert find_subcircuit(circuit, (x, y), start=1, end=2) == -1
|
||||
assert find_subcircuit(Mul(*circuit), (x, y), start=1, end=3) == -1
|
||||
assert find_subcircuit(circuit, (x, y), start=1, end=4) == 2
|
||||
assert find_subcircuit(circuit, (x, y), start=2, end=4) == 2
|
||||
|
||||
circuit = (x, y, z, x1, x, y, z, h, x, y, x1,
|
||||
x, y, z, h, y1, h)
|
||||
assert find_subcircuit(circuit, (x, y, z, h, y1)) == 11
|
||||
|
||||
circuit = (x, y, x_i0, y_i0, z_i0, z)
|
||||
assert find_subcircuit(circuit, (x_i0, y_i0, z_i0)) == 2
|
||||
|
||||
circuit = (x_i0, y_i0, z_i0, x_i0, y_i0, h_i0)
|
||||
subcircuit = (x_i0, y_i0, z_i0)
|
||||
result = find_subcircuit(circuit, subcircuit)
|
||||
assert result == 0
|
||||
|
||||
|
||||
def test_replace_subcircuit():
|
||||
x = X(0)
|
||||
y = Y(0)
|
||||
z = Z(0)
|
||||
h = H(0)
|
||||
cnot = CNOT(1, 0)
|
||||
cgate_z = CGate((0,), Z(1))
|
||||
|
||||
# Standard cases
|
||||
circuit = (z, y, x, x)
|
||||
remove = (z, y, x)
|
||||
assert replace_subcircuit(circuit, Mul(*remove)) == (x,)
|
||||
assert replace_subcircuit(circuit, remove + (x,)) == ()
|
||||
assert replace_subcircuit(circuit, remove, pos=1) == circuit
|
||||
assert replace_subcircuit(circuit, remove, pos=0) == (x,)
|
||||
assert replace_subcircuit(circuit, (x, x), pos=2) == (z, y)
|
||||
assert replace_subcircuit(circuit, (h,)) == circuit
|
||||
|
||||
circuit = (x, y, x, y, z)
|
||||
remove = (x, y, z)
|
||||
assert replace_subcircuit(Mul(*circuit), Mul(*remove)) == (x, y)
|
||||
remove = (x, y, x, y)
|
||||
assert replace_subcircuit(circuit, remove) == (z,)
|
||||
|
||||
circuit = (x, h, cgate_z, h, cnot)
|
||||
remove = (x, h, cgate_z)
|
||||
assert replace_subcircuit(circuit, Mul(*remove), pos=-1) == (h, cnot)
|
||||
assert replace_subcircuit(circuit, remove, pos=1) == circuit
|
||||
remove = (h, h)
|
||||
assert replace_subcircuit(circuit, remove) == circuit
|
||||
remove = (h, cgate_z, h, cnot)
|
||||
assert replace_subcircuit(circuit, remove) == (x,)
|
||||
|
||||
replace = (h, x)
|
||||
actual = replace_subcircuit(circuit, remove,
|
||||
replace=replace)
|
||||
assert actual == (x, h, x)
|
||||
|
||||
circuit = (x, y, h, x, y, z)
|
||||
remove = (x, y)
|
||||
replace = (cnot, cgate_z)
|
||||
actual = replace_subcircuit(circuit, remove,
|
||||
replace=Mul(*replace))
|
||||
assert actual == (cnot, cgate_z, h, x, y, z)
|
||||
|
||||
actual = replace_subcircuit(circuit, remove,
|
||||
replace=replace, pos=1)
|
||||
assert actual == (x, y, h, cnot, cgate_z, z)
|
||||
|
||||
|
||||
def test_convert_to_symbolic_indices():
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
|
||||
i0 = Symbol('i0')
|
||||
exp_map = {i0: Integer(0)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices((x,))
|
||||
assert actual == (X(i0),)
|
||||
assert act_map == exp_map
|
||||
|
||||
expected = (X(i0), Y(i0), Z(i0), H(i0))
|
||||
exp_map = {i0: Integer(0)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices((x, y, z, h))
|
||||
assert actual == expected
|
||||
assert exp_map == act_map
|
||||
|
||||
(x1, y1, z1, h1) = create_gate_sequence(1)
|
||||
i1 = Symbol('i1')
|
||||
|
||||
expected = (X(i0), Y(i0), Z(i0), H(i0))
|
||||
exp_map = {i0: Integer(1)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices((x1, y1, z1, h1))
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
expected = (X(i0), Y(i0), Z(i0), H(i0), X(i1), Y(i1), Z(i1), H(i1))
|
||||
exp_map = {i0: Integer(0), i1: Integer(1)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices((x, y, z, h,
|
||||
x1, y1, z1, h1))
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
exp_map = {i0: Integer(1), i1: Integer(0)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(Mul(x1, y1,
|
||||
z1, h1, x, y, z, h))
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
expected = (X(i0), X(i1), Y(i0), Y(i1), Z(i0), Z(i1), H(i0), H(i1))
|
||||
exp_map = {i0: Integer(0), i1: Integer(1)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(Mul(x, x1,
|
||||
y, y1, z, z1, h, h1))
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
exp_map = {i0: Integer(1), i1: Integer(0)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices((x1, x, y1, y,
|
||||
z1, z, h1, h))
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
cnot_10 = CNOT(1, 0)
|
||||
cnot_01 = CNOT(0, 1)
|
||||
cgate_z_10 = CGate(1, Z(0))
|
||||
cgate_z_01 = CGate(0, Z(1))
|
||||
|
||||
expected = (X(i0), X(i1), Y(i0), Y(i1), Z(i0), Z(i1),
|
||||
H(i0), H(i1), CNOT(i1, i0), CNOT(i0, i1),
|
||||
CGate(i1, Z(i0)), CGate(i0, Z(i1)))
|
||||
exp_map = {i0: Integer(0), i1: Integer(1)}
|
||||
args = (x, x1, y, y1, z, z1, h, h1, cnot_10, cnot_01,
|
||||
cgate_z_10, cgate_z_01)
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(args)
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
args = (x1, x, y1, y, z1, z, h1, h, cnot_10, cnot_01,
|
||||
cgate_z_10, cgate_z_01)
|
||||
expected = (X(i0), X(i1), Y(i0), Y(i1), Z(i0), Z(i1),
|
||||
H(i0), H(i1), CNOT(i0, i1), CNOT(i1, i0),
|
||||
CGate(i0, Z(i1)), CGate(i1, Z(i0)))
|
||||
exp_map = {i0: Integer(1), i1: Integer(0)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(args)
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
args = (cnot_10, h, cgate_z_01, h)
|
||||
expected = (CNOT(i0, i1), H(i1), CGate(i1, Z(i0)), H(i1))
|
||||
exp_map = {i0: Integer(1), i1: Integer(0)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(args)
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
args = (cnot_01, h1, cgate_z_10, h1)
|
||||
exp_map = {i0: Integer(0), i1: Integer(1)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(args)
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
args = (cnot_10, h1, cgate_z_01, h1)
|
||||
expected = (CNOT(i0, i1), H(i0), CGate(i1, Z(i0)), H(i0))
|
||||
exp_map = {i0: Integer(1), i1: Integer(0)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(args)
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
i2 = Symbol('i2')
|
||||
ccgate_z = CGate(0, CGate(1, Z(2)))
|
||||
ccgate_x = CGate(1, CGate(2, X(0)))
|
||||
args = (ccgate_z, ccgate_x)
|
||||
|
||||
expected = (CGate(i0, CGate(i1, Z(i2))), CGate(i1, CGate(i2, X(i0))))
|
||||
exp_map = {i0: Integer(0), i1: Integer(1), i2: Integer(2)}
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(args)
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
ndx_map = {i0: Integer(0)}
|
||||
index_gen = numbered_symbols(prefix='i', start=1)
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(args,
|
||||
qubit_map=ndx_map,
|
||||
start=i0,
|
||||
gen=index_gen)
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
i3 = Symbol('i3')
|
||||
cgate_x0_c321 = CGate((3, 2, 1), X(0))
|
||||
exp_map = {i0: Integer(3), i1: Integer(2),
|
||||
i2: Integer(1), i3: Integer(0)}
|
||||
expected = (CGate((i0, i1, i2), X(i3)),)
|
||||
args = (cgate_x0_c321,)
|
||||
actual, act_map, sndx, gen = convert_to_symbolic_indices(args)
|
||||
assert actual == expected
|
||||
assert act_map == exp_map
|
||||
|
||||
|
||||
def test_convert_to_real_indices():
|
||||
i0 = Symbol('i0')
|
||||
i1 = Symbol('i1')
|
||||
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
|
||||
x_i0 = X(i0)
|
||||
y_i0 = Y(i0)
|
||||
z_i0 = Z(i0)
|
||||
|
||||
qubit_map = {i0: 0}
|
||||
args = (z_i0, y_i0, x_i0)
|
||||
expected = (z, y, x)
|
||||
actual = convert_to_real_indices(args, qubit_map)
|
||||
assert actual == expected
|
||||
|
||||
cnot_10 = CNOT(1, 0)
|
||||
cnot_01 = CNOT(0, 1)
|
||||
cgate_z_10 = CGate(1, Z(0))
|
||||
cgate_z_01 = CGate(0, Z(1))
|
||||
|
||||
cnot_i1_i0 = CNOT(i1, i0)
|
||||
cnot_i0_i1 = CNOT(i0, i1)
|
||||
cgate_z_i1_i0 = CGate(i1, Z(i0))
|
||||
|
||||
qubit_map = {i0: 0, i1: 1}
|
||||
args = (cnot_i1_i0,)
|
||||
expected = (cnot_10,)
|
||||
actual = convert_to_real_indices(args, qubit_map)
|
||||
assert actual == expected
|
||||
|
||||
args = (cgate_z_i1_i0,)
|
||||
expected = (cgate_z_10,)
|
||||
actual = convert_to_real_indices(args, qubit_map)
|
||||
assert actual == expected
|
||||
|
||||
args = (cnot_i0_i1,)
|
||||
expected = (cnot_01,)
|
||||
actual = convert_to_real_indices(args, qubit_map)
|
||||
assert actual == expected
|
||||
|
||||
qubit_map = {i0: 1, i1: 0}
|
||||
args = (cgate_z_i1_i0,)
|
||||
expected = (cgate_z_01,)
|
||||
actual = convert_to_real_indices(args, qubit_map)
|
||||
assert actual == expected
|
||||
|
||||
i2 = Symbol('i2')
|
||||
ccgate_z = CGate(i0, CGate(i1, Z(i2)))
|
||||
ccgate_x = CGate(i1, CGate(i2, X(i0)))
|
||||
|
||||
qubit_map = {i0: 0, i1: 1, i2: 2}
|
||||
args = (ccgate_z, ccgate_x)
|
||||
expected = (CGate(0, CGate(1, Z(2))), CGate(1, CGate(2, X(0))))
|
||||
actual = convert_to_real_indices(Mul(*args), qubit_map)
|
||||
assert actual == expected
|
||||
|
||||
qubit_map = {i0: 1, i2: 0, i1: 2}
|
||||
args = (ccgate_x, ccgate_z)
|
||||
expected = (CGate(2, CGate(0, X(1))), CGate(1, CGate(2, Z(0))))
|
||||
actual = convert_to_real_indices(args, qubit_map)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@slow
|
||||
def test_random_reduce():
|
||||
x = X(0)
|
||||
y = Y(0)
|
||||
z = Z(0)
|
||||
h = H(0)
|
||||
cnot = CNOT(1, 0)
|
||||
cgate_z = CGate((0,), Z(1))
|
||||
|
||||
gate_list = [x, y, z]
|
||||
ids = list(bfs_identity_search(gate_list, 1, max_depth=4))
|
||||
|
||||
circuit = (x, y, h, z, cnot)
|
||||
assert random_reduce(circuit, []) == circuit
|
||||
assert random_reduce(circuit, ids) == circuit
|
||||
|
||||
seq = [2, 11, 9, 3, 5]
|
||||
circuit = (x, y, z, x, y, h)
|
||||
assert random_reduce(circuit, ids, seed=seq) == (x, y, h)
|
||||
|
||||
circuit = (x, x, y, y, z, z)
|
||||
assert random_reduce(circuit, ids, seed=seq) == (x, x, y, y)
|
||||
|
||||
seq = [14, 13, 0]
|
||||
assert random_reduce(circuit, ids, seed=seq) == (y, y, z, z)
|
||||
|
||||
gate_list = [x, y, z, h, cnot, cgate_z]
|
||||
ids = list(bfs_identity_search(gate_list, 2, max_depth=4))
|
||||
|
||||
seq = [25]
|
||||
circuit = (x, y, z, y, h, y, h, cgate_z, h, cnot)
|
||||
expected = (x, y, z, cgate_z, h, cnot)
|
||||
assert random_reduce(circuit, ids, seed=seq) == expected
|
||||
circuit = Mul(*circuit)
|
||||
assert random_reduce(circuit, ids, seed=seq) == expected
|
||||
|
||||
|
||||
@slow
|
||||
def test_random_insert():
|
||||
x = X(0)
|
||||
y = Y(0)
|
||||
z = Z(0)
|
||||
h = H(0)
|
||||
cnot = CNOT(1, 0)
|
||||
cgate_z = CGate((0,), Z(1))
|
||||
|
||||
choices = [(x, x)]
|
||||
circuit = (y, y)
|
||||
loc, choice = 0, 0
|
||||
actual = random_insert(circuit, choices, seed=[loc, choice])
|
||||
assert actual == (x, x, y, y)
|
||||
|
||||
circuit = (x, y, z, h)
|
||||
choices = [(h, h), (x, y, z)]
|
||||
expected = (x, x, y, z, y, z, h)
|
||||
loc, choice = 1, 1
|
||||
actual = random_insert(circuit, choices, seed=[loc, choice])
|
||||
assert actual == expected
|
||||
|
||||
gate_list = [x, y, z, h, cnot, cgate_z]
|
||||
ids = list(bfs_identity_search(gate_list, 2, max_depth=4))
|
||||
|
||||
eq_ids = flatten_ids(ids)
|
||||
|
||||
circuit = (x, y, h, cnot, cgate_z)
|
||||
expected = (x, z, x, z, x, y, h, cnot, cgate_z)
|
||||
loc, choice = 1, 30
|
||||
actual = random_insert(circuit, eq_ids, seed=[loc, choice])
|
||||
assert actual == expected
|
||||
circuit = Mul(*circuit)
|
||||
actual = random_insert(circuit, eq_ids, seed=[loc, choice])
|
||||
assert actual == expected
|
||||
@@ -0,0 +1,81 @@
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.symbol import symbols
|
||||
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.commutator import Commutator as Comm
|
||||
from sympy.physics.quantum.operator import Operator
|
||||
|
||||
|
||||
a, b, c = symbols('a,b,c')
|
||||
n = symbols('n', integer=True)
|
||||
A, B, C, D = symbols('A,B,C,D', commutative=False)
|
||||
|
||||
|
||||
def test_commutator():
|
||||
c = Comm(A, B)
|
||||
assert c.is_commutative is False
|
||||
assert isinstance(c, Comm)
|
||||
assert c.subs(A, C) == Comm(C, B)
|
||||
|
||||
|
||||
def test_commutator_identities():
|
||||
assert Comm(a*A, b*B) == a*b*Comm(A, B)
|
||||
assert Comm(A, A) == 0
|
||||
assert Comm(a, b) == 0
|
||||
assert Comm(A, B) == -Comm(B, A)
|
||||
assert Comm(A, B).doit() == A*B - B*A
|
||||
assert Comm(A, B*C).expand(commutator=True) == Comm(A, B)*C + B*Comm(A, C)
|
||||
assert Comm(A*B, C*D).expand(commutator=True) == \
|
||||
A*C*Comm(B, D) + A*Comm(B, C)*D + C*Comm(A, D)*B + Comm(A, C)*D*B
|
||||
assert Comm(A, B**2).expand(commutator=True) == Comm(A, B)*B + B*Comm(A, B)
|
||||
assert Comm(A**2, C**2).expand(commutator=True) == \
|
||||
Comm(A*B, C*D).expand(commutator=True).replace(B, A).replace(D, C) == \
|
||||
A*C*Comm(A, C) + A*Comm(A, C)*C + C*Comm(A, C)*A + Comm(A, C)*C*A
|
||||
assert Comm(A, C**-2).expand(commutator=True) == \
|
||||
Comm(A, (1/C)*(1/D)).expand(commutator=True).replace(D, C)
|
||||
assert Comm(A + B, C + D).expand(commutator=True) == \
|
||||
Comm(A, C) + Comm(A, D) + Comm(B, C) + Comm(B, D)
|
||||
assert Comm(A, B + C).expand(commutator=True) == Comm(A, B) + Comm(A, C)
|
||||
assert Comm(A**n, B).expand(commutator=True) == Comm(A**n, B)
|
||||
|
||||
e = Comm(A, Comm(B, C)) + Comm(B, Comm(C, A)) + Comm(C, Comm(A, B))
|
||||
assert e.doit().expand() == 0
|
||||
|
||||
|
||||
def test_commutator_dagger():
|
||||
comm = Comm(A*B, C)
|
||||
assert Dagger(comm).expand(commutator=True) == \
|
||||
- Comm(Dagger(B), Dagger(C))*Dagger(A) - \
|
||||
Dagger(B)*Comm(Dagger(A), Dagger(C))
|
||||
|
||||
|
||||
class Foo(Operator):
|
||||
|
||||
def _eval_commutator_Bar(self, bar):
|
||||
return Integer(0)
|
||||
|
||||
|
||||
class Bar(Operator):
|
||||
pass
|
||||
|
||||
|
||||
class Tam(Operator):
|
||||
|
||||
def _eval_commutator_Foo(self, foo):
|
||||
return Integer(1)
|
||||
|
||||
|
||||
def test_eval_commutator():
|
||||
F = Foo('F')
|
||||
B = Bar('B')
|
||||
T = Tam('T')
|
||||
assert Comm(F, B).doit() == 0
|
||||
assert Comm(B, F).doit() == 0
|
||||
assert Comm(F, T).doit() == -1
|
||||
assert Comm(T, F).doit() == 1
|
||||
assert Comm(B, T).doit() == B*T - T*B
|
||||
assert Comm(F**2, B).expand(commutator=True).doit() == 0
|
||||
assert Comm(F**2, T).expand(commutator=True).doit() == -2*F
|
||||
assert Comm(F, T**2).expand(commutator=True).doit() == -2*T
|
||||
assert Comm(T**2, F).expand(commutator=True).doit() == 2*T
|
||||
assert Comm(T**2, F**3).expand(commutator=True).doit() == 2*F*T*F + 2*F**2*T + 2*T*F**2
|
||||
@@ -0,0 +1,13 @@
|
||||
from sympy.core.numbers import Float
|
||||
|
||||
from sympy.physics.quantum.constants import hbar
|
||||
|
||||
|
||||
def test_hbar():
|
||||
assert hbar.is_commutative is True
|
||||
assert hbar.is_real is True
|
||||
assert hbar.is_positive is True
|
||||
assert hbar.is_negative is False
|
||||
assert hbar.is_irrational is True
|
||||
|
||||
assert hbar.evalf() == Float(1.05457162e-34)
|
||||
@@ -0,0 +1,103 @@
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (I, Integer)
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.complexes import conjugate
|
||||
from sympy.matrices.dense import Matrix
|
||||
|
||||
from sympy.physics.quantum.dagger import adjoint, Dagger
|
||||
from sympy.external import import_module
|
||||
from sympy.testing.pytest import skip, warns_deprecated_sympy
|
||||
from sympy.physics.quantum.operator import Operator, IdentityOperator
|
||||
|
||||
|
||||
def test_scalars():
|
||||
x = symbols('x', complex=True)
|
||||
assert Dagger(x) == conjugate(x)
|
||||
assert Dagger(I*x) == -I*conjugate(x)
|
||||
|
||||
i = symbols('i', real=True)
|
||||
assert Dagger(i) == i
|
||||
|
||||
p = symbols('p')
|
||||
assert isinstance(Dagger(p), conjugate)
|
||||
|
||||
i = Integer(3)
|
||||
assert Dagger(i) == i
|
||||
|
||||
A = symbols('A', commutative=False)
|
||||
assert Dagger(A).is_commutative is False
|
||||
|
||||
|
||||
def test_matrix():
|
||||
x = symbols('x')
|
||||
m = Matrix([[I, x*I], [2, 4]])
|
||||
assert Dagger(m) == m.H
|
||||
|
||||
|
||||
def test_dagger_mul():
|
||||
O = Operator('O')
|
||||
assert Dagger(O)*O == Dagger(O)*O
|
||||
with warns_deprecated_sympy():
|
||||
I = IdentityOperator()
|
||||
assert Dagger(O)*O*I == Mul(Dagger(O), O)*I
|
||||
assert Dagger(O)*Dagger(O) == Dagger(O)**2
|
||||
assert Dagger(O)*Dagger(I) == Dagger(O)
|
||||
|
||||
|
||||
class Foo(Expr):
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return I
|
||||
|
||||
|
||||
def test_eval_adjoint():
|
||||
f = Foo()
|
||||
d = Dagger(f)
|
||||
assert d == I
|
||||
|
||||
np = import_module('numpy')
|
||||
|
||||
|
||||
def test_numpy_dagger():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
|
||||
a = np.array([[1.0, 2.0j], [-1.0j, 2.0]])
|
||||
adag = a.copy().transpose().conjugate()
|
||||
assert (Dagger(a) == adag).all()
|
||||
|
||||
|
||||
scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
|
||||
|
||||
|
||||
def test_scipy_sparse_dagger():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
if not scipy:
|
||||
skip("scipy not installed.")
|
||||
else:
|
||||
sparse = scipy.sparse
|
||||
|
||||
a = sparse.csr_matrix([[1.0 + 0.0j, 2.0j], [-1.0j, 2.0 + 0.0j]])
|
||||
adag = a.copy().transpose().conjugate()
|
||||
assert np.linalg.norm((Dagger(a) - adag).todense()) == 0.0
|
||||
|
||||
|
||||
def test_unknown():
|
||||
"""Check treatment of unknown objects.
|
||||
Objects without adjoint or conjugate/transpose methods
|
||||
are sympified and wrapped in dagger.
|
||||
"""
|
||||
x = symbols("x", commutative=False)
|
||||
result = Dagger(x)
|
||||
assert result.args == (x,) and isinstance(result, adjoint)
|
||||
|
||||
|
||||
def test_unevaluated():
|
||||
"""Check that evaluate=False returns unevaluated Dagger.
|
||||
"""
|
||||
x = symbols("x", real=True)
|
||||
assert Dagger(x) == x
|
||||
result = Dagger(x, evaluate=False)
|
||||
assert result.args == (x,) and isinstance(result, adjoint)
|
||||
@@ -0,0 +1,289 @@
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.exponential import log
|
||||
from sympy.external import import_module
|
||||
from sympy.physics.quantum.density import Density, entropy, fidelity
|
||||
from sympy.physics.quantum.state import Ket, TimeDepKet
|
||||
from sympy.physics.quantum.qubit import Qubit
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.cartesian import XKet, PxKet, PxOp, XOp
|
||||
from sympy.physics.quantum.spin import JzKet
|
||||
from sympy.physics.quantum.operator import OuterProduct
|
||||
from sympy.physics.quantum.trace import Tr
|
||||
from sympy.functions import sqrt
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.physics.quantum.matrixutils import scipy_sparse_matrix
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct
|
||||
|
||||
|
||||
def test_eval_args():
|
||||
# check instance created
|
||||
assert isinstance(Density([Ket(0), 0.5], [Ket(1), 0.5]), Density)
|
||||
assert isinstance(Density([Qubit('00'), 1/sqrt(2)],
|
||||
[Qubit('11'), 1/sqrt(2)]), Density)
|
||||
|
||||
#test if Qubit object type preserved
|
||||
d = Density([Qubit('00'), 1/sqrt(2)], [Qubit('11'), 1/sqrt(2)])
|
||||
for (state, prob) in d.args:
|
||||
assert isinstance(state, Qubit)
|
||||
|
||||
# check for value error, when prob is not provided
|
||||
raises(ValueError, lambda: Density([Ket(0)], [Ket(1)]))
|
||||
|
||||
|
||||
def test_doit():
|
||||
|
||||
x, y = symbols('x y')
|
||||
A, B, C, D, E, F = symbols('A B C D E F', commutative=False)
|
||||
d = Density([XKet(), 0.5], [PxKet(), 0.5])
|
||||
assert (0.5*(PxKet()*Dagger(PxKet())) +
|
||||
0.5*(XKet()*Dagger(XKet()))) == d.doit()
|
||||
|
||||
# check for kets with expr in them
|
||||
d_with_sym = Density([XKet(x*y), 0.5], [PxKet(x*y), 0.5])
|
||||
assert (0.5*(PxKet(x*y)*Dagger(PxKet(x*y))) +
|
||||
0.5*(XKet(x*y)*Dagger(XKet(x*y)))) == d_with_sym.doit()
|
||||
|
||||
d = Density([(A + B)*C, 1.0])
|
||||
assert d.doit() == (1.0*A*C*Dagger(C)*Dagger(A) +
|
||||
1.0*A*C*Dagger(C)*Dagger(B) +
|
||||
1.0*B*C*Dagger(C)*Dagger(A) +
|
||||
1.0*B*C*Dagger(C)*Dagger(B))
|
||||
|
||||
# With TensorProducts as args
|
||||
# Density with simple tensor products as args
|
||||
t = TensorProduct(A, B, C)
|
||||
d = Density([t, 1.0])
|
||||
assert d.doit() == \
|
||||
1.0 * TensorProduct(A*Dagger(A), B*Dagger(B), C*Dagger(C))
|
||||
|
||||
# Density with multiple Tensorproducts as states
|
||||
t2 = TensorProduct(A, B)
|
||||
t3 = TensorProduct(C, D)
|
||||
|
||||
d = Density([t2, 0.5], [t3, 0.5])
|
||||
assert d.doit() == (0.5 * TensorProduct(A*Dagger(A), B*Dagger(B)) +
|
||||
0.5 * TensorProduct(C*Dagger(C), D*Dagger(D)))
|
||||
|
||||
#Density with mixed states
|
||||
d = Density([t2 + t3, 1.0])
|
||||
assert d.doit() == (1.0 * TensorProduct(A*Dagger(A), B*Dagger(B)) +
|
||||
1.0 * TensorProduct(A*Dagger(C), B*Dagger(D)) +
|
||||
1.0 * TensorProduct(C*Dagger(A), D*Dagger(B)) +
|
||||
1.0 * TensorProduct(C*Dagger(C), D*Dagger(D)))
|
||||
|
||||
#Density operators with spin states
|
||||
tp1 = TensorProduct(JzKet(1, 1), JzKet(1, -1))
|
||||
d = Density([tp1, 1])
|
||||
|
||||
# full trace
|
||||
t = Tr(d)
|
||||
assert t.doit() == 1
|
||||
|
||||
#Partial trace on density operators with spin states
|
||||
t = Tr(d, [0])
|
||||
assert t.doit() == JzKet(1, -1) * Dagger(JzKet(1, -1))
|
||||
t = Tr(d, [1])
|
||||
assert t.doit() == JzKet(1, 1) * Dagger(JzKet(1, 1))
|
||||
|
||||
# with another spin state
|
||||
tp2 = TensorProduct(JzKet(S.Half, S.Half), JzKet(S.Half, Rational(-1, 2)))
|
||||
d = Density([tp2, 1])
|
||||
|
||||
#full trace
|
||||
t = Tr(d)
|
||||
assert t.doit() == 1
|
||||
|
||||
#Partial trace on density operators with spin states
|
||||
t = Tr(d, [0])
|
||||
assert t.doit() == JzKet(S.Half, Rational(-1, 2)) * Dagger(JzKet(S.Half, Rational(-1, 2)))
|
||||
t = Tr(d, [1])
|
||||
assert t.doit() == JzKet(S.Half, S.Half) * Dagger(JzKet(S.Half, S.Half))
|
||||
|
||||
|
||||
def test_apply_op():
|
||||
d = Density([Ket(0), 0.5], [Ket(1), 0.5])
|
||||
assert d.apply_op(XOp()) == Density([XOp()*Ket(0), 0.5],
|
||||
[XOp()*Ket(1), 0.5])
|
||||
|
||||
|
||||
def test_represent():
|
||||
x, y = symbols('x y')
|
||||
d = Density([XKet(), 0.5], [PxKet(), 0.5])
|
||||
assert (represent(0.5*(PxKet()*Dagger(PxKet()))) +
|
||||
represent(0.5*(XKet()*Dagger(XKet())))) == represent(d)
|
||||
|
||||
# check for kets with expr in them
|
||||
d_with_sym = Density([XKet(x*y), 0.5], [PxKet(x*y), 0.5])
|
||||
assert (represent(0.5*(PxKet(x*y)*Dagger(PxKet(x*y)))) +
|
||||
represent(0.5*(XKet(x*y)*Dagger(XKet(x*y))))) == \
|
||||
represent(d_with_sym)
|
||||
|
||||
# check when given explicit basis
|
||||
assert (represent(0.5*(XKet()*Dagger(XKet())), basis=PxOp()) +
|
||||
represent(0.5*(PxKet()*Dagger(PxKet())), basis=PxOp())) == \
|
||||
represent(d, basis=PxOp())
|
||||
|
||||
|
||||
def test_states():
|
||||
d = Density([Ket(0), 0.5], [Ket(1), 0.5])
|
||||
states = d.states()
|
||||
assert states[0] == Ket(0) and states[1] == Ket(1)
|
||||
|
||||
|
||||
def test_probs():
|
||||
d = Density([Ket(0), .75], [Ket(1), 0.25])
|
||||
probs = d.probs()
|
||||
assert probs[0] == 0.75 and probs[1] == 0.25
|
||||
|
||||
#probs can be symbols
|
||||
x, y = symbols('x y')
|
||||
d = Density([Ket(0), x], [Ket(1), y])
|
||||
probs = d.probs()
|
||||
assert probs[0] == x and probs[1] == y
|
||||
|
||||
|
||||
def test_get_state():
|
||||
x, y = symbols('x y')
|
||||
d = Density([Ket(0), x], [Ket(1), y])
|
||||
states = (d.get_state(0), d.get_state(1))
|
||||
assert states[0] == Ket(0) and states[1] == Ket(1)
|
||||
|
||||
|
||||
def test_get_prob():
|
||||
x, y = symbols('x y')
|
||||
d = Density([Ket(0), x], [Ket(1), y])
|
||||
probs = (d.get_prob(0), d.get_prob(1))
|
||||
assert probs[0] == x and probs[1] == y
|
||||
|
||||
|
||||
def test_entropy():
|
||||
up = JzKet(S.Half, S.Half)
|
||||
down = JzKet(S.Half, Rational(-1, 2))
|
||||
d = Density((up, S.Half), (down, S.Half))
|
||||
|
||||
# test for density object
|
||||
ent = entropy(d)
|
||||
assert entropy(d) == log(2)/2
|
||||
assert d.entropy() == log(2)/2
|
||||
|
||||
np = import_module('numpy', min_module_version='1.4.0')
|
||||
if np:
|
||||
#do this test only if 'numpy' is available on test machine
|
||||
np_mat = represent(d, format='numpy')
|
||||
ent = entropy(np_mat)
|
||||
assert isinstance(np_mat, np.ndarray)
|
||||
assert ent.real == 0.69314718055994529
|
||||
assert ent.imag == 0
|
||||
|
||||
scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
|
||||
if scipy and np:
|
||||
#do this test only if numpy and scipy are available
|
||||
mat = represent(d, format="scipy.sparse")
|
||||
assert isinstance(mat, scipy_sparse_matrix)
|
||||
assert ent.real == 0.69314718055994529
|
||||
assert ent.imag == 0
|
||||
|
||||
|
||||
def test_eval_trace():
|
||||
up = JzKet(S.Half, S.Half)
|
||||
down = JzKet(S.Half, Rational(-1, 2))
|
||||
d = Density((up, 0.5), (down, 0.5))
|
||||
|
||||
t = Tr(d)
|
||||
assert t.doit() == 1.0
|
||||
|
||||
#test dummy time dependent states
|
||||
class TestTimeDepKet(TimeDepKet):
|
||||
def _eval_trace(self, bra, **options):
|
||||
return 1
|
||||
|
||||
x, t = symbols('x t')
|
||||
k1 = TestTimeDepKet(0, 0.5)
|
||||
k2 = TestTimeDepKet(0, 1)
|
||||
d = Density([k1, 0.5], [k2, 0.5])
|
||||
assert d.doit() == (0.5 * OuterProduct(k1, k1.dual) +
|
||||
0.5 * OuterProduct(k2, k2.dual))
|
||||
|
||||
t = Tr(d)
|
||||
assert t.doit() == 1.0
|
||||
|
||||
|
||||
def test_fidelity():
|
||||
#test with kets
|
||||
up = JzKet(S.Half, S.Half)
|
||||
down = JzKet(S.Half, Rational(-1, 2))
|
||||
updown = (S.One/sqrt(2))*up + (S.One/sqrt(2))*down
|
||||
|
||||
#check with matrices
|
||||
up_dm = represent(up * Dagger(up))
|
||||
down_dm = represent(down * Dagger(down))
|
||||
updown_dm = represent(updown * Dagger(updown))
|
||||
|
||||
assert abs(fidelity(up_dm, up_dm) - 1) < 1e-3
|
||||
assert fidelity(up_dm, down_dm) < 1e-3
|
||||
assert abs(fidelity(up_dm, updown_dm) - (S.One/sqrt(2))) < 1e-3
|
||||
assert abs(fidelity(updown_dm, down_dm) - (S.One/sqrt(2))) < 1e-3
|
||||
|
||||
#check with density
|
||||
up_dm = Density([up, 1.0])
|
||||
down_dm = Density([down, 1.0])
|
||||
updown_dm = Density([updown, 1.0])
|
||||
|
||||
assert abs(fidelity(up_dm, up_dm) - 1) < 1e-3
|
||||
assert abs(fidelity(up_dm, down_dm)) < 1e-3
|
||||
assert abs(fidelity(up_dm, updown_dm) - (S.One/sqrt(2))) < 1e-3
|
||||
assert abs(fidelity(updown_dm, down_dm) - (S.One/sqrt(2))) < 1e-3
|
||||
|
||||
#check mixed states with density
|
||||
updown2 = sqrt(3)/2*up + S.Half*down
|
||||
d1 = Density([updown, 0.25], [updown2, 0.75])
|
||||
d2 = Density([updown, 0.75], [updown2, 0.25])
|
||||
assert abs(fidelity(d1, d2) - 0.991) < 1e-3
|
||||
assert abs(fidelity(d2, d1) - fidelity(d1, d2)) < 1e-3
|
||||
|
||||
#using qubits/density(pure states)
|
||||
state1 = Qubit('0')
|
||||
state2 = Qubit('1')
|
||||
state3 = S.One/sqrt(2)*state1 + S.One/sqrt(2)*state2
|
||||
state4 = sqrt(Rational(2, 3))*state1 + S.One/sqrt(3)*state2
|
||||
|
||||
state1_dm = Density([state1, 1])
|
||||
state2_dm = Density([state2, 1])
|
||||
state3_dm = Density([state3, 1])
|
||||
|
||||
assert fidelity(state1_dm, state1_dm) == 1
|
||||
assert fidelity(state1_dm, state2_dm) == 0
|
||||
assert abs(fidelity(state1_dm, state3_dm) - 1/sqrt(2)) < 1e-3
|
||||
assert abs(fidelity(state3_dm, state2_dm) - 1/sqrt(2)) < 1e-3
|
||||
|
||||
#using qubits/density(mixed states)
|
||||
d1 = Density([state3, 0.70], [state4, 0.30])
|
||||
d2 = Density([state3, 0.20], [state4, 0.80])
|
||||
assert abs(fidelity(d1, d1) - 1) < 1e-3
|
||||
assert abs(fidelity(d1, d2) - 0.996) < 1e-3
|
||||
assert abs(fidelity(d1, d2) - fidelity(d2, d1)) < 1e-3
|
||||
|
||||
#TODO: test for invalid arguments
|
||||
# non-square matrix
|
||||
mat1 = [[0, 0],
|
||||
[0, 0],
|
||||
[0, 0]]
|
||||
|
||||
mat2 = [[0, 0],
|
||||
[0, 0]]
|
||||
raises(ValueError, lambda: fidelity(mat1, mat2))
|
||||
|
||||
# unequal dimensions
|
||||
mat1 = [[0, 0],
|
||||
[0, 0]]
|
||||
mat2 = [[0, 0, 0],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]]
|
||||
raises(ValueError, lambda: fidelity(mat1, mat2))
|
||||
|
||||
# unsupported data-type
|
||||
x, y = 1, 2 # random values that is not a matrix
|
||||
raises(ValueError, lambda: fidelity(x, y))
|
||||
@@ -0,0 +1,62 @@
|
||||
from pytest import raises
|
||||
|
||||
import sympy
|
||||
from sympy.physics.quantum import Dagger, AntiCommutator, qapply
|
||||
from sympy.physics.quantum.fermion import FermionOp
|
||||
from sympy.physics.quantum.fermion import FermionFockKet, FermionFockBra
|
||||
from sympy import Symbol
|
||||
|
||||
|
||||
def test_fermionoperator():
|
||||
c = FermionOp('c')
|
||||
d = FermionOp('d')
|
||||
|
||||
assert isinstance(c, FermionOp)
|
||||
assert isinstance(Dagger(c), FermionOp)
|
||||
|
||||
assert c.is_annihilation
|
||||
assert not Dagger(c).is_annihilation
|
||||
|
||||
assert FermionOp("c") == FermionOp("c", True)
|
||||
assert FermionOp("c") != FermionOp("d")
|
||||
assert FermionOp("c", True) != FermionOp("c", False)
|
||||
|
||||
assert AntiCommutator(c, Dagger(c)).doit() == 1
|
||||
|
||||
assert AntiCommutator(c, Dagger(d)).doit() == c * Dagger(d) + Dagger(d) * c
|
||||
|
||||
|
||||
def test_fermion_states():
|
||||
c = FermionOp("c")
|
||||
|
||||
# Fock states
|
||||
assert (FermionFockBra(0) * FermionFockKet(1)).doit() == 0
|
||||
assert (FermionFockBra(1) * FermionFockKet(1)).doit() == 1
|
||||
|
||||
assert qapply(c * FermionFockKet(1)) == FermionFockKet(0)
|
||||
assert qapply(c * FermionFockKet(0)) == 0
|
||||
|
||||
assert qapply(Dagger(c) * FermionFockKet(0)) == FermionFockKet(1)
|
||||
assert qapply(Dagger(c) * FermionFockKet(1)) == 0
|
||||
|
||||
|
||||
def test_power():
|
||||
c = FermionOp("c")
|
||||
assert c**0 == 1
|
||||
assert c**1 == c
|
||||
assert c**2 == 0
|
||||
assert c**3 == 0
|
||||
assert Dagger(c)**1 == Dagger(c)
|
||||
assert Dagger(c)**2 == 0
|
||||
|
||||
assert (c**Symbol('a')).func == sympy.core.power.Pow
|
||||
assert (c**Symbol('a')).args == (c, Symbol('a'))
|
||||
|
||||
with raises(ValueError):
|
||||
c**-1
|
||||
|
||||
with raises(ValueError):
|
||||
c**3.2
|
||||
|
||||
with raises(TypeError):
|
||||
c**1j
|
||||
@@ -0,0 +1,360 @@
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (I, Integer, Rational, pi)
|
||||
from sympy.core.symbol import (Wild, symbols)
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.matrices import Matrix, ImmutableMatrix
|
||||
|
||||
from sympy.physics.quantum.gate import (XGate, YGate, ZGate, random_circuit,
|
||||
CNOT, IdentityGate, H, X, Y, S, T, Z, SwapGate, gate_simp, gate_sort,
|
||||
CNotGate, TGate, HadamardGate, PhaseGate, UGate, CGate)
|
||||
from sympy.physics.quantum.commutator import Commutator
|
||||
from sympy.physics.quantum.anticommutator import AntiCommutator
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.qubit import Qubit, IntQubit, qubit_to_matrix, \
|
||||
matrix_to_qubit
|
||||
from sympy.physics.quantum.matrixutils import matrix_to_zero
|
||||
from sympy.physics.quantum.matrixcache import sqrt2_inv
|
||||
from sympy.physics.quantum import Dagger
|
||||
|
||||
|
||||
def test_gate():
|
||||
"""Test a basic gate."""
|
||||
h = HadamardGate(1)
|
||||
assert h.min_qubits == 2
|
||||
assert h.nqubits == 1
|
||||
|
||||
i0 = Wild('i0')
|
||||
i1 = Wild('i1')
|
||||
h0_w1 = HadamardGate(i0)
|
||||
h0_w2 = HadamardGate(i0)
|
||||
h1_w1 = HadamardGate(i1)
|
||||
|
||||
assert h0_w1 == h0_w2
|
||||
assert h0_w1 != h1_w1
|
||||
assert h1_w1 != h0_w2
|
||||
|
||||
cnot_10_w1 = CNOT(i1, i0)
|
||||
cnot_10_w2 = CNOT(i1, i0)
|
||||
cnot_01_w1 = CNOT(i0, i1)
|
||||
|
||||
assert cnot_10_w1 == cnot_10_w2
|
||||
assert cnot_10_w1 != cnot_01_w1
|
||||
assert cnot_10_w2 != cnot_01_w1
|
||||
|
||||
|
||||
def test_UGate():
|
||||
a, b, c, d = symbols('a,b,c,d')
|
||||
uMat = Matrix([[a, b], [c, d]])
|
||||
|
||||
# Test basic case where gate exists in 1-qubit space
|
||||
u1 = UGate((0,), uMat)
|
||||
assert represent(u1, nqubits=1) == uMat
|
||||
assert qapply(u1*Qubit('0')) == a*Qubit('0') + c*Qubit('1')
|
||||
assert qapply(u1*Qubit('1')) == b*Qubit('0') + d*Qubit('1')
|
||||
|
||||
# Test case where gate exists in a larger space
|
||||
u2 = UGate((1,), uMat)
|
||||
u2Rep = represent(u2, nqubits=2)
|
||||
for i in range(4):
|
||||
assert u2Rep*qubit_to_matrix(IntQubit(i, 2)) == \
|
||||
qubit_to_matrix(qapply(u2*IntQubit(i, 2)))
|
||||
|
||||
|
||||
def test_cgate():
|
||||
"""Test the general CGate."""
|
||||
# Test single control functionality
|
||||
CNOTMatrix = Matrix(
|
||||
[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
|
||||
assert represent(CGate(1, XGate(0)), nqubits=2) == CNOTMatrix
|
||||
|
||||
# Test multiple control bit functionality
|
||||
ToffoliGate = CGate((1, 2), XGate(0))
|
||||
assert represent(ToffoliGate, nqubits=3) == \
|
||||
Matrix(
|
||||
[[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0,
|
||||
1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0]])
|
||||
|
||||
ToffoliGate = CGate((3, 0), XGate(1))
|
||||
assert qapply(ToffoliGate*Qubit('1001')) == \
|
||||
matrix_to_qubit(represent(ToffoliGate*Qubit('1001'), nqubits=4))
|
||||
assert qapply(ToffoliGate*Qubit('0000')) == \
|
||||
matrix_to_qubit(represent(ToffoliGate*Qubit('0000'), nqubits=4))
|
||||
|
||||
CYGate = CGate(1, YGate(0))
|
||||
CYGate_matrix = Matrix(
|
||||
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 0, -I), (0, 0, I, 0)))
|
||||
# Test 2 qubit controlled-Y gate decompose method.
|
||||
assert represent(CYGate.decompose(), nqubits=2) == CYGate_matrix
|
||||
|
||||
CZGate = CGate(0, ZGate(1))
|
||||
CZGate_matrix = Matrix(
|
||||
((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, -1)))
|
||||
assert qapply(CZGate*Qubit('11')) == -Qubit('11')
|
||||
assert matrix_to_qubit(represent(CZGate*Qubit('11'), nqubits=2)) == \
|
||||
-Qubit('11')
|
||||
# Test 2 qubit controlled-Z gate decompose method.
|
||||
assert represent(CZGate.decompose(), nqubits=2) == CZGate_matrix
|
||||
|
||||
CPhaseGate = CGate(0, PhaseGate(1))
|
||||
assert qapply(CPhaseGate*Qubit('11')) == \
|
||||
I*Qubit('11')
|
||||
assert matrix_to_qubit(represent(CPhaseGate*Qubit('11'), nqubits=2)) == \
|
||||
I*Qubit('11')
|
||||
|
||||
# Test that the dagger, inverse, and power of CGate is evaluated properly
|
||||
assert Dagger(CZGate) == CZGate
|
||||
assert pow(CZGate, 1) == Dagger(CZGate)
|
||||
assert Dagger(CZGate) == CZGate.inverse()
|
||||
assert Dagger(CPhaseGate) != CPhaseGate
|
||||
assert Dagger(CPhaseGate) == CPhaseGate.inverse()
|
||||
assert Dagger(CPhaseGate) == pow(CPhaseGate, -1)
|
||||
assert pow(CPhaseGate, -1) == CPhaseGate.inverse()
|
||||
|
||||
|
||||
def test_UGate_CGate_combo():
|
||||
a, b, c, d = symbols('a,b,c,d')
|
||||
uMat = Matrix([[a, b], [c, d]])
|
||||
cMat = Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, a, b], [0, 0, c, d]])
|
||||
|
||||
# Test basic case where gate exists in 1-qubit space.
|
||||
u1 = UGate((0,), uMat)
|
||||
cu1 = CGate(1, u1)
|
||||
assert represent(cu1, nqubits=2) == cMat
|
||||
assert qapply(cu1*Qubit('10')) == a*Qubit('10') + c*Qubit('11')
|
||||
assert qapply(cu1*Qubit('11')) == b*Qubit('10') + d*Qubit('11')
|
||||
assert qapply(cu1*Qubit('01')) == Qubit('01')
|
||||
assert qapply(cu1*Qubit('00')) == Qubit('00')
|
||||
|
||||
# Test case where gate exists in a larger space.
|
||||
u2 = UGate((1,), uMat)
|
||||
u2Rep = represent(u2, nqubits=2)
|
||||
for i in range(4):
|
||||
assert u2Rep*qubit_to_matrix(IntQubit(i, 2)) == \
|
||||
qubit_to_matrix(qapply(u2*IntQubit(i, 2)))
|
||||
|
||||
def test_UGate_OneQubitGate_combo():
|
||||
v, w, f, g = symbols('v w f g')
|
||||
uMat1 = ImmutableMatrix([[v, w], [f, g]])
|
||||
cMat1 = Matrix([[v, w + 1, 0, 0], [f + 1, g, 0, 0], [0, 0, v, w + 1], [0, 0, f + 1, g]])
|
||||
u1 = X(0) + UGate(0, uMat1)
|
||||
assert represent(u1, nqubits=2) == cMat1
|
||||
|
||||
uMat2 = ImmutableMatrix([[1/sqrt(2), 1/sqrt(2)], [I/sqrt(2), -I/sqrt(2)]])
|
||||
cMat2_1 = Matrix([[Rational(1, 2) + I/2, Rational(1, 2) - I/2],
|
||||
[Rational(1, 2) - I/2, Rational(1, 2) + I/2]])
|
||||
cMat2_2 = Matrix([[1, 0], [0, I]])
|
||||
u2 = UGate(0, uMat2)
|
||||
assert represent(H(0)*u2, nqubits=1) == cMat2_1
|
||||
assert represent(u2*H(0), nqubits=1) == cMat2_2
|
||||
|
||||
def test_represent_hadamard():
|
||||
"""Test the representation of the hadamard gate."""
|
||||
circuit = HadamardGate(0)*Qubit('00')
|
||||
answer = represent(circuit, nqubits=2)
|
||||
# Check that the answers are same to within an epsilon.
|
||||
assert answer == Matrix([sqrt2_inv, sqrt2_inv, 0, 0])
|
||||
|
||||
|
||||
def test_represent_xgate():
|
||||
"""Test the representation of the X gate."""
|
||||
circuit = XGate(0)*Qubit('00')
|
||||
answer = represent(circuit, nqubits=2)
|
||||
assert Matrix([0, 1, 0, 0]) == answer
|
||||
|
||||
|
||||
def test_represent_ygate():
|
||||
"""Test the representation of the Y gate."""
|
||||
circuit = YGate(0)*Qubit('00')
|
||||
answer = represent(circuit, nqubits=2)
|
||||
assert answer[0] == 0 and answer[1] == I and \
|
||||
answer[2] == 0 and answer[3] == 0
|
||||
|
||||
|
||||
def test_represent_zgate():
|
||||
"""Test the representation of the Z gate."""
|
||||
circuit = ZGate(0)*Qubit('00')
|
||||
answer = represent(circuit, nqubits=2)
|
||||
assert Matrix([1, 0, 0, 0]) == answer
|
||||
|
||||
|
||||
def test_represent_phasegate():
|
||||
"""Test the representation of the S gate."""
|
||||
circuit = PhaseGate(0)*Qubit('01')
|
||||
answer = represent(circuit, nqubits=2)
|
||||
assert Matrix([0, I, 0, 0]) == answer
|
||||
|
||||
|
||||
def test_represent_tgate():
|
||||
"""Test the representation of the T gate."""
|
||||
circuit = TGate(0)*Qubit('01')
|
||||
assert Matrix([0, exp(I*pi/4), 0, 0]) == represent(circuit, nqubits=2)
|
||||
|
||||
|
||||
def test_compound_gates():
|
||||
"""Test a compound gate representation."""
|
||||
circuit = YGate(0)*ZGate(0)*XGate(0)*HadamardGate(0)*Qubit('00')
|
||||
answer = represent(circuit, nqubits=2)
|
||||
assert Matrix([I/sqrt(2), I/sqrt(2), 0, 0]) == answer
|
||||
|
||||
|
||||
def test_cnot_gate():
|
||||
"""Test the CNOT gate."""
|
||||
circuit = CNotGate(1, 0)
|
||||
assert represent(circuit, nqubits=2) == \
|
||||
Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
|
||||
circuit = circuit*Qubit('111')
|
||||
assert matrix_to_qubit(represent(circuit, nqubits=3)) == \
|
||||
qapply(circuit)
|
||||
|
||||
circuit = CNotGate(1, 0)
|
||||
assert Dagger(circuit) == circuit
|
||||
assert Dagger(Dagger(circuit)) == circuit
|
||||
assert circuit*circuit == 1
|
||||
|
||||
|
||||
def test_gate_sort():
|
||||
"""Test gate_sort."""
|
||||
for g in (X, Y, Z, H, S, T):
|
||||
assert gate_sort(g(2)*g(1)*g(0)) == g(0)*g(1)*g(2)
|
||||
e = gate_sort(X(1)*H(0)**2*CNOT(0, 1)*X(1)*X(0))
|
||||
assert e == H(0)**2*CNOT(0, 1)*X(0)*X(1)**2
|
||||
assert gate_sort(Z(0)*X(0)) == -X(0)*Z(0)
|
||||
assert gate_sort(Z(0)*X(0)**2) == X(0)**2*Z(0)
|
||||
assert gate_sort(Y(0)*H(0)) == -H(0)*Y(0)
|
||||
assert gate_sort(Y(0)*X(0)) == -X(0)*Y(0)
|
||||
assert gate_sort(Z(0)*Y(0)) == -Y(0)*Z(0)
|
||||
assert gate_sort(T(0)*S(0)) == S(0)*T(0)
|
||||
assert gate_sort(Z(0)*S(0)) == S(0)*Z(0)
|
||||
assert gate_sort(Z(0)*T(0)) == T(0)*Z(0)
|
||||
assert gate_sort(Z(0)*CNOT(0, 1)) == CNOT(0, 1)*Z(0)
|
||||
assert gate_sort(S(0)*CNOT(0, 1)) == CNOT(0, 1)*S(0)
|
||||
assert gate_sort(T(0)*CNOT(0, 1)) == CNOT(0, 1)*T(0)
|
||||
assert gate_sort(X(1)*CNOT(0, 1)) == CNOT(0, 1)*X(1)
|
||||
# This takes a long time and should only be uncommented once in a while.
|
||||
# nqubits = 5
|
||||
# ngates = 10
|
||||
# trials = 10
|
||||
# for i in range(trials):
|
||||
# c = random_circuit(ngates, nqubits)
|
||||
# assert represent(c, nqubits=nqubits) == \
|
||||
# represent(gate_sort(c), nqubits=nqubits)
|
||||
|
||||
|
||||
def test_gate_simp():
|
||||
"""Test gate_simp."""
|
||||
e = H(0)*X(1)*H(0)**2*CNOT(0, 1)*X(1)**3*X(0)*Z(3)**2*S(4)**3
|
||||
assert gate_simp(e) == H(0)*CNOT(0, 1)*S(4)*X(0)*Z(4)
|
||||
assert gate_simp(X(0)*X(0)) == 1
|
||||
assert gate_simp(Y(0)*Y(0)) == 1
|
||||
assert gate_simp(Z(0)*Z(0)) == 1
|
||||
assert gate_simp(H(0)*H(0)) == 1
|
||||
assert gate_simp(T(0)*T(0)) == S(0)
|
||||
assert gate_simp(S(0)*S(0)) == Z(0)
|
||||
assert gate_simp(Integer(1)) == Integer(1)
|
||||
assert gate_simp(X(0)**2 + Y(0)**2) == Integer(2)
|
||||
|
||||
|
||||
def test_swap_gate():
|
||||
"""Test the SWAP gate."""
|
||||
swap_gate_matrix = Matrix(
|
||||
((1, 0, 0, 0), (0, 0, 1, 0), (0, 1, 0, 0), (0, 0, 0, 1)))
|
||||
assert represent(SwapGate(1, 0).decompose(), nqubits=2) == swap_gate_matrix
|
||||
assert qapply(SwapGate(1, 3)*Qubit('0010')) == Qubit('1000')
|
||||
nqubits = 4
|
||||
for i in range(nqubits):
|
||||
for j in range(i):
|
||||
assert represent(SwapGate(i, j), nqubits=nqubits) == \
|
||||
represent(SwapGate(i, j).decompose(), nqubits=nqubits)
|
||||
|
||||
|
||||
def test_one_qubit_commutators():
|
||||
"""Test single qubit gate commutation relations."""
|
||||
for g1 in (IdentityGate, X, Y, Z, H, T, S):
|
||||
for g2 in (IdentityGate, X, Y, Z, H, T, S):
|
||||
e = Commutator(g1(0), g2(0))
|
||||
a = matrix_to_zero(represent(e, nqubits=1, format='sympy'))
|
||||
b = matrix_to_zero(represent(e.doit(), nqubits=1, format='sympy'))
|
||||
assert a == b
|
||||
|
||||
e = Commutator(g1(0), g2(1))
|
||||
assert e.doit() == 0
|
||||
|
||||
|
||||
def test_one_qubit_anticommutators():
|
||||
"""Test single qubit gate anticommutation relations."""
|
||||
for g1 in (IdentityGate, X, Y, Z, H):
|
||||
for g2 in (IdentityGate, X, Y, Z, H):
|
||||
e = AntiCommutator(g1(0), g2(0))
|
||||
a = matrix_to_zero(represent(e, nqubits=1, format='sympy'))
|
||||
b = matrix_to_zero(represent(e.doit(), nqubits=1, format='sympy'))
|
||||
assert a == b
|
||||
e = AntiCommutator(g1(0), g2(1))
|
||||
a = matrix_to_zero(represent(e, nqubits=2, format='sympy'))
|
||||
b = matrix_to_zero(represent(e.doit(), nqubits=2, format='sympy'))
|
||||
assert a == b
|
||||
|
||||
|
||||
def test_cnot_commutators():
|
||||
"""Test commutators of involving CNOT gates."""
|
||||
assert Commutator(CNOT(0, 1), Z(0)).doit() == 0
|
||||
assert Commutator(CNOT(0, 1), T(0)).doit() == 0
|
||||
assert Commutator(CNOT(0, 1), S(0)).doit() == 0
|
||||
assert Commutator(CNOT(0, 1), X(1)).doit() == 0
|
||||
assert Commutator(CNOT(0, 1), CNOT(0, 1)).doit() == 0
|
||||
assert Commutator(CNOT(0, 1), CNOT(0, 2)).doit() == 0
|
||||
assert Commutator(CNOT(0, 2), CNOT(0, 1)).doit() == 0
|
||||
assert Commutator(CNOT(1, 2), CNOT(1, 0)).doit() == 0
|
||||
|
||||
|
||||
def test_random_circuit():
|
||||
c = random_circuit(10, 3)
|
||||
assert isinstance(c, Mul)
|
||||
m = represent(c, nqubits=3)
|
||||
assert m.shape == (8, 8)
|
||||
assert isinstance(m, Matrix)
|
||||
|
||||
|
||||
def test_hermitian_XGate():
|
||||
x = XGate(1, 2)
|
||||
x_dagger = Dagger(x)
|
||||
|
||||
assert (x == x_dagger)
|
||||
|
||||
|
||||
def test_hermitian_YGate():
|
||||
y = YGate(1, 2)
|
||||
y_dagger = Dagger(y)
|
||||
|
||||
assert (y == y_dagger)
|
||||
|
||||
|
||||
def test_hermitian_ZGate():
|
||||
z = ZGate(1, 2)
|
||||
z_dagger = Dagger(z)
|
||||
|
||||
assert (z == z_dagger)
|
||||
|
||||
|
||||
def test_unitary_XGate():
|
||||
x = XGate(1, 2)
|
||||
x_dagger = Dagger(x)
|
||||
|
||||
assert (x*x_dagger == 1)
|
||||
|
||||
|
||||
def test_unitary_YGate():
|
||||
y = YGate(1, 2)
|
||||
y_dagger = Dagger(y)
|
||||
|
||||
assert (y*y_dagger == 1)
|
||||
|
||||
|
||||
def test_unitary_ZGate():
|
||||
z = ZGate(1, 2)
|
||||
z_dagger = Dagger(z)
|
||||
|
||||
assert (z*z_dagger == 1)
|
||||
@@ -0,0 +1,92 @@
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.qubit import IntQubit
|
||||
from sympy.physics.quantum.grover import (apply_grover, superposition_basis,
|
||||
OracleGate, grover_iteration, WGate)
|
||||
|
||||
|
||||
def return_one_on_two(qubits):
|
||||
return qubits == IntQubit(2, qubits.nqubits)
|
||||
|
||||
|
||||
def return_one_on_one(qubits):
|
||||
return qubits == IntQubit(1, nqubits=qubits.nqubits)
|
||||
|
||||
|
||||
def test_superposition_basis():
|
||||
nbits = 2
|
||||
first_half_state = IntQubit(0, nqubits=nbits)/2 + IntQubit(1, nqubits=nbits)/2
|
||||
second_half_state = IntQubit(2, nbits)/2 + IntQubit(3, nbits)/2
|
||||
assert first_half_state + second_half_state == superposition_basis(nbits)
|
||||
|
||||
nbits = 3
|
||||
firstq = (1/sqrt(8))*IntQubit(0, nqubits=nbits) + (1/sqrt(8))*IntQubit(1, nqubits=nbits)
|
||||
secondq = (1/sqrt(8))*IntQubit(2, nbits) + (1/sqrt(8))*IntQubit(3, nbits)
|
||||
thirdq = (1/sqrt(8))*IntQubit(4, nbits) + (1/sqrt(8))*IntQubit(5, nbits)
|
||||
fourthq = (1/sqrt(8))*IntQubit(6, nbits) + (1/sqrt(8))*IntQubit(7, nbits)
|
||||
assert firstq + secondq + thirdq + fourthq == superposition_basis(nbits)
|
||||
|
||||
|
||||
def test_OracleGate():
|
||||
v = OracleGate(1, lambda qubits: qubits == IntQubit(0))
|
||||
assert qapply(v*IntQubit(0)) == -IntQubit(0)
|
||||
assert qapply(v*IntQubit(1)) == IntQubit(1)
|
||||
|
||||
nbits = 2
|
||||
v = OracleGate(2, return_one_on_two)
|
||||
assert qapply(v*IntQubit(0, nbits)) == IntQubit(0, nqubits=nbits)
|
||||
assert qapply(v*IntQubit(1, nbits)) == IntQubit(1, nqubits=nbits)
|
||||
assert qapply(v*IntQubit(2, nbits)) == -IntQubit(2, nbits)
|
||||
assert qapply(v*IntQubit(3, nbits)) == IntQubit(3, nbits)
|
||||
|
||||
assert represent(OracleGate(1, lambda qubits: qubits == IntQubit(0)), nqubits=1) == \
|
||||
Matrix([[-1, 0], [0, 1]])
|
||||
assert represent(v, nqubits=2) == Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
|
||||
|
||||
|
||||
def test_WGate():
|
||||
nqubits = 2
|
||||
basis_states = superposition_basis(nqubits)
|
||||
assert qapply(WGate(nqubits)*basis_states) == basis_states
|
||||
|
||||
expected = ((2/sqrt(pow(2, nqubits)))*basis_states) - IntQubit(1, nqubits=nqubits)
|
||||
assert qapply(WGate(nqubits)*IntQubit(1, nqubits=nqubits)) == expected
|
||||
|
||||
|
||||
def test_grover_iteration_1():
|
||||
numqubits = 2
|
||||
basis_states = superposition_basis(numqubits)
|
||||
v = OracleGate(numqubits, return_one_on_one)
|
||||
expected = IntQubit(1, nqubits=numqubits)
|
||||
assert qapply(grover_iteration(basis_states, v)) == expected
|
||||
|
||||
|
||||
def test_grover_iteration_2():
|
||||
numqubits = 4
|
||||
basis_states = superposition_basis(numqubits)
|
||||
v = OracleGate(numqubits, return_one_on_two)
|
||||
# After (pi/4)sqrt(pow(2, n)), IntQubit(2) should have highest prob
|
||||
# In this case, after around pi times (3 or 4)
|
||||
iterated = grover_iteration(basis_states, v)
|
||||
iterated = qapply(iterated)
|
||||
iterated = grover_iteration(iterated, v)
|
||||
iterated = qapply(iterated)
|
||||
iterated = grover_iteration(iterated, v)
|
||||
iterated = qapply(iterated)
|
||||
# In this case, probability was highest after 3 iterations
|
||||
# Probability of Qubit('0010') was 251/256 (3) vs 781/1024 (4)
|
||||
# Ask about measurement
|
||||
expected = (-13*basis_states)/64 + 264*IntQubit(2, numqubits)/256
|
||||
assert qapply(expected) == iterated
|
||||
|
||||
|
||||
def test_grover():
|
||||
nqubits = 2
|
||||
assert apply_grover(return_one_on_one, nqubits) == IntQubit(1, nqubits=nqubits)
|
||||
|
||||
nqubits = 4
|
||||
basis_states = superposition_basis(nqubits)
|
||||
expected = (-13*basis_states)/64 + 264*IntQubit(2, nqubits)/256
|
||||
assert apply_grover(return_one_on_two, 4) == qapply(expected)
|
||||
@@ -0,0 +1,110 @@
|
||||
from sympy.physics.quantum.hilbert import (
|
||||
HilbertSpace, ComplexSpace, L2, FockSpace, TensorProductHilbertSpace,
|
||||
DirectSumHilbertSpace, TensorPowerHilbertSpace
|
||||
)
|
||||
|
||||
from sympy.core.numbers import oo
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.printing.repr import srepr
|
||||
from sympy.printing.str import sstr
|
||||
from sympy.sets.sets import Interval
|
||||
|
||||
|
||||
def test_hilbert_space():
|
||||
hs = HilbertSpace()
|
||||
assert isinstance(hs, HilbertSpace)
|
||||
assert sstr(hs) == 'H'
|
||||
assert srepr(hs) == 'HilbertSpace()'
|
||||
|
||||
|
||||
def test_complex_space():
|
||||
c1 = ComplexSpace(2)
|
||||
assert isinstance(c1, ComplexSpace)
|
||||
assert c1.dimension == 2
|
||||
assert sstr(c1) == 'C(2)'
|
||||
assert srepr(c1) == 'ComplexSpace(Integer(2))'
|
||||
|
||||
n = Symbol('n')
|
||||
c2 = ComplexSpace(n)
|
||||
assert isinstance(c2, ComplexSpace)
|
||||
assert c2.dimension == n
|
||||
assert sstr(c2) == 'C(n)'
|
||||
assert srepr(c2) == "ComplexSpace(Symbol('n'))"
|
||||
assert c2.subs(n, 2) == ComplexSpace(2)
|
||||
|
||||
|
||||
def test_L2():
|
||||
b1 = L2(Interval(-oo, 1))
|
||||
assert isinstance(b1, L2)
|
||||
assert b1.dimension is oo
|
||||
assert b1.interval == Interval(-oo, 1)
|
||||
|
||||
x = Symbol('x', real=True)
|
||||
y = Symbol('y', real=True)
|
||||
b2 = L2(Interval(x, y))
|
||||
assert b2.dimension is oo
|
||||
assert b2.interval == Interval(x, y)
|
||||
assert b2.subs(x, -1) == L2(Interval(-1, y))
|
||||
|
||||
|
||||
def test_fock_space():
|
||||
f1 = FockSpace()
|
||||
f2 = FockSpace()
|
||||
assert isinstance(f1, FockSpace)
|
||||
assert f1.dimension is oo
|
||||
assert f1 == f2
|
||||
|
||||
|
||||
def test_tensor_product():
|
||||
n = Symbol('n')
|
||||
hs1 = ComplexSpace(2)
|
||||
hs2 = ComplexSpace(n)
|
||||
|
||||
h = hs1*hs2
|
||||
assert isinstance(h, TensorProductHilbertSpace)
|
||||
assert h.dimension == 2*n
|
||||
assert h.spaces == (hs1, hs2)
|
||||
|
||||
h = hs2*hs2
|
||||
assert isinstance(h, TensorPowerHilbertSpace)
|
||||
assert h.base == hs2
|
||||
assert h.exp == 2
|
||||
assert h.dimension == n**2
|
||||
|
||||
f = FockSpace()
|
||||
h = hs1*hs2*f
|
||||
assert h.dimension is oo
|
||||
|
||||
|
||||
def test_tensor_power():
|
||||
n = Symbol('n')
|
||||
hs1 = ComplexSpace(2)
|
||||
hs2 = ComplexSpace(n)
|
||||
|
||||
h = hs1**2
|
||||
assert isinstance(h, TensorPowerHilbertSpace)
|
||||
assert h.base == hs1
|
||||
assert h.exp == 2
|
||||
assert h.dimension == 4
|
||||
|
||||
h = hs2**3
|
||||
assert isinstance(h, TensorPowerHilbertSpace)
|
||||
assert h.base == hs2
|
||||
assert h.exp == 3
|
||||
assert h.dimension == n**3
|
||||
|
||||
|
||||
def test_direct_sum():
|
||||
n = Symbol('n')
|
||||
hs1 = ComplexSpace(2)
|
||||
hs2 = ComplexSpace(n)
|
||||
|
||||
h = hs1 + hs2
|
||||
assert isinstance(h, DirectSumHilbertSpace)
|
||||
assert h.dimension == 2 + n
|
||||
assert h.spaces == (hs1, hs2)
|
||||
|
||||
f = FockSpace()
|
||||
h = hs1 + f + hs2
|
||||
assert h.dimension is oo
|
||||
assert h.spaces == (hs1, f, hs2)
|
||||
@@ -0,0 +1,492 @@
|
||||
from sympy.external import import_module
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.gate import (X, Y, Z, H, CNOT,
|
||||
IdentityGate, CGate, PhaseGate, TGate)
|
||||
from sympy.physics.quantum.identitysearch import (generate_gate_rules,
|
||||
generate_equivalent_ids, GateIdentity, bfs_identity_search,
|
||||
is_scalar_sparse_matrix,
|
||||
is_scalar_nonsparse_matrix, is_degenerate, is_reducible)
|
||||
from sympy.testing.pytest import skip
|
||||
|
||||
|
||||
def create_gate_sequence(qubit=0):
|
||||
gates = (X(qubit), Y(qubit), Z(qubit), H(qubit))
|
||||
return gates
|
||||
|
||||
|
||||
def test_generate_gate_rules_1():
|
||||
# Test with tuples
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
ph = PhaseGate(0)
|
||||
cgate_t = CGate(0, TGate(1))
|
||||
|
||||
assert generate_gate_rules((x,)) == {((x,), ())}
|
||||
|
||||
gate_rules = {((x, x), ()),
|
||||
((x,), (x,))}
|
||||
assert generate_gate_rules((x, x)) == gate_rules
|
||||
|
||||
gate_rules = {((x, y, x), ()),
|
||||
((y, x, x), ()),
|
||||
((x, x, y), ()),
|
||||
((y, x), (x,)),
|
||||
((x, y), (x,)),
|
||||
((y,), (x, x))}
|
||||
assert generate_gate_rules((x, y, x)) == gate_rules
|
||||
|
||||
gate_rules = {((x, y, z), ()), ((y, z, x), ()), ((z, x, y), ()),
|
||||
((), (x, z, y)), ((), (y, x, z)), ((), (z, y, x)),
|
||||
((x,), (z, y)), ((y, z), (x,)), ((y,), (x, z)),
|
||||
((z, x), (y,)), ((z,), (y, x)), ((x, y), (z,))}
|
||||
actual = generate_gate_rules((x, y, z))
|
||||
assert actual == gate_rules
|
||||
|
||||
gate_rules = {
|
||||
((), (h, z, y, x)), ((), (x, h, z, y)), ((), (y, x, h, z)),
|
||||
((), (z, y, x, h)), ((h,), (z, y, x)), ((x,), (h, z, y)),
|
||||
((y,), (x, h, z)), ((z,), (y, x, h)), ((h, x), (z, y)),
|
||||
((x, y), (h, z)), ((y, z), (x, h)), ((z, h), (y, x)),
|
||||
((h, x, y), (z,)), ((x, y, z), (h,)), ((y, z, h), (x,)),
|
||||
((z, h, x), (y,)), ((h, x, y, z), ()), ((x, y, z, h), ()),
|
||||
((y, z, h, x), ()), ((z, h, x, y), ())}
|
||||
actual = generate_gate_rules((x, y, z, h))
|
||||
assert actual == gate_rules
|
||||
|
||||
gate_rules = {((), (cgate_t**(-1), ph**(-1), x)),
|
||||
((), (ph**(-1), x, cgate_t**(-1))),
|
||||
((), (x, cgate_t**(-1), ph**(-1))),
|
||||
((cgate_t,), (ph**(-1), x)),
|
||||
((ph,), (x, cgate_t**(-1))),
|
||||
((x,), (cgate_t**(-1), ph**(-1))),
|
||||
((cgate_t, x), (ph**(-1),)),
|
||||
((ph, cgate_t), (x,)),
|
||||
((x, ph), (cgate_t**(-1),)),
|
||||
((cgate_t, x, ph), ()),
|
||||
((ph, cgate_t, x), ()),
|
||||
((x, ph, cgate_t), ())}
|
||||
actual = generate_gate_rules((x, ph, cgate_t))
|
||||
assert actual == gate_rules
|
||||
|
||||
gate_rules = {(Integer(1), cgate_t**(-1)*ph**(-1)*x),
|
||||
(Integer(1), ph**(-1)*x*cgate_t**(-1)),
|
||||
(Integer(1), x*cgate_t**(-1)*ph**(-1)),
|
||||
(cgate_t, ph**(-1)*x),
|
||||
(ph, x*cgate_t**(-1)),
|
||||
(x, cgate_t**(-1)*ph**(-1)),
|
||||
(cgate_t*x, ph**(-1)),
|
||||
(ph*cgate_t, x),
|
||||
(x*ph, cgate_t**(-1)),
|
||||
(cgate_t*x*ph, Integer(1)),
|
||||
(ph*cgate_t*x, Integer(1)),
|
||||
(x*ph*cgate_t, Integer(1))}
|
||||
actual = generate_gate_rules((x, ph, cgate_t), return_as_muls=True)
|
||||
assert actual == gate_rules
|
||||
|
||||
|
||||
def test_generate_gate_rules_2():
|
||||
# Test with Muls
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
ph = PhaseGate(0)
|
||||
cgate_t = CGate(0, TGate(1))
|
||||
|
||||
# Note: 1 (type int) is not the same as 1 (type One)
|
||||
expected = {(x, Integer(1))}
|
||||
assert generate_gate_rules((x,), return_as_muls=True) == expected
|
||||
|
||||
expected = {(Integer(1), Integer(1))}
|
||||
assert generate_gate_rules(x*x, return_as_muls=True) == expected
|
||||
|
||||
expected = {((), ())}
|
||||
assert generate_gate_rules(x*x, return_as_muls=False) == expected
|
||||
|
||||
gate_rules = {(x*y*x, Integer(1)),
|
||||
(y, Integer(1)),
|
||||
(y*x, x),
|
||||
(x*y, x)}
|
||||
assert generate_gate_rules(x*y*x, return_as_muls=True) == gate_rules
|
||||
|
||||
gate_rules = {(x*y*z, Integer(1)),
|
||||
(y*z*x, Integer(1)),
|
||||
(z*x*y, Integer(1)),
|
||||
(Integer(1), x*z*y),
|
||||
(Integer(1), y*x*z),
|
||||
(Integer(1), z*y*x),
|
||||
(x, z*y),
|
||||
(y*z, x),
|
||||
(y, x*z),
|
||||
(z*x, y),
|
||||
(z, y*x),
|
||||
(x*y, z)}
|
||||
actual = generate_gate_rules(x*y*z, return_as_muls=True)
|
||||
assert actual == gate_rules
|
||||
|
||||
gate_rules = {(Integer(1), h*z*y*x),
|
||||
(Integer(1), x*h*z*y),
|
||||
(Integer(1), y*x*h*z),
|
||||
(Integer(1), z*y*x*h),
|
||||
(h, z*y*x), (x, h*z*y),
|
||||
(y, x*h*z), (z, y*x*h),
|
||||
(h*x, z*y), (z*h, y*x),
|
||||
(x*y, h*z), (y*z, x*h),
|
||||
(h*x*y, z), (x*y*z, h),
|
||||
(y*z*h, x), (z*h*x, y),
|
||||
(h*x*y*z, Integer(1)),
|
||||
(x*y*z*h, Integer(1)),
|
||||
(y*z*h*x, Integer(1)),
|
||||
(z*h*x*y, Integer(1))}
|
||||
actual = generate_gate_rules(x*y*z*h, return_as_muls=True)
|
||||
assert actual == gate_rules
|
||||
|
||||
gate_rules = {(Integer(1), cgate_t**(-1)*ph**(-1)*x),
|
||||
(Integer(1), ph**(-1)*x*cgate_t**(-1)),
|
||||
(Integer(1), x*cgate_t**(-1)*ph**(-1)),
|
||||
(cgate_t, ph**(-1)*x),
|
||||
(ph, x*cgate_t**(-1)),
|
||||
(x, cgate_t**(-1)*ph**(-1)),
|
||||
(cgate_t*x, ph**(-1)),
|
||||
(ph*cgate_t, x),
|
||||
(x*ph, cgate_t**(-1)),
|
||||
(cgate_t*x*ph, Integer(1)),
|
||||
(ph*cgate_t*x, Integer(1)),
|
||||
(x*ph*cgate_t, Integer(1))}
|
||||
actual = generate_gate_rules(x*ph*cgate_t, return_as_muls=True)
|
||||
assert actual == gate_rules
|
||||
|
||||
gate_rules = {((), (cgate_t**(-1), ph**(-1), x)),
|
||||
((), (ph**(-1), x, cgate_t**(-1))),
|
||||
((), (x, cgate_t**(-1), ph**(-1))),
|
||||
((cgate_t,), (ph**(-1), x)),
|
||||
((ph,), (x, cgate_t**(-1))),
|
||||
((x,), (cgate_t**(-1), ph**(-1))),
|
||||
((cgate_t, x), (ph**(-1),)),
|
||||
((ph, cgate_t), (x,)),
|
||||
((x, ph), (cgate_t**(-1),)),
|
||||
((cgate_t, x, ph), ()),
|
||||
((ph, cgate_t, x), ()),
|
||||
((x, ph, cgate_t), ())}
|
||||
actual = generate_gate_rules(x*ph*cgate_t)
|
||||
assert actual == gate_rules
|
||||
|
||||
|
||||
def test_generate_equivalent_ids_1():
|
||||
# Test with tuples
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
|
||||
assert generate_equivalent_ids((x,)) == {(x,)}
|
||||
assert generate_equivalent_ids((x, x)) == {(x, x)}
|
||||
assert generate_equivalent_ids((x, y)) == {(x, y), (y, x)}
|
||||
|
||||
gate_seq = (x, y, z)
|
||||
gate_ids = {(x, y, z), (y, z, x), (z, x, y), (z, y, x),
|
||||
(y, x, z), (x, z, y)}
|
||||
assert generate_equivalent_ids(gate_seq) == gate_ids
|
||||
|
||||
gate_ids = {Mul(x, y, z), Mul(y, z, x), Mul(z, x, y),
|
||||
Mul(z, y, x), Mul(y, x, z), Mul(x, z, y)}
|
||||
assert generate_equivalent_ids(gate_seq, return_as_muls=True) == gate_ids
|
||||
|
||||
gate_seq = (x, y, z, h)
|
||||
gate_ids = {(x, y, z, h), (y, z, h, x),
|
||||
(h, x, y, z), (h, z, y, x),
|
||||
(z, y, x, h), (y, x, h, z),
|
||||
(z, h, x, y), (x, h, z, y)}
|
||||
assert generate_equivalent_ids(gate_seq) == gate_ids
|
||||
|
||||
gate_seq = (x, y, x, y)
|
||||
gate_ids = {(x, y, x, y), (y, x, y, x)}
|
||||
assert generate_equivalent_ids(gate_seq) == gate_ids
|
||||
|
||||
cgate_y = CGate((1,), y)
|
||||
gate_seq = (y, cgate_y, y, cgate_y)
|
||||
gate_ids = {(y, cgate_y, y, cgate_y), (cgate_y, y, cgate_y, y)}
|
||||
assert generate_equivalent_ids(gate_seq) == gate_ids
|
||||
|
||||
cnot = CNOT(1, 0)
|
||||
cgate_z = CGate((0,), Z(1))
|
||||
gate_seq = (cnot, h, cgate_z, h)
|
||||
gate_ids = {(cnot, h, cgate_z, h), (h, cgate_z, h, cnot),
|
||||
(h, cnot, h, cgate_z), (cgate_z, h, cnot, h)}
|
||||
assert generate_equivalent_ids(gate_seq) == gate_ids
|
||||
|
||||
|
||||
def test_generate_equivalent_ids_2():
|
||||
# Test with Muls
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
|
||||
assert generate_equivalent_ids((x,), return_as_muls=True) == {x}
|
||||
|
||||
gate_ids = {Integer(1)}
|
||||
assert generate_equivalent_ids(x*x, return_as_muls=True) == gate_ids
|
||||
|
||||
gate_ids = {x*y, y*x}
|
||||
assert generate_equivalent_ids(x*y, return_as_muls=True) == gate_ids
|
||||
|
||||
gate_ids = {(x, y), (y, x)}
|
||||
assert generate_equivalent_ids(x*y) == gate_ids
|
||||
|
||||
circuit = Mul(*(x, y, z))
|
||||
gate_ids = {x*y*z, y*z*x, z*x*y, z*y*x,
|
||||
y*x*z, x*z*y}
|
||||
assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids
|
||||
|
||||
circuit = Mul(*(x, y, z, h))
|
||||
gate_ids = {x*y*z*h, y*z*h*x,
|
||||
h*x*y*z, h*z*y*x,
|
||||
z*y*x*h, y*x*h*z,
|
||||
z*h*x*y, x*h*z*y}
|
||||
assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids
|
||||
|
||||
circuit = Mul(*(x, y, x, y))
|
||||
gate_ids = {x*y*x*y, y*x*y*x}
|
||||
assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids
|
||||
|
||||
cgate_y = CGate((1,), y)
|
||||
circuit = Mul(*(y, cgate_y, y, cgate_y))
|
||||
gate_ids = {y*cgate_y*y*cgate_y, cgate_y*y*cgate_y*y}
|
||||
assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids
|
||||
|
||||
cnot = CNOT(1, 0)
|
||||
cgate_z = CGate((0,), Z(1))
|
||||
circuit = Mul(*(cnot, h, cgate_z, h))
|
||||
gate_ids = {cnot*h*cgate_z*h, h*cgate_z*h*cnot,
|
||||
h*cnot*h*cgate_z, cgate_z*h*cnot*h}
|
||||
assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids
|
||||
|
||||
|
||||
def test_is_scalar_nonsparse_matrix():
|
||||
numqubits = 2
|
||||
id_only = False
|
||||
|
||||
id_gate = (IdentityGate(1),)
|
||||
actual = is_scalar_nonsparse_matrix(id_gate, numqubits, id_only)
|
||||
assert actual is True
|
||||
|
||||
x0 = X(0)
|
||||
xx_circuit = (x0, x0)
|
||||
actual = is_scalar_nonsparse_matrix(xx_circuit, numqubits, id_only)
|
||||
assert actual is True
|
||||
|
||||
x1 = X(1)
|
||||
y1 = Y(1)
|
||||
xy_circuit = (x1, y1)
|
||||
actual = is_scalar_nonsparse_matrix(xy_circuit, numqubits, id_only)
|
||||
assert actual is False
|
||||
|
||||
z1 = Z(1)
|
||||
xyz_circuit = (x1, y1, z1)
|
||||
actual = is_scalar_nonsparse_matrix(xyz_circuit, numqubits, id_only)
|
||||
assert actual is True
|
||||
|
||||
cnot = CNOT(1, 0)
|
||||
cnot_circuit = (cnot, cnot)
|
||||
actual = is_scalar_nonsparse_matrix(cnot_circuit, numqubits, id_only)
|
||||
assert actual is True
|
||||
|
||||
h = H(0)
|
||||
hh_circuit = (h, h)
|
||||
actual = is_scalar_nonsparse_matrix(hh_circuit, numqubits, id_only)
|
||||
assert actual is True
|
||||
|
||||
h1 = H(1)
|
||||
xhzh_circuit = (x1, h1, z1, h1)
|
||||
actual = is_scalar_nonsparse_matrix(xhzh_circuit, numqubits, id_only)
|
||||
assert actual is True
|
||||
|
||||
id_only = True
|
||||
actual = is_scalar_nonsparse_matrix(xhzh_circuit, numqubits, id_only)
|
||||
assert actual is True
|
||||
actual = is_scalar_nonsparse_matrix(xyz_circuit, numqubits, id_only)
|
||||
assert actual is False
|
||||
actual = is_scalar_nonsparse_matrix(cnot_circuit, numqubits, id_only)
|
||||
assert actual is True
|
||||
actual = is_scalar_nonsparse_matrix(hh_circuit, numqubits, id_only)
|
||||
assert actual is True
|
||||
|
||||
|
||||
def test_is_scalar_sparse_matrix():
|
||||
np = import_module('numpy')
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
|
||||
scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
|
||||
if not scipy:
|
||||
skip("scipy not installed.")
|
||||
|
||||
numqubits = 2
|
||||
id_only = False
|
||||
|
||||
id_gate = (IdentityGate(1),)
|
||||
assert is_scalar_sparse_matrix(id_gate, numqubits, id_only) is True
|
||||
|
||||
x0 = X(0)
|
||||
xx_circuit = (x0, x0)
|
||||
assert is_scalar_sparse_matrix(xx_circuit, numqubits, id_only) is True
|
||||
|
||||
x1 = X(1)
|
||||
y1 = Y(1)
|
||||
xy_circuit = (x1, y1)
|
||||
assert is_scalar_sparse_matrix(xy_circuit, numqubits, id_only) is False
|
||||
|
||||
z1 = Z(1)
|
||||
xyz_circuit = (x1, y1, z1)
|
||||
assert is_scalar_sparse_matrix(xyz_circuit, numqubits, id_only) is True
|
||||
|
||||
cnot = CNOT(1, 0)
|
||||
cnot_circuit = (cnot, cnot)
|
||||
assert is_scalar_sparse_matrix(cnot_circuit, numqubits, id_only) is True
|
||||
|
||||
h = H(0)
|
||||
hh_circuit = (h, h)
|
||||
assert is_scalar_sparse_matrix(hh_circuit, numqubits, id_only) is True
|
||||
|
||||
# NOTE:
|
||||
# The elements of the sparse matrix for the following circuit
|
||||
# is actually 1.0000000000000002+0.0j.
|
||||
h1 = H(1)
|
||||
xhzh_circuit = (x1, h1, z1, h1)
|
||||
assert is_scalar_sparse_matrix(xhzh_circuit, numqubits, id_only) is True
|
||||
|
||||
id_only = True
|
||||
assert is_scalar_sparse_matrix(xhzh_circuit, numqubits, id_only) is True
|
||||
assert is_scalar_sparse_matrix(xyz_circuit, numqubits, id_only) is False
|
||||
assert is_scalar_sparse_matrix(cnot_circuit, numqubits, id_only) is True
|
||||
assert is_scalar_sparse_matrix(hh_circuit, numqubits, id_only) is True
|
||||
|
||||
|
||||
def test_is_degenerate():
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
|
||||
gate_id = GateIdentity(x, y, z)
|
||||
ids = {gate_id}
|
||||
|
||||
another_id = (z, y, x)
|
||||
assert is_degenerate(ids, another_id) is True
|
||||
|
||||
|
||||
def test_is_reducible():
|
||||
nqubits = 2
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
|
||||
circuit = (x, y, y)
|
||||
assert is_reducible(circuit, nqubits, 1, 3) is True
|
||||
|
||||
circuit = (x, y, x)
|
||||
assert is_reducible(circuit, nqubits, 1, 3) is False
|
||||
|
||||
circuit = (x, y, y, x)
|
||||
assert is_reducible(circuit, nqubits, 0, 4) is True
|
||||
|
||||
circuit = (x, y, y, x)
|
||||
assert is_reducible(circuit, nqubits, 1, 3) is True
|
||||
|
||||
circuit = (x, y, z, y, y)
|
||||
assert is_reducible(circuit, nqubits, 1, 5) is True
|
||||
|
||||
|
||||
def test_bfs_identity_search():
|
||||
assert bfs_identity_search([], 1) == set()
|
||||
|
||||
(x, y, z, h) = create_gate_sequence()
|
||||
|
||||
gate_list = [x]
|
||||
id_set = {GateIdentity(x, x)}
|
||||
assert bfs_identity_search(gate_list, 1, max_depth=2) == id_set
|
||||
|
||||
# Set should not contain degenerate quantum circuits
|
||||
gate_list = [x, y, z]
|
||||
id_set = {GateIdentity(x, x),
|
||||
GateIdentity(y, y),
|
||||
GateIdentity(z, z),
|
||||
GateIdentity(x, y, z)}
|
||||
assert bfs_identity_search(gate_list, 1) == id_set
|
||||
|
||||
id_set = {GateIdentity(x, x),
|
||||
GateIdentity(y, y),
|
||||
GateIdentity(z, z),
|
||||
GateIdentity(x, y, z),
|
||||
GateIdentity(x, y, x, y),
|
||||
GateIdentity(x, z, x, z),
|
||||
GateIdentity(y, z, y, z)}
|
||||
assert bfs_identity_search(gate_list, 1, max_depth=4) == id_set
|
||||
assert bfs_identity_search(gate_list, 1, max_depth=5) == id_set
|
||||
|
||||
gate_list = [x, y, z, h]
|
||||
id_set = {GateIdentity(x, x),
|
||||
GateIdentity(y, y),
|
||||
GateIdentity(z, z),
|
||||
GateIdentity(h, h),
|
||||
GateIdentity(x, y, z),
|
||||
GateIdentity(x, y, x, y),
|
||||
GateIdentity(x, z, x, z),
|
||||
GateIdentity(x, h, z, h),
|
||||
GateIdentity(y, z, y, z),
|
||||
GateIdentity(y, h, y, h)}
|
||||
assert bfs_identity_search(gate_list, 1) == id_set
|
||||
|
||||
id_set = {GateIdentity(x, x),
|
||||
GateIdentity(y, y),
|
||||
GateIdentity(z, z),
|
||||
GateIdentity(h, h)}
|
||||
assert id_set == bfs_identity_search(gate_list, 1, max_depth=3,
|
||||
identity_only=True)
|
||||
|
||||
id_set = {GateIdentity(x, x),
|
||||
GateIdentity(y, y),
|
||||
GateIdentity(z, z),
|
||||
GateIdentity(h, h),
|
||||
GateIdentity(x, y, z),
|
||||
GateIdentity(x, y, x, y),
|
||||
GateIdentity(x, z, x, z),
|
||||
GateIdentity(x, h, z, h),
|
||||
GateIdentity(y, z, y, z),
|
||||
GateIdentity(y, h, y, h),
|
||||
GateIdentity(x, y, h, x, h),
|
||||
GateIdentity(x, z, h, y, h),
|
||||
GateIdentity(y, z, h, z, h)}
|
||||
assert bfs_identity_search(gate_list, 1, max_depth=5) == id_set
|
||||
|
||||
id_set = {GateIdentity(x, x),
|
||||
GateIdentity(y, y),
|
||||
GateIdentity(z, z),
|
||||
GateIdentity(h, h),
|
||||
GateIdentity(x, h, z, h)}
|
||||
assert id_set == bfs_identity_search(gate_list, 1, max_depth=4,
|
||||
identity_only=True)
|
||||
|
||||
cnot = CNOT(1, 0)
|
||||
gate_list = [x, cnot]
|
||||
id_set = {GateIdentity(x, x),
|
||||
GateIdentity(cnot, cnot),
|
||||
GateIdentity(x, cnot, x, cnot)}
|
||||
assert bfs_identity_search(gate_list, 2, max_depth=4) == id_set
|
||||
|
||||
cgate_x = CGate((1,), x)
|
||||
gate_list = [x, cgate_x]
|
||||
id_set = {GateIdentity(x, x),
|
||||
GateIdentity(cgate_x, cgate_x),
|
||||
GateIdentity(x, cgate_x, x, cgate_x)}
|
||||
assert bfs_identity_search(gate_list, 2, max_depth=4) == id_set
|
||||
|
||||
cgate_z = CGate((0,), Z(1))
|
||||
gate_list = [cnot, cgate_z, h]
|
||||
id_set = {GateIdentity(h, h),
|
||||
GateIdentity(cgate_z, cgate_z),
|
||||
GateIdentity(cnot, cnot),
|
||||
GateIdentity(cnot, h, cgate_z, h)}
|
||||
assert bfs_identity_search(gate_list, 2, max_depth=4) == id_set
|
||||
|
||||
s = PhaseGate(0)
|
||||
t = TGate(0)
|
||||
gate_list = [s, t]
|
||||
id_set = {GateIdentity(s, s, s, s)}
|
||||
assert bfs_identity_search(gate_list, 1, max_depth=4) == id_set
|
||||
|
||||
|
||||
def test_bfs_identity_search_xfail():
|
||||
s = PhaseGate(0)
|
||||
t = TGate(0)
|
||||
gate_list = [Dagger(s), t]
|
||||
id_set = {GateIdentity(Dagger(s), t, t)}
|
||||
assert bfs_identity_search(gate_list, 1, max_depth=3) == id_set
|
||||
@@ -0,0 +1,71 @@
|
||||
from sympy.core.numbers import (I, Integer)
|
||||
|
||||
from sympy.physics.quantum.innerproduct import InnerProduct
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.state import Bra, Ket, StateBase
|
||||
|
||||
|
||||
def test_innerproduct():
|
||||
k = Ket('k')
|
||||
b = Bra('b')
|
||||
ip = InnerProduct(b, k)
|
||||
assert isinstance(ip, InnerProduct)
|
||||
assert ip.bra == b
|
||||
assert ip.ket == k
|
||||
assert b*k == InnerProduct(b, k)
|
||||
assert k*(b*k)*b == k*InnerProduct(b, k)*b
|
||||
assert InnerProduct(b, k).subs(b, Dagger(k)) == Dagger(k)*k
|
||||
|
||||
|
||||
def test_innerproduct_dagger():
|
||||
k = Ket('k')
|
||||
b = Bra('b')
|
||||
ip = b*k
|
||||
assert Dagger(ip) == Dagger(k)*Dagger(b)
|
||||
|
||||
|
||||
class FooState(StateBase):
|
||||
pass
|
||||
|
||||
|
||||
class FooKet(Ket, FooState):
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return FooBra
|
||||
|
||||
def _eval_innerproduct_FooBra(self, bra):
|
||||
return Integer(1)
|
||||
|
||||
def _eval_innerproduct_BarBra(self, bra):
|
||||
return I
|
||||
|
||||
|
||||
class FooBra(Bra, FooState):
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return FooKet
|
||||
|
||||
|
||||
class BarState(StateBase):
|
||||
pass
|
||||
|
||||
|
||||
class BarKet(Ket, BarState):
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return BarBra
|
||||
|
||||
|
||||
class BarBra(Bra, BarState):
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return BarKet
|
||||
|
||||
|
||||
def test_doit():
|
||||
f = FooKet('foo')
|
||||
b = BarBra('bar')
|
||||
assert InnerProduct(b, f).doit() == I
|
||||
assert InnerProduct(Dagger(f), Dagger(b)).doit() == -I
|
||||
assert InnerProduct(Dagger(f), f).doit() == Integer(1)
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Tests for sympy.physics.quantum.kind."""
|
||||
|
||||
from sympy.core.kind import NumberKind, UndefinedKind
|
||||
from sympy.core.symbol import symbols
|
||||
|
||||
from sympy.physics.quantum.kind import (
|
||||
OperatorKind, KetKind, BraKind
|
||||
)
|
||||
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.operator import Operator
|
||||
from sympy.physics.quantum.state import Ket, Bra
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct
|
||||
|
||||
k = Ket('k')
|
||||
b = Bra('k')
|
||||
A = Operator('A')
|
||||
B = Operator('B')
|
||||
x, y, z = symbols('x y z', integer=True)
|
||||
|
||||
def test_bra_ket():
|
||||
assert k.kind == KetKind
|
||||
assert b.kind == BraKind
|
||||
assert (b*k).kind == NumberKind # inner product
|
||||
assert (x*k).kind == KetKind
|
||||
assert (x*b).kind == BraKind
|
||||
|
||||
|
||||
def test_operator_kind():
|
||||
assert A.kind == OperatorKind
|
||||
assert (A*B).kind == OperatorKind
|
||||
assert (x*A).kind == OperatorKind
|
||||
assert (x*A*B).kind == OperatorKind
|
||||
assert (x*k*b).kind == OperatorKind # outer product
|
||||
|
||||
|
||||
def test_undefind_kind():
|
||||
# Because of limitations in the kind dispatcher API, we are currently
|
||||
# unable to have OperatorKind*KetKind -> KetKind (and similar for bras).
|
||||
assert (A*k).kind == UndefinedKind
|
||||
assert (b*A).kind == UndefinedKind
|
||||
assert (x*b*A*k).kind == UndefinedKind
|
||||
|
||||
|
||||
def test_dagger_kind():
|
||||
assert Dagger(k).kind == BraKind
|
||||
assert Dagger(b).kind == KetKind
|
||||
assert Dagger(A).kind == OperatorKind
|
||||
|
||||
|
||||
def test_commutator_kind():
|
||||
assert Commutator(A, B).kind == OperatorKind
|
||||
assert Commutator(A, x*B).kind == OperatorKind
|
||||
assert Commutator(x*A, B).kind == OperatorKind
|
||||
assert Commutator(x*A, x*B).kind == OperatorKind
|
||||
|
||||
|
||||
def test_anticommutator_kind():
|
||||
assert AntiCommutator(A, B).kind == OperatorKind
|
||||
assert AntiCommutator(A, x*B).kind == OperatorKind
|
||||
assert AntiCommutator(x*A, B).kind == OperatorKind
|
||||
assert AntiCommutator(x*A, x*B).kind == OperatorKind
|
||||
|
||||
|
||||
def test_tensorproduct_kind():
|
||||
assert TensorProduct(k,k).kind == KetKind
|
||||
assert TensorProduct(b,b).kind == BraKind
|
||||
assert TensorProduct(x*k,y*k).kind == KetKind
|
||||
assert TensorProduct(x*b,y*b).kind == BraKind
|
||||
assert TensorProduct(x*b*k, y*b*k).kind == NumberKind
|
||||
assert TensorProduct(x*k*b, y*k*b).kind == OperatorKind
|
||||
assert TensorProduct(A, B).kind == OperatorKind
|
||||
assert TensorProduct(A, x*B).kind == OperatorKind
|
||||
assert TensorProduct(x*A, B).kind == OperatorKind
|
||||
@@ -0,0 +1,136 @@
|
||||
from sympy.core.random import randint
|
||||
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.matrices.dense import (Matrix, ones, zeros)
|
||||
|
||||
from sympy.physics.quantum.matrixutils import (
|
||||
to_sympy, to_numpy, to_scipy_sparse, matrix_tensor_product,
|
||||
matrix_to_zero, matrix_zeros, numpy_ndarray, scipy_sparse_matrix
|
||||
)
|
||||
|
||||
from sympy.external import import_module
|
||||
from sympy.testing.pytest import skip
|
||||
|
||||
m = Matrix([[1, 2], [3, 4]])
|
||||
|
||||
|
||||
def test_sympy_to_sympy():
|
||||
assert to_sympy(m) == m
|
||||
|
||||
|
||||
def test_matrix_to_zero():
|
||||
assert matrix_to_zero(m) == m
|
||||
assert matrix_to_zero(Matrix([[0, 0], [0, 0]])) == Integer(0)
|
||||
|
||||
np = import_module('numpy')
|
||||
|
||||
|
||||
def test_to_numpy():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
|
||||
result = np.array([[1, 2], [3, 4]], dtype='complex')
|
||||
assert (to_numpy(m) == result).all()
|
||||
|
||||
|
||||
def test_matrix_tensor_product():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
|
||||
l1 = zeros(4)
|
||||
for i in range(16):
|
||||
l1[i] = 2**i
|
||||
l2 = zeros(4)
|
||||
for i in range(16):
|
||||
l2[i] = i
|
||||
l3 = zeros(2)
|
||||
for i in range(4):
|
||||
l3[i] = i
|
||||
vec = Matrix([1, 2, 3])
|
||||
|
||||
#test for Matrix known 4x4 matrices
|
||||
numpyl1 = np.array(l1.tolist())
|
||||
numpyl2 = np.array(l2.tolist())
|
||||
numpy_product = np.kron(numpyl1, numpyl2)
|
||||
args = [l1, l2]
|
||||
sympy_product = matrix_tensor_product(*args)
|
||||
assert numpy_product.tolist() == sympy_product.tolist()
|
||||
numpy_product = np.kron(numpyl2, numpyl1)
|
||||
args = [l2, l1]
|
||||
sympy_product = matrix_tensor_product(*args)
|
||||
assert numpy_product.tolist() == sympy_product.tolist()
|
||||
|
||||
#test for other known matrix of different dimensions
|
||||
numpyl2 = np.array(l3.tolist())
|
||||
numpy_product = np.kron(numpyl1, numpyl2)
|
||||
args = [l1, l3]
|
||||
sympy_product = matrix_tensor_product(*args)
|
||||
assert numpy_product.tolist() == sympy_product.tolist()
|
||||
numpy_product = np.kron(numpyl2, numpyl1)
|
||||
args = [l3, l1]
|
||||
sympy_product = matrix_tensor_product(*args)
|
||||
assert numpy_product.tolist() == sympy_product.tolist()
|
||||
|
||||
#test for non square matrix
|
||||
numpyl2 = np.array(vec.tolist())
|
||||
numpy_product = np.kron(numpyl1, numpyl2)
|
||||
args = [l1, vec]
|
||||
sympy_product = matrix_tensor_product(*args)
|
||||
assert numpy_product.tolist() == sympy_product.tolist()
|
||||
numpy_product = np.kron(numpyl2, numpyl1)
|
||||
args = [vec, l1]
|
||||
sympy_product = matrix_tensor_product(*args)
|
||||
assert numpy_product.tolist() == sympy_product.tolist()
|
||||
|
||||
#test for random matrix with random values that are floats
|
||||
random_matrix1 = np.random.rand(randint(1, 5), randint(1, 5))
|
||||
random_matrix2 = np.random.rand(randint(1, 5), randint(1, 5))
|
||||
numpy_product = np.kron(random_matrix1, random_matrix2)
|
||||
args = [Matrix(random_matrix1.tolist()), Matrix(random_matrix2.tolist())]
|
||||
sympy_product = matrix_tensor_product(*args)
|
||||
assert not (sympy_product - Matrix(numpy_product.tolist())).tolist() > \
|
||||
(ones(sympy_product.rows, sympy_product.cols)*epsilon).tolist()
|
||||
|
||||
#test for three matrix kronecker
|
||||
sympy_product = matrix_tensor_product(l1, vec, l2)
|
||||
|
||||
numpy_product = np.kron(l1, np.kron(vec, l2))
|
||||
assert numpy_product.tolist() == sympy_product.tolist()
|
||||
|
||||
|
||||
scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
|
||||
|
||||
|
||||
def test_to_scipy_sparse():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
if not scipy:
|
||||
skip("scipy not installed.")
|
||||
else:
|
||||
sparse = scipy.sparse
|
||||
|
||||
result = sparse.csr_matrix([[1, 2], [3, 4]], dtype='complex')
|
||||
assert np.linalg.norm((to_scipy_sparse(m) - result).todense()) == 0.0
|
||||
|
||||
epsilon = .000001
|
||||
|
||||
|
||||
def test_matrix_zeros_sympy():
|
||||
sym = matrix_zeros(4, 4, format='sympy')
|
||||
assert isinstance(sym, Matrix)
|
||||
|
||||
def test_matrix_zeros_numpy():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
|
||||
num = matrix_zeros(4, 4, format='numpy')
|
||||
assert isinstance(num, numpy_ndarray)
|
||||
|
||||
def test_matrix_zeros_scipy():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
if not scipy:
|
||||
skip("scipy not installed.")
|
||||
|
||||
sci = matrix_zeros(4, 4, format='scipy.sparse')
|
||||
assert isinstance(sci, scipy_sparse_matrix)
|
||||
@@ -0,0 +1,269 @@
|
||||
from sympy.core.function import (Derivative, Function, diff)
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (Integer, pi)
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.elementary.trigonometric import sin
|
||||
from sympy.physics.quantum.qexpr import QExpr
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.hilbert import HilbertSpace
|
||||
from sympy.physics.quantum.operator import (Operator, UnitaryOperator,
|
||||
HermitianOperator, OuterProduct,
|
||||
DifferentialOperator,
|
||||
IdentityOperator)
|
||||
from sympy.physics.quantum.state import Ket, Bra, Wavefunction
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.physics.quantum.spin import JzKet, JzBra
|
||||
from sympy.physics.quantum.trace import Tr
|
||||
from sympy.matrices import eye
|
||||
|
||||
from sympy.testing.pytest import warns_deprecated_sympy
|
||||
|
||||
|
||||
class CustomKet(Ket):
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("t",)
|
||||
|
||||
|
||||
class CustomOp(HermitianOperator):
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("T",)
|
||||
|
||||
t_ket = CustomKet()
|
||||
t_op = CustomOp()
|
||||
|
||||
|
||||
def test_operator():
|
||||
A = Operator('A')
|
||||
B = Operator('B')
|
||||
C = Operator('C')
|
||||
|
||||
assert isinstance(A, Operator)
|
||||
assert isinstance(A, QExpr)
|
||||
|
||||
assert A.label == (Symbol('A'),)
|
||||
assert A.is_commutative is False
|
||||
assert A.hilbert_space == HilbertSpace()
|
||||
|
||||
assert A*B != B*A
|
||||
|
||||
assert (A*(B + C)).expand() == A*B + A*C
|
||||
assert ((A + B)**2).expand() == A**2 + A*B + B*A + B**2
|
||||
|
||||
assert t_op.label[0] == Symbol(t_op.default_args()[0])
|
||||
|
||||
assert Operator() == Operator("O")
|
||||
with warns_deprecated_sympy():
|
||||
assert A*IdentityOperator() == A
|
||||
|
||||
|
||||
def test_operator_inv():
|
||||
A = Operator('A')
|
||||
assert A*A.inv() == 1
|
||||
assert A.inv()*A == 1
|
||||
|
||||
|
||||
def test_hermitian():
|
||||
H = HermitianOperator('H')
|
||||
|
||||
assert isinstance(H, HermitianOperator)
|
||||
assert isinstance(H, Operator)
|
||||
|
||||
assert Dagger(H) == H
|
||||
assert H.inv() != H
|
||||
assert H.is_commutative is False
|
||||
assert Dagger(H).is_commutative is False
|
||||
|
||||
|
||||
def test_unitary():
|
||||
U = UnitaryOperator('U')
|
||||
|
||||
assert isinstance(U, UnitaryOperator)
|
||||
assert isinstance(U, Operator)
|
||||
|
||||
assert U.inv() == Dagger(U)
|
||||
assert U*Dagger(U) == 1
|
||||
assert Dagger(U)*U == 1
|
||||
assert U.is_commutative is False
|
||||
assert Dagger(U).is_commutative is False
|
||||
|
||||
|
||||
def test_identity():
|
||||
with warns_deprecated_sympy():
|
||||
I = IdentityOperator()
|
||||
O = Operator('O')
|
||||
x = Symbol("x")
|
||||
three = sympify(3)
|
||||
|
||||
assert isinstance(I, IdentityOperator)
|
||||
assert isinstance(I, Operator)
|
||||
|
||||
assert I * O == O
|
||||
assert O * I == O
|
||||
assert I * Dagger(O) == Dagger(O)
|
||||
assert Dagger(O) * I == Dagger(O)
|
||||
assert isinstance(I * I, IdentityOperator)
|
||||
assert three * I == three
|
||||
assert I * x == x
|
||||
assert I.inv() == I
|
||||
assert Dagger(I) == I
|
||||
assert qapply(I * O) == O
|
||||
assert qapply(O * I) == O
|
||||
|
||||
for n in [2, 3, 5]:
|
||||
assert represent(IdentityOperator(n)) == eye(n)
|
||||
|
||||
|
||||
def test_outer_product():
|
||||
k = Ket('k')
|
||||
b = Bra('b')
|
||||
op = OuterProduct(k, b)
|
||||
|
||||
assert isinstance(op, OuterProduct)
|
||||
assert isinstance(op, Operator)
|
||||
|
||||
assert op.ket == k
|
||||
assert op.bra == b
|
||||
assert op.label == (k, b)
|
||||
assert op.is_commutative is False
|
||||
|
||||
op = k*b
|
||||
|
||||
assert isinstance(op, OuterProduct)
|
||||
assert isinstance(op, Operator)
|
||||
|
||||
assert op.ket == k
|
||||
assert op.bra == b
|
||||
assert op.label == (k, b)
|
||||
assert op.is_commutative is False
|
||||
|
||||
op = 2*k*b
|
||||
|
||||
assert op == Mul(Integer(2), k, b)
|
||||
|
||||
op = 2*(k*b)
|
||||
|
||||
assert op == Mul(Integer(2), OuterProduct(k, b))
|
||||
|
||||
assert Dagger(k*b) == OuterProduct(Dagger(b), Dagger(k))
|
||||
assert Dagger(k*b).is_commutative is False
|
||||
|
||||
#test the _eval_trace
|
||||
assert Tr(OuterProduct(JzKet(1, 1), JzBra(1, 1))).doit() == 1
|
||||
|
||||
# test scaled kets and bras
|
||||
assert OuterProduct(2 * k, b) == 2 * OuterProduct(k, b)
|
||||
assert OuterProduct(k, 2 * b) == 2 * OuterProduct(k, b)
|
||||
|
||||
# test sums of kets and bras
|
||||
k1, k2 = Ket('k1'), Ket('k2')
|
||||
b1, b2 = Bra('b1'), Bra('b2')
|
||||
assert (OuterProduct(k1 + k2, b1) ==
|
||||
OuterProduct(k1, b1) + OuterProduct(k2, b1))
|
||||
assert (OuterProduct(k1, b1 + b2) ==
|
||||
OuterProduct(k1, b1) + OuterProduct(k1, b2))
|
||||
assert (OuterProduct(1 * k1 + 2 * k2, 3 * b1 + 4 * b2) ==
|
||||
3 * OuterProduct(k1, b1) +
|
||||
4 * OuterProduct(k1, b2) +
|
||||
6 * OuterProduct(k2, b1) +
|
||||
8 * OuterProduct(k2, b2))
|
||||
|
||||
|
||||
def test_operator_dagger():
|
||||
A = Operator('A')
|
||||
B = Operator('B')
|
||||
assert Dagger(A*B) == Dagger(B)*Dagger(A)
|
||||
assert Dagger(A + B) == Dagger(A) + Dagger(B)
|
||||
assert Dagger(A**2) == Dagger(A)**2
|
||||
|
||||
|
||||
def test_differential_operator():
|
||||
x = Symbol('x')
|
||||
f = Function('f')
|
||||
d = DifferentialOperator(Derivative(f(x), x), f(x))
|
||||
g = Wavefunction(x**2, x)
|
||||
assert qapply(d*g) == Wavefunction(2*x, x)
|
||||
assert d.expr == Derivative(f(x), x)
|
||||
assert d.function == f(x)
|
||||
assert d.variables == (x,)
|
||||
assert diff(d, x) == DifferentialOperator(Derivative(f(x), x, 2), f(x))
|
||||
|
||||
d = DifferentialOperator(Derivative(f(x), x, 2), f(x))
|
||||
g = Wavefunction(x**3, x)
|
||||
assert qapply(d*g) == Wavefunction(6*x, x)
|
||||
assert d.expr == Derivative(f(x), x, 2)
|
||||
assert d.function == f(x)
|
||||
assert d.variables == (x,)
|
||||
assert diff(d, x) == DifferentialOperator(Derivative(f(x), x, 3), f(x))
|
||||
|
||||
d = DifferentialOperator(1/x*Derivative(f(x), x), f(x))
|
||||
assert d.expr == 1/x*Derivative(f(x), x)
|
||||
assert d.function == f(x)
|
||||
assert d.variables == (x,)
|
||||
assert diff(d, x) == \
|
||||
DifferentialOperator(Derivative(1/x*Derivative(f(x), x), x), f(x))
|
||||
assert qapply(d*g) == Wavefunction(3*x, x)
|
||||
|
||||
# 2D cartesian Laplacian
|
||||
y = Symbol('y')
|
||||
d = DifferentialOperator(Derivative(f(x, y), x, 2) +
|
||||
Derivative(f(x, y), y, 2), f(x, y))
|
||||
w = Wavefunction(x**3*y**2 + y**3*x**2, x, y)
|
||||
assert d.expr == Derivative(f(x, y), x, 2) + Derivative(f(x, y), y, 2)
|
||||
assert d.function == f(x, y)
|
||||
assert d.variables == (x, y)
|
||||
assert diff(d, x) == \
|
||||
DifferentialOperator(Derivative(d.expr, x), f(x, y))
|
||||
assert diff(d, y) == \
|
||||
DifferentialOperator(Derivative(d.expr, y), f(x, y))
|
||||
assert qapply(d*w) == Wavefunction(2*x**3 + 6*x*y**2 + 6*x**2*y + 2*y**3,
|
||||
x, y)
|
||||
|
||||
# 2D polar Laplacian (th = theta)
|
||||
r, th = symbols('r th')
|
||||
d = DifferentialOperator(1/r*Derivative(r*Derivative(f(r, th), r), r) +
|
||||
1/(r**2)*Derivative(f(r, th), th, 2), f(r, th))
|
||||
w = Wavefunction(r**2*sin(th), r, (th, 0, pi))
|
||||
assert d.expr == \
|
||||
1/r*Derivative(r*Derivative(f(r, th), r), r) + \
|
||||
1/(r**2)*Derivative(f(r, th), th, 2)
|
||||
assert d.function == f(r, th)
|
||||
assert d.variables == (r, th)
|
||||
assert diff(d, r) == \
|
||||
DifferentialOperator(Derivative(d.expr, r), f(r, th))
|
||||
assert diff(d, th) == \
|
||||
DifferentialOperator(Derivative(d.expr, th), f(r, th))
|
||||
assert qapply(d*w) == Wavefunction(3*sin(th), r, (th, 0, pi))
|
||||
|
||||
|
||||
def test_eval_power():
|
||||
from sympy.core import Pow
|
||||
from sympy.core.expr import unchanged
|
||||
O = Operator('O')
|
||||
U = UnitaryOperator('U')
|
||||
H = HermitianOperator('H')
|
||||
assert O**-1 == O.inv() # same as doc test
|
||||
assert U**-1 == U.inv()
|
||||
assert H**-1 == H.inv()
|
||||
x = symbols("x", commutative = True)
|
||||
assert unchanged(Pow, H, x) # verify Pow(H,x)=="X^n"
|
||||
assert H**x == Pow(H, x)
|
||||
assert Pow(H,x) == Pow(H, x, evaluate=False) # Just check
|
||||
from sympy.physics.quantum.gate import XGate
|
||||
X = XGate(0) # is hermitian and unitary
|
||||
assert unchanged(Pow, X, x) # verify Pow(X,x)=="X^x"
|
||||
assert X**x == Pow(X, x)
|
||||
assert Pow(X, x, evaluate=False) == Pow(X, x) # Just check
|
||||
n = symbols("n", integer=True, even=True)
|
||||
assert X**n == 1
|
||||
n = symbols("n", integer=True, odd=True)
|
||||
assert X**n == X
|
||||
n = symbols("n", integer=True)
|
||||
assert unchanged(Pow, X, n) # verify Pow(X,n)=="X^n"
|
||||
assert X**n == Pow(X, n)
|
||||
assert Pow(X, n, evaluate=False)==Pow(X, n) # Just check
|
||||
assert X**4 == 1
|
||||
assert X**7 == X
|
||||
@@ -0,0 +1,50 @@
|
||||
from sympy.physics.quantum import Dagger
|
||||
from sympy.physics.quantum.boson import BosonOp
|
||||
from sympy.physics.quantum.fermion import FermionOp
|
||||
from sympy.physics.quantum.operatorordering import (normal_order,
|
||||
normal_ordered_form)
|
||||
|
||||
|
||||
def test_normal_order():
|
||||
a = BosonOp('a')
|
||||
|
||||
c = FermionOp('c')
|
||||
|
||||
assert normal_order(a * Dagger(a)) == Dagger(a) * a
|
||||
assert normal_order(Dagger(a) * a) == Dagger(a) * a
|
||||
assert normal_order(a * Dagger(a) ** 2) == Dagger(a) ** 2 * a
|
||||
|
||||
assert normal_order(c * Dagger(c)) == - Dagger(c) * c
|
||||
assert normal_order(Dagger(c) * c) == Dagger(c) * c
|
||||
assert normal_order(c * Dagger(c) ** 2) == Dagger(c) ** 2 * c
|
||||
|
||||
|
||||
def test_normal_ordered_form():
|
||||
a = BosonOp('a')
|
||||
b = BosonOp('b')
|
||||
|
||||
c = FermionOp('c')
|
||||
d = FermionOp('d')
|
||||
|
||||
assert normal_ordered_form(Dagger(a) * a) == Dagger(a) * a
|
||||
assert normal_ordered_form(a * Dagger(a)) == 1 + Dagger(a) * a
|
||||
assert normal_ordered_form(a ** 2 * Dagger(a)) == \
|
||||
2 * a + Dagger(a) * a ** 2
|
||||
assert normal_ordered_form(a ** 3 * Dagger(a)) == \
|
||||
3 * a ** 2 + Dagger(a) * a ** 3
|
||||
|
||||
assert normal_ordered_form(Dagger(c) * c) == Dagger(c) * c
|
||||
assert normal_ordered_form(c * Dagger(c)) == 1 - Dagger(c) * c
|
||||
assert normal_ordered_form(c ** 2 * Dagger(c)) == Dagger(c) * c ** 2
|
||||
assert normal_ordered_form(c ** 3 * Dagger(c)) == \
|
||||
c ** 2 - Dagger(c) * c ** 3
|
||||
|
||||
assert normal_ordered_form(a * Dagger(b), True) == Dagger(b) * a
|
||||
assert normal_ordered_form(Dagger(a) * b, True) == Dagger(a) * b
|
||||
assert normal_ordered_form(b * a, True) == a * b
|
||||
assert normal_ordered_form(Dagger(b) * Dagger(a), True) == Dagger(a) * Dagger(b)
|
||||
|
||||
assert normal_ordered_form(c * Dagger(d), True) == -Dagger(d) * c
|
||||
assert normal_ordered_form(Dagger(c) * d, True) == Dagger(c) * d
|
||||
assert normal_ordered_form(d * c, True) == -c * d
|
||||
assert normal_ordered_form(Dagger(d) * Dagger(c), True) == -Dagger(c) * Dagger(d)
|
||||
@@ -0,0 +1,68 @@
|
||||
from sympy.core.singleton import S
|
||||
|
||||
from sympy.physics.quantum.operatorset import (
|
||||
operators_to_state, state_to_operators
|
||||
)
|
||||
|
||||
from sympy.physics.quantum.cartesian import (
|
||||
XOp, XKet, PxOp, PxKet, XBra, PxBra
|
||||
)
|
||||
|
||||
from sympy.physics.quantum.state import Ket, Bra
|
||||
from sympy.physics.quantum.operator import Operator
|
||||
from sympy.physics.quantum.spin import (
|
||||
JxKet, JyKet, JzKet, JxBra, JyBra, JzBra,
|
||||
JxOp, JyOp, JzOp, J2Op
|
||||
)
|
||||
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_spin():
|
||||
assert operators_to_state({J2Op, JxOp}) == JxKet
|
||||
assert operators_to_state({J2Op, JyOp}) == JyKet
|
||||
assert operators_to_state({J2Op, JzOp}) == JzKet
|
||||
assert operators_to_state({J2Op(), JxOp()}) == JxKet
|
||||
assert operators_to_state({J2Op(), JyOp()}) == JyKet
|
||||
assert operators_to_state({J2Op(), JzOp()}) == JzKet
|
||||
|
||||
assert state_to_operators(JxKet) == {J2Op, JxOp}
|
||||
assert state_to_operators(JyKet) == {J2Op, JyOp}
|
||||
assert state_to_operators(JzKet) == {J2Op, JzOp}
|
||||
assert state_to_operators(JxBra) == {J2Op, JxOp}
|
||||
assert state_to_operators(JyBra) == {J2Op, JyOp}
|
||||
assert state_to_operators(JzBra) == {J2Op, JzOp}
|
||||
|
||||
assert state_to_operators(JxKet(S.Half, S.Half)) == {J2Op(), JxOp()}
|
||||
assert state_to_operators(JyKet(S.Half, S.Half)) == {J2Op(), JyOp()}
|
||||
assert state_to_operators(JzKet(S.Half, S.Half)) == {J2Op(), JzOp()}
|
||||
assert state_to_operators(JxBra(S.Half, S.Half)) == {J2Op(), JxOp()}
|
||||
assert state_to_operators(JyBra(S.Half, S.Half)) == {J2Op(), JyOp()}
|
||||
assert state_to_operators(JzBra(S.Half, S.Half)) == {J2Op(), JzOp()}
|
||||
|
||||
|
||||
def test_op_to_state():
|
||||
assert operators_to_state(XOp) == XKet()
|
||||
assert operators_to_state(PxOp) == PxKet()
|
||||
assert operators_to_state(Operator) == Ket()
|
||||
|
||||
assert state_to_operators(operators_to_state(XOp("Q"))) == XOp("Q")
|
||||
assert state_to_operators(operators_to_state(XOp())) == XOp()
|
||||
|
||||
raises(NotImplementedError, lambda: operators_to_state(XKet))
|
||||
|
||||
|
||||
def test_state_to_op():
|
||||
assert state_to_operators(XKet) == XOp()
|
||||
assert state_to_operators(PxKet) == PxOp()
|
||||
assert state_to_operators(XBra) == XOp()
|
||||
assert state_to_operators(PxBra) == PxOp()
|
||||
assert state_to_operators(Ket) == Operator()
|
||||
assert state_to_operators(Bra) == Operator()
|
||||
|
||||
assert operators_to_state(state_to_operators(XKet("test"))) == XKet("test")
|
||||
assert operators_to_state(state_to_operators(XBra("test"))) == XKet("test")
|
||||
assert operators_to_state(state_to_operators(XKet())) == XKet()
|
||||
assert operators_to_state(state_to_operators(XBra())) == XKet()
|
||||
|
||||
raises(NotImplementedError, lambda: state_to_operators(XOp))
|
||||
@@ -0,0 +1,159 @@
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import I
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.printing.latex import latex
|
||||
from sympy.physics.quantum import (Dagger, Commutator, AntiCommutator, qapply,
|
||||
Operator, represent)
|
||||
from sympy.physics.quantum.pauli import (SigmaOpBase, SigmaX, SigmaY, SigmaZ,
|
||||
SigmaMinus, SigmaPlus,
|
||||
qsimplify_pauli)
|
||||
from sympy.physics.quantum.pauli import SigmaZKet, SigmaZBra
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
sx, sy, sz = SigmaX(), SigmaY(), SigmaZ()
|
||||
sx1, sy1, sz1 = SigmaX(1), SigmaY(1), SigmaZ(1)
|
||||
sx2, sy2, sz2 = SigmaX(2), SigmaY(2), SigmaZ(2)
|
||||
|
||||
sm, sp = SigmaMinus(), SigmaPlus()
|
||||
sm1, sp1 = SigmaMinus(1), SigmaPlus(1)
|
||||
A, B = Operator("A"), Operator("B")
|
||||
|
||||
|
||||
def test_pauli_operators_types():
|
||||
|
||||
assert isinstance(sx, SigmaOpBase) and isinstance(sx, SigmaX)
|
||||
assert isinstance(sy, SigmaOpBase) and isinstance(sy, SigmaY)
|
||||
assert isinstance(sz, SigmaOpBase) and isinstance(sz, SigmaZ)
|
||||
assert isinstance(sm, SigmaOpBase) and isinstance(sm, SigmaMinus)
|
||||
assert isinstance(sp, SigmaOpBase) and isinstance(sp, SigmaPlus)
|
||||
|
||||
|
||||
def test_pauli_operators_commutator():
|
||||
|
||||
assert Commutator(sx, sy).doit() == 2 * I * sz
|
||||
assert Commutator(sy, sz).doit() == 2 * I * sx
|
||||
assert Commutator(sz, sx).doit() == 2 * I * sy
|
||||
|
||||
|
||||
def test_pauli_operators_commutator_with_labels():
|
||||
|
||||
assert Commutator(sx1, sy1).doit() == 2 * I * sz1
|
||||
assert Commutator(sy1, sz1).doit() == 2 * I * sx1
|
||||
assert Commutator(sz1, sx1).doit() == 2 * I * sy1
|
||||
|
||||
assert Commutator(sx2, sy2).doit() == 2 * I * sz2
|
||||
assert Commutator(sy2, sz2).doit() == 2 * I * sx2
|
||||
assert Commutator(sz2, sx2).doit() == 2 * I * sy2
|
||||
|
||||
assert Commutator(sx1, sy2).doit() == 0
|
||||
assert Commutator(sy1, sz2).doit() == 0
|
||||
assert Commutator(sz1, sx2).doit() == 0
|
||||
|
||||
|
||||
def test_pauli_operators_anticommutator():
|
||||
|
||||
assert AntiCommutator(sy, sz).doit() == 0
|
||||
assert AntiCommutator(sz, sx).doit() == 0
|
||||
assert AntiCommutator(sx, sm).doit() == 1
|
||||
assert AntiCommutator(sx, sp).doit() == 1
|
||||
|
||||
|
||||
def test_pauli_operators_adjoint():
|
||||
|
||||
assert Dagger(sx) == sx
|
||||
assert Dagger(sy) == sy
|
||||
assert Dagger(sz) == sz
|
||||
|
||||
|
||||
def test_pauli_operators_adjoint_with_labels():
|
||||
|
||||
assert Dagger(sx1) == sx1
|
||||
assert Dagger(sy1) == sy1
|
||||
assert Dagger(sz1) == sz1
|
||||
|
||||
assert Dagger(sx1) != sx2
|
||||
assert Dagger(sy1) != sy2
|
||||
assert Dagger(sz1) != sz2
|
||||
|
||||
|
||||
def test_pauli_operators_multiplication():
|
||||
|
||||
assert qsimplify_pauli(sx * sx) == 1
|
||||
assert qsimplify_pauli(sy * sy) == 1
|
||||
assert qsimplify_pauli(sz * sz) == 1
|
||||
|
||||
assert qsimplify_pauli(sx * sy) == I * sz
|
||||
assert qsimplify_pauli(sy * sz) == I * sx
|
||||
assert qsimplify_pauli(sz * sx) == I * sy
|
||||
|
||||
assert qsimplify_pauli(sy * sx) == - I * sz
|
||||
assert qsimplify_pauli(sz * sy) == - I * sx
|
||||
assert qsimplify_pauli(sx * sz) == - I * sy
|
||||
|
||||
|
||||
def test_pauli_operators_multiplication_with_labels():
|
||||
|
||||
assert qsimplify_pauli(sx1 * sx1) == 1
|
||||
assert qsimplify_pauli(sy1 * sy1) == 1
|
||||
assert qsimplify_pauli(sz1 * sz1) == 1
|
||||
|
||||
assert isinstance(sx1 * sx2, Mul)
|
||||
assert isinstance(sy1 * sy2, Mul)
|
||||
assert isinstance(sz1 * sz2, Mul)
|
||||
|
||||
assert qsimplify_pauli(sx1 * sy1 * sx2 * sy2) == - sz1 * sz2
|
||||
assert qsimplify_pauli(sy1 * sz1 * sz2 * sx2) == - sx1 * sy2
|
||||
|
||||
|
||||
def test_pauli_states():
|
||||
sx, sz = SigmaX(), SigmaZ()
|
||||
|
||||
up = SigmaZKet(0)
|
||||
down = SigmaZKet(1)
|
||||
|
||||
assert qapply(sx * up) == down
|
||||
assert qapply(sx * down) == up
|
||||
assert qapply(sz * up) == up
|
||||
assert qapply(sz * down) == - down
|
||||
|
||||
up = SigmaZBra(0)
|
||||
down = SigmaZBra(1)
|
||||
|
||||
assert qapply(up * sx, dagger=True) == down
|
||||
assert qapply(down * sx, dagger=True) == up
|
||||
assert qapply(up * sz, dagger=True) == up
|
||||
assert qapply(down * sz, dagger=True) == - down
|
||||
|
||||
assert Dagger(SigmaZKet(0)) == SigmaZBra(0)
|
||||
assert Dagger(SigmaZBra(1)) == SigmaZKet(1)
|
||||
raises(ValueError, lambda: SigmaZBra(2))
|
||||
raises(ValueError, lambda: SigmaZKet(2))
|
||||
|
||||
|
||||
def test_use_name():
|
||||
assert sm.use_name is False
|
||||
assert sm1.use_name is True
|
||||
assert sx.use_name is False
|
||||
assert sx1.use_name is True
|
||||
|
||||
|
||||
def test_printing():
|
||||
assert latex(sx) == r'{\sigma_x}'
|
||||
assert latex(sx1) == r'{\sigma_x^{(1)}}'
|
||||
assert latex(sy) == r'{\sigma_y}'
|
||||
assert latex(sy1) == r'{\sigma_y^{(1)}}'
|
||||
assert latex(sz) == r'{\sigma_z}'
|
||||
assert latex(sz1) == r'{\sigma_z^{(1)}}'
|
||||
assert latex(sm) == r'{\sigma_-}'
|
||||
assert latex(sm1) == r'{\sigma_-^{(1)}}'
|
||||
assert latex(sp) == r'{\sigma_+}'
|
||||
assert latex(sp1) == r'{\sigma_+^{(1)}}'
|
||||
|
||||
|
||||
def test_represent():
|
||||
assert represent(sx) == Matrix([[0, 1], [1, 0]])
|
||||
assert represent(sy) == Matrix([[0, -I], [I, 0]])
|
||||
assert represent(sz) == Matrix([[1, 0], [0, -1]])
|
||||
assert represent(sm) == Matrix([[0, 0], [1, 0]])
|
||||
assert represent(sp) == Matrix([[0, 1], [0, 0]])
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Tests for piab.py"""
|
||||
|
||||
from sympy.core.numbers import pi
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import sin
|
||||
from sympy.sets.sets import Interval
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.physics.quantum import L2, qapply, hbar, represent
|
||||
from sympy.physics.quantum.piab import PIABHamiltonian, PIABKet, PIABBra, m, L
|
||||
|
||||
i, j, n, x = symbols('i j n x')
|
||||
|
||||
|
||||
def test_H():
|
||||
assert PIABHamiltonian('H').hilbert_space == \
|
||||
L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
assert qapply(PIABHamiltonian('H')*PIABKet(n)) == \
|
||||
(n**2*pi**2*hbar**2)/(2*m*L**2)*PIABKet(n)
|
||||
|
||||
|
||||
def test_states():
|
||||
assert PIABKet(n).dual_class() == PIABBra
|
||||
assert PIABKet(n).hilbert_space == \
|
||||
L2(Interval(S.NegativeInfinity, S.Infinity))
|
||||
assert represent(PIABKet(n)) == sqrt(2/L)*sin(n*pi*x/L)
|
||||
assert (PIABBra(i)*PIABKet(j)).doit() == KroneckerDelta(i, j)
|
||||
assert PIABBra(n).dual_class() == PIABKet
|
||||
@@ -0,0 +1,900 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
TODO:
|
||||
* Address Issue 2251, printing of spin states
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from sympy.physics.quantum.anticommutator import AntiCommutator
|
||||
from sympy.physics.quantum.cg import CG, Wigner3j, Wigner6j, Wigner9j
|
||||
from sympy.physics.quantum.commutator import Commutator
|
||||
from sympy.physics.quantum.constants import hbar
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.gate import CGate, CNotGate, IdentityGate, UGate, XGate
|
||||
from sympy.physics.quantum.hilbert import ComplexSpace, FockSpace, HilbertSpace, L2
|
||||
from sympy.physics.quantum.innerproduct import InnerProduct
|
||||
from sympy.physics.quantum.operator import Operator, OuterProduct, DifferentialOperator
|
||||
from sympy.physics.quantum.qexpr import QExpr
|
||||
from sympy.physics.quantum.qubit import Qubit, IntQubit
|
||||
from sympy.physics.quantum.spin import Jz, J2, JzBra, JzBraCoupled, JzKet, JzKetCoupled, Rotation, WignerD
|
||||
from sympy.physics.quantum.state import Bra, Ket, TimeDepBra, TimeDepKet
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct
|
||||
from sympy.physics.quantum.sho1d import RaisingOp
|
||||
|
||||
from sympy.core.function import (Derivative, Function)
|
||||
from sympy.core.numbers import oo
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.sets.sets import Interval
|
||||
from sympy.testing.pytest import XFAIL
|
||||
|
||||
# Imports used in srepr strings
|
||||
from sympy.physics.quantum.spin import JzOp
|
||||
|
||||
from sympy.printing import srepr
|
||||
from sympy.printing.pretty import pretty as xpretty
|
||||
from sympy.printing.latex import latex
|
||||
|
||||
MutableDenseMatrix = Matrix
|
||||
|
||||
|
||||
ENV: dict[str, Any] = {}
|
||||
exec('from sympy import *', ENV)
|
||||
exec('from sympy.physics.quantum import *', ENV)
|
||||
exec('from sympy.physics.quantum.cg import *', ENV)
|
||||
exec('from sympy.physics.quantum.spin import *', ENV)
|
||||
exec('from sympy.physics.quantum.hilbert import *', ENV)
|
||||
exec('from sympy.physics.quantum.qubit import *', ENV)
|
||||
exec('from sympy.physics.quantum.qexpr import *', ENV)
|
||||
exec('from sympy.physics.quantum.gate import *', ENV)
|
||||
exec('from sympy.physics.quantum.constants import *', ENV)
|
||||
|
||||
|
||||
def sT(expr, string):
|
||||
"""
|
||||
sT := sreprTest
|
||||
from sympy/printing/tests/test_repr.py
|
||||
"""
|
||||
assert srepr(expr) == string
|
||||
assert eval(string, ENV) == expr
|
||||
|
||||
|
||||
def pretty(expr):
|
||||
"""ASCII pretty-printing"""
|
||||
return xpretty(expr, use_unicode=False, wrap_line=False)
|
||||
|
||||
|
||||
def upretty(expr):
|
||||
"""Unicode pretty-printing"""
|
||||
return xpretty(expr, use_unicode=True, wrap_line=False)
|
||||
|
||||
|
||||
def test_anticommutator():
|
||||
A = Operator('A')
|
||||
B = Operator('B')
|
||||
ac = AntiCommutator(A, B)
|
||||
ac_tall = AntiCommutator(A**2, B)
|
||||
assert str(ac) == '{A,B}'
|
||||
assert pretty(ac) == '{A,B}'
|
||||
assert upretty(ac) == '{A,B}'
|
||||
assert latex(ac) == r'\left\{A,B\right\}'
|
||||
sT(ac, "AntiCommutator(Operator(Symbol('A')),Operator(Symbol('B')))")
|
||||
assert str(ac_tall) == '{A**2,B}'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/ 2 \\\n\
|
||||
<A ,B>\n\
|
||||
\\ /\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎧ 2 ⎫\n\
|
||||
⎨A ,B⎬\n\
|
||||
⎩ ⎭\
|
||||
"""
|
||||
assert pretty(ac_tall) == ascii_str
|
||||
assert upretty(ac_tall) == ucode_str
|
||||
assert latex(ac_tall) == r'\left\{A^{2},B\right\}'
|
||||
sT(ac_tall, "AntiCommutator(Pow(Operator(Symbol('A')), Integer(2)),Operator(Symbol('B')))")
|
||||
|
||||
|
||||
def test_cg():
|
||||
cg = CG(1, 2, 3, 4, 5, 6)
|
||||
wigner3j = Wigner3j(1, 2, 3, 4, 5, 6)
|
||||
wigner6j = Wigner6j(1, 2, 3, 4, 5, 6)
|
||||
wigner9j = Wigner9j(1, 2, 3, 4, 5, 6, 7, 8, 9)
|
||||
assert str(cg) == 'CG(1, 2, 3, 4, 5, 6)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
5,6 \n\
|
||||
C \n\
|
||||
1,2,3,4\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
5,6 \n\
|
||||
C \n\
|
||||
1,2,3,4\
|
||||
"""
|
||||
assert pretty(cg) == ascii_str
|
||||
assert upretty(cg) == ucode_str
|
||||
assert latex(cg) == 'C^{5,6}_{1,2,3,4}'
|
||||
assert latex(cg ** 2) == R'\left(C^{5,6}_{1,2,3,4}\right)^{2}'
|
||||
sT(cg, "CG(Integer(1), Integer(2), Integer(3), Integer(4), Integer(5), Integer(6))")
|
||||
assert str(wigner3j) == 'Wigner3j(1, 2, 3, 4, 5, 6)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/1 3 5\\\n\
|
||||
| |\n\
|
||||
\\2 4 6/\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎛1 3 5⎞\n\
|
||||
⎜ ⎟\n\
|
||||
⎝2 4 6⎠\
|
||||
"""
|
||||
assert pretty(wigner3j) == ascii_str
|
||||
assert upretty(wigner3j) == ucode_str
|
||||
assert latex(wigner3j) == \
|
||||
r'\left(\begin{array}{ccc} 1 & 3 & 5 \\ 2 & 4 & 6 \end{array}\right)'
|
||||
sT(wigner3j, "Wigner3j(Integer(1), Integer(2), Integer(3), Integer(4), Integer(5), Integer(6))")
|
||||
assert str(wigner6j) == 'Wigner6j(1, 2, 3, 4, 5, 6)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/1 2 3\\\n\
|
||||
< >\n\
|
||||
\\4 5 6/\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎧1 2 3⎫\n\
|
||||
⎨ ⎬\n\
|
||||
⎩4 5 6⎭\
|
||||
"""
|
||||
assert pretty(wigner6j) == ascii_str
|
||||
assert upretty(wigner6j) == ucode_str
|
||||
assert latex(wigner6j) == \
|
||||
r'\left\{\begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \end{array}\right\}'
|
||||
sT(wigner6j, "Wigner6j(Integer(1), Integer(2), Integer(3), Integer(4), Integer(5), Integer(6))")
|
||||
assert str(wigner9j) == 'Wigner9j(1, 2, 3, 4, 5, 6, 7, 8, 9)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/1 2 3\\\n\
|
||||
| |\n\
|
||||
<4 5 6>\n\
|
||||
| |\n\
|
||||
\\7 8 9/\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎧1 2 3⎫\n\
|
||||
⎪ ⎪\n\
|
||||
⎨4 5 6⎬\n\
|
||||
⎪ ⎪\n\
|
||||
⎩7 8 9⎭\
|
||||
"""
|
||||
assert pretty(wigner9j) == ascii_str
|
||||
assert upretty(wigner9j) == ucode_str
|
||||
assert latex(wigner9j) == \
|
||||
r'\left\{\begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{array}\right\}'
|
||||
sT(wigner9j, "Wigner9j(Integer(1), Integer(2), Integer(3), Integer(4), Integer(5), Integer(6), Integer(7), Integer(8), Integer(9))")
|
||||
|
||||
|
||||
def test_commutator():
|
||||
A = Operator('A')
|
||||
B = Operator('B')
|
||||
c = Commutator(A, B)
|
||||
c_tall = Commutator(A**2, B)
|
||||
assert str(c) == '[A,B]'
|
||||
assert pretty(c) == '[A,B]'
|
||||
assert upretty(c) == '[A,B]'
|
||||
assert latex(c) == r'\left[A,B\right]'
|
||||
sT(c, "Commutator(Operator(Symbol('A')),Operator(Symbol('B')))")
|
||||
assert str(c_tall) == '[A**2,B]'
|
||||
ascii_str = \
|
||||
"""\
|
||||
[ 2 ]\n\
|
||||
[A ,B]\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎡ 2 ⎤\n\
|
||||
⎣A ,B⎦\
|
||||
"""
|
||||
assert pretty(c_tall) == ascii_str
|
||||
assert upretty(c_tall) == ucode_str
|
||||
assert latex(c_tall) == r'\left[A^{2},B\right]'
|
||||
sT(c_tall, "Commutator(Pow(Operator(Symbol('A')), Integer(2)),Operator(Symbol('B')))")
|
||||
|
||||
|
||||
def test_constants():
|
||||
assert str(hbar) == 'hbar'
|
||||
assert pretty(hbar) == 'hbar'
|
||||
assert upretty(hbar) == 'ℏ'
|
||||
assert latex(hbar) == r'\hbar'
|
||||
sT(hbar, "HBar()")
|
||||
|
||||
|
||||
def test_dagger():
|
||||
x = symbols('x', commutative=False)
|
||||
expr = Dagger(x)
|
||||
assert str(expr) == 'Dagger(x)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
+\n\
|
||||
x \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
†\n\
|
||||
x \
|
||||
"""
|
||||
assert pretty(expr) == ascii_str
|
||||
assert upretty(expr) == ucode_str
|
||||
assert latex(expr) == r'x^{\dagger}'
|
||||
sT(expr, "Dagger(Symbol('x', commutative=False))")
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_gate_failing():
|
||||
a, b, c, d = symbols('a,b,c,d')
|
||||
uMat = Matrix([[a, b], [c, d]])
|
||||
g = UGate((0,), uMat)
|
||||
assert str(g) == 'U(0)'
|
||||
|
||||
|
||||
def test_gate():
|
||||
a, b, c, d = symbols('a,b,c,d')
|
||||
uMat = Matrix([[a, b], [c, d]])
|
||||
q = Qubit(1, 0, 1, 0, 1)
|
||||
g1 = IdentityGate(2)
|
||||
g2 = CGate((3, 0), XGate(1))
|
||||
g3 = CNotGate(1, 0)
|
||||
g4 = UGate((0,), uMat)
|
||||
assert str(g1) == '1(2)'
|
||||
assert pretty(g1) == '1 \n 2'
|
||||
assert upretty(g1) == '1 \n 2'
|
||||
assert latex(g1) == r'1_{2}'
|
||||
sT(g1, "IdentityGate(Integer(2))")
|
||||
assert str(g1*q) == '1(2)*|10101>'
|
||||
ascii_str = \
|
||||
"""\
|
||||
1 *|10101>\n\
|
||||
2 \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
1 ⋅❘10101⟩\n\
|
||||
2 \
|
||||
"""
|
||||
assert pretty(g1*q) == ascii_str
|
||||
assert upretty(g1*q) == ucode_str
|
||||
assert latex(g1*q) == r'1_{2} {\left|10101\right\rangle }'
|
||||
sT(g1*q, "Mul(IdentityGate(Integer(2)), Qubit(Integer(1),Integer(0),Integer(1),Integer(0),Integer(1)))")
|
||||
assert str(g2) == 'C((3,0),X(1))'
|
||||
ascii_str = \
|
||||
"""\
|
||||
C /X \\\n\
|
||||
3,0\\ 1/\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
C ⎛X ⎞\n\
|
||||
3,0⎝ 1⎠\
|
||||
"""
|
||||
assert pretty(g2) == ascii_str
|
||||
assert upretty(g2) == ucode_str
|
||||
assert latex(g2) == r'C_{3,0}{\left(X_{1}\right)}'
|
||||
sT(g2, "CGate(Tuple(Integer(3), Integer(0)),XGate(Integer(1)))")
|
||||
assert str(g3) == 'CNOT(1,0)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
CNOT \n\
|
||||
1,0\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
CNOT \n\
|
||||
1,0\
|
||||
"""
|
||||
assert pretty(g3) == ascii_str
|
||||
assert upretty(g3) == ucode_str
|
||||
assert latex(g3) == r'\text{CNOT}_{1,0}'
|
||||
sT(g3, "CNotGate(Integer(1),Integer(0))")
|
||||
ascii_str = \
|
||||
"""\
|
||||
U \n\
|
||||
0\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
U \n\
|
||||
0\
|
||||
"""
|
||||
assert str(g4) == \
|
||||
"""\
|
||||
U((0,),Matrix([\n\
|
||||
[a, b],\n\
|
||||
[c, d]]))\
|
||||
"""
|
||||
assert pretty(g4) == ascii_str
|
||||
assert upretty(g4) == ucode_str
|
||||
assert latex(g4) == r'U_{0}'
|
||||
sT(g4, "UGate(Tuple(Integer(0)),ImmutableDenseMatrix([[Symbol('a'), Symbol('b')], [Symbol('c'), Symbol('d')]]))")
|
||||
|
||||
|
||||
def test_hilbert():
|
||||
h1 = HilbertSpace()
|
||||
h2 = ComplexSpace(2)
|
||||
h3 = FockSpace()
|
||||
h4 = L2(Interval(0, oo))
|
||||
assert str(h1) == 'H'
|
||||
assert pretty(h1) == 'H'
|
||||
assert upretty(h1) == 'H'
|
||||
assert latex(h1) == r'\mathcal{H}'
|
||||
sT(h1, "HilbertSpace()")
|
||||
assert str(h2) == 'C(2)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
2\n\
|
||||
C \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
2\n\
|
||||
C \
|
||||
"""
|
||||
assert pretty(h2) == ascii_str
|
||||
assert upretty(h2) == ucode_str
|
||||
assert latex(h2) == r'\mathcal{C}^{2}'
|
||||
sT(h2, "ComplexSpace(Integer(2))")
|
||||
assert str(h3) == 'F'
|
||||
assert pretty(h3) == 'F'
|
||||
assert upretty(h3) == 'F'
|
||||
assert latex(h3) == r'\mathcal{F}'
|
||||
sT(h3, "FockSpace()")
|
||||
assert str(h4) == 'L2(Interval(0, oo))'
|
||||
ascii_str = \
|
||||
"""\
|
||||
2\n\
|
||||
L \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
2\n\
|
||||
L \
|
||||
"""
|
||||
assert pretty(h4) == ascii_str
|
||||
assert upretty(h4) == ucode_str
|
||||
assert latex(h4) == r'{\mathcal{L}^2}\left( \left[0, \infty\right) \right)'
|
||||
sT(h4, "L2(Interval(Integer(0), oo, false, true))")
|
||||
assert str(h1 + h2) == 'H+C(2)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
2\n\
|
||||
H + C \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
2\n\
|
||||
H ⊕ C \
|
||||
"""
|
||||
assert pretty(h1 + h2) == ascii_str
|
||||
assert upretty(h1 + h2) == ucode_str
|
||||
assert latex(h1 + h2)
|
||||
sT(h1 + h2, "DirectSumHilbertSpace(HilbertSpace(),ComplexSpace(Integer(2)))")
|
||||
assert str(h1*h2) == "H*C(2)"
|
||||
ascii_str = \
|
||||
"""\
|
||||
2\n\
|
||||
H x C \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
2\n\
|
||||
H ⨂ C \
|
||||
"""
|
||||
assert pretty(h1*h2) == ascii_str
|
||||
assert upretty(h1*h2) == ucode_str
|
||||
assert latex(h1*h2)
|
||||
sT(h1*h2,
|
||||
"TensorProductHilbertSpace(HilbertSpace(),ComplexSpace(Integer(2)))")
|
||||
assert str(h1**2) == 'H**2'
|
||||
ascii_str = \
|
||||
"""\
|
||||
x2\n\
|
||||
H \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⨂2\n\
|
||||
H \
|
||||
"""
|
||||
assert pretty(h1**2) == ascii_str
|
||||
assert upretty(h1**2) == ucode_str
|
||||
assert latex(h1**2) == r'{\mathcal{H}}^{\otimes 2}'
|
||||
sT(h1**2, "TensorPowerHilbertSpace(HilbertSpace(),Integer(2))")
|
||||
|
||||
|
||||
def test_innerproduct():
|
||||
x = symbols('x')
|
||||
ip1 = InnerProduct(Bra(), Ket())
|
||||
ip2 = InnerProduct(TimeDepBra(), TimeDepKet())
|
||||
ip3 = InnerProduct(JzBra(1, 1), JzKet(1, 1))
|
||||
ip4 = InnerProduct(JzBraCoupled(1, 1, (1, 1)), JzKetCoupled(1, 1, (1, 1)))
|
||||
ip_tall1 = InnerProduct(Bra(x/2), Ket(x/2))
|
||||
ip_tall2 = InnerProduct(Bra(x), Ket(x/2))
|
||||
ip_tall3 = InnerProduct(Bra(x/2), Ket(x))
|
||||
assert str(ip1) == '<psi|psi>'
|
||||
assert pretty(ip1) == '<psi|psi>'
|
||||
assert upretty(ip1) == '⟨ψ❘ψ⟩'
|
||||
assert latex(
|
||||
ip1) == r'\left\langle \psi \right. {\left|\psi\right\rangle }'
|
||||
sT(ip1, "InnerProduct(Bra(Symbol('psi')),Ket(Symbol('psi')))")
|
||||
assert str(ip2) == '<psi;t|psi;t>'
|
||||
assert pretty(ip2) == '<psi;t|psi;t>'
|
||||
assert upretty(ip2) == '⟨ψ;t❘ψ;t⟩'
|
||||
assert latex(ip2) == \
|
||||
r'\left\langle \psi;t \right. {\left|\psi;t\right\rangle }'
|
||||
sT(ip2, "InnerProduct(TimeDepBra(Symbol('psi'),Symbol('t')),TimeDepKet(Symbol('psi'),Symbol('t')))")
|
||||
assert str(ip3) == "<1,1|1,1>"
|
||||
assert pretty(ip3) == '<1,1|1,1>'
|
||||
assert upretty(ip3) == '⟨1,1❘1,1⟩'
|
||||
assert latex(ip3) == r'\left\langle 1,1 \right. {\left|1,1\right\rangle }'
|
||||
sT(ip3, "InnerProduct(JzBra(Integer(1),Integer(1)),JzKet(Integer(1),Integer(1)))")
|
||||
assert str(ip4) == "<1,1,j1=1,j2=1|1,1,j1=1,j2=1>"
|
||||
assert pretty(ip4) == '<1,1,j1=1,j2=1|1,1,j1=1,j2=1>'
|
||||
assert upretty(ip4) == '⟨1,1,j₁=1,j₂=1❘1,1,j₁=1,j₂=1⟩'
|
||||
assert latex(ip4) == \
|
||||
r'\left\langle 1,1,j_{1}=1,j_{2}=1 \right. {\left|1,1,j_{1}=1,j_{2}=1\right\rangle }'
|
||||
sT(ip4, "InnerProduct(JzBraCoupled(Integer(1),Integer(1),Tuple(Integer(1), Integer(1)),Tuple(Tuple(Integer(1), Integer(2), Integer(1)))),JzKetCoupled(Integer(1),Integer(1),Tuple(Integer(1), Integer(1)),Tuple(Tuple(Integer(1), Integer(2), Integer(1)))))")
|
||||
assert str(ip_tall1) == '<x/2|x/2>'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/ | \\ \n\
|
||||
/ x|x \\\n\
|
||||
\\ -|- /\n\
|
||||
\\2|2/ \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
╱ │ ╲ \n\
|
||||
╱ x│x ╲\n\
|
||||
╲ ─│─ ╱\n\
|
||||
╲2│2╱ \
|
||||
"""
|
||||
assert pretty(ip_tall1) == ascii_str
|
||||
assert upretty(ip_tall1) == ucode_str
|
||||
assert latex(ip_tall1) == \
|
||||
r'\left\langle \frac{x}{2} \right. {\left|\frac{x}{2}\right\rangle }'
|
||||
sT(ip_tall1, "InnerProduct(Bra(Mul(Rational(1, 2), Symbol('x'))),Ket(Mul(Rational(1, 2), Symbol('x'))))")
|
||||
assert str(ip_tall2) == '<x|x/2>'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/ | \\ \n\
|
||||
/ |x \\\n\
|
||||
\\ x|- /\n\
|
||||
\\ |2/ \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
╱ │ ╲ \n\
|
||||
╱ │x ╲\n\
|
||||
╲ x│─ ╱\n\
|
||||
╲ │2╱ \
|
||||
"""
|
||||
assert pretty(ip_tall2) == ascii_str
|
||||
assert upretty(ip_tall2) == ucode_str
|
||||
assert latex(ip_tall2) == \
|
||||
r'\left\langle x \right. {\left|\frac{x}{2}\right\rangle }'
|
||||
sT(ip_tall2,
|
||||
"InnerProduct(Bra(Symbol('x')),Ket(Mul(Rational(1, 2), Symbol('x'))))")
|
||||
assert str(ip_tall3) == '<x/2|x>'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/ | \\ \n\
|
||||
/ x| \\\n\
|
||||
\\ -|x /\n\
|
||||
\\2| / \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
╱ │ ╲ \n\
|
||||
╱ x│ ╲\n\
|
||||
╲ ─│x ╱\n\
|
||||
╲2│ ╱ \
|
||||
"""
|
||||
assert pretty(ip_tall3) == ascii_str
|
||||
assert upretty(ip_tall3) == ucode_str
|
||||
assert latex(ip_tall3) == \
|
||||
r'\left\langle \frac{x}{2} \right. {\left|x\right\rangle }'
|
||||
sT(ip_tall3,
|
||||
"InnerProduct(Bra(Mul(Rational(1, 2), Symbol('x'))),Ket(Symbol('x')))")
|
||||
|
||||
|
||||
def test_operator():
|
||||
a = Operator('A')
|
||||
b = Operator('B', Symbol('t'), S.Half)
|
||||
inv = a.inv()
|
||||
f = Function('f')
|
||||
x = symbols('x')
|
||||
d = DifferentialOperator(Derivative(f(x), x), f(x))
|
||||
op = OuterProduct(Ket(), Bra())
|
||||
assert str(a) == 'A'
|
||||
assert pretty(a) == 'A'
|
||||
assert upretty(a) == 'A'
|
||||
assert latex(a) == 'A'
|
||||
sT(a, "Operator(Symbol('A'))")
|
||||
assert str(inv) == 'A**(-1)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
-1\n\
|
||||
A \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
-1\n\
|
||||
A \
|
||||
"""
|
||||
assert pretty(inv) == ascii_str
|
||||
assert upretty(inv) == ucode_str
|
||||
assert latex(inv) == r'A^{-1}'
|
||||
sT(inv, "Pow(Operator(Symbol('A')), Integer(-1))")
|
||||
assert str(d) == 'DifferentialOperator(Derivative(f(x), x),f(x))'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/d \\\n\
|
||||
DifferentialOperator|--(f(x)),f(x)|\n\
|
||||
\\dx /\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎛d ⎞\n\
|
||||
DifferentialOperator⎜──(f(x)),f(x)⎟\n\
|
||||
⎝dx ⎠\
|
||||
"""
|
||||
assert pretty(d) == ascii_str
|
||||
assert upretty(d) == ucode_str
|
||||
assert latex(d) == \
|
||||
r'DifferentialOperator\left(\frac{d}{d x} f{\left(x \right)},f{\left(x \right)}\right)'
|
||||
sT(d, "DifferentialOperator(Derivative(Function('f')(Symbol('x')), Tuple(Symbol('x'), Integer(1))),Function('f')(Symbol('x')))")
|
||||
assert str(b) == 'Operator(B,t,1/2)'
|
||||
assert pretty(b) == 'Operator(B,t,1/2)'
|
||||
assert upretty(b) == 'Operator(B,t,1/2)'
|
||||
assert latex(b) == r'Operator\left(B,t,\frac{1}{2}\right)'
|
||||
sT(b, "Operator(Symbol('B'),Symbol('t'),Rational(1, 2))")
|
||||
assert str(op) == '|psi><psi|'
|
||||
assert pretty(op) == '|psi><psi|'
|
||||
assert upretty(op) == '❘ψ⟩⟨ψ❘'
|
||||
assert latex(op) == r'{\left|\psi\right\rangle }{\left\langle \psi\right|}'
|
||||
sT(op, "OuterProduct(Ket(Symbol('psi')),Bra(Symbol('psi')))")
|
||||
|
||||
|
||||
def test_qexpr():
|
||||
q = QExpr('q')
|
||||
assert str(q) == 'q'
|
||||
assert pretty(q) == 'q'
|
||||
assert upretty(q) == 'q'
|
||||
assert latex(q) == r'q'
|
||||
sT(q, "QExpr(Symbol('q'))")
|
||||
|
||||
|
||||
def test_qubit():
|
||||
q1 = Qubit('0101')
|
||||
q2 = IntQubit(8)
|
||||
assert str(q1) == '|0101>'
|
||||
assert pretty(q1) == '|0101>'
|
||||
assert upretty(q1) == '❘0101⟩'
|
||||
assert latex(q1) == r'{\left|0101\right\rangle }'
|
||||
sT(q1, "Qubit(Integer(0),Integer(1),Integer(0),Integer(1))")
|
||||
assert str(q2) == '|8>'
|
||||
assert pretty(q2) == '|8>'
|
||||
assert upretty(q2) == '❘8⟩'
|
||||
assert latex(q2) == r'{\left|8\right\rangle }'
|
||||
sT(q2, "IntQubit(8)")
|
||||
|
||||
|
||||
def test_spin():
|
||||
lz = JzOp('L')
|
||||
ket = JzKet(1, 0)
|
||||
bra = JzBra(1, 0)
|
||||
cket = JzKetCoupled(1, 0, (1, 2))
|
||||
cbra = JzBraCoupled(1, 0, (1, 2))
|
||||
cket_big = JzKetCoupled(1, 0, (1, 2, 3))
|
||||
cbra_big = JzBraCoupled(1, 0, (1, 2, 3))
|
||||
rot = Rotation(1, 2, 3)
|
||||
bigd = WignerD(1, 2, 3, 4, 5, 6)
|
||||
smalld = WignerD(1, 2, 3, 0, 4, 0)
|
||||
assert str(lz) == 'Lz'
|
||||
ascii_str = \
|
||||
"""\
|
||||
L \n\
|
||||
z\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
L \n\
|
||||
z\
|
||||
"""
|
||||
assert pretty(lz) == ascii_str
|
||||
assert upretty(lz) == ucode_str
|
||||
assert latex(lz) == 'L_z'
|
||||
sT(lz, "JzOp(Symbol('L'))")
|
||||
assert str(J2) == 'J2'
|
||||
ascii_str = \
|
||||
"""\
|
||||
2\n\
|
||||
J \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
2\n\
|
||||
J \
|
||||
"""
|
||||
assert pretty(J2) == ascii_str
|
||||
assert upretty(J2) == ucode_str
|
||||
assert latex(J2) == r'J^2'
|
||||
sT(J2, "J2Op(Symbol('J'))")
|
||||
assert str(Jz) == 'Jz'
|
||||
ascii_str = \
|
||||
"""\
|
||||
J \n\
|
||||
z\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
J \n\
|
||||
z\
|
||||
"""
|
||||
assert pretty(Jz) == ascii_str
|
||||
assert upretty(Jz) == ucode_str
|
||||
assert latex(Jz) == 'J_z'
|
||||
sT(Jz, "JzOp(Symbol('J'))")
|
||||
assert str(ket) == '|1,0>'
|
||||
assert pretty(ket) == '|1,0>'
|
||||
assert upretty(ket) == '❘1,0⟩'
|
||||
assert latex(ket) == r'{\left|1,0\right\rangle }'
|
||||
sT(ket, "JzKet(Integer(1),Integer(0))")
|
||||
assert str(bra) == '<1,0|'
|
||||
assert pretty(bra) == '<1,0|'
|
||||
assert upretty(bra) == '⟨1,0❘'
|
||||
assert latex(bra) == r'{\left\langle 1,0\right|}'
|
||||
sT(bra, "JzBra(Integer(1),Integer(0))")
|
||||
assert str(cket) == '|1,0,j1=1,j2=2>'
|
||||
assert pretty(cket) == '|1,0,j1=1,j2=2>'
|
||||
assert upretty(cket) == '❘1,0,j₁=1,j₂=2⟩'
|
||||
assert latex(cket) == r'{\left|1,0,j_{1}=1,j_{2}=2\right\rangle }'
|
||||
sT(cket, "JzKetCoupled(Integer(1),Integer(0),Tuple(Integer(1), Integer(2)),Tuple(Tuple(Integer(1), Integer(2), Integer(1))))")
|
||||
assert str(cbra) == '<1,0,j1=1,j2=2|'
|
||||
assert pretty(cbra) == '<1,0,j1=1,j2=2|'
|
||||
assert upretty(cbra) == '⟨1,0,j₁=1,j₂=2❘'
|
||||
assert latex(cbra) == r'{\left\langle 1,0,j_{1}=1,j_{2}=2\right|}'
|
||||
sT(cbra, "JzBraCoupled(Integer(1),Integer(0),Tuple(Integer(1), Integer(2)),Tuple(Tuple(Integer(1), Integer(2), Integer(1))))")
|
||||
assert str(cket_big) == '|1,0,j1=1,j2=2,j3=3,j(1,2)=3>'
|
||||
# TODO: Fix non-unicode pretty printing
|
||||
# i.e. j1,2 -> j(1,2)
|
||||
assert pretty(cket_big) == '|1,0,j1=1,j2=2,j3=3,j1,2=3>'
|
||||
assert upretty(cket_big) == '❘1,0,j₁=1,j₂=2,j₃=3,j₁,₂=3⟩'
|
||||
assert latex(cket_big) == \
|
||||
r'{\left|1,0,j_{1}=1,j_{2}=2,j_{3}=3,j_{1,2}=3\right\rangle }'
|
||||
sT(cket_big, "JzKetCoupled(Integer(1),Integer(0),Tuple(Integer(1), Integer(2), Integer(3)),Tuple(Tuple(Integer(1), Integer(2), Integer(3)), Tuple(Integer(1), Integer(3), Integer(1))))")
|
||||
assert str(cbra_big) == '<1,0,j1=1,j2=2,j3=3,j(1,2)=3|'
|
||||
assert pretty(cbra_big) == '<1,0,j1=1,j2=2,j3=3,j1,2=3|'
|
||||
assert upretty(cbra_big) == '⟨1,0,j₁=1,j₂=2,j₃=3,j₁,₂=3❘'
|
||||
assert latex(cbra_big) == \
|
||||
r'{\left\langle 1,0,j_{1}=1,j_{2}=2,j_{3}=3,j_{1,2}=3\right|}'
|
||||
sT(cbra_big, "JzBraCoupled(Integer(1),Integer(0),Tuple(Integer(1), Integer(2), Integer(3)),Tuple(Tuple(Integer(1), Integer(2), Integer(3)), Tuple(Integer(1), Integer(3), Integer(1))))")
|
||||
assert str(rot) == 'R(1,2,3)'
|
||||
assert pretty(rot) == 'R (1,2,3)'
|
||||
assert upretty(rot) == 'ℛ (1,2,3)'
|
||||
assert latex(rot) == r'\mathcal{R}\left(1,2,3\right)'
|
||||
sT(rot, "Rotation(Integer(1),Integer(2),Integer(3))")
|
||||
assert str(bigd) == 'WignerD(1, 2, 3, 4, 5, 6)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
1 \n\
|
||||
D (4,5,6)\n\
|
||||
2,3 \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
1 \n\
|
||||
D (4,5,6)\n\
|
||||
2,3 \
|
||||
"""
|
||||
assert pretty(bigd) == ascii_str
|
||||
assert upretty(bigd) == ucode_str
|
||||
assert latex(bigd) == r'D^{1}_{2,3}\left(4,5,6\right)'
|
||||
sT(bigd, "WignerD(Integer(1), Integer(2), Integer(3), Integer(4), Integer(5), Integer(6))")
|
||||
assert str(smalld) == 'WignerD(1, 2, 3, 0, 4, 0)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
1 \n\
|
||||
d (4)\n\
|
||||
2,3 \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
1 \n\
|
||||
d (4)\n\
|
||||
2,3 \
|
||||
"""
|
||||
assert pretty(smalld) == ascii_str
|
||||
assert upretty(smalld) == ucode_str
|
||||
assert latex(smalld) == r'd^{1}_{2,3}\left(4\right)'
|
||||
sT(smalld, "WignerD(Integer(1), Integer(2), Integer(3), Integer(0), Integer(4), Integer(0))")
|
||||
|
||||
|
||||
def test_state():
|
||||
x = symbols('x')
|
||||
bra = Bra()
|
||||
ket = Ket()
|
||||
bra_tall = Bra(x/2)
|
||||
ket_tall = Ket(x/2)
|
||||
tbra = TimeDepBra()
|
||||
tket = TimeDepKet()
|
||||
assert str(bra) == '<psi|'
|
||||
assert pretty(bra) == '<psi|'
|
||||
assert upretty(bra) == '⟨ψ❘'
|
||||
assert latex(bra) == r'{\left\langle \psi\right|}'
|
||||
sT(bra, "Bra(Symbol('psi'))")
|
||||
assert str(ket) == '|psi>'
|
||||
assert pretty(ket) == '|psi>'
|
||||
assert upretty(ket) == '❘ψ⟩'
|
||||
assert latex(ket) == r'{\left|\psi\right\rangle }'
|
||||
sT(ket, "Ket(Symbol('psi'))")
|
||||
assert str(bra_tall) == '<x/2|'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/ |\n\
|
||||
/ x|\n\
|
||||
\\ -|\n\
|
||||
\\2|\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
╱ │\n\
|
||||
╱ x│\n\
|
||||
╲ ─│\n\
|
||||
╲2│\
|
||||
"""
|
||||
assert pretty(bra_tall) == ascii_str
|
||||
assert upretty(bra_tall) == ucode_str
|
||||
assert latex(bra_tall) == r'{\left\langle \frac{x}{2}\right|}'
|
||||
sT(bra_tall, "Bra(Mul(Rational(1, 2), Symbol('x')))")
|
||||
assert str(ket_tall) == '|x/2>'
|
||||
ascii_str = \
|
||||
"""\
|
||||
| \\ \n\
|
||||
|x \\\n\
|
||||
|- /\n\
|
||||
|2/ \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
│ ╲ \n\
|
||||
│x ╲\n\
|
||||
│─ ╱\n\
|
||||
│2╱ \
|
||||
"""
|
||||
assert pretty(ket_tall) == ascii_str
|
||||
assert upretty(ket_tall) == ucode_str
|
||||
assert latex(ket_tall) == r'{\left|\frac{x}{2}\right\rangle }'
|
||||
sT(ket_tall, "Ket(Mul(Rational(1, 2), Symbol('x')))")
|
||||
assert str(tbra) == '<psi;t|'
|
||||
assert pretty(tbra) == '<psi;t|'
|
||||
assert upretty(tbra) == '⟨ψ;t❘'
|
||||
assert latex(tbra) == r'{\left\langle \psi;t\right|}'
|
||||
sT(tbra, "TimeDepBra(Symbol('psi'),Symbol('t'))")
|
||||
assert str(tket) == '|psi;t>'
|
||||
assert pretty(tket) == '|psi;t>'
|
||||
assert upretty(tket) == '❘ψ;t⟩'
|
||||
assert latex(tket) == r'{\left|\psi;t\right\rangle }'
|
||||
sT(tket, "TimeDepKet(Symbol('psi'),Symbol('t'))")
|
||||
|
||||
|
||||
def test_tensorproduct():
|
||||
tp = TensorProduct(JzKet(1, 1), JzKet(1, 0))
|
||||
assert str(tp) == '|1,1>x|1,0>'
|
||||
assert pretty(tp) == '|1,1>x |1,0>'
|
||||
assert upretty(tp) == '❘1,1⟩⨂ ❘1,0⟩'
|
||||
assert latex(tp) == \
|
||||
r'{{\left|1,1\right\rangle }}\otimes {{\left|1,0\right\rangle }}'
|
||||
sT(tp, "TensorProduct(JzKet(Integer(1),Integer(1)), JzKet(Integer(1),Integer(0)))")
|
||||
|
||||
|
||||
def test_big_expr():
|
||||
f = Function('f')
|
||||
x = symbols('x')
|
||||
e1 = Dagger(AntiCommutator(Operator('A') + Operator('B'), Pow(DifferentialOperator(Derivative(f(x), x), f(x)), 3))*TensorProduct(Jz**2, Operator('A') + Operator('B')))*(JzBra(1, 0) + JzBra(1, 1))*(JzKet(0, 0) + JzKet(1, -1))
|
||||
e2 = Commutator(Jz**2, Operator('A') + Operator('B'))*AntiCommutator(Dagger(Operator('C')*Operator('D')), Operator('E').inv()**2)*Dagger(Commutator(Jz, J2))
|
||||
e3 = Wigner3j(1, 2, 3, 4, 5, 6)*TensorProduct(Commutator(Operator('A') + Dagger(Operator('B')), Operator('C') + Operator('D')), Jz - J2)*Dagger(OuterProduct(Dagger(JzBra(1, 1)), JzBra(1, 0)))*TensorProduct(JzKetCoupled(1, 1, (1, 1)) + JzKetCoupled(1, 0, (1, 1)), JzKetCoupled(1, -1, (1, 1)))
|
||||
e4 = (ComplexSpace(1)*ComplexSpace(2) + FockSpace()**2)*(L2(Interval(
|
||||
0, oo)) + HilbertSpace())
|
||||
assert str(e1) == '(Jz**2)x(Dagger(A) + Dagger(B))*{Dagger(DifferentialOperator(Derivative(f(x), x),f(x)))**3,Dagger(A) + Dagger(B)}*(<1,0| + <1,1|)*(|0,0> + |1,-1>)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
/ 3 \\ \n\
|
||||
|/ +\\ | \n\
|
||||
2 / + +\\ <| /d \\ | + +> \n\
|
||||
/J \\ x \\A + B /*||DifferentialOperator|--(f(x)),f(x)| | ,A + B |*(<1,0| + <1,1|)*(|0,0> + |1,-1>)\n\
|
||||
\\ z/ \\\\ \\dx / / / \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎧ 3 ⎫ \n\
|
||||
⎪⎛ †⎞ ⎪ \n\
|
||||
2 ⎛ † †⎞ ⎨⎜ ⎛d ⎞ ⎟ † †⎬ \n\
|
||||
⎛J ⎞ ⨂ ⎝A + B ⎠⋅⎪⎜DifferentialOperator⎜──(f(x)),f(x)⎟ ⎟ ,A + B ⎪⋅(⟨1,0❘ + ⟨1,1❘)⋅(❘0,0⟩ + ❘1,-1⟩)\n\
|
||||
⎝ z⎠ ⎩⎝ ⎝dx ⎠ ⎠ ⎭ \
|
||||
"""
|
||||
assert pretty(e1) == ascii_str
|
||||
assert upretty(e1) == ucode_str
|
||||
assert latex(e1) == \
|
||||
r'{J_z^{2}}\otimes \left({A^{\dagger} + B^{\dagger}}\right) \left\{\left(DifferentialOperator\left(\frac{d}{d x} f{\left(x \right)},f{\left(x \right)}\right)^{\dagger}\right)^{3},A^{\dagger} + B^{\dagger}\right\} \left({\left\langle 1,0\right|} + {\left\langle 1,1\right|}\right) \left({\left|0,0\right\rangle } + {\left|1,-1\right\rangle }\right)'
|
||||
sT(e1, "Mul(TensorProduct(Pow(JzOp(Symbol('J')), Integer(2)), Add(Dagger(Operator(Symbol('A'))), Dagger(Operator(Symbol('B'))))), AntiCommutator(Pow(Dagger(DifferentialOperator(Derivative(Function('f')(Symbol('x')), Tuple(Symbol('x'), Integer(1))),Function('f')(Symbol('x')))), Integer(3)),Add(Dagger(Operator(Symbol('A'))), Dagger(Operator(Symbol('B'))))), Add(JzBra(Integer(1),Integer(0)), JzBra(Integer(1),Integer(1))), Add(JzKet(Integer(0),Integer(0)), JzKet(Integer(1),Integer(-1))))")
|
||||
assert str(e2) == '[Jz**2,A + B]*{E**(-2),Dagger(D)*Dagger(C)}*[J2,Jz]'
|
||||
ascii_str = \
|
||||
"""\
|
||||
[ 2 ] / -2 + +\\ [ 2 ]\n\
|
||||
[/J \\ ,A + B]*<E ,D *C >*[J ,J ]\n\
|
||||
[\\ z/ ] \\ / [ z]\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎡ 2 ⎤ ⎧ -2 † †⎫ ⎡ 2 ⎤\n\
|
||||
⎢⎛J ⎞ ,A + B⎥⋅⎨E ,D ⋅C ⎬⋅⎢J ,J ⎥\n\
|
||||
⎣⎝ z⎠ ⎦ ⎩ ⎭ ⎣ z⎦\
|
||||
"""
|
||||
assert pretty(e2) == ascii_str
|
||||
assert upretty(e2) == ucode_str
|
||||
assert latex(e2) == \
|
||||
r'\left[J_z^{2},A + B\right] \left\{E^{-2},D^{\dagger} C^{\dagger}\right\} \left[J^2,J_z\right]'
|
||||
sT(e2, "Mul(Commutator(Pow(JzOp(Symbol('J')), Integer(2)),Add(Operator(Symbol('A')), Operator(Symbol('B')))), AntiCommutator(Pow(Operator(Symbol('E')), Integer(-2)),Mul(Dagger(Operator(Symbol('D'))), Dagger(Operator(Symbol('C'))))), Commutator(J2Op(Symbol('J')),JzOp(Symbol('J'))))")
|
||||
assert str(e3) == \
|
||||
"Wigner3j(1, 2, 3, 4, 5, 6)*[Dagger(B) + A,C + D]x(-J2 + Jz)*|1,0><1,1|*(|1,0,j1=1,j2=1> + |1,1,j1=1,j2=1>)x|1,-1,j1=1,j2=1>"
|
||||
ascii_str = \
|
||||
"""\
|
||||
[ + ] / 2 \\ \n\
|
||||
/1 3 5\\*[B + A,C + D]x |- J + J |*|1,0><1,1|*(|1,0,j1=1,j2=1> + |1,1,j1=1,j2=1>)x |1,-1,j1=1,j2=1>\n\
|
||||
| | \\ z/ \n\
|
||||
\\2 4 6/ \
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎡ † ⎤ ⎛ 2 ⎞ \n\
|
||||
⎛1 3 5⎞⋅⎣B + A,C + D⎦⨂ ⎜- J + J ⎟⋅❘1,0⟩⟨1,1❘⋅(❘1,0,j₁=1,j₂=1⟩ + ❘1,1,j₁=1,j₂=1⟩)⨂ ❘1,-1,j₁=1,j₂=1⟩\n\
|
||||
⎜ ⎟ ⎝ z⎠ \n\
|
||||
⎝2 4 6⎠ \
|
||||
"""
|
||||
assert pretty(e3) == ascii_str
|
||||
assert upretty(e3) == ucode_str
|
||||
assert latex(e3) == \
|
||||
r'\left(\begin{array}{ccc} 1 & 3 & 5 \\ 2 & 4 & 6 \end{array}\right) {\left[B^{\dagger} + A,C + D\right]}\otimes \left({- J^2 + J_z}\right) {\left|1,0\right\rangle }{\left\langle 1,1\right|} \left({{\left|1,0,j_{1}=1,j_{2}=1\right\rangle } + {\left|1,1,j_{1}=1,j_{2}=1\right\rangle }}\right)\otimes {{\left|1,-1,j_{1}=1,j_{2}=1\right\rangle }}'
|
||||
sT(e3, "Mul(Wigner3j(Integer(1), Integer(2), Integer(3), Integer(4), Integer(5), Integer(6)), TensorProduct(Commutator(Add(Dagger(Operator(Symbol('B'))), Operator(Symbol('A'))),Add(Operator(Symbol('C')), Operator(Symbol('D')))), Add(Mul(Integer(-1), J2Op(Symbol('J'))), JzOp(Symbol('J')))), OuterProduct(JzKet(Integer(1),Integer(0)),JzBra(Integer(1),Integer(1))), TensorProduct(Add(JzKetCoupled(Integer(1),Integer(0),Tuple(Integer(1), Integer(1)),Tuple(Tuple(Integer(1), Integer(2), Integer(1)))), JzKetCoupled(Integer(1),Integer(1),Tuple(Integer(1), Integer(1)),Tuple(Tuple(Integer(1), Integer(2), Integer(1))))), JzKetCoupled(Integer(1),Integer(-1),Tuple(Integer(1), Integer(1)),Tuple(Tuple(Integer(1), Integer(2), Integer(1))))))")
|
||||
assert str(e4) == '(C(1)*C(2)+F**2)*(L2(Interval(0, oo))+H)'
|
||||
ascii_str = \
|
||||
"""\
|
||||
// 1 2\\ x2\\ / 2 \\\n\
|
||||
\\\\C x C / + F / x \\L + H/\
|
||||
"""
|
||||
ucode_str = \
|
||||
"""\
|
||||
⎛⎛ 1 2⎞ ⨂2⎞ ⎛ 2 ⎞\n\
|
||||
⎝⎝C ⨂ C ⎠ ⊕ F ⎠ ⨂ ⎝L ⊕ H⎠\
|
||||
"""
|
||||
assert pretty(e4) == ascii_str
|
||||
assert upretty(e4) == ucode_str
|
||||
assert latex(e4) == \
|
||||
r'\left(\left(\mathcal{C}^{1}\otimes \mathcal{C}^{2}\right)\oplus {\mathcal{F}}^{\otimes 2}\right)\otimes \left({\mathcal{L}^2}\left( \left[0, \infty\right) \right)\oplus \mathcal{H}\right)'
|
||||
sT(e4, "TensorProductHilbertSpace((DirectSumHilbertSpace(TensorProductHilbertSpace(ComplexSpace(Integer(1)),ComplexSpace(Integer(2))),TensorPowerHilbertSpace(FockSpace(),Integer(2)))),(DirectSumHilbertSpace(L2(Interval(Integer(0), oo, false, true)),HilbertSpace())))")
|
||||
|
||||
|
||||
def _test_sho1d():
|
||||
ad = RaisingOp('a')
|
||||
assert pretty(ad) == ' \N{DAGGER}\na '
|
||||
assert latex(ad) == 'a^{\\dagger}'
|
||||
@@ -0,0 +1,152 @@
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (I, Integer, Rational)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
|
||||
from sympy.physics.quantum.anticommutator import AntiCommutator
|
||||
from sympy.physics.quantum.commutator import Commutator
|
||||
from sympy.physics.quantum.constants import hbar
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.gate import H, XGate, IdentityGate
|
||||
from sympy.physics.quantum.operator import Operator, IdentityOperator
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.spin import Jx, Jy, Jz, Jplus, Jminus, J2, JzKet
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct
|
||||
from sympy.physics.quantum.state import Ket
|
||||
from sympy.physics.quantum.density import Density
|
||||
from sympy.physics.quantum.qubit import Qubit, QubitBra
|
||||
from sympy.physics.quantum.boson import BosonOp, BosonFockKet, BosonFockBra
|
||||
from sympy.testing.pytest import warns_deprecated_sympy
|
||||
|
||||
|
||||
j, jp, m, mp = symbols("j j' m m'")
|
||||
|
||||
z = JzKet(1, 0)
|
||||
po = JzKet(1, 1)
|
||||
mo = JzKet(1, -1)
|
||||
|
||||
A = Operator('A')
|
||||
|
||||
|
||||
class Foo(Operator):
|
||||
def _apply_operator_JzKet(self, ket, **options):
|
||||
return ket
|
||||
|
||||
|
||||
def test_basic():
|
||||
assert qapply(Jz*po) == hbar*po
|
||||
assert qapply(Jx*z) == hbar*po/sqrt(2) + hbar*mo/sqrt(2)
|
||||
assert qapply((Jplus + Jminus)*z/sqrt(2)) == hbar*po + hbar*mo
|
||||
assert qapply(Jz*(po + mo)) == hbar*po - hbar*mo
|
||||
assert qapply(Jz*po + Jz*mo) == hbar*po - hbar*mo
|
||||
assert qapply(Jminus*Jminus*po) == 2*hbar**2*mo
|
||||
assert qapply(Jplus**2*mo) == 2*hbar**2*po
|
||||
assert qapply(Jplus**2*Jminus**2*po) == 4*hbar**4*po
|
||||
|
||||
|
||||
def test_extra():
|
||||
extra = z.dual*A*z
|
||||
assert qapply(Jz*po*extra) == hbar*po*extra
|
||||
assert qapply(Jx*z*extra) == (hbar*po/sqrt(2) + hbar*mo/sqrt(2))*extra
|
||||
assert qapply(
|
||||
(Jplus + Jminus)*z/sqrt(2)*extra) == hbar*po*extra + hbar*mo*extra
|
||||
assert qapply(Jz*(po + mo)*extra) == hbar*po*extra - hbar*mo*extra
|
||||
assert qapply(Jz*po*extra + Jz*mo*extra) == hbar*po*extra - hbar*mo*extra
|
||||
assert qapply(Jminus*Jminus*po*extra) == 2*hbar**2*mo*extra
|
||||
assert qapply(Jplus**2*mo*extra) == 2*hbar**2*po*extra
|
||||
assert qapply(Jplus**2*Jminus**2*po*extra) == 4*hbar**4*po*extra
|
||||
|
||||
|
||||
def test_innerproduct():
|
||||
assert qapply(po.dual*Jz*po, ip_doit=False) == hbar*(po.dual*po)
|
||||
assert qapply(po.dual*Jz*po) == hbar
|
||||
|
||||
|
||||
def test_zero():
|
||||
assert qapply(0) == 0
|
||||
assert qapply(Integer(0)) == 0
|
||||
|
||||
|
||||
def test_commutator():
|
||||
assert qapply(Commutator(Jx, Jy)*Jz*po) == I*hbar**3*po
|
||||
assert qapply(Commutator(J2, Jz)*Jz*po) == 0
|
||||
assert qapply(Commutator(Jz, Foo('F'))*po) == 0
|
||||
assert qapply(Commutator(Foo('F'), Jz)*po) == 0
|
||||
|
||||
|
||||
def test_anticommutator():
|
||||
assert qapply(AntiCommutator(Jz, Foo('F'))*po) == 2*hbar*po
|
||||
assert qapply(AntiCommutator(Foo('F'), Jz)*po) == 2*hbar*po
|
||||
|
||||
|
||||
def test_outerproduct():
|
||||
e = Jz*(mo*po.dual)*Jz*po
|
||||
assert qapply(e) == -hbar**2*mo
|
||||
assert qapply(e, ip_doit=False) == -hbar**2*(po.dual*po)*mo
|
||||
assert qapply(e).doit() == -hbar**2*mo
|
||||
|
||||
|
||||
def test_tensorproduct():
|
||||
a = BosonOp("a")
|
||||
b = BosonOp("b")
|
||||
ket1 = TensorProduct(BosonFockKet(1), BosonFockKet(2))
|
||||
ket2 = TensorProduct(BosonFockKet(0), BosonFockKet(0))
|
||||
ket3 = TensorProduct(BosonFockKet(0), BosonFockKet(2))
|
||||
bra1 = TensorProduct(BosonFockBra(0), BosonFockBra(0))
|
||||
bra2 = TensorProduct(BosonFockBra(1), BosonFockBra(2))
|
||||
assert qapply(TensorProduct(a, b ** 2) * ket1) == sqrt(2) * ket2
|
||||
assert qapply(TensorProduct(a, Dagger(b) * b) * ket1) == 2 * ket3
|
||||
assert qapply(bra1 * TensorProduct(a, b * b),
|
||||
dagger=True) == sqrt(2) * bra2
|
||||
assert qapply(bra2 * ket1).doit() == S.One
|
||||
assert qapply(TensorProduct(a, b * b) * ket1) == sqrt(2) * ket2
|
||||
assert qapply(Dagger(TensorProduct(a, b * b) * ket1),
|
||||
dagger=True) == sqrt(2) * Dagger(ket2)
|
||||
|
||||
|
||||
def test_dagger():
|
||||
lhs = Dagger(Qubit(0))*Dagger(H(0))
|
||||
rhs = Dagger(Qubit(1))/sqrt(2) + Dagger(Qubit(0))/sqrt(2)
|
||||
assert qapply(lhs, dagger=True) == rhs
|
||||
|
||||
|
||||
def test_issue_6073():
|
||||
x, y = symbols('x y', commutative=False)
|
||||
A = Ket(x, y)
|
||||
B = Operator('B')
|
||||
assert qapply(A) == A
|
||||
assert qapply(A.dual*B) == A.dual*B
|
||||
|
||||
|
||||
def test_density():
|
||||
d = Density([Jz*mo, 0.5], [Jz*po, 0.5])
|
||||
assert qapply(d) == Density([-hbar*mo, 0.5], [hbar*po, 0.5])
|
||||
|
||||
|
||||
def test_issue3044():
|
||||
expr1 = TensorProduct(Jz*JzKet(S(2),S.NegativeOne)/sqrt(2), Jz*JzKet(S.Half,S.Half))
|
||||
result = Mul(S.NegativeOne, Rational(1, 4), 2**S.Half, hbar**2)
|
||||
result *= TensorProduct(JzKet(2,-1), JzKet(S.Half,S.Half))
|
||||
assert qapply(expr1) == result
|
||||
|
||||
|
||||
# Issue 24158: Tests whether qapply incorrectly evaluates some ket*op as op*ket
|
||||
def test_issue24158_ket_times_op():
|
||||
P = BosonFockKet(0) * BosonOp("a") # undefined term
|
||||
# Does lhs._apply_operator_BosonOp(rhs) still evaluate ket*op as op*ket?
|
||||
assert qapply(P) == P # qapply(P) -> BosonOp("a")*BosonFockKet(0) = 0 before fix
|
||||
P = Qubit(1) * XGate(0) # undefined term
|
||||
# Does rhs._apply_operator_Qubit(lhs) still evaluate ket*op as op*ket?
|
||||
assert qapply(P) == P # qapply(P) -> Qubit(0) before fix
|
||||
P1 = Mul(QubitBra(0), Mul(QubitBra(0), Qubit(0)), XGate(0)) # legal expr <0| * (<1|*|1>) * X
|
||||
assert qapply(P1) == QubitBra(0) * XGate(0) # qapply(P1) -> 0 before fix
|
||||
P1 = qapply(P1, dagger = True) # unsatisfactorily -> <0|*X(0), expect <1| since dagger=True
|
||||
assert qapply(P1, dagger = True) == QubitBra(1) # qapply(P1, dagger=True) -> 0 before fix
|
||||
P2 = QubitBra(0) * (QubitBra(0) * Qubit(0)) * XGate(0) # 'forgot' to set brackets
|
||||
P2 = qapply(P2, dagger = True) # unsatisfactorily -> <0|*X(0), expect <1| since dagger=True
|
||||
assert P2 == QubitBra(1) # qapply(P1) -> 0 before fix
|
||||
# Pull Request 24237: IdentityOperator from the right without dagger=True option
|
||||
with warns_deprecated_sympy():
|
||||
assert qapply(QubitBra(1)*IdentityOperator()) == QubitBra(1)
|
||||
assert qapply(IdentityGate(0)*(Qubit(0) + Qubit(1))) == Qubit(0) + Qubit(1)
|
||||
@@ -0,0 +1,89 @@
|
||||
from sympy.physics.quantum.qasm import Qasm, flip_index, trim,\
|
||||
get_index, nonblank, fullsplit, fixcommand, stripquotes, read_qasm
|
||||
from sympy.physics.quantum.gate import X, Z, H, S, T
|
||||
from sympy.physics.quantum.gate import CNOT, SWAP, CPHASE, CGate, CGateS
|
||||
from sympy.physics.quantum.circuitplot import Mz
|
||||
|
||||
def test_qasm_readqasm():
|
||||
qasm_lines = """\
|
||||
qubit q_0
|
||||
qubit q_1
|
||||
h q_0
|
||||
cnot q_0,q_1
|
||||
"""
|
||||
q = read_qasm(qasm_lines)
|
||||
assert q.get_circuit() == CNOT(1,0)*H(1)
|
||||
|
||||
def test_qasm_ex1():
|
||||
q = Qasm('qubit q0', 'qubit q1', 'h q0', 'cnot q0,q1')
|
||||
assert q.get_circuit() == CNOT(1,0)*H(1)
|
||||
|
||||
def test_qasm_ex1_methodcalls():
|
||||
q = Qasm()
|
||||
q.qubit('q_0')
|
||||
q.qubit('q_1')
|
||||
q.h('q_0')
|
||||
q.cnot('q_0', 'q_1')
|
||||
assert q.get_circuit() == CNOT(1,0)*H(1)
|
||||
|
||||
def test_qasm_swap():
|
||||
q = Qasm('qubit q0', 'qubit q1', 'cnot q0,q1', 'cnot q1,q0', 'cnot q0,q1')
|
||||
assert q.get_circuit() == CNOT(1,0)*CNOT(0,1)*CNOT(1,0)
|
||||
|
||||
|
||||
def test_qasm_ex2():
|
||||
q = Qasm('qubit q_0', 'qubit q_1', 'qubit q_2', 'h q_1',
|
||||
'cnot q_1,q_2', 'cnot q_0,q_1', 'h q_0',
|
||||
'measure q_1', 'measure q_0',
|
||||
'c-x q_1,q_2', 'c-z q_0,q_2')
|
||||
assert q.get_circuit() == CGate(2,Z(0))*CGate(1,X(0))*Mz(2)*Mz(1)*H(2)*CNOT(2,1)*CNOT(1,0)*H(1)
|
||||
|
||||
def test_qasm_1q():
|
||||
for symbol, gate in [('x', X), ('z', Z), ('h', H), ('s', S), ('t', T), ('measure', Mz)]:
|
||||
q = Qasm('qubit q_0', '%s q_0' % symbol)
|
||||
assert q.get_circuit() == gate(0)
|
||||
|
||||
def test_qasm_2q():
|
||||
for symbol, gate in [('cnot', CNOT), ('swap', SWAP), ('cphase', CPHASE)]:
|
||||
q = Qasm('qubit q_0', 'qubit q_1', '%s q_0,q_1' % symbol)
|
||||
assert q.get_circuit() == gate(1,0)
|
||||
|
||||
def test_qasm_3q():
|
||||
q = Qasm('qubit q0', 'qubit q1', 'qubit q2', 'toffoli q2,q1,q0')
|
||||
assert q.get_circuit() == CGateS((0,1),X(2))
|
||||
|
||||
def test_qasm_flip_index():
|
||||
assert flip_index(0, 2) == 1
|
||||
assert flip_index(1, 2) == 0
|
||||
|
||||
def test_qasm_trim():
|
||||
assert trim('nothing happens here') == 'nothing happens here'
|
||||
assert trim("Something #happens here") == "Something "
|
||||
|
||||
def test_qasm_get_index():
|
||||
assert get_index('q0', ['q0', 'q1']) == 1
|
||||
assert get_index('q1', ['q0', 'q1']) == 0
|
||||
|
||||
def test_qasm_nonblank():
|
||||
assert list(nonblank('abcd')) == list('abcd')
|
||||
assert list(nonblank('abc ')) == list('abc')
|
||||
|
||||
def test_qasm_fullsplit():
|
||||
assert fullsplit('g q0,q1,q2, q3') == ('g', ['q0', 'q1', 'q2', 'q3'])
|
||||
|
||||
def test_qasm_fixcommand():
|
||||
assert fixcommand('foo') == 'foo'
|
||||
assert fixcommand('def') == 'qdef'
|
||||
|
||||
def test_qasm_stripquotes():
|
||||
assert stripquotes("'S'") == 'S'
|
||||
assert stripquotes('"S"') == 'S'
|
||||
assert stripquotes('S') == 'S'
|
||||
|
||||
def test_qasm_qdef():
|
||||
# weaker test condition (str) since we don't have access to the actual class
|
||||
q = Qasm("def Q,0,Q",'qubit q0','Q q0')
|
||||
assert str(q.get_circuit()) == 'Q(0)'
|
||||
|
||||
q = Qasm("def CQ,1,Q", 'qubit q0', 'qubit q1', 'CQ q0,q1')
|
||||
assert str(q.get_circuit()) == 'C((1),Q(0))'
|
||||
@@ -0,0 +1,64 @@
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.concrete import Sum
|
||||
from sympy.physics.quantum.qexpr import QExpr, _qsympify_sequence
|
||||
from sympy.physics.quantum.hilbert import HilbertSpace
|
||||
from sympy.core.containers import Tuple
|
||||
|
||||
x = Symbol('x')
|
||||
y = Symbol('y')
|
||||
n = Symbol('n', integer=True)
|
||||
m = Symbol('m', integer=True)
|
||||
|
||||
|
||||
def test_qexpr_new():
|
||||
q = QExpr(0)
|
||||
assert q.label == (0,)
|
||||
assert q.hilbert_space == HilbertSpace()
|
||||
assert q.is_commutative is False
|
||||
|
||||
q = QExpr(0, 1)
|
||||
assert q.label == (Integer(0), Integer(1))
|
||||
|
||||
q = QExpr._new_rawargs(HilbertSpace(), Integer(0), Integer(1))
|
||||
assert q.label == (Integer(0), Integer(1))
|
||||
assert q.hilbert_space == HilbertSpace()
|
||||
|
||||
|
||||
def test_qexpr_commutative():
|
||||
q1 = QExpr(x)
|
||||
q2 = QExpr(y)
|
||||
assert q1.is_commutative is False
|
||||
assert q2.is_commutative is False
|
||||
assert q1*q2 != q2*q1
|
||||
|
||||
q = QExpr._new_rawargs(Integer(0), Integer(1), HilbertSpace())
|
||||
assert q.is_commutative is False
|
||||
|
||||
|
||||
def test_qexpr_free_symbols():
|
||||
q1 = QExpr(x, y)
|
||||
assert q1.free_symbols == {x, y}
|
||||
|
||||
|
||||
def test_qexpr_sum():
|
||||
q1 = Sum(QExpr(n), (n,0,2))
|
||||
assert q1.doit() == QExpr(0) + QExpr(1) + QExpr(2)
|
||||
|
||||
q2 = Sum(QExpr(n, m), (n, 0, 2), (m, 0, 2))
|
||||
assert q2.doit() == QExpr(0, 0) + QExpr(0, 1) + QExpr(0, 2) + \
|
||||
QExpr(1, 0) + QExpr(1, 1) + QExpr(1, 2) + \
|
||||
QExpr(2, 0) + QExpr(2, 1) + QExpr(2, 2)
|
||||
|
||||
|
||||
def test_qexpr_subs():
|
||||
q1 = QExpr(x, y)
|
||||
assert q1.subs(x, y) == QExpr(y, y)
|
||||
assert q1.subs({x: 1, y: 2}) == QExpr(1, 2)
|
||||
|
||||
|
||||
def test_qsympify():
|
||||
assert _qsympify_sequence([[1, 2], [1, 3]]) == (Tuple(1, 2), Tuple(1, 3))
|
||||
assert _qsympify_sequence(([1, 2, [3, 4, [2, ]], 1], 3)) == \
|
||||
(Tuple(1, 2, Tuple(3, 4, Tuple(2,)), 1), 3)
|
||||
assert _qsympify_sequence((1,)) == (1,)
|
||||
@@ -0,0 +1,52 @@
|
||||
from sympy.core.numbers import (I, pi)
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.matrices.dense import Matrix
|
||||
|
||||
from sympy.physics.quantum.qft import QFT, IQFT, RkGate
|
||||
from sympy.physics.quantum.gate import (ZGate, SwapGate, HadamardGate, CGate,
|
||||
PhaseGate, TGate)
|
||||
from sympy.physics.quantum.qubit import Qubit
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.represent import represent
|
||||
|
||||
from sympy.functions.elementary.complexes import sign
|
||||
|
||||
|
||||
def test_RkGate():
|
||||
x = Symbol('x')
|
||||
assert RkGate(1, x).k == x
|
||||
assert RkGate(1, x).targets == (1,)
|
||||
assert RkGate(1, 1) == ZGate(1)
|
||||
assert RkGate(2, 2) == PhaseGate(2)
|
||||
assert RkGate(3, 3) == TGate(3)
|
||||
|
||||
assert represent(
|
||||
RkGate(0, x), nqubits=1) == Matrix([[1, 0], [0, exp(sign(x)*2*pi*I/(2**abs(x)))]])
|
||||
|
||||
|
||||
def test_quantum_fourier():
|
||||
assert QFT(0, 3).decompose() == \
|
||||
SwapGate(0, 2)*HadamardGate(0)*CGate((0,), PhaseGate(1)) * \
|
||||
HadamardGate(1)*CGate((0,), TGate(2))*CGate((1,), PhaseGate(2)) * \
|
||||
HadamardGate(2)
|
||||
|
||||
assert IQFT(0, 3).decompose() == \
|
||||
HadamardGate(2)*CGate((1,), RkGate(2, -2))*CGate((0,), RkGate(2, -3)) * \
|
||||
HadamardGate(1)*CGate((0,), RkGate(1, -2))*HadamardGate(0)*SwapGate(0, 2)
|
||||
|
||||
assert represent(QFT(0, 3), nqubits=3) == \
|
||||
Matrix([[exp(2*pi*I/8)**(i*j % 8)/sqrt(8) for i in range(8)] for j in range(8)])
|
||||
|
||||
assert QFT(0, 4).decompose() # non-trivial decomposition
|
||||
assert qapply(QFT(0, 3).decompose()*Qubit(0, 0, 0)).expand() == qapply(
|
||||
HadamardGate(0)*HadamardGate(1)*HadamardGate(2)*Qubit(0, 0, 0)
|
||||
).expand()
|
||||
|
||||
|
||||
def test_qft_represent():
|
||||
c = QFT(0, 3)
|
||||
a = represent(c, nqubits=3)
|
||||
b = represent(c.decompose(), nqubits=3)
|
||||
assert a.evalf(n=10) == b.evalf(n=10)
|
||||
@@ -0,0 +1,264 @@
|
||||
import random
|
||||
|
||||
from sympy.core.numbers import (Integer, Rational)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.physics.quantum.qubit import (measure_all, measure_all_oneshot, measure_partial,
|
||||
matrix_to_qubit, matrix_to_density,
|
||||
qubit_to_matrix, IntQubit,
|
||||
IntQubitBra, QubitBra)
|
||||
from sympy.physics.quantum.gate import (HadamardGate, CNOT, XGate, YGate,
|
||||
ZGate, PhaseGate)
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.physics.quantum.shor import Qubit
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.physics.quantum.density import Density
|
||||
from sympy.physics.quantum.trace import Tr
|
||||
|
||||
x, y = symbols('x,y')
|
||||
|
||||
epsilon = .000001
|
||||
|
||||
|
||||
def test_Qubit():
|
||||
array = [0, 0, 1, 1, 0]
|
||||
qb = Qubit('00110')
|
||||
assert qb.flip(0) == Qubit('00111')
|
||||
assert qb.flip(1) == Qubit('00100')
|
||||
assert qb.flip(4) == Qubit('10110')
|
||||
assert qb.qubit_values == (0, 0, 1, 1, 0)
|
||||
assert qb.dimension == 5
|
||||
for i in range(5):
|
||||
assert qb[i] == array[4 - i]
|
||||
assert len(qb) == 5
|
||||
qb = Qubit('110')
|
||||
|
||||
|
||||
def test_QubitBra():
|
||||
qb = Qubit(0)
|
||||
qb_bra = QubitBra(0)
|
||||
assert qb.dual_class() == QubitBra
|
||||
assert qb_bra.dual_class() == Qubit
|
||||
|
||||
qb = Qubit(1, 1, 0)
|
||||
qb_bra = QubitBra(1, 1, 0)
|
||||
assert represent(qb, nqubits=3).H == represent(qb_bra, nqubits=3)
|
||||
|
||||
qb = Qubit(0, 1)
|
||||
qb_bra = QubitBra(1,0)
|
||||
assert qb._eval_innerproduct_QubitBra(qb_bra) == Integer(0)
|
||||
|
||||
qb_bra = QubitBra(0, 1)
|
||||
assert qb._eval_innerproduct_QubitBra(qb_bra) == Integer(1)
|
||||
|
||||
|
||||
def test_IntQubit():
|
||||
# issue 9136
|
||||
iqb = IntQubit(0, nqubits=1)
|
||||
assert qubit_to_matrix(Qubit('0')) == qubit_to_matrix(iqb)
|
||||
|
||||
qb = Qubit('1010')
|
||||
assert qubit_to_matrix(IntQubit(qb)) == qubit_to_matrix(qb)
|
||||
|
||||
iqb = IntQubit(1, nqubits=1)
|
||||
assert qubit_to_matrix(Qubit('1')) == qubit_to_matrix(iqb)
|
||||
assert qubit_to_matrix(IntQubit(1)) == qubit_to_matrix(iqb)
|
||||
|
||||
iqb = IntQubit(7, nqubits=4)
|
||||
assert qubit_to_matrix(Qubit('0111')) == qubit_to_matrix(iqb)
|
||||
assert qubit_to_matrix(IntQubit(7, 4)) == qubit_to_matrix(iqb)
|
||||
|
||||
iqb = IntQubit(8)
|
||||
assert iqb.as_int() == 8
|
||||
assert iqb.qubit_values == (1, 0, 0, 0)
|
||||
|
||||
iqb = IntQubit(7, 4)
|
||||
assert iqb.qubit_values == (0, 1, 1, 1)
|
||||
assert IntQubit(3) == IntQubit(3, 2)
|
||||
|
||||
#test Dual Classes
|
||||
iqb = IntQubit(3)
|
||||
iqb_bra = IntQubitBra(3)
|
||||
assert iqb.dual_class() == IntQubitBra
|
||||
assert iqb_bra.dual_class() == IntQubit
|
||||
|
||||
iqb = IntQubit(5)
|
||||
iqb_bra = IntQubitBra(5)
|
||||
assert iqb._eval_innerproduct_IntQubitBra(iqb_bra) == Integer(1)
|
||||
|
||||
iqb = IntQubit(4)
|
||||
iqb_bra = IntQubitBra(5)
|
||||
assert iqb._eval_innerproduct_IntQubitBra(iqb_bra) == Integer(0)
|
||||
raises(ValueError, lambda: IntQubit(4, 1))
|
||||
|
||||
raises(ValueError, lambda: IntQubit('5'))
|
||||
raises(ValueError, lambda: IntQubit(5, '5'))
|
||||
raises(ValueError, lambda: IntQubit(5, nqubits='5'))
|
||||
raises(TypeError, lambda: IntQubit(5, bad_arg=True))
|
||||
|
||||
def test_superposition_of_states():
|
||||
state = 1/sqrt(2)*Qubit('01') + 1/sqrt(2)*Qubit('10')
|
||||
state_gate = CNOT(0, 1)*HadamardGate(0)*state
|
||||
state_expanded = Qubit('01')/2 + Qubit('00')/2 - Qubit('11')/2 + Qubit('10')/2
|
||||
assert qapply(state_gate).expand() == state_expanded
|
||||
assert matrix_to_qubit(represent(state_gate, nqubits=2)) == state_expanded
|
||||
|
||||
|
||||
#test apply methods
|
||||
def test_apply_represent_equality():
|
||||
gates = [HadamardGate(int(3*random.random())),
|
||||
XGate(int(3*random.random())), ZGate(int(3*random.random())),
|
||||
YGate(int(3*random.random())), ZGate(int(3*random.random())),
|
||||
PhaseGate(int(3*random.random()))]
|
||||
|
||||
circuit = Qubit(int(random.random()*2), int(random.random()*2),
|
||||
int(random.random()*2), int(random.random()*2), int(random.random()*2),
|
||||
int(random.random()*2))
|
||||
for i in range(int(random.random()*6)):
|
||||
circuit = gates[int(random.random()*6)]*circuit
|
||||
|
||||
mat = represent(circuit, nqubits=6)
|
||||
states = qapply(circuit)
|
||||
state_rep = matrix_to_qubit(mat)
|
||||
states = states.expand()
|
||||
state_rep = state_rep.expand()
|
||||
assert state_rep == states
|
||||
|
||||
|
||||
def test_matrix_to_qubits():
|
||||
qb = Qubit(0, 0, 0, 0)
|
||||
mat = Matrix([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
assert matrix_to_qubit(mat) == qb
|
||||
assert qubit_to_matrix(qb) == mat
|
||||
|
||||
state = 2*sqrt(2)*(Qubit(0, 0, 0) + Qubit(0, 0, 1) + Qubit(0, 1, 0) +
|
||||
Qubit(0, 1, 1) + Qubit(1, 0, 0) + Qubit(1, 0, 1) +
|
||||
Qubit(1, 1, 0) + Qubit(1, 1, 1))
|
||||
ones = sqrt(2)*2*Matrix([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
assert matrix_to_qubit(ones) == state.expand()
|
||||
assert qubit_to_matrix(state) == ones
|
||||
|
||||
|
||||
def test_measure_normalize():
|
||||
a, b = symbols('a b')
|
||||
state = a*Qubit('110') + b*Qubit('111')
|
||||
assert measure_partial(state, (0,), normalize=False) == \
|
||||
[(a*Qubit('110'), a*a.conjugate()), (b*Qubit('111'), b*b.conjugate())]
|
||||
assert measure_all(state, normalize=False) == \
|
||||
[(Qubit('110'), a*a.conjugate()), (Qubit('111'), b*b.conjugate())]
|
||||
|
||||
|
||||
def test_measure_partial():
|
||||
#Basic test of collapse of entangled two qubits (Bell States)
|
||||
state = Qubit('01') + Qubit('10')
|
||||
assert measure_partial(state, (0,)) == \
|
||||
[(Qubit('10'), S.Half), (Qubit('01'), S.Half)]
|
||||
assert measure_partial(state, int(0)) == \
|
||||
[(Qubit('10'), S.Half), (Qubit('01'), S.Half)]
|
||||
assert measure_partial(state, (0,)) == \
|
||||
measure_partial(state, (1,))[::-1]
|
||||
|
||||
#Test of more complex collapse and probability calculation
|
||||
state1 = sqrt(2)/sqrt(3)*Qubit('00001') + 1/sqrt(3)*Qubit('11111')
|
||||
assert measure_partial(state1, (0,)) == \
|
||||
[(sqrt(2)/sqrt(3)*Qubit('00001') + 1/sqrt(3)*Qubit('11111'), 1)]
|
||||
assert measure_partial(state1, (1, 2)) == measure_partial(state1, (3, 4))
|
||||
assert measure_partial(state1, (1, 2, 3)) == \
|
||||
[(Qubit('00001'), Rational(2, 3)), (Qubit('11111'), Rational(1, 3))]
|
||||
|
||||
#test of measuring multiple bits at once
|
||||
state2 = Qubit('1111') + Qubit('1101') + Qubit('1011') + Qubit('1000')
|
||||
assert measure_partial(state2, (0, 1, 3)) == \
|
||||
[(Qubit('1000'), Rational(1, 4)), (Qubit('1101'), Rational(1, 4)),
|
||||
(Qubit('1011')/sqrt(2) + Qubit('1111')/sqrt(2), S.Half)]
|
||||
assert measure_partial(state2, (0,)) == \
|
||||
[(Qubit('1000'), Rational(1, 4)),
|
||||
(Qubit('1111')/sqrt(3) + Qubit('1101')/sqrt(3) +
|
||||
Qubit('1011')/sqrt(3), Rational(3, 4))]
|
||||
|
||||
|
||||
def test_measure_all():
|
||||
assert measure_all(Qubit('11')) == [(Qubit('11'), 1)]
|
||||
state = Qubit('11') + Qubit('10')
|
||||
assert measure_all(state) == [(Qubit('10'), S.Half),
|
||||
(Qubit('11'), S.Half)]
|
||||
state2 = Qubit('11')/sqrt(5) + 2*Qubit('00')/sqrt(5)
|
||||
assert measure_all(state2) == \
|
||||
[(Qubit('00'), Rational(4, 5)), (Qubit('11'), Rational(1, 5))]
|
||||
|
||||
# from issue #12585
|
||||
assert measure_all(qapply(Qubit('0'))) == [(Qubit('0'), 1)]
|
||||
|
||||
|
||||
def test_measure_all_oneshot():
|
||||
random.seed(42)
|
||||
# for issue #27092
|
||||
assert measure_all_oneshot(Qubit('11')) == Qubit('11')
|
||||
assert measure_all_oneshot(Qubit('1')) == Qubit('1')
|
||||
assert measure_all_oneshot(Qubit('0')/sqrt(2) + Qubit('1')/sqrt(2)) == \
|
||||
Qubit('0')
|
||||
|
||||
|
||||
def test_eval_trace():
|
||||
q1 = Qubit('10110')
|
||||
q2 = Qubit('01010')
|
||||
d = Density([q1, 0.6], [q2, 0.4])
|
||||
|
||||
t = Tr(d)
|
||||
assert t.doit() == 1.0
|
||||
|
||||
# extreme bits
|
||||
t = Tr(d, 0)
|
||||
assert t.doit() == (0.4*Density([Qubit('0101'), 1]) +
|
||||
0.6*Density([Qubit('1011'), 1]))
|
||||
t = Tr(d, 4)
|
||||
assert t.doit() == (0.4*Density([Qubit('1010'), 1]) +
|
||||
0.6*Density([Qubit('0110'), 1]))
|
||||
# index somewhere in between
|
||||
t = Tr(d, 2)
|
||||
assert t.doit() == (0.4*Density([Qubit('0110'), 1]) +
|
||||
0.6*Density([Qubit('1010'), 1]))
|
||||
#trace all indices
|
||||
t = Tr(d, [0, 1, 2, 3, 4])
|
||||
assert t.doit() == 1.0
|
||||
|
||||
# trace some indices, initialized in
|
||||
# non-canonical order
|
||||
t = Tr(d, [2, 1, 3])
|
||||
assert t.doit() == (0.4*Density([Qubit('00'), 1]) +
|
||||
0.6*Density([Qubit('10'), 1]))
|
||||
|
||||
# mixed states
|
||||
q = (1/sqrt(2)) * (Qubit('00') + Qubit('11'))
|
||||
d = Density( [q, 1.0] )
|
||||
t = Tr(d, 0)
|
||||
assert t.doit() == (0.5*Density([Qubit('0'), 1]) +
|
||||
0.5*Density([Qubit('1'), 1]))
|
||||
|
||||
|
||||
def test_matrix_to_density():
|
||||
mat = Matrix([[0, 0], [0, 1]])
|
||||
assert matrix_to_density(mat) == Density([Qubit('1'), 1])
|
||||
|
||||
mat = Matrix([[1, 0], [0, 0]])
|
||||
assert matrix_to_density(mat) == Density([Qubit('0'), 1])
|
||||
|
||||
mat = Matrix([[0, 0], [0, 0]])
|
||||
assert matrix_to_density(mat) == 0
|
||||
|
||||
mat = Matrix([[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 0]])
|
||||
|
||||
assert matrix_to_density(mat) == Density([Qubit('10'), 1])
|
||||
|
||||
mat = Matrix([[1, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0]])
|
||||
|
||||
assert matrix_to_density(mat) == Density([Qubit('00'), 1])
|
||||
@@ -0,0 +1,186 @@
|
||||
from sympy.core.numbers import (Float, I, Integer)
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.external import import_module
|
||||
from sympy.testing.pytest import skip
|
||||
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.represent import (represent, rep_innerproduct,
|
||||
rep_expectation, enumerate_states)
|
||||
from sympy.physics.quantum.state import Bra, Ket
|
||||
from sympy.physics.quantum.operator import Operator, OuterProduct
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct
|
||||
from sympy.physics.quantum.tensorproduct import matrix_tensor_product
|
||||
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.matrixutils import (numpy_ndarray,
|
||||
scipy_sparse_matrix, to_numpy,
|
||||
to_scipy_sparse, to_sympy)
|
||||
from sympy.physics.quantum.cartesian import XKet, XOp, XBra
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.operatorset import operators_to_state
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
Amat = Matrix([[1, I], [-I, 1]])
|
||||
Bmat = Matrix([[1, 2], [3, 4]])
|
||||
Avec = Matrix([[1], [I]])
|
||||
|
||||
|
||||
class AKet(Ket):
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return ABra
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_AOp(None, **options)
|
||||
|
||||
def _represent_AOp(self, basis, **options):
|
||||
return Avec
|
||||
|
||||
|
||||
class ABra(Bra):
|
||||
|
||||
@classmethod
|
||||
def dual_class(self):
|
||||
return AKet
|
||||
|
||||
|
||||
class AOp(Operator):
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_AOp(None, **options)
|
||||
|
||||
def _represent_AOp(self, basis, **options):
|
||||
return Amat
|
||||
|
||||
|
||||
class BOp(Operator):
|
||||
|
||||
def _represent_default_basis(self, **options):
|
||||
return self._represent_AOp(None, **options)
|
||||
|
||||
def _represent_AOp(self, basis, **options):
|
||||
return Bmat
|
||||
|
||||
|
||||
k = AKet('a')
|
||||
b = ABra('a')
|
||||
A = AOp('A')
|
||||
B = BOp('B')
|
||||
|
||||
_tests = [
|
||||
# Bra
|
||||
(b, Dagger(Avec)),
|
||||
(Dagger(b), Avec),
|
||||
# Ket
|
||||
(k, Avec),
|
||||
(Dagger(k), Dagger(Avec)),
|
||||
# Operator
|
||||
(A, Amat),
|
||||
(Dagger(A), Dagger(Amat)),
|
||||
# OuterProduct
|
||||
(OuterProduct(k, b), Avec*Avec.H),
|
||||
# TensorProduct
|
||||
(TensorProduct(A, B), matrix_tensor_product(Amat, Bmat)),
|
||||
# Pow
|
||||
(A**2, Amat**2),
|
||||
# Add/Mul
|
||||
(A*B + 2*A, Amat*Bmat + 2*Amat),
|
||||
# Commutator
|
||||
(Commutator(A, B), Amat*Bmat - Bmat*Amat),
|
||||
# AntiCommutator
|
||||
(AntiCommutator(A, B), Amat*Bmat + Bmat*Amat),
|
||||
# InnerProduct
|
||||
(InnerProduct(b, k), (Avec.H*Avec)[0])
|
||||
]
|
||||
|
||||
|
||||
def test_format_sympy():
|
||||
for test in _tests:
|
||||
lhs = represent(test[0], basis=A, format='sympy')
|
||||
rhs = to_sympy(test[1])
|
||||
assert lhs == rhs
|
||||
|
||||
|
||||
def test_scalar_sympy():
|
||||
assert represent(Integer(1)) == Integer(1)
|
||||
assert represent(Float(1.0)) == Float(1.0)
|
||||
assert represent(1.0 + I) == 1.0 + I
|
||||
|
||||
|
||||
np = import_module('numpy')
|
||||
|
||||
|
||||
def test_format_numpy():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
|
||||
for test in _tests:
|
||||
lhs = represent(test[0], basis=A, format='numpy')
|
||||
rhs = to_numpy(test[1])
|
||||
if isinstance(lhs, numpy_ndarray):
|
||||
assert (lhs == rhs).all()
|
||||
else:
|
||||
assert lhs == rhs
|
||||
|
||||
|
||||
def test_scalar_numpy():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
|
||||
assert represent(Integer(1), format='numpy') == 1
|
||||
assert represent(Float(1.0), format='numpy') == 1.0
|
||||
assert represent(1.0 + I, format='numpy') == 1.0 + 1.0j
|
||||
|
||||
|
||||
scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
|
||||
|
||||
|
||||
def test_format_scipy_sparse():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
if not scipy:
|
||||
skip("scipy not installed.")
|
||||
|
||||
for test in _tests:
|
||||
lhs = represent(test[0], basis=A, format='scipy.sparse')
|
||||
rhs = to_scipy_sparse(test[1])
|
||||
if isinstance(lhs, scipy_sparse_matrix):
|
||||
assert np.linalg.norm((lhs - rhs).todense()) == 0.0
|
||||
else:
|
||||
assert lhs == rhs
|
||||
|
||||
|
||||
def test_scalar_scipy_sparse():
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
if not scipy:
|
||||
skip("scipy not installed.")
|
||||
|
||||
assert represent(Integer(1), format='scipy.sparse') == 1
|
||||
assert represent(Float(1.0), format='scipy.sparse') == 1.0
|
||||
assert represent(1.0 + I, format='scipy.sparse') == 1.0 + 1.0j
|
||||
|
||||
x_ket = XKet('x')
|
||||
x_bra = XBra('x')
|
||||
x_op = XOp('X')
|
||||
|
||||
|
||||
def test_innerprod_represent():
|
||||
assert rep_innerproduct(x_ket) == InnerProduct(XBra("x_1"), x_ket).doit()
|
||||
assert rep_innerproduct(x_bra) == InnerProduct(x_bra, XKet("x_1")).doit()
|
||||
raises(TypeError, lambda: rep_innerproduct(x_op))
|
||||
|
||||
|
||||
def test_operator_represent():
|
||||
basis_kets = enumerate_states(operators_to_state(x_op), 1, 2)
|
||||
assert rep_expectation(
|
||||
x_op) == qapply(basis_kets[1].dual*x_op*basis_kets[0])
|
||||
|
||||
|
||||
def test_enumerate_states():
|
||||
test = XKet("foo")
|
||||
assert enumerate_states(test, 1, 1) == [XKet("foo_1")]
|
||||
assert enumerate_states(
|
||||
test, [1, 2, 4]) == [XKet("foo_1"), XKet("foo_2"), XKet("foo_4")]
|
||||
@@ -0,0 +1,176 @@
|
||||
"""Tests for sho1d.py"""
|
||||
|
||||
from sympy.concrete import Sum
|
||||
from sympy.core import oo
|
||||
from sympy.core.numbers import (I, Integer)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol, symbols
|
||||
from sympy.functions.combinatorial.factorials import factorial
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.physics.quantum import Dagger
|
||||
from sympy.physics.quantum.constants import hbar
|
||||
from sympy.physics.quantum import Commutator
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.innerproduct import InnerProduct
|
||||
from sympy.physics.quantum.cartesian import X, Px
|
||||
from sympy.physics.quantum.hilbert import ComplexSpace
|
||||
from sympy.physics.quantum.represent import represent
|
||||
from sympy.simplify import simplify
|
||||
from sympy.external import import_module
|
||||
from sympy.tensor import IndexedBase, Idx
|
||||
from sympy.testing.pytest import skip, raises
|
||||
|
||||
from sympy.physics.quantum.sho1d import (RaisingOp, LoweringOp,
|
||||
SHOKet, SHOBra,
|
||||
Hamiltonian, NumberOp)
|
||||
|
||||
ad = RaisingOp('a')
|
||||
a = LoweringOp('a')
|
||||
k = SHOKet('k')
|
||||
kz = SHOKet(0)
|
||||
kf = SHOKet(1)
|
||||
k3 = SHOKet(3)
|
||||
b = SHOBra('b')
|
||||
b3 = SHOBra(3)
|
||||
H = Hamiltonian('H')
|
||||
N = NumberOp('N')
|
||||
omega = Symbol('omega')
|
||||
m = Symbol('m')
|
||||
ndim = Integer(4)
|
||||
p = Symbol('p', integer=True)
|
||||
q = Symbol('q', nonnegative=True, integer=True)
|
||||
|
||||
|
||||
np = import_module('numpy')
|
||||
scipy = import_module('scipy', import_kwargs={'fromlist': ['sparse']})
|
||||
|
||||
ad_rep_sympy = represent(ad, basis=N, ndim=4, format='sympy')
|
||||
a_rep = represent(a, basis=N, ndim=4, format='sympy')
|
||||
N_rep = represent(N, basis=N, ndim=4, format='sympy')
|
||||
H_rep = represent(H, basis=N, ndim=4, format='sympy')
|
||||
k3_rep = represent(k3, basis=N, ndim=4, format='sympy')
|
||||
b3_rep = represent(b3, basis=N, ndim=4, format='sympy')
|
||||
|
||||
def test_RaisingOp():
|
||||
assert Dagger(ad) == a
|
||||
assert Commutator(ad, a).doit() == Integer(-1)
|
||||
assert Commutator(ad, N).doit() == Integer(-1)*ad
|
||||
assert qapply(ad*k) == (sqrt(k.n + 1)*SHOKet(k.n + 1)).expand()
|
||||
assert qapply(ad*kz) == (sqrt(kz.n + 1)*SHOKet(kz.n + 1)).expand()
|
||||
assert qapply(ad*kf) == (sqrt(kf.n + 1)*SHOKet(kf.n + 1)).expand()
|
||||
assert ad.rewrite('xp').doit() == \
|
||||
(Integer(1)/sqrt(Integer(2)*hbar*m*omega))*(Integer(-1)*I*Px + m*omega*X)
|
||||
assert ad.hilbert_space == ComplexSpace(S.Infinity)
|
||||
for i in range(ndim - 1):
|
||||
assert ad_rep_sympy[i + 1,i] == sqrt(i + 1)
|
||||
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
|
||||
ad_rep_numpy = represent(ad, basis=N, ndim=4, format='numpy')
|
||||
for i in range(ndim - 1):
|
||||
assert ad_rep_numpy[i + 1,i] == float(sqrt(i + 1))
|
||||
|
||||
if not np:
|
||||
skip("numpy not installed.")
|
||||
if not scipy:
|
||||
skip("scipy not installed.")
|
||||
|
||||
ad_rep_scipy = represent(ad, basis=N, ndim=4, format='scipy.sparse', spmatrix='lil')
|
||||
for i in range(ndim - 1):
|
||||
assert ad_rep_scipy[i + 1,i] == float(sqrt(i + 1))
|
||||
|
||||
assert ad_rep_numpy.dtype == 'float64'
|
||||
assert ad_rep_scipy.dtype == 'float64'
|
||||
|
||||
def test_LoweringOp():
|
||||
assert Dagger(a) == ad
|
||||
assert Commutator(a, ad).doit() == Integer(1)
|
||||
assert Commutator(a, N).doit() == a
|
||||
assert qapply(a*k) == (sqrt(k.n)*SHOKet(k.n-Integer(1))).expand()
|
||||
assert qapply(a*kz) == Integer(0)
|
||||
assert qapply(a*kf) == (sqrt(kf.n)*SHOKet(kf.n-Integer(1))).expand()
|
||||
assert a.rewrite('xp').doit() == \
|
||||
(Integer(1)/sqrt(Integer(2)*hbar*m*omega))*(I*Px + m*omega*X)
|
||||
for i in range(ndim - 1):
|
||||
assert a_rep[i,i + 1] == sqrt(i + 1)
|
||||
|
||||
def test_NumberOp():
|
||||
assert Commutator(N, ad).doit() == ad
|
||||
assert Commutator(N, a).doit() == Integer(-1)*a
|
||||
assert Commutator(N, H).doit() == Integer(0)
|
||||
assert qapply(N*k) == (k.n*k).expand()
|
||||
assert N.rewrite('a').doit() == ad*a
|
||||
assert N.rewrite('xp').doit() == (Integer(1)/(Integer(2)*m*hbar*omega))*(
|
||||
Px**2 + (m*omega*X)**2) - Integer(1)/Integer(2)
|
||||
assert N.rewrite('H').doit() == H/(hbar*omega) - Integer(1)/Integer(2)
|
||||
for i in range(ndim):
|
||||
assert N_rep[i,i] == i
|
||||
assert N_rep == ad_rep_sympy*a_rep
|
||||
|
||||
def test_Hamiltonian():
|
||||
assert Commutator(H, N).doit() == Integer(0)
|
||||
assert qapply(H*k) == ((hbar*omega*(k.n + Integer(1)/Integer(2)))*k).expand()
|
||||
assert H.rewrite('a').doit() == hbar*omega*(ad*a + Integer(1)/Integer(2))
|
||||
assert H.rewrite('xp').doit() == \
|
||||
(Integer(1)/(Integer(2)*m))*(Px**2 + (m*omega*X)**2)
|
||||
assert H.rewrite('N').doit() == hbar*omega*(N + Integer(1)/Integer(2))
|
||||
for i in range(ndim):
|
||||
assert H_rep[i,i] == hbar*omega*(i + Integer(1)/Integer(2))
|
||||
|
||||
def test_SHOKet():
|
||||
assert SHOKet('k').dual_class() == SHOBra
|
||||
assert SHOBra('b').dual_class() == SHOKet
|
||||
assert InnerProduct(b,k).doit() == KroneckerDelta(k.n, b.n)
|
||||
assert k.hilbert_space == ComplexSpace(S.Infinity)
|
||||
assert k3_rep[k3.n, 0] == Integer(1)
|
||||
assert b3_rep[0, b3.n] == Integer(1)
|
||||
|
||||
def test_sho_sums():
|
||||
e1 = Sum(SHOKet(p)*SHOBra(p), (p, 0, 1))
|
||||
assert e1.doit() == SHOKet(0)*SHOBra(0) + SHOKet(1)*SHOBra(1)
|
||||
|
||||
# Test qapply with Sum on the left
|
||||
assert qapply(
|
||||
Sum(SHOKet(p)*SHOBra(p), (p, 0, oo))*SHOKet(q),
|
||||
sum_doit=True
|
||||
) == SHOKet(q)
|
||||
|
||||
# Test qapply with Sum on the right
|
||||
a = IndexedBase('a')
|
||||
n = symbols('n', cls=Idx)
|
||||
result = qapply(SHOBra(q)*Sum(a[n]*SHOKet(n), (n,0,oo)), sum_doit=True)
|
||||
assert result == a[q]
|
||||
|
||||
# Test qapply with a product of Sums
|
||||
result = qapply(
|
||||
SHOBra(q)*Sum(SHOKet(p)*SHOBra(p), (p, 0, oo))*Sum(a[n]*SHOKet(n), (n,0,oo)),
|
||||
sum_doit=True
|
||||
)
|
||||
assert result == a[q]
|
||||
|
||||
with raises(ValueError):
|
||||
result = qapply(
|
||||
SHOBra(q)*Sum(SHOKet(p)*SHOBra(p), (p, 0, oo))*Sum(a[p]*SHOKet(p), (p,0,oo)),
|
||||
sum_doit=True
|
||||
)
|
||||
|
||||
def test_sho_coherant_state():
|
||||
alpha = Symbol('alpha', is_complex=True)
|
||||
cstate = exp(-Abs(alpha)**2/S(2))*Sum(((alpha**p)/sqrt(factorial(p)))*SHOKet(p), (p,0,oo))
|
||||
# Projection onto the number eigenstate
|
||||
assert qapply(SHOBra(q)*cstate, sum_doit=True) == exp(-Abs(alpha)**2/S(2))*alpha**q/sqrt(factorial(q))
|
||||
# Ensure that the coherent state is an eigenstate of annihilation operator
|
||||
assert simplify(qapply(SHOBra(q)*a*cstate, sum_doit=True)) == simplify(qapply(SHOBra(q)*alpha*cstate, sum_doit=True))
|
||||
|
||||
def test_issue_26495():
|
||||
nbar = Symbol('nbar', real=True, nonnegative=True)
|
||||
n = Symbol('n', integer=True)
|
||||
i = Symbol('i', integer=True, nonnegative=True)
|
||||
j = Symbol('j', integer=True, nonnegative=True)
|
||||
rho = Sum((nbar/(1+nbar))**n*SHOKet(n)*SHOBra(n), (n,0,oo))
|
||||
result = qapply(SHOBra(i)*rho*SHOKet(j), sum_doit=True)
|
||||
assert simplify(result) == (nbar/(nbar+1))**i*KroneckerDelta(i,j)
|
||||
@@ -0,0 +1,21 @@
|
||||
from sympy.testing.pytest import XFAIL
|
||||
|
||||
from sympy.physics.quantum.qapply import qapply
|
||||
from sympy.physics.quantum.qubit import Qubit
|
||||
from sympy.physics.quantum.shor import CMod, getr
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_CMod():
|
||||
assert qapply(CMod(4, 2, 2)*Qubit(0, 0, 1, 0, 0, 0, 0, 0)) == \
|
||||
Qubit(0, 0, 1, 0, 0, 0, 0, 0)
|
||||
assert qapply(CMod(5, 5, 7)*Qubit(0, 0, 1, 0, 0, 0, 0, 0, 0, 0)) == \
|
||||
Qubit(0, 0, 1, 0, 0, 0, 0, 0, 1, 0)
|
||||
assert qapply(CMod(3, 2, 3)*Qubit(0, 1, 0, 0, 0, 0)) == \
|
||||
Qubit(0, 1, 0, 0, 0, 1)
|
||||
|
||||
|
||||
def test_continued_frac():
|
||||
assert getr(513, 1024, 10) == 2
|
||||
assert getr(169, 1024, 11) == 6
|
||||
assert getr(314, 4096, 16) == 13
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,248 @@
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.function import diff
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.numbers import (I, Integer, Rational, oo, pi)
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.elementary.complexes import conjugate
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import sin
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.qexpr import QExpr
|
||||
from sympy.physics.quantum.state import (
|
||||
Ket, Bra, TimeDepKet, TimeDepBra,
|
||||
KetBase, BraBase, StateBase, Wavefunction,
|
||||
OrthogonalKet, OrthogonalBra
|
||||
)
|
||||
from sympy.physics.quantum.hilbert import HilbertSpace
|
||||
|
||||
x, y, t = symbols('x,y,t')
|
||||
|
||||
|
||||
class CustomKet(Ket):
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("test",)
|
||||
|
||||
|
||||
class CustomKetMultipleLabels(Ket):
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("r", "theta", "phi")
|
||||
|
||||
|
||||
class CustomTimeDepKet(TimeDepKet):
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("test", "t")
|
||||
|
||||
|
||||
class CustomTimeDepKetMultipleLabels(TimeDepKet):
|
||||
@classmethod
|
||||
def default_args(self):
|
||||
return ("r", "theta", "phi", "t")
|
||||
|
||||
|
||||
def test_ket():
|
||||
k = Ket('0')
|
||||
|
||||
assert isinstance(k, Ket)
|
||||
assert isinstance(k, KetBase)
|
||||
assert isinstance(k, StateBase)
|
||||
assert isinstance(k, QExpr)
|
||||
|
||||
assert k.label == (Symbol('0'),)
|
||||
assert k.hilbert_space == HilbertSpace()
|
||||
assert k.is_commutative is False
|
||||
|
||||
# Make sure this doesn't get converted to the number pi.
|
||||
k = Ket('pi')
|
||||
assert k.label == (Symbol('pi'),)
|
||||
|
||||
k = Ket(x, y)
|
||||
assert k.label == (x, y)
|
||||
assert k.hilbert_space == HilbertSpace()
|
||||
assert k.is_commutative is False
|
||||
|
||||
assert k.dual_class() == Bra
|
||||
assert k.dual == Bra(x, y)
|
||||
assert k.subs(x, y) == Ket(y, y)
|
||||
|
||||
k = CustomKet()
|
||||
assert k == CustomKet("test")
|
||||
|
||||
k = CustomKetMultipleLabels()
|
||||
assert k == CustomKetMultipleLabels("r", "theta", "phi")
|
||||
|
||||
assert Ket() == Ket('psi')
|
||||
|
||||
|
||||
def test_bra():
|
||||
b = Bra('0')
|
||||
|
||||
assert isinstance(b, Bra)
|
||||
assert isinstance(b, BraBase)
|
||||
assert isinstance(b, StateBase)
|
||||
assert isinstance(b, QExpr)
|
||||
|
||||
assert b.label == (Symbol('0'),)
|
||||
assert b.hilbert_space == HilbertSpace()
|
||||
assert b.is_commutative is False
|
||||
|
||||
# Make sure this doesn't get converted to the number pi.
|
||||
b = Bra('pi')
|
||||
assert b.label == (Symbol('pi'),)
|
||||
|
||||
b = Bra(x, y)
|
||||
assert b.label == (x, y)
|
||||
assert b.hilbert_space == HilbertSpace()
|
||||
assert b.is_commutative is False
|
||||
|
||||
assert b.dual_class() == Ket
|
||||
assert b.dual == Ket(x, y)
|
||||
assert b.subs(x, y) == Bra(y, y)
|
||||
|
||||
assert Bra() == Bra('psi')
|
||||
|
||||
|
||||
def test_ops():
|
||||
k0 = Ket(0)
|
||||
k1 = Ket(1)
|
||||
k = 2*I*k0 - (x/sqrt(2))*k1
|
||||
assert k == Add(Mul(2, I, k0),
|
||||
Mul(Rational(-1, 2), x, Pow(2, S.Half), k1))
|
||||
|
||||
|
||||
def test_time_dep_ket():
|
||||
k = TimeDepKet(0, t)
|
||||
|
||||
assert isinstance(k, TimeDepKet)
|
||||
assert isinstance(k, KetBase)
|
||||
assert isinstance(k, StateBase)
|
||||
assert isinstance(k, QExpr)
|
||||
|
||||
assert k.label == (Integer(0),)
|
||||
assert k.args == (Integer(0), t)
|
||||
assert k.time == t
|
||||
|
||||
assert k.dual_class() == TimeDepBra
|
||||
assert k.dual == TimeDepBra(0, t)
|
||||
|
||||
assert k.subs(t, 2) == TimeDepKet(0, 2)
|
||||
|
||||
k = TimeDepKet(x, 0.5)
|
||||
assert k.label == (x,)
|
||||
assert k.args == (x, sympify(0.5))
|
||||
|
||||
k = CustomTimeDepKet()
|
||||
assert k.label == (Symbol("test"),)
|
||||
assert k.time == Symbol("t")
|
||||
assert k == CustomTimeDepKet("test", "t")
|
||||
|
||||
k = CustomTimeDepKetMultipleLabels()
|
||||
assert k.label == (Symbol("r"), Symbol("theta"), Symbol("phi"))
|
||||
assert k.time == Symbol("t")
|
||||
assert k == CustomTimeDepKetMultipleLabels("r", "theta", "phi", "t")
|
||||
|
||||
assert TimeDepKet() == TimeDepKet("psi", "t")
|
||||
|
||||
|
||||
def test_time_dep_bra():
|
||||
b = TimeDepBra(0, t)
|
||||
|
||||
assert isinstance(b, TimeDepBra)
|
||||
assert isinstance(b, BraBase)
|
||||
assert isinstance(b, StateBase)
|
||||
assert isinstance(b, QExpr)
|
||||
|
||||
assert b.label == (Integer(0),)
|
||||
assert b.args == (Integer(0), t)
|
||||
assert b.time == t
|
||||
|
||||
assert b.dual_class() == TimeDepKet
|
||||
assert b.dual == TimeDepKet(0, t)
|
||||
|
||||
k = TimeDepBra(x, 0.5)
|
||||
assert k.label == (x,)
|
||||
assert k.args == (x, sympify(0.5))
|
||||
|
||||
assert TimeDepBra() == TimeDepBra("psi", "t")
|
||||
|
||||
|
||||
def test_bra_ket_dagger():
|
||||
x = symbols('x', complex=True)
|
||||
k = Ket('k')
|
||||
b = Bra('b')
|
||||
assert Dagger(k) == Bra('k')
|
||||
assert Dagger(b) == Ket('b')
|
||||
assert Dagger(k).is_commutative is False
|
||||
|
||||
k2 = Ket('k2')
|
||||
e = 2*I*k + x*k2
|
||||
assert Dagger(e) == conjugate(x)*Dagger(k2) - 2*I*Dagger(k)
|
||||
|
||||
|
||||
def test_wavefunction():
|
||||
x, y = symbols('x y', real=True)
|
||||
L = symbols('L', positive=True)
|
||||
n = symbols('n', integer=True, positive=True)
|
||||
|
||||
f = Wavefunction(x**2, x)
|
||||
p = f.prob()
|
||||
lims = f.limits
|
||||
|
||||
assert f.is_normalized is False
|
||||
assert f.norm is oo
|
||||
assert f(10) == 100
|
||||
assert p(10) == 10000
|
||||
assert lims[x] == (-oo, oo)
|
||||
assert diff(f, x) == Wavefunction(2*x, x)
|
||||
raises(NotImplementedError, lambda: f.normalize())
|
||||
assert conjugate(f) == Wavefunction(conjugate(f.expr), x)
|
||||
assert conjugate(f) == Dagger(f)
|
||||
|
||||
g = Wavefunction(x**2*y + y**2*x, (x, 0, 1), (y, 0, 2))
|
||||
lims_g = g.limits
|
||||
|
||||
assert lims_g[x] == (0, 1)
|
||||
assert lims_g[y] == (0, 2)
|
||||
assert g.is_normalized is False
|
||||
assert g.norm == sqrt(42)/3
|
||||
assert g(2, 4) == 0
|
||||
assert g(1, 1) == 2
|
||||
assert diff(diff(g, x), y) == Wavefunction(2*x + 2*y, (x, 0, 1), (y, 0, 2))
|
||||
assert conjugate(g) == Wavefunction(conjugate(g.expr), *g.args[1:])
|
||||
assert conjugate(g) == Dagger(g)
|
||||
|
||||
h = Wavefunction(sqrt(5)*x**2, (x, 0, 1))
|
||||
assert h.is_normalized is True
|
||||
assert h.normalize() == h
|
||||
assert conjugate(h) == Wavefunction(conjugate(h.expr), (x, 0, 1))
|
||||
assert conjugate(h) == Dagger(h)
|
||||
|
||||
piab = Wavefunction(sin(n*pi*x/L), (x, 0, L))
|
||||
assert piab.norm == sqrt(L/2)
|
||||
assert piab(L + 1) == 0
|
||||
assert piab(0.5) == sin(0.5*n*pi/L)
|
||||
assert piab(0.5, n=1, L=1) == sin(0.5*pi)
|
||||
assert piab.normalize() == \
|
||||
Wavefunction(sqrt(2)/sqrt(L)*sin(n*pi*x/L), (x, 0, L))
|
||||
assert conjugate(piab) == Wavefunction(conjugate(piab.expr), (x, 0, L))
|
||||
assert conjugate(piab) == Dagger(piab)
|
||||
|
||||
k = Wavefunction(x**2, 'x')
|
||||
assert type(k.variables[0]) == Symbol
|
||||
|
||||
def test_orthogonal_states():
|
||||
bracket = OrthogonalBra(x) * OrthogonalKet(x)
|
||||
assert bracket.doit() == 1
|
||||
|
||||
bracket = OrthogonalBra(x) * OrthogonalKet(x+1)
|
||||
assert bracket.doit() == 0
|
||||
|
||||
bracket = OrthogonalBra(x) * OrthogonalKet(y)
|
||||
assert bracket.doit() == bracket
|
||||
@@ -0,0 +1,142 @@
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.matrices import Matrix, SparseMatrix, ImmutableMatrix
|
||||
from sympy.testing.pytest import warns_deprecated_sympy
|
||||
|
||||
from sympy.physics.quantum.commutator import Commutator as Comm
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct as TP
|
||||
from sympy.physics.quantum.tensorproduct import tensor_product_simp
|
||||
from sympy.physics.quantum.dagger import Dagger
|
||||
from sympy.physics.quantum.qubit import Qubit, QubitBra
|
||||
from sympy.physics.quantum.operator import OuterProduct, Operator
|
||||
from sympy.physics.quantum.density import Density
|
||||
from sympy.physics.quantum.trace import Tr
|
||||
|
||||
A = Operator('A')
|
||||
B = Operator('B')
|
||||
C = Operator('C')
|
||||
D = Operator('D')
|
||||
x = symbols('x')
|
||||
y = symbols('y', integer=True, positive=True)
|
||||
|
||||
mat1 = Matrix([[1, 2*I], [1 + I, 3]])
|
||||
mat2 = Matrix([[2*I, 3], [4*I, 2]])
|
||||
|
||||
|
||||
def test_sparse_matrices():
|
||||
spm = SparseMatrix.diag(1, 0)
|
||||
assert unchanged(TensorProduct, spm, spm)
|
||||
|
||||
|
||||
def test_tensor_product_dagger():
|
||||
assert Dagger(TensorProduct(I*A, B)) == \
|
||||
-I*TensorProduct(Dagger(A), Dagger(B))
|
||||
assert Dagger(TensorProduct(mat1, mat2)) == \
|
||||
TensorProduct(Dagger(mat1), Dagger(mat2))
|
||||
|
||||
|
||||
def test_tensor_product_abstract():
|
||||
|
||||
assert TP(x*A, 2*B) == x*2*TP(A, B)
|
||||
assert TP(A, B) != TP(B, A)
|
||||
assert TP(A, B).is_commutative is False
|
||||
assert isinstance(TP(A, B), TP)
|
||||
assert TP(A, B).subs(A, C) == TP(C, B)
|
||||
|
||||
|
||||
def test_tensor_product_expand():
|
||||
assert TP(A + B, B + C).expand(tensorproduct=True) == \
|
||||
TP(A, B) + TP(A, C) + TP(B, B) + TP(B, C)
|
||||
#Tests for fix of issue #24142
|
||||
assert TP(A-B, B-A).expand(tensorproduct=True) == \
|
||||
TP(A, B) - TP(A, A) - TP(B, B) + TP(B, A)
|
||||
assert TP(2*A + B, A + B).expand(tensorproduct=True) == \
|
||||
2 * TP(A, A) + 2 * TP(A, B) + TP(B, A) + TP(B, B)
|
||||
assert TP(2 * A * B + A, A + B).expand(tensorproduct=True) == \
|
||||
2 * TP(A*B, A) + 2 * TP(A*B, B) + TP(A, A) + TP(A, B)
|
||||
|
||||
|
||||
def test_tensor_product_commutator():
|
||||
assert TP(Comm(A, B), C).doit().expand(tensorproduct=True) == \
|
||||
TP(A*B, C) - TP(B*A, C)
|
||||
assert Comm(TP(A, B), TP(B, C)).doit() == \
|
||||
TP(A, B)*TP(B, C) - TP(B, C)*TP(A, B)
|
||||
|
||||
|
||||
def test_tensor_product_simp():
|
||||
with warns_deprecated_sympy():
|
||||
assert tensor_product_simp(TP(A, B)*TP(B, C)) == TP(A*B, B*C)
|
||||
# tests for Pow-expressions
|
||||
assert TP(A, B)**y == TP(A**y, B**y)
|
||||
assert tensor_product_simp(TP(A, B)**y) == TP(A**y, B**y)
|
||||
assert tensor_product_simp(x*TP(A, B)**2) == x*TP(A**2,B**2)
|
||||
assert tensor_product_simp(x*(TP(A, B)**2)*TP(C,D)) == x*TP(A**2*C,B**2*D)
|
||||
assert tensor_product_simp(TP(A,B)-TP(C,D)**y) == TP(A,B)-TP(C**y,D**y)
|
||||
|
||||
|
||||
def test_issue_5923():
|
||||
# most of the issue regarding sympification of args has been handled
|
||||
# and is tested internally by the use of args_cnc through the quantum
|
||||
# module, but the following is a test from the issue that used to raise.
|
||||
assert TensorProduct(1, Qubit('1')*Qubit('1').dual) == \
|
||||
TensorProduct(1, OuterProduct(Qubit(1), QubitBra(1)))
|
||||
|
||||
|
||||
def test_eval_trace():
|
||||
# This test includes tests with dependencies between TensorProducts
|
||||
#and density operators. Since, the test is more to test the behavior of
|
||||
#TensorProducts it remains here
|
||||
|
||||
# Density with simple tensor products as args
|
||||
t = TensorProduct(A, B)
|
||||
d = Density([t, 1.0])
|
||||
tr = Tr(d)
|
||||
assert tr.doit() == 1.0*Tr(A*Dagger(A))*Tr(B*Dagger(B))
|
||||
|
||||
## partial trace with simple tensor products as args
|
||||
t = TensorProduct(A, B, C)
|
||||
d = Density([t, 1.0])
|
||||
tr = Tr(d, [1])
|
||||
assert tr.doit() == 1.0*A*Dagger(A)*Tr(B*Dagger(B))*C*Dagger(C)
|
||||
|
||||
tr = Tr(d, [0, 2])
|
||||
assert tr.doit() == 1.0*Tr(A*Dagger(A))*B*Dagger(B)*Tr(C*Dagger(C))
|
||||
|
||||
# Density with multiple Tensorproducts as states
|
||||
t2 = TensorProduct(A, B)
|
||||
t3 = TensorProduct(C, D)
|
||||
|
||||
d = Density([t2, 0.5], [t3, 0.5])
|
||||
t = Tr(d)
|
||||
assert t.doit() == (0.5*Tr(A*Dagger(A))*Tr(B*Dagger(B)) +
|
||||
0.5*Tr(C*Dagger(C))*Tr(D*Dagger(D)))
|
||||
|
||||
t = Tr(d, [0])
|
||||
assert t.doit() == (0.5*Tr(A*Dagger(A))*B*Dagger(B) +
|
||||
0.5*Tr(C*Dagger(C))*D*Dagger(D))
|
||||
|
||||
#Density with mixed states
|
||||
d = Density([t2 + t3, 1.0])
|
||||
t = Tr(d)
|
||||
assert t.doit() == ( 1.0*Tr(A*Dagger(A))*Tr(B*Dagger(B)) +
|
||||
1.0*Tr(A*Dagger(C))*Tr(B*Dagger(D)) +
|
||||
1.0*Tr(C*Dagger(A))*Tr(D*Dagger(B)) +
|
||||
1.0*Tr(C*Dagger(C))*Tr(D*Dagger(D)))
|
||||
|
||||
t = Tr(d, [1] )
|
||||
assert t.doit() == ( 1.0*A*Dagger(A)*Tr(B*Dagger(B)) +
|
||||
1.0*A*Dagger(C)*Tr(B*Dagger(D)) +
|
||||
1.0*C*Dagger(A)*Tr(D*Dagger(B)) +
|
||||
1.0*C*Dagger(C)*Tr(D*Dagger(D)))
|
||||
|
||||
|
||||
def test_pr24993():
|
||||
from sympy.matrices.expressions.kronecker import matrix_kronecker_product
|
||||
from sympy.physics.quantum.matrixutils import matrix_tensor_product
|
||||
X = Matrix([[0, 1], [1, 0]])
|
||||
Xi = ImmutableMatrix(X)
|
||||
assert TensorProduct(Xi, Xi) == TensorProduct(X, X)
|
||||
assert TensorProduct(Xi, Xi) == matrix_tensor_product(X, X)
|
||||
assert TensorProduct(Xi, Xi) == matrix_kronecker_product(X, X)
|
||||
@@ -0,0 +1,109 @@
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.physics.quantum.trace import Tr
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
|
||||
|
||||
def test_trace_new():
|
||||
a, b, c, d, Y = symbols('a b c d Y')
|
||||
A, B, C, D = symbols('A B C D', commutative=False)
|
||||
|
||||
assert Tr(a + b) == a + b
|
||||
assert Tr(A + B) == Tr(A) + Tr(B)
|
||||
|
||||
#check trace args not implicitly permuted
|
||||
assert Tr(C*D*A*B).args[0].args == (C, D, A, B)
|
||||
|
||||
# check for mul and adds
|
||||
assert Tr((a*b) + ( c*d)) == (a*b) + (c*d)
|
||||
# Tr(scalar*A) = scalar*Tr(A)
|
||||
assert Tr(a*A) == a*Tr(A)
|
||||
assert Tr(a*A*B*b) == a*b*Tr(A*B)
|
||||
|
||||
# since A is symbol and not commutative
|
||||
assert isinstance(Tr(A), Tr)
|
||||
|
||||
#POW
|
||||
assert Tr(pow(a, b)) == a**b
|
||||
assert isinstance(Tr(pow(A, a)), Tr)
|
||||
|
||||
#Matrix
|
||||
M = Matrix([[1, 1], [2, 2]])
|
||||
assert Tr(M) == 3
|
||||
|
||||
##test indices in different forms
|
||||
#no index
|
||||
t = Tr(A)
|
||||
assert t.args[1] == Tuple()
|
||||
|
||||
#single index
|
||||
t = Tr(A, 0)
|
||||
assert t.args[1] == Tuple(0)
|
||||
|
||||
#index in a list
|
||||
t = Tr(A, [0])
|
||||
assert t.args[1] == Tuple(0)
|
||||
|
||||
t = Tr(A, [0, 1, 2])
|
||||
assert t.args[1] == Tuple(0, 1, 2)
|
||||
|
||||
#index is tuple
|
||||
t = Tr(A, (0))
|
||||
assert t.args[1] == Tuple(0)
|
||||
|
||||
t = Tr(A, (1, 2))
|
||||
assert t.args[1] == Tuple(1, 2)
|
||||
|
||||
#trace indices test
|
||||
t = Tr((A + B), [2])
|
||||
assert t.args[0].args[1] == Tuple(2) and t.args[1].args[1] == Tuple(2)
|
||||
|
||||
t = Tr(a*A, [2, 3])
|
||||
assert t.args[1].args[1] == Tuple(2, 3)
|
||||
|
||||
#class with trace method defined
|
||||
#to simulate numpy objects
|
||||
class Foo:
|
||||
def trace(self):
|
||||
return 1
|
||||
assert Tr(Foo()) == 1
|
||||
|
||||
#argument test
|
||||
# check for value error, when either/both arguments are not provided
|
||||
raises(ValueError, lambda: Tr())
|
||||
raises(ValueError, lambda: Tr(A, 1, 2))
|
||||
|
||||
|
||||
def test_trace_doit():
|
||||
a, b, c, d = symbols('a b c d')
|
||||
A, B, C, D = symbols('A B C D', commutative=False)
|
||||
|
||||
#TODO: needed while testing reduced density operations, etc.
|
||||
|
||||
|
||||
def test_permute():
|
||||
A, B, C, D, E, F, G = symbols('A B C D E F G', commutative=False)
|
||||
t = Tr(A*B*C*D*E*F*G)
|
||||
|
||||
assert t.permute(0).args[0].args == (A, B, C, D, E, F, G)
|
||||
assert t.permute(2).args[0].args == (F, G, A, B, C, D, E)
|
||||
assert t.permute(4).args[0].args == (D, E, F, G, A, B, C)
|
||||
assert t.permute(6).args[0].args == (B, C, D, E, F, G, A)
|
||||
assert t.permute(8).args[0].args == t.permute(1).args[0].args
|
||||
|
||||
assert t.permute(-1).args[0].args == (B, C, D, E, F, G, A)
|
||||
assert t.permute(-3).args[0].args == (D, E, F, G, A, B, C)
|
||||
assert t.permute(-5).args[0].args == (F, G, A, B, C, D, E)
|
||||
assert t.permute(-8).args[0].args == t.permute(-1).args[0].args
|
||||
|
||||
t = Tr((A + B)*(B*B)*C*D)
|
||||
assert t.permute(2).args[0].args == (C, D, (A + B), (B**2))
|
||||
|
||||
t1 = Tr(A*B)
|
||||
t2 = t1.permute(1)
|
||||
assert id(t1) != id(t2) and t1 == t2
|
||||
|
||||
def test_deprecated_core_trace():
|
||||
with warns_deprecated_sympy():
|
||||
from sympy.core.trace import Tr # noqa:F401
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Tests of transforms of quantum expressions for Mul and Pow."""
|
||||
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
from sympy.physics.quantum.operator import (
|
||||
Operator, OuterProduct
|
||||
)
|
||||
from sympy.physics.quantum.state import Ket, Bra
|
||||
from sympy.physics.quantum.innerproduct import InnerProduct
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct
|
||||
|
||||
|
||||
k1 = Ket('k1')
|
||||
k2 = Ket('k2')
|
||||
k3 = Ket('k3')
|
||||
b1 = Bra('b1')
|
||||
b2 = Bra('b2')
|
||||
b3 = Bra('b3')
|
||||
A = Operator('A')
|
||||
B = Operator('B')
|
||||
C = Operator('C')
|
||||
x, y, z = symbols('x y z')
|
||||
|
||||
|
||||
def test_bra_ket():
|
||||
assert b1*k1 == InnerProduct(b1, k1)
|
||||
assert k1*b1 == OuterProduct(k1, b1)
|
||||
# Test priority of inner product
|
||||
assert OuterProduct(k1, b1)*k2 == InnerProduct(b1, k2)*k1
|
||||
assert b1*OuterProduct(k1, b2) == InnerProduct(b1, k1)*b2
|
||||
|
||||
|
||||
def test_tensor_product():
|
||||
# We are attempting to be rigourous and raise TypeError when a user tries
|
||||
# to combine bras, kets, and operators in a manner that doesn't make sense.
|
||||
# In particular, we are not trying to interpret regular ``*`` multiplication
|
||||
# as a tensor product.
|
||||
with raises(TypeError):
|
||||
k1*k1
|
||||
with raises(TypeError):
|
||||
b1*b1
|
||||
with raises(TypeError):
|
||||
k1*TensorProduct(k2, k3)
|
||||
with raises(TypeError):
|
||||
b1*TensorProduct(b2, b3)
|
||||
with raises(TypeError):
|
||||
TensorProduct(k2, k3)*k1
|
||||
with raises(TypeError):
|
||||
TensorProduct(b2, b3)*b1
|
||||
|
||||
assert TensorProduct(A, B, C)*TensorProduct(k1, k2, k3) == \
|
||||
TensorProduct(A*k1, B*k2, C*k3)
|
||||
assert TensorProduct(b1, b2, b3)*TensorProduct(A, B, C) == \
|
||||
TensorProduct(b1*A, b2*B, b3*C)
|
||||
assert TensorProduct(b1, b2, b3)*TensorProduct(k1, k2, k3) == \
|
||||
InnerProduct(b1, k1)*InnerProduct(b2, k2)*InnerProduct(b3, k3)
|
||||
assert TensorProduct(b1, b2, b3)*TensorProduct(A, B, C)*TensorProduct(k1, k2, k3) == \
|
||||
TensorProduct(b1*A*k1, b2*B*k2, b3*C*k3)
|
||||
|
||||
|
||||
def test_outer_product():
|
||||
assert OuterProduct(k1, b1)*OuterProduct(k2, b2) == \
|
||||
InnerProduct(b1, k2)*OuterProduct(k1, b2)
|
||||
|
||||
|
||||
def test_compound():
|
||||
e1 = b1*A*B*k1*b2*k2*b3
|
||||
assert e1 == InnerProduct(b2, k2)*b1*A*B*OuterProduct(k1, b3)
|
||||
|
||||
e2 = TensorProduct(k1, k2)*TensorProduct(b1, b2)
|
||||
assert e2 == TensorProduct(
|
||||
OuterProduct(k1, b1),
|
||||
OuterProduct(k2, b2)
|
||||
)
|
||||
@@ -0,0 +1,230 @@
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.matrices import Matrix
|
||||
|
||||
|
||||
def _is_scalar(e):
|
||||
""" Helper method used in Tr"""
|
||||
|
||||
# sympify to set proper attributes
|
||||
e = sympify(e)
|
||||
if isinstance(e, Expr):
|
||||
if (e.is_Integer or e.is_Float or
|
||||
e.is_Rational or e.is_Number or
|
||||
(e.is_Symbol and e.is_commutative)
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _cycle_permute(l):
|
||||
""" Cyclic permutations based on canonical ordering
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
This method does the sort based ascii values while
|
||||
a better approach would be to used lexicographic sort.
|
||||
|
||||
TODO: Handle condition such as symbols have subscripts/superscripts
|
||||
in case of lexicographic sort
|
||||
|
||||
"""
|
||||
|
||||
if len(l) == 1:
|
||||
return l
|
||||
|
||||
min_item = min(l, key=default_sort_key)
|
||||
indices = [i for i, x in enumerate(l) if x == min_item]
|
||||
|
||||
le = list(l)
|
||||
le.extend(l) # duplicate and extend string for easy processing
|
||||
|
||||
# adding the first min_item index back for easier looping
|
||||
indices.append(len(l) + indices[0])
|
||||
|
||||
# create sublist of items with first item as min_item and last_item
|
||||
# in each of the sublist is item just before the next occurrence of
|
||||
# minitem in the cycle formed.
|
||||
sublist = [[le[indices[i]:indices[i + 1]]] for i in
|
||||
range(len(indices) - 1)]
|
||||
|
||||
# we do comparison of strings by comparing elements
|
||||
# in each sublist
|
||||
idx = sublist.index(min(sublist))
|
||||
ordered_l = le[indices[idx]:indices[idx] + len(l)]
|
||||
|
||||
return ordered_l
|
||||
|
||||
|
||||
def _rearrange_args(l):
|
||||
""" this just moves the last arg to first position
|
||||
to enable expansion of args
|
||||
A,B,A ==> A**2,B
|
||||
"""
|
||||
if len(l) == 1:
|
||||
return l
|
||||
|
||||
x = list(l[-1:])
|
||||
x.extend(l[0:-1])
|
||||
return Mul(*x).args
|
||||
|
||||
|
||||
class Tr(Expr):
|
||||
""" Generic Trace operation than can trace over:
|
||||
|
||||
a) SymPy matrix
|
||||
b) operators
|
||||
c) outer products
|
||||
|
||||
Parameters
|
||||
==========
|
||||
o : operator, matrix, expr
|
||||
i : tuple/list indices (optional)
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
# TODO: Need to handle printing
|
||||
|
||||
a) Trace(A+B) = Tr(A) + Tr(B)
|
||||
b) Trace(scalar*Operator) = scalar*Trace(Operator)
|
||||
|
||||
>>> from sympy.physics.quantum.trace import Tr
|
||||
>>> from sympy import symbols, Matrix
|
||||
>>> a, b = symbols('a b', commutative=True)
|
||||
>>> A, B = symbols('A B', commutative=False)
|
||||
>>> Tr(a*A,[2])
|
||||
a*Tr(A)
|
||||
>>> m = Matrix([[1,2],[1,1]])
|
||||
>>> Tr(m)
|
||||
2
|
||||
|
||||
"""
|
||||
def __new__(cls, *args):
|
||||
""" Construct a Trace object.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
args = SymPy expression
|
||||
indices = tuple/list if indices, optional
|
||||
|
||||
"""
|
||||
|
||||
# expect no indices,int or a tuple/list/Tuple
|
||||
if (len(args) == 2):
|
||||
if not isinstance(args[1], (list, Tuple, tuple)):
|
||||
indices = Tuple(args[1])
|
||||
else:
|
||||
indices = Tuple(*args[1])
|
||||
|
||||
expr = args[0]
|
||||
elif (len(args) == 1):
|
||||
indices = Tuple()
|
||||
expr = args[0]
|
||||
else:
|
||||
raise ValueError("Arguments to Tr should be of form "
|
||||
"(expr[, [indices]])")
|
||||
|
||||
if isinstance(expr, Matrix):
|
||||
return expr.trace()
|
||||
elif hasattr(expr, 'trace') and callable(expr.trace):
|
||||
#for any objects that have trace() defined e.g numpy
|
||||
return expr.trace()
|
||||
elif isinstance(expr, Add):
|
||||
return Add(*[Tr(arg, indices) for arg in expr.args])
|
||||
elif isinstance(expr, Mul):
|
||||
c_part, nc_part = expr.args_cnc()
|
||||
if len(nc_part) == 0:
|
||||
return Mul(*c_part)
|
||||
else:
|
||||
obj = Expr.__new__(cls, Mul(*nc_part), indices )
|
||||
#this check is needed to prevent cached instances
|
||||
#being returned even if len(c_part)==0
|
||||
return Mul(*c_part)*obj if len(c_part) > 0 else obj
|
||||
elif isinstance(expr, Pow):
|
||||
if (_is_scalar(expr.args[0]) and
|
||||
_is_scalar(expr.args[1])):
|
||||
return expr
|
||||
else:
|
||||
return Expr.__new__(cls, expr, indices)
|
||||
else:
|
||||
if (_is_scalar(expr)):
|
||||
return expr
|
||||
|
||||
return Expr.__new__(cls, expr, indices)
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
expr = self.args[0]
|
||||
expr_kind = expr.kind
|
||||
return expr_kind.element_kind
|
||||
|
||||
def doit(self, **hints):
|
||||
""" Perform the trace operation.
|
||||
|
||||
#TODO: Current version ignores the indices set for partial trace.
|
||||
|
||||
>>> from sympy.physics.quantum.trace import Tr
|
||||
>>> from sympy.physics.quantum.operator import OuterProduct
|
||||
>>> from sympy.physics.quantum.spin import JzKet, JzBra
|
||||
>>> t = Tr(OuterProduct(JzKet(1,1), JzBra(1,1)))
|
||||
>>> t.doit()
|
||||
1
|
||||
|
||||
"""
|
||||
if hasattr(self.args[0], '_eval_trace'):
|
||||
return self.args[0]._eval_trace(indices=self.args[1])
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def is_number(self):
|
||||
# TODO : improve this implementation
|
||||
return True
|
||||
|
||||
#TODO: Review if the permute method is needed
|
||||
# and if it needs to return a new instance
|
||||
def permute(self, pos):
|
||||
""" Permute the arguments cyclically.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
pos : integer, if positive, shift-right, else shift-left
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.physics.quantum.trace import Tr
|
||||
>>> from sympy import symbols
|
||||
>>> A, B, C, D = symbols('A B C D', commutative=False)
|
||||
>>> t = Tr(A*B*C*D)
|
||||
>>> t.permute(2)
|
||||
Tr(C*D*A*B)
|
||||
>>> t.permute(-2)
|
||||
Tr(C*D*A*B)
|
||||
|
||||
"""
|
||||
if pos > 0:
|
||||
pos = pos % len(self.args[0].args)
|
||||
else:
|
||||
pos = -(abs(pos) % len(self.args[0].args))
|
||||
|
||||
args = list(self.args[0].args[-pos:] + self.args[0].args[0:-pos])
|
||||
|
||||
return Tr(Mul(*(args)))
|
||||
|
||||
def _hashable_content(self):
|
||||
if isinstance(self.args[0], Mul):
|
||||
args = _cycle_permute(_rearrange_args(self.args[0].args))
|
||||
else:
|
||||
args = [self.args[0]]
|
||||
|
||||
return tuple(args) + (self.args[1], )
|
||||
@@ -0,0 +1,291 @@
|
||||
"""Transforms that are always applied to quantum expressions.
|
||||
|
||||
This module uses the kind and _constructor_postprocessor_mapping APIs
|
||||
to transform different combinations of Operators, Bras, and Kets into
|
||||
Inner/Outer/TensorProducts. These transformations are registered
|
||||
with the postprocessing API of core classes like `Mul` and `Pow` and
|
||||
are always applied to any expression involving Bras, Kets, and
|
||||
Operators. This API replaces the custom `__mul__` and `__pow__`
|
||||
methods of the quantum classes, which were found to be inconsistent.
|
||||
|
||||
THIS IS EXPERIMENTAL.
|
||||
"""
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.singleton import S
|
||||
from sympy.multipledispatch.dispatcher import (
|
||||
Dispatcher, ambiguity_register_error_ignore_dup
|
||||
)
|
||||
from sympy.utilities.misc import debug
|
||||
|
||||
from sympy.physics.quantum.innerproduct import InnerProduct
|
||||
from sympy.physics.quantum.kind import KetKind, BraKind, OperatorKind
|
||||
from sympy.physics.quantum.operator import (
|
||||
OuterProduct, IdentityOperator, Operator
|
||||
)
|
||||
from sympy.physics.quantum.state import BraBase, KetBase, StateBase
|
||||
from sympy.physics.quantum.tensorproduct import TensorProduct
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Multipledispatch based transformed for Mul and Pow
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
_transform_state_pair = Dispatcher('_transform_state_pair')
|
||||
"""Transform a pair of expression in a Mul to their canonical form.
|
||||
|
||||
All functions that are registered with this dispatcher need to take
|
||||
two inputs and return either tuple of transformed outputs, or None if no
|
||||
transform is applied. The output tuple is inserted into the right place
|
||||
of the ``Mul`` that is being put into canonical form. It works something like
|
||||
the following:
|
||||
|
||||
``Mul(a, b, c, d, e, f) -> Mul(*(_transform_state_pair(a, b) + (c, d, e, f))))``
|
||||
|
||||
The transforms here are always applied when quantum objects are multiplied.
|
||||
|
||||
THIS IS EXPERIMENTAL.
|
||||
|
||||
However, users of ``sympy.physics.quantum`` can import this dispatcher and
|
||||
register their own transforms to control the canonical form of products
|
||||
of quantum expressions.
|
||||
"""
|
||||
|
||||
@_transform_state_pair.register(Expr, Expr)
|
||||
def _transform_expr(a, b):
|
||||
"""Default transformer that does nothing for base types."""
|
||||
return None
|
||||
|
||||
|
||||
# The identity times anything is the anything.
|
||||
_transform_state_pair.add(
|
||||
(IdentityOperator, Expr),
|
||||
lambda x, y: (y,),
|
||||
on_ambiguity=ambiguity_register_error_ignore_dup
|
||||
)
|
||||
_transform_state_pair.add(
|
||||
(Expr, IdentityOperator),
|
||||
lambda x, y: (x,),
|
||||
on_ambiguity=ambiguity_register_error_ignore_dup
|
||||
)
|
||||
_transform_state_pair.add(
|
||||
(IdentityOperator, IdentityOperator),
|
||||
lambda x, y: S.One,
|
||||
on_ambiguity=ambiguity_register_error_ignore_dup
|
||||
)
|
||||
|
||||
@_transform_state_pair.register(BraBase, KetBase)
|
||||
def _transform_bra_ket(a, b):
|
||||
"""Transform a bra*ket -> InnerProduct(bra, ket)."""
|
||||
return (InnerProduct(a, b),)
|
||||
|
||||
@_transform_state_pair.register(KetBase, BraBase)
|
||||
def _transform_ket_bra(a, b):
|
||||
"""Transform a keT*bra -> OuterProduct(ket, bra)."""
|
||||
return (OuterProduct(a, b),)
|
||||
|
||||
@_transform_state_pair.register(KetBase, KetBase)
|
||||
def _transform_ket_ket(a, b):
|
||||
"""Raise a TypeError if a user tries to multiply two kets.
|
||||
|
||||
Multiplication based on `*` is not a shorthand for tensor products.
|
||||
"""
|
||||
raise TypeError(
|
||||
'Multiplication of two kets is not allowed. Use TensorProduct instead.'
|
||||
)
|
||||
|
||||
@_transform_state_pair.register(BraBase, BraBase)
|
||||
def _transform_bra_bra(a, b):
|
||||
"""Raise a TypeError if a user tries to multiply two bras.
|
||||
|
||||
Multiplication based on `*` is not a shorthand for tensor products.
|
||||
"""
|
||||
raise TypeError(
|
||||
'Multiplication of two bras is not allowed. Use TensorProduct instead.'
|
||||
)
|
||||
|
||||
@_transform_state_pair.register(OuterProduct, KetBase)
|
||||
def _transform_op_ket(a, b):
|
||||
return (InnerProduct(a.bra, b), a.ket)
|
||||
|
||||
@_transform_state_pair.register(BraBase, OuterProduct)
|
||||
def _transform_bra_op(a, b):
|
||||
return (InnerProduct(a, b.ket), b.bra)
|
||||
|
||||
@_transform_state_pair.register(TensorProduct, KetBase)
|
||||
def _transform_tp_ket(a, b):
|
||||
"""Raise a TypeError if a user tries to multiply TensorProduct(*kets)*ket.
|
||||
|
||||
Multiplication based on `*` is not a shorthand for tensor products.
|
||||
"""
|
||||
if a.kind == KetKind:
|
||||
raise TypeError(
|
||||
'Multiplication of TensorProduct(*kets)*ket is invalid.'
|
||||
)
|
||||
|
||||
@_transform_state_pair.register(KetBase, TensorProduct)
|
||||
def _transform_ket_tp(a, b):
|
||||
"""Raise a TypeError if a user tries to multiply ket*TensorProduct(*kets).
|
||||
|
||||
Multiplication based on `*` is not a shorthand for tensor products.
|
||||
"""
|
||||
if b.kind == KetKind:
|
||||
raise TypeError(
|
||||
'Multiplication of ket*TensorProduct(*kets) is invalid.'
|
||||
)
|
||||
|
||||
@_transform_state_pair.register(TensorProduct, BraBase)
|
||||
def _transform_tp_bra(a, b):
|
||||
"""Raise a TypeError if a user tries to multiply TensorProduct(*bras)*bra.
|
||||
|
||||
Multiplication based on `*` is not a shorthand for tensor products.
|
||||
"""
|
||||
if a.kind == BraKind:
|
||||
raise TypeError(
|
||||
'Multiplication of TensorProduct(*bras)*bra is invalid.'
|
||||
)
|
||||
|
||||
@_transform_state_pair.register(BraBase, TensorProduct)
|
||||
def _transform_bra_tp(a, b):
|
||||
"""Raise a TypeError if a user tries to multiply bra*TensorProduct(*bras).
|
||||
|
||||
Multiplication based on `*` is not a shorthand for tensor products.
|
||||
"""
|
||||
if b.kind == BraKind:
|
||||
raise TypeError(
|
||||
'Multiplication of bra*TensorProduct(*bras) is invalid.'
|
||||
)
|
||||
|
||||
@_transform_state_pair.register(TensorProduct, TensorProduct)
|
||||
def _transform_tp_tp(a, b):
|
||||
"""Combine a product of tensor products if their number of args matches."""
|
||||
debug('_transform_tp_tp', a, b)
|
||||
if len(a.args) == len(b.args):
|
||||
if a.kind == BraKind and b.kind == KetKind:
|
||||
return tuple([InnerProduct(i, j) for (i, j) in zip(a.args, b.args)])
|
||||
else:
|
||||
return (TensorProduct(*(i*j for (i, j) in zip(a.args, b.args))), )
|
||||
|
||||
@_transform_state_pair.register(OuterProduct, OuterProduct)
|
||||
def _transform_op_op(a, b):
|
||||
"""Extract an inner produt from a product of outer products."""
|
||||
return (InnerProduct(a.bra, b.ket), OuterProduct(a.ket, b.bra))
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Postprocessing transforms for Mul and Pow
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _postprocess_state_mul(expr):
|
||||
"""Transform a ``Mul`` of quantum expressions into canonical form.
|
||||
|
||||
This function is registered ``_constructor_postprocessor_mapping`` as a
|
||||
transformer for ``Mul``. This means that every time a quantum expression
|
||||
is multiplied, this function will be called to transform it into canonical
|
||||
form as defined by the binary functions registered with
|
||||
``_transform_state_pair``.
|
||||
|
||||
The algorithm of this function is as follows. It walks the args
|
||||
of the input ``Mul`` from left to right and calls ``_transform_state_pair``
|
||||
on every overlapping pair of args. Each time ``_transform_state_pair``
|
||||
is called it can return a tuple of items or None. If None, the pair isn't
|
||||
transformed. If a tuple, then the last element of the tuple goes back into
|
||||
the args to be transformed again and the others are extended onto the result
|
||||
args list.
|
||||
|
||||
The algorithm can be visualized in the following table:
|
||||
|
||||
step result args
|
||||
============================================================================
|
||||
#0 [] [a, b, c, d, e, f]
|
||||
#1 [] [T(a,b), c, d, e, f]
|
||||
#2 [T(a,b)[:-1]] [T(a,b)[-1], c, d, e, f]
|
||||
#3 [T(a,b)[:-1]] [T(T(a,b)[-1], c), d, e, f]
|
||||
#4 [T(a,b)[:-1], T(T(a,b)[-1], c)[:-1]] [T(T(T(a,b)[-1], c)[-1], d), e, f]
|
||||
#5 ...
|
||||
|
||||
One limitation of the current implementation is that we assume that only the
|
||||
last item of the transformed tuple goes back into the args to be transformed
|
||||
again. These seems to handle the cases needed for Mul. However, we may need
|
||||
to extend the algorithm to have the entire tuple go back into the args for
|
||||
further transformation.
|
||||
"""
|
||||
args = list(expr.args)
|
||||
result = []
|
||||
|
||||
# Continue as long as we have at least 2 elements
|
||||
while len(args) > 1:
|
||||
# Get first two elements
|
||||
first = args.pop(0)
|
||||
second = args[0] # Look at second element without popping yet
|
||||
|
||||
transformed = _transform_state_pair(first, second)
|
||||
|
||||
if transformed is None:
|
||||
# If transform returns None, append first element
|
||||
result.append(first)
|
||||
else:
|
||||
# This item was transformed, pop and discard
|
||||
args.pop(0)
|
||||
# The last item goes back to be transformed again
|
||||
args.insert(0, transformed[-1])
|
||||
# All other items go directly into the result
|
||||
result.extend(transformed[:-1])
|
||||
|
||||
# Append any remaining element
|
||||
if args:
|
||||
result.append(args[0])
|
||||
|
||||
return Mul._from_args(result, is_commutative=False)
|
||||
|
||||
|
||||
def _postprocess_state_pow(expr):
|
||||
"""Handle bras and kets raised to powers.
|
||||
|
||||
Under ``*`` multiplication this is invalid. Users should use a
|
||||
TensorProduct instead.
|
||||
"""
|
||||
base, exp = expr.as_base_exp()
|
||||
if base.kind == KetKind or base.kind == BraKind:
|
||||
raise TypeError(
|
||||
'A bra or ket to a power is invalid, use TensorProduct instead.'
|
||||
)
|
||||
|
||||
|
||||
def _postprocess_tp_pow(expr):
|
||||
"""Handle TensorProduct(*operators)**(positive integer).
|
||||
|
||||
This handles a tensor product of operators, to an integer power.
|
||||
The power here is interpreted as regular multiplication, not
|
||||
tensor product exponentiation. The form of exponentiation performed
|
||||
here leaves the space and dimension of the object the same.
|
||||
|
||||
This operation does not make sense for tensor product's of states.
|
||||
"""
|
||||
base, exp = expr.as_base_exp()
|
||||
debug('_postprocess_tp_pow: ', base, exp, expr.args)
|
||||
if isinstance(base, TensorProduct) and exp.is_integer and exp.is_positive and base.kind == OperatorKind:
|
||||
new_args = [a**exp for a in base.args]
|
||||
return TensorProduct(*new_args)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Register the transformers with Basic._constructor_postprocessor_mapping
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
Basic._constructor_postprocessor_mapping[StateBase] = {
|
||||
"Mul": [_postprocess_state_mul],
|
||||
"Pow": [_postprocess_state_pow]
|
||||
}
|
||||
|
||||
Basic._constructor_postprocessor_mapping[TensorProduct] = {
|
||||
"Mul": [_postprocess_state_mul],
|
||||
"Pow": [_postprocess_tp_pow]
|
||||
}
|
||||
|
||||
Basic._constructor_postprocessor_mapping[Operator] = {
|
||||
"Mul": [_postprocess_state_mul]
|
||||
}
|
||||
Reference in New Issue
Block a user