sho1d.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. """Simple Harmonic Oscillator 1-Dimension"""
  2. from sympy.core.numbers import (I, Integer)
  3. from sympy.core.singleton import S
  4. from sympy.core.symbol import Symbol
  5. from sympy.functions.elementary.miscellaneous import sqrt
  6. from sympy.physics.quantum.constants import hbar
  7. from sympy.physics.quantum.operator import Operator
  8. from sympy.physics.quantum.state import Bra, Ket, State
  9. from sympy.physics.quantum.qexpr import QExpr
  10. from sympy.physics.quantum.cartesian import X, Px
  11. from sympy.functions.special.tensor_functions import KroneckerDelta
  12. from sympy.physics.quantum.hilbert import ComplexSpace
  13. from sympy.physics.quantum.matrixutils import matrix_zeros
  14. #------------------------------------------------------------------------------
  15. class SHOOp(Operator):
  16. """A base class for the SHO Operators.
  17. We are limiting the number of arguments to be 1.
  18. """
  19. @classmethod
  20. def _eval_args(cls, args):
  21. args = QExpr._eval_args(args)
  22. if len(args) == 1:
  23. return args
  24. else:
  25. raise ValueError("Too many arguments")
  26. @classmethod
  27. def _eval_hilbert_space(cls, label):
  28. return ComplexSpace(S.Infinity)
  29. class RaisingOp(SHOOp):
  30. """The Raising Operator or a^dagger.
  31. When a^dagger acts on a state it raises the state up by one. Taking
  32. the adjoint of a^dagger returns 'a', the Lowering Operator. a^dagger
  33. can be rewritten in terms of position and momentum. We can represent
  34. a^dagger as a matrix, which will be its default basis.
  35. Parameters
  36. ==========
  37. args : tuple
  38. The list of numbers or parameters that uniquely specify the
  39. operator.
  40. Examples
  41. ========
  42. Create a Raising Operator and rewrite it in terms of position and
  43. momentum, and show that taking its adjoint returns 'a':
  44. >>> from sympy.physics.quantum.sho1d import RaisingOp
  45. >>> from sympy.physics.quantum import Dagger
  46. >>> ad = RaisingOp('a')
  47. >>> ad.rewrite('xp').doit()
  48. sqrt(2)*(m*omega*X - I*Px)/(2*sqrt(hbar)*sqrt(m*omega))
  49. >>> Dagger(ad)
  50. a
  51. Taking the commutator of a^dagger with other Operators:
  52. >>> from sympy.physics.quantum import Commutator
  53. >>> from sympy.physics.quantum.sho1d import RaisingOp, LoweringOp
  54. >>> from sympy.physics.quantum.sho1d import NumberOp
  55. >>> ad = RaisingOp('a')
  56. >>> a = LoweringOp('a')
  57. >>> N = NumberOp('N')
  58. >>> Commutator(ad, a).doit()
  59. -1
  60. >>> Commutator(ad, N).doit()
  61. -RaisingOp(a)
  62. Apply a^dagger to a state:
  63. >>> from sympy.physics.quantum import qapply
  64. >>> from sympy.physics.quantum.sho1d import RaisingOp, SHOKet
  65. >>> ad = RaisingOp('a')
  66. >>> k = SHOKet('k')
  67. >>> qapply(ad*k)
  68. sqrt(k + 1)*|k + 1>
  69. Matrix Representation
  70. >>> from sympy.physics.quantum.sho1d import RaisingOp
  71. >>> from sympy.physics.quantum.represent import represent
  72. >>> ad = RaisingOp('a')
  73. >>> represent(ad, basis=N, ndim=4, format='sympy')
  74. Matrix([
  75. [0, 0, 0, 0],
  76. [1, 0, 0, 0],
  77. [0, sqrt(2), 0, 0],
  78. [0, 0, sqrt(3), 0]])
  79. """
  80. def _eval_rewrite_as_xp(self, *args, **kwargs):
  81. return (S.One/sqrt(Integer(2)*hbar*m*omega))*(
  82. S.NegativeOne*I*Px + m*omega*X)
  83. def _eval_adjoint(self):
  84. return LoweringOp(*self.args)
  85. def _eval_commutator_LoweringOp(self, other):
  86. return S.NegativeOne
  87. def _eval_commutator_NumberOp(self, other):
  88. return S.NegativeOne*self
  89. def _apply_operator_SHOKet(self, ket):
  90. temp = ket.n + S.One
  91. return sqrt(temp)*SHOKet(temp)
  92. def _represent_default_basis(self, **options):
  93. return self._represent_NumberOp(None, **options)
  94. def _represent_XOp(self, basis, **options):
  95. # This logic is good but the underlying position
  96. # representation logic is broken.
  97. # temp = self.rewrite('xp').doit()
  98. # result = represent(temp, basis=X)
  99. # return result
  100. raise NotImplementedError('Position representation is not implemented')
  101. def _represent_NumberOp(self, basis, **options):
  102. ndim_info = options.get('ndim', 4)
  103. format = options.get('format','sympy')
  104. matrix = matrix_zeros(ndim_info, ndim_info, **options)
  105. for i in range(ndim_info - 1):
  106. value = sqrt(i + 1)
  107. if format == 'scipy.sparse':
  108. value = float(value)
  109. matrix[i + 1, i] = value
  110. if format == 'scipy.sparse':
  111. matrix = matrix.tocsr()
  112. return matrix
  113. #--------------------------------------------------------------------------
  114. # Printing Methods
  115. #--------------------------------------------------------------------------
  116. def _print_contents(self, printer, *args):
  117. arg0 = printer._print(self.args[0], *args)
  118. return '%s(%s)' % (self.__class__.__name__, arg0)
  119. def _print_contents_pretty(self, printer, *args):
  120. from sympy.printing.pretty.stringpict import prettyForm
  121. pform = printer._print(self.args[0], *args)
  122. pform = pform**prettyForm('\N{DAGGER}')
  123. return pform
  124. def _print_contents_latex(self, printer, *args):
  125. arg = printer._print(self.args[0])
  126. return '%s^{\\dagger}' % arg
  127. class LoweringOp(SHOOp):
  128. """The Lowering Operator or 'a'.
  129. When 'a' acts on a state it lowers the state up by one. Taking
  130. the adjoint of 'a' returns a^dagger, the Raising Operator. 'a'
  131. can be rewritten in terms of position and momentum. We can
  132. represent 'a' as a matrix, which will be its default basis.
  133. Parameters
  134. ==========
  135. args : tuple
  136. The list of numbers or parameters that uniquely specify the
  137. operator.
  138. Examples
  139. ========
  140. Create a Lowering Operator and rewrite it in terms of position and
  141. momentum, and show that taking its adjoint returns a^dagger:
  142. >>> from sympy.physics.quantum.sho1d import LoweringOp
  143. >>> from sympy.physics.quantum import Dagger
  144. >>> a = LoweringOp('a')
  145. >>> a.rewrite('xp').doit()
  146. sqrt(2)*(m*omega*X + I*Px)/(2*sqrt(hbar)*sqrt(m*omega))
  147. >>> Dagger(a)
  148. RaisingOp(a)
  149. Taking the commutator of 'a' with other Operators:
  150. >>> from sympy.physics.quantum import Commutator
  151. >>> from sympy.physics.quantum.sho1d import LoweringOp, RaisingOp
  152. >>> from sympy.physics.quantum.sho1d import NumberOp
  153. >>> a = LoweringOp('a')
  154. >>> ad = RaisingOp('a')
  155. >>> N = NumberOp('N')
  156. >>> Commutator(a, ad).doit()
  157. 1
  158. >>> Commutator(a, N).doit()
  159. a
  160. Apply 'a' to a state:
  161. >>> from sympy.physics.quantum import qapply
  162. >>> from sympy.physics.quantum.sho1d import LoweringOp, SHOKet
  163. >>> a = LoweringOp('a')
  164. >>> k = SHOKet('k')
  165. >>> qapply(a*k)
  166. sqrt(k)*|k - 1>
  167. Taking 'a' of the lowest state will return 0:
  168. >>> from sympy.physics.quantum import qapply
  169. >>> from sympy.physics.quantum.sho1d import LoweringOp, SHOKet
  170. >>> a = LoweringOp('a')
  171. >>> k = SHOKet(0)
  172. >>> qapply(a*k)
  173. 0
  174. Matrix Representation
  175. >>> from sympy.physics.quantum.sho1d import LoweringOp
  176. >>> from sympy.physics.quantum.represent import represent
  177. >>> a = LoweringOp('a')
  178. >>> represent(a, basis=N, ndim=4, format='sympy')
  179. Matrix([
  180. [0, 1, 0, 0],
  181. [0, 0, sqrt(2), 0],
  182. [0, 0, 0, sqrt(3)],
  183. [0, 0, 0, 0]])
  184. """
  185. def _eval_rewrite_as_xp(self, *args, **kwargs):
  186. return (S.One/sqrt(Integer(2)*hbar*m*omega))*(
  187. I*Px + m*omega*X)
  188. def _eval_adjoint(self):
  189. return RaisingOp(*self.args)
  190. def _eval_commutator_RaisingOp(self, other):
  191. return S.One
  192. def _eval_commutator_NumberOp(self, other):
  193. return self
  194. def _apply_operator_SHOKet(self, ket):
  195. temp = ket.n - Integer(1)
  196. if ket.n is S.Zero:
  197. return S.Zero
  198. else:
  199. return sqrt(ket.n)*SHOKet(temp)
  200. def _represent_default_basis(self, **options):
  201. return self._represent_NumberOp(None, **options)
  202. def _represent_XOp(self, basis, **options):
  203. # This logic is good but the underlying position
  204. # representation logic is broken.
  205. # temp = self.rewrite('xp').doit()
  206. # result = represent(temp, basis=X)
  207. # return result
  208. raise NotImplementedError('Position representation is not implemented')
  209. def _represent_NumberOp(self, basis, **options):
  210. ndim_info = options.get('ndim', 4)
  211. format = options.get('format', 'sympy')
  212. matrix = matrix_zeros(ndim_info, ndim_info, **options)
  213. for i in range(ndim_info - 1):
  214. value = sqrt(i + 1)
  215. if format == 'scipy.sparse':
  216. value = float(value)
  217. matrix[i,i + 1] = value
  218. if format == 'scipy.sparse':
  219. matrix = matrix.tocsr()
  220. return matrix
  221. class NumberOp(SHOOp):
  222. """The Number Operator is simply a^dagger*a
  223. It is often useful to write a^dagger*a as simply the Number Operator
  224. because the Number Operator commutes with the Hamiltonian. And can be
  225. expressed using the Number Operator. Also the Number Operator can be
  226. applied to states. We can represent the Number Operator as a matrix,
  227. which will be its default basis.
  228. Parameters
  229. ==========
  230. args : tuple
  231. The list of numbers or parameters that uniquely specify the
  232. operator.
  233. Examples
  234. ========
  235. Create a Number Operator and rewrite it in terms of the ladder
  236. operators, position and momentum operators, and Hamiltonian:
  237. >>> from sympy.physics.quantum.sho1d import NumberOp
  238. >>> N = NumberOp('N')
  239. >>> N.rewrite('a').doit()
  240. RaisingOp(a)*a
  241. >>> N.rewrite('xp').doit()
  242. -1/2 + (m**2*omega**2*X**2 + Px**2)/(2*hbar*m*omega)
  243. >>> N.rewrite('H').doit()
  244. -1/2 + H/(hbar*omega)
  245. Take the Commutator of the Number Operator with other Operators:
  246. >>> from sympy.physics.quantum import Commutator
  247. >>> from sympy.physics.quantum.sho1d import NumberOp, Hamiltonian
  248. >>> from sympy.physics.quantum.sho1d import RaisingOp, LoweringOp
  249. >>> N = NumberOp('N')
  250. >>> H = Hamiltonian('H')
  251. >>> ad = RaisingOp('a')
  252. >>> a = LoweringOp('a')
  253. >>> Commutator(N,H).doit()
  254. 0
  255. >>> Commutator(N,ad).doit()
  256. RaisingOp(a)
  257. >>> Commutator(N,a).doit()
  258. -a
  259. Apply the Number Operator to a state:
  260. >>> from sympy.physics.quantum import qapply
  261. >>> from sympy.physics.quantum.sho1d import NumberOp, SHOKet
  262. >>> N = NumberOp('N')
  263. >>> k = SHOKet('k')
  264. >>> qapply(N*k)
  265. k*|k>
  266. Matrix Representation
  267. >>> from sympy.physics.quantum.sho1d import NumberOp
  268. >>> from sympy.physics.quantum.represent import represent
  269. >>> N = NumberOp('N')
  270. >>> represent(N, basis=N, ndim=4, format='sympy')
  271. Matrix([
  272. [0, 0, 0, 0],
  273. [0, 1, 0, 0],
  274. [0, 0, 2, 0],
  275. [0, 0, 0, 3]])
  276. """
  277. def _eval_rewrite_as_a(self, *args, **kwargs):
  278. return ad*a
  279. def _eval_rewrite_as_xp(self, *args, **kwargs):
  280. return (S.One/(Integer(2)*m*hbar*omega))*(Px**2 + (
  281. m*omega*X)**2) - S.Half
  282. def _eval_rewrite_as_H(self, *args, **kwargs):
  283. return H/(hbar*omega) - S.Half
  284. def _apply_operator_SHOKet(self, ket):
  285. return ket.n*ket
  286. def _eval_commutator_Hamiltonian(self, other):
  287. return S.Zero
  288. def _eval_commutator_RaisingOp(self, other):
  289. return other
  290. def _eval_commutator_LoweringOp(self, other):
  291. return S.NegativeOne*other
  292. def _represent_default_basis(self, **options):
  293. return self._represent_NumberOp(None, **options)
  294. def _represent_XOp(self, basis, **options):
  295. # This logic is good but the underlying position
  296. # representation logic is broken.
  297. # temp = self.rewrite('xp').doit()
  298. # result = represent(temp, basis=X)
  299. # return result
  300. raise NotImplementedError('Position representation is not implemented')
  301. def _represent_NumberOp(self, basis, **options):
  302. ndim_info = options.get('ndim', 4)
  303. format = options.get('format', 'sympy')
  304. matrix = matrix_zeros(ndim_info, ndim_info, **options)
  305. for i in range(ndim_info):
  306. value = i
  307. if format == 'scipy.sparse':
  308. value = float(value)
  309. matrix[i,i] = value
  310. if format == 'scipy.sparse':
  311. matrix = matrix.tocsr()
  312. return matrix
  313. class Hamiltonian(SHOOp):
  314. """The Hamiltonian Operator.
  315. The Hamiltonian is used to solve the time-independent Schrodinger
  316. equation. The Hamiltonian can be expressed using the ladder operators,
  317. as well as by position and momentum. We can represent the Hamiltonian
  318. Operator as a matrix, which will be its default basis.
  319. Parameters
  320. ==========
  321. args : tuple
  322. The list of numbers or parameters that uniquely specify the
  323. operator.
  324. Examples
  325. ========
  326. Create a Hamiltonian Operator and rewrite it in terms of the ladder
  327. operators, position and momentum, and the Number Operator:
  328. >>> from sympy.physics.quantum.sho1d import Hamiltonian
  329. >>> H = Hamiltonian('H')
  330. >>> H.rewrite('a').doit()
  331. hbar*omega*(1/2 + RaisingOp(a)*a)
  332. >>> H.rewrite('xp').doit()
  333. (m**2*omega**2*X**2 + Px**2)/(2*m)
  334. >>> H.rewrite('N').doit()
  335. hbar*omega*(1/2 + N)
  336. Take the Commutator of the Hamiltonian and the Number Operator:
  337. >>> from sympy.physics.quantum import Commutator
  338. >>> from sympy.physics.quantum.sho1d import Hamiltonian, NumberOp
  339. >>> H = Hamiltonian('H')
  340. >>> N = NumberOp('N')
  341. >>> Commutator(H,N).doit()
  342. 0
  343. Apply the Hamiltonian Operator to a state:
  344. >>> from sympy.physics.quantum import qapply
  345. >>> from sympy.physics.quantum.sho1d import Hamiltonian, SHOKet
  346. >>> H = Hamiltonian('H')
  347. >>> k = SHOKet('k')
  348. >>> qapply(H*k)
  349. hbar*k*omega*|k> + hbar*omega*|k>/2
  350. Matrix Representation
  351. >>> from sympy.physics.quantum.sho1d import Hamiltonian
  352. >>> from sympy.physics.quantum.represent import represent
  353. >>> H = Hamiltonian('H')
  354. >>> represent(H, basis=N, ndim=4, format='sympy')
  355. Matrix([
  356. [hbar*omega/2, 0, 0, 0],
  357. [ 0, 3*hbar*omega/2, 0, 0],
  358. [ 0, 0, 5*hbar*omega/2, 0],
  359. [ 0, 0, 0, 7*hbar*omega/2]])
  360. """
  361. def _eval_rewrite_as_a(self, *args, **kwargs):
  362. return hbar*omega*(ad*a + S.Half)
  363. def _eval_rewrite_as_xp(self, *args, **kwargs):
  364. return (S.One/(Integer(2)*m))*(Px**2 + (m*omega*X)**2)
  365. def _eval_rewrite_as_N(self, *args, **kwargs):
  366. return hbar*omega*(N + S.Half)
  367. def _apply_operator_SHOKet(self, ket):
  368. return (hbar*omega*(ket.n + S.Half))*ket
  369. def _eval_commutator_NumberOp(self, other):
  370. return S.Zero
  371. def _represent_default_basis(self, **options):
  372. return self._represent_NumberOp(None, **options)
  373. def _represent_XOp(self, basis, **options):
  374. # This logic is good but the underlying position
  375. # representation logic is broken.
  376. # temp = self.rewrite('xp').doit()
  377. # result = represent(temp, basis=X)
  378. # return result
  379. raise NotImplementedError('Position representation is not implemented')
  380. def _represent_NumberOp(self, basis, **options):
  381. ndim_info = options.get('ndim', 4)
  382. format = options.get('format', 'sympy')
  383. matrix = matrix_zeros(ndim_info, ndim_info, **options)
  384. for i in range(ndim_info):
  385. value = i + S.Half
  386. if format == 'scipy.sparse':
  387. value = float(value)
  388. matrix[i,i] = value
  389. if format == 'scipy.sparse':
  390. matrix = matrix.tocsr()
  391. return hbar*omega*matrix
  392. #------------------------------------------------------------------------------
  393. class SHOState(State):
  394. """State class for SHO states"""
  395. @classmethod
  396. def _eval_hilbert_space(cls, label):
  397. return ComplexSpace(S.Infinity)
  398. @property
  399. def n(self):
  400. return self.args[0]
  401. class SHOKet(SHOState, Ket):
  402. """1D eigenket.
  403. Inherits from SHOState and Ket.
  404. Parameters
  405. ==========
  406. args : tuple
  407. The list of numbers or parameters that uniquely specify the ket
  408. This is usually its quantum numbers or its symbol.
  409. Examples
  410. ========
  411. Ket's know about their associated bra:
  412. >>> from sympy.physics.quantum.sho1d import SHOKet
  413. >>> k = SHOKet('k')
  414. >>> k.dual
  415. <k|
  416. >>> k.dual_class()
  417. <class 'sympy.physics.quantum.sho1d.SHOBra'>
  418. Take the Inner Product with a bra:
  419. >>> from sympy.physics.quantum import InnerProduct
  420. >>> from sympy.physics.quantum.sho1d import SHOKet, SHOBra
  421. >>> k = SHOKet('k')
  422. >>> b = SHOBra('b')
  423. >>> InnerProduct(b,k).doit()
  424. KroneckerDelta(b, k)
  425. Vector representation of a numerical state ket:
  426. >>> from sympy.physics.quantum.sho1d import SHOKet, NumberOp
  427. >>> from sympy.physics.quantum.represent import represent
  428. >>> k = SHOKet(3)
  429. >>> N = NumberOp('N')
  430. >>> represent(k, basis=N, ndim=4)
  431. Matrix([
  432. [0],
  433. [0],
  434. [0],
  435. [1]])
  436. """
  437. @classmethod
  438. def dual_class(self):
  439. return SHOBra
  440. def _eval_innerproduct_SHOBra(self, bra, **hints):
  441. result = KroneckerDelta(self.n, bra.n)
  442. return result
  443. def _represent_default_basis(self, **options):
  444. return self._represent_NumberOp(None, **options)
  445. def _represent_NumberOp(self, basis, **options):
  446. ndim_info = options.get('ndim', 4)
  447. format = options.get('format', 'sympy')
  448. options['spmatrix'] = 'lil'
  449. vector = matrix_zeros(ndim_info, 1, **options)
  450. if isinstance(self.n, Integer):
  451. if self.n >= ndim_info:
  452. return ValueError("N-Dimension too small")
  453. if format == 'scipy.sparse':
  454. vector[int(self.n), 0] = 1.0
  455. vector = vector.tocsr()
  456. elif format == 'numpy':
  457. vector[int(self.n), 0] = 1.0
  458. else:
  459. vector[self.n, 0] = S.One
  460. return vector
  461. else:
  462. return ValueError("Not Numerical State")
  463. class SHOBra(SHOState, Bra):
  464. """A time-independent Bra in SHO.
  465. Inherits from SHOState and Bra.
  466. Parameters
  467. ==========
  468. args : tuple
  469. The list of numbers or parameters that uniquely specify the ket
  470. This is usually its quantum numbers or its symbol.
  471. Examples
  472. ========
  473. Bra's know about their associated ket:
  474. >>> from sympy.physics.quantum.sho1d import SHOBra
  475. >>> b = SHOBra('b')
  476. >>> b.dual
  477. |b>
  478. >>> b.dual_class()
  479. <class 'sympy.physics.quantum.sho1d.SHOKet'>
  480. Vector representation of a numerical state bra:
  481. >>> from sympy.physics.quantum.sho1d import SHOBra, NumberOp
  482. >>> from sympy.physics.quantum.represent import represent
  483. >>> b = SHOBra(3)
  484. >>> N = NumberOp('N')
  485. >>> represent(b, basis=N, ndim=4)
  486. Matrix([[0, 0, 0, 1]])
  487. """
  488. @classmethod
  489. def dual_class(self):
  490. return SHOKet
  491. def _represent_default_basis(self, **options):
  492. return self._represent_NumberOp(None, **options)
  493. def _represent_NumberOp(self, basis, **options):
  494. ndim_info = options.get('ndim', 4)
  495. format = options.get('format', 'sympy')
  496. options['spmatrix'] = 'lil'
  497. vector = matrix_zeros(1, ndim_info, **options)
  498. if isinstance(self.n, Integer):
  499. if self.n >= ndim_info:
  500. return ValueError("N-Dimension too small")
  501. if format == 'scipy.sparse':
  502. vector[0, int(self.n)] = 1.0
  503. vector = vector.tocsr()
  504. elif format == 'numpy':
  505. vector[0, int(self.n)] = 1.0
  506. else:
  507. vector[0, self.n] = S.One
  508. return vector
  509. else:
  510. return ValueError("Not Numerical State")
  511. ad = RaisingOp('a')
  512. a = LoweringOp('a')
  513. H = Hamiltonian('H')
  514. N = NumberOp('N')
  515. omega = Symbol('omega')
  516. m = Symbol('m')