├── sympsi ├── tests │ ├── __init__.py │ ├── test_constants.py │ ├── test_fermion.py │ ├── test_anticommutator.py │ ├── test_boson.py │ ├── test_operatorordering.py │ ├── test_qexpr.py │ ├── test_innerproduct.py │ ├── test_dagger.py │ ├── test_commutator.py │ ├── test_operatorset.py │ ├── test_hilbert.py │ ├── test_pauli.py │ ├── test_tensorproduct.py │ ├── test_qapply.py │ ├── test_represent.py │ ├── test_operator.py │ ├── test_state.py │ ├── test_density.py │ └── test_identitysearch.py ├── __init__.py ├── constants.py ├── dagger.py ├── anticommutator.py ├── innerproduct.py ├── qapply.py ├── commutator.py ├── expectation.py ├── fermion.py ├── operatorset.py ├── density.py ├── tensorproduct.py ├── qexpr.py ├── boson.py └── operatorordering.py ├── .gitignore ├── README.md ├── LICENSE └── setup.py /sympsi/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sympsi/__init__.py: -------------------------------------------------------------------------------- 1 | # sympsi global imports 2 | from .anticommutator import * 3 | from .qapply import * 4 | from .commutator import * 5 | from .dagger import * 6 | from .hilbert import * 7 | from .innerproduct import * 8 | from .operator import * 9 | from .represent import * 10 | from .state import * 11 | from .tensorproduct import * 12 | from .constants import * 13 | from .qutility import * -------------------------------------------------------------------------------- /sympsi/tests/test_constants.py: -------------------------------------------------------------------------------- 1 | from sympy import Float 2 | 3 | from sympy.physics.quantum.constants import hbar 4 | 5 | 6 | def test_hbar(): 7 | assert hbar.is_commutative is True 8 | assert hbar.is_real is True 9 | assert hbar.is_positive is True 10 | assert hbar.is_negative is False 11 | assert hbar.is_irrational is True 12 | 13 | assert hbar.evalf() == Float(1.05457162e-34) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SymΨ - Symbolic Quantum Mechanics with Python and SymPy 2 | ======================================================= 3 | 4 | This Python package is an exerimental fork and extension of the [quantum](https://github.com/sympy/sympy/tree/master/sympy/physics/quantum) module in [SymPy](http://www.sympy.org). For a list of contributors to the SymPy quantum module, see [this page](https://github.com/sympy/sympy/commits/master/sympy/physics/quantum). New features developed for SymPsi will be contributed upstream to the SymPy quantum module when mature and tested. 5 | 6 | Installation 7 | ------------ 8 | 9 | SymPsi requires a recent version of SymPy. To install SymPy (see also the [SymPy installation instructions](http://docs.sympy.org/latest/install.html)), run: 10 | 11 | $ pip install sympy 12 | 13 | The SymPsi package can be installed using: 14 | 15 | $ pip install git+https://github.com/sympsi/sympsi.git 16 | 17 | and to upgrade an existing installation, just add the `--upgrade` flag: 18 | 19 | $ pip install --upgrade git+https://github.com/sympsi/sympsi.git 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sympsi/tests/test_fermion.py: -------------------------------------------------------------------------------- 1 | from sympy.physics.quantum import Dagger, AntiCommutator, qapply 2 | from sympy.physics.quantum.fermion import FermionOp 3 | from sympy.physics.quantum.fermion import FermionFockKet, FermionFockBra 4 | 5 | 6 | def test_fermionoperator(): 7 | c = FermionOp('c') 8 | d = FermionOp('d') 9 | 10 | assert isinstance(c, FermionOp) 11 | assert isinstance(Dagger(c), FermionOp) 12 | 13 | assert c.is_annihilation 14 | assert not Dagger(c).is_annihilation 15 | 16 | assert FermionOp("c") == FermionOp("c") 17 | assert FermionOp("c") != FermionOp("d") 18 | assert FermionOp("c", True) != FermionOp("c", False) 19 | 20 | assert AntiCommutator(c, Dagger(c)).doit() == 1 21 | 22 | assert AntiCommutator(c, Dagger(d)).doit() == c * Dagger(d) + Dagger(d) * c 23 | 24 | 25 | def test_fermion_states(): 26 | c = FermionOp("c") 27 | 28 | # Fock states 29 | assert (FermionFockBra(0) * FermionFockKet(1)).doit() == 0 30 | assert (FermionFockBra(1) * FermionFockKet(1)).doit() == 1 31 | 32 | assert qapply(c * FermionFockKet(1)) == FermionFockKet(0) 33 | assert qapply(c * FermionFockKet(0)) == 0 34 | 35 | assert qapply(Dagger(c) * FermionFockKet(0)) == FermionFockKet(1) 36 | assert qapply(Dagger(c) * FermionFockKet(1)) == 0 37 | -------------------------------------------------------------------------------- /sympsi/tests/test_anticommutator.py: -------------------------------------------------------------------------------- 1 | from sympy import symbols, Integer 2 | 3 | from sympy.physics.quantum.dagger import Dagger 4 | from sympy.physics.quantum.anticommutator import AntiCommutator as AComm 5 | from sympy.physics.quantum.operator import Operator 6 | 7 | 8 | a, b, c = symbols('a,b,c') 9 | A, B, C, D = symbols('A,B,C,D', commutative=False) 10 | 11 | 12 | def test_anticommutator(): 13 | ac = AComm(A, B) 14 | assert isinstance(ac, AComm) 15 | assert ac.is_commutative is False 16 | assert ac.subs(A, C) == AComm(C, B) 17 | 18 | 19 | def test_commutator_identities(): 20 | assert AComm(a*A, b*B) == a*b*AComm(A, B) 21 | assert AComm(A, A) == 2*A**2 22 | assert AComm(A, B) == AComm(B, A) 23 | assert AComm(a, b) == 2*a*b 24 | assert AComm(A, B).doit() == A*B + B*A 25 | 26 | 27 | def test_anticommutator_dagger(): 28 | assert Dagger(AComm(A, B)) == AComm(Dagger(A), Dagger(B)) 29 | 30 | 31 | class Foo(Operator): 32 | 33 | def _eval_anticommutator_Bar(self, bar): 34 | return Integer(0) 35 | 36 | 37 | class Bar(Operator): 38 | pass 39 | 40 | 41 | class Tam(Operator): 42 | 43 | def _eval_anticommutator_Foo(self, foo): 44 | return Integer(1) 45 | 46 | 47 | def test_eval_commutator(): 48 | F = Foo('F') 49 | B = Bar('B') 50 | T = Tam('T') 51 | assert AComm(F, B).doit() == 0 52 | assert AComm(B, F).doit() == 0 53 | assert AComm(F, T).doit() == 1 54 | assert AComm(T, F).doit() == 1 55 | assert AComm(B, T).doit() == B*T + T*B 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, SymPsi and sympy.physics.quantum developers. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of sympsi nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /sympsi/tests/test_boson.py: -------------------------------------------------------------------------------- 1 | from sympy import sqrt, exp, S, prod 2 | from sympy.physics.quantum import Dagger, Commutator, qapply 3 | from sympy.physics.quantum.boson import BosonOp 4 | from sympy.physics.quantum.boson import ( 5 | BosonFockKet, BosonFockBra, BosonCoherentKet, BosonCoherentBra) 6 | 7 | 8 | def test_bosonoperator(): 9 | a = BosonOp('a') 10 | b = BosonOp('b') 11 | 12 | assert isinstance(a, BosonOp) 13 | assert isinstance(Dagger(a), BosonOp) 14 | 15 | assert a.is_annihilation 16 | assert not Dagger(a).is_annihilation 17 | 18 | assert BosonOp("a") == BosonOp("a") 19 | assert BosonOp("a") != BosonOp("c") 20 | assert BosonOp("a", True) != BosonOp("a", False) 21 | 22 | assert Commutator(a, Dagger(a)).doit() == 1 23 | 24 | assert Commutator(a, Dagger(b)).doit() == a * Dagger(b) - Dagger(b) * a 25 | 26 | 27 | def test_boson_states(): 28 | a = BosonOp("a") 29 | 30 | # Fock states 31 | n = 3 32 | assert (BosonFockBra(0) * BosonFockKet(1)).doit() == 0 33 | assert (BosonFockBra(1) * BosonFockKet(1)).doit() == 1 34 | assert qapply(BosonFockBra(n) * Dagger(a)**n * BosonFockKet(0)) \ 35 | == sqrt(prod(range(1, n+1))) 36 | 37 | # Coherent states 38 | alpha1, alpha2 = 1.2, 4.3 39 | assert (BosonCoherentBra(alpha1) * BosonCoherentKet(alpha1)).doit() == 1 40 | assert (BosonCoherentBra(alpha2) * BosonCoherentKet(alpha2)).doit() == 1 41 | assert abs((BosonCoherentBra(alpha1) * BosonCoherentKet(alpha2)).doit() - 42 | exp(-S(1) / 2 * (alpha1 - alpha2) ** 2)) < 1e-12 43 | assert qapply(a * BosonCoherentKet(alpha1)) == \ 44 | alpha1 * BosonCoherentKet(alpha1) 45 | -------------------------------------------------------------------------------- /sympsi/tests/test_operatorordering.py: -------------------------------------------------------------------------------- 1 | from sympy.physics.quantum import Dagger 2 | from sympy.physics.quantum.boson import BosonOp 3 | from sympy.physics.quantum.fermion import FermionOp 4 | from sympy.physics.quantum.operatorordering import (normal_order, 5 | normal_ordered_form) 6 | 7 | 8 | def test_normal_order(): 9 | a = BosonOp('a') 10 | b = BosonOp('b') 11 | 12 | c = FermionOp('c') 13 | d = FermionOp('d') 14 | 15 | assert normal_order(a * Dagger(a)) == Dagger(a) * a 16 | assert normal_order(Dagger(a) * a) == Dagger(a) * a 17 | assert normal_order(a * Dagger(a) ** 2) == Dagger(a) ** 2 * a 18 | 19 | assert normal_order(c * Dagger(c)) == - Dagger(c) * c 20 | assert normal_order(Dagger(c) * c) == Dagger(c) * c 21 | assert normal_order(c * Dagger(c) ** 2) == Dagger(c) ** 2 * c 22 | 23 | 24 | def test_normal_ordered_form(): 25 | a = BosonOp('a') 26 | b = BosonOp('b') 27 | 28 | c = FermionOp('c') 29 | d = FermionOp('d') 30 | 31 | assert normal_ordered_form(Dagger(a) * a) == Dagger(a) * a 32 | assert normal_ordered_form(a * Dagger(a)) == 1 + Dagger(a) * a 33 | assert normal_ordered_form(a ** 2 * Dagger(a)) == \ 34 | 2 * a + Dagger(a) * a ** 2 35 | assert normal_ordered_form(a ** 3 * Dagger(a)) == \ 36 | 3 * a ** 2 + Dagger(a) * a ** 3 37 | 38 | assert normal_ordered_form(Dagger(c) * c) == Dagger(c) * c 39 | assert normal_ordered_form(c * Dagger(c)) == 1 - Dagger(c) * c 40 | assert normal_ordered_form(c ** 2 * Dagger(c)) == Dagger(c) * c ** 2 41 | assert normal_ordered_form(c ** 3 * Dagger(c)) == \ 42 | c ** 2 - Dagger(c) * c ** 3 43 | -------------------------------------------------------------------------------- /sympsi/tests/test_qexpr.py: -------------------------------------------------------------------------------- 1 | from sympy import Symbol, Integer 2 | from sympy.physics.quantum.qexpr import QExpr, _qsympify_sequence 3 | from sympy.physics.quantum.hilbert import HilbertSpace 4 | from sympy.core.containers import Tuple 5 | 6 | x = Symbol('x') 7 | y = Symbol('y') 8 | 9 | 10 | def test_qexpr_new(): 11 | q = QExpr(0) 12 | assert q.label == (0,) 13 | assert q.hilbert_space == HilbertSpace() 14 | assert q.is_commutative is False 15 | 16 | q = QExpr(0, 1) 17 | assert q.label == (Integer(0), Integer(1)) 18 | 19 | q = QExpr._new_rawargs(HilbertSpace(), Integer(0), Integer(1)) 20 | assert q.label == (Integer(0), Integer(1)) 21 | assert q.hilbert_space == HilbertSpace() 22 | 23 | 24 | def test_qexpr_commutative(): 25 | q1 = QExpr(x) 26 | q2 = QExpr(y) 27 | assert q1.is_commutative is False 28 | assert q2.is_commutative is False 29 | assert q1*q2 != q2*q1 30 | 31 | q = QExpr._new_rawargs(0, 1, HilbertSpace()) 32 | assert q.is_commutative is False 33 | 34 | def test_qexpr_commutative_free_symbols(): 35 | q1 = QExpr(x) 36 | assert q1.free_symbols.pop().is_commutative is False 37 | 38 | q2 = QExpr('q2') 39 | assert q2.free_symbols.pop().is_commutative is False 40 | 41 | def test_qexpr_subs(): 42 | q1 = QExpr(x, y) 43 | assert q1.subs(x, y) == QExpr(y, y) 44 | assert q1.subs({x: 1, y: 2}) == QExpr(1, 2) 45 | 46 | 47 | def test_qsympify(): 48 | assert _qsympify_sequence([[1, 2], [1, 3]]) == (Tuple(1, 2), Tuple(1, 3)) 49 | assert _qsympify_sequence(([1, 2, [3, 4, [2, ]], 1], 3)) == \ 50 | (Tuple(1, 2, Tuple(3, 4, Tuple(2,)), 1), 3) 51 | assert _qsympify_sequence((1,)) == (1,) 52 | -------------------------------------------------------------------------------- /sympsi/constants.py: -------------------------------------------------------------------------------- 1 | """Constants (like hbar) related to quantum mechanics.""" 2 | 3 | from __future__ import print_function, division 4 | 5 | from sympy.core.numbers import NumberSymbol 6 | from sympy.core.singleton import Singleton 7 | from sympy.core.compatibility import u, with_metaclass 8 | from sympy.printing.pretty.stringpict import prettyForm 9 | import sympy.mpmath.libmp as mlib 10 | 11 | #----------------------------------------------------------------------------- 12 | # Constants 13 | #----------------------------------------------------------------------------- 14 | 15 | __all__ = [ 16 | 'hbar' 17 | ] 18 | 19 | 20 | class HBar(with_metaclass(Singleton, NumberSymbol)): 21 | """Reduced Plank's constant in numerical and symbolic form [1]_. 22 | 23 | Examples 24 | ======== 25 | 26 | >>> from sympsi.constants import hbar 27 | >>> hbar.evalf() 28 | 1.05457162000000e-34 29 | 30 | References 31 | ========== 32 | 33 | .. [1] http://en.wikipedia.org/wiki/Planck_constant 34 | """ 35 | 36 | is_real = True 37 | is_positive = True 38 | is_negative = False 39 | is_irrational = True 40 | 41 | __slots__ = [] 42 | 43 | def _as_mpf_val(self, prec): 44 | return mlib.from_float(1.05457162e-34, prec) 45 | 46 | def _sympyrepr(self, printer, *args): 47 | return 'HBar()' 48 | 49 | def _sympystr(self, printer, *args): 50 | return 'hbar' 51 | 52 | def _pretty(self, printer, *args): 53 | if printer._use_unicode: 54 | return prettyForm(u('\u210f')) 55 | return prettyForm('hbar') 56 | 57 | def _latex(self, printer, *args): 58 | return r'\hbar' 59 | 60 | # Create an instance for everyone to use. 61 | hbar = HBar() 62 | -------------------------------------------------------------------------------- /sympsi/tests/test_innerproduct.py: -------------------------------------------------------------------------------- 1 | from sympy import I, Integer, latex, pretty 2 | 3 | from sympy.physics.quantum.innerproduct import InnerProduct 4 | from sympy.physics.quantum.dagger import Dagger 5 | from sympy.physics.quantum.state import Bra, Ket, StateBase 6 | 7 | 8 | def test_innerproduct(): 9 | k = Ket('k') 10 | b = Bra('b') 11 | ip = InnerProduct(b, k) 12 | assert isinstance(ip, InnerProduct) 13 | assert ip.bra == b 14 | assert ip.ket == k 15 | assert b*k == InnerProduct(b, k) 16 | assert k*(b*k)*b == k*InnerProduct(b, k)*b 17 | assert InnerProduct(b, k).subs(b, Dagger(k)) == Dagger(k)*k 18 | 19 | 20 | def test_innerproduct_dagger(): 21 | k = Ket('k') 22 | b = Bra('b') 23 | ip = b*k 24 | assert Dagger(ip) == Dagger(k)*Dagger(b) 25 | 26 | 27 | class FooState(StateBase): 28 | pass 29 | 30 | 31 | class FooKet(Ket, FooState): 32 | 33 | @classmethod 34 | def dual_class(self): 35 | return FooBra 36 | 37 | def _eval_innerproduct_FooBra(self, bra): 38 | return Integer(1) 39 | 40 | def _eval_innerproduct_BarBra(self, bra): 41 | return I 42 | 43 | 44 | class FooBra(Bra, FooState): 45 | @classmethod 46 | def dual_class(self): 47 | return FooKet 48 | 49 | 50 | class BarState(StateBase): 51 | pass 52 | 53 | 54 | class BarKet(Ket, BarState): 55 | @classmethod 56 | def dual_class(self): 57 | return BarBra 58 | 59 | 60 | class BarBra(Bra, BarState): 61 | @classmethod 62 | def dual_class(self): 63 | return BarKet 64 | 65 | 66 | def test_doit(): 67 | f = FooKet('foo') 68 | b = BarBra('bar') 69 | assert InnerProduct(b, f).doit() == I 70 | assert InnerProduct(Dagger(f), Dagger(b)).doit() == -I 71 | assert InnerProduct(Dagger(f), f).doit() == Integer(1) 72 | -------------------------------------------------------------------------------- /sympsi/tests/test_dagger.py: -------------------------------------------------------------------------------- 1 | from sympy import I, Matrix, symbols, conjugate, Expr, Integer 2 | 3 | from sympy.physics.quantum.dagger import adjoint, Dagger 4 | from sympy.external import import_module 5 | from sympy.utilities.pytest import skip 6 | 7 | 8 | def test_scalars(): 9 | x = symbols('x', complex=True) 10 | assert Dagger(x) == conjugate(x) 11 | assert Dagger(I*x) == -I*conjugate(x) 12 | 13 | i = symbols('i', real=True) 14 | assert Dagger(i) == i 15 | 16 | p = symbols('p') 17 | assert isinstance(Dagger(p), adjoint) 18 | 19 | i = Integer(3) 20 | assert Dagger(i) == i 21 | 22 | A = symbols('A', commutative=False) 23 | assert Dagger(A).is_commutative is False 24 | 25 | 26 | def test_matrix(): 27 | x = symbols('x') 28 | m = Matrix([[I, x*I], [2, 4]]) 29 | assert Dagger(m) == m.H 30 | 31 | 32 | class Foo(Expr): 33 | 34 | def _eval_adjoint(self): 35 | return I 36 | 37 | 38 | def test_eval_adjoint(): 39 | f = Foo() 40 | d = Dagger(f) 41 | assert d == I 42 | 43 | np = import_module('numpy') 44 | 45 | 46 | def test_numpy_dagger(): 47 | if not np: 48 | skip("numpy not installed.") 49 | 50 | a = np.matrix([[1.0, 2.0j], [-1.0j, 2.0]]) 51 | adag = a.copy().transpose().conjugate() 52 | assert (Dagger(a) == adag).all() 53 | 54 | 55 | scipy = import_module('scipy', __import__kwargs={'fromlist': ['sparse']}) 56 | 57 | 58 | def test_scipy_sparse_dagger(): 59 | if not np: 60 | skip("numpy not installed.") 61 | if not scipy: 62 | skip("scipy not installed.") 63 | else: 64 | sparse = scipy.sparse 65 | 66 | a = sparse.csr_matrix([[1.0 + 0.0j, 2.0j], [-1.0j, 2.0 + 0.0j]]) 67 | adag = a.copy().transpose().conjugate() 68 | assert np.linalg.norm((Dagger(a) - adag).todense()) == 0.0 69 | -------------------------------------------------------------------------------- /sympsi/tests/test_commutator.py: -------------------------------------------------------------------------------- 1 | from sympy import symbols, Integer 2 | 3 | from sympy.physics.quantum.dagger import Dagger 4 | from sympy.physics.quantum.commutator import Commutator as Comm 5 | from sympy.physics.quantum.operator import Operator 6 | 7 | 8 | a, b, c = symbols('a,b,c') 9 | A, B, C, D = symbols('A,B,C,D', commutative=False) 10 | 11 | 12 | def test_commutator(): 13 | c = Comm(A, B) 14 | assert c.is_commutative is False 15 | assert isinstance(c, Comm) 16 | assert c.subs(A, C) == Comm(C, B) 17 | 18 | 19 | def test_commutator_identities(): 20 | assert Comm(a*A, b*B) == a*b*Comm(A, B) 21 | assert Comm(A, A) == 0 22 | assert Comm(a, b) == 0 23 | assert Comm(A, B) == -Comm(B, A) 24 | assert Comm(A, B).doit() == A*B - B*A 25 | assert Comm(A, B*C).expand(commutator=True) == Comm(A, B)*C + B*Comm(A, C) 26 | assert Comm(A*B, C*D).expand(commutator=True) == \ 27 | A*C*Comm(B, D) + A*Comm(B, C)*D + C*Comm(A, D)*B + Comm(A, C)*D*B 28 | assert Comm(A + B, C + D).expand(commutator=True) == \ 29 | Comm(A, C) + Comm(A, D) + Comm(B, C) + Comm(B, D) 30 | assert Comm(A, B + C).expand(commutator=True) == Comm(A, B) + Comm(A, C) 31 | e = Comm(A, Comm(B, C)) + Comm(B, Comm(C, A)) + Comm(C, Comm(A, B)) 32 | assert e.doit().expand() == 0 33 | 34 | 35 | def test_commutator_dagger(): 36 | comm = Comm(A*B, C) 37 | assert Dagger(comm).expand(commutator=True) == \ 38 | - Comm(Dagger(B), Dagger(C))*Dagger(A) - \ 39 | Dagger(B)*Comm(Dagger(A), Dagger(C)) 40 | 41 | 42 | class Foo(Operator): 43 | 44 | def _eval_commutator_Bar(self, bar): 45 | return Integer(0) 46 | 47 | 48 | class Bar(Operator): 49 | pass 50 | 51 | 52 | class Tam(Operator): 53 | 54 | def _eval_commutator_Foo(self, foo): 55 | return Integer(1) 56 | 57 | 58 | def test_eval_commutator(): 59 | F = Foo('F') 60 | B = Bar('B') 61 | T = Tam('T') 62 | assert Comm(F, B).doit() == 0 63 | assert Comm(B, F).doit() == 0 64 | assert Comm(F, T).doit() == -1 65 | assert Comm(T, F).doit() == 1 66 | assert Comm(B, T).doit() == B*T - T*B 67 | -------------------------------------------------------------------------------- /sympsi/dagger.py: -------------------------------------------------------------------------------- 1 | """Hermitian conjugation.""" 2 | 3 | from __future__ import print_function, division 4 | 5 | from sympy.core import Expr 6 | from sympy.functions.elementary.complexes import adjoint 7 | 8 | __all__ = [ 9 | 'Dagger' 10 | ] 11 | 12 | 13 | class Dagger(adjoint): 14 | """General Hermitian conjugate operation. 15 | 16 | Take the Hermetian conjugate of an argument [1]_. For matrices this 17 | operation is equivalent to transpose and complex conjugate [2]_. 18 | 19 | Parameters 20 | ========== 21 | 22 | arg : Expr 23 | The sympy expression that we want to take the dagger of. 24 | 25 | Examples 26 | ======== 27 | 28 | Daggering various quantum objects: 29 | 30 | >>> from sympsi.dagger import Dagger 31 | >>> from sympsi.state import Ket, Bra 32 | >>> from sympsi.operator import Operator 33 | >>> Dagger(Ket('psi')) 34 | >> Dagger(Bra('phi')) 36 | |phi> 37 | >>> Dagger(Operator('A')) 38 | Dagger(A) 39 | 40 | Inner and outer products:: 41 | 42 | >>> from sympsi import InnerProduct, OuterProduct 43 | >>> Dagger(InnerProduct(Bra('a'), Ket('b'))) 44 | 45 | >>> Dagger(OuterProduct(Ket('a'), Bra('b'))) 46 | |b>>> A = Operator('A') 51 | >>> B = Operator('B') 52 | >>> Dagger(A*B) 53 | Dagger(B)*Dagger(A) 54 | >>> Dagger(A+B) 55 | Dagger(A) + Dagger(B) 56 | >>> Dagger(A**2) 57 | Dagger(A)**2 58 | 59 | Dagger also seamlessly handles complex numbers and matrices:: 60 | 61 | >>> from sympy import Matrix, I 62 | >>> m = Matrix([[1,I],[2,I]]) 63 | >>> m 64 | Matrix([ 65 | [1, I], 66 | [2, I]]) 67 | >>> Dagger(m) 68 | Matrix([ 69 | [ 1, 2], 70 | [-I, -I]]) 71 | 72 | References 73 | ========== 74 | 75 | .. [1] http://en.wikipedia.org/wiki/Hermitian_adjoint 76 | .. [2] http://en.wikipedia.org/wiki/Hermitian_transpose 77 | """ 78 | 79 | def __new__(cls, arg): 80 | if hasattr(arg, 'adjoint'): 81 | obj = arg.adjoint() 82 | elif hasattr(arg, 'conjugate') and hasattr(arg, 'transpose'): 83 | obj = arg.conjugate().transpose() 84 | if obj is not None: 85 | return obj 86 | return Expr.__new__(cls, arg) 87 | 88 | adjoint.__name__ = "Dagger" 89 | adjoint._sympyrepr = lambda a, b: "Dagger(%s)" % b._print(a.args[0]) 90 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """SymPsi: Symbolic quantum mechanics. 3 | """ 4 | 5 | DOCLINES = __doc__.split('\n') 6 | 7 | CLASSIFIERS = """\ 8 | Development Status :: 4 - Beta 9 | Intended Audience :: Science/Research 10 | License :: OSI Approved :: BSD License 11 | Programming Language :: Python 12 | Programming Language :: Python :: 3 13 | Topic :: Scientific/Engineering 14 | Operating System :: MacOS 15 | Operating System :: POSIX 16 | Operating System :: Unix 17 | Operating System :: Microsoft :: Windows 18 | """ 19 | 20 | import os 21 | import sys 22 | import numpy as np 23 | from numpy.distutils.core import setup 24 | 25 | MAJOR = 0 26 | MINOR = 1 27 | MICRO = 0 28 | ISRELEASED = False 29 | VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO) 30 | REQUIRES = ['sympy (>= 0.7.5)'] 31 | PACKAGES = ['sympsi'] 32 | NAME = "sympsi" 33 | AUTHOR = "Robert Johansson, Eunjong Kim" 34 | AUTHOR_EMAIL = "robert@riken.jp" 35 | LICENSE = "BSD" 36 | DESCRIPTION = DOCLINES[0] 37 | LONG_DESCRIPTION = "\n".join(DOCLINES[2:]) 38 | KEYWORDS = "symbolic quantum mechanics" 39 | URL = "http://github.com/jrjohansson/sympsi" 40 | CLASSIFIERS = [_f for _f in CLASSIFIERS.split('\n') if _f] 41 | PLATFORMS = ["Linux", "Mac OSX", "Unix", "Windows"] 42 | 43 | 44 | def git_short_hash(): 45 | try: 46 | return "-" + os.popen('git log -1 --format="%h"').read().strip() 47 | except: 48 | return "" 49 | 50 | FULLVERSION = VERSION 51 | if not ISRELEASED: 52 | FULLVERSION += '.dev' + git_short_hash() 53 | 54 | 55 | def write_version_py(filename='sympsi/version.py'): 56 | cnt = """\ 57 | # THIS FILE IS GENERATED FROM SYMPSI SETUP.PY 58 | short_version = '%(version)s' 59 | version = '%(fullversion)s' 60 | release = %(isrelease)s 61 | """ 62 | with open(filename, 'w') as f: 63 | f.write(cnt % {'version': VERSION, 'fullversion': 64 | FULLVERSION, 'isrelease': str(ISRELEASED)}) 65 | 66 | write_version_py() 67 | 68 | 69 | def configuration(parent_package='', top_path=None): 70 | from numpy.distutils.misc_util import Configuration 71 | 72 | config = Configuration(None, parent_package, top_path) 73 | config.set_options(ignore_setup_xxx_py=True, 74 | assume_default_configuration=True, 75 | delegate_options_to_subpackages=True, 76 | quiet=True) 77 | 78 | config.add_subpackage('sympsi') 79 | config.get_version('sympsi/version.py') 80 | 81 | return config 82 | 83 | 84 | setup( 85 | name=NAME, 86 | packages=PACKAGES, 87 | author=AUTHOR, 88 | author_email=AUTHOR_EMAIL, 89 | license=LICENSE, 90 | description=DESCRIPTION, 91 | long_description=LONG_DESCRIPTION, 92 | keywords=KEYWORDS, 93 | url=URL, 94 | classifiers=CLASSIFIERS, 95 | platforms=PLATFORMS, 96 | requires=REQUIRES, 97 | configuration=configuration 98 | ) 99 | -------------------------------------------------------------------------------- /sympsi/tests/test_operatorset.py: -------------------------------------------------------------------------------- 1 | from sympy.physics.quantum.operatorset import ( 2 | operators_to_state, state_to_operators 3 | ) 4 | 5 | from sympy.physics.quantum.cartesian import ( 6 | XOp, XKet, PxOp, PxKet, XBra, PxBra 7 | ) 8 | 9 | from sympy.physics.quantum.state import Ket, Bra 10 | from sympy.physics.quantum.operator import Operator 11 | from sympy.physics.quantum.spin import ( 12 | JxKet, JyKet, JzKet, JxBra, JyBra, JzBra, 13 | JxOp, JyOp, JzOp, J2Op 14 | ) 15 | 16 | from sympy.utilities.pytest import raises 17 | 18 | from sympy.utilities.pytest import XFAIL 19 | 20 | 21 | @XFAIL 22 | def test_spin(): 23 | assert operators_to_state(set([J2Op, JxOp])) == JxKet() 24 | assert operators_to_state(set([J2Op, JyOp])) == JyKet() 25 | assert operators_to_state(set([J2Op, JzOp])) == JzKet() 26 | assert operators_to_state(set([J2Op(), JxOp()])) == JxKet() 27 | assert operators_to_state(set([J2Op(), JyOp()])) == JyKet() 28 | assert operators_to_state(set([J2Op(), JzOp()])) == JzKet() 29 | 30 | assert state_to_operators(JxKet) == set([J2Op(), JxOp()]) 31 | assert state_to_operators(JyKet) == set([J2Op(), JyOp()]) 32 | assert state_to_operators(JzKet) == set([J2Op(), JzOp()]) 33 | assert state_to_operators(JxBra) == set([J2Op(), JxOp()]) 34 | assert state_to_operators(JyBra) == set([J2Op(), JyOp()]) 35 | assert state_to_operators(JzBra) == set([J2Op(), JzOp()]) 36 | 37 | assert state_to_operators(JxKet()) == set([J2Op(), JxOp()]) 38 | assert state_to_operators(JyKet()) == set([J2Op(), JyOp()]) 39 | assert state_to_operators(JzKet()) == set([J2Op(), JzOp()]) 40 | assert state_to_operators(JxBra()) == set([J2Op(), JxOp()]) 41 | assert state_to_operators(JyBra()) == set([J2Op(), JyOp()]) 42 | assert state_to_operators(JzBra()) == set([J2Op(), JzOp()]) 43 | 44 | 45 | def test_op_to_state(): 46 | assert operators_to_state(XOp) == XKet() 47 | assert operators_to_state(PxOp) == PxKet() 48 | assert operators_to_state(Operator) == Ket() 49 | 50 | assert state_to_operators(operators_to_state(XOp("Q"))) == XOp("Q") 51 | assert state_to_operators(operators_to_state(XOp())) == XOp() 52 | 53 | raises(NotImplementedError, lambda: operators_to_state(XKet)) 54 | 55 | 56 | def test_state_to_op(): 57 | assert state_to_operators(XKet) == XOp() 58 | assert state_to_operators(PxKet) == PxOp() 59 | assert state_to_operators(XBra) == XOp() 60 | assert state_to_operators(PxBra) == PxOp() 61 | assert state_to_operators(Ket) == Operator() 62 | assert state_to_operators(Bra) == Operator() 63 | 64 | assert operators_to_state(state_to_operators(XKet("test"))) == XKet("test") 65 | assert operators_to_state(state_to_operators(XBra("test"))) == XKet("test") 66 | assert operators_to_state(state_to_operators(XKet())) == XKet() 67 | assert operators_to_state(state_to_operators(XBra())) == XKet() 68 | 69 | raises(NotImplementedError, lambda: state_to_operators(XOp)) 70 | -------------------------------------------------------------------------------- /sympsi/tests/test_hilbert.py: -------------------------------------------------------------------------------- 1 | from sympy.physics.quantum.hilbert import ( 2 | HilbertSpace, ComplexSpace, L2, FockSpace, TensorProductHilbertSpace, 3 | DirectSumHilbertSpace, TensorPowerHilbertSpace 4 | ) 5 | 6 | from sympy import Interval, oo, Symbol, sstr, srepr 7 | 8 | 9 | def test_hilbert_space(): 10 | hs = HilbertSpace() 11 | assert isinstance(hs, HilbertSpace) 12 | assert sstr(hs) == 'H' 13 | assert srepr(hs) == 'HilbertSpace()' 14 | 15 | 16 | def test_complex_space(): 17 | c1 = ComplexSpace(2) 18 | assert isinstance(c1, ComplexSpace) 19 | assert c1.dimension == 2 20 | assert sstr(c1) == 'C(2)' 21 | assert srepr(c1) == 'ComplexSpace(Integer(2))' 22 | 23 | n = Symbol('n') 24 | c2 = ComplexSpace(n) 25 | assert isinstance(c2, ComplexSpace) 26 | assert c2.dimension == n 27 | assert sstr(c2) == 'C(n)' 28 | assert srepr(c2) == "ComplexSpace(Symbol('n'))" 29 | assert c2.subs(n, 2) == ComplexSpace(2) 30 | 31 | 32 | def test_L2(): 33 | b1 = L2(Interval(-oo, 1)) 34 | assert isinstance(b1, L2) 35 | assert b1.dimension == oo 36 | assert b1.interval == Interval(-oo, 1) 37 | 38 | x = Symbol('x', real=True) 39 | y = Symbol('y', real=True) 40 | b2 = L2(Interval(x, y)) 41 | assert b2.dimension == oo 42 | assert b2.interval == Interval(x, y) 43 | assert b2.subs(x, -1) == L2(Interval(-1, y)) 44 | 45 | 46 | def test_fock_space(): 47 | f1 = FockSpace() 48 | f2 = FockSpace() 49 | assert isinstance(f1, FockSpace) 50 | assert f1.dimension == oo 51 | assert f1 == f2 52 | 53 | 54 | def test_tensor_product(): 55 | n = Symbol('n') 56 | hs1 = ComplexSpace(2) 57 | hs2 = ComplexSpace(n) 58 | 59 | h = hs1*hs2 60 | assert isinstance(h, TensorProductHilbertSpace) 61 | assert h.dimension == 2*n 62 | assert h.spaces == (hs1, hs2) 63 | 64 | h = hs2*hs2 65 | assert isinstance(h, TensorPowerHilbertSpace) 66 | assert h.base == hs2 67 | assert h.exp == 2 68 | assert h.dimension == n**2 69 | 70 | f = FockSpace() 71 | h = hs1*hs2*f 72 | assert h.dimension == oo 73 | 74 | 75 | def test_tensor_power(): 76 | n = Symbol('n') 77 | hs1 = ComplexSpace(2) 78 | hs2 = ComplexSpace(n) 79 | 80 | h = hs1**2 81 | assert isinstance(h, TensorPowerHilbertSpace) 82 | assert h.base == hs1 83 | assert h.exp == 2 84 | assert h.dimension == 4 85 | 86 | h = hs2**3 87 | assert isinstance(h, TensorPowerHilbertSpace) 88 | assert h.base == hs2 89 | assert h.exp == 3 90 | assert h.dimension == n**3 91 | 92 | 93 | def test_direct_sum(): 94 | n = Symbol('n') 95 | hs1 = ComplexSpace(2) 96 | hs2 = ComplexSpace(n) 97 | 98 | h = hs1 + hs2 99 | assert isinstance(h, DirectSumHilbertSpace) 100 | assert h.dimension == 2 + n 101 | assert h.spaces == (hs1, hs2) 102 | 103 | f = FockSpace() 104 | h = hs1 + f + hs2 105 | assert h.dimension == oo 106 | assert h.spaces == (hs1, f, hs2) 107 | -------------------------------------------------------------------------------- /sympsi/tests/test_pauli.py: -------------------------------------------------------------------------------- 1 | from sympy import I, Mul 2 | from sympy.physics.quantum import Dagger, Commutator, AntiCommutator, qapply 3 | from sympy.physics.quantum.pauli import (SigmaOpBase, SigmaX, SigmaY, SigmaZ, 4 | SigmaMinus, SigmaPlus) 5 | from sympy.physics.quantum.pauli import SigmaZKet, SigmaZBra 6 | 7 | 8 | sx, sy, sz = SigmaX(), SigmaY(), SigmaZ() 9 | sx1, sy1, sz1 = SigmaX(1), SigmaY(1), SigmaZ(1) 10 | sx2, sy2, sz2 = SigmaX(2), SigmaY(2), SigmaZ(2) 11 | 12 | sm, sp = SigmaMinus(), SigmaPlus() 13 | 14 | 15 | def test_pauli_operators_types(): 16 | 17 | assert isinstance(sx, SigmaOpBase) and isinstance(sx, SigmaX) 18 | assert isinstance(sy, SigmaOpBase) and isinstance(sy, SigmaY) 19 | assert isinstance(sz, SigmaOpBase) and isinstance(sz, SigmaZ) 20 | assert isinstance(sm, SigmaOpBase) and isinstance(sm, SigmaMinus) 21 | assert isinstance(sp, SigmaOpBase) and isinstance(sp, SigmaPlus) 22 | 23 | 24 | def test_pauli_operators_commutator(): 25 | 26 | assert Commutator(sx, sy).doit() == 2 * I * sz 27 | assert Commutator(sy, sz).doit() == 2 * I * sx 28 | assert Commutator(sz, sx).doit() == 2 * I * sy 29 | 30 | 31 | def test_pauli_operators_commutator_with_labels(): 32 | 33 | assert Commutator(sx1, sy1).doit() == 2 * I * sz1 34 | assert Commutator(sy1, sz1).doit() == 2 * I * sx1 35 | assert Commutator(sz1, sx1).doit() == 2 * I * sy1 36 | 37 | assert Commutator(sx2, sy2).doit() == 2 * I * sz2 38 | assert Commutator(sy2, sz2).doit() == 2 * I * sx2 39 | assert Commutator(sz2, sx2).doit() == 2 * I * sy2 40 | 41 | assert Commutator(sx1, sy2).doit() == 0 42 | assert Commutator(sy1, sz2).doit() == 0 43 | assert Commutator(sz1, sx2).doit() == 0 44 | 45 | 46 | def test_pauli_operators_anticommutator(): 47 | 48 | assert AntiCommutator(sy, sz).doit() == 0 49 | assert AntiCommutator(sz, sx).doit() == 0 50 | assert AntiCommutator(sx, sm).doit() == 1 51 | assert AntiCommutator(sx, sp).doit() == 1 52 | 53 | 54 | def test_pauli_operators_adjoint(): 55 | 56 | assert Dagger(sx) == sx 57 | assert Dagger(sy) == sy 58 | assert Dagger(sz) == sz 59 | 60 | 61 | def test_pauli_operators_adjoint_with_labels(): 62 | 63 | assert Dagger(sx1) == sx1 64 | assert Dagger(sy1) == sy1 65 | assert Dagger(sz1) == sz1 66 | 67 | assert Dagger(sx1) != sx2 68 | assert Dagger(sy1) != sy2 69 | assert Dagger(sz1) != sz2 70 | 71 | 72 | def test_pauli_operators_multiplication(): 73 | 74 | assert sx * sx == 1 75 | assert sy * sy == 1 76 | assert sz * sz == 1 77 | 78 | assert sx * sy == I * sz 79 | assert sy * sz == I * sx 80 | assert sz * sx == I * sy 81 | 82 | assert sy * sx == - I * sz 83 | assert sz * sy == - I * sx 84 | assert sx * sz == - I * sy 85 | 86 | 87 | def test_pauli_operators_multiplication_with_labels(): 88 | 89 | assert sx1 * sx1 == 1 90 | assert sy1 * sy1 == 1 91 | assert sz1 * sz1 == 1 92 | 93 | assert isinstance(sx1 * sx2, Mul) 94 | assert isinstance(sy1 * sy2, Mul) 95 | assert isinstance(sz1 * sz2, Mul) 96 | 97 | 98 | def test_pauli_states(): 99 | sx, sz = SigmaX(), SigmaZ() 100 | 101 | up = SigmaZKet(0) 102 | down = SigmaZKet(1) 103 | 104 | assert qapply(sx * up) == down 105 | assert qapply(sx * down) == up 106 | assert qapply(sz * up) == up 107 | assert qapply(sz * down) == - down 108 | 109 | up = SigmaZBra(0) 110 | down = SigmaZBra(1) 111 | 112 | assert qapply(up * sx, dagger=True) == down 113 | assert qapply(down * sx, dagger=True) == up 114 | assert qapply(up * sz, dagger=True) == up 115 | assert qapply(down * sz, dagger=True) == - down 116 | 117 | assert Dagger(SigmaZKet(0)) == SigmaZBra(0) 118 | assert Dagger(SigmaZBra(1)) == SigmaZKet(1) 119 | -------------------------------------------------------------------------------- /sympsi/tests/test_tensorproduct.py: -------------------------------------------------------------------------------- 1 | from sympy import I, symbols, Matrix 2 | 3 | from sympy.physics.quantum.commutator import Commutator as Comm 4 | from sympy.physics.quantum.tensorproduct import TensorProduct 5 | from sympy.physics.quantum.tensorproduct import TensorProduct as TP 6 | from sympy.physics.quantum.tensorproduct import tensor_product_simp 7 | from sympy.physics.quantum.dagger import Dagger 8 | from sympy.physics.quantum.qubit import Qubit, QubitBra 9 | from sympy.physics.quantum.operator import OuterProduct 10 | from sympy.physics.quantum.density import Density 11 | from sympy.core.trace import Tr 12 | 13 | A, B, C = symbols('A,B,C', commutative=False) 14 | x = symbols('x') 15 | 16 | mat1 = Matrix([[1, 2*I], [1 + I, 3]]) 17 | mat2 = Matrix([[2*I, 3], [4*I, 2]]) 18 | 19 | 20 | def test_tensor_product_dagger(): 21 | assert Dagger(TensorProduct(I*A, B)) == \ 22 | -I*TensorProduct(Dagger(A), Dagger(B)) 23 | assert Dagger(TensorProduct(mat1, mat2)) == \ 24 | TensorProduct(Dagger(mat1), Dagger(mat2)) 25 | 26 | 27 | def test_tensor_product_abstract(): 28 | 29 | assert TP(x*A, 2*B) == x*2*TP(A, B) 30 | assert TP(A, B) != TP(B, A) 31 | assert TP(A, B).is_commutative is False 32 | assert isinstance(TP(A, B), TP) 33 | assert TP(A, B).subs(A, C) == TP(C, B) 34 | 35 | 36 | def test_tensor_product_expand(): 37 | assert TP(A + B, B + C).expand(tensorproduct=True) == \ 38 | TP(A, B) + TP(A, C) + TP(B, B) + TP(B, C) 39 | 40 | 41 | def test_tensor_product_commutator(): 42 | assert TP(Comm(A, B), C).doit().expand(tensorproduct=True) == \ 43 | TP(A*B, C) - TP(B*A, C) 44 | assert Comm(TP(A, B), TP(B, C)).doit() == \ 45 | TP(A, B)*TP(B, C) - TP(B, C)*TP(A, B) 46 | 47 | 48 | def test_tensor_product_simp(): 49 | assert tensor_product_simp(TP(A, B)*TP(B, C)) == TP(A*B, B*C) 50 | 51 | 52 | def test_issue_5923(): 53 | # most of the issue regarding sympification of args has been handled 54 | # and is tested internally by the use of args_cnc through the quantum 55 | # module, but the following is a test from the issue that used to raise. 56 | assert TensorProduct(1, Qubit('1')*Qubit('1').dual) == \ 57 | TensorProduct(1, OuterProduct(Qubit(1), QubitBra(1))) 58 | 59 | 60 | def test_eval_trace(): 61 | # This test includes tests with dependencies between TensorProducts 62 | #and density operators. Since, the test is more to test the behavior of 63 | #TensorProducts it remains here 64 | 65 | A, B, C, D, E, F = symbols('A B C D E F', commutative=False) 66 | 67 | # Density with simple tensor products as args 68 | t = TensorProduct(A, B) 69 | d = Density([t, 1.0]) 70 | tr = Tr(d) 71 | assert tr.doit() == 1.0*Tr(A*Dagger(A))*Tr(B*Dagger(B)) 72 | 73 | ## partial trace with simple tensor products as args 74 | t = TensorProduct(A, B, C) 75 | d = Density([t, 1.0]) 76 | tr = Tr(d, [1]) 77 | assert tr.doit() == 1.0*A*Dagger(A)*Tr(B*Dagger(B))*C*Dagger(C) 78 | 79 | tr = Tr(d, [0, 2]) 80 | assert tr.doit() == 1.0*Tr(A*Dagger(A))*B*Dagger(B)*Tr(C*Dagger(C)) 81 | 82 | # Density with multiple Tensorproducts as states 83 | t2 = TensorProduct(A, B) 84 | t3 = TensorProduct(C, D) 85 | 86 | d = Density([t2, 0.5], [t3, 0.5]) 87 | t = Tr(d) 88 | assert t.doit() == (0.5*Tr(A*Dagger(A))*Tr(B*Dagger(B)) + 89 | 0.5*Tr(C*Dagger(C))*Tr(D*Dagger(D))) 90 | 91 | t = Tr(d, [0]) 92 | assert t.doit() == (0.5*Tr(A*Dagger(A))*B*Dagger(B) + 93 | 0.5*Tr(C*Dagger(C))*D*Dagger(D)) 94 | 95 | #Density with mixed states 96 | d = Density([t2 + t3, 1.0]) 97 | t = Tr(d) 98 | assert t.doit() == ( 1.0*Tr(A*Dagger(A))*Tr(B*Dagger(B)) + 99 | 1.0*Tr(A*Dagger(C))*Tr(B*Dagger(D)) + 100 | 1.0*Tr(C*Dagger(A))*Tr(D*Dagger(B)) + 101 | 1.0*Tr(C*Dagger(C))*Tr(D*Dagger(D))) 102 | 103 | t = Tr(d, [1] ) 104 | assert t.doit() == ( 1.0*A*Dagger(A)*Tr(B*Dagger(B)) + 105 | 1.0*A*Dagger(C)*Tr(B*Dagger(D)) + 106 | 1.0*C*Dagger(A)*Tr(D*Dagger(B)) + 107 | 1.0*C*Dagger(C)*Tr(D*Dagger(D))) 108 | -------------------------------------------------------------------------------- /sympsi/tests/test_qapply.py: -------------------------------------------------------------------------------- 1 | from sympy import I, Integer, sqrt, symbols, Matrix 2 | 3 | from sympy.physics.quantum.anticommutator import AntiCommutator 4 | from sympy.physics.quantum.commutator import Commutator 5 | from sympy.physics.quantum.constants import hbar 6 | from sympy.physics.quantum.dagger import Dagger 7 | from sympy.physics.quantum.gate import H 8 | from sympy.physics.quantum.operator import Operator, UnitaryOperator 9 | from sympy.physics.quantum.qapply import qapply 10 | from sympy.physics.quantum.qubit import Qubit 11 | from sympy.physics.quantum.spin import Jx, Jy, Jz, Jplus, Jminus, J2, JzKet 12 | from sympy.physics.quantum.state import Ket 13 | from sympy.physics.quantum.density import Density 14 | from sympy.physics.quantum.qubit import Qubit 15 | from sympy.physics.quantum.gate import UGate 16 | from sympy.physics.quantum.boson import BosonOp, BosonFockKet, BosonFockBra 17 | from sympy.physics.quantum.tensorproduct import TensorProduct 18 | 19 | 20 | j, jp, m, mp = symbols("j j' m m'") 21 | 22 | z = JzKet(1, 0) 23 | po = JzKet(1, 1) 24 | mo = JzKet(1, -1) 25 | 26 | A = Operator('A') 27 | 28 | 29 | class Foo(Operator): 30 | def _apply_operator_JzKet(self, ket, **options): 31 | return ket 32 | 33 | 34 | def test_basic(): 35 | assert qapply(Jz*po) == hbar*po 36 | assert qapply(Jx*z) == hbar*po/sqrt(2) + hbar*mo/sqrt(2) 37 | assert qapply((Jplus + Jminus)*z/sqrt(2)) == hbar*po + hbar*mo 38 | assert qapply(Jz*(po + mo)) == hbar*po - hbar*mo 39 | assert qapply(Jz*po + Jz*mo) == hbar*po - hbar*mo 40 | assert qapply(Jminus*Jminus*po) == 2*hbar**2*mo 41 | assert qapply(Jplus**2*mo) == 2*hbar**2*po 42 | assert qapply(Jplus**2*Jminus**2*po) == 4*hbar**4*po 43 | 44 | 45 | def test_extra(): 46 | extra = z.dual*A*z 47 | assert qapply(Jz*po*extra) == hbar*po*extra 48 | assert qapply(Jx*z*extra) == (hbar*po/sqrt(2) + hbar*mo/sqrt(2))*extra 49 | assert qapply( 50 | (Jplus + Jminus)*z/sqrt(2)*extra) == hbar*po*extra + hbar*mo*extra 51 | assert qapply(Jz*(po + mo)*extra) == hbar*po*extra - hbar*mo*extra 52 | assert qapply(Jz*po*extra + Jz*mo*extra) == hbar*po*extra - hbar*mo*extra 53 | assert qapply(Jminus*Jminus*po*extra) == 2*hbar**2*mo*extra 54 | assert qapply(Jplus**2*mo*extra) == 2*hbar**2*po*extra 55 | assert qapply(Jplus**2*Jminus**2*po*extra) == 4*hbar**4*po*extra 56 | 57 | 58 | def test_innerproduct(): 59 | assert qapply(po.dual*Jz*po, ip_doit=False) == hbar*(po.dual*po) 60 | assert qapply(po.dual*Jz*po) == hbar 61 | 62 | 63 | def test_zero(): 64 | assert qapply(0) == 0 65 | assert qapply(Integer(0)) == 0 66 | 67 | 68 | def test_commutator(): 69 | assert qapply(Commutator(Jx, Jy)*Jz*po) == I*hbar**3*po 70 | assert qapply(Commutator(J2, Jz)*Jz*po) == 0 71 | assert qapply(Commutator(Jz, Foo('F'))*po) == 0 72 | assert qapply(Commutator(Foo('F'), Jz)*po) == 0 73 | 74 | 75 | def test_anticommutator(): 76 | assert qapply(AntiCommutator(Jz, Foo('F'))*po) == 2*hbar*po 77 | assert qapply(AntiCommutator(Foo('F'), Jz)*po) == 2*hbar*po 78 | 79 | 80 | def test_outerproduct(): 81 | e = Jz*(mo*po.dual)*Jz*po 82 | assert qapply(e) == -hbar**2*mo 83 | assert qapply(e, ip_doit=False) == -hbar**2*(po.dual*po)*mo 84 | assert qapply(e).doit() == -hbar**2*mo 85 | 86 | 87 | def test_tensorproduct(): 88 | a = BosonOp("a") 89 | b = BosonOp("b") 90 | ket1 = TensorProduct(BosonFockKet(1), BosonFockKet(2)) 91 | ket2 = TensorProduct(BosonFockKet(0), BosonFockKet(0)) 92 | ket3 = TensorProduct(BosonFockKet(0), BosonFockKet(2)) 93 | bra1 = TensorProduct(BosonFockBra(0), BosonFockBra(0)) 94 | bra2 = TensorProduct(BosonFockBra(1), BosonFockBra(2)) 95 | assert qapply(TensorProduct(a, b ** 2) * ket1) == sqrt(2) * ket2 96 | assert qapply(TensorProduct(a, Dagger(b) * b) * ket1) == 2 * ket3 97 | assert qapply(bra1 * TensorProduct(a, b * b), 98 | dagger=True) == sqrt(2) * bra2 99 | assert qapply(bra2 * ket1).doit() == TensorProduct(1, 1) 100 | assert qapply(TensorProduct(a, b * b) * ket1) == sqrt(2) * ket2 101 | assert qapply(Dagger(TensorProduct(a, b * b) * ket1), 102 | dagger=True) == sqrt(2) * Dagger(ket2) 103 | 104 | 105 | def test_dagger(): 106 | lhs = Dagger(Qubit(0))*Dagger(H(0)) 107 | rhs = Dagger(Qubit(1))/sqrt(2) + Dagger(Qubit(0))/sqrt(2) 108 | assert qapply(lhs, dagger=True) == rhs 109 | 110 | 111 | def test_issue_6073(): 112 | x, y = symbols('x y', commutative=False) 113 | A = Ket(x, y) 114 | B = Operator('B') 115 | assert qapply(A) == A 116 | assert qapply(A.dual*B) == A.dual*B 117 | 118 | 119 | def test_density(): 120 | d = Density([Jz*mo, 0.5], [Jz*po, 0.5]) 121 | assert qapply(d) == Density([-hbar*mo, 0.5], [hbar*po, 0.5]) 122 | -------------------------------------------------------------------------------- /sympsi/anticommutator.py: -------------------------------------------------------------------------------- 1 | """The anti-commutator: ``{A,B} = A*B + B*A``.""" 2 | 3 | from __future__ import print_function, division 4 | 5 | from sympy import S, Expr, Mul, Integer 6 | from sympy.core.compatibility import u 7 | from sympy.printing.pretty.stringpict import prettyForm 8 | 9 | from sympsi.operator import Operator 10 | from sympsi.dagger import Dagger 11 | 12 | __all__ = [ 13 | 'AntiCommutator' 14 | ] 15 | 16 | #----------------------------------------------------------------------------- 17 | # Anti-commutator 18 | #----------------------------------------------------------------------------- 19 | 20 | 21 | class AntiCommutator(Expr): 22 | """The standard anticommutator, in an unevaluated state. 23 | 24 | Evaluating an anticommutator is defined [1]_ as: ``{A, B} = A*B + B*A``. 25 | This class returns the anticommutator in an unevaluated form. To evaluate 26 | the anticommutator, use the ``.doit()`` method. 27 | 28 | Cannonical ordering of an anticommutator is ``{A, B}`` for ``A < B``. The 29 | arguments of the anticommutator are put into canonical order using 30 | ``__cmp__``. If ``B < A``, then ``{A, B}`` is returned as ``{B, A}``. 31 | 32 | Parameters 33 | ========== 34 | 35 | A : Expr 36 | The first argument of the anticommutator {A,B}. 37 | B : Expr 38 | The second argument of the anticommutator {A,B}. 39 | 40 | Examples 41 | ======== 42 | 43 | >>> from sympy import symbols 44 | >>> from sympsi import AntiCommutator 45 | >>> from sympsi import Operator, Dagger 46 | >>> x, y = symbols('x,y') 47 | >>> A = Operator('A') 48 | >>> B = Operator('B') 49 | 50 | Create an anticommutator and use ``doit()`` to multiply them out. 51 | 52 | >>> ac = AntiCommutator(A,B); ac 53 | {A,B} 54 | >>> ac.doit() 55 | A*B + B*A 56 | 57 | The commutator orders it arguments in canonical order: 58 | 59 | >>> ac = AntiCommutator(B,A); ac 60 | {A,B} 61 | 62 | Commutative constants are factored out: 63 | 64 | >>> AntiCommutator(3*x*A,x*y*B) 65 | 3*x**2*y*{A,B} 66 | 67 | Adjoint operations applied to the anticommutator are properly applied to 68 | the arguments: 69 | 70 | >>> Dagger(AntiCommutator(A,B)) 71 | {Dagger(A),Dagger(B)} 72 | 73 | References 74 | ========== 75 | 76 | .. [1] http://en.wikipedia.org/wiki/Commutator 77 | """ 78 | is_commutative = False 79 | 80 | def __new__(cls, A, B): 81 | r = cls.eval(A, B) 82 | if r is not None: 83 | return r 84 | obj = Expr.__new__(cls, A, B) 85 | return obj 86 | 87 | @classmethod 88 | def eval(cls, a, b): 89 | if not (a and b): 90 | return S.Zero 91 | if a == b: 92 | return Integer(2)*a**2 93 | if a.is_commutative or b.is_commutative: 94 | return Integer(2)*a*b 95 | 96 | # [xA,yB] -> xy*[A,B] 97 | # from sympy.physics.qmul import QMul 98 | ca, nca = a.args_cnc() 99 | cb, ncb = b.args_cnc() 100 | c_part = ca + cb 101 | if c_part: 102 | return Mul(Mul(*c_part), cls(Mul._from_args(nca), Mul._from_args(ncb))) 103 | 104 | # Canonical ordering of arguments 105 | #The Commutator [A,B] is on canonical form if A < B. 106 | if a.compare(b) == 1: 107 | return cls(b, a) 108 | 109 | def doit(self, **hints): 110 | """ Evaluate anticommutator """ 111 | A = self.args[0] 112 | B = self.args[1] 113 | if isinstance(A, Operator) and isinstance(B, Operator): 114 | try: 115 | comm = A._eval_anticommutator(B, **hints) 116 | except NotImplementedError: 117 | try: 118 | comm = B._eval_anticommutator(A, **hints) 119 | except NotImplementedError: 120 | comm = None 121 | if comm is not None: 122 | return comm.doit(**hints) 123 | return (A*B + B*A).doit(**hints) 124 | 125 | def _eval_adjoint(self): 126 | return AntiCommutator(Dagger(self.args[0]), Dagger(self.args[1])) 127 | 128 | def _sympyrepr(self, printer, *args): 129 | return "%s(%s,%s)" % ( 130 | self.__class__.__name__, printer._print( 131 | self.args[0]), printer._print(self.args[1]) 132 | ) 133 | 134 | def _sympystr(self, printer, *args): 135 | return "{%s,%s}" % (self.args[0], self.args[1]) 136 | 137 | def _pretty(self, printer, *args): 138 | pform = printer._print(self.args[0], *args) 139 | pform = prettyForm(*pform.right((prettyForm(u(','))))) 140 | pform = prettyForm(*pform.right((printer._print(self.args[1], *args)))) 141 | pform = prettyForm(*pform.parens(left='{', right='}')) 142 | return pform 143 | 144 | def _latex(self, printer, *args): 145 | return "\\left\\{%s,%s\\right\\}" % tuple([ 146 | printer._print(arg, *args) for arg in self.args]) 147 | -------------------------------------------------------------------------------- /sympsi/innerproduct.py: -------------------------------------------------------------------------------- 1 | """Symbolic inner product.""" 2 | 3 | from __future__ import print_function, division 4 | 5 | from sympy import Expr, conjugate 6 | from sympy.printing.latex import latex 7 | from sympy.printing.pretty.stringpict import prettyForm 8 | from sympsi.dagger import Dagger 9 | from sympsi.state import KetBase, BraBase 10 | 11 | __all__ = [ 12 | 'InnerProduct' 13 | ] 14 | 15 | 16 | # InnerProduct is not an QExpr because it is really just a regular commutative 17 | # number. We have gone back and forth about this, but we gain a lot by having 18 | # it subclass Expr. The main challenges were getting Dagger to work 19 | # (we use _eval_conjugate) and represent (we can use atoms and subs). Having 20 | # it be an Expr, mean that there are no commutative QExpr subclasses, 21 | # which simplifies the design of everything. 22 | 23 | class InnerProduct(Expr): 24 | """An unevaluated inner product between a Bra and a Ket [1]. 25 | 26 | Parameters 27 | ========== 28 | 29 | bra : BraBase or subclass 30 | The bra on the left side of the inner product. 31 | ket : KetBase or subclass 32 | The ket on the right side of the inner product. 33 | 34 | Examples 35 | ======== 36 | 37 | Create an InnerProduct and check its properties: 38 | 39 | >>> from sympsi import Bra, Ket, InnerProduct 40 | >>> b = Bra('b') 41 | >>> k = Ket('k') 42 | >>> ip = b*k 43 | >>> ip 44 | 45 | >>> ip.bra 46 | >> ip.ket 48 | |k> 49 | 50 | In simple products of kets and bras inner products will be automatically 51 | identified and created:: 52 | 53 | >>> b*k 54 | 55 | 56 | But in more complex expressions, there is ambiguity in whether inner or 57 | outer products should be created:: 58 | 59 | >>> k*b*k*b 60 | |k>*>> k*(b*k)*b 66 | *|k>* moved to the left of the expression 69 | because inner products are commutative complex numbers. 70 | 71 | References 72 | ========== 73 | 74 | .. [1] http://en.wikipedia.org/wiki/Inner_product 75 | """ 76 | is_complex = True 77 | 78 | def __new__(cls, bra, ket): 79 | if not isinstance(ket, KetBase): 80 | raise TypeError('KetBase subclass expected, got: %r' % ket) 81 | if not isinstance(bra, BraBase): 82 | raise TypeError('BraBase subclass expected, got: %r' % ket) 83 | obj = Expr.__new__(cls, bra, ket) 84 | return obj 85 | 86 | @property 87 | def bra(self): 88 | return self.args[0] 89 | 90 | @property 91 | def ket(self): 92 | return self.args[1] 93 | 94 | def _eval_conjugate(self): 95 | return InnerProduct(Dagger(self.ket), Dagger(self.bra)) 96 | 97 | def _sympyrepr(self, printer, *args): 98 | return '%s(%s,%s)' % (self.__class__.__name__, 99 | printer._print(self.bra, *args), printer._print(self.ket, *args)) 100 | 101 | def _sympystr(self, printer, *args): 102 | sbra = str(self.bra) 103 | sket = str(self.ket) 104 | return '%s|%s' % (sbra[:-1], sket[1:]) 105 | 106 | def _pretty(self, printer, *args): 107 | # Print state contents 108 | bra = self.bra._print_contents_pretty(printer, *args) 109 | ket = self.ket._print_contents_pretty(printer, *args) 110 | # Print brackets 111 | height = max(bra.height(), ket.height()) 112 | use_unicode = printer._use_unicode 113 | lbracket, _ = self.bra._pretty_brackets(height, use_unicode) 114 | cbracket, rbracket = self.ket._pretty_brackets(height, use_unicode) 115 | # Build innerproduct 116 | pform = prettyForm(*bra.left(lbracket)) 117 | pform = prettyForm(*pform.right(cbracket)) 118 | pform = prettyForm(*pform.right(ket)) 119 | pform = prettyForm(*pform.right(rbracket)) 120 | return pform 121 | 122 | def _latex(self, printer, *args): 123 | bra_str = latex(self.bra) 124 | rvert = r'\right|' 125 | ket_str = latex(self.ket) 126 | 127 | bra_str_without_vert = (bra_str[:bra_str.find(rvert)] + r'\right.' 128 | + bra_str[bra_str.find(rvert) + len(rvert):]) 129 | 130 | return r'%s %s' % (bra_str_without_vert, ket_str) 131 | 132 | def doit(self, **hints): 133 | try: 134 | r = self.ket._eval_innerproduct(self.bra, **hints) 135 | except NotImplementedError: 136 | try: 137 | r = conjugate( 138 | self.bra.dual._eval_innerproduct(self.ket.dual, **hints) 139 | ) 140 | except NotImplementedError: 141 | r = None 142 | if r is not None: 143 | return r 144 | return self 145 | -------------------------------------------------------------------------------- /sympsi/tests/test_represent.py: -------------------------------------------------------------------------------- 1 | from sympy import Float, I, Integer, Matrix 2 | from sympy.external import import_module 3 | from sympy.utilities.pytest import skip 4 | 5 | from sympy.physics.quantum.dagger import Dagger 6 | from sympy.physics.quantum.represent import (represent, rep_innerproduct, 7 | rep_expectation, enumerate_states) 8 | from sympy.physics.quantum.state import Bra, Ket 9 | from sympy.physics.quantum.operator import Operator, OuterProduct 10 | from sympy.physics.quantum.tensorproduct import TensorProduct 11 | from sympy.physics.quantum.tensorproduct import matrix_tensor_product 12 | from sympy.physics.quantum.commutator import Commutator 13 | from sympy.physics.quantum.anticommutator import AntiCommutator 14 | from sympy.physics.quantum.innerproduct import InnerProduct 15 | from sympy.physics.quantum.matrixutils import (numpy_ndarray, 16 | scipy_sparse_matrix, to_numpy, 17 | to_scipy_sparse, to_sympy) 18 | from sympy.physics.quantum.cartesian import XKet, XOp, XBra 19 | from sympy.physics.quantum.qapply import qapply 20 | from sympy.physics.quantum.operatorset import operators_to_state 21 | 22 | Amat = Matrix([[1, I], [-I, 1]]) 23 | Bmat = Matrix([[1, 2], [3, 4]]) 24 | Avec = Matrix([[1], [I]]) 25 | 26 | 27 | class AKet(Ket): 28 | 29 | @classmethod 30 | def dual_class(self): 31 | return ABra 32 | 33 | def _represent_default_basis(self, **options): 34 | return self._represent_AOp(None, **options) 35 | 36 | def _represent_AOp(self, basis, **options): 37 | return Avec 38 | 39 | 40 | class ABra(Bra): 41 | 42 | @classmethod 43 | def dual_class(self): 44 | return AKet 45 | 46 | 47 | class AOp(Operator): 48 | 49 | def _represent_default_basis(self, **options): 50 | return self._represent_AOp(None, **options) 51 | 52 | def _represent_AOp(self, basis, **options): 53 | return Amat 54 | 55 | 56 | class BOp(Operator): 57 | 58 | def _represent_default_basis(self, **options): 59 | return self._represent_AOp(None, **options) 60 | 61 | def _represent_AOp(self, basis, **options): 62 | return Bmat 63 | 64 | 65 | k = AKet('a') 66 | b = ABra('a') 67 | A = AOp('A') 68 | B = BOp('B') 69 | 70 | _tests = [ 71 | # Bra 72 | (b, Dagger(Avec)), 73 | (Dagger(b), Avec), 74 | # Ket 75 | (k, Avec), 76 | (Dagger(k), Dagger(Avec)), 77 | # Operator 78 | (A, Amat), 79 | (Dagger(A), Dagger(Amat)), 80 | # OuterProduct 81 | (OuterProduct(k, b), Avec*Avec.H), 82 | # TensorProduct 83 | (TensorProduct(A, B), matrix_tensor_product(Amat, Bmat)), 84 | # Pow 85 | (A**2, Amat**2), 86 | # Add/Mul 87 | (A*B + 2*A, Amat*Bmat + 2*Amat), 88 | # Commutator 89 | (Commutator(A, B), Amat*Bmat - Bmat*Amat), 90 | # AntiCommutator 91 | (AntiCommutator(A, B), Amat*Bmat + Bmat*Amat), 92 | # InnerProduct 93 | (InnerProduct(b, k), (Avec.H*Avec)[0]) 94 | ] 95 | 96 | 97 | def test_format_sympy(): 98 | for test in _tests: 99 | lhs = represent(test[0], basis=A, format='sympy') 100 | rhs = to_sympy(test[1]) 101 | assert lhs == rhs 102 | 103 | 104 | def test_scalar_sympy(): 105 | assert represent(Integer(1)) == Integer(1) 106 | assert represent(Float(1.0)) == Float(1.0) 107 | assert represent(1.0 + I) == 1.0 + I 108 | 109 | 110 | np = import_module('numpy') 111 | 112 | 113 | def test_format_numpy(): 114 | if not np: 115 | skip("numpy not installed.") 116 | 117 | for test in _tests: 118 | lhs = represent(test[0], basis=A, format='numpy') 119 | rhs = to_numpy(test[1]) 120 | if isinstance(lhs, numpy_ndarray): 121 | assert (lhs == rhs).all() 122 | else: 123 | assert lhs == rhs 124 | 125 | 126 | def test_scalar_numpy(): 127 | if not np: 128 | skip("numpy not installed.") 129 | 130 | assert represent(Integer(1), format='numpy') == 1 131 | assert represent(Float(1.0), format='numpy') == 1.0 132 | assert represent(1.0 + I, format='numpy') == 1.0 + 1.0j 133 | 134 | 135 | scipy = import_module('scipy', __import__kwargs={'fromlist': ['sparse']}) 136 | 137 | 138 | def test_format_scipy_sparse(): 139 | if not np: 140 | skip("numpy not installed.") 141 | if not scipy: 142 | skip("scipy not installed.") 143 | 144 | for test in _tests: 145 | lhs = represent(test[0], basis=A, format='scipy.sparse') 146 | rhs = to_scipy_sparse(test[1]) 147 | if isinstance(lhs, scipy_sparse_matrix): 148 | assert np.linalg.norm((lhs - rhs).todense()) == 0.0 149 | else: 150 | assert lhs == rhs 151 | 152 | 153 | def test_scalar_scipy_sparse(): 154 | if not np: 155 | skip("numpy not installed.") 156 | if not scipy: 157 | skip("scipy not installed.") 158 | 159 | assert represent(Integer(1), format='scipy.sparse') == 1 160 | assert represent(Float(1.0), format='scipy.sparse') == 1.0 161 | assert represent(1.0 + I, format='scipy.sparse') == 1.0 + 1.0j 162 | 163 | x_ket = XKet('x') 164 | x_bra = XBra('x') 165 | x_op = XOp('X') 166 | 167 | 168 | def test_innerprod_represent(): 169 | assert rep_innerproduct(x_ket) == InnerProduct(XBra("x_1"), x_ket).doit() 170 | assert rep_innerproduct(x_bra) == InnerProduct(x_bra, XKet("x_1")).doit() 171 | 172 | try: 173 | rep_innerproduct(x_op) 174 | except TypeError: 175 | return True 176 | 177 | 178 | def test_operator_represent(): 179 | basis_kets = enumerate_states(operators_to_state(x_op), 1, 2) 180 | assert rep_expectation( 181 | x_op) == qapply(basis_kets[1].dual*x_op*basis_kets[0]) 182 | 183 | 184 | def test_enumerate_states(): 185 | test = XKet("foo") 186 | assert enumerate_states(test, 1, 1) == [XKet("foo_1")] 187 | assert enumerate_states( 188 | test, [1, 2, 4]) == [XKet("foo_1"), XKet("foo_2"), XKet("foo_4")] 189 | -------------------------------------------------------------------------------- /sympsi/qapply.py: -------------------------------------------------------------------------------- 1 | """Logic for applying operators to states. 2 | 3 | Todo: 4 | * Sometimes the final result needs to be expanded, we should do this by hand. 5 | """ 6 | 7 | from __future__ import print_function, division 8 | 9 | from sympy import Add, Mul, Pow, sympify, S 10 | 11 | from sympsi.anticommutator import AntiCommutator 12 | from sympsi.commutator import Commutator 13 | from sympsi.dagger import Dagger 14 | from sympsi.innerproduct import InnerProduct 15 | from sympsi.operator import OuterProduct, Operator 16 | from sympsi.state import State, KetBase, BraBase, Wavefunction 17 | from sympsi.tensorproduct import TensorProduct 18 | 19 | __all__ = [ 20 | 'qapply' 21 | ] 22 | 23 | 24 | #----------------------------------------------------------------------------- 25 | # Main code 26 | #----------------------------------------------------------------------------- 27 | 28 | def qapply(e, **options): 29 | """Apply operators to states in a quantum expression. 30 | 31 | Parameters 32 | ========== 33 | 34 | e : Expr 35 | The expression containing operators and states. This expression tree 36 | will be walked to find operators acting on states symbolically. 37 | options : dict 38 | A dict of key/value pairs that determine how the operator actions 39 | are carried out. 40 | 41 | The following options are valid: 42 | 43 | * ``dagger``: try to apply Dagger operators to the left 44 | (default: False). 45 | * ``ip_doit``: call ``.doit()`` in inner products when they are 46 | encountered (default: True). 47 | 48 | Returns 49 | ======= 50 | 51 | e : Expr 52 | The original expression, but with the operators applied to states. 53 | """ 54 | from sympsi.density import Density 55 | 56 | dagger = options.get('dagger', False) 57 | 58 | if e == 0: 59 | return S.Zero 60 | 61 | # This may be a bit aggressive but ensures that everything gets expanded 62 | # to its simplest form before trying to apply operators. This includes 63 | # things like (A+B+C)*|a> and A*(|a>+|b>) and all Commutators and 64 | # TensorProducts. The only problem with this is that if we can't apply 65 | # all the Operators, we have just expanded everything. 66 | # TODO: don't expand the scalars in front of each Mul. 67 | e = e.expand(commutator=True, tensorproduct=True) 68 | 69 | # If we just have a raw ket, return it. 70 | if isinstance(e, KetBase): 71 | return e 72 | 73 | # We have an Add(a, b, c, ...) and compute 74 | # Add(qapply(a), qapply(b), ...) 75 | elif isinstance(e, Add): 76 | result = 0 77 | for arg in e.args: 78 | result += qapply(arg, **options) 79 | return result 80 | 81 | # For a Density operator call qapply on its state 82 | elif isinstance(e, Density): 83 | new_args = [(qapply(state, **options), prob) for (state, 84 | prob) in e.args] 85 | return Density(*new_args) 86 | 87 | # For a raw TensorProduct, call qapply on its args. 88 | elif isinstance(e, TensorProduct): 89 | return TensorProduct(*[qapply(t, **options) for t in e.args]) 90 | 91 | # For a Pow, call qapply on its base. 92 | elif isinstance(e, Pow): 93 | return qapply(e.base, **options)**e.exp 94 | 95 | # We have a Mul where there might be actual operators to apply to kets. 96 | elif isinstance(e, Mul): 97 | result = qapply_Mul(e, **options) 98 | if result == e and dagger: 99 | return Dagger(qapply_Mul(Dagger(e), **options)) 100 | else: 101 | return result 102 | 103 | # In all other cases (State, Operator, Pow, Commutator, InnerProduct, 104 | # OuterProduct) we won't ever have operators to apply to kets. 105 | else: 106 | return e 107 | 108 | 109 | def qapply_Mul(e, **options): 110 | 111 | ip_doit = options.get('ip_doit', True) 112 | 113 | args = list(e.args) 114 | 115 | # If we only have 0 or 1 args, we have nothing to do and return. 116 | if len(args) <= 1 or not isinstance(e, Mul): 117 | return e 118 | rhs = args.pop() 119 | lhs = args.pop() 120 | 121 | # Make sure we have two non-commutative objects before proceeding. 122 | if (sympify(rhs).is_commutative and not isinstance(rhs, Wavefunction)) or \ 123 | (sympify(lhs).is_commutative and not isinstance(lhs, Wavefunction)): 124 | return e 125 | 126 | # For a Pow with an integer exponent, apply one of them and reduce the 127 | # exponent by one. 128 | if isinstance(lhs, Pow) and lhs.exp.is_Integer: 129 | args.append(lhs.base**(lhs.exp - 1)) 130 | lhs = lhs.base 131 | 132 | # Pull OuterProduct apart 133 | if isinstance(lhs, OuterProduct): 134 | args.append(lhs.ket) 135 | lhs = lhs.bra 136 | 137 | # Call .doit() on Commutator/AntiCommutator. 138 | if isinstance(lhs, (Commutator, AntiCommutator)): 139 | comm = lhs.doit() 140 | if isinstance(comm, Add): 141 | return qapply( 142 | e.func(*(args + [comm.args[0], rhs])) + 143 | e.func(*(args + [comm.args[1], rhs])), 144 | **options 145 | ) 146 | else: 147 | return qapply(e.func(*args)*comm*rhs, **options) 148 | 149 | # Apply tensor products of operators to states 150 | if isinstance(lhs, TensorProduct) and all([isinstance(arg, (Operator, State, Mul, Pow)) or arg == 1 for arg in lhs.args]) and \ 151 | isinstance(rhs, TensorProduct) and all([isinstance(arg, (Operator, State, Mul, Pow)) or arg == 1 for arg in rhs.args]) and \ 152 | len(lhs.args) == len(rhs.args): 153 | result = TensorProduct(*[qapply(lhs.args[n]*rhs.args[n], **options) for n in range(len(lhs.args))]).expand(tensorproduct=True) 154 | return qapply_Mul(e.func(*args), **options)*result 155 | 156 | # Now try to actually apply the operator and build an inner product. 157 | try: 158 | result = lhs._apply_operator(rhs, **options) 159 | except (NotImplementedError, AttributeError): 160 | try: 161 | result = rhs._apply_operator(lhs, **options) 162 | except (NotImplementedError, AttributeError): 163 | if isinstance(lhs, BraBase) and isinstance(rhs, KetBase): 164 | result = InnerProduct(lhs, rhs) 165 | if ip_doit: 166 | result = result.doit() 167 | else: 168 | result = None 169 | 170 | # TODO: I may need to expand before returning the final result. 171 | if result == 0: 172 | return S.Zero 173 | elif result is None: 174 | if len(args) == 0: 175 | # We had two args to begin with so args=[]. 176 | return e 177 | else: 178 | return qapply_Mul(e.func(*(args + [lhs])), **options)*rhs 179 | elif isinstance(result, InnerProduct): 180 | return result*qapply_Mul(e.func(*args), **options) 181 | else: # result is a scalar times a Mul, Add or TensorProduct 182 | return qapply(e.func(*args)*result, **options) 183 | -------------------------------------------------------------------------------- /sympsi/tests/test_operator.py: -------------------------------------------------------------------------------- 1 | from sympy import (Derivative, diff, Function, Integer, Mul, pi, sin, Symbol, 2 | symbols) 3 | from sympy.physics.quantum.qexpr import QExpr 4 | from sympy.physics.quantum.dagger import Dagger 5 | from sympy.physics.quantum.hilbert import HilbertSpace 6 | from sympy.physics.quantum.operator import (Operator, UnitaryOperator, 7 | HermitianOperator, OuterProduct, 8 | DifferentialOperator, 9 | IdentityOperator) 10 | from sympy.physics.quantum.state import Ket, Bra, Wavefunction 11 | from sympy.physics.quantum.qapply import qapply 12 | from sympy.physics.quantum.represent import represent 13 | from sympy.core.trace import Tr 14 | from sympy.physics.quantum.spin import JzKet, JzBra 15 | from sympy.matrices import eye 16 | 17 | 18 | class CustomKet(Ket): 19 | @classmethod 20 | def default_args(self): 21 | return ("t",) 22 | 23 | 24 | class CustomOp(HermitianOperator): 25 | @classmethod 26 | def default_args(self): 27 | return ("T",) 28 | 29 | t_ket = CustomKet() 30 | t_op = CustomOp() 31 | 32 | 33 | def test_operator(): 34 | A = Operator('A') 35 | B = Operator('B') 36 | C = Operator('C') 37 | 38 | assert isinstance(A, Operator) 39 | assert isinstance(A, QExpr) 40 | 41 | assert A.label == (Symbol('A'),) 42 | assert A.is_commutative is False 43 | assert A.hilbert_space == HilbertSpace() 44 | 45 | assert A*B != B*A 46 | 47 | assert (A*(B + C)).expand() == A*B + A*C 48 | assert ((A + B)**2).expand() == A**2 + A*B + B*A + B**2 49 | 50 | assert t_op.label[0] == Symbol(t_op.default_args()[0]) 51 | 52 | assert Operator() == Operator("O") 53 | 54 | 55 | def test_operator_inv(): 56 | A = Operator('A') 57 | assert A*A.inv() == 1 58 | assert A.inv()*A == 1 59 | 60 | 61 | def test_hermitian(): 62 | H = HermitianOperator('H') 63 | 64 | assert isinstance(H, HermitianOperator) 65 | assert isinstance(H, Operator) 66 | 67 | assert Dagger(H) == H 68 | assert H.inv() != H 69 | assert H.is_commutative is False 70 | assert Dagger(H).is_commutative is False 71 | 72 | 73 | def test_unitary(): 74 | U = UnitaryOperator('U') 75 | 76 | assert isinstance(U, UnitaryOperator) 77 | assert isinstance(U, Operator) 78 | 79 | assert U.inv() == Dagger(U) 80 | assert U*Dagger(U) == 1 81 | assert Dagger(U)*U == 1 82 | assert U.is_commutative is False 83 | assert Dagger(U).is_commutative is False 84 | 85 | 86 | def test_identity(): 87 | I = IdentityOperator() 88 | O = Operator('O') 89 | x = Symbol("x") 90 | 91 | assert isinstance(I, IdentityOperator) 92 | assert isinstance(I, Operator) 93 | 94 | assert I * O == O 95 | assert O * I == O 96 | assert isinstance(I * I, IdentityOperator) 97 | assert isinstance(3 * I, Mul) 98 | assert isinstance(I * x, Mul) 99 | assert I.inv() == I 100 | assert Dagger(I) == I 101 | assert qapply(I * O) == O 102 | assert qapply(O * I) == O 103 | 104 | for n in [2, 3, 5]: 105 | assert represent(IdentityOperator(n)) == eye(n) 106 | 107 | 108 | def test_outer_product(): 109 | k = Ket('k') 110 | b = Bra('b') 111 | op = OuterProduct(k, b) 112 | 113 | assert isinstance(op, OuterProduct) 114 | assert isinstance(op, Operator) 115 | 116 | assert op.ket == k 117 | assert op.bra == b 118 | assert op.label == (k, b) 119 | assert op.is_commutative is False 120 | 121 | op = k*b 122 | 123 | assert isinstance(op, OuterProduct) 124 | assert isinstance(op, Operator) 125 | 126 | assert op.ket == k 127 | assert op.bra == b 128 | assert op.label == (k, b) 129 | assert op.is_commutative is False 130 | 131 | op = 2*k*b 132 | 133 | assert op == Mul(Integer(2), k, b) 134 | 135 | op = 2*(k*b) 136 | 137 | assert op == Mul(Integer(2), OuterProduct(k, b)) 138 | 139 | assert Dagger(k*b) == OuterProduct(Dagger(b), Dagger(k)) 140 | assert Dagger(k*b).is_commutative is False 141 | 142 | #test the _eval_trace 143 | assert Tr(OuterProduct(JzKet(1, 1), JzBra(1, 1))).doit() == 1 144 | 145 | 146 | def test_operator_dagger(): 147 | A = Operator('A') 148 | B = Operator('B') 149 | assert Dagger(A*B) == Dagger(B)*Dagger(A) 150 | assert Dagger(A + B) == Dagger(A) + Dagger(B) 151 | assert Dagger(A**2) == Dagger(A)**2 152 | 153 | 154 | def test_differential_operator(): 155 | x = Symbol('x') 156 | f = Function('f') 157 | d = DifferentialOperator(Derivative(f(x), x), f(x)) 158 | g = Wavefunction(x**2, x) 159 | assert qapply(d*g) == Wavefunction(2*x, x) 160 | assert d.expr == Derivative(f(x), x) 161 | assert d.function == f(x) 162 | assert d.variables == (x,) 163 | assert diff(d, x) == DifferentialOperator(Derivative(f(x), x, 2), f(x)) 164 | 165 | d = DifferentialOperator(Derivative(f(x), x, 2), f(x)) 166 | g = Wavefunction(x**3, x) 167 | assert qapply(d*g) == Wavefunction(6*x, x) 168 | assert d.expr == Derivative(f(x), x, 2) 169 | assert d.function == f(x) 170 | assert d.variables == (x,) 171 | assert diff(d, x) == DifferentialOperator(Derivative(f(x), x, 3), f(x)) 172 | 173 | d = DifferentialOperator(1/x*Derivative(f(x), x), f(x)) 174 | assert d.expr == 1/x*Derivative(f(x), x) 175 | assert d.function == f(x) 176 | assert d.variables == (x,) 177 | assert diff(d, x) == \ 178 | DifferentialOperator(Derivative(1/x*Derivative(f(x), x), x), f(x)) 179 | assert qapply(d*g) == Wavefunction(3*x, x) 180 | 181 | # 2D cartesian Laplacian 182 | y = Symbol('y') 183 | d = DifferentialOperator(Derivative(f(x, y), x, 2) + 184 | Derivative(f(x, y), y, 2), f(x, y)) 185 | w = Wavefunction(x**3*y**2 + y**3*x**2, x, y) 186 | assert d.expr == Derivative(f(x, y), x, 2) + Derivative(f(x, y), y, 2) 187 | assert d.function == f(x, y) 188 | assert d.variables == (x, y) 189 | assert diff(d, x) == \ 190 | DifferentialOperator(Derivative(d.expr, x), f(x, y)) 191 | assert diff(d, y) == \ 192 | DifferentialOperator(Derivative(d.expr, y), f(x, y)) 193 | assert qapply(d*w) == Wavefunction(2*x**3 + 6*x*y**2 + 6*x**2*y + 2*y**3, 194 | x, y) 195 | 196 | # 2D polar Laplacian (th = theta) 197 | r, th = symbols('r th') 198 | d = DifferentialOperator(1/r*Derivative(r*Derivative(f(r, th), r), r) + 199 | 1/(r**2)*Derivative(f(r, th), th, 2), f(r, th)) 200 | w = Wavefunction(r**2*sin(th), r, (th, 0, pi)) 201 | assert d.expr == \ 202 | 1/r*Derivative(r*Derivative(f(r, th), r), r) + \ 203 | 1/(r**2)*Derivative(f(r, th), th, 2) 204 | assert d.function == f(r, th) 205 | assert d.variables == (r, th) 206 | assert diff(d, r) == \ 207 | DifferentialOperator(Derivative(d.expr, r), f(r, th)) 208 | assert diff(d, th) == \ 209 | DifferentialOperator(Derivative(d.expr, th), f(r, th)) 210 | assert qapply(d*w) == Wavefunction(3*sin(th), r, (th, 0, pi)) 211 | -------------------------------------------------------------------------------- /sympsi/tests/test_state.py: -------------------------------------------------------------------------------- 1 | from sympy import (Add, conjugate, diff, I, Integer, latex, Mul, oo, pi, Pow, 2 | pretty, Rational, sin, sqrt, Symbol, symbols, sympify) 3 | from sympy.utilities.pytest import raises 4 | 5 | from sympy.physics.quantum.dagger import Dagger 6 | from sympy.physics.quantum.qexpr import QExpr 7 | from sympy.physics.quantum.state import ( 8 | Ket, Bra, TimeDepKet, TimeDepBra, 9 | KetBase, BraBase, StateBase, Wavefunction 10 | ) 11 | from sympy.physics.quantum.hilbert import HilbertSpace 12 | 13 | x, y, t = symbols('x,y,t') 14 | 15 | 16 | class CustomKet(Ket): 17 | @classmethod 18 | def default_args(self): 19 | return ("test",) 20 | 21 | 22 | class CustomKetMultipleLabels(Ket): 23 | @classmethod 24 | def default_args(self): 25 | return ("r", "theta", "phi") 26 | 27 | 28 | class CustomTimeDepKet(TimeDepKet): 29 | @classmethod 30 | def default_args(self): 31 | return ("test", "t") 32 | 33 | 34 | class CustomTimeDepKetMultipleLabels(TimeDepKet): 35 | @classmethod 36 | def default_args(self): 37 | return ("r", "theta", "phi", "t") 38 | 39 | 40 | def test_ket(): 41 | k = Ket('0') 42 | 43 | assert isinstance(k, Ket) 44 | assert isinstance(k, KetBase) 45 | assert isinstance(k, StateBase) 46 | assert isinstance(k, QExpr) 47 | 48 | assert k.label == (Symbol('0'),) 49 | assert k.hilbert_space == HilbertSpace() 50 | assert k.is_commutative is False 51 | 52 | # Make sure this doesn't get converted to the number pi. 53 | k = Ket('pi') 54 | assert k.label == (Symbol('pi'),) 55 | 56 | k = Ket(x, y) 57 | assert k.label == (x, y) 58 | assert k.hilbert_space == HilbertSpace() 59 | assert k.is_commutative is False 60 | 61 | assert k.dual_class() == Bra 62 | assert k.dual == Bra(x, y) 63 | assert k.subs(x, y) == Ket(y, y) 64 | 65 | k = CustomKet() 66 | assert k == CustomKet("test") 67 | 68 | k = CustomKetMultipleLabels() 69 | assert k == CustomKetMultipleLabels("r", "theta", "phi") 70 | 71 | assert Ket() == Ket('psi') 72 | 73 | 74 | def test_bra(): 75 | b = Bra('0') 76 | 77 | assert isinstance(b, Bra) 78 | assert isinstance(b, BraBase) 79 | assert isinstance(b, StateBase) 80 | assert isinstance(b, QExpr) 81 | 82 | assert b.label == (Symbol('0'),) 83 | assert b.hilbert_space == HilbertSpace() 84 | assert b.is_commutative is False 85 | 86 | # Make sure this doesn't get converted to the number pi. 87 | b = Bra('pi') 88 | assert b.label == (Symbol('pi'),) 89 | 90 | b = Bra(x, y) 91 | assert b.label == (x, y) 92 | assert b.hilbert_space == HilbertSpace() 93 | assert b.is_commutative is False 94 | 95 | assert b.dual_class() == Ket 96 | assert b.dual == Ket(x, y) 97 | assert b.subs(x, y) == Bra(y, y) 98 | 99 | assert Bra() == Bra('psi') 100 | 101 | 102 | def test_ops(): 103 | k0 = Ket(0) 104 | k1 = Ket(1) 105 | k = 2*I*k0 - (x/sqrt(2))*k1 106 | assert k == Add(Mul(2, I, k0), 107 | Mul(Rational(-1, 2), x, Pow(2, Rational(1, 2)), k1)) 108 | 109 | 110 | def test_time_dep_ket(): 111 | k = TimeDepKet(0, t) 112 | 113 | assert isinstance(k, TimeDepKet) 114 | assert isinstance(k, KetBase) 115 | assert isinstance(k, StateBase) 116 | assert isinstance(k, QExpr) 117 | 118 | assert k.label == (Integer(0),) 119 | assert k.args == (Integer(0), t) 120 | assert k.time == t 121 | 122 | assert k.dual_class() == TimeDepBra 123 | assert k.dual == TimeDepBra(0, t) 124 | 125 | assert k.subs(t, 2) == TimeDepKet(0, 2) 126 | 127 | k = TimeDepKet(x, 0.5) 128 | assert k.label == (x,) 129 | assert k.args == (x, sympify(0.5)) 130 | 131 | k = CustomTimeDepKet() 132 | assert k.label == (Symbol("test"),) 133 | assert k.time == Symbol("t") 134 | assert k == CustomTimeDepKet("test", "t") 135 | 136 | k = CustomTimeDepKetMultipleLabels() 137 | assert k.label == (Symbol("r"), Symbol("theta"), Symbol("phi")) 138 | assert k.time == Symbol("t") 139 | assert k == CustomTimeDepKetMultipleLabels("r", "theta", "phi", "t") 140 | 141 | assert TimeDepKet() == TimeDepKet("psi", "t") 142 | 143 | 144 | def test_time_dep_bra(): 145 | b = TimeDepBra(0, t) 146 | 147 | assert isinstance(b, TimeDepBra) 148 | assert isinstance(b, BraBase) 149 | assert isinstance(b, StateBase) 150 | assert isinstance(b, QExpr) 151 | 152 | assert b.label == (Integer(0),) 153 | assert b.args == (Integer(0), t) 154 | assert b.time == t 155 | 156 | assert b.dual_class() == TimeDepKet 157 | assert b.dual == TimeDepKet(0, t) 158 | 159 | k = TimeDepBra(x, 0.5) 160 | assert k.label == (x,) 161 | assert k.args == (x, sympify(0.5)) 162 | 163 | assert TimeDepBra() == TimeDepBra("psi", "t") 164 | 165 | 166 | def test_bra_ket_dagger(): 167 | x = symbols('x', complex=True) 168 | k = Ket('k') 169 | b = Bra('b') 170 | assert Dagger(k) == Bra('k') 171 | assert Dagger(b) == Ket('b') 172 | assert Dagger(k).is_commutative is False 173 | 174 | k2 = Ket('k2') 175 | e = 2*I*k + x*k2 176 | assert Dagger(e) == conjugate(x)*Dagger(k2) - 2*I*Dagger(k) 177 | 178 | 179 | def test_wavefunction(): 180 | x, y = symbols('x y', real=True) 181 | L = symbols('L', positive=True) 182 | n = symbols('n', integer=True, positive=True) 183 | 184 | f = Wavefunction(x**2, x) 185 | p = f.prob() 186 | lims = f.limits 187 | 188 | assert f.is_normalized is False 189 | assert f.norm == oo 190 | assert f(10) == 100 191 | assert p(10) == 10000 192 | assert lims[x] == (-oo, oo) 193 | assert diff(f, x) == Wavefunction(2*x, x) 194 | raises(NotImplementedError, lambda: f.normalize()) 195 | assert conjugate(f) == Wavefunction(conjugate(f.expr), x) 196 | assert conjugate(f) == Dagger(f) 197 | 198 | g = Wavefunction(x**2*y + y**2*x, (x, 0, 1), (y, 0, 2)) 199 | lims_g = g.limits 200 | 201 | assert lims_g[x] == (0, 1) 202 | assert lims_g[y] == (0, 2) 203 | assert g.is_normalized is False 204 | assert g.norm == sqrt(42)/3 205 | assert g(2, 4) == 0 206 | assert g(1, 1) == 2 207 | assert diff(diff(g, x), y) == Wavefunction(2*x + 2*y, (x, 0, 1), (y, 0, 2)) 208 | assert conjugate(g) == Wavefunction(conjugate(g.expr), *g.args[1:]) 209 | assert conjugate(g) == Dagger(g) 210 | 211 | h = Wavefunction(sqrt(5)*x**2, (x, 0, 1)) 212 | assert h.is_normalized is True 213 | assert h.normalize() == h 214 | assert conjugate(h) == Wavefunction(conjugate(h.expr), (x, 0, 1)) 215 | assert conjugate(h) == Dagger(h) 216 | 217 | piab = Wavefunction(sin(n*pi*x/L), (x, 0, L)) 218 | assert piab.norm == sqrt(L/2) 219 | assert piab(L + 1) == 0 220 | assert piab(0.5) == sin(0.5*n*pi/L) 221 | assert piab(0.5, n=1, L=1) == sin(0.5*pi) 222 | assert piab.normalize() == \ 223 | Wavefunction(sqrt(2)/sqrt(L)*sin(n*pi*x/L), (x, 0, L)) 224 | assert conjugate(piab) == Wavefunction(conjugate(piab.expr), (x, 0, L)) 225 | assert conjugate(piab) == Dagger(piab) 226 | 227 | k = Wavefunction(x**2, 'x') 228 | assert type(k.variables[0]) == Symbol 229 | -------------------------------------------------------------------------------- /sympsi/commutator.py: -------------------------------------------------------------------------------- 1 | """The commutator: [A,B] = A*B - B*A.""" 2 | 3 | from __future__ import print_function, division 4 | 5 | from sympy import S, Expr, Mul, Add 6 | from sympy.core.compatibility import u 7 | from sympy.integrals.integrals import Integral 8 | from sympy.printing.pretty.stringpict import prettyForm 9 | 10 | from sympsi.dagger import Dagger 11 | from sympsi.operator import Operator 12 | 13 | 14 | __all__ = [ 15 | 'Commutator' 16 | ] 17 | 18 | #----------------------------------------------------------------------------- 19 | # Commutator 20 | #----------------------------------------------------------------------------- 21 | 22 | 23 | class Commutator(Expr): 24 | """The standard commutator, in an unevaluated state. 25 | 26 | Evaluating a commutator is defined [1]_ as: ``[A, B] = A*B - B*A``. This 27 | class returns the commutator in an unevaluated form. To evaluate the 28 | commutator, use the ``.doit()`` method. 29 | 30 | Cannonical ordering of a commutator is ``[A, B]`` for ``A < B``. The 31 | arguments of the commutator are put into canonical order using ``__cmp__``. 32 | If ``B < A``, then ``[B, A]`` is returned as ``-[A, B]``. 33 | 34 | Parameters 35 | ========== 36 | 37 | A : Expr 38 | The first argument of the commutator [A,B]. 39 | B : Expr 40 | The second argument of the commutator [A,B]. 41 | 42 | Examples 43 | ======== 44 | 45 | >>> from sympsi import Commutator, Dagger, Operator 46 | >>> from sympy.abc import x, y 47 | >>> A = Operator('A') 48 | >>> B = Operator('B') 49 | >>> C = Operator('C') 50 | 51 | Create a commutator and use ``.doit()`` to evaluate it: 52 | 53 | >>> comm = Commutator(A, B) 54 | >>> comm 55 | [A,B] 56 | >>> comm.doit() 57 | A*B - B*A 58 | 59 | The commutator orders it arguments in canonical order: 60 | 61 | >>> comm = Commutator(B, A); comm 62 | -[A,B] 63 | 64 | Commutative constants are factored out: 65 | 66 | >>> Commutator(3*x*A, x*y*B) 67 | 3*x**2*y*[A,B] 68 | 69 | Using ``.expand(commutator=True)``, the standard commutator expansion rules 70 | can be applied: 71 | 72 | >>> Commutator(A+B, C).expand(commutator=True) 73 | [A,C] + [B,C] 74 | >>> Commutator(A, B+C).expand(commutator=True) 75 | [A,B] + [A,C] 76 | >>> Commutator(A*B, C).expand(commutator=True) 77 | [A,C]*B + A*[B,C] 78 | >>> Commutator(A, B*C).expand(commutator=True) 79 | [A,B]*C + B*[A,C] 80 | 81 | Adjoint operations applied to the commutator are properly applied to the 82 | arguments: 83 | 84 | >>> Dagger(Commutator(A, B)) 85 | -[Dagger(A),Dagger(B)] 86 | 87 | References 88 | ========== 89 | 90 | .. [1] http://en.wikipedia.org/wiki/Commutator 91 | """ 92 | is_commutative = False 93 | 94 | def __new__(cls, A, B): 95 | r = cls.eval(A, B) 96 | if r is not None: 97 | return r 98 | obj = Expr.__new__(cls, A, B) 99 | return obj 100 | 101 | @classmethod 102 | def eval(cls, a, b): 103 | if not (a and b): 104 | return S.Zero 105 | if a == b: 106 | return S.Zero 107 | if a.is_commutative or b.is_commutative: 108 | return S.Zero 109 | 110 | # [xA,yB] -> xy*[A,B] 111 | # from sympy.physics.qmul import QMul 112 | ca, nca = a.args_cnc() 113 | cb, ncb = b.args_cnc() 114 | c_part = ca + cb 115 | if c_part: 116 | return Mul(Mul(*c_part), cls(Mul._from_args(nca), Mul._from_args(ncb))) 117 | 118 | # Canonical ordering of arguments 119 | # The Commutator [A, B] is in canonical form if A < B. 120 | if a.compare(b) == 1: 121 | return S.NegativeOne*cls(b, a) 122 | 123 | def _eval_expand_commutator(self, **hints): 124 | A = self.args[0] 125 | B = self.args[1] 126 | 127 | if isinstance(A, Add): 128 | # [A + B, C] -> [A, C] + [B, C] 129 | sargs = [] 130 | for term in A.args: 131 | comm = Commutator(term, B) 132 | if isinstance(comm, Commutator): 133 | comm = comm._eval_expand_commutator() 134 | sargs.append(comm) 135 | return Add(*sargs) 136 | elif isinstance(B, Add): 137 | # [A, B + C] -> [A, B] + [A, C] 138 | sargs = [] 139 | for term in B.args: 140 | comm = Commutator(A, term) 141 | if isinstance(comm, Commutator): 142 | comm = comm._eval_expand_commutator() 143 | sargs.append(comm) 144 | return Add(*sargs) 145 | elif isinstance(A, Mul): 146 | # [A*B, C] -> A*[B, C] + [A, C]*B 147 | a = A.args[0] 148 | b = Mul(*A.args[1:]) 149 | c = B 150 | comm1 = Commutator(b, c) 151 | comm2 = Commutator(a, c) 152 | if isinstance(comm1, Commutator): 153 | comm1 = comm1._eval_expand_commutator() 154 | if isinstance(comm2, Commutator): 155 | comm2 = comm2._eval_expand_commutator() 156 | first = Mul(a, comm1) 157 | second = Mul(comm2, b) 158 | return Add(first, second) 159 | elif isinstance(B, Mul): 160 | # [A, B*C] -> [A, B]*C + B*[A, C] 161 | a = A 162 | b = B.args[0] 163 | c = Mul(*B.args[1:]) 164 | comm1 = Commutator(a, b) 165 | comm2 = Commutator(a, c) 166 | if isinstance(comm1, Commutator): 167 | comm1 = comm1._eval_expand_commutator() 168 | if isinstance(comm2, Commutator): 169 | comm2 = comm2._eval_expand_commutator() 170 | first = Mul(comm1, c) 171 | second = Mul(b, comm2) 172 | return Add(first, second) 173 | elif isinstance(A, Integral): 174 | # [∫adx, B] -> ∫[a, B]dx 175 | func, lims = A.function, A.limits 176 | new_args = [Commutator(func, B)] 177 | for lim in lims: 178 | new_args.append(lim) 179 | return Integral(*new_args) 180 | elif isinstance(B, Integral): 181 | # [A, ∫bdx] -> ∫[A, b]dx 182 | func, lims = B.function, B.limits 183 | new_args = [Commutator(A, func)] 184 | for lim in lims: 185 | new_args.append(lim) 186 | return Integral(*new_args) 187 | # No changes, so return self 188 | return self 189 | 190 | def doit(self, **hints): 191 | """ Evaluate commutator """ 192 | A = self.args[0] 193 | B = self.args[1] 194 | if isinstance(A, Operator) and isinstance(B, Operator): 195 | try: 196 | comm = A._eval_commutator(B, **hints) 197 | except NotImplementedError: 198 | try: 199 | comm = -1*B._eval_commutator(A, **hints) 200 | except NotImplementedError: 201 | comm = None 202 | if comm is not None: 203 | return comm.doit(**hints) 204 | return (A*B - B*A).doit(**hints) 205 | 206 | def _eval_adjoint(self): 207 | return Commutator(Dagger(self.args[1]), Dagger(self.args[0])) 208 | 209 | def _sympyrepr(self, printer, *args): 210 | return "%s(%s,%s)" % ( 211 | self.__class__.__name__, printer._print( 212 | self.args[0]), printer._print(self.args[1]) 213 | ) 214 | 215 | def _sympystr(self, printer, *args): 216 | return "[%s,%s]" % (self.args[0], self.args[1]) 217 | 218 | def _pretty(self, printer, *args): 219 | pform = printer._print(self.args[0], *args) 220 | pform = prettyForm(*pform.right((prettyForm(u(','))))) 221 | pform = prettyForm(*pform.right((printer._print(self.args[1], *args)))) 222 | pform = prettyForm(*pform.parens(left='[', right=']')) 223 | return pform 224 | 225 | def _latex(self, printer, *args): 226 | return "\\left[%s,%s\\right]" % tuple([ 227 | printer._print(arg, *args) for arg in self.args]) 228 | -------------------------------------------------------------------------------- /sympsi/expectation.py: -------------------------------------------------------------------------------- 1 | """Expectation values and other statistical measures for operators: ``""" 2 | 3 | from __future__ import print_function, division 4 | 5 | from sympy import Expr, Add, Mul, Integer, Symbol, Integral 6 | from sympsi.dagger import Dagger 7 | from sympsi.qapply import qapply 8 | 9 | 10 | __all__ = [ 11 | 'Expectation', 12 | 'Covariance' 13 | ] 14 | 15 | #----------------------------------------------------------------------------- 16 | # Expectation 17 | #----------------------------------------------------------------------------- 18 | 19 | class Expectation(Expr): 20 | """ 21 | Expectation Value of an operator, expressed in terms of bracket . 22 | 23 | If the second argument, 'is_normal_order' is 'True', 24 | the normal ordering notation (<: :>) is attached. 25 | 26 | doit() returns the normally ordered operator inside the bracket. 27 | 28 | Parameters 29 | ========== 30 | 31 | A : Expr 32 | The argument of the expectation value 33 | 34 | is_normal_order : bool 35 | A bool that indicates if the operator inside the Expectation 36 | value bracket should be normally ordered (True) or left 37 | untouched (False, default value) 38 | 39 | Examples 40 | ======== 41 | 42 | >>> a = BosonOp("a") 43 | >>> Expectation(a * Dagger(a)) 44 | 45 | >>> Expectation(a * Dagger(a), True) 46 | <:a a†:> 47 | >>> Expectation(a * Dagger(a), True).doit() 48 | 49 | 50 | """ 51 | is_commutative = True 52 | 53 | @property 54 | def expression(self): 55 | return self.args[0] 56 | 57 | @property 58 | def is_normal_order(self): 59 | return bool(self.args[1]) 60 | 61 | @classmethod 62 | def default_args(self): 63 | return (Symbol("A"), False) 64 | 65 | def __new__(cls, *args): 66 | if not len(args) in [1, 2]: 67 | raise ValueError('1 or 2 parameters expected, got %s' % str(args)) 68 | if len(args) == 1: 69 | args = (args[0], Integer(0)) 70 | if len(args) == 2: 71 | args = (args[0], Integer(args[1])) 72 | return Expr.__new__(cls, *args) 73 | 74 | def _eval_expand_expectation(self, **hints): 75 | A = self.args[0] 76 | if isinstance(A, Add): 77 | # = + 78 | return Add(*(Expectation(a, self.is_normal_order).expand(expectation=True) for a in A.args)) 79 | 80 | if isinstance(A, Mul): 81 | # = c where c is a commutative term 82 | A = A.expand() 83 | cA, ncA = A.args_cnc() 84 | return Mul(Mul(*cA), Expectation(Mul._from_args(ncA), self.is_normal_order).expand()) 85 | 86 | if isinstance(A, Integral): 87 | # <∫adx> -> ∫dx 88 | func, lims = A.function, A.limits 89 | new_args = [Expectation(func, self.is_normal_order).expand()] 90 | for lim in lims: 91 | new_args.append(lim) 92 | return Integral(*new_args) 93 | 94 | return self 95 | 96 | def doit(self, **hints): 97 | """ 98 | return the expectation value normally ordered operator if is_normal_order=True 99 | """ 100 | from sympsi.operatorordering import normal_order 101 | if self.is_normal_order == True: 102 | return Expectation(normal_order(self.args[0]), False) 103 | return self 104 | 105 | def eval_state(self, state): 106 | return qapply(Dagger(state) * self.args[0] * state, dagger=True).doit() 107 | 108 | def _latex(self, printer, *args): 109 | if self.is_normal_order: 110 | return r"\left\langle: %s :\right\rangle" % printer._print(self.args[0], *args) 111 | else: 112 | return r"\left\langle %s \right\rangle" % printer._print(self.args[0], *args) 113 | 114 | #----------------------------------------------------------------------------- 115 | # Covariance 116 | #----------------------------------------------------------------------------- 117 | 118 | class Covariance(Expr): 119 | """Covariance of two operators, expressed in terms of bracket 120 | 121 | If the third argument, 'is_normal_order' is 'True', 122 | the normal ordering notation (<: , :>) is attached. 123 | 124 | doit() returns the expression in terms of expectation values. 125 | < A, B > --> < AB > - < A >< B > 126 | 127 | Parameters 128 | ========== 129 | 130 | A : Expr 131 | The first argument of the expectation value 132 | 133 | B : Expr 134 | The second argument of the expectation value 135 | 136 | is_normal_order : bool 137 | A bool that indicates if the operator inside the Expectation 138 | value bracket should be normally ordered (True) or left 139 | untouched (False, default value) 140 | 141 | Examples 142 | ======== 143 | 144 | >>> A, B = Operator("A"), Operator("B") 145 | >>> Covariance(A, B) 146 | < A, B > 147 | >>> Covariance(A, B, True) 148 | <:A, B:> 149 | >>> Covariance(A, B).doit() 150 | < AB > - < A >< B > 151 | >>> Covariance(A, B, True).doit() 152 | <:AB:> - <:A:><:B:> 153 | 154 | """ 155 | 156 | is_commutative = True 157 | @property 158 | def is_normal_order(self): 159 | return bool(self.args[2]) 160 | 161 | @classmethod 162 | def default_args(self): 163 | return (Symbol("A"), Symbol("B"), False) 164 | 165 | def __new__(cls, *args, **hints): 166 | if not len(args) in [2, 3]: 167 | raise ValueError('2 or 3 parameters expected, got %s' % args) 168 | 169 | if len(args) == 2: 170 | args = (args[0], args[1], Integer(0)) 171 | 172 | if len(args) == 3: 173 | args = (args[0], args[1], Integer(args[2])) 174 | 175 | return Expr.__new__(cls, *args) 176 | 177 | def _eval_expand_covariance(self, **hints): 178 | A, B = self.args[0], self.args[1] 179 | # = + 180 | if isinstance(A, Add): 181 | return Add(*(Covariance(a, B, self.is_normal_order).expand() 182 | for a in A.args)) 183 | 184 | # = + 185 | if isinstance(B, Add): 186 | return Add(*(Covariance(A, b, self.is_normal_order).expand() 187 | for b in B.args)) 188 | 189 | if isinstance(A, Mul): 190 | A = A.expand() 191 | cA, ncA = A.args_cnc() 192 | return Mul(Mul(*cA), Covariance(Mul._from_args(ncA), B, 193 | self.is_normal_order).expand()) 194 | if isinstance(B, Mul): 195 | B = B.expand() 196 | cB, ncB = B.args_cnc() 197 | return Mul(Mul(*cB), Covariance(A, Mul._from_args(ncB), 198 | self.is_normal_order).expand()) 199 | if isinstance(A, Integral): 200 | # <∫adx, B> -> ∫dx 201 | func, lims = A.function, A.limits 202 | new_args = [Covariance(func, B, self.is_normal_order).expand()] 203 | for lim in lims: 204 | new_args.append(lim) 205 | return Integral(*new_args) 206 | if isinstance(B, Integral): 207 | # -> ∫dx 208 | func, lims = B.function, B.limits 209 | new_args = [Covariance(A, func, self.is_normal_order).expand()] 210 | for lim in lims: 211 | new_args.append(lim) 212 | return Integral(*new_args) 213 | return self 214 | 215 | def doit(self, **hints): 216 | """ Evaluate covariance of two operators A and B """ 217 | A = self.args[0] 218 | B = self.args[1] 219 | no = self.is_normal_order 220 | return Expectation(A*B, no) - Expectation(A, no) * Expectation(B, no) 221 | 222 | def _latex(self, printer, *args): 223 | if self.is_normal_order: 224 | return r"\left\langle: %s, %s :\right\rangle" % tuple([ 225 | printer._print(self.args[0], *args), 226 | printer._print(self.args[1], *args)]) 227 | else: 228 | return r"\left\langle %s, %s \right\rangle" % tuple([ 229 | printer._print(self.args[0], *args), 230 | printer._print(self.args[1], *args)]) 231 | -------------------------------------------------------------------------------- /sympsi/fermion.py: -------------------------------------------------------------------------------- 1 | """Fermionic quantum operators.""" 2 | 3 | from warnings import warn 4 | 5 | from sympy.core.compatibility import u 6 | from sympy import Add, Mul, Pow, Integer, exp, sqrt, conjugate 7 | from sympy.functions.special.tensor_functions import KroneckerDelta 8 | 9 | from sympsi import Operator, Commutator, AntiCommutator, Dagger 10 | from sympsi import HilbertSpace, FockSpace, Ket, Bra 11 | 12 | __all__ = [ 13 | 'FermionOp', 14 | 'FermionFockKet', 15 | 'FermionFockBra', 16 | 'MultiFermionOp' 17 | ] 18 | 19 | 20 | class FermionOp(Operator): 21 | """A fermionic operator that satisfies {c, Dagger(c)} == 1. 22 | 23 | Parameters 24 | ========== 25 | 26 | name : str 27 | A string that labels the fermionic mode. 28 | 29 | annihilation : bool 30 | A bool that indicates if the fermionic operator is an annihilation 31 | (True, default value) or creation operator (False) 32 | 33 | Examples 34 | ======== 35 | 36 | >>> from sympsi import Dagger, AntiCommutator 37 | >>> from sympsi.fermion import FermionOp 38 | >>> c = FermionOp("c") 39 | >>> AntiCommutator(c, Dagger(c)).doit() 40 | 1 41 | """ 42 | @property 43 | def name(self): 44 | return self.args[0] 45 | 46 | @property 47 | def is_annihilation(self): 48 | return bool(self.args[1]) 49 | 50 | @classmethod 51 | def default_args(self): 52 | return ("c", True) 53 | 54 | def __new__(cls, *args, **hints): 55 | if not len(args) in [1, 2]: 56 | raise ValueError('1 or 2 parameters expected, got %s' % args) 57 | 58 | if len(args) == 1: 59 | args = (args[0], Integer(1)) 60 | 61 | if len(args) == 2: 62 | args = (args[0], Integer(args[1])) 63 | 64 | return Operator.__new__(cls, *args) 65 | 66 | def _eval_commutator_FermionOp(self, other, **hints): 67 | if 'independent' in hints and hints['independent']: 68 | # [c, d] = 0 69 | return Integer(0) 70 | 71 | return None 72 | 73 | def _eval_anticommutator_FermionOp(self, other, **hints): 74 | if self.name == other.name: 75 | # {a^\dagger, a} = 1 76 | if not self.is_annihilation and other.is_annihilation: 77 | return Integer(1) 78 | 79 | elif 'independent' in hints and hints['independent']: 80 | # {c, d} = 2 * c * d, because [c, d] = 0 for independent operators 81 | return 2 * self * other 82 | 83 | return None 84 | 85 | def _eval_anticommutator_BosonOp(self, other, **hints): 86 | # because fermions and bosons commute 87 | return 2 * self * other 88 | 89 | def _eval_commutator_BosonOp(self, other, **hints): 90 | return Integer(0) 91 | 92 | def _eval_adjoint(self): 93 | return FermionOp(str(self.name), not self.is_annihilation) 94 | 95 | def _print_contents_latex(self, printer, *args): 96 | if self.is_annihilation: 97 | return r'{%s}' % str(self.name) 98 | else: 99 | return r'{{%s}^\dag}' % str(self.name) 100 | 101 | def _print_contents(self, printer, *args): 102 | if self.is_annihilation: 103 | return r'%s' % str(self.name) 104 | else: 105 | return r'Dagger(%s)' % str(self.name) 106 | 107 | def _print_contents_pretty(self, printer, *args): 108 | from sympy.printing.pretty.stringpict import prettyForm 109 | pform = printer._print(self.args[0], *args) 110 | if self.is_annihilation: 111 | return pform 112 | else: 113 | return pform**prettyForm(u('\u2020')) 114 | 115 | class MultiFermionOp(Operator): 116 | """Fermionic operators that satisfy the commutation relations: 117 | for discrete label for modes: 118 | {a(k1), Dagger(a(k2))} == KroneckerDelta(k1, k2). 119 | 120 | for continuous label for modes: 121 | {a(k1), Dagger(a(k2))} == DiracDelta(k1 - k2). 122 | 123 | and in both cases: 124 | {a(k1), a(k2)} == {Dagger(a(k1)), Dagger(a(k2))} == 0. 125 | 126 | 127 | Parameters 128 | ========== 129 | 130 | name : str 131 | A string that labels the bosonic mode. 132 | 133 | mode: Symbol 134 | A symbol that denotes the mode label. 135 | 136 | normalization : ['discrete', 'continuous'] 137 | 'discrete' for KroneckerDelta function, 138 | 'continuous' for DiracDelta function. 139 | should be specified in any case. 140 | 141 | annihilation : bool 142 | A bool that indicates if the bosonic operator is an annihilation (True, 143 | default value) or creation operator (False) 144 | 145 | 146 | Examples 147 | ======== 148 | 149 | >>> from sympsi import Dagger, Commutator 150 | >>> from sympsi.fermion import MultiFermionOp 151 | >>> w1, w2 = symbols("w1, w2") 152 | >>> a1 = MultiFermionOp("a", w1, 'discrete') 153 | >>> a2 = MultiFermionOp("a", w2, 'discrete') 154 | >>> Commutator(a1, Dagger(a2)).doit() 155 | KroneckerDelta(w1, w2) 156 | >>> Commutator(a1, a2).doit() 157 | 0 158 | >>> Commutator(Dagger(a1), Dagger(a2)).doit() 159 | 0 160 | >>> b1 = MultiFermionOp("b", w1, 'continuous') 161 | >>> b2 = MultiFermionOp("b", w2, 'continuous') 162 | >>> AntiCommutator(b1, Dagger(b2)).doit() 163 | DiracDelta(w1 - w2) 164 | >>> AntiCommutator(b1, b2).doit() 165 | 0 166 | >>> AntiCommutator(Dagger(b1), Dagger(b2)).doit() 167 | 0 168 | 169 | """ 170 | 171 | @property 172 | def free_symbols(self): 173 | return self.args[1].free_symbols 174 | 175 | @property 176 | def name(self): 177 | return self.args[0] 178 | 179 | @property 180 | def mode(self): 181 | return self.args[1] 182 | 183 | @property 184 | def normalization_type(self): 185 | return str(self.args[3]) 186 | 187 | @property 188 | def is_annihilation(self): 189 | return bool(self.args[1]) 190 | 191 | @classmethod 192 | def default_args(self): 193 | return ("a", Symbol("\omega"), "discrete", True) 194 | 195 | def __new__(cls, *args, **hints): 196 | if not len(args) in [3, 4]: 197 | raise ValueError('3 or 4 parameters expected, got %s' % args) 198 | 199 | if str(args[2]) not in ['discrete', 'continuous']: 200 | print("discrete or continuous: %s" % args[2]) 201 | raise ValueError('The third argument should be "discrete" or "continuous", got %s' % args) 202 | 203 | if len(args) == 3: 204 | args = (args[0], args[1], str(args[2]), Integer(1)) 205 | 206 | if len(args) == 4: 207 | args = (args[0], args[1], str(args[2]), Integer(args[3])) 208 | 209 | return Operator.__new__(cls, *args) 210 | 211 | 212 | ######### 213 | def _eval_commutator_FermionOp(self, other, **hints): 214 | if 'independent' in hints and hints['independent']: 215 | # [c, d] = 0 216 | return Integer(0) 217 | 218 | return None 219 | 220 | 221 | def _eval_anticommutator_FermionOp(self, other, **hints): 222 | if self.name == other.name: 223 | # {a^\dagger, a} = 1 224 | if not self.is_annihilation and other.is_annihilation: 225 | return Integer(1) 226 | 227 | elif 'independent' in hints and hints['independent']: 228 | # {c, d} = 2 * c * d, because [c, d] = 0 for independent operators 229 | return 2 * self * other 230 | 231 | return None 232 | 233 | def _eval_anticommutator_BosonOp(self, other, **hints): 234 | # because fermions and bosons commute 235 | return 2 * self * other 236 | 237 | def _eval_commutator_BosonOp(self, other, **hints): 238 | return Integer(0) 239 | 240 | def _eval_adjoint(self): 241 | return FermionOp(str(self.name), not self.is_annihilation) 242 | 243 | def _print_contents_latex(self, printer, *args): 244 | if self.is_annihilation: 245 | return r'{%s}' % str(self.name) 246 | else: 247 | return r'{{%s}^\dag}' % str(self.name) 248 | 249 | def _print_contents(self, printer, *args): 250 | if self.is_annihilation: 251 | return r'%s' % str(self.name) 252 | else: 253 | return r'Dagger(%s)' % str(self.name) 254 | 255 | def _print_contents_pretty(self, printer, *args): 256 | from sympy.printing.pretty.stringpict import prettyForm 257 | pform = printer._print(self.args[0], *args) 258 | if self.is_annihilation: 259 | return pform 260 | else: 261 | return pform**prettyForm(u('\u2020')) 262 | 263 | 264 | class FermionFockKet(Ket): 265 | """Fock state ket for a fermionic mode. 266 | 267 | Parameters 268 | ========== 269 | 270 | n : Number 271 | The Fock state number. 272 | 273 | """ 274 | 275 | def __new__(cls, n): 276 | if n not in [0, 1]: 277 | raise ValueError("n must be 0 or 1") 278 | return Ket.__new__(cls, n) 279 | 280 | @property 281 | def n(self): 282 | return self.label[0] 283 | 284 | @classmethod 285 | def dual_class(self): 286 | return FermionFockBra 287 | 288 | @classmethod 289 | def _eval_hilbert_space(cls, label): 290 | return HilbertSpace() 291 | 292 | def _eval_innerproduct_FermionFockBra(self, bra, **hints): 293 | return KroneckerDelta(self.n, bra.n) 294 | 295 | def _apply_operator_FermionOp(self, op, **options): 296 | if op.is_annihilation: 297 | if self.n == 1: 298 | return FermionFockKet(0) 299 | else: 300 | return Integer(0) 301 | else: 302 | if self.n == 0: 303 | return FermionFockKet(1) 304 | else: 305 | return Integer(0) 306 | 307 | 308 | class FermionFockBra(Bra): 309 | """Fock state bra for a fermionic mode. 310 | 311 | Parameters 312 | ========== 313 | 314 | n : Number 315 | The Fock state number. 316 | 317 | """ 318 | 319 | def __new__(cls, n): 320 | if n not in [0, 1]: 321 | raise ValueError("n must be 0 or 1") 322 | return Bra.__new__(cls, n) 323 | 324 | @property 325 | def n(self): 326 | return self.label[0] 327 | 328 | @classmethod 329 | def dual_class(self): 330 | return FermionFockKet 331 | -------------------------------------------------------------------------------- /sympsi/operatorset.py: -------------------------------------------------------------------------------- 1 | """ A module for mapping operators to their corresponding eigenstates 2 | and vice versa 3 | 4 | It contains a global dictionary with eigenstate-operator pairings. 5 | If a new state-operator pair is created, this dictionary should be 6 | updated as well. 7 | 8 | It also contains functions operators_to_state and state_to_operators 9 | for mapping between the two. These can handle both classes and 10 | instances of operators and states. See the individual function 11 | descriptions for details. 12 | 13 | TODO List: 14 | - Update the dictionary with a complete list of state-operator pairs 15 | """ 16 | 17 | from __future__ import print_function, division 18 | 19 | from sympy.physics.quantum.cartesian import (XOp, YOp, ZOp, XKet, PxOp, PxKet, 20 | PositionKet3D) 21 | from sympsi.operator import Operator 22 | from sympsi.state import StateBase, BraBase, Ket 23 | from sympsi.spin import (JxOp, JyOp, JzOp, J2Op, JxKet, JyKet, 24 | JzKet) 25 | 26 | __all__ = [ 27 | 'operators_to_state', 28 | 'state_to_operators' 29 | ] 30 | 31 | #state_mapping stores the mappings between states and their associated 32 | #operators or tuples of operators. This should be updated when new 33 | #classes are written! Entries are of the form PxKet : PxOp or 34 | #something like 3DKet : (ROp, ThetaOp, PhiOp) 35 | 36 | #frozenset is used so that the reverse mapping can be made 37 | #(regular sets are not hashable because they are mutable 38 | state_mapping = { JxKet: frozenset((J2Op, JxOp)), 39 | JyKet: frozenset((J2Op, JyOp)), 40 | JzKet: frozenset((J2Op, JzOp)), 41 | Ket: Operator, 42 | PositionKet3D: frozenset((XOp, YOp, ZOp)), 43 | PxKet: PxOp, 44 | XKet: XOp } 45 | 46 | op_mapping = dict((v, k) for k, v in state_mapping.items()) 47 | 48 | 49 | def operators_to_state(operators, **options): 50 | """ Returns the eigenstate of the given operator or set of operators 51 | 52 | A global function for mapping operator classes to their associated 53 | states. It takes either an Operator or a set of operators and 54 | returns the state associated with these. 55 | 56 | This function can handle both instances of a given operator or 57 | just the class itself (i.e. both XOp() and XOp) 58 | 59 | There are multiple use cases to consider: 60 | 61 | 1) A class or set of classes is passed: First, we try to 62 | instantiate default instances for these operators. If this fails, 63 | then the class is simply returned. If we succeed in instantiating 64 | default instances, then we try to call state._operators_to_state 65 | on the operator instances. If this fails, the class is returned. 66 | Otherwise, the instance returned by _operators_to_state is returned. 67 | 68 | 2) An instance or set of instances is passed: In this case, 69 | state._operators_to_state is called on the instances passed. If 70 | this fails, a state class is returned. If the method returns an 71 | instance, that instance is returned. 72 | 73 | In both cases, if the operator class or set does not exist in the 74 | state_mapping dictionary, None is returned. 75 | 76 | Parameters 77 | ========== 78 | 79 | arg: Operator or set 80 | The class or instance of the operator or set of operators 81 | to be mapped to a state 82 | 83 | Examples 84 | ======== 85 | 86 | >>> from sympsi.cartesian import XOp, PxOp 87 | >>> from sympsi.operatorset import operators_to_state 88 | >>> from sympsi.operator import Operator 89 | >>> operators_to_state(XOp) 90 | |x> 91 | >>> operators_to_state(XOp()) 92 | |x> 93 | >>> operators_to_state(PxOp) 94 | |px> 95 | >>> operators_to_state(PxOp()) 96 | |px> 97 | >>> operators_to_state(Operator) 98 | |psi> 99 | >>> operators_to_state(Operator()) 100 | |psi> 101 | """ 102 | 103 | if not (isinstance(operators, Operator) 104 | or isinstance(operators, set) or issubclass(operators, Operator)): 105 | raise NotImplementedError("Argument is not an Operator or a set!") 106 | 107 | if isinstance(operators, set): 108 | for s in operators: 109 | if not (isinstance(s, Operator) 110 | or issubclass(s, Operator)): 111 | raise NotImplementedError("Set is not all Operators!") 112 | 113 | #ops = tuple(operators) 114 | ops = frozenset(operators) 115 | 116 | if ops in op_mapping: # ops is a list of classes in this case 117 | #Try to get an object from default instances of the 118 | #operators...if this fails, return the class 119 | try: 120 | op_instances = [op() for op in ops] 121 | ret = _get_state(op_mapping[ops], set(op_instances), **options) 122 | except NotImplementedError: 123 | ret = op_mapping[ops] 124 | 125 | return ret 126 | else: 127 | tmp = [type(o) for o in ops] 128 | classes = frozenset(tmp) 129 | 130 | if classes in op_mapping: 131 | ret = _get_state(op_mapping[classes], ops, **options) 132 | else: 133 | ret = None 134 | 135 | return ret 136 | else: 137 | if operators in op_mapping: 138 | try: 139 | op_instance = operators() 140 | ret = _get_state(op_mapping[operators], op_instance, **options) 141 | except NotImplementedError: 142 | ret = op_mapping[operators] 143 | 144 | return ret 145 | elif type(operators) in op_mapping: 146 | return _get_state(op_mapping[type(operators)], operators, **options) 147 | else: 148 | return None 149 | 150 | 151 | def state_to_operators(state, **options): 152 | """ Returns the operator or set of operators corresponding to the 153 | given eigenstate 154 | 155 | A global function for mapping state classes to their associated 156 | operators or sets of operators. It takes either a state class 157 | or instance. 158 | 159 | This function can handle both instances of a given state or just 160 | the class itself (i.e. both XKet() and XKet) 161 | 162 | There are multiple use cases to consider: 163 | 164 | 1) A state class is passed: In this case, we first try 165 | instantiating a default instance of the class. If this succeeds, 166 | then we try to call state._state_to_operators on that instance. 167 | If the creation of the default instance or if the calling of 168 | _state_to_operators fails, then either an operator class or set of 169 | operator classes is returned. Otherwise, the appropriate 170 | operator instances are returned. 171 | 172 | 2) A state instance is returned: Here, state._state_to_operators 173 | is called for the instance. If this fails, then a class or set of 174 | operator classes is returned. Otherwise, the instances are returned. 175 | 176 | In either case, if the state's class does not exist in 177 | state_mapping, None is returned. 178 | 179 | Parameters 180 | ========== 181 | 182 | arg: StateBase class or instance (or subclasses) 183 | The class or instance of the state to be mapped to an 184 | operator or set of operators 185 | 186 | Examples 187 | ======== 188 | 189 | >>> from sympsi.cartesian import XKet, PxKet, XBra, PxBra 190 | >>> from sympsi.operatorset import state_to_operators 191 | >>> from sympsi.state import Ket, Bra 192 | >>> state_to_operators(XKet) 193 | X 194 | >>> state_to_operators(XKet()) 195 | X 196 | >>> state_to_operators(PxKet) 197 | Px 198 | >>> state_to_operators(PxKet()) 199 | Px 200 | >>> state_to_operators(PxBra) 201 | Px 202 | >>> state_to_operators(XBra) 203 | X 204 | >>> state_to_operators(Ket) 205 | O 206 | >>> state_to_operators(Bra) 207 | O 208 | """ 209 | 210 | if not (isinstance(state, StateBase) or issubclass(state, StateBase)): 211 | raise NotImplementedError("Argument is not a state!") 212 | 213 | if state in state_mapping: # state is a class 214 | state_inst = _make_default(state) 215 | try: 216 | ret = _get_ops(state_inst, 217 | _make_set(state_mapping[state]), **options) 218 | except (NotImplementedError, TypeError): 219 | ret = state_mapping[state] 220 | elif type(state) in state_mapping: 221 | ret = _get_ops(state, 222 | _make_set(state_mapping[type(state)]), **options) 223 | elif isinstance(state, BraBase) and state.dual_class() in state_mapping: 224 | ret = _get_ops(state, 225 | _make_set(state_mapping[state.dual_class()])) 226 | elif issubclass(state, BraBase) and state.dual_class() in state_mapping: 227 | state_inst = _make_default(state) 228 | try: 229 | ret = _get_ops(state_inst, 230 | _make_set(state_mapping[state.dual_class()])) 231 | except (NotImplementedError, TypeError): 232 | ret = state_mapping[state.dual_class()] 233 | else: 234 | ret = None 235 | 236 | return _make_set(ret) 237 | 238 | 239 | def _make_default(expr): 240 | try: 241 | ret = expr() 242 | except Exception: 243 | ret = expr 244 | 245 | return ret 246 | 247 | 248 | def _get_state(state_class, ops, **options): 249 | # Try to get a state instance from the operator INSTANCES. 250 | # If this fails, get the class 251 | try: 252 | ret = state_class._operators_to_state(ops, **options) 253 | except NotImplementedError: 254 | ret = _make_default(state_class) 255 | 256 | return ret 257 | 258 | 259 | def _get_ops(state_inst, op_classes, **options): 260 | # Try to get operator instances from the state INSTANCE. 261 | # If this fails, just return the classes 262 | try: 263 | ret = state_inst._state_to_operators(op_classes, **options) 264 | except NotImplementedError: 265 | if isinstance(op_classes, (set, tuple, frozenset)): 266 | ret = tuple(map(lambda x: _make_default(x), op_classes)) 267 | else: 268 | ret = _make_default(op_classes) 269 | 270 | if isinstance(ret, set) and len(ret) == 1: 271 | return ret[0] 272 | 273 | return ret 274 | 275 | 276 | def _make_set(ops): 277 | if isinstance(ops, (tuple, list, frozenset)): 278 | return set(ops) 279 | else: 280 | return ops 281 | -------------------------------------------------------------------------------- /sympsi/density.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | from itertools import product 4 | 5 | from sympy import Tuple, Add, Mul, Matrix, log, expand, Rational 6 | from sympy.core.trace import Tr 7 | from sympy.printing.pretty.stringpict import prettyForm 8 | from sympy.physics.quantum.matrixutils import (numpy_ndarray, 9 | scipy_sparse_matrix, to_numpy) 10 | 11 | from sympsi.dagger import Dagger 12 | from sympsi.operator import HermitianOperator 13 | from sympsi.represent import represent 14 | from sympsi.tensorproduct import TensorProduct, tensor_product_simp 15 | 16 | 17 | class Density(HermitianOperator): 18 | """Density operator for representing mixed states. 19 | 20 | TODO: Density operator support for Qubits 21 | 22 | Parameters 23 | ========== 24 | 25 | values : tuples/lists 26 | Each tuple/list should be of form (state, prob) or [state,prob] 27 | 28 | Examples 29 | ========= 30 | 31 | Create a density operator with 2 states represented by Kets. 32 | 33 | >>> from sympsi.state import Ket 34 | >>> from sympsi.density import Density 35 | >>> d = Density([Ket(0), 0.5], [Ket(1),0.5]) 36 | >>> d 37 | 'Density'((|0>, 0.5),(|1>, 0.5)) 38 | 39 | """ 40 | 41 | @classmethod 42 | def _eval_args(cls, args): 43 | # call this to qsympify the args 44 | args = super(Density, cls)._eval_args(args) 45 | 46 | for arg in args: 47 | # Check if arg is a tuple 48 | if not (isinstance(arg, Tuple) and 49 | len(arg) == 2): 50 | raise ValueError("Each argument should be of form [state,prob]" 51 | " or ( state, prob )") 52 | 53 | return args 54 | 55 | def states(self): 56 | """Return list of all states. 57 | 58 | Examples 59 | ========= 60 | 61 | >>> from sympsi.state import Ket 62 | >>> from sympsi.density import Density 63 | >>> d = Density([Ket(0), 0.5], [Ket(1),0.5]) 64 | >>> d.states() 65 | (|0>, |1>) 66 | 67 | """ 68 | return Tuple(*[arg[0] for arg in self.args]) 69 | 70 | def probs(self): 71 | """Return list of all probabilities. 72 | 73 | Examples 74 | ========= 75 | 76 | >>> from sympsi.state import Ket 77 | >>> from sympsi.density import Density 78 | >>> d = Density([Ket(0), 0.5], [Ket(1),0.5]) 79 | >>> d.probs() 80 | (0.5, 0.5) 81 | 82 | """ 83 | return Tuple(*[arg[1] for arg in self.args]) 84 | 85 | def get_state(self, index): 86 | """Return specfic state by index. 87 | 88 | Parameters 89 | ========== 90 | 91 | index : index of state to be returned 92 | 93 | Examples 94 | ========= 95 | 96 | >>> from sympsi.state import Ket 97 | >>> from sympsi.density import Density 98 | >>> d = Density([Ket(0), 0.5], [Ket(1),0.5]) 99 | >>> d.states()[1] 100 | |1> 101 | 102 | """ 103 | state = self.args[index][0] 104 | return state 105 | 106 | def get_prob(self, index): 107 | """Return probability of specific state by index. 108 | 109 | Parameters 110 | =========== 111 | 112 | index : index of states whose probability is returned. 113 | 114 | Examples 115 | ========= 116 | 117 | >>> from sympsi.state import Ket 118 | >>> from sympsi.density import Density 119 | >>> d = Density([Ket(0), 0.5], [Ket(1),0.5]) 120 | >>> d.probs()[1] 121 | 0.500000000000000 122 | 123 | """ 124 | prob = self.args[index][1] 125 | return prob 126 | 127 | def apply_op(self, op): 128 | """op will operate on each individual state. 129 | 130 | Parameters 131 | ========== 132 | 133 | op : Operator 134 | 135 | Examples 136 | ========= 137 | 138 | >>> from sympsi.state import Ket 139 | >>> from sympsi.density import Density 140 | >>> from sympsi.operator import Operator 141 | >>> A = Operator('A') 142 | >>> d = Density([Ket(0), 0.5], [Ket(1),0.5]) 143 | >>> d.apply_op(A) 144 | 'Density'((A*|0>, 0.5),(A*|1>, 0.5)) 145 | 146 | """ 147 | new_args = [(op*state, prob) for (state, prob) in self.args] 148 | return Density(*new_args) 149 | 150 | def doit(self, **hints): 151 | """Expand the density operator into an outer product format. 152 | 153 | Examples 154 | ========= 155 | 156 | >>> from sympsi.state import Ket 157 | >>> from sympsi.density import Density 158 | >>> from sympsi.operator import Operator 159 | >>> A = Operator('A') 160 | >>> d = Density([Ket(0), 0.5], [Ket(1),0.5]) 161 | >>> d.doit() 162 | 0.5*|0><0| + 0.5*|1><1| 163 | 164 | """ 165 | 166 | terms = [] 167 | for (state, prob) in self.args: 168 | state = state.expand() # needed to break up (a+b)*c 169 | if (isinstance(state, Add)): 170 | for arg in product(state.args, repeat=2): 171 | terms.append(prob * 172 | self._generate_outer_prod(arg[0], arg[1])) 173 | else: 174 | terms.append(prob * 175 | self._generate_outer_prod(state, state)) 176 | 177 | return Add(*terms) 178 | 179 | def _generate_outer_prod(self, arg1, arg2): 180 | c_part1, nc_part1 = arg1.args_cnc() 181 | c_part2, nc_part2 = arg2.args_cnc() 182 | 183 | if ( len(nc_part1) == 0 or 184 | len(nc_part2) == 0 ): 185 | raise ValueError('Atleast one-pair of' 186 | ' Non-commutative instance required' 187 | ' for outer product.') 188 | 189 | # Muls of Tensor Products should be expanded 190 | # before this function is called 191 | if (isinstance(nc_part1[0], TensorProduct) and 192 | len(nc_part1) == 1 and len(nc_part2) == 1): 193 | op = tensor_product_simp(nc_part1[0] * Dagger(nc_part2[0])) 194 | else: 195 | op = Mul(*nc_part1) * Dagger(Mul(*nc_part2)) 196 | 197 | return Mul(*c_part1)*Mul(*c_part2)*op 198 | 199 | def _represent(self, **options): 200 | return represent(self.doit(), **options) 201 | 202 | def _print_operator_name_latex(self, printer, *args): 203 | return printer._print(r'\rho', *args) 204 | 205 | def _print_operator_name_pretty(self, printer, *args): 206 | return prettyForm(unichr('\u03C1')) 207 | 208 | def _eval_trace(self, **kwargs): 209 | indices = kwargs.get('indices', []) 210 | return Tr(self.doit(), indices).doit() 211 | 212 | def entropy(self): 213 | """ Compute the entropy of a density matrix. 214 | 215 | Refer to density.entropy() method for examples. 216 | """ 217 | return entropy(self) 218 | 219 | 220 | def entropy(density): 221 | """Compute the entropy of a matrix/density object. 222 | 223 | This computes -Tr(density*ln(density)) using the eigenvalue decomposition 224 | of density, which is given as either a Density instance or a matrix 225 | (numpy.ndarray, sympy.Matrix or scipy.sparse). 226 | 227 | Parameters 228 | ========== 229 | 230 | density : density matrix of type Density, sympy matrix, 231 | scipy.sparse or numpy.ndarray 232 | 233 | Examples: 234 | ======== 235 | 236 | >>> from sympsi.density import Density, entropy 237 | >>> from sympsi.represent import represent 238 | >>> from sympsi.matrixutils import scipy_sparse_matrix 239 | >>> from sympsi.spin import JzKet, Jz 240 | >>> from sympy import S, log 241 | >>> up = JzKet(S(1)/2,S(1)/2) 242 | >>> down = JzKet(S(1)/2,-S(1)/2) 243 | >>> d = Density((up,0.5),(down,0.5)) 244 | >>> entropy(d) 245 | log(2)/2 246 | 247 | """ 248 | if isinstance(density, Density): 249 | density = represent(density) # represent in Matrix 250 | 251 | if isinstance(density, scipy_sparse_matrix): 252 | density = to_numpy(density) 253 | 254 | if isinstance(density, Matrix): 255 | eigvals = density.eigenvals().keys() 256 | return expand(-sum(e*log(e) for e in eigvals)) 257 | elif isinstance(density, numpy_ndarray): 258 | import numpy as np 259 | eigvals = np.linalg.eigvals(density) 260 | return -np.sum(eigvals*np.log(eigvals)) 261 | else: 262 | raise ValueError( 263 | "numpy.ndarray, scipy.sparse or sympy matrix expected") 264 | 265 | 266 | def fidelity(state1, state2): 267 | """ Computes the fidelity between two quantum states 268 | (http://en.wikipedia.org/wiki/Fidelity_of_quantum_states) 269 | 270 | The arguments provided to this function should be a square matrix or a 271 | Density object. If it is a square matrix, it is assumed to be diagonalizable. 272 | 273 | Parameters: 274 | ========== 275 | 276 | state1, state2 : a density matrix or Matrix 277 | 278 | 279 | Examples: 280 | ========= 281 | 282 | >>> from sympy import S, sqrt 283 | >>> from sympsi.dagger import Dagger 284 | >>> from sympsi.spin import JzKet 285 | >>> from sympsi.density import Density, fidelity 286 | >>> from sympsi.represent import represent 287 | >>> 288 | >>> up = JzKet(S(1)/2,S(1)/2) 289 | >>> down = JzKet(S(1)/2,-S(1)/2) 290 | >>> amp = 1/sqrt(2) 291 | >>> updown = (amp * up) + (amp * down) 292 | >>> 293 | >>> # represent turns Kets into matrices 294 | >>> up_dm = represent(up * Dagger(up)) 295 | >>> down_dm = represent(down * Dagger(down)) 296 | >>> updown_dm = represent(updown * Dagger(updown)) 297 | >>> 298 | >>> fidelity(up_dm, up_dm) 299 | 1 300 | >>> fidelity(up_dm, down_dm) #orthogonal states 301 | 0 302 | >>> fidelity(up_dm, updown_dm).evalf().round(3) 303 | 0.707 304 | 305 | """ 306 | state1 = represent(state1) if isinstance(state1, Density) else state1 307 | state2 = represent(state2) if isinstance(state2, Density) else state2 308 | 309 | if (not isinstance(state1, Matrix) or 310 | not isinstance(state2, Matrix)): 311 | raise ValueError("state1 and state2 must be of type Density or Matrix " 312 | "received type=%s for state1 and type=%s for state2" % 313 | (type(state1), type(state2))) 314 | 315 | if ( state1.shape != state2.shape and state1.is_square): 316 | raise ValueError("The dimensions of both args should be equal and the " 317 | "matrix obtained should be a square matrix") 318 | 319 | sqrt_state1 = state1**Rational(1, 2) 320 | return Tr((sqrt_state1 * state2 * sqrt_state1)**Rational(1, 2)).doit() 321 | -------------------------------------------------------------------------------- /sympsi/tests/test_density.py: -------------------------------------------------------------------------------- 1 | from sympy import pprint, latex, symbols, S, log 2 | from sympy.matrices import Matrix 3 | from sympy.core.trace import Tr 4 | from sympy.external import import_module 5 | from sympy.physics.quantum.density import Density, entropy, fidelity 6 | from sympy.physics.quantum.state import Ket, Bra, TimeDepKet 7 | from sympy.physics.quantum.qubit import Qubit 8 | from sympy.physics.quantum.qapply import qapply 9 | from sympy.physics.quantum.gate import HadamardGate 10 | from sympy.physics.quantum.represent import represent 11 | from sympy.physics.quantum.dagger import Dagger 12 | from sympy.physics.quantum.cartesian import XKet, PxKet, PxOp, XOp 13 | from sympy.physics.quantum.spin import JzKet, Jz 14 | from sympy.physics.quantum.operator import OuterProduct 15 | from sympy.functions import sqrt 16 | from sympy.utilities.pytest import raises 17 | from sympy.physics.quantum.matrixutils import scipy_sparse_matrix 18 | from sympy.physics.quantum.tensorproduct import TensorProduct 19 | 20 | 21 | def test_eval_args(): 22 | # check instance created 23 | assert isinstance(Density([Ket(0), 0.5], [Ket(1), 0.5]), Density) 24 | assert isinstance(Density([Qubit('00'), 1/sqrt(2)], 25 | [Qubit('11'), 1/sqrt(2)]), Density) 26 | 27 | #test if Qubit object type preserved 28 | d = Density([Qubit('00'), 1/sqrt(2)], [Qubit('11'), 1/sqrt(2)]) 29 | for (state, prob) in d.args: 30 | assert isinstance(state, Qubit) 31 | 32 | # check for value error, when prob is not provided 33 | raises(ValueError, lambda: Density([Ket(0)], [Ket(1)])) 34 | 35 | 36 | def test_doit(): 37 | 38 | x, y = symbols('x y') 39 | A, B, C, D, E, F = symbols('A B C D E F', commutative=False) 40 | d = Density([XKet(), 0.5], [PxKet(), 0.5]) 41 | assert (0.5*(PxKet()*Dagger(PxKet())) + 42 | 0.5*(XKet()*Dagger(XKet()))) == d.doit() 43 | 44 | # check for kets with expr in them 45 | d_with_sym = Density([XKet(x*y), 0.5], [PxKet(x*y), 0.5]) 46 | assert (0.5*(PxKet(x*y)*Dagger(PxKet(x*y))) + 47 | 0.5*(XKet(x*y)*Dagger(XKet(x*y)))) == d_with_sym.doit() 48 | 49 | d = Density([(A + B)*C, 1.0]) 50 | assert d.doit() == (1.0*A*C*Dagger(C)*Dagger(A) + 51 | 1.0*A*C*Dagger(C)*Dagger(B) + 52 | 1.0*B*C*Dagger(C)*Dagger(A) + 53 | 1.0*B*C*Dagger(C)*Dagger(B)) 54 | 55 | # With TensorProducts as args 56 | # Density with simple tensor products as args 57 | t = TensorProduct(A, B, C) 58 | d = Density([t, 1.0]) 59 | assert d.doit() == \ 60 | 1.0 * TensorProduct(A*Dagger(A), B*Dagger(B), C*Dagger(C)) 61 | 62 | # Density with multiple Tensorproducts as states 63 | t2 = TensorProduct(A, B) 64 | t3 = TensorProduct(C, D) 65 | 66 | d = Density([t2, 0.5], [t3, 0.5]) 67 | assert d.doit() == (0.5 * TensorProduct(A*Dagger(A), B*Dagger(B)) + 68 | 0.5 * TensorProduct(C*Dagger(C), D*Dagger(D))) 69 | 70 | #Density with mixed states 71 | d = Density([t2 + t3, 1.0]) 72 | assert d.doit() == (1.0 * TensorProduct(A*Dagger(A), B*Dagger(B)) + 73 | 1.0 * TensorProduct(A*Dagger(C), B*Dagger(D)) + 74 | 1.0 * TensorProduct(C*Dagger(A), D*Dagger(B)) + 75 | 1.0 * TensorProduct(C*Dagger(C), D*Dagger(D))) 76 | 77 | #Density operators with spin states 78 | tp1 = TensorProduct(JzKet(1, 1), JzKet(1, -1)) 79 | d = Density([tp1, 1]) 80 | 81 | # full trace 82 | t = Tr(d) 83 | assert t.doit() == 1 84 | 85 | #Partial trace on density operators with spin states 86 | t = Tr(d, [0]) 87 | assert t.doit() == JzKet(1, -1) * Dagger(JzKet(1, -1)) 88 | t = Tr(d, [1]) 89 | assert t.doit() == JzKet(1, 1) * Dagger(JzKet(1, 1)) 90 | 91 | # with another spin state 92 | tp2 = TensorProduct(JzKet(S(1)/2, S(1)/2), JzKet(S(1)/2, -S(1)/2)) 93 | d = Density([tp2, 1]) 94 | 95 | #full trace 96 | t = Tr(d) 97 | assert t.doit() == 1 98 | 99 | #Partial trace on density operators with spin states 100 | t = Tr(d, [0]) 101 | assert t.doit() == JzKet(S(1)/2, -S(1)/2) * Dagger(JzKet(S(1)/2, -S(1)/2)) 102 | t = Tr(d, [1]) 103 | assert t.doit() == JzKet(S(1)/2, S(1)/2) * Dagger(JzKet(S(1)/2, S(1)/2)) 104 | 105 | 106 | def test_apply_op(): 107 | d = Density([Ket(0), 0.5], [Ket(1), 0.5]) 108 | assert d.apply_op(XOp()) == Density([XOp()*Ket(0), 0.5], 109 | [XOp()*Ket(1), 0.5]) 110 | 111 | 112 | def test_represent(): 113 | x, y = symbols('x y') 114 | d = Density([XKet(), 0.5], [PxKet(), 0.5]) 115 | assert (represent(0.5*(PxKet()*Dagger(PxKet()))) + 116 | represent(0.5*(XKet()*Dagger(XKet())))) == represent(d) 117 | 118 | # check for kets with expr in them 119 | d_with_sym = Density([XKet(x*y), 0.5], [PxKet(x*y), 0.5]) 120 | assert (represent(0.5*(PxKet(x*y)*Dagger(PxKet(x*y)))) + 121 | represent(0.5*(XKet(x*y)*Dagger(XKet(x*y))))) == \ 122 | represent(d_with_sym) 123 | 124 | # check when given explicit basis 125 | assert (represent(0.5*(XKet()*Dagger(XKet())), basis=PxOp()) + 126 | represent(0.5*(PxKet()*Dagger(PxKet())), basis=PxOp())) == \ 127 | represent(d, basis=PxOp()) 128 | 129 | 130 | def test_states(): 131 | d = Density([Ket(0), 0.5], [Ket(1), 0.5]) 132 | states = d.states() 133 | assert states[0] == Ket(0) and states[1] == Ket(1) 134 | 135 | 136 | def test_probs(): 137 | d = Density([Ket(0), .75], [Ket(1), 0.25]) 138 | probs = d.probs() 139 | assert probs[0] == 0.75 and probs[1] == 0.25 140 | 141 | #probs can be symbols 142 | x, y = symbols('x y') 143 | d = Density([Ket(0), x], [Ket(1), y]) 144 | probs = d.probs() 145 | assert probs[0] == x and probs[1] == y 146 | 147 | 148 | def test_get_state(): 149 | x, y = symbols('x y') 150 | d = Density([Ket(0), x], [Ket(1), y]) 151 | states = (d.get_state(0), d.get_state(1)) 152 | assert states[0] == Ket(0) and states[1] == Ket(1) 153 | 154 | 155 | def test_get_prob(): 156 | x, y = symbols('x y') 157 | d = Density([Ket(0), x], [Ket(1), y]) 158 | probs = (d.get_prob(0), d.get_prob(1)) 159 | assert probs[0] == x and probs[1] == y 160 | 161 | 162 | def test_entropy(): 163 | up = JzKet(S(1)/2, S(1)/2) 164 | down = JzKet(S(1)/2, -S(1)/2) 165 | d = Density((up, 0.5), (down, 0.5)) 166 | 167 | # test for density object 168 | ent = entropy(d) 169 | assert entropy(d) == 0.5*log(2) 170 | assert d.entropy() == 0.5*log(2) 171 | 172 | np = import_module('numpy', min_module_version='1.4.0') 173 | if np: 174 | #do this test only if 'numpy' is available on test machine 175 | np_mat = represent(d, format='numpy') 176 | ent = entropy(np_mat) 177 | assert isinstance(np_mat, np.matrixlib.defmatrix.matrix) 178 | assert ent.real == 0.69314718055994529 179 | assert ent.imag == 0 180 | 181 | scipy = import_module('scipy', __import__kwargs={'fromlist': ['sparse']}) 182 | if scipy and np: 183 | #do this test only if numpy and scipy are available 184 | mat = represent(d, format="scipy.sparse") 185 | assert isinstance(mat, scipy_sparse_matrix) 186 | assert ent.real == 0.69314718055994529 187 | assert ent.imag == 0 188 | 189 | 190 | def test_eval_trace(): 191 | up = JzKet(S(1)/2, S(1)/2) 192 | down = JzKet(S(1)/2, -S(1)/2) 193 | d = Density((up, 0.5), (down, 0.5)) 194 | 195 | t = Tr(d) 196 | assert t.doit() == 1 197 | 198 | #test dummy time dependent states 199 | class TestTimeDepKet(TimeDepKet): 200 | def _eval_trace(self, bra, **options): 201 | return 1 202 | 203 | x, t = symbols('x t') 204 | k1 = TestTimeDepKet(0, 0.5) 205 | k2 = TestTimeDepKet(0, 1) 206 | d = Density([k1, 0.5], [k2, 0.5]) 207 | assert d.doit() == (0.5 * OuterProduct(k1, k1.dual) + 208 | 0.5 * OuterProduct(k2, k2.dual)) 209 | 210 | t = Tr(d) 211 | assert t.doit() == 1 212 | 213 | 214 | def test_fidelity(): 215 | #test with kets 216 | up = JzKet(S(1)/2, S(1)/2) 217 | down = JzKet(S(1)/2, -S(1)/2) 218 | updown = (S(1)/sqrt(2))*up + (S(1)/sqrt(2))*down 219 | 220 | #check with matrices 221 | up_dm = represent(up * Dagger(up)) 222 | down_dm = represent(down * Dagger(down)) 223 | updown_dm = represent(updown * Dagger(updown)) 224 | 225 | assert abs(fidelity(up_dm, up_dm) - 1) < 1e-3 226 | assert fidelity(up_dm, down_dm) < 1e-3 227 | assert abs(fidelity(up_dm, updown_dm) - (S(1)/sqrt(2))) < 1e-3 228 | assert abs(fidelity(updown_dm, down_dm) - (S(1)/sqrt(2))) < 1e-3 229 | 230 | #check with density 231 | up_dm = Density([up, 1.0]) 232 | down_dm = Density([down, 1.0]) 233 | updown_dm = Density([updown, 1.0]) 234 | 235 | assert abs(fidelity(up_dm, up_dm) - 1) < 1e-3 236 | assert abs(fidelity(up_dm, down_dm)) < 1e-3 237 | assert abs(fidelity(up_dm, updown_dm) - (S(1)/sqrt(2))) < 1e-3 238 | assert abs(fidelity(updown_dm, down_dm) - (S(1)/sqrt(2))) < 1e-3 239 | 240 | #check mixed states with density 241 | updown2 = (sqrt(3)/2)*up + (S(1)/2)*down 242 | d1 = Density([updown, 0.25], [updown2, 0.75]) 243 | d2 = Density([updown, 0.75], [updown2, 0.25]) 244 | assert abs(fidelity(d1, d2) - 0.991) < 1e-3 245 | assert abs(fidelity(d2, d1) - fidelity(d1, d2)) < 1e-3 246 | 247 | #using qubits/density(pure states) 248 | state1 = Qubit('0') 249 | state2 = Qubit('1') 250 | state3 = (S(1)/sqrt(2))*state1 + (S(1)/sqrt(2))*state2 251 | state4 = (sqrt(S(2)/3))*state1 + (S(1)/sqrt(3))*state2 252 | 253 | state1_dm = Density([state1, 1]) 254 | state2_dm = Density([state2, 1]) 255 | state3_dm = Density([state3, 1]) 256 | 257 | assert fidelity(state1_dm, state1_dm) == 1 258 | assert fidelity(state1_dm, state2_dm) == 0 259 | assert abs(fidelity(state1_dm, state3_dm) - 1/sqrt(2)) < 1e-3 260 | assert abs(fidelity(state3_dm, state2_dm) - 1/sqrt(2)) < 1e-3 261 | 262 | #using qubits/density(mixed states) 263 | d1 = Density([state3, 0.70], [state4, 0.30]) 264 | d2 = Density([state3, 0.20], [state4, 0.80]) 265 | assert abs(fidelity(d1, d1) - 1) < 1e-3 266 | assert abs(fidelity(d1, d2) - 0.996) < 1e-3 267 | assert abs(fidelity(d1, d2) - fidelity(d2, d1)) < 1e-3 268 | 269 | #TODO: test for invalid arguments 270 | # non-square matrix 271 | mat1 = [[0, 0], 272 | [0, 0], 273 | [0, 0]] 274 | 275 | mat2 = [[0, 0], 276 | [0, 0]] 277 | raises(ValueError, lambda: fidelity(mat1, mat2)) 278 | 279 | # unequal dimensions 280 | mat1 = [[0, 0], 281 | [0, 0]] 282 | mat2 = [[0, 0, 0], 283 | [0, 0, 0], 284 | [0, 0, 0]] 285 | raises(ValueError, lambda: fidelity(mat1, mat2)) 286 | 287 | # unsupported data-type 288 | x, y = 1, 2 # random values that is not a matrix 289 | raises(ValueError, lambda: fidelity(x, y)) 290 | -------------------------------------------------------------------------------- /sympsi/tensorproduct.py: -------------------------------------------------------------------------------- 1 | """Abstract tensor product.""" 2 | 3 | from __future__ import print_function, division 4 | 5 | from sympy import Expr, Add, Mul, Matrix, Pow, sympify 6 | from sympy.core.compatibility import u 7 | from sympy.core.trace import Tr 8 | from sympy.printing.pretty.stringpict import prettyForm 9 | from sympy.physics.quantum.matrixutils import ( 10 | numpy_ndarray, 11 | scipy_sparse_matrix, 12 | matrix_tensor_product 13 | ) 14 | 15 | from sympsi.qexpr import QuantumError 16 | from sympsi.dagger import Dagger 17 | from sympsi.commutator import Commutator 18 | from sympsi.anticommutator import AntiCommutator 19 | from sympsi.state import Ket, Bra 20 | 21 | 22 | __all__ = [ 23 | 'TensorProduct', 24 | 'tensor_product_simp' 25 | ] 26 | 27 | #----------------------------------------------------------------------------- 28 | # Tensor product 29 | #----------------------------------------------------------------------------- 30 | 31 | _combined_printing = False 32 | 33 | 34 | def combined_tensor_printing(combined): 35 | """Set flag controlling whether tensor products of states should be 36 | printed as a combined bra/ket or as an explicit tensor product of different 37 | bra/kets. This is a global setting for all TensorProduct class instances. 38 | 39 | Parameters 40 | ---------- 41 | combine : bool 42 | When true, tensor product states are combined into one ket/bra, and 43 | when false explicit tensor product notation is used between each 44 | ket/bra. 45 | """ 46 | global _combined_printing 47 | _combined_printing = combined 48 | 49 | 50 | class TensorProduct(Expr): 51 | """The tensor product of two or more arguments. 52 | 53 | For matrices, this uses ``matrix_tensor_product`` to compute the Kronecker 54 | or tensor product matrix. For other objects a symbolic ``TensorProduct`` 55 | instance is returned. The tensor product is a non-commutative 56 | multiplication that is used primarily with operators and states in quantum 57 | mechanics. 58 | 59 | Currently, the tensor product distinguishes between commutative and non- 60 | commutative arguments. Commutative arguments are assumed to be scalars and 61 | are pulled out in front of the ``TensorProduct``. Non-commutative arguments 62 | remain in the resulting ``TensorProduct``. 63 | 64 | Parameters 65 | ========== 66 | 67 | args : tuple 68 | A sequence of the objects to take the tensor product of. 69 | 70 | Examples 71 | ======== 72 | 73 | Start with a simple tensor product of sympy matrices:: 74 | 75 | >>> from sympy import I, Matrix, symbols 76 | >>> from sympsi import TensorProduct 77 | 78 | >>> m1 = Matrix([[1,2],[3,4]]) 79 | >>> m2 = Matrix([[1,0],[0,1]]) 80 | >>> TensorProduct(m1, m2) 81 | Matrix([ 82 | [1, 0, 2, 0], 83 | [0, 1, 0, 2], 84 | [3, 0, 4, 0], 85 | [0, 3, 0, 4]]) 86 | >>> TensorProduct(m2, m1) 87 | Matrix([ 88 | [1, 2, 0, 0], 89 | [3, 4, 0, 0], 90 | [0, 0, 1, 2], 91 | [0, 0, 3, 4]]) 92 | 93 | We can also construct tensor products of non-commutative symbols: 94 | 95 | >>> from sympy import Symbol 96 | >>> A = Symbol('A',commutative=False) 97 | >>> B = Symbol('B',commutative=False) 98 | >>> tp = TensorProduct(A, B) 99 | >>> tp 100 | AxB 101 | 102 | We can take the dagger of a tensor product (note the order does NOT reverse 103 | like the dagger of a normal product): 104 | 105 | >>> from sympsi import Dagger 106 | >>> Dagger(tp) 107 | Dagger(A)xDagger(B) 108 | 109 | Expand can be used to distribute a tensor product across addition: 110 | 111 | >>> C = Symbol('C',commutative=False) 112 | >>> tp = TensorProduct(A+B,C) 113 | >>> tp 114 | (A + B)xC 115 | >>> tp.expand(tensorproduct=True) 116 | AxC + BxC 117 | """ 118 | is_commutative = False 119 | 120 | def __new__(cls, *args): 121 | if isinstance(args[0], (Matrix, numpy_ndarray, scipy_sparse_matrix)): 122 | return matrix_tensor_product(*args) 123 | c_part, new_args = cls.flatten(sympify(args)) 124 | c_part = Mul(*c_part) 125 | if len(new_args) == 0: 126 | return c_part 127 | elif len(new_args) == 1: 128 | return c_part * new_args[0] 129 | else: 130 | tp = Expr.__new__(cls, *new_args) 131 | return c_part * tp 132 | 133 | @classmethod 134 | def flatten(cls, args): 135 | # TODO: disallow nested TensorProducts. 136 | c_part = [] 137 | nc_parts = [] 138 | for arg in args: 139 | cp, ncp = arg.args_cnc() 140 | c_part.extend(list(cp)) 141 | nc_parts.append(Mul._from_args(ncp)) 142 | return c_part, nc_parts 143 | 144 | def _eval_adjoint(self): 145 | return TensorProduct(*[Dagger(i) for i in self.args]) 146 | 147 | def _eval_rewrite(self, pattern, rule, **hints): 148 | sargs = self.args 149 | terms = [t._eval_rewrite(pattern, rule, **hints) for t in sargs] 150 | return TensorProduct(*terms).expand(tensorproduct=True) 151 | 152 | def _sympystr(self, printer, *args): 153 | from sympy.printing.str import sstr 154 | length = len(self.args) 155 | s = '' 156 | for i in range(length): 157 | if isinstance(self.args[i], (Add, Pow, Mul)): 158 | s = s + '(' 159 | s = s + sstr(self.args[i]) 160 | if isinstance(self.args[i], (Add, Pow, Mul)): 161 | s = s + ')' 162 | if i != length - 1: 163 | s = s + 'x' 164 | return s 165 | 166 | def _pretty(self, printer, *args): 167 | 168 | if (_combined_printing and 169 | (all([isinstance(arg, Ket) for arg in self.args]) or 170 | all([isinstance(arg, Bra) for arg in self.args]))): 171 | 172 | length = len(self.args) 173 | pform = printer._print('', *args) 174 | for i in range(length): 175 | next_pform = printer._print('', *args) 176 | length_i = len(self.args[i].args) 177 | for j in range(length_i): 178 | part_pform = printer._print(self.args[i].args[j], *args) 179 | next_pform = prettyForm(*next_pform.right(part_pform)) 180 | if j != length_i - 1: 181 | next_pform = prettyForm(*next_pform.right(', ')) 182 | 183 | if len(self.args[i].args) > 1: 184 | next_pform = prettyForm( 185 | *next_pform.parens(left='{', right='}')) 186 | pform = prettyForm(*pform.right(next_pform)) 187 | if i != length - 1: 188 | pform = prettyForm(*pform.right(',' + ' ')) 189 | 190 | pform = prettyForm(*pform.left(self.args[0].lbracket)) 191 | pform = prettyForm(*pform.right(self.args[0].rbracket)) 192 | return pform 193 | 194 | length = len(self.args) 195 | pform = printer._print('', *args) 196 | for i in range(length): 197 | next_pform = printer._print(self.args[i], *args) 198 | if isinstance(self.args[i], (Add, Mul)): 199 | next_pform = prettyForm( 200 | *next_pform.parens(left='(', right=')') 201 | ) 202 | pform = prettyForm(*pform.right(next_pform)) 203 | if i != length - 1: 204 | if printer._use_unicode: 205 | pform = prettyForm(*pform.right(u('\u2a02') + u(' '))) 206 | else: 207 | pform = prettyForm(*pform.right('x' + ' ')) 208 | return pform 209 | 210 | def _latex(self, printer, *args): 211 | 212 | if (_combined_printing and 213 | (all([isinstance(arg, Ket) for arg in self.args]) or 214 | all([isinstance(arg, Bra) for arg in self.args]))): 215 | 216 | def _label_wrap(label, nlabels): 217 | return label if nlabels == 1 else r"\left\{%s\right\}" % label 218 | 219 | s = r", ".join([_label_wrap(arg._print_label_latex(printer, *args), 220 | len(arg.args)) for arg in self.args]) 221 | 222 | return r"{%s%s%s}" % (self.args[0].lbracket_latex, s, 223 | self.args[0].rbracket_latex) 224 | 225 | length = len(self.args) 226 | s = '' 227 | for i in range(length): 228 | if isinstance(self.args[i], (Add, Mul)): 229 | s = s + '\\left(' 230 | # The extra {} brackets are needed to get matplotlib's latex 231 | # rendered to render this properly. 232 | s = s + '{' + printer._print(self.args[i], *args) + '}' 233 | if isinstance(self.args[i], (Add, Mul)): 234 | s = s + '\\right)' 235 | if i != length - 1: 236 | s = s + '\\otimes ' 237 | return s 238 | 239 | def doit(self, **hints): 240 | return TensorProduct(*[item.doit(**hints) for item in self.args]) 241 | 242 | def _eval_expand_tensorproduct(self, **hints): 243 | """Distribute TensorProducts across addition.""" 244 | args = self.args 245 | add_args = [] 246 | stop = False 247 | for i in range(len(args)): 248 | if isinstance(args[i], Add): 249 | for aa in args[i].args: 250 | tp = TensorProduct(*args[:i] + (aa,) + args[i + 1:]) 251 | if isinstance(tp, TensorProduct): 252 | tp = tp._eval_expand_tensorproduct() 253 | add_args.append(tp) 254 | break 255 | 256 | if add_args: 257 | return Add(*add_args) 258 | else: 259 | return self 260 | 261 | def _eval_trace(self, **kwargs): 262 | indices = kwargs.get('indices', None) 263 | exp = tensor_product_simp(self) 264 | 265 | if indices is None or len(indices) == 0: 266 | return Mul(*[Tr(arg).doit() for arg in exp.args]) 267 | else: 268 | return Mul(*[Tr(value).doit() if idx in indices else value 269 | for idx, value in enumerate(exp.args)]) 270 | 271 | 272 | def tensor_product_simp_Mul(e): 273 | """Simplify a Mul with TensorProducts. 274 | 275 | Current the main use of this is to simplify a ``Mul`` of ``TensorProduct``s 276 | to a ``TensorProduct`` of ``Muls``. It currently only works for relatively 277 | simple cases where the initial ``Mul`` only has scalars and raw 278 | ``TensorProduct``s, not ``Add``, ``Pow``, ``Commutator``s of 279 | ``TensorProduct``s. 280 | 281 | Parameters 282 | ========== 283 | 284 | e : Expr 285 | A ``Mul`` of ``TensorProduct``s to be simplified. 286 | 287 | Returns 288 | ======= 289 | 290 | e : Expr 291 | A ``TensorProduct`` of ``Mul``s. 292 | 293 | Examples 294 | ======== 295 | 296 | This is an example of the type of simplification that this function 297 | performs:: 298 | 299 | >>> from sympsi.tensorproduct import \ 300 | tensor_product_simp_Mul, TensorProduct 301 | >>> from sympy import Symbol 302 | >>> A = Symbol('A',commutative=False) 303 | >>> B = Symbol('B',commutative=False) 304 | >>> C = Symbol('C',commutative=False) 305 | >>> D = Symbol('D',commutative=False) 306 | >>> e = TensorProduct(A,B)*TensorProduct(C,D) 307 | >>> e 308 | AxB*CxD 309 | >>> tensor_product_simp_Mul(e) 310 | (A*C)x(B*D) 311 | 312 | """ 313 | # TODO: This won't work with Muls that have other composites of 314 | # TensorProducts, like an Add, Pow, Commutator, etc. 315 | # TODO: This only works for the equivalent of single Qbit gates. 316 | if not isinstance(e, Mul): 317 | return e 318 | c_part, nc_part = e.args_cnc() 319 | n_nc = len(nc_part) 320 | if n_nc == 0 or n_nc == 1: 321 | return e 322 | elif e.has(TensorProduct): 323 | current = nc_part[0] 324 | if not isinstance(current, TensorProduct): 325 | raise TypeError('TensorProduct expected, got: %r' % current) 326 | n_terms = len(current.args) 327 | new_args = list(current.args) 328 | for next in nc_part[1:]: 329 | # TODO: check the hilbert spaces of next and current here. 330 | if isinstance(next, TensorProduct): 331 | if n_terms != len(next.args): 332 | raise QuantumError( 333 | 'TensorProducts of different lengths: %r and %r' % 334 | (current, next) 335 | ) 336 | for i in range(len(new_args)): 337 | new_args[i] = new_args[i] * next.args[i] 338 | else: 339 | # this won't quite work as we don't want next in the 340 | # TensorProduct 341 | for i in range(len(new_args)): 342 | new_args[i] = new_args[i] * next 343 | current = next 344 | return Mul(*c_part) * TensorProduct(*new_args) 345 | else: 346 | return e 347 | 348 | 349 | def tensor_product_simp(e, **hints): 350 | """Try to simplify and combine TensorProducts. 351 | 352 | In general this will try to pull expressions inside of ``TensorProducts``. 353 | It currently only works for relatively simple cases where the products have 354 | only scalars, raw ``TensorProducts``, not ``Add``, ``Pow``, ``Commutators`` 355 | of ``TensorProducts``. It is best to see what it does by showing examples. 356 | 357 | Examples 358 | ======== 359 | 360 | >>> from sympsi import tensor_product_simp 361 | >>> from sympsi import TensorProduct 362 | >>> from sympy import Symbol 363 | >>> A = Symbol('A',commutative=False) 364 | >>> B = Symbol('B',commutative=False) 365 | >>> C = Symbol('C',commutative=False) 366 | >>> D = Symbol('D',commutative=False) 367 | 368 | First see what happens to products of tensor products: 369 | 370 | >>> e = TensorProduct(A,B)*TensorProduct(C,D) 371 | >>> e 372 | AxB*CxD 373 | >>> tensor_product_simp(e) 374 | (A*C)x(B*D) 375 | 376 | This is the core logic of this function, and it works inside, powers, sums, 377 | commutators and anticommutators as well: 378 | 379 | >>> tensor_product_simp(e**2) 380 | (A*C)x(B*D)**2 381 | 382 | """ 383 | if isinstance(e, Add): 384 | return Add(*[tensor_product_simp(arg) for arg in e.args]) 385 | elif isinstance(e, Pow): 386 | return tensor_product_simp(e.base) ** e.exp 387 | elif isinstance(e, Mul): 388 | return tensor_product_simp_Mul(e) 389 | elif isinstance(e, Commutator): 390 | return Commutator(*[tensor_product_simp(arg) for arg in e.args]) 391 | elif isinstance(e, AntiCommutator): 392 | return AntiCommutator(*[tensor_product_simp(arg) for arg in e.args]) 393 | else: 394 | return e 395 | -------------------------------------------------------------------------------- /sympsi/qexpr.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | from sympy import Expr, sympify, Symbol, Matrix 4 | from sympy.printing.pretty.stringpict import prettyForm 5 | from sympy.core.containers import Tuple 6 | from sympy.core.compatibility import is_sequence, string_types, u 7 | from sympy.physics.quantum.matrixutils import ( 8 | numpy_ndarray, scipy_sparse_matrix, 9 | to_sympy, to_numpy, to_scipy_sparse 10 | ) 11 | 12 | from sympsi.dagger import Dagger 13 | 14 | __all__ = [ 15 | 'QuantumError', 16 | 'QExpr' 17 | ] 18 | 19 | 20 | #----------------------------------------------------------------------------- 21 | # Error handling 22 | #----------------------------------------------------------------------------- 23 | 24 | class QuantumError(Exception): 25 | pass 26 | 27 | 28 | def _qsympify_sequence(seq): 29 | """Convert elements of a sequence to standard form. 30 | 31 | This is like sympify, but it performs special logic for arguments passed 32 | to QExpr. The following conversions are done: 33 | 34 | * (list, tuple, Tuple) => _qsympify_sequence each element and convert 35 | sequence to a Tuple. 36 | * basestring => Symbol 37 | * Matrix => Matrix 38 | * other => sympify 39 | 40 | Strings are passed to Symbol, not sympify to make sure that variables like 41 | 'pi' are kept as Symbols, not the SymPy built-in number subclasses. 42 | 43 | Examples 44 | ======== 45 | 46 | >>> from sympsi.qexpr import _qsympify_sequence 47 | >>> _qsympify_sequence((1,2,[3,4,[1,]])) 48 | (1, 2, (3, 4, (1,))) 49 | 50 | """ 51 | 52 | return tuple(__qsympify_sequence_helper(seq)) 53 | 54 | 55 | def __qsympify_sequence_helper(seq): 56 | """ 57 | Helper function for _qsympify_sequence 58 | This function does the actual work. 59 | """ 60 | #base case. If not a list, do Sympification 61 | if not is_sequence(seq): 62 | if isinstance(seq, Matrix): 63 | return seq 64 | elif isinstance(seq, string_types): 65 | return Symbol(seq) 66 | else: 67 | return sympify(seq) 68 | 69 | # base condition, when seq is QExpr and also 70 | # is iterable. 71 | if isinstance(seq, QExpr): 72 | return seq 73 | 74 | #if list, recurse on each item in the list 75 | result = [__qsympify_sequence_helper(item) for item in seq] 76 | 77 | return Tuple(*result) 78 | 79 | 80 | #----------------------------------------------------------------------------- 81 | # Basic Quantum Expression from which all objects descend 82 | #----------------------------------------------------------------------------- 83 | 84 | class QExpr(Expr): 85 | """A base class for all quantum object like operators and states.""" 86 | 87 | # In sympy, slots are for instance attributes that are computed 88 | # dynamically by the __new__ method. They are not part of args, but they 89 | # derive from args. 90 | 91 | # The Hilbert space a quantum Object belongs to. 92 | __slots__ = ['hilbert_space'] 93 | 94 | is_commutative = False 95 | 96 | # The separator used in printing the label. 97 | _label_separator = u('') 98 | 99 | @property 100 | def free_symbols(self): 101 | return set([self]) 102 | 103 | def __new__(cls, *args, **old_assumptions): 104 | """Construct a new quantum object. 105 | 106 | Parameters 107 | ========== 108 | 109 | args : tuple 110 | The list of numbers or parameters that uniquely specify the 111 | quantum object. For a state, this will be its symbol or its 112 | set of quantum numbers. 113 | 114 | Examples 115 | ======== 116 | 117 | >>> from sympsi.qexpr import QExpr 118 | >>> q = QExpr(0) 119 | >>> q 120 | 0 121 | >>> q.label 122 | (0,) 123 | >>> q.hilbert_space 124 | H 125 | >>> q.args 126 | (0,) 127 | >>> q.is_commutative 128 | False 129 | """ 130 | 131 | # First compute args and call Expr.__new__ to create the instance 132 | args = cls._eval_args(args) 133 | if len(args) == 0: 134 | args = cls._eval_args(tuple(cls.default_args())) 135 | inst = Expr.__new__(cls, *args, **old_assumptions) 136 | # Now set the slots on the instance 137 | inst.hilbert_space = cls._eval_hilbert_space(args) 138 | return inst 139 | 140 | @classmethod 141 | def _new_rawargs(cls, hilbert_space, *args, **old_assumptions): 142 | """Create new instance of this class with hilbert_space and args. 143 | 144 | This is used to bypass the more complex logic in the ``__new__`` 145 | method in cases where you already have the exact ``hilbert_space`` 146 | and ``args``. This should be used when you are positive these 147 | arguments are valid, in their final, proper form and want to optimize 148 | the creation of the object. 149 | """ 150 | 151 | obj = Expr.__new__(cls, *args, **old_assumptions) 152 | obj.hilbert_space = hilbert_space 153 | return obj 154 | 155 | #------------------------------------------------------------------------- 156 | # Properties 157 | #------------------------------------------------------------------------- 158 | 159 | @property 160 | def label(self): 161 | """The label is the unique set of identifiers for the object. 162 | 163 | Usually, this will include all of the information about the state 164 | *except* the time (in the case of time-dependent objects). 165 | 166 | This must be a tuple, rather than a Tuple. 167 | """ 168 | if len(self.args) == 0: # If there is no label specified, return the default 169 | return self._eval_args(list(self.default_args())) 170 | else: 171 | return self.args 172 | 173 | @property 174 | def is_symbolic(self): 175 | return True 176 | 177 | @classmethod 178 | def default_args(self): 179 | """If no arguments are specified, then this will return a default set 180 | of arguments to be run through the constructor. 181 | 182 | NOTE: Any classes that override this MUST return a tuple of arguments. 183 | Should be overidden by subclasses to specify the default arguments for kets and operators 184 | """ 185 | raise NotImplementedError("No default arguments for this class!") 186 | 187 | #------------------------------------------------------------------------- 188 | # _eval_* methods 189 | #------------------------------------------------------------------------- 190 | 191 | def _eval_adjoint(self): 192 | obj = Expr._eval_adjoint(self) 193 | if obj is None: 194 | obj = Expr.__new__(Dagger, self) 195 | if isinstance(obj, QExpr): 196 | obj.hilbert_space = self.hilbert_space 197 | return obj 198 | 199 | @classmethod 200 | def _eval_args(cls, args): 201 | """Process the args passed to the __new__ method. 202 | 203 | This simply runs args through _qsympify_sequence. 204 | """ 205 | return _qsympify_sequence(args) 206 | 207 | @classmethod 208 | def _eval_hilbert_space(cls, args): 209 | """Compute the Hilbert space instance from the args. 210 | """ 211 | from sympsi.hilbert import HilbertSpace 212 | return HilbertSpace() 213 | 214 | #------------------------------------------------------------------------- 215 | # Printing 216 | #------------------------------------------------------------------------- 217 | 218 | # Utilities for printing: these operate on raw sympy objects 219 | 220 | def _print_sequence(self, seq, sep, printer, *args): 221 | result = [] 222 | for item in seq: 223 | result.append(printer._print(item, *args)) 224 | return sep.join(result) 225 | 226 | def _print_sequence_pretty(self, seq, sep, printer, *args): 227 | pform = printer._print(seq[0], *args) 228 | for item in seq[1:]: 229 | pform = prettyForm(*pform.right((sep))) 230 | pform = prettyForm(*pform.right((printer._print(item, *args)))) 231 | return pform 232 | 233 | # Utilities for printing: these operate prettyForm objects 234 | 235 | def _print_subscript_pretty(self, a, b): 236 | top = prettyForm(*b.left(' '*a.width())) 237 | bot = prettyForm(*a.right(' '*b.width())) 238 | return prettyForm(binding=prettyForm.POW, *bot.below(top)) 239 | 240 | def _print_superscript_pretty(self, a, b): 241 | return a**b 242 | 243 | def _print_parens_pretty(self, pform, left='(', right=')'): 244 | return prettyForm(*pform.parens(left=left, right=right)) 245 | 246 | # Printing of labels (i.e. args) 247 | 248 | def _print_label(self, printer, *args): 249 | """Prints the label of the QExpr 250 | 251 | This method prints self.label, using self._label_separator to separate 252 | the elements. This method should not be overridden, instead, override 253 | _print_contents to change printing behavior. 254 | """ 255 | return self._print_sequence( 256 | self.label, self._label_separator, printer, *args 257 | ) 258 | 259 | def _print_label_repr(self, printer, *args): 260 | return self._print_sequence( 261 | self.label, ',', printer, *args 262 | ) 263 | 264 | def _print_label_pretty(self, printer, *args): 265 | return self._print_sequence_pretty( 266 | self.label, self._label_separator, printer, *args 267 | ) 268 | 269 | def _print_label_latex(self, printer, *args): 270 | return self._print_sequence( 271 | self.label, self._label_separator, printer, *args 272 | ) 273 | 274 | # Printing of contents (default to label) 275 | 276 | def _print_contents(self, printer, *args): 277 | """Printer for contents of QExpr 278 | 279 | Handles the printing of any unique identifying contents of a QExpr to 280 | print as its contents, such as any variables or quantum numbers. The 281 | default is to print the label, which is almost always the args. This 282 | should not include printing of any brackets or parenteses. 283 | """ 284 | return self._print_label(printer, *args) 285 | 286 | def _print_contents_pretty(self, printer, *args): 287 | return self._print_label_pretty(printer, *args) 288 | 289 | def _print_contents_latex(self, printer, *args): 290 | return self._print_label_latex(printer, *args) 291 | 292 | # Main printing methods 293 | 294 | def _sympystr(self, printer, *args): 295 | """Default printing behavior of QExpr objects 296 | 297 | Handles the default printing of a QExpr. To add other things to the 298 | printing of the object, such as an operator name to operators or 299 | brackets to states, the class should override the _print/_pretty/_latex 300 | functions directly and make calls to _print_contents where appropriate. 301 | This allows things like InnerProduct to easily control its printing the 302 | printing of contents. 303 | """ 304 | return self._print_contents(printer, *args) 305 | 306 | def _sympyrepr(self, printer, *args): 307 | classname = self.__class__.__name__ 308 | label = self._print_label_repr(printer, *args) 309 | return '%s(%s)' % (classname, label) 310 | 311 | def _pretty(self, printer, *args): 312 | pform = self._print_contents_pretty(printer, *args) 313 | return pform 314 | 315 | def _latex(self, printer, *args): 316 | return self._print_contents_latex(printer, *args) 317 | 318 | #------------------------------------------------------------------------- 319 | # Methods from Basic and Expr 320 | #------------------------------------------------------------------------- 321 | 322 | def doit(self, **kw_args): 323 | return self 324 | 325 | def _eval_rewrite(self, pattern, rule, **hints): 326 | if hints.get('deep', False): 327 | args = [ a._eval_rewrite(pattern, rule, **hints) 328 | for a in self.args ] 329 | else: 330 | args = self.args 331 | 332 | # TODO: Make Basic.rewrite use hints in evaluating 333 | # self.rule(*args, **hints), not having hints breaks spin state 334 | # (un)coupling on rewrite 335 | if pattern is None or isinstance(self, pattern): 336 | if hasattr(self, rule): 337 | rewritten = getattr(self, rule)(*args, **hints) 338 | 339 | if rewritten is not None: 340 | return rewritten 341 | 342 | return self 343 | 344 | #------------------------------------------------------------------------- 345 | # Represent 346 | #------------------------------------------------------------------------- 347 | 348 | def _represent_default_basis(self, **options): 349 | raise NotImplementedError('This object does not have a default basis') 350 | 351 | def _represent(self, **options): 352 | """Represent this object in a given basis. 353 | 354 | This method dispatches to the actual methods that perform the 355 | representation. Subclases of QExpr should define various methods to 356 | determine how the object will be represented in various bases. The 357 | format of these methods is:: 358 | 359 | def _represent_BasisName(self, basis, **options): 360 | 361 | Thus to define how a quantum object is represented in the basis of 362 | the operator Position, you would define:: 363 | 364 | def _represent_Position(self, basis, **options): 365 | 366 | Usually, basis object will be instances of Operator subclasses, but 367 | there is a chance we will relax this in the future to accomodate other 368 | types of basis sets that are not associated with an operator. 369 | 370 | If the ``format`` option is given it can be ("sympy", "numpy", 371 | "scipy.sparse"). This will ensure that any matrices that result from 372 | representing the object are returned in the appropriate matrix format. 373 | 374 | Parameters 375 | ========== 376 | 377 | basis : Operator 378 | The Operator whose basis functions will be used as the basis for 379 | representation. 380 | options : dict 381 | A dictionary of key/value pairs that give options and hints for 382 | the representation, such as the number of basis functions to 383 | be used. 384 | """ 385 | basis = options.pop('basis', None) 386 | if basis is None: 387 | result = self._represent_default_basis(**options) 388 | else: 389 | result = dispatch_method(self, '_represent', basis, **options) 390 | 391 | # If we get a matrix representation, convert it to the right format. 392 | format = options.get('format', 'sympy') 393 | result = self._format_represent(result, format) 394 | return result 395 | 396 | def _format_represent(self, result, format): 397 | if format == 'sympy' and not isinstance(result, Matrix): 398 | return to_sympy(result) 399 | elif format == 'numpy' and not isinstance(result, numpy_ndarray): 400 | return to_numpy(result) 401 | elif format == 'scipy.sparse' and \ 402 | not isinstance(result, scipy_sparse_matrix): 403 | return to_scipy_sparse(result) 404 | 405 | return result 406 | 407 | 408 | def split_commutative_parts(e): 409 | """Split into commutative and non-commutative parts.""" 410 | c_part, nc_part = e.args_cnc() 411 | c_part = list(c_part) 412 | return c_part, nc_part 413 | 414 | 415 | def split_qexpr_parts(e): 416 | """Split an expression into Expr and noncommutative QExpr parts.""" 417 | expr_part = [] 418 | qexpr_part = [] 419 | for arg in e.args: 420 | if not isinstance(arg, QExpr): 421 | expr_part.append(arg) 422 | else: 423 | qexpr_part.append(arg) 424 | return expr_part, qexpr_part 425 | 426 | 427 | def dispatch_method(self, basename, arg, **options): 428 | """Dispatch a method to the proper handlers.""" 429 | method_name = '%s_%s' % (basename, arg.__class__.__name__) 430 | if hasattr(self, method_name): 431 | f = getattr(self, method_name) 432 | # This can raise and we will allow it to propagate. 433 | result = f(arg, **options) 434 | if result is not None: 435 | return result 436 | raise NotImplementedError( 437 | "%s.%s can't handle: %r" % 438 | (self.__class__.__name__, basename, arg) 439 | ) 440 | -------------------------------------------------------------------------------- /sympsi/boson.py: -------------------------------------------------------------------------------- 1 | """Bosonic quantum operators.""" 2 | 3 | from sympy.core.compatibility import u 4 | from sympy import Mul, Integer, exp, sqrt, conjugate, DiracDelta, Symbol 5 | from sympsi import Operator, Commutator 6 | from sympsi import HilbertSpace, FockSpace, Ket, Bra, IdentityOperator 7 | from sympy.functions.special.tensor_functions import KroneckerDelta 8 | 9 | 10 | __all__ = [ 11 | 'BosonOp', 12 | 'MultiBosonOp', 13 | 'BosonFockKet', 14 | 'BosonFockBra', 15 | 'BosonCoherentKet', 16 | 'BosonCoherentBra', 17 | 'MultiBosonFockKet', 18 | 'MultiBosonFockBra', 19 | 'BosonVacuumKet', 20 | 'BosonVacuumBra' 21 | ] 22 | 23 | 24 | class BosonOp(Operator): 25 | """A bosonic operator that satisfies [a, Dagger(a)] == 1. 26 | 27 | Parameters 28 | ========== 29 | 30 | name : str 31 | A string that labels the bosonic mode. 32 | 33 | annihilation : bool 34 | A bool that indicates if the bosonic operator is an annihilation (True, 35 | default value) or creation operator (False) 36 | 37 | Examples 38 | ======== 39 | 40 | >>> from sympsi import Dagger, Commutator 41 | >>> from sympsi.boson import BosonOp 42 | >>> a = BosonOp("a") 43 | >>> Commutator(a, Dagger(a)).doit() 44 | 1 45 | """ 46 | 47 | @property 48 | def name(self): 49 | return self.args[0] 50 | 51 | @property 52 | def is_annihilation(self): 53 | return bool(self.args[1]) 54 | 55 | @classmethod 56 | def default_args(self): 57 | return ("a", True) 58 | 59 | def __new__(cls, *args, **hints): 60 | if not len(args) in [1, 2]: 61 | raise ValueError('1 or 2 parameters expected, got %s' % str(args)) 62 | 63 | if len(args) == 1: 64 | args = (args[0], Integer(1)) 65 | 66 | if len(args) == 2: 67 | args = (args[0], Integer(args[1])) 68 | 69 | return Operator.__new__(cls, *args) 70 | 71 | def _eval_commutator_BosonOp(self, other, **hints): 72 | if self.name == other.name: 73 | # [a^\dagger, a] = -1 74 | if not self.is_annihilation and other.is_annihilation: 75 | return Integer(-1) 76 | 77 | elif 'independent' in hints and hints['independent']: 78 | # [a, b] = 0 79 | return Integer(0) 80 | 81 | return None 82 | 83 | def _eval_commutator_FermionOp(self, other, **hints): 84 | return Integer(0) 85 | 86 | def _eval_anticommutator_BosonOp(self, other, **hints): 87 | if 'independent' in hints and hints['independent']: 88 | # {a, b} = 2 * a * b, because [a, b] = 0 89 | return 2 * self * other 90 | 91 | return None 92 | 93 | def _eval_adjoint(self): 94 | return BosonOp(str(self.name), not self.is_annihilation) 95 | 96 | def __mul__(self, other): 97 | 98 | if other == IdentityOperator(2): 99 | return self 100 | 101 | if isinstance(other, Mul): 102 | args1 = tuple(arg for arg in other.args if arg.is_commutative) 103 | args2 = tuple(arg for arg in other.args if not arg.is_commutative) 104 | x = self 105 | for y in args2: 106 | x = x * y 107 | return Mul(*args1) * x 108 | 109 | return Mul(self, other) 110 | 111 | def _print_contents_latex(self, printer, *args): 112 | if self.is_annihilation: 113 | return r'{%s}' % str(self.name) 114 | else: 115 | return r'{{%s}^\dag}' % str(self.name) 116 | 117 | def _print_contents(self, printer, *args): 118 | if self.is_annihilation: 119 | return r'%s' % str(self.name) 120 | else: 121 | return r'Dagger(%s)' % str(self.name) 122 | 123 | def _print_contents_pretty(self, printer, *args): 124 | from sympy.printing.pretty.stringpict import prettyForm 125 | pform = printer._print(self.args[0], *args) 126 | if self.is_annihilation: 127 | return pform 128 | else: 129 | return pform**prettyForm(u('\u2020')) 130 | 131 | 132 | class MultiBosonOp(BosonOp): 133 | """Bosonic operators that satisfy the commutation relations: 134 | for discrete label for modes: 135 | [a(k1), Dagger(a(k2))] == KroneckerDelta(k1, k2). 136 | 137 | for continuous label for modes: 138 | [a(k1), Dagger(a(k2))] == DiracDelta(k1 - k2). 139 | 140 | and in both cases: 141 | [a(k1), a(k2)] == [Dagger(a(k1)), Dagger(a(k2))] == 0. 142 | 143 | 144 | Parameters 145 | ========== 146 | 147 | name : str 148 | A string that labels the bosonic mode. 149 | 150 | mode: Symbol 151 | A symbol that denotes the mode label. 152 | 153 | normalization : ['discrete', 'continuous'] 154 | 'discrete' for KroneckerDelta function, 155 | 'continuous' for DiracDelta function. 156 | should be specified in any case. 157 | 158 | annihilation : bool 159 | A bool that indicates if the bosonic operator is an annihilation (True, 160 | default value) or creation operator (False) 161 | 162 | 163 | Examples 164 | ======== 165 | 166 | >>> from sympsi import Dagger, Commutator 167 | >>> from sympsi.boson import MultiBosonOp 168 | >>> w1, w2 = symbols("w1, w2") 169 | >>> a1 = MultiBosonOp("a", w1, 'discrete') 170 | >>> a2 = MultiBosonOp("a", w2, 'discrete') 171 | >>> Commutator(a1, Dagger(a2)).doit() 172 | KroneckerDelta(w1, w2) 173 | >>> Commutator(a1, a2).doit() 174 | 0 175 | >>> Commutator(Dagger(a1), Dagger(a2)).doit() 176 | 0 177 | >>> b1 = MultiBosonOp("b", w1, 'continuous') 178 | >>> b2 = MultiBosonOp("b", w2, 'continuous') 179 | >>> Commutator(b1, Dagger(b2)).doit() 180 | DiracDelta(w1 - w2) 181 | >>> Commutator(b1, b2).doit() 182 | 0 183 | >>> Commutator(Dagger(b1), Dagger(b2)).doit() 184 | 0 185 | 186 | """ 187 | @property 188 | def name(self): 189 | return self.args[0] 190 | 191 | @property 192 | def mode(self): 193 | return self.args[1] 194 | 195 | @property 196 | def free_symbols(self): 197 | return set([self, self.mode]) 198 | 199 | @property 200 | def normalization_type(self): 201 | return str(self.args[2]) 202 | 203 | @property 204 | def is_annihilation(self): 205 | return bool(self.args[3]) 206 | 207 | @classmethod 208 | def default_args(self): 209 | return ("a", Symbol("\omega"), "discrete", True) 210 | 211 | def __new__(cls, *args, **hints): 212 | if not len(args) in [3, 4]: 213 | raise ValueError('3 or 4 parameters expected, got %s' % args) 214 | 215 | if str(args[2]) not in ['discrete', 'continuous']: 216 | print("discrete or continuous: %s" % args[2]) 217 | raise ValueError('The third argument should be "discrete" or "continuous", got %s' % args) 218 | 219 | if len(args) == 3: 220 | args = (args[0], args[1], str(args[2]), Integer(1)) 221 | 222 | if len(args) == 4: 223 | args = (args[0], args[1], str(args[2]), Integer(args[3])) 224 | 225 | return Operator.__new__(cls, *args) 226 | 227 | def _eval_commutator_BosonOp(self, other, **hints): 228 | if 'independent' in hints and hints['independent']: 229 | # [a, b] = 0 230 | return Integer(0) 231 | 232 | def _eval_commutator_MultiBosonOp(self, other, **hints): 233 | if (self.name == other.name and 234 | self.normalization_type == other.normalization_type): 235 | if not self.is_annihilation and other.is_annihilation: 236 | if self.normalization_type == 'discrete': 237 | return - KroneckerDelta(self.mode, other.mode) 238 | elif self.normalization_type == 'continuous': 239 | return - DiracDelta(self.mode - other.mode) 240 | elif not self.is_annihilation and not other.is_annihilation: 241 | return Integer(0) 242 | elif self.is_annihilation and other.is_annihilation: 243 | return Integer(0) 244 | 245 | elif 'independent' in hints and hints['independent']: 246 | # [a, b] = 0 247 | return Integer(0) 248 | 249 | return None 250 | 251 | def _eval_commutator_FermionOp(self, other, **hints): 252 | return Integer(0) 253 | 254 | def _eval_anticommutator_BosonOp(self, other, **hints): 255 | return 2 * self * other 256 | 257 | def _eval_anticommutator_MultiBosonOp(self, other, **hints): 258 | if 'independent' in hints and hints['independent']: 259 | # {a, b} = 2 * a * b, because [a, b] = 0 260 | return 2 * self * other 261 | else: 262 | return 2 * self * other - Commutator(self, other) 263 | 264 | return None 265 | 266 | def _eval_adjoint(self): 267 | return MultiBosonOp(self.name, self.mode, self.normalization_type, not self.is_annihilation) 268 | 269 | def _print_contents_latex(self, printer, *args): 270 | if self.is_annihilation: 271 | return r'{%s_{%s}}' % (str(self.name), str(self.mode)) 272 | else: 273 | return r'{{%s_{%s}}^\dag}' % (str(self.name), str(self.mode)) 274 | 275 | def _print_contents(self, printer, *args): 276 | if self.is_annihilation: 277 | return r'%s(%s)' % (str(self.name), str(self.mode)) 278 | else: 279 | return r'Dagger(%s(%s))' % (str(self.name), str(self.mode)) 280 | 281 | def _print_contents_pretty(self, printer, *args): 282 | # TODO 283 | from sympy.printing.pretty.stringpict import prettyForm 284 | pform = printer._print(self.args[0], *args) 285 | if self.is_annihilation: 286 | return pform 287 | else: 288 | return pform**prettyForm(u('\u2020')) 289 | 290 | 291 | class BosonFockKet(Ket): 292 | """Fock state ket for a bosonic mode. 293 | 294 | Parameters 295 | ========== 296 | 297 | n : Number 298 | The Fock state number. 299 | 300 | 301 | """ 302 | 303 | def __new__(cls, n): 304 | return Ket.__new__(cls, n) 305 | 306 | @property 307 | def n(self): 308 | return self.label[0] 309 | 310 | @classmethod 311 | def dual_class(self): 312 | return BosonFockBra 313 | 314 | @classmethod 315 | def _eval_hilbert_space(cls, label): 316 | return FockSpace() 317 | 318 | def _eval_innerproduct_BosonFockBra(self, bra, **hints): 319 | return KroneckerDelta(self.n, bra.n) 320 | 321 | def _apply_operator_BosonOp(self, op, **options): 322 | if op.is_annihilation: 323 | return sqrt(self.n) * BosonFockKet(self.n - 1) 324 | else: 325 | return sqrt(self.n + 1) * BosonFockKet(self.n + 1) 326 | 327 | class BosonFockBra(Bra): 328 | """Fock state bra for a bosonic mode. 329 | 330 | Parameters 331 | ========== 332 | 333 | n : Number 334 | The Fock state number. 335 | 336 | """ 337 | 338 | def __new__(cls, n): 339 | return Bra.__new__(cls, n) 340 | 341 | @property 342 | def n(self): 343 | return self.label[0] 344 | 345 | @classmethod 346 | def dual_class(self): 347 | return BosonFockKet 348 | 349 | @classmethod 350 | def _eval_hilbert_space(cls, label): 351 | return FockSpace() 352 | 353 | class MultiBosonFockKet(Ket): 354 | """Fock state ket for a multimode-bosonic mode(KroneckerDelta normalization). 355 | 356 | Parameters 357 | ========== 358 | 359 | n : Number 360 | The Fock state number. 361 | 362 | mode : Symbol 363 | A symbol that denotes the mode label. 364 | 365 | """ 366 | 367 | def __new__(cls, n, mode): 368 | return Ket.__new__(cls, n, mode) 369 | 370 | @property 371 | def n(self): 372 | return self.label[0] 373 | 374 | @property 375 | def mode(self): 376 | return self.label[1] 377 | 378 | @classmethod 379 | def default_args(self): 380 | return (Integer(0), Symbol("k")) 381 | 382 | @classmethod 383 | def dual_class(self): 384 | return MultiBosonFockBra 385 | 386 | @classmethod 387 | def _eval_hilbert_space(cls, label): 388 | return FockSpace() 389 | 390 | def _eval_innerproduct_MultiBosonFockBra(self, bra, **hints): 391 | return KroneckerDelta(self.n, bra.n) * KroneckerDelta(self.mode, bra.mode) 392 | 393 | def _apply_operator_MultiBosonOp(self, op, **options): 394 | if op.normalization_type == 'discrete': 395 | if op.mode != self.mode: 396 | return None 397 | if op.is_annihilation: 398 | return sqrt(self.n) * MultiBosonFockKet(self.n - 1, op.mode) 399 | else: 400 | return sqrt(self.n + 1) * MultiBosonFockKet(self.n + 1, op.mode) 401 | else: 402 | return None 403 | 404 | def _latex(self, printer, *args): 405 | return r'{\left| {%s} \right\rangle}{_{%s}}' % (str(self.n), str(self.mode)) 406 | 407 | class MultiBosonFockBra(Bra): 408 | """Fock state bra for a multimode-bosonic mode(KroneckerDelta normalization). 409 | 410 | Parameters 411 | ========== 412 | 413 | n : Number 414 | The Fock state number. 415 | 416 | mode : Symbol 417 | A symbol that denotes the mode label. 418 | 419 | """ 420 | 421 | def __new__(cls, n, mode): 422 | return Ket.__new__(cls, n, mode) 423 | 424 | @property 425 | def n(self): 426 | return self.label[0] 427 | 428 | @property 429 | def mode(self): 430 | return self.label[1] 431 | 432 | @classmethod 433 | def dual_class(self): 434 | return MultiBosonFockKet 435 | 436 | @classmethod 437 | def _eval_hilbert_space(cls, label): 438 | return FockSpace() 439 | 440 | def _latex(self, printer, *args): 441 | return r'{_{%s}}{\left\langle {%s} \right|}' % (str(self.mode), str(self.n)) 442 | 443 | class BosonCoherentKet(Ket): 444 | """Coherent state ket for a bosonic mode. 445 | 446 | Parameters 447 | ========== 448 | 449 | alpha : Number, Symbol 450 | The complex amplitude of the coherent state. 451 | 452 | """ 453 | 454 | def __new__(cls, alpha): 455 | return Ket.__new__(cls, alpha) 456 | 457 | @property 458 | def alpha(self): 459 | return self.label[0] 460 | 461 | @classmethod 462 | def dual_class(self): 463 | return BosonCoherentBra 464 | 465 | @classmethod 466 | def _eval_hilbert_space(cls, label): 467 | return HilbertSpace() 468 | 469 | def _eval_innerproduct_BosonCoherentBra(self, bra, **hints): 470 | if self.alpha == bra.alpha: 471 | return Integer(1) 472 | else: 473 | return exp(-(abs(self.alpha)**2 + abs(bra.alpha)**2 - 2 * conjugate(bra.alpha) * self.alpha)/2) 474 | 475 | def _apply_operator_BosonOp(self, op, **options): 476 | if op.is_annihilation: 477 | return self.alpha * self 478 | else: 479 | return None 480 | 481 | 482 | class BosonCoherentBra(Bra): 483 | """Coherent state bra for a bosonic mode. 484 | 485 | Parameters 486 | ========== 487 | 488 | alpha : Number, Symbol 489 | The complex amplitude of the coherent state. 490 | 491 | """ 492 | 493 | def __new__(cls, alpha): 494 | return Bra.__new__(cls, alpha) 495 | 496 | @property 497 | def alpha(self): 498 | return self.label[0] 499 | 500 | @classmethod 501 | def dual_class(self): 502 | return BosonCoherentKet 503 | 504 | def _apply_operator_BosonOp(self, op, **options): 505 | if not op.is_annihilation: 506 | return self.alpha * self 507 | else: 508 | return None 509 | 510 | 511 | class BosonVacuumKet(Ket): 512 | """ 513 | Ket representing the Vacuum State. |vac> 514 | returns zero if an annihilation operator is applied on the left. 515 | 516 | """ 517 | def __new__(cls): 518 | return Ket.__new__(cls) 519 | 520 | @classmethod 521 | def dual_class(self): 522 | return BosonVacuumBra 523 | 524 | @classmethod 525 | def _eval_hilbert_space(cls, label): 526 | return FockSpace() 527 | 528 | def _eval_innerproduct_BosonVacuumBra(self, bra, **hints): 529 | return Integer(1) 530 | 531 | def _apply_operator_BosonOp(self, op, **options): 532 | if op.is_annihilation: 533 | return Integer(0) 534 | else: 535 | return None 536 | 537 | def _apply_operator_MultiBosonOp(self, op, **options): 538 | if op.is_annihilation: 539 | return Integer(0) 540 | else: 541 | return None 542 | 543 | def _latex(self, printer, *args): 544 | return r'{\left| \mathrm{vac} \right\rangle}' 545 | 546 | class BosonVacuumBra(Bra): 547 | """Bra representing the Vacuum State. 0): 32 | for n in range(factor.args[1]): 33 | new_factors.append(factor.args[0]) 34 | else: 35 | new_factors.append(factor) 36 | 37 | return new_factors 38 | 39 | 40 | def _normal_ordered_form_factor(product, independent=False, recursive_limit=10, 41 | _recursive_depth=0): 42 | """ 43 | Helper function for normal_ordered_form_factor: Write multiplication 44 | expression with bosonic or fermionic operators on normally ordered form, 45 | using the bosonic and fermionic commutation relations. The resulting 46 | operator expression is equivalent to the argument, but will in general be 47 | a sum of operator products instead of a simple product. 48 | """ 49 | 50 | factors = _expand_powers(product) 51 | 52 | new_factors = [] 53 | n = 0 54 | while n < len(factors) - 1: 55 | 56 | if (isinstance(factors[n], OperatorFunction) and 57 | isinstance(factors[n].operator, BosonOp)): 58 | # boson 59 | if (not isinstance(factors[n + 1], OperatorFunction) or 60 | (isinstance(factors[n + 1], OperatorFunction) and 61 | not isinstance(factors[n + 1].operator, BosonOp))): 62 | new_factors.append(factors[n]) 63 | 64 | elif factors[n].operator.is_annihilation == factors[n + 1].operator.is_annihilation: 65 | if (independent and 66 | str(factors[n].operator.name) > str(factors[n + 1].operator.name)): 67 | new_factors.append(factors[n + 1]) 68 | new_factors.append(factors[n]) 69 | n += 1 70 | else: 71 | new_factors.append(factors[n]) 72 | 73 | elif not factors[n].operator.is_annihilation: 74 | new_factors.append(factors[n]) 75 | 76 | else: 77 | if factors[n + 1].operator.is_annihilation: 78 | new_factors.append(factors[n]) 79 | else: 80 | if factors[n].operator.args[0] != factors[n + 1].operator.args[0]: 81 | if independent: 82 | c = 0 83 | else: 84 | c = Commutator(factors[n], factors[n + 1]) 85 | new_factors.append(factors[n + 1] * factors[n] + c) 86 | else: 87 | c = Commutator(factors[n], factors[n + 1]) 88 | new_factors.append( 89 | factors[n + 1] * factors[n] + c.doit()) 90 | n += 1 91 | 92 | elif isinstance(factors[n], Expectation): 93 | factor = Expectation(normal_ordered_form(factors[n].args[0]), factors[n].is_normal_order) 94 | new_factors.append(factor) 95 | 96 | elif isinstance(factors[n], BosonOp): 97 | # boson 98 | if not isinstance(factors[n + 1], BosonOp): 99 | new_factors.append(factors[n]) 100 | 101 | elif factors[n].is_annihilation == factors[n + 1].is_annihilation: 102 | if (independent and 103 | str(factors[n].name) > str(factors[n + 1].name)): 104 | new_factors.append(factors[n + 1]) 105 | new_factors.append(factors[n]) 106 | n += 1 107 | else: 108 | new_factors.append(factors[n]) 109 | 110 | elif not factors[n].is_annihilation: 111 | new_factors.append(factors[n]) 112 | 113 | else: 114 | if factors[n + 1].is_annihilation: 115 | new_factors.append(factors[n]) 116 | else: 117 | if factors[n].args[0] != factors[n + 1].args[0]: 118 | if independent: 119 | c = 0 120 | else: 121 | c = Commutator(factors[n], factors[n + 1]) 122 | new_factors.append(factors[n + 1] * factors[n] + c) 123 | else: 124 | c = Commutator(factors[n], factors[n + 1]) 125 | new_factors.append( 126 | factors[n + 1] * factors[n] + c.doit()) 127 | n += 1 128 | 129 | elif isinstance(factors[n], FermionOp): 130 | # fermion 131 | if not isinstance(factors[n + 1], FermionOp): 132 | new_factors.append(factors[n]) 133 | 134 | elif factors[n].is_annihilation == factors[n + 1].is_annihilation: 135 | if (independent and 136 | str(factors[n].name) > str(factors[n + 1].name)): 137 | new_factors.append(factors[n + 1]) 138 | new_factors.append(factors[n]) 139 | n += 1 140 | else: 141 | new_factors.append(factors[n]) 142 | 143 | elif not factors[n].is_annihilation: 144 | new_factors.append(factors[n]) 145 | 146 | else: 147 | if factors[n + 1].is_annihilation: 148 | new_factors.append(factors[n]) 149 | else: 150 | if factors[n].args[0] != factors[n + 1].args[0]: 151 | if independent: 152 | c = 0 153 | else: 154 | c = AntiCommutator(factors[n], factors[n + 1]) 155 | new_factors.append(-factors[n + 1] * factors[n] + c) 156 | else: 157 | c = AntiCommutator(factors[n], factors[n + 1]) 158 | new_factors.append( 159 | -factors[n + 1] * factors[n] + c.doit()) 160 | n += 1 161 | 162 | elif isinstance(factors[n], SigmaOpBase): 163 | 164 | if isinstance(factors[n + 1], BosonOp): 165 | new_factors.append(factors[n + 1]) 166 | new_factors.append(factors[n]) 167 | n += 1 168 | elif (isinstance(factors[n + 1], OperatorFunction) and 169 | isinstance(factors[n + 1].operator, BosonOp)): 170 | new_factors.append(factors[n + 1]) 171 | new_factors.append(factors[n]) 172 | n += 1 173 | else: 174 | new_factors.append(factors[n]) 175 | 176 | elif isinstance(factors[n], Operator): 177 | if isinstance(factors[n], (BosonOp, FermionOp)): 178 | if isinstance(factors[n + 1], (BosonOp, FermionOp)): 179 | new_factors.append(factors[n + 1]) 180 | new_factors.append(factors[n]) 181 | n += 1 182 | elif (isinstance(factors[n + 1], OperatorFunction) and 183 | isinstance(factors[n + 1].operator, (BosonOp, FermionOp))): 184 | new_factors.append(factors[n + 1]) 185 | new_factors.append(factors[n]) 186 | n += 1 187 | else: 188 | new_factors.append(factors[n]) 189 | 190 | elif isinstance(factors[n], OperatorFunction): 191 | 192 | if isinstance(factors[n].operator, (BosonOp, FermionOp)): 193 | if isinstance(factors[n + 1], (BosonOp, FermionOp)): 194 | new_factors.append(factors[n + 1]) 195 | new_factors.append(factors[n]) 196 | n += 1 197 | elif (isinstance(factors[n + 1], OperatorFunction) and 198 | isinstance(factors[n + 1].operator, (BosonOp, FermionOp))): 199 | new_factors.append(factors[n + 1]) 200 | new_factors.append(factors[n]) 201 | n += 1 202 | else: 203 | new_factors.append(factors[n]) 204 | 205 | else: 206 | new_factors.append(normal_ordered_form(factors[n], 207 | recursive_limit=recursive_limit, 208 | _recursive_depth=_recursive_depth + 1, 209 | independent=independent)) 210 | 211 | n += 1 212 | 213 | if n == len(factors) - 1: 214 | new_factors.append(normal_ordered_form(factors[-1], 215 | recursive_limit=recursive_limit, 216 | _recursive_depth=_recursive_depth + 1, 217 | independent=independent)) 218 | 219 | if new_factors == factors: 220 | return product 221 | else: 222 | expr = Mul(*new_factors).expand() 223 | return normal_ordered_form(expr, 224 | recursive_limit=recursive_limit, 225 | _recursive_depth=_recursive_depth + 1, 226 | independent=independent) 227 | 228 | 229 | def _normal_ordered_form_terms(expr, independent=False, recursive_limit=10, 230 | _recursive_depth=0): 231 | """ 232 | Helper function for normal_ordered_form: loop through each term in an 233 | addition expression and call _normal_ordered_form_factor to perform the 234 | factor to an normally ordered expression. 235 | """ 236 | 237 | new_terms = [] 238 | for term in expr.args: 239 | if isinstance(term, Mul): 240 | new_term = _normal_ordered_form_factor( 241 | term, recursive_limit=recursive_limit, 242 | _recursive_depth=_recursive_depth, independent=independent) 243 | new_terms.append(new_term) 244 | elif isinstance(term, Expectation): 245 | term = Expectation(normal_ordered_form(term.args[0]), term.is_normal_order) 246 | new_terms.append(term) 247 | else: 248 | new_terms.append(term) 249 | 250 | return Add(*new_terms) 251 | 252 | 253 | def normal_ordered_form(expr, independent=False, recursive_limit=10, 254 | _recursive_depth=0): 255 | """Write an expression with bosonic or fermionic operators on normal 256 | ordered form, where each term is normally ordered. Note that this 257 | normal ordered form is equivalent to the original expression. 258 | 259 | Parameters 260 | ========== 261 | 262 | expr : expression 263 | The expression write on normal ordered form. 264 | 265 | recursive_limit : int (default 10) 266 | The number of allowed recursive applications of the function. 267 | 268 | Examples 269 | ======== 270 | 271 | >>> from sympsi import Dagger 272 | >>> from sympsi.boson import BosonOp 273 | >>> from sympsi.operatorordering import normal_ordered_form 274 | >>> a = BosonOp("a") 275 | >>> normal_ordered_form(a * Dagger(a)) 276 | 1 + Dagger(a)*a 277 | """ 278 | 279 | if _recursive_depth > recursive_limit: 280 | warnings.warn("Too many recursions, aborting") 281 | return expr 282 | 283 | if isinstance(expr, Add): 284 | return _normal_ordered_form_terms(expr, 285 | recursive_limit=recursive_limit, 286 | _recursive_depth=_recursive_depth, 287 | independent=independent) 288 | elif isinstance(expr, Mul): 289 | return _normal_ordered_form_factor(expr, 290 | recursive_limit=recursive_limit, 291 | _recursive_depth=_recursive_depth, 292 | independent=independent) 293 | 294 | elif isinstance(expr, Expectation): 295 | return Expectation(normal_ordered_form(expr.expression), 296 | expr.is_normal_order) 297 | 298 | elif isinstance(expr, (Sum, Integral)): 299 | nargs = [normal_ordered_form(expr.function, 300 | recursive_limit=recursive_limit, 301 | _recursive_depth=_recursive_depth, 302 | independent=independent)] 303 | for lim in expr.limits: 304 | nargs.append(lim) 305 | return type(expr)(*nargs) 306 | 307 | else: 308 | return expr 309 | 310 | 311 | def _normal_order_factor(product, recursive_limit=10, _recursive_depth=0): 312 | """ 313 | Helper function for normal_order: Normal order a multiplication expression 314 | with bosonic or fermionic operators. In general the resulting operator 315 | expression will not be equivalent to original product. 316 | """ 317 | 318 | factors = _expand_powers(product) 319 | 320 | n = 0 321 | new_factors = [] 322 | while n < len(factors) - 1: 323 | 324 | if (isinstance(factors[n], OperatorFunction) and 325 | isinstance(factors[n].operator, BosonOp) and 326 | factors[n].operator.is_annihilation): 327 | # boson 328 | if not isinstance(factors[n + 1].operator, BosonOp): 329 | new_factors.append(factors[n]) 330 | else: 331 | if factors[n + 1].is_annihilation: 332 | new_factors.append(factors[n]) 333 | else: 334 | if factors[n].operator.args[0] != factors[n + 1].operator.args[0]: 335 | new_factors.append(factors[n + 1] * factors[n]) 336 | else: 337 | new_factors.append(factors[n + 1] * factors[n]) 338 | n += 1 339 | 340 | elif (isinstance(factors[n], BosonOp) and 341 | factors[n].is_annihilation): 342 | # boson 343 | if not isinstance(factors[n + 1], BosonOp): 344 | new_factors.append(factors[n]) 345 | else: 346 | if factors[n + 1].is_annihilation: 347 | new_factors.append(factors[n]) 348 | else: 349 | if factors[n].args[0] != factors[n + 1].args[0]: 350 | new_factors.append(factors[n + 1] * factors[n]) 351 | else: 352 | new_factors.append(factors[n + 1] * factors[n]) 353 | n += 1 354 | 355 | elif (isinstance(factors[n], FermionOp) and 356 | factors[n].is_annihilation): 357 | # fermion 358 | if not isinstance(factors[n + 1], FermionOp): 359 | new_factors.append(factors[n]) 360 | else: 361 | if factors[n + 1].is_annihilation: 362 | new_factors.append(factors[n]) 363 | else: 364 | if factors[n].args[0] != factors[n + 1].args[0]: 365 | new_factors.append(-factors[n + 1] * factors[n]) 366 | else: 367 | new_factors.append(-factors[n + 1] * factors[n]) 368 | n += 1 369 | 370 | else: 371 | new_factors.append(factors[n]) 372 | 373 | n += 1 374 | 375 | if n == len(factors) - 1: 376 | new_factors.append(factors[-1]) 377 | 378 | if new_factors == factors: 379 | return product 380 | else: 381 | expr = Mul(*new_factors).expand() 382 | return normal_order(expr, 383 | recursive_limit=recursive_limit, 384 | _recursive_depth=_recursive_depth + 1) 385 | 386 | 387 | def _normal_order_terms(expr, recursive_limit=10, _recursive_depth=0): 388 | """ 389 | Helper function for normal_order: look through each term in an addition 390 | expression and call _normal_order_factor to perform the normal ordering 391 | on the factors. 392 | """ 393 | 394 | new_terms = [] 395 | for term in expr.args: 396 | if isinstance(term, Mul): 397 | new_term = _normal_order_factor(term, 398 | recursive_limit=recursive_limit, 399 | _recursive_depth=_recursive_depth) 400 | new_terms.append(new_term) 401 | else: 402 | new_terms.append(term) 403 | 404 | return Add(*new_terms) 405 | 406 | 407 | def normal_order(expr, recursive_limit=10, _recursive_depth=0): 408 | """Normal order an expression with bosonic or fermionic operators. Note 409 | that this normal order is not equivalent to the original expression, but 410 | the creation and annihilation operators in each term in expr is reordered 411 | so that the expression becomes normal ordered. 412 | 413 | Parameters 414 | ========== 415 | 416 | expr : expression 417 | The expression to normal order. 418 | 419 | recursive_limit : int (default 10) 420 | The number of allowed recursive applications of the function. 421 | 422 | Examples 423 | ======== 424 | 425 | >>> from sympsi import Dagger 426 | >>> from sympsi.boson import BosonOp 427 | >>> from sympsi.operatorordering import normal_order 428 | >>> a = BosonOp("a") 429 | >>> normal_order(a * Dagger(a)) 430 | Dagger(a)*a 431 | """ 432 | if _recursive_depth > recursive_limit: 433 | warnings.warn("Too many recursions, aborting") 434 | return expr 435 | 436 | if isinstance(expr, Add): 437 | return _normal_order_terms(expr, recursive_limit=recursive_limit, 438 | _recursive_depth=_recursive_depth) 439 | elif isinstance(expr, Mul): 440 | return _normal_order_factor(expr, recursive_limit=recursive_limit, 441 | _recursive_depth=_recursive_depth) 442 | else: 443 | return expr 444 | -------------------------------------------------------------------------------- /sympsi/tests/test_identitysearch.py: -------------------------------------------------------------------------------- 1 | from sympy.external import import_module 2 | from sympy import Mul, Integer 3 | from sympy.physics.quantum.dagger import Dagger 4 | from sympy.physics.quantum.gate import (X, Y, Z, H, S, T, CNOT, 5 | IdentityGate, CGate, PhaseGate, TGate, gate_simp) 6 | from sympy.physics.quantum.identitysearch import (generate_gate_rules, 7 | generate_equivalent_ids, GateIdentity, bfs_identity_search, 8 | random_identity_search, is_scalar_sparse_matrix, 9 | is_scalar_nonsparse_matrix, is_degenerate, is_reducible) 10 | from sympy.utilities.pytest import skip 11 | 12 | 13 | def create_gate_sequence(qubit=0): 14 | gates = (X(qubit), Y(qubit), Z(qubit), H(qubit)) 15 | return gates 16 | 17 | 18 | def test_generate_gate_rules_1(): 19 | # Test with tuples 20 | (x, y, z, h) = create_gate_sequence() 21 | ph = PhaseGate(0) 22 | cgate_t = CGate(0, TGate(1)) 23 | 24 | assert generate_gate_rules((x,)) == set([((x,), ())]) 25 | 26 | gate_rules = set([((x, x), ()), 27 | ((x,), (x,))]) 28 | assert generate_gate_rules((x, x)) == gate_rules 29 | 30 | gate_rules = set([((x, y, x), ()), 31 | ((y, x, x), ()), 32 | ((x, x, y), ()), 33 | ((y, x), (x,)), 34 | ((x, y), (x,)), 35 | ((y,), (x, x))]) 36 | assert generate_gate_rules((x, y, x)) == gate_rules 37 | 38 | gate_rules = set([((x, y, z), ()), ((y, z, x), ()), ((z, x, y), ()), 39 | ((), (x, z, y)), ((), (y, x, z)), ((), (z, y, x)), 40 | ((x,), (z, y)), ((y, z), (x,)), ((y,), (x, z)), 41 | ((z, x), (y,)), ((z,), (y, x)), ((x, y), (z,))]) 42 | actual = generate_gate_rules((x, y, z)) 43 | assert actual == gate_rules 44 | 45 | gate_rules = set( 46 | [((), (h, z, y, x)), ((), (x, h, z, y)), ((), (y, x, h, z)), 47 | ((), (z, y, x, h)), ((h,), (z, y, x)), ((x,), (h, z, y)), 48 | ((y,), (x, h, z)), ((z,), (y, x, h)), ((h, x), (z, y)), 49 | ((x, y), (h, z)), ((y, z), (x, h)), ((z, h), (y, x)), 50 | ((h, x, y), (z,)), ((x, y, z), (h,)), ((y, z, h), (x,)), 51 | ((z, h, x), (y,)), ((h, x, y, z), ()), ((x, y, z, h), ()), 52 | ((y, z, h, x), ()), ((z, h, x, y), ())]) 53 | actual = generate_gate_rules((x, y, z, h)) 54 | assert actual == gate_rules 55 | 56 | gate_rules = set([((), (cgate_t**(-1), ph**(-1), x)), 57 | ((), (ph**(-1), x, cgate_t**(-1))), 58 | ((), (x, cgate_t**(-1), ph**(-1))), 59 | ((cgate_t,), (ph**(-1), x)), 60 | ((ph,), (x, cgate_t**(-1))), 61 | ((x,), (cgate_t**(-1), ph**(-1))), 62 | ((cgate_t, x), (ph**(-1),)), 63 | ((ph, cgate_t), (x,)), 64 | ((x, ph), (cgate_t**(-1),)), 65 | ((cgate_t, x, ph), ()), 66 | ((ph, cgate_t, x), ()), 67 | ((x, ph, cgate_t), ())]) 68 | actual = generate_gate_rules((x, ph, cgate_t)) 69 | assert actual == gate_rules 70 | 71 | gate_rules = set([(Integer(1), cgate_t**(-1)*ph**(-1)*x), 72 | (Integer(1), ph**(-1)*x*cgate_t**(-1)), 73 | (Integer(1), x*cgate_t**(-1)*ph**(-1)), 74 | (cgate_t, ph**(-1)*x), 75 | (ph, x*cgate_t**(-1)), 76 | (x, cgate_t**(-1)*ph**(-1)), 77 | (cgate_t*x, ph**(-1)), 78 | (ph*cgate_t, x), 79 | (x*ph, cgate_t**(-1)), 80 | (cgate_t*x*ph, Integer(1)), 81 | (ph*cgate_t*x, Integer(1)), 82 | (x*ph*cgate_t, Integer(1))]) 83 | actual = generate_gate_rules((x, ph, cgate_t), return_as_muls=True) 84 | assert actual == gate_rules 85 | 86 | 87 | def test_generate_gate_rules_2(): 88 | # Test with Muls 89 | (x, y, z, h) = create_gate_sequence() 90 | ph = PhaseGate(0) 91 | cgate_t = CGate(0, TGate(1)) 92 | 93 | # Note: 1 (type int) is not the same as 1 (type One) 94 | expected = set([(x, Integer(1))]) 95 | assert generate_gate_rules((x,), return_as_muls=True) == expected 96 | 97 | expected = set([(Integer(1), Integer(1))]) 98 | assert generate_gate_rules(x*x, return_as_muls=True) == expected 99 | 100 | expected = set([((), ())]) 101 | assert generate_gate_rules(x*x, return_as_muls=False) == expected 102 | 103 | gate_rules = set([(x*y*x, Integer(1)), 104 | (y, Integer(1)), 105 | (y*x, x), 106 | (x*y, x)]) 107 | assert generate_gate_rules(x*y*x, return_as_muls=True) == gate_rules 108 | 109 | gate_rules = set([(x*y*z, Integer(1)), 110 | (y*z*x, Integer(1)), 111 | (z*x*y, Integer(1)), 112 | (Integer(1), x*z*y), 113 | (Integer(1), y*x*z), 114 | (Integer(1), z*y*x), 115 | (x, z*y), 116 | (y*z, x), 117 | (y, x*z), 118 | (z*x, y), 119 | (z, y*x), 120 | (x*y, z)]) 121 | actual = generate_gate_rules(x*y*z, return_as_muls=True) 122 | assert actual == gate_rules 123 | 124 | gate_rules = set([(Integer(1), h*z*y*x), 125 | (Integer(1), x*h*z*y), 126 | (Integer(1), y*x*h*z), 127 | (Integer(1), z*y*x*h), 128 | (h, z*y*x), (x, h*z*y), 129 | (y, x*h*z), (z, y*x*h), 130 | (h*x, z*y), (z*h, y*x), 131 | (x*y, h*z), (y*z, x*h), 132 | (h*x*y, z), (x*y*z, h), 133 | (y*z*h, x), (z*h*x, y), 134 | (h*x*y*z, Integer(1)), 135 | (x*y*z*h, Integer(1)), 136 | (y*z*h*x, Integer(1)), 137 | (z*h*x*y, Integer(1))]) 138 | actual = generate_gate_rules(x*y*z*h, return_as_muls=True) 139 | assert actual == gate_rules 140 | 141 | gate_rules = set([(Integer(1), cgate_t**(-1)*ph**(-1)*x), 142 | (Integer(1), ph**(-1)*x*cgate_t**(-1)), 143 | (Integer(1), x*cgate_t**(-1)*ph**(-1)), 144 | (cgate_t, ph**(-1)*x), 145 | (ph, x*cgate_t**(-1)), 146 | (x, cgate_t**(-1)*ph**(-1)), 147 | (cgate_t*x, ph**(-1)), 148 | (ph*cgate_t, x), 149 | (x*ph, cgate_t**(-1)), 150 | (cgate_t*x*ph, Integer(1)), 151 | (ph*cgate_t*x, Integer(1)), 152 | (x*ph*cgate_t, Integer(1))]) 153 | actual = generate_gate_rules(x*ph*cgate_t, return_as_muls=True) 154 | assert actual == gate_rules 155 | 156 | gate_rules = set([((), (cgate_t**(-1), ph**(-1), x)), 157 | ((), (ph**(-1), x, cgate_t**(-1))), 158 | ((), (x, cgate_t**(-1), ph**(-1))), 159 | ((cgate_t,), (ph**(-1), x)), 160 | ((ph,), (x, cgate_t**(-1))), 161 | ((x,), (cgate_t**(-1), ph**(-1))), 162 | ((cgate_t, x), (ph**(-1),)), 163 | ((ph, cgate_t), (x,)), 164 | ((x, ph), (cgate_t**(-1),)), 165 | ((cgate_t, x, ph), ()), 166 | ((ph, cgate_t, x), ()), 167 | ((x, ph, cgate_t), ())]) 168 | actual = generate_gate_rules(x*ph*cgate_t) 169 | assert actual == gate_rules 170 | 171 | 172 | def test_generate_equivalent_ids_1(): 173 | # Test with tuples 174 | (x, y, z, h) = create_gate_sequence() 175 | 176 | assert generate_equivalent_ids((x,)) == set([(x,)]) 177 | assert generate_equivalent_ids((x, x)) == set([(x, x)]) 178 | assert generate_equivalent_ids((x, y)) == set([(x, y), (y, x)]) 179 | 180 | gate_seq = (x, y, z) 181 | gate_ids = set([(x, y, z), (y, z, x), (z, x, y), (z, y, x), 182 | (y, x, z), (x, z, y)]) 183 | assert generate_equivalent_ids(gate_seq) == gate_ids 184 | 185 | gate_ids = set([Mul(x, y, z), Mul(y, z, x), Mul(z, x, y), 186 | Mul(z, y, x), Mul(y, x, z), Mul(x, z, y)]) 187 | assert generate_equivalent_ids(gate_seq, return_as_muls=True) == gate_ids 188 | 189 | gate_seq = (x, y, z, h) 190 | gate_ids = set([(x, y, z, h), (y, z, h, x), 191 | (h, x, y, z), (h, z, y, x), 192 | (z, y, x, h), (y, x, h, z), 193 | (z, h, x, y), (x, h, z, y)]) 194 | assert generate_equivalent_ids(gate_seq) == gate_ids 195 | 196 | gate_seq = (x, y, x, y) 197 | gate_ids = set([(x, y, x, y), (y, x, y, x)]) 198 | assert generate_equivalent_ids(gate_seq) == gate_ids 199 | 200 | cgate_y = CGate((1,), y) 201 | gate_seq = (y, cgate_y, y, cgate_y) 202 | gate_ids = set([(y, cgate_y, y, cgate_y), (cgate_y, y, cgate_y, y)]) 203 | assert generate_equivalent_ids(gate_seq) == gate_ids 204 | 205 | cnot = CNOT(1, 0) 206 | cgate_z = CGate((0,), Z(1)) 207 | gate_seq = (cnot, h, cgate_z, h) 208 | gate_ids = set([(cnot, h, cgate_z, h), (h, cgate_z, h, cnot), 209 | (h, cnot, h, cgate_z), (cgate_z, h, cnot, h)]) 210 | assert generate_equivalent_ids(gate_seq) == gate_ids 211 | 212 | 213 | def test_generate_equivalent_ids_2(): 214 | # Test with Muls 215 | (x, y, z, h) = create_gate_sequence() 216 | 217 | assert generate_equivalent_ids((x,), return_as_muls=True) == set([x]) 218 | 219 | gate_ids = set([Integer(1)]) 220 | assert generate_equivalent_ids(x*x, return_as_muls=True) == gate_ids 221 | 222 | gate_ids = set([x*y, y*x]) 223 | assert generate_equivalent_ids(x*y, return_as_muls=True) == gate_ids 224 | 225 | gate_ids = set([(x, y), (y, x)]) 226 | assert generate_equivalent_ids(x*y) == gate_ids 227 | 228 | circuit = Mul(*(x, y, z)) 229 | gate_ids = set([x*y*z, y*z*x, z*x*y, z*y*x, 230 | y*x*z, x*z*y]) 231 | assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids 232 | 233 | circuit = Mul(*(x, y, z, h)) 234 | gate_ids = set([x*y*z*h, y*z*h*x, 235 | h*x*y*z, h*z*y*x, 236 | z*y*x*h, y*x*h*z, 237 | z*h*x*y, x*h*z*y]) 238 | assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids 239 | 240 | circuit = Mul(*(x, y, x, y)) 241 | gate_ids = set([x*y*x*y, y*x*y*x]) 242 | assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids 243 | 244 | cgate_y = CGate((1,), y) 245 | circuit = Mul(*(y, cgate_y, y, cgate_y)) 246 | gate_ids = set([y*cgate_y*y*cgate_y, cgate_y*y*cgate_y*y]) 247 | assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids 248 | 249 | cnot = CNOT(1, 0) 250 | cgate_z = CGate((0,), Z(1)) 251 | circuit = Mul(*(cnot, h, cgate_z, h)) 252 | gate_ids = set([cnot*h*cgate_z*h, h*cgate_z*h*cnot, 253 | h*cnot*h*cgate_z, cgate_z*h*cnot*h]) 254 | assert generate_equivalent_ids(circuit, return_as_muls=True) == gate_ids 255 | 256 | 257 | def test_is_scalar_nonsparse_matrix(): 258 | numqubits = 2 259 | id_only = False 260 | 261 | id_gate = (IdentityGate(1),) 262 | actual = is_scalar_nonsparse_matrix(id_gate, numqubits, id_only) 263 | assert actual is True 264 | 265 | x0 = X(0) 266 | xx_circuit = (x0, x0) 267 | actual = is_scalar_nonsparse_matrix(xx_circuit, numqubits, id_only) 268 | assert actual is True 269 | 270 | x1 = X(1) 271 | y1 = Y(1) 272 | xy_circuit = (x1, y1) 273 | actual = is_scalar_nonsparse_matrix(xy_circuit, numqubits, id_only) 274 | assert actual is False 275 | 276 | z1 = Z(1) 277 | xyz_circuit = (x1, y1, z1) 278 | actual = is_scalar_nonsparse_matrix(xyz_circuit, numqubits, id_only) 279 | assert actual is True 280 | 281 | cnot = CNOT(1, 0) 282 | cnot_circuit = (cnot, cnot) 283 | actual = is_scalar_nonsparse_matrix(cnot_circuit, numqubits, id_only) 284 | assert actual is True 285 | 286 | h = H(0) 287 | hh_circuit = (h, h) 288 | actual = is_scalar_nonsparse_matrix(hh_circuit, numqubits, id_only) 289 | assert actual is True 290 | 291 | h1 = H(1) 292 | xhzh_circuit = (x1, h1, z1, h1) 293 | actual = is_scalar_nonsparse_matrix(xhzh_circuit, numqubits, id_only) 294 | assert actual is True 295 | 296 | id_only = True 297 | actual = is_scalar_nonsparse_matrix(xhzh_circuit, numqubits, id_only) 298 | assert actual is True 299 | actual = is_scalar_nonsparse_matrix(xyz_circuit, numqubits, id_only) 300 | assert actual is False 301 | actual = is_scalar_nonsparse_matrix(cnot_circuit, numqubits, id_only) 302 | assert actual is True 303 | actual = is_scalar_nonsparse_matrix(hh_circuit, numqubits, id_only) 304 | assert actual is True 305 | 306 | 307 | def test_is_scalar_sparse_matrix(): 308 | np = import_module('numpy') 309 | if not np: 310 | skip("numpy not installed.") 311 | 312 | scipy = import_module('scipy', __import__kwargs={'fromlist': ['sparse']}) 313 | if not scipy: 314 | skip("scipy not installed.") 315 | 316 | numqubits = 2 317 | id_only = False 318 | 319 | id_gate = (IdentityGate(1),) 320 | assert is_scalar_sparse_matrix(id_gate, numqubits, id_only) is True 321 | 322 | x0 = X(0) 323 | xx_circuit = (x0, x0) 324 | assert is_scalar_sparse_matrix(xx_circuit, numqubits, id_only) is True 325 | 326 | x1 = X(1) 327 | y1 = Y(1) 328 | xy_circuit = (x1, y1) 329 | assert is_scalar_sparse_matrix(xy_circuit, numqubits, id_only) is False 330 | 331 | z1 = Z(1) 332 | xyz_circuit = (x1, y1, z1) 333 | assert is_scalar_sparse_matrix(xyz_circuit, numqubits, id_only) is True 334 | 335 | cnot = CNOT(1, 0) 336 | cnot_circuit = (cnot, cnot) 337 | assert is_scalar_sparse_matrix(cnot_circuit, numqubits, id_only) is True 338 | 339 | h = H(0) 340 | hh_circuit = (h, h) 341 | assert is_scalar_sparse_matrix(hh_circuit, numqubits, id_only) is True 342 | 343 | # NOTE: 344 | # The elements of the sparse matrix for the following circuit 345 | # is actually 1.0000000000000002+0.0j. 346 | h1 = H(1) 347 | xhzh_circuit = (x1, h1, z1, h1) 348 | assert is_scalar_sparse_matrix(xhzh_circuit, numqubits, id_only) is True 349 | 350 | id_only = True 351 | assert is_scalar_sparse_matrix(xhzh_circuit, numqubits, id_only) is True 352 | assert is_scalar_sparse_matrix(xyz_circuit, numqubits, id_only) is False 353 | assert is_scalar_sparse_matrix(cnot_circuit, numqubits, id_only) is True 354 | assert is_scalar_sparse_matrix(hh_circuit, numqubits, id_only) is True 355 | 356 | 357 | def test_is_degenerate(): 358 | (x, y, z, h) = create_gate_sequence() 359 | 360 | gate_id = GateIdentity(x, y, z) 361 | ids = set([gate_id]) 362 | 363 | another_id = (z, y, x) 364 | assert is_degenerate(ids, another_id) is True 365 | 366 | 367 | def test_is_reducible(): 368 | nqubits = 2 369 | (x, y, z, h) = create_gate_sequence() 370 | 371 | circuit = (x, y, y) 372 | assert is_reducible(circuit, nqubits, 1, 3) is True 373 | 374 | circuit = (x, y, x) 375 | assert is_reducible(circuit, nqubits, 1, 3) is False 376 | 377 | circuit = (x, y, y, x) 378 | assert is_reducible(circuit, nqubits, 0, 4) is True 379 | 380 | circuit = (x, y, y, x) 381 | assert is_reducible(circuit, nqubits, 1, 3) is True 382 | 383 | circuit = (x, y, z, y, y) 384 | assert is_reducible(circuit, nqubits, 1, 5) is True 385 | 386 | 387 | def test_bfs_identity_search(): 388 | assert bfs_identity_search([], 1) == set() 389 | 390 | (x, y, z, h) = create_gate_sequence() 391 | 392 | gate_list = [x] 393 | id_set = set([GateIdentity(x, x)]) 394 | assert bfs_identity_search(gate_list, 1, max_depth=2) == id_set 395 | 396 | # Set should not contain degenerate quantum circuits 397 | gate_list = [x, y, z] 398 | id_set = set([GateIdentity(x, x), 399 | GateIdentity(y, y), 400 | GateIdentity(z, z), 401 | GateIdentity(x, y, z)]) 402 | assert bfs_identity_search(gate_list, 1) == id_set 403 | 404 | id_set = set([GateIdentity(x, x), 405 | GateIdentity(y, y), 406 | GateIdentity(z, z), 407 | GateIdentity(x, y, z), 408 | GateIdentity(x, y, x, y), 409 | GateIdentity(x, z, x, z), 410 | GateIdentity(y, z, y, z)]) 411 | assert bfs_identity_search(gate_list, 1, max_depth=4) == id_set 412 | assert bfs_identity_search(gate_list, 1, max_depth=5) == id_set 413 | 414 | gate_list = [x, y, z, h] 415 | id_set = set([GateIdentity(x, x), 416 | GateIdentity(y, y), 417 | GateIdentity(z, z), 418 | GateIdentity(h, h), 419 | GateIdentity(x, y, z), 420 | GateIdentity(x, y, x, y), 421 | GateIdentity(x, z, x, z), 422 | GateIdentity(x, h, z, h), 423 | GateIdentity(y, z, y, z), 424 | GateIdentity(y, h, y, h)]) 425 | assert bfs_identity_search(gate_list, 1) == id_set 426 | 427 | id_set = set([GateIdentity(x, x), 428 | GateIdentity(y, y), 429 | GateIdentity(z, z), 430 | GateIdentity(h, h)]) 431 | assert id_set == bfs_identity_search(gate_list, 1, max_depth=3, 432 | identity_only=True) 433 | 434 | id_set = set([GateIdentity(x, x), 435 | GateIdentity(y, y), 436 | GateIdentity(z, z), 437 | GateIdentity(h, h), 438 | GateIdentity(x, y, z), 439 | GateIdentity(x, y, x, y), 440 | GateIdentity(x, z, x, z), 441 | GateIdentity(x, h, z, h), 442 | GateIdentity(y, z, y, z), 443 | GateIdentity(y, h, y, h), 444 | GateIdentity(x, y, h, x, h), 445 | GateIdentity(x, z, h, y, h), 446 | GateIdentity(y, z, h, z, h)]) 447 | assert bfs_identity_search(gate_list, 1, max_depth=5) == id_set 448 | 449 | id_set = set([GateIdentity(x, x), 450 | GateIdentity(y, y), 451 | GateIdentity(z, z), 452 | GateIdentity(h, h), 453 | GateIdentity(x, h, z, h)]) 454 | assert id_set == bfs_identity_search(gate_list, 1, max_depth=4, 455 | identity_only=True) 456 | 457 | cnot = CNOT(1, 0) 458 | gate_list = [x, cnot] 459 | id_set = set([GateIdentity(x, x), 460 | GateIdentity(cnot, cnot), 461 | GateIdentity(x, cnot, x, cnot)]) 462 | assert bfs_identity_search(gate_list, 2, max_depth=4) == id_set 463 | 464 | cgate_x = CGate((1,), x) 465 | gate_list = [x, cgate_x] 466 | id_set = set([GateIdentity(x, x), 467 | GateIdentity(cgate_x, cgate_x), 468 | GateIdentity(x, cgate_x, x, cgate_x)]) 469 | assert bfs_identity_search(gate_list, 2, max_depth=4) == id_set 470 | 471 | cgate_z = CGate((0,), Z(1)) 472 | gate_list = [cnot, cgate_z, h] 473 | id_set = set([GateIdentity(h, h), 474 | GateIdentity(cgate_z, cgate_z), 475 | GateIdentity(cnot, cnot), 476 | GateIdentity(cnot, h, cgate_z, h)]) 477 | assert bfs_identity_search(gate_list, 2, max_depth=4) == id_set 478 | 479 | s = PhaseGate(0) 480 | t = TGate(0) 481 | gate_list = [s, t] 482 | id_set = set([GateIdentity(s, s, s, s)]) 483 | assert bfs_identity_search(gate_list, 1, max_depth=4) == id_set 484 | 485 | # Throws an error in represent: "exponent must be >= 0" 486 | #gate_list = [Dagger(s), t] 487 | #id_set = set([GateIdentity(Dagger(s), t, t)]) 488 | #assert bfs_identity_search(gate_list, 1, max_depth=3) == id_set 489 | --------------------------------------------------------------------------------