123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- from itertools import product
- from sympy.core.add import Add
- from sympy.core.containers import Tuple
- from sympy.core.function import expand
- from sympy.core.mul import Mul
- from sympy.core.singleton import S
- from sympy.functions.elementary.exponential import log
- from sympy.matrices.dense import MutableDenseMatrix as Matrix
- from sympy.printing.pretty.stringpict import prettyForm
- from sympy.physics.quantum.dagger import Dagger
- from sympy.physics.quantum.operator import HermitianOperator
- from sympy.physics.quantum.represent import represent
- from sympy.physics.quantum.matrixutils import numpy_ndarray, scipy_sparse_matrix, to_numpy
- from sympy.physics.quantum.tensorproduct import TensorProduct, tensor_product_simp
- from sympy.physics.quantum.trace import Tr
- class Density(HermitianOperator):
- """Density operator for representing mixed states.
- TODO: Density operator support for Qubits
- Parameters
- ==========
- values : tuples/lists
- Each tuple/list should be of form (state, prob) or [state,prob]
- Examples
- ========
- Create a density operator with 2 states represented by Kets.
- >>> from sympy.physics.quantum.state import Ket
- >>> from sympy.physics.quantum.density import Density
- >>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
- >>> d
- Density((|0>, 0.5),(|1>, 0.5))
- """
- @classmethod
- def _eval_args(cls, args):
- # call this to qsympify the args
- args = super()._eval_args(args)
- for arg in args:
- # Check if arg is a tuple
- if not (isinstance(arg, Tuple) and len(arg) == 2):
- raise ValueError("Each argument should be of form [state,prob]"
- " or ( state, prob )")
- return args
- def states(self):
- """Return list of all states.
- Examples
- ========
- >>> from sympy.physics.quantum.state import Ket
- >>> from sympy.physics.quantum.density import Density
- >>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
- >>> d.states()
- (|0>, |1>)
- """
- return Tuple(*[arg[0] for arg in self.args])
- def probs(self):
- """Return list of all probabilities.
- Examples
- ========
- >>> from sympy.physics.quantum.state import Ket
- >>> from sympy.physics.quantum.density import Density
- >>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
- >>> d.probs()
- (0.5, 0.5)
- """
- return Tuple(*[arg[1] for arg in self.args])
- def get_state(self, index):
- """Return specific state by index.
- Parameters
- ==========
- index : index of state to be returned
- Examples
- ========
- >>> from sympy.physics.quantum.state import Ket
- >>> from sympy.physics.quantum.density import Density
- >>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
- >>> d.states()[1]
- |1>
- """
- state = self.args[index][0]
- return state
- def get_prob(self, index):
- """Return probability of specific state by index.
- Parameters
- ===========
- index : index of states whose probability is returned.
- Examples
- ========
- >>> from sympy.physics.quantum.state import Ket
- >>> from sympy.physics.quantum.density import Density
- >>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
- >>> d.probs()[1]
- 0.500000000000000
- """
- prob = self.args[index][1]
- return prob
- def apply_op(self, op):
- """op will operate on each individual state.
- Parameters
- ==========
- op : Operator
- Examples
- ========
- >>> from sympy.physics.quantum.state import Ket
- >>> from sympy.physics.quantum.density import Density
- >>> from sympy.physics.quantum.operator import Operator
- >>> A = Operator('A')
- >>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
- >>> d.apply_op(A)
- Density((A*|0>, 0.5),(A*|1>, 0.5))
- """
- new_args = [(op*state, prob) for (state, prob) in self.args]
- return Density(*new_args)
- def doit(self, **hints):
- """Expand the density operator into an outer product format.
- Examples
- ========
- >>> from sympy.physics.quantum.state import Ket
- >>> from sympy.physics.quantum.density import Density
- >>> from sympy.physics.quantum.operator import Operator
- >>> A = Operator('A')
- >>> d = Density([Ket(0), 0.5], [Ket(1),0.5])
- >>> d.doit()
- 0.5*|0><0| + 0.5*|1><1|
- """
- terms = []
- for (state, prob) in self.args:
- state = state.expand() # needed to break up (a+b)*c
- if (isinstance(state, Add)):
- for arg in product(state.args, repeat=2):
- terms.append(prob*self._generate_outer_prod(arg[0],
- arg[1]))
- else:
- terms.append(prob*self._generate_outer_prod(state, state))
- return Add(*terms)
- def _generate_outer_prod(self, arg1, arg2):
- c_part1, nc_part1 = arg1.args_cnc()
- c_part2, nc_part2 = arg2.args_cnc()
- if (len(nc_part1) == 0 or len(nc_part2) == 0):
- raise ValueError('Atleast one-pair of'
- ' Non-commutative instance required'
- ' for outer product.')
- # Muls of Tensor Products should be expanded
- # before this function is called
- if (isinstance(nc_part1[0], TensorProduct) and len(nc_part1) == 1
- and len(nc_part2) == 1):
- op = tensor_product_simp(nc_part1[0]*Dagger(nc_part2[0]))
- else:
- op = Mul(*nc_part1)*Dagger(Mul(*nc_part2))
- return Mul(*c_part1)*Mul(*c_part2) * op
- def _represent(self, **options):
- return represent(self.doit(), **options)
- def _print_operator_name_latex(self, printer, *args):
- return r'\rho'
- def _print_operator_name_pretty(self, printer, *args):
- return prettyForm('\N{GREEK SMALL LETTER RHO}')
- def _eval_trace(self, **kwargs):
- indices = kwargs.get('indices', [])
- return Tr(self.doit(), indices).doit()
- def entropy(self):
- """ Compute the entropy of a density matrix.
- Refer to density.entropy() method for examples.
- """
- return entropy(self)
- def entropy(density):
- """Compute the entropy of a matrix/density object.
- This computes -Tr(density*ln(density)) using the eigenvalue decomposition
- of density, which is given as either a Density instance or a matrix
- (numpy.ndarray, sympy.Matrix or scipy.sparse).
- Parameters
- ==========
- density : density matrix of type Density, SymPy matrix,
- scipy.sparse or numpy.ndarray
- Examples
- ========
- >>> from sympy.physics.quantum.density import Density, entropy
- >>> from sympy.physics.quantum.spin import JzKet
- >>> from sympy import S
- >>> up = JzKet(S(1)/2,S(1)/2)
- >>> down = JzKet(S(1)/2,-S(1)/2)
- >>> d = Density((up,S(1)/2),(down,S(1)/2))
- >>> entropy(d)
- log(2)/2
- """
- if isinstance(density, Density):
- density = represent(density) # represent in Matrix
- if isinstance(density, scipy_sparse_matrix):
- density = to_numpy(density)
- if isinstance(density, Matrix):
- eigvals = density.eigenvals().keys()
- return expand(-sum(e*log(e) for e in eigvals))
- elif isinstance(density, numpy_ndarray):
- import numpy as np
- eigvals = np.linalg.eigvals(density)
- return -np.sum(eigvals*np.log(eigvals))
- else:
- raise ValueError(
- "numpy.ndarray, scipy.sparse or SymPy matrix expected")
- def fidelity(state1, state2):
- """ Computes the fidelity [1]_ between two quantum states
- The arguments provided to this function should be a square matrix or a
- Density object. If it is a square matrix, it is assumed to be diagonalizable.
- Parameters
- ==========
- state1, state2 : a density matrix or Matrix
- Examples
- ========
- >>> from sympy import S, sqrt
- >>> from sympy.physics.quantum.dagger import Dagger
- >>> from sympy.physics.quantum.spin import JzKet
- >>> from sympy.physics.quantum.density import fidelity
- >>> from sympy.physics.quantum.represent import represent
- >>>
- >>> up = JzKet(S(1)/2,S(1)/2)
- >>> down = JzKet(S(1)/2,-S(1)/2)
- >>> amp = 1/sqrt(2)
- >>> updown = (amp*up) + (amp*down)
- >>>
- >>> # represent turns Kets into matrices
- >>> up_dm = represent(up*Dagger(up))
- >>> down_dm = represent(down*Dagger(down))
- >>> updown_dm = represent(updown*Dagger(updown))
- >>>
- >>> fidelity(up_dm, up_dm)
- 1
- >>> fidelity(up_dm, down_dm) #orthogonal states
- 0
- >>> fidelity(up_dm, updown_dm).evalf().round(3)
- 0.707
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Fidelity_of_quantum_states
- """
- state1 = represent(state1) if isinstance(state1, Density) else state1
- state2 = represent(state2) if isinstance(state2, Density) else state2
- if not isinstance(state1, Matrix) or not isinstance(state2, Matrix):
- raise ValueError("state1 and state2 must be of type Density or Matrix "
- "received type=%s for state1 and type=%s for state2" %
- (type(state1), type(state2)))
- if state1.shape != state2.shape and state1.is_square:
- raise ValueError("The dimensions of both args should be equal and the "
- "matrix obtained should be a square matrix")
- sqrt_state1 = state1**S.Half
- return Tr((sqrt_state1*state2*sqrt_state1)**S.Half).doit()
|