add read me
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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
243
venv/lib/python3.12/site-packages/sympy/physics/quantum/boson.py
Normal file
243
venv/lib/python3.12/site-packages/sympy/physics/quantum/boson.py
Normal file
@@ -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
|
||||
754
venv/lib/python3.12/site-packages/sympy/physics/quantum/cg.py
Normal file
754
venv/lib/python3.12/site-packages/sympy/physics/quantum/cg.py
Normal file
@@ -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
|
||||
1309
venv/lib/python3.12/site-packages/sympy/physics/quantum/gate.py
Normal file
1309
venv/lib/python3.12/site-packages/sympy/physics/quantum/gate.py
Normal file
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
|
||||
103
venv/lib/python3.12/site-packages/sympy/physics/quantum/kind.py
Normal file
103
venv/lib/python3.12/site-packages/sympy/physics/quantum/kind.py
Normal file
@@ -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
|
||||
675
venv/lib/python3.12/site-packages/sympy/physics/quantum/pauli.py
Normal file
675
venv/lib/python3.12/site-packages/sympy/physics/quantum/pauli.py
Normal file
@@ -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
|
||||
224
venv/lib/python3.12/site-packages/sympy/physics/quantum/qasm.py
Normal file
224
venv/lib/python3.12/site-packages/sympy/physics/quantum/qasm.py
Normal file
@@ -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)
|
||||
409
venv/lib/python3.12/site-packages/sympy/physics/quantum/qexpr.py
Normal file
409
venv/lib/python3.12/site-packages/sympy/physics/quantum/qexpr.py
Normal file
@@ -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)
|
||||
)
|
||||
215
venv/lib/python3.12/site-packages/sympy/physics/quantum/qft.py
Normal file
215
venv/lib/python3.12/site-packages/sympy/physics/quantum/qft.py
Normal file
@@ -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)
|
||||
811
venv/lib/python3.12/site-packages/sympy/physics/quantum/qubit.py
Normal file
811
venv/lib/python3.12/site-packages/sympy/physics/quantum/qubit.py
Normal file
@@ -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
|
||||
679
venv/lib/python3.12/site-packages/sympy/physics/quantum/sho1d.py
Normal file
679
venv/lib/python3.12/site-packages/sympy/physics/quantum/sho1d.py
Normal file
@@ -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')
|
||||
173
venv/lib/python3.12/site-packages/sympy/physics/quantum/shor.py
Normal file
173
venv/lib/python3.12/site-packages/sympy/physics/quantum/shor.py
Normal file
@@ -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
|
||||
2150
venv/lib/python3.12/site-packages/sympy/physics/quantum/spin.py
Normal file
2150
venv/lib/python3.12/site-packages/sympy/physics/quantum/spin.py
Normal file
File diff suppressed because it is too large
Load Diff
987
venv/lib/python3.12/site-packages/sympy/physics/quantum/state.py
Normal file
987
venv/lib/python3.12/site-packages/sympy/physics/quantum/state.py
Normal file
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user